From dd81f86711be1c8a54c175f2d163f5f3c9e90c3a Mon Sep 17 00:00:00 2001 From: Ivan Priest Date: Tue, 16 Jul 2019 13:54:13 +0100 Subject: [PATCH] [RAMEN9610-19422][9630] wlbt: add WLBT drivers 10.1.1.0 Add WLBT driver drop for Neus From tag scsc_release_10.1.1_mx450_p_ww Change-Id: Ic7fe507ff007a870431f4dcbb6c263e49c334d9c SCSC-Bug-Id: RELS-3034 Signed-off-by: Ivan Priest Signed-off-by: Kim Gunho --- drivers/misc/samsung/Kconfig | 4 + drivers/misc/samsung/Makefile | 8 + drivers/misc/samsung/gpio_debug/Kconfig | 3 + drivers/misc/samsung/gpio_debug/Makefile | 1 + drivers/misc/samsung/gpio_debug/gpio_debug.c | 427 + drivers/misc/samsung/kic/Kconfig | 9 + drivers/misc/samsung/kic/Makefile | 18 + drivers/misc/samsung/kic/slsi_kic_core.c | 793 ++ drivers/misc/samsung/kic/slsi_kic_filtering.c | 8 + drivers/misc/samsung/kic/slsi_kic_internal.h | 89 + drivers/misc/samsung/kic/slsi_kic_sap_ant.c | 40 + drivers/misc/samsung/kic/slsi_kic_sap_bt.c | 38 + drivers/misc/samsung/kic/slsi_kic_sap_cm.c | 38 + drivers/misc/samsung/kic/slsi_kic_sap_wifi.c | 38 + drivers/misc/samsung/scsc/Kconfig | 189 + drivers/misc/samsung/scsc/Makefile | 162 + drivers/misc/samsung/scsc/client_test.c | 420 + drivers/misc/samsung/scsc/cpacket_buffer.c | 373 + drivers/misc/samsung/scsc/cpacket_buffer.h | 147 + drivers/misc/samsung/scsc/functor.h | 42 + drivers/misc/samsung/scsc/fw_panic_record.c | 178 + drivers/misc/samsung/scsc/fw_panic_record.h | 18 + drivers/misc/samsung/scsc/fwhdr.c | 110 + drivers/misc/samsung/scsc/fwhdr.h | 40 + drivers/misc/samsung/scsc/fwimage.c | 59 + drivers/misc/samsung/scsc/fwimage.h | 14 + drivers/misc/samsung/scsc/gdb_transport.c | 263 + drivers/misc/samsung/scsc/gdb_transport.h | 93 + drivers/misc/samsung/scsc/mif_reg.h | 135 + drivers/misc/samsung/scsc/mif_reg_S5E7872.h | 149 + drivers/misc/samsung/scsc/mif_reg_S5E7885.h | 140 + drivers/misc/samsung/scsc/mif_reg_S5E9610.h | 182 + drivers/misc/samsung/scsc/mif_reg_S5E9630.h | 320 + drivers/misc/samsung/scsc/mif_reg_smapper.h | 74 + drivers/misc/samsung/scsc/mifintrbit.c | 263 + drivers/misc/samsung/scsc/mifintrbit.h | 60 + drivers/misc/samsung/scsc/mifmboxman.c | 116 + drivers/misc/samsung/scsc/mifmboxman.h | 39 + drivers/misc/samsung/scsc/mifproc.c | 519 ++ drivers/misc/samsung/scsc/mifproc.h | 115 + drivers/misc/samsung/scsc/mifqos.c | 135 + drivers/misc/samsung/scsc/mifqos.h | 30 + drivers/misc/samsung/scsc/miframman.c | 267 + drivers/misc/samsung/scsc/miframman.h | 58 + drivers/misc/samsung/scsc/mifsmapper.c | 360 + drivers/misc/samsung/scsc/mifsmapper.h | 54 + drivers/misc/samsung/scsc/mifstream.c | 185 + drivers/misc/samsung/scsc/mifstream.h | 191 + drivers/misc/samsung/scsc/mx140_clk.c | 786 ++ drivers/misc/samsung/scsc/mx140_clk_test.c | 161 + drivers/misc/samsung/scsc/mx140_clk_test.h | 13 + drivers/misc/samsung/scsc/mx140_file.c | 652 ++ drivers/misc/samsung/scsc/mx250_fm.c | 385 + drivers/misc/samsung/scsc/mx250_fm_test.c | 143 + drivers/misc/samsung/scsc/mx250_fm_test.h | 13 + drivers/misc/samsung/scsc/mx_dbg_sampler.c | 769 ++ drivers/misc/samsung/scsc/mx_dbg_sampler.h | 121 + drivers/misc/samsung/scsc/mx_mmap.c | 545 ++ drivers/misc/samsung/scsc/mxconf.h | 188 + drivers/misc/samsung/scsc/mxfwconfig.c | 224 + drivers/misc/samsung/scsc/mxfwconfig.h | 35 + drivers/misc/samsung/scsc/mxlog.c | 410 + drivers/misc/samsung/scsc/mxlog.h | 97 + drivers/misc/samsung/scsc/mxlog_transport.c | 310 + drivers/misc/samsung/scsc/mxlog_transport.h | 77 + drivers/misc/samsung/scsc/mxlogger.c | 945 ++ drivers/misc/samsung/scsc/mxlogger.h | 219 + drivers/misc/samsung/scsc/mxman.c | 2601 ++++++ drivers/misc/samsung/scsc/mxman.h | 102 + drivers/misc/samsung/scsc/mxmgmt_transport.c | 302 + drivers/misc/samsung/scsc/mxmgmt_transport.h | 104 + .../samsung/scsc/mxmgmt_transport_format.h | 23 + .../samsung/scsc/mxmgmt_transport_streams.h | 24 + drivers/misc/samsung/scsc/mxproc.c | 585 ++ drivers/misc/samsung/scsc/mxproc.h | 30 + drivers/misc/samsung/scsc/mxsyserr.c | 45 + drivers/misc/samsung/scsc/mxsyserr.h | 27 + .../misc/samsung/scsc/panic_record_r4_defs.h | 138 + drivers/misc/samsung/scsc/panicmon.c | 40 + drivers/misc/samsung/scsc/panicmon.h | 20 + drivers/misc/samsung/scsc/pcie_mbox.c | 129 + drivers/misc/samsung/scsc/pcie_mbox.h | 109 + drivers/misc/samsung/scsc/pcie_mbox_intgen.c | 140 + drivers/misc/samsung/scsc/pcie_mbox_intgen.h | 105 + .../scsc/pcie_mbox_intgen_shared_data.h | 35 + .../misc/samsung/scsc/pcie_mbox_shared_data.h | 41 + .../samsung/scsc/pcie_mbox_shared_data_defs.h | 55 + drivers/misc/samsung/scsc/pcie_mif.c | 701 ++ drivers/misc/samsung/scsc/pcie_mif.h | 150 + drivers/misc/samsung/scsc/pcie_mif_module.c | 190 + drivers/misc/samsung/scsc/pcie_mif_module.h | 29 + drivers/misc/samsung/scsc/pcie_proc.c | 278 + drivers/misc/samsung/scsc/pcie_proc.h | 102 + drivers/misc/samsung/scsc/peterson_mutex.h | 50 + drivers/misc/samsung/scsc/platform_mif.c | 2524 ++++++ drivers/misc/samsung/scsc/platform_mif.h | 31 + drivers/misc/samsung/scsc/platform_mif_9610.c | 1956 +++++ drivers/misc/samsung/scsc/platform_mif_9630.c | 2265 +++++ .../misc/samsung/scsc/platform_mif_module.c | 252 + .../misc/samsung/scsc/platform_mif_module.h | 12 + drivers/misc/samsung/scsc/scsc_app_msg.h | 49 + drivers/misc/samsung/scsc/scsc_lerna.c | 276 + drivers/misc/samsung/scsc/scsc_lerna.h | 16 + .../misc/samsung/scsc/scsc_log_collector.c | 538 ++ .../samsung/scsc/scsc_log_collector_mmap.c | 141 + .../samsung/scsc/scsc_log_collector_mmap.h | 12 + .../samsung/scsc/scsc_log_collector_proc.c | 96 + .../samsung/scsc/scsc_log_collector_proc.h | 77 + .../misc/samsung/scsc/scsc_logring_common.h | 48 + .../misc/samsung/scsc/scsc_logring_debugfs.c | 706 ++ .../misc/samsung/scsc/scsc_logring_debugfs.h | 61 + drivers/misc/samsung/scsc/scsc_logring_main.c | 663 ++ drivers/misc/samsung/scsc/scsc_logring_main.h | 34 + drivers/misc/samsung/scsc/scsc_logring_ring.c | 831 ++ drivers/misc/samsung/scsc/scsc_logring_ring.h | 244 + drivers/misc/samsung/scsc/scsc_loopback.c | 48 + drivers/misc/samsung/scsc/scsc_loopback.h | 24 + drivers/misc/samsung/scsc/scsc_mif_abs.h | 234 + drivers/misc/samsung/scsc/scsc_mx_impl.c | 225 + drivers/misc/samsung/scsc/scsc_mx_impl.h | 63 + drivers/misc/samsung/scsc/scsc_mx_module.c | 200 + drivers/misc/samsung/scsc/scsc_service.c | 1110 +++ drivers/misc/samsung/scsc/scsc_wifilogger.h | 199 + .../misc/samsung/scsc/scsc_wifilogger_api.c | 261 + .../misc/samsung/scsc/scsc_wifilogger_core.c | 470 + .../misc/samsung/scsc/scsc_wifilogger_core.h | 270 + .../samsung/scsc/scsc_wifilogger_debugfs.c | 439 + .../samsung/scsc/scsc_wifilogger_debugfs.h | 67 + .../samsung/scsc/scsc_wifilogger_internal.c | 232 + .../samsung/scsc/scsc_wifilogger_internal.h | 53 + .../samsung/scsc/scsc_wifilogger_module.c | 47 + .../samsung/scsc/scsc_wifilogger_module.h | 27 + .../scsc/scsc_wifilogger_ring_connectivity.c | 121 + .../scsc/scsc_wifilogger_ring_connectivity.h | 35 + .../scsc_wifilogger_ring_connectivity_api.h | 79 + .../scsc/scsc_wifilogger_ring_pktfate.c | 284 + .../scsc/scsc_wifilogger_ring_pktfate.h | 40 + .../scsc/scsc_wifilogger_ring_pktfate_api.h | 63 + .../samsung/scsc/scsc_wifilogger_ring_test.c | 135 + .../samsung/scsc/scsc_wifilogger_ring_test.h | 28 + .../scsc/scsc_wifilogger_ring_wakelock.c | 81 + .../scsc/scsc_wifilogger_ring_wakelock.h | 25 + .../scsc/scsc_wifilogger_ring_wakelock_api.h | 42 + .../misc/samsung/scsc/scsc_wifilogger_rings.h | 15 + .../misc/samsung/scsc/scsc_wifilogger_types.h | 435 + drivers/misc/samsung/scsc/scsc_wlbtd.c | 704 ++ drivers/misc/samsung/scsc/scsc_wlbtd.h | 83 + drivers/misc/samsung/scsc/servman_messages.h | 35 + drivers/misc/samsung/scsc/srvman.h | 37 + drivers/misc/samsung/scsc/suspendmon.c | 53 + drivers/misc/samsung/scsc/suspendmon.h | 23 + drivers/misc/samsung/scsc_bt/Kconfig | 29 + drivers/misc/samsung/scsc_bt/Makefile | 31 + drivers/misc/samsung/scsc_bt/scsc_ant.c | 676 ++ .../misc/samsung/scsc_bt/scsc_avdtp_detect.c | 807 ++ drivers/misc/samsung/scsc_bt/scsc_bluez.c | 337 + drivers/misc/samsung/scsc_bt/scsc_bt_hci.h | 112 + drivers/misc/samsung/scsc_bt/scsc_bt_module.c | 2161 +++++ drivers/misc/samsung/scsc_bt/scsc_bt_priv.h | 450 + drivers/misc/samsung/scsc_bt/scsc_shm.c | 1593 ++++ drivers/misc/samsung/scsc_bt/scsc_shm.h | 37 + drivers/net/wireless/scsc/Kconfig | 264 + drivers/net/wireless/scsc/Makefile | 127 + drivers/net/wireless/scsc/acm_api.c | 74 + drivers/net/wireless/scsc/ba.c | 616 ++ drivers/net/wireless/scsc/ba.h | 26 + drivers/net/wireless/scsc/cac.c | 1487 ++++ drivers/net/wireless/scsc/cac.h | 182 + drivers/net/wireless/scsc/cfg80211_ops.c | 3557 ++++++++ drivers/net/wireless/scsc/cfg80211_ops.h | 28 + drivers/net/wireless/scsc/cm_if.c | 706 ++ drivers/net/wireless/scsc/const.h | 54 + drivers/net/wireless/scsc/debug.c | 437 + drivers/net/wireless/scsc/debug.h | 283 + drivers/net/wireless/scsc/debug_frame.c | 1120 +++ drivers/net/wireless/scsc/dev.c | 596 ++ drivers/net/wireless/scsc/dev.h | 1294 +++ drivers/net/wireless/scsc/fapi.h | 4252 +++++++++ drivers/net/wireless/scsc/fw_test.c | 971 ++ drivers/net/wireless/scsc/fw_test.h | 28 + drivers/net/wireless/scsc/hip.c | 303 + drivers/net/wireless/scsc/hip.h | 79 + drivers/net/wireless/scsc/hip4.c | 2894 ++++++ drivers/net/wireless/scsc/hip4.h | 389 + drivers/net/wireless/scsc/hip4_sampler.c | 945 ++ drivers/net/wireless/scsc/hip4_sampler.h | 412 + drivers/net/wireless/scsc/hip4_smapper.c | 432 + drivers/net/wireless/scsc/hip4_smapper.h | 99 + drivers/net/wireless/scsc/hip_bh.h | 15 + drivers/net/wireless/scsc/ioctl.c | 2775 ++++++ drivers/net/wireless/scsc/ioctl.h | 28 + drivers/net/wireless/scsc/kic.c | 53 + drivers/net/wireless/scsc/kic.h | 13 + drivers/net/wireless/scsc/log_clients.c | 118 + drivers/net/wireless/scsc/log_clients.h | 51 + drivers/net/wireless/scsc/mbulk.c | 453 + drivers/net/wireless/scsc/mbulk.h | 523 ++ drivers/net/wireless/scsc/mbulk_def.h | 171 + drivers/net/wireless/scsc/mgt.c | 5996 +++++++++++++ drivers/net/wireless/scsc/mgt.h | 567 ++ drivers/net/wireless/scsc/mib.c | 598 ++ drivers/net/wireless/scsc/mib.h | 7785 +++++++++++++++++ drivers/net/wireless/scsc/mib_text_convert.c | 327 + drivers/net/wireless/scsc/mib_text_convert.h | 46 + drivers/net/wireless/scsc/mlme.c | 4365 +++++++++ drivers/net/wireless/scsc/mlme.h | 280 + drivers/net/wireless/scsc/mlme_nan.c | 640 ++ drivers/net/wireless/scsc/netif.c | 2109 +++++ drivers/net/wireless/scsc/netif.h | 105 + drivers/net/wireless/scsc/nl80211_vendor.c | 4920 +++++++++++ drivers/net/wireless/scsc/nl80211_vendor.h | 1030 +++ .../net/wireless/scsc/nl80211_vendor_nan.c | 1842 ++++ .../net/wireless/scsc/nl80211_vendor_nan.h | 888 ++ drivers/net/wireless/scsc/porting_imx.h | 39 + drivers/net/wireless/scsc/procfs.c | 1204 +++ drivers/net/wireless/scsc/procfs.h | 127 + drivers/net/wireless/scsc/rx.c | 2786 ++++++ drivers/net/wireless/scsc/sap.h | 35 + drivers/net/wireless/scsc/sap_dbg.c | 173 + drivers/net/wireless/scsc/sap_dbg.h | 14 + drivers/net/wireless/scsc/sap_ma.c | 752 ++ drivers/net/wireless/scsc/sap_ma.h | 14 + drivers/net/wireless/scsc/sap_mlme.c | 434 + drivers/net/wireless/scsc/sap_mlme.h | 39 + drivers/net/wireless/scsc/sap_test.c | 67 + drivers/net/wireless/scsc/sap_test.h | 14 + drivers/net/wireless/scsc/scsc_wifi_cm_if.h | 89 + drivers/net/wireless/scsc/scsc_wifi_fcq.c | 1159 +++ drivers/net/wireless/scsc/scsc_wifi_fcq.h | 115 + drivers/net/wireless/scsc/src_sink.c | 417 + drivers/net/wireless/scsc/src_sink.h | 22 + drivers/net/wireless/scsc/test/Makefile | 108 + drivers/net/wireless/scsc/test/dev_test.c | 511 ++ drivers/net/wireless/scsc/test/hip_test.c | 276 + drivers/net/wireless/scsc/test/hydra_test.c | 74 + drivers/net/wireless/scsc/test/udi_test.c | 632 ++ drivers/net/wireless/scsc/test/unittest.h | 129 + drivers/net/wireless/scsc/traffic_monitor.c | 330 + drivers/net/wireless/scsc/traffic_monitor.h | 77 + drivers/net/wireless/scsc/tx.c | 623 ++ drivers/net/wireless/scsc/udi.c | 1175 +++ drivers/net/wireless/scsc/udi.h | 31 + drivers/net/wireless/scsc/unifiio.h | 455 + drivers/net/wireless/scsc/utils.h | 656 ++ drivers/net/wireless/scsc/wakelock.c | 117 + drivers/net/wireless/scsc/wakelock.h | 31 + drivers/net/wireless/scsc/wl_result.h | 28 + include/scsc/api/asmhcp.h | 76 + include/scsc/api/bhcs.h | 44 + include/scsc/api/bsmhcp.h | 325 + include/scsc/api/bt_audio.h | 133 + include/scsc/api/cme3_prim.h | 375 + include/scsc/kic/slsi_kic.h | 21 + include/scsc/kic/slsi_kic_ant.h | 65 + include/scsc/kic/slsi_kic_bt.h | 65 + include/scsc/kic/slsi_kic_cm.h | 50 + include/scsc/kic/slsi_kic_lib.h | 70 + include/scsc/kic/slsi_kic_prim.h | 426 + include/scsc/kic/slsi_kic_wifi.h | 64 + include/scsc/scsc_log_collector.h | 146 + include/scsc/scsc_logring.h | 709 ++ include/scsc/scsc_mifram.h | 16 + include/scsc/scsc_mx.h | 442 + include/scsc/scsc_release.h | 20 + 264 files changed, 115767 insertions(+) create mode 100644 drivers/misc/samsung/Kconfig create mode 100644 drivers/misc/samsung/Makefile create mode 100644 drivers/misc/samsung/gpio_debug/Kconfig create mode 100644 drivers/misc/samsung/gpio_debug/Makefile create mode 100644 drivers/misc/samsung/gpio_debug/gpio_debug.c create mode 100644 drivers/misc/samsung/kic/Kconfig create mode 100644 drivers/misc/samsung/kic/Makefile create mode 100755 drivers/misc/samsung/kic/slsi_kic_core.c create mode 100644 drivers/misc/samsung/kic/slsi_kic_filtering.c create mode 100755 drivers/misc/samsung/kic/slsi_kic_internal.h create mode 100755 drivers/misc/samsung/kic/slsi_kic_sap_ant.c create mode 100755 drivers/misc/samsung/kic/slsi_kic_sap_bt.c create mode 100755 drivers/misc/samsung/kic/slsi_kic_sap_cm.c create mode 100755 drivers/misc/samsung/kic/slsi_kic_sap_wifi.c create mode 100644 drivers/misc/samsung/scsc/Kconfig create mode 100644 drivers/misc/samsung/scsc/Makefile create mode 100755 drivers/misc/samsung/scsc/client_test.c create mode 100644 drivers/misc/samsung/scsc/cpacket_buffer.c create mode 100644 drivers/misc/samsung/scsc/cpacket_buffer.h create mode 100644 drivers/misc/samsung/scsc/functor.h create mode 100644 drivers/misc/samsung/scsc/fw_panic_record.c create mode 100644 drivers/misc/samsung/scsc/fw_panic_record.h create mode 100644 drivers/misc/samsung/scsc/fwhdr.c create mode 100644 drivers/misc/samsung/scsc/fwhdr.h create mode 100644 drivers/misc/samsung/scsc/fwimage.c create mode 100644 drivers/misc/samsung/scsc/fwimage.h create mode 100644 drivers/misc/samsung/scsc/gdb_transport.c create mode 100644 drivers/misc/samsung/scsc/gdb_transport.h create mode 100755 drivers/misc/samsung/scsc/mif_reg.h create mode 100755 drivers/misc/samsung/scsc/mif_reg_S5E7872.h create mode 100644 drivers/misc/samsung/scsc/mif_reg_S5E7885.h create mode 100644 drivers/misc/samsung/scsc/mif_reg_S5E9610.h create mode 100644 drivers/misc/samsung/scsc/mif_reg_S5E9630.h create mode 100755 drivers/misc/samsung/scsc/mif_reg_smapper.h create mode 100644 drivers/misc/samsung/scsc/mifintrbit.c create mode 100644 drivers/misc/samsung/scsc/mifintrbit.h create mode 100644 drivers/misc/samsung/scsc/mifmboxman.c create mode 100644 drivers/misc/samsung/scsc/mifmboxman.h create mode 100644 drivers/misc/samsung/scsc/mifproc.c create mode 100644 drivers/misc/samsung/scsc/mifproc.h create mode 100644 drivers/misc/samsung/scsc/mifqos.c create mode 100644 drivers/misc/samsung/scsc/mifqos.h create mode 100644 drivers/misc/samsung/scsc/miframman.c create mode 100644 drivers/misc/samsung/scsc/miframman.h create mode 100644 drivers/misc/samsung/scsc/mifsmapper.c create mode 100644 drivers/misc/samsung/scsc/mifsmapper.h create mode 100644 drivers/misc/samsung/scsc/mifstream.c create mode 100644 drivers/misc/samsung/scsc/mifstream.h create mode 100755 drivers/misc/samsung/scsc/mx140_clk.c create mode 100644 drivers/misc/samsung/scsc/mx140_clk_test.c create mode 100644 drivers/misc/samsung/scsc/mx140_clk_test.h create mode 100644 drivers/misc/samsung/scsc/mx140_file.c create mode 100755 drivers/misc/samsung/scsc/mx250_fm.c create mode 100644 drivers/misc/samsung/scsc/mx250_fm_test.c create mode 100644 drivers/misc/samsung/scsc/mx250_fm_test.h create mode 100755 drivers/misc/samsung/scsc/mx_dbg_sampler.c create mode 100644 drivers/misc/samsung/scsc/mx_dbg_sampler.h create mode 100644 drivers/misc/samsung/scsc/mx_mmap.c create mode 100644 drivers/misc/samsung/scsc/mxconf.h create mode 100644 drivers/misc/samsung/scsc/mxfwconfig.c create mode 100644 drivers/misc/samsung/scsc/mxfwconfig.h create mode 100644 drivers/misc/samsung/scsc/mxlog.c create mode 100644 drivers/misc/samsung/scsc/mxlog.h create mode 100644 drivers/misc/samsung/scsc/mxlog_transport.c create mode 100644 drivers/misc/samsung/scsc/mxlog_transport.h create mode 100644 drivers/misc/samsung/scsc/mxlogger.c create mode 100644 drivers/misc/samsung/scsc/mxlogger.h create mode 100755 drivers/misc/samsung/scsc/mxman.c create mode 100755 drivers/misc/samsung/scsc/mxman.h create mode 100644 drivers/misc/samsung/scsc/mxmgmt_transport.c create mode 100644 drivers/misc/samsung/scsc/mxmgmt_transport.h create mode 100644 drivers/misc/samsung/scsc/mxmgmt_transport_format.h create mode 100644 drivers/misc/samsung/scsc/mxmgmt_transport_streams.h create mode 100644 drivers/misc/samsung/scsc/mxproc.c create mode 100644 drivers/misc/samsung/scsc/mxproc.h create mode 100755 drivers/misc/samsung/scsc/mxsyserr.c create mode 100755 drivers/misc/samsung/scsc/mxsyserr.h create mode 100644 drivers/misc/samsung/scsc/panic_record_r4_defs.h create mode 100644 drivers/misc/samsung/scsc/panicmon.c create mode 100644 drivers/misc/samsung/scsc/panicmon.h create mode 100644 drivers/misc/samsung/scsc/pcie_mbox.c create mode 100644 drivers/misc/samsung/scsc/pcie_mbox.h create mode 100644 drivers/misc/samsung/scsc/pcie_mbox_intgen.c create mode 100644 drivers/misc/samsung/scsc/pcie_mbox_intgen.h create mode 100644 drivers/misc/samsung/scsc/pcie_mbox_intgen_shared_data.h create mode 100644 drivers/misc/samsung/scsc/pcie_mbox_shared_data.h create mode 100644 drivers/misc/samsung/scsc/pcie_mbox_shared_data_defs.h create mode 100644 drivers/misc/samsung/scsc/pcie_mif.c create mode 100644 drivers/misc/samsung/scsc/pcie_mif.h create mode 100644 drivers/misc/samsung/scsc/pcie_mif_module.c create mode 100644 drivers/misc/samsung/scsc/pcie_mif_module.h create mode 100644 drivers/misc/samsung/scsc/pcie_proc.c create mode 100644 drivers/misc/samsung/scsc/pcie_proc.h create mode 100644 drivers/misc/samsung/scsc/peterson_mutex.h create mode 100755 drivers/misc/samsung/scsc/platform_mif.c create mode 100644 drivers/misc/samsung/scsc/platform_mif.h create mode 100644 drivers/misc/samsung/scsc/platform_mif_9610.c create mode 100644 drivers/misc/samsung/scsc/platform_mif_9630.c create mode 100644 drivers/misc/samsung/scsc/platform_mif_module.c create mode 100644 drivers/misc/samsung/scsc/platform_mif_module.h create mode 100644 drivers/misc/samsung/scsc/scsc_app_msg.h create mode 100644 drivers/misc/samsung/scsc/scsc_lerna.c create mode 100644 drivers/misc/samsung/scsc/scsc_lerna.h create mode 100644 drivers/misc/samsung/scsc/scsc_log_collector.c create mode 100644 drivers/misc/samsung/scsc/scsc_log_collector_mmap.c create mode 100644 drivers/misc/samsung/scsc/scsc_log_collector_mmap.h create mode 100644 drivers/misc/samsung/scsc/scsc_log_collector_proc.c create mode 100644 drivers/misc/samsung/scsc/scsc_log_collector_proc.h create mode 100644 drivers/misc/samsung/scsc/scsc_logring_common.h create mode 100644 drivers/misc/samsung/scsc/scsc_logring_debugfs.c create mode 100644 drivers/misc/samsung/scsc/scsc_logring_debugfs.h create mode 100644 drivers/misc/samsung/scsc/scsc_logring_main.c create mode 100644 drivers/misc/samsung/scsc/scsc_logring_main.h create mode 100644 drivers/misc/samsung/scsc/scsc_logring_ring.c create mode 100644 drivers/misc/samsung/scsc/scsc_logring_ring.h create mode 100644 drivers/misc/samsung/scsc/scsc_loopback.c create mode 100644 drivers/misc/samsung/scsc/scsc_loopback.h create mode 100644 drivers/misc/samsung/scsc/scsc_mif_abs.h create mode 100644 drivers/misc/samsung/scsc/scsc_mx_impl.c create mode 100644 drivers/misc/samsung/scsc/scsc_mx_impl.h create mode 100755 drivers/misc/samsung/scsc/scsc_mx_module.c create mode 100755 drivers/misc/samsung/scsc/scsc_service.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_api.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_core.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_core.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_internal.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_internal.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_module.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_module.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity_api.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate_api.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.c create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock_api.h create mode 100644 drivers/misc/samsung/scsc/scsc_wifilogger_rings.h create mode 100755 drivers/misc/samsung/scsc/scsc_wifilogger_types.h create mode 100755 drivers/misc/samsung/scsc/scsc_wlbtd.c create mode 100644 drivers/misc/samsung/scsc/scsc_wlbtd.h create mode 100644 drivers/misc/samsung/scsc/servman_messages.h create mode 100755 drivers/misc/samsung/scsc/srvman.h create mode 100644 drivers/misc/samsung/scsc/suspendmon.c create mode 100644 drivers/misc/samsung/scsc/suspendmon.h create mode 100644 drivers/misc/samsung/scsc_bt/Kconfig create mode 100644 drivers/misc/samsung/scsc_bt/Makefile create mode 100644 drivers/misc/samsung/scsc_bt/scsc_ant.c create mode 100644 drivers/misc/samsung/scsc_bt/scsc_avdtp_detect.c create mode 100755 drivers/misc/samsung/scsc_bt/scsc_bluez.c create mode 100755 drivers/misc/samsung/scsc_bt/scsc_bt_hci.h create mode 100755 drivers/misc/samsung/scsc_bt/scsc_bt_module.c create mode 100755 drivers/misc/samsung/scsc_bt/scsc_bt_priv.h create mode 100755 drivers/misc/samsung/scsc_bt/scsc_shm.c create mode 100644 drivers/misc/samsung/scsc_bt/scsc_shm.h create mode 100755 drivers/net/wireless/scsc/Kconfig create mode 100755 drivers/net/wireless/scsc/Makefile create mode 100755 drivers/net/wireless/scsc/acm_api.c create mode 100755 drivers/net/wireless/scsc/ba.c create mode 100755 drivers/net/wireless/scsc/ba.h create mode 100755 drivers/net/wireless/scsc/cac.c create mode 100755 drivers/net/wireless/scsc/cac.h create mode 100755 drivers/net/wireless/scsc/cfg80211_ops.c create mode 100755 drivers/net/wireless/scsc/cfg80211_ops.h create mode 100755 drivers/net/wireless/scsc/cm_if.c create mode 100755 drivers/net/wireless/scsc/const.h create mode 100755 drivers/net/wireless/scsc/debug.c create mode 100644 drivers/net/wireless/scsc/debug.h create mode 100755 drivers/net/wireless/scsc/debug_frame.c create mode 100755 drivers/net/wireless/scsc/dev.c create mode 100755 drivers/net/wireless/scsc/dev.h create mode 100644 drivers/net/wireless/scsc/fapi.h create mode 100755 drivers/net/wireless/scsc/fw_test.c create mode 100755 drivers/net/wireless/scsc/fw_test.h create mode 100755 drivers/net/wireless/scsc/hip.c create mode 100644 drivers/net/wireless/scsc/hip.h create mode 100644 drivers/net/wireless/scsc/hip4.c create mode 100755 drivers/net/wireless/scsc/hip4.h create mode 100755 drivers/net/wireless/scsc/hip4_sampler.c create mode 100644 drivers/net/wireless/scsc/hip4_sampler.h create mode 100644 drivers/net/wireless/scsc/hip4_smapper.c create mode 100644 drivers/net/wireless/scsc/hip4_smapper.h create mode 100644 drivers/net/wireless/scsc/hip_bh.h create mode 100755 drivers/net/wireless/scsc/ioctl.c create mode 100755 drivers/net/wireless/scsc/ioctl.h create mode 100755 drivers/net/wireless/scsc/kic.c create mode 100644 drivers/net/wireless/scsc/kic.h create mode 100755 drivers/net/wireless/scsc/log_clients.c create mode 100644 drivers/net/wireless/scsc/log_clients.h create mode 100644 drivers/net/wireless/scsc/mbulk.c create mode 100644 drivers/net/wireless/scsc/mbulk.h create mode 100644 drivers/net/wireless/scsc/mbulk_def.h create mode 100755 drivers/net/wireless/scsc/mgt.c create mode 100755 drivers/net/wireless/scsc/mgt.h create mode 100755 drivers/net/wireless/scsc/mib.c create mode 100644 drivers/net/wireless/scsc/mib.h create mode 100644 drivers/net/wireless/scsc/mib_text_convert.c create mode 100644 drivers/net/wireless/scsc/mib_text_convert.h create mode 100755 drivers/net/wireless/scsc/mlme.c create mode 100755 drivers/net/wireless/scsc/mlme.h create mode 100755 drivers/net/wireless/scsc/mlme_nan.c create mode 100755 drivers/net/wireless/scsc/netif.c create mode 100755 drivers/net/wireless/scsc/netif.h create mode 100755 drivers/net/wireless/scsc/nl80211_vendor.c create mode 100755 drivers/net/wireless/scsc/nl80211_vendor.h create mode 100755 drivers/net/wireless/scsc/nl80211_vendor_nan.c create mode 100755 drivers/net/wireless/scsc/nl80211_vendor_nan.h create mode 100644 drivers/net/wireless/scsc/porting_imx.h create mode 100755 drivers/net/wireless/scsc/procfs.c create mode 100644 drivers/net/wireless/scsc/procfs.h create mode 100755 drivers/net/wireless/scsc/rx.c create mode 100644 drivers/net/wireless/scsc/sap.h create mode 100644 drivers/net/wireless/scsc/sap_dbg.c create mode 100644 drivers/net/wireless/scsc/sap_dbg.h create mode 100755 drivers/net/wireless/scsc/sap_ma.c create mode 100644 drivers/net/wireless/scsc/sap_ma.h create mode 100755 drivers/net/wireless/scsc/sap_mlme.c create mode 100755 drivers/net/wireless/scsc/sap_mlme.h create mode 100644 drivers/net/wireless/scsc/sap_test.c create mode 100644 drivers/net/wireless/scsc/sap_test.h create mode 100755 drivers/net/wireless/scsc/scsc_wifi_cm_if.h create mode 100755 drivers/net/wireless/scsc/scsc_wifi_fcq.c create mode 100644 drivers/net/wireless/scsc/scsc_wifi_fcq.h create mode 100755 drivers/net/wireless/scsc/src_sink.c create mode 100644 drivers/net/wireless/scsc/src_sink.h create mode 100755 drivers/net/wireless/scsc/test/Makefile create mode 100755 drivers/net/wireless/scsc/test/dev_test.c create mode 100755 drivers/net/wireless/scsc/test/hip_test.c create mode 100644 drivers/net/wireless/scsc/test/hydra_test.c create mode 100755 drivers/net/wireless/scsc/test/udi_test.c create mode 100755 drivers/net/wireless/scsc/test/unittest.h create mode 100644 drivers/net/wireless/scsc/traffic_monitor.c create mode 100644 drivers/net/wireless/scsc/traffic_monitor.h create mode 100755 drivers/net/wireless/scsc/tx.c create mode 100755 drivers/net/wireless/scsc/udi.c create mode 100644 drivers/net/wireless/scsc/udi.h create mode 100644 drivers/net/wireless/scsc/unifiio.h create mode 100755 drivers/net/wireless/scsc/utils.h create mode 100644 drivers/net/wireless/scsc/wakelock.c create mode 100644 drivers/net/wireless/scsc/wakelock.h create mode 100644 drivers/net/wireless/scsc/wl_result.h create mode 100755 include/scsc/api/asmhcp.h create mode 100644 include/scsc/api/bhcs.h create mode 100755 include/scsc/api/bsmhcp.h create mode 100755 include/scsc/api/bt_audio.h create mode 100644 include/scsc/api/cme3_prim.h create mode 100755 include/scsc/kic/slsi_kic.h create mode 100755 include/scsc/kic/slsi_kic_ant.h create mode 100755 include/scsc/kic/slsi_kic_bt.h create mode 100755 include/scsc/kic/slsi_kic_cm.h create mode 100644 include/scsc/kic/slsi_kic_lib.h create mode 100755 include/scsc/kic/slsi_kic_prim.h create mode 100755 include/scsc/kic/slsi_kic_wifi.h create mode 100644 include/scsc/scsc_log_collector.h create mode 100644 include/scsc/scsc_logring.h create mode 100644 include/scsc/scsc_mifram.h create mode 100755 include/scsc/scsc_mx.h create mode 100644 include/scsc/scsc_release.h diff --git a/drivers/misc/samsung/Kconfig b/drivers/misc/samsung/Kconfig new file mode 100644 index 000000000000..7a0ed3b0ea3b --- /dev/null +++ b/drivers/misc/samsung/Kconfig @@ -0,0 +1,4 @@ +source "drivers/misc/samsung/gpio_debug/Kconfig" +source "drivers/misc/samsung/scsc/Kconfig" +source "drivers/misc/samsung/scsc_bt/Kconfig" +source "drivers/misc/samsung/kic/Kconfig" diff --git a/drivers/misc/samsung/Makefile b/drivers/misc/samsung/Makefile new file mode 100644 index 000000000000..c4fa7425b196 --- /dev/null +++ b/drivers/misc/samsung/Makefile @@ -0,0 +1,8 @@ +# Needed since this subdir is symlinked in the main Kernel tree +# without this our samsung subdir is NOT cleaned. +clean-files := built-in.o + +obj-$(CONFIG_GPIO_DEBUG) += gpio_debug/ +obj-$(CONFIG_SAMSUNG_KIC) += kic/ +obj-$(CONFIG_SCSC_CORE) += scsc/ +obj-$(CONFIG_SCSC_BT) += scsc_bt/ diff --git a/drivers/misc/samsung/gpio_debug/Kconfig b/drivers/misc/samsung/gpio_debug/Kconfig new file mode 100644 index 000000000000..05d49e33d705 --- /dev/null +++ b/drivers/misc/samsung/gpio_debug/Kconfig @@ -0,0 +1,3 @@ +config GPIO_DEBUG + tristate "GPIO debug functionality" + diff --git a/drivers/misc/samsung/gpio_debug/Makefile b/drivers/misc/samsung/gpio_debug/Makefile new file mode 100644 index 000000000000..ec13da512364 --- /dev/null +++ b/drivers/misc/samsung/gpio_debug/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_GPIO_DEBUG) += gpio_debug.o diff --git a/drivers/misc/samsung/gpio_debug/gpio_debug.c b/drivers/misc/samsung/gpio_debug/gpio_debug.c new file mode 100644 index 000000000000..26dbecccb5b2 --- /dev/null +++ b/drivers/misc/samsung/gpio_debug/gpio_debug.c @@ -0,0 +1,427 @@ +/* Copyright (c) 2014 Samsung Electronics Co., Ltd */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_debug_data; + +struct gpio_debug_event { + int gpio; + int gpio_idx; + struct gpio_debug_event_def def; + struct gpio_debug_data *data; + struct dentry *file; +}; + +struct gpio_debug_data { + int gpio_count; + int *gpios; + struct dentry *gpio_debug_dir; + struct dentry *gpio_debug_events_dir; + struct platform_device *pdev; + struct gpio_debug_event *events; + int event_count; + int event_base; +}; + +static struct gpio_debug_data debug_data; + +DEFINE_MUTEX(debug_lock); + +enum { + GPIO_DEBUG_TOGGLE_100, + GPIO_DEBUG_TOGGLE_200, +}; + +static struct gpio_debug_event_def debug_events_table[] = { + [GPIO_DEBUG_TOGGLE_100] = { + .name = "toggle100", + .description = "Toggle the GPIO 100 times at initialisation", + }, + [GPIO_DEBUG_TOGGLE_200] = { + .name = "toggle200", + .description = "Toggle the GPIO 200 times at initialisation", + }, +}; + +static void gpio_debug_event(int gpio, int state) +{ + if (gpio >= 0) + gpio_set_value(gpio, state); +} + +static void gpio_debug_event_exec(int event_id, int state) +{ + if ((event_id >= 0) && (event_id < debug_data.event_count) && debug_data.events) + gpio_debug_event(debug_data.events[event_id].gpio, state); +} + +void gpio_debug_event_enter(int base, int id) +{ + gpio_debug_event_exec(base + id, 0); +} + +void gpio_debug_event_exit(int base, int id) +{ + gpio_debug_event_exec(base + id, 1); +} + +int gpio_debug_event_enabled(int base, int id) +{ + int event_id = base + id; + + if ((event_id >= 0) && + (event_id < debug_data.event_count) && + debug_data.events && + debug_data.events[event_id].gpio >= 0) + return 1; + else + return 0; +} + +static int gpio_debug_event_link(struct gpio_debug_event *event, int gpio_index) +{ + struct gpio_debug_data *data = event->data; + + if (gpio_index >= data->gpio_count) + return -ERANGE; + + if (gpio_index >= 0) + event->gpio = data->gpios[gpio_index]; + else + event->gpio = -1; + + event->gpio_idx = gpio_index; + + return 0; +} + +static ssize_t event_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + ssize_t ret = 0; + struct gpio_debug_event *event = file->f_inode->i_private; + char buf[256]; + int pos; + + mutex_lock(&debug_lock); + + pos = snprintf(buf, sizeof(buf), "Description:\n%s\n\nEvent is mapped to GPIO index %d with number %d\n", event->def.description, event->gpio_idx, event->gpio); + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + + mutex_unlock(&debug_lock); + + return ret; +} + +static ssize_t event_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + char *user_string; + ssize_t ret; + struct gpio_debug_event *event = file->f_inode->i_private; + int new_index = -1; + + mutex_lock(&debug_lock); + + user_string = kmalloc(count + 1, GFP_KERNEL); + memory_read_from_buffer(user_string, count, ppos, user_buf, count); + + user_string[count] = '\0'; + ret = (ssize_t)strnlen(user_string, count + 1); + + if (kstrtou32(user_string, 10, &new_index)) { + return -EINVAL; + + gpio_debug_event_link(event, new_index); + + kfree(user_string); + mutex_unlock(&debug_lock); + + return ret; +} + +static const struct file_operations event_ops = { + .read = event_read, + .write = event_write, +}; + +static void create_event_file(struct gpio_debug_event *event) +{ + struct gpio_debug_data *data = event->data; + + if (data && data->gpio_debug_events_dir) { + event->file = debugfs_create_file(event->def.name, 0660, data->gpio_debug_events_dir, event, &event_ops); + if (IS_ERR_OR_NULL(event->file)) { + event->file = NULL; + pr_warn("%s: Could not create debugfs file for %s\n", __func__, event->def.name); + } + } +} + +static void remove_event_file(struct gpio_debug_event *event) +{ + if (event && event->file) { + debugfs_remove(event->file); + event->file = NULL; + } +} + +static void gpio_debug_init_event(struct gpio_debug_data *data, struct gpio_debug_event_def *event, struct gpio_debug_event *event_save) +{ + event_save->def.description = event->description; + event_save->def.name = event->name; + event_save->gpio = -1; + event_save->gpio_idx = -1; + event_save->data = data; + + create_event_file(event_save); +} + +static void gpio_debug_destroy_event(struct gpio_debug_event *event) +{ + remove_event_file(event); + event->def.description = NULL; + event->def.name = NULL; + event->gpio = -1; + event->gpio_idx = -1; + event->data = NULL; +} + +int gpio_debug_event_list_register(struct gpio_debug_event_def *events, int event_count) +{ + struct gpio_debug_data *data = &debug_data; + int start_index = data->event_count; + struct gpio_debug_event *new_events; + int new_event_count = data->event_count + event_count; + int i, j; + + mutex_lock(&debug_lock); + + if (data->events) + for (i = 0; i < data->event_count; i++) + remove_event_file(&data->events[i]); + + new_events = krealloc(data->events, new_event_count * sizeof(struct gpio_debug_event), GFP_KERNEL); + if (!new_events) { + pr_warn("%s: Could not expand for extra events\n", __func__); + /* If krealloc fails, data->events is unchanged, so just exit */ + return -ENOMEM; + } + data->events = new_events; + for (i = 0; i < data->event_count; i++) + create_event_file(&data->events[i]); + + data->event_count = new_event_count; + + for (i = 0, j = start_index; (i < event_count) && (j < data->event_count); i++, j++) + gpio_debug_init_event(data, &events[i], &data->events[j]); + + mutex_unlock(&debug_lock); + return start_index; +} + +void gpio_debug_event_list_unregister(int base, int event_count) +{ + int i; + struct gpio_debug_data *data = &debug_data; + + mutex_lock(&debug_lock); + + for (i = base; (i < (event_count + base)) && (i < data->event_count); i++) + gpio_debug_destroy_event(&data->events[i]); + + mutex_unlock(&debug_lock); +} + +static ssize_t event_list_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + int i; + ssize_t ret = 0; + int length = 0; + char *buf; + struct gpio_debug_data *data = file->f_inode->i_private; + struct device *dev = &data->pdev->dev; + char headline[] = " gpio event\n"; + + mutex_lock(&debug_lock); + + length += strlen(headline); + + for (i = 0; i < data->event_count; i++) + if (data->events[i].def.name) + length += strlen(data->events[i].def.name) + 7; + length++; /* Reserve space for NULL termination */ + + buf = devm_kzalloc(dev, length, GFP_KERNEL); + buf[0] = '\0'; + snprintf(buf, length, "%s", headline); + for (i = 0; i < data->event_count; i++) + if (data->events[i].data) { + if (data->events[i].gpio_idx >= 0) + snprintf(buf, length, "%s%5d %s\n", buf, data->events[i].gpio_idx, data->events[i].def.name); + else + snprintf(buf, length, "%s %s\n", buf, data->events[i].def.name); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, length); + devm_kfree(dev, buf); + + mutex_unlock(&debug_lock); + + return ret; +} + +static const struct file_operations event_list_ops = { + .read = event_list_read, +}; + +static ssize_t num_gpios_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + ssize_t ret = 0; + struct gpio_debug_data *data = file->f_inode->i_private; + char buf[256]; + int pos; + + pos = snprintf(buf, sizeof(buf), "%d\n", data->gpio_count); + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + return ret; +} + +static const struct file_operations num_gpios_ops = { + .read = num_gpios_read, +}; + +static int gpio_debug_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int count; + struct gpio_debug_data *data = &debug_data; + int i, j; + + mutex_lock(&debug_lock); + + count = of_gpio_count(np); + if (count < 0) + count = 0; /* Errors register as no GPIOs available */ + + data->gpio_count = count; + data->gpios = NULL; + data->pdev = pdev; + + if (count) { + data->gpios = devm_kzalloc(dev, count * sizeof(int), GFP_KERNEL); + for (i = 0; i < count; i++) { + data->gpios[i] = of_get_gpio(np, i); + dev_info(dev, "GPIO at index %d has number %d\n", i, data->gpios[i]); + if (gpio_is_valid(data->gpios[i])) { + char label[256]; + + sprintf(label, "debug-gpio-%d", i); + dev_info(dev, "Requesting GPIO %d index %d with label %s\n", data->gpios[i], i, label); + if (devm_gpio_request(dev, data->gpios[i], label)) + dev_err(dev, "GPIO [%d] request failed\n", data->gpios[i]); + gpio_set_value(data->gpios[i], 1); + } else + dev_warn(dev, "GPIO at index %d is invalid\n", i); + } + } + + data->gpio_debug_dir = debugfs_create_dir("gpio_debug", NULL); + if (!IS_ERR_OR_NULL(data->gpio_debug_dir)) { + data->gpio_debug_events_dir = debugfs_create_dir("events", data->gpio_debug_dir); + if (IS_ERR_OR_NULL(data->gpio_debug_events_dir)) { + data->gpio_debug_events_dir = NULL; + dev_err(dev, "Debugfs cannot create subdir\n"); + } + debugfs_create_file("event_list", 0440, data->gpio_debug_dir, data, &event_list_ops); + debugfs_create_file("num_gpios", 0440, data->gpio_debug_dir, data, &num_gpios_ops); + } else { + data->gpio_debug_dir = NULL; + dev_warn(dev, "Debugfs is not available, configuration of GPIO debug is not possible\n"); + } + + for (i = 0; i < data->event_count; i++) + create_event_file(&data->events[i]); + + mutex_unlock(&debug_lock); + + data->event_base = gpio_debug_event_list_register(debug_events_table, ARRAY_SIZE(debug_events_table)); + + for (i = 0; i < count; i++) { + gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_100], i); + for (j = 0; j < 100; j++) { + gpio_debug_event_enter(data->event_base, GPIO_DEBUG_TOGGLE_100); + gpio_debug_event_exit(data->event_base, GPIO_DEBUG_TOGGLE_100); + } + } + gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_100], -1); + + for (i = 0; i < count; i++) { + gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_200], i); + for (j = 0; j < 200; j++) { + gpio_debug_event_enter(data->event_base, GPIO_DEBUG_TOGGLE_200); + gpio_debug_event_exit(data->event_base, GPIO_DEBUG_TOGGLE_200); + } + } + gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_200], -1); + + return 0; +} + +static int gpio_debug_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_debug_data *data = &debug_data; + + mutex_lock(&debug_lock); + debugfs_remove_recursive(data->gpio_debug_dir); + data->gpio_debug_dir = NULL; + data->gpio_debug_events_dir = NULL; + + if (data->gpios) { + int i; + + for (i = 0; i < data->gpio_count; i++) + if (gpio_is_valid(data->gpios[i])) + devm_gpio_free(dev, data->gpios[i]); + devm_kfree(dev, data->gpios); + data->gpios = NULL; + data->gpio_count = 0; + } + data->pdev = NULL; + kfree(data->events); + data->events = NULL; + data->event_count = 0; + mutex_unlock(&debug_lock); + + return 0; +} + +static const struct of_device_id gpio_debug_match[] = { + { .compatible = "samsung,gpio-debug", }, + {}, +}; +MODULE_DEVICE_TABLE(of, gpio_debug_match); + +static struct platform_driver gpio_debug_driver = { + .probe = gpio_debug_probe, + .remove = gpio_debug_remove, + .driver = { + .name = "gpio_debug", + .of_match_table = gpio_debug_match, + } +}; +module_platform_driver(gpio_debug_driver); + +MODULE_DESCRIPTION("GPIO Debug framework"); +MODULE_AUTHOR("Samsung Electronics Co., Ltd"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/misc/samsung/kic/Kconfig b/drivers/misc/samsung/kic/Kconfig new file mode 100644 index 000000000000..0851fb06ee99 --- /dev/null +++ b/drivers/misc/samsung/kic/Kconfig @@ -0,0 +1,9 @@ +config SAMSUNG_KIC + tristate "Kernel Information and Control (KIC) interface for Samsung Wi-Fi and Bluetooth chips" + +if SAMSUNG_KIC != n + +config SLSI_KIC_API_ENABLED + bool "Enable the KIC kernel API" + depends on SAMSUNG_KIC +endif diff --git a/drivers/misc/samsung/kic/Makefile b/drivers/misc/samsung/kic/Makefile new file mode 100644 index 000000000000..f865ef35c939 --- /dev/null +++ b/drivers/misc/samsung/kic/Makefile @@ -0,0 +1,18 @@ +obj-$(CONFIG_SAMSUNG_KIC) += samsung_kic.o +samsung_kic-y += \ + slsi_kic_core.o \ + slsi_kic_filtering.o \ + slsi_kic_sap_wifi.o \ + slsi_kic_sap_cm.o \ + slsi_kic_sap_bt.o \ + slsi_kic_sap_ant.o + +ccflags-y += $(CONFIG_SAMSUNG_KIC_EXTRA) + +# ---------------------------------------------------------------------------- +# KIC configuration +# ---------------------------------------------------------------------------- + +ccflags-$(CONFIG_SLSI_KIC_API_ENABLED) += -DCONFIG_SLSI_KIC_API_ENABLED + +ccflags-$(CONFIG_SCSC_LOGRING) += -DCONFIG_SCSC_PRINTK diff --git a/drivers/misc/samsung/kic/slsi_kic_core.c b/drivers/misc/samsung/kic/slsi_kic_core.c new file mode 100755 index 000000000000..1f90614ecedf --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_core.c @@ -0,0 +1,793 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include "slsi_kic_internal.h" + +static DEFINE_MUTEX(kic_lock); +static struct slsi_kic_pdata *pdata; + +#define SLSI_MAX_NUM_KIC_OPS 7 +#define SLSI_MAX_NUM_MULTICAST_GROUP 1 + +static struct genl_ops slsi_kic_ops[SLSI_MAX_NUM_KIC_OPS]; + +static int slsi_kic_pre_doit(const struct genl_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + SCSC_TAG_ERR(KIC_COMMON, "%s Handle CMD %d, seq %d\n", + __func__, ops->cmd, info->snd_seq); + + OS_UNUSED_PARAMETER(skb); + + /* Called BEFORE the command cb - do filtering here */ + + /* Consider doing some check for "test_mode" primitives here: + * It could be a way to prevent test primitives (which can be + * powerful) to run unless test_mode has been configured. */ + + return 0; +} + +static void slsi_kic_post_doit(const struct genl_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + OS_UNUSED_PARAMETER(ops); + OS_UNUSED_PARAMETER(skb); + OS_UNUSED_PARAMETER(info); + + /* Called AFTER the command cb - could do something here */ +} + +static const struct genl_multicast_group slsi_kic_general_system_mcgrp[SLSI_MAX_NUM_MULTICAST_GROUP] = { + { .name = "general_system", }, +}; + + +/* The netlink family */ +static struct genl_family slsi_kic_fam = { + .name = "slsi_kic", /* Have users key off the name instead */ + .hdrsize = 0, /* No private header */ + .version = 2, + .netnsok = true, + .maxattr = SLSI_KIC_ATTR_MAX, + .pre_doit = slsi_kic_pre_doit, + .post_doit = slsi_kic_post_doit, + .ops = slsi_kic_ops, + .n_ops = SLSI_MAX_NUM_KIC_OPS, + .mcgrps = slsi_kic_general_system_mcgrp, + .n_mcgrps = SLSI_MAX_NUM_MULTICAST_GROUP, +}; + +/** + * Message building helpers + */ +static inline void *kic_hdr_put(struct sk_buff *skb, uint32_t portid, uint32_t seq, + int flags, u8 cmd) +{ + /* Since there is no private header just add the generic one */ + return genlmsg_put(skb, portid, seq, &slsi_kic_fam, flags, cmd); +} + +static int kic_build_u32_msg(struct sk_buff *msg, uint32_t portid, uint32_t seq, int flags, + enum slsi_kic_commands cmd, int attrtype, uint32_t payload) +{ + void *hdr; + + hdr = kic_hdr_put(msg, portid, seq, flags, cmd); + if (!hdr) + return -EFAULT; + + if (nla_put_u32(msg, attrtype, payload)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + + return 0; + +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int kic_add_timestamp_attrs(struct sk_buff *msg) +{ + struct timespec ts; + + /** + * Use getrawmonotonic instead of getnstimeofday to avoid problems with + * NTP updating things, which can make things look weird. + */ + getrawmonotonic(&ts); + + if (nla_put_u64_64bit(msg, SLSI_KIC_ATTR_TIMESTAMP_TV_SEC, ts.tv_sec, IFLA_BR_PAD)) + goto nla_put_failure; + + if (nla_put_u64_64bit(msg, SLSI_KIC_ATTR_TIMESTAMP_TV_NSEC, ts.tv_nsec, IFLA_BR_PAD)) + goto nla_put_failure; + + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int kic_build_system_event_msg(struct sk_buff *msg, uint32_t portid, + uint32_t seq, int flags, + uint32_t event_cat, uint32_t event) +{ + void *hdr; + struct nlattr *nla; + + hdr = kic_hdr_put(msg, portid, seq, flags, SLSI_KIC_CMD_SYSTEM_EVENT_IND); + if (!hdr) + return -EFAULT; + + nla = nla_nest_start(msg, SLSI_KIC_ATTR_TIMESTAMP); + if (kic_add_timestamp_attrs(msg) < 0) + nla_nest_cancel(msg, nla); + else + nla_nest_end(msg, nla); + + if (nla_put_u32(msg, SLSI_KIC_ATTR_SYSTEM_EVENT_CATEGORY, event_cat)) + goto nla_put_failure; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_SYSTEM_EVENT, event)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + + return 0; + +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + + +static int kic_build_firmware_event_msg(struct sk_buff *msg, uint32_t portid, + uint32_t seq, int flags, + uint16_t firmware_event_type, + enum slsi_kic_technology_type tech_type, + uint32_t contain_type, + struct slsi_kic_firmware_event_ccp_host *event) +{ + void *hdr; + struct nlattr *nla; + + hdr = kic_hdr_put(msg, portid, seq, flags, SLSI_KIC_CMD_FIRMWARE_EVENT_IND); + if (!hdr) { + nlmsg_free(msg); + return -EFAULT; + } + + if (nla_put_u16(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_TYPE, firmware_event_type)) + goto nla_put_failure; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_TECHNOLOGY_TYPE, tech_type)) + goto nla_put_failure; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_FIRMWARE_CONTAINER_TYPE, contain_type)) + goto nla_put_failure; + + nla = nla_nest_start(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CONTAINER_CCP_HOST); + if (nla_put_u32(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ID, event->id)) + goto nla_put_failure_cancel; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL, event->level)) + goto nla_put_failure_cancel; + + if (nla_put_string(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL_STRING, event->level_string)) + goto nla_put_failure_cancel; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_TIMESTAMP, event->timestamp)) + goto nla_put_failure_cancel; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_CPU, event->cpu)) + goto nla_put_failure_cancel; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_OCCURENCES, event->occurences)) + goto nla_put_failure_cancel; + + if (nla_put(msg, SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ARG, event->arg_length, event->arg)) + goto nla_put_failure_cancel; + nla_nest_end(msg, nla); + + genlmsg_end(msg, hdr); + + return 0; + +nla_put_failure_cancel: + nla_nest_cancel(msg, nla); + +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + + +static int kic_build_service_info_msg_add_service(struct sk_buff *msg, + enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info) +{ + struct nlattr *nla = NULL; + + if (!msg || !info) + goto nla_put_failure; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_TECHNOLOGY_TYPE, tech)) + goto nla_put_failure; + + nla = nla_nest_start(msg, SLSI_KIC_ATTR_SERVICE_INFO); + if (nla_put_string(msg, SLSI_KIC_ATTR_SERVICE_INFO_VER_STR, info->ver_str)) + goto nla_put_failure; + + if (nla_put_u16(msg, SLSI_KIC_ATTR_SERVICE_INFO_FW_API_MAJOR, info->fw_api_major)) + goto nla_put_failure; + + if (nla_put_u16(msg, SLSI_KIC_ATTR_SERVICE_INFO_FW_API_MINOR, info->fw_api_minor)) + goto nla_put_failure; + + if (nla_put_u16(msg, SLSI_KIC_ATTR_SERVICE_INFO_RELEASE_PRODUCT, info->release_product)) + goto nla_put_failure; + + if (nla_put_u16(msg, SLSI_KIC_ATTR_SERVICE_INFO_HOST_RELEASE_ITERATION, info->host_release_iteration)) + goto nla_put_failure; + + if (nla_put_u16(msg, SLSI_KIC_ATTR_SERVICE_INFO_HOST_RELEASE_CANDIDATE, info->host_release_candidate)) + goto nla_put_failure; + + nla_nest_end(msg, nla); + + return 0; + +nla_put_failure: + if (nla) + nla_nest_cancel(msg, nla); + + return -EMSGSIZE; +} + +static int kic_build_service_info_msg(struct sk_buff *msg, uint32_t portid, + uint32_t seq, int flags, + enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info) +{ + void *hdr; + + hdr = kic_hdr_put(msg, portid, seq, flags, SLSI_KIC_CMD_SERVICE_INFORMATION_IND); + if (!hdr) + return -EFAULT; + + if (kic_build_service_info_msg_add_service(msg, tech, info) < 0) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + + return 0; + +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + + +static int get_snd_pid(struct genl_info *info) +{ + uint32_t snd_pid = 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) + snd_pid = info->snd_pid; +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 6, 0) + snd_pid = info->snd_portid; +#endif + + return snd_pid; +} + +struct slsi_kic_pdata *slsi_kic_core_get_context(void) +{ + return pdata; +} + +/** + * Set the record to NULL to free and delete all stored records. + */ +static int service_info_delete_record(struct slsi_kic_service_details *record) +{ + struct slsi_kic_pdata *pdata = slsi_kic_core_get_context(); + + if (!pdata) + return -EINVAL; + + if (down_interruptible(&pdata->chip_details.proxy_service_list_mutex)) + SCSC_TAG_ERR(KIC_COMMON, "Failed to lock service info mutex - continue anyway\n"); + + if (record == NULL) { + struct slsi_kic_service_details *service, *tmp_node; + + list_for_each_entry_safe(service, tmp_node, &pdata->chip_details.proxy_service_list, proxy_q) { + list_del(&service->proxy_q); + kfree(service); + } + } else { + list_del(&record->proxy_q); + kfree(record); + } + up(&pdata->chip_details.proxy_service_list_mutex); + + return 0; +} + +static struct slsi_kic_service_details * +service_info_find_entry(enum slsi_kic_technology_type tech) +{ + struct slsi_kic_pdata *pdata = slsi_kic_core_get_context(); + struct slsi_kic_service_details *service, *tmp_node; + + if (!pdata) + return NULL; + + list_for_each_entry_safe(service, tmp_node, &pdata->chip_details.proxy_service_list, proxy_q) { + if (service->tech == tech) + return service; + } + + return NULL; +} + +static int service_info_update_record(enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info) +{ + struct slsi_kic_pdata *pdata = slsi_kic_core_get_context(); + static struct slsi_kic_service_details *record; + + if (!pdata) + return -EINVAL; + + if (down_interruptible(&pdata->chip_details.proxy_service_list_mutex)) + goto err_out; + + record = service_info_find_entry(tech); + if (record == NULL) { + up(&pdata->chip_details.proxy_service_list_mutex); + goto err_out; + } + + record->tech = tech; + memcpy(&record->info, info, sizeof(struct slsi_kic_service_info)); + up(&pdata->chip_details.proxy_service_list_mutex); + + return 0; + +err_out: + SCSC_TAG_ERR(KIC_COMMON, "Failed to update service info record\n"); + return -EFAULT; +} + +static int service_info_add(enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info) +{ + struct slsi_kic_service_details *new_entry; + struct slsi_kic_pdata *pdata = slsi_kic_core_get_context(); + + if (!pdata) + return -EINVAL; + + new_entry = kmalloc(sizeof(struct slsi_kic_service_details), GFP_KERNEL); + if (!new_entry) + return -ENOMEM; + + new_entry->tech = tech; + memcpy(&new_entry->info, info, sizeof(struct slsi_kic_service_info)); + + if (down_interruptible(&pdata->chip_details.proxy_service_list_mutex)) + goto err_out; + + list_add_tail(&new_entry->proxy_q, &pdata->chip_details.proxy_service_list); + up(&pdata->chip_details.proxy_service_list_mutex); + + return 0; + +err_out: + SCSC_TAG_ERR(KIC_COMMON, "Failed to add service info record to list\n"); + kfree(new_entry); + return -EFAULT; +} + + +/** + * Command callbacks + */ + +/* This function shall not do anything since the direction is + * kernel->user space for this primitive. We should look into if it's + * possible to handle this better than having an empty stub function. */ +static int slsi_kic_wrong_direction(struct sk_buff *skb, struct genl_info *info) +{ + OS_UNUSED_PARAMETER(skb); + + SCSC_TAG_ERR(KIC_COMMON, "%s Received CMD from pid %u seq %u: Wrong direction only supports kernel->user space\n", + __func__, info->snd_seq, get_snd_pid(info)); + return -EINVAL; +} + +static int slsi_kic_interface_version_number_req(struct sk_buff *skb, struct genl_info *info) +{ + struct sk_buff *msg; + void *hdr; + + OS_UNUSED_PARAMETER(skb); + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = kic_hdr_put(msg, 0, info->snd_seq, 0, SLSI_KIC_CMD_KIC_INTERFACE_VERSION_NUMBER_REQ); + if (!hdr) + goto nl_hdr_failure; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_KIC_VERSION_MAJOR, SLSI_KIC_INTERFACE_VERSION_MAJOR)) + goto nla_put_failure; + + if (nla_put_u32(msg, SLSI_KIC_ATTR_KIC_VERSION_MINOR, SLSI_KIC_INTERFACE_VERSION_MINOR)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + genlmsg_cancel(msg, hdr); + +nl_hdr_failure: + nlmsg_free(msg); + return -ENOBUFS; +} + +static int slsi_kic_echo_req(struct sk_buff *skb, struct genl_info *info) +{ + struct sk_buff *msg; + uint32_t payload = 0; + + OS_UNUSED_PARAMETER(skb); + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + if (info->attrs[SLSI_KIC_ATTR_ECHO]) + payload = nla_get_u32(info->attrs[SLSI_KIC_ATTR_ECHO]); + + if (kic_build_u32_msg(msg, get_snd_pid(info), info->snd_seq, 0, + SLSI_KIC_CMD_ECHO_REQ, SLSI_KIC_ATTR_ECHO, payload) < 0) { + nlmsg_free(msg); + return -ENOBUFS; + } + + return genlmsg_reply(msg, info); +} + +static int slsi_kic_service_information_req(struct sk_buff *skb, struct genl_info *info) +{ + struct slsi_kic_pdata *pdata = slsi_kic_core_get_context(); + int counter = 0, i; + struct sk_buff *msg; + struct slsi_kic_service_details *sr; + void *hdr; + + OS_UNUSED_PARAMETER(skb); + + if (!pdata) + return -EINVAL; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = kic_hdr_put(msg, 0, info->snd_seq, 0, SLSI_KIC_CMD_SERVICE_INFORMATION_REQ); + if (!hdr) + goto nla_put_failure; + + if (down_interruptible(&pdata->chip_details.proxy_service_list_mutex)) + goto nla_put_failure; + + /* The request doesn't carry attributes, so no validation required. + * Query the list for information for each technology and encode. */ + for (i = 0; i < slsi_kic_technology_type__after_last; i++) { + sr = service_info_find_entry(i); + if (sr) { + counter++; + if (kic_build_service_info_msg_add_service(msg, i, &sr->info) < 0) { + up(&pdata->chip_details.proxy_service_list_mutex); + goto nla_put_failure; + } + } + } + up(&pdata->chip_details.proxy_service_list_mutex); + + if (nla_put_u32(msg, SLSI_KIC_ATTR_NUMBER_OF_ENCODED_SERVICES, counter)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + nlmsg_free(msg); + return -EMSGSIZE; +} + +static int slsi_kic_test_trigger_recovery_req(struct sk_buff *skb, struct genl_info *info) +{ + struct sk_buff *msg; + uint32_t technology = 0, recovery_type = 0; + struct slsi_kic_pdata *pdata = slsi_kic_core_get_context(); + enum slsi_kic_test_recovery_status status = slsi_kic_test_recovery_status_ok; + + OS_UNUSED_PARAMETER(skb); + + if (info->attrs[SLSI_KIC_ATTR_TECHNOLOGY_TYPE]) + technology = nla_get_u32(info->attrs[SLSI_KIC_ATTR_TECHNOLOGY_TYPE]); + + if (info->attrs[SLSI_KIC_ATTR_TEST_RECOVERY_TYPE]) + recovery_type = nla_get_u32(info->attrs[SLSI_KIC_ATTR_TEST_RECOVERY_TYPE]); + + if (pdata) { + int err = -EFAULT; + + if (technology == slsi_kic_technology_type_wifi) { + struct slsi_kic_wifi_ops_tuple *wifi_ops = NULL; + + wifi_ops = &pdata->wifi_ops_tuple; + + mutex_lock(&wifi_ops->ops_mutex); + if (wifi_ops->wifi_ops.trigger_recovery) + err = wifi_ops->wifi_ops.trigger_recovery(wifi_ops->priv, + (enum slsi_kic_test_recovery_type)recovery_type); + mutex_unlock(&wifi_ops->ops_mutex); + } else if (technology == slsi_kic_technology_type_curator) { + struct slsi_kic_cm_ops_tuple *cm_ops = NULL; + + cm_ops = &pdata->cm_ops_tuple; + + mutex_lock(&cm_ops->ops_mutex); + if (cm_ops->cm_ops.trigger_recovery) + err = cm_ops->cm_ops.trigger_recovery(cm_ops->priv, + (enum slsi_kic_test_recovery_type)recovery_type); + mutex_unlock(&cm_ops->ops_mutex); + } else if (technology == slsi_kic_technology_type_bt) { + struct slsi_kic_bt_ops_tuple *bt_ops = NULL; + + bt_ops = &pdata->bt_ops_tuple; + + mutex_lock(&bt_ops->ops_mutex); + if (bt_ops->bt_ops.trigger_recovery) + err = bt_ops->bt_ops.trigger_recovery(bt_ops->priv, + (enum slsi_kic_test_recovery_type)recovery_type); + mutex_unlock(&bt_ops->ops_mutex); + } else if (technology == slsi_kic_technology_type_ant) { + struct slsi_kic_ant_ops_tuple *ant_ops = NULL; + + ant_ops = &pdata->ant_ops_tuple; + + mutex_lock(&ant_ops->ops_mutex); + if (ant_ops->ant_ops.trigger_recovery) + err = ant_ops->ant_ops.trigger_recovery(ant_ops->priv, + (enum slsi_kic_test_recovery_type)recovery_type); + mutex_unlock(&ant_ops->ops_mutex); + } + + if (err < 0) + status = slsi_kic_test_recovery_status_error_send_msg; + } else + status = slsi_kic_test_recovery_status_error_invald_param; + + /* Prepare reply */ + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + if (kic_build_u32_msg(msg, get_snd_pid(info), info->snd_seq, 0, + SLSI_KIC_CMD_TEST_TRIGGER_RECOVERY_REQ, SLSI_KIC_ATTR_TRIGGER_RECOVERY_STATUS, status) < 0) + goto nl_hdr_failure; + + return genlmsg_reply(msg, info); + +nl_hdr_failure: + nlmsg_free(msg); + return -ENOBUFS; +} + + +int slsi_kic_service_information_ind(enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info) +{ + struct sk_buff *msg; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + if (service_info_find_entry(tech) == NULL) { + if (service_info_add(tech, info) < 0) + SCSC_TAG_ERR(KIC_COMMON, "%s Failed to add record\n", __func__); + } else if (service_info_update_record(tech, info) < 0) + SCSC_TAG_ERR(KIC_COMMON, "%s Failed to update record\n", __func__); + + if (kic_build_service_info_msg(msg, 0, 0, 0, tech, info) < 0) + goto err; + + return genlmsg_multicast(&slsi_kic_fam, msg, 0, 0, GFP_KERNEL); + +err: + nlmsg_free(msg); + return -ENOBUFS; +} +EXPORT_SYMBOL(slsi_kic_service_information_ind); + + +int slsi_kic_system_event_ind(enum slsi_kic_system_event_category event_cat, + enum slsi_kic_system_events event, gfp_t flags) +{ + struct sk_buff *msg; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, flags); + if (!msg) + return -ENOMEM; + + if (kic_build_system_event_msg(msg, 0, 0, 0, event_cat, event) < 0) + goto err; + + return genlmsg_multicast(&slsi_kic_fam, msg, 0, 0, flags); + +err: + nlmsg_free(msg); + return -ENOBUFS; +} +EXPORT_SYMBOL(slsi_kic_system_event_ind); + + +int slsi_kic_firmware_event_ind(uint16_t firmware_event_type, enum slsi_kic_technology_type tech_type, + uint32_t contain_type, struct slsi_kic_firmware_event_ccp_host *event) +{ + struct sk_buff *msg; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + if (kic_build_firmware_event_msg(msg, 0, 0, 0, firmware_event_type, tech_type, contain_type, event) < 0) + return -ENOBUFS; + + return genlmsg_multicast(&slsi_kic_fam, msg, 0, 0, GFP_KERNEL); +} +EXPORT_SYMBOL(slsi_kic_firmware_event_ind); + + +static struct genl_ops slsi_kic_ops[SLSI_MAX_NUM_KIC_OPS] = { + { + .cmd = SLSI_KIC_CMD_KIC_INTERFACE_VERSION_NUMBER_REQ, + .doit = slsi_kic_interface_version_number_req, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = SLSI_KIC_CMD_SYSTEM_EVENT_IND, + .doit = slsi_kic_wrong_direction, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = SLSI_KIC_CMD_SERVICE_INFORMATION_REQ, + .doit = slsi_kic_service_information_req, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = SLSI_KIC_CMD_SERVICE_INFORMATION_IND, + .doit = slsi_kic_wrong_direction, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = SLSI_KIC_CMD_FIRMWARE_EVENT_IND, + .doit = slsi_kic_wrong_direction, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = SLSI_KIC_CMD_ECHO_REQ, + .doit = slsi_kic_echo_req, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = SLSI_KIC_CMD_TEST_TRIGGER_RECOVERY_REQ, + .doit = slsi_kic_test_trigger_recovery_req, + .policy = slsi_kic_attr_policy, + .flags = GENL_ADMIN_PERM, + }, +}; + +static int __init slsi_kic_init(void) +{ + int err; + + SCSC_TAG_DEBUG(KIC_COMMON, "%s Enter\n", __func__); + + mutex_lock(&kic_lock); + + pdata = kzalloc(sizeof(struct slsi_kic_pdata), GFP_KERNEL); + if (!pdata) { + SCSC_TAG_ERR(KIC_COMMON, "%s Exit - no mem\n", __func__); + mutex_unlock(&kic_lock); + return -ENOMEM; + } + + mutex_init(&pdata->wifi_ops_tuple.ops_mutex); + mutex_init(&pdata->cm_ops_tuple.ops_mutex); + mutex_init(&pdata->bt_ops_tuple.ops_mutex); + mutex_init(&pdata->ant_ops_tuple.ops_mutex); + + /* Init chip information proxy list */ + INIT_LIST_HEAD(&pdata->chip_details.proxy_service_list); + sema_init(&pdata->chip_details.proxy_service_list_mutex, 1); + pdata->state = idle; + + err = genl_register_family(&slsi_kic_fam); + if (err != 0) + goto err_out; + + mutex_unlock(&kic_lock); + SCSC_TAG_DEBUG(KIC_COMMON, "%s Exit\n", __func__); + return 0; + +err_out: + genl_unregister_family(&slsi_kic_fam); + mutex_unlock(&kic_lock); + SCSC_TAG_ERR(KIC_COMMON, "%s Exit - err %d\n", __func__, err); + return err; +} + +static void __exit slsi_kic_exit(void) +{ + int err; + + SCSC_TAG_DEBUG(KIC_COMMON, "%s Enter\n", __func__); + + BUG_ON(!pdata); + if (!pdata) { + SCSC_TAG_ERR(KIC_COMMON, "%s Exit - invalid pdata\n", __func__); + return; + } + + mutex_lock(&kic_lock); + err = genl_unregister_family(&slsi_kic_fam); + if (err < 0) + SCSC_TAG_ERR(KIC_COMMON, "%s Failed to unregister family\n", __func__); + + if (service_info_delete_record(NULL) < 0) + SCSC_TAG_ERR(KIC_COMMON, "%s Deleting service info liste failed\n", __func__); + + mutex_destroy(&pdata->wifi_ops_tuple.ops_mutex); + mutex_destroy(&pdata->cm_ops_tuple.ops_mutex); + mutex_destroy(&pdata->bt_ops_tuple.ops_mutex); + mutex_destroy(&pdata->ant_ops_tuple.ops_mutex); + + kfree(pdata); + pdata = NULL; + mutex_unlock(&kic_lock); + + SCSC_TAG_DEBUG(KIC_COMMON, "%s Exit\n", __func__); +} + +module_init(slsi_kic_init); +module_exit(slsi_kic_exit); + +MODULE_DESCRIPTION("SCSC Kernel Information and Control (KIC) interface"); +MODULE_AUTHOR("Samsung Electronics Co., Ltd"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/misc/samsung/kic/slsi_kic_filtering.c b/drivers/misc/samsung/kic/slsi_kic_filtering.c new file mode 100644 index 000000000000..93c0c2d3d79c --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_filtering.c @@ -0,0 +1,8 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Implement subscriber and filtering here. This won't be essential for + * first draft of the kernel KIC implementation */ diff --git a/drivers/misc/samsung/kic/slsi_kic_internal.h b/drivers/misc/samsung/kic/slsi_kic_internal.h new file mode 100755 index 000000000000..444ecdd0121f --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_internal.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_INTERNAL_H +#define __SLSI_KIC_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define OS_UNUSED_PARAMETER(x) ((void)(x)) + +/** + * Core instance + */ +enum slsi_kic_state { + idle, + initialised, + ready +}; + +struct slsi_kic_service_details { + struct list_head proxy_q; + enum slsi_kic_technology_type tech; + struct slsi_kic_service_info info; +}; + +struct slsi_kic_chip_details { + struct semaphore proxy_service_list_mutex; + struct list_head proxy_service_list; +}; + +struct slsi_kic_wifi_ops_tuple { + void *priv; + struct slsi_kic_wifi_ops wifi_ops; + struct mutex ops_mutex; +}; + +struct slsi_kic_bt_ops_tuple { + void *priv; + struct slsi_kic_bt_ops bt_ops; + struct mutex ops_mutex; +}; + +struct slsi_kic_ant_ops_tuple { + void *priv; + struct slsi_kic_ant_ops ant_ops; + struct mutex ops_mutex; +}; + +struct slsi_kic_cm_ops_tuple { + void *priv; + struct slsi_kic_cm_ops cm_ops; + struct mutex ops_mutex; +}; + +struct slsi_kic_pdata { + enum slsi_kic_state state; + struct slsi_kic_chip_details chip_details; + struct slsi_kic_wifi_ops_tuple wifi_ops_tuple; + struct slsi_kic_cm_ops_tuple cm_ops_tuple; + struct slsi_kic_bt_ops_tuple bt_ops_tuple; + struct slsi_kic_ant_ops_tuple ant_ops_tuple; + uint32_t seq; /* This should *perhaps* be moved to a record struct for + * each subscription - will look into that during the + * filtering work. */ +}; + +struct slsi_kic_pdata *slsi_kic_core_get_context(void); + +#endif /* #ifndef __SLSI_KIC_INTERNAL_H */ diff --git a/drivers/misc/samsung/kic/slsi_kic_sap_ant.c b/drivers/misc/samsung/kic/slsi_kic_sap_ant.c new file mode 100755 index 000000000000..2e6ec7c066d0 --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_sap_ant.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "slsi_kic_internal.h" + +#if defined(CONFIG_SCSC_ANT) && defined(CONFIG_SAMSUNG_KIC) +int slsi_kic_ant_ops_register(void *priv, struct slsi_kic_ant_ops *ant_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + if (!kic_inst) + return -EFAULT; + + mutex_lock(&kic_inst->ant_ops_tuple.ops_mutex); + memcpy(&kic_inst->ant_ops_tuple.ant_ops, ant_ops, sizeof(struct slsi_kic_ant_ops)); + kic_inst->ant_ops_tuple.priv = priv; + mutex_unlock(&kic_inst->ant_ops_tuple.ops_mutex); + return 0; +} +EXPORT_SYMBOL(slsi_kic_ant_ops_register); + +void slsi_kic_ant_ops_unregister(struct slsi_kic_ant_ops *ant_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + OS_UNUSED_PARAMETER(ant_ops); + + if (!kic_inst) + return; + + mutex_lock(&kic_inst->ant_ops_tuple.ops_mutex); + memset(&kic_inst->ant_ops_tuple.ant_ops, 0, sizeof(struct slsi_kic_ant_ops)); + kic_inst->ant_ops_tuple.priv = NULL; + mutex_unlock(&kic_inst->ant_ops_tuple.ops_mutex); +} +EXPORT_SYMBOL(slsi_kic_ant_ops_unregister); +#endif diff --git a/drivers/misc/samsung/kic/slsi_kic_sap_bt.c b/drivers/misc/samsung/kic/slsi_kic_sap_bt.c new file mode 100755 index 000000000000..ebc48f72317d --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_sap_bt.c @@ -0,0 +1,38 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "slsi_kic_internal.h" + +int slsi_kic_bt_ops_register(void *priv, struct slsi_kic_bt_ops *bt_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + if (!kic_inst) + return -EFAULT; + + mutex_lock(&kic_inst->bt_ops_tuple.ops_mutex); + memcpy(&kic_inst->bt_ops_tuple.bt_ops, bt_ops, sizeof(struct slsi_kic_bt_ops)); + kic_inst->bt_ops_tuple.priv = priv; + mutex_unlock(&kic_inst->bt_ops_tuple.ops_mutex); + return 0; +} +EXPORT_SYMBOL(slsi_kic_bt_ops_register); + +void slsi_kic_bt_ops_unregister(struct slsi_kic_bt_ops *bt_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + OS_UNUSED_PARAMETER(bt_ops); + + if (!kic_inst) + return; + + mutex_lock(&kic_inst->bt_ops_tuple.ops_mutex); + memset(&kic_inst->bt_ops_tuple.bt_ops, 0, sizeof(struct slsi_kic_bt_ops)); + kic_inst->bt_ops_tuple.priv = NULL; + mutex_unlock(&kic_inst->bt_ops_tuple.ops_mutex); +} +EXPORT_SYMBOL(slsi_kic_bt_ops_unregister); diff --git a/drivers/misc/samsung/kic/slsi_kic_sap_cm.c b/drivers/misc/samsung/kic/slsi_kic_sap_cm.c new file mode 100755 index 000000000000..666c1041b96e --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_sap_cm.c @@ -0,0 +1,38 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "slsi_kic_internal.h" + +int slsi_kic_cm_ops_register(void *priv, struct slsi_kic_cm_ops *cm_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + if (!kic_inst) + return -EFAULT; + + mutex_lock(&kic_inst->cm_ops_tuple.ops_mutex); + memcpy(&kic_inst->cm_ops_tuple.cm_ops, cm_ops, sizeof(struct slsi_kic_cm_ops)); + kic_inst->cm_ops_tuple.priv = priv; + mutex_unlock(&kic_inst->cm_ops_tuple.ops_mutex); + return 0; +} +EXPORT_SYMBOL(slsi_kic_cm_ops_register); + +void slsi_kic_cm_ops_unregister(struct slsi_kic_cm_ops *cm_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + OS_UNUSED_PARAMETER(cm_ops); + + if (!kic_inst) + return; + + mutex_lock(&kic_inst->cm_ops_tuple.ops_mutex); + memset(&kic_inst->cm_ops_tuple.cm_ops, 0, sizeof(struct slsi_kic_cm_ops)); + kic_inst->cm_ops_tuple.priv = NULL; + mutex_unlock(&kic_inst->cm_ops_tuple.ops_mutex); +} +EXPORT_SYMBOL(slsi_kic_cm_ops_unregister); diff --git a/drivers/misc/samsung/kic/slsi_kic_sap_wifi.c b/drivers/misc/samsung/kic/slsi_kic_sap_wifi.c new file mode 100755 index 000000000000..869f27d9fb88 --- /dev/null +++ b/drivers/misc/samsung/kic/slsi_kic_sap_wifi.c @@ -0,0 +1,38 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "slsi_kic_internal.h" + +int slsi_kic_wifi_ops_register(void *priv, struct slsi_kic_wifi_ops *wifi_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + if (!kic_inst) + return -EFAULT; + + mutex_lock(&kic_inst->wifi_ops_tuple.ops_mutex); + memcpy(&kic_inst->wifi_ops_tuple.wifi_ops, wifi_ops, sizeof(struct slsi_kic_wifi_ops)); + kic_inst->wifi_ops_tuple.priv = priv; + mutex_unlock(&kic_inst->wifi_ops_tuple.ops_mutex); + return 0; +} +EXPORT_SYMBOL(slsi_kic_wifi_ops_register); + +void slsi_kic_wifi_ops_unregister(struct slsi_kic_wifi_ops *wifi_ops) +{ + struct slsi_kic_pdata *kic_inst = slsi_kic_core_get_context(); + + OS_UNUSED_PARAMETER(wifi_ops); + + if (!kic_inst) + return; + + mutex_lock(&kic_inst->wifi_ops_tuple.ops_mutex); + memset(&kic_inst->wifi_ops_tuple.wifi_ops, 0, sizeof(struct slsi_kic_wifi_ops)); + kic_inst->wifi_ops_tuple.priv = NULL; + mutex_unlock(&kic_inst->wifi_ops_tuple.ops_mutex); +} +EXPORT_SYMBOL(slsi_kic_wifi_ops_unregister); diff --git a/drivers/misc/samsung/scsc/Kconfig b/drivers/misc/samsung/scsc/Kconfig new file mode 100644 index 000000000000..260c60046074 --- /dev/null +++ b/drivers/misc/samsung/scsc/Kconfig @@ -0,0 +1,189 @@ +config SCSC_CORE_CM + tristate "Samsung SCSC Maxwell driver Core Module" + depends on SCSC_CORE + +config SCSC_CORE + tristate "Samsung SCSC Maxwell driver" + select SCSC_CORE_CM + +config SCSC_WLBT_CONFIG_PLATFORM + string "SCSC WLBT PLATFORM" + default "" + ---help--- + Select the platform, e.g. lassen_smdk + +config SCSC_CORE_FW_LOCATION + string "SCSC FW location" + default "/system/etc/wifi" + ---help--- + Select the FW location, when CONFIG_SCSC_CORE_FW_LOCATION_AUTO=n + +config SCSC_CORE_FW_LOCATION_AUTO + string "SCSC FW location auto select" + default y + ---help--- + Select the FW location (Android N/O) automatically + +config SCSC_CORE_TOOL_LOCATION + string "SCSC tool location" + default "/system/bin" + ---help--- + Select the moredump tool location, when CONFIG_SCSC_CORE_FW_LOCATION_AUTO=n + +config SCSC_PCIE + tristate "Samsung SCSC MIF PCIE implementation" + depends on SCSC_CORE + depends on PCI + +config SCSC_MXLOGGER + bool "Samsung SCSC MXLOGGER" + depends on SCSC_CORE + default y + ---help--- + MXLOGGER provides FW level logging in DRAM + +config SCSC_SMAPPER + bool "Samsung SCSC WLAN Smapper support" + default y + depends on SCSC_CORE + ---help--- + SMAPPER provides direct access to Linux SKBs + +config SCSC_WLBTD + bool "Samsung SCSC Daemon support for Android O" + depends on SCSC_CORE + default n + ---help--- + Uses generic netlink messages to communicate events to userspace daemon which takes necessary actions e.g. taking moredump + +config SCSC_WRITE_INFO_FILE_WLBTD + bool "SCSC flag to decide whether to write file via wlbtd or not" + depends on SCSC_WLBTD + default n + +config SCSC_QOS + bool "Samsung SCSC kernel PM QoS support" + default y + depends on SCSC_CORE + ---help--- + SCSC_QOS provides support to configure kernel PM QoS to register configuration as per performance expectations + +config SCSC_PLATFORM + tristate "Samsung SCSC MIF Platform driver implementation" + depends on SCSC_CORE + +config SCSC_CM_MX_CLIENT_TEST + tristate "Samsung SCSC Test Client" + depends on SCSC_CORE + +config SCSC_MX_ALWAYS_ON + tristate "Samsung SCSC MX140 always booted" + depends on SCSC_CM_MX_CLIENT_TEST + +config SCSC_CLK20MHZ + tristate "Samsung SCSC USB 20MHz Clock" + depends on SCSC_CORE + +config SCSC_CLK20MHZ_TEST + tristate "Samsung SCSC USB 20MHz Clock Test" + depends on SCSC_CLK20MHZ + +config SCSC_FM + tristate "Samsung SCSC MX250 enables LDOs used for FM" + depends on SCSC_CORE + +config SCSC_FM_TEST + tristate "Samsung SCSC MX250 Test for enabling LDOs used for FM" + depends on SCSC_FM + +config SCSC_MMAP + tristate "Samsung SCSC MMAP/GDB User space interface" + depends on SCSC_CORE + +config SCSC_DBG_SAMPLER + tristate "Samsung SCSC Debug Sampler Service" + depends on SCSC_CORE + +config SCSC_DEBUG + tristate "Samsung SCSC Logging" + depends on SCSC_CORE && DEBUG_FS + default n + +config SCSC_DEBUG_COMPATIBILITY + bool "Samsung SCSC Logging mode" + default y + +config SCSC_LOGRING + tristate "Samsung SCSC Kernel Logging" + default y + ---help--- + SCSC Drivers logging mechanism + +config SCSC_STATIC_RING + tristate "Samsung SCSC Logging use static ring" + depends on SCSC_LOGRING + default y + +config SCSC_STATIC_RING_SIZE + int "Size of the static ring" + depends on SCSC_STATIC_RING + default "1048576" + range 1024 16777216 + +config SCSC_CHV_SUPPORT + bool "Samsung CHV f/w support" + depends on SCSC_CORE + default n + +config SCSC_GPR4_CON_DEBUG + bool "GPR4 PIO muxes switching to the Maxwell Subsystem" + depends on SCSC_PLATFORM + default n + +config SCSC_BUILD_TYPE + string "Type of Kernel Build" + ---help--- + Type of Kernel Build: User, User Debug, Engineering + +config SCSC_WIFILOGGER + tristate "Samsung SCSC Android Wi-Fi Logger" + depends on SCSC_CORE + default n + +config SCSC_WIFILOGGER_DEBUGFS + bool "Samsung SCSC Android Wi-Fi Logger DebugFS" + depends on SCSC_WIFILOGGER && DEBUG_FS + default y + +config SCSC_WIFILOGGER_TEST + bool "Samsung SCSC Android Wi-Fi Logger Test Harness" + depends on SCSC_WIFILOGGER && SCSC_WIFILOGGER_DEBUGFS + default n + +config SCSC_LOG_COLLECTION + bool "Samsung SCSC Log Collection" + depends on SCSC_CORE + default y + ---help--- + Enable LOG collection to collect Chunks (host and FW) and generate a SBL file + +config SCSC_COMMON_HCF + bool "Enable Common HCF loader" + depends on SCSC_CORE + default n + ---help--- + Enable Common HCF loader + +config SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE + bool "Permanently disable WLBT autorecovery, ignroring module parameter" + depends on SCSC_CORE + default n + ---help--- + Override module parameter, and disable recovery + +config SCSC_MX450_GDB_SUPPORT + bool "Enable GDB Channels for MX450" + depends on SCSC_CORE + default n + ---help--- + Enable GDB Channels for MX450 diff --git a/drivers/misc/samsung/scsc/Makefile b/drivers/misc/samsung/scsc/Makefile new file mode 100644 index 000000000000..c6c8e90dc624 --- /dev/null +++ b/drivers/misc/samsung/scsc/Makefile @@ -0,0 +1,162 @@ +############################################################################# +# +# Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +# +############################################################################# + +# Needed since this subdir is symlinked in the main Kernel tree +# without this our samsung subdir is NOT cleaned. +clean-files := *.o *.ko + +ifneq ($(CONFIG_SCSC_CORE),n) + +ccflags-y += -Iinclude/scsc +ccflags-y += $(CONFIG_SAMSUNG_MAXWELL_EXTRA) + +obj-$(CONFIG_SCSC_LOGRING) += scsc_logring.o +scsc_logring-$(CONFIG_SCSC_LOGRING) += scsc_logring_main.o scsc_logring_ring.o scsc_logring_debugfs.o + +obj-$(CONFIG_SCSC_LOG_COLLECTION) += scsc_log_collection.o +scsc_log_collection-y += scsc_log_collector.o scsc_log_collector_proc.o scsc_log_collector_mmap.o +## +## The following cumbersome ifeq/else is the only way to make +## this CONFIG_SCSC_PRINTK propagate correctly in any scenario (y,m,n) +## Otherwise using ONLY the else branch when CONFIG_SCSC_DEBUG evaluates +## to m ==>> ccflags-m does NOT work correctly in every build scenario: +## - kernel debug modules : builtin with scsc_logring support +## - kernel nodebug modules : builtins without scsc_logring support +## - kernel debug|nodebug nomodules : no builtin / no scsclogring +## +ifeq ($(CONFIG_SCSC_DEBUG), m) +ccflags-y += -DCONFIG_SCSC_PRINTK +else +ccflags-$(CONFIG_SCSC_LOGRING) += -DCONFIG_SCSC_PRINTK +endif + +ifneq ($(CONFIG_SCSC_DEBUG),n) +ccflags-$(CONFIG_SCSC_STATIC_RING) += -DCONFIG_SCSC_STATIC_RING_SIZE=$(CONFIG_SCSC_STATIC_RING_SIZE) +endif + +obj-$(CONFIG_SCSC_WIFILOGGER) += scsc_wifilogger.o +scsc_wifilogger-y += scsc_wifilogger_api.o \ + scsc_wifilogger_internal.o \ + scsc_wifilogger_core.o \ + scsc_wifilogger_module.o \ + scsc_wifilogger_ring_connectivity.o \ + scsc_wifilogger_ring_wakelock.o \ + scsc_wifilogger_ring_pktfate.o + +scsc_wifilogger-$(CONFIG_SCSC_WIFILOGGER_DEBUGFS) += scsc_wifilogger_debugfs.o +scsc_wifilogger-$(CONFIG_SCSC_WIFILOGGER_TEST) += scsc_wifilogger_ring_test.o + +# GDB channels for MoreDump +ccflags-$(CONFIG_SCSC_MX450_GDB_SUPPORT) += -DCONFIG_SCSC_MX450_GDB_SUPPORT + +# PCI-E MIF +obj-$(CONFIG_SCSC_PCIE) += scsc_pcie_mif.o +scsc_pcie_mif-y += pcie_mif_module.o pcie_mif.o pcie_proc.o pcie_mbox.o pcie_mbox_intgen.o + +obj-$(CONFIG_SCSC_PLATFORM) += scsc_platform_mif.o + +scsc_platform_mif-$(CONFIG_SCSC_PLATFORM) += \ + platform_mif_module.o + +ifeq ($(CONFIG_SOC_EXYNOS9610),y) + scsc_platform_mif-$(CONFIG_SCSC_PLATFORM) += platform_mif_9610.o +endif +ifeq ($(CONFIG_SOC_EXYNOS9630),y) + scsc_platform_mif-$(CONFIG_SCSC_PLATFORM) += platform_mif_9630.o +endif + +ifeq ($(CONFIG_SOC_EXYNOS7570),y) + scsc_platform_mif-$(CONFIG_SCSC_PLATFORM) += platform_mif.o +endif +ifeq ($(CONFIG_SOC_EXYNOS7872),y) + scsc_platform_mif-$(CONFIG_SCSC_PLATFORM) += platform_mif.o +endif +ifeq ($(CONFIG_SOC_EXYNOS7885),y) + scsc_platform_mif-$(CONFIG_SCSC_PLATFORM) += platform_mif.o +endif + +#core_module for static functions and registration client module (all the service driver modules -wlan, bt,...-) +#core for instance +obj-$(CONFIG_SCSC_CORE_CM) += scsc_mx.o +scsc_mx-y += \ + mxlog_transport.o \ + fw_panic_record.o \ + panicmon.o \ + suspendmon.o \ + mxlog.o \ + mxproc.o \ + scsc_service.o \ + scsc_mx_module.o \ + scsc_mx_impl.o \ + mxman.o \ + miframman.o \ + mifmboxman.o \ + mifproc.o \ + mifintrbit.o \ + fwhdr.o \ + fwimage.o \ + cpacket_buffer.o \ + mifstream.o \ + mxmgmt_transport.o \ + gdb_transport.o \ + scsc_lerna.o \ + mxsyserr.o \ + mxfwconfig.o \ + mx140_file.o + +scsc_mx-$(CONFIG_SCSC_MXLOGGER) += mxlogger.o + +scsc_mx-$(CONFIG_SCSC_SMAPPER) += mifsmapper.o + +scsc_mx-$(CONFIG_SCSC_WLBTD) += scsc_wlbtd.o + +scsc_mx-$(CONFIG_SCSC_QOS) += mifqos.o + +ifneq ($(CONFIG_SCSC_CM_MX_CLIENT_TEST),n) +obj-$(CONFIG_SCSC_CM_MX_CLIENT_TEST) += mx_client_test.o +mx_client_test-y += client_test.o +endif + +ifneq ($(CONFIG_SCSC_MMAP),n) +obj-$(CONFIG_SCSC_MMAP) += scsc_mmap.o +scsc_mmap-y += mx_mmap.o +endif + +ifneq ($(CONFIG_SCSC_CLK20MHZ),n) +obj-$(CONFIG_SCSC_CLK20MHZ) += scsc_mx140_clk.o +scsc_mx140_clk-y += mx140_clk.o +ifeq ($(CONFIG_SCSC_CLK20MHZ_TEST),y) +scsc_mx140_clk-y += mx140_clk_test.o +endif +endif + +ifneq ($(CONFIG_SCSC_FM),n) +obj-$(CONFIG_SCSC_FM) += scsc_mx250_fm.o +scsc_mx250_fm-y += mx250_fm.o +ifeq ($(CONFIG_SCSC_FM_TEST),y) +scsc_mx250_fm-y += mx250_fm_test.o +endif +endif + +obj-$(CONFIG_SCSC_DBG_SAMPLER) += scsc_dbg_sampler.o +scsc_dbg_sampler-y += mx_dbg_sampler.o + +endif + +# The below section is only for DTE purpose. It is controlled by the flag SCSC_DTE_BUILD which is set only by the +# DTE top level Makefile. Hence, no risk. +# In the below include dir the KERNEL_DIR comes from the DTE top level makefile. +ifeq ($(SCSC_DTE_BUILD),y) +obj-m := scsc_mx.o +obj-m += mx_client_test.o +mx_client_test-y += client_test.o + +ccflags-y += -I$(KERNEL_DIR)/include/scsc/ +ccflags-y += -I$(KERNEL_DIR)/include +ccflags-y += -I$(KERNEL_DIR)/drivers/misc/samsung/scsc +ccflags-y += -I$(KERNEL_DIR)/drivers/net/wireless/scsc + +endif diff --git a/drivers/misc/samsung/scsc/client_test.c b/drivers/misc/samsung/scsc/client_test.c new file mode 100755 index 000000000000..d106251ed9ab --- /dev/null +++ b/drivers/misc/samsung/scsc/client_test.c @@ -0,0 +1,420 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct scsc_mx_test { + /* scsc_service_client has to be the first */ + struct scsc_service_client test_service_client; + struct scsc_service *primary_service; + struct scsc_service *secondary_service; + struct scsc_mx *mx; + bool started; +}; + +static struct scsc_mx_test *test; + +/* First service to start */ +static int service_id = SCSC_SERVICE_ID_NULL; +module_param(service_id, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(service_id, "ID of service to start, Default 0:NULL, 1:WLAN, 2:BT, 3:ANT, 5:ECHO"); + +/* Second service to start if != -1 */ +static int service_id_2 = -1; +module_param(service_id_2, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(service_id_2, "ID of optional second service to start: Default -1:None, 0:NULL, 1:WLAN, 2:BT, 3:ANT, 5:ECHO"); + +#ifdef CONFIG_SCSC_MX_ALWAYS_ON +static int auto_start = 2; +#else +static int auto_start; +#endif +module_param(auto_start, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(auto_start, "Start service automatically: 0: disabled, 1: Enabled, 2: Deferred"); + +/* Delay after probe before starting mx140 when auto_start=2 */ +#define SCSC_MX_BOOT_DELAY_MS 30000 + +static DEFINE_MUTEX(ss_lock); + +/* char device entry declarations */ +static dev_t client_test_dev_t; +static struct class *client_test_class; +static struct cdev *client_test_cdev; + +static void test_stop_on_failure(struct scsc_service_client *client) +{ + SCSC_TAG_DEBUG(MXMAN_TEST, "OK\n"); +} + +static void test_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + (void)scsc_panic_code; + SCSC_TAG_ERR(MXMAN_TEST, "OK\n"); +} + + +static void stop_close_services(void) +{ + int r; + + mutex_lock(&ss_lock); + + if (!test->started) { + pr_info("mx140: already stopped\n"); + goto done; + } + + if (test->primary_service) { + r = scsc_mx_service_stop(test->primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_stop(primary_service) failed err: %d\n", r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_stop(primary_service) OK\n"); + r = scsc_mx_service_close(test->primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_close(%d) failed err: %d\n", service_id, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_close(%d) OK\n", service_id); + test->primary_service = NULL; + } + + if (test->secondary_service) { + r = scsc_mx_service_stop(test->secondary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_stop(secondary_service) failed err: %d\n", r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_stop(secondary_service) OK\n"); + r = scsc_mx_service_close(test->secondary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_close(%d) failed err: %d\n", service_id_2, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_close(%d) OK\n", service_id_2); + test->secondary_service = NULL; + } + test->started = false; +done: + mutex_unlock(&ss_lock); +} + +static bool open_start_services(struct scsc_mx *mx) +{ + struct scsc_service *primary_service; + struct scsc_service *secondary_service; + int r; + bool ok; + + mutex_lock(&ss_lock); + + if (test->started) { + pr_info("mx140: already started\n"); + ok = true; + goto done; + } + + primary_service = scsc_mx_service_open(mx, service_id, &test->test_service_client, &r); + if (!primary_service) { + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_open for primary_service failed %d\n", r); + ok = false; + goto done; + } + + r = scsc_mx_service_start(primary_service, 0); + if (r) { + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_start for primary_service failed\n"); + r = scsc_mx_service_close(primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_close for primary_service %d failed\n", r); + ok = false; + goto done; + } + + test->primary_service = primary_service; + + if (service_id_2 != -1) { + secondary_service = scsc_mx_service_open(mx, service_id_2, &test->test_service_client, &r); + if (!secondary_service) { + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_open for secondary_service failed %d\n", r); + r = scsc_mx_service_stop(test->primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_stop(%d) failed err: %d\n", service_id, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_stop(%d) OK\n", service_id); + r = scsc_mx_service_close(test->primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_close(%d) failed err: %d\n", service_id, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_close(%d) OK\n", service_id); + ok = false; + goto done; + } + r = scsc_mx_service_start(secondary_service, 0); + if (r) { + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_start for secondary_service failed\n"); + r = scsc_mx_service_close(secondary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_close(%d) failed err: %d\n", service_id, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_close(%d) OK\n", service_id); + + r = scsc_mx_service_stop(test->primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_stop(%d) failed err: %d\n", service_id, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_stop(%d) OK\n", service_id); + r = scsc_mx_service_close(test->primary_service); + if (r) + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_service_close(%d) failed err: %d\n", service_id, r); + else + SCSC_TAG_DEBUG(MXMAN_TEST, "scsc_mx_service_close(%d) OK\n", service_id); + ok = false; + goto done; + } + test->secondary_service = secondary_service; + } + test->started = true; + ok = true; +done: + mutex_unlock(&ss_lock); + return ok; +} + +static void delay_start_func(struct work_struct *work) +{ + (void)work; + + pr_info("mx140: Start wlbt null service\n"); + + if (!test->mx) + return; + + if (!open_start_services(test->mx)) + pr_err("mx140: Error starting delayed service\n"); +} + +DECLARE_DELAYED_WORK(delay_start, delay_start_func); + +/* Start the null service after a delay */ +static void delay_open_start_services(void) +{ + schedule_delayed_work(&delay_start, msecs_to_jiffies(SCSC_MX_BOOT_DELAY_MS)); +} + +/* Start service(s) and leave running until module unload */ +void client_module_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + /* Avoid unused error */ + (void)module_client; + + SCSC_TAG_ERR(MXMAN_TEST, "mx140:\n"); + + test = kzalloc(sizeof(*test), GFP_KERNEL); + if (!test) + return; + + test->test_service_client.stop_on_failure = test_stop_on_failure; + test->test_service_client.failure_reset = test_failure_reset; + test->mx = mx; + + switch (auto_start) { + case 1: + if (!open_start_services(test->mx)) { + SCSC_TAG_ERR(MXMAN_TEST, "Error starting service/s\n"); + kfree(test); + return; + } + break; + case 2: + pr_info("mx140: delayed auto-start\n"); + delay_open_start_services(); + break; + default: + break; + } + + SCSC_TAG_ERR(MXMAN_TEST, "OK\n"); +} + +void client_module_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + /* Avoid unused error */ + (void)module_client; + + pr_info("mx140: %s\n", __func__); + + if (!test) + return; + if (test->mx != mx) { + SCSC_TAG_ERR(MXMAN_TEST, "test->mx != mx\n"); + return; + } + + /* Cancel any delayed start attempt */ + cancel_delayed_work_sync(&delay_start); + + stop_close_services(); + + /* de-allocate test structure */ + kfree(test); + SCSC_TAG_DEBUG(MXMAN_TEST, "OK\n"); +} + + +/* Test client driver registration */ +struct scsc_mx_module_client client_test_driver = { + .name = "MX client test driver", + .probe = client_module_probe, + .remove = client_module_remove, +}; + + +static int client_test_dev_open(struct inode *inode, struct file *file) +{ + SCSC_TAG_ERR(MXMAN_TEST, "open client test\n"); + return 0; +} + +static ssize_t client_test_dev_write(struct file *file, const char *data, size_t len, loff_t *offset) +{ + unsigned long count; + char str[2]; /* One value and carry return */ + long int val = 0; + bool ok = true; + + if (len > 2) { + SCSC_TAG_ERR(MXMAN_TEST, "Incorrect value len %zd\n", len); + goto error; + } + + count = copy_from_user(str, data, len); + + str[1] = 0; + + if (kstrtol(str, 10, &val)) { + SCSC_TAG_ERR(MXMAN_TEST, "Invalid value\n"); + goto error; + } + + if (test) { + if (val) { + SCSC_TAG_INFO(MXMAN_TEST, "Start services\n"); + ok = open_start_services(test->mx); + } else { + SCSC_TAG_INFO(MXMAN_TEST, "Stop services\n"); + stop_close_services(); + } + } else { + SCSC_TAG_ERR(MXMAN_TEST, "Test not created\n"); + goto error; + } +error: + SCSC_TAG_ERR(MXMAN_TEST, "%s\n", ok ? "OK" : "FAIL"); + return ok ? len : -EIO; +} + +static ssize_t client_test_dev_read(struct file *filp, char *buffer, size_t length, loff_t *offset) +{ + return length; +} + +static int client_test_dev_release(struct inode *inode, struct file *file) +{ + SCSC_TAG_DEBUG(MXMAN_TEST, "close client test\n"); + return 0; +} + +static const struct file_operations client_test_dev_fops = { + .owner = THIS_MODULE, + .open = client_test_dev_open, + .read = client_test_dev_read, + .write = client_test_dev_write, + .release = client_test_dev_release, +}; + +static int __init scsc_client_test_module_init(void) +{ + int r; + + SCSC_TAG_DEBUG(MXMAN_TEST, "mx140:\n"); + + r = scsc_mx_module_register_client_module(&client_test_driver); + if (r) { + SCSC_TAG_ERR(MXMAN_TEST, "scsc_mx_module_register_client_module failed: r=%d\n", r); + return r; + } + + r = alloc_chrdev_region(&client_test_dev_t, 0, 1, "wlbt-null-service"); + if (r < 0) { + SCSC_TAG_ERR(MXMAN_TEST, "failed to alloc chrdev region\n"); + goto fail_alloc_chrdev_region; + } + + client_test_cdev = cdev_alloc(); + if (!client_test_cdev) { + r = -ENOMEM; + SCSC_TAG_ERR(MXMAN_TEST, "failed to alloc cdev\n"); + goto fail_alloc_cdev; + } + + cdev_init(client_test_cdev, &client_test_dev_fops); + r = cdev_add(client_test_cdev, client_test_dev_t, 1); + if (r < 0) { + SCSC_TAG_ERR(MXMAN_TEST, "failed to add cdev\n"); + goto fail_add_cdev; + } + + client_test_class = class_create(THIS_MODULE, "sample"); + if (!client_test_class) { + r = -EEXIST; + SCSC_TAG_ERR(MXMAN_TEST, "failed to create class\n"); + goto fail_create_class; + } + + if (!device_create(client_test_class, NULL, client_test_dev_t, NULL, "mx_client_test_%d", MINOR(client_test_dev_t))) { + r = -EINVAL; + SCSC_TAG_ERR(MXMAN_TEST, "failed to create device\n"); + goto fail_create_device; + } + + return 0; +fail_create_device: + class_destroy(client_test_class); +fail_create_class: + cdev_del(client_test_cdev); +fail_add_cdev: +fail_alloc_cdev: + unregister_chrdev_region(client_test_dev_t, 1); +fail_alloc_chrdev_region: + return r; +} + +static void __exit scsc_client_test_module_exit(void) +{ + SCSC_TAG_DEBUG(MXMAN_TEST, "mx140:\n"); + scsc_mx_module_unregister_client_module(&client_test_driver); + SCSC_TAG_DEBUG(MXMAN_TEST, "exit\n"); + + device_destroy(client_test_class, client_test_dev_t); + class_destroy(client_test_class); + cdev_del(client_test_cdev); + unregister_chrdev_region(client_test_dev_t, 1); +} + +late_initcall(scsc_client_test_module_init); +module_exit(scsc_client_test_module_exit); + +MODULE_DESCRIPTION("mx140 Client Test Driver"); +MODULE_AUTHOR("SCSC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/samsung/scsc/cpacket_buffer.c b/drivers/misc/samsung/scsc/cpacket_buffer.c new file mode 100644 index 000000000000..ba555c8b362b --- /dev/null +++ b/drivers/misc/samsung/scsc/cpacket_buffer.c @@ -0,0 +1,373 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Software Mailbox Emulation shared data definitions. +* +* Ref: SC-506707-DD - Structure version 2 +* +****************************************************************************/ +/** + * Circular buffer backed packet stream (Implementation) + * + * To allow easy distinction between full and empty buffers, there + * is one slot reserved in the available space. This means that the following + * conditions can be used to easily test the buffer's status without tracking + * the used size explicitly: + * - read_index == write_index : Buffer is empty + * - write_index + 1 == read_index : Buffer is full + * Otherwise if read_index == write_index then the buffer could be either + * empty or full. + */ +/* Implements */ +#include "cpacket_buffer.h" + +/* Uses */ +#include +#include +#include "miframman.h" + + +/** + * Advances the read/write index by the given amount, wrapping around if this + * exceeds the buffer length. + */ +static inline void cpacketbuffer_advance_index(uint32_t *idx, uint32_t amount, uint32_t buffer_size) +{ + *idx = (*idx + amount) % buffer_size; +} + +/** + * Converts a buffer address to a read/write index. + * + * The address must be at the start of a packet. + */ +static inline uint32_t cpacketbuffer_address_to_index(struct cpacketbuffer *buffer, const uint8_t *address) +{ + ptrdiff_t offset = address - (uint8_t *)buffer->buffer; + + return (offset / buffer->packet_size) % buffer->num_packets; +} + +/** + * Converts a buffer read/write index to an address. + */ +static inline uint8_t *cpacketbuffer_index_to_address(struct cpacketbuffer *buffer, uint32_t *idx) +{ + return (uint8_t *)buffer->buffer + (*idx % buffer->num_packets) * buffer->packet_size; +} + +/** Returns the current read index of the buffer */ +static inline uint32_t cpacketbuffer_read_index(const struct cpacketbuffer *buffer) +{ + return *buffer->read_index; +} + +/** Returns the current write index of the buffer */ +static inline uint32_t cpacketbuffer_write_index(const struct cpacketbuffer *buffer) +{ + return *buffer->write_index; +} + +/** Writes a set of whole packets to the buffer */ +static bool cpacketbuffer_write_block(struct cpacketbuffer *buffer, const void *buf, uint32_t num_bytes) +{ + uint32_t num_packets = (num_bytes + buffer->packet_size - 1) / buffer->packet_size; + const uint8_t *source_data; + uint32_t start_write_index; + uint32_t end_write_index; + + if (num_packets > cpacketbuffer_free_space(buffer)) + /* Not enough free packets to write this block */ + return false; + + source_data = (const uint8_t *)buf; + start_write_index = cpacketbuffer_write_index(buffer); + end_write_index = start_write_index; + cpacketbuffer_advance_index(&end_write_index, num_packets - 1, buffer->num_packets); + + if (end_write_index < start_write_index) { + /* Writes wrap around the buffer, split the write in two */ + uint32_t initial_write_size = (buffer->num_packets - start_write_index) * buffer->packet_size; + + memcpy(cpacketbuffer_index_to_address(buffer, buffer->write_index), source_data, initial_write_size); + memcpy(buffer->buffer, source_data + initial_write_size, num_bytes - initial_write_size); + } else + memcpy(cpacketbuffer_index_to_address(buffer, buffer->write_index), source_data, num_bytes); + + /* CPU memory barrier */ + wmb(); + cpacketbuffer_advance_index(buffer->write_index, num_packets, buffer->num_packets); + + return true; +} + +/** Log buffer configuration at DEBUG level */ +static void mxcbufconf_print(const struct mxcbufconf *buf_conf) +{ + SCSC_TAG_DBG4(CPKTBUFF, "mxcbufconf\n\tbuffer_loc: 0x%x\n\tnum_packets: %d\n\tpacket_size: %d\n\treadix: 0x%x\n\twriteix: 0x%x\n", + buf_conf->buffer_loc, + buf_conf->num_packets, + buf_conf->packet_size, + buf_conf->read_index_loc, + buf_conf->write_index_loc + ); +} + +/** Externally visible functions */ + +int cpacketbuffer_init(struct cpacketbuffer *buffer, uint32_t num_packets, uint32_t packet_size, struct scsc_mx *mx) +{ + struct miframman *miframman; + uint32_t *ridx; + uint32_t *widx; + void *mem; + + buffer->mx = mx; + + miframman = scsc_mx_get_ramman(mx); + mem = miframman_alloc(miframman, num_packets * packet_size, 4, MIFRAMMAN_OWNER_COMMON); + if (!mem) + return -ENOMEM; + + ridx = miframman_alloc(miframman, sizeof(uint32_t), 4, MIFRAMMAN_OWNER_COMMON); + if (!ridx) { + miframman_free(miframman, mem); + return -ENOMEM; + } + + widx = miframman_alloc(miframman, sizeof(uint32_t), 4, MIFRAMMAN_OWNER_COMMON); + if (!widx) { + miframman_free(miframman, ridx); + miframman_free(miframman, mem); + return -ENOMEM; + } + + buffer->buffer = mem; + buffer->num_packets = num_packets; + buffer->packet_size = packet_size; + buffer->read_index = ridx; + buffer->write_index = widx; + + *buffer->read_index = 0; + *buffer->write_index = 0; + return 0; +} + +void cpacketbuffer_release(struct cpacketbuffer *buffer) +{ + struct miframman *miframman; + + miframman = scsc_mx_get_ramman(buffer->mx); + + miframman_free(miframman, buffer->read_index); + miframman_free(miframman, buffer->write_index); + miframman_free(miframman, buffer->buffer); +} + +bool cpacketbuffer_write(struct cpacketbuffer *buffer, const void *buf, uint32_t num_bytes) +{ + uint32_t start_write_index; + + if (buf == NULL || num_bytes == 0) + return false; + + SCSC_TAG_DBG4(CPKTBUFF, "Before: *buffer->read_index=0x%x *buffer->write_index=0x%x\n", + *buffer->read_index, *buffer->write_index); + + start_write_index = cpacketbuffer_write_index(buffer); + if (!cpacketbuffer_write_block(buffer, buf, num_bytes)) + return false; + + /* CPU memory barrier */ + wmb(); + + SCSC_TAG_DBG4(CPKTBUFF, "After: *buffer->read_index=0x%x *buffer->write_index=0x%x\n", + *buffer->read_index, *buffer->write_index); + + return true; +} + +bool cpacketbuffer_write_gather(struct cpacketbuffer *buffer, const void **bufs, uint32_t *num_bytes, uint32_t num_bufs) +{ + uint32_t start_write_index; + uint32_t i; + + if (bufs == NULL || num_bytes == 0 || num_bufs == 0) + return false; + + start_write_index = cpacketbuffer_write_index(buffer); + for (i = 0; i < num_bufs; ++i) { + /* Write all the whole packets from this buffer */ + uint32_t partial_packet_len = num_bytes[i] % buffer->packet_size; + uint32_t whole_packet_len = num_bytes[i] - partial_packet_len; + + if (whole_packet_len > 0 && + !cpacketbuffer_write_block(buffer, bufs[i], whole_packet_len)) + return false; + + if (partial_packet_len != 0) { + /* Partial packet present - write this and enough from the next data block(s) to fill this packet + * before continuing */ + uint32_t needed_bytes; + uint8_t *write_ptr = cpacketbuffer_index_to_address(buffer, buffer->write_index); + + memcpy(write_ptr, (const uint8_t *)bufs[i] + whole_packet_len, partial_packet_len); + write_ptr += partial_packet_len; + needed_bytes = buffer->packet_size - partial_packet_len; + while (i + 1 < num_bufs && needed_bytes > 0) { + uint32_t num_bytes_to_take = num_bytes[i + 1] >= needed_bytes ? needed_bytes : num_bytes[i + 1]; + + memcpy(write_ptr, bufs[i + 1], num_bytes_to_take); + bufs[i + 1] = (const uint8_t *)bufs[i + 1] + num_bytes_to_take; + num_bytes[i + 1] -= num_bytes_to_take; + write_ptr += num_bytes_to_take; + needed_bytes -= num_bytes_to_take; + + if (num_bytes[i + 1] == 0) + /* This buffer has been consumed entirely, move to the next */ + ++i; + } + + /* CPU memory barrier */ + wmb(); + cpacketbuffer_advance_index(buffer->write_index, 1, buffer->num_packets); + } + } + + /* CPU memory barrier */ + wmb(); + + return true; +} + +uint32_t cpacketbuffer_read(struct cpacketbuffer *buffer, void *buf, uint32_t num_bytes) +{ + uint8_t *read_start; + uint32_t num_packets; + uint32_t num_available_packets; + + if (buf == NULL || cpacketbuffer_is_empty(buffer)) + return 0; + + /* Work out where we're reading from */ + read_start = cpacketbuffer_index_to_address(buffer, buffer->read_index); + num_packets = num_bytes / buffer->packet_size; + if (num_bytes % buffer->packet_size != 0) + /* Partial data packet read requested, this means we remove the whole thing */ + ++num_packets; + + /* Ensure we have enough actual data to satisfy the read request, otherwise + * truncate the read request to the amount of data available. */ + num_available_packets = buffer->num_packets - (cpacketbuffer_free_space(buffer) + 1); + if (num_packets > num_available_packets) { + num_packets = num_available_packets; + num_bytes = num_packets * buffer->packet_size; + } + + if (cpacketbuffer_read_index(buffer) + num_packets > buffer->num_packets) { + /* The read wraps around the end of the buffer, do it in two parts */ + uint32_t initial_read_size = (buffer->num_packets - cpacketbuffer_read_index(buffer)) * buffer->packet_size; + + memcpy(buf, read_start, initial_read_size); + memcpy((uint8_t *)buf + initial_read_size, buffer->buffer, num_bytes - initial_read_size); + } else + memcpy(buf, read_start, num_bytes); + + /* CPU memory barrier */ + wmb(); + /* Update the read index with how many packets we pulled out of the stream */ + cpacketbuffer_advance_index(buffer->read_index, num_packets, buffer->num_packets); + /* CPU memory barrier */ + wmb(); + + return num_bytes; +} + +const void *cpacketbuffer_peek(struct cpacketbuffer *buffer, const void *current_packet) +{ + uint32_t next_packet_index; + + SCSC_TAG_DBG4(CPKTBUFF, "*buffer->read_index=0x%x *buffer->write_index=0x%x\n", + *buffer->read_index, *buffer->write_index); + if (current_packet == NULL) + /* Reading the first available packet */ + next_packet_index = cpacketbuffer_read_index(buffer); + else + /* Reading the next available packet past the current value of current_packet */ + next_packet_index = cpacketbuffer_address_to_index(buffer, + (const uint8_t *)current_packet + buffer->packet_size); + + if (next_packet_index == cpacketbuffer_write_index(buffer)) + /* No more packets available */ + return NULL; + + return cpacketbuffer_index_to_address(buffer, &next_packet_index); +} + +void cpacketbuffer_peek_complete(struct cpacketbuffer *buffer, const void *current_packet) +{ + if (current_packet == NULL) + return; + + /* The address we're given is the last packet read, so the new read index is for the next one */ + *buffer->read_index = cpacketbuffer_address_to_index(buffer, + (const uint8_t *)current_packet + buffer->packet_size); + /* CPU memory barrier */ + wmb(); +} + +bool cpacketbuffer_is_empty(const struct cpacketbuffer *buffer) +{ + return cpacketbuffer_read_index(buffer) == cpacketbuffer_write_index(buffer); +} + +bool cpacketbuffer_is_full(const struct cpacketbuffer *buffer) +{ + return (cpacketbuffer_write_index(buffer) + 1) % buffer->num_packets == cpacketbuffer_read_index(buffer); +} + +uint32_t cpacketbuffer_free_space(const struct cpacketbuffer *buffer) +{ + uint32_t base_free_space = cpacketbuffer_write_index(buffer) >= cpacketbuffer_read_index(buffer) ? + cpacketbuffer_read_index(buffer) + buffer->num_packets - cpacketbuffer_write_index(buffer) : + cpacketbuffer_read_index(buffer) - cpacketbuffer_write_index(buffer); + + /* Subtract the full/empty identification reserved slot from the free space */ + return base_free_space - 1; +} + +uint32_t cpacketbuffer_packet_size(const struct cpacketbuffer *buffer) +{ + return buffer->packet_size; +} + +void cpacketbuffer_config_serialise(const struct cpacketbuffer *buffer, struct mxcbufconf *buf_conf) +{ + scsc_mifram_ref mifram_ref; + struct scsc_mif_abs *mif; + + mif = scsc_mx_get_mif_abs(buffer->mx); + + mif->get_mifram_ref(mif, buffer->buffer, &mifram_ref); + buf_conf->buffer_loc = mifram_ref; + buf_conf->num_packets = buffer->num_packets; + buf_conf->packet_size = buffer->packet_size; + mif->get_mifram_ref(mif, buffer->read_index, &mifram_ref); + buf_conf->read_index_loc = mifram_ref; + mif->get_mifram_ref(mif, buffer->write_index, &mifram_ref); + buf_conf->write_index_loc = mifram_ref; + + mxcbufconf_print(buf_conf); +} + +void cpacketbuffer_log(const struct cpacketbuffer *buffer, enum scsc_log_level log_level) +{ + const uint8_t *read_start = cpacketbuffer_index_to_address((struct cpacketbuffer *)buffer, buffer->read_index); + + SCSC_TAG_LVL((CPKTBUFF), log_level, + "read_index=0x%x write_index=0x%x, read_start[0]=0x%08x\n", + *buffer->read_index, *buffer->write_index, + *(uint32_t *)read_start); +} + diff --git a/drivers/misc/samsung/scsc/cpacket_buffer.h b/drivers/misc/samsung/scsc/cpacket_buffer.h new file mode 100644 index 000000000000..884cf0b8ad32 --- /dev/null +++ b/drivers/misc/samsung/scsc/cpacket_buffer.h @@ -0,0 +1,147 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * Circular buffer backed packet stream (Interface) + */ + +#ifndef CPACKET_BUFFER_H__ +#define CPACKET_BUFFER_H__ + +/* Uses */ +#include +#include "scsc_mx_impl.h" +#include "mxconf.h" +#include "scsc_logring_common.h" + +struct cpacketbuffer; + +/** + * Initialises the circular buffer. + * The memory buffer length must be a multiple of the packet size. + */ +int cpacketbuffer_init(struct cpacketbuffer *buffer, uint32_t num_packets, uint32_t packet_size, struct scsc_mx *mx); +void cpacketbuffer_release(struct cpacketbuffer *buffer); + +/** + * Reads the gven amount of data from the buffer, copying it to the provided address. + * This automatically removes the read data from the buffer. + * + * If the amount of data requested is not a multiple of the packet size + * only the requested number of bytes will be read, but the partially read packet + * will still be removed from the buffer. + * + * Returns true if the packet was read. + */ +uint32_t cpacketbuffer_read(struct cpacketbuffer *buffer, void *buf, uint32_t num_bytes); + +/** + * Returns a pointer to the next packet of data within the buffer, without + * removing it. This can be used to processss data in place without needing to + * copy it first. + * + * If multiple packets are present these can be read in turn by setting the value + * of current_packet to the returned value from the previous call to cpacketbuffer_peek. + * + * cpacketbuffer_peek_complete must be called to remove the packet(s) from the buffer. + * + * Returns a pointer to the beginning of the packet to read, or NULL if there is no + * packet to process. + * + * Example use: + * // Get the first data packet + * void *current_packet = cpacketbuffer_peek( buffer, NULL ); + * void *last_packet = NULL; + * while( current_packet != NULL ) + * { + * // Process data packet + * ... + * + * // Get the next data packet + * last_packet = current_packet; + * current_packet = cpacketbuffer_peek( buffer, current_packet ); + * } + * + * // Remove all processed packets from the buffer + * if( last_packet != NULL ) + * { + * cpacketbuffer_peek_complete( buffer, last_packet ); + * } + */ +const void *cpacketbuffer_peek(struct cpacketbuffer *buffer, const void *current_packet); + +/** + * Removes all packets from the buffer up to and including the given + * packet. + * + * This must be called after using cpacketbuffer_peek to indicate that packet(s) + * can be removed from the buffer. + */ +void cpacketbuffer_peek_complete(struct cpacketbuffer *buffer, const void *packet); + +/** + * Writes a number of bytes to the buffer. This will always use up whole packets in the buffer + * even if the amount of data written is not an exact multiple of the packet size. + * + * Returns true if the data was written, false if there is not enough free space in the buffer. + */ +bool cpacketbuffer_write(struct cpacketbuffer *buffer, const void *buf, uint32_t num_bytes); + +/** + * Writes a set of non-contiguous data blocks to the buffer as a contiguous set. + * This will always use up whole packets even if the + * amount of data written is not an exact multiple of the packet size. + * + * Returns true if the blocks were written, false if there is not enough + * free space in the buffer for all the blocks. + */ +bool cpacketbuffer_write_gather(struct cpacketbuffer *buffer, const void **bufs, uint32_t *num_bytes, uint32_t num_bufs); + +/** + * Returns the number of free packets in the buffer. + */ +uint32_t cpacketbuffer_free_space(const struct cpacketbuffer *buffer); + +/** + * Returns true if the buffer is empty. + */ +bool cpacketbuffer_is_empty(const struct cpacketbuffer *buffer); + +/** + * Returns true if the buffer is full. + */ +bool cpacketbuffer_is_full(const struct cpacketbuffer *buffer); + +/** + * Returns the packet size the buffer was initialised with. This is the same value + * as the packet_size argument passed to cpacketbuffer_init(). + */ +uint32_t cpacketbuffer_packet_size(const struct cpacketbuffer *buffer); + +void cpacketbuffer_config_serialise(const struct cpacketbuffer *buffer, struct mxcbufconf *buf_conf); + +/** + * Log the state of this packet buffer at the specified log_level. + */ +void cpacketbuffer_log(const struct cpacketbuffer *buffer, enum scsc_log_level log_level); + +/** + * Buffer context object. + */ +struct cpacketbuffer { + struct scsc_mx *mx; + void *buffer; /* Buffer location */ + uint32_t num_packets; /* Total number of packets that can be stored in the buffer */ + uint32_t packet_size; /* Size of each individual packet within the buffer */ + + /** Pointers to 32bit R/W indexes - these should point to uint32_ts */ + uint32_t *read_index; /* Pointer to the location of the read index, which + * contains the index of the next packet to read. */ + uint32_t *write_index; /* Pointer to the location of the write index, which + * contains the index after the last packet written. */ +}; + +#endif /* CPACKET_BUFFER_H__ */ diff --git a/drivers/misc/samsung/scsc/functor.h b/drivers/misc/samsung/scsc/functor.h new file mode 100644 index 000000000000..e6dabde3f550 --- /dev/null +++ b/drivers/misc/samsung/scsc/functor.h @@ -0,0 +1,42 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +****************************************************************************/ + +#ifndef __FUNCTOR_H +#define __FUNCTOR_H + +/** + * Minimal Functor (no returns, no args other than self). + */ +struct functor { + /** + * The callback invoked by functor_call(). + * + * A pointer to the functor itself is passed to the call. + * + * Typically the implementation wil upcast this (container_of) + * to access a container context. + */ + void (*call)(struct functor *f); +}; + +/** + * Initialise this functor. + */ +static inline void functor_init(struct functor *f, void (*call)(struct functor *f)) +{ + f->call = call; +} + +/** + * Invoke this functor. + */ +static inline void functor_call(struct functor *f) +{ + f->call(f); +} + +#endif /* __FUNCTOR_H */ + diff --git a/drivers/misc/samsung/scsc/fw_panic_record.c b/drivers/misc/samsung/scsc/fw_panic_record.c new file mode 100644 index 000000000000..4d31ddcb792d --- /dev/null +++ b/drivers/misc/samsung/scsc/fw_panic_record.c @@ -0,0 +1,178 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include +#include +#include "panic_record_r4_defs.h" + +#define PANIC_RECORD_CKSUM_SEED 0xa5a5a5a5 +/* + * version 2 r4 panic record defs + */ +#define R4_PANIC_RECORD_VERSION_2 2 +#define R4_PANIC_RECORD_LENGTH_INDEX_V2 1 +#define R4_PANIC_RECORD_MAX_LENGTH_V2 256 + +/* + * version 1 mr4 panic record defs + */ +#define M4_PANIC_RECORD_VERSION_1 1 +#define M4_PANIC_RECORD_VERSION_INDEX 0 +#define M4_PANIC_RECORD_LENGTH_INDEX 1 +#define M4_PANIC_RECORD_MAX_LENGTH 256 + +#define R4_PANIC_RECORD_V2_SYMPATHETIC_PANIC_FLAG_INDEX 51 +#define M4_PANIC_RECORD_SYMPATHETIC_PANIC_FLAG_INDEX 39 + +/** + * Compute 32bit xor of specified seed value and data. + * + * @param seed Initial seed value. + * @param data Array of uint32s to be xored + * @param len Number of uint32s to be xored + * + * @return Computed 32bit xor of specified seed value and data. + */ +static u32 xor32(uint32_t seed, const u32 data[], size_t len) +{ + const u32 *i; + u32 xor = seed; + + for (i = data; i != data + len; ++i) + xor ^= *i; + return xor; +} + +static void panic_record_dump(u32 *panic_record, u32 panic_record_length, bool r4) +{ + int i; + + SCSC_TAG_INFO(FW_PANIC, "%s panic record dump(length=%d):\n", + r4 ? "R4" : "M4", panic_record_length); + for (i = 0; i < panic_record_length; i++) + SCSC_TAG_INFO(FW_PANIC, "%s_panic_record[%d] = %08x\n", + r4 ? "r4" : "m4", i, panic_record[i]); +} + +static bool fw_parse_r4_panic_record_v2(u32 *r4_panic_record, u32 *r4_panic_record_length) +{ + u32 panic_record_cksum; + u32 calculated_cksum; + u32 panic_record_length = *(r4_panic_record + R4_PANIC_RECORD_LENGTH_INDEX_V2) / 4; + + SCSC_TAG_INFO(FW_PANIC, "panic_record_length: %d\n", panic_record_length); + + if (panic_record_length < R4_PANIC_RECORD_MAX_LENGTH_V2) { + panic_record_cksum = *(r4_panic_record + panic_record_length - 1); + calculated_cksum = xor32(PANIC_RECORD_CKSUM_SEED, r4_panic_record, panic_record_length - 1); + if (calculated_cksum == panic_record_cksum) { + SCSC_TAG_INFO(FW_PANIC, "panic_record_cksum OK: %08x\n", + calculated_cksum); + panic_record_dump(r4_panic_record, panic_record_length, true); + *r4_panic_record_length = panic_record_length; + return true; + } else { + SCSC_TAG_ERR(FW_PANIC, "BAD panic_record_cksum: 0x%x calculated_cksum: 0x%x\n", + panic_record_cksum, calculated_cksum); + } + } else { + SCSC_TAG_ERR(FW_PANIC, "BAD panic_record_length: %d\n", + panic_record_length); + } + return false; +} + +static bool fw_parse_m4_panic_record_v1(u32 *m4_panic_record, u32 *m4_panic_record_length) +{ + u32 panic_record_cksum; + u32 calculated_cksum; + u32 panic_record_length = *(m4_panic_record + M4_PANIC_RECORD_LENGTH_INDEX) / 4; + + SCSC_TAG_INFO(FW_PANIC, "panic_record_length: %d\n", panic_record_length); + + if (panic_record_length < M4_PANIC_RECORD_MAX_LENGTH) { + panic_record_cksum = *(m4_panic_record + panic_record_length - 1); + calculated_cksum = xor32(PANIC_RECORD_CKSUM_SEED, m4_panic_record, panic_record_length - 1); + if (calculated_cksum == panic_record_cksum) { + SCSC_TAG_INFO(FW_PANIC, "panic_record_cksum OK: %08x\n", + calculated_cksum); + panic_record_dump(m4_panic_record, panic_record_length, false); + *m4_panic_record_length = panic_record_length; + return true; + } else { + SCSC_TAG_ERR(FW_PANIC, "BAD panic_record_cksum: 0x%x calculated_cksum: 0x%x\n", + panic_record_cksum, calculated_cksum); + } + } else { + SCSC_TAG_ERR(FW_PANIC, "BAD panic_record_length: %d\n", + panic_record_length); + } + return false; +} + +bool fw_parse_r4_panic_record(u32 *r4_panic_record, u32 *r4_panic_record_length) +{ + u32 panic_record_version = *(r4_panic_record + PANIC_RECORD_R4_VERSION_INDEX); + + SCSC_TAG_INFO(FW_PANIC, "panic_record_version: %d\n", panic_record_version); + + switch (panic_record_version) { + default: + SCSC_TAG_ERR(FW_PANIC, "BAD panic_record_version: %d\n", + panic_record_version); + break; + case R4_PANIC_RECORD_VERSION_2: + return fw_parse_r4_panic_record_v2(r4_panic_record, r4_panic_record_length); + } + return false; +} + +bool fw_parse_m4_panic_record(u32 *m4_panic_record, u32 *m4_panic_record_length) +{ + u32 panic_record_version = *(m4_panic_record + M4_PANIC_RECORD_VERSION_INDEX); + + SCSC_TAG_INFO(FW_PANIC, "panic_record_version: %d\n", panic_record_version); + switch (panic_record_version) { + default: + SCSC_TAG_ERR(FW_PANIC, "BAD panic_record_version: %d\n", + panic_record_version); + break; + case M4_PANIC_RECORD_VERSION_1: + return fw_parse_m4_panic_record_v1(m4_panic_record, m4_panic_record_length); + } + return false; +} + +bool fw_parse_get_r4_sympathetic_panic_flag(u32 *r4_panic_record) +{ + bool sympathetic_panic_flag = *(r4_panic_record + R4_PANIC_RECORD_V2_SYMPATHETIC_PANIC_FLAG_INDEX); + + return sympathetic_panic_flag; +} + +bool fw_parse_get_m4_sympathetic_panic_flag(u32 *m4_panic_record) +{ + bool sympathetic_panic_flag = *(m4_panic_record + M4_PANIC_RECORD_SYMPATHETIC_PANIC_FLAG_INDEX); + + return sympathetic_panic_flag; +} + +int panic_record_dump_buffer(char *processor, u32 *panic_record, + u32 panic_record_length, char *buffer, size_t blen) +{ + int i, used; + + if (!processor) + processor = "WLBT"; + + used = snprintf(buffer, blen, "%s panic record dump(length=%d):\n", + processor, panic_record_length); + for (i = 0; i < panic_record_length && used < blen; i++) + used += snprintf(buffer + used, blen - used, "%s_panic_record[%d] = %08x\n", + processor, i, panic_record[i]); + + return used; +} diff --git a/drivers/misc/samsung/scsc/fw_panic_record.h b/drivers/misc/samsung/scsc/fw_panic_record.h new file mode 100644 index 000000000000..209ae35c237a --- /dev/null +++ b/drivers/misc/samsung/scsc/fw_panic_record.h @@ -0,0 +1,18 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef FW_PANIC_RECORD_H__ +#define FW_PANIC_RECORD_H__ + +bool fw_parse_r4_panic_record(u32 *r4_panic_record, u32 *r4_panic_record_length); +bool fw_parse_m4_panic_record(u32 *m4_panic_record, u32 *m4_panic_record_length); + +bool fw_parse_get_r4_sympathetic_panic_flag(u32 *r4_panic_record); +bool fw_parse_get_m4_sympathetic_panic_flag(u32 *m4_panic_record); + +int panic_record_dump_buffer(char *processor, u32 *panic_record, + u32 panic_record_length, char *buffer, size_t blen); +#endif /* FW_PANIC_RECORD_H__ */ diff --git a/drivers/misc/samsung/scsc/fwhdr.c b/drivers/misc/samsung/scsc/fwhdr.c new file mode 100644 index 000000000000..b4aaabf97389 --- /dev/null +++ b/drivers/misc/samsung/scsc/fwhdr.c @@ -0,0 +1,110 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include "fwhdr.h" + +/* + * The Maxwell Firmware Header Format is defined in SC-505846-SW + */ + +#define FWHDR_02_TRAMPOLINE_OFFSET 0 +#define FWHDR_02_MAGIC_OFFSET 8 +#define FWHDR_02_VERSION_MINOR_OFFSET 12 +#define FWHDR_02_VERSION_MAJOR_OFFSET 14 +#define FWHDR_02_LENGTH_OFFSET 16 +#define FWHDR_02_FIRMWARE_API_VERSION_MINOR_OFFSET 20 +#define FWHDR_02_FIRMWARE_API_VERSION_MAJOR_OFFSET 22 +#define FWHDR_02_FIRMWARE_CRC_OFFSET 24 +#define FWHDR_02_CONST_FW_LENGTH_OFFSET 28 +#define FWHDR_02_CONST_CRC_OFFSET 32 +#define FWHDR_02_FIRMWARE_RUNTIME_LENGTH_OFFSET 36 +#define FWHDR_02_FIRMWARE_ENTRY_POINT_OFFSET 40 +#define FWHDR_02_BUILD_ID_OFFSET 48 +#define FWHDR_02_R4_PANIC_RECORD_OFFSET_OFFSET 176 +#define FWHDR_02_M4_PANIC_RECORD_OFFSET_OFFSET 180 +#define FWHDR_02_TTID_OFFSET 184 + +/* + * Firmware header format for version 1.0 is same as version for 0.2 + */ +#define FWHDR_02_TRAMPOLINE(__fw) (*((u32 *)(__fw + FWHDR_02_TRAMPOLINE_OFFSET))) +#define FWHDR_02_HEADER_FIRMWARE_ENTRY_POINT(__fw) (*((u32 *)(__fw + FWHDR_02_FIRMWARE_ENTRY_POINT_OFFSET))) +#define FWHDR_02_HEADER_FIRMWARE_RUNTIME_LENGTH(__fw) (*((u32 *)(__fw + FWHDR_02_FIRMWARE_RUNTIME_LENGTH_OFFSET))) +#define FWHDR_02_HEADER_VERSION_MAJOR(__fw) (*((u16 *)(__fw + FWHDR_02_VERSION_MAJOR_OFFSET))) +#define FWHDR_02_HEADER_VERSION_MINOR(__fw) (*((u16 *)(__fw + FWHDR_02_VERSION_MINOR_OFFSET))) +#define FWHDR_02_HEADER_FIRMWARE_API_VERSION_MINOR(__fw) (*((u16 *)(__fw + FWHDR_02_FIRMWARE_API_VERSION_MINOR_OFFSET))) +#define FWHDR_02_HEADER_FIRMWARE_API_VERSION_MAJOR(__fw) (*((u16 *)(__fw + FWHDR_02_FIRMWARE_API_VERSION_MAJOR_OFFSET))) +#define FWHDR_02_FW_CRC32(__fw) (*((u32 *)(__fw + FWHDR_02_FIRMWARE_CRC_OFFSET))) +#define FWHDR_02_HDR_LENGTH(__fw) (*((u32 *)(__fw + FWHDR_02_LENGTH_OFFSET))) +#define FWHDR_02_HEADER_CRC32(__fw) (*((u32 *)(__fw + (FWHDR_02_HDR_LENGTH(__fw)) - sizeof(u32)))) +#define FWHDR_02_CONST_CRC32(__fw) (*((u32 *)(__fw + FWHDR_02_CONST_CRC_OFFSET))) +#define FWHDR_02_CONST_FW_LENGTH(__fw) (*((u32 *)(__fw + FWHDR_02_CONST_FW_LENGTH_OFFSET))) +#define FWHDR_02_R4_PANIC_RECORD_OFFSET(__fw) (*((u32 *)(__fw + FWHDR_02_R4_PANIC_RECORD_OFFSET_OFFSET))) +#define FWHDR_02_M4_PANIC_RECORD_OFFSET(__fw) (*((u32 *)(__fw + FWHDR_02_M4_PANIC_RECORD_OFFSET_OFFSET))) + +/* firmware header has a panic record if the firmware header length is at least 192 bytes long */ +#define MIN_HEADER_LENGTH_WITH_PANIC_RECORD 188 + +#define FWHDR_MAGIC_STRING "smxf" + +static bool fwhdr_parse_v02(char *fw, struct fwhdr *fwhdr) +{ + if (!memcmp(fw + FWHDR_02_MAGIC_OFFSET, FWHDR_MAGIC_STRING, sizeof(FWHDR_MAGIC_STRING) - 1)) { + fwhdr->firmware_entry_point = FWHDR_02_HEADER_FIRMWARE_ENTRY_POINT(fw); + fwhdr->hdr_major = FWHDR_02_HEADER_VERSION_MAJOR(fw); + fwhdr->hdr_minor = FWHDR_02_HEADER_VERSION_MINOR(fw); + fwhdr->fwapi_major = FWHDR_02_HEADER_FIRMWARE_API_VERSION_MAJOR(fw); + fwhdr->fwapi_minor = FWHDR_02_HEADER_FIRMWARE_API_VERSION_MINOR(fw); + fwhdr->fw_crc32 = FWHDR_02_FW_CRC32(fw); + fwhdr->const_crc32 = FWHDR_02_CONST_CRC32(fw); + fwhdr->header_crc32 = FWHDR_02_HEADER_CRC32(fw); + fwhdr->const_fw_length = FWHDR_02_CONST_FW_LENGTH(fw); + fwhdr->hdr_length = FWHDR_02_HDR_LENGTH(fw); + fwhdr->fw_runtime_length = FWHDR_02_HEADER_FIRMWARE_RUNTIME_LENGTH(fw); + SCSC_TAG_INFO(FW_LOAD, "hdr_length=%d\n", fwhdr->hdr_length); + fwhdr->r4_panic_record_offset = FWHDR_02_R4_PANIC_RECORD_OFFSET(fw); + fwhdr->m4_panic_record_offset = FWHDR_02_M4_PANIC_RECORD_OFFSET(fw); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + fwhdr->m4_1_panic_record_offset = FWHDR_02_M4_PANIC_RECORD_OFFSET(fw); +#endif + return true; + } + return false; +} + +static char *fwhdr_get_build_id_v02(char *fw, struct fwhdr *fwhdr) +{ + if (!memcmp(fw + FWHDR_02_MAGIC_OFFSET, FWHDR_MAGIC_STRING, sizeof(FWHDR_MAGIC_STRING) - 1)) + return fw + FWHDR_02_BUILD_ID_OFFSET; + return NULL; +} + +static char *fwhdr_get_ttid_v02(char *fw, struct fwhdr *fwhdr) +{ + if (fwhdr->hdr_length < FWHDR_02_TTID_OFFSET) + return NULL; + if (!memcmp(fw + FWHDR_02_MAGIC_OFFSET, FWHDR_MAGIC_STRING, sizeof(FWHDR_MAGIC_STRING) - 1)) + return fw + FWHDR_02_TTID_OFFSET; + return NULL; +} + +bool fwhdr_parse(char *fw, struct fwhdr *fwhdr) +{ + return fwhdr_parse_v02(fw, fwhdr); +} + +char *fwhdr_get_build_id(char *fw, struct fwhdr *fwhdr) +{ + return fwhdr_get_build_id_v02(fw, fwhdr); +} + +char *fwhdr_get_ttid(char *fw, struct fwhdr *fwhdr) +{ + return fwhdr_get_ttid_v02(fw, fwhdr); +} diff --git a/drivers/misc/samsung/scsc/fwhdr.h b/drivers/misc/samsung/scsc/fwhdr.h new file mode 100644 index 000000000000..d85118859156 --- /dev/null +++ b/drivers/misc/samsung/scsc/fwhdr.h @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef FWHDR_H +#define FWHDR_H + +#define FW_BUILD_ID_SZ 128 +#define FW_TTID_SZ 32 + +struct fwhdr { + u16 hdr_major; + u16 hdr_minor; + + u16 fwapi_major; + u16 fwapi_minor; + + u32 firmware_entry_point; + u32 fw_runtime_length; + + u32 fw_crc32; + u32 const_crc32; + u32 header_crc32; + + u32 const_fw_length; + u32 hdr_length; + u32 r4_panic_record_offset; + u32 m4_panic_record_offset; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + u32 m4_1_panic_record_offset; +#endif +}; + +bool fwhdr_parse(char *fw, struct fwhdr *fwhdr); +char *fwhdr_get_build_id(char *fw, struct fwhdr *fwhdr); +char *fwhdr_get_ttid(char *fw, struct fwhdr *fwhdr); + +#endif /* FWHDR_H */ diff --git a/drivers/misc/samsung/scsc/fwimage.c b/drivers/misc/samsung/scsc/fwimage.c new file mode 100644 index 000000000000..6e7bd3375fc2 --- /dev/null +++ b/drivers/misc/samsung/scsc/fwimage.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include "fwimage.h" + +int fwimage_check_fw_header_crc(char *fw, u32 hdr_length, u32 header_crc32) +{ + u32 header_crc32_calculated; + + /* + * The last 4-bytes are header CRC + */ + header_crc32_calculated = ether_crc(hdr_length - sizeof(u32), fw); + if (header_crc32_calculated != header_crc32) { + SCSC_TAG_ERR(FW_LOAD, "CRC32 doesn't match: header_crc32_calculated=%d header_crc32=%d\n", + header_crc32_calculated, header_crc32); + return -EINVAL; + } + SCSC_TAG_DEBUG(FW_LOAD, "CRC32 OK: header_crc32_calculated=%d header_crc32=%d\n", + header_crc32_calculated, header_crc32); + return 0; +} + +int fwimage_check_fw_const_section_crc(char *fw, u32 const_crc32, u32 const_fw_length, u32 hdr_length) +{ + u32 const_crc32_calculated; + + const_crc32_calculated = ether_crc(const_fw_length - hdr_length, fw + hdr_length); + if (const_crc32_calculated != const_crc32) { + SCSC_TAG_ERR(FW_LOAD, "CRC32 doesn't match: const_crc32_calculated=%d const_crc32=%d\n", + const_crc32_calculated, const_crc32); + return -EINVAL; + } + SCSC_TAG_DEBUG(FW_LOAD, "CRC32 OK: const_crc32_calculated=%d const_crc32=%d\n", + const_crc32_calculated, const_crc32); + return 0; +} + +int fwimage_check_fw_crc(char *fw, u32 fw_image_length, u32 hdr_length, u32 fw_crc32) +{ + u32 fw_crc32_calculated; + + fw_crc32_calculated = ether_crc(fw_image_length - hdr_length, fw + hdr_length); + if (fw_crc32_calculated != fw_crc32) { + SCSC_TAG_ERR(FW_LOAD, "CRC32 doesn't match: fw_crc32_calculated=%d fw_crc32=%d\n", + fw_crc32_calculated, fw_crc32); + return -EINVAL; + } + SCSC_TAG_DEBUG(FW_LOAD, "CRC32 OK: fw_crc32_calculated=%d fw_crc32=%d\n", + fw_crc32_calculated, fw_crc32); + return 0; +} diff --git a/drivers/misc/samsung/scsc/fwimage.h b/drivers/misc/samsung/scsc/fwimage.h new file mode 100644 index 000000000000..88e0d838b706 --- /dev/null +++ b/drivers/misc/samsung/scsc/fwimage.h @@ -0,0 +1,14 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef FWIMAGE_H +#define FWIMAGE_H + +int fwimage_check_fw_header_crc(char *fw, u32 hdr_length, u32 header_crc32); +int fwimage_check_fw_const_section_crc(char *fw, u32 const_crc32, u32 const_fw_length, u32 hdr_length); +int fwimage_check_fw_crc(char *fw, u32 fw_runtime_length, u32 hdr_length, u32 fw_crc32); + +#endif /* FWIMAGE_H */ diff --git a/drivers/misc/samsung/scsc/gdb_transport.c b/drivers/misc/samsung/scsc/gdb_transport.c new file mode 100644 index 000000000000..84e37a6396db --- /dev/null +++ b/drivers/misc/samsung/scsc/gdb_transport.c @@ -0,0 +1,263 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** Implements */ +#include "gdb_transport.h" + +/** Uses */ +#include +#include +#include +#include "mifintrbit.h" + +struct clients_node { + struct list_head list; + struct gdb_transport_client *gdb_client; +}; + +struct gdb_transport_node { + struct list_head list; + struct gdb_transport *gdb_transport; +}; + +static struct gdb_transport_module { + struct list_head clients_list; + struct list_head gdb_transport_list; +} gdb_transport_module = { + .clients_list = LIST_HEAD_INIT(gdb_transport_module.clients_list), + .gdb_transport_list = LIST_HEAD_INIT(gdb_transport_module.gdb_transport_list) +}; + +/** Handle incoming packets and pass to handler */ +static void gdb_input_irq_handler(int irq, void *data) +{ + struct gdb_transport *gdb_transport = (struct gdb_transport *)data; + struct scsc_mif_abs *mif_abs; + u32 num_bytes; + u32 alloc_bytes; + char *buf; + + SCSC_TAG_DEBUG(GDB_TRANS, "Handling write signal.\n"); + + /* 1st length */ + /* Clear the interrupt first to ensure we can't possibly miss one */ + mif_abs = scsc_mx_get_mif_abs(gdb_transport->mx); + mif_abs->irq_bit_clear(mif_abs, irq); + + while (mif_stream_read(&gdb_transport->mif_istream, &num_bytes, sizeof(uint32_t))) { + SCSC_TAG_DEBUG(GDB_TRANS, "Transferring %d byte payload to handler.\n", num_bytes); + if (num_bytes > 0 && num_bytes + < (GDB_TRANSPORT_BUF_LENGTH - sizeof(uint32_t))) { + alloc_bytes = sizeof(char) * num_bytes; + /* This is called in atomic context so must use kmalloc with GFP_ATOMIC flag */ + buf = kmalloc(alloc_bytes, GFP_ATOMIC); + /* 2nd payload (msg) */ + mif_stream_read(&gdb_transport->mif_istream, buf, num_bytes); + gdb_transport->channel_handler_fn(buf, num_bytes, gdb_transport->channel_handler_data); + kfree(buf); + } else { + SCSC_TAG_ERR(GDB_TRANS, "Incorrect num_bytes: 0x%08x\n", num_bytes); + mif_stream_log(&gdb_transport->mif_istream, SCSC_ERR); + } + } +} + + +/** MIF Interrupt handler for acknowledging reads made by the AP */ +static void gdb_output_irq_handler(int irq, void *data) +{ + struct scsc_mif_abs *mif_abs; + struct gdb_transport *gdb_transport = (struct gdb_transport *)data; + + SCSC_TAG_DEBUG(GDB_TRANS, "Ignoring read signal.\n"); + + /* Clear the interrupt first to ensure we can't possibly miss one */ + /* The FW read some data from the output stream. + * Currently we do not care, so just clear the interrupt. */ + mif_abs = scsc_mx_get_mif_abs(gdb_transport->mx); + mif_abs->irq_bit_clear(mif_abs, irq); +} + +static void gdb_transport_probe_registered_clients(struct gdb_transport *gdb_transport) +{ + bool client_registered = false; + struct clients_node *gdb_client_node, *gdb_client_next; + struct scsc_mif_abs *mif_abs; + char *dev_uid; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(gdb_client_node, gdb_client_next, &gdb_transport_module.clients_list, list) { + /* Get UID */ + mif_abs = scsc_mx_get_mif_abs(gdb_transport->mx); + dev_uid = mif_abs->get_uid(mif_abs); + gdb_client_node->gdb_client->probe(gdb_client_node->gdb_client, gdb_transport, dev_uid); + client_registered = true; + } + if (client_registered == false) + SCSC_TAG_INFO(GDB_TRANS, "No clients registered\n"); +} + +void gdb_transport_release(struct gdb_transport *gdb_transport) +{ + struct clients_node *gdb_client_node, *gdb_client_next; + struct gdb_transport_node *gdb_transport_node, *gdb_transport_node_next; + bool match = false; + + list_for_each_entry_safe(gdb_transport_node, gdb_transport_node_next, &gdb_transport_module.gdb_transport_list, list) { + if (gdb_transport_node->gdb_transport == gdb_transport) { + match = true; + SCSC_TAG_INFO(GDB_TRANS, "release client\n"); + /* Wait for client to close */ + mutex_lock(&gdb_transport->channel_open_mutex); + /* Need to notify clients using the transport has been released */ + list_for_each_entry_safe(gdb_client_node, gdb_client_next, &gdb_transport_module.clients_list, list) { + gdb_client_node->gdb_client->remove(gdb_client_node->gdb_client, gdb_transport); + } + mutex_unlock(&gdb_transport->channel_open_mutex); + list_del(&gdb_transport_node->list); + kfree(gdb_transport_node); + } + } + if (match == false) + SCSC_TAG_INFO(GDB_TRANS, "No match for given scsc_mif_abs\n"); + + mif_stream_release(&gdb_transport->mif_istream); + mif_stream_release(&gdb_transport->mif_ostream); +} + +void gdb_transport_config_serialise(struct gdb_transport *gdb_transport, + struct mxtransconf *trans_conf) +{ + mif_stream_config_serialise(&gdb_transport->mif_istream, &trans_conf->to_ap_stream_conf); + mif_stream_config_serialise(&gdb_transport->mif_ostream, &trans_conf->from_ap_stream_conf); +} + + +/** Public functions */ +int gdb_transport_init(struct gdb_transport *gdb_transport, struct scsc_mx *mx, enum gdb_transport_enum type) +{ + int r; + uint32_t mem_length = GDB_TRANSPORT_BUF_LENGTH; + uint32_t packet_size = 4; + uint32_t num_packets; + struct gdb_transport_node *gdb_transport_node; + + gdb_transport_node = kzalloc(sizeof(*gdb_transport_node), GFP_KERNEL); + if (!gdb_transport_node) + return -EIO; + + memset(gdb_transport, 0, sizeof(struct gdb_transport)); + num_packets = mem_length / packet_size; + mutex_init(&gdb_transport->channel_handler_mutex); + mutex_init(&gdb_transport->channel_open_mutex); + gdb_transport->mx = mx; + + if (type == GDB_TRANSPORT_M4) + r = mif_stream_init(&gdb_transport->mif_istream, SCSC_MIF_ABS_TARGET_M4, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, gdb_input_irq_handler, gdb_transport); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (type == GDB_TRANSPORT_M4_1) + r = mif_stream_init(&gdb_transport->mif_istream, SCSC_MIF_ABS_TARGET_M4_1, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, gdb_input_irq_handler, gdb_transport); +#endif + else + r = mif_stream_init(&gdb_transport->mif_istream, SCSC_MIF_ABS_TARGET_R4, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, gdb_input_irq_handler, gdb_transport); + if (r) { + kfree(gdb_transport_node); + return r; + } + + if (type == GDB_TRANSPORT_M4) + r = mif_stream_init(&gdb_transport->mif_ostream, SCSC_MIF_ABS_TARGET_M4, MIF_STREAM_DIRECTION_OUT, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_RESERVED, gdb_output_irq_handler, gdb_transport); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (type == GDB_TRANSPORT_M4_1) + r = mif_stream_init(&gdb_transport->mif_ostream, SCSC_MIF_ABS_TARGET_M4_1, MIF_STREAM_DIRECTION_OUT, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_RESERVED, gdb_output_irq_handler, gdb_transport); +#endif + else + r = mif_stream_init(&gdb_transport->mif_ostream, SCSC_MIF_ABS_TARGET_R4, MIF_STREAM_DIRECTION_OUT, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_RESERVED, gdb_output_irq_handler, gdb_transport); + if (r) { + mif_stream_release(&gdb_transport->mif_istream); + kfree(gdb_transport_node); + return r; + } + + gdb_transport->channel_handler_fn = NULL; + gdb_transport->channel_handler_data = NULL; + + gdb_transport_node->gdb_transport = gdb_transport; + /* Add gdb_transport node */ + list_add_tail(&gdb_transport_node->list, &gdb_transport_module.gdb_transport_list); + gdb_transport->type = type; + gdb_transport_probe_registered_clients(gdb_transport); + return 0; +} + +void gdb_transport_send(struct gdb_transport *gdb_transport, void *message, uint32_t message_length) +{ + char msg[300]; + + if (message_length > sizeof(msg)) + return; + + memcpy(msg, message, message_length); + + mutex_lock(&gdb_transport->channel_handler_mutex); + /* 1st length */ + mif_stream_write(&gdb_transport->mif_ostream, &message_length, sizeof(uint32_t)); + /* 2nd payload (msg) */ + mif_stream_write(&gdb_transport->mif_ostream, message, message_length); + mutex_unlock(&gdb_transport->channel_handler_mutex); +} +EXPORT_SYMBOL(gdb_transport_send); + +void gdb_transport_register_channel_handler(struct gdb_transport *gdb_transport, + gdb_channel_handler handler, void *data) +{ + mutex_lock(&gdb_transport->channel_handler_mutex); + gdb_transport->channel_handler_fn = handler; + gdb_transport->channel_handler_data = (void *)data; + mutex_unlock(&gdb_transport->channel_handler_mutex); +} +EXPORT_SYMBOL(gdb_transport_register_channel_handler); + +int gdb_transport_register_client(struct gdb_transport_client *gdb_client) +{ + struct clients_node *gdb_client_node; + struct gdb_transport_node *gdb_transport_node; + struct scsc_mif_abs *mif_abs; + char *dev_uid; + + /* Add node in modules linked list */ + gdb_client_node = kzalloc(sizeof(*gdb_client_node), GFP_KERNEL); + if (!gdb_client_node) + return -ENOMEM; + + gdb_client_node->gdb_client = gdb_client; + list_add_tail(&gdb_client_node->list, &gdb_transport_module.clients_list); + + + /* Traverse Linked List for transport registered */ + list_for_each_entry(gdb_transport_node, &gdb_transport_module.gdb_transport_list, list) { + /* Get UID */ + mif_abs = scsc_mx_get_mif_abs(gdb_transport_node->gdb_transport->mx); + dev_uid = mif_abs->get_uid(mif_abs); + gdb_client->probe(gdb_client, gdb_transport_node->gdb_transport, dev_uid); + } + return 0; +} +EXPORT_SYMBOL(gdb_transport_register_client); + +void gdb_transport_unregister_client(struct gdb_transport_client *gdb_client) +{ + struct clients_node *gdb_client_node, *gdb_client_next; + + /* Traverse Linked List for each client_list */ + list_for_each_entry_safe(gdb_client_node, gdb_client_next, &gdb_transport_module.clients_list, list) { + if (gdb_client_node->gdb_client == gdb_client) { + list_del(&gdb_client_node->list); + kfree(gdb_client_node); + } + } +} +EXPORT_SYMBOL(gdb_transport_unregister_client); diff --git a/drivers/misc/samsung/scsc/gdb_transport.h b/drivers/misc/samsung/scsc/gdb_transport.h new file mode 100644 index 000000000000..527d7efa9c67 --- /dev/null +++ b/drivers/misc/samsung/scsc/gdb_transport.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * Maxwell gdb transport (Interface) + * + * Provides bi-directional communication between the firmware and the + * host. + * + * This interface also provides a utility method for sending messages across + * the stream. + */ + +#ifndef GDB_TRANSPORT_H__ +#define GDB_TRANSPORT_H__ + +/** Uses */ +#include +#include "mifstream.h" + +/* + * Initialising a buffer of 1 byte is never legitimate, do not allow it. + * The memory buffer length must be a multiple of the packet size. + */ +#define GDB_TRANSPORT_BUF_LENGTH (2 * 1024) + +struct gdb_transport; + +enum gdb_transport_enum { + GDB_TRANSPORT_R4 = 0, + GDB_TRANSPORT_M4, +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + GDB_TRANSPORT_M4_1, +#endif +}; +/** + * Transport channel callback handler. This will be invoked each time a message on a channel is + * received. Handlers may perform work within + * their callback implementation, but should not block. + * + * Note that the message pointer passed is only valid for the duration of the function call. + */ +typedef void (*gdb_channel_handler)(const void *message, size_t length, void *data); + +/** + * Sends a message to the AP across the given channel. + * + * This function is safe to call from any RTOS thread. + */ +void gdb_transport_send(struct gdb_transport *gdb_transport, + void *message, uint32_t message_length); + +/** + * Initialises the maxwell management transport and configures the necessary + * interrupt handlers. Called once during boot. + */ +int gdb_transport_init(struct gdb_transport *gdb_transport, struct scsc_mx *mx, enum gdb_transport_enum type); +void gdb_transport_release(struct gdb_transport *gdb_transport); + +/* + * Initialises the configuration area incl. Maxwell Infrastructure Configuration, + * MIF Management Transport Configuration and MIF Management Stream Configuration. + */ +void gdb_transport_config_serialise(struct gdb_transport *gdb_transport, struct mxtransconf *trans_conf); +void gdb_transport_set_error(struct gdb_transport *gdb_transport); + +struct gdb_transport { + struct scsc_mx *mx; + struct mif_stream mif_istream; + struct mif_stream mif_ostream; + /** Registered channel handlers for messages coming from the AP for each channel */ + gdb_channel_handler channel_handler_fn; + void *channel_handler_data; + struct mutex channel_handler_mutex; + struct mutex channel_open_mutex; + /* Transport processor type */ + enum gdb_transport_enum type; +}; + +struct gdb_transport_client { + char *name; + void (*probe)(struct gdb_transport_client *gdb_client, struct gdb_transport *gdb_transport, char *dev_uid); + void (*remove)(struct gdb_transport_client *gdb_client, struct gdb_transport *gdb_transport); +}; + +int gdb_transport_register_client(struct gdb_transport_client *gdb_client); +void gdb_transport_unregister_client(struct gdb_transport_client *gdb_client); +void gdb_transport_register_channel_handler(struct gdb_transport *gdb_transport, gdb_channel_handler handler, void *data); +void gdb_transport_register_char_device(struct scsc_mx *mx, struct gdb_transport **gdb_transport_handler); +#endif /* GDB_TRANSPORT_H__ */ diff --git a/drivers/misc/samsung/scsc/mif_reg.h b/drivers/misc/samsung/scsc/mif_reg.h new file mode 100755 index 000000000000..785471bcc873 --- /dev/null +++ b/drivers/misc/samsung/scsc/mif_reg.h @@ -0,0 +1,135 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIF_REG_H +#define __MIF_REG_H + +/*********************************/ +/* PLATFORM register definitions */ +/*********************************/ +#define NUM_MBOX_PLAT 8 +#define NUM_SEMAPHORE 12 + +#define MAILBOX_WLBT_BASE 0x0000 +#define MAILBOX_WLBT_REG(r) (MAILBOX_WLBT_BASE + (r)) +#define MCUCTRL 0x000 /* MCU Controller Register */ +/* R0 [31:16] - Int FROM R4/M4 */ +#define INTGR0 0x008 /* Interrupt Generation Register 0 (r/w) */ +#define INTCR0 0x00C /* Interrupt Clear Register 0 (w) */ +#define INTMR0 0x010 /* Interrupt Mask Register 0 (r/w) */ +#define INTSR0 0x014 /* Interrupt Status Register 0 (r) */ +#define INTMSR0 0x018 /* Interrupt Mask Status Register 0 (r) */ +/* R1 [15:0] - Int TO R4 */ +#define INTGR1 0x01c /* Interrupt Generation Register 1 */ +#define INTCR1 0x020 /* Interrupt Clear Register 1 */ +#define INTMR1 0x024 /* Interrupt Mask Register 1 */ +#define INTSR1 0x028 /* Interrupt Status Register 1 */ +#define INTMSR1 0x02c /* Interrupt Mask Status Register 1 */ +/* R2 [15:0] - Int TO M4 */ +#define INTGR2 0x030 /* Interrupt Generation Register 2 */ +#define INTCR2 0x034 /* Interrupt Clear Register 2 */ +#define INTMR2 0x038 /* Interrupt Mask Register 2 */ +#define INTSR2 0x03c /* Interrupt Status Register 2 */ +#define INTMSR2 0x040 /* Interrupt Mask Status Register 2 */ +#define MIF_INIT 0x04c /* MIF_init */ +#define IS_VERSION 0x050 /* Version Information Register */ +#define ISSR_BASE 0x080 /* IS_Shared_Register Base address */ +#define ISSR(r) (ISSR_BASE + (4 * (r))) +#define SEMAPHORE_BASE 0x180 /* IS_Shared_Register Base address */ +#define SEMAPHORE(r) (SEMAPHORE_BASE + (4 * (r))) +#define SEMA0CON 0x1c0 +#define SEMA0STATE 0x1c8 + + +/* POWER */ +/* Page 594 datasheet */ +/* Base Address - 0x11C8_0000 */ +#define WIFI_CTRL_NS 0x0140 /* WIFI Control SFR non-secure */ +#define WIFI_PWRON BIT(1) +#define WIFI_RESET_SET BIT(2) +#define WIFI_ACTIVE_EN BIT(5) /* Enable of WIFI_ACTIVE_REQ */ +#define WIFI_ACTIVE_CLR BIT(6) /* WIFI_ACTIVE_REQ is clear internally on WAKEUP */ +#define WIFI_RESET_REQ_EN BIT(7) /* 1:enable, 0:disable Enable of WIFI_RESET_REQ */ +#define WIFI_RESET_REQ_CLR BIT(8) /* WIFI_RESET_REQ is clear internally on WAKEUP */ +#define MASK_WIFI_PWRDN_DONE BIT(9) /* 1:mask, 0 : pass RTC clock out enable to WIFI + * This mask WIFI_PWRDN_DONE come in from WIFI. + * If MASK_WIFI_PWRDN_DONE = 1, WIFI enter to DOWN + * state without checking WIFI_PWRDN_DONE*/ + + + +#define WIFI_CTRL_S 0x0144 /* WIFI Control SFR secure */ +#define WIFI_START BIT(3) /* WIFI Reset release control If WIFI_START = 1, + * WIFI exit from DOWN state and go to UP state. + * If this field is set to high (WIFI_START = 1) + * WIFI state can go to UP state. This signal can be + * auto-clear by DIRECTWR at UP */ + +#define WIFI_STAT 0x0148 /* Indicate whether WIFI uses MIF domain */ +#define WIFI_DEBUG 0x014c /* MIF sleep, wakeup debugging control */ +/* Page 1574 datasheet */ +#define PMU_ALIVE_BASE 0x0000 +#define PMU_ALIVE_REG(r) (PMU_ALIVE_BASE + (r)) +#define WIFI2AP_MEM_CONFIG0 0x0150 /* Control WLBT_MEM_SIZE. */ +#define WLBT2AP_MIF_ACCESS_WIN0 0x0154 /* ACCESS_CONTROL_PERI_IP */ +#define WLBT2AP_MIF_ACCESS_WIN1 0x0158 /* ACCESS_CONTROL_PERI_IP */ +#define WLBT2AP_MIF_ACCESS_WIN2 0x015a /* ACCESS_CONTROL_PERI_IP */ +#define WLBT2AP_MIF_ACCESS_WIN3 0x0160 /* ACCESS_CONTROL_PERI_IP */ +#define WIFI2AP_MEM_CONFIG1 0x0164 /* Control WLBT_MEM_BA0 */ +#define WLBT_BOOT_TEST_RST_CFG 0x0168 /* WLBT_IRAM_BOOT_OFFSET */ + /* WLBT_IRAM_BOOT_TEST */ + /* WLBT2AP_PERI_PROT2 */ +#define WLBT2AP_PERI_ACCESS_WIN 0x016c /* WLBT2AP_PERI_ACCESS_END - WLBT2AP_PERI_ACCESS_START */ +#define WIFI2AP_MODAPIF_CONFIG 0x0170 /* WLBT2AP_PERI_ACCESS_END - WLBT2AP_PERI_ACCESS_START */ +#define WIFI2AP_QOS 0x0170 /* RT */ +#define WIFI2AP_MEM_CONFIG2 0x017c /* Control WLBT_MEM_BA1 */ +#define WIFI2AP_MEM_CONFIG3 0x0184 /* Control WLBT_ADDR_RNG */ + +/* Power down registers */ +#define RESET_ASB_WIFI_SYS_PWR_REG 0x11f4 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define TCXO_GATE_WIFI_SYS_PWR_REG 0x11f0 /* Control power state in LOWPWR mode 1 - on, 0 */ +#define LOGIC_RESET_WIFI_SYS_PWR_REG 0x11f8 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define CLEANY_BUS_WIFI_SYS_PWR_REG 0x11fc /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define CENTRAL_SEQ_WIFI_CONFIGURATION 0x0380 /* bit 16. Decides whether system-level low-power mode + * is used HIGH: System-level Low-Power mode + * disabled. LOW: System-level Low-Power mode + * enabled. When system enters low-power mode, + * this field is automatically cleared to HIGH. */ + +#define CENTRAL_SEQ_WIFI_STATUS 0x0384 /* 23:16 Check statemachine status */ +#define STATES 0xff0000 + +#define SYS_PWR_CFG BIT(0) +#define SYS_PWR_CFG_2 (BIT(0) | BIT(1)) +#define SYS_PWR_CFG_16 BIT(16) + + +/* CMU registers to request PLL for USB Clock */ +#define USBPLL_CON0 0x1000 +#define AP2WIFI_USBPLL_REQ BIT(0) /* 1: Request PLL, 0: Release PLL */ + +#define USBPLL_CON1 0x1004 /* */ +#define AP2WLBT_USBPLL_WPLL_SEL BIT(0) /* 1: WLBT, 0: AP */ +#define AP2WLBT_USBPLL_WPLL_EN BIT(1) /* 1: Enable, 0: Disable */ + +/***** Interrupts ******** + * + * - MBOX + * - WIFI_ACTIVE (pag 553) + * comes from BLK_WIFI. Initial value is low and then this value becomes high after WIFI booting. If + * some problem occurs within WIFI, WIFI_ACTIVE can be low by WIFI CPU. AP CPU detects that WIFI_ACTIVE is + * low after WIFI_ACTIVE is high. And WIFI_ACTIVE detected goes to GIC's interrupt source. At ISR, AP CPU + * makes wake source and interrupt clear as setting WIFI_CTRL__WIFI_ACTIVE_CLR. WIFI_ACTIVE_CLR is auto + * clear by direct-write function. + * + * - WIFI_RESET_REQ (pag 554) + * WIFI can request WIFI reset only by WIFI_RESET_REQ. If WIFI_RESET_REQ is asserted, AP PMU detects it as + * wakeup source and interrupt source. At ISR, AP CPU makes wakeup source clear as setting + * WIFI_CTRL__CP_RESET_REQ_CLR. But, interrupt can be not clear because the interrupt goes to GIC directly + * from WIFI. (It use make function within GIC) WIFI_RESET_REQ_CLR is auto clear by direct-write function. + */ + +#endif /* __MIF_REG_H */ diff --git a/drivers/misc/samsung/scsc/mif_reg_S5E7872.h b/drivers/misc/samsung/scsc/mif_reg_S5E7872.h new file mode 100755 index 000000000000..e1fb2a04d57b --- /dev/null +++ b/drivers/misc/samsung/scsc/mif_reg_S5E7872.h @@ -0,0 +1,149 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIF_REG_7872_H +#define __MIF_REG_7872_H + +/*********************************/ +/* PLATFORM register definitions */ +/*********************************/ +#define NUM_MBOX_PLAT 8 +#define NUM_SEMAPHORE 12 + +#define MAILBOX_WLBT_BASE 0x0000 +#define MAILBOX_WLBT_REG(r) (MAILBOX_WLBT_BASE + (r)) +#define MCUCTRL 0x000 /* MCU Controller Register */ +/* R0 [31:16] - Int FROM R4/M4 */ +#define INTGR0 0x008 /* Interrupt Generation Register 0 (r/w) */ +#define INTCR0 0x00C /* Interrupt Clear Register 0 (w) */ +#define INTMR0 0x010 /* Interrupt Mask Register 0 (r/w) */ +#define INTSR0 0x014 /* Interrupt Status Register 0 (r) */ +#define INTMSR0 0x018 /* Interrupt Mask Status Register 0 (r) */ +/* R1 [15:0] - Int TO R4/M4 */ +#define INTGR1 0x01c /* Interrupt Generation Register 1 */ +#define INTCR1 0x020 /* Interrupt Clear Register 1 */ +#define INTMR1 0x024 /* Interrupt Mask Register 1 */ +#define INTSR1 0x028 /* Interrupt Status Register 1 */ +#define INTMSR1 0x02c /* Interrupt Mask Status Register 1 */ +#define MIF_INIT 0x04c /* MIF_init */ +#define IS_VERSION 0x050 /* Version Information Register */ +#define ISSR_BASE 0x080 /* IS_Shared_Register Base address */ +#define ISSR(r) (ISSR_BASE + (4 * (r))) +#define SEMAPHORE_BASE 0x180 /* IS_Shared_Register Base address */ +#define SEMAPHORE(r) (SEMAPHORE_BASE + (4 * (r))) +#define SEMA0CON 0x1c0 +#define SEMA0STATE 0x1c8 +#define SEMA1CON 0x1e0 +#define SEMA1STATE 0x1e8 + + +/* POWER */ +/* Page 594 datasheet */ +/* Base Address - 0x11C8_0000 */ +#define WIFI_CTRL_NS 0x0140 /* WIFI Control SFR non-secure */ +#define WIFI_PWRON BIT(1) +#define WIFI_RESET_SET BIT(2) +#define WIFI_ACTIVE_EN BIT(5) /* Enable of WIFI_ACTIVE_REQ */ +#define WIFI_ACTIVE_CLR BIT(6) /* WIFI_ACTIVE_REQ is clear internally on WAKEUP */ +#define WIFI_RESET_REQ_EN BIT(7) /* 1:enable, 0:disable Enable of WIFI_RESET_REQ */ +#define WIFI_RESET_REQ_CLR BIT(8) /* WIFI_RESET_REQ is clear internally on WAKEUP */ +#define MASK_WIFI_PWRDN_DONE BIT(9) /* 1:mask, 0 : pass RTC clock out enable to WIFI + * This mask WIFI_PWRDN_DONE come in from WIFI. + * If MASK_WIFI_PWRDN_DONE = 1, WIFI enter to DOWN + * state without checking WIFI_PWRDN_DONE*/ +#define RTC_OUT_EN BIT(10) /* 1:enable, 0 : disable This is enable signal on RTC + * CLK(32KHz). This clock can be used as WIFI PMU + * clock when WIFI is internal power-down and + * TCXO(26MHz) is disable at WIFI side.*/ +#define TCXO_ENABLE_SW BIT(11) /* 1:enable, 0 : disable This is enable signal on TCXO + * clock of WIFI. This signal can decide whether TCXO + * clock is active by software when WIFI is internal + * power-down or WIFI is in reset state at WIFI side. if + * this value is HIGH, TCXO is active regardless of + * hardware control */ +#define MASK_MIF_REQ BIT(12) /* 1:mask MIF_REQ comming from WIFI, 0 : disable */ +#define SET_SW_MIF_REQ BIT(13) /* MIF SLEEP control by SW 1: if MASK_MIF_REQ is + set to HIGH, MIF enters into down state by + SET_SW_MIF_REQ. */ +#define SWEEPER_BYPASS_DATA_EN BIT(16) /* CLEANY bypass mode control(WIFI2AP MEM path) + If this bit is set to 1, CLEANY in MIF block starts + operation. If this bit is set to 0, CLEANY is bypass + mode.*/ +#define SFR_SERIALIZER_DUR_DATA2REQ (BIT(20) | BIT(21)) /* Duration between DATA and REQUEST on + SFR_SERIALIZER */ + + +#define WIFI_CTRL_S 0x0144 /* WIFI Control SFR secure */ +#define WIFI_START BIT(3) /* WIFI Reset release control If WIFI_START = 1, + * WIFI exit from DOWN state and go to UP state. + * If this field is set to high (WIFI_START = 1) + * WIFI state can go to UP state. This signal can be + * auto-clear by DIRECTWR at UP */ + +#define WIFI_STAT 0x0148 +#define WIFI_PWRDN_DONE BIT(0) /* Check WIFI power-down status.*/ +#define WIFI_ACCESS_MIF BIT(4) /* Check whether WIFI accesses MIF doman */ + +#define WIFI_DEBUG 0x014c /* MIF sleep, wakeup debugging control */ +#define EN_MIF_REQ BIT(0) /* Control MIF_REQ through GPIO_ALIVE. */ +#define EN_WIFI_ACTIVE BIT(2) /* Control WIFI_ACTIVE through GPIO_ALIVE. */ +#define EN_MIF_RESET_REQ BIT(3) /* Control WIFI_RESET_REQ through GPIO_ALIVE. */ +#define MASK_CLKREQ_WIFI BIT(8) /* When this field is set to HIGH, ALIVE ignores + * CLKREQ from WIFI.*/ + +/* TODO: Might be 0x10480000 */ +#define PMU_ALIVE_BASE 0x0000 +#define PMU_ALIVE_REG(r) (PMU_ALIVE_BASE + (r)) +#define WIFI2AP_MEM_CONFIG0 0x7300 /* MEM_SIZE SECTION_0 */ +#define WIFI2AP_MEM_CONFIG1 0x7304 /* BASE ADDRESS SECTION 0*/ +#define WIFI2AP_MEM_CONFIG2 0x7300 /* MEM_SIZE SECTION_0 */ +#define WIFI2AP_MEM_CONFIG3 0x7304 /* BASE ADDRESS SECTION 1*/ +#define WIFI2AP_MEM_CONFIG4 0x7300 /* MEM_SIZE SECTION_1 */ +#define WIFI2AP_MEM_CONFIG5 0x7304 /* BASE ADDRESS SECTION 0*/ +#define WIFI2AP_MIF_ACCESS_WIN0 0x7318 /* ACCESS_CONTROL SFR*/ +#define WIFI2AP_MIF_ACCESS_WIN1 0x731c /* ACCESS_CONTROL SFR*/ +#define WIFI2AP_PERI0_ACCESS_WIN0 0x7320 /* ACCESS WINDOW PERI */ +#define WIFI2AP_PERI0_ACCESS_WIN1 0x7324 /* ACCESS WINDOW PERI */ +#define WIFI2AP_PERI0_ACCESS_WIN2 0x7328 /* ACCESS WINDOW PERI */ +#define WIFI2AP_PERI0_ACCESS_WIN3 0x732c /* ACCESS WINDOW PERI */ +#define WLBT_BOOT_TEST_RST_CFG 0x7330 /* WLBT_IRAM_BOOT_OFFSET */ + /* WLBT_IRAM_BOOT_TEST */ + /* WLBT2AP_PERI_PROT2 */ + +/* Power down registers */ +#define RESET_AHEAD_WIFI_PWR_REG 0x1360 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define CLEANY_BUS_WIFI_SYS_PWR_REG 0x1364 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define LOGIC_RESET_WIFI_SYS_PWR_REG 0x1368 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define TCXO_GATE_WIFI_SYS_PWR_REG 0x136c /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WIFI_DISABLE_ISO_SYS_PWR_REG 0x1370 /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WIFI_RESET_ISO_SYS_PWR_REG 0x1374 /* Control power state in LOWPWR mode 1 - on, 0 */ + +#define CENTRAL_SEQ_WIFI_CONFIGURATION 0x0380 /* bit 16. Decides whether system-level low-power mode + * is used HIGH: System-level Low-Power mode + * disabled. LOW: System-level Low-Power mode + * enabled. When system enters low-power mode, + * this field is automatically cleared to HIGH. */ + +#define CENTRAL_SEQ_WIFI_STATUS 0x0384 /* 23:16 Check statemachine status */ +#define STATES 0xff0000 + +#define SYS_PWR_CFG BIT(0) +#define SYS_PWR_CFG_2 (BIT(0) | BIT(1)) +#define SYS_PWR_CFG_16 BIT(16) + + +/* CMU registers to request PLL for USB Clock */ +#define USBPLL_CON0 0x0200 +#define AP2WIFI_USBPLL_REQ BIT(0) /* 1: Request PLL, 0: Release PLL */ + +#define USBPLL_CON1 0x0204 +#define AP2WLBT_USBPLL_WPLL_SEL BIT(0) /* 1: WLBT, 0: AP */ +#define AP2WLBT_USBPLL_WPLL_EN BIT(1) /* 1: Enable, 0: Disable */ + +/* TZASC configuration for Katmai onward */ +#define WLBT_TZASC 0 +#define EXYNOS_SMC_WLBT_TZASC_CMD 0x82000710 +#endif /* __MIF_REG_7872_H */ diff --git a/drivers/misc/samsung/scsc/mif_reg_S5E7885.h b/drivers/misc/samsung/scsc/mif_reg_S5E7885.h new file mode 100644 index 000000000000..449967cec839 --- /dev/null +++ b/drivers/misc/samsung/scsc/mif_reg_S5E7885.h @@ -0,0 +1,140 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIF_REG_7885_H +#define __MIF_REG_7885_H + +/*********************************/ +/* PLATFORM register definitions */ +/*********************************/ +#define NUM_MBOX_PLAT 8 +#define NUM_SEMAPHORE 12 + +#define MAILBOX_WLBT_BASE 0x0000 +#define MAILBOX_WLBT_REG(r) (MAILBOX_WLBT_BASE + (r)) +#define MCUCTRL 0x000 /* MCU Controller Register */ +/* R0 [31:16] - Int FROM R4/M4 */ +#define INTGR0 0x008 /* Interrupt Generation Register 0 (r/w) */ +#define INTCR0 0x00C /* Interrupt Clear Register 0 (w) */ +#define INTMR0 0x010 /* Interrupt Mask Register 0 (r/w) */ +#define INTSR0 0x014 /* Interrupt Status Register 0 (r) */ +#define INTMSR0 0x018 /* Interrupt Mask Status Register 0 (r) */ +/* R1 [15:0] - Int TO R4/M4 */ +#define INTGR1 0x01c /* Interrupt Generation Register 1 */ +#define INTCR1 0x020 /* Interrupt Clear Register 1 */ +#define INTMR1 0x024 /* Interrupt Mask Register 1 */ +#define INTSR1 0x028 /* Interrupt Status Register 1 */ +#define INTMSR1 0x02c /* Interrupt Mask Status Register 1 */ +#define MIF_INIT 0x04c /* MIF_init */ +#define IS_VERSION 0x050 /* Version Information Register */ +#define ISSR_BASE 0x080 /* IS_Shared_Register Base address */ +#define ISSR(r) (ISSR_BASE + (4 * (r))) +#define SEMAPHORE_BASE 0x180 /* IS_Shared_Register Base address */ +#define SEMAPHORE(r) (SEMAPHORE_BASE + (4 * (r))) +#define SEMA0CON 0x1c0 +#define SEMA0STATE 0x1c8 +#define SEMA1CON 0x1e0 +#define SEMA1STATE 0x1e8 + + +/* POWER */ +/* Page 1173 datasheet */ +/* Base Address - 0x11C8_0000 */ +#define WIFI_CTRL_NS 0x0140 /* WIFI Control SFR non-secure */ +#define WIFI_PWRON BIT(1) +#define WIFI_RESET_SET BIT(2) +#define WIFI_ACTIVE_EN BIT(5) /* Enable of WIFI_ACTIVE_REQ */ +#define WIFI_ACTIVE_CLR BIT(6) /* WIFI_ACTIVE_REQ is clear internally on WAKEUP */ +#define WIFI_RESET_REQ_EN BIT(7) /* 1:enable, 0:disable Enable of WIFI_RESET_REQ */ +#define WIFI_RESET_REQ_CLR BIT(8) /* WIFI_RESET_REQ is clear internally on WAKEUP */ +#define MASK_WIFI_PWRDN_DONE BIT(9) /* 1:mask, 0 : pass RTC clock out enable to WIFI + * This mask WIFI_PWRDN_DONE come in from WIFI. + * If MASK_WIFI_PWRDN_DONE = 1, WIFI enter to DOWN + * state without checking WIFI_PWRDN_DONE*/ +#define RTC_OUT_EN BIT(10) /* 1:enable, 0 : disable This is enable signal on RTC + * CLK(32KHz). This clock can be used as WIFI PMU + * clock when WIFI is internal power-down and + * TCXO(26MHz) is disable at WIFI side.*/ +#define TCXO_ENABLE_SW BIT(11) /* 1:enable, 0 : disable This is enable signal on TCXO + * clock of WIFI. This signal can decide whether TCXO + * clock is active by software when WIFI is internal + * power-down or WIFI is in reset state at WIFI side. if + * this value is HIGH, TCXO is active regardless of + * hardware control */ +#define MASK_MIF_REQ BIT(12) /* 1:mask MIF_REQ comming from WIFI, 0 : disable */ +#define SET_SW_MIF_REQ BIT(13) /* MIF SLEEP control by SW 1: if MASK_MIF_REQ is + set to HIGH, MIF enters into down state by + SET_SW_MIF_REQ. */ +#define SWEEPER_BYPASS_DATA_EN BIT(16) /* CLEANY bypass mode control(WIFI2AP MEM path) + If this bit is set to 1, CLEANY in MIF block starts + operation. If this bit is set to 0, CLEANY is bypass + mode.*/ +#define SFR_SERIALIZER_DUR_DATA2REQ (BIT(20) | BIT(21)) /* Duration between DATA and REQUEST on + SFR_SERIALIZER */ + + +#define WIFI_CTRL_S 0x0144 /* WIFI Control SFR secure */ +#define WIFI_START BIT(3) /* WIFI Reset release control If WIFI_START = 1, + * WIFI exit from DOWN state and go to UP state. + * If this field is set to high (WIFI_START = 1) + * WIFI state can go to UP state. This signal can be + * auto-clear by DIRECTWR at UP */ + +#define WIFI_STAT 0x0148 +#define WIFI_PWRDN_DONE BIT(0) /* Check WIFI power-down status.*/ +#define WIFI_ACCESS_MIF BIT(4) /* Check whether WIFI accesses MIF doman */ + +#define WIFI_DEBUG 0x014c /* MIF sleep, wakeup debugging control */ +#define EN_MIF_REQ BIT(0) /* Control MIF_REQ through GPIO_ALIVE. */ +#define EN_WIFI_ACTIVE BIT(2) /* Control WIFI_ACTIVE through GPIO_ALIVE. */ +#define EN_MIF_RESET_REQ BIT(3) /* Control WIFI_RESET_REQ through GPIO_ALIVE. */ +#define MASK_CLKREQ_WIFI BIT(8) /* When this field is set to HIGH, ALIVE ignores + * CLKREQ from WIFI.*/ + +/* TODO: Might be 0x10480000 */ +#define PMU_ALIVE_BASE 0x0000 +#define PMU_ALIVE_REG(r) (PMU_ALIVE_BASE + (r)) +#define WIFI2AP_MEM_CONFIG0 0x7300 /* MEM_SIZE SECTION_0 */ +#define WIFI2AP_MEM_CONFIG1 0x7304 /* BASE ADDRESS SECTION 0*/ +#define WIFI2AP_MEM_CONFIG2 0x7300 /* MEM_SIZE SECTION_0 */ +#define WIFI2AP_MEM_CONFIG3 0x7304 /* BASE ADDRESS SECTION 1*/ +#define WIFI2AP_MEM_CONFIG4 0x7300 /* MEM_SIZE SECTION_1 */ +#define WIFI2AP_MEM_CONFIG5 0x7304 /* BASE ADDRESS SECTION 0*/ +#define WIFI2AP_MIF_ACCESS_WIN0 0x7318 /* ACCESS_CONTROL SFR*/ +#define WIFI2AP_MIF_ACCESS_WIN1 0x731c /* ACCESS_CONTROL SFR*/ +#define WIFI2AP_PERI0_ACCESS_WIN0 0x7320 /* ACCESS WINDOW PERI */ +#define WIFI2AP_PERI0_ACCESS_WIN1 0x7324 /* ACCESS WINDOW PERI */ +#define WIFI2AP_PERI0_ACCESS_WIN2 0x7328 /* ACCESS WINDOW PERI */ +#define WIFI2AP_PERI0_ACCESS_WIN3 0x732c /* ACCESS WINDOW PERI */ +#define WLBT_BOOT_TEST_RST_CFG 0x7330 /* WLBT_IRAM_BOOT_OFFSET */ + /* WLBT_IRAM_BOOT_TEST */ + /* WLBT2AP_PERI_PROT2 */ + +/* Power down registers */ +#define RESET_AHEAD_WIFI_PWR_REG 0x1360 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define CLEANY_BUS_WIFI_SYS_PWR_REG 0x1364 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define LOGIC_RESET_WIFI_SYS_PWR_REG 0x1368 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define TCXO_GATE_WIFI_SYS_PWR_REG 0x136c /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WIFI_DISABLE_ISO_SYS_PWR_REG 0x1370 /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WIFI_RESET_ISO_SYS_PWR_REG 0x1374 /* Control power state in LOWPWR mode 1 - on, 0 */ + +#define CENTRAL_SEQ_WIFI_CONFIGURATION 0x0380 /* bit 16. Decides whether system-level low-power mode + * is used HIGH: System-level Low-Power mode + * disabled. LOW: System-level Low-Power mode + * enabled. When system enters low-power mode, + * this field is automatically cleared to HIGH. */ + +#define CENTRAL_SEQ_WIFI_STATUS 0x0384 /* 23:16 Check statemachine status */ +#define STATES 0xff0000 + +#define SYS_PWR_CFG BIT(0) +#define SYS_PWR_CFG_2 (BIT(0) | BIT(1)) +#define SYS_PWR_CFG_16 BIT(16) + +/* TZASC configuration for Katmai onward */ +#define WLBT_TZASC 0 +#define EXYNOS_SMC_WLBT_TZASC_CMD 0x82000710 +#endif /* __MIF_REG_7885_H */ diff --git a/drivers/misc/samsung/scsc/mif_reg_S5E9610.h b/drivers/misc/samsung/scsc/mif_reg_S5E9610.h new file mode 100644 index 000000000000..49120eb08e00 --- /dev/null +++ b/drivers/misc/samsung/scsc/mif_reg_S5E9610.h @@ -0,0 +1,182 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIF_REG_9610_H +#define __MIF_REG_9610_H + +/*********************************/ +/* PLATFORM register definitions */ +/*********************************/ +#define NUM_MBOX_PLAT 8 +#define NUM_SEMAPHORE 12 + +#define MAILBOX_WLBT_BASE 0x0000 +#define MAILBOX_WLBT_REG(r) (MAILBOX_WLBT_BASE + (r)) +#define MCUCTRL 0x000 /* MCU Controller Register */ +/* R0 [31:16] - Int FROM R4/M4 */ +#define INTGR0 0x008 /* Interrupt Generation Register 0 (r/w) */ +#define INTCR0 0x00C /* Interrupt Clear Register 0 (w) */ +#define INTMR0 0x010 /* Interrupt Mask Register 0 (r/w) */ +#define INTSR0 0x014 /* Interrupt Status Register 0 (r) */ +#define INTMSR0 0x018 /* Interrupt Mask Status Register 0 (r) */ +/* R1 [15:0] - Int TO R4/M4 */ +#define INTGR1 0x01c /* Interrupt Generation Register 1 */ +#define INTCR1 0x020 /* Interrupt Clear Register 1 */ +#define INTMR1 0x024 /* Interrupt Mask Register 1 */ +#define INTSR1 0x028 /* Interrupt Status Register 1 */ +#define INTMSR1 0x02c /* Interrupt Mask Status Register 1 */ +#define MIF_INIT 0x04c /* MIF_init */ +#define IS_VERSION 0x050 /* Version Information Register */ +#define ISSR_BASE 0x080 /* IS_Shared_Register Base address */ +#define ISSR(r) (ISSR_BASE + (4 * (r))) +#define SEMAPHORE_BASE 0x180 /* IS_Shared_Register Base address */ +#define SEMAPHORE(r) (SEMAPHORE_BASE + (4 * (r))) +#define SEMA0CON 0x1c0 +#define SEMA0STATE 0x1c8 +#define SEMA1CON 0x1e0 +#define SEMA1STATE 0x1e8 + +/* POWER */ +/* Exynos 9610 UM - 9.9.1.16 */ +#define WLBT_CTRL_NS 0x0050 /* WLBT Control SFR non-secure */ + +#define WLBT_PWRON BIT(1) +#define WLBT_RESET_SET BIT(0) /* WLBT reset assertion control by using + * PMU_ALIVE_WLBT. + * 0x1: Reset Assertion, + * 0x0: Reset Release + */ +#define WLBT_ACTIVE_EN BIT(5) /* Enable of WIFI_ACTIVE_REQ */ +#define WLBT_ACTIVE_CLR BIT(6) /* WLBT_ACTIVE_REQ is clear internally on WAKEUP */ +#define WLBT_RESET_REQ_EN BIT(7) /* 1:enable, 0:disable Enable of WLBT_RESET_REQ */ +#define WLBT_RESET_REQ_CLR BIT(8) /* WLBT_RESET_REQ is clear internally on WAKEUP */ +#define MASK_PWR_REQ BIT(18) /* 1:mask PWR_REQ coming from WLBT, 0 : disable */ +#define MASK_TCXO_REQ BIT(20) /* 1:mask TCXO_REQ coming from CP, + * 0:enable request source + */ + +#define RTC_OUT_EN BIT(10) /* 1:enable, 0 : disable This is enable signal on RTC + * CLK(32KHz). This clock can be used as WLBT PMU + * clock when WLBT is internal power-down and + * TCXO(26MHz) is disable at WLBT side. + */ +#define TCXO_ENABLE_SW BIT(11) /* 1:enable, 0 : disable This is enable signal on TCXO + * clock of WLBT. This signal can decide whether TCXO + * clock is active by software when WLBT is internal + * power-down or WLBT is in reset state at WLBT side. if + * this value is HIGH, TCXO is active regardless of + * hardware control + */ +#define MASK_MIF_REQ BIT(12) /* 1:mask MIF_REQ coming from WLBT, 0 : disable */ +#define SET_SW_MIF_REQ BIT(13) /* MIF SLEEP control by SW 1: if MASK_MIF_REQ is + * set to HIGH, MIF enters into down state by + * SET_SW_MIF_REQ. + */ +#define SWEEPER_BYPASS_DATA_EN BIT(16) /* SWEEPER bypass mode control(WLBT2AP path) If + * this bit is set to 1, SWEEPER is bypass mode. + */ + +#define WLBT_CTRL_S 0x0054 /* WLBT Control SFR secure */ +#define WLBT_START BIT(0) /* WLBT initial Reset release control + * If CP_START = 1, PMU_RESET_SEQUENCER_CP + * starts initial reset release sequence + * and goes to UP state. + */ + +#define WLBT_STAT 0x0058 +#define WLBT_PWRDN_DONE BIT(0) /* Check WLBT power-down status.*/ +#define WLBT_ACCESS_MIF BIT(4) /* Check whether WLBT accesses MIF doman */ + +#define WLBT_DEBUG 0x005c /* MIF sleep, wakeup debugging control */ +#define EN_MIF_REQ BIT(0) /* Control MIF_REQ through GPIO_ALIVE. */ +#define EN_WLBT_ACTIVE BIT(2) /* Control WLBT_ACTIVE through GPIO_ALIVE. */ +#define EN_WLBT_RESET_REQ BIT(3) /* Control WLBT_RESET_REQ through GPIO_ALIVE. */ +#define MASK_CLKREQ_WLBT BIT(8) /* When this field is set to HIGH, ALIVE ignores + * CLKREQ from WLBT. + */ + +/* New WLBT SFRs for MEM config */ + +/* end address is exclusive so the ENDx register should be set to the first + * address that is not accessible through that BAAW. + * + * Another very important point to note here is we are using BAAW0 to expose + * 16MB region, so other BAAWs can be used for other purposes + */ +#define WLBT_DBUS_BAAW_0_START 0x80000000 +#define WLBT_DBUS_BAAW_0_END WLBT_DBUS_BAAW_4_START +#define WLBT_DBUS_BAAW_1_START 0x80400000 +#define WLBT_DBUS_BAAW_1_END WLBT_DBUS_BAAW_2_START +#define WLBT_DBUS_BAAW_2_START 0x80800000 +#define WLBT_DBUS_BAAW_2_END WLBT_DBUS_BAAW_3_START +#define WLBT_DBUS_BAAW_3_START 0x80C00000 +#define WLBT_DBUS_BAAW_3_END WLBT_DBUS_BAAW_4_START +#define WLBT_DBUS_BAAW_4_START 0x81000000 +#define WLBT_DBUS_BAAW_4_END 0x81400000 + +#define WLBT_BAAW_CON_INIT_DONE (1 << 31) +#define WLBT_BAAW_CON_EN_WRITE (1 << 1) +#define WLBT_BAAW_CON_EN_READ (1 << 0) +#define WLBT_BAAW_ACCESS_CTRL (WLBT_BAAW_CON_INIT_DONE | WLBT_BAAW_CON_EN_WRITE | WLBT_BAAW_CON_EN_READ) + +#define WLBT_PBUS_BAAW_0_START 0xA0000000 +#define WLBT_PBUS_BAAW_0_END WLBT_PBUS_BAAW_1_START +#define WLBT_PBUS_BAAW_1_START 0xA0010000 +#define WLBT_PBUS_BAAW_1_END WLBT_PBUS_BAAW_2_START +#define WLBT_PBUS_BAAW_2_START 0xA0060000 +#define WLBT_PBUS_BAAW_2_END WLBT_PBUS_BAAW_3_START +#define WLBT_PBUS_BAAW_3_START 0xA0100000 +#define WLBT_PBUS_BAAW_3_END WLBT_PBUS_BAAW_4_START +#define WLBT_PBUS_BAAW_4_START 0xA0110000 +#define WLBT_PBUS_BAAW_4_END WLBT_PBUS_BAAW_5_START +#define WLBT_PBUS_BAAW_5_START 0xA0120000 +#define WLBT_PBUS_BAAW_5_END 0xA0160000 + +#define WLBT_PBUS_MBOX_CP2WLBT_BASE 0x11950000//0xA0000000 +#define WLBT_PBUS_MBOX_SHUB2WLBT_BASE 0x119A0000//0xA0010000 +#define WLBT_PBUS_USI_CMG00_BASE 0x11D00000//0xA0060000 +#define WLBT_PBUS_SYSREG_CMGP2WLBT_BASE 0x11C80000//0xA0100000 +#define WLBT_PBUS_GPIO_CMGP_BASE 0x11C20000//0xA0110000 +#define WLBT_PBUS_SHUB_BASE 0x11200000//0xA0120000 + +/* EMA settings overloaded onto CHIP_VERSION_ID SFR + * (remap block) + */ +#define CHIP_VERSION_ID_VER_MASK 0xffc00000 /* [22:32] Version ID */ +#define CHIP_VERSION_ID_EMA_MASK 0x003fffff /* [0:21] EMA params */ +#define CHIP_VERSION_ID_EMA_VALUE (BIT(20) | \ + BIT(18) | \ + BIT(13) | \ + BIT(11) | \ + BIT(5) | \ + BIT(2) ) + +/* Power down registers */ +#define RESET_AHEAD_WLBT_SYS_PWR_REG 0x1360 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define CLEANY_BUS_WLBT_SYS_PWR_REG 0x1364 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define LOGIC_RESET_WLBT_SYS_PWR_REG 0x1368 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define TCXO_GATE_WLBT_SYS_PWR_REG 0x136C /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WLBT_DISABLE_ISO_SYS_PWR_REG 0x1370 /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WLBT_RESET_ISO_SYS_PWR_REG 0x1374 /* Control power state in LOWPWR mode 1 - on, 0 */ + +#define CENTRAL_SEQ_WLBT_CONFIGURATION 0x0180 /* bit 16. Decides whether system-level low-power mode + * is used HIGH: System-level Low-Power mode + * disabled. LOW: System-level Low-Power mode + * enabled. When system enters low-power mode, + * this field is automatically cleared to HIGH. + */ + +#define CENTRAL_SEQ_WLBT_STATUS 0x0184 /* 23:16 Check statemachine status */ +#define STATES 0xff0000 + +#define SYS_PWR_CFG BIT(0) +#define SYS_PWR_CFG_2 (BIT(0) | BIT(1)) +#define SYS_PWR_CFG_16 BIT(16) + +/* TZASC (TrustZone Address Space Controller) configuration for Katmai onwards */ +#define EXYNOS_SET_CONN_TZPC 0 +//#define SMC_CMD_CONN_IF 0x82000710 +#endif /* __MIF_REG_9610_H */ diff --git a/drivers/misc/samsung/scsc/mif_reg_S5E9630.h b/drivers/misc/samsung/scsc/mif_reg_S5E9630.h new file mode 100644 index 000000000000..3e6d2f55d8d9 --- /dev/null +++ b/drivers/misc/samsung/scsc/mif_reg_S5E9630.h @@ -0,0 +1,320 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIF_REG_9630_H +#define __MIF_REG_9630_H + +/*********************************/ +/* PLATFORM register definitions */ +/*********************************/ +#define NUM_MBOX_PLAT 8 +#define NUM_SEMAPHORE 12 + +#define MAILBOX_WLBT_BASE 0x0000 +#define MAILBOX_WLBT_REG(r) (MAILBOX_WLBT_BASE + (r)) +#define MCUCTRL 0x000 /* MCU Controller Register */ +/* R0 [31:16] - Int FROM R4/M4 */ +#define INTGR0 0x008 /* Interrupt Generation Register 0 (r/w) */ +#define INTCR0 0x00C /* Interrupt Clear Register 0 (w) */ +#define INTMR0 0x010 /* Interrupt Mask Register 0 (r/w) */ +#define INTSR0 0x014 /* Interrupt Status Register 0 (r) */ +#define INTMSR0 0x018 /* Interrupt Mask Status Register 0 (r) */ +/* R1 [15:0] - Int TO R4/M4 */ +#define INTGR1 0x01c /* Interrupt Generation Register 1 */ +#define INTCR1 0x020 /* Interrupt Clear Register 1 */ +#define INTMR1 0x024 /* Interrupt Mask Register 1 */ +#define INTSR1 0x028 /* Interrupt Status Register 1 */ +#define INTMSR1 0x02c /* Interrupt Mask Status Register 1 */ +#define MIF_INIT 0x04c /* MIF_init */ +#define IS_VERSION 0x050 /* Version Information Register */ +#define ISSR_BASE 0x080 /* IS_Shared_Register Base address */ +#define ISSR(r) (ISSR_BASE + (4 * (r))) +#define SEMAPHORE_BASE 0x180 /* IS_Shared_Register Base address */ +#define SEMAPHORE(r) (SEMAPHORE_BASE + (4 * (r))) +#define SEMA0CON 0x1c0 +#define SEMA0STATE 0x1c8 +#define SEMA1CON 0x1e0 +#define SEMA1STATE 0x1e8 + +#define WLBT_PBUS_BASE 0x14400000 + +// CBUS : APM_BUS +// PBUS : CFG_BUS + +/* New WLBT SFRs for MEM config */ +#define WLBT_PBUS_D_TZPC_SFR (WLBT_PBUS_BASE + 0x10000) +#define WLBT_PBUS_BAAW_DBUS (WLBT_PBUS_BASE + 0x20000) +#define WLBT_PBUS_BAAW_CBUS (WLBT_PBUS_BASE + 0x30000) +#define WLBT_PBUS_SMAPPER (WLBT_PBUS_BASE + 0x40000) +#define WLBT_PBUS_SYSREG (WLBT_PBUS_BASE + 0x50000) +#define WLBT_PBUS_BOOT (WLBT_PBUS_BASE + 0x60000) + +#define TZPC_PROT0STAT 0x14410200 +#define TZPC_PROT0SET 0x14410204 + +/* POWER */ + +/* Exynos 9630 UM - 9.8.719 */ +#define WLBT_CONFIGURATION 0x3300 +#define LOCAL_PWR_CFG BIT(0) /* Control power state 0: Power down 1: Power on */ + +/* Exynos 9630 UM - 9.8.720 */ +#define WLBT_STATUS 0x3304 +#define STATUS BIT(0) /* Status 0 : Power down 1 : Power on */ + +/* Exynos 9630 UM - 9.8.721 */ +#define WLBT_STATES 0x3308 /* STATES [7:0] States index for debugging + * 0x00 : Reset + * 0x10 : Power up + * 0x80 : Power down + * */ + +/* Exynos 9630 UM - 9.8.723 */ +#define WLBT_CTRL_NS 0x3310 /* WLBT Control SFR non-secure */ +#define WLBT_ACTIVE_CLR BIT(8) /* WLBT_ACTIVE_REQ is clear internally on WAKEUP */ +#define WLBT_ACTIVE_EN BIT(7) /* Enable of WIFI_ACTIVE_REQ */ +#define SW_TCXO_REQ BIT(6) /* SW TCXO Request register, if MASK_TCXO_REQ + * filed value is 1, This register value control TCXO Request*/ +#define MASK_TCXO_REQ BIT(5) /* 1:mask TCXO_REQ coming from CP, + * 0:enable request source + */ +#define TCXO_GATE BIT(4) /* TCXO gate control 0: TCXO enabled 1: TCXO gated */ +/*#define SET_SW_MIF_REQ BIT(13)*/ /* MIF SLEEP control by SW 1: if MASK_MIF_REQ is + * set to HIGH, MIF enters into down state by + * SET_SW_MIF_REQ. + */ +/*#define MASK_MIF_REQ BIT(12)*/ /* 1:mask MIF_REQ coming from WLBT, 0 : disable */ +/*#define RTC_OUT_EN BIT(10)*/ /* 1:enable, 0 : disable This is enable signal on RTC + * CLK(32KHz). This clock can be used as WLBT PMU + * clock when WLBT is internal power-down and + * TCXO(26MHz) is disable at WLBT side. + */ + + +/*------------------------------------*/ + +//??????#define WLBT_PWRON BIT(1) +#define WLBT_RESET_SET BIT(0) /* WLBT reset assertion control by using + * PMU_ALIVE_WLBT. + * 0x1: Reset Assertion, + * 0x0: Reset Release + */ +#define WLBT_RESET_REQ_EN BIT(7) /* 1:enable, 0:disable Enable of WLBT_RESET_REQ */ +#define WLBT_RESET_REQ_CLR BIT(8) /* WLBT_RESET_REQ is clear internally on WAKEUP */ +#define MASK_PWR_REQ BIT(1) /* 1:mask PWR_REQ coming from WLBT, 0 : disable */ +#define TCXO_ENABLE_SW BIT(1) /* 1:enable, 0 : disable This is enable signal on TCXO + * clock of WLBT. This signal can decide whether TCXO + * clock is active by software when WLBT is internal + * power-down or WLBT is in reset state at WLBT side. if + * this value is HIGH, TCXO is active regardless of + * hardware control + */ +/* from wlbt_if_S5E7920.h excite code */ + +/* PMU_ALIVE Bit Field */ +/* WLBT_CTRL_NS */ +//#define CLEANY_BYPASS_DATA_EN BIT(16) +//#define SET_SW_MIF_REQ BIT(13) +//#define MASK_MIF_REQ BIT(12) +//#define RTC_OUT_EN BIT(10) +//#define MASK_WLBT_PWRDN_DONE BIT(9) +//#define WLBT_RESET_REQ_CLR BIT(8) +//#define WLBT_RESET_REQ_EN BIT(7) +//#define WLBT_ACTIVE_CLR BIT(6) +//#define WLBT_ACTIVE_EN BIT(5) +//#define WLBT_RESET_SET BIT(0) +//#define WLBT_PWRON BIT(1) + +/* WLBT_CTRL_S */ +//#define WLBT_START BIT(0) + +/* WLBT_STAT */ +//#define WLBT_ACCESS_MIF BIT(4) +//#define WLBT_PWRDN_DONE BIT(0) + +/* WLBT_DEBUG */ +//#define MASK_CLKREQ_WLBT BIT(8) +//#define EN_PWR_REQ BIT(5) +//#define EN_WLBT_WAKEUP_REQ BIT(4) +//#define EN_WLBT_RESET_REQ BIT(3) +//#define EN_WLBT_ACTIVE BIT(2) +//#define EN_MIF_REQ BIT(0) + +/* WLBT_BOOT_TEST_RST_CONFIG */ +#define WLBT_IRAM_BOOT_OFFSET (BIT(15) | BIT(14) | BIT(13) | BIT(12) | BIT(11) | BIT(10) | BIT(9) | BIT(8)) +#define WLBT_IRAM_BOOT_TEST BIT(5) +#define WLBT_NOREMAP_BOOT_TEST BIT(4) +#define WLBT2AP_PERI_PROT2 BIT(2) + +/* WLBT_QOS */ +#define WLBT_AWQOS (BIT(19) | BIT(18) | BIT(17) | BIT(16)) +#define WLBT_ARQOS (BIT(11) | BIT(10) | BIT(9) | BIT(8)) +#define WLBT_QOS_OVERRIDE BIT(0) + +/*------------------------------------*/ + +/* Exynos 9630 UM - 9.8.724 */ +#define WLBT_CTRL_S 0x3314 /* WLBT Control SFR secure */ +#define WLBT_START BIT(3) /* CP control enable 0: Disable 1: Enable */ + +/* Exynos 9630 UM - 9.8.725 */ +#define WLBT_OUT 0x3320 +#define SWEEPER_BYPASS BIT(13) /* SWEEPER bypass mode control(WLBT2AP path) If + * this bit is set to 1, SWEEPER is bypass mode. + */ +#define SWEEPER_PND_CLR_REQ BIT(7) /* SWEEPER_CLEAN Request. SWPPER is the IP + * that can clean up hung transaction in the Long hop + * async Bus Interface, when get hung + * state. 0: Normal 1: SWEEPER CLEAN Requested + */ + +/* Exynos 9630 UM - 9.8.726 */ +#define WLBT_IN 0x3324 +#define BUS_READY BIT(4) /* BUS ready indication signal when reset released. 0: + * Normal 1: BUS ready state */ +#define PWRDOWN_IND BIT(2) /* PWRDOWN state indication 0: Normal 1: In the + * power down state */ +#define SWEEPER_PND_CLR_ACK BIT(0) /* SWEEPER_CLEAN ACK signal. SWPPER is the IP + * that can clean up hung transaction in the Long hop + * async Bus Interface, when get hung + * state. 0: Normal 1: SWEEPER CLEAN + * Acknowledged */ +/* Exynos 9630 UM - 9.8.728 */ +#define WLBT_INT_EN 0x3344 +#define PWR_REQ_F BIT(3) +#define TCXO_REQ_F BIT(5) + +/* Exynos 9630 UM - 9.8.10 */ +#define WLBT_STAT 0x0058 +#define WLBT_PWRDN_DONE BIT(0) /* Check WLBT power-down status.*/ +#define WLBT_ACCESS_MIF BIT(4) /* Check whether WLBT accesses MIF domain */ + +/* Exynos 9630 UM - 9.8.11 */ +#define WLBT_DEBUG 0x005c /* MIF sleep, wakeup debugging control */ +/* need to find where have they moved */ +//#define EN_MIF_REQ BIT(0) /* Control MIF_REQ through GPIO_ALIVE. */ +//#define EN_WLBT_ACTIVE BIT(2) /* Control WLBT_ACTIVE through GPIO_ALIVE. */ +//#define EN_WLBT_RESET_REQ BIT(3) /* Control WLBT_RESET_REQ through GPIO_ALIVE. */ +#define MASK_CLKREQ_WLBT BIT(8) /* When this field is set to HIGH, ALIVE ignores + * CLKREQ from WLBT. + */ + +#define RESET_SEQUENCER_STATUS 0x0504 +#define RESET_STATUS_MASK (BIT(10)|BIT(9)|BIT(8)) +#define RESET_STATUS (5 << 8) + +#define PMU_SHARED_PWR_REQ_WLBT_CONTROL_STATUS 0x8008 +#define CTRL_STATUS_MASK 0x1 + +#define CLEANY_BUS_WLBT_CONFIGURATION 0x3b20 +#define CLEANY_CFG_MASK 0x1 + +#define CLEANY_BUS_WLBT_STATUS 0x3b24 +#define CLEANY_STATUS_MASK (BIT(17)|BIT(16)) + +/* Exynos 9630 UM - 9.8.763 */ +#define SYSTEM_OUT 0x3a20 +#define PWRRGTON_CON BIT(9) /* XPWRRTON_CON control 0: Disable 1: Enable */ + +/* Exynos 9630 UM - 9.8.812 */ +#define TCXO_BUF_CTRL 0x3c10 +#define TCXO_BUF_BIAS_EN_WLBT BIT(2) +#define TCXO_BUF_EN_WLBT BIT(3) + +/* New WLBT SFRs for MEM config */ + +/* end address is exclusive so the ENDx register should be set to the first + * address that is not accessible through that BAAW. + * + * Another very important point to note here is we are using BAAW0 to expose + * 16MB region, so other BAAWs can be used for other purposes + */ +#define WLBT_DBUS_BAAW_0_START 0x80000000 // Start of DRAM for WLBT R7 +#define WLBT_DBUS_BAAW_0_END WLBT_DBUS_BAAW_4_START // 16 MB +#define WLBT_DBUS_BAAW_1_START 0x80400000 +#define WLBT_DBUS_BAAW_1_END WLBT_DBUS_BAAW_2_START +#define WLBT_DBUS_BAAW_2_START 0x80800000 +#define WLBT_DBUS_BAAW_2_END WLBT_DBUS_BAAW_3_START +#define WLBT_DBUS_BAAW_3_START 0x80C00000 +#define WLBT_DBUS_BAAW_3_END WLBT_DBUS_BAAW_4_START +#define WLBT_DBUS_BAAW_4_START 0x81000000 +#define WLBT_DBUS_BAAW_4_END 0x813FFFFF + +#define WLBT_BAAW_CON_INIT_DONE (1 << 31) +#define WLBT_BAAW_CON_EN_WRITE (1 << 1) +#define WLBT_BAAW_CON_EN_READ (1 << 0) +#define WLBT_BAAW_ACCESS_CTRL (WLBT_BAAW_CON_INIT_DONE | WLBT_BAAW_CON_EN_WRITE | WLBT_BAAW_CON_EN_READ) + +/* ref Confluence Maxwell450+Memory+Map */ +#define WLBT_CBUS_BAAW_0_START 0xA0000000 // CP2WLBT MBOX +#define WLBT_CBUS_BAAW_0_END 0xA000FFFF + +#define WLBT_CBUS_BAAW_1_START 0xA0010000 // GNSS,APM,AP,ABOX,CHUB2WLBT MBOX +#define WLBT_CBUS_BAAW_1_END 0xA005FFFF + +#define WLBT_CBUS_BAAW_2_START 0xA0060000 // CMGP SFR GPIO_CMGP_BASE +#define WLBT_CBUS_BAAW_2_END 0xA009FFFF + +#define WLBT_CBUS_BAAW_3_START 0xA00A0000 // CMGP SFR SYSREG_CMGP2WLBT_BASE +#define WLBT_CBUS_BAAW_3_END 0xA00CFFFF + +#define WLBT_CBUS_BAAW_4_START 0xA00D0000 // CMGP SFR USI_CMG00_BASE +#define WLBT_CBUS_BAAW_4_END 0xA015FFFF + +#define WLBT_CBUS_BAAW_5_START 0xA0160000 // CHUB SFR CHUB_USICHUB0_BASE +#define WLBT_CBUS_BAAW_5_END 0xA01BFFFF + +#define WLBT_CBUS_BAAW_6_START 0xA01C0000 // CHUB SFR CHUB_BASE +#define WLBT_CBUS_BAAW_6_END 0xA01EFFFF + +#define WLBT_PBUS_MBOX_CP2WLBT_BASE 0x10F50000 +#define WLBT_PBUS_MBOX_GNSS2WLBT_BASE 0x10FA0000 +#define WLBT_PBUS_MBOX_SHUB2WLBT_BASE 0x119A0000 +#define WLBT_PBUS_USI_CMG00_BASE 0x11500000 +#define WLBT_PBUS_SYSREG_CMGP2WLBT_BASE 0x11490000 +#define WLBT_PBUS_GPIO_CMGP_BASE 0x11430000 +#define WLBT_PBUS_CHUB_USICHUB0_BASE 0x11B70000 +#define WLBT_PBUS_CHUB_BASE 0x11A00000 + + + +/* EMA settings overloaded onto CHIP_VERSION_ID SFR + * (remap block) + */ +#define CHIP_VERSION_ID_VER_MASK 0xffc00000 /* [22:32] Version ID */ +#define CHIP_VERSION_ID_EMA_MASK 0x003fffff /* [0:21] EMA params */ +#define CHIP_VERSION_ID_EMA_VALUE (BIT(20) | \ + BIT(18) | \ + BIT(13) | \ + BIT(11) | \ + BIT(5) | \ + BIT(2) ) +/* Power down registers */ +#define RESET_AHEAD_WLBT_SYS_PWR_REG 0x1360 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define CLEANY_BUS_WLBT_SYS_PWR_REG 0x1364 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define LOGIC_RESET_WLBT_SYS_PWR_REG 0x1368 /* Control power state in LOWPWR mode 1 - on, 0 - down*/ +#define TCXO_GATE_WLBT_SYS_PWR_REG 0x136C /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WLBT_DISABLE_ISO_SYS_PWR_REG 0x1370 /* Control power state in LOWPWR mode 1 - on, 0 */ +#define WLBT_RESET_ISO_SYS_PWR_REG 0x1374 /* Control power state in LOWPWR mode 1 - on, 0 */ + +#define CENTRAL_SEQ_WLBT_CONFIGURATION 0x0180 /* bit 16. Decides whether system-level low-power mode + * is used HIGH: System-level Low-Power mode + * disabled. LOW: System-level Low-Power mode + * enabled. When system enters low-power mode, + * this field is automatically cleared to HIGH. + */ + +//#define CENTRAL_SEQ_WLBT_STATUS 0x0184 /* 23:16 Check statemachine status */ +//#define STATES 0xff0000 + +#define SYS_PWR_CFG BIT(0) +#define SYS_PWR_CFG_2 (BIT(0) | BIT(1)) +#define SYS_PWR_CFG_16 BIT(16) + +/* TZASC (TrustZone Address Space Controller) configuration for Katmai onwards */ +#define EXYNOS_SET_CONN_TZPC 0 +#define SMC_CMD_CONN_IF (0x82000710) +#endif /* __MIF_REG_9630_H */ diff --git a/drivers/misc/samsung/scsc/mif_reg_smapper.h b/drivers/misc/samsung/scsc/mif_reg_smapper.h new file mode 100755 index 000000000000..3a2294f20194 --- /dev/null +++ b/drivers/misc/samsung/scsc/mif_reg_smapper.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIF_REG_SMAPPER_H +#define __MIF_REG_SMAPPER_H + +/***************************************/ +/* SMAPPER v2.0.1 register definitions */ +/***************************************/ +#define NUM_BANKS_160 4 +#define NUM_BANKS_64 7 +#define NUM_BANKS (NUM_BANKS_160 + NUM_BANKS_64) + +/* It enables CP_ADDR_MAP */ +#define ADDR_MAP_EN_BASE 0x000 +#define ADDR_MAP_EN(b) (ADDR_MAP_EN_BASE + (0x10 * (b))) + +/* SRAM write control. Set this register before CP initializes SRAM. + * If disable this bit, CP cannot acces SRAM by APB I/F. You need to + * disable ADDR_MAP_CTRL before you set this bit 1'b1 means + * SRAM write, 1'b0 means SRAM read + */ +#define SRAM_WRITE_CTRL_BASE 0x004 +#define SRAM_WRITE_CTRL(b) (SRAM_WRITE_CTRL_BASE + (0x10 * (b))) + +/* It defines the start address of CP virtual addres for 0-31. You + * need to disable ADDR_MAP_EN before you set this bit + */ +#define START_ADDR_BASE 0x008 +#define START_ADDR(b) (START_ADDR_BASE + (0x10 * (b))) + +/* For CP_ADDR_GRANULARITY between 0-31. You + * need to disable ADDR_MAP_EN before you set this bit + */ +#define ADDR_GRANULARITY_BASE 0x00c +#define ADDR_GRANULARITY(b) (ADDR_GRANULARITY_BASE + (0x10 * (b))) + +/* It defines the MSB part of 36-bit AP phys address [35:0]. It is starting + * point of access permission. + */ +#define AW_START_ADDR 0x100 +/* It defines the MSB part of 36-bit AP phys address [35:0]. It is end + * point of access permission. + */ +#define AW_END_ADDR 0x104 +/* It defines out-of-bound of access windows when it is set to 1'b1 by + * Access Window + */ +#define AW_ADDR_MAP_STATUS 0x200 +#define ORIGIN_ADDR_AR 0x204 +#define ORIGIN_ADDR_AW 0x208 +/* Read APB bus errors */ +#define APB_STATUS_0 0x300 +#define APB_STATUS_1 0x304 + +/* The Q-channel interfaces enable communication to an external + * power controller + */ +#define SMAPPER_QCH_DISABLE 0x400 + +/* SRAM r/w addres + * PWDATA[24:0] is used for 25'b SRAM read/write. + * Only can access ADDR_MAP_EN is disabled + */ +#define SRAM_BANK_BASE 0x1000 + +#define SRAM_BANK_INDEX(b, r) ((SRAM_BANK_BASE + (b * 0x400)) + (4 * (r))) + +#define ADDR_MAP_EN_BIT BIT(0) + +#endif /*_MIF_REG_SMAPPER_H*/ diff --git a/drivers/misc/samsung/scsc/mifintrbit.c b/drivers/misc/samsung/scsc/mifintrbit.c new file mode 100644 index 000000000000..fb6a55447b5c --- /dev/null +++ b/drivers/misc/samsung/scsc/mifintrbit.c @@ -0,0 +1,263 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Uses */ +#include +#include +#include +#include +#include "scsc_mif_abs.h" + +/* Implements */ +#include "mifintrbit.h" + +/* default handler just logs a warning and clears the bit */ +static void mifintrbit_default_handler(int irq, void *data) +{ + struct mifintrbit *intr = (struct mifintrbit *)data; + unsigned long flags; + + spin_lock_irqsave(&intr->spinlock, flags); + intr->mif->irq_bit_clear(intr->mif, irq); + spin_unlock_irqrestore(&intr->spinlock, flags); +} + +static void print_bitmaps(struct mifintrbit *intr) +{ + unsigned long dst1, dst2, dst3; + + bitmap_copy_le(&dst1, intr->bitmap_tohost, MIFINTRBIT_NUM_INT); + bitmap_copy_le(&dst2, intr->bitmap_fromhost_r4, MIFINTRBIT_NUM_INT); + bitmap_copy_le(&dst3, intr->bitmap_fromhost_m4, MIFINTRBIT_NUM_INT); +} + +static void mifiintrman_isr(int irq, void *data) +{ + struct mifintrbit *intr = (struct mifintrbit *)data; + unsigned long flags; + unsigned long int irq_reg = 0; + int bit; + + /* Avoid unused parameter error */ + (void)irq; + + spin_lock_irqsave(&intr->spinlock, flags); + irq_reg = intr->mif->irq_get(intr->mif); + + print_bitmaps(intr); + for_each_set_bit(bit, &irq_reg, MIFINTRBIT_NUM_INT) { + if (intr->mifintrbit_irq_handler[bit] != mifintrbit_default_handler) + intr->mifintrbit_irq_handler[bit](bit, intr->irq_data[bit]); + } + + spin_unlock_irqrestore(&intr->spinlock, flags); +} + +/* Public functions */ +int mifintrbit_alloc_tohost(struct mifintrbit *intr, mifintrbit_handler handler, void *data) +{ + struct scsc_mif_abs *mif; + unsigned long flags; + int which_bit = 0; + + spin_lock_irqsave(&intr->spinlock, flags); + + /* Search for free slots */ + which_bit = find_first_zero_bit(intr->bitmap_tohost, MIFINTRBIT_NUM_INT); + + if (which_bit >= MIFINTRBIT_NUM_INT) + goto error; + + if (intr->mifintrbit_irq_handler[which_bit] != mifintrbit_default_handler) { + spin_unlock_irqrestore(&intr->spinlock, flags); + goto error; + } + + /* Get abs implementation */ + mif = intr->mif; + + /* Mask to prevent spurious incoming interrupts */ + mif->irq_bit_mask(mif, which_bit); + /* Clear the interrupt */ + mif->irq_bit_clear(mif, which_bit); + + /* Register the handler */ + intr->mifintrbit_irq_handler[which_bit] = handler; + intr->irq_data[which_bit] = data; + + /* Once registration is set, and IRQ has been cleared, unmask the interrupt */ + mif->irq_bit_unmask(mif, which_bit); + + /* Update bit mask */ + set_bit(which_bit, intr->bitmap_tohost); + + spin_unlock_irqrestore(&intr->spinlock, flags); + + return which_bit; + +error: + spin_unlock_irqrestore(&intr->spinlock, flags); + SCSC_TAG_ERR(MIF, "Error registering irq\n"); + return -EIO; +} + +int mifintrbit_free_tohost(struct mifintrbit *intr, int which_bit) +{ + struct scsc_mif_abs *mif; + unsigned long flags; + + if (which_bit >= MIFINTRBIT_NUM_INT) + goto error; + + spin_lock_irqsave(&intr->spinlock, flags); + /* Get abs implementation */ + mif = intr->mif; + + /* Mask to prevent spurious incoming interrupts */ + mif->irq_bit_mask(mif, which_bit); + /* Set the handler with default */ + intr->mifintrbit_irq_handler[which_bit] = mifintrbit_default_handler; + intr->irq_data[which_bit] = NULL; + /* Clear the interrupt for hygiene */ + mif->irq_bit_clear(mif, which_bit); + /* Update bit mask */ + clear_bit(which_bit, intr->bitmap_tohost); + spin_unlock_irqrestore(&intr->spinlock, flags); + + return 0; + +error: + SCSC_TAG_ERR(MIF, "Error unregistering irq\n"); + return -EIO; +} + +int mifintrbit_alloc_fromhost(struct mifintrbit *intr, enum scsc_mif_abs_target target) +{ + unsigned long flags; + int which_bit = 0; + unsigned long *p; + + + spin_lock_irqsave(&intr->spinlock, flags); + + if (target == SCSC_MIF_ABS_TARGET_R4) + p = intr->bitmap_fromhost_r4; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (target == SCSC_MIF_ABS_TARGET_M4) + p = intr->bitmap_fromhost_r4; + else if (target == SCSC_MIF_ABS_TARGET_M4_1) + p = intr->bitmap_fromhost_r4; +#else + else if (target == SCSC_MIF_ABS_TARGET_M4) + p = intr->bitmap_fromhost_m4; +#endif + else + goto error; + + /* Search for free slots */ + which_bit = find_first_zero_bit(p, MIFINTRBIT_NUM_INT); + + if (which_bit == MIFINTRBIT_NUM_INT) + goto error; + + /* Update bit mask */ + set_bit(which_bit, p); + + spin_unlock_irqrestore(&intr->spinlock, flags); + + return which_bit; +error: + spin_unlock_irqrestore(&intr->spinlock, flags); + SCSC_TAG_ERR(MIF, "Error allocating bit %d on %s\n", + which_bit, target ? "M4" : "R4"); + return -EIO; +} + +int mifintrbit_free_fromhost(struct mifintrbit *intr, int which_bit, enum scsc_mif_abs_target target) +{ + unsigned long flags; + unsigned long *p; + + spin_lock_irqsave(&intr->spinlock, flags); + + if (which_bit >= MIFINTRBIT_NUM_INT) + goto error; + + if (target == SCSC_MIF_ABS_TARGET_R4) + p = intr->bitmap_fromhost_r4; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (target == SCSC_MIF_ABS_TARGET_M4) + p = intr->bitmap_fromhost_r4; + else if (target == SCSC_MIF_ABS_TARGET_M4_1) + p = intr->bitmap_fromhost_r4; +#else + else if (target == SCSC_MIF_ABS_TARGET_M4) + p = intr->bitmap_fromhost_m4; +#endif + else + goto error; + + /* Clear bit mask */ + clear_bit(which_bit, p); + spin_unlock_irqrestore(&intr->spinlock, flags); + + return 0; +error: + spin_unlock_irqrestore(&intr->spinlock, flags); + SCSC_TAG_ERR(MIF, "Error freeing bit %d on %s\n", + which_bit, target ? "M4" : "R4"); + return -EIO; +} + +/* core API */ +void mifintrbit_deinit(struct mifintrbit *intr) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&intr->spinlock, flags); + /* Set all handlers to default before unregistering the handler */ + for (i = 0; i < MIFINTRBIT_NUM_INT; i++) + intr->mifintrbit_irq_handler[i] = mifintrbit_default_handler; + intr->mif->irq_unreg_handler(intr->mif); + spin_unlock_irqrestore(&intr->spinlock, flags); +} + +void mifintrbit_init(struct mifintrbit *intr, struct scsc_mif_abs *mif) +{ + int i; + + spin_lock_init(&intr->spinlock); + /* Set all handlersd to default before hooking the hardware interrupt */ + for (i = 0; i < MIFINTRBIT_NUM_INT; i++) + intr->mifintrbit_irq_handler[i] = mifintrbit_default_handler; + + /* reset bitmaps */ + bitmap_zero(intr->bitmap_tohost, MIFINTRBIT_NUM_INT); + bitmap_zero(intr->bitmap_fromhost_r4, MIFINTRBIT_NUM_INT); + bitmap_zero(intr->bitmap_fromhost_m4, MIFINTRBIT_NUM_INT); + + /** + * Pre-allocate/reserve MIF interrupt bits 0 in both + * .._fromhost_r4 and .._fromhost_m4 interrupt bits. + * + * These bits are used for purpose of forcing Panics from + * either MX manager or GDB monitor channels. + */ + set_bit(MIFINTRBIT_RESERVED_PANIC_R4, intr->bitmap_fromhost_r4); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + set_bit(MIFINTRBIT_RESERVED_PANIC_M4, intr->bitmap_fromhost_m4); + set_bit(MIFINTRBIT_RESERVED_PANIC_M4_1, intr->bitmap_fromhost_m4_1); +#else + set_bit(MIFINTRBIT_RESERVED_PANIC_M4, intr->bitmap_fromhost_m4); +#endif + + /* register isr with mif abstraction */ + mif->irq_reg_handler(mif, mifiintrman_isr, (void *)intr); + + /* cache mif */ + intr->mif = mif; +} diff --git a/drivers/misc/samsung/scsc/mifintrbit.h b/drivers/misc/samsung/scsc/mifintrbit.h new file mode 100644 index 000000000000..d8019736dcbf --- /dev/null +++ b/drivers/misc/samsung/scsc/mifintrbit.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIFINTRBIT_H +#define __MIFINTRBIT_H + +#include + +/** MIF Interrupt Bit Handler prototype. */ +typedef void (*mifintrbit_handler)(int which_bit, void *data); + +struct mifintrbit; /* fwd - opaque pointer */ + +#define MIFINTRBIT_NUM_INT 16 + +/** Reserve MIF interrupt bits 0 in the to-r4 and to-m4 registers for purpose of forcing panics */ +#define MIFINTRBIT_RESERVED_PANIC_R4 0 +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT +#define MIFINTRBIT_RESERVED_PANIC_M4 0 +#define MIFINTRBIT_RESERVED_PANIC_M4_1 0 +#else +#define MIFINTRBIT_RESERVED_PANIC_M4 0 +#endif + +void mifintrbit_init(struct mifintrbit *intr, struct scsc_mif_abs *mif); +void mifintrbit_deinit(struct mifintrbit *intr); + +/** Allocates TOHOST MIF interrupt bits, and associates handler for the AP bit. + * Returns the bit index.*/ +int mifintrbit_alloc_tohost(struct mifintrbit *intr, mifintrbit_handler handler, void *data); +/** Deallocates TOHOST MIF interrupt bits */ +int mifintrbit_free_tohost(struct mifintrbit *intr, int which_bit); +/* Get an interrupt bit associated with the target (R4/M4) -FROMHOST direction + * Function returns the IRQ bit associated , -EIO if error */ +int mifintrbit_alloc_fromhost(struct mifintrbit *intr, enum scsc_mif_abs_target target); +/* Free an interrupt bit associated with the target (R4/M4) -FROMHOST direction + * Function returns the 0 if succedes , -EIO if error */ +int mifintrbit_free_fromhost(struct mifintrbit *intr, int which_bit, enum scsc_mif_abs_target target); + + +struct mifintrbit { + void(*mifintrbit_irq_handler[MIFINTRBIT_NUM_INT]) (int irq, void *data); + void *irq_data[MIFINTRBIT_NUM_INT]; + struct scsc_mif_abs *mif; + /* Use spinlock is it may be in IRQ context */ + spinlock_t spinlock; + /* Interrupt allocation bitmaps */ + DECLARE_BITMAP(bitmap_tohost, MIFINTRBIT_NUM_INT); + DECLARE_BITMAP(bitmap_fromhost_r4, MIFINTRBIT_NUM_INT); + DECLARE_BITMAP(bitmap_fromhost_m4, MIFINTRBIT_NUM_INT); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + DECLARE_BITMAP(bitmap_fromhost_m4_1, MIFINTRBIT_NUM_INT); +#endif +}; + + +#endif diff --git a/drivers/misc/samsung/scsc/mifmboxman.c b/drivers/misc/samsung/scsc/mifmboxman.c new file mode 100644 index 000000000000..7d18555a71a9 --- /dev/null +++ b/drivers/misc/samsung/scsc/mifmboxman.c @@ -0,0 +1,116 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* uses */ +#include +#include +#include +#include +#include +#include "scsc_mif_abs.h" + +/* Implements */ +#include "mifmboxman.h" + +int mifmboxman_init(struct mifmboxman *mbox) +{ + if (mbox->in_use) + return -EBUSY; + + mutex_init(&mbox->lock); + mbox->mbox_free = MIFMBOX_NUM; + mbox->in_use = true; + bitmap_zero(mbox->bitmap, MIFMBOX_NUM); + + return 0; +} + +bool mifmboxman_alloc_mboxes(struct mifmboxman *mbox, int n, int *first_mbox_index) +{ + unsigned int index = 0; + unsigned int available; + u8 i; + + mutex_lock(&mbox->lock); + + if ((n > MIFMBOX_NUM) || (n == 0) || !mbox->in_use) + goto error; + + while (index <= (MIFMBOX_NUM - n)) { + available = 0; + + /* Search consecutive blocks */ + for (i = 0; i < n; i++) { + if (test_bit((i + index), mbox->bitmap)) + break; + available++; + } + + if (available == n) { + *first_mbox_index = index; + + for (i = 0; i < n; i++) + set_bit(index++, mbox->bitmap); + + mbox->mbox_free -= n; + goto exit; + } else + index = index + available + 1; + } +error: + SCSC_TAG_ERR(MIF, "Error allocating mbox\n"); + mutex_unlock(&mbox->lock); + return false; +exit: + mutex_unlock(&mbox->lock); + return true; + +} + +void mifmboxman_free_mboxes(struct mifmboxman *mbox, int first_mbox_index, int n) +{ + int index = 0; + int total_free = 0; + + mutex_lock(&mbox->lock); + if ((n > MIFMBOX_NUM) || + ((n + first_mbox_index) > MIFMBOX_NUM) || + (n == 0) || + !mbox->in_use) + goto error; + + for (index = first_mbox_index; index < (first_mbox_index + n); index++) + if (test_bit(index, mbox->bitmap)) { + clear_bit(index, mbox->bitmap); + total_free++; + } + + mbox->mbox_free += total_free; + mutex_unlock(&mbox->lock); + return; +error: + SCSC_TAG_ERR(MIF, "Error freeing mbox\n"); + mutex_unlock(&mbox->lock); +} + +u32 *mifmboxman_get_mbox_ptr(struct mifmboxman *mbox, struct scsc_mif_abs *mif_abs, int mbox_index) +{ + /* Avoid unused parameter error */ + (void)mbox; + + return mif_abs->get_mbox_ptr(mif_abs, mbox_index); +} + + +int mifmboxman_deinit(struct mifmboxman *mbox) +{ + mutex_lock(&mbox->lock); + if (!mbox->in_use) + return -ENODEV; + mbox->in_use = false; + mutex_unlock(&mbox->lock); + return 0; +} diff --git a/drivers/misc/samsung/scsc/mifmboxman.h b/drivers/misc/samsung/scsc/mifmboxman.h new file mode 100644 index 000000000000..81f3da96937a --- /dev/null +++ b/drivers/misc/samsung/scsc/mifmboxman.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIFMBOXMAN_H +#define __MIFMBOXMAN_H + +#include + +/* TODO: Needs to define the max mem */ + +struct mifmboxman; +struct scsc_mif_abs; +struct mutex; + +int mifmboxman_init(struct mifmboxman *mbox); +bool mifmboxman_alloc_mboxes(struct mifmboxman *mbox, int n, int *first_mbox_index); +void mifmboxman_free_mboxes(struct mifmboxman *mbox, int first_mbox_index, int n); +u32 *mifmboxman_get_mbox_ptr(struct mifmboxman *mbox, struct scsc_mif_abs *mif_abs, int mbox_index); +int mifmboxman_deinit(struct mifmboxman *mbox); + +#ifdef CONFIG_SOC_EXYNOS7570 +#define MIFMBOX_NUM 8 +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) || defined(CONFIG_SOC_EXYNOS9610) +#define MIFMBOX_NUM 4 +#else /* emulation */ +#define MIFMBOX_NUM 8 +#endif + +/* Inclusion in core.c treat it as opaque */ +struct mifmboxman { + bool in_use; + u32 mbox_free; + DECLARE_BITMAP(bitmap, MIFMBOX_NUM); + struct mutex lock; +}; +#endif diff --git a/drivers/misc/samsung/scsc/mifproc.c b/drivers/misc/samsung/scsc/mifproc.c new file mode 100644 index 000000000000..c162accc18d3 --- /dev/null +++ b/drivers/misc/samsung/scsc/mifproc.c @@ -0,0 +1,519 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include "mifproc.h" +#include "scsc_mif_abs.h" +#include "miframman.h" + +#define MX_MAX_PROC_RAMMAN 2 /* Number of RAMMANs to track */ + +static struct proc_dir_entry *procfs_dir; +static struct proc_dir_entry *procfs_dir_ramman[MX_MAX_PROC_RAMMAN]; + +/* WARNING --- SINGLETON FOR THE TIME BEING */ +/* EXTEND PROC ENTRIES IF NEEDED!!!!! */ +struct scsc_mif_abs *mif_global; + +static int mifprocfs_open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = MIF_PDE_DATA(inode); + return 0; +} + +MIF_PROCFS_RW_FILE_OPS(mif_dump); +MIF_PROCFS_RW_FILE_OPS(mif_writemem); +MIF_PROCFS_RW_FILE_OPS(mif_reg); + +/* miframman ops */ +MIF_PROCFS_RO_FILE_OPS(ramman_total); +MIF_PROCFS_RO_FILE_OPS(ramman_offset); +MIF_PROCFS_RO_FILE_OPS(ramman_start); +MIF_PROCFS_RO_FILE_OPS(ramman_free); +MIF_PROCFS_RO_FILE_OPS(ramman_used); +MIF_PROCFS_RO_FILE_OPS(ramman_size); + +MIF_PROCFS_SEQ_FILE_OPS(ramman_list); + +static ssize_t mifprocfs_mif_reg_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + /* Avoid unused parameter error */ + (void)file; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + + +static ssize_t mifprocfs_mif_writemem_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + /* Avoid unused parameter error */ + (void)file; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mifprocfs_mif_dump_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + /* Avoid unused parameter error */ + (void)file; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mifprocfs_mif_writemem_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + char *sptr, *token; + unsigned int len = 0, pass = 0; + u32 value = 0, address = 0; + int match = 0; + void *mem; + + /* Avoid unused parameter error */ + (void)file; + (void)ppos; + + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + while ((token = strsep(&sptr, " ")) != NULL) { + switch (pass) { + /* register */ + case 0: + if ((token[0] == '0') && (token[1] == 'x')) { + if (kstrtou32(token, 16, &address)) { + SCSC_TAG_INFO(MIF, "Wrong format:
\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); + goto error; + } + } else { + SCSC_TAG_INFO(MIF, "Wrong format:
\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); + goto error; + } + break; + /* value */ + case 1: + if ((token[0] == '0') && (token[1] == 'x')) { + if (kstrtou32(token, 16, &value)) { + SCSC_TAG_INFO(MIF, "Wrong format:
\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); + goto error; + } + } else { + SCSC_TAG_INFO(MIF, "Wrong format:
\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); + goto error; + } + break; + } + pass++; + } + if (pass != 2 && !match) { + SCSC_TAG_INFO(MIF, "Wrong format:
\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); + goto error; + } + + /* Get memory offset */ + mem = mif_global->get_mifram_ptr(mif_global, 0); + if (!mem) { + SCSC_TAG_INFO(MIF, "Mem not allocated\n"); + goto error; + } + + SCSC_TAG_INFO(MIF, "Setting value 0x%x at address 0x%x offset\n", value, address); + + + *((u32 *)(mem + address)) = value; +error: + return count; +} + +static ssize_t mifprocfs_mif_dump_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + char *sptr, *token; + unsigned int len = 0, pass = 0; + u32 address = 0; + u32 size; + u8 unit; + void *mem; + + (void)file; + (void)ppos; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + while ((token = strsep(&sptr, " ")) != NULL) { + switch (pass) { + /* address */ + case 0: + if ((token[0] == '0') && (token[1] == 'x')) { + if (kstrtou32(token, 16, &address)) { + SCSC_TAG_INFO(MIF, "Incorrect format,,,address should start by 0x\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 256 8\"\n"); + goto error; + } + SCSC_TAG_INFO(MIF, "address %d 0x%x\n", address, address); + } else { + SCSC_TAG_INFO(MIF, "Incorrect format,,,address should start by 0x\n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 256 8\"\n"); + goto error; + } + break; + /* size */ + case 1: + if (kstrtou32(token, 0, &size)) { + SCSC_TAG_INFO(MIF, "Incorrect format,,, for size\n"); + goto error; + } + SCSC_TAG_INFO(MIF, "size: %d\n", size); + break; + + /* unit */ + case 2: + if (kstrtou8(token, 0, &unit)) { + SCSC_TAG_INFO(MIF, "Incorrect format,,, for unit\n"); + goto error; + } + if ((unit != 8) && (unit != 16) && (unit != 32)) { + SCSC_TAG_INFO(MIF, "Unit %d should be 8/16/32\n", unit); + goto error; + } + SCSC_TAG_INFO(MIF, "unit: %d\n", unit); + break; + } + pass++; + } + if (pass != 3) { + SCSC_TAG_INFO(MIF, "Wrong format: \n"); + SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 256 8\"\n"); + goto error; + } + + mem = mif_global->get_mifram_ptr(mif_global, 0); + if (!mem) { + SCSC_TAG_INFO(MIF, "Mem not allocated\n"); + goto error; + } + + /* Add offset */ + mem = mem + address; + + SCSC_TAG_INFO(MIF, "Phy addr :%p ref addr :%x\n", mem, address); + SCSC_TAG_INFO(MIF, "------------------------------------------------------------------------\n"); + print_hex_dump(KERN_WARNING, "ref addr offset: ", DUMP_PREFIX_OFFSET, 16, unit/8, mem, size, 1); + SCSC_TAG_INFO(MIF, "------------------------------------------------------------------------\n"); +error: + return count; +} + +static ssize_t mifprocfs_mif_reg_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + void *mem; + + if (!mif_global) { + SCSC_TAG_INFO(MIF, "Endpoint not registered\n"); + return 0; + } + + mem = mif_global->get_mifram_ptr(mif_global, 0); + + SCSC_TAG_INFO(MIF, "Phy addr :%p\n", mem); + + mif_global->mif_dump_registers(mif_global); + + return count; +} +/* + * TODO: Add here any debug message should be exported +static int mifprocfs_mif_dbg_show(struct seq_file *m, void *v) +{ + (void)v; + + if (!mif_global) { + seq_puts(m, "endpoint not registered"); + return 0; + } + return 0; +} +*/ + +/* Total space in the memory region containing this ramman (assumes one region per ramman) */ +static ssize_t mifprocfs_ramman_total_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct miframman *ramman = (struct miframman *)file->private_data; + + pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", (ptrdiff_t)(ramman->start_dram - ramman->start_region) + ramman->size_pool); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* Offset of the pool within the region (the space before is reserved by FW) */ +static ssize_t mifprocfs_ramman_offset_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct miframman *ramman = (struct miframman *)file->private_data; + + pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", (ptrdiff_t)(ramman->start_dram - ramman->start_region)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* Start address of the pool within the region */ +static ssize_t mifprocfs_ramman_start_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct miframman *ramman = (struct miframman *)file->private_data; + + pos += scnprintf(buf + pos, bufsz - pos, "%p\n", ramman->start_dram); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* Size of the pool */ +static ssize_t mifprocfs_ramman_size_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct miframman *ramman = (struct miframman *)file->private_data; + + pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", ramman->size_pool); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* Space remaining within the pool */ +static ssize_t mifprocfs_ramman_free_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct miframman *ramman = (struct miframman *)file->private_data; + + pos += scnprintf(buf + pos, bufsz - pos, "%u\n", ramman->free_mem); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* Bytes used within the pool */ +static ssize_t mifprocfs_ramman_used_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct miframman *ramman = (struct miframman *)file->private_data; + + pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", ramman->size_pool - (size_t)ramman->free_mem); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* List allocations per ramman */ +static int mifprocfs_ramman_list_show(struct seq_file *m, void *v) +{ + struct miframman *ramman = (struct miframman *)m->private; + (void)v; + + miframman_log(ramman, m); + + return 0; +} + +static const char *procdir = "driver/mif_ctrl"; +static int refcount; + +#define MIF_DIRLEN 128 + +static struct proc_dir_entry *create_procfs_dir(void) +{ + char dir[MIF_DIRLEN]; + + if (refcount++ == 0) { + (void)snprintf(dir, sizeof(dir), "%s", procdir); + procfs_dir = proc_mkdir(dir, NULL); + } + return procfs_dir; +} + +static void destroy_procfs_dir(void) +{ + char dir[MIF_DIRLEN]; + + if (--refcount == 0) { + (void)snprintf(dir, sizeof(dir), "%s", procdir); + remove_proc_entry(dir, NULL); + procfs_dir = NULL; + } + WARN_ON(refcount < 0); +} + + +int mifproc_create_proc_dir(struct scsc_mif_abs *mif) +{ + struct proc_dir_entry *parent; + + /* WARNING --- SINGLETON FOR THE TIME BEING */ + /* EXTEND PROC ENTRIES IF NEEDED!!!!! */ + if (mif_global) + return -EBUSY; + + /* Ref the root dir */ + parent = create_procfs_dir(); + if (parent) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = NULL; +#endif + MIF_PROCFS_ADD_FILE(NULL, mif_writemem, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(NULL, mif_dump, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(NULL, mif_reg, parent, S_IRUSR | S_IRGRP); + } else { + SCSC_TAG_INFO(MIF, "failed to create /proc dir\n"); + return -EINVAL; + } + + mif_global = mif; + + return 0; +/* +err: + return -EINVAL; +*/ +} + +void mifproc_remove_proc_dir(void) +{ + if (procfs_dir) { + MIF_PROCFS_REMOVE_FILE(mif_writemem, procfs_dir); + MIF_PROCFS_REMOVE_FILE(mif_dump, procfs_dir); + MIF_PROCFS_REMOVE_FILE(mif_reg, procfs_dir); + + /* De-ref the root dir */ + destroy_procfs_dir(); + } + + mif_global = NULL; +} + +/* /proc/driver/mif_ctrl/rammanX */ +static const char *ramman_procdir = "ramman"; +struct miframman *proc_miframman[MX_MAX_PROC_RAMMAN]; +static int ramman_instance; + +int mifproc_create_ramman_proc_dir(struct miframman *ramman) +{ + char dir[MIF_DIRLEN]; + struct proc_dir_entry *parent; + struct proc_dir_entry *root; + + if ((ramman_instance > ARRAY_SIZE(proc_miframman) - 1)) + return -EINVAL; + + /* Ref the root dir /proc/driver/mif_ctrl */ + root = create_procfs_dir(); + if (!root) + return -EINVAL; + + (void)snprintf(dir, sizeof(dir), "%s%d", ramman_procdir, ramman_instance); + parent = proc_mkdir(dir, root); + if (parent) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = NULL; +#endif + MIF_PROCFS_ADD_FILE(ramman, ramman_total, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(ramman, ramman_offset, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(ramman, ramman_start, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(ramman, ramman_size, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(ramman, ramman_free, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + MIF_PROCFS_ADD_FILE(ramman, ramman_used, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + MIF_PROCFS_SEQ_ADD_FILE(ramman, ramman_list, parent, S_IRUSR | S_IRGRP | S_IROTH); + + procfs_dir_ramman[ramman_instance] = parent; + proc_miframman[ramman_instance] = ramman; + + ramman_instance++; + + } else { + SCSC_TAG_INFO(MIF, "failed to create /proc dir\n"); + destroy_procfs_dir(); + return -EINVAL; + } + + return 0; +} + +void mifproc_remove_ramman_proc_dir(struct miframman *ramman) +{ + (void)ramman; + + if (ramman_instance <= 0) { + WARN_ON(ramman_instance < 0); + return; + } + + --ramman_instance; + + if (procfs_dir_ramman[ramman_instance]) { + char dir[MIF_DIRLEN]; + + MIF_PROCFS_REMOVE_FILE(ramman_total, procfs_dir_ramman[ramman_instance]); + MIF_PROCFS_REMOVE_FILE(ramman_offset, procfs_dir_ramman[ramman_instance]); + MIF_PROCFS_REMOVE_FILE(ramman_start, procfs_dir_ramman[ramman_instance]); + MIF_PROCFS_REMOVE_FILE(ramman_size, procfs_dir_ramman[ramman_instance]); + MIF_PROCFS_REMOVE_FILE(ramman_free, procfs_dir_ramman[ramman_instance]); + MIF_PROCFS_REMOVE_FILE(ramman_used, procfs_dir_ramman[ramman_instance]); + + MIF_PROCFS_REMOVE_FILE(ramman_list, procfs_dir_ramman[ramman_instance]); + + (void)snprintf(dir, sizeof(dir), "%s%d", ramman_procdir, ramman_instance); + remove_proc_entry(dir, procfs_dir); + procfs_dir_ramman[ramman_instance] = NULL; + proc_miframman[ramman_instance] = NULL; + } + + /* De-ref the root dir */ + destroy_procfs_dir(); +} diff --git a/drivers/misc/samsung/scsc/mifproc.h b/drivers/misc/samsung/scsc/mifproc.h new file mode 100644 index 000000000000..ea92c5bf264d --- /dev/null +++ b/drivers/misc/samsung/scsc/mifproc.h @@ -0,0 +1,115 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* + * Chip Manager /proc interface + */ +#include +#include +#include + +#ifndef SCSC_MIF_PROC_H +#define SCSC_MIF_PROC_H + +#ifndef AID_MX +#define AID_MX 0444 +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MIF_PDE_DATA(inode) PDE_DATA(inode) +#else +#define MIF_PDE_DATA(inode) (PDE(inode)->data) +#endif + +#define MIF_PROCFS_SEQ_FILE_OPS(name) \ + static int mifprocfs_ ## name ## _show(struct seq_file *m, void *v); \ + static int mifprocfs_ ## name ## _open(struct inode *inode, struct file *file) \ + { \ + return single_open(file, mifprocfs_ ## name ## _show, MIF_PDE_DATA(inode)); \ + } \ + static const struct file_operations mifprocfs_ ## name ## _fops = { \ + .open = mifprocfs_ ## name ## _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + } + +#define MIF_PROCFS_SEQ_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = proc_create_data(# name, mode, parent, &mifprocfs_ ## name ## _fops, _sdev); \ + if (!entry) { \ + break; \ + } \ + MIF_PROCFS_SET_UID_GID(entry); \ + } while (0) + +#define MIF_PROCFS_RW_FILE_OPS(name) \ + static ssize_t mifprocfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static ssize_t mifprocfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations mifprocfs_ ## name ## _fops = { \ + .read = mifprocfs_ ## name ## _read, \ + .write = mifprocfs_ ## name ## _write, \ + .open = mifprocfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + + +#define MIF_PROCFS_RO_FILE_OPS(name) \ + static ssize_t mifprocfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations mifprocfs_ ## name ## _fops = { \ + .read = mifprocfs_ ## name ## _read, \ + .open = mifprocfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MIF_PROCFS_SET_UID_GID(_entry) \ + do { \ + kuid_t proc_kuid = KUIDT_INIT(AID_MX); \ + kgid_t proc_kgid = KGIDT_INIT(AID_MX); \ + proc_set_user(_entry, proc_kuid, proc_kgid); \ + } while (0) +#else +#define MIF_PROCFS_SET_UID_GID(entry) \ + do { \ + (entry)->uid = AID_MX; \ + (entry)->gid = AID_MX; \ + } while (0) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MIF_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &mifprocfs_ ## name ## _fops, _sdev); \ + MIF_PROCFS_SET_UID_GID(entry); \ + } while (0) +#else +#define MIF_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = create_proc_entry(# name, mode, parent); \ + if (entry) { \ + entry->proc_fops = &mifprocfs_ ## name ## _fops; \ + entry->data = _sdev; \ + MIF_PROCFS_SET_UID_GID(entry); \ + } \ + } while (0) +#endif + +#define MIF_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent) + +struct scsc_mif_abs; +struct miframman; + +int mifproc_create_proc_dir(struct scsc_mif_abs *mif); +void mifproc_remove_proc_dir(void); +int mifproc_create_ramman_proc_dir(struct miframman *miframman); +void mifproc_remove_ramman_proc_dir(struct miframman *miframman); + +struct mifproc { +}; +#endif /* SCSC_mif_PROC_H */ diff --git a/drivers/misc/samsung/scsc/mifqos.c b/drivers/misc/samsung/scsc/mifqos.c new file mode 100644 index 000000000000..51d111a773ae --- /dev/null +++ b/drivers/misc/samsung/scsc/mifqos.c @@ -0,0 +1,135 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* uses */ +#include +#include +#include +#include +#include +#include "scsc_mif_abs.h" + +/* Implements */ +#include "mifqos.h" + +int mifqos_init(struct mifqos *qos, struct scsc_mif_abs *mif) +{ + u8 i; + + if (!qos) + return -EIO; + + SCSC_TAG_INFO(MIF, "Init MIF QoS\n"); + + for (i = 0; i < SCSC_SERVICE_TOTAL; i++) + qos->qos_in_use[i] = false; + + mutex_init(&qos->lock); + qos->mif = mif; + + return 0; +} + +int mifqos_add_request(struct mifqos *qos, enum scsc_service_id id, enum scsc_qos_config config) +{ + struct scsc_mif_abs *mif; + struct scsc_mifqos_request *req; + int ret = 0; + + if (!qos) + return -EIO; + + mutex_lock(&qos->lock); + if (qos->qos_in_use[id]) { + mutex_unlock(&qos->lock); + return -EIO; + } + + SCSC_TAG_INFO(MIF, "Service id %d add QoS request %d\n", id, config); + + mif = qos->mif; + req = &qos->qos_req[id]; + + if (mif->mif_pm_qos_add_request) + ret = mif->mif_pm_qos_add_request(mif, req, config); + if (ret) { + mutex_unlock(&qos->lock); + return ret; + } + qos->qos_in_use[id] = true; + mutex_unlock(&qos->lock); + + return 0; +} + +int mifqos_update_request(struct mifqos *qos, enum scsc_service_id id, enum scsc_qos_config config) +{ + struct scsc_mif_abs *mif; + struct scsc_mifqos_request *req; + + if (!qos) + return -EIO; + + mutex_lock(&qos->lock); + if (!qos->qos_in_use[id]) { + mutex_unlock(&qos->lock); + return -EIO; + } + + SCSC_TAG_INFO(MIF, "Service id %d update QoS request %d\n", id, config); + + mif = qos->mif; + req = &qos->qos_req[id]; + + mutex_unlock(&qos->lock); + + if (mif->mif_pm_qos_update_request) + return mif->mif_pm_qos_update_request(mif, req, config); + else + return 0; +} + +int mifqos_remove_request(struct mifqos *qos, enum scsc_service_id id) +{ + struct scsc_mif_abs *mif; + struct scsc_mifqos_request *req; + + if (!qos) + return -EIO; + + mutex_lock(&qos->lock); + if (!qos->qos_in_use[id]) { + mutex_unlock(&qos->lock); + return -EIO; + } + + SCSC_TAG_INFO(MIF, "Service id %d remove QoS\n", id); + + mif = qos->mif; + req = &qos->qos_req[id]; + + qos->qos_in_use[id] = false; + + mutex_unlock(&qos->lock); + + if (mif->mif_pm_qos_remove_request) + return mif->mif_pm_qos_remove_request(mif, req); + else + return 0; +} + +int mifqos_deinit(struct mifqos *qos) +{ + enum scsc_service_id i; + + SCSC_TAG_INFO(MIF, "Deinit MIF QoS\n"); + + for (i = 0; i < SCSC_SERVICE_TOTAL; i++) + mifqos_remove_request(qos, i); + + return 0; +} + diff --git a/drivers/misc/samsung/scsc/mifqos.h b/drivers/misc/samsung/scsc/mifqos.h new file mode 100644 index 000000000000..a9e7af9e596f --- /dev/null +++ b/drivers/misc/samsung/scsc/mifqos.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#ifndef __MIFQOS_H +#define __MIFQOS_H + +#include +#include + +struct scsc_mif_abs; +struct mifqos; + +int mifqos_init(struct mifqos *qos, struct scsc_mif_abs *mif); +int mifqos_add_request(struct mifqos *qos, enum scsc_service_id id, enum scsc_qos_config config); +int mifqos_update_request(struct mifqos *qos, enum scsc_service_id id, enum scsc_qos_config config); +int mifqos_remove_request(struct mifqos *qos, enum scsc_service_id id); +int mifqos_list(struct mifqos *qos); +int mifqos_deinit(struct mifqos *qos); + +struct scsc_mifqos_request; + +struct mifqos { + bool qos_in_use[SCSC_SERVICE_TOTAL]; + struct mutex lock; + struct scsc_mif_abs *mif; + struct scsc_mifqos_request qos_req[SCSC_SERVICE_TOTAL]; +}; +#endif diff --git a/drivers/misc/samsung/scsc/miframman.c b/drivers/misc/samsung/scsc/miframman.c new file mode 100644 index 000000000000..d2f16b135014 --- /dev/null +++ b/drivers/misc/samsung/scsc/miframman.c @@ -0,0 +1,267 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include "scsc_mif_abs.h" + +#include "scsc/api/bt_audio.h" +#include "miframman.h" +#include "mifproc.h" + +/* Caller should provide locking */ +void miframman_init(struct miframman *ram, void *start_dram, size_t size_pool, void *start_region) +{ + mutex_init(&ram->lock); + + SCSC_TAG_INFO(MIF, "MIFRAMMAN_BLOCK_SIZE = %d\n", MIFRAMMAN_BLOCK_SIZE); + ram->num_blocks = size_pool / MIFRAMMAN_BLOCK_SIZE; + + if (ram->num_blocks == 0) { + SCSC_TAG_ERR(MIF, "Pool size < BLOCK_SIZE\n"); + return; + } + + if (ram->num_blocks >= MIFRAMMAN_NUM_BLOCKS) { + SCSC_TAG_ERR(MIF, "Not enough memory\n"); + return; + } + + memset(ram->bitmap, BLOCK_FREE, sizeof(ram->bitmap)); + + ram->start_region = start_region; /* For monitoring purposes only */ + ram->start_dram = start_dram; + ram->size_pool = size_pool; + ram->free_mem = ram->num_blocks * MIFRAMMAN_BLOCK_SIZE; + + mifproc_create_ramman_proc_dir(ram); +} + +void miframabox_init(struct mifabox *mifabox, void *start_aboxram) +{ + /* No locking as not a shared resource */ + mifabox->aboxram = (struct scsc_bt_audio_abox *)start_aboxram; +} + +void *__miframman_alloc(struct miframman *ram, size_t nbytes, int tag) +{ + unsigned int index = 0; + unsigned int available; + unsigned int i; + size_t num_blocks; + void *free_mem = NULL; + + if (!nbytes || nbytes > ram->free_mem) + goto end; + + /* Number of blocks required (rounding up) */ + num_blocks = nbytes / MIFRAMMAN_BLOCK_SIZE + + ((nbytes % MIFRAMMAN_BLOCK_SIZE) > 0 ? 1 : 0); + + if (num_blocks > ram->num_blocks) + goto end; + + while (index <= (ram->num_blocks - num_blocks)) { + available = 0; + + /* Search consecutive blocks */ + for (i = 0; i < num_blocks; i++) { + if (ram->bitmap[i + index] != BLOCK_FREE) + break; + available++; + } + if (available == num_blocks) { + free_mem = ram->start_dram + + MIFRAMMAN_BLOCK_SIZE * index; + + /* Mark the block boundary as used */ + ram->bitmap[index] = BLOCK_BOUND; + ram->bitmap[index] |= (u8)(tag << MIFRAMMAN_BLOCK_OWNER_SHIFT); /* Add owner tack for tracking */ + index++; + + /* Additional blocks in this allocation */ + for (i = 1; i < num_blocks; i++) { + ram->bitmap[index] = BLOCK_INUSE; + ram->bitmap[index] |= (u8)(tag << MIFRAMMAN_BLOCK_OWNER_SHIFT); /* Add owner tack for tracking */ + index++; + } + + ram->free_mem -= num_blocks * MIFRAMMAN_BLOCK_SIZE; + goto exit; + } else + index = index + available + 1; + } +end: + SCSC_TAG_INFO(MIF, "Not enough shared memory (nbytes %zd, free_mem %u)\n", + nbytes, ram->free_mem); + return NULL; +exit: + return free_mem; +} + + +#define MIFRAMMAN_ALIGN(mem, align) \ + ((void *)((((uintptr_t)(mem) + (align + sizeof(void *))) \ + & (~(uintptr_t)(align - 1))))) + +#define MIFRAMMAN_PTR(mem) \ + (*(((void **)((uintptr_t)(mem) & \ + (~(uintptr_t)(sizeof(void *) - 1)))) - 1)) + +/* + * Allocate shared DRAM block + * + * Parameters: + * ram - pool identifier + * nbytes - allocation size + * align - allocation alignment + * tag - owner identifier (typically service ID), 4 bits. + * + * Returns + * Pointer to allocated area, or NULL + */ +void *miframman_alloc(struct miframman *ram, size_t nbytes, size_t align, int tag) +{ + void *mem, *align_mem = NULL; + + mutex_lock(&ram->lock); + if (!is_power_of_2(align) || nbytes == 0) { + SCSC_TAG_ERR(MIF, "Failed size/alignment check (nbytes %zd, align %zd)\n", nbytes, align); + goto end; + } + + if (align < sizeof(void *)) + align = sizeof(void *); + + mem = __miframman_alloc(ram, nbytes + align + sizeof(void *), tag); + if (!mem) + goto end; + + align_mem = MIFRAMMAN_ALIGN(mem, align); + + /* Store allocated pointer */ + MIFRAMMAN_PTR(align_mem) = mem; +end: + mutex_unlock(&ram->lock); + return align_mem; +} + +/* + * Free shared DRAM block + * + * Parameters: + * ram - pool identifier + * mem - buffer to free + */ +void __miframman_free(struct miframman *ram, void *mem) +{ + unsigned int index, num_blocks = 0; + + if (ram->start_dram == NULL || !mem) { + SCSC_TAG_ERR(MIF, "Mem is NULL\n"); + return; + } + + /* Get block index */ + index = (unsigned int)((mem - ram->start_dram) + / MIFRAMMAN_BLOCK_SIZE); + + /* Check */ + if (index >= ram->num_blocks) { + SCSC_TAG_ERR(MIF, "Incorrect index %d\n", index); + return; + } + + /* Check it is a Boundary block */ + if ((ram->bitmap[index] & MIFRAMMAN_BLOCK_STATUS_MASK) != BLOCK_BOUND) { + SCSC_TAG_ERR(MIF, "Incorrect Block descriptor\n"); + return; + } + ram->bitmap[index++] = BLOCK_FREE; + + /* Free remaining blocks */ + num_blocks++; + while (index < ram->num_blocks && (ram->bitmap[index] & MIFRAMMAN_BLOCK_STATUS_MASK) == BLOCK_INUSE) { + ram->bitmap[index++] = BLOCK_FREE; + num_blocks++; + } + + ram->free_mem += num_blocks * MIFRAMMAN_BLOCK_SIZE; +} + +void miframman_free(struct miframman *ram, void *mem) +{ + mutex_lock(&ram->lock); + /* Restore allocated pointer */ + if (mem) + __miframman_free(ram, MIFRAMMAN_PTR(mem)); + mutex_unlock(&ram->lock); +} + +/* Caller should provide locking */ +void miframman_deinit(struct miframman *ram) +{ + /* Mark all the blocks as INUSE (by Common) to prevent new allocations */ + memset(ram->bitmap, BLOCK_INUSE, sizeof(ram->bitmap)); + + ram->num_blocks = 0; + ram->start_dram = NULL; + ram->size_pool = 0; + ram->free_mem = 0; + + mifproc_remove_ramman_proc_dir(ram); +} + +void miframabox_deinit(struct mifabox *mifabox) +{ + /* not dynamic - so just mark as NULL */ + /* Maybe this function should be empty? */ + mifabox->aboxram = NULL; +} + +/* Log current allocations in a ramman in proc */ +void miframman_log(struct miframman *ram, struct seq_file *fd) +{ + unsigned int b; + unsigned int i; + int tag; + size_t num_blocks = 0; + + if (!ram) + return; + + seq_printf(fd, "ramman: start_dram %p, size %zd, free_mem %u\n\n", + ram->start_region, ram->size_pool, ram->free_mem); + + for (b = 0; b < ram->num_blocks; b++) { + if ((ram->bitmap[b] & MIFRAMMAN_BLOCK_STATUS_MASK) == BLOCK_BOUND) { + /* Found a boundary allocation */ + num_blocks++; + tag = (ram->bitmap[b] & MIFRAMMAN_BLOCK_OWNER_MASK) >> MIFRAMMAN_BLOCK_OWNER_SHIFT; + + /* Count subsequent blocks in this group */ + for (i = 1; + i < ram->num_blocks && (ram->bitmap[b + i] & MIFRAMMAN_BLOCK_STATUS_MASK) == BLOCK_INUSE; + i++) { + /* Check owner matches boundary block */ + int newtag = (ram->bitmap[b + i] & MIFRAMMAN_BLOCK_OWNER_MASK) >> MIFRAMMAN_BLOCK_OWNER_SHIFT; + if (newtag != tag) { + seq_printf(fd, "Allocated block tag %d doesn't match boundary tag %d, index %d, %p\n", + newtag, tag, b + i, + ram->start_dram + (b + i) * MIFRAMMAN_BLOCK_SIZE); + } + num_blocks++; + } + seq_printf(fd, "index %8d, svc %d, bytes %12d, blocks %10d, %p\n", + b, tag, + (i * MIFRAMMAN_BLOCK_SIZE), + i, + ram->start_dram + (b * MIFRAMMAN_BLOCK_SIZE)); + } + } +} diff --git a/drivers/misc/samsung/scsc/miframman.h b/drivers/misc/samsung/scsc/miframman.h new file mode 100644 index 000000000000..acce9dac4daf --- /dev/null +++ b/drivers/misc/samsung/scsc/miframman.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MIFRAMMAN_H +#define __MIFRAMMAN_H + +#include +#include "scsc/api/bt_audio.h" + +/* TODO: Needs to define the max mem */ + +struct miframman; +struct mifabox; + + +void miframman_init(struct miframman *ram, void *start_dram, size_t size_pool, void *start_region); +void miframabox_init(struct mifabox *mifabox, void *start_aboxram); +void *miframman_alloc(struct miframman *ram, size_t nbytes, size_t align, int tag); +void miframman_free(struct miframman *ram, void *mem); +void miframman_deinit(struct miframman *ram); +void miframabox_deinit(struct mifabox *mifabox); +void miframman_log(struct miframman *ram, struct seq_file *fd); + +#define MIFRAMMAN_MAXMEM (16 * 1024 * 1024) +#define MIFRAMMAN_BLOCK_SIZE (64) + +#define MIFRAMMAN_NUM_BLOCKS ((MIFRAMMAN_MAXMEM) / (MIFRAMMAN_BLOCK_SIZE)) + +/* Block status in lower nibble */ +#define MIFRAMMAN_BLOCK_STATUS_MASK 0x0f +#define BLOCK_FREE 0 +#define BLOCK_INUSE 1 +#define BLOCK_BOUND 2 /* Block allocation boundary */ + +/* Block owner in upper nibble */ +#define MIFRAMMAN_BLOCK_OWNER_MASK 0xf0 +#define MIFRAMMAN_BLOCK_OWNER_SHIFT 4 + +#define MIFRAMMAN_OWNER_COMMON 0 /* Owner tag for Common driver */ + +/* Inclusion in core.c treat it as opaque */ +struct miframman { + void *start_region; /* Base address of region containing the pool */ + void *start_dram; /* Base address of allocator pool */ + size_t size_pool; /* Size of allocator pool */ + char bitmap[MIFRAMMAN_NUM_BLOCKS]; /* Zero initialized-> all blocks free */ + u32 num_blocks; /* Blocks of MIFRAMMAN_BLOCK_SIZE in pool */ + u32 free_mem; /* Bytes remaining in allocator pool */ + struct mutex lock; +}; + +struct mifabox { + struct scsc_bt_audio_abox *aboxram; +}; +#endif diff --git a/drivers/misc/samsung/scsc/mifsmapper.c b/drivers/misc/samsung/scsc/mifsmapper.c new file mode 100644 index 000000000000..aedaeca7970d --- /dev/null +++ b/drivers/misc/samsung/scsc/mifsmapper.c @@ -0,0 +1,360 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* uses */ +#include +#include +#include +#include +#include +#include "scsc_mif_abs.h" + +/* Implements */ +#include "mifsmapper.h" + + +static int mifsmapper_get_num_banks(u8 *phy_map, u8 *log_map, bool large) +{ + u8 i = 0, count = 0; + + for (i = 0; i < SCSC_MIF_SMAPPER_MAX_BANKS; i++) { + if (large && phy_map[i] == SCSC_MIF_ABS_LARGE_BANK) { + log_map[count] = i; + count++; + } else if (!large && phy_map[i] == SCSC_MIF_ABS_SMALL_BANK) { + log_map[count] = i; + count++; + } + } + + return count; +} + +int mifsmapper_init(struct mifsmapper *smapper, struct scsc_mif_abs *mif) +{ + /* TODO: Protect the function if allocations fail */ + struct scsc_mif_smapper_info bank_info; + u8 i = 0, total_num_banks; + u8 phy_map[SCSC_MIF_SMAPPER_MAX_BANKS] = { 0 }; + u8 log_map_large[SCSC_MIF_SMAPPER_MAX_BANKS] = { 0 }; + u8 log_map_small[SCSC_MIF_SMAPPER_MAX_BANKS] = { 0 }; + + if (smapper->in_use) + return -EBUSY; + + SCSC_TAG_INFO(MIF, "Init SMAPPER\n"); + + spin_lock_init(&smapper->lock); + /* Get physical mapping of the banks */ + if (mif->mif_smapper_get_mapping(mif, phy_map, &smapper->align)) { + SCSC_TAG_ERR(MIF, "SMAPPER is not present\n"); + return -EINVAL; + } + + smapper->in_use = true; + smapper->mif = mif; + + smapper->num_large_banks = mifsmapper_get_num_banks(phy_map, log_map_large, true); + smapper->num_small_banks = mifsmapper_get_num_banks(phy_map, log_map_small, false); + total_num_banks = smapper->num_large_banks + smapper->num_small_banks; + + smapper->bank = kmalloc_array(total_num_banks, sizeof(struct mifsmapper_bank), + GFP_KERNEL); + + smapper->bank_bm_large = kmalloc(BITS_TO_LONGS(smapper->num_large_banks) * sizeof(unsigned long), GFP_KERNEL); + bitmap_zero(smapper->bank_bm_large, smapper->num_large_banks); + + smapper->bank_bm_small = kmalloc(BITS_TO_LONGS(smapper->num_small_banks) * sizeof(unsigned long), GFP_KERNEL); + bitmap_zero(smapper->bank_bm_small, smapper->num_small_banks); + + /* LSB bit of banks will be the large banks the rest will be the small banks */ + /* Get large bank info */ + for (; i < smapper->num_large_banks; i++) { + /* get phy bank */ + mif->mif_smapper_get_bank_info(mif, log_map_large[i], &bank_info); + smapper->bank[i].entries_bm = kmalloc(BITS_TO_LONGS(bank_info.num_entries) * sizeof(unsigned long), GFP_KERNEL); + smapper->bank[i].num_entries = bank_info.num_entries; + smapper->bank[i].mem_range_bytes = bank_info.mem_range_bytes; + smapper->bank[i].phy_index = log_map_large[i]; + SCSC_TAG_INFO(MIF, "phy bank %d mapped to logical %d. Large, entries %d range 0x%x\n", + log_map_large[i], i, bank_info.num_entries, bank_info.mem_range_bytes); + bitmap_zero(smapper->bank[i].entries_bm, bank_info.num_entries); + } + + /* Get small bank info */ + for (; i < total_num_banks; i++) { + /* get phy bank */ + mif->mif_smapper_get_bank_info(mif, log_map_small[i - smapper->num_large_banks], &bank_info); + smapper->bank[i].entries_bm = kmalloc(BITS_TO_LONGS(bank_info.num_entries) * sizeof(unsigned long), GFP_KERNEL); + smapper->bank[i].num_entries = bank_info.num_entries; + smapper->bank[i].mem_range_bytes = bank_info.mem_range_bytes; + smapper->bank[i].phy_index = log_map_small[i - smapper->num_large_banks]; + SCSC_TAG_INFO(MIF, "phy bank %d mapped to logical %d. Small, entries %d range 0x%x\n", + log_map_small[i - smapper->num_large_banks], i, bank_info.num_entries, bank_info.mem_range_bytes); + bitmap_zero(smapper->bank[i].entries_bm, bank_info.num_entries); + } + + return 0; +} + +u16 mifsmapper_get_alignment(struct mifsmapper *smapper) +{ + return smapper->align; +} + +int mifsmapper_alloc_bank(struct mifsmapper *smapper, bool large_bank, u32 entry_size, u16 *entries) +{ + struct mifsmapper_bank *bank; + unsigned long *bitmap; + u8 max_banks, offset = 0; + int which_bit = 0; + + spin_lock(&smapper->lock); + + if (!smapper->in_use) + goto error; + + bank = smapper->bank; + if (large_bank) { + max_banks = smapper->num_large_banks; + bitmap = smapper->bank_bm_large; + } else { + max_banks = smapper->num_small_banks; + bitmap = smapper->bank_bm_small; + offset = smapper->num_large_banks; + } + + /* Search for free slots */ + which_bit = find_first_zero_bit(bitmap, max_banks); + if (which_bit >= max_banks) + goto error; + + /* Update bit mask */ + set_bit(which_bit, bitmap); + + /* Retrieve Bank capabilities and return the number of entries available */ + /* size must be a power of 2 */ + /* TODO : check that granularity is correct */ + BUG_ON(!is_power_of_2(entry_size)); + + /* Clear bank entries */ + bitmap_zero(bank[which_bit + offset].entries_bm, bank[which_bit + offset].num_entries); + + *entries = bank[which_bit + offset].mem_range_bytes/entry_size; + /* Saturate */ + if (*entries > bank[which_bit + offset].num_entries) + *entries = bank[which_bit + offset].num_entries; + else if (*entries < bank[which_bit + offset].num_entries) { + u16 i; + + SCSC_TAG_INFO(MIF, "Nominal entries %d reduced to %d\n", + bank[which_bit + offset].num_entries, *entries); + + for (i = *entries; i < bank[which_bit + offset].num_entries; i++) + /* Mark the MSB of the bitmap as used */ + set_bit(i, bank[which_bit + offset].entries_bm); + } + /* Update number of entries */ + bank[which_bit + offset].num_entries = *entries; + bank[which_bit + offset].num_entries_left = *entries; + bank[which_bit + offset].in_use = true; + bank[which_bit + offset].granularity = entry_size; + + SCSC_TAG_INFO(MIF, "entries %d bank.num_entries %d large bank %d logical bank %d entries left %d\n", *entries, bank[which_bit + offset].num_entries, large_bank, which_bit + offset, + bank[which_bit + offset].num_entries_left); + + spin_unlock(&smapper->lock); + return which_bit + offset; + +error: + SCSC_TAG_ERR(MIF, "Error allocating bank\n"); + + *entries = 0; + spin_unlock(&smapper->lock); + return -EIO; +} + +int mifsmapper_free_bank(struct mifsmapper *smapper, u8 bank) +{ + unsigned long *bitmap; + u8 max_banks, offset = 0; + struct mifsmapper_bank *bank_en; + + spin_lock(&smapper->lock); + + if (!smapper->in_use || ((bank >= (smapper->num_large_banks + smapper->num_small_banks)))) + goto error; + + /* check if it is a large or small bank */ + if (bank >= smapper->num_large_banks) { + max_banks = smapper->num_small_banks; + bitmap = smapper->bank_bm_small; + offset = bank - smapper->num_large_banks; + } else { + max_banks = smapper->num_large_banks; + bitmap = smapper->bank_bm_large; + offset = bank; + } + + /* Update bit mask */ + if (!test_and_clear_bit(offset, bitmap)) + SCSC_TAG_ERR(MIF, "bank was not allocated\n"); + + bank_en = smapper->bank; + bank_en[bank].in_use = false; + + spin_unlock(&smapper->lock); + + return 0; +error: + SCSC_TAG_ERR(MIF, "Error freeing bank %d\n", bank); + spin_unlock(&smapper->lock); + + return -EIO; +} + +int mifsmapper_get_entries(struct mifsmapper *smapper, u8 bank, u8 num_entries, u8 *entries) +{ + struct mifsmapper_bank *bank_en; + unsigned long *bitmap; + u32 max_bits, i, ent; + + if (!smapper->bank) + return -EINVAL; + + bank_en = smapper->bank; + + if (!bank_en[bank].in_use) { + SCSC_TAG_ERR(MIF, "Bank %d not allocated.\n", bank); + return -EINVAL; + } + + + max_bits = bank_en[bank].num_entries_left; + ent = bank_en[bank].num_entries; + if (num_entries > max_bits) { + SCSC_TAG_ERR(MIF, "Not enough entries. Requested %d, left %d\n", num_entries, max_bits); + return -ENOMEM; + } + + bitmap = bank_en[bank].entries_bm; + + for (i = 0; i < num_entries; i++) { + entries[i] = find_first_zero_bit(bitmap, ent); + if (entries[i] >= ent) + return -EIO; + /* Update bit mask */ + set_bit(entries[i], bitmap); + } + + smapper->bank[bank].num_entries_left -= num_entries; + + return 0; +} + +int mifsmapper_free_entries(struct mifsmapper *smapper, u8 bank, u8 num_entries, u8 *entries) +{ + struct mifsmapper_bank *bank_en; + unsigned long *bitmap; + u32 max_bits, i, ent, total = 0; + + if (!smapper->bank) + return -EINVAL; + + bank_en = smapper->bank; + + if (!bank_en[bank].in_use) { + SCSC_TAG_ERR(MIF, "Bank %d not allocated.\n", bank); + return -EINVAL; + } + + + max_bits = bank_en[bank].num_entries_left; + ent = bank_en[bank].num_entries; + if ((max_bits + num_entries) > ent) { + SCSC_TAG_ERR(MIF, "Tried to free more entries. Requested %d, left %d\n", num_entries, max_bits); + return -ENOMEM; + } + + bitmap = bank_en[bank].entries_bm; + + for (i = 0; i < num_entries; i++) { + /* Update bit mask */ + if (!test_and_clear_bit(entries[i], bitmap)) + SCSC_TAG_ERR(MIF, "entry was not allocated\n"); + else + total++; + } + + smapper->bank[bank].num_entries_left += total; + + return 0; +} + +void mifsmapper_configure(struct mifsmapper *smapper, u32 granularity) +{ + struct scsc_mif_abs *mif; + /* Get abs implementation */ + mif = smapper->mif; + + mif->mif_smapper_configure(mif, granularity); +} + +int mifsmapper_write_sram(struct mifsmapper *smapper, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr) +{ + struct scsc_mif_abs *mif; + + if (!smapper->bank[bank].in_use) { + SCSC_TAG_ERR(MIF, "Bank %d not allocated.\n", bank); + return -EINVAL; + } + + /* Get abs implementation */ + mif = smapper->mif; + + /* use the phy address of the bank */ + return mif->mif_smapper_write_sram(mif, smapper->bank[bank].phy_index, num_entries, first_entry, addr); +} + +u32 mifsmapper_get_bank_base_address(struct mifsmapper *smapper, u8 bank) +{ + struct scsc_mif_abs *mif; + + /* Get abs implementation */ + mif = smapper->mif; + + return mif->mif_smapper_get_bank_base_address(mif, bank); +} + +int mifsmapper_deinit(struct mifsmapper *smapper) +{ + u8 i = 0, total_num_banks; + + spin_lock(&smapper->lock); + + SCSC_TAG_INFO(MIF, "Deinit SMAPPER\n"); + + if (!smapper->in_use) { + spin_unlock(&smapper->lock); + return -ENODEV; + } + + total_num_banks = smapper->num_large_banks + smapper->num_small_banks; + for (; i < total_num_banks; i++) { + kfree(smapper->bank[i].entries_bm); + smapper->bank[i].num_entries = 0; + smapper->bank[i].mem_range_bytes = 0; + } + + kfree(smapper->bank_bm_large); + kfree(smapper->bank_bm_small); + kfree(smapper->bank); + + smapper->in_use = false; + + spin_unlock(&smapper->lock); + return 0; +} diff --git a/drivers/misc/samsung/scsc/mifsmapper.h b/drivers/misc/samsung/scsc/mifsmapper.h new file mode 100644 index 000000000000..715f1f5503a8 --- /dev/null +++ b/drivers/misc/samsung/scsc/mifsmapper.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#ifndef __MIFSMAPPER_H +#define __MIFSMAPPER_H + +#include + +struct mifsmapper; +struct scsc_mif_abs; +struct mutex; + +int mifsmapper_init(struct mifsmapper *smapper, struct scsc_mif_abs *mif); +u16 mifsmapper_get_alignment(struct mifsmapper *smapper); +int mifsmapper_alloc_bank(struct mifsmapper *smapper, bool large_bank, u32 entry_size, u16 *entries); +int mifsmapper_free_bank(struct mifsmapper *smapper, u8 bank); +int mifsmapper_get_entries(struct mifsmapper *smapper, u8 bank, u8 num_entries, u8 *entr); +int mifsmapper_free_entries(struct mifsmapper *smapper, u8 bank, u8 num_entries, u8 *entries); +void mifsmapper_configure(struct mifsmapper *smapper, u32 granularity); +int mifsmapper_write_sram(struct mifsmapper *smapper, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr); +u32 mifsmapper_get_bank_base_address(struct mifsmapper *smapper, u8 bank); +int mifsmapper_deinit(struct mifsmapper *smapper); + +#define MIFSMAPPER_160 4 +#define MIFSMAPPER_64 7 + +#define MIFSMAPPER_NOT_VALID 0 +#define MIFSMAPPER_VALID 1 + +struct mifsmapper_bank { + unsigned long *entries_bm; + u32 num_entries; + u32 num_entries_left; + u32 mem_range_bytes; + u8 phy_index; + u32 granularity; + bool in_use; +}; + +/* Inclusion in core.c treat it as opaque */ +struct mifsmapper { + bool in_use; + spinlock_t lock; + struct scsc_mif_abs *mif; + struct mifsmapper_bank *bank; /* Bank reference created after reading HW capabilities */ + unsigned long *bank_bm_large; + unsigned long *bank_bm_small; + u32 num_large_banks; + u32 num_small_banks; + u16 align; +}; +#endif diff --git a/drivers/misc/samsung/scsc/mifstream.c b/drivers/misc/samsung/scsc/mifstream.c new file mode 100644 index 000000000000..6cbb664f565c --- /dev/null +++ b/drivers/misc/samsung/scsc/mifstream.c @@ -0,0 +1,185 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * MIF Byte Stream (Implementation) + */ + +/* Implements */ +#include "scsc_mx_impl.h" +#include "mifstream.h" + +/* Uses */ +#include "mifintrbit.h" + +/* Public Functions */ + +void mif_stream_config_serialise(struct mif_stream *stream, struct mxstreamconf *stream_conf) +{ + stream_conf->read_bit_idx = stream->read_bit_idx; + stream_conf->write_bit_idx = stream->write_bit_idx; + cpacketbuffer_config_serialise(&stream->buffer, &stream_conf->buf_conf); +} + +int mif_stream_init(struct mif_stream *stream, enum scsc_mif_abs_target target, enum MIF_STREAM_DIRECTION direction, uint32_t num_packets, uint32_t packet_size, + struct scsc_mx *mx, enum MIF_STREAM_INTRBIT_TYPE intrbit, mifintrbit_handler tohost_irq_handler, void *data) +{ + struct mifintrbit *intr; + int r, r1, r2; + + stream->mx = mx; + r = cpacketbuffer_init(&stream->buffer, num_packets, packet_size, mx); + if (r) + return r; + + intr = scsc_mx_get_intrbit(mx); + + r1 = mifintrbit_alloc_tohost(intr, tohost_irq_handler, data); + if (r1 < 0) { + cpacketbuffer_release(&stream->buffer); + return r1; + } + + /** + * MIF interrupt bit 0 in both the to-r4 and to-m4 registers are reserved + * for purpose of forcing panics from the MX Manager directly or via the + * gdb monitor stacks. + * + * At stream initialization the gdb transport requests for the reserved bits + * rather than dynamic allocation of interrupt bits. + * + * So if the interrupt bit type requested is Reserved, just Assign the pre- + * reserved interrupt bits. + */ + if (intrbit == MIF_STREAM_INTRBIT_TYPE_RESERVED) { + if (target == SCSC_MIF_ABS_TARGET_M4) + r2 = MIFINTRBIT_RESERVED_PANIC_M4; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (target == SCSC_MIF_ABS_TARGET_M4_1) + r2 = MIFINTRBIT_RESERVED_PANIC_M4_1; +#endif + else + r2 = MIFINTRBIT_RESERVED_PANIC_R4; + } else + r2 = mifintrbit_alloc_fromhost(intr, target); + + if (r2 < 0) { + cpacketbuffer_release(&stream->buffer); + mifintrbit_free_tohost(intr, r1); + return r2; + } + switch (direction) { + case MIF_STREAM_DIRECTION_OUT: + stream->read_bit_idx = r1; + stream->write_bit_idx = r2; + break; + case MIF_STREAM_DIRECTION_IN: + /* Default value for the shared memory region */ + memset(stream->buffer.buffer, 0xff, num_packets * packet_size); + /* Commit */ + smp_wmb(); + stream->read_bit_idx = r2; + stream->write_bit_idx = r1; + break; + default: + cpacketbuffer_release(&stream->buffer); + mifintrbit_free_tohost(intr, r1); + mifintrbit_free_fromhost(intr, r2, target); + return -EINVAL; + } + stream->direction = direction; + stream->peer = (enum MIF_STREAM_PEER)target; + return 0; +} + +void mif_stream_release(struct mif_stream *stream) +{ + struct mifintrbit *intr; + + intr = scsc_mx_get_intrbit(stream->mx); + if (stream->direction == MIF_STREAM_DIRECTION_IN) { + mifintrbit_free_tohost(intr, stream->write_bit_idx); + mifintrbit_free_fromhost(intr, stream->read_bit_idx, (enum scsc_mif_abs_target)stream->peer); + } else { + mifintrbit_free_tohost(intr, stream->read_bit_idx); + mifintrbit_free_fromhost(intr, stream->write_bit_idx, (enum scsc_mif_abs_target)stream->peer); + } + cpacketbuffer_release(&stream->buffer); +} + +uint32_t mif_stream_read(struct mif_stream *stream, void *buf, uint32_t num_bytes) +{ + struct scsc_mif_abs *mif_abs = scsc_mx_get_mif_abs(stream->mx); + + uint32_t num_bytes_read = cpacketbuffer_read(&stream->buffer, buf, num_bytes); + + if (num_bytes_read > 0) + /* Signal that the read is finished to anyone interested */ + mif_abs->irq_bit_set(mif_abs, stream->read_bit_idx, (enum scsc_mif_abs_target)stream->peer); + + return num_bytes_read; +} + +const void *mif_stream_peek(struct mif_stream *stream, const void *current_packet) +{ + return cpacketbuffer_peek(&stream->buffer, current_packet); +} + +void mif_stream_peek_complete(struct mif_stream *stream, const void *packet) +{ + struct scsc_mif_abs *mif_abs = scsc_mx_get_mif_abs(stream->mx); + + cpacketbuffer_peek_complete(&stream->buffer, packet); + + /* Signal that the read is finished to anyone interested */ + mif_abs->irq_bit_set(mif_abs, stream->read_bit_idx, (enum scsc_mif_abs_target)stream->peer); +} + +bool mif_stream_write(struct mif_stream *stream, const void *buf, uint32_t num_bytes) +{ + struct scsc_mif_abs *mif_abs = scsc_mx_get_mif_abs(stream->mx); + + if (!cpacketbuffer_write(&stream->buffer, buf, num_bytes)) + return false; + + /* Kick the assigned interrupt to let others know new data is available */ + mif_abs->irq_bit_set(mif_abs, stream->write_bit_idx, (enum scsc_mif_abs_target)stream->peer); + + return true; +} + +bool mif_stream_write_gather(struct mif_stream *stream, const void **bufs, uint32_t *lengths, uint32_t num_bufs) +{ + struct scsc_mif_abs *mif_abs = scsc_mx_get_mif_abs(stream->mx); + + if (!cpacketbuffer_write_gather(&stream->buffer, bufs, lengths, num_bufs)) + return false; + + /* Kick the assigned interrupt to let others know new data is available */ + mif_abs->irq_bit_set(mif_abs, stream->write_bit_idx, (enum scsc_mif_abs_target)stream->peer); + return true; +} + +uint32_t mif_stream_block_size(struct mif_stream *stream) +{ + return cpacketbuffer_packet_size(&stream->buffer); +} + +uint8_t mif_stream_read_interrupt(struct mif_stream *stream) +{ + return stream->read_bit_idx; +} + +uint8_t mif_stream_write_interrupt(struct mif_stream *stream) +{ + return stream->write_bit_idx; +} + +void mif_stream_log(const struct mif_stream *stream, enum scsc_log_level log_level) +{ + cpacketbuffer_log(&stream->buffer, log_level); +} + diff --git a/drivers/misc/samsung/scsc/mifstream.h b/drivers/misc/samsung/scsc/mifstream.h new file mode 100644 index 000000000000..fe02a0bde2a3 --- /dev/null +++ b/drivers/misc/samsung/scsc/mifstream.h @@ -0,0 +1,191 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * MIF stream (Interface) + * + * Provides a one-way communication mechanism between two points. The consumer side + * will be notified via an interrupt when the producer side writes data to the + * stream, and likewise the producer will be notified when the consumer has read + * data from the stream. + * + * It is expected that the data sent across the stream consists of fixed-size + * packets, and that the underlying storage mechanism is initialised to use a packet size + * that is at least as large as the largest message size. If this is not the case, + * callers are responsible for handling reading of partial messages from the stream + * in multiples of the packet size. + */ + +#ifndef MIFSTREAM_H__ +#define MIFSTREAM_H__ + +/* Uses */ + +#include "cpacket_buffer.h" +#include "mifintrbit.h" +#include "scsc_logring_common.h" + +/* Public Types */ + +enum MIF_STREAM_PEER { + MIF_STREAM_PEER_R4, + MIF_STREAM_PEER_M4, +}; + +enum MIF_STREAM_DIRECTION { + MIF_STREAM_DIRECTION_IN, + MIF_STREAM_DIRECTION_OUT, +}; + +/** + * Defines for the MIF Stream interrupt bits + * + * MIF_STREAM_INTRBIT_TYPE_RESERVED: the bits are reserved + * at initialization and are assigned to GDB transport channels. + * It is for purpose of forcing Panics from either MX manager or GDB + * + * MIF_STREAM_INTRBIT_TYPE_ALLOC: the bits are allocated dynamically + * when a stream is initialized + */ +enum MIF_STREAM_INTRBIT_TYPE { + MIF_STREAM_INTRBIT_TYPE_RESERVED, + MIF_STREAM_INTRBIT_TYPE_ALLOC, +}; + +/* Forward Decls */ + +struct mif_stream; + +/* Public Functions */ + +/** + * Initialises MIF Stream state. + */ +int mif_stream_init(struct mif_stream *stream, enum scsc_mif_abs_target target, enum MIF_STREAM_DIRECTION direction, uint32_t num_packets, uint32_t packet_size, + struct scsc_mx *mx, enum MIF_STREAM_INTRBIT_TYPE intrbit, mifintrbit_handler tohost_irq_handler, void *data); +/** + * Initialises MIF Output Stream state. + */ +void mif_stream_release(struct mif_stream *stream); +/** + * Reads the given number of bytes from the MIF stream, copying them + * to the provided address. This removes the read data from the stream. + * + * Returns the number of bytes read. + */ +uint32_t mif_stream_read(struct mif_stream *stream, void *buf, uint32_t num_bytes); + +/** + * Returns a pointer to the next packet of data within the stream, without + * removing it. This can be used to processss data in place without needing to + * copy it first. + * + * If multiple packets are present these can be read in turn by setting the value + * of current_packet to the returned value from the previous call to mif_stream_peek. + * Each time the returned pointer will advance in the stream by mif_stream_block_size() + * bytes. + * + * Callers cannot assume that multiple calls to mif_stream_peek() will return + * consecutive addresses. + * + * mif_stream_peek_complete must be called to remove the packet(s) from the stream. + * + * Returns a pointer to the beginning of the packet to read, or NULL if there is no + * packet to process. + * + * Example use: + * // Get the first data packet + * void *current_packet = mif_stream_peek( buffer, NULL ); + * void *last_packet = NULL; + * while( current_packet != NULL ) + * { + * // Process data packet + * ... + * + * // Get the next data packet + * last_packet = current_packet; + * current_packet = mif_stream_peek( buffer, current_packet ); + * } + * + * // Remove all processed packets from the stream + * if( last_packet != NULL ) + * { + * mif_stream_peek( buffer, last_packet ); + * } + */ +const void *mif_stream_peek(struct mif_stream *stream, const void *current_packet); + +/** + * Removes all packets from the stream up to and including the given + * packet. + * + * This must be called after using mif_stream_peek to indicate that packet(s) + * can be removed from the stream. + */ +void mif_stream_peek_complete(struct mif_stream *stream, const void *packet); + +/** + * Writes the given number of bytes to the MIF stream. + * + * Returns true if the block was written, false if there is not enough + * free space in the buffer for the data. + */ +bool mif_stream_write(struct mif_stream *stream, const void *buf, uint32_t num_bytes); + +/** + * Writes a set of non-contiguous data blocks to the MIF stream + * as a contiguous set. + * + * Returns true if the blocks were written, false if there is not enough + * free space in the buffer for the block. + */ +bool mif_stream_write_gather(struct mif_stream *stream, const void **bufs, uint32_t *lengths, uint32_t num_bufs); + +/** + * Returns the size in bytes of each individual block within the stream. + * + * When reading data from the stream using mif_stream_read or mif_stream_peek + * this value is the amount of data + */ +uint32_t mif_stream_block_size(struct mif_stream *stream); + +/** + * Returns the interrupt number that will be triggered by reads from the stream + */ +uint8_t mif_stream_read_interrupt(struct mif_stream *stream); + +/** + * Returns the interrupt number that will be triggered by writes to the stream + */ +uint8_t mif_stream_write_interrupt(struct mif_stream *stream); + +/* + * Initialises the stream's part of the configuration area + */ +void mif_stream_config_serialise(struct mif_stream *stream, struct mxstreamconf *stream_conf); + +/** + * Log the state of this stream at the specified log_level. + */ +void mif_stream_log(const struct mif_stream *stream, enum scsc_log_level log_level); + +/** + * MIF Packet Stream Descriptor. + */ +struct mif_stream { + struct scsc_mx *mx; + struct cpacketbuffer buffer; + + /** MIF stream peer, R4 or M4? */ + enum MIF_STREAM_PEER peer; + + /** MIF interrupt bit index, one in each direction */ + uint8_t read_bit_idx; + uint8_t write_bit_idx; + enum MIF_STREAM_DIRECTION direction; +}; + +#endif /* MIFSTREAM_H__ */ diff --git a/drivers/misc/samsung/scsc/mx140_clk.c b/drivers/misc/samsung/scsc/mx140_clk.c new file mode 100755 index 000000000000..661398034159 --- /dev/null +++ b/drivers/misc/samsung/scsc/mx140_clk.c @@ -0,0 +1,786 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "scsc_mx_impl.h" + +#ifdef CONFIG_SCSC_CLK20MHZ_TEST +#include "mx140_clk_test.h" +#endif + +/* Note: define MX140_CLK_VERBOSE_CALLBACKS to get more callbacks when events occur. + * without this, the only callbacks are failure/success from request() + */ +static int auto_start; +module_param(auto_start, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(auto_start, "Start service automatically: Default 0: disabled, 1: Enabled"); + +static DEFINE_MUTEX(clk_lock); +static DEFINE_MUTEX(clk_work_lock); + +struct workqueue_struct *mx140_clk20mhz_wq; +struct work_struct mx140_clk20mhz_work; + +static int recovery; +static int recovery_pending_stop_close; +#define MX140_SERVICE_RECOVERY_TIMEOUT 20000 + +/* Static Singleton */ +static struct mx140_clk20mhz { + /* scsc_service_client has to be the first */ + struct scsc_service_client mx140_clk20mhz_service_client; + struct scsc_service *mx140_clk20mhz_service; + struct scsc_mx *mx; + + atomic_t clk_request; + atomic_t maxwell_is_present; + atomic_t mx140_clk20mhz_service_started; + atomic_t request_pending; + atomic_t mx140_clk20mhz_service_failed; + + void *data; + void (*mx140_clk20mhz_client_cb)(void *data, enum mx140_clk20mhz_status event); + + struct proc_dir_entry *procfs_ctrl_dir; + u32 procfs_ctrl_dir_num; + + struct wake_lock clk_wake_lock; + struct completion recovery_probe_completion; + +} clk20mhz; + +static void mx140_clk20mhz_wq_stop(void); +static int mx140_clk20mhz_stop_service(struct scsc_mx *mx); + +#ifndef AID_MXPROC +#define AID_MXPROC 0 +#endif + +static void mx140_clk20mhz_restart(void); + +#define MX_CLK20_DIRLEN 128 +static const char *procdir_ctrl = "driver/mx140_clk"; +static u32 proc_count; + +/* Framework for registering proc handlers */ +#define MX_CLK20_PROCFS_RW_FILE_OPS(name) \ + static ssize_t mx_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static ssize_t mx_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations mx_procfs_ ## name ## _fops = { \ + .read = mx_procfs_ ## name ## _read, \ + .write = mx_procfs_ ## name ## _write, \ + .open = mx_clk20_procfs_generic_open, \ + .llseek = generic_file_llseek \ + } +#define MX_CLK20_PROCFS_RO_FILE_OPS(name) \ + static ssize_t mx_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations mx_procfs_ ## name ## _fops = { \ + .read = mx_procfs_ ## name ## _read, \ + .open = mx_clk20_procfs_generic_open, \ + .llseek = generic_file_llseek \ + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MX_PDE_DATA(inode) PDE_DATA(inode) +#else +#define MX_PDE_DATA(inode) (PDE(inode)->data) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MX_CLK20_PROCFS_SET_UID_GID(_entry) \ + do { \ + kuid_t proc_kuid = KUIDT_INIT(AID_MXPROC); \ + kgid_t proc_kgid = KGIDT_INIT(AID_MXPROC); \ + proc_set_user(_entry, proc_kuid, proc_kgid); \ + } while (0) +#else +#define MX_CLK20_PROCFS_SET_UID_GID(entry) \ + do { \ + (entry)->uid = AID_MXPROC; \ + (entry)->gid = AID_MXPROC; \ + } while (0) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MX_CLK20_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &mx_procfs_ ## name ## _fops, _sdev); \ + MX_CLK20_PROCFS_SET_UID_GID(entry); \ + } while (0) +#else +#define MX_CLK20_PROCFS_ADD_FILE(_data, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = create_proc_entry(# name, mode, parent); \ + if (entry) { \ + entry->proc_fops = &mx_procfs_ ## name ## _fops; \ + entry->data = _data; \ + MX_CLK20_PROCFS_SET_UID_GID(entry); \ + } \ + } while (0) +#endif + +#define MX_CLK20_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent) + +/* Open handler */ +static int mx_clk20_procfs_generic_open(struct inode *inode, struct file *file) +{ + file->private_data = MX_PDE_DATA(inode); + return 0; +} + +/* No-op */ +static ssize_t mx_procfs_restart_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + (void)user_buf; + (void)count; + (void)ppos; + + return 0; +} + +/* Restart clock service */ +static ssize_t mx_procfs_restart_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + (void)file; + (void)user_buf; + (void)ppos; + + mx140_clk20mhz_restart(); + + SCSC_TAG_INFO(MX_PROC, "OK\n"); + return count; +} + +/* Register proc handler */ +MX_CLK20_PROCFS_RW_FILE_OPS(restart); + +/* Populate proc node */ +static int mx140_clk20mhz_create_ctrl_proc_dir(struct mx140_clk20mhz *clk20mhz) +{ + char dir[MX_CLK20_DIRLEN]; + struct proc_dir_entry *parent; + + (void)snprintf(dir, sizeof(dir), "%s%d", procdir_ctrl, proc_count); + parent = proc_mkdir(dir, NULL); + if (!parent) { + SCSC_TAG_ERR(MX_PROC, "failed to create proc dir %s\n", procdir_ctrl); + return -EINVAL; + } +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = clk20mhz; +#endif + clk20mhz->procfs_ctrl_dir = parent; + clk20mhz->procfs_ctrl_dir_num = proc_count; + MX_CLK20_PROCFS_ADD_FILE(clk20mhz, restart, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SCSC_TAG_DEBUG(MX_PROC, "created %s proc dir\n", dir); + proc_count++; + + return 0; +} + +/* Remove proc node */ +static void mx140_clk20mhz_remove_ctrl_proc_dir(struct mx140_clk20mhz *clk20mhz) +{ + if (clk20mhz->procfs_ctrl_dir) { + char dir[MX_CLK20_DIRLEN]; + + MX_CLK20_PROCFS_REMOVE_FILE(restart, clk20mhz->procfs_ctrl_dir); + (void)snprintf(dir, sizeof(dir), "%s%d", procdir_ctrl, clk20mhz->procfs_ctrl_dir_num); + remove_proc_entry(dir, NULL); + clk20mhz->procfs_ctrl_dir = NULL; + proc_count--; + SCSC_TAG_DEBUG(MX_PROC, "removed %s proc dir\n", dir); + } +} + +/* Maxwell manager has detected an issue and the service should freeze */ +static void mx140_clk20mhz_stop_on_failure(struct scsc_service_client *client) +{ + atomic_set(&clk20mhz.mx140_clk20mhz_service_failed, 1); + + mutex_lock(&clk_work_lock); + recovery = 1; + reinit_completion(&clk20mhz.recovery_probe_completion); + mutex_unlock(&clk_work_lock); + +#ifdef MX140_CLK_VERBOSE_CALLBACKS + /* If call back is registered, inform the user about an asynchronous failure */ + if (clk20mhz.mx140_clk20mhz_client_cb) + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, MX140_CLK_ASYNC_FAIL); +#endif + + SCSC_TAG_INFO(CLK20, "\n"); +} + +/* Maxwell manager has handled a failure and the chip has been resat. */ +static void mx140_clk20mhz_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + (void)scsc_panic_code; + atomic_set(&clk20mhz.mx140_clk20mhz_service_failed, 1); + +#ifdef MX140_CLK_VERBOSE_CALLBACKS + /* If call back is registered, inform the user about an asynchronous failure */ + if (clk20mhz.mx140_clk20mhz_client_cb) + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, MX140_CLK_ASYNC_FAIL); +#endif + SCSC_TAG_INFO(CLK20, "\n"); +} + +static int mx140_clk20mhz_start_service(struct scsc_mx *mx) +{ + int r; + + /* Open the service and get resource pointers */ + clk20mhz.mx140_clk20mhz_service = scsc_mx_service_open(mx, SCSC_SERVICE_ID_CLK20MHZ, &clk20mhz.mx140_clk20mhz_service_client, &r); + if (!clk20mhz.mx140_clk20mhz_service) { + SCSC_TAG_ERR(CLK20, "scsc_mx_service_open failed %d\n", r); + return r; + } + + /* In case of recovery ensure WLBT has ownership */ + if (atomic_read(&clk20mhz.mx140_clk20mhz_service_failed)) { + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(clk20mhz.mx); + + if (!mif) + goto error; + + if (mif->mif_restart) + mif->mif_restart(mif); + + atomic_set(&clk20mhz.mx140_clk20mhz_service_failed, 0); + } + + /* Start service. Will bring-up the chip if the chip is disabled */ + if (scsc_mx_service_start(clk20mhz.mx140_clk20mhz_service, 0)) { + SCSC_TAG_ERR(CLK20, "scsc_mx_service_start failed\n"); + goto error; + } + + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 1); + + /* If call back is registered, inform the user that the service has started */ + if (atomic_read(&clk20mhz.clk_request) && clk20mhz.mx140_clk20mhz_client_cb) + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, MX140_CLK_STARTED); + + return 0; +error: + return -EIO; +} + +static int mx140_clk20mhz_stop_service(struct scsc_mx *mx) +{ + int r; + + if (!atomic_read(&clk20mhz.mx140_clk20mhz_service_started)) { + SCSC_TAG_INFO(CLK20, "Service not started\n"); + return -ENODEV; + } + + /* Stop service. */ + if (scsc_mx_service_stop(clk20mhz.mx140_clk20mhz_service)) { + SCSC_TAG_ERR(CLK20, "scsc_mx_service_stop failed\n"); +#ifdef MX140_CLK_VERBOSE_CALLBACKS + /* If call back is registered, inform the user that the service has failed to stop */ + if (clk20mhz.mx140_clk20mhz_client_cb) + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, MX140_CLK_NOT_STOPPED); + return -EIO; +#endif + } + + /* Ignore a service_stop timeout above as it's better to try to close */ + + /* Close service, if no other service is using Maxwell, chip will turn off */ + r = scsc_mx_service_close(clk20mhz.mx140_clk20mhz_service); + if (r) { + SCSC_TAG_ERR(CLK20, "scsc_mx_service_close failed\n"); + +#ifdef MX140_CLK_VERBOSE_CALLBACKS + /* If call back is registered, inform the user that the service has failed to close */ + if (clk20mhz.mx140_clk20mhz_client_cb) + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, MX140_CLK_NOT_STOPPED); + return -EIO; +#endif + } + + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 0); + +#ifdef MX140_CLK_VERBOSE_CALLBACKS + /* If call back is registered, inform the user that the service has stopped */ + if (clk20mhz.mx140_clk20mhz_client_cb) + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, MX140_CLK_STOPPED); +#endif + return 0; +} + +#define MX140_CLK_TRIES (20) + +static void mx140_clk20mhz_work_func(struct work_struct *work) +{ + int i; + int r = 0; + enum mx140_clk20mhz_status status; + + mutex_lock(&clk_work_lock); + + for (i = 0; i < MX140_CLK_TRIES; i++) { + if (atomic_read(&clk20mhz.clk_request) == 0) { + SCSC_TAG_INFO(CLK20, "mx140_clk20mhz_start_service no longer requested\n"); + recovery = 0; + mutex_unlock(&clk_work_lock); + return; + } + + SCSC_TAG_INFO(CLK20, "Calling mx140_clk20mhz_start_service\n"); + r = mx140_clk20mhz_start_service(clk20mhz.mx); + switch (r) { + case 0: + SCSC_TAG_INFO(CLK20, "mx140_clk20mhz_start_service OK\n"); + recovery = 0; + mutex_unlock(&clk_work_lock); + return; + case -EAGAIN: + SCSC_TAG_INFO(CLK20, "FW not found because filesystem not mounted yet, retrying...\n"); + msleep(500); /* No FS yet, retry */ + break; + default: + SCSC_TAG_INFO(CLK20, "mx140_clk20mhz_start_service failed %d\n", r); + goto err; + } + } +err: + SCSC_TAG_ERR(CLK20, "Unable to start the 20MHz clock service\n"); + + /* Deferred service start failure or timeout. + * We assume it'll never manage to start - e.g. bad or missing f/w. + */ + if (r) { + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(clk20mhz.mx); + + SCSC_TAG_ERR(CLK20, "Deferred start timeout (%d)\n", r); + atomic_set(&clk20mhz.mx140_clk20mhz_service_failed, 1); + + /* Switch USBPLL ownership to AP so USB may be used */ + if (mif && mif->mif_cleanup) + mif->mif_cleanup(mif); + } + + /* If call back is registered, inform the user that the service has failed to start */ + if (atomic_read(&clk20mhz.clk_request) && clk20mhz.mx140_clk20mhz_client_cb) { + /* The USB/PLL driver has inadequate error handing... + * Lie that the start was successful when AP has control + */ + status = atomic_read(&clk20mhz.mx140_clk20mhz_service_failed) ? MX140_CLK_STARTED : MX140_CLK_NOT_STARTED; + + /* Also lie that the start was successful when the mx140 driver is halted after f/w panic */ + if (r == -EILSEQ || r == -EPERM) + status = MX140_CLK_STARTED; + + SCSC_TAG_INFO(CLK20, "cb %d\n", status); + clk20mhz.mx140_clk20mhz_client_cb(clk20mhz.data, status); + } + recovery = 0; + mutex_unlock(&clk_work_lock); +} + +static void mx140_clk20mhz_wq_init(void) +{ + mx140_clk20mhz_wq = create_singlethread_workqueue("mx140_clk20mhz_wq"); + INIT_WORK(&mx140_clk20mhz_work, mx140_clk20mhz_work_func); +} + +static void mx140_clk20mhz_wq_stop(void) +{ + cancel_work_sync(&mx140_clk20mhz_work); + flush_workqueue(mx140_clk20mhz_wq); +} + +static void mx140_clk20mhz_wq_deinit(void) +{ + mx140_clk20mhz_wq_stop(); + destroy_workqueue(mx140_clk20mhz_wq); +} + +static void mx140_clk20mhz_wq_start(void) +{ + queue_work(mx140_clk20mhz_wq, &mx140_clk20mhz_work); +} + +/* Register a callback function to indicate to the (USB) client the status of + * the clock request + */ +int mx140_clk20mhz_register(void (*client_cb)(void *data, enum mx140_clk20mhz_status event), void *data) +{ + SCSC_TAG_INFO(CLK20, "cb %p, %p\n", client_cb, data); + + mutex_lock(&clk_lock); + if (clk20mhz.mx140_clk20mhz_client_cb == NULL) { + SCSC_TAG_INFO(CLK20, "clk20Mhz client registered\n"); + clk20mhz.mx140_clk20mhz_client_cb = client_cb; + clk20mhz.data = data; + mutex_unlock(&clk_lock); + return 0; + } + + SCSC_TAG_ERR(CLK20, "clk20Mhz client already registered\n"); + mutex_unlock(&clk_lock); + return -EEXIST; +} +EXPORT_SYMBOL(mx140_clk20mhz_register); + +/* Unregister callback function */ +void mx140_clk20mhz_unregister(void) +{ + SCSC_TAG_INFO(CLK20, "\n"); + + mutex_lock(&clk_lock); + if (clk20mhz.mx140_clk20mhz_client_cb == NULL) { + SCSC_TAG_INFO(CLK20, "clk20Mhz client not registered\n"); + mutex_unlock(&clk_lock); + return; + } + + clk20mhz.mx140_clk20mhz_client_cb = NULL; + clk20mhz.data = NULL; + mutex_unlock(&clk_lock); +} +EXPORT_SYMBOL(mx140_clk20mhz_unregister); + +/* Indicate that an external client requires mx140's 20 MHz clock. + * The Core driver will boot mx140 as required and ensure that the + * clock remains running. + * + * If a callback was installed by register(), do this asynchronously. + */ +int mx140_clk20mhz_request(void) +{ + mutex_lock(&clk_lock); + atomic_inc(&clk20mhz.clk_request); + + SCSC_TAG_INFO(CLK20, "%d\n", atomic_read(&clk20mhz.clk_request)); + + if (!atomic_read(&clk20mhz.maxwell_is_present)) { + SCSC_TAG_INFO(CLK20, "Maxwell is not present yet, store request\n"); + atomic_set(&clk20mhz.request_pending, 1); + mutex_unlock(&clk_lock); + return 0; + } + + if (recovery) { + int r; + + mutex_unlock(&clk_lock); + r = wait_for_completion_timeout(&clk20mhz.recovery_probe_completion, + msecs_to_jiffies(MX140_SERVICE_RECOVERY_TIMEOUT)); + mutex_lock(&clk_lock); + if (r == 0) { + SCSC_TAG_INFO(CLK20, "recovery_probe_completion timeout - try a start\n"); + mx140_clk20mhz_wq_start(); + } + } else if (!atomic_read(&clk20mhz.mx140_clk20mhz_service_started)) + mx140_clk20mhz_wq_start(); + else + SCSC_TAG_INFO(CLK20, "Service already started\n"); + + mutex_unlock(&clk_lock); + return 0; +} +EXPORT_SYMBOL(mx140_clk20mhz_request); + +/* Indicate that an external client no requires mx140's 20 MHz clock + * The Core driver will shut down mx140 if no other services are + * currently running + * + * If a callback was installed by register(), do this asynchronously. + */ +int mx140_clk20mhz_release(void) +{ + int ret = 0; + + mutex_lock(&clk_lock); + atomic_dec(&clk20mhz.clk_request); + SCSC_TAG_INFO(CLK20, "%d\n", atomic_read(&clk20mhz.clk_request)); + + if (!atomic_read(&clk20mhz.maxwell_is_present)) { + SCSC_TAG_INFO(CLK20, "Maxwell is released before probe\n"); + if (!atomic_read(&clk20mhz.request_pending)) { + SCSC_TAG_INFO(CLK20, "Maxwell had request pending. Cancel it\n"); + atomic_set(&clk20mhz.request_pending, 0); + } + mutex_unlock(&clk_lock); + return 0; + } + + /* Cancel any pending attempt */ + mx140_clk20mhz_wq_stop(); + + if (recovery) { + recovery_pending_stop_close = 1; + } else { + ret = mx140_clk20mhz_stop_service(clk20mhz.mx); + if (ret == -ENODEV) { + /* Suppress error if it wasn't running */ + ret = 0; + } + } + + /* Suppress stop failure if the service is failed */ + if (atomic_read(&clk20mhz.mx140_clk20mhz_service_failed)) { + SCSC_TAG_INFO(CLK20, "Return OK as control is with AP\n"); + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 0); + ret = 0; + } + + mutex_unlock(&clk_lock); + return ret; +} +EXPORT_SYMBOL(mx140_clk20mhz_release); + +/* Probe callback after platform driver is registered */ +void mx140_clk20mhz_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + SCSC_TAG_INFO(CLK20, "\n"); + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery) { + SCSC_TAG_INFO(CLK20, "Ignore probe - no recovery in progress\n"); + return; + } + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery) { + SCSC_TAG_INFO(CLK20, "Recovery probe\n"); + + /** + * If recovery_pending_stop_close is set, then there was a stop + * during recovery (could be due to USB cable unplugged) so + * recovery should just stop here. + * The mx140_clk service has been closed in the remove callback. + */ + mutex_lock(&clk_lock); + if (recovery_pending_stop_close) { + SCSC_TAG_INFO(CLK20, "Recovery probe - stop during recovery, so don't recover\n"); + recovery_pending_stop_close = 0; + recovery = 0; + mutex_unlock(&clk_lock); + /** + * Should there have been a new start request during + * recovery (very unlikely), then the complete timeout + * will ensure that a start is requested. + */ + return; + } + mutex_unlock(&clk_lock); + + mutex_lock(&clk_work_lock); + mx140_clk20mhz_wq_start(); + mutex_unlock(&clk_work_lock); + complete_all(&clk20mhz.recovery_probe_completion); + } else { + SCSC_TAG_INFO(CLK20, "Maxwell probed\n"); + clk20mhz.mx = mx; + clk20mhz.mx140_clk20mhz_service_client.stop_on_failure = mx140_clk20mhz_stop_on_failure; + clk20mhz.mx140_clk20mhz_service_client.failure_reset = mx140_clk20mhz_failure_reset; + + mx140_clk20mhz_create_ctrl_proc_dir(&clk20mhz); + + mx140_clk20mhz_wq_init(); + + atomic_set(&clk20mhz.maxwell_is_present, 1); + + mutex_lock(&clk_work_lock); + if ((auto_start || atomic_read(&clk20mhz.request_pending))) { + atomic_set(&clk20mhz.request_pending, 0); + SCSC_TAG_INFO(CLK20, "start pending service\n"); + mx140_clk20mhz_wq_start(); + } + mutex_unlock(&clk_work_lock); + } +} + + +static void mx140_clk20mhz_restart(void) +{ + int r; + struct scsc_mif_abs *mif; + + SCSC_TAG_INFO(CLK20, "\n"); + + wake_lock(&clk20mhz.clk_wake_lock); + + mutex_lock(&clk_lock); + + if (!atomic_read(&clk20mhz.mx140_clk20mhz_service_started)) { + SCSC_TAG_INFO(CLK20, "service wasn't started\n"); + goto done; + } + + mif = scsc_mx_get_mif_abs(clk20mhz.mx); + if (mif == NULL) + goto done; + + /* Don't stop the 20 MHz clock service. Leave it running until + * WLBT resets due to the service_close(). + */ + + /* Ensure USBPLL is running and owned by AP, to stop USB disconnect */ + if (mif->mif_cleanup) + mif->mif_cleanup(mif); + + r = scsc_mx_service_close(clk20mhz.mx140_clk20mhz_service); + if (r) { + SCSC_TAG_ERR(CLK20, "scsc_mx_service_close failed (%d)\n", r); + goto done; + } + + /* ...and restart the 20 MHz clock service */ + clk20mhz.mx140_clk20mhz_service = scsc_mx_service_open(clk20mhz.mx, SCSC_SERVICE_ID_CLK20MHZ, &clk20mhz.mx140_clk20mhz_service_client, &r); + if (clk20mhz.mx140_clk20mhz_service == NULL) { + SCSC_TAG_ERR(CLK20, "reopen failed %d\n", r); + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 0); + goto done; + } + + /* Ensure USBPLL is owned by WLBT again */ + if (mif->mif_restart) + mif->mif_restart(mif); + + r = scsc_mx_service_start(clk20mhz.mx140_clk20mhz_service, 0); + if (r) { + SCSC_TAG_ERR(CLK20, "restart failed %d\n", r); + r = scsc_mx_service_close(clk20mhz.mx140_clk20mhz_service); + if (r) + SCSC_TAG_ERR(CLK20, "scsc_mx_service_close failed %d\n", r); + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 0); + goto done; + } + + SCSC_TAG_INFO(CLK20, "restarted\n"); +done: + mutex_unlock(&clk_lock); + wake_unlock(&clk20mhz.clk_wake_lock); +} + +/* Remove callback platform driver is unregistered */ +void mx140_clk20mhz_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + int r; + + mutex_lock(&clk_work_lock); + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery) { + SCSC_TAG_INFO(CLK20, "Ignore recovery remove: Service driver not active\n"); + goto done; + } else if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery) { + struct scsc_mif_abs *mif; + + SCSC_TAG_INFO(CLK20, "Recovery remove\n"); + + mutex_lock(&clk_lock); + mx140_clk20mhz_wq_stop(); + + mif = scsc_mx_get_mif_abs(clk20mhz.mx); + if (mif == NULL) + goto done_local; + + /** + * If there's been a stop during recovery ensure that the + * mx140_clk service is closed in the mx driver, but do not + * touch USBPLL ownership since this will already have been + * handled. + */ + if (!recovery_pending_stop_close) { + /* Don't stop the clock service - leave it running until + * service_close() resets WLBT. + */ + + /* Switch ownership of USBPLL to the AP. Ownership + * returns to WLBT after recovery completes. + */ + if (mif->mif_cleanup) + mif->mif_cleanup(mif); + } + + r = scsc_mx_service_close(clk20mhz.mx140_clk20mhz_service); + if (r) + SCSC_TAG_INFO(CLK20, "scsc_mx_service_close failed %d\n", r); + + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 0); +done_local: + mutex_unlock(&clk_lock); + } else { + SCSC_TAG_INFO(CLK20, "Maxwell removed\n"); + mx140_clk20mhz_remove_ctrl_proc_dir(&clk20mhz); + atomic_set(&clk20mhz.maxwell_is_present, 0); + mx140_clk20mhz_wq_deinit(); + } + +done: + mutex_unlock(&clk_work_lock); +} + +/* 20MHz client driver */ +struct scsc_mx_module_client mx140_clk20mhz_driver = { + .name = "MX 20MHz clock client", + .probe = mx140_clk20mhz_probe, + .remove = mx140_clk20mhz_remove, +}; + +/* 20MHz service driver initialization */ +static int __init mx140_clk20mhz_init(void) +{ + int ret; + + SCSC_TAG_INFO(CLK20, "Registering service\n"); + + wake_lock_init(&clk20mhz.clk_wake_lock, WAKE_LOCK_SUSPEND, "clk20_wl"); + init_completion(&clk20mhz.recovery_probe_completion); + + atomic_set(&clk20mhz.clk_request, 0); + atomic_set(&clk20mhz.maxwell_is_present, 0); + atomic_set(&clk20mhz.mx140_clk20mhz_service_started, 0); + atomic_set(&clk20mhz.request_pending, 0); + atomic_set(&clk20mhz.mx140_clk20mhz_service_failed, 0); + + /* Register with Maxwell Framework */ + ret = scsc_mx_module_register_client_module(&mx140_clk20mhz_driver); + if (ret) { + SCSC_TAG_ERR(CLK20, "scsc_mx_module_register_client_module failed: r=%d\n", ret); + return ret; + } + +#ifdef CONFIG_SCSC_CLK20MHZ_TEST + mx140_clk_test_init(); +#endif + return 0; +} + +static void __exit mx140_clk20mhz_exit(void) +{ + scsc_mx_module_unregister_client_module(&mx140_clk20mhz_driver); +#ifdef CONFIG_SCSC_CLK20MHZ_TEST + mx140_clk_test_exit(); +#endif + + complete_all(&clk20mhz.recovery_probe_completion); + wake_lock_destroy(&clk20mhz.clk_wake_lock); +} + +module_init(mx140_clk20mhz_init); +module_exit(mx140_clk20mhz_exit); + +MODULE_DESCRIPTION("Samsung Maxwell 20MHz Clock Service"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/misc/samsung/scsc/mx140_clk_test.c b/drivers/misc/samsung/scsc/mx140_clk_test.c new file mode 100644 index 000000000000..858efb1fd362 --- /dev/null +++ b/drivers/misc/samsung/scsc/mx140_clk_test.c @@ -0,0 +1,161 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "scsc_mx_impl.h" + +/* char device entry declarations */ +static dev_t mx140_clk_test_dev_t; +static struct class *mx140_clk_test_class; +static struct cdev *mx140_clk_test_cdev; + +/* Call back function registered with 20MHz clock framework */ +static void client_cb(void *data, enum mx140_clk20mhz_status event) +{ + switch (event) { + case MX140_CLK_STARTED: + SCSC_TAG_INFO(CLK20_TEST, "Event MX140_CLK_STARTED received\n"); + break; + case MX140_CLK_STOPPED: + SCSC_TAG_INFO(CLK20_TEST, "Event MX140_CLK_STOPPED received\n"); + break; + case MX140_CLK_NOT_STARTED: + SCSC_TAG_INFO(CLK20_TEST, "Event MX140_CLK_NOT_STARTED received\n"); + break; + case MX140_CLK_NOT_STOPPED: + SCSC_TAG_INFO(CLK20_TEST, "Event MX140_CLK_NOT_STOPPED received\n"); + break; + case MX140_CLK_ASYNC_FAIL: + SCSC_TAG_INFO(CLK20_TEST, "Event MX140_CLK_ASYNC_FAIL received\n"); + break; + default: + break; + } +} + +static int mx140_clk_test_dev_open(struct inode *inode, struct file *file) +{ + mx140_clk20mhz_register(client_cb, NULL); + return 0; +} + +static int mx140_clk_test_dev_release(struct inode *inode, struct file *file) +{ + mx140_clk20mhz_unregister(); + return 0; +} + + +static ssize_t mx140_clk_test_dev_write(struct file *file, const char *data, size_t len, loff_t *offset) +{ + unsigned long count; + char str[2]; /* One value and carry return */ + long int val = 0; + + if (len > 2) { + SCSC_TAG_ERR(CLK20_TEST, "Incorrect value len %zd\n", len); + goto error; + } + + count = copy_from_user(str, data, len); + + str[1] = 0; + + if (kstrtol(str, 10, &val)) { + SCSC_TAG_ERR(CLK20_TEST, "Invalid value\n"); + goto error; + } + + if (val == 1) + mx140_clk20mhz_request(); + else if (val == 0) + mx140_clk20mhz_release(); + else + SCSC_TAG_INFO(CLK20_TEST, "val %ld is not valid, 1 - on, 0 - off\n", val); +error: + return len; +} + +static ssize_t mx140_clk_test_dev_read(struct file *filp, char *buffer, size_t length, loff_t *offset) +{ + return length; +} + +static const struct file_operations mx140_clk_test_dev_fops = { + .owner = THIS_MODULE, + .open = mx140_clk_test_dev_open, + .read = mx140_clk_test_dev_read, + .write = mx140_clk_test_dev_write, + .release = mx140_clk_test_dev_release, +}; + +/* 20MHz service driver registration */ +void mx140_clk_test_init(void) +{ + int ret; + + SCSC_TAG_INFO(CLK20_TEST, "Registering mx140 TEST\n"); + + ret = alloc_chrdev_region(&mx140_clk_test_dev_t, 0, 1, "mx140_clk_test-cdev"); + if (ret < 0) { + SCSC_TAG_ERR(CLK20_TEST, "failed to alloc chrdev region\n"); + goto fail_alloc_chrdev_region; + } + + mx140_clk_test_cdev = cdev_alloc(); + if (!mx140_clk_test_cdev) { + ret = -ENOMEM; + SCSC_TAG_ERR(CLK20_TEST, "failed to alloc cdev\n"); + goto fail_alloc_cdev; + } + + cdev_init(mx140_clk_test_cdev, &mx140_clk_test_dev_fops); + ret = cdev_add(mx140_clk_test_cdev, mx140_clk_test_dev_t, 1); + if (ret < 0) { + SCSC_TAG_ERR(CLK20_TEST, "failed to add cdev\n"); + goto fail_add_cdev; + } + + mx140_clk_test_class = class_create(THIS_MODULE, "mx140_clk_test"); + if (!mx140_clk_test_class) { + ret = -EEXIST; + SCSC_TAG_ERR(CLK20_TEST, "failed to create class\n"); + goto fail_create_class; + } + + if (!device_create(mx140_clk_test_class, NULL, mx140_clk_test_dev_t, NULL, "mx140_usb_clk_test_%d", MINOR(mx140_clk_test_dev_t))) { + ret = -EINVAL; + SCSC_TAG_ERR(CLK20_TEST, "failed to create device\n"); + goto fail_create_device; + } + + return; +fail_create_device: + class_destroy(mx140_clk_test_class); +fail_create_class: + cdev_del(mx140_clk_test_cdev); +fail_add_cdev: +fail_alloc_cdev: + unregister_chrdev_region(mx140_clk_test_dev_t, 1); +fail_alloc_chrdev_region: + return; +} + +void mx140_clk_test_exit(void) +{ + device_destroy(mx140_clk_test_class, mx140_clk_test_dev_t); + class_destroy(mx140_clk_test_class); + cdev_del(mx140_clk_test_cdev); + unregister_chrdev_region(mx140_clk_test_dev_t, 1); +} diff --git a/drivers/misc/samsung/scsc/mx140_clk_test.h b/drivers/misc/samsung/scsc/mx140_clk_test.h new file mode 100644 index 000000000000..c51fe2f9ac04 --- /dev/null +++ b/drivers/misc/samsung/scsc/mx140_clk_test.h @@ -0,0 +1,13 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MX140_CLK_TEST_H__ +#define __MX140_CLK_TEST_H___ + + +void mx140_clk_test_init(void); +void mx140_clk_test_exit(void); +#endif /* __MX140_CLK_TEST_H___*/ diff --git a/drivers/misc/samsung/scsc/mx140_file.c b/drivers/misc/samsung/scsc/mx140_file.c new file mode 100644 index 000000000000..3df437ef60fc --- /dev/null +++ b/drivers/misc/samsung/scsc/mx140_file.c @@ -0,0 +1,652 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include +#include +#include +#include +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE +#include +#else +#include +#endif +#include +#include + +#include +#include + +#include "scsc_mx_impl.h" + +/* Firmware directory definitions */ + +#define SCSC_MULTI_RF_CHIP_ID /* Select FW by RF chip ID, not rev */ + +#if defined(CONFIG_SCSC_CORE_FW_LOCATION) && !defined(CONFIG_SCSC_CORE_FW_LOCATION_AUTO) +#define MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI CONFIG_SCSC_CORE_FW_LOCATION +#define MX140_FW_BASE_DIR_VENDOR_ETC_WIFI CONFIG_SCSC_CORE_FW_LOCATION +#else +#define MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI "/system/etc/wifi" +#define MX140_FW_BASE_DIR_VENDOR_ETC_WIFI "/vendor/etc/wifi" +#endif + +/* Look for this file in /etc/wifi */ +#ifdef CONFIG_ANDROID +#define MX140_FW_DETECT "mx" +#else +/* Linux host vfs_stat() doesn't find mx* with "mx" */ +#define MX140_FW_DETECT "mx140.bin" +#endif + +/* Paths for vendor utilities, used when CONFIG_SCSC_CORE_FW_LOCATION_AUTO=n */ +#define MX140_EXE_DIR_VENDOR "/vendor/bin" /* Oreo */ +#define MX140_EXE_DIR_SYSTEM "/system/bin" /* Before Oreo */ + +#define MX140_FW_CONF_SUBDIR "conf" +#define MX140_FW_DEBUG_SUBDIR "debug" +#define MX140_FW_BIN "mx140.bin" +#define MX140_FW_PATH_MAX_LENGTH (512) + +#define MX140_FW_VARIANT_DEFAULT "mx140" + +/* Table of suffixes to append to f/w name */ +struct fw_suffix { + char suffix[6]; + u32 hw_ver; +}; + +#ifdef SCSC_MULTI_RF_CHIP_ID /* Select by chip ID (S611, S612) */ + +/* This scheme has one firmware binary for all revisions of an + * RF chip ID. + */ + +/* Table of known RF h/w IDs */ +static const struct fw_suffix fw_suffixes[] = { + { .suffix = "", .hw_ver = 0xff, }, /* plain mx140.bin, always used if found */ + { .suffix = "_s612", .hw_ver = 0xb1, }, + { .suffix = "_s611", .hw_ver = 0xb0, }, +}; + +#else /* Select by chip revision (EVT0.0, EVT0.1) */ + +/* This legacy scheme assumes a different fw binary for each revision + * of an RF chip ID, and those will uniquely identify the + * right build. This was used for early S5E7570 until a unified + * binary was available. + */ + +/* Table of known RF h/w revs */ +static const struct fw_suffix fw_suffixes[] = { + { .suffix = "_11", .hw_ver = 0x11, }, + { .suffix = "_10", .hw_ver = 0x10, }, + { .suffix = "_00", .hw_ver = 0x00, }, + { .suffix = "", .hw_ver = 0xff, }, /* plain mx140.bin, must be last */ +}; +#endif + +/* Once set, we always load this firmware suffix */ +static int fw_suffix_found = -1; + +/* Variant of firmware binary to load */ +static char *firmware_variant = MX140_FW_VARIANT_DEFAULT; +module_param(firmware_variant, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(firmware_variant, "mx140 firmware variant, default mx140"); + +/* RF hardware version of firmware to load. If "auto" this gets replaced with + * the suffix of FW that got loaded. + * If "manual" it loads the version specified by firmware_variant, verbatim. + */ +static char *firmware_hw_ver = "auto"; +module_param(firmware_hw_ver, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(firmware_hw_ver, "mx140 hw version detect, manual=disable"); + +/* FW base dir readable by usermode script */ +#ifdef CONFIG_SCSC_CORE_FW_LOCATION_AUTO +static char *fw_base_dir; +#else +static char *fw_base_dir = CONFIG_SCSC_CORE_FW_LOCATION; +#endif +module_param_named(base_dir, fw_base_dir, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(base_dir, "WLBT FW base directory"); + +/* Firmware and tool (moredump) exe base directory */ +#ifdef CONFIG_SCSC_CORE_FW_LOCATION_AUTO +static char base_dir[MX140_FW_PATH_MAX_LENGTH]; /* auto detect */ +static char exe_dir[MX140_FW_PATH_MAX_LENGTH]; /* auto detect */ +#else +static char base_dir[] = CONFIG_SCSC_CORE_FW_LOCATION; /* fixed in defconfig */ +static char exe_dir[] = CONFIG_SCSC_CORE_TOOL_LOCATION; /* fixed in defconfig */ +#endif + + +static bool enable_auto_sense; +module_param(enable_auto_sense, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_auto_sense, "deprecated"); + +static bool use_new_fw_structure = true; +module_param(use_new_fw_structure, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(use_new_fw_structure, "deprecated"); + +static char *cfg_platform = "default"; +module_param(cfg_platform, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(cfg_platform, "HCF config subdirectory"); + +/* Reads a configuration file into memory (f/w profile specific) */ +static int __mx140_file_request_conf(struct scsc_mx *mx, + const struct firmware **conf, + const char *platform_dir, + const char *config_rel_path, + const char *filename, + const bool flat) + +{ + char config_path[MX140_FW_PATH_MAX_LENGTH]; + int r; + + if (mx140_basedir_file(mx)) + return -ENOENT; + + if (flat) { + /* e.g. /etc/wifi/mx140_wlan.hcf */ + + scnprintf(config_path, sizeof(config_path), + "%s/%s%s_%s", + base_dir, + firmware_variant, + fw_suffixes[fw_suffix_found].suffix, + filename); + } else { + /* e.g. /etc/wifi/mx140/conf/$platform_dir/wlan/wlan.hcf */ + + scnprintf(config_path, sizeof(config_path), + "%s/%s%s/%s/%s%s%s/%s", + base_dir, + firmware_variant, + fw_suffixes[fw_suffix_found].suffix, + MX140_FW_CONF_SUBDIR, + platform_dir, + (platform_dir[0] != '\0' ? "/" : ""), /* add "/" if platform_dir not empty */ + config_rel_path, + filename); + } + SCSC_TAG_INFO(MX_FILE, "try %s\n", config_path); + + r = mx140_request_file(mx, config_path, conf); + + /* Confirm what we read */ + if (r == 0) + SCSC_TAG_INFO(MX_FILE, "loaded %s\n", config_path); + + return r; +} + +int mx140_file_request_conf(struct scsc_mx *mx, + const struct firmware **conf, + const char *config_rel_path, + const char *filename) +{ + int r; + + /* First, if the config subdirectory has been overriden by cfg_platform + * module parameter, search only in that location. + */ + if (strcmp(cfg_platform, "default")) { + SCSC_TAG_INFO(MX_FILE, "module param cfg_platform = %s\n", cfg_platform); + return __mx140_file_request_conf(mx, conf, cfg_platform, config_rel_path, filename, false); + } + + /* Search in generic location. This is an override. + * e.g. /etc/wifi/mx140/conf/wlan/wlan.hcf + */ + r = __mx140_file_request_conf(mx, conf, "", config_rel_path, filename, false); + +#if defined CONFIG_SCSC_WLBT_CONFIG_PLATFORM + /* Then search in platform location + * e.g. /etc/wifi/mx140/conf/$platform_dir/wlan/wlan.hcf + */ + if (r) { + const char *plat = CONFIG_SCSC_WLBT_CONFIG_PLATFORM; + + /* Don't bother if plat is empty string */ + if (plat[0] != '\0') + r = __mx140_file_request_conf(mx, conf, plat, config_rel_path, filename, false); + } +#endif + + /* Finally request "flat" conf, where all hcf files are in FW root dir + * e.g. /etc/wifi/-wlan.hcf + */ + if (r) + r = __mx140_file_request_conf(mx, conf, "", config_rel_path, filename, true); + + return r; +} + +EXPORT_SYMBOL(mx140_file_request_conf); + +/* Reads a debug configuration file into memory (f/w profile specific) */ +int mx140_file_request_debug_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_rel_path) +{ + char config_path[MX140_FW_PATH_MAX_LENGTH]; + + if (mx140_basedir_file(mx)) + return -ENOENT; + + /* e.g. /etc/wifi/mx140/debug/log_strings.bin */ + + scnprintf(config_path, sizeof(config_path), "%s/%s%s/%s/%s", + base_dir, + firmware_variant, + fw_suffixes[fw_suffix_found].suffix, + MX140_FW_DEBUG_SUBDIR, + config_rel_path); + + return mx140_request_file(mx, config_path, conf); +} +EXPORT_SYMBOL(mx140_file_request_debug_conf); + +/* Read device configuration file into memory (whole device specific) */ +int mx140_file_request_device_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_rel_path) +{ + char config_path[MX140_FW_PATH_MAX_LENGTH]; + + if (mx140_basedir_file(mx)) + return -ENOENT; + + /* e.g. /etc/wifi/conf/wlan/mac.txt */ + + snprintf(config_path, sizeof(config_path), "%s/%s%s/%s", + base_dir, + fw_suffixes[fw_suffix_found].suffix, + MX140_FW_CONF_SUBDIR, + config_rel_path); + + return mx140_request_file(mx, config_path, conf); +} +EXPORT_SYMBOL(mx140_file_request_device_conf); + +/* Release configuration file memory. */ +void mx140_file_release_conf(struct scsc_mx *mx, const struct firmware *conf) +{ + (void)mx; + + mx140_release_file(mx, conf); +} +EXPORT_SYMBOL(mx140_file_release_conf); + +static int __mx140_file_download_fw(struct scsc_mx *mx, void *dest, size_t dest_size, u32 *fw_image_size, const char *fw_suffix) +{ + const struct firmware *firm; + int r = 0; + char img_path_name[MX140_FW_PATH_MAX_LENGTH]; + + if (mx140_basedir_file(mx)) + return -ENOENT; + + SCSC_TAG_INFO(MX_FILE, "firmware_variant=%s (%s)\n", firmware_variant, fw_suffix); + + /* e.g. /etc/wifi/mx140.bin */ + scnprintf(img_path_name, sizeof(img_path_name), "%s/%s%s.bin", + base_dir, + firmware_variant, + fw_suffix); + + SCSC_TAG_INFO(MX_FILE, "Load WLBT fw %s in shared address %p\n", img_path_name, dest); + r = mx140_request_file(mx, img_path_name, &firm); + if (r) { + SCSC_TAG_ERR(MX_FILE, "Error Loading FW, error %d\n", r); + return r; + } + SCSC_TAG_DEBUG(MX_FILE, "FW Download, size %zu\n", firm->size); + + if (firm->size > dest_size) { + SCSC_TAG_ERR(MX_FILE, "firmware image too big for buffer (%zu > %u)", dest_size, *fw_image_size); + r = -EINVAL; + } else { + memcpy(dest, firm->data, firm->size); + *fw_image_size = firm->size; + } + mx140_release_file(mx, firm); + return r; +} + +/* Download firmware binary into a buffer supplied by the caller */ +int mx140_file_download_fw(struct scsc_mx *mx, void *dest, size_t dest_size, u32 *fw_image_size) +{ + int r; + int i; + int manual; + + /* Override to use the verbatim image only */ + manual = !strcmp(firmware_hw_ver, "manual"); + if (manual) { + SCSC_TAG_INFO(MX_FILE, "manual hw version\n"); + fw_suffix_found = sizeof(fw_suffixes) / sizeof(fw_suffixes[0]) - 1; + } + + SCSC_TAG_DEBUG(MX_FILE, "fw_suffix_found %d\n", fw_suffix_found); + + /* If we know which f/w suffix to use, select it immediately */ + if (fw_suffix_found != -1) { + r = __mx140_file_download_fw(mx, dest, dest_size, fw_image_size, fw_suffixes[fw_suffix_found].suffix); + goto done; + } + + /* Otherwise try the list */ + for (i = 0; i < sizeof(fw_suffixes) / sizeof(fw_suffixes[0]); i++) { + /* Try to find each suffix in turn */ + SCSC_TAG_INFO(MX_FILE, "try %d %s\n", i, fw_suffixes[i].suffix); + r = __mx140_file_download_fw(mx, dest, dest_size, fw_image_size, fw_suffixes[i].suffix); + if (r != -ENOENT) + break; + } + + /* Save this for next time */ + if (r == 0) + fw_suffix_found = i; +done: + /* Update firmware_hw_ver to reflect what got auto selected, for moredump */ + if (fw_suffix_found != -1 && !manual) { + /* User will only read this, so casting away const is safe */ + firmware_hw_ver = (char *)fw_suffixes[fw_suffix_found].suffix; + } + return r; +} + +int mx140_request_file(struct scsc_mx *mx, char *path, const struct firmware **firmp) +{ + struct file *f; + mm_segment_t fs; + struct kstat stat; + const int max_read_size = 4096; + int r, whats_left, to_read, size; + struct firmware *firm; + char *buf, *p; + + SCSC_TAG_DEBUG(MX_FILE, "request %s\n", path); + + *firmp = NULL; + + /* Check FS is ready */ + + /* Try to determine base dir */ + r = mx140_basedir_file(mx); + if (r) { + SCSC_TAG_ERR(MX_FILE, "detect failed for fw base_dir %d\n", r); + return r; + } + + /* Current segment. */ + fs = get_fs(); + /* Set to kernel segment. */ + set_fs(get_ds()); + + r = vfs_stat(base_dir, &stat); + if (r != 0) { + set_fs(fs); + SCSC_TAG_ERR(MX_FILE, "vfs_stat() failed for %s\n", base_dir); + return -EAGAIN; + } + + /* Check f/w bin */ + r = vfs_stat(path, &stat); + if (r != 0) { + set_fs(fs); + SCSC_TAG_ERR(MX_FILE, "vfs_stat() failed for %s\n", path); + return -ENOENT; + } + /* Revert to original segment. */ + set_fs(fs); + + /* Round up for minimum sizes */ + size = (stat.size + 256) & ~255; + /* Get memory for file contents. */ + buf = vzalloc(size); + if (!buf) { + SCSC_TAG_ERR(MX_FILE, "kzalloc(%d) failed for %s\n", size, path); + return -ENOMEM; + } + p = buf; + /* Get firmware structure. */ + firm = kzalloc(sizeof(*firm), GFP_KERNEL); + if (!firm) { + vfree(buf); + SCSC_TAG_ERR(MX_FILE, "kzalloc(%zu) failed for %s\n", sizeof(*firmp), path); + return -ENOMEM; + } + /* Open the file for reading. */ + f = filp_open(path, O_RDONLY, 0); + if (IS_ERR(f)) { + vfree(buf); + kfree(firm); + SCSC_TAG_ERR(MX_FILE, "filp_open() failed for %s with %ld\n", path, PTR_ERR(f)); + return -ENOENT; + } + + whats_left = stat.size; + + fs = get_fs(); + set_fs(get_ds()); + + /* Special case if file length is reported as zero - try to read until it fails. + * This allows us to read /proc + */ + if (whats_left == 0) { + do { + r = vfs_read(f, p, max_read_size, &f->f_pos); + if (r < 0) { + SCSC_TAG_INFO(MX_FILE, "No more data %s\n", path); + break; + } + p += r; + if (r < max_read_size) { + SCSC_TAG_INFO(MX_FILE, "Read %zd from %s\n", (ptrdiff_t)(p - buf), path); + break; + } + } while (r > 0); + + goto done; + } + + /* Read at most max_read_size in each read. Loop until the whole file has + * been copied to the local buffer. + */ + while (whats_left) { + to_read = whats_left < max_read_size ? whats_left : max_read_size; + r = vfs_read(f, p, to_read, &f->f_pos); + if (r < 0) { + SCSC_TAG_ERR(MX_FILE, "error reading %s\n", path); + break; + } + if (r == 0 || r < to_read) + break; + whats_left -= r; + p += r; + } +done: + set_fs(fs); + filp_close(f, NULL); + + if (r >= 0) { + r = 0; + /* Pass to caller. Caller will free allocated memory through + * mx140_release_file(). + */ + firm->size = p - buf; + firm->data = buf; + *firmp = firm; + } else { + vfree(buf); + kfree(firm); + } + return r; + +} +EXPORT_SYMBOL(mx140_request_file); + +int mx140_release_file(struct scsc_mx *mx, const struct firmware *firmp) +{ + if (!firmp || !firmp->data) { + SCSC_TAG_ERR(MX_FILE, "firmp=%p\n", firmp); + return -EINVAL; + } + + SCSC_TAG_DEBUG(MX_FILE, "release firmp=%p, data=%p\n", firmp, firmp->data); + + vfree(firmp->data); + kfree(firmp); + return 0; +} +EXPORT_SYMBOL(mx140_release_file); + +/* Work out correct path for vendor binaries */ +int mx140_exe_path(struct scsc_mx *mx, char *path, size_t len, const char *bin) +{ + (void)mx; + + /* Set up when we detect FW path, or statically when + * auto-detect is off + */ + if (exe_dir[0] == '\0') + return -ENOENT; + + if (path == NULL) + return -EINVAL; + + snprintf(path, len, "%s/%s", exe_dir, bin); + + SCSC_TAG_DEBUG(MX_FILE, "exe: %s\n", path); + return 0; +} +EXPORT_SYMBOL(mx140_exe_path); + +/* Try to auto detect f/w directory */ +int mx140_basedir_file(struct scsc_mx *mx) +{ + struct kstat stat; + mm_segment_t fs; + int r = 0; + + /* Already worked out base dir. This is + * static if auto-detect is off. + */ + if (base_dir[0] != '\0') + return 0; + + /* Default to pre-O bin dir, until we detect O */ + strlcpy(exe_dir, MX140_EXE_DIR_SYSTEM, sizeof(exe_dir)); + + /* Current segment. */ + fs = get_fs(); + /* Set to kernel segment. */ + set_fs(get_ds()); + + /* If /system isn't present, assume platform isn't ready yet */ + r = vfs_stat("/system", &stat); + if (r != 0) { + SCSC_TAG_ERR(MX_FILE, "/system not mounted yet\n"); + r = -EAGAIN; + goto done; + } + + /* If /vendor isn't present, assume platform isn't ready yet. + * Android M and N still have /vendor, though we don't use it. + */ + r = vfs_stat("/vendor", &stat); + if (r != 0) { + SCSC_TAG_ERR(MX_FILE, "/vendor not mounted yet\n"); + r = -EAGAIN; + goto done; + } + + /* Now partitions are mounted, so let's see what's in them. */ + + /* Try /vendor partition (Oreo) first. + * If it's present, it'll contain our FW + */ + r = vfs_stat(MX140_FW_BASE_DIR_VENDOR_ETC_WIFI"/"MX140_FW_DETECT, &stat); + if (r != 0) { + SCSC_TAG_ERR(MX_FILE, "Base dir: %s/%s doesn't exist\n", + MX140_FW_BASE_DIR_VENDOR_ETC_WIFI, MX140_FW_DETECT); + base_dir[0] = '\0'; + r = -ENOENT; + } else { + strlcpy(base_dir, MX140_FW_BASE_DIR_VENDOR_ETC_WIFI, sizeof(base_dir)); + fw_base_dir = MX140_FW_BASE_DIR_VENDOR_ETC_WIFI; + strlcpy(exe_dir, MX140_EXE_DIR_VENDOR, sizeof(exe_dir)); + goto done; + } + + /* Try /system partition (pre-Oreo) */ + r = vfs_stat(MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI"/"MX140_FW_DETECT, &stat); + if (r != 0) { + SCSC_TAG_ERR(MX_FILE, "Base dir: %s/%s doesn't exist\n", + MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI, MX140_FW_DETECT); + base_dir[0] = '\0'; + r = -ENOENT; + } else { + strlcpy(base_dir, MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI, sizeof(base_dir)); + fw_base_dir = MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI; + } + +done: + /* Restore segment */ + set_fs(fs); + SCSC_TAG_INFO(MX_FILE, "WLBT fw base dir is %s\n", base_dir[0] ? base_dir : "not found"); + + return r; +} + +/* Select file for h/w version from filesystem */ +int mx140_file_select_fw(struct scsc_mx *mx, u32 hw_ver) +{ + int i; + + SCSC_TAG_INFO(MX_FILE, "select f/w for 0x%04x\n", hw_ver); + +#ifdef SCSC_MULTI_RF_CHIP_ID + hw_ver = (hw_ver & 0x00ff); /* LSB is the RF HW ID (e.g. S610) */ +#else + hw_ver = (hw_ver & 0xff00) >> 8; /* MSB is the RF HW rev (e.g. EVT1.1) */ +#endif + + for (i = 0; i < sizeof(fw_suffixes) / sizeof(fw_suffixes[0]); i++) { + if (fw_suffixes[i].hw_ver == hw_ver) { + fw_suffix_found = i; + SCSC_TAG_DEBUG(MX_FILE, "f/w for 0x%04x: index %d, suffix '%s'\n", + hw_ver, i, fw_suffixes[i].suffix); + return 0; + } + } + + SCSC_TAG_ERR(MX_FILE, "No known f/w for 0x%04x, default to catchall\n", hw_ver); + + /* Enable when a unified FW image is installed */ +#ifdef MX140_UNIFIED_HW_FW + /* The last f/w is the non-suffixed ".bin", assume it's compatible */ + fw_suffix_found = i - 1; +#else + fw_suffix_found = -1; /* None found */ +#endif + return -EINVAL; +} + +/* Query whether this HW is supported by the current FW file set */ +bool mx140_file_supported_hw(struct scsc_mx *mx, u32 hw_ver) +{ +#ifdef SCSC_MULTI_RF_CHIP_ID + hw_ver = (hw_ver & 0x00ff); /* LSB is the RF HW ID (e.g. S610) */ +#else + hw_ver = (hw_ver & 0xff00) >> 8; /* MSB is the RF HW rev (e.g. EVT1.0) */ +#endif + /* Assume installed 0xff is always compatible, and f/w will panic if it isn't */ + if (fw_suffixes[fw_suffix_found].hw_ver == 0xff) + return true; + + /* Does the select f/w match the hw_ver from chip? */ + return (fw_suffixes[fw_suffix_found].hw_ver == hw_ver); +} diff --git a/drivers/misc/samsung/scsc/mx250_fm.c b/drivers/misc/samsung/scsc/mx250_fm.c new file mode 100755 index 000000000000..df6dabc04d3b --- /dev/null +++ b/drivers/misc/samsung/scsc/mx250_fm.c @@ -0,0 +1,385 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mxman.h" /* Special case service driver that looks inside mxman */ + +#ifdef CONFIG_SCSC_FM_TEST +#include "mx250_fm_test.h" +#endif + + +struct scsc_mx_fm_client { + /* scsc_service_client has to be the first */ + struct scsc_service_client fm_service_client; + struct scsc_service *fm_service; + struct scsc_mx *mx; + bool fm_api_available; + scsc_mifram_ref ref; + struct workqueue_struct *fm_client_wq; + struct work_struct fm_client_work; + struct completion fm_client_work_completion; + int fm_client_work_completion_status; + bool ldo_on; +}; + +static struct scsc_mx_fm_client *fm_client; +/* service to start */ +static int service_id = SCSC_SERVICE_ID_FM; + +static DEFINE_MUTEX(ss_lock); + + +static void fm_client_stop_on_failure(struct scsc_service_client *client) +{ + (void)client; + mutex_lock(&ss_lock); + fm_client->fm_api_available = false; + mutex_unlock(&ss_lock); + SCSC_TAG_DEBUG(FM, "OK\n"); +} + +static void fm_client_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + (void)client; + (void)scsc_panic_code; + SCSC_TAG_DEBUG(FM, "OK\n"); +} + +static int stop_close_service(void) +{ + int r; + + if (!fm_client->fm_service) { + SCSC_TAG_ERR(FM, "No fm_service\n"); + r = -EINVAL; + goto done; + } + + r = scsc_mx_service_stop(fm_client->fm_service); + if (r) { + SCSC_TAG_ERR(FM, "scsc_mx_service_stop(fm_service) failed %d\n", r); + goto done; + } + SCSC_TAG_DEBUG(FM, "scsc_mx_service_stop(fm_service) OK\n"); + + scsc_mx_service_mifram_free(fm_client->fm_service, fm_client->ref); + + r = scsc_mx_service_close(fm_client->fm_service); + if (r) { + SCSC_TAG_ERR(FM, "scsc_mx_service_close(fm_service) failed %d\n", r); + goto done; + } else + SCSC_TAG_DEBUG(FM, "scsc_mx_service_close(fm_service) OK\n"); + + fm_client->fm_service = NULL; + fm_client->ref = 0; +done: + return r; +} + +static int open_start_service(void) +{ + struct scsc_service *fm_service; + int r; + int r2; + struct fm_ldo_conf *ldo_conf; + scsc_mifram_ref ref; + + fm_service = scsc_mx_service_open(fm_client->mx, service_id, &fm_client->fm_service_client, &r); + if (!fm_service) { + r = -EINVAL; + SCSC_TAG_ERR(FM, "scsc_mx_service_open(fm_service) failed %d\n", r); + goto done; + } + /* Allocate memory */ + r = scsc_mx_service_mifram_alloc(fm_service, sizeof(struct fm_ldo_conf), &ref, 32); + if (r) { + SCSC_TAG_ERR(FM, "scsc_mx_service_mifram_alloc(fm_service) failed %d\n", r); + r2 = scsc_mx_service_close(fm_service); + if (r2) + SCSC_TAG_ERR(FM, "scsc_mx_service_close(fm_service) failed %d\n", r2); + goto done; + } + ldo_conf = (struct fm_ldo_conf *)scsc_mx_service_mif_addr_to_ptr(fm_service, ref); + ldo_conf->version = FM_LDO_CONFIG_VERSION; + ldo_conf->ldo_on = fm_client->ldo_on; + + r = scsc_mx_service_start(fm_service, ref); + if (r) { + SCSC_TAG_ERR(FM, "scsc_mx_service_start(fm_service) failed %d\n", r); + r2 = scsc_mx_service_close(fm_service); + if (r2) + SCSC_TAG_ERR(FM, "scsc_mx_service_close(fm_service) failed %d\n", r2); + scsc_mx_service_mifram_free(fm_service, ref); + goto done; + } + + fm_client->fm_service = fm_service; + fm_client->ref = ref; +done: + return r; +} + +static int open_start_close_service(void) +{ + int r; + + r = open_start_service(); + if (r) { + SCSC_TAG_ERR(FM, "Error starting service: open_start_service(fm_service) failed %d\n", r); + + if (!fm_client->ldo_on) { + /* Do not return here. For the case where WLBT FW is crashed, and FM off request is + * rejected, it's safest to continue to let scsc_service_on_halt_ldos_off() reset + * the global flag to indicate that FM is no longer needed when WLBT next boots. + * Otherwise LDO could be stuck always-on. + */ + } else + return r; + } + + if (fm_client->ldo_on) { + /* FM turning on */ + mxman_fm_on_halt_ldos_on(); + + } else { + /* FM turning off */ + mxman_fm_on_halt_ldos_off(); + + /* Invalidate stored FM params */ + mxman_fm_set_params(NULL); + } + + r = stop_close_service(); + if (r) { + SCSC_TAG_ERR(FM, "Error starting service: stop_close_service(fm_service) failed %d\n", r); + return r; + } + return 0; +} + +static void fm_client_work_func(struct work_struct *work) +{ + SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__); + + fm_client->fm_client_work_completion_status = open_start_close_service(); + if (fm_client->fm_client_work_completion_status) { + SCSC_TAG_ERR(FM, "open_start_close_service(fm_service) failed %d\n", + fm_client->fm_client_work_completion_status); + } else { + SCSC_TAG_DEBUG(FM, "OK\n"); + } + complete(&fm_client->fm_client_work_completion); + +} + +static void fm_client_wq_init(void) +{ + fm_client->fm_client_wq = create_singlethread_workqueue("fm_client_wq"); + INIT_WORK(&fm_client->fm_client_work, fm_client_work_func); +} + +static void fm_client_wq_stop(void) +{ + cancel_work_sync(&fm_client->fm_client_work); + flush_workqueue(fm_client->fm_client_wq); +} + +static void fm_client_wq_deinit(void) +{ + fm_client_wq_stop(); + destroy_workqueue(fm_client->fm_client_wq); +} + +static void fm_client_wq_start(void) +{ + queue_work(fm_client->fm_client_wq, &fm_client->fm_client_work); +} + +static int fm_client_wq_start_blocking(void) +{ + SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__); + + fm_client_wq_start(); + wait_for_completion(&fm_client->fm_client_work_completion); + if (fm_client->fm_client_work_completion_status) { + SCSC_TAG_ERR(FM, "%s failed: fm_client_wq_completion_status = %d\n", + __func__, fm_client->fm_client_work_completion_status); + return fm_client->fm_client_work_completion_status; + } + SCSC_TAG_DEBUG(FM, "OK\n"); + return 0; +} + +static int mx250_fm_re(bool ldo_on) +{ + int r; + + mutex_lock(&ss_lock); + SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__); + if (!fm_client) { + SCSC_TAG_ERR(FM, "fm_client = NULL\n"); + mutex_unlock(&ss_lock); + return -ENODEV; + } + + if (!fm_client->fm_api_available) { + SCSC_TAG_WARNING(FM, "FM LDO API unavailable\n"); + mutex_unlock(&ss_lock); + return -EAGAIN; + } + fm_client->ldo_on = ldo_on; + reinit_completion(&fm_client->fm_client_work_completion); + r = fm_client_wq_start_blocking(); + mutex_unlock(&ss_lock); + return r; + +} + +/* + * FM Radio is starting, tell WLBT drivers + */ +int mx250_fm_request(void) +{ + + SCSC_TAG_INFO(FM, "request\n"); + return mx250_fm_re(true); +} +EXPORT_SYMBOL(mx250_fm_request); + +/* + * FM Radio is stopping, tell WLBT drivers + */ +int mx250_fm_release(void) +{ + SCSC_TAG_INFO(FM, "release\n"); + return mx250_fm_re(false); +} +EXPORT_SYMBOL(mx250_fm_release); + +/* + * FM Radio parameters are changing, tell WLBT drivers + */ +void mx250_fm_set_params(struct wlbt_fm_params *info) +{ + SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__); + + if (!info) + return; + + mutex_lock(&ss_lock); + + SCSC_TAG_INFO(FM, "freq %u\n", info->freq); + + mxman_fm_set_params(info); + + mutex_unlock(&ss_lock); +} +EXPORT_SYMBOL(mx250_fm_set_params); + +void fm_client_module_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, + enum scsc_module_client_reason reason) +{ + /* Avoid unused error */ + (void)module_client; + + SCSC_TAG_INFO(FM, "probe\n"); + + mutex_lock(&ss_lock); + if (reason == SCSC_MODULE_CLIENT_REASON_HW_PROBE) { + fm_client = kzalloc(sizeof(*fm_client), GFP_KERNEL); + if (!fm_client) { + mutex_unlock(&ss_lock); + return; + } + init_completion(&fm_client->fm_client_work_completion); + fm_client_wq_init(); + fm_client->fm_service_client.stop_on_failure = fm_client_stop_on_failure; + fm_client->fm_service_client.failure_reset = fm_client_failure_reset; + fm_client->mx = mx; + } + fm_client->fm_api_available = true; + SCSC_TAG_DEBUG(FM, "OK\n"); + mutex_unlock(&ss_lock); +} + +void fm_client_module_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, + enum scsc_module_client_reason reason) +{ + /* Avoid unused error */ + (void)module_client; + + SCSC_TAG_INFO(FM, "remove\n"); + + mutex_lock(&ss_lock); + if (reason == SCSC_MODULE_CLIENT_REASON_HW_REMOVE) { + if (!fm_client) { + mutex_unlock(&ss_lock); + return; + } + if (fm_client->mx != mx) { + SCSC_TAG_ERR(FM, "fm_client->mx != mx\n"); + mutex_unlock(&ss_lock); + return; + } + fm_client_wq_deinit(); + kfree(fm_client); + fm_client = NULL; + } + SCSC_TAG_DEBUG(FM, "OK\n"); + mutex_unlock(&ss_lock); +} + +/* FM client driver registration */ +struct scsc_mx_module_client fm_client_driver = { + .name = "FM client driver", + .probe = fm_client_module_probe, + .remove = fm_client_module_remove, +}; + +static int __init scsc_fm_client_module_init(void) +{ + int r; + + SCSC_TAG_INFO(FM, "init\n"); + + r = scsc_mx_module_register_client_module(&fm_client_driver); + if (r) { + SCSC_TAG_ERR(FM, "scsc_mx_module_register_client_module failed: r=%d\n", r); + return r; + } +#ifdef CONFIG_SCSC_FM_TEST + mx250_fm_test_init(); +#endif + return 0; +} + +static void __exit scsc_fm_client_module_exit(void) +{ + SCSC_TAG_INFO(FM, "exit\n"); + scsc_mx_module_unregister_client_module(&fm_client_driver); +#ifdef CONFIG_SCSC_FM_TEST + mx250_fm_test_exit(); +#endif + SCSC_TAG_DEBUG(FM, "exit\n"); +} + +late_initcall(scsc_fm_client_module_init); +module_exit(scsc_fm_client_module_exit); + +MODULE_DESCRIPTION("FM Client Driver"); +MODULE_AUTHOR("SCSC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/samsung/scsc/mx250_fm_test.c b/drivers/misc/samsung/scsc/mx250_fm_test.c new file mode 100644 index 000000000000..7132367ef303 --- /dev/null +++ b/drivers/misc/samsung/scsc/mx250_fm_test.c @@ -0,0 +1,143 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "scsc_mx_impl.h" + +/* char device entry declarations */ +static dev_t mx250_fm_test_dev_t; +static struct class *mx250_fm_test_class; +static struct cdev *mx250_fm_test_cdev; + + +static int mx250_fm_test_dev_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mx250_fm_test_dev_release(struct inode *inode, struct file *file) +{ + return 0; +} + + +static ssize_t mx250_fm_test_dev_write(struct file *file, const char *data, size_t len, loff_t *offset) +{ + unsigned long count; + char str[20]; /* One value and carry return */ + long val = 0; + struct wlbt_fm_params params; + int r; + + count = copy_from_user(str, data, len); + + str[sizeof(str) - 1] = 0; + if (len < sizeof(str)) + str[len - 1] = 0; + + r = kstrtol(str, 0, &val); + if (r) { + SCSC_TAG_ERR(FM_TEST, "parse error %d, l=%zd\n", r, len); + goto error; + } + + if (val == 1) + mx250_fm_request(); + else if (val == 0) + mx250_fm_release(); + else { + /* All other values are frequency info */ + params.freq = (u32)val; + + SCSC_TAG_INFO(FM_TEST, "FM freq=%u\n", params.freq); + + mx250_fm_set_params(¶ms); + } +error: + return len; +} + +static ssize_t mx250_fm_test_dev_read(struct file *filp, char *buffer, size_t length, loff_t *offset) +{ + return length; +} + +static const struct file_operations mx250_fm_test_dev_fops = { + .owner = THIS_MODULE, + .open = mx250_fm_test_dev_open, + .read = mx250_fm_test_dev_read, + .write = mx250_fm_test_dev_write, + .release = mx250_fm_test_dev_release, +}; + +/* FM service driver registration */ +void mx250_fm_test_init(void) +{ + int ret; + + SCSC_TAG_INFO(FM_TEST, "Registering mx250 TEST\n"); + + ret = alloc_chrdev_region(&mx250_fm_test_dev_t, 0, 1, "mx250_fm_test-cdev"); + if (ret < 0) { + SCSC_TAG_ERR(FM_TEST, "failed to alloc chrdev region\n"); + goto fail_alloc_chrdev_region; + } + + mx250_fm_test_cdev = cdev_alloc(); + if (!mx250_fm_test_cdev) { + ret = -ENOMEM; + SCSC_TAG_ERR(FM_TEST, "failed to alloc cdev\n"); + goto fail_alloc_cdev; + } + + cdev_init(mx250_fm_test_cdev, &mx250_fm_test_dev_fops); + ret = cdev_add(mx250_fm_test_cdev, mx250_fm_test_dev_t, 1); + if (ret < 0) { + SCSC_TAG_ERR(FM_TEST, "failed to add cdev\n"); + goto fail_add_cdev; + } + + mx250_fm_test_class = class_create(THIS_MODULE, "mx250_fm_test"); + if (!mx250_fm_test_class) { + ret = -EEXIST; + SCSC_TAG_ERR(FM_TEST, "failed to create class\n"); + goto fail_create_class; + } + + if (!device_create(mx250_fm_test_class, NULL, mx250_fm_test_dev_t, NULL, "mx250_fm_test_%d", + MINOR(mx250_fm_test_dev_t))) { + ret = -EINVAL; + SCSC_TAG_ERR(FM_TEST, "failed to create device\n"); + goto fail_create_device; + } + + return; +fail_create_device: + class_destroy(mx250_fm_test_class); +fail_create_class: + cdev_del(mx250_fm_test_cdev); +fail_add_cdev: +fail_alloc_cdev: + unregister_chrdev_region(mx250_fm_test_dev_t, 1); +fail_alloc_chrdev_region: + return; +} + +void mx250_fm_test_exit(void) +{ + device_destroy(mx250_fm_test_class, mx250_fm_test_dev_t); + class_destroy(mx250_fm_test_class); + cdev_del(mx250_fm_test_cdev); + unregister_chrdev_region(mx250_fm_test_dev_t, 1); +} diff --git a/drivers/misc/samsung/scsc/mx250_fm_test.h b/drivers/misc/samsung/scsc/mx250_fm_test.h new file mode 100644 index 000000000000..6b92a15498c5 --- /dev/null +++ b/drivers/misc/samsung/scsc/mx250_fm_test.h @@ -0,0 +1,13 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MX250_FM_TEST_H___ +#define __MX250_FM_TEST_H___ + + +void mx250_fm_test_init(void); +void mx250_fm_test_exit(void); +#endif /* __MX250_FM_TEST_H___*/ diff --git a/drivers/misc/samsung/scsc/mx_dbg_sampler.c b/drivers/misc/samsung/scsc/mx_dbg_sampler.c new file mode 100755 index 000000000000..7c7ab995083d --- /dev/null +++ b/drivers/misc/samsung/scsc/mx_dbg_sampler.c @@ -0,0 +1,769 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mx_dbg_sampler.h" +#include "scsc_mif_abs.h" +#include "mxman.h" +#include "scsc_mx_impl.h" +#include "miframman.h" + +#include + +static unsigned int source_addr = 0xd0300028; +module_param(source_addr, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(source_addr, "Relative address of Location to sample (usually a register) - default: 0xd0300028. Loaded at /dev open"); + +static unsigned int num_bytes = 4; +module_param(num_bytes, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(num_bytes, "Number of significant octets (1,2 or 4) to log (lsbytes from source) - default: 4. Loaded at /dev open"); + +static unsigned int period_usecs; +module_param(period_usecs, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(period_usecs, "Sampling period. 0 means as fast as possible (powers of 2 only) - default: 0. Loaded at /dev open"); + +static bool auto_start; +module_param(auto_start, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(auto_start, "Start/stop sampling when service is started/stopped? - default: N. Loaded at /dev open"); + +static unsigned int buf_len = 512 * 1024; +module_param(buf_len, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(buf_len, "Circular buffer length (octets, 2^n) in bytes - default: 524288. Loaded at /dev open"); + +static unsigned int kfifo_len = 4 * 1024 * 1024; +module_param(kfifo_len, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(kfifo_len, "Kfifo buffer length (octets, 2^n) in bytes - default: 4194304. Loaded at /dev open"); + +static bool self_test; +module_param(self_test, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(self_test, "Execute self test by triggering a Kernel thread which writes into shared memory and then calls irg handler - default: N. Loaded at /dev open"); + +#define DRV_NAME "mx_dbg_sampler" +#define DEVICE_NAME "mx_dbg_sampler" + +#ifndef VM_RESERVED +#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP) +#endif + +#define VER_MAJOR 0 +#define VER_MINOR 0 + +#define SCSC_MX_DEBUG_NODE 1 + +#define SCSC_MX_DEBUG_INTERFACES (5 * (SCSC_MX_DEBUG_NODE)) + +DECLARE_BITMAP(bitmap_dbg_sampler_minor, SCSC_MX_DEBUG_INTERFACES); + +#define NO_ERROR 0 +#define BUFFER_OVERFLOW 1 +#define KFIFO_ERROR 2 +#define KFIFO_FULL 3 + +struct mx_dbg_sampler_dev { + /* file pointer */ + struct file *filp; + /* char device */ + struct cdev cdev; + /*device pointer*/ + struct device *dev; + /* mx_wlan_client */ + struct scsc_service_client mx_client; + /*service pointer*/ + struct scsc_service *service; + /*service pointer*/ + scsc_mifram_ref ref; + /*mx pointer*/ + struct scsc_mx *mx; + /* Associated kfifo */ + DECLARE_KFIFO_PTR(fifo, u8); + /* Associated read_wait queue.*/ + wait_queue_head_t read_wait; + /* Associated debug_buffer */ + struct debug_sampler_config info; + /* Buffer read index */ + u32 read_idx; + /* Device in error */ + u8 error; + /* Device node spinlock for IRQ */ + spinlock_t spinlock; + /* Device node mutex for fops */ + struct mutex mutex; + /* To profile kfifo num elements */ + u32 kfifo_max; + /* Device is in use */ + bool in_use; +}; + +/** + * SCSC User Space debug sampler interface (singleton) + */ +static struct { + dev_t device; + struct class *class_mx_dbg_sampler; + struct mx_dbg_sampler_dev devs[SCSC_MX_DEBUG_INTERFACES]; +} mx_dbg_sampler; + +static int recovery_in_progress; + +static void mx_dbg_sampler_stop_on_failure(struct scsc_service_client *client) +{ + SCSC_TAG_INFO(MX_SAMPLER, "TODO\n"); + recovery_in_progress = 1; +} + +static void mx_dbg_sampler_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + (void)scsc_panic_code; + SCSC_TAG_INFO(MX_SAMPLER, "TODO\n"); +} + +static void mx_wlan_read_process(const void *data, size_t length, struct mx_dbg_sampler_dev *mx_dev) +{ + int ret; + void *read_ptr; + u32 elements; + + /* Adjust lenght for kfifo type (u8)- elements -*/ + elements = length; + + if (mx_dev->filp) { + /* put string into the fifo */ + if (kfifo_avail(&mx_dev->fifo) >= elements) { + /* Push values in Fifo*/ + read_ptr = (void *)data + (mx_dev->read_idx & (buf_len - 1)); + ret = kfifo_in(&mx_dev->fifo, read_ptr, elements); + mx_dev->read_idx += ret; + if (ret != elements || ret == 0) { + mx_dev->error = KFIFO_ERROR; + return; + } + ret = kfifo_len(&mx_dev->fifo); + if (ret > mx_dev->kfifo_max) + mx_dev->kfifo_max = ret; + } else { + /* Mask interrupt to avoid interrupt storm */ + mx_dev->error = KFIFO_FULL; + return; + } + wake_up_interruptible(&mx_dev->read_wait); + } + /* Device is closed. Silenty return */ +} + +static void mx_dbg_sampler_irq_handler(int irq, void *data) +{ + struct mx_dbg_sampler_dev *mx_dev = (struct mx_dbg_sampler_dev *)data; + struct scsc_service *service = mx_dev->service; + u32 write_ref; + u32 data_ref; + void *write_ptr; + void *data_ptr; + u32 read_idx; + u32 write_idx; + size_t to_read; + unsigned long flags; + + spin_lock_irqsave(&mx_dev->spinlock, flags); + + /* check whether service has been released */ + if (!mx_dev->service) { + spin_unlock_irqrestore(&mx_dev->spinlock, flags); + return; + } + + read_idx = mx_dev->read_idx; + + write_ref = mx_dev->info.buffer_info.write_index_offset; + write_ptr = scsc_mx_service_mif_addr_to_ptr(service, write_ref); + write_idx = *((u32 *)write_ptr); + + to_read = abs((s32)write_idx - (s32)read_idx); + + /* TODO: Decide whether we need to do the memdump on a workqueue/tasklet or just in the int handler */ + if (to_read > mx_dev->info.buffer_info.buf_len) { + scsc_service_mifintrbit_bit_clear(service, irq); + scsc_service_mifintrbit_bit_mask(service, irq); + mx_dev->error = BUFFER_OVERFLOW; + goto end; + } + + data_ref = mx_dev->info.buffer_info.buf_offset; + data_ptr = scsc_mx_service_mif_addr_to_ptr(service, data_ref); + mx_wlan_read_process(data_ptr, to_read, mx_dev); /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(service, irq); + scsc_service_mifintrbit_bit_unmask(service, irq); +end: + spin_unlock_irqrestore(&mx_dev->spinlock, flags); + + /* Mask if dev is in error */ + /* We shouldn't be printing out lots of stuff here, but it is in error condition */ + if (mx_dev->error != NO_ERROR) { + scsc_service_mifintrbit_bit_mask(service, irq); + if (mx_dev->error == BUFFER_OVERFLOW) + SCSC_TAG_ERR(MX_SAMPLER, "Error, Buffer Overflow %zu write_idx 0x%x read_idex 0x%x\n", to_read, write_idx, read_idx); + else if (mx_dev->error == KFIFO_ERROR) + SCSC_TAG_ERR(MX_SAMPLER, "Error Pushing values in kfifo\n"); + else if (mx_dev->error == KFIFO_FULL) + SCSC_TAG_ERR(MX_SAMPLER, "Error kfifo is full\n"); + } +} + +static struct task_struct *mx_dbg_sampler_task; + +#define BULK_DATA (16 * 1024) +int mx_dbg_sampler_thread(void *data) +{ + struct mx_dbg_sampler_dev *dev = (struct mx_dbg_sampler_dev *)data; + struct scsc_service *service = dev->service; + u32 write; + u32 mem; + void *write_ptr; + u32 *mem_ptr; + u32 val; + u32 i; + u32 end; + + while (!kthread_should_stop() && !(dev->error != NO_ERROR)) { + write = dev->info.buffer_info.write_index_offset; + write_ptr = scsc_mx_service_mif_addr_to_ptr(service, write); + val = *((u32 *)write_ptr); + val += BULK_DATA; + *((u32 *)write_ptr) = val; + + end = BULK_DATA; + + + mem = dev->info.buffer_info.buf_offset; + mem_ptr = scsc_mx_service_mif_addr_to_ptr(service, mem); + + mem_ptr += dev->read_idx / sizeof(u32); + + for (i = 0; i < end / 4; i++) + *((u32 *)mem_ptr++) = 0x33323130; + + mx_dbg_sampler_irq_handler(0, dev); + mdelay(100); + } + mx_dbg_sampler_task = NULL; + return 0; +} + +static int mx_dbg_sampler_allocate_resources(struct scsc_service *service, struct mx_dbg_sampler_dev *mx_dev) +{ + scsc_mifram_ref ref, ref_buffer, ref_index; + int ret = 0; + struct debug_sampler_align *mem; + + /* Allocate memory */ + ret = scsc_mx_service_mifram_alloc(service, buf_len + sizeof(struct debug_sampler_align), &ref, 64); + if (ret) + return -ENOMEM; + mem = (struct debug_sampler_align *)scsc_mx_service_mif_addr_to_ptr(service, ref); + + /* Allocate interrupt */ + ret = scsc_service_mifintrbit_register_tohost(service, mx_dbg_sampler_irq_handler, mx_dev); + if (ret < 0) { + SCSC_TAG_ERR(MX_SAMPLER, "Error allocating interrupt\n"); + scsc_mx_service_mifram_free(service, ref); + return ret; + } + /* Populate the buffer_info */ + mem->config.version = mx_dev->info.version = 0; + + scsc_mx_service_mif_ptr_to_addr(service, &mem->mem, &ref_buffer); + mem->config.buffer_info.buf_offset = mx_dev->info.buffer_info.buf_offset = ref_buffer; + + mem->config.buffer_info.buf_len = mx_dev->info.buffer_info.buf_len = buf_len; + + scsc_mx_service_mif_ptr_to_addr(service, &mem->index, &ref_index); + mem->config.buffer_info.write_index_offset = + mx_dev->info.buffer_info.write_index_offset = ref_index; + + /* Reset write index */ + mem->index = 0; + + mem->config.buffer_info.intr_num = mx_dev->info.buffer_info.intr_num = ret; + + mem->config.sample_spec.source_addr = source_addr; + mem->config.sample_spec.num_bytes = num_bytes; + mem->config.sample_spec.period_usecs = period_usecs; + mem->config.auto_start = auto_start; + + mx_dev->ref = ref; + /* Reset read index */ + mx_dev->read_idx = 0; + + return 0; +} + +static int mx_dbg_sampler_free_resources(struct scsc_service *service, struct mx_dbg_sampler_dev *mx_dev) +{ + if (self_test) + if (mx_dbg_sampler_task) + kthread_stop(mx_dbg_sampler_task); + + scsc_service_mifintrbit_unregister_tohost(service, + mx_dev->info.buffer_info.intr_num); + scsc_mx_service_mifram_free(service, + mx_dev->ref); + return 0; +} + +int mx_dbg_sampler_open(struct inode *inode, struct file *filp) +{ + struct mx_dbg_sampler_dev *mx_dev; + int ret = 0, r; + + mx_dev = container_of(inode->i_cdev, struct mx_dbg_sampler_dev, cdev); + + if (mutex_lock_interruptible(&mx_dev->mutex)) + return -ERESTARTSYS; + + if (mx_dev->in_use) { + SCSC_TAG_ERR(MX_SAMPLER, "Device node already opened. Only one instance allowed. Exit\n"); + ret = -EIO; + goto end; + } + + if (filp->private_data) { + SCSC_TAG_ERR(MX_SAMPLER, "Service already started\n"); + ret = -EIO; + goto end; + } + + filp->private_data = mx_dev; + mx_dev->filp = filp; + /* Clear any remaining error */ + mx_dev->error = NO_ERROR; + + ret = kfifo_alloc(&mx_dev->fifo, kfifo_len, GFP_KERNEL); + if (ret) { + SCSC_TAG_ERR(MX_SAMPLER, "kfifo_alloc failed"); + ret = -EIO; + goto error; + } + + mx_dev->service = scsc_mx_service_open(mx_dev->mx, SCSC_SERVICE_ID_DBG_SAMPLER, &mx_dev->mx_client, &ret); + if (!mx_dev->service) { + SCSC_TAG_ERR(MX_SAMPLER, "Error opening service is NULL\n"); + kfifo_free(&mx_dev->fifo); + ret = -EIO; + goto error; + } + /* Allocate resources */ + ret = mx_dbg_sampler_allocate_resources(mx_dev->service, mx_dev); + if (ret) { + SCSC_TAG_ERR(MX_SAMPLER, "Error Allocating resources\n"); + kfifo_free(&mx_dev->fifo); + r = scsc_mx_service_close(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_close failed %d\n", r); + goto error; + } + + ret = scsc_mx_service_start(mx_dev->service, mx_dev->ref); + if (ret) { + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_start failed\n"); + mx_dbg_sampler_free_resources(mx_dev->service, mx_dev); + kfifo_free(&mx_dev->fifo); + r = scsc_mx_service_close(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_close failed %d\n", r); + goto error; + } + /* WARNING: At this point we may be receiving interrupts from Maxwell */ + + /* Trigger the dummy thread to test the functionality */ + if (self_test) + mx_dbg_sampler_task = kthread_run(mx_dbg_sampler_thread, (void *)mx_dev, "mx_dbg_sampler_thread"); + + SCSC_TAG_INFO(MX_SAMPLER, "%s: Sampling....\n", DRV_NAME); + mx_dev->in_use = true; + mutex_unlock(&mx_dev->mutex); + return 0; +error: + filp->private_data = NULL; + mx_dev->filp = NULL; + mx_dev->service = NULL; +end: + mutex_unlock(&mx_dev->mutex); + return ret; +} + +static ssize_t mx_dbg_sampler_read(struct file *filp, char __user *buf, size_t len, loff_t *offset) +{ + unsigned int copied; + int ret = 0; + struct mx_dbg_sampler_dev *mx_dev; + + mx_dev = filp->private_data; + + if (mutex_lock_interruptible(&mx_dev->mutex)) + return -EINTR; + + /* Check whether the device is in error */ + if (mx_dev->error != NO_ERROR) { + SCSC_TAG_ERR(MX_SAMPLER, "Device in error\n"); + ret = -EIO; + goto end; + } + + while (len) { + if (kfifo_len(&mx_dev->fifo)) { + ret = kfifo_to_user(&mx_dev->fifo, buf, len, &copied); + if (!ret) + ret = copied; + break; + } + + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + ret = wait_event_interruptible(mx_dev->read_wait, + !kfifo_is_empty(&mx_dev->fifo)); + if (ret < 0) + break; + } +end: + mutex_unlock(&mx_dev->mutex); + return ret; +} + +static unsigned mx_dbg_sampler_poll(struct file *filp, poll_table *wait) +{ + struct mx_dbg_sampler_dev *mx_dev; + int ret; + + mx_dev = filp->private_data; + + if (mutex_lock_interruptible(&mx_dev->mutex)) + return -EINTR; + + if (mx_dev->error != NO_ERROR) { + ret = POLLERR; + goto end; + } + + poll_wait(filp, &mx_dev->read_wait, wait); + + if (!kfifo_is_empty(&mx_dev->fifo)) { + ret = POLLIN | POLLRDNORM; /* readeable */ + goto end; + } + + ret = POLLOUT | POLLWRNORM; /* writable */ + +end: + mutex_unlock(&mx_dev->mutex); + return ret; +} + +int mx_dbg_sampler_release(struct inode *inode, struct file *filp) +{ + struct mx_dbg_sampler_dev *mx_dev; + unsigned long flags; + int r; + + mx_dev = container_of(inode->i_cdev, struct mx_dbg_sampler_dev, cdev); + + if (mutex_lock_interruptible(&mx_dev->mutex)) + return -EINTR; + + if (mx_dev->filp == NULL) { + SCSC_TAG_ERR(MX_SAMPLER, "Device already closed\n"); + mutex_unlock(&mx_dev->mutex); + return -EIO; + } + + if (mx_dev != filp->private_data) { + SCSC_TAG_ERR(MX_SAMPLER, "Data mismatch\n"); + mutex_unlock(&mx_dev->mutex); + return -EIO; + } + + spin_lock_irqsave(&mx_dev->spinlock, flags); + filp->private_data = NULL; + mx_dev->filp = NULL; + mx_dev->in_use = false; + kfifo_free(&mx_dev->fifo); + spin_unlock_irqrestore(&mx_dev->spinlock, flags); + + if (mx_dev->service) { + r = scsc_mx_service_stop(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_stop failed err: %d\n", r); + mx_dbg_sampler_free_resources(mx_dev->service, mx_dev); + r = scsc_mx_service_close(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_close failed %d\n", r); + + spin_lock_irqsave(&mx_dev->spinlock, flags); + mx_dev->service = NULL; + spin_unlock_irqrestore(&mx_dev->spinlock, flags); + } + + mutex_unlock(&mx_dev->mutex); + SCSC_TAG_INFO(MX_SAMPLER, "%s: Sampling... end. Kfifo_max = %d\n", DRV_NAME, mx_dev->kfifo_max); + return 0; +} + +static const struct file_operations mx_dbg_sampler_fops = { + .owner = THIS_MODULE, + .open = mx_dbg_sampler_open, + .read = mx_dbg_sampler_read, + .release = mx_dbg_sampler_release, + .poll = mx_dbg_sampler_poll, +}; + +void mx_dbg_sampler_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + dev_t devn; + int ret, i = SCSC_MX_DEBUG_INTERFACES; + char dev_name[20]; + long uid = 0; + int minor; + struct mx_dbg_sampler_dev *mx_dev; + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress) { + SCSC_TAG_INFO(MX_SAMPLER, "Recovery remove - no recovery in progress\n"); + return; + } + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery_in_progress) { + SCSC_TAG_INFO(MX_SAMPLER, "Recovery probe\n"); + + while (i--) + if (mx_dbg_sampler.devs[i].cdev.dev && mx_dbg_sampler.devs[i].mx) { + mx_dev = &mx_dbg_sampler.devs[i]; + /* This should be never be true - as knod should prevent unloading while + * the service (device node) is open */ + + mx_dev->service = scsc_mx_service_open(mx_dev->mx, SCSC_SERVICE_ID_DBG_SAMPLER, &mx_dev->mx_client, &ret); + if (!mx_dev->service) { + SCSC_TAG_ERR(MX_SAMPLER, "Error opening service is NULL\n"); + } else { + int r; + + ret = scsc_mx_service_start(mx_dev->service, mx_dev->ref); + if (ret) { + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_start failed\n"); + mx_dbg_sampler_free_resources(mx_dev->service, mx_dev); + r = scsc_mx_service_close(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, + "scsc_mx_service_close failed %d\n", r); + } + } + } + recovery_in_progress = 0; + } else { + /* Search for free minors */ + minor = find_first_zero_bit(bitmap_dbg_sampler_minor, SCSC_MX_DEBUG_INTERFACES); + if (minor >= SCSC_MX_DEBUG_INTERFACES) { + SCSC_TAG_ERR(MX_SAMPLER, "minor %d > SCSC_TTY_MINORS\n", minor); + return; + } + +#if 0 + /* TODO GET UID */ + if (kstrtol(dev_uid, 10, &uid)) { + SCSC_TAG_ERR(MX_SAMPLER, "Invalid device uid default to zero\n"); + uid = 0; + } +#endif + + devn = MKDEV(MAJOR(mx_dbg_sampler.device), MINOR(minor)); + + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "mx", (int)uid, "debug_sampler"); + + cdev_init(&mx_dbg_sampler.devs[minor].cdev, &mx_dbg_sampler_fops); + mx_dbg_sampler.devs[minor].cdev.owner = THIS_MODULE; + mx_dbg_sampler.devs[minor].cdev.ops = &mx_dbg_sampler_fops; + + ret = cdev_add(&mx_dbg_sampler.devs[minor].cdev, devn, 1); + if (ret) { + mx_dbg_sampler.devs[minor].cdev.dev = 0; + mx_dbg_sampler.devs[minor].dev = NULL; + return; + } + + mx_dbg_sampler.devs[minor].dev = device_create(mx_dbg_sampler.class_mx_dbg_sampler, NULL, mx_dbg_sampler.devs[minor].cdev.dev, NULL, dev_name); + + if (mx_dbg_sampler.devs[minor].dev == NULL) { + SCSC_TAG_ERR(MX_SAMPLER, "dev is NULL\n"); + cdev_del(&mx_dbg_sampler.devs[minor].cdev); + return; + } + + mx_dbg_sampler.devs[minor].mx = mx; + mx_dbg_sampler.devs[minor].mx_client.stop_on_failure = mx_dbg_sampler_stop_on_failure; + mx_dbg_sampler.devs[minor].mx_client.failure_reset = mx_dbg_sampler_failure_reset; + + mutex_init(&mx_dbg_sampler.devs[minor].mutex); + spin_lock_init(&mx_dbg_sampler.devs[minor].spinlock); + mx_dbg_sampler.devs[minor].kfifo_max = 0; + + init_waitqueue_head(&mx_dbg_sampler.devs[minor].read_wait); + + /* Update bit mask */ + set_bit(minor, bitmap_dbg_sampler_minor); + + SCSC_TAG_INFO(MX_SAMPLER, "%s: Ready to start sampling....\n", DRV_NAME); + } +} + +void mx_dbg_sampler_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + int i = SCSC_MX_DEBUG_INTERFACES, r; + struct mx_dbg_sampler_dev *mx_dev; + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress) { + SCSC_TAG_INFO(MX_SAMPLER, "Recovery remove - no recovery in progress\n"); + return; + } + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery_in_progress) { + SCSC_TAG_INFO(MX_SAMPLER, "Recovery remove\n"); + + while (i--) + if (mx_dbg_sampler.devs[i].cdev.dev && mx_dbg_sampler.devs[i].mx) { + mx_dev = &mx_dbg_sampler.devs[i]; + /* This should be never be true - as knod should prevent unloading while + * the service (device node) is open */ + if (mx_dbg_sampler.devs[i].service) { + r = scsc_mx_service_stop(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_stop failed err: %d\n", r); + + r = scsc_mx_service_close(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_close failed err: %d\n", r); + } + } + } else { + while (i--) + if (mx_dbg_sampler.devs[i].mx == mx) { + device_destroy(mx_dbg_sampler.class_mx_dbg_sampler, mx_dbg_sampler.devs[i].cdev.dev); + cdev_del(&mx_dbg_sampler.devs[i].cdev); + memset(&mx_dbg_sampler.devs[i].cdev, 0, sizeof(struct cdev)); + mx_dbg_sampler.devs[i].mx = NULL; + clear_bit(i, bitmap_dbg_sampler_minor); + } + } +} + +/* Test client driver registration */ +struct scsc_mx_module_client mx_dbg_sampler_driver = { + .name = "MX client test driver", + .probe = mx_dbg_sampler_probe, + .remove = mx_dbg_sampler_remove, +}; + +/* Test client driver registration */ +static int __init mx_dbg_sampler_init(void) +{ + int ret; + + SCSC_TAG_INFO(MX_SAMPLER, "mx_dbg_sampler INIT; version: %d.%d\n", VER_MAJOR, VER_MINOR); + + ret = alloc_chrdev_region(&mx_dbg_sampler.device, 0, SCSC_MX_DEBUG_INTERFACES, "mx_dbg_sampler_char"); + if (ret) + goto error; + + mx_dbg_sampler.class_mx_dbg_sampler = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mx_dbg_sampler.class_mx_dbg_sampler)) { + SCSC_TAG_ERR(MX_SAMPLER, "mx_dbg_sampler class creation failed\n"); + ret = PTR_ERR(mx_dbg_sampler.class_mx_dbg_sampler); + goto error_class; + } + + ret = scsc_mx_module_register_client_module(&mx_dbg_sampler_driver); + if (ret) { + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_module_register_client_module failed: ret=%d\n", ret); + goto error_reg; + } + + return 0; + +error_reg: + class_destroy(mx_dbg_sampler.class_mx_dbg_sampler); +error_class: + unregister_chrdev_region(mx_dbg_sampler.device, SCSC_MX_DEBUG_INTERFACES); +error: + return ret; +} + +/* module level */ +static void __exit mx_dbg_sampler_unload(void) +{ + int i = SCSC_MX_DEBUG_INTERFACES; + unsigned long flags; + struct mx_dbg_sampler_dev *mx_dev; + int r; + + while (i--) + if (mx_dbg_sampler.devs[i].cdev.dev && mx_dbg_sampler.devs[i].mx) { + mx_dev = &mx_dbg_sampler.devs[i]; + /* This should be never be true - as knod should prevent unloading while + * the service (device node) is open */ + if (mx_dbg_sampler.devs[i].service) { + r = scsc_mx_service_stop(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_stop failed err: %d\n", r); + mx_dbg_sampler_free_resources(mx_dev->service, mx_dev); + r = scsc_mx_service_close(mx_dev->service); + if (r) + SCSC_TAG_ERR(MX_SAMPLER, "scsc_mx_service_close failed err: %d\n", r); + + spin_lock_irqsave(&mx_dbg_sampler.devs[i].spinlock, flags); + mx_dbg_sampler.devs[i].filp = NULL; + kfifo_free(&mx_dbg_sampler.devs[i].fifo); + mx_dbg_sampler.devs[i].service = NULL; + spin_unlock_irqrestore(&mx_dev->spinlock, flags); + } + device_destroy(mx_dbg_sampler.class_mx_dbg_sampler, mx_dbg_sampler.devs[i].cdev.dev); + cdev_del(&mx_dbg_sampler.devs[i].cdev); + memset(&mx_dbg_sampler.devs[i].cdev, 0, sizeof(struct cdev)); + mx_dbg_sampler.devs[i].mx = NULL; + clear_bit(i, bitmap_dbg_sampler_minor); + } + class_destroy(mx_dbg_sampler.class_mx_dbg_sampler); + unregister_chrdev_region(mx_dbg_sampler.device, SCSC_MX_DEBUG_INTERFACES); + + SCSC_TAG_INFO(MX_SAMPLER, "mx_dbg_sampler EXIT; version: %d.%d\n", VER_MAJOR, VER_MINOR); +} + +module_init(mx_dbg_sampler_init); +module_exit(mx_dbg_sampler_unload); + +MODULE_DESCRIPTION("Samsung debug sampler Driver"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); +/* + * MODULE_INFO(version, VER_MAJOR); + * MODULE_INFO(build, SLSI_BUILD_STRING); + * MODULE_INFO(release, SLSI_RELEASE_STRING); + */ diff --git a/drivers/misc/samsung/scsc/mx_dbg_sampler.h b/drivers/misc/samsung/scsc/mx_dbg_sampler.h new file mode 100644 index 000000000000..240601089e01 --- /dev/null +++ b/drivers/misc/samsung/scsc/mx_dbg_sampler.h @@ -0,0 +1,121 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MX_DBG_SAMPLER_H__ +#define __MX_DBG_SAMPLER_H__ + + +/** + * Debug Sampler DRAM Buffer descriptor. + * + * Initialised by Debug Sampler Driver on AP and passed by + * reference to Debug Sampler (Proxy) on R4 (by reference in + * WLAN config). + * + * Integer fields are LittleEndian. + */ +struct debug_sampler_buffer_info { + /** + * Offset of circular octet buffer w.r.t. shared dram start + */ + uint32_t buf_offset; + + /** + * Circular buffer length (octets, 2^n) + * + * Default = 32KiB default + */ + uint32_t buf_len; + + /** + * Offset of 32bit write index (not wrapped, counts octets) w.r.t. shared dram start + */ + uint32_t write_index_offset; + + /** + * To AP interrupt number (0 – 15) + */ + uint32_t intr_num; +}; + +struct debug_sampler_sample_spec { + /** + * -relative address of Location to sample (usually a register) + * + * Default = 0x00000000 + */ + uint32_t source_addr; + + /** + * Number of significant octets (1,2 or 4) to log (lsbytes from source) + * + * Default = 4 + */ + uint32_t num_bytes; + + /** + * Sampling period. + * + * 0 means as fast as possible (powers of 2 only) + * + * Default = 0 + */ + uint32_t period_usecs; +}; + + +/** + * Debug Sampler Config Structure. + * + * This structure is allocated and initialised by the Debug Sampler driver + * on the AP and passed via the service_start message. + */ +struct debug_sampler_config { + /** + * Config Structure Version (= DBGSAMPLER_CONFIG_VERSION) + * + * Set by driver, checked by service. + */ + uint32_t version; + + /** + * To-host circular buffer desciptor. + */ + struct debug_sampler_buffer_info buffer_info; + + /** + * Init/default sampling specification. + * + * (There is also an API on R4 to allow dynamic specification + * change - e.g. by WLAN service) + */ + struct debug_sampler_sample_spec sample_spec; + + /** + * Start/stop sampling when service is started/stopped? + * + * (There is also an API on R4 to allow dynamic start/stop + * - e.g. by WLAN service) + * + * Default = 0 + */ + uint32_t auto_start; +}; + +struct debug_sampler_align { + + struct debug_sampler_config config __aligned(4); + + u32 index; + + void *mem __aligned(64); + +}; + + +#endif /* __MX_DBG_SAMPLER_H__ */ + + diff --git a/drivers/misc/samsung/scsc/mx_mmap.c b/drivers/misc/samsung/scsc/mx_mmap.c new file mode 100644 index 000000000000..e080e764609e --- /dev/null +++ b/drivers/misc/samsung/scsc/mx_mmap.c @@ -0,0 +1,545 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scsc_mif_abs.h" +#include "mxman.h" +#include "scsc_mx_impl.h" +#include "gdb_transport.h" + +#define DRV_NAME "mx_mmap" +#define DEVICE_NAME "maxwell_mmap" + +#ifndef VM_RESERVED +#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP) +#endif + +#define VER_MAJOR 0 +#define VER_MINOR 0 + +#define SCSC_MMAP_NODE 1 +#define SCSC_GDB_NODE 1 +#define SCSC_GDB_DEF_BUF_SZ 64 + +#define SCSC_MAX_INTERFACES (5 * (SCSC_MMAP_NODE + SCSC_GDB_NODE)) + +#define MAX_MEMORY (8 * 1024 * 1024UL) /* maximum memory: this should match MX_DRAM_SIZE_SECTION_1 */ + +DECLARE_BITMAP(bitmap_minor, SCSC_MAX_INTERFACES); + +struct mx_mmap_dev { + /* file pointer */ + struct file *filp; + /* char device */ + struct cdev cdev; + /*device pointer*/ + struct device *dev; + /*mif_abs pointer*/ + struct scsc_mif_abs *mif_abs; + /*mif_abs pointer*/ + struct scsc_mx *mx; + /*mif_abs pointer*/ + struct gdb_transport *gdb_transport; + /*memory cache*/ + void *mem; + /* Associated kfifo */ + struct kfifo fifo; + /* Associated read_wait queue.*/ + wait_queue_head_t read_wait; + /* User count */ + volatile unsigned long lock; +}; + +/** + * SCSC User Space mmap interface (singleton) + */ +static struct { + dev_t device; + struct class *class_mx_mmap; + struct mx_mmap_dev devs[SCSC_MAX_INTERFACES]; /*MMAP NODE + GDB NODE*/ +} mx_mmap; + +int mx_mmap_open(struct inode *inode, struct file *filp) +{ + struct mx_mmap_dev *dev; + + dev = container_of(inode->i_cdev, struct mx_mmap_dev, cdev); + + SCSC_TAG_INFO(MX_MMAP, "open %p\n", filp); + + filp->private_data = dev; + + return 0; +} + +/* + * This function maps the contiguous device mapped area + * to user space. This is specfic to device which is called though fd. + */ +int mx_mmap_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int err; + struct mx_mmap_dev *mx_dev; + uintptr_t pfn = 0; + + if (vma->vm_end - vma->vm_start > MAX_MEMORY) { + SCSC_TAG_ERR(MX_MMAP, "Incorrect mapping size %ld, should be less than %ld\n", + vma->vm_end - vma->vm_start, MAX_MEMORY); + err = -EINVAL; + } + mx_dev = filp->private_data; + + /* Get the memory */ + mx_dev->mem = mx_dev->mif_abs->get_mifram_ptr(mx_dev->mif_abs, 0); + + if (!mx_dev->mem) + return -ENODEV; + + /* Get page frame number from virtual abstraction layer */ + pfn = mx_dev->mif_abs->get_mifram_pfn(mx_dev->mif_abs); + + /* remap kernel memory to userspace */ + err = remap_pfn_range(vma, vma->vm_start, pfn, + vma->vm_end - vma->vm_start, vma->vm_page_prot); + + return err; +} + + + +int mx_mmap_release(struct inode *inode, struct file *filp) +{ + SCSC_TAG_INFO(MX_MMAP, "close %p\n", filp); + + /* TODO : Unmap pfn_range */ + return 0; +} + +static const struct file_operations mx_mmap_fops = { + .owner = THIS_MODULE, + .open = mx_mmap_open, + .mmap = mx_mmap_mmap, + .release = mx_mmap_release, +}; + +int mx_gdb_open(struct inode *inode, struct file *filp) +{ + struct mx_mmap_dev *mx_dev; + int ret; + + mx_dev = container_of(inode->i_cdev, struct mx_mmap_dev, cdev); + + SCSC_TAG_INFO(MX_MMAP, "open %p\n", filp); + + if (!mx_dev->gdb_transport) { + SCSC_TAG_ERR(MX_MMAP, "no transport %p\n", filp); + return -ENODEV; + } + + if (test_and_set_bit_lock(0, &mx_dev->lock)) { + SCSC_TAG_ERR(MX_MMAP, "already open %p\n", filp); + return -EBUSY; + } + + /* Prevent channel teardown while client has open */ + mutex_lock(&mx_dev->gdb_transport->channel_open_mutex); + + filp->private_data = mx_dev; + mx_dev->filp = filp; + ret = kfifo_alloc(&mx_dev->fifo, GDB_TRANSPORT_BUF_LENGTH, GFP_KERNEL); + if (ret) { + mutex_unlock(&mx_dev->gdb_transport->channel_open_mutex); + clear_bit_unlock(0, &mx_dev->lock); + return -ENOMEM; + } + + return 0; +} + +static ssize_t mx_gdb_write(struct file *filp, const char __user *ubuf, size_t len, loff_t *offset) +{ + struct mx_mmap_dev *mx_dev; + char *wbuf = NULL, *lbuf = NULL, buf[SCSC_GDB_DEF_BUF_SZ] = {}; + + mx_dev = filp->private_data; + /* When write_req do NOT fit inside the auto array just dyn-alloc */ + if (len <= SCSC_GDB_DEF_BUF_SZ) { + wbuf = buf; + } else { + wbuf = kzalloc(len, GFP_KERNEL); + if (!wbuf) + return -ENOMEM; + /* Use the freshly dyn-allocated buf */ + SCSC_TAG_DEBUG(MX_MMAP, "Allocated GDB write dyn-buffer [%zd]\n", len); + lbuf = wbuf; + } + + if (copy_from_user(wbuf, ubuf, len)) { + kfree(lbuf); + return -EINVAL; + } + + gdb_transport_send(mx_dev->gdb_transport, (void *)wbuf, len); + kfree(lbuf); + + return len; +} + +static ssize_t mx_gdb_read(struct file *filp, char __user *buf, size_t len, loff_t *offset) +{ + int ret = 0; + unsigned int copied; + struct mx_mmap_dev *mx_dev; + + mx_dev = filp->private_data; + + while (len) { + if (kfifo_len(&mx_dev->fifo)) { + ret = kfifo_to_user(&mx_dev->fifo, buf, len, &copied); + if (!ret) { + SCSC_TAG_DEBUG(MX_MMAP, "Copied %d bytes to user.\n", copied); + ret = copied; + } + break; + } + + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + ret = wait_event_interruptible(mx_dev->read_wait, + !kfifo_is_empty(&mx_dev->fifo)); + if (ret < 0) + break; + } + + return ret; +} + +void gdb_read_callback(const void *message, size_t length, void *data) +{ + struct mx_mmap_dev *mx_dev = (struct mx_mmap_dev *)data; + int ret; + + if (mx_dev->filp) { + if (kfifo_avail(&mx_dev->fifo) >= length) { + ret = kfifo_in(&mx_dev->fifo, message, length); + if (ret != length) { + SCSC_TAG_ERR(MX_MMAP, "Unable to push into Kfifo Buffer\n"); + return; + } + SCSC_TAG_DEBUG(MX_MMAP, "Buffered %zu bytes\n", length); + } else { + SCSC_TAG_ERR(MX_MMAP, "Kfifo Buffer Overflow\n"); + return; + } + + wake_up_interruptible(&mx_dev->read_wait); + } else + SCSC_TAG_ERR(MX_MMAP, "Device is closed. Dropping %zu octets\n", + length); +} + +static unsigned int mx_gdb_poll(struct file *filp, poll_table *wait) +{ + struct mx_mmap_dev *mx_dev; + + mx_dev = filp->private_data; + + poll_wait(filp, &mx_dev->read_wait, wait); + + if (!kfifo_is_empty(&mx_dev->fifo)) + return POLLIN | POLLRDNORM; /* readeable */ + + return POLLOUT | POLLWRNORM; /* writable */ +} + +int mx_gdb_release(struct inode *inode, struct file *filp) +{ + struct mx_mmap_dev *mx_dev; + + mx_dev = container_of(inode->i_cdev, struct mx_mmap_dev, cdev); + + SCSC_TAG_INFO(MX_MMAP, "close %p\n", filp); + + if (mx_dev->filp == NULL) { + SCSC_TAG_ERR(MX_MMAP, "Device already closed\n"); + return -EIO; + } + + if (mx_dev != filp->private_data) { + SCSC_TAG_ERR(MX_MMAP, "Data mismatch\n"); + return -EIO; + } + + + clear_bit_unlock(0, &mx_dev->lock); + + filp->private_data = NULL; + mx_dev->filp = NULL; + kfifo_free(&mx_dev->fifo); + + mutex_unlock(&mx_dev->gdb_transport->channel_open_mutex); + + return 0; +} + +static const struct file_operations mx_gdb_fops = { + .owner = THIS_MODULE, + .open = mx_gdb_open, + .write = mx_gdb_write, + .read = mx_gdb_read, + .release = mx_gdb_release, + .poll = mx_gdb_poll, +}; + +/* + * Receive handler for messages from the FW along the maxwell management transport + */ +void client_gdb_probe(struct gdb_transport_client *gdb_client, struct gdb_transport *gdb_transport, char *dev_uid) +{ + dev_t devn; + int ret; + char dev_name[20]; + struct mx_mmap_dev *mx_dev; + long uid = 0; + int minor; + + /************/ + /* GDB node */ + /************/ + /* Search for free minors */ + minor = find_first_zero_bit(bitmap_minor, SCSC_MAX_INTERFACES); + + if (minor >= SCSC_MAX_INTERFACES) { + SCSC_TAG_ERR(MX_MMAP, "minor %d > SCSC_TTY_MINORS\n", minor); + return; + } + + if (kstrtol(dev_uid, 10, &uid)) { + SCSC_TAG_ERR(MX_MMAP, "Invalid device uid default to zero\n"); + uid = 0; + } + + devn = MKDEV(MAJOR(mx_mmap.device), MINOR(minor)); + + if (gdb_transport->type == GDB_TRANSPORT_M4) + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "mx", (int)uid, "m4_gdb"); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (gdb_transport->type == GDB_TRANSPORT_M4_1) + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "mx", (int)uid, "m4_1_gdb"); +#endif + else + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "mx", (int)uid, "r4_gdb"); + + cdev_init(&mx_mmap.devs[minor].cdev, &mx_gdb_fops); + mx_mmap.devs[minor].cdev.owner = THIS_MODULE; + mx_mmap.devs[minor].cdev.ops = &mx_gdb_fops; + + ret = cdev_add(&mx_mmap.devs[minor].cdev, devn, 1); + if (ret) { + SCSC_TAG_ERR(MX_MMAP, "cdev_add failed for device %s\n", dev_name); + mx_mmap.devs[minor].cdev.dev = 0; + mx_mmap.devs[minor].dev = NULL; + return; + } + + mx_mmap.devs[minor].dev = device_create(mx_mmap.class_mx_mmap, NULL, mx_mmap.devs[minor].cdev.dev, NULL, dev_name); + + if (mx_mmap.devs[minor].dev == NULL) { + cdev_del(&mx_mmap.devs[minor].cdev); + return; + } + + mx_dev = &mx_mmap.devs[minor]; + mx_mmap.devs[minor].gdb_transport = gdb_transport; + + gdb_transport_register_channel_handler(gdb_transport, gdb_read_callback, (void *)mx_dev); + init_waitqueue_head(&mx_mmap.devs[minor].read_wait); + + /* Update bit mask */ + set_bit(minor, bitmap_minor); +} + +void client_gdb_remove(struct gdb_transport_client *gdb_client, struct gdb_transport *gdb_transport) +{ + int i = SCSC_MAX_INTERFACES; + + while (i--) + if (mx_mmap.devs[i].gdb_transport == gdb_transport) { + device_destroy(mx_mmap.class_mx_mmap, mx_mmap.devs[i].cdev.dev); + cdev_del(&mx_mmap.devs[i].cdev); + memset(&mx_mmap.devs[i].cdev, 0, sizeof(struct cdev)); + mx_mmap.devs[i].gdb_transport = NULL; + clear_bit(i, bitmap_minor); + } +} + +/* Test client driver registration */ +struct gdb_transport_client client_gdb_driver = { + .name = "GDB client driver", + .probe = client_gdb_probe, + .remove = client_gdb_remove, +}; + +void scsc_mx_mmap_module_probe(struct scsc_mif_mmap_driver *abs_driver, struct scsc_mif_abs *mif_abs) +{ + dev_t devn; + int ret; + char dev_name[20]; + char *dev_uid; + long uid = 0; + int minor = 0; + + /* Search for free minors */ + minor = find_first_zero_bit(bitmap_minor, SCSC_MAX_INTERFACES); + + if (minor >= SCSC_MAX_INTERFACES) { + SCSC_TAG_ERR(MX_MMAP, "minor %d > SCSC_TTY_MINORS\n", minor); + return; + } + + /*************/ + /* MMAP node */ + /*************/ + dev_uid = mif_abs->get_uid(mif_abs); + if (kstrtol(dev_uid, 10, &uid)) + uid = 0; + + devn = MKDEV(MAJOR(mx_mmap.device), MINOR(minor)); + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "mx", (int)uid, "mmap"); + + cdev_init(&mx_mmap.devs[minor].cdev, &mx_mmap_fops); + mx_mmap.devs[minor].cdev.owner = THIS_MODULE; + mx_mmap.devs[minor].cdev.ops = &mx_mmap_fops; + + ret = cdev_add(&mx_mmap.devs[minor].cdev, devn, 1); + if (ret) { + SCSC_TAG_ERR(MX_MMAP, "cdev_add failed for device %s\n", dev_name); + mx_mmap.devs[minor].cdev.dev = 0; + mx_mmap.devs[minor].dev = NULL; + return; + } + + mx_mmap.devs[minor].dev = device_create(mx_mmap.class_mx_mmap, NULL, mx_mmap.devs[minor].cdev.dev, NULL, dev_name); + + if (mx_mmap.devs[minor].dev == NULL) { + cdev_del(&mx_mmap.devs[minor].cdev); + return; + } + + mx_mmap.devs[minor].mif_abs = mif_abs; + + mx_mmap.devs[minor].mem = mif_abs->get_mifram_ptr(mif_abs, 0); + + /* Update bit mask */ + set_bit(minor, bitmap_minor); +} + + +void scsc_mx_mmap_module_remove(struct scsc_mif_abs *mif_abs) +{ + int i = SCSC_MAX_INTERFACES; + + while (i--) + if (mx_mmap.devs[i].mif_abs == mif_abs) { + device_destroy(mx_mmap.class_mx_mmap, mx_mmap.devs[i].cdev.dev); + cdev_del(&mx_mmap.devs[i].cdev); + memset(&mx_mmap.devs[i].cdev, 0, sizeof(struct cdev)); + mx_mmap.devs[i].mif_abs = NULL; + clear_bit(i, bitmap_minor); + } +} + +static struct scsc_mif_mmap_driver mx_module_mmap_if = { + .name = "Maxwell mmap Driver", + .probe = scsc_mx_mmap_module_probe, + .remove = scsc_mx_mmap_module_remove, +}; + +static int __init mx_mmap_init(void) +{ + int ret; + + SCSC_TAG_INFO(MX_MMAP, "mx_mmap INIT; version: %d.%d\n", VER_MAJOR, VER_MINOR); + + + ret = alloc_chrdev_region(&mx_mmap.device, 0, SCSC_MAX_INTERFACES, "mx_mmap_char"); + if (ret) + goto error; + + mx_mmap.class_mx_mmap = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mx_mmap.class_mx_mmap)) { + ret = PTR_ERR(mx_mmap.class_mx_mmap); + goto error_class; + } + + scsc_mif_mmap_register(&mx_module_mmap_if); + + ret = gdb_transport_register_client(&client_gdb_driver); + if (ret) + SCSC_TAG_ERR(MX_MMAP, "scsc_mx_module_register_client_module failed: r=%d\n", ret); + + + return 0; + +error_class: + unregister_chrdev_region(mx_mmap.device, SCSC_MAX_INTERFACES); +error: + return ret; +} + +static void __exit mx_mmap_cleanup(void) +{ + int i = SCSC_MAX_INTERFACES; + + while (i--) + if (mx_mmap.devs[i].cdev.dev) { + device_destroy(mx_mmap.class_mx_mmap, mx_mmap.devs[i].cdev.dev); + cdev_del(&mx_mmap.devs[i].cdev); + memset(&mx_mmap.devs[i].cdev, 0, sizeof(struct cdev)); + clear_bit(i, bitmap_minor); + } + class_destroy(mx_mmap.class_mx_mmap); + unregister_chrdev_region(mx_mmap.device, SCSC_MAX_INTERFACES); + SCSC_TAG_INFO(MX_MMAP, "mx_mmap EXIT; version: %d.%d\n", VER_MAJOR, VER_MINOR); + + gdb_transport_unregister_client(&client_gdb_driver); + /* Notify lower layers that we are unloading */ + scsc_mif_mmap_unregister(&mx_module_mmap_if); +} + +module_init(mx_mmap_init); +module_exit(mx_mmap_cleanup); + +MODULE_DESCRIPTION("Samsung MMAP/GDB Driver"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); +/* + * MODULE_INFO(version, VER_MAJOR); + * MODULE_INFO(build, SLSI_BUILD_STRING); + * MODULE_INFO(release, SLSI_RELEASE_STRING); + */ diff --git a/drivers/misc/samsung/scsc/mxconf.h b/drivers/misc/samsung/scsc/mxconf.h new file mode 100644 index 000000000000..8fa520c92621 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxconf.h @@ -0,0 +1,188 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * mx140 Infrastructure Configuration Structure. + * + * Used to pass configuration data from AP to R4 infrastructure + * on Maxwell Subsystem startup. + * + * Notes: + * + * - All multi-octet integers shall be stored LittleEndian. + * + * - All location fields ("*_loc") are 32 bit octet offsets w.r.t. the R4 + * address map. They can therefore refer to DRAM memory or Mailbox registers. + * + * - "typedefs" are avoided to allow inclusion in linux source code. + */ +#ifndef MXCONF_H__ +#define MXCONF_H__ + +/* Uses */ + +/* It appears that due to the previous syntax "__packed struct foo" used in this + * header, the structures here don't actually get packed. Clang warns that + * that syntax is ignored. But correcting it causes a misalignment with FW. + * The __MXPACKED macro is used to stop packing the structures in this + * header until we've investigated further. + */ +#define __MXPACKED /* TODO: HACK - don't actually pack! */ + +/* Definitions */ + +/** + * Config structure magic number. + * + * The AP writes this value and the R4 checks it to trap endian mismatches. + */ +#define MXCONF_MAGIC 0x79828486 + +/** + * Config structure version + * + * The AP writes these values and the R4 checks them to trap config structure + * mismatches. + */ +#define MXCONF_VERSION_MAJOR 0 +#define MXCONF_VERSION_MINOR 5 /* For fleximac moredump */ + +/* Types */ + +/** + * Maxwell Circular Packet Buffer Configuration. + */ +struct mxcbufconf { + scsc_mifram_ref buffer_loc; /**< Location of allocated buffer in DRAM */ + uint32_t num_packets; /**< Total number of packets that can be stored in the buffer */ + uint32_t packet_size; /**< Size of each individual packet within the buffer */ + scsc_mifram_ref read_index_loc; /**< Location of 32bit read index in DRAM or Mailbox */ + scsc_mifram_ref write_index_loc; /**< Location of 32bit write index */ +} __MXPACKED; + +/** + * Maxwell Management Simplex Stream Configuration + * + * A circular buffer plus a pair of R/W signaling bits. + */ +struct mxstreamconf { + /** Circular Packet Buffer configuration */ + struct mxcbufconf buf_conf; + + /** Allocated MIF Interrupt Read Bit Index */ + uint8_t read_bit_idx; + + /** Allocated MIF Interrupt Write Bit Index */ + uint8_t write_bit_idx; +} __MXPACKED; + +/** + * Maxwell Management Transport Configuration + * + * A pair of simplex streams. + */ +struct mxtransconf { + struct mxstreamconf to_ap_stream_conf; + struct mxstreamconf from_ap_stream_conf; +} __MXPACKED; + +/** + * Maxwell Infrastructure Configuration Version + */ +struct mxconfversion { + uint16_t major; + uint16_t minor; +} __MXPACKED; + +/** + * Mxlog Event Buffer Configuration. + * + * A circular buffer. Size must be a multiple of 2. + */ +struct mxlogconf +{ + struct mxstreamconf stream_conf; +} __MXPACKED; + + +/** + * Maxwell Infrastructure Configuration Override (HCF block) + */ +struct mxmibref { + uint32_t offset; + uint32_t size; +} __MXPACKED; + + +/** + * Maxwell Infrastructure Configuration + */ +struct mxconf { + /** + * Config Magic Number + * + * Always 1st field in config. + */ + uint32_t magic; + + /** + * Config Version. + * + * Always second field in config. + */ + struct mxconfversion version; + + /** + * MX Management Message Transport Configuration. + */ + struct mxtransconf mx_trans_conf; + + /** + * MX Management GDB Message Transport Configuration. + */ + /* Cortex-R4 channel */ + struct mxtransconf mx_trans_conf_gdb_r4; + /* Cortex-M4 channel */ + struct mxtransconf mx_trans_conf_gdb_m4; + + /** + * Mxlog Event Buffer Configuration. + */ + struct mxlogconf mxlogconf; + + /* FROM MINOR_2 */ + + /** + * SOC HW revision override from host + */ + uint32_t soc_revision; + + /* FROM MINOR_3 */ + + /** + * Setup flags + */ +#define MXCONF_FLAGS_FM_ON (BIT(0)) /* FM already on */ + uint32_t flags; + + /* FROM MINOR_4 */ + + /** + * Common HCF offset + */ + struct mxmibref fwconfig; + + /* FROM MINOR_5 */ + + /* Fleximac Cortex-M3_1 piggy back as M4 channel. + * (Driver must initialise from-ap buffer address to 0 + * if channel is not in use). + */ + struct mxtransconf mx_trans_conf_gdb_m4_1; + +} __MXPACKED; + +#endif /* MXCONF_H__ */ diff --git a/drivers/misc/samsung/scsc/mxfwconfig.c b/drivers/misc/samsung/scsc/mxfwconfig.c new file mode 100644 index 000000000000..fa6ec7d01181 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxfwconfig.c @@ -0,0 +1,224 @@ +/***************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include + +#include "mxfwconfig.h" +#include "miframman.h" +#include "scsc_mx_impl.h" +#include "mxconf.h" + +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +#define MXFWCONFIG_CFG_SUBDIR "common" +#define MXFWCONFIG_CFG_FILE_HW "common.hcf" +#define MXFWCONFIG_CFG_FILE_SW "common_sw.hcf" + +static void mxfwconfig_get_dram_ref(struct scsc_mx *mx, struct mxmibref *cfg_ref); + +/* Load config into non-shared DRAM */ +static int mxfwconfig_load_cfg(struct scsc_mx *mx, struct mxfwconfig *cfg, const char *filename) +{ + int r = 0; + u32 i; + + if (cfg->configs >= SCSC_MX_MAX_COMMON_CFG) { + SCSC_TAG_ERR(MX_CFG, "Too many common config files (%u)\n", cfg->configs); + return -E2BIG; + } + + i = cfg->configs++; /* Claim next config slot */ + + /* Load config file from file system into DRAM */ + r = mx140_file_request_conf(mx, &cfg->config[i].fw, MXFWCONFIG_CFG_SUBDIR, filename); + if (r) + return r; + + /* Initial size of file */ + cfg->config[i].cfg_len = cfg->config[i].fw->size; + cfg->config[i].cfg_data = cfg->config[i].fw->data; + + /* Validate file in DRAM */ + if (cfg->config[i].cfg_len >= MX_COMMON_HCF_HDR_SIZE && /* Room for header */ + /*(cfg->config[i].cfg[6] & 0xF0) == 0x10 && */ /* Curator subsystem */ + cfg->config[i].cfg_data[7] == 1) { /* First file format */ + int j; + + cfg->config[i].cfg_hash = 0; + + /* Calculate hash */ + for (j = 0; j < MX_COMMON_HASH_SIZE_BYTES; j++) { + cfg->config[i].cfg_hash = + (cfg->config[i].cfg_hash << 8) | cfg->config[i].cfg_data[j + MX_COMMON_HASH_OFFSET]; + } + + SCSC_TAG_INFO(MX_CFG, "CFG hash: 0x%.04x\n", cfg->config[i].cfg_hash); + + /* All good - consume header and continue */ + cfg->config[i].cfg_len -= MX_COMMON_HCF_HDR_SIZE; + cfg->config[i].cfg_data += MX_COMMON_HCF_HDR_SIZE; + } else { + SCSC_TAG_ERR(MX_CFG, "Invalid HCF header size %zu\n", cfg->config[i].cfg_len); + + /* Caller must call mxfwconfig_unload_cfg() to release the buffer */ + return -EINVAL; + } + + /* Running shtotal payload */ + cfg->shtotal += cfg->config[i].cfg_len; + + SCSC_TAG_INFO(MX_CFG, "Loaded common config %s, size %zu, payload size %zu, shared dram total %zu\n", + filename, cfg->config[i].fw->size, cfg->config[i].cfg_len, cfg->shtotal); + + return r; +} + +/* Unload config from non-shared DRAM */ +static int mxfwconfig_unload_cfg(struct scsc_mx *mx, struct mxfwconfig *cfg, u32 index) +{ + if (index >= SCSC_MX_MAX_COMMON_CFG) { + SCSC_TAG_ERR(MX_CFG, "Out of range index (%u)\n", index); + return -E2BIG; + } + + if (cfg->config[index].fw) { + SCSC_TAG_DBG3(MX_CFG, "Unload common config %u\n", index); + + mx140_file_release_conf(mx, cfg->config[index].fw); + + cfg->config[index].fw = NULL; + cfg->config[index].cfg_data = NULL; + cfg->config[index].cfg_len = 0; + } + + return 0; +} + +/* + * Load Common config files + */ +int mxfwconfig_load(struct scsc_mx *mx, struct mxmibref *cfg_ref) +{ + struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx); + struct miframman *miframman = scsc_mx_get_ramman(mx); + int r; + u32 i; + u8 *dest; + + /* HW file is optional */ + r = mxfwconfig_load_cfg(mx, cfg, MXFWCONFIG_CFG_FILE_HW); + if (r) + goto done; + + /* SW file is optional, but not without HW file */ + r = mxfwconfig_load_cfg(mx, cfg, MXFWCONFIG_CFG_FILE_SW); + if (r == -EINVAL) { + /* If SW file is corrupt, abandon both HW and SW */ + goto done; + } + + /* Allocate shared DRAM */ + cfg->shdram = miframman_alloc(miframman, cfg->shtotal, 4, MIFRAMMAN_OWNER_COMMON); + if (!cfg->shdram) { + SCSC_TAG_ERR(MX_CFG, "MIF alloc failed for %zu octets\n", cfg->shtotal); + r = -ENOMEM; + goto done; + } + + /* Copy files into shared DRAM */ + for (i = 0, dest = (u8 *)cfg->shdram; + i < cfg->configs; + i++) { + /* Add to shared DRAM block */ + memcpy(dest, cfg->config[i].cfg_data, cfg->config[i].cfg_len); + dest += cfg->config[i].cfg_len; + } + +done: + /* Release the files from non-shared DRAM */ + for (i = 0; i < cfg->configs; i++) + mxfwconfig_unload_cfg(mx, cfg, i); + + /* Configs abandoned on error */ + if (r) + cfg->configs = 0; + + /* Pass offset of common HCF data. + * FW must ignore if zero length, so set up even if we loaded nothing. + */ + mxfwconfig_get_dram_ref(mx, cfg_ref); + + return r; +} + +/* + * Unload Common config data + */ +void mxfwconfig_unload(struct scsc_mx *mx) +{ + struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx); + struct miframman *miframman = scsc_mx_get_ramman(mx); + + /* Free config block in shared DRAM */ + if (cfg->shdram) { + SCSC_TAG_INFO(MX_CFG, "Free common config %zu bytes shared DRAM\n", cfg->shtotal); + + miframman_free(miframman, cfg->shdram); + + cfg->configs = 0; + cfg->shtotal = 0; + cfg->shdram = NULL; + } +} + +/* + * Get ref (offset) of config block in shared DRAM + */ +static void mxfwconfig_get_dram_ref(struct scsc_mx *mx, struct mxmibref *cfg_ref) +{ + struct mxfwconfig *mxfwconfig = scsc_mx_get_mxfwconfig(mx); + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mx); + + if (!mxfwconfig->shdram) { + cfg_ref->offset = (scsc_mifram_ref)0; + cfg_ref->size = 0; + } else { + mif->get_mifram_ref(mif, mxfwconfig->shdram, &cfg_ref->offset); + cfg_ref->size = mxfwconfig->shtotal; + } + + SCSC_TAG_INFO(MX_CFG, "cfg_ref: 0x%x, size %u\n", cfg_ref->offset, cfg_ref->size); +} + +/* + * Init config file module + */ +int mxfwconfig_init(struct scsc_mx *mx) +{ + struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx); + + cfg->configs = 0; + cfg->shtotal = 0; + cfg->shdram = NULL; + + return 0; +} + +/* + * Exit config file module + */ +void mxfwconfig_deinit(struct scsc_mx *mx) +{ + struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx); + + /* Leaked memory? */ + WARN_ON(cfg->configs > 0); + WARN_ON(cfg->shdram); +} + diff --git a/drivers/misc/samsung/scsc/mxfwconfig.h b/drivers/misc/samsung/scsc/mxfwconfig.h new file mode 100644 index 000000000000..f24d44c599ef --- /dev/null +++ b/drivers/misc/samsung/scsc/mxfwconfig.h @@ -0,0 +1,35 @@ +/***************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MXFWCONFIG_H +#define __MXFWCONFIG_H + +#define SCSC_MX_MAX_COMMON_CFG 2 +#define MX_COMMON_HCF_HDR_SIZE 8 +#define MX_COMMON_HASH_SIZE_BYTES 2 /* Hash will be contained in a uint32 */ +#define MX_COMMON_HASH_OFFSET 4 + +struct mxfwconfig { + u32 configs; /* Number of files */ + void *shdram; /* Combined payload in shared DRAM */ + size_t shtotal; /* Size of combined payload in shared DRAM */ + + struct { + const struct firmware *fw; /* File image in DRAM */ + const u8 *cfg_data; /* Payload in DRAM */ + size_t cfg_len; /* Length of payload */ + u32 cfg_hash; /* ID hash */ + } config[SCSC_MX_MAX_COMMON_CFG]; +}; + +struct mxmibref; + +int mxfwconfig_init(struct scsc_mx *mx); +void mxfwconfig_deinit(struct scsc_mx *mx); +int mxfwconfig_load(struct scsc_mx *mx, struct mxmibref *cfg_ref); +void mxfwconfig_unload(struct scsc_mx *mx); + +#endif // __MXFWCONFIG_H diff --git a/drivers/misc/samsung/scsc/mxlog.c b/drivers/misc/samsung/scsc/mxlog.c new file mode 100644 index 000000000000..e15afba58cfa --- /dev/null +++ b/drivers/misc/samsung/scsc/mxlog.c @@ -0,0 +1,410 @@ +/***************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include "scsc_mx_impl.h" +#include "mxmgmt_transport.h" +#include "mxlog_transport.h" +#include "fwhdr.h" +#include "mxlog.h" + +/* + * Receive handler for messages from the FW along the maxwell management transport + */ +static inline void mxlog_phase4_message_handler(const void *message, + size_t length, u32 level, + void *data) +{ + unsigned char *buf = (unsigned char *)message; + + SCSC_TAG_LVL(MX_FW, level, SCSC_PREFIX"%d: %s\n", (int)length, buf); +} + +/** + * This function is used to parse a NULL terminated format string + * and report on the provided output bitmaps smap/lmap which args + * are 'long' and which are signed.. + * + * We will care only about length and specifier fields + * + * %[flags][width][.precision][length]specifier + * + * and since flags width and .precision are represented + * by NON chars, we will grossly compare simply against an 'A', + * because we are NOT trying to make a full sanity check here BUT only + * to search for long and signed values to provide the proper cast. + * + * Supporting: + * - ESCAPES %%ld + * + * - %x %X %d %ld %lld %i %li %lli %u %lu %llu %hd %hhd %hu %hhu + * + * NOT supporting: + * - %s -> MARKED AS UNSUPPORTED + */ +static inline void build_len_sign_maps(char *fmt, u32 *smap, u32 *lmap, + u32 *strmap) +{ + u32 p = 0; + char *s = fmt; + bool escaping = false; + + if (!s) + return; + for (; *s != '\0'; ++s) { + /* Skip any escaped fmtstring like %%d and move on */ + if (escaping) { + if (*s == ' ') + escaping = false; + continue; + } + if (*s != '%') + continue; + /* Start escape seq ... */ + if (*(s + 1) == '%') { + escaping = true; + continue; + } + /* skip [flags][width][.precision] if any */ + for (; *++s < 'A';) + ; + if (*s == 'l') { + *lmap |= (1 << p); + /* %lld ? skip */ + if (*++s == 'l') + s++; + } else if (*s == 'h') { + /* just skip h modifiers */ + /* hhd ? */ + if (*++s == 'h') + s++; + } + if (*s == 'd' || *s == 'i') + *smap |= (1 << p); + else if (*s == 's') + *strmap |= (1 << p); + p++; + } +} + +/** + * The binary protocol described at: + * + * http://wiki/Maxwell_common_firmware/Mxlog#Phase_5_:_string_decoded_on_the_host + * + * states that we'd receive the following record content on each mxlog + * message from FW, where: + * + * - each element is a 32bit word + * - 1st element is a record header + * - len = number of elements following the first element + * + * | 1st | 2nd | 3rd | 4th | 5th | 6th + * ----------------------------------------------------------- + * | sync|lvl|len || tstamp || offset || arg1 || arg2 || arg3. + * ----------------------------------------------------------- + * | e l o g m s g | + * + * BUT NOTE THAT: here we DO NOT receive 1st header element BUT + * instead we got: + * @message: pointer to 2nd element + * @length: in bytes of the message (so starting from 2nd element) and + * including tstamp and offset elements: we must calculate + * num_args accordingly. + * @level: the debug level already remapped from FW to Kernel namespace + */ +static inline void mxlog_phase5_message_handler(const void *message, + size_t length, u32 level, + void *data) +{ + struct mxlog *mxlog = (struct mxlog *)data; + struct mxlog_event_log_msg *elogmsg = + (struct mxlog_event_log_msg *)message; + + if (length < MINIMUM_MXLOG_MSG_LEN_BYTES) + return; + if (mxlog && elogmsg) { + int num_args = 0; + char spare[MAX_SPARE_FMT + TSTAMP_LEN] = {}; + char *fmt = NULL; + size_t fmt_sz = 0; + u32 smap = 0, lmap = 0, strmap = 0; + u32 *args = NULL; + + /* Check OFFSET sanity... beware of FW guys :D ! */ + if (elogmsg->offset >= MXLS_SZ(mxlog)) { + SCSC_TAG_ERR(MX_FW, + "Received fmtstr OFFSET(%d) is OUT OF range(%zd)...skip..\n", + elogmsg->offset, MXLS_SZ(mxlog)); + return; + } + args = (u32 *)(elogmsg + 1); + num_args = + (length - MINIMUM_MXLOG_MSG_LEN_BYTES) / + MXLOG_ELEMENT_SIZE; + fmt = (char *)(MXLS_DATA(mxlog) + elogmsg->offset); + /* Avoid being fooled by a NON NULL-terminated strings too ! */ + fmt_sz = strnlen(fmt, MXLS_SZ(mxlog) - elogmsg->offset); + if (fmt_sz >= MAX_SPARE_FMT - 1) { + SCSC_TAG_ERR(MX_FW, + "UNSUPPORTED message length %zd ... truncated.\n", + fmt_sz); + fmt_sz = MAX_SPARE_FMT - 2; + } + /* Pre-Process fmt string to be able to do proper casting */ + if (num_args) + build_len_sign_maps(fmt, &smap, &lmap, &strmap); + + /* Add FW provided tstamp on front and proper \n at + * the end when needed + */ + snprintf(spare, MAX_SPARE_FMT + TSTAMP_LEN - 2, SCSC_PREFIX"%08X %s%c", + elogmsg->timestamp, fmt, + (fmt[fmt_sz] != '\n') ? '\n' : '\0'); + fmt = spare; + + switch (num_args) { + case 0: + SCSC_TAG_LVL(MX_FW, level, fmt); + break; + case 1: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 2: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 3: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[2], 2, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 4: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[2], 2, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[3], 3, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 5: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[2], 2, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[3], 3, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[4], 4, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 6: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[2], 2, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[3], 3, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[4], 4, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[5], 5, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 7: + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[2], 2, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[3], 3, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[4], 4, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[5], 5, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[6], 6, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + case 8: + default: + if (num_args > MAX_MX_LOG_ARGS) + SCSC_TAG_ERR(MX_FW, + "MXLOG: Too many args:%d ... print only first %d\n", + num_args, MAX_MX_LOG_ARGS); + SCSC_TAG_LVL(MX_FW, level, fmt, + MXLOG_CAST(args[0], 0, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[1], 1, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[2], 2, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[3], 3, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[4], 4, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[5], 5, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[6], 6, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog)), + MXLOG_CAST(args[7], 7, smap, lmap, strmap, + MXLS_DATA(mxlog), MXLS_SZ(mxlog))); + break; + } + } +} + +/* A generic message handler to multiplex between phases */ +static void mxlog_message_handler(u8 phase, const void *message, + size_t length, u32 level, void *data) +{ + struct mxlog *mxlog = (struct mxlog *)data; + + if (!mxlog) { + SCSC_TAG_ERR(MX_FW, "Missing MXLOG reference.\n"); + return; + } + + switch (phase) { + case MX_LOG_PHASE_4: + mxlog_phase4_message_handler(message, length, level, data); + break; + case MX_LOG_PHASE_5: + if (mxlog->logstrings) + mxlog_phase5_message_handler(message, length, + level, data); + else + SCSC_TAG_ERR(MX_FW, + "Missing LogStrings...dropping incoming PHASE5 message !\n"); + break; + default: + SCSC_TAG_ERR(MX_FW, + "MXLOG Unsupported phase %d ... dropping message !\n", + phase); + break; + } +} + +static int mxlog_header_parser(u32 header, u8 *phase, + u8 *level, u32 *num_bytes) +{ + u32 fw2kern_map[] = { + 0, /* 0 MX_ERROR --> 0 KERN_EMERG .. it's panic.*/ + 4, /* 1 MX_WARN --> 4 KERN_WARNING */ + 5, /* 2 MX_MAJOR --> 5 KERN_NOTICE */ + 6, /* 3 MX_MINOR --> 6 KERN_INFO */ + 7, /* 4 MX_DETAIL --> 7 KERN_DEBUG */ + }; + u16 sync = ((header & 0xFFFF0000) >> 16); + + switch (sync) { + case SYNC_VALUE_PHASE_4: + *phase = MX_LOG_PHASE_4; + /* len() field represent number of chars bytes */ + *num_bytes = header & 0x000000FF; + break; + case SYNC_VALUE_PHASE_5: + *phase = MX_LOG_PHASE_5; + /* len() field represent number of 4 bytes words */ + *num_bytes = (header & 0x000000FF) * 4; + break; + default: + return -1; + } + /* Remap FW debug levels to KERN debug levels domain */ + *level = (header & 0x0000FF00) >> 8; + if (*level < ARRAY_SIZE(fw2kern_map)) { + *level = fw2kern_map[*level]; + } else { + SCSC_TAG_ERR(MX_FW, + "UNKNOWN MX debug level %d ... marking as MX_DETAIL.\n", + *level); + *level = fw2kern_map[ARRAY_SIZE(fw2kern_map) - 1]; + } + + return 0; +} + +void mxlog_init(struct mxlog *mxlog, struct scsc_mx *mx, char *fw_build_id) +{ + int ret = 0; + + mxlog->mx = mx; + mxlog->index = 0; + mxlog->logstrings = NULL; + + /* File is in f/w profile directory */ + ret = mx140_file_request_debug_conf(mx, + (const struct firmware **)&mxlog->logstrings, + MX_LOG_LOGSTRINGS_PATH); + + if (!ret && mxlog->logstrings && mxlog->logstrings->data) { + SCSC_TAG_INFO(MX_FW, "Loaded %zd bytes of log-strings from %s\n", + mxlog->logstrings->size, MX_LOG_LOGSTRINGS_PATH); + if (fw_build_id && mxlog->logstrings->data[0] != 0x00 && + mxlog->logstrings->size >= FW_BUILD_ID_SZ) { + SCSC_TAG_INFO(MX_FW, "Log-strings is versioned...checking against fw_build_id.\n"); + if (strncmp(fw_build_id, mxlog->logstrings->data, FW_BUILD_ID_SZ)) { + char found[FW_BUILD_ID_SZ] = {}; + + /** + * NULL-terminate it just in case we fetched + * never-ending garbage. + */ + strncpy(found, mxlog->logstrings->data, + FW_BUILD_ID_SZ - 1); + SCSC_TAG_WARNING(MX_FW, + "--> Log-strings VERSION MISMATCH !!!\n"); + SCSC_TAG_WARNING(MX_FW, + "--> Expected: |%s|\n", fw_build_id); + SCSC_TAG_WARNING(MX_FW, + "--> FOUND: |%s|\n", found); + SCSC_TAG_WARNING(MX_FW, + "As a consequence the following mxlog debug messages could be corrupted.\n"); + SCSC_TAG_WARNING(MX_FW, + "The whole firmware package should be pushed to device when updating (not only the mx140.bin).\n"); + } + } else { + SCSC_TAG_INFO(MX_FW, "Log-strings is not versioned.\n"); + } + } else { + SCSC_TAG_ERR(MX_FW, "Failed to read %s needed by MXlog Phase 5\n", + MX_LOG_LOGSTRINGS_PATH); + } + /* Registering a generic channel handler */ + mxlog_transport_register_channel_handler(scsc_mx_get_mxlog_transport(mx), + &mxlog_header_parser, + &mxlog_message_handler, mxlog); +} + +void mxlog_release(struct mxlog *mxlog) +{ + mxlog_transport_register_channel_handler(scsc_mx_get_mxlog_transport(mxlog->mx), + NULL, NULL, NULL); + if (mxlog->logstrings) + mx140_release_file(mxlog->mx, mxlog->logstrings); + mxlog->logstrings = NULL; +} + diff --git a/drivers/misc/samsung/scsc/mxlog.h b/drivers/misc/samsung/scsc/mxlog.h new file mode 100644 index 000000000000..5b22fed4bdca --- /dev/null +++ b/drivers/misc/samsung/scsc/mxlog.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _MXLOG_H +#define _MXLOG_H + +#include + +#define MX_LOG_PHASE_4 4 +#define MX_LOG_PHASE_5 5 + +#define SYNC_VALUE_PHASE_4 (0xA55A) +#define SYNC_VALUE_PHASE_5 (0x9669) + +#define MXLOG_BUFFER_SIZE 512 + +#define MINIMUM_MXLOG_MSG_LEN_BYTES (sizeof(u32) * 2) +#define MXLOG_ELEMENT_SIZE (sizeof(u32)) +#define MAX_SPARE_FMT 256 +#define TSTAMP_LEN 9 +#define MAX_MX_LOG_ARGS 8 +#define MX_LOG_LOGSTRINGS_PATH "common/log-strings.bin" /* in f/w debug dir */ +#define MXLOG_SEXT(x) (((x) & 0x80000000) ? ((x) | 0xffffffff00000000) : (x)) + +#define MXLS_DATA(mx) ((mx)->logstrings->data) +#define MXLS_SZ(mx) ((mx)->logstrings->size) +#define MXLOG_DEFSTR "<<%s OFFSET OUT OF RANGE. Check log-strings.bin>>" +#define MXLOG_STR_SANE(x, base, size, cast) \ + (((x) < (size)) ? (typeof(cast))((base) + (x)) : (typeof(cast))(MXLOG_DEFSTR)) + +#ifdef __aarch64__ +/** + * ARM64 + * ----- + * We must process MXLOG messages 32bit-args coming from FW that have + * a different fmt string interpretation in Kernel: + * + * FW KERN MXLOG_CAST + * --------------------------------------------------------- + * %d s32 s32 (s32) + * %u %x u32 u32 (u32) + * %ld s32 s64 (SIGN_EXT((s64))) + * %lu u32 u64 (u64) + * + * Additionally we take care to skip any %s using defstr, a char pointer, + * as def value for the argument; we casted it to u64 (sizeof(char *)) to fool + * cond expr compilation warnings about types. + */ +#define MXLOG_CAST(x, p, smap, lmap, strmap, base, size) \ + (((strmap) & 1 << (p)) ? MXLOG_STR_SANE(x, base, size, u64) : \ + (((smap) & 1 << (p)) ? \ + (((lmap) & 1 << (p)) ? MXLOG_SEXT((s64)(x)) : (s32)(x)) : \ + (((lmap) & 1 << (p)) ? (u64)(x) : (u32)(x)))) +#else /* __arm__ */ +/** + * ARM32 + * ----- + * We must process MXLOG messages 32bit-args coming from FW BUT in + * ARM 32bit iMX6 they should have the same fmt string interpretation: + * + * FW KERN MXLOG_CAST + * --------------------------------------------------------- + * %d s32 s32 (s32) + * %u %x u32 u32 (u32) + * %ld s32 s32 (s32) + * %lu u32 u32 (u32) + * + * So here we ignore long modifiers and ONLY take care to skip any %s using + * defstr, a char pointer, as def value for the argument; we casted it to + * u32 (sizeof(char *) to fool cond expr compilation warnings about types. + */ +#define MXLOG_CAST(x, p, smap, lmap, strmap, base, size) \ + (((strmap) & 1 << (p)) ? MXLOG_STR_SANE(x, base, size, u32) : \ + (((smap) & 1 << (p)) ? ((s32)(x)) : (u32)(x))) +#endif /* __arch64__ */ + +struct mxlog_event_log_msg { + u32 timestamp; + u32 offset; +} __packed; + +struct mxlog; + +void mxlog_init(struct mxlog *mxlog, struct scsc_mx *mx, char *fw_build_id); +void mxlog_release(struct mxlog *mxlog); + +struct mxlog { + struct scsc_mx *mx; + u8 buffer[MXLOG_BUFFER_SIZE]; + u16 index; + struct firmware *logstrings; +}; + +#endif /* _MXLOG_H */ diff --git a/drivers/misc/samsung/scsc/mxlog_transport.c b/drivers/misc/samsung/scsc/mxlog_transport.c new file mode 100644 index 000000000000..ea163b57e76b --- /dev/null +++ b/drivers/misc/samsung/scsc/mxlog_transport.c @@ -0,0 +1,310 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +/** Uses */ +#include +#include +#include +#include +#include +#include "scsc_mif_abs.h" +#include "mifintrbit.h" +/** Implements */ +#include "mxlog_transport.h" + +#define MXLOG_TRANSPORT_BUF_LENGTH (16 * 1024) +#define MXLOG_TRANSPORT_PACKET_SIZE (4) + +/* Flag that an error has occurred so the I/O thread processing should stop */ +void mxlog_transport_set_error(struct mxlog_transport *mxlog_transport) +{ + SCSC_TAG_WARNING(MXLOG_TRANS, "I/O thread processing is suspended\n"); + + mxlog_transport->mxlog_thread.block_thread = 1; +} + +static void input_irq_handler(int irq, void *data) +{ + struct mxlog_transport *mxlog_transport = (struct mxlog_transport *)data; + struct mxlog_thread *th = &mxlog_transport->mxlog_thread; + struct scsc_mif_abs *mif_abs; + + SCSC_TAG_DEBUG(MXLOG_TRANS, "mxlog intr\n"); + /* Clear the interrupt first to ensure we can't possibly miss one */ + mif_abs = scsc_mx_get_mif_abs(mxlog_transport->mx); + mif_abs->irq_bit_clear(mif_abs, irq); + + /* The the other side wrote some data to the input stream, + * wake up the thread that deals with this. + */ + if (th->task == NULL) { + SCSC_TAG_ERR(MXLOG_TRANS, "mxlog_thread is NOT running\n"); + return; + } + /* + * If an error has occured, we discard silently all messages from + * the stream until the error has been processed and the system has + * been reinitialised. + */ + if (th->block_thread == 1) { + SCSC_TAG_DEBUG(MXLOG_TRANS, "discard message.\n"); + /* + * Do not try to acknowledge a pending interrupt here. + * This function is called by a function which in turn can be + * running in an atomic or 'disabled irq' level. + */ + return; + } + th->wakeup_flag = 1; + + /* wake up I/O thread */ + wake_up_interruptible(&th->wakeup_q); +} + +static void thread_wait_until_stopped(struct mxlog_transport *mxlog_transport) +{ + struct mxlog_thread *th = &mxlog_transport->mxlog_thread; + + /* + * kthread_stop() cannot handle the th exiting while + * kthread_should_stop() is false, so sleep until kthread_stop() + * wakes us up. + */ + SCSC_TAG_INFO(MXLOG_TRANS, "%s waiting for the stop signal.\n", th->name); + set_current_state(TASK_INTERRUPTIBLE); + if (!kthread_should_stop()) { + SCSC_TAG_DEBUG(MXLOG_TRANS, "%s schedule....\n", th->name); + schedule(); + } + + /** + * Caller that spawned the kthread did a get_task_struct() + * on task_struct, that will be released on stop... + * ...so we should NOT nullify th->task here. + */ + SCSC_TAG_DEBUG(MXLOG_TRANS, "%s exiting.\n", th->name); +} + +/** + * A thread that forwards messages sent across the transport to + * the registered handlers for each channel. + */ +static int mxlog_thread_function(void *arg) +{ + struct mxlog_transport *mxlog_transport = (struct mxlog_transport *)arg; + struct mxlog_thread *th = &mxlog_transport->mxlog_thread; + int ret; + u32 header; + char *buf = NULL; + size_t buf_sz = 4096; + + buf = kmalloc(buf_sz, GFP_KERNEL); + if (!buf) { + SCSC_TAG_ERR(MXLOG_TRANS, "Failed to alloc %s local buffer...exiting.\n", th->name); + return -ENOMEM; + } + /* completion is used only for startup thread-synchronization */ + complete(&th->completion); + /* Thread ready...start ISR processing*/ + th->block_thread = 0; + while (!kthread_should_stop()) { + /* wait until an error occurs, or we need to process */ + ret = wait_event_interruptible(th->wakeup_q, + (th->wakeup_flag && !th->block_thread) || + kthread_should_stop()); + + if (kthread_should_stop()) { + SCSC_TAG_DEBUG(MXLOG_TRANS, "signalled to exit\n"); + break; + } + if (ret < 0) { + SCSC_TAG_DEBUG(MXLOG_TRANS, + "wait_event returned %d, thread will exit\n", ret); + thread_wait_until_stopped(mxlog_transport); + break; + } + th->wakeup_flag = 0; + SCSC_TAG_DEBUG(MXLOG_TRANS, "wokeup: r=%d\n", ret); + if (!mxlog_transport->header_handler_fn) { + /* Invalid header handler: + * unrecoverable log and terminate + */ + SCSC_TAG_WARNING(MXLOG_TRANS, + "mxlog_transport->header_handler_fn_==NULL\n"); + break; + } + while (mif_stream_read(&mxlog_transport->mif_stream, + &header, sizeof(uint32_t))) { + u8 level = 0; + u8 phase = 0; + u32 num_bytes = 0; + + mutex_lock(&mxlog_transport->lock); + if (!mxlog_transport->header_handler_fn) { + /* Invalid header handler: + * unrecoverable log and terminate + */ + SCSC_TAG_WARNING(MXLOG_TRANS, + "mxlog_transport->header_handler_fn_==NULL. Channel has been released\n"); + mutex_unlock(&mxlog_transport->lock); + /* not recoverable, terminate straight away */ + goto mxlog_thread_exit; + } + /** + * A generic header processor will properly retrieve + * level and num_bytes as specifically implemented + * by the phase. + */ + if (mxlog_transport->header_handler_fn(header, &phase, + &level, &num_bytes)) { + SCSC_TAG_ERR(MXLOG_TRANS, + "Bad sync in header: header=0x%08x\n", header); + mutex_unlock(&mxlog_transport->lock); + /* not recoverable, terminate straight away */ + goto mxlog_thread_exit; + } + if (num_bytes > 0 && + num_bytes < (MXLOG_TRANSPORT_BUF_LENGTH - sizeof(uint32_t))) { + u32 ret_bytes = 0; + + /* 2nd read - payload (msg) */ + ret_bytes = mif_stream_read(&mxlog_transport->mif_stream, + buf, num_bytes); + mxlog_transport->channel_handler_fn(phase, buf, + ret_bytes, + level, + mxlog_transport->channel_handler_data); + } else { + SCSC_TAG_ERR(MXLOG_TRANS, + "Bad num_bytes(%d) in header: header=0x%08x\n", + num_bytes, header); + } + mutex_unlock(&mxlog_transport->lock); + } + } + +mxlog_thread_exit: + SCSC_TAG_INFO(MXLOG_TRANS, "exiting....\n"); + kfree(buf); + return 0; +} + +static int mxlog_thread_start(struct mxlog_transport *mxlog_transport) +{ + int err; + struct mxlog_thread *th = &mxlog_transport->mxlog_thread; + + if (th->task != NULL) { + SCSC_TAG_WARNING(MXLOG_TRANS, "%s thread already started\n", th->name); + return 0; + } + + /* Initialise thread structure */ + th->block_thread = 1; + init_waitqueue_head(&th->wakeup_q); + init_completion(&th->completion); + th->wakeup_flag = 0; + snprintf(th->name, MXLOG_THREAD_NAME_MAX_LENGTH, "mxlog_thread"); + + /* Start the kernel thread */ + th->task = kthread_run(mxlog_thread_function, mxlog_transport, "%s", th->name); + if (IS_ERR(th->task)) { + int err = (int)PTR_ERR(th->task); + + th->task = NULL; + return err; + } + + /** + * Avoid this task_struct vanishes immediately + * if the kthread exits by its own. + */ + get_task_struct(th->task); + + /* wait until thread function is running */ +#define LOG_THREAD_START_TMO_SEC (3) + err = wait_for_completion_timeout(&th->completion, msecs_to_jiffies(LOG_THREAD_START_TMO_SEC * 1000)); + if (err == 0) { + SCSC_TAG_ERR(MXLOG_TRANS, "timeout starting %s\n", th->name); + kthread_stop(th->task); + put_task_struct(th->task); + return -ETIMEDOUT; + } + SCSC_TAG_INFO(MXLOG_TRANS, "Started thread %s\n", th->name); + + return 0; +} + +static void mxlog_thread_stop(struct mxlog_transport *mxlog_transport) +{ + struct mxlog_thread *th = &mxlog_transport->mxlog_thread; + + if (!th->task) { + SCSC_TAG_WARNING(MXLOG_TRANS, "%s is already stopped\n", th->name); + return; + } + SCSC_TAG_INFO(MXLOG_TRANS, "Stopping thread %s [%d]\n", th->name, th->task->pid); + /* kthread_stop() marks thread as KTHREAD_SHOULD_STOP + * and wait for it to terminate + */ + if (kthread_stop(th->task)) + SCSC_TAG_ERR(MXLOG_TRANS, "Failed to stop %s [%d]\n", th->name, th->task->pid); + /* Finally release the task_struct we held on start */ + put_task_struct(th->task); + th->task = NULL; +} + +void mxlog_transport_release(struct mxlog_transport *mxlog_transport) +{ + mxlog_thread_stop(mxlog_transport); + mif_stream_release(&mxlog_transport->mif_stream); +} + +void mxlog_transport_config_serialise(struct mxlog_transport *mxlog_transport, + struct mxlogconf *mxlogconf) +{ + mif_stream_config_serialise(&mxlog_transport->mif_stream, &mxlogconf->stream_conf); +} + +/** Public functions */ +int mxlog_transport_init(struct mxlog_transport *mxlog_transport, struct scsc_mx *mx) +{ + int r; + uint32_t mem_length = MXLOG_TRANSPORT_BUF_LENGTH; + uint32_t packet_size = MXLOG_TRANSPORT_PACKET_SIZE; + uint32_t num_packets; + + /* + * Initialising a buffer of 1 byte is never legitimate, do not allow it. + * The memory buffer length must be a multiple of the packet size. + */ + + memset(mxlog_transport, 0, sizeof(struct mxlog_transport)); + mutex_init(&mxlog_transport->lock); + num_packets = mem_length / packet_size; + mxlog_transport->mx = mx; + r = mif_stream_init(&mxlog_transport->mif_stream, SCSC_MIF_ABS_TARGET_R4, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, input_irq_handler, mxlog_transport); + if (r) + return r; + r = mxlog_thread_start(mxlog_transport); + if (r) { + mif_stream_release(&mxlog_transport->mif_stream); + return r; + } + + return 0; +} + +void mxlog_transport_register_channel_handler(struct mxlog_transport *mxlog_transport, + mxlog_header_handler parser, + mxlog_channel_handler handler, + void *data) +{ + mutex_lock(&mxlog_transport->lock); + mxlog_transport->header_handler_fn = parser; + mxlog_transport->channel_handler_fn = handler; + mxlog_transport->channel_handler_data = (void *)data; + mutex_unlock(&mxlog_transport->lock); +} diff --git a/drivers/misc/samsung/scsc/mxlog_transport.h b/drivers/misc/samsung/scsc/mxlog_transport.h new file mode 100644 index 000000000000..f82a5ec54541 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxlog_transport.h @@ -0,0 +1,77 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * Maxwell mxlog transport (Interface) + * + * Provides communication between the firmware and the host. + * + */ + +#ifndef MXLOG_TRANSPORT_H__ +#define MXLOG_TRANSPORT_H__ + +/** Uses */ +#include +#include "mifstream.h" + +struct mxlog_transport; + +typedef int (*mxlog_header_handler)(u32 header, u8 *phase, + u8 *level, u32 *num_bytes); +/** + * Transport channel callback handler. This will be invoked each time a message on a channel is + * received. Handlers may perform work within their callback implementation, but should not block. + * The detected phase is passed as first parameter. + * + * Note that the message pointer passed is only valid for the duration of the function call. + */ +typedef void (*mxlog_channel_handler)(u8 phase, const void *message, + size_t length, u32 level, void *data); + +/** + * Initialises the maxwell management transport and configures the necessary + * interrupt handlers. + */ +int mxlog_transport_init(struct mxlog_transport *mxlog_transport, struct scsc_mx *mx); +void mxlog_transport_release(struct mxlog_transport *mxlog_transport); +/* + * Initialises the configuration area incl. Maxwell Infrastructure Configuration, + * MIF Management Transport Configuration and MIF Management Stream Configuration. + */ +void mxlog_transport_config_serialise(struct mxlog_transport *mxlog_transport, struct mxlogconf *mxlogconf); +void mxlog_transport_register_channel_handler(struct mxlog_transport *mxlog_transport, + mxlog_header_handler parser, + mxlog_channel_handler handler, + void *data); +void mxlog_transport_set_error(struct mxlog_transport *mxlog_transport); + +#define MXLOG_THREAD_NAME_MAX_LENGTH 32 +struct mxlog_thread { + struct task_struct *task; + char name[MXLOG_THREAD_NAME_MAX_LENGTH]; + int prio; + struct completion completion; + wait_queue_head_t wakeup_q; + unsigned int wakeup_flag; + /* + * Use it to block the I/O thread when + * an error occurs. + */ + int block_thread; +}; + +struct mxlog_transport { + struct scsc_mx *mx; + struct mxlog_thread mxlog_thread; + struct mif_stream mif_stream; + mxlog_header_handler header_handler_fn; + mxlog_channel_handler channel_handler_fn; + void *channel_handler_data; + struct mutex lock; +}; + +#endif /* MXLOG_TRANSPORT_H__ */ diff --git a/drivers/misc/samsung/scsc/mxlogger.c b/drivers/misc/samsung/scsc/mxlogger.c new file mode 100644 index 000000000000..80c1d030ad27 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxlogger.c @@ -0,0 +1,945 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** Implements */ +#include "mxlogger.h" + +/** Uses */ +#include +#include +#include +#include +#include +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +#include "srvman.h" +#include "scsc_mif_abs.h" +#include "miframman.h" +#include "mifintrbit.h" +#include "mxmgmt_transport.h" + +static bool mxlogger_disabled; +module_param(mxlogger_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mxlogger_disabled, "Disable MXLOGGER Configuration. Effective only at next WLBT boot."); + +bool mxlogger_set_enabled_status(bool enable) +{ + mxlogger_disabled = !enable; + + SCSC_TAG_INFO(MXMAN, "MXLOGGER has been NOW %sABLED. Effective at next WLBT boot.\n", + mxlogger_disabled ? "DIS" : "EN"); + + return mxlogger_disabled; +} +EXPORT_SYMBOL(mxlogger_set_enabled_status); + +static bool mxlogger_forced_to_host; + +static void update_fake_observer(void) +{ + static bool mxlogger_fake_observers_registered; + + if (mxlogger_forced_to_host) { + if (!mxlogger_fake_observers_registered) { + mxlogger_register_global_observer("FAKE_OBSERVER"); + mxlogger_fake_observers_registered = true; + } + SCSC_TAG_INFO(MXMAN, "MXLOGGER is now FORCED TO HOST.\n"); + } else { + if (mxlogger_fake_observers_registered) { + mxlogger_unregister_global_observer("FAKE_OBSERVER"); + mxlogger_fake_observers_registered = false; + } + SCSC_TAG_INFO(MXMAN, "MXLOGGER is now operating NORMALLY.\n"); + } +} + +static int mxlogger_force_to_host_set_param_cb(const char *val, + const struct kernel_param *kp) +{ + bool nval; + + if (!val || strtobool(val, &nval)) + return -EINVAL; + + if (mxlogger_forced_to_host ^ nval) { + mxlogger_forced_to_host = nval; + update_fake_observer(); + } + return 0; +} + +/** + * As described in struct kernel_param+ops the _get method: + * -> returns length written or -errno. Buffer is 4k (ie. be short!) + */ +static int mxlogger_force_to_host_get_param_cb(char *buffer, + const struct kernel_param *kp) +{ + return sprintf(buffer, "%c", mxlogger_forced_to_host ? 'Y' : 'N'); +} + +static struct kernel_param_ops mxlogger_force_to_host_ops = { + .set = mxlogger_force_to_host_set_param_cb, + .get = mxlogger_force_to_host_get_param_cb, +}; +module_param_cb(mxlogger_force_to_host, &mxlogger_force_to_host_ops, NULL, 0644); +MODULE_PARM_DESC(mxlogger_force_to_host, "Force mxlogger to redirect to Host all the time, using a fake observer."); + +/** + * Observers of log material could come and go before mxman and mxlogger + * are initialized and started...so we keep this stuff here out of mxman, + * but all the lifecycle of mxlogger should be reviewed. + */ +static u8 active_global_observers; +static DEFINE_MUTEX(global_lock); + +struct mxlogger_node { + struct list_head list; + struct mxlogger *mxl; +}; +static struct mxlogger_list { struct list_head list; } mxlogger_list = { + .list = LIST_HEAD_INIT(mxlogger_list.list) +}; + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static int mxlogger_collect_init(struct scsc_log_collector_client *collect_client); +static int mxlogger_collect(struct scsc_log_collector_client *collect_client, size_t size); +static int mxlogger_collect_end(struct scsc_log_collector_client *collect_client); + +/* Collect client registration SYNC buffer */ +/* SYNC - SHOULD BE THE FIRST CHUNK TO BE CALLED - SO USE THE INIT/END ON THIS CLIENT */ +struct scsc_log_collector_client mxlogger_collect_client_sync = { + .name = "Sync", + .type = SCSC_LOG_CHUNK_SYNC, + .collect_init = mxlogger_collect_init, + .collect = mxlogger_collect, + .collect_end = mxlogger_collect_end, + .prv = NULL, +}; + +/* Collect client registration IMP buffer */ +struct scsc_log_collector_client mxlogger_collect_client_imp = { + .name = "Important", + .type = SCSC_LOG_CHUNK_IMP, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; + +struct scsc_log_collector_client mxlogger_collect_client_rsv_common = { + .name = "Rsv_common", + .type = SCSC_LOG_RESERVED_COMMON, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; + +struct scsc_log_collector_client mxlogger_collect_client_rsv_bt = { + .name = "Rsv_bt", + .type = SCSC_LOG_RESERVED_BT, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; + +struct scsc_log_collector_client mxlogger_collect_client_rsv_wlan = { + .name = "Rsv_wlan", + .type = SCSC_LOG_RESERVED_WLAN, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; + +struct scsc_log_collector_client mxlogger_collect_client_rsv_radio = { + .name = "Rsv_radio", + .type = SCSC_LOG_RESERVED_RADIO, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; +/* Collect client registration MXL buffer */ +struct scsc_log_collector_client mxlogger_collect_client_mxl = { + .name = "MXL", + .type = SCSC_LOG_CHUNK_MXL, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; + +/* Collect client registration MXL buffer */ +struct scsc_log_collector_client mxlogger_collect_client_udi = { + .name = "UDI", + .type = SCSC_LOG_CHUNK_UDI, + .collect_init = NULL, + .collect = mxlogger_collect, + .collect_end = NULL, + .prv = NULL, +}; +#endif + +const char *mxlogger_buf_name[] = { "syn", "imp", "rsv_common", "rsv_bt", "rsv_wlan", "rsv_radio", "mxl", "udi" }; + +static void mxlogger_message_handler(const void *message, void *data) +{ + struct mxlogger __attribute__((unused)) *mxlogger = (struct mxlogger *)data; + const struct log_msg_packet *msg = message; + u16 reason_code; + + switch (msg->msg) { + case MM_MXLOGGER_INITIALIZED_EVT: + SCSC_TAG_INFO(MXMAN, "MXLOGGER Initialized.\n"); + mxlogger->initialized = true; + complete(&mxlogger->rings_serialized_ops); + break; + case MM_MXLOGGER_STARTED_EVT: + SCSC_TAG_INFO(MXMAN, "MXLOGGER:: RINGS Enabled.\n"); + mxlogger->enabled = true; + complete(&mxlogger->rings_serialized_ops); + break; + case MM_MXLOGGER_STOPPED_EVT: + SCSC_TAG_INFO(MXMAN, "MXLOGGER:: RINGS Disabled.\n"); + mxlogger->enabled = false; + complete(&mxlogger->rings_serialized_ops); + break; + case MM_MXLOGGER_COLLECTION_FW_REQ_EVT: + /* If arg is zero, FW is using the 16bit reason code API */ + /* therefore, the reason code is in the payload */ + if (msg->arg == 0x00) + memcpy(&reason_code, &msg->payload[0], sizeof(u16)); + else + /* old API */ + reason_code = msg->arg; +#ifdef CONFIG_SCSC_LOG_COLLECTION + SCSC_TAG_INFO(MXMAN, "MXLOGGER:: FW requested collection - Reason code:0x%04x\n", reason_code); + scsc_log_collector_schedule_collection(SCSC_LOG_FW, reason_code); +#endif + break; + default: + SCSC_TAG_WARNING(MXMAN, + "Received UNKNOWN msg on MMTRANS_CHAN_ID_MAXWELL_LOGGING -- msg->msg=%d\n", + msg->msg); + break; + } +} + +static int __mxlogger_generate_sync_record(struct mxlogger *mxlogger, enum mxlogger_sync_event event) +{ + struct mxlogger_sync_record *sync_r_mem; + struct timeval t; + struct log_msg_packet msg = {}; + unsigned long int jd; + void *mem; + ktime_t t1, t2; + + /* Assume mxlogger->lock mutex is held */ + if (!mxlogger || !mxlogger->configured) + return -EIO; + + msg.msg = MM_MXLOGGER_SYNC_RECORD; + msg.arg = MM_MXLOGGER_SYNC_INDEX; + memcpy(&msg.payload, &mxlogger->sync_buffer_index, sizeof(mxlogger->sync_buffer_index)); + + /* Get the pointer from the index of the sync array */ + mem = mxlogger->mem_sync_buf + mxlogger->sync_buffer_index * sizeof(struct mxlogger_sync_record); + sync_r_mem = (struct mxlogger_sync_record *)mem; + /* Write values in record as FW migth be doing sanity checks */ + sync_r_mem->tv_sec = 1; + sync_r_mem->tv_usec = 1; + sync_r_mem->kernel_time = 1; + sync_r_mem->sync_event = event; + sync_r_mem->fw_time = 0; + sync_r_mem->fw_wrap = 0; + + + SCSC_TAG_INFO(MXMAN, "Get FW time\n"); + preempt_disable(); + /* set the tight loop timeout - we do not require precission but something to not + * loop forever + */ + jd = jiffies + msecs_to_jiffies(20); + /* Send the msg as fast as possible */ + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + &msg, sizeof(msg)); + t1 = ktime_get(); + /* Tight loop to read memory */ + while (time_before(jiffies, jd) && sync_r_mem->fw_time == 0 && sync_r_mem->fw_wrap == 0) + ; + t2 = ktime_get(); + do_gettimeofday(&t); + preempt_enable(); + + /* Do the processing */ + if (sync_r_mem->fw_wrap == 0 && sync_r_mem->fw_time == 0) { + /* FW didn't update the record (FW panic?) */ + SCSC_TAG_INFO(MXMAN, "FW failure updating the FW time\n"); + SCSC_TAG_INFO(MXMAN, "Sync delta %lld\n", ktime_to_ns(ktime_sub(t2, t1))); + sync_r_mem->tv_sec = (u64)t.tv_sec; + sync_r_mem->tv_usec = (u64)t.tv_usec; + sync_r_mem->kernel_time = ktime_to_ns(t2); + sync_r_mem->sync_event = event; + return 0; + } + + sync_r_mem->tv_sec = (u64)t.tv_sec; + sync_r_mem->tv_usec = (u64)t.tv_usec; + sync_r_mem->kernel_time = ktime_to_ns(t2); + sync_r_mem->sync_event = event; + + SCSC_TAG_INFO(MXMAN, "Sample, %lld, %u, %lld.%06lld\n", + ktime_to_ns(sync_r_mem->kernel_time), sync_r_mem->fw_time, sync_r_mem->tv_sec, sync_r_mem->tv_usec); + SCSC_TAG_INFO(MXMAN, "Sync delta %lld\n", ktime_to_ns(ktime_sub(t2, t1))); + + mxlogger->sync_buffer_index++; + mxlogger->sync_buffer_index &= SYNC_MASK; + + return 0; +} + +int mxlogger_generate_sync_record(struct mxlogger *mxlogger, enum mxlogger_sync_event event) +{ + int r; + + mutex_lock(&mxlogger->lock); + r = __mxlogger_generate_sync_record(mxlogger, event); + mutex_unlock(&mxlogger->lock); + + return r; +} + +static void mxlogger_wait_for_msg_reinit_completion(struct mxlogger *mxlogger) +{ + reinit_completion(&mxlogger->rings_serialized_ops); +} + +static bool mxlogger_wait_for_msg_reply(struct mxlogger *mxlogger) +{ + int ret; + + ret = wait_for_completion_timeout(&mxlogger->rings_serialized_ops, usecs_to_jiffies(MXLOGGER_RINGS_TMO_US)); + if (ret) { + int i; + + SCSC_TAG_DBG3(MXMAN, "MXLOGGER RINGS -- replied in %lu usecs.\n", + MXLOGGER_RINGS_TMO_US - jiffies_to_usecs(ret)); + + for (i = 0; i < MXLOGGER_NUM_BUFFERS; i++) + SCSC_TAG_DBG3(MXMAN, "MXLOGGER:: RING[%d] -- INFO[0x%X] STATUS[0x%X]\n", i, + mxlogger->cfg->bfds[i].info, mxlogger->cfg->bfds[i].status); + } else { + SCSC_TAG_ERR(MXMAN, "MXLOGGER timeout waiting for reply.\n"); + } + + return ret ? true : false; +} + +static inline void __mxlogger_enable(struct mxlogger *mxlogger, bool enable, uint8_t reason) +{ + struct log_msg_packet msg = {}; + + msg.msg = MM_MXLOGGER_LOGGER_CMD; + msg.arg = (enable) ? MM_MXLOGGER_LOGGER_ENABLE : MM_MXLOGGER_LOGGER_DISABLE; + msg.payload[0] = reason; + + /* Reinit the completion before sending the message over cpacketbuffer + * otherwise there might be a race condition + */ + mxlogger_wait_for_msg_reinit_completion(mxlogger); + + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + &msg, sizeof(msg)); + + SCSC_TAG_DBG4(MXMAN, "MXLOGGER RINGS -- enable:%d reason:%d\n", + enable, reason); + + mxlogger_wait_for_msg_reply(mxlogger); +} + +static void mxlogger_enable(struct mxlogger *mxlogger, bool enable) +{ + return __mxlogger_enable(mxlogger, enable, MM_MXLOGGER_DISABLE_REASON_STOP); +} + +static int mxlogger_send_config(struct mxlogger *mxlogger) +{ + struct log_msg_packet msg = {}; + + SCSC_TAG_INFO(MXMAN, "MXLOGGER Config mifram_ref: 0x%x size:%d\n", + mxlogger->mifram_ref, mxlogger->msz); + + msg.msg = MM_MXLOGGER_CONFIG_CMD; + msg.arg = MM_MXLOGGER_CONFIG_BASE_ADDR; + memcpy(&msg.payload, &mxlogger->mifram_ref, sizeof(mxlogger->mifram_ref)); + + /* Reinit the completion before sending the message over cpacketbuffer + * otherwise there might be a race condition + */ + mxlogger_wait_for_msg_reinit_completion(mxlogger); + + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + &msg, sizeof(msg)); + + SCSC_TAG_INFO(MXMAN, "MXLOGGER Config SENT\n"); + if (!mxlogger_wait_for_msg_reply(mxlogger)) + return -1; + + return 0; +} + +static void mxlogger_to_shared_dram(struct mxlogger *mxlogger) +{ + int r; + struct log_msg_packet msg = { .msg = MM_MXLOGGER_DIRECTION_CMD, + .arg = MM_MXLOGGER_DIRECTION_DRAM }; + + SCSC_TAG_INFO(MXMAN, "MXLOGGER -- NO active observers detected. Send logs to DRAM\n"); + + r = __mxlogger_generate_sync_record(mxlogger, MXLOGGER_SYN_TORAM); + if (r) + return; /* mxlogger is not configured */ + + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + &msg, sizeof(msg)); +} + +static void mxlogger_to_host(struct mxlogger *mxlogger) +{ + int r; + struct log_msg_packet msg = { .msg = MM_MXLOGGER_DIRECTION_CMD, + .arg = MM_MXLOGGER_DIRECTION_HOST }; + + SCSC_TAG_INFO(MXMAN, "MXLOGGER -- active observers detected. Send logs to host\n"); + + r = __mxlogger_generate_sync_record(mxlogger, MXLOGGER_SYN_TOHOST); + if (r) + return; /* mxlogger is not configured */ + + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + &msg, sizeof(msg)); +} + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static void mxlogger_disable_for_collection(struct mxlogger *mxlogger) +{ + return __mxlogger_enable(mxlogger, false, MM_MXLOGGER_DISABLE_REASON_COLLECTION); +} + +static int mxlogger_collect_init(struct scsc_log_collector_client *collect_client) +{ + struct mxlogger *mxlogger = (struct mxlogger *)collect_client->prv; + + if (!mxlogger->initialized) + return 0; + + mutex_lock(&mxlogger->lock); + + SCSC_TAG_INFO(MXMAN, "Started log collection\n"); + + __mxlogger_generate_sync_record(mxlogger, MXLOGGER_SYN_LOGCOLLECTION); + + mxlogger->re_enable = mxlogger->enabled; + /** + * If enabled, tell FW we are stopping for collection: + * this way FW can dump last minute stuff and flush properly + * its cache + */ + if (mxlogger->enabled) + mxlogger_disable_for_collection(mxlogger); + + mutex_unlock(&mxlogger->lock); + + return 0; +} + +static int mxlogger_collect(struct scsc_log_collector_client *collect_client, size_t size) +{ + struct scsc_mif_abs *mif; + struct mxlogger *mxlogger = (struct mxlogger *)collect_client->prv; + void *buf; + int ret = 0; + int i; + size_t sz; + + if (mxlogger && mxlogger->mx) + mif = scsc_mx_get_mif_abs(mxlogger->mx); + else + /* Return 0 as 'success' to continue the collection of other chunks */ + return 0; + + mutex_lock(&mxlogger->lock); + + if (mxlogger->initialized == false) { + SCSC_TAG_ERR(MXMAN, "MXLOGGER not initialized\n"); + mutex_unlock(&mxlogger->lock); + return 0; + } + + if (collect_client->type == SCSC_LOG_CHUNK_SYNC) + i = MXLOGGER_SYNC; + else if (collect_client->type == SCSC_LOG_CHUNK_IMP) + i = MXLOGGER_IMP; + else if (collect_client->type == SCSC_LOG_RESERVED_COMMON) + i = MXLOGGER_RESERVED_COMMON; + else if (collect_client->type == SCSC_LOG_RESERVED_BT) + i = MXLOGGER_RESERVED_BT; + else if (collect_client->type == SCSC_LOG_RESERVED_WLAN) + i = MXLOGGER_RESERVED_WLAN; + else if (collect_client->type == SCSC_LOG_RESERVED_RADIO) + i = MXLOGGER_RESERVED_RADIO; + else if (collect_client->type == SCSC_LOG_CHUNK_MXL) + i = MXLOGGER_MXLOG; + else if (collect_client->type == SCSC_LOG_CHUNK_UDI) + i = MXLOGGER_UDI; + else { + SCSC_TAG_ERR(MXMAN, "MXLOGGER Incorrect type. Return 'success' and continue to collect other buffers\n"); + mutex_unlock(&mxlogger->lock); + return 0; + } + + sz = mxlogger->cfg->bfds[i].size; + buf = mif->get_mifram_ptr(mif, mxlogger->cfg->bfds[i].location); + SCSC_TAG_INFO(MXMAN, "Writing buffer %s size: %zu\n", mxlogger_buf_name[i], sz); + ret = scsc_log_collector_write(buf, sz, 1); + if (ret) { + mutex_unlock(&mxlogger->lock); + return ret; + } + + mutex_unlock(&mxlogger->lock); + return 0; +} + +static int mxlogger_collect_end(struct scsc_log_collector_client *collect_client) +{ + struct mxlogger *mxlogger = (struct mxlogger *)collect_client->prv; + + if (!mxlogger->initialized) + return 0; + + mutex_lock(&mxlogger->lock); + + SCSC_TAG_INFO(MXMAN, "End log collection\n"); + + /* Renable again if was previoulsy enabled */ + if (mxlogger->re_enable) + mxlogger_enable(mxlogger, true); + + mutex_unlock(&mxlogger->lock); + return 0; +} +#endif + +void mxlogger_print_mapping(struct mxlogger_config_area *cfg) +{ + u8 i; + + SCSC_TAG_INFO(MXMAN, "MXLOGGER -- Configured Buffers [%d]\n", cfg->config.num_buffers); + for (i = 0; i < MXLOGGER_NUM_BUFFERS; i++) + SCSC_TAG_INFO(MXMAN, "buffer %s loc: 0x%08x size: %u\n", + mxlogger_buf_name[i], cfg->bfds[i].location, cfg->bfds[i].size); + +} + +/* Lock should be acquired by caller */ +int mxlogger_init(struct scsc_mx *mx, struct mxlogger *mxlogger, uint32_t mem_sz) +{ + struct miframman *miframman; + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mx); + struct mxlogger_config_area *cfg; + size_t remaining_mem; + size_t udi_mxl_mem_sz; + struct mxlogger_node *mn; + + MEM_LAYOUT_CHECK(); + + mxlogger->configured = false; + + if (mem_sz <= (sizeof(struct mxlogger_config_area) + MXLOGGER_TOTAL_FIX_BUF)) { + SCSC_TAG_ERR(MXMAN, "Insufficient memory allocation\n"); + return -EIO; + } + + mxlogger->mx = mx; + miframman = scsc_mx_get_ramman2(mx); + if (!miframman) + return -ENOMEM; + mxlogger->mem = miframman_alloc(miframman, mem_sz, 32, MIFRAMMAN_OWNER_COMMON); + if (!mxlogger->mem) { + SCSC_TAG_ERR(MXMAN, "Error allocating memory for MXLOGGER\n"); + return -ENOMEM; + } + mxlogger->msz = mem_sz; + + /* Clear memory to avoid reading old records */ + memset(mxlogger->mem, 0, mxlogger->msz); + mif->get_mifram_ref(mif, mxlogger->mem, &mxlogger->mifram_ref); + + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + &mxlogger_message_handler, mxlogger); + + /* Initialize configuration structure */ + SCSC_TAG_INFO(MXMAN, "MXLOGGER Configuration: 0x%x\n", (u32)mxlogger->mifram_ref); + cfg = (struct mxlogger_config_area *)mxlogger->mem; + + cfg->config.magic_number = MXLOGGER_MAGIG_NUMBER; + cfg->config.config_major = MXLOGGER_MAJOR; + cfg->config.config_minor = MXLOGGER_MINOR; + cfg->config.num_buffers = MXLOGGER_NUM_BUFFERS; + + /** + * Populate information of Fixed size buffers + * These are mifram-reletive references + */ + cfg->bfds[MXLOGGER_SYNC].location = mxlogger->mifram_ref + + offsetof(struct mxlogger_config_area, buffers_start); + cfg->bfds[MXLOGGER_SYNC].size = MXLOGGER_SYNC_SIZE; + /* additionally cache the va of sync_buffer */ + mxlogger->mem_sync_buf = mxlogger->mem + + offsetof(struct mxlogger_config_area, buffers_start); + + cfg->bfds[MXLOGGER_IMP].location = + cfg->bfds[MXLOGGER_IMP - 1].location + + cfg->bfds[MXLOGGER_IMP - 1].size; + cfg->bfds[MXLOGGER_IMP].size = MXLOGGER_IMP_SIZE; + + cfg->bfds[MXLOGGER_RESERVED_COMMON].location = + cfg->bfds[MXLOGGER_RESERVED_COMMON - 1].location + + cfg->bfds[MXLOGGER_RESERVED_COMMON - 1].size; + cfg->bfds[MXLOGGER_RESERVED_COMMON].size = MXLOGGER_RSV_COMMON_SZ; + + cfg->bfds[MXLOGGER_RESERVED_BT].location = + cfg->bfds[MXLOGGER_RESERVED_BT - 1].location + + cfg->bfds[MXLOGGER_RESERVED_BT - 1].size; + cfg->bfds[MXLOGGER_RESERVED_BT].size = MXLOGGER_RSV_BT_SZ; + + cfg->bfds[MXLOGGER_RESERVED_WLAN].location = + cfg->bfds[MXLOGGER_RESERVED_WLAN - 1].location + + cfg->bfds[MXLOGGER_RESERVED_WLAN - 1].size; + cfg->bfds[MXLOGGER_RESERVED_WLAN].size = MXLOGGER_RSV_WLAN_SZ; + + cfg->bfds[MXLOGGER_RESERVED_RADIO].location = + cfg->bfds[MXLOGGER_RESERVED_RADIO - 1].location + + cfg->bfds[MXLOGGER_RESERVED_RADIO - 1].size; + cfg->bfds[MXLOGGER_RESERVED_RADIO].size = MXLOGGER_RSV_RADIO_SZ; + + /* Compute buffer locations and size based on the remaining space */ + remaining_mem = mem_sz - (sizeof(struct mxlogger_config_area) + MXLOGGER_TOTAL_FIX_BUF); + + /* Align the buffer to be cache friendly */ + udi_mxl_mem_sz = (remaining_mem >> 1) & ~(MXLOGGER_NON_FIX_BUF_ALIGN - 1); + + SCSC_TAG_INFO(MXMAN, "remaining_mem %zu udi/mxlogger size %zu\n", remaining_mem, udi_mxl_mem_sz); + + cfg->bfds[MXLOGGER_MXLOG].location = + cfg->bfds[MXLOGGER_MXLOG - 1].location + + cfg->bfds[MXLOGGER_MXLOG - 1].size; + cfg->bfds[MXLOGGER_MXLOG].size = udi_mxl_mem_sz; + + cfg->bfds[MXLOGGER_UDI].location = + cfg->bfds[MXLOGGER_UDI - 1].location + + cfg->bfds[MXLOGGER_UDI - 1].size; + cfg->bfds[MXLOGGER_UDI].size = udi_mxl_mem_sz; + + /* Save offset to buffers array */ + mif->get_mifram_ref(mif, cfg->bfds, &cfg->config.bfds_ref); + + mxlogger_print_mapping(cfg); + + mxlogger->cfg = cfg; + + init_completion(&mxlogger->rings_serialized_ops); + mxlogger->enabled = false; + + mutex_init(&mxlogger->lock); + + mn = kzalloc(sizeof(*mn), GFP_KERNEL); + if (!mn) { + miframman_free(miframman, mxlogger->mem); + return -ENOMEM; + } + + /** + * Update observers status considering + * current value of mxlogger_forced_to_host + */ + update_fake_observer(); + + mutex_lock(&global_lock); + mxlogger->observers = active_global_observers; + if (mxlogger->observers) + SCSC_TAG_INFO(MXMAN, "Detected global %d observer[s]\n", active_global_observers); + mutex_unlock(&global_lock); + + mxlogger->sync_buffer_index = 0; + + mn->mxl = mxlogger; + list_add_tail(&mn->list, &mxlogger_list.list); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + /** + * Register to the collection infrastructure + * + * All of mxlogger buffers are registered here, NO matter if + * MXLOGGER initialization was successfull FW side. + * + * In such a case MXLOGGER-FW will simply ignore all of our following + * requests and we'll end up dumping empty buffers, BUT with a partially + * meaningful sync buffer. (since this last is written also Host side) + */ + mxlogger_collect_client_sync.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_sync); + + mxlogger_collect_client_imp.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_imp); + + mxlogger_collect_client_rsv_common.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_rsv_common); + + mxlogger_collect_client_rsv_bt.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_rsv_bt); + + mxlogger_collect_client_rsv_wlan.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_rsv_wlan); + + mxlogger_collect_client_rsv_radio.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_rsv_radio); + + mxlogger_collect_client_udi.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_udi); + + mxlogger_collect_client_mxl.prv = mxlogger; + scsc_log_collector_register_client(&mxlogger_collect_client_mxl); +#endif + mxlogger->configured = true; + SCSC_TAG_INFO(MXMAN, "MXLOGGER Configured\n"); + return 0; +} + +int mxlogger_start(struct mxlogger *mxlogger) +{ + if (mxlogger_disabled) { + SCSC_TAG_WARNING(MXMAN, "MXLOGGER is disabled. Not Starting.\n"); + return -1; + } + + if (!mxlogger || !mxlogger->configured) { + SCSC_TAG_WARNING(MXMAN, "MXLOGGER is not valid or not configured.\n"); + return -1; + } + + SCSC_TAG_INFO(MXMAN, "Starting mxlogger with %d observer[s]\n", mxlogger->observers); + + mutex_lock(&mxlogger->lock); + if (mxlogger_send_config(mxlogger)) { + mutex_unlock(&mxlogger->lock); + return -ENOMEM; + } + + /** + * MXLOGGER on FW-side is at this point starting up too during + * WLBT chip boot and it cannot make any assumption till about + * the current number of observers and direction set: so, during + * MXLOGGER FW-side initialization, ZERO observers were registered. + * + * As a consequence on chip-boot FW-MXLOGGER defaults to: + * - direction DRAM + * - all rings disabled (ingressing messages discarded) + */ + if (!mxlogger->observers) { + /* Enabling BEFORE communicating direction DRAM + * to avoid losing messages on rings. + */ + mxlogger_enable(mxlogger, true); + mxlogger_to_shared_dram(mxlogger); +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_is_observer(false); +#endif + } else { + mxlogger_to_host(mxlogger); +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_is_observer(true); +#endif + /* Enabling AFTER communicating direction HOST + * to avoid wrongly spilling messages into the + * rings early at start (like at boot). + */ + mxlogger_enable(mxlogger, true); + } + + SCSC_TAG_INFO(MXMAN, "MXLOGGER Started.\n"); + mutex_unlock(&mxlogger->lock); + + return 0; +} + +void mxlogger_deinit(struct scsc_mx *mx, struct mxlogger *mxlogger) +{ + struct miframman *miframman = NULL; + struct mxlogger_node *mn, *next; + bool match = false; + + SCSC_TAG_INFO(MXMAN, "\n"); + + if (!mxlogger || !mxlogger->configured) { + SCSC_TAG_WARNING(MXMAN, "MXLOGGER is not valid or not configured.\n"); + return; + } + /* Run deregistration before adquiring the mxlogger lock to avoid + * deadlock with log_collector. + */ +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_unregister_client(&mxlogger_collect_client_sync); + scsc_log_collector_unregister_client(&mxlogger_collect_client_imp); + scsc_log_collector_unregister_client(&mxlogger_collect_client_rsv_common); + scsc_log_collector_unregister_client(&mxlogger_collect_client_rsv_bt); + scsc_log_collector_unregister_client(&mxlogger_collect_client_rsv_wlan); + scsc_log_collector_unregister_client(&mxlogger_collect_client_rsv_radio); + scsc_log_collector_unregister_client(&mxlogger_collect_client_mxl); + scsc_log_collector_unregister_client(&mxlogger_collect_client_udi); +#endif + mutex_lock(&mxlogger->lock); + + mxlogger_to_host(mxlogger); /* immediately before deconfigure to get a last sync rec */ + mxlogger->configured = false; + mxlogger->initialized = false; + +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_is_observer(true); +#endif + mxlogger_enable(mxlogger, false); + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxlogger->mx), + MMTRANS_CHAN_ID_MAXWELL_LOGGING, + NULL, NULL); + miframman = scsc_mx_get_ramman2(mx); + if (miframman) + miframman_free(miframman, mxlogger->mem); + + list_for_each_entry_safe(mn, next, &mxlogger_list.list, list) { + if (mn->mxl == mxlogger) { + match = true; + list_del(&mn->list); + kfree(mn); + } + } + + if (match == false) + SCSC_TAG_ERR(MXMAN, "FATAL, no match for given scsc_mif_abs\n"); + + SCSC_TAG_INFO(MXMAN, "End\n"); + mutex_unlock(&mxlogger->lock); +} + +int mxlogger_register_observer(struct mxlogger *mxlogger, char *name) +{ + mutex_lock(&mxlogger->lock); + + mxlogger->observers++; + + SCSC_TAG_INFO(MXMAN, "Register observer[%d] -- %s\n", + mxlogger->observers, name); + + /* Switch logs to host */ + mxlogger_to_host(mxlogger); +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_is_observer(true); +#endif + + mutex_unlock(&mxlogger->lock); + + return 0; +} + +int mxlogger_unregister_observer(struct mxlogger *mxlogger, char *name) +{ + mutex_lock(&mxlogger->lock); + + if (mxlogger->observers == 0) { + SCSC_TAG_INFO(MXMAN, "Incorrect number of observers\n"); + mutex_unlock(&mxlogger->lock); + return -EIO; + } + + mxlogger->observers--; + + SCSC_TAG_INFO(MXMAN, "UN-register observer[%d] -- %s\n", + mxlogger->observers, name); + + if (mxlogger->observers == 0) { + mxlogger_to_shared_dram(mxlogger); +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_is_observer(false); +#endif + } + + mutex_unlock(&mxlogger->lock); + + return 0; +} + +/* Global observer are not associated to any [mx] mxlogger instance. So it registers as + * an observer to all the [mx] mxlogger instances. + */ +int mxlogger_register_global_observer(char *name) +{ + struct mxlogger_node *mn, *next; + + mutex_lock(&global_lock); + active_global_observers++; + + SCSC_TAG_INFO(MXMAN, "Register global observer[%d] -- %s\n", + active_global_observers, name); + + if (list_empty(&mxlogger_list.list)) { + SCSC_TAG_INFO(MXMAN, "No instances of mxman\n"); + mutex_unlock(&global_lock); + return -EIO; + } + + list_for_each_entry_safe(mn, next, &mxlogger_list.list, list) { + /* There is a mxlogger instance */ + mxlogger_register_observer(mn->mxl, name); + } + + mutex_unlock(&global_lock); + + return 0; +} + +int mxlogger_unregister_global_observer(char *name) +{ + struct mxlogger_node *mn, *next; + + mutex_lock(&global_lock); + if (active_global_observers) + active_global_observers--; + + SCSC_TAG_INFO(MXMAN, "UN-register global observer[%d] -- %s\n", + active_global_observers, name); + + list_for_each_entry_safe(mn, next, &mxlogger_list.list, list) { + /* There is a mxlogger instance */ + mxlogger_unregister_observer(mn->mxl, name); + } + + mutex_unlock(&global_lock); + + return 0; +} + diff --git a/drivers/misc/samsung/scsc/mxlogger.h b/drivers/misc/samsung/scsc/mxlogger.h new file mode 100644 index 000000000000..ad80abd7c3b5 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxlogger.h @@ -0,0 +1,219 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * Maxwell mxlogger (Interface) + * + * Provides bi-directional communication between the firmware and the + * host. + * + */ + +#ifndef __MX_LOGGER_H__ +#define __MX_LOGGER_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mxmgmt_transport_format.h" + +/** + * ___________________________________________________________________ + * | Cmd | Arg | ... payload (opt) ... | + * ------------------------------------------------------------------- + * <-- uint8_t --><-- uint8_t --><----- uint8_t[] buffer -----------> + * + */ + +#define MXL_POOL_SZ (6 * 1024 * 1024) + +#define MXLOGGER_RINGS_TMO_US 200000 + +/* CMD/EVENTS */ +#define MM_MXLOGGER_LOGGER_CMD (0) +#define MM_MXLOGGER_DIRECTION_CMD (1) +#define MM_MXLOGGER_CONFIG_CMD (2) +#define MM_MXLOGGER_INITIALIZED_EVT (3) +#define MM_MXLOGGER_SYNC_RECORD (4) +#define MM_MXLOGGER_STARTED_EVT (5) +#define MM_MXLOGGER_STOPPED_EVT (6) +#define MM_MXLOGGER_COLLECTION_FW_REQ_EVT (7) + +/* ARG - LOGGER */ +#define MM_MXLOGGER_LOGGER_ENABLE (0) +#define MM_MXLOGGER_LOGGER_DISABLE (1) +#define MM_MXLOGGER_DISABLE_REASON_STOP (0) +#define MM_MXLOGGER_DISABLE_REASON_COLLECTION (1) + +/* ARG - DIRECTION */ +#define MM_MXLOGGER_DIRECTION_DRAM (0) +#define MM_MXLOGGER_DIRECTION_HOST (1) + +/* ARG - CONFIG TABLE */ +#define MM_MXLOGGER_CONFIG_BASE_ADDR (0) + +/* ARG - CONFIG TABLE */ +#define MM_MXLOGGER_SYNC_INDEX (0) + +#define MM_MXLOGGER_PAYLOAD_SZ (MXMGR_MESSAGE_PAYLOAD_SIZE - 2) + +#define MXLOGGER_SYNC_SIZE (10 * 1024) +#define MXLOGGER_IMP_SIZE (102 * 1024) +#define MXLOGGER_RSV_COMMON_SZ (4 * 1024) +#define MXLOGGER_RSV_BT_SZ (4 * 1024) +#define MXLOGGER_RSV_WLAN_SZ (2 * 1024 * 1024) +#define MXLOGGER_RSV_RADIO_SZ (4 * 1024) + +#define MXLOGGER_TOTAL_FIX_BUF (MXLOGGER_SYNC_SIZE + MXLOGGER_IMP_SIZE + \ + MXLOGGER_RSV_COMMON_SZ + MXLOGGER_RSV_BT_SZ + \ + MXLOGGER_RSV_WLAN_SZ + MXLOGGER_RSV_RADIO_SZ) + +#define MXLOGGER_NON_FIX_BUF_ALIGN 32 + +#define MXLOGGER_MAGIG_NUMBER 0xcaba0401 +#define MXLOGGER_MAJOR 0 +#define MXLOGGER_MINOR 0 + +#define NUM_SYNC_RECORDS 256 +#define SYNC_MASK (NUM_SYNC_RECORDS - 1) + +/* Shared memory Layout + * + * |-------------------------| CONFIG + * | CONFIG AREA | + * | ... | + * | *bufs |------| + * | .... | | + * | .... | | + * | -------------------- |<-----| + * | | + * | loc | sz | state |info |---------------------| + * | loc | sz | state |info |---------------------| + * | loc | sz | state |info |---------------------| + * | ... | | + * |-------------------------| Fixed size buffers | + * | SYNC BUFFER |<--------------------| + * |-------------------------| | + * | IMPORTANT EVENTS |<--------------------| + * |-------------------------| | + * | Reserved COMMON |<--------------------| + * |-------------------------| + * | Reserved BT | + * |-------------------------| + * | Reserved WL | + * |-------------------------| + * | Reserved RADIO | + * |-------------------------| Not fixed size buffers + * | MXLOG | + * |-------------------------| + * | UDI | + * |-------------------------| + * | Future buffers (TBD) | + * |-------------------------| + * | Future buffers (TBD) | + * |-------------------------| + */ + +enum mxlogger_buffers { + MXLOGGER_FIRST_FIXED_SZ, + MXLOGGER_SYNC = MXLOGGER_FIRST_FIXED_SZ, + MXLOGGER_IMP, + MXLOGGER_RESERVED_COMMON, + MXLOGGER_RESERVED_BT, + MXLOGGER_RESERVED_WLAN, + MXLOGGER_RESERVED_RADIO, + MXLOGGER_LAST_FIXED_SZ = MXLOGGER_RESERVED_RADIO, + MXLOGGER_MXLOG, + MXLOGGER_UDI, + MXLOGGER_NUM_BUFFERS +}; + +enum mxlogger_sync_event { + MXLOGGER_SYN_SUSPEND, + MXLOGGER_SYN_RESUME, + MXLOGGER_SYN_TOHOST, + MXLOGGER_SYN_TORAM, + MXLOGGER_SYN_LOGCOLLECTION, +}; + +struct mxlogger_sync_record { + u64 tv_sec; /* struct timeval.tv_sec */ + u64 tv_usec; /* struct timeval.tv_usec */ + u64 kernel_time; /* ktime_t */ + u32 sync_event; /* type of sync event*/ + u32 fw_time; + u32 fw_wrap; + u8 reserved[4]; +} __packed; + +struct buffer_desc { + u32 location; /* Buffer location */ + u32 size; /* Buffer sz (in bytes) */ + u32 status; /* buffer status */ + u32 info; /* buffer info */ +} __packed; + +struct mxlogger_config { + u32 magic_number; /* 0xcaba0401 */ + u32 config_major; /* Version Major */ + u32 config_minor; /* Version Minor */ + u32 num_buffers; /* configured buffers */ + scsc_mifram_ref bfds_ref; +} __packed; + +struct mxlogger_config_area { + struct mxlogger_config config; + struct buffer_desc bfds[MXLOGGER_NUM_BUFFERS]; + uint8_t *buffers_start; +} __packed; + +struct log_msg_packet { + uint8_t msg; /* cmd or event id */ + uint8_t arg; + uint8_t payload[MM_MXLOGGER_PAYLOAD_SZ]; +} __packed; + +struct mxlogger { + bool initialized; + bool configured; + bool enabled; + struct scsc_mx *mx; + void *mem; + void *mem_sync_buf; + uint32_t msz; + scsc_mifram_ref mifram_ref; + struct mutex lock; + struct mxlogger_config_area *cfg; + u8 observers; + u8 sync_buffer_index; + /* collection variables */ + bool re_enable; + struct completion rings_serialized_ops; +}; + +int mxlogger_generate_sync_record(struct mxlogger *mxlogger, enum mxlogger_sync_event event); +int mxlogger_dump_shared_memory_to_file(struct mxlogger *mxlogger); +int mxlogger_init(struct scsc_mx *mx, struct mxlogger *mxlogger, uint32_t mem_sz); +void mxlogger_deinit(struct scsc_mx *mx, struct mxlogger *mxlogger); +int mxlogger_start(struct mxlogger *mxlogger); +int mxlogger_register_observer(struct mxlogger *mxlogger, char *name); +int mxlogger_unregister_observer(struct mxlogger *mxlogger, char *name); +int mxlogger_register_global_observer(char *name); +int mxlogger_unregister_global_observer(char *name); +bool mxlogger_set_enabled_status(bool enable); + +#define MEM_LAYOUT_CHECK() \ +({ \ + BUILD_BUG_ON((sizeof(struct mxlogger_sync_record) * NUM_SYNC_RECORDS) > MXLOGGER_SYNC_SIZE); \ + BUILD_BUG_ON((MXLOGGER_TOTAL_FIX_BUF + sizeof(struct mxlogger_config_area)) > MXL_POOL_SZ); \ +}) + +#endif /* __MX_LOGGER_H__ */ diff --git a/drivers/misc/samsung/scsc/mxman.c b/drivers/misc/samsung/scsc/mxman.c new file mode 100755 index 000000000000..fbb39aae0868 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxman.c @@ -0,0 +1,2601 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "scsc_mx_impl.h" +#include "miframman.h" +#include "mifmboxman.h" +#include "mxman.h" +#include "srvman.h" +#include "mxmgmt_transport.h" +#include "gdb_transport.h" +#include "mxconf.h" +#include "fwimage.h" +#include "fwhdr.h" +#include "mxlog.h" +#include "mxlogger.h" +#include "fw_panic_record.h" +#include "panicmon.h" +#include "mxproc.h" +#include "mxlog_transport.h" +#include "mxsyserr.h" +#ifdef CONFIG_SCSC_SMAPPER +#include "mifsmapper.h" +#endif +#ifdef CONFIG_SCSC_QOS +#include "mifqos.h" +#endif +#include "mxfwconfig.h" +#include +#include +#include +#include +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +#include +#ifdef CONFIG_SCSC_WLBTD +#include "scsc_wlbtd.h" +#define SCSC_SCRIPT_MOREDUMP "moredump" +#define SCSC_SCRIPT_LOGGER_DUMP "mx_logger_dump.sh" +static struct work_struct wlbtd_work; +#endif + +#include "scsc_lerna.h" + +#include +#include + +#define STRING_BUFFER_MAX_LENGTH 512 +#define NUMBER_OF_STRING_ARGS 5 +#define MX_DRAM_SIZE (4 * 1024 * 1024) +#define MX_DRAM_SIZE_SECTION_1 (8 * 1024 * 1024) +#define MX_DRAM_SIZE_SECTION_2 (8 * 1024 * 1024) +#define MX_FW_RUNTIME_LENGTH (1024 * 1024) +#define WAIT_FOR_FW_TO_START_DELAY_MS 1000 +#define MBOX2_MAGIC_NUMBER 0xbcdeedcb +#define MBOX_INDEX_0 0 +#define MBOX_INDEX_1 1 +#define MBOX_INDEX_2 2 +#define MBOX_INDEX_3 3 +#ifdef CONFIG_SOC_EXYNOS7570 +#define MBOX_INDEX_4 4 +#define MBOX_INDEX_5 5 +#define MBOX_INDEX_6 6 +#define MBOX_INDEX_7 7 +#endif + +#define SCSC_PANIC_ORIGIN_FW (0x0 << 15) +#define SCSC_PANIC_ORIGIN_HOST (0x1 << 15) + +#define SCSC_PANIC_TECH_WLAN (0x0 << 13) +#define SCSC_PANIC_TECH_CORE (0x1 << 13) +#define SCSC_PANIC_TECH_BT (0x2 << 13) +#define SCSC_PANIC_TECH_UNSP (0x3 << 13) + +#define SCSC_PANIC_ORIGIN_MASK 0x8000 +#define SCSC_PANIC_TECH_MASK 0x6000 +#define SCSC_PANIC_SUBCODE_MASK_LEGACY 0x0FFF +#define SCSC_PANIC_SUBCODE_MASK 0x7FFF + +#define SCSC_R4_V2_MINOR_52 52 +#define SCSC_R4_V2_MINOR_53 53 + +#define MM_HALT_RSP_TIMEOUT_MS 100 + +static char panic_record_dump[PANIC_RECORD_DUMP_BUFFER_SZ]; +static BLOCKING_NOTIFIER_HEAD(firmware_chain); + +/** + * This will be returned as fw version ONLY if Maxwell + * was never found or was unloaded. + */ +static char saved_fw_build_id[FW_BUILD_ID_SZ] = "Maxwell WLBT unavailable"; + +static bool allow_unidentified_firmware; +module_param(allow_unidentified_firmware, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(allow_unidentified_firmware, "Allow unidentified firmware"); + +static bool skip_header; +module_param(skip_header, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(skip_header, "Skip header, assuming unidentified firmware"); + +static bool crc_check_allow_none = true; +module_param(crc_check_allow_none, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(crc_check_allow_none, "Allow skipping firmware CRC checks if CRC is not present"); + +static int crc_check_period_ms = 30000; +module_param(crc_check_period_ms, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(crc_check_period_ms, "Time period for checking the firmware CRCs"); + +static ulong mm_completion_timeout_ms = 2000; +module_param(mm_completion_timeout_ms, ulong, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mm_completion_timeout_ms, "Timeout wait_for_mm_msg_start_ind (ms) - default 1000. 0 = infinite"); + +static bool skip_mbox0_check; +module_param(skip_mbox0_check, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(skip_mbox0_check, "Allow skipping firmware mbox0 signature check"); + +static uint firmware_startup_flags; +module_param(firmware_startup_flags, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(firmware_startup_flags, "0 = Proceed as normal (default); Bit 0 = 1 - spin at start of CRT0; Other bits reserved = 0"); + +#ifdef CONFIG_SCSC_CHV_SUPPORT +/* First arg controls chv function */ +int chv_run; +module_param(chv_run, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(chv_run, "Run chv f/w: 0 = feature disabled, 1 = for continuous checking, 2 = 1 shot, anything else, undefined"); + +/* Optional array of args for firmware to interpret when chv_run = 1 */ +static unsigned int chv_argv[32]; +static int chv_argc; + +module_param_array(chv_argv, uint, &chv_argc, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(chv_argv, "Array of up to 32 x u32 args for the CHV firmware when chv_run = 1"); +#endif + +static bool disable_auto_coredump; +module_param(disable_auto_coredump, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_auto_coredump, "Disable driver automatic coredump"); + +static bool disable_error_handling; +module_param(disable_error_handling, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_error_handling, "Disable error handling"); + +int disable_recovery_handling = 1; /* MEMDUMP_FILE_FOR_RECOVERY : for /sys/wifi/memdump */ +module_param(disable_recovery_handling, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_recovery_handling, "Disable recovery handling"); +static bool disable_recovery_from_memdump_file = true; +static int memdump = -1; +static bool disable_recovery_until_reboot; + +static uint panic_record_delay = 1; +module_param(panic_record_delay, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(panic_record_delay, "Delay in ms before accessing the panic record"); + +static bool disable_logger = true; +module_param(disable_logger, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_logger, "Disable launch of user space logger"); + +/* + * shared between this module and mgt.c as this is the kobject referring to + * /sys/wifi directory. Core driver is called 1st we create the directory + * here and share the kobject, so in mgt.c wifi driver can create + * /sys/wif/mac_addr using sysfs_create_file api using the kobject + * + * If both modules tried to create the dir we were getting kernel panic + * failure due to kobject associated with dir already existed + */ +static struct kobject *wifi_kobj_ref; +static int refcount; +static ssize_t sysfs_show_memdump(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t sysfs_store_memdump(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count); +static struct kobj_attribute memdump_attr = + __ATTR(memdump, 0660, sysfs_show_memdump, sysfs_store_memdump); + + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static int mxman_minimoredump_collect(struct scsc_log_collector_client *collect_client, size_t size) +{ + int ret = 0; + struct mxman *mxman = (struct mxman *) collect_client->prv; + + if (!mxman || !mxman->start_dram) + return ret; + + SCSC_TAG_INFO(MXMAN, "Collecting Minimoredump runtime_length %d fw_image_size %d\n", + mxman->fwhdr.fw_runtime_length, mxman->fw_image_size); + /* collect RAM sections of FW */ + ret = scsc_log_collector_write(mxman->start_dram + mxman->fw_image_size, + mxman->fwhdr.fw_runtime_length - mxman->fw_image_size, 1); + + return ret; +} + +struct scsc_log_collector_client mini_moredump_client = { + .name = "minimoredump", + .type = SCSC_LOG_MINIMOREDUMP, + .collect_init = NULL, + .collect = mxman_minimoredump_collect, + .collect_end = NULL, + .prv = NULL, +}; +#endif + +/* Retrieve memdump in sysfs global */ +static ssize_t sysfs_show_memdump(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", memdump); +} + +/* Update memdump in sysfs global */ +static ssize_t sysfs_store_memdump(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + int r; + + r = kstrtoint(buf, 10, &memdump); + if (r < 0) + memdump = -1; + + switch (memdump) { + case 0: + case 2: + disable_recovery_from_memdump_file = false; + break; + case 3: + default: + disable_recovery_from_memdump_file = true; + break; + } + + SCSC_TAG_INFO(MXMAN, "memdump: %d\n", memdump); + + return (r == 0) ? count : 0; +} + +struct kobject *mxman_wifi_kobject_ref_get(void) +{ + if (refcount++ == 0) { + /* Create sysfs directory /sys/wifi */ + wifi_kobj_ref = kobject_create_and_add("wifi", NULL); + kobject_get(wifi_kobj_ref); + kobject_uevent(wifi_kobj_ref, KOBJ_ADD); + SCSC_TAG_INFO(MXMAN, "wifi_kobj_ref: 0x%p\n", wifi_kobj_ref); + WARN_ON(refcount == 0); + } + return wifi_kobj_ref; +} +EXPORT_SYMBOL(mxman_wifi_kobject_ref_get); + +void mxman_wifi_kobject_ref_put(void) +{ + if (--refcount == 0) { + kobject_put(wifi_kobj_ref); + kobject_uevent(wifi_kobj_ref, KOBJ_REMOVE); + wifi_kobj_ref = NULL; + WARN_ON(refcount < 0); + } +} +EXPORT_SYMBOL(mxman_wifi_kobject_ref_put); + +/* Register memdump override */ +void mxman_create_sysfs_memdump(void) +{ + int r; + struct kobject *kobj_ref = mxman_wifi_kobject_ref_get(); + + SCSC_TAG_INFO(MXMAN, "kobj_ref: 0x%p\n", kobj_ref); + + if (kobj_ref) { + /* Create sysfs file /sys/wifi/memdump */ + r = sysfs_create_file(kobj_ref, &memdump_attr.attr); + if (r) { + /* Failed, so clean up dir */ + SCSC_TAG_ERR(MXMAN, "Can't create /sys/wifi/memdump\n"); + mxman_wifi_kobject_ref_put(); + return; + } + } else { + SCSC_TAG_ERR(MXMAN, "failed to create /sys/wifi directory"); + } +} + +/* Unregister memdump override */ +void mxman_destroy_sysfs_memdump(void) +{ + if (!wifi_kobj_ref) + return; + + /* Destroy /sys/wifi/memdump file */ + sysfs_remove_file(wifi_kobj_ref, &memdump_attr.attr); + + /* Destroy /sys/wifi virtual dir */ + mxman_wifi_kobject_ref_put(); +} + +/* Track when WLBT reset fails to allow debug */ +bool reset_failed; +static u64 reset_failed_time; + +/* Status of FM driver request, which persists beyond the lifecyle + * of the scsx_mx driver. + */ +#ifdef CONFIG_SCSC_FM +static u32 is_fm_on; +#endif + +static int firmware_runtime_flags; +/** + * This mxman reference is initialized/nullified via mxman_init/deinit + * called by scsc_mx_create/destroy on module probe/remove. + */ +static struct mxman *active_mxman; +static bool send_fw_config_to_active_mxman(uint32_t fw_runtime_flags); + +static int fw_runtime_flags_setter(const char *val, const struct kernel_param *kp) +{ + int ret = -EINVAL; + uint32_t fw_runtime_flags = 0; + + if (!val) + return ret; + ret = kstrtouint(val, 10, &fw_runtime_flags); + if (!ret) { + if (send_fw_config_to_active_mxman(fw_runtime_flags)) + firmware_runtime_flags = fw_runtime_flags; + else + ret = -EINVAL; + } + return ret; +} + +/** + * We don't bother to keep an updated copy of the runtime flags effectively + * currently set into FW...we should add a new message answer handling both in + * Kenrel and FW side to be sure and this is just to easy debug at the end. + */ +static struct kernel_param_ops fw_runtime_kops = { + .set = fw_runtime_flags_setter, + .get = NULL +}; + +module_param_cb(firmware_runtime_flags, &fw_runtime_kops, NULL, 0200); +MODULE_PARM_DESC(firmware_runtime_flags, + "0 = Proceed as normal (default); nnn = Provides FW runtime flags bitmask: unknown bits will be ignored."); + +/** + * Maxwell Agent Management Messages. + * + * TODO: common defn with firmware, generated. + * + * The numbers here *must* match the firmware! + */ +enum { + MM_START_IND = 0, + MM_HALT_REQ = 1, + MM_FORCE_PANIC = 2, + MM_HOST_SUSPEND = 3, + MM_HOST_RESUME = 4, + MM_FW_CONFIG = 5, + MM_HALT_RSP = 6, + MM_FM_RADIO_CONFIG = 7, + MM_LERNA_CONFIG = 8, + MM_SYSERR_IND = 9, +} ma_msg; + +/** + * Format of the Maxwell agent messages + * on the Maxwell management transport stream. + */ +struct ma_msg_packet { + + uint8_t ma_msg; /* Message from ma_msg enum */ + uint32_t arg; /* Optional arg set by f/w in some to-host messages */ +} __packed; + +/** + * Special case Maxwell management, carrying FM radio configuration structure + */ +struct ma_msg_packet_fm_radio_config { + + uint8_t ma_msg; /* Message from ma_msg enum */ + struct wlbt_fm_params fm_params; /* FM Radio parameters */ +} __packed; + +static bool send_fw_config_to_active_mxman(uint32_t fw_runtime_flags) +{ + bool ret = false; + struct srvman *srvman = NULL; + + SCSC_TAG_INFO(MXMAN, "\n"); + if (!active_mxman) { + SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send running FW config.\n"); + return ret; + } + + mutex_lock(&active_mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(active_mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&active_mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return ret; + } + + if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { + struct ma_msg_packet message = { .ma_msg = MM_FW_CONFIG, + .arg = fw_runtime_flags }; + + SCSC_TAG_INFO(MXMAN, "MM_FW_CONFIG - firmware_runtime_flags:%d\n", message.arg); + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), + MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, + sizeof(message)); + ret = true; + } else { + SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_FW_CONFIG msg.\n"); + } + mutex_unlock(&active_mxman->mxman_mutex); + + return ret; +} + +static bool send_fm_params_to_active_mxman(struct wlbt_fm_params *params) +{ + bool ret = false; + struct srvman *srvman = NULL; + + SCSC_TAG_INFO(MXMAN, "\n"); + if (!active_mxman) { + SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send FM params\n"); + return false; + } + + mutex_lock(&active_mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(active_mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&active_mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return false; + } + + if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { + struct ma_msg_packet_fm_radio_config message = { .ma_msg = MM_FM_RADIO_CONFIG, + .fm_params = *params }; + + SCSC_TAG_INFO(MXMAN, "MM_FM_RADIO_CONFIG\n"); + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), + MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, + sizeof(message)); + + ret = true; /* Success */ + } else + SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_FM_RADIO_CONFIG msg.\n"); + + mutex_unlock(&active_mxman->mxman_mutex); + + return ret; +} + +static void mxman_stop(struct mxman *mxman); +static void print_mailboxes(struct mxman *mxman); +#ifdef CONFIG_SCSC_WLBTD +static int _mx_exec(char *prog, int wait_exec) __attribute__((unused)); +#else +static int _mx_exec(char *prog, int wait_exec); +#endif +static int wait_for_mm_msg(struct mxman *mxman, struct completion *mm_msg_completion, ulong timeout_ms) +{ + int r; + + (void)mxman; /* unused */ + + if (timeout_ms == 0) { + /* Zero implies infinite wait */ + r = wait_for_completion_interruptible(mm_msg_completion); + /* r = -ERESTARTSYS if interrupted, 0 if completed */ + return r; + } + r = wait_for_completion_timeout(mm_msg_completion, msecs_to_jiffies(timeout_ms)); + if (r == 0) { + SCSC_TAG_ERR(MXMAN, "timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int wait_for_mm_msg_start_ind(struct mxman *mxman) +{ + return wait_for_mm_msg(mxman, &mxman->mm_msg_start_ind_completion, mm_completion_timeout_ms); +} + +static int wait_for_mm_msg_halt_rsp(struct mxman *mxman) +{ + int r; + (void)mxman; /* unused */ + + if (MM_HALT_RSP_TIMEOUT_MS == 0) { + /* Zero implies infinite wait */ + r = wait_for_completion_interruptible(&mxman->mm_msg_halt_rsp_completion); + /* r = -ERESTARTSYS if interrupted, 0 if completed */ + return r; + } + + r = wait_for_completion_timeout(&mxman->mm_msg_halt_rsp_completion, msecs_to_jiffies(MM_HALT_RSP_TIMEOUT_MS)); + if (r) + SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP from firmware"); + + return r; +} + +#ifndef CONFIG_SCSC_WLBTD +static int coredump_helper(void) +{ + int r; + int i; + static char mdbin[128]; + + /* Determine path to moredump helper script */ + r = mx140_exe_path(NULL, mdbin, sizeof(mdbin), "moredump"); + if (r) { + SCSC_TAG_ERR(MXMAN, "moredump path error\n"); + return r; + } + + for (i = 0; i < 20; i++) { + r = _mx_exec(mdbin, UMH_WAIT_PROC); + if (r != -EBUSY) + break; + /* If the usermode helper fails with -EBUSY, the userspace is + * likely still frozen from suspend. Back off and retry. + */ + SCSC_TAG_INFO(MXMAN, "waiting for userspace to thaw...\n"); + msleep(1000); + } + + /* Application return codes are in the MSB */ + if (r > 0xffL) + SCSC_TAG_INFO(MXMAN, "moredump.bin exit(%ld), check syslog\n", (r & 0xff00L) >> 8); + + return r; +} +#endif + +static int send_mm_msg_stop_blocking(struct mxman *mxman) +{ + int r; +#ifdef CONFIG_SCSC_FM + struct ma_msg_packet message = { .ma_msg = MM_HALT_REQ, + .arg = mxman->on_halt_ldos_on }; +#else + struct ma_msg_packet message = { .ma_msg = MM_HALT_REQ }; +#endif + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); + + r = wait_for_mm_msg_halt_rsp(mxman); + if (r) { + /* + * MM_MSG_HALT_RSP is not implemented in all versions of firmware, so don't treat it's non-arrival + * as an error + */ + SCSC_TAG_INFO(MXMAN, "wait_for_MM_HALT_RSP completed"); + } + + return 0; +} + +static char *chip_version(u32 rf_hw_ver) +{ + switch (rf_hw_ver & 0x00ff) { + default: + break; + case 0x00b0: + if ((rf_hw_ver & 0xff00) > 0x1000) + return "S610/S611"; + else + return "S610"; + case 0x00b1: + return "S612"; + case 0x00b2: + return "S620"; + case 0x0000: +#ifndef CONFIG_SOC_EXYNOS9610 + return "Error: check if RF chip is present"; +#else + return "Unknown"; +#endif + } + return "Unknown"; +} + +/* + * This function is used in this file and in mxproc.c to generate consistent + * RF CHIP VERSION string for logging on console and for storing the same + * in proc/drivers/mxman_info/rf_chip_version file. + */ +int mxman_print_rf_hw_version(struct mxman *mxman, char *buf, const size_t bufsz) +{ + int r; + + r = snprintf(buf, bufsz, "RF_CHIP_VERSION: 0x%04x: %s (0x%02x), EVT%x.%x\n", + mxman->rf_hw_ver, + chip_version(mxman->rf_hw_ver), (mxman->rf_hw_ver & 0x00ff), + ((mxman->rf_hw_ver >> 12) & 0xfU), ((mxman->rf_hw_ver >> 8) & 0xfU)); + + return r; +} + +static void mxman_print_versions(struct mxman *mxman) +{ + char buf[80]; + + memset(buf, '\0', sizeof(buf)); + + (void)mxman_print_rf_hw_version(mxman, buf, sizeof(buf)); + + SCSC_TAG_INFO(MXMAN, "%s", buf); + SCSC_TAG_INFO(MXMAN, "WLBT FW: %s\n", mxman->fw_build_id); + SCSC_TAG_INFO(MXMAN, "WLBT Driver: %d.%d.%d.%d\n", + SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION, SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT); +#ifdef CONFIG_SCSC_WLBTD + scsc_wlbtd_get_and_print_build_type(); +#endif +} + +/** Receive handler for messages from the FW along the maxwell management transport */ +static void mxman_message_handler(const void *message, void *data) +{ + struct mxman *mxman = (struct mxman *)data; + + /* Forward the message to the applicable service to deal with */ + const struct ma_msg_packet *msg = message; + + switch (msg->ma_msg) { + case MM_START_IND: + /* The arg can be used to determine the WLBT/S610 hardware revision */ + SCSC_TAG_INFO(MXMAN, "Received MM_START_IND message from the firmware, arg=0x%04x\n", msg->arg); + mxman->rf_hw_ver = msg->arg; + mxman_print_versions(mxman); + atomic_inc(&mxman->boot_count); + complete(&mxman->mm_msg_start_ind_completion); + break; + case MM_HALT_RSP: + complete(&mxman->mm_msg_halt_rsp_completion); + SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP message from the firmware\n"); + break; + case MM_LERNA_CONFIG: + /* Message response to a firmware configuration query. */ + SCSC_TAG_INFO(MXMAN, "Received MM_LERNA_CONFIG message from firmware\n"); + scsc_lerna_response(message); + break; + case MM_SYSERR_IND: + /* System Error report from firmware */ + SCSC_TAG_INFO(MXMAN, "Received MM_SYSERR_IND message from firmware\n"); + mx_syserr_handler(mxman, message); + break; + default: + /* HERE: Unknown message, raise fault */ + SCSC_TAG_WARNING(MXMAN, "Received unknown message from the firmware: msg->ma_msg=%d\n", msg->ma_msg); + break; + } +} + +/* + * This function calulates and checks two or three (depending on crc32_over_binary flag) + * crc32 values in the firmware header. The function will check crc32 over the firmware binary + * (i.e. everything in the file following the header) only if the crc32_over_binary is set to 'true'. + * This includes initialised data regions so it can be used to check when loading but will not be + * meaningful once execution starts. + */ +static int do_fw_crc32_checks(char *fw, u32 fw_image_size, struct fwhdr *fwhdr, bool crc32_over_binary) +{ + int r; + + if ((fwhdr->fw_crc32 == 0 || fwhdr->header_crc32 == 0 || fwhdr->const_crc32 == 0) && crc_check_allow_none == 0) { + SCSC_TAG_ERR(MXMAN, "error: CRC is missing fw_crc32=%d header_crc32=%d crc_check_allow_none=%d\n", + fwhdr->fw_crc32, fwhdr->header_crc32, crc_check_allow_none); + return -EINVAL; + } + + if (fwhdr->header_crc32 == 0 && crc_check_allow_none == 1) { + SCSC_TAG_INFO(MXMAN, "Skipping CRC check header_crc32=%d crc_check_allow_none=%d\n", + fwhdr->header_crc32, crc_check_allow_none); + } else { + /* + * CRC-32-IEEE of all preceding header fields (including other CRCs). + * Always the last word in the header. + */ + r = fwimage_check_fw_header_crc(fw, fwhdr->hdr_length, fwhdr->header_crc32); + if (r) { + SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_header_crc() failed\n"); + return r; + } + } + + if (fwhdr->const_crc32 == 0 && crc_check_allow_none == 1) { + SCSC_TAG_INFO(MXMAN, "Skipping CRC check const_crc32=%d crc_check_allow_none=%d\n", + fwhdr->const_crc32, crc_check_allow_none); + } else { + /* + * CRC-32-IEEE over the constant sections grouped together at start of firmware binary. + * This CRC should remain valid during execution. It can be used by run-time checker on + * host to detect firmware corruption (not all memory masters are subject to MPUs). + */ + r = fwimage_check_fw_const_section_crc(fw, fwhdr->const_crc32, fwhdr->const_fw_length, fwhdr->hdr_length); + if (r) { + SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_const_section_crc() failed\n"); + return r; + } + } + + if (crc32_over_binary) { + if (fwhdr->fw_crc32 == 0 && crc_check_allow_none == 1) + SCSC_TAG_INFO(MXMAN, "Skipping CRC check fw_crc32=%d crc_check_allow_none=%d\n", + fwhdr->fw_crc32, crc_check_allow_none); + else { + /* + * CRC-32-IEEE over the firmware binary (i.e. everything + * in the file following this header). + * This includes initialised data regions so it can be used to + * check when loading but will not be meaningful once execution starts. + */ + r = fwimage_check_fw_crc(fw, fw_image_size, fwhdr->hdr_length, fwhdr->fw_crc32); + if (r) { + SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_crc() failed\n"); + return r; + } + } + } + + return 0; +} + + +static void fw_crc_wq_start(struct mxman *mxman) +{ + if (mxman->check_crc && crc_check_period_ms) + queue_delayed_work(mxman->fw_crc_wq, &mxman->fw_crc_work, msecs_to_jiffies(crc_check_period_ms)); +} + + +static void fw_crc_work_func(struct work_struct *work) +{ + int r; + struct mxman *mxman = container_of((struct delayed_work *)work, struct mxman, fw_crc_work); + + r = do_fw_crc32_checks(mxman->fw, mxman->fw_image_size, &mxman->fwhdr, false); + if (r) { + SCSC_TAG_ERR(MXMAN, "do_fw_crc32_checks() failed r=%d\n", r); + mxman_fail(mxman, SCSC_PANIC_CODE_HOST << 15, __func__); + return; + } + fw_crc_wq_start(mxman); +} + + +static void fw_crc_wq_init(struct mxman *mxman) +{ + mxman->fw_crc_wq = create_singlethread_workqueue("fw_crc_wq"); + INIT_DELAYED_WORK(&mxman->fw_crc_work, fw_crc_work_func); +} + +static void fw_crc_wq_stop(struct mxman *mxman) +{ + mxman->check_crc = false; + cancel_delayed_work(&mxman->fw_crc_work); + flush_workqueue(mxman->fw_crc_wq); +} + +static void fw_crc_wq_deinit(struct mxman *mxman) +{ + fw_crc_wq_stop(mxman); + destroy_workqueue(mxman->fw_crc_wq); +} + +static int transports_init(struct mxman *mxman) +{ + struct mxconf *mxconf; + int r; + struct scsc_mx *mx = mxman->mx; + + /* Initialise mx management stack */ + r = mxmgmt_transport_init(scsc_mx_get_mxmgmt_transport(mx), mx); + if (r) { + SCSC_TAG_ERR(MXMAN, "mxmgmt_transport_init() failed %d\n", r); + return r; + } + + /* Initialise gdb transport for cortex-R4 */ + r = gdb_transport_init(scsc_mx_get_gdb_transport_r4(mx), mx, GDB_TRANSPORT_R4); + if (r) { + SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r); + mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); + return r; + } + + /* Initialise gdb transport for cortex-M4 */ + r = gdb_transport_init(scsc_mx_get_gdb_transport_m4(mx), mx, GDB_TRANSPORT_M4); + if (r) { + SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r); + gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); + mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); + return r; + } +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + /* Initialise gdb transport for cortex-M4 */ + r = gdb_transport_init(scsc_mx_get_gdb_transport_m4_1(mx), mx, GDB_TRANSPORT_M4_1); + if (r) { + SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r); + gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); + mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); + return r; + } +#endif + + /* Initialise mxlog transport */ + r = mxlog_transport_init(scsc_mx_get_mxlog_transport(mx), mx); + if (r) { + SCSC_TAG_ERR(MXMAN, "mxlog_transport_init() failed %d\n", r); + gdb_transport_release(scsc_mx_get_gdb_transport_m4(mx)); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mx)); +#endif + gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); + mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); + return r; + } + + /* + * Allocate & Initialise Infrastructre Config Structure + * including the mx management stack config information. + */ + mxconf = miframman_alloc(scsc_mx_get_ramman(mx), sizeof(struct mxconf), 4, MIFRAMMAN_OWNER_COMMON); + if (!mxconf) { + SCSC_TAG_ERR(MXMAN, "miframman_alloc() failed\n"); + gdb_transport_release(scsc_mx_get_gdb_transport_m4(mx)); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mx)); +#endif + gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); + mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); + mxlog_transport_release(scsc_mx_get_mxlog_transport(mx)); + return -ENOMEM; + } + mxman->mxconf = mxconf; + mxconf->magic = MXCONF_MAGIC; + mxconf->version.major = MXCONF_VERSION_MAJOR; + mxconf->version.minor = MXCONF_VERSION_MINOR; + + /* Pass pre-existing FM status to FW */ + mxconf->flags = 0; +#ifdef CONFIG_SCSC_FM + mxconf->flags |= is_fm_on ? MXCONF_FLAGS_FM_ON : 0; +#endif + SCSC_TAG_INFO(MXMAN, "mxconf flags 0x%08x\n", mxconf->flags); + + /* serialise mxmgmt transport */ + mxmgmt_transport_config_serialise(scsc_mx_get_mxmgmt_transport(mx), &mxconf->mx_trans_conf); + /* serialise Cortex-R4 gdb transport */ + gdb_transport_config_serialise(scsc_mx_get_gdb_transport_r4(mx), &mxconf->mx_trans_conf_gdb_r4); + /* serialise Cortex-M4 gdb transport */ + gdb_transport_config_serialise(scsc_mx_get_gdb_transport_m4(mx), &mxconf->mx_trans_conf_gdb_m4); + + /* Default to Fleximac M4_1 monitor channel not in use. + * Allows CONFIG_SCSC_MX450_GDB_SUPPORT to be turned off in Kconfig even though mxconf + * struct v5 defines M4_1 channel + */ + mxconf->mx_trans_conf_gdb_m4_1.from_ap_stream_conf.buf_conf.buffer_loc = 0; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + /* serialise Cortex-M4 gdb transport */ + gdb_transport_config_serialise(scsc_mx_get_gdb_transport_m4_1(mx), &mxconf->mx_trans_conf_gdb_m4_1); +#endif + /* serialise mxlog transport */ + mxlog_transport_config_serialise(scsc_mx_get_mxlog_transport(mx), &mxconf->mxlogconf); + SCSC_TAG_DEBUG(MXMAN, "read_bit_idx=%d write_bit_idx=%d buffer=%p num_packets=%d packet_size=%d read_index=%d write_index=%d\n", + scsc_mx_get_mxlog_transport(mx)->mif_stream.read_bit_idx, + scsc_mx_get_mxlog_transport(mx)->mif_stream.write_bit_idx, + scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.buffer, + scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.num_packets, + scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.packet_size, + *scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.read_index, + *scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.write_index + ); + + /* Need to initialise fwconfig or else random data can make firmware data abort. */ + mxconf->fwconfig.offset = 0; + mxconf->fwconfig.size = 0; +#ifdef CONFIG_SCSC_COMMON_HCF + /* Load Common Config HCF */ + mxfwconfig_load(mxman->mx, &mxconf->fwconfig); +#endif + return 0; +} + +static void transports_release(struct mxman *mxman) +{ + mxlog_transport_release(scsc_mx_get_mxlog_transport(mxman->mx)); + mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mxman->mx)); + gdb_transport_release(scsc_mx_get_gdb_transport_r4(mxman->mx)); + gdb_transport_release(scsc_mx_get_gdb_transport_m4(mxman->mx)); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mxman->mx)); +#endif + miframman_free(scsc_mx_get_ramman(mxman->mx), mxman->mxconf); +} + +static void mbox_init(struct mxman *mxman, u32 firmware_entry_point) +{ + u32 *mbox0; + u32 *mbox1; + u32 *mbox2; + u32 *mbox3; + scsc_mifram_ref mifram_ref; + struct scsc_mx *mx = mxman->mx; + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx); + + /* Place firmware entry address in MIF MBOX 0 so R4 ROM knows where to jump to! */ + mbox0 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_0); + mbox1 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_1); + + /* Write (and flush) entry point to MailBox 0, config address to MBOX 1 */ + *mbox0 = firmware_entry_point; + mif->get_mifram_ref(mif, mxman->mxconf, &mifram_ref); + *mbox1 = mifram_ref; /* must be R4-relative address here */ + /* CPU memory barrier */ + wmb(); + /* + * write the magic number "0xbcdeedcb" to MIF Mailbox #2 & + * copy the firmware_startup_flags to MIF Mailbox #3 before starting (reset = 0) the R4 + */ + mbox2 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_2); + *mbox2 = MBOX2_MAGIC_NUMBER; + mbox3 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_3); + *mbox3 = firmware_startup_flags; +} + +static int fwhdr_init(char *fw, struct fwhdr *fwhdr, bool *fwhdr_parsed_ok, bool *check_crc) +{ + /* + * Validate the fw image including checking the firmware header, majic #, version, checksum so on + * then do CRC on the entire image + * + * Derive some values from header - + * + * PORT: assumes little endian + */ + if (skip_header) + *fwhdr_parsed_ok = false; /* Allows the forced start address to be used */ + else + *fwhdr_parsed_ok = fwhdr_parse(fw, fwhdr); + *check_crc = false; + if (*fwhdr_parsed_ok) { + SCSC_TAG_INFO(MXMAN, "FW HEADER version: hdr_major: %d hdr_minor: %d\n", fwhdr->hdr_major, fwhdr->hdr_minor); + switch (fwhdr->hdr_major) { + case 0: + switch (fwhdr->hdr_minor) { + case 2: + *check_crc = true; + break; + default: + SCSC_TAG_ERR(MXMAN, "Unsupported FW HEADER version: hdr_major: %d hdr_minor: %d\n", + fwhdr->hdr_major, fwhdr->hdr_minor); + return -EINVAL; + } + break; + case 1: + *check_crc = true; + break; + default: + SCSC_TAG_ERR(MXMAN, "Unsupported FW HEADER version: hdr_major: %d hdr_minor: %d\n", + fwhdr->hdr_major, fwhdr->hdr_minor); + return -EINVAL; + } + switch (fwhdr->fwapi_major) { + case 0: + switch (fwhdr->fwapi_minor) { + case 2: + SCSC_TAG_INFO(MXMAN, "FWAPI version: fwapi_major: %d fwapi_minor: %d\n", + fwhdr->fwapi_major, fwhdr->fwapi_minor); + break; + default: + SCSC_TAG_ERR(MXMAN, "Unsupported FWAPI version: fwapi_major: %d fwapi_minor: %d\n", + fwhdr->fwapi_major, fwhdr->fwapi_minor); + return -EINVAL; + } + break; + default: + SCSC_TAG_ERR(MXMAN, "Unsupported FWAPI version: fwapi_major: %d fwapi_minor: %d\n", + fwhdr->fwapi_major, fwhdr->fwapi_minor); + return -EINVAL; + } + } else { + /* This is unidetified pre-header firmware - assume it is built to run at 0xb8000000 == 0 for bootrom */ + if (allow_unidentified_firmware) { + SCSC_TAG_INFO(MXMAN, "Unidentified firmware override\n"); + fwhdr->firmware_entry_point = 0; + fwhdr->fw_runtime_length = MX_FW_RUNTIME_LENGTH; + } else { + SCSC_TAG_ERR(MXMAN, "Unidentified firmware is not allowed\n"); + return -EINVAL; + } + } + return 0; +} + +static int fw_init(struct mxman *mxman, void *start_dram, size_t size_dram, bool *fwhdr_parsed_ok) +{ + int r; + char *build_id; + char *ttid; + u32 fw_image_size; + struct fwhdr *fwhdr = &mxman->fwhdr; + char *fw = start_dram; + + r = mx140_file_download_fw(mxman->mx, start_dram, size_dram, &fw_image_size); + if (r) { + SCSC_TAG_ERR(MXMAN, "mx140_file_download_fw() failed (%d)\n", r); + return r; + } + + r = fwhdr_init(fw, fwhdr, fwhdr_parsed_ok, &mxman->check_crc); + if (r) { + SCSC_TAG_ERR(MXMAN, "fwhdr_init() failed\n"); + return r; + } + mxman->fw = fw; + mxman->fw_image_size = fw_image_size; + if (mxman->check_crc) { + /* do CRC on the entire image */ + r = do_fw_crc32_checks(fw, fw_image_size, &mxman->fwhdr, true); + if (r) { + SCSC_TAG_ERR(MXMAN, "do_fw_crc32_checks() failed\n"); + return r; + } + fw_crc_wq_start(mxman); + } + + if (*fwhdr_parsed_ok) { + build_id = fwhdr_get_build_id(fw, fwhdr); + if (build_id) { + struct slsi_kic_service_info kic_info; + + (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "%s", build_id); + SCSC_TAG_INFO(MXMAN, "Firmware BUILD_ID: %s\n", mxman->fw_build_id); + memcpy(saved_fw_build_id, mxman->fw_build_id, + sizeof(saved_fw_build_id)); + + (void) snprintf(kic_info.ver_str, + min(sizeof(mxman->fw_build_id), sizeof(kic_info.ver_str)), + "%s", mxman->fw_build_id); + kic_info.fw_api_major = fwhdr->fwapi_major; + kic_info.fw_api_minor = fwhdr->fwapi_minor; + kic_info.release_product = SCSC_RELEASE_PRODUCT; + kic_info.host_release_iteration = SCSC_RELEASE_ITERATION; + kic_info.host_release_candidate = SCSC_RELEASE_CANDIDATE; + + slsi_kic_service_information(slsi_kic_technology_type_common, &kic_info); + } else + SCSC_TAG_ERR(MXMAN, "Failed to get Firmware BUILD_ID\n"); + + ttid = fwhdr_get_ttid(fw, fwhdr); + if (ttid) { + (void)snprintf(mxman->fw_ttid, sizeof(mxman->fw_ttid), "%s", ttid); + SCSC_TAG_INFO(MXMAN, "Firmware ttid: %s\n", mxman->fw_ttid); + } + } + + SCSC_TAG_DEBUG(MXMAN, "firmware_entry_point=0x%x fw_runtime_length=%d\n", fwhdr->firmware_entry_point, fwhdr->fw_runtime_length); + + return 0; + +} + +static int mxman_start(struct mxman *mxman) +{ + void *start_dram; + size_t size_dram = MX_DRAM_SIZE; + struct scsc_mif_abs *mif; + struct fwhdr *fwhdr = &mxman->fwhdr; + bool fwhdr_parsed_ok; + void *start_mifram_heap; + u32 length_mifram_heap; + void *start_mifram_heap2; + u32 length_mifram_heap2; + int r; + + if (reset_failed) { + struct timeval tval = ns_to_timeval(reset_failed_time); + + SCSC_TAG_ERR(MXMAN, "previous reset failed at [%6lu.%06ld], ignoring\n", tval.tv_sec, tval.tv_usec); + return -EIO; + } + + (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown"); + + /* If the option is set to skip header, we must allow unidentified f/w */ + if (skip_header) { + SCSC_TAG_INFO(MXMAN, "Ignoring firmware header block\n"); + allow_unidentified_firmware = true; + } + + mif = scsc_mx_get_mif_abs(mxman->mx); + start_dram = mif->map(mif, &size_dram); + + if (!start_dram) { + SCSC_TAG_ERR(MXMAN, "Error allocating dram\n"); + return -ENOMEM; + } + + SCSC_TAG_INFO(MXMAN, "Allocated %zu bytes\n", size_dram); + +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_run) + allow_unidentified_firmware = true; + /* Set up chv arguments. */ + +#endif + + mxman->start_dram = start_dram; + + r = fw_init(mxman, start_dram, size_dram, &fwhdr_parsed_ok); + if (r) { + SCSC_TAG_ERR(MXMAN, "fw_init() failed\n"); + mif->unmap(mif, mxman->start_dram); + return r; + } + + /* set up memory protection (read only) from start_dram to start_dram+fw_length + * rounding up the size if required + */ + start_mifram_heap = (char *)start_dram + fwhdr->fw_runtime_length; + length_mifram_heap = MX_DRAM_SIZE_SECTION_1 - fwhdr->fw_runtime_length; + + + start_mifram_heap2 = (char *)start_dram + MX_DRAM_SIZE_SECTION_2; + + /* ABox reserved at end so adjust length - round to multiple of PAGE_SIZE */ + length_mifram_heap2 = MX_DRAM_SIZE_SECTION_2 - + ((sizeof(struct scsc_bt_audio_abox) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)); + + miframman_init(scsc_mx_get_ramman(mxman->mx), start_mifram_heap, length_mifram_heap, start_dram); + miframman_init(scsc_mx_get_ramman2(mxman->mx), start_mifram_heap2, length_mifram_heap2, start_mifram_heap2); + miframabox_init(scsc_mx_get_aboxram(mxman->mx), start_mifram_heap2 + length_mifram_heap2); + mifmboxman_init(scsc_mx_get_mboxman(mxman->mx)); + mifintrbit_init(scsc_mx_get_intrbit(mxman->mx), mif); + mxfwconfig_init(mxman->mx); + + /* Initialise transports */ + r = transports_init(mxman); + if (r) { + SCSC_TAG_ERR(MXMAN, "transports_init() failed\n"); + fw_crc_wq_stop(mxman); + mifintrbit_deinit(scsc_mx_get_intrbit(mxman->mx)); + miframman_deinit(scsc_mx_get_ramman(mxman->mx)); + miframman_deinit(scsc_mx_get_ramman2(mxman->mx)); + miframabox_deinit(scsc_mx_get_aboxram(mxman->mx)); + mifmboxman_deinit(scsc_mx_get_mboxman(mxman->mx)); + /* Release the MIF memory resources */ + mif->unmap(mif, mxman->start_dram); + return r; + } + mbox_init(mxman, fwhdr->firmware_entry_point); + init_completion(&mxman->mm_msg_start_ind_completion); + init_completion(&mxman->mm_msg_halt_rsp_completion); + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxman->mx), + MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, + &mxman_message_handler, mxman); + + mxlog_init(scsc_mx_get_mxlog(mxman->mx), mxman->mx, mxman->fw_build_id); +#ifdef CONFIG_SCSC_MXLOGGER + mxlogger_init(mxman->mx, scsc_mx_get_mxlogger(mxman->mx), MXL_POOL_SZ); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Register minimoredump client */ + mini_moredump_client.prv = mxman; + scsc_log_collector_register_client(&mini_moredump_client); +#endif +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* Initialize SMAPPER */ + mifsmapper_init(scsc_mx_get_smapper(mxman->mx), mif); +#endif +#ifdef CONFIG_SCSC_QOS + mifqos_init(scsc_mx_get_qos(mxman->mx), mif); +#endif + +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_run) { + int i; + + u32 *p = (u32 *)((u8 *)start_dram + SCSC_CHV_ARGV_ADDR_OFFSET); + + if (chv_argc == 0) { + /* + * Setup the chv f/w arguments. + * Argument of 0 means run once (driver never set this). + * Argument of 1 means run forever. + */ + SCSC_TAG_INFO(MXMAN, "Setting up CHV arguments: start_dram=%p arg=%p, chv_run=%d\n", start_dram, p, chv_run); + *p++ = 1; /* argc */ + *p++ = chv_run == 1 ? 0 : 1; /* arg */ + } else { + /* Pass separate args */ + *p++ = chv_argc; /* argc */ + SCSC_TAG_INFO(MXMAN, "Setting up additional CHV args: chv_argc = %d\n", chv_argc); + + for (i = 0; i < chv_argc; i++) { + SCSC_TAG_INFO(MXMAN, "Setting up additional CHV args: chv_argv[%d]: *(%p) = 0x%x\n", i, p, (u32)chv_argv[i]); + *p++ = (u32)chv_argv[i]; /* arg */ + } + } + } +#endif + mxproc_create_ctrl_proc_dir(&mxman->mxproc, mxman); + panicmon_init(scsc_mx_get_panicmon(mxman->mx), mxman->mx); + + /* Change state to STARTING to allow coredump as we come out of reset */ + mxman->mxman_state = MXMAN_STATE_STARTING; + + /* release Maxwell from reset */ + r = mif->reset(mif, 0); + if (r) { + reset_failed = true; + SCSC_TAG_INFO(MXMAN, "HW reset deassertion failed\n"); + + /* Save log at point of failure */ +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_schedule_collection(SCSC_LOG_HOST_COMMON, SCSC_LOG_HOST_COMMON_REASON_START); +#else + mx140_log_dump(); +#endif + } + if (fwhdr_parsed_ok) { + r = wait_for_mm_msg_start_ind(mxman); + if (r) { + SCSC_TAG_ERR(MXMAN, "wait_for_MM_START_IND() failed: r=%d\n", r); + print_mailboxes(mxman); + if (skip_mbox0_check) { + SCSC_TAG_ERR(MXMAN, "timeout ignored in skip_mbox0_check mode\n"); + return 0; + } + mxman_stop(mxman); + return r; + } +#ifdef CONFIG_SCSC_MXLOGGER + mxlogger_start(scsc_mx_get_mxlogger(mxman->mx)); +#endif + } else { + msleep(WAIT_FOR_FW_TO_START_DELAY_MS); + } + + return 0; +} + +static bool is_bug_on_enabled(struct scsc_mx *mx) +{ + bool bug_on_enabled; + const struct firmware *firm; + int r; + + if (memdump == 3) + bug_on_enabled = true; + else + bug_on_enabled = false; +#ifdef CONFIG_SCSC_LOG_COLLECTION + (void)firm; /* unused */ + (void)r; /* unused */ + return bug_on_enabled; +#else + /* non SABLE platforms should also follow /sys/wifi/memdump if enabled */ + if (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY) + return bug_on_enabled; + + /* for legacy platforms (including Andorid P) using .memdump.info */ +#if defined(ANDROID_VERSION) && (ANDROID_VERSION >= 90000) + #define MX140_MEMDUMP_INFO_FILE "/data/vendor/conn/.memdump.info" +#else + #define MX140_MEMDUMP_INFO_FILE "/data/misc/conn/.memdump.info" +#endif + + SCSC_TAG_INFO(MX_FILE, "Loading %s file\n", MX140_MEMDUMP_INFO_FILE); + r = mx140_request_file(mx, MX140_MEMDUMP_INFO_FILE, &firm); + if (r) { + SCSC_TAG_WARNING(MX_FILE, "Error Loading %s file %d\n", MX140_MEMDUMP_INFO_FILE, r); + return bug_on_enabled; + } + if (firm->size < sizeof(char)) + SCSC_TAG_WARNING(MX_FILE, "file is too small\n"); + else if (*firm->data == '3') + bug_on_enabled = true; + mx140_release_file(mx, firm); + SCSC_TAG_INFO(MX_FILE, "bug_on_enabled %d\n", bug_on_enabled); + return bug_on_enabled; +#endif //CONFIG_SCSC_LOG_COLLECTION +} + +static void print_panic_code_legacy(u16 code) +{ + u16 tech = code & SCSC_PANIC_TECH_MASK; + u16 origin = code & SCSC_PANIC_ORIGIN_MASK; + + SCSC_TAG_INFO(MXMAN, "Decoding panic code=0x%x:\n", code); + switch (origin) { + default: + SCSC_TAG_INFO(MXMAN, "Failed to identify panic origin\n"); + break; + case SCSC_PANIC_ORIGIN_FW: + SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_ORIGIN_FW\n"); + break; + case SCSC_PANIC_ORIGIN_HOST: + SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_ORIGIN_HOST\n"); + break; + } + + switch (tech) { + default: + SCSC_TAG_INFO(MXMAN, "Failed to identify panic technology\n"); + break; + case SCSC_PANIC_TECH_WLAN: + SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_WLAN\n"); + break; + case SCSC_PANIC_TECH_CORE: + SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_CORE\n"); + break; + case SCSC_PANIC_TECH_BT: + SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_BT\n"); + break; + case SCSC_PANIC_TECH_UNSP: + SCSC_TAG_INFO(MXMAN, "PANIC_TECH_UNSP\n"); + break; + } + SCSC_TAG_INFO(MXMAN, "panic subcode=0x%x\n", code & SCSC_PANIC_SUBCODE_MASK_LEGACY); +} + +static void print_panic_code(u16 code) +{ + u16 origin = code & SCSC_PANIC_ORIGIN_MASK; /* Panic origin (host/fw) */ + u16 subcode = code & SCSC_PANIC_SUBCODE_MASK; /* The panic code */ + + SCSC_TAG_INFO(MXMAN, "Decoding panic code=0x%x:\n", code); + SCSC_TAG_INFO(MXMAN, "panic subcode=0x%x\n", code & SCSC_PANIC_SUBCODE_MASK); + + switch (origin) { + default: + SCSC_TAG_INFO(MXMAN, "Failed to identify panic origin\n"); + break; + case SCSC_PANIC_ORIGIN_FW: + SCSC_TAG_INFO(MXMAN, "WLBT FW PANIC: 0x%02x\n", subcode); + break; + case SCSC_PANIC_ORIGIN_HOST: + SCSC_TAG_INFO(MXMAN, "WLBT HOST detected FW failure, service:\n"); + switch (subcode >> SCSC_SYSERR_HOST_SERVICE_SHIFT) { + case SCSC_SERVICE_ID_WLAN: + SCSC_TAG_INFO(MXMAN, " WLAN\n"); + break; + case SCSC_SERVICE_ID_BT: + SCSC_TAG_INFO(MXMAN, " BT\n"); + break; + case SCSC_SERVICE_ID_ANT: + SCSC_TAG_INFO(MXMAN, " ANT\n"); + break; + case SCSC_SERVICE_ID_CLK20MHZ: + SCSC_TAG_INFO(MXMAN, " CLK20MHZ\n"); + break; + default: + SCSC_TAG_INFO(MXMAN, " Service 0x%x\n", subcode); + break; + } + break; + } +} + +/** + * Print the last panic record collected to aid in post mortem. + * + * Helps when all we have is kernel log showing WLBT failed some time ago + * + * Only prints the R4 record + */ +void mxman_show_last_panic(struct mxman *mxman) +{ + u32 r4_panic_record_length = 0; /* in u32s */ + + /* Any valid panic? */ + if (mxman->scsc_panic_code == 0) + return; + + SCSC_TAG_INFO(MXMAN, "\n\n--- DETAILS OF LAST WLBT FAILURE ---\n\n"); + + switch (mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) { + case SCSC_PANIC_ORIGIN_HOST: + SCSC_TAG_INFO(MXMAN, "Last panic was host induced:\n"); + break; + + case SCSC_PANIC_ORIGIN_FW: + SCSC_TAG_INFO(MXMAN, "Last panic was FW:\n"); + fw_parse_r4_panic_record(mxman->last_panic_rec_r, &r4_panic_record_length); + break; + + default: + SCSC_TAG_INFO(MXMAN, "Last panic unknown origin %d\n", mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK); + break; + } + + print_panic_code(mxman->scsc_panic_code); + + SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : ""); + SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", disable_recovery_handling ? "off" : "on"); + + if (mxman_recovery_disabled()) { + /* Labour the point that a reboot is needed when autorecovery is disabled */ + SCSC_TAG_INFO(MXMAN, "\n\n*** HANDSET REBOOT NEEDED TO RESTART WLAN AND BT ***\n\n"); + } + + SCSC_TAG_INFO(MXMAN, "\n\n--- END DETAILS OF LAST WLBT FAILURE ---\n\n"); +} + +static void process_panic_record(struct mxman *mxman) +{ + u32 *r4_panic_record = NULL; + u32 *m4_panic_record = NULL; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + u32 *m4_1_panic_record = NULL; +#endif + u32 r4_panic_record_length = 0; /* in u32s */ + u32 m4_panic_record_length = 0; /* in u32s */ +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + u32 m4_1_panic_record_length = 0; /* in u32s */ +#endif + bool r4_panic_record_ok = false; + bool m4_panic_record_ok = false; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + bool m4_1_panic_record_ok = false; +#endif + bool r4_sympathetic_panic_flag = false; + bool m4_sympathetic_panic_flag = false; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + bool m4_1_sympathetic_panic_flag = false; +#endif + + /* some configurable delay before accessing the panic record */ + msleep(panic_record_delay); + /* + * Check if the panic was trigered by MX and set the subcode if so. + */ + if ((mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) == SCSC_PANIC_ORIGIN_FW) { + if (mxman->fwhdr.r4_panic_record_offset) { + r4_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.r4_panic_record_offset); + r4_panic_record_ok = fw_parse_r4_panic_record(r4_panic_record, &r4_panic_record_length); + } else { + SCSC_TAG_INFO(MXMAN, "R4 panic record doesn't exist in the firmware header\n"); + } + if (mxman->fwhdr.m4_panic_record_offset) { + m4_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.m4_panic_record_offset); + m4_panic_record_ok = fw_parse_m4_panic_record(m4_panic_record, &m4_panic_record_length); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + } else if (mxman->fwhdr.m4_1_panic_record_offset) { + m4_1_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.m4_1_panic_record_offset); + m4_1_panic_record_ok = fw_parse_m4_panic_record(m4_panic_record, &m4_1_panic_record_length); +#endif + } else { + SCSC_TAG_INFO(MXMAN, "M4 panic record doesn't exist in the firmware header\n"); + } + + /* Extract and print the panic code */ + switch (r4_panic_record_length) { + default: + SCSC_TAG_WARNING(MXMAN, "Bad panic record length/subversion\n"); + break; + case SCSC_R4_V2_MINOR_52: + if (r4_panic_record_ok) + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK_LEGACY & r4_panic_record[2]; + else if (m4_panic_record_ok) + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK_LEGACY & m4_panic_record[2]; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + else if (m4_1_panic_record_ok) + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK_LEGACY & m4_1_panic_record[2]; +#endif + /* Set unspecified technology for now */ + mxman->scsc_panic_code |= SCSC_PANIC_TECH_UNSP; + print_panic_code_legacy(mxman->scsc_panic_code); + break; + case SCSC_R4_V2_MINOR_53: + if (r4_panic_record_ok) { + /* Save the last R4 panic record for future display */ + BUG_ON(sizeof(mxman->last_panic_rec_r) < SCSC_R4_V2_MINOR_53 * sizeof(u32)); + memcpy((u8 *)mxman->last_panic_rec_r, (u8 *)r4_panic_record, SCSC_R4_V2_MINOR_53 * sizeof(u32)); + mxman->last_panic_rec_sz = r4_panic_record_length; + + r4_sympathetic_panic_flag = fw_parse_get_r4_sympathetic_panic_flag(r4_panic_record); + SCSC_TAG_INFO(MXMAN, "r4_panic_record_ok=%d r4_sympathetic_panic_flag=%d\n", + r4_panic_record_ok, + r4_sympathetic_panic_flag + ); + if (r4_sympathetic_panic_flag == false) { + /* process R4 record */ + SCSC_TAG_INFO(MXMAN, "process R4 record\n"); + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & r4_panic_record[3]; + print_panic_code(mxman->scsc_panic_code); + break; + } + } + if (m4_panic_record_ok) { + m4_sympathetic_panic_flag = fw_parse_get_m4_sympathetic_panic_flag(m4_panic_record); + SCSC_TAG_INFO(MXMAN, "m4_panic_record_ok=%d m4_sympathetic_panic_flag=%d\n", + m4_panic_record_ok, + m4_sympathetic_panic_flag + ); + if (m4_sympathetic_panic_flag == false) { + /* process M4 record */ + SCSC_TAG_INFO(MXMAN, "process M4 record\n"); + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & m4_panic_record[3]; + } else if (r4_panic_record_ok) { + /* process R4 record */ + SCSC_TAG_INFO(MXMAN, "process R4 record\n"); + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & r4_panic_record[3]; + } + print_panic_code(mxman->scsc_panic_code); + } +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT /* this is wrong but not sure what is "right" */ +/* "sympathetic panics" are not really a thing on the Neus architecture unless */ +/* generated by the host */ + if (m4_1_panic_record_ok) { + m4_1_sympathetic_panic_flag = fw_parse_get_m4_sympathetic_panic_flag(m4_panic_record); + SCSC_TAG_INFO(MXMAN, "m4_1_panic_record_ok=%d m4_1_sympathetic_panic_flag=%d\n", + m4_1_panic_record_ok, + m4_1_sympathetic_panic_flag + ); + if (m4_1_sympathetic_panic_flag == false) { + /* process M4 record */ + SCSC_TAG_INFO(MXMAN, "process M4_1 record\n"); + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & m4_1_panic_record[3]; + } else if (r4_panic_record_ok) { + /* process R4 record */ + SCSC_TAG_INFO(MXMAN, "process R4 record\n"); + mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & r4_panic_record[3]; + } + print_panic_code(mxman->scsc_panic_code); + } +#endif + break; + } + } +} + +#define MAX_UHELP_TMO_MS 20000 +/* + * workqueue thread + */ +static void mxman_failure_work(struct work_struct *work) +{ + struct mxman *mxman = container_of(work, struct mxman, failure_work); + struct srvman *srvman; + struct scsc_mx *mx = mxman->mx; + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx); + int used = 0, r = 0; + +#ifdef CONFIG_ANDROID + wake_lock(&mxman->recovery_wake_lock); +#endif + + slsi_kic_system_event(slsi_kic_system_event_category_error, + slsi_kic_system_events_subsystem_crashed, GFP_KERNEL); + + blocking_notifier_call_chain(&firmware_chain, SCSC_FW_EVENT_FAILURE, NULL); + + SCSC_TAG_INFO(MXMAN, "Complete mm_msg_start_ind_completion\n"); + complete(&mxman->mm_msg_start_ind_completion); + mutex_lock(&mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(mxman->mx); + + if (mxman->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STARTING) { + SCSC_TAG_WARNING(MXMAN, "Not in started state: mxman->mxman_state=%d\n", mxman->mxman_state); +#ifdef CONFIG_ANDROID + wake_unlock(&mxman->recovery_wake_lock); +#endif + mutex_unlock(&mxman->mxman_mutex); + return; + } + + /** + * Set error on mxlog and unregister mxlog msg-handlers. + * mxlog ISR and kthread will ignore further messages + * but mxlog_thread is NOT stopped here. + */ + mxlog_transport_set_error(scsc_mx_get_mxlog_transport(mx)); + mxlog_release(scsc_mx_get_mxlog(mx)); + /* unregister channel handler */ + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, + NULL, NULL); + mxmgmt_transport_set_error(scsc_mx_get_mxmgmt_transport(mx)); + srvman_set_error(srvman); + fw_crc_wq_stop(mxman); + + mxman->mxman_state = mxman->mxman_next_state; + + if (mxman->mxman_state != MXMAN_STATE_FAILED + && mxman->mxman_state != MXMAN_STATE_FREEZED) { + WARN_ON(mxman->mxman_state != MXMAN_STATE_FAILED + && mxman->mxman_state != MXMAN_STATE_FREEZED); + SCSC_TAG_ERR(MXMAN, "Bad state=%d\n", mxman->mxman_state); +#ifdef CONFIG_ANDROID + wake_unlock(&mxman->recovery_wake_lock); +#endif + mutex_unlock(&mxman->mxman_mutex); + return; + } + /* Signal panic to r4 and m4 processors */ + SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_R4\n"); + mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_R4, SCSC_MIF_ABS_TARGET_R4); /* SCSC_MIFINTR_TARGET_R4 */ +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4\n"); + mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4, SCSC_MIF_ABS_TARGET_M4); /* SCSC_MIFINTR_TARGET_M4 */ + SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4_1\n"); + mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4_1, SCSC_MIF_ABS_TARGET_M4_1); /* SCSC_MIFINTR_TARGET_M4 */ +#else + SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4\n"); + mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4, SCSC_MIF_ABS_TARGET_M4); /* SCSC_MIFINTR_TARGET_M4 */ +#endif + + srvman_freeze_services(srvman); + if (mxman->mxman_state == MXMAN_STATE_FAILED) { + mxman->last_panic_time = local_clock(); + process_panic_record(mxman); + SCSC_TAG_INFO(MXMAN, "Trying to schedule coredump\n"); + SCSC_TAG_INFO(MXMAN, "scsc_release %d.%d.%d.%d\n", + SCSC_RELEASE_PRODUCT, + SCSC_RELEASE_ITERATION, + SCSC_RELEASE_CANDIDATE, + SCSC_RELEASE_POINT); + SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on"); +#ifdef CONFIG_SCSC_WLBTD + scsc_wlbtd_get_and_print_build_type(); +#endif + + /* schedule coredump and wait for it to finish */ + if (disable_auto_coredump) { + SCSC_TAG_INFO(MXMAN, "Driver automatic coredump disabled, not launching coredump helper\n"); + } else { + /** + * Releasing mxman_mutex here gives way to any + * eventually running resume process while waiting for + * the usermode helper subsystem to be resurrected, + * since this last will be re-enabled right at the end + * of the resume process itself. + */ + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, + "waiting up to %dms for usermode_helper subsystem.\n", + MAX_UHELP_TMO_MS); + /* Waits for the usermode_helper subsytem to be re-enabled. */ + if (usermodehelper_read_lock_wait(msecs_to_jiffies(MAX_UHELP_TMO_MS))) { + /** + * Release immediately the rwsem on usermode_helper + * enabled since we anyway already hold a wakelock here + */ + usermodehelper_read_unlock(); + /** + * We claim back the mxman_mutex immediately to avoid anyone + * shutting down the chip while we are dumping the coredump. + */ + mutex_lock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Invoking coredump helper\n"); + slsi_kic_system_event(slsi_kic_system_event_category_recovery, + slsi_kic_system_events_coredump_in_progress, + GFP_KERNEL); +#ifdef CONFIG_SCSC_WLBTD + /* we can safely call call_wlbtd as we are + * in workqueue context + */ +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Collect mxlogger logs */ + scsc_log_collector_schedule_collection(SCSC_LOG_FW_PANIC, mxman->scsc_panic_code); +#else + r = call_wlbtd(SCSC_SCRIPT_MOREDUMP); +#endif +#else + r = coredump_helper(); +#endif + if (r >= 0) { + slsi_kic_system_event(slsi_kic_system_event_category_recovery, + slsi_kic_system_events_coredump_done, GFP_KERNEL); + } + + used = snprintf(panic_record_dump, + PANIC_RECORD_DUMP_BUFFER_SZ, + "RF HW Ver: 0x%X\n", mxman->rf_hw_ver); + used += snprintf(panic_record_dump + used, + PANIC_RECORD_DUMP_BUFFER_SZ - used, + "SCSC Panic Code:: 0x%X\n", mxman->scsc_panic_code); + used += snprintf(panic_record_dump + used, + PANIC_RECORD_DUMP_BUFFER_SZ - used, + "SCSC Last Panic Time:: %lld\n", mxman->last_panic_time); + panic_record_dump_buffer("r4", mxman->last_panic_rec_r, + mxman->last_panic_rec_sz, + panic_record_dump + used, + PANIC_RECORD_DUMP_BUFFER_SZ - used); + + /* Print the host code/reason again so it's near the FW panic + * record in the kernel log + */ + print_panic_code(mxman->scsc_panic_code); + SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : ""); + + blocking_notifier_call_chain(&firmware_chain, + SCSC_FW_EVENT_MOREDUMP_COMPLETE, + &panic_record_dump); + } else { + SCSC_TAG_INFO(MXMAN, + "timed out waiting for usermode_helper. Skipping coredump.\n"); + mutex_lock(&mxman->mxman_mutex); + } + } + + if (is_bug_on_enabled(mx)) { + SCSC_TAG_ERR(MX_FILE, "Deliberately panic the kernel due to WLBT firmware failure!\n"); + SCSC_TAG_ERR(MX_FILE, "calling BUG_ON(1)\n"); + BUG_ON(1); + } + /* Clean up the MIF following error handling */ + if (mif->mif_cleanup && mxman_recovery_disabled()) + mif->mif_cleanup(mif); + } + SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", + mxman_recovery_disabled() ? "off" : "on"); + + if (!mxman_recovery_disabled()) + srvman_clear_error(srvman); + mutex_unlock(&mxman->mxman_mutex); + if (!mxman_recovery_disabled()) { + SCSC_TAG_INFO(MXMAN, "Calling srvman_unfreeze_services\n"); + srvman_unfreeze_services(srvman, mxman->scsc_panic_code); + if (scsc_mx_module_reset() < 0) + SCSC_TAG_INFO(MXMAN, "failed to call scsc_mx_module_reset\n"); + atomic_inc(&mxman->recovery_count); + } + + /** + * If recovery is disabled and an scsc_mx_service_open has been hold up, + * release it, rather than wait for the recovery_completion to timeout. + */ + if (mxman_recovery_disabled()) + complete(&mxman->recovery_completion); + +#ifdef CONFIG_ANDROID + wake_unlock(&mxman->recovery_wake_lock); +#endif +} + +static void failure_wq_init(struct mxman *mxman) +{ + mxman->failure_wq = create_singlethread_workqueue("failure_wq"); + INIT_WORK(&mxman->failure_work, mxman_failure_work); +} + +static void failure_wq_stop(struct mxman *mxman) +{ + cancel_work_sync(&mxman->failure_work); + flush_workqueue(mxman->failure_wq); +} + +static void failure_wq_deinit(struct mxman *mxman) +{ + failure_wq_stop(mxman); + destroy_workqueue(mxman->failure_wq); +} + +static void failure_wq_start(struct mxman *mxman) +{ + if (disable_error_handling) + SCSC_TAG_INFO(MXMAN, "error handling disabled\n"); + else + queue_work(mxman->failure_wq, &mxman->failure_work); +} + +static void print_mailboxes(struct mxman *mxman) +{ + struct scsc_mif_abs *mif; + struct mifmboxman *mboxman; + int i; + + mif = scsc_mx_get_mif_abs(mxman->mx); + mboxman = scsc_mx_get_mboxman(mxman->mx); + + SCSC_TAG_INFO(MXMAN, "Printing mailbox values:\n"); + for (i = 0; i < MIFMBOX_NUM; i++) + SCSC_TAG_INFO(MXMAN, "MBOX_%d: 0x%x\n", i, *mifmboxman_get_mbox_ptr(mboxman, mif, i)); +} +#ifdef CONFIG_SCSC_WLBTD +static void wlbtd_work_func(struct work_struct *work) +{ + /* require sleep-able workqueue to run successfully */ +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Collect mxlogger logs */ + /* Extend to scsc_log_collector_collect() if required */ +#else + call_wlbtd(SCSC_SCRIPT_LOGGER_DUMP); +#endif +} + +static void wlbtd_wq_init(struct mxman *mx) +{ + INIT_WORK(&wlbtd_work, wlbtd_work_func); +} + +static void wlbtd_wq_deinit(struct mxman *mx) +{ + /* flush and block until work is complete */ + flush_work(&wlbtd_work); +} +#endif +/* + * Check for matching f/w and h/w + * + * Returns 0: f/w and h/w match + * 1: f/w and h/w mismatch, try the next config + * -ve fatal error + */ +static int mxman_hw_ver_check(struct mxman *mxman) +{ + if (mx140_file_supported_hw(mxman->mx, mxman->rf_hw_ver)) + return 0; + else + return 1; +} + +/* + * Select the f/w version to load next + */ +static int mxman_select_next_fw(struct mxman *mxman) +{ + return mx140_file_select_fw(mxman->mx, mxman->rf_hw_ver); +} + +/* Boot MX140 with given f/w */ +static int __mxman_open(struct mxman *mxman) +{ + int r; + struct srvman *srvman; + + mx140_basedir_file(mxman->mx); + + mutex_lock(&mxman->mxman_mutex); + if (mxman->scsc_panic_code) { + SCSC_TAG_INFO(MXMAN, "Previously recorded crash panic code: scsc_panic_code=0x%x\n", mxman->scsc_panic_code); + SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : ""); + print_panic_code(mxman->scsc_panic_code); + } + SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on"); + srvman = scsc_mx_get_srvman(mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return -EINVAL; + } + + /* Reset the state after a previous crash during f/w boot */ + if (mxman->mxman_state == MXMAN_STATE_STARTING) + mxman->mxman_state = MXMAN_STATE_STOPPED; + + if (mxman->mxman_state == MXMAN_STATE_STARTED) { + /* if in the STARTED state there MUST already be some users */ + if (WARN_ON(!mxman->users)) { + SCSC_TAG_ERR(MXMAN, "ERROR mxman->mxman_state=%d users=%d\n", mxman->mxman_state, mxman->users); + mutex_unlock(&mxman->mxman_mutex); + return -EINVAL; + } + mxman->users++; + SCSC_TAG_INFO(MXMAN, "Already opened: users=%d\n", mxman->users); + mxman_print_versions(mxman); + mutex_unlock(&mxman->mxman_mutex); + return 0; + } else if (mxman->mxman_state == MXMAN_STATE_STOPPED) { + r = mxman_start(mxman); + if (r) { + SCSC_TAG_ERR(MXMAN, "maxwell_manager_start() failed r=%d users=%d\n", r, mxman->users); + mutex_unlock(&mxman->mxman_mutex); + return r; + } + mxman->users++; + mxman->mxman_state = MXMAN_STATE_STARTED; + mutex_unlock(&mxman->mxman_mutex); + /* Start mxlogger */ + if (!disable_logger) { + static char mxlbin[128]; + + r = mx140_exe_path(NULL, mxlbin, sizeof(mxlbin), "mx_logger.sh"); + if (r) { + /* Not found */ + SCSC_TAG_ERR(MXMAN, "mx_logger.sh path error\n"); + } else { + /* Launch it */ + _mx_exec(mxlbin, UMH_WAIT_EXEC); + } + } + return 0; + } + WARN_ON(mxman->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STOPPED); + SCSC_TAG_ERR(MXMAN, "Bad state: mxman->mxman_state=%d\n", mxman->mxman_state); + mutex_unlock(&mxman->mxman_mutex); + return -EIO; +} + +int mxman_open(struct mxman *mxman) +{ + int r; + int try = 0; + + struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx); + + for (try = 0; try < 2; try++) { + /* Boot WLBT. This will determine the h/w version */ + r = __mxman_open(mxman); + if (r) + return r; + + /* On retries, restore USBPLL owner as WLBT */ + if (try > 0 && mif->mif_restart) + mif->mif_restart(mif); + + /* Check the h/w and f/w versions are compatible */ + r = mxman_hw_ver_check(mxman); + if (r > 0) { + /* Not compatible, so try next f/w */ + SCSC_TAG_INFO(MXMAN, "Incompatible h/w 0x%04x vs f/w, close and try next\n", mxman->rf_hw_ver); + + /* Temporarily return USBPLL owner to AP to keep USB alive */ + if (mif->mif_cleanup) + mif->mif_cleanup(mif); + + /* Stop WLBT */ + mxman_close(mxman); + + /* Select the new f/w for this hw ver */ + mxman_select_next_fw(mxman); + } else + break; /* Running or given up */ + } + + /* If we have stored FM radio parameters, deliver them to FW now */ + if (r == 0 && mxman->fm_params_pending) { + SCSC_TAG_INFO(MXMAN, "Send pending FM params\n"); + mxman_fm_set_params(&mxman->fm_params); + } + + return r; +} + +static void mxman_stop(struct mxman *mxman) +{ + int r; + struct scsc_mif_abs *mif; + + SCSC_TAG_INFO(MXMAN, "\n"); + + /* If reset is failed, prevent new resets */ + if (reset_failed) { + struct timeval tval = ns_to_timeval(reset_failed_time); + + SCSC_TAG_ERR(MXMAN, "previous reset failed at [%6lu.%06ld], ignoring\n", tval.tv_sec, tval.tv_usec); + return; + } + + (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown"); + + mxproc_remove_ctrl_proc_dir(&mxman->mxproc); + + /* Shutdown the hardware */ + mif = scsc_mx_get_mif_abs(mxman->mx); + r = mif->reset(mif, 1); + if (r) { + reset_failed_time = local_clock(); + SCSC_TAG_INFO(MXMAN, "HW reset failed\n"); + reset_failed = true; + + /* Save log at point of failure */ +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_schedule_collection(SCSC_LOG_HOST_COMMON, SCSC_LOG_HOST_COMMON_REASON_STOP); +#else + mx140_log_dump(); +#endif + } + + panicmon_deinit(scsc_mx_get_panicmon(mxman->mx)); + transports_release(mxman); + mxfwconfig_unload(mxman->mx); + + mxlog_release(scsc_mx_get_mxlog(mxman->mx)); + /* unregister channel handler */ + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, + NULL, NULL); + fw_crc_wq_stop(mxman); + + /* Unitialise components (they may perform some checks - e.g. all memory freed) */ + mxfwconfig_deinit(mxman->mx); + mifintrbit_deinit(scsc_mx_get_intrbit(mxman->mx)); + miframman_deinit(scsc_mx_get_ramman(mxman->mx)); + miframman_deinit(scsc_mx_get_ramman2(mxman->mx)); + miframabox_deinit(scsc_mx_get_aboxram(mxman->mx)); + mifmboxman_deinit(scsc_mx_get_mboxman(mxman->mx)); +#ifdef CONFIG_SCSC_SMAPPER + mifsmapper_deinit(scsc_mx_get_smapper(mxman->mx)); +#endif +#ifdef CONFIG_SCSC_QOS + mifqos_deinit(scsc_mx_get_qos(mxman->mx)); +#endif + /* Release the MIF memory resources */ + mif->unmap(mif, mxman->start_dram); +} + +void mxman_close(struct mxman *mxman) +{ + int r; + struct srvman *srvman; + + mutex_lock(&mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return; + } + + SCSC_TAG_INFO(MXMAN, "\n"); + + if (mxman->mxman_state == MXMAN_STATE_STARTED) { + if (WARN_ON(!mxman->users)) { + SCSC_TAG_ERR(MXMAN, "ERROR users=%d\n", mxman->users); + mutex_unlock(&mxman->mxman_mutex); + return; + } + mxman->users--; + if (mxman->users) { + SCSC_TAG_INFO(MXMAN, "Current number of users=%d\n", mxman->users); + mutex_unlock(&mxman->mxman_mutex); + return; + } +#ifdef CONFIG_SCSC_MXLOGGER +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Unregister minimoredump client */ + scsc_log_collector_unregister_client(&mini_moredump_client); +#endif + /** + * Deinit mxlogger on last service stop...BUT before asking for HALT + */ + mxlogger_deinit(mxman->mx, scsc_mx_get_mxlogger(mxman->mx)); +#endif + /* + * Ask the subsystem to stop (MM_STOP_REQ), and wait + * for response (MM_STOP_RSP). + */ + r = send_mm_msg_stop_blocking(mxman); + if (r) + SCSC_TAG_ERR(MXMAN, "send_mm_msg_stop_blocking failed: r=%d\n", r); + + mxman_stop(mxman); + mxman->mxman_state = MXMAN_STATE_STOPPED; + mutex_unlock(&mxman->mxman_mutex); + } else if (mxman->mxman_state == MXMAN_STATE_FAILED) { + if (WARN_ON(!mxman->users)) + SCSC_TAG_ERR(MXMAN, "ERROR users=%d\n", mxman->users); + + mxman->users--; + if (mxman->users) { + SCSC_TAG_INFO(MXMAN, "Current number of users=%d\n", mxman->users); + mutex_unlock(&mxman->mxman_mutex); + return; + } +#ifdef CONFIG_SCSC_MXLOGGER +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Unregister minimoredump client */ + scsc_log_collector_unregister_client(&mini_moredump_client); +#endif + /** + * Deinit mxlogger on last service stop...BUT before asking for HALT + */ + mxlogger_deinit(mxman->mx, scsc_mx_get_mxlogger(mxman->mx)); +#endif + + mxman_stop(mxman); + mxman->mxman_state = MXMAN_STATE_STOPPED; + mutex_unlock(&mxman->mxman_mutex); + complete(&mxman->recovery_completion); + } else { + WARN_ON(mxman->mxman_state != MXMAN_STATE_STARTED); + SCSC_TAG_ERR(MXMAN, "Bad state: mxman->mxman_state=%d\n", mxman->mxman_state); + mutex_unlock(&mxman->mxman_mutex); + return; + } +} + +void mxman_syserr(struct mxman *mxman, struct mx_syserr *syserr) +{ +} + +void mxman_fail(struct mxman *mxman, u16 scsc_panic_code, const char *reason) +{ + SCSC_TAG_WARNING(MXMAN, "WLBT FW failure\n"); + + /* The STARTING state allows a crash during firmware boot to be handled */ + if (mxman->mxman_state == MXMAN_STATE_STARTED || mxman->mxman_state == MXMAN_STATE_STARTING) { + mxman->mxman_next_state = MXMAN_STATE_FAILED; + mxman->scsc_panic_code = scsc_panic_code; + strlcpy(mxman->failure_reason, reason, sizeof(mxman->failure_reason)); + /* If recovery is disabled, don't let it be + * re-enabled from now on. Device must reboot + */ + if (mxman_recovery_disabled()) + disable_recovery_until_reboot = true; + + failure_wq_start(mxman); + } else { + SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state); + } +} + +void mxman_freeze(struct mxman *mxman) +{ + SCSC_TAG_WARNING(MXMAN, "WLBT FW frozen\n"); + + if (mxman->mxman_state == MXMAN_STATE_STARTED) { + mxman->mxman_next_state = MXMAN_STATE_FREEZED; + failure_wq_start(mxman); + } else { + SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state); + } +} + +void mxman_init(struct mxman *mxman, struct scsc_mx *mx) +{ + mxman->mx = mx; + mxman->suspended = 0; +#ifdef CONFIG_SCSC_FM + mxman->on_halt_ldos_on = 0; + mxman->fm_params_pending = 0; +#endif + fw_crc_wq_init(mxman); + failure_wq_init(mxman); +#ifdef CONFIG_SCSC_WLBTD + wlbtd_wq_init(mxman); +#endif + mutex_init(&mxman->mxman_mutex); + init_completion(&mxman->recovery_completion); +#ifdef CONFIG_ANDROID + wake_lock_init(&mxman->recovery_wake_lock, WAKE_LOCK_SUSPEND, "mxman_recovery"); +#endif + + /* set the initial state */ + mxman->mxman_state = MXMAN_STATE_STOPPED; + (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown"); + memcpy(saved_fw_build_id, mxman->fw_build_id, + sizeof(saved_fw_build_id)); + (void)snprintf(mxman->fw_ttid, sizeof(mxman->fw_ttid), "unknown"); + mxproc_create_info_proc_dir(&mxman->mxproc, mxman); + active_mxman = mxman; + +#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 + mxman_create_sysfs_memdump(); +#endif + scsc_lerna_init(); +} + +void mxman_deinit(struct mxman *mxman) +{ + scsc_lerna_deinit(); +#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 + mxman_destroy_sysfs_memdump(); +#endif + active_mxman = NULL; + mxproc_remove_info_proc_dir(&mxman->mxproc); + fw_crc_wq_deinit(mxman); + failure_wq_deinit(mxman); +#ifdef CONFIG_SCSC_WLBTD + wlbtd_wq_deinit(mxman); +#endif +#ifdef CONFIG_ANDROID + wake_lock_destroy(&mxman->recovery_wake_lock); +#endif + mutex_destroy(&mxman->mxman_mutex); +} + +int mxman_force_panic(struct mxman *mxman) +{ + struct srvman *srvman; + struct ma_msg_packet message = { .ma_msg = MM_FORCE_PANIC }; + + mutex_lock(&mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return -EINVAL; + } + + if (mxman->mxman_state == MXMAN_STATE_STARTED) { + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); + mutex_unlock(&mxman->mxman_mutex); + return 0; + } + mutex_unlock(&mxman->mxman_mutex); + return -EINVAL; +} + +int mxman_suspend(struct mxman *mxman) +{ + struct srvman *srvman; + struct ma_msg_packet message = { .ma_msg = MM_HOST_SUSPEND }; + int ret; + + SCSC_TAG_INFO(MXMAN, "\n"); + + mutex_lock(&mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return 0; + } + + /* Call Service suspend callbacks */ + ret = srvman_suspend_services(srvman); + if (ret) { + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Service Suspend canceled - ignore %d\n", ret); + return ret; + } + + if (mxman->mxman_state == MXMAN_STATE_STARTED) { + SCSC_TAG_INFO(MXMAN, "MM_HOST_SUSPEND\n"); +#ifdef CONFIG_SCSC_MXLOGGER + mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_SUSPEND); +#endif + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); + mxman->suspended = 1; + atomic_inc(&mxman->suspend_count); + } + mutex_unlock(&mxman->mxman_mutex); + return 0; +} + +#ifdef CONFIG_SCSC_FM +void mxman_fm_on_halt_ldos_on(void) +{ + /* Should always be an active mxman unless module is unloaded */ + if (!active_mxman) { + SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); + return; + } + + active_mxman->on_halt_ldos_on = 1; + + /* FM status to pass into FW at next FW init, + * by which time driver context is lost. + * This is required, because now WLBT gates + * LDOs with TCXO instead of leaving them + * always on, to save power in deep sleep. + * FM, however, needs them always on. So + * we need to know when to leave the LDOs + * alone at WLBT boot. + */ + is_fm_on = 1; +} + +void mxman_fm_on_halt_ldos_off(void) +{ + /* Should always be an active mxman unless module is unloaded */ + if (!active_mxman) { + SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); + return; + } + + /* Newer FW no longer need set shared LDOs + * always-off at WLBT halt, as TCXO gating + * has the same effect. But pass the "off" + * request for backwards compatibility + * with old FW. + */ + active_mxman->on_halt_ldos_on = 0; + is_fm_on = 0; +} + +/* Update parameters passed to WLBT FM */ +int mxman_fm_set_params(struct wlbt_fm_params *params) +{ + /* Should always be an active mxman unless module is unloaded */ + if (!active_mxman) { + SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); + return -EINVAL; + } + + /* Params are no longer valid (FM stopped) */ + if (!params) { + active_mxman->fm_params_pending = 0; + SCSC_TAG_INFO(MXMAN, "FM params cleared\n"); + return 0; + } + + /* Once set the value needs to be remembered for each time WLBT starts */ + active_mxman->fm_params = *params; + active_mxman->fm_params_pending = 1; + + if (send_fm_params_to_active_mxman(params)) { + SCSC_TAG_INFO(MXMAN, "FM params sent to FW\n"); + return 0; + } + + /* Stored for next time FW is up */ + SCSC_TAG_INFO(MXMAN, "FM params stored\n"); + + return -EAGAIN; +} +#endif + +void mxman_resume(struct mxman *mxman) +{ + struct srvman *srvman; + struct ma_msg_packet message = { .ma_msg = MM_HOST_RESUME }; + int ret; + + SCSC_TAG_INFO(MXMAN, "\n"); + + mutex_lock(&mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); + return; + } + + if (mxman->mxman_state == MXMAN_STATE_STARTED) { + SCSC_TAG_INFO(MXMAN, "MM_HOST_RESUME\n"); +#ifdef CONFIG_SCSC_MXLOGGER + mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_RESUME); +#endif + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); + mxman->suspended = 0; + } + + /* Call Service Resume callbacks */ + ret = srvman_resume_services(srvman); + if (ret) + SCSC_TAG_INFO(MXMAN, "Service Resume error %d\n", ret); + + mutex_unlock(&mxman->mxman_mutex); +} + +static void _mx_exec_cleanup(struct subprocess_info *sp_info) +{ + if (!sp_info) { + SCSC_TAG_ERR(MXMAN, "sp_info is null\n"); + return; + } + if (!sp_info->argv) { + SCSC_TAG_ERR(MXMAN, "argv is null\n"); + return; + } + + SCSC_TAG_INFO(MXMAN, "0x%p\n", sp_info->argv); + argv_free(sp_info->argv); +} + +/* prog - full path to programme + * wait_exec - one of UMH_WAIT_EXEC, UMH_WAIT_PROC, UMH_KILLABLE, UMH_NO_WAIT + */ +static int _mx_exec(char *prog, int wait_exec) +{ + /** + * ENV vars ANDROID_ROOT and ANDROID_DATA are needed to have + * the UMH spawned process working properly (as an example finding + * Timezones files) + */ + static char const *envp[] = { "HOME=/", "PATH=/sbin:/system/sbin:/system/bin:/system/xbin:/vendor/bin:/vendor/xbin", + "ANDROID_ROOT=/system", "ANDROID_DATA=/data", NULL }; + char **argv; + char argv_str[STRING_BUFFER_MAX_LENGTH]; + int argc, result, len; + struct subprocess_info *sp_info; + + len = snprintf(argv_str, STRING_BUFFER_MAX_LENGTH, "%s", prog); + if (len >= STRING_BUFFER_MAX_LENGTH) { + /* snprintf() returns a value of buffer size of greater if it had to truncate the format string. */ + SCSC_TAG_ERR(MXMAN, + "exec string buffer insufficient (buffer size=%d, actual string=%d)\n", + STRING_BUFFER_MAX_LENGTH, len); + return -E2BIG; + } + + /* Kernel library function argv_split() will allocate memory for argv. */ + argc = 0; + argv = argv_split(GFP_KERNEL, argv_str, &argc); + if (!argv) { + SCSC_TAG_ERR(MXMAN, "failed to allocate argv for userspace helper\n"); + return -ENOMEM; + } + + /* Check the argument count just to avoid future abuse */ + if (argc > NUMBER_OF_STRING_ARGS) { + SCSC_TAG_ERR(MXMAN, + "exec string has the wrong number of arguments (has %d, should be %d)\n", + argc, NUMBER_OF_STRING_ARGS); + argv_free(argv); + return -E2BIG; + } + + /* Allocate sp_info and initialise pointers to argv and envp. */ + sp_info = call_usermodehelper_setup(argv[0], argv, (char **)envp, + GFP_KERNEL, NULL, _mx_exec_cleanup, + NULL); + + if (!sp_info) { + SCSC_TAG_ERR(MXMAN, "call_usermodehelper_setup() failed\n"); + argv_free(argv); + return -EIO; + } + + /* Put sp_info into work queue for processing by khelper. */ + SCSC_TAG_INFO(MXMAN, "Launch %s\n", prog); + + result = call_usermodehelper_exec(sp_info, wait_exec); + + if (result != 0) { + /* + * call_usermodehelper_exec() will free sp_info and call any cleanup function + * whether it succeeds or fails, so do not free argv. + */ + if (result == -ENOENT) + SCSC_TAG_ERR(MXMAN, "call_usermodehelper() failed with %d, Executable not found %s'\n", + result, prog); + else + SCSC_TAG_ERR(MXMAN, "call_usermodehelper_exec() failed with %d\n", result); + } + return result; +} + +#if defined(CONFIG_SCSC_PRINTK) && !defined(CONFIG_SCSC_WLBTD) +static int __stat(const char *file) +{ + struct kstat stat; + mm_segment_t fs; + int r; + + fs = get_fs(); + set_fs(get_ds()); + r = vfs_stat(file, &stat); + set_fs(fs); + + return r; +} +#endif + +int mx140_log_dump(void) +{ +#ifdef CONFIG_SCSC_PRINTK + int r; +# ifdef CONFIG_SCSC_WLBTD + r = schedule_work(&wlbtd_work); +# else + char mxlbin[128]; + + r = mx140_exe_path(NULL, mxlbin, sizeof(mxlbin), "mx_logger_dump.sh"); + if (r) { + SCSC_TAG_ERR(MXMAN, "mx_logger_dump.sh path error\n"); + } else { + /* + * Test presence of script before invoking, to suppress + * unnecessary error message if not installed. + */ + r = __stat(mxlbin); + if (r) { + SCSC_TAG_DEBUG(MXMAN, "%s not installed\n", mxlbin); + return r; + } + SCSC_TAG_INFO(MXMAN, "Invoking mx_logger_dump.sh UHM\n"); + r = _mx_exec(mxlbin, UMH_WAIT_EXEC); + if (r) + SCSC_TAG_ERR(MXMAN, "mx_logger_dump.sh err:%d\n", r); + } +# endif /* CONFIG_SCSC_WLBTD */ + return r; +#else + return 0; +#endif +} +EXPORT_SYMBOL(mx140_log_dump); + +bool mxman_recovery_disabled(void) +{ +#ifdef CONFIG_SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE + /* Add option to kill autorecovery, ignoring module parameter + * to work around platform that enables it against our wishes + */ + SCSC_TAG_ERR(MXMAN, "CONFIG_SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE is set\n"); + return true; +#endif + /* If FW has panicked when recovery was disabled, don't allow it to + * be enabled. The horse has bolted. + */ + if (disable_recovery_until_reboot) + return true; + + if (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY) + return disable_recovery_from_memdump_file; + else + return disable_recovery_handling ? true : false; +} +EXPORT_SYMBOL(mxman_recovery_disabled); + +/** + * This returns the last known loaded FW build_id + * even when the fw is NOT running at the time of the request. + * + * It could be used anytime by Android Enhanced Logging + * to query for fw version. + */ +void mxman_get_fw_version(char *version, size_t ver_sz) +{ + /* unavailable only if chip not probed ! */ + snprintf(version, ver_sz, "%s", saved_fw_build_id); +} +EXPORT_SYMBOL(mxman_get_fw_version); + +void mxman_get_driver_version(char *version, size_t ver_sz) +{ + /* IMPORTANT - Do not change the formatting as User space tooling is parsing the string + * to read SAP fapi versions. */ + snprintf(version, ver_sz, "drv_ver: %u.%u.%u.%u", + SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION, SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT); +#ifdef CONFIG_SCSC_WLBTD + scsc_wlbtd_get_and_print_build_type(); +#endif +} +EXPORT_SYMBOL(mxman_get_driver_version); + +int mxman_register_firmware_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&firmware_chain, nb); +} +EXPORT_SYMBOL(mxman_register_firmware_notifier); + +int mxman_unregister_firmware_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&firmware_chain, nb); +} +EXPORT_SYMBOL(mxman_unregister_firmware_notifier); + + +int mxman_lerna_send(struct mxman *mxman, void *message, u32 message_size) +{ + struct srvman *srvman = NULL; + + /* May be called when WLBT is off, so find the context in this case */ + if (!mxman) + mxman = active_mxman; + + if (!active_mxman) { + SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); + return -EINVAL; + } + + if (!message || (message_size == 0)) { + SCSC_TAG_INFO(MXMAN, "No lerna request provided.\n"); + return 0; + } + + mutex_lock(&active_mxman->mxman_mutex); + srvman = scsc_mx_get_srvman(active_mxman->mx); + if (srvman && srvman->error) { + mutex_unlock(&active_mxman->mxman_mutex); + SCSC_TAG_INFO(MXMAN, "Lerna configuration called during error - ignore\n"); + return 0; + } + + if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { + SCSC_TAG_INFO(MXMAN, "MM_LERNA_CONFIG\n"); + mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), + MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, message, + message_size); + mutex_unlock(&active_mxman->mxman_mutex); + return 0; + } + + SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_LERNA_CONFIG msg.\n"); + mutex_unlock(&active_mxman->mxman_mutex); + return -EAGAIN; +} diff --git a/drivers/misc/samsung/scsc/mxman.h b/drivers/misc/samsung/scsc/mxman.h new file mode 100755 index 000000000000..25ac9b4160da --- /dev/null +++ b/drivers/misc/samsung/scsc/mxman.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _MAXWELL_MANAGER_H +#define _MAXWELL_MANAGER_H +#include +#include "fwhdr.h" +#include "mxmgmt_transport.h" +#include "mxproc.h" +#include +#ifdef CONFIG_ANDROID +#include +#endif + +struct mxman; + +void mxman_init(struct mxman *mxman, struct scsc_mx *mx); +void mxman_deinit(struct mxman *mxman); +int mxman_open(struct mxman *mxman); +void mxman_close(struct mxman *mxman); +void mxman_fail(struct mxman *mxman, u16 scsc_panic_code, const char *reason); +void mxman_freeze(struct mxman *mxman); +int mxman_force_panic(struct mxman *mxman); +int mxman_suspend(struct mxman *mxman); +void mxman_resume(struct mxman *mxman); +void mxman_show_last_panic(struct mxman *mxman); + +#ifdef CONFIG_SCSC_FM +void mxman_fm_on_halt_ldos_on(void); +void mxman_fm_on_halt_ldos_off(void); +int mxman_fm_set_params(struct wlbt_fm_params *params); +#endif +int mxman_lerna_send(struct mxman *mxman, void *data, u32 size); + +enum mxman_state { + MXMAN_STATE_STOPPED, + MXMAN_STATE_STARTING, + MXMAN_STATE_STARTED, + MXMAN_STATE_FAILED, + MXMAN_STATE_FREEZED, +}; + +#define SCSC_FAILURE_REASON_LEN 256 + +struct mxman { + struct scsc_mx *mx; + int users; + void *start_dram; + struct workqueue_struct *fw_crc_wq; + struct delayed_work fw_crc_work; + struct workqueue_struct *failure_wq; + struct work_struct failure_work; + char *fw; + u32 fw_image_size; + struct completion mm_msg_start_ind_completion; + struct completion mm_msg_halt_rsp_completion; + struct fwhdr fwhdr; + struct mxconf *mxconf; + enum mxman_state mxman_state; + enum mxman_state mxman_next_state; + struct mutex mxman_mutex; + struct mxproc mxproc; + int suspended; + atomic_t suspend_count; + atomic_t recovery_count; + atomic_t boot_count; + bool check_crc; + char fw_build_id[FW_BUILD_ID_SZ]; /* Defined in SC-505846-SW */ + struct completion recovery_completion; +#ifdef CONFIG_ANDROID + struct wake_lock recovery_wake_lock; +#endif + u32 rf_hw_ver; + u16 scsc_panic_code; + u64 last_panic_time; + u32 last_panic_rec_r[PANIC_RECORD_SIZE]; /* Must be at least SCSC_R4_V2_MINOR_53 */ + u16 last_panic_rec_sz; +#ifdef CONFIG_SCSC_FM + u32 on_halt_ldos_on; +#endif + char failure_reason[SCSC_FAILURE_REASON_LEN]; /* previous failure reason */ + struct wlbt_fm_params fm_params; /* FM freq info */ + int fm_params_pending; /* FM freq info waiting to be delivered to FW */ + + char fw_ttid[FW_TTID_SZ]; /* Defined in SC-505846-SW */ +}; + +void mxman_register_gdb_channel(struct scsc_mx *mx, mxmgmt_channel_handler handler, void *data); +void mxman_send_gdb_channel(struct scsc_mx *mx, void *data, size_t length); + +#ifdef CONFIG_SCSC_CHV_SUPPORT +#define SCSC_CHV_ARGV_ADDR_OFFSET 0x200008 + +extern int chv_run; +#endif + +#define SCSC_SYSERR_HOST_SERVICE_SHIFT 4 + +#endif diff --git a/drivers/misc/samsung/scsc/mxmgmt_transport.c b/drivers/misc/samsung/scsc/mxmgmt_transport.c new file mode 100644 index 000000000000..ec4f95c82b8f --- /dev/null +++ b/drivers/misc/samsung/scsc/mxmgmt_transport.c @@ -0,0 +1,302 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * Maxwell management transport (implementation) + */ + +/** Implements */ +#include "mxmgmt_transport.h" + +/** Uses */ +#include +#include +#include "mxmgmt_transport_format.h" +#include "mifintrbit.h" + +/* Flag that an error has occurred so the I/O thread processing should stop */ +void mxmgmt_transport_set_error(struct mxmgmt_transport *mxmgmt_transport) +{ + SCSC_TAG_WARNING(MXMGT_TRANS, "I/O thread processing is suspended\n"); + + mxmgmt_transport->mxmgmt_thread.block_thread = 1; +} + +/** MIF Interrupt handler for writes made to the AP */ +static void input_irq_handler(int irq, void *data) +{ + struct mxmgmt_transport *mxmgmt_transport = (struct mxmgmt_transport *)data; + struct mxmgmt_thread *th = &mxmgmt_transport->mxmgmt_thread; + struct scsc_mif_abs *mif_abs; + + SCSC_TAG_DEBUG(MXMGT_TRANS, "IN\n"); + /* Clear the interrupt first to ensure we can't possibly miss one */ + mif_abs = scsc_mx_get_mif_abs(mxmgmt_transport->mx); + mif_abs->irq_bit_clear(mif_abs, irq); + + /* The the other side wrote some data to the input stream, wake up the thread + * that deals with this. */ + if (th->task == NULL) { + SCSC_TAG_ERR(MXMGT_TRANS, "th is NOT running\n"); + return; + } + /* + * If an error has occured, we discard silently all messages from the stream + * until the error has been processed and the system has been reinitialised. + */ + if (th->block_thread == 1) { + SCSC_TAG_DEBUG(MXMGT_TRANS, "discard message.\n"); + /* + * Do not try to acknowledge a pending interrupt here. + * This function is called by a function which in turn can be + * running in an atomic or 'disabled irq' level. + */ + return; + } + th->wakeup_flag = 1; + + /* wake up I/O thread */ + wake_up_interruptible(&th->wakeup_q); +} + +/** MIF Interrupt handler for acknowledging writes made by the AP */ +static void output_irq_handler(int irq, void *data) +{ + struct scsc_mif_abs *mif_abs; + struct mxmgmt_transport *mxmgmt_transport = (struct mxmgmt_transport *)data; + + SCSC_TAG_DEBUG(MXMGT_TRANS, "OUT\n"); + + /* Clear the interrupt first to ensure we can't possibly miss one */ + /* The FW read some data from the output stream. + * Currently we do not care, so just clear the interrupt. */ + mif_abs = scsc_mx_get_mif_abs(mxmgmt_transport->mx); + mif_abs->irq_bit_clear(mif_abs, irq); + + /* The driver doesn't use the ack IRQ, so mask it from now on, + * otherwise we may get spurious host-wakes. + */ + mif_abs->irq_bit_mask(mif_abs, irq); +} + + +static void thread_wait_until_stopped(struct mxmgmt_transport *mxmgmt_transport) +{ + struct mxmgmt_thread *th = &mxmgmt_transport->mxmgmt_thread; + + /* + * kthread_stop() cannot handle the th exiting while + * kthread_should_stop() is false, so sleep until kthread_stop() + * wakes us up. + */ + SCSC_TAG_DEBUG(MXMGT_TRANS, "%s waiting for the stop signal.\n", th->name); + set_current_state(TASK_INTERRUPTIBLE); + if (!kthread_should_stop()) { + SCSC_TAG_DEBUG(MXMGT_TRANS, "%s schedule....\n", th->name); + schedule(); + } + + th->task = NULL; + SCSC_TAG_DEBUG(MXMGT_TRANS, "%s exiting....\n", th->name); +} + +/** + * A thread that forwards messages sent across the transport to + * the registered handlers for each channel. + */ +static int mxmgmt_thread_function(void *arg) +{ + struct mxmgmt_transport *mxmgmt_transport = (struct mxmgmt_transport *)arg; + struct mxmgmt_thread *th = &mxmgmt_transport->mxmgmt_thread; + const struct mxmgr_message *current_message; + int ret; + + complete(&th->completion); + + th->block_thread = 0; + while (!kthread_should_stop()) { + /* wait until an error occurs, or we need to process something. */ + + ret = wait_event_interruptible(th->wakeup_q, + (th->wakeup_flag && !th->block_thread) || + kthread_should_stop()); + + if (kthread_should_stop()) { + SCSC_TAG_DEBUG(MXMGT_TRANS, "signalled to exit\n"); + break; + } + if (ret < 0) { + SCSC_TAG_DEBUG(MXMGT_TRANS, "wait_event returned %d, thread will exit\n", ret); + thread_wait_until_stopped(mxmgmt_transport); + break; + } + th->wakeup_flag = 0; + SCSC_TAG_DEBUG(MXMGT_TRANS, "wokeup: r=%d\n", ret); + /* Forward each pending message to the applicable channel handler */ + current_message = mif_stream_peek(&mxmgmt_transport->mif_istream, NULL); + while (current_message != NULL) { + mutex_lock(&mxmgmt_transport->channel_handler_mutex); + if (current_message->channel_id < MMTRANS_NUM_CHANNELS && + mxmgmt_transport->channel_handler_fns[current_message->channel_id]) { + SCSC_TAG_DEBUG(MXMGT_TRANS, "Calling handler for channel_id: %d\n", current_message->channel_id); + (*mxmgmt_transport->channel_handler_fns[current_message->channel_id])(current_message->payload, + mxmgmt_transport->channel_handler_data[current_message->channel_id]); + } else + /* HERE: Invalid channel or no handler, raise fault or log message */ + SCSC_TAG_WARNING(MXMGT_TRANS, "Invalid channel or no handler channel_id: %d\n", current_message->channel_id); + mutex_unlock(&mxmgmt_transport->channel_handler_mutex); + /* Remove the current message from the buffer before processing the next + * one in case it generated another message, otherwise it's possible we + * could run out of space in the stream before we get through all the messages. */ + mif_stream_peek_complete(&mxmgmt_transport->mif_istream, current_message); + current_message = mif_stream_peek(&mxmgmt_transport->mif_istream, NULL); + } + } + + SCSC_TAG_DEBUG(MXMGT_TRANS, "exiting....\n"); + complete(&th->completion); + return 0; +} + + +static int mxmgmt_thread_start(struct mxmgmt_transport *mxmgmt_transport) +{ + int err; + struct mxmgmt_thread *th = &mxmgmt_transport->mxmgmt_thread; + + if (th->task != NULL) { + SCSC_TAG_WARNING(MXMGT_TRANS, "%s thread already started\n", th->name); + return 0; + } + + /* Initialise thread structure */ + th->block_thread = 1; + init_waitqueue_head(&th->wakeup_q); + init_completion(&th->completion); + th->wakeup_flag = 0; + snprintf(th->name, MXMGMT_THREAD_NAME_MAX_LENGTH, "mxmgmt_thread"); + + /* Start the kernel thread */ + th->task = kthread_run(mxmgmt_thread_function, mxmgmt_transport, "%s", th->name); + if (IS_ERR(th->task)) { + SCSC_TAG_ERR(MXMGT_TRANS, "error creating kthread\n"); + return (int)PTR_ERR(th->task); + } + + SCSC_TAG_DEBUG(MXMGT_TRANS, "Started thread %s\n", th->name); + + /* wait until thread is started */ +#define MGMT_THREAD_START_TMO_SEC (3) + err = wait_for_completion_timeout(&th->completion, msecs_to_jiffies(MGMT_THREAD_START_TMO_SEC*1000)); + if (err == 0) { + SCSC_TAG_ERR(MXMGT_TRANS, "timeout in starting thread\n"); + return -ETIMEDOUT; + } + return 0; +} + +static void mgmt_thread_stop(struct mxmgmt_transport *mxmgmt_transport) +{ + unsigned long left_jiffies; + struct mxmgmt_thread *th = &mxmgmt_transport->mxmgmt_thread; + + if (!th->task) { + SCSC_TAG_WARNING(MXMGT_TRANS, "%s mgmt_thread is already stopped\n", th->name); + return; + } + SCSC_TAG_DEBUG(MXMGT_TRANS, "Stopping %s mgmt_thread\n", th->name); + kthread_stop(th->task); + /* wait until th stopped */ +#define MGMT_THREAD_STOP_TMO_SEC (3) + left_jiffies = + wait_for_completion_timeout(&th->completion, msecs_to_jiffies(MGMT_THREAD_STOP_TMO_SEC*1000)); + if (!left_jiffies) + SCSC_TAG_ERR(MXMGT_TRANS, "Failed to stop mgmt_thread %s\n", + th->name); + else + th->task = NULL; +} + +void mxmgmt_transport_release(struct mxmgmt_transport *mxmgmt_transport) +{ + mgmt_thread_stop(mxmgmt_transport); + mif_stream_release(&mxmgmt_transport->mif_istream); + mif_stream_release(&mxmgmt_transport->mif_ostream); +} + +void mxmgmt_transport_config_serialise(struct mxmgmt_transport *mxmgmt_transport, + struct mxtransconf *trans_conf) +{ + mif_stream_config_serialise(&mxmgmt_transport->mif_istream, &trans_conf->to_ap_stream_conf); + mif_stream_config_serialise(&mxmgmt_transport->mif_ostream, &trans_conf->from_ap_stream_conf); +} + + +/** Public functions */ +int mxmgmt_transport_init(struct mxmgmt_transport *mxmgmt_transport, struct scsc_mx *mx) +{ +#define MEM_LENGTH 512 + int r; + uint32_t mem_length = MEM_LENGTH; + uint32_t packet_size = sizeof(struct mxmgr_message); + uint32_t num_packets; + + + /* + * Initialising a buffer of 1 byte is never legitimate, do not allow it. + * The memory buffer length must be a multiple of the packet size. + */ + if (mem_length <= 1 || mem_length % packet_size != 0) + return -EIO; + memset(mxmgmt_transport, 0, sizeof(struct mxmgmt_transport)); + num_packets = mem_length / packet_size; + mutex_init(&mxmgmt_transport->channel_handler_mutex); + mxmgmt_transport->mx = mx; + r = mif_stream_init(&mxmgmt_transport->mif_istream, SCSC_MIF_ABS_TARGET_R4, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, input_irq_handler, mxmgmt_transport); + if (r) { + SCSC_TAG_ERR(MXMGT_TRANS, "mif_stream_init IN failed %d\n", r); + return r; + } + r = mif_stream_init(&mxmgmt_transport->mif_ostream, SCSC_MIF_ABS_TARGET_R4, MIF_STREAM_DIRECTION_OUT, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, output_irq_handler, mxmgmt_transport); + if (r) { + SCSC_TAG_ERR(MXMGT_TRANS, "mif_stream_init OUT failed %d\n", r); + mif_stream_release(&mxmgmt_transport->mif_istream); + return r; + } + + r = mxmgmt_thread_start(mxmgmt_transport); + if (r) { + SCSC_TAG_ERR(MXMGT_TRANS, "mxmgmt_thread_start failed %d\n", r); + mif_stream_release(&mxmgmt_transport->mif_istream); + mif_stream_release(&mxmgmt_transport->mif_ostream); + return r; + } + return 0; +} + +void mxmgmt_transport_register_channel_handler(struct mxmgmt_transport *mxmgmt_transport, enum mxmgr_channels channel_id, + mxmgmt_channel_handler handler, void *data) +{ + if (channel_id >= MMTRANS_NUM_CHANNELS) { + SCSC_TAG_ERR(MXMGT_TRANS, "Invalid channel id: %d\n", channel_id); + return; + } + mutex_lock(&mxmgmt_transport->channel_handler_mutex); + mxmgmt_transport->channel_handler_fns[channel_id] = handler; + mxmgmt_transport->channel_handler_data[channel_id] = data; + mutex_unlock(&mxmgmt_transport->channel_handler_mutex); +} + +void mxmgmt_transport_send(struct mxmgmt_transport *mxmgmt_transport, enum mxmgr_channels channel_id, + void *message, uint32_t message_length) +{ + struct mxmgr_message transport_msg = { .channel_id = channel_id }; + + const void *bufs[2] = { &transport_msg.channel_id, message }; + uint32_t buf_lengths[2] = { sizeof(transport_msg.channel_id), message_length }; + + mif_stream_write_gather(&mxmgmt_transport->mif_ostream, bufs, buf_lengths, 2); +} diff --git a/drivers/misc/samsung/scsc/mxmgmt_transport.h b/drivers/misc/samsung/scsc/mxmgmt_transport.h new file mode 100644 index 000000000000..36039bd54495 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxmgmt_transport.h @@ -0,0 +1,104 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/** + * mx140 management transport (Interface) + * + * Provides bi-directional communication between the firmware and the + * host. Messages sent across the transport are divided into a + * number of channels, with each channel having its own dedicated handler. + * + * This interface also provides a utility method for sending messages across + * the stream. + */ + +#ifndef MXMANAGEMENT_TRANSPORT_H__ +#define MXMANAGEMENT_TRANSPORT_H__ + +/** Uses */ +#include +#include "mifstream.h" + +struct mxmgmt_transport; + +/** + * The various channels that can send messages + * across the transport. + * + * Channel IDs are limited to one byte. + */ +enum mxmgr_channels { + MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT = 0, + MMTRANS_CHAN_ID_SERVICE_MANAGEMENT = 1, + MMTRANS_CHAN_ID_MAXWELL_LOGGING = 2, + MMTRANS_NUM_CHANNELS = 3 +}; + +/** + * Transport channel callback handler. This will be invoked each time a message on a channel is + * received in the context of the transport stream's thread. Handlers may perform work within + * their callback implementation, but should not block. + * + * Note that the message pointer passed is only valid for the duration of the function call. + */ +typedef void (*mxmgmt_channel_handler)(const void *message, void *data); + +/** + * Registers the callback function that will be invoked to handle data coming in from the AP + * for the given channel. + */ +void mxmgmt_transport_register_channel_handler(struct mxmgmt_transport *mxmgmt_transport, enum mxmgr_channels channel_id, + mxmgmt_channel_handler handler, void *data); + +/** + * Sends a message to the AP across the given channel. + * + * This function is safe to call from any RTOS thread. + */ +void mxmgmt_transport_send(struct mxmgmt_transport *mxmgmt_transport, enum mxmgr_channels channel_id, + void *message, uint32_t message_length); + +/** + * Initialises the maxwell management transport and configures the necessary + * interrupt handlers. Called once during boot. + */ +int mxmgmt_transport_init(struct mxmgmt_transport *mxmgmt_transport, struct scsc_mx *mx); +void mxmgmt_transport_release(struct mxmgmt_transport *mxmgmt_transport); + +/* + * Initialises the configuration area incl. Maxwell Infrastructure Configuration, + * MIF Management Transport Configuration and MIF Management Stream Configuration. + */ +void mxmgmt_transport_config_serialise(struct mxmgmt_transport *mxmgmt_transport, struct mxtransconf *trans_conf); +void mxmgmt_transport_set_error(struct mxmgmt_transport *mxmgmt_transport); + +#define MXMGMT_THREAD_NAME_MAX_LENGTH 32 +struct mxmgmt_thread { + struct task_struct *task; + char name[MXMGMT_THREAD_NAME_MAX_LENGTH]; + int prio; + struct completion completion; + wait_queue_head_t wakeup_q; + unsigned int wakeup_flag; + /* + * Use it to block the I/O thread when + * an error occurs. + */ + int block_thread; +}; + +struct mxmgmt_transport { + struct scsc_mx *mx; + struct mif_stream mif_istream; + struct mif_stream mif_ostream; + struct mxmgmt_thread mxmgmt_thread; + /** Registered channel handlers for messages coming from the AP for each channel */ + mxmgmt_channel_handler channel_handler_fns[MMTRANS_NUM_CHANNELS]; + void *channel_handler_data[MMTRANS_NUM_CHANNELS]; + struct mutex channel_handler_mutex; +}; + +#endif /* MXMANAGEMENT_TRANSPORT_H__ */ diff --git a/drivers/misc/samsung/scsc/mxmgmt_transport_format.h b/drivers/misc/samsung/scsc/mxmgmt_transport_format.h new file mode 100644 index 000000000000..291faca9b644 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxmgmt_transport_format.h @@ -0,0 +1,23 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef MXMGR_TRANSPORT_FORMAT_H__ +#define MXMGR_TRANSPORT_FORMAT_H__ + +#define MXMGR_MESSAGE_PAYLOAD_SIZE 127 + +/** + * Layout of messages across the manager transport streams. + * + * HERE: This is a dummy definition and will be replaced + * once more of the service management infrastructure is completed. + */ +struct mxmgr_message { + uint8_t channel_id; /* Channel ID from mxmgr_channels */ + uint8_t payload[MXMGR_MESSAGE_PAYLOAD_SIZE]; /* Message content to store in the transport stream - user defined format */ +} __packed; + +#endif /* MXMGR_TRANSPORT_FORMAT_H__ */ diff --git a/drivers/misc/samsung/scsc/mxmgmt_transport_streams.h b/drivers/misc/samsung/scsc/mxmgmt_transport_streams.h new file mode 100644 index 000000000000..e906003c2616 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxmgmt_transport_streams.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef MXMGR_TRANSPORT_STREAMS_H__ +#define MXMGR_TRANSPORT_STREAMS_H__ + +/** + * MIF input/output streams to/from the AP. + * These are seperated out to allow their use directly from within unit tests. + */ +struct { + /** from AP */ + mif_stream *istream; + /** to AP */ + mif_stream *ostream; +} mxmgr_stream_container; + +extern mxmgr_stream_container mxmgr_streams; + + +#endif /* MXMGR_TRANSPORT_STREAMS_H__ */ diff --git a/drivers/misc/samsung/scsc/mxproc.c b/drivers/misc/samsung/scsc/mxproc.c new file mode 100644 index 000000000000..a5f4b3783a2f --- /dev/null +++ b/drivers/misc/samsung/scsc/mxproc.c @@ -0,0 +1,585 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "mxman.h" +#include "mxproc.h" +#ifdef CONFIG_SCSC_WLBTD +#include "scsc_wlbtd.h" +#endif + +#ifndef AID_MXPROC +#define AID_MXPROC 0 +#endif + +#define MX_PROCFS_RW_FILE_OPS(name) \ + static ssize_t mx_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static ssize_t mx_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations mx_procfs_ ## name ## _fops = { \ + .read = mx_procfs_ ## name ## _read, \ + .write = mx_procfs_ ## name ## _write, \ + .open = mx_procfs_generic_open, \ + .llseek = generic_file_llseek \ + } +#define MX_PROCFS_RO_FILE_OPS(name) \ + static ssize_t mx_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations mx_procfs_ ## name ## _fops = { \ + .read = mx_procfs_ ## name ## _read, \ + .open = mx_procfs_generic_open, \ + .llseek = generic_file_llseek \ + } + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MX_PDE_DATA(inode) PDE_DATA(inode) +#else +#define MX_PDE_DATA(inode) (PDE(inode)->data) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MX_PROCFS_SET_UID_GID(_entry) \ + do { \ + kuid_t proc_kuid = KUIDT_INIT(AID_MXPROC); \ + kgid_t proc_kgid = KGIDT_INIT(AID_MXPROC); \ + proc_set_user(_entry, proc_kuid, proc_kgid); \ + } while (0) +#else +#define MX_PROCFS_SET_UID_GID(entry) \ + do { \ + (entry)->uid = AID_MXPROC; \ + (entry)->gid = AID_MXPROC; \ + } while (0) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define MX_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &mx_procfs_ ## name ## _fops, _sdev); \ + MX_PROCFS_SET_UID_GID(entry); \ + } while (0) +#else +#define MX_PROCFS_ADD_FILE(_data, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = create_proc_entry(# name, mode, parent); \ + if (entry) { \ + entry->proc_fops = &mx_procfs_ ## name ## _fops; \ + entry->data = _data; \ + MX_PROCFS_SET_UID_GID(entry); \ + } \ + } while (0) +#endif + +#define MX_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent) + +#define OS_UNUSED_PARAMETER(x) ((void)(x)) +#define MX_DIRLEN 128 +static const char *procdir_ctrl = "driver/mxman_ctrl"; +static const char *procdir_info = "driver/mxman_info"; + +static int mx_procfs_generic_open(struct inode *inode, struct file *file) +{ + file->private_data = MX_PDE_DATA(inode); + return 0; +} + +static ssize_t mx_procfs_mx_fail_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + + OS_UNUSED_PARAMETER(mxproc); + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(user_buf); + OS_UNUSED_PARAMETER(count); + OS_UNUSED_PARAMETER(ppos); + + SCSC_TAG_DEBUG(MX_PROC, "OK\n"); + return 0; +} + +static ssize_t mx_procfs_mx_fail_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(user_buf); + OS_UNUSED_PARAMETER(count); + OS_UNUSED_PARAMETER(ppos); + + if (mxproc) + mxman_fail(mxproc->mxman, SCSC_PANIC_CODE_HOST << 15, __func__); + SCSC_TAG_DEBUG(MX_PROC, "OK\n"); + + return count; +} + +static ssize_t mx_procfs_mx_freeze_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + + OS_UNUSED_PARAMETER(mxproc); + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(user_buf); + OS_UNUSED_PARAMETER(count); + OS_UNUSED_PARAMETER(ppos); + + SCSC_TAG_DEBUG(MX_PROC, "OK\n"); + return 0; +} + +static ssize_t mx_procfs_mx_freeze_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(user_buf); + OS_UNUSED_PARAMETER(count); + OS_UNUSED_PARAMETER(ppos); + + if (mxproc) + mxman_freeze(mxproc->mxman); + SCSC_TAG_INFO(MX_PROC, "OK\n"); + + return count; +} + +static ssize_t mx_procfs_mx_panic_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + + OS_UNUSED_PARAMETER(mxproc); + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(user_buf); + OS_UNUSED_PARAMETER(count); + OS_UNUSED_PARAMETER(ppos); + + /* Force a FW panic on read as well as write to allow test in user builds */ + if (mxproc) + mxman_force_panic(mxproc->mxman); + SCSC_TAG_INFO(MX_PROC, "OK\n"); + + return 0; +} + +static ssize_t mx_procfs_mx_panic_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(user_buf); + OS_UNUSED_PARAMETER(count); + OS_UNUSED_PARAMETER(ppos); + + if (mxproc) + mxman_force_panic(mxproc->mxman); + SCSC_TAG_INFO(MX_PROC, "OK\n"); + + return count; +} + +static ssize_t mx_procfs_mx_lastpanic_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + char buf[64]; + int bytes; + + if (!mxproc || !mxproc->mxman) + return -EINVAL; + + memset(buf, '\0', sizeof(buf)); + + bytes = snprintf(buf, sizeof(buf), "scsc_panic_code : 0x%x\nscsc_panic_subcode : 0x%x\n", + (mxproc->mxman->scsc_panic_code), (mxproc->mxman->scsc_panic_code & 0x7FFF)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, bytes); +} + +static ssize_t mx_procfs_mx_suspend_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + char buf[3]; + + OS_UNUSED_PARAMETER(file); + + buf[0] = mxproc->mxman->suspended ? 'Y' : 'N'; + buf[1] = '\n'; + buf[2] = '\0'; + + SCSC_TAG_INFO(MX_PROC, "suspended: %c\n", buf[0]); + + return simple_read_from_buffer(user_buf, count, ppos, buf, sizeof(buf)); +} + +static ssize_t mx_procfs_mx_suspend_count_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + int pos = 0; + char buf[32]; + const size_t bufsz = sizeof(buf); + u32 suspend_count; + + OS_UNUSED_PARAMETER(file); + + if (!mxproc || !mxproc->mxman) + return 0; + + suspend_count = atomic_read(&mxproc->mxman->suspend_count); + SCSC_TAG_INFO(MX_PROC, "suspend_count: %u\n", suspend_count); + pos += scnprintf(buf + pos, bufsz - pos, "%u\n", suspend_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mx_procfs_mx_recovery_count_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + int pos = 0; + char buf[32]; + const size_t bufsz = sizeof(buf); + u32 recovery_count; + + OS_UNUSED_PARAMETER(file); + + if (!mxproc || !mxproc->mxman) + return 0; + + recovery_count = atomic_read(&mxproc->mxman->recovery_count); + SCSC_TAG_INFO(MX_PROC, "recovery_count: %u\n", recovery_count); + pos += scnprintf(buf + pos, bufsz - pos, "%u\n", recovery_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mx_procfs_mx_boot_count_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + int pos = 0; + char buf[32]; + const size_t bufsz = sizeof(buf); + u32 boot_count; + + OS_UNUSED_PARAMETER(file); + + if (!mxproc || !mxproc->mxman) + return 0; + + boot_count = atomic_read(&mxproc->mxman->boot_count); + SCSC_TAG_INFO(MX_PROC, "boot_count: %u\n", boot_count); + pos += scnprintf(buf + pos, bufsz - pos, "%u\n", boot_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mx_procfs_mx_suspend_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + int r; + + OS_UNUSED_PARAMETER(file); + OS_UNUSED_PARAMETER(ppos); + + if (count && mxproc) { + switch (user_buf[0]) { + case 'Y': + SCSC_TAG_INFO(MX_PROC, "force suspend\n"); + r = mxman_suspend(mxproc->mxman); + if (r) { + SCSC_TAG_INFO(MX_PROC, "mx_suspend failed %d\n", r); + return r; + } + break; + case 'N': + SCSC_TAG_INFO(MX_PROC, "force resume\n"); + mxman_resume(mxproc->mxman); + break; + default: + SCSC_TAG_INFO(MX_PROC, "invalid value %c\n", user_buf[0]); + return -EINVAL; + } + } + + return count; +} + +static ssize_t mx_procfs_mx_status_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + char buf[32]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + if (!mxproc || !mxproc->mxman) + return 0; + + switch (mxproc->mxman->mxman_state) { + case MXMAN_STATE_STOPPED: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "MXMAN_STATE_STOPPED"); + break; + case MXMAN_STATE_STARTING: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "MXMAN_STATE_STARTING"); + break; + case MXMAN_STATE_STARTED: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "MXMAN_STATE_STARTED"); + break; + case MXMAN_STATE_FAILED: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "MXMAN_STATE_FAILED"); + break; + case MXMAN_STATE_FREEZED: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "MXMAN_STATE_FREEZED"); + break; + default: + return 0; + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mx_procfs_mx_services_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + char buf[256]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + if (!mxproc || !mxproc->mxman) + return 0; + + pos = scsc_mx_list_services(mxproc->mxman, buf, bufsz); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t mx_procfs_mx_wlbt_stat_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct mxproc *mxproc = file->private_data; + struct scsc_mif_abs *mif_abs; + int pos = 0; + int r; + char buf[32]; + const size_t bufsz = sizeof(buf); + u32 val = 0xff; + + OS_UNUSED_PARAMETER(file); + + if (!mxproc || !mxproc->mxman || !mxproc->mxman->mx) + return 0; + + mif_abs = scsc_mx_get_mif_abs(mxproc->mxman->mx); + + /* Read WLBT_STAT register */ + if (mif_abs->mif_read_register) { + r = mif_abs->mif_read_register(mif_abs, SCSC_REG_READ_WLBT_STAT, &val); + if (r) + val = 0xff; /* failed */ + } + + pos += scnprintf(buf + pos, bufsz - pos, "0x%x\n", val); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +MX_PROCFS_RW_FILE_OPS(mx_fail); +MX_PROCFS_RW_FILE_OPS(mx_freeze); +MX_PROCFS_RW_FILE_OPS(mx_panic); +MX_PROCFS_RW_FILE_OPS(mx_suspend); +MX_PROCFS_RO_FILE_OPS(mx_suspend_count); +MX_PROCFS_RO_FILE_OPS(mx_recovery_count); +MX_PROCFS_RO_FILE_OPS(mx_boot_count); +MX_PROCFS_RO_FILE_OPS(mx_status); +MX_PROCFS_RO_FILE_OPS(mx_services); +MX_PROCFS_RO_FILE_OPS(mx_lastpanic); +MX_PROCFS_RO_FILE_OPS(mx_wlbt_stat); + +static u32 proc_count; + +int mxproc_create_ctrl_proc_dir(struct mxproc *mxproc, struct mxman *mxman) +{ + char dir[MX_DIRLEN]; + struct proc_dir_entry *parent; + + (void)snprintf(dir, sizeof(dir), "%s%d", procdir_ctrl, proc_count); + parent = proc_mkdir(dir, NULL); + if (!parent) { + SCSC_TAG_ERR(MX_PROC, "failed to create proc dir %s\n", procdir_ctrl); + return -EINVAL; + } +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = mxproc; +#endif + mxproc->mxman = mxman; + mxproc->procfs_ctrl_dir = parent; + mxproc->procfs_ctrl_dir_num = proc_count; + MX_PROCFS_ADD_FILE(mxproc, mx_fail, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_freeze, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_panic, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_suspend, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_suspend_count, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_recovery_count, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_status, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_services, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_lastpanic, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_wlbt_stat, parent, S_IRUSR | S_IRGRP | S_IROTH); + + SCSC_TAG_DEBUG(MX_PROC, "created %s proc dir\n", dir); + proc_count++; + + return 0; +} + +void mxproc_remove_ctrl_proc_dir(struct mxproc *mxproc) +{ + if (mxproc->procfs_ctrl_dir) { + char dir[MX_DIRLEN]; + + MX_PROCFS_REMOVE_FILE(mx_fail, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_freeze, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_panic, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_suspend, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_suspend_count, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_recovery_count, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_status, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_services, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_lastpanic, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_wlbt_stat, mxproc->procfs_ctrl_dir); + (void)snprintf(dir, sizeof(dir), "%s%d", procdir_ctrl, mxproc->procfs_ctrl_dir_num); + remove_proc_entry(dir, NULL); + mxproc->procfs_ctrl_dir = NULL; + proc_count--; + SCSC_TAG_DEBUG(MX_PROC, "removed %s proc dir\n", dir); + } +} + +static ssize_t mx_procfs_mx_rf_hw_ver_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[256]; + int bytes; + struct mxproc *mxproc = file->private_data; + + if (!mxproc || !mxproc->mxman) + return -EINVAL; + + memset(buf, '\0', sizeof(buf)); + bytes = snprintf(buf, sizeof(buf), "RF version: 0x%04x\n", (mxproc->mxman->rf_hw_ver)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, bytes); +} + +MX_PROCFS_RO_FILE_OPS(mx_rf_hw_ver); + +static ssize_t mx_procfs_mx_rf_hw_name_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[256]; + int bytes; + struct mxproc *mxproc = file->private_data; + + if (!mxproc || !mxproc->mxman) + return -EINVAL; + + memset(buf, '\0', sizeof(buf)); + + bytes = mxman_print_rf_hw_version(mxproc->mxman, buf, sizeof(buf)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, bytes); +} + +MX_PROCFS_RO_FILE_OPS(mx_rf_hw_name); + +static ssize_t mx_procfs_mx_release_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[256]; + int bytes; + struct mxproc *mxproc = file->private_data; + char *build_id = 0; + + OS_UNUSED_PARAMETER(file); + + if (mxproc && mxproc->mxman) + build_id = mxproc->mxman->fw_build_id; + + memset(buf, '\0', sizeof(buf)); + + bytes = snprintf(buf, sizeof(buf), "Release: %d.%d.%d.%d (f/w: %s)\n", + SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION, SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT, + build_id ? build_id : "unknown"); + + if (bytes > sizeof(buf)) + bytes = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, bytes); +} + +MX_PROCFS_RO_FILE_OPS(mx_release); + +static ssize_t mx_procfs_mx_ttid_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[256]; + int bytes; + struct mxproc *mxproc = file->private_data; + char *id = 0; + + OS_UNUSED_PARAMETER(file); + + if (mxproc && mxproc->mxman) + id = mxproc->mxman->fw_ttid; + + memset(buf, '\0', sizeof(buf)); + + bytes = snprintf(buf, sizeof(buf), "%s\n", id); + + if (bytes > sizeof(buf)) + bytes = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, bytes); +} + +MX_PROCFS_RO_FILE_OPS(mx_ttid); + +int mxproc_create_info_proc_dir(struct mxproc *mxproc, struct mxman *mxman) +{ + char dir[MX_DIRLEN]; + struct proc_dir_entry *parent; + + (void)snprintf(dir, sizeof(dir), "%s", procdir_info); + parent = proc_mkdir(dir, NULL); + if (!parent) { + SCSC_TAG_ERR(MX_PROC, "failed to create /proc dir\n"); + return -EINVAL; + } +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = mxproc; +#endif + mxproc->mxman = mxman; + mxproc->procfs_info_dir = parent; + MX_PROCFS_ADD_FILE(mxproc, mx_release, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_rf_hw_ver, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_rf_hw_name, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_boot_count, parent, S_IRUSR | S_IRGRP | S_IROTH); + MX_PROCFS_ADD_FILE(mxproc, mx_ttid, parent, S_IRUSR | S_IRGRP | S_IROTH); + SCSC_TAG_DEBUG(MX_PROC, "created %s proc dir\n", dir); + + return 0; +} + +void mxproc_remove_info_proc_dir(struct mxproc *mxproc) +{ + if (mxproc->procfs_info_dir) { + char dir[MX_DIRLEN]; + + MX_PROCFS_REMOVE_FILE(mx_ttid, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_boot_count, mxproc->procfs_ctrl_dir); + MX_PROCFS_REMOVE_FILE(mx_release, mxproc->procfs_info_dir); + MX_PROCFS_REMOVE_FILE(mx_rf_hw_ver, mxproc->procfs_info_dir); + MX_PROCFS_REMOVE_FILE(mx_rf_hw_name, mxproc->procfs_info_dir); + (void)snprintf(dir, sizeof(dir), "%s", procdir_info); + remove_proc_entry(dir, NULL); + mxproc->procfs_info_dir = NULL; + SCSC_TAG_DEBUG(MX_PROC, "removed %s proc dir\n", dir); + } +} diff --git a/drivers/misc/samsung/scsc/mxproc.h b/drivers/misc/samsung/scsc/mxproc.h new file mode 100644 index 000000000000..fcaae23cadcb --- /dev/null +++ b/drivers/misc/samsung/scsc/mxproc.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* + * mx140 proc interface + */ + +#ifndef MXPROC_H +#define MXPROC_H + +struct mxproc; + +int mxproc_create_ctrl_proc_dir(struct mxproc *mxproc, struct mxman *mxman); +void mxproc_remove_ctrl_proc_dir(struct mxproc *mxproc); +int mxproc_create_info_proc_dir(struct mxproc *mxproc, struct mxman *mxman); +void mxproc_remove_info_proc_dir(struct mxproc *mxproc); +extern int scsc_mx_list_services(struct mxman *mxman_p, char *buf, const size_t bufsz); +extern int mxman_print_rf_hw_version(struct mxman *mxman, char *buf, const size_t bufsz); + +struct mxproc { + struct mxman *mxman; + struct proc_dir_entry *procfs_ctrl_dir; + u32 procfs_ctrl_dir_num; + struct proc_dir_entry *procfs_info_dir; +}; + +#endif /* MXPROC_H */ diff --git a/drivers/misc/samsung/scsc/mxsyserr.c b/drivers/misc/samsung/scsc/mxsyserr.c new file mode 100755 index 000000000000..a4116dfc0b2a --- /dev/null +++ b/drivers/misc/samsung/scsc/mxsyserr.c @@ -0,0 +1,45 @@ +/**************************************************************************** + * + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scsc_mx_impl.h" +#include "miframman.h" +#include "mifmboxman.h" +#include "mxman.h" +#include "srvman.h" +#include "mxmgmt_transport.h" +#include "mxlog.h" +#include "mxlogger.h" +#include "fw_panic_record.h" +#include "panicmon.h" +#include "mxproc.h" +#include "mxsyserr.h" + +#include +#include +#include + +void mx_syserr_handler(struct mxman *mx, const void *message) +{ + const struct mx_syserr_msg *msg = (const struct mx_syserr_msg *)message; + + SCSC_TAG_INFO(MXMAN, "MM_SYSERR_IND len: %u, ts: 0x%08X, tf: 0x%08X, str: 0x%x, code: 0x%08x, p0: 0x%x, p1: 0x%x\n", + msg->syserr.length, + msg->syserr.slow_clock, + msg->syserr.fast_clock, + msg->syserr.string_index, + msg->syserr.syserr_code, + msg->syserr.param[0], + msg->syserr.param[1]); +} diff --git a/drivers/misc/samsung/scsc/mxsyserr.h b/drivers/misc/samsung/scsc/mxsyserr.h new file mode 100755 index 000000000000..43e134fdbb47 --- /dev/null +++ b/drivers/misc/samsung/scsc/mxsyserr.h @@ -0,0 +1,27 @@ +/**************************************************************************** + * + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __MX_SYSERR_H +#define __MX_SYSERR_H + + +struct mx_syserr { + u16 length; /* Length of this structure for future extension */ + u32 slow_clock; + u32 fast_clock; + u32 string_index; + u32 syserr_code; + u32 param[2]; +} __packed; + +struct mx_syserr_msg { + u8 id; + struct mx_syserr syserr; +} __packed; + +void mx_syserr_handler(struct mxman *mx, const void *message); + +#endif diff --git a/drivers/misc/samsung/scsc/panic_record_r4_defs.h b/drivers/misc/samsung/scsc/panic_record_r4_defs.h new file mode 100644 index 000000000000..4f631bac53fb --- /dev/null +++ b/drivers/misc/samsung/scsc/panic_record_r4_defs.h @@ -0,0 +1,138 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef PANIC_RECORD_R4_DEFS_H__ +#define PANIC_RECORD_R4_DEFS_H__ +/* + * R4 Panic Record Definitions. + * + * This record is used to pass summary information about the context of + * Maxwell R4 firmware panics to the host. + * + * The record location, relative to shared DRAM memory, is defined by the + * R4_PANIC_RECORD_OFFSET field in the firmware header [see SC-505846-SW]. + * + * Notes:- + * - The host panic handler should _not_ expect the R4 record to be + * written prior to a panic indication from Maxwell, and it may never + * be written at all. The checksum should indicate a valid record. + * + * N.B. Defined in this standalone header for inclusion in .s and .c. + */ + +/* + * The current version of the PANIC_RECORD_R4 structure defined below. + * Written to version field by firmware, checked by host. + * This also serves as a rudimentary endianess check. + */ +#define PANIC_RECORD_R4_VERSION_1 1 + +/* + * Total number of R4 registers saved. + */ +#define PANIC_RECORD_R4_REGISTER_COUNT 18 + +/* + * Number of panic info arguments. + */ +#define PANIC_RECORD_R4_INFO_COUNT 4 + +/* + * Checksum seed to prevent false match on all zeros or ones. + */ +#define PANIC_RECORD_R4_CKSUM_SEED 0xa5a5a5a5 + +/***************************************************************************** + * R4 Panic Record 32bit field indices. + *****************************************************************************/ + +/* + * Version of this structure. + */ +#define PANIC_RECORD_R4_VERSION_INDEX 0 + +/* + * Clock counters at time of the R4 panic. + * + * The 1M clock is generally the most useful but there is period + * after IP wake-up when it is not monotonic. The 32K count + * is included in-case of a panic during wake-up. + */ +#define PANIC_RECORD_R4_TIMESTAMP_1M_INDEX (PANIC_RECORD_R4_VERSION_INDEX + 1) +#define PANIC_RECORD_R4_TIMESTAMP_32K_INDEX (PANIC_RECORD_R4_TIMESTAMP_1M_INDEX + 1) + +/* + * Snapshot of main r4 CPU registers. + */ +#define PANIC_RECORD_R4_REGISTERS_INDEX (PANIC_RECORD_R4_TIMESTAMP_32K_INDEX + 1) + +/* + * Panic info. + * + * 1st field is key/index of panic_string. + */ +#define PANIC_RECORD_R4_INFO_INDEX (PANIC_RECORD_R4_REGISTERS_INDEX + PANIC_RECORD_R4_REGISTER_COUNT) + +/* + * 32bit XOR of all the fields above + PANIC_RECORD_R4_CKSUM_SEED + * + * Written by firmware on panic, checked by host. + */ +#define PANIC_RECORD_R4_CKSUM_INDEX (PANIC_RECORD_R4_INFO_INDEX + PANIC_RECORD_R4_INFO_COUNT) + +/* + * Length of the r4 panic record (uint32s). + */ +#define PANIC_RECORD_R4_LEN (PANIC_RECORD_R4_CKSUM_INDEX + 1) + +/***************************************************************************** + * R4 uint32 Register indices relative to PANIC_RECORD_R4_REGISTERS_INDEX + *****************************************************************************/ + +#define PANIC_RECORD_R4_REGISTER_R0 0 +#define PANIC_RECORD_R4_REGISTER_R1 1 +#define PANIC_RECORD_R4_REGISTER_R2 2 +#define PANIC_RECORD_R4_REGISTER_R3 3 +#define PANIC_RECORD_R4_REGISTER_R4 4 +#define PANIC_RECORD_R4_REGISTER_R5 5 +#define PANIC_RECORD_R4_REGISTER_R6 6 +#define PANIC_RECORD_R4_REGISTER_R7 7 +#define PANIC_RECORD_R4_REGISTER_R8 8 +#define PANIC_RECORD_R4_REGISTER_R9 9 +#define PANIC_RECORD_R4_REGISTER_R10 10 +#define PANIC_RECORD_R4_REGISTER_R11 11 +#define PANIC_RECORD_R4_REGISTER_R12 12 +#define PANIC_RECORD_R4_REGISTER_SP 13 +#define PANIC_RECORD_R4_REGISTER_LR 14 +#define PANIC_RECORD_R4_REGISTER_SPSR 15 +#define PANIC_RECORD_R4_REGISTER_PC 16 +#define PANIC_RECORD_R4_REGISTER_CPSR 17 + +/***************************************************************************** + * R4 Register octet offsets relative to PANIC_RECORD_R4_REGISTERS_INDEX + *****************************************************************************/ + +#define PANIC_RECORD_R4_REGISTER_OFFSET_R0 (PANIC_RECORD_R4_REGISTER_R0 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R1 (PANIC_RECORD_R4_REGISTER_R1 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R2 (PANIC_RECORD_R4_REGISTER_R2 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R3 (PANIC_RECORD_R4_REGISTER_R3 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R4 (PANIC_RECORD_R4_REGISTER_R4 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R5 (PANIC_RECORD_R4_REGISTER_R5 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R6 (PANIC_RECORD_R4_REGISTER_R6 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R7 (PANIC_RECORD_R4_REGISTER_R7 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R8 (PANIC_RECORD_R4_REGISTER_R8 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R9 (PANIC_RECORD_R4_REGISTER_R9 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R10 (PANIC_RECORD_R4_REGISTER_R10 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R11 (PANIC_RECORD_R4_REGISTER_R11 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_R12 (PANIC_RECORD_R4_REGISTER_R12 * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_SP (PANIC_RECORD_R4_REGISTER_SP * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_LR (PANIC_RECORD_R4_REGISTER_LR * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_SPSR (PANIC_RECORD_R4_REGISTER_SPSR * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_PC (PANIC_RECORD_R4_REGISTER_PC * 4) +#define PANIC_RECORD_R4_REGISTER_OFFSET_CPSR (PANIC_RECORD_R4_REGISTER_CPSR * 4) + +#endif /* PANIC_RECORD_R4_DEFS_H__ */ + diff --git a/drivers/misc/samsung/scsc/panicmon.c b/drivers/misc/samsung/scsc/panicmon.c new file mode 100644 index 000000000000..90a960deca1c --- /dev/null +++ b/drivers/misc/samsung/scsc/panicmon.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include + +#include "panicmon.h" +#include "scsc_mif_abs.h" +#include "mxman.h" + +static void panicmon_isr(int irq, void *data) +{ + struct panicmon *panicmon = (struct panicmon *)data; + + SCSC_TAG_DEBUG(PANIC_MON, "panicmon=%p panicmon->mx=%p mxman=%p\n", panicmon, panicmon->mx, scsc_mx_get_mxman(panicmon->mx)); + /* Avoid unused parameter error */ + (void)irq; + mxman_fail(scsc_mx_get_mxman(panicmon->mx), SCSC_PANIC_CODE_FW << 15, __func__); +} + + +void panicmon_init(struct panicmon *panicmon, struct scsc_mx *mx) +{ + struct scsc_mif_abs *mif; + + panicmon->mx = mx; + mif = scsc_mx_get_mif_abs(mx); + /* register isr with mif abstraction */ + mif->irq_reg_reset_request_handler(mif, panicmon_isr, (void *)panicmon); +} + +void panicmon_deinit(struct panicmon *panicmon) +{ + struct scsc_mif_abs *mif; + + mif = scsc_mx_get_mif_abs(panicmon->mx); + mif->irq_unreg_reset_request_handler(mif); +} diff --git a/drivers/misc/samsung/scsc/panicmon.h b/drivers/misc/samsung/scsc/panicmon.h new file mode 100644 index 000000000000..cbc9c7da964f --- /dev/null +++ b/drivers/misc/samsung/scsc/panicmon.h @@ -0,0 +1,20 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#ifndef _PANICMON_H +#define _PANICMON_H +#include "mxman.h" + +struct panicmon; + +void panicmon_init(struct panicmon *panicmon, struct scsc_mx *mx); +void panicmon_deinit(struct panicmon *panicmon); + +struct panicmon { + struct scsc_mx *mx; +}; + +#endif /* _PANICMON_H */ diff --git a/drivers/misc/samsung/scsc/pcie_mbox.c b/drivers/misc/samsung/scsc/pcie_mbox.c new file mode 100644 index 000000000000..3c9012f68e15 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox.c @@ -0,0 +1,129 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Mailbox Hardware Emulation (Implementation) +* +****************************************************************************/ + +/* Implements */ + +#include "pcie_mbox.h" + +/* Uses */ + +#include +#include +#include +#include "pcie_mbox_shared_data.h" +#include "pcie_mbox_intgen.h" + +/* Private Functions */ + +/** + * Initialise the mailbox emulation shared structure. + */ +static void pcie_mbox_shared_data_init(struct pcie_mbox_shared_data *shared_data) +{ + memset(shared_data, 0, sizeof(*shared_data)); + shared_data->magic = PCIE_MIF_MBOX_MAGIC_NUMBER; + shared_data->version = PCIE_MIF_MBOX_VERSION_NUMBER; + pcie_mbox_shared_data_wmb(); +} + +/* Public Functions */ + +void pcie_mbox_init( + struct pcie_mbox *mbox, + void *shared_data_region, + __iomem void *pcie_registers, + struct functor *ap_interrupt_trigger, + struct functor *r4_interrupt_trigger, +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + struct functor *m4_interrupt_trigger, + struct functor *m4_1_interrupt_trigger +#else + struct functor *m4_interrupt_trigger +#endif + ) +{ + mbox->shared_data = (struct pcie_mbox_shared_data *)shared_data_region; + + pcie_mbox_shared_data_init(mbox->shared_data); + + /* Interrupt Generator Emulations */ + + pcie_mbox_intgen_init(&mbox->ap_intgen, "AP", &mbox->shared_data->ap_interrupt, ap_interrupt_trigger); + pcie_mbox_intgen_init(&mbox->r4_intgen, "R4", &mbox->shared_data->r4_interrupt, r4_interrupt_trigger); + pcie_mbox_intgen_init(&mbox->m4_intgen, "M4", &mbox->shared_data->m4_interrupt, m4_interrupt_trigger); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + pcie_mbox_intgen_init(&mbox->m4_intgen_1, "M4", &mbox->shared_data->m4_1_interrupt, m4_1_interrupt_trigger); +#endif +} + +u32 pcie_mbox_get_ap_interrupt_masked_bitmask(const struct pcie_mbox *mbox) +{ + /* Delegate to ap intgen component */ + return pcie_mbox_intgen_get_masked_bitmask(&mbox->ap_intgen); +} + +u32 pcie_mbox_get_ap_interrupt_pending_bitmask(const struct pcie_mbox *mbox) +{ + /* Delegate to ap intgen component */ + return pcie_mbox_intgen_get_pending_bitmask(&mbox->ap_intgen); +} + +bool pcie_mbox_is_ap_interrupt_source_pending(const struct pcie_mbox *mbox, int source_num) +{ + return pcie_mbox_intgen_is_source_pending(&mbox->ap_intgen, source_num); +} + +void pcie_mbox_clear_ap_interrupt_source(struct pcie_mbox *mbox, int source_num) +{ + /* Delegate to ap intgen component */ + pcie_mbox_intgen_clear_source(&mbox->ap_intgen, source_num); +} + +void pcie_mbox_mask_ap_interrupt_source(struct pcie_mbox *mbox, int source_num) +{ + /* Delegate to ap intgen component */ + pcie_mbox_intgen_mask_source(&mbox->ap_intgen, source_num); +} + +void pcie_mbox_unmask_ap_interrupt_source(struct pcie_mbox *mbox, int source_num) +{ + /* Delegate to ap intgen component */ + pcie_mbox_intgen_unmask_source(&mbox->ap_intgen, source_num); +} + +void pcie_mbox_set_outgoing_interrupt_source(struct pcie_mbox *mbox, enum scsc_mif_abs_target target_node, int source_num) +{ + /* Delegate to appropriate intgen instance*/ + switch (target_node) { + case SCSC_MIF_ABS_TARGET_R4: + pcie_mbox_intgen_set_source(&mbox->r4_intgen, source_num); + break; + case SCSC_MIF_ABS_TARGET_M4: + pcie_mbox_intgen_set_source(&mbox->m4_intgen, source_num); + break; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + case SCSC_MIF_ABS_TARGET_M4_1: + pcie_mbox_intgen_set_source(&mbox->m4_intgen, source_num); + break; +#endif + default: + SCSC_TAG_ERR(PCIE_MIF, "Invalid interrupt target %d\n", target_node); + return; + } +} + +u32 *pcie_mbox_get_mailbox_ptr(struct pcie_mbox *mbox, u32 mbox_index) +{ + if (mbox_index >= PCIE_MIF_MBOX_ISSR_COUNT) { + SCSC_TAG_ERR(PCIE_MIF, "Invalid mailbox index %d\n", mbox_index); + return NULL; + } + + return &mbox->shared_data->mailbox[mbox_index]; +} + diff --git a/drivers/misc/samsung/scsc/pcie_mbox.h b/drivers/misc/samsung/scsc/pcie_mbox.h new file mode 100644 index 000000000000..04bb9dfdb0a5 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox.h @@ -0,0 +1,109 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Mailbox Hardware Emulation (Interface) +* +****************************************************************************/ + +#ifndef __PCIE_MBOX_H +#define __PCIE_MBOX_H + +/* Uses */ + +#include "scsc_mif_abs.h" /* for enum scsc_mif_abs_target */ +#include "pcie_mbox_intgen.h" + +/* Forward */ + +struct pcie_mbox_shared_data; + +/* Types */ + +/** + * Maxwell Mailbox Hardware Emulation. + * + * Uses structure in shared memory to emulate the MX Mailbox Hardware in + * conjunction with matching logic in R4 & M4 firmware. + * + * The emulated hardware includes an array of simple 32 bit mailboxes and + * 3 instances of Interrupt Generator (intgen) hardware (to ap, to r4 & to m4). + */ +struct pcie_mbox { + /** Pointer to shared Mailbox emulation state */ + struct pcie_mbox_shared_data *shared_data; + + /** Interrupt Generator Emulations */ + struct pcie_mbox_intgen ap_intgen; + struct pcie_mbox_intgen r4_intgen; + struct pcie_mbox_intgen m4_intgen; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + struct pcie_mbox_intgen m4_intgen_1; +#endif +}; + +/* Public Functions */ + +/** + * Initialise the mailbox emulation. + */ +void pcie_mbox_init( + struct pcie_mbox *mbox, + void *shared_data_region, + __iomem void *pcie_registers, + struct functor *ap_interrupt_trigger, + struct functor *r4_interrupt_trigger, +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + struct functor *m4_interrupt_trigger, + struct functor *m4_1_interrupt_trigger +#else + struct functor *m4_interrupt_trigger +#endif + ); + +/** + * Get the AP interrupt source mask state as a bitmask. + */ +u32 pcie_mbox_get_ap_interrupt_masked_bitmask(const struct pcie_mbox *mbox); + +/** + * Get the AP interrupt source pending (set and not masked) state as a bitmask. + */ +u32 pcie_mbox_get_ap_interrupt_pending_bitmask(const struct pcie_mbox *mbox); + +/** + * Is the specified AP interrupt source pending (set and not masked)? + */ +bool pcie_mbox_is_ap_interrupt_source_pending(const struct pcie_mbox *mbox, int source_num); + +/** + * Clear the specified AP interrupt source. + */ +void pcie_mbox_clear_ap_interrupt_source(struct pcie_mbox *mbox, int source_num); + +/** + * Mask the specified AP interrupt source. + */ +void pcie_mbox_mask_ap_interrupt_source(struct pcie_mbox *mbox, int source_num); + +/** + * Unmask the specified AP interrupt source. + * + * The interrupt will trigger if the source is currently set. + */ +void pcie_mbox_unmask_ap_interrupt_source(struct pcie_mbox *mbox, int source_num); + +/** + * Set an outgoing interrupt source to R4 or M4 node. + * + * Triggers interrupt in target node if the source is not masked. + */ +void pcie_mbox_set_outgoing_interrupt_source(struct pcie_mbox *mbox, enum scsc_mif_abs_target target_node, int source_num); + +/** + * Get pointer to the specified 32bit Mailbox in shared memory. + */ +u32 *pcie_mbox_get_mailbox_ptr(struct pcie_mbox *mbox, u32 mbox_index); + +#endif /* __PCIE_MBOX_H */ + diff --git a/drivers/misc/samsung/scsc/pcie_mbox_intgen.c b/drivers/misc/samsung/scsc/pcie_mbox_intgen.c new file mode 100644 index 000000000000..a9d6ff839b9f --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox_intgen.c @@ -0,0 +1,140 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Mailbox Interrupt Generator Emulation (Implementation) +* +****************************************************************************/ + +/* Implements */ + +#include "pcie_mbox_intgen.h" + +/* Uses */ + +#include "pcie_mbox_intgen_shared_data.h" +#include "functor.h" +#include + +/* Private Functions */ + +/** + * Trigger the hardware interrupt associated with this Interrupt Generator. + */ +static void pcie_mbox_intgen_trigger_interrupt(struct pcie_mbox_intgen *intgen) +{ + /* Implementation is abstracted to hide the differences from this module */ + functor_call(intgen->trigger_interrupt_fn); +} + +/* Public Functions */ + +void pcie_mbox_intgen_init( + struct pcie_mbox_intgen *intgen, + const char *name, + struct pcie_mbox_intgen_shared_data *shared_data, + struct functor *trigger_interrupt_fn + ) +{ + strncpy(intgen->name, name, sizeof(intgen->name)); + intgen->shared_data = shared_data; + intgen->trigger_interrupt_fn = trigger_interrupt_fn; +} + +u32 pcie_mbox_intgen_get_masked_bitmask(const struct pcie_mbox_intgen *intgen) +{ + /* Compile bitmask from the emulation's separate source mask fields */ + + u32 masked_bitmask = 0; + int source_num; + + for (source_num = 0; source_num < PCIE_MIF_MBOX_NUM_INTR_SOURCES; ++source_num) + if (intgen->shared_data->mask[source_num]) + masked_bitmask |= (1 << source_num); + + return masked_bitmask; +} + +bool pcie_mbox_intgen_is_source_pending(const struct pcie_mbox_intgen *intgen, int source_num) +{ + return intgen->shared_data->status[source_num] && !intgen->shared_data->mask[source_num]; +} + +u32 pcie_mbox_intgen_get_pending_bitmask(const struct pcie_mbox_intgen *intgen) +{ + /* Compile bitmask from the emulation's separate source status and mask fields */ + + u32 pending_bitmask = 0; + int source_num; + + for (source_num = 0; source_num < PCIE_MIF_MBOX_NUM_INTR_SOURCES; ++source_num) + if (pcie_mbox_intgen_is_source_pending(intgen, source_num)) + pending_bitmask |= (1 << source_num); + + return pending_bitmask; +} + +void pcie_mbox_intgen_set_source(struct pcie_mbox_intgen *intgen, int source_num) +{ + if (source_num >= PCIE_MIF_MBOX_NUM_INTR_SOURCES) { + SCSC_TAG_ERR(PCIE_MIF, "Invalid intgen source %d\n", source_num); + return; + } + + SCSC_TAG_DEBUG(PCIE_MIF, "Set source %s:%d. (P %08x, M %08x)\n", + intgen->name, source_num, + pcie_mbox_intgen_get_pending_bitmask(intgen), + pcie_mbox_intgen_get_masked_bitmask(intgen) + ); + + intgen->shared_data->status[source_num] = 1; + pcie_mbox_shared_data_wmb(); + if (!intgen->shared_data->mask[source_num]) + pcie_mbox_intgen_trigger_interrupt(intgen); +} + +void pcie_mbox_intgen_clear_source(struct pcie_mbox_intgen *intgen, int source_num) +{ + if (source_num >= PCIE_MIF_MBOX_NUM_INTR_SOURCES) { + SCSC_TAG_ERR(PCIE_MIF, "Invalid intgen source %d\n", source_num); + return; + } + + SCSC_TAG_DEBUG(PCIE_MIF, "Clear source %s:%d. (P %08x, M %08x)\n", + intgen->name, source_num, + pcie_mbox_intgen_get_pending_bitmask(intgen), + pcie_mbox_intgen_get_masked_bitmask(intgen) + ); + + intgen->shared_data->status[source_num] = 0; + pcie_mbox_shared_data_wmb(); +} + +void pcie_mbox_intgen_mask_source(struct pcie_mbox_intgen *intgen, int source_num) +{ + if (source_num >= PCIE_MIF_MBOX_NUM_INTR_SOURCES) { + SCSC_TAG_ERR(PCIE_MIF, "Invalid intgen source %d\n", source_num); + return; + } + + SCSC_TAG_DEBUG(PCIE_MIF, "Mask source %s:%d.\n", intgen->name, source_num); + + intgen->shared_data->mask[source_num] = 1; + pcie_mbox_shared_data_wmb(); +} + +void pcie_mbox_intgen_unmask_source(struct pcie_mbox_intgen *intgen, int source_num) +{ + if (source_num >= PCIE_MIF_MBOX_NUM_INTR_SOURCES) { + SCSC_TAG_ERR(PCIE_MIF, "Invalid intgen source %d\n", source_num); + return; + } + + SCSC_TAG_DEBUG(PCIE_MIF, "UnMask source %s:%d.\n", intgen->name, source_num); + + intgen->shared_data->mask[source_num] = 0; + pcie_mbox_shared_data_wmb(); + if (intgen->shared_data->status[source_num]) + pcie_mbox_intgen_trigger_interrupt(intgen); +} + diff --git a/drivers/misc/samsung/scsc/pcie_mbox_intgen.h b/drivers/misc/samsung/scsc/pcie_mbox_intgen.h new file mode 100644 index 000000000000..b76c198e4757 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox_intgen.h @@ -0,0 +1,105 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Mailbox Interrupt Generator Emulation (Interface) +* +****************************************************************************/ + +#ifndef __PCIE_MBOX_INTGEN_H +#define __PCIE_MBOX_INTGEN_H + +/* Uses */ + +#include "linux/types.h" + +/* Forward Types */ + +struct pcie_mbox_intgen_shared_data; +struct functor; + +/* Public Types */ + +/** + * Maxwell Mailbox Interrupt Generator Emulation descriptor + * + * Uses structures in shared memory to emulate the Interrupt Generation + * Hardware in conjunction with matching logic in R4 & M4 firmware. + * + * This is used to implement 3 instances of the Interrupt Generation (intgen) + * hardware (to ap, to r4 & to m4). + */ +struct pcie_mbox_intgen { + char name[16]; + struct pcie_mbox_intgen_shared_data *shared_data; + struct functor *trigger_interrupt_fn; +}; + +/* Public Functions */ + +/** + * Initialise this Interrupt Generator. + */ +void pcie_mbox_intgen_init( + + /** This Interrupt Generator emulation */ + struct pcie_mbox_intgen *intgen, + + /** Name for debugging purposes */ + const char *name, + + /** Pointer to shared data for this emulation */ + struct pcie_mbox_intgen_shared_data *shared_data, + + /** + * Functor to trigger associated hardware interrupt. + * + * The trigger is abstracted so that the same logic can be + * re-used for the incoming and outgoing emulations. + */ + struct functor *trigger_interrupt_fn + ); + +/** + * Get this Interrupt Generator's source masked state as a bitmask. + */ +u32 pcie_mbox_intgen_get_masked_bitmask(const struct pcie_mbox_intgen *intgen); + +/** + * Get this Interrupt Generator's source pending state (set and not masked) as a bitmask. + */ +u32 pcie_mbox_intgen_get_pending_bitmask(const struct pcie_mbox_intgen *intgen); + +/** + * Set specified Interrupt Generator source. + * + * Triggers interrupt on the interrupt target if the source is not masked. + */ +void pcie_mbox_intgen_set_source(struct pcie_mbox_intgen *intgen, int source_num); + +/** + * Clear specified Interrupt Generator source. + */ +void pcie_mbox_intgen_clear_source(struct pcie_mbox_intgen *intgen, int source_num); + +/** + * Mask specified Interrupt Generator source. + */ +void pcie_mbox_intgen_mask_source(struct pcie_mbox_intgen *intgen, int source_num); + +/** + * Unmask specified Interrupt Generator source. + * + * The associated hardware interrupt will be triggered if + * the specified source is currently set. + */ +void pcie_mbox_intgen_unmask_source(struct pcie_mbox_intgen *intgen, int source_num); + +/** + * Is the specified interrupt source pending (set and not masked)? + */ +bool pcie_mbox_intgen_is_source_pending(const struct pcie_mbox_intgen *intgen, int source_num); + + +#endif /* __PCIE_MBOX_INTGEN_H */ + diff --git a/drivers/misc/samsung/scsc/pcie_mbox_intgen_shared_data.h b/drivers/misc/samsung/scsc/pcie_mbox_intgen_shared_data.h new file mode 100644 index 000000000000..ff0dd5cb8b6f --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox_intgen_shared_data.h @@ -0,0 +1,35 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Software Mailbox Interrupt Generator Emulation shared data +* structure. +* +****************************************************************************/ + +#ifndef __PCIE_MBOX_INTGEN_SHARED_DATA_H +#define __PCIE_MBOX_INTGEN_SHARED_DATA_H + +/* Uses */ + +#include "pcie_mbox_shared_data_defs.h" +#include "linux/types.h" + +/* Types */ + +/** + * Mailbox Interrupt Generator Emulation shared state. + * + * Notes: + * - Structure must be packed. + * - All integers are LittleEndian. + */ +PCI_MBOX_SHARED_DATA_ATTR struct pcie_mbox_intgen_shared_data { + /** Interrupt source mask state (whole word each to avoid RMW issues) */ + uint32_t mask[PCIE_MIF_MBOX_NUM_INTR_SOURCES]; + /** Interrupt source set state (whole word each to avoid RMW issues) */ + uint32_t status[PCIE_MIF_MBOX_NUM_INTR_SOURCES]; +}; + +#endif /* __PCIE_MBOX_INTGEN_SHARED_DATA_H */ + diff --git a/drivers/misc/samsung/scsc/pcie_mbox_shared_data.h b/drivers/misc/samsung/scsc/pcie_mbox_shared_data.h new file mode 100644 index 000000000000..40dcb0f8f12e --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox_shared_data.h @@ -0,0 +1,41 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Software Mailbox Emulation shared data structure. +* +* Ref: SC-506707-DD - Structure version 2 +* +****************************************************************************/ + +#ifndef __PCIE_MBOX_SHARED_DATA_H +#define __PCIE_MBOX_SHARED_DATA_H + +/* Uses */ + +#include "pcie_mbox_shared_data_defs.h" +#include "pcie_mbox_intgen_shared_data.h" + +/* Types */ + +/** + * Mailbox Emulation Generator shared state. + * + * Notes: + * - Structure must be packed. + * - All integers are LittleEndian. + */ +PCI_MBOX_SHARED_DATA_ATTR struct pcie_mbox_shared_data { + uint32_t mailbox[PCIE_MIF_MBOX_ISSR_COUNT]; + uint32_t magic; + uint32_t version; + struct pcie_mbox_intgen_shared_data ap_interrupt; + struct pcie_mbox_intgen_shared_data r4_interrupt; + struct pcie_mbox_intgen_shared_data m4_interrupt; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + struct pcie_mbox_intgen_shared_data m4_1_interrupt; +#endif +}; + +#endif /* __PCIE_MBOX_SHARED_DATA_H */ + diff --git a/drivers/misc/samsung/scsc/pcie_mbox_shared_data_defs.h b/drivers/misc/samsung/scsc/pcie_mbox_shared_data_defs.h new file mode 100644 index 000000000000..fe9227ed234a --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mbox_shared_data_defs.h @@ -0,0 +1,55 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +* Maxwell Software Mailbox Emulation shared data definitions. +* +* Ref: SC-506707-DD - Structure version 2 +* +****************************************************************************/ + +#ifndef __PCIE_MBOX_SHARED_DATA_DEFS_H +#define __PCIE_MBOX_SHARED_DATA_DEFS_H + +/* Defines */ + +/** Offset of shared data structure from end of shared ram */ +#define PCIE_MIF_MBOX_RESERVED_LEN (0x400) + +#define PCIE_MIF_MBOX_MAGIC_NUMBER (0x3a11b0c5) + +#define PCIE_MIF_MBOX_VERSION_NUMBER (0x00000002) + +/** + * Number of mailboxes. + * + * Note: Current hardware supports 16 mailboxes. The extra mailboxes + * in the emulation may be used to emulate other signals. + */ +#define PCIE_MIF_MBOX_ISSR_COUNT (32) + +/** + * Number of interrupt sources per Interrupt Generator Emulation instance. + * + * Note: Current hardware supports 16 sources. The extra sources + * in the emulation may be used to emulate other signals + * (e.g. RESET_REQUEST from MX to AP). + * + */ +#define PCIE_MIF_MBOX_NUM_INTR_SOURCES (32) + +/** + * Structure must be packed. + */ +#define PCI_MBOX_SHARED_DATA_ATTR __packed + +/** + * Write barrier for syncing writes to pcie_mbox_shared_data + * shared data area. + * + * HERE: Can we use something lighter? E.g. dma_wmb()? + */ +#define pcie_mbox_shared_data_wmb() wmb() + +#endif /* __PCIE_MBOX_SHARED_DATA_DEFS_H */ + diff --git a/drivers/misc/samsung/scsc/pcie_mif.c b/drivers/misc/samsung/scsc/pcie_mif.c new file mode 100644 index 000000000000..c889ff9dd798 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mif.c @@ -0,0 +1,701 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved +* +****************************************************************************/ + +/* Implements */ + +#include "pcie_mif.h" + +/* Uses */ + +#include +#include +#include +#include +#include +#include +#include "pcie_mif_module.h" +#include "pcie_proc.h" +#include "pcie_mbox.h" +#include "functor.h" + +#define PCIE_MIF_RESET_REQUEST_SOURCE 31 + +/* Module parameters */ + +static bool enable_pcie_mif_arm_reset = true; +module_param(enable_pcie_mif_arm_reset, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_pcie_mif_arm_reset, "Enables ARM cores reset"); + +/* Types */ + +struct pcie_mif { + struct scsc_mif_abs interface; + struct pci_dev *pdev; + int dma_using_dac; /* =1 if 64-bit DMA is used, =0 otherwise. */ + __iomem void *registers; + + struct device *dev; + + u8 *mem; /* DMA memory mapped to PCIe space for MX-AP comms */ + struct pcie_mbox mbox; /* mailbox emulation */ + size_t mem_allocated; + dma_addr_t dma_addr; + + /* Callback function and dev pointer mif_intr manager handler */ + void (*r4_handler)(int irq, void *data); + void *irq_dev; + + /* Reset Request handler and context */ + void (*reset_request_handler)(int irq_num_ignored, void *data); + void *reset_request_handler_data; + + /** + * Functors to trigger, or simulate, MIF WLBT Mailbox interrupts. + * + * These functors isolates the Interrupt Generator logic + * from differences in physical interrupt generation. + */ + struct functor trigger_ap_interrupt; + struct functor trigger_r4_interrupt; + struct functor trigger_m4_interrupt; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + struct functor trigger_m4_1_interrupt; +#endif +}; + +/* Private Macros */ + +/** Upcast from interface member to pcie_mif */ +#define pcie_mif_from_mif_abs(MIF_ABS_PTR) container_of(MIF_ABS_PTR, struct pcie_mif, interface) + +/** Upcast from trigger_ap_interrupt member to pcie_mif */ +#define pcie_mif_from_trigger_ap_interrupt(trigger) container_of(trigger, struct pcie_mif, trigger_ap_interrupt) + +/** Upcast from trigger_r4_interrupt member to pcie_mif */ +#define pcie_mif_from_trigger_r4_interrupt(trigger) container_of(trigger, struct pcie_mif, trigger_r4_interrupt) + +/** Upcast from trigger_m4_interrupt member to pcie_mif */ +#define pcie_mif_from_trigger_m4_interrupt(trigger) container_of(trigger, struct pcie_mif, trigger_m4_interrupt) + +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT +/** Upcast from trigger_m4_interrupt member to pcie_mif */ +#define pcie_mif_from_trigger_m4_1_interrupt(trigger) container_of(trigger, struct pcie_mif, trigger_m4_1_interrupt) +#endif + +/* Private Functions */ + +static void pcie_mif_irq_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; +} + +static void pcie_mif_emulate_reset_request_interrupt(struct pcie_mif *pcie) +{ + /* The RESET_REQUEST interrupt is emulated over PCIe using a spare MIF interrupt source */ + if (pcie_mbox_is_ap_interrupt_source_pending(&pcie->mbox, PCIE_MIF_RESET_REQUEST_SOURCE)) { + /* Invoke handler if registered */ + if (pcie->reset_request_handler) + pcie->reset_request_handler(0, pcie->reset_request_handler_data); + /* Clear the source to emulate hardware interrupt behaviour */ + pcie_mbox_clear_ap_interrupt_source(&pcie->mbox, PCIE_MIF_RESET_REQUEST_SOURCE); + } +} + +#ifdef CONFIG_SCSC_QOS +static int pcie_mif_pm_qos_add_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + return 0; +} + +static int pcie_mif_pm_qos_update_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + return 0; +} + +static int pcie_mif_pm_qos_remove_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req) +{ + return 0; +} +#endif + +irqreturn_t pcie_mif_isr(int irq, void *data) +{ + struct pcie_mif *pcie = (struct pcie_mif *)data; + + SCSC_TAG_DEBUG(PCIE_MIF, "MIF Interrupt Received. (Pending 0x%08x, Mask 0x%08x)\n", + pcie_mbox_get_ap_interrupt_pending_bitmask(&pcie->mbox), + pcie_mbox_get_ap_interrupt_masked_bitmask(&pcie->mbox) + ); + + /* + * Intercept mailbox interrupt sources (numbers > 15) used to emulate other + * signalling paths missing from emulator/PCIe hardware. + */ + pcie_mif_emulate_reset_request_interrupt(pcie); + + /* Invoke the normal MIF interrupt handler */ + if (pcie->r4_handler != pcie_mif_irq_default_handler) + pcie->r4_handler(irq, pcie->irq_dev); + else + SCSC_TAG_INFO(PCIE_MIF, "Any handler registered\n"); + + return IRQ_HANDLED; +} + +/** + * Trigger, or simulate, inbound (to AP) PCIe interrupt. + * + * Called back via functor. + */ +static void pcie_mif_trigger_ap_interrupt(struct functor *trigger) +{ + struct pcie_mif *pcie = pcie_mif_from_trigger_ap_interrupt(trigger); + + /* + * Invoke the normal isr handler synchronously. + * + * If synchronous handling proves problematic then launch + * an async task or trigger GIC interrupt manually (if supported). + */ + (void)pcie_mif_isr(0, (void *)pcie); +}; + +/** + * Trigger PCIe interrupt to R4. + * + * Called back via functor. + */ +static void pcie_mif_trigger_r4_interrupt(struct functor *trigger) +{ + struct pcie_mif *pcie = pcie_mif_from_trigger_r4_interrupt(trigger); + + SCSC_TAG_DEBUG(PCIE_MIF, "Triggering R4 Mailbox interrupt.\n"); + + iowrite32(0x00000001, pcie->registers + SCSC_PCIE_NEWMSG); + mmiowb(); +}; + +/** + * Trigger PCIe interrupt to M4. + * + * Called back via functor. + */ +static void pcie_mif_trigger_m4_interrupt(struct functor *trigger) +{ + struct pcie_mif *pcie = pcie_mif_from_trigger_m4_interrupt(trigger); + + SCSC_TAG_DEBUG(PCIE_MIF, "Triggering M4 Mailbox interrupt.\n"); + + iowrite32(0x00000001, pcie->registers + SCSC_PCIE_NEWMSG2); + mmiowb(); +}; + +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT +/** + * Trigger PCIe interrupt to M4. + * + * Called back via functor. + */ +static void pcie_mif_trigger_m4_1_interrupt(struct functor *trigger) +{ + struct pcie_mif *pcie = pcie_mif_from_trigger_m4_1_interrupt(trigger); + + SCSC_TAG_DEBUG(PCIE_MIF, "Triggering M4 1 Mailbox interrupt.\n"); + + iowrite32(0x00000001, pcie->registers + SCSC_PCIE_NEWMSG3); + mmiowb(); +}; +#endif + +static void pcie_mif_destroy(struct scsc_mif_abs *interface) +{ + /* Avoid unused parameter error */ + (void)interface; +} + +static char *pcie_mif_get_uid(struct scsc_mif_abs *interface) +{ + /* Avoid unused parameter error */ + (void)interface; + /* TODO */ + /* return "0" for the time being */ + return "0"; +} + +static int pcie_mif_reset(struct scsc_mif_abs *interface, bool reset) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + int ret; + + if (enable_pcie_mif_arm_reset || !reset) { + /* Sanity check */ + iowrite32(0xdeadbeef, pcie->registers + SCSC_PCIE_SIGNATURE); + mmiowb(); + ret = ioread32(pcie->registers + SCSC_PCIE_SIGNATURE); + if (ret != 0xdeadbeef) { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, "Can't acces BAR0 magic number. Readed: 0x%x Expected: 0x%x\n", + ret, 0xdeadbeef); + return -ENODEV; + } + + iowrite32(reset ? 1 : 0, + pcie->registers + SCSC_PCIE_GRST_OFFSET); + mmiowb(); + } else + SCSC_TAG_INFO(PCIE_MIF, "Not resetting ARM Cores enable_pcie_mif_arm_reset: %d\n", + enable_pcie_mif_arm_reset); + return 0; +} + +static void *pcie_mif_map(struct scsc_mif_abs *interface, size_t *allocated) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + int ret; + size_t map_len = PCIE_MIF_ALLOC_MEM; + + if (allocated) + *allocated = 0; + + if (map_len > (PCIE_MIF_PREALLOC_MEM - 1)) { + SCSC_TAG_ERR(PCIE_MIF, "Error allocating DMA memory, requested %zu, maximum %d, consider different size\n", map_len, PCIE_MIF_PREALLOC_MEM); + return NULL; + } + + /* should return PAGE_ALIGN Memory */ + pcie->mem = dma_alloc_coherent(pcie->dev, + PCIE_MIF_PREALLOC_MEM, &pcie->dma_addr, GFP_KERNEL); + if (pcie->mem == NULL) { + SCSC_TAG_ERR(PCIE_MIF, "Error allocating %d DMA memory\n", PCIE_MIF_PREALLOC_MEM); + return NULL; + } + + pcie->mem_allocated = map_len; + + SCSC_TAG_INFO_DEV(PCIE_MIF, pcie->dev, "Allocated dma coherent mem: %p addr %p\n", pcie->mem, (void *)pcie->dma_addr); + + iowrite32((unsigned int)pcie->dma_addr, + pcie->registers + SCSC_PCIE_OFFSET); + mmiowb(); + ret = ioread32(pcie->registers + SCSC_PCIE_OFFSET); + SCSC_TAG_INFO(PCIE_MIF, "Read SHARED_BA 0x%0x\n", ret); + if (ret != (unsigned int)pcie->dma_addr) { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, "Can't acces BAR0 Shared BA. Readed: 0x%x Expected: 0x%x\n", ret, (unsigned int)pcie->dma_addr); + return NULL; + } + + /* Initialised the interrupt trigger functors required by mbox emulation */ + functor_init(&pcie->trigger_ap_interrupt, pcie_mif_trigger_ap_interrupt); + functor_init(&pcie->trigger_r4_interrupt, pcie_mif_trigger_r4_interrupt); + functor_init(&pcie->trigger_m4_interrupt, pcie_mif_trigger_m4_interrupt); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + functor_init(&pcie->trigger_m4_1_interrupt, pcie_mif_trigger_m4_1_interrupt); +#endif + + /* Initialise mailbox emulation to use shared memory at the end of PCIE_MIF_PREALLOC_MEM */ + pcie_mbox_init( + &pcie->mbox, + pcie->mem + PCIE_MIF_PREALLOC_MEM - PCIE_MIF_MBOX_RESERVED_LEN, + pcie->registers, + &pcie->trigger_ap_interrupt, + &pcie->trigger_r4_interrupt, +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + &pcie->trigger_m4_interrupt, + &pcie->trigger_m4_1_interrupt +#else + &pcie->trigger_m4_interrupt +#endif + ); + + /* Return the max allocatable memory on this abs. implementation */ + if (allocated) + *allocated = map_len; + + return pcie->mem; +} + +/* HERE: Not sure why mem is passed in - its stored in pcie - as it should be */ +static void pcie_mif_unmap(struct scsc_mif_abs *interface, void *mem) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* Avoid unused parameter error */ + (void)mem; + + dma_free_coherent(pcie->dev, PCIE_MIF_PREALLOC_MEM, pcie->mem, pcie->dma_addr); + SCSC_TAG_INFO_DEV(PCIE_MIF, pcie->dev, "Freed dma coherent mem: %p addr %p\n", pcie->mem, (void *)pcie->dma_addr); +} + +static u32 pcie_mif_irq_bit_mask_status_get(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox emulation component */ + return pcie_mbox_get_ap_interrupt_masked_bitmask(&pcie->mbox); +} + +static u32 pcie_mif_irq_get(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox emulation component */ + return pcie_mbox_get_ap_interrupt_pending_bitmask(&pcie->mbox); +} + +static void pcie_mif_irq_bit_clear(struct scsc_mif_abs *interface, int bit_num) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox emulation component */ + pcie_mbox_clear_ap_interrupt_source(&pcie->mbox, bit_num); +} + +static void pcie_mif_irq_bit_mask(struct scsc_mif_abs *interface, int bit_num) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox interrupt emulation component */ + pcie_mbox_mask_ap_interrupt_source(&pcie->mbox, bit_num); +} + +static void pcie_mif_irq_bit_unmask(struct scsc_mif_abs *interface, int bit_num) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox emulation component */ + pcie_mbox_unmask_ap_interrupt_source(&pcie->mbox, bit_num); +} + +static void pcie_mif_irq_bit_set(struct scsc_mif_abs *interface, int bit_num, enum scsc_mif_abs_target target) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox emulation sub-module */ + pcie_mbox_set_outgoing_interrupt_source(&pcie->mbox, target, bit_num); +} + +static void pcie_mif_irq_reg_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + pcie->r4_handler = handler; + pcie->irq_dev = dev; +} + +static void pcie_mif_irq_unreg_handler(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + pcie->r4_handler = pcie_mif_irq_default_handler; + pcie->irq_dev = NULL; +} + +static void pcie_mif_irq_reg_reset_request_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + pcie->reset_request_handler = handler; + pcie->reset_request_handler_data = dev; + + pcie_mbox_clear_ap_interrupt_source(&pcie->mbox, PCIE_MIF_RESET_REQUEST_SOURCE); + pcie_mbox_unmask_ap_interrupt_source(&pcie->mbox, PCIE_MIF_RESET_REQUEST_SOURCE); +} + +static void pcie_mif_irq_unreg_reset_request_handler(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + pcie_mbox_mask_ap_interrupt_source(&pcie->mbox, PCIE_MIF_RESET_REQUEST_SOURCE); + pcie->reset_request_handler = NULL; +} + +static u32 *pcie_mif_get_mbox_ptr(struct scsc_mif_abs *interface, u32 mbox_index) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + /* delegate to mbox emulation sub-module */ + return pcie_mbox_get_mailbox_ptr(&pcie->mbox, mbox_index); +} + +static int pcie_mif_get_mifram_ref(struct scsc_mif_abs *interface, void *ptr, scsc_mifram_ref *ref) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + if ((u8 *)ptr > (pcie->mem + 4 * 1024 * 1024)) { + SCSC_TAG_ERR(PCIE_MIF, "ooops limits reached\n"); + return -ENOMEM; + } + + /* Ref is byte offset wrt start of shared memory */ + *ref = (scsc_mifram_ref)((uintptr_t)ptr - (uintptr_t)pcie->mem); + + return 0; +} + +static void *pcie_mif_get_mifram_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PCIE_MIF, pcie->dev, "\n"); + + /* Check limits */ + if (ref >= 0 && ref < PCIE_MIF_ALLOC_MEM) + return (void *)((uintptr_t)pcie->mem + (uintptr_t)ref); + else + return NULL; +} + +static uintptr_t pcie_mif_get_mif_pfn(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + return virt_to_phys(pcie->mem) >> PAGE_SHIFT; +} + +static struct device *pcie_mif_get_mif_device(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + return pcie->dev; +} + +static void pcie_mif_irq_clear(void) +{ +} + +static void pcie_mif_dump_register(struct scsc_mif_abs *interface) +{ +} + +struct scsc_mif_abs *pcie_mif_create(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int rc = 0; + struct scsc_mif_abs *pcie_if; + struct pcie_mif *pcie = (struct pcie_mif *)devm_kzalloc(&pdev->dev, sizeof(struct pcie_mif), GFP_KERNEL); + u16 cmd; + + /* Avoid unused parameter error */ + (void)id; + + if (!pcie) + return NULL; + + pcie_if = &pcie->interface; + + /* initialise interface structure */ + pcie_if->destroy = pcie_mif_destroy; + pcie_if->get_uid = pcie_mif_get_uid; + pcie_if->reset = pcie_mif_reset; + pcie_if->map = pcie_mif_map; + pcie_if->unmap = pcie_mif_unmap; +#ifdef MAILBOX_SETGET + pcie_if->mailbox_set = pcie_mif_mailbox_set; + pcie_if->mailbox_get = pcie_mif_mailbox_get; +#endif + pcie_if->irq_bit_set = pcie_mif_irq_bit_set; + pcie_if->irq_get = pcie_mif_irq_get; + pcie_if->irq_bit_mask_status_get = pcie_mif_irq_bit_mask_status_get; + pcie_if->irq_bit_clear = pcie_mif_irq_bit_clear; + pcie_if->irq_bit_mask = pcie_mif_irq_bit_mask; + pcie_if->irq_bit_unmask = pcie_mif_irq_bit_unmask; + pcie_if->irq_reg_handler = pcie_mif_irq_reg_handler; + pcie_if->irq_unreg_handler = pcie_mif_irq_unreg_handler; + pcie_if->irq_reg_reset_request_handler = pcie_mif_irq_reg_reset_request_handler; + pcie_if->irq_unreg_reset_request_handler = pcie_mif_irq_unreg_reset_request_handler; + pcie_if->get_mbox_ptr = pcie_mif_get_mbox_ptr; + pcie_if->get_mifram_ptr = pcie_mif_get_mifram_ptr; + pcie_if->get_mifram_ref = pcie_mif_get_mifram_ref; + pcie_if->get_mifram_pfn = pcie_mif_get_mif_pfn; + pcie_if->get_mif_device = pcie_mif_get_mif_device; + pcie_if->irq_clear = pcie_mif_irq_clear; + pcie_if->mif_dump_registers = pcie_mif_dump_register; + pcie_if->mif_read_register = NULL; +#ifdef CONFIG_SCSC_QOS + pcie_if->mif_pm_qos_add_request = pcie_mif_pm_qos_add_request; + pcie_if->mif_pm_qos_update_request = pcie_mif_pm_qos_update_request; + pcie_if->mif_pm_qos_remove_request = pcie_mif_pm_qos_remove_request; +#endif + /* Suspend/resume not supported in PCIe MIF */ + pcie_if->suspend_reg_handler = NULL; + pcie_if->suspend_unreg_handler = NULL; + + /* Update state */ + pcie->pdev = pdev; + + pcie->dev = &pdev->dev; + + pcie->r4_handler = pcie_mif_irq_default_handler; + pcie->irq_dev = NULL; + + /* Just do whats is necessary to meet the pci probe + * -BAR0 stuff + * -Interrupt (will be able to handle interrupts?) + */ + + /* My stuff */ + pci_set_drvdata(pdev, pcie); + + rc = pcim_enable_device(pdev); + if (rc) { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, + "Error enabling device.\n"); + return NULL; + } + + /* This function returns the flags associated with this resource.*/ + /* esource flags are used to define some features of the individual resource. + * For PCI resources associated with PCI I/O regions, the information is extracted from the base address registers */ + /* IORESOURCE_MEM = If the associated I/O region exists, one and only one of these flags is set */ + if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) { + SCSC_TAG_ERR(PCIE_MIF, "Incorrect BAR configuration\n"); + return NULL; + } + + /* old --- rc = pci_request_regions(pdev, "foo"); */ + /* Request and iomap regions specified by @mask (0x01 ---> BAR0)*/ + rc = pcim_iomap_regions(pdev, BIT(0), DRV_NAME); + if (rc) { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, + "pcim_iomap_regions() failed. Aborting.\n"); + return NULL; + } + + + pci_set_master(pdev); + + /* Access iomap allocation table */ + /* return __iomem * const * */ + pcie->registers = pcim_iomap_table(pdev)[0]; + + /* Set up a single MSI interrupt */ + if (pci_enable_msi(pdev)) { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, + "Failed to enable MSI interrupts. Aborting.\n"); + return NULL; + } + rc = devm_request_irq(&pdev->dev, pdev->irq, pcie_mif_isr, 0, + DRV_NAME, pcie); + if (rc) { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, + "Failed to register MSI handler. Aborting.\n"); + return NULL; + } + +/* if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) { + * SCSC_TAG_INFO_DEV(PCIE_MIF, pcie->dev, "DMA mask 64bits.\n"); + * pcie->dma_using_dac = 1; */ + if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { + SCSC_TAG_INFO_DEV(PCIE_MIF, pcie->dev, "DMA mask 32bits.\n"); + pcie->dma_using_dac = 0; + } else { + SCSC_TAG_ERR_DEV(PCIE_MIF, pcie->dev, "Failed to set DMA mask. Aborting.\n"); + return NULL; + } + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + + /* Make sure Mx is in the reset state */ + pcie_mif_reset(pcie_if, true); + + /* Create debug proc entry */ + pcie_create_proc_dir(pcie); + + return pcie_if; +} + +void pcie_mif_destroy_pcie(struct pci_dev *pdev, struct scsc_mif_abs *interface) +{ + /* Create debug proc entry */ + pcie_remove_proc_dir(); + + pci_disable_device(pdev); +} + +struct pci_dev *pcie_mif_get_pci_dev(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + BUG_ON(!interface || !pcie); + + return pcie->pdev; +} + +struct device *pcie_mif_get_dev(struct scsc_mif_abs *interface) +{ + struct pcie_mif *pcie = pcie_mif_from_mif_abs(interface); + + BUG_ON(!interface || !pcie); + + return pcie->dev; +} + + + +/* Functions for proc entry */ +int pcie_mif_set_bar0_register(struct pcie_mif *pcie, unsigned int value, unsigned int offset) +{ + iowrite32(value, pcie->registers + offset); + mmiowb(); + + return 0; +} + +void pcie_mif_get_bar0(struct pcie_mif *pcie, struct scsc_bar0_reg *bar0) +{ + bar0->NEWMSG = ioread32(pcie->registers + SCSC_PCIE_NEWMSG); + bar0->SIGNATURE = ioread32(pcie->registers + SCSC_PCIE_SIGNATURE); + bar0->OFFSET = ioread32(pcie->registers + SCSC_PCIE_OFFSET); + bar0->RUNEN = ioread32(pcie->registers + SCSC_PCIE_RUNEN); + bar0->DEBUG = ioread32(pcie->registers + SCSC_PCIE_DEBUG); + bar0->AXIWCNT = ioread32(pcie->registers + SCSC_PCIE_AXIWCNT); + bar0->AXIRCNT = ioread32(pcie->registers + SCSC_PCIE_AXIRCNT); + bar0->AXIWADDR = ioread32(pcie->registers + SCSC_PCIE_AXIWADDR); + bar0->AXIRADDR = ioread32(pcie->registers + SCSC_PCIE_AXIRADDR); + bar0->TBD = ioread32(pcie->registers + SCSC_PCIE_TBD); + bar0->AXICTRL = ioread32(pcie->registers + SCSC_PCIE_AXICTRL); + bar0->AXIDATA = ioread32(pcie->registers + SCSC_PCIE_AXIDATA); + bar0->AXIRDBP = ioread32(pcie->registers + SCSC_PCIE_AXIRDBP); + bar0->IFAXIWCNT = ioread32(pcie->registers + SCSC_PCIE_IFAXIWCNT); + bar0->IFAXIRCNT = ioread32(pcie->registers + SCSC_PCIE_IFAXIRCNT); + bar0->IFAXIWADDR = ioread32(pcie->registers + SCSC_PCIE_IFAXIWADDR); + bar0->IFAXIRADDR = ioread32(pcie->registers + SCSC_PCIE_IFAXIRADDR); + bar0->IFAXICTRL = ioread32(pcie->registers + SCSC_PCIE_IFAXICTRL); + bar0->GRST = ioread32(pcie->registers + SCSC_PCIE_GRST); + bar0->AMBA2TRANSAXIWCNT = ioread32(pcie->registers + SCSC_PCIE_AMBA2TRANSAXIWCNT); + bar0->AMBA2TRANSAXIRCNT = ioread32(pcie->registers + SCSC_PCIE_AMBA2TRANSAXIRCNT); + bar0->AMBA2TRANSAXIWADDR = ioread32(pcie->registers + SCSC_PCIE_AMBA2TRANSAXIWADDR); + bar0->AMBA2TRANSAXIRADDR = ioread32(pcie->registers + SCSC_PCIE_AMBA2TRANSAXIRADDR); + bar0->AMBA2TRANSAXICTR = ioread32(pcie->registers + SCSC_PCIE_AMBA2TRANSAXICTR); + bar0->TRANS2PCIEREADALIGNAXIWCNT = ioread32(pcie->registers + SCSC_PCIE_TRANS2PCIEREADALIGNAXIWCNT); + bar0->TRANS2PCIEREADALIGNAXIRCNT = ioread32(pcie->registers + SCSC_PCIE_TRANS2PCIEREADALIGNAXIRCNT); + bar0->TRANS2PCIEREADALIGNAXIWADDR = ioread32(pcie->registers + SCSC_PCIE_TRANS2PCIEREADALIGNAXIWADDR); + bar0->TRANS2PCIEREADALIGNAXIRADDR = ioread32(pcie->registers + SCSC_PCIE_TRANS2PCIEREADALIGNAXIRADDR); + bar0->TRANS2PCIEREADALIGNAXICTRL = ioread32(pcie->registers + SCSC_PCIE_TRANS2PCIEREADALIGNAXICTRL); + bar0->READROUNDTRIPMIN = ioread32(pcie->registers + SCSC_PCIE_READROUNDTRIPMIN); + bar0->READROUNDTRIPMAX = ioread32(pcie->registers + SCSC_PCIE_READROUNDTRIPMAX); + bar0->READROUNDTRIPLAST = ioread32(pcie->registers + SCSC_PCIE_READROUNDTRIPLAST); + bar0->CPTAW0 = ioread32(pcie->registers + SCSC_PCIE_CPTAW0); + bar0->CPTAW1 = ioread32(pcie->registers + SCSC_PCIE_CPTAW1); + bar0->CPTAR0 = ioread32(pcie->registers + SCSC_PCIE_CPTAR0); + bar0->CPTAR1 = ioread32(pcie->registers + SCSC_PCIE_CPTAR1); + bar0->CPTB0 = ioread32(pcie->registers + SCSC_PCIE_CPTB0); + bar0->CPTW0 = ioread32(pcie->registers + SCSC_PCIE_CPTW0); + bar0->CPTW1 = ioread32(pcie->registers + SCSC_PCIE_CPTW1); + bar0->CPTW2 = ioread32(pcie->registers + SCSC_PCIE_CPTW2); + bar0->CPTR0 = ioread32(pcie->registers + SCSC_PCIE_CPTR0); + bar0->CPTR1 = ioread32(pcie->registers + SCSC_PCIE_CPTR1); + bar0->CPTR2 = ioread32(pcie->registers + SCSC_PCIE_CPTR2); + bar0->CPTRES = ioread32(pcie->registers + SCSC_PCIE_CPTRES); + bar0->CPTAWDELAY = ioread32(pcie->registers + SCSC_PCIE_CPTAWDELAY); + bar0->CPTARDELAY = ioread32(pcie->registers + SCSC_PCIE_CPTARDELAY); + bar0->CPTSRTADDR = ioread32(pcie->registers + SCSC_PCIE_CPTSRTADDR); + bar0->CPTENDADDR = ioread32(pcie->registers + SCSC_PCIE_CPTENDADDR); + bar0->CPTSZLTHID = ioread32(pcie->registers + SCSC_PCIE_CPTSZLTHID); + bar0->CPTPHSEL = ioread32(pcie->registers + SCSC_PCIE_CPTPHSEL); + bar0->CPTRUN = ioread32(pcie->registers + SCSC_PCIE_CPTRUN); + bar0->FPGAVER = ioread32(pcie->registers + SCSC_PCIE_FPGAVER); +} diff --git a/drivers/misc/samsung/scsc/pcie_mif.h b/drivers/misc/samsung/scsc/pcie_mif.h new file mode 100644 index 000000000000..f213c928801d --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mif.h @@ -0,0 +1,150 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved +* +****************************************************************************/ + +#ifndef __PCIE_MIF_H +#define __PCIE_MIF_H +#include +#include "scsc_mif_abs.h" + +#ifdef CONDOR +#define FPGA_OFFSET 0xb8000000 +#else +#define FPGA_OFFSET 0x80000000 +#endif + +#define SCSC_PCIE_MAGIC_VAL 0xdeadbeef + +#define SCSC_PCIE_GRST_OFFSET 0x48 + +/* BAR0 Registers [PCIeBridgeBAR0Regs.doc] */ +#define SCSC_PCIE_NEWMSG 0x0 +#define SCSC_PCIE_SIGNATURE 0x4 +#define SCSC_PCIE_OFFSET 0x8 +#define SCSC_PCIE_RUNEN 0xC +#define SCSC_PCIE_DEBUG 0x10 +#define SCSC_PCIE_AXIWCNT 0x14 +#define SCSC_PCIE_AXIRCNT 0x18 +#define SCSC_PCIE_AXIWADDR 0x1C +#define SCSC_PCIE_AXIRADDR 0x20 +#define SCSC_PCIE_TBD 0x24 +#define SCSC_PCIE_AXICTRL 0x28 +#define SCSC_PCIE_AXIDATA 0x2C +#define SCSC_PCIE_AXIRDBP 0x30 +#define SCSC_PCIE_IFAXIWCNT 0x34 +#define SCSC_PCIE_IFAXIRCNT 0x38 +#define SCSC_PCIE_IFAXIWADDR 0x3C +#define SCSC_PCIE_IFAXIRADDR 0x40 +#define SCSC_PCIE_IFAXICTRL 0x44 +#define SCSC_PCIE_GRST 0x48 +#define SCSC_PCIE_AMBA2TRANSAXIWCNT 0x4C +#define SCSC_PCIE_AMBA2TRANSAXIRCNT 0x50 +#define SCSC_PCIE_AMBA2TRANSAXIWADDR 0x54 +#define SCSC_PCIE_AMBA2TRANSAXIRADDR 0x58 +#define SCSC_PCIE_AMBA2TRANSAXICTR 0x5C +#define SCSC_PCIE_TRANS2PCIEREADALIGNAXIWCNT 0x60 +#define SCSC_PCIE_TRANS2PCIEREADALIGNAXIRCNT 0x64 +#define SCSC_PCIE_TRANS2PCIEREADALIGNAXIWADDR 0x68 +#define SCSC_PCIE_TRANS2PCIEREADALIGNAXIRADDR 0x6C +#define SCSC_PCIE_TRANS2PCIEREADALIGNAXICTRL 0x70 +#define SCSC_PCIE_READROUNDTRIPMIN 0x74 +#define SCSC_PCIE_READROUNDTRIPMAX 0x78 +#define SCSC_PCIE_READROUNDTRIPLAST 0x7C +#define SCSC_PCIE_CPTAW0 0x80 +#define SCSC_PCIE_CPTAW1 0x84 +#define SCSC_PCIE_CPTAR0 0x88 +#define SCSC_PCIE_CPTAR1 0x8C +#define SCSC_PCIE_CPTB0 0x90 +#define SCSC_PCIE_CPTW0 0x94 +#define SCSC_PCIE_CPTW1 0x98 +#define SCSC_PCIE_CPTW2 0x9C +#define SCSC_PCIE_CPTR0 0xA0 +#define SCSC_PCIE_CPTR1 0xA4 +#define SCSC_PCIE_CPTR2 0xA8 +#define SCSC_PCIE_CPTRES 0xAC +#define SCSC_PCIE_CPTAWDELAY 0xB0 +#define SCSC_PCIE_CPTARDELAY 0xB4 +#define SCSC_PCIE_CPTSRTADDR 0xB8 +#define SCSC_PCIE_CPTENDADDR 0xBC +#define SCSC_PCIE_CPTSZLTHID 0xC0 +#define SCSC_PCIE_CPTPHSEL 0xC4 +#define SCSC_PCIE_CPTRUN 0xC8 +#define SCSC_PCIE_FPGAVER 0xCC + +/* from mx141 */ +#define SCSC_PCIE_NEWMSG2 0xD0 +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT +#define SCSC_PCIE_NEWMSG3 0xD4 +#endif + +struct scsc_bar0_reg { + u32 NEWMSG; + u32 SIGNATURE; + u32 OFFSET; + u32 RUNEN; + u32 DEBUG; + u32 AXIWCNT; + u32 AXIRCNT; + u32 AXIWADDR; + u32 AXIRADDR; + u32 TBD; + u32 AXICTRL; + u32 AXIDATA; + u32 AXIRDBP; + u32 IFAXIWCNT; + u32 IFAXIRCNT; + u32 IFAXIWADDR; + u32 IFAXIRADDR; + u32 IFAXICTRL; + u32 GRST; + u32 AMBA2TRANSAXIWCNT; + u32 AMBA2TRANSAXIRCNT; + u32 AMBA2TRANSAXIWADDR; + u32 AMBA2TRANSAXIRADDR; + u32 AMBA2TRANSAXICTR; + u32 TRANS2PCIEREADALIGNAXIWCNT; + u32 TRANS2PCIEREADALIGNAXIRCNT; + u32 TRANS2PCIEREADALIGNAXIWADDR; + u32 TRANS2PCIEREADALIGNAXIRADDR; + u32 TRANS2PCIEREADALIGNAXICTRL; + u32 READROUNDTRIPMIN; + u32 READROUNDTRIPMAX; + u32 READROUNDTRIPLAST; + u32 CPTAW0; + u32 CPTAW1; + u32 CPTAR0; + u32 CPTAR1; + u32 CPTB0; + u32 CPTW0; + u32 CPTW1; + u32 CPTW2; + u32 CPTR0; + u32 CPTR1; + u32 CPTR2; + u32 CPTRES; + u32 CPTAWDELAY; + u32 CPTARDELAY; + u32 CPTSRTADDR; + u32 CPTENDADDR; + u32 CPTSZLTHID; + u32 CPTPHSEL; + u32 CPTRUN; + u32 FPGAVER; + + /* from mx141 */ + u32 NEWMSG2; +}; + +struct scsc_mif_abs *pcie_mif_create(struct pci_dev *pdev, const struct pci_device_id *id); +void pcie_mif_destroy_pcie(struct pci_dev *pdev, struct scsc_mif_abs *interface); +struct pci_dev *pcie_mif_get_pci_dev(struct scsc_mif_abs *interface); +struct device *pcie_mif_get_dev(struct scsc_mif_abs *interface); + +struct pcie_mif; + +void pcie_mif_get_bar0(struct pcie_mif *pcie, struct scsc_bar0_reg *bar0); +int pcie_mif_set_bar0_register(struct pcie_mif *pcie, unsigned int value, unsigned int offset); + +#endif diff --git a/drivers/misc/samsung/scsc/pcie_mif_module.c b/drivers/misc/samsung/scsc/pcie_mif_module.c new file mode 100644 index 000000000000..bc8c61d23316 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mif_module.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include "pcie_mif_module.h" +#include "pcie_mif.h" + +/* Implements */ +#include "scsc_mif_abs.h" + +struct mif_abs_node { + struct list_head list; + struct scsc_mif_abs *mif_abs; +}; + +struct mif_driver_node { + struct list_head list; + struct scsc_mif_abs_driver *driver; /* list of drivers (in practice just the core_module) */ +}; + +struct mif_mmap_node { + struct list_head list; + struct scsc_mif_mmap_driver *driver; /* list of drivers (in practive just the core_module) */ +}; + +static struct pcie_mif_module { + struct list_head mif_abs_list; + struct list_head mif_driver_list; + struct list_head mif_mmap_list; +} mif_module = { + .mif_abs_list = LIST_HEAD_INIT(mif_module.mif_abs_list), + .mif_driver_list = LIST_HEAD_INIT(mif_module.mif_driver_list), + .mif_mmap_list = LIST_HEAD_INIT(mif_module.mif_mmap_list), +}; + + +static const struct pci_device_id pcie_mif_module_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_SAMSUNG_SCSC) }, + { /*End: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, pcie_mif_module_tbl); + +static void pcie_mif_module_probe_registered_clients(struct scsc_mif_abs *mif_abs) +{ + struct mif_driver_node *mif_driver_node, *next; + struct device *dev; + bool driver_registered = false; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(mif_driver_node, next, &mif_module.mif_driver_list, list) { + SCSC_TAG_INFO(PCIE_MIF, "node %p\n", mif_driver_node); + + dev = pcie_mif_get_dev(mif_abs); + mif_driver_node->driver->probe(mif_driver_node->driver, mif_abs); + driver_registered = true; + } + if (driver_registered == false) + SCSC_TAG_INFO(PCIE_MIF, "No mif drivers registered\n"); +} + +static int pcie_mif_module_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct mif_abs_node *mif_node; + struct scsc_mif_abs *mif_abs; + + mif_node = kzalloc(sizeof(*mif_node), GFP_KERNEL); + if (!mif_node) + return -ENODEV; + + mif_abs = pcie_mif_create(pdev, id); + if (!mif_abs) { + SCSC_TAG_INFO(PCIE_MIF, "Error creating PCIe interface\n"); + kfree(mif_node); + return -ENODEV; + } + /* Add node */ + mif_node->mif_abs = mif_abs; + SCSC_TAG_INFO(PCIE_MIF, "mif_node A %p\n", mif_node); + list_add_tail(&mif_node->list, &mif_module.mif_abs_list); + + pcie_mif_module_probe_registered_clients(mif_abs); + + return 0; +} + +static void pcie_mif_module_remove(struct pci_dev *pdev) +{ + struct mif_abs_node *mif_node, *next; + bool match = false; + + /* Remove node */ + list_for_each_entry_safe(mif_node, next, &mif_module.mif_abs_list, list) { + if (pcie_mif_get_pci_dev(mif_node->mif_abs) == pdev) { + match = true; + SCSC_TAG_INFO(PCIE_MIF, "Match, destroy pcie_mif\n"); + pcie_mif_destroy_pcie(pdev, mif_node->mif_abs); + list_del(&mif_node->list); + kfree(mif_node); + } + } + if (match == false) + SCSC_TAG_INFO(PCIE_MIF, "FATAL, no match for given scsc_mif_abs\n"); +} + +static struct pci_driver scsc_pcie = { + .name = DRV_NAME, + .id_table = pcie_mif_module_tbl, + .probe = pcie_mif_module_probe, + .remove = pcie_mif_module_remove, +}; + +void scsc_mif_abs_register(struct scsc_mif_abs_driver *driver) +{ + struct mif_driver_node *mif_driver_node; + struct mif_abs_node *mif_node; + struct device *dev; + + /* Add node in driver linked list */ + mif_driver_node = kzalloc(sizeof(*mif_driver_node), GFP_KERNEL); + if (!mif_driver_node) + return; + + mif_driver_node->driver = driver; + list_add_tail(&mif_driver_node->list, &mif_module.mif_driver_list); + + /* Traverse Linked List for each mif_abs node */ + list_for_each_entry(mif_node, &mif_module.mif_abs_list, list) { + dev = pcie_mif_get_dev(mif_node->mif_abs); + driver->probe(driver, mif_node->mif_abs); + } +} +EXPORT_SYMBOL(scsc_mif_abs_register); + +void scsc_mif_abs_unregister(struct scsc_mif_abs_driver *driver) +{ + struct mif_driver_node *mif_driver_node, *next; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(mif_driver_node, next, &mif_module.mif_driver_list, list) { + if (mif_driver_node->driver == driver) { + list_del(&mif_driver_node->list); + kfree(mif_driver_node); + } + } +} +EXPORT_SYMBOL(scsc_mif_abs_unregister); + +/* Register a mmap - debug driver - for this specific transport*/ +void scsc_mif_mmap_register(struct scsc_mif_mmap_driver *mmap_driver) +{ + struct mif_mmap_node *mif_mmap_node; + struct mif_abs_node *mif_node; + + /* Add node in driver linked list */ + mif_mmap_node = kzalloc(sizeof(*mif_mmap_node), GFP_KERNEL); + if (!mif_mmap_node) + return; + + mif_mmap_node->driver = mmap_driver; + list_add_tail(&mif_mmap_node->list, &mif_module.mif_mmap_list); + + /* Traverse Linked List for each mif_abs node */ + list_for_each_entry(mif_node, &mif_module.mif_abs_list, list) { + mmap_driver->probe(mmap_driver, mif_node->mif_abs); + } +} +EXPORT_SYMBOL(scsc_mif_mmap_register); + +/* Unregister a mmap - debug driver - for this specific transport*/ +void scsc_mif_mmap_unregister(struct scsc_mif_mmap_driver *mmap_driver) +{ + struct mif_mmap_node *mif_mmap_node, *next; + + /* Traverse Linked List for each mif_mmap_driver node */ + list_for_each_entry_safe(mif_mmap_node, next, &mif_module.mif_mmap_list, list) { + if (mif_mmap_node->driver == mmap_driver) { + list_del(&mif_mmap_node->list); + kfree(mif_mmap_node); + } + } +} +EXPORT_SYMBOL(scsc_mif_mmap_unregister); + +module_pci_driver(scsc_pcie); + +MODULE_DESCRIPTION("SLSI PCIe mx140 MIF abstraction"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/samsung/scsc/pcie_mif_module.h b/drivers/misc/samsung/scsc/pcie_mif_module.h new file mode 100644 index 000000000000..30b1dfcde022 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_mif_module.h @@ -0,0 +1,29 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "pcie_mbox_shared_data_defs.h" + +#define PCI_DEVICE_ID_SAMSUNG_SCSC 0x7011 +#define DRV_NAME "scscPCIe" + +/* Max amount of memory allocated by dma_alloc_coherent */ +#define PCIE_MIF_PREALLOC_MEM (4 * 1024 * 1024) +/* Allocatable memory for upper layers */ +/* This value should take into account PCIE_MIF_PREALLOC_MEM - mbox/register + * emulation - peterson mutex ex: */ +/* -------------------- PCIE_MIF_PREALLOC_MEM + | scsc_mbox_s | + | -------------------- + | peterson_m | + | -------------------- + | /////////////// | + | ------------------- PCIE_MIF_ALLOC_MEM + | alloc memory | + | | + | | + | -------------------- + */ +#define PCIE_MIF_ALLOC_MEM ((PCIE_MIF_PREALLOC_MEM) - (PCIE_MIF_MBOX_RESERVED_LEN)) diff --git a/drivers/misc/samsung/scsc/pcie_proc.c b/drivers/misc/samsung/scsc/pcie_proc.c new file mode 100644 index 000000000000..a2c7c993ea43 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_proc.c @@ -0,0 +1,278 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include "pcie_proc.h" +#include "pcie_mif.h" + +static struct proc_dir_entry *procfs_dir; +static bool pcie_val; + +/* singleton */ +struct pcie_mif *pcie_global; + +static int pcie_procfs_open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = PCIE_PDE_DATA(inode); + return 0; +} + +PCIE_PROCFS_RW_FILE_OPS(pcie_trg); +PCIE_PROCFS_SEQ_FILE_OPS(pcie_dbg); + +static ssize_t pcie_procfs_pcie_trg_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", (pcie_val ? 1 : 0)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +#define ROW 52 +#define COL 2 +char *lookup_regs[ROW][COL] = { + { "NEWMSG", "0" }, + { "SIGNATURE", "4" }, + { "OFFSET", "8" }, + { "RUNEN", "12" }, + { "DEBUG", "16" }, + { "AXIWCNT", "20" }, + { "AXIRCNT", "24" }, + { "AXIWADDR", "28" }, + { "AXIRADDR", "32" }, + { "TBD", "36" }, + { "AXICTRL", "40" }, + { "AXIDATA", "44" }, + { "AXIRDBP", "48" }, + { "IFAXIWCNT", "52" }, + { "IFAXIRCNT", "56" }, + { "IFAXIWADDR", "60" }, + { "IFAXIRADDR", "64" }, + { "IFAXICTRL", "68" }, + { "GRST", "72" }, + { "AMBA2TRANSAXIWCNT", "76" }, + { "AMBA2TRANSAXIRCNT", "80" }, + { "AMBA2TRANSAXIWADDR", "84" }, + { "AMBA2TRANSAXIRADDR", "88" }, + { "AMBA2TRANSAXICTR", "92" }, + { "TRANS2PCIEREADALIGNAXIWCNT", "96" }, + { "TRANS2PCIEREADALIGNAXIRCNT", "100" }, + { "TRANS2PCIEREADALIGNAXIWADDR", "104" }, + { "TRANS2PCIEREADALIGNAXIRADDR", "108" }, + { "TRANS2PCIEREADALIGNAXICTRL", "112" }, + { "READROUNDTRIPMIN", "116" }, + { "READROUNDTRIPMAX", "120" }, + { "READROUNDTRIPLAST", "124" }, + { "CPTAW0", "128" }, + { "CPTAW1", "132" }, + { "CPTAR0", "136" }, + { "CPTAR1", "140" }, + { "CPTB0", "144" }, + { "CPTW0", "148" }, + { "CPTW1", "152" }, + { "CPTW2", "156" }, + { "CPTR0", "160" }, + { "CPTR1", "164" }, + { "CPTR2", "168" }, + { "CPTRES", "172" }, + { "CPTAWDELAY", "176" }, + { "CPTARDELAY", "180" }, + { "CPTSRTADDR", "184" }, + { "CPTENDADDR", "188" }, + { "CPTSZLTHID", "192" }, + { "CPTPHSEL", "196" }, + { "CPTRUN", "200" }, + { "FPGAVER", "204" }, +}; + +/* Trigger boot of Curator over SDIO without Chip Power Manager present */ +static ssize_t pcie_procfs_pcie_trg_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + char *sptr, *token; + unsigned int len = 0, pass = 0; + u32 value = 0; + int i = 0; + int rc; + + int match = 0, offset = 0; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + while ((token = strsep(&sptr, " ")) != NULL) { + switch (pass) { + /* register */ + case 0: + SCSC_TAG_INFO(PCIE_MIF, "str %s\n", lookup_regs[0][0]); + SCSC_TAG_INFO(PCIE_MIF, "token %s\n", token); + SCSC_TAG_INFO(PCIE_MIF, "len %d\n", len); + for (i = 0; i < ROW; i++) + if (!strncmp(lookup_regs[i][0], token, len)) { + rc = kstrtou32(lookup_regs[i][1], 0, &offset); + if (rc) + match = 0; + else + match = 1; + break; + } + + if (!match) { + SCSC_TAG_INFO(PCIE_MIF, "Register %s not Found!!\n", token); + SCSC_TAG_INFO(PCIE_MIF, "Type 'cat /proc/driver/pcie_ctrl/pcie_dbg' to get register names\n"); + } + break; + /* value */ + case 1: + if ((token[0] == '0') && (token[1] == 'x')) { + if (kstrtou32(token, 16, &value)) { + SCSC_TAG_INFO(PCIE_MIF, "Incorrect format,,,address should start by 0x\n"); + SCSC_TAG_INFO(PCIE_MIF, "Example: \"0xaaaabbbb 256 8\"\n"); + goto error; + } + } else { + SCSC_TAG_INFO(PCIE_MIF, "Incorrect format,,,address should start by 0x\n"); + SCSC_TAG_INFO(PCIE_MIF, "Example: \"0xaaaabbbb 256 8\"\n"); + goto error; + } + break; + } + pass++; + } + if (pass != 2 && !match) { + SCSC_TAG_INFO(PCIE_MIF, "Wrong format: \n"); + SCSC_TAG_INFO(PCIE_MIF, "Example: \"DEBUGADDR 0xaaaabbbb\"\n"); + goto error; + } + SCSC_TAG_INFO(PCIE_MIF, "Setting value 0x%x to register %s offset %d\n", value, lookup_regs[i][0], offset); + pcie_mif_set_bar0_register(pcie_global, value, offset); +error: + return count; +} + +static int pcie_procfs_pcie_dbg_show(struct seq_file *m, void *v) +{ + struct scsc_bar0_reg bar0; + + if (!pcie_global) { + seq_puts(m, "endpoint not registered"); + return 0; + } + + pcie_mif_get_bar0(pcie_global, &bar0); + + seq_puts(m, "\n---------BAR0---------\n"); + + seq_printf(m, "NEWMSG 0x%08X\n", bar0.NEWMSG); + seq_printf(m, "SIGNATURE 0x%08X\n", bar0.SIGNATURE); + seq_printf(m, "OFFSET 0x%08X\n", bar0.OFFSET); + seq_printf(m, "RUNEN 0x%08X\n", bar0.RUNEN); + seq_printf(m, "DEBUG 0x%08X\n", bar0.DEBUG); + seq_printf(m, "AXIWCNT 0x%08X\n", bar0.AXIWCNT); + seq_printf(m, "AXIRCNT 0x%08X\n", bar0.AXIRCNT); + seq_printf(m, "AXIWADDR 0x%08X\n", bar0.AXIWADDR); + seq_printf(m, "AXIRADDR 0x%08X\n", bar0.AXIRADDR); + seq_printf(m, "TBD 0x%08X\n", bar0.TBD); + seq_printf(m, "AXICTRL 0x%08X\n", bar0.AXICTRL); + seq_printf(m, "AXIDATA 0x%08X\n", bar0.AXIDATA); + seq_printf(m, "AXIRDBP 0x%08X\n", bar0.AXIRDBP); + seq_printf(m, "IFAXIWCNT 0x%08X\n", bar0.IFAXIWCNT); + seq_printf(m, "IFAXIRCNT 0x%08X\n", bar0.IFAXIRCNT); + seq_printf(m, "IFAXIWADDR 0x%08X\n", bar0.IFAXIWADDR); + seq_printf(m, "IFAXIRADDR 0x%08X\n", bar0.IFAXIRADDR); + seq_printf(m, "IFAXICTRL 0x%08X\n", bar0.IFAXICTRL); + seq_printf(m, "GRST 0x%08X\n", bar0.GRST); + seq_printf(m, "AMBA2TRANSAXIWCNT 0x%08X\n", bar0.AMBA2TRANSAXIWCNT); + seq_printf(m, "AMBA2TRANSAXIRCNT 0x%08X\n", bar0.AMBA2TRANSAXIRCNT); + seq_printf(m, "AMBA2TRANSAXIWADDR 0x%08X\n", bar0.AMBA2TRANSAXIWADDR); + seq_printf(m, "AMBA2TRANSAXIRADDR 0x%08X\n", bar0.AMBA2TRANSAXIRADDR); + seq_printf(m, "AMBA2TRANSAXICTR 0x%08X\n", bar0.AMBA2TRANSAXICTR); + seq_printf(m, "TRANS2PCIEREADALIGNAXIWCNT 0x%08X\n", bar0.TRANS2PCIEREADALIGNAXIWCNT); + seq_printf(m, "TRANS2PCIEREADALIGNAXIRCNT 0x%08X\n", bar0.TRANS2PCIEREADALIGNAXIRCNT); + seq_printf(m, "TRANS2PCIEREADALIGNAXIWADDR 0x%08X\n", bar0.TRANS2PCIEREADALIGNAXIWADDR); + seq_printf(m, "TRANS2PCIEREADALIGNAXIRADDR 0x%08X\n", bar0.TRANS2PCIEREADALIGNAXIRADDR); + seq_printf(m, "TRANS2PCIEREADALIGNAXICTRL 0x%08X\n", bar0.TRANS2PCIEREADALIGNAXICTRL); + seq_printf(m, "READROUNDTRIPMIN 0x%08X\n", bar0.READROUNDTRIPMIN); + seq_printf(m, "READROUNDTRIPMAX 0x%08X\n", bar0.READROUNDTRIPMAX); + seq_printf(m, "READROUNDTRIPLAST 0x%08X\n", bar0.READROUNDTRIPLAST); + seq_printf(m, "CPTAW0 0x%08X\n", bar0.CPTAW0); + seq_printf(m, "CPTAW1 0x%08X\n", bar0.CPTAW1); + seq_printf(m, "CPTAR0 0x%08X\n", bar0.CPTAR0); + seq_printf(m, "CPTAR1 0x%08X\n", bar0.CPTAR1); + seq_printf(m, "CPTB0 0x%08X\n", bar0.CPTB0); + seq_printf(m, "CPTW0 0x%08X\n", bar0.CPTW0); + seq_printf(m, "CPTW1 0x%08X\n", bar0.CPTW1); + seq_printf(m, "CPTW2 0x%08X\n", bar0.CPTW2); + seq_printf(m, "CPTR0 0x%08X\n", bar0.CPTR0); + seq_printf(m, "CPTR1 0x%08X\n", bar0.CPTR1); + seq_printf(m, "CPTR2 0x%08X\n", bar0.CPTR2); + seq_printf(m, "CPTRES 0x%08X\n", bar0.CPTRES); + seq_printf(m, "CPTAWDELAY 0x%08X\n", bar0.CPTAWDELAY); + seq_printf(m, "CPTARDELAY 0x%08X\n", bar0.CPTARDELAY); + seq_printf(m, "CPTSRTADDR 0x%08X\n", bar0.CPTSRTADDR); + seq_printf(m, "CPTENDADDR 0x%08X\n", bar0.CPTENDADDR); + seq_printf(m, "CPTSZLTHID 0x%08X\n", bar0.CPTSZLTHID); + seq_printf(m, "CPTPHSEL 0x%08X\n", bar0.CPTPHSEL); + seq_printf(m, "CPTRUN 0x%08X\n", bar0.CPTRUN); + seq_printf(m, "FPGAVER 0x%08X\n", bar0.FPGAVER); + return 0; +} + +static const char *procdir = "driver/pcie_ctrl"; + +#define PCIE_DIRLEN 128 + + +int pcie_create_proc_dir(struct pcie_mif *pcie) +{ + char dir[PCIE_DIRLEN]; + struct proc_dir_entry *parent; + + (void)snprintf(dir, sizeof(dir), "%s", procdir); + parent = proc_mkdir(dir, NULL); + if (parent) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = NULL; +#endif + procfs_dir = parent; + PCIE_PROCFS_ADD_FILE(NULL, pcie_trg, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + PCIE_PROCFS_SEQ_ADD_FILE(NULL, pcie_dbg, parent, S_IRUSR | S_IRGRP | S_IROTH); + } else { + SCSC_TAG_INFO(PCIE_MIF, "failed to create /proc dir\n"); + return -EINVAL; + } + + pcie_global = pcie; + + return 0; + +err: + return -EINVAL; +} + +void pcie_remove_proc_dir(void) +{ + if (procfs_dir) { + char dir[PCIE_DIRLEN]; + + PCIE_PROCFS_REMOVE_FILE(pcie_trg, procfs_dir); + PCIE_PROCFS_REMOVE_FILE(pcie_dbg, procfs_dir); + + (void)snprintf(dir, sizeof(dir), "%s", procdir); + remove_proc_entry(dir, NULL); + procfs_dir = NULL; + } + + pcie_global = NULL; +} diff --git a/drivers/misc/samsung/scsc/pcie_proc.h b/drivers/misc/samsung/scsc/pcie_proc.h new file mode 100644 index 000000000000..77ad2e7fb676 --- /dev/null +++ b/drivers/misc/samsung/scsc/pcie_proc.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* + * Chip Manager /proc interface + */ +#include +#include +#include + +#include "pcie_mif.h" + +#ifndef SCSC_PCIE_PROC_H +#define SCSC_PCIE_PROC_H + +#ifndef AID_WIFI +#define AID_WIFI 0444 +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define PCIE_PDE_DATA(inode) PDE_DATA(inode) +#else +#define PCIE_PDE_DATA(inode) (PDE(inode)->data) +#endif + +#define PCIE_PROCFS_SEQ_FILE_OPS(name) \ + static int pcie_procfs_ ## name ## _show(struct seq_file *m, void *v); \ + static int pcie_procfs_ ## name ## _open(struct inode *inode, struct file *file) \ + { \ + return single_open(file, pcie_procfs_ ## name ## _show, PCIE_PDE_DATA(inode)); \ + } \ + static const struct file_operations pcie_procfs_ ## name ## _fops = { \ + .open = pcie_procfs_ ## name ## _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + } + +#define PCIE_PROCFS_SEQ_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = proc_create_data(# name, mode, parent, &pcie_procfs_ ## name ## _fops, _sdev); \ + if (!entry) { \ + goto err; \ + } \ + PCIE_PROCFS_SET_UID_GID(entry); \ + } while (0) + +#define PCIE_PROCFS_RW_FILE_OPS(name) \ + static ssize_t pcie_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static ssize_t pcie_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations pcie_procfs_ ## name ## _fops = { \ + .read = pcie_procfs_ ## name ## _read, \ + .write = pcie_procfs_ ## name ## _write, \ + .open = pcie_procfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define PCIE_PROCFS_SET_UID_GID(_entry) \ + do { \ + kuid_t proc_kuid = KUIDT_INIT(AID_WIFI); \ + kgid_t proc_kgid = KGIDT_INIT(AID_WIFI); \ + proc_set_user(_entry, proc_kuid, proc_kgid); \ + } while (0) +#else +#define PCIE_PROCFS_SET_UID_GID(entry) \ + do { \ + (entry)->uid = AID_WIFI; \ + (entry)->gid = AID_WIFI; \ + } while (0) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define PCIE_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &pcie_procfs_ ## name ## _fops, _sdev); \ + PCIE_PROCFS_SET_UID_GID(entry); \ + } while (0) +#else +#define PCIE_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = create_proc_entry(# name, mode, parent); \ + if (entry) { \ + entry->proc_fops = &pcie_procfs_ ## name ## _fops; \ + entry->data = _sdev; \ + PCIE_PROCFS_SET_UID_GID(entry); \ + } \ + } while (0) +#endif + +#define PCIE_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent) + +int pcie_create_proc_dir(struct pcie_mif *pcie); +void pcie_remove_proc_dir(void); + +#endif /* SCSC_PCIE_PROC_H */ diff --git a/drivers/misc/samsung/scsc/peterson_mutex.h b/drivers/misc/samsung/scsc/peterson_mutex.h new file mode 100644 index 000000000000..7f24332c1331 --- /dev/null +++ b/drivers/misc/samsung/scsc/peterson_mutex.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SCSC_PETERSON_H +#define __SCSC_PETERSON_H + +#include +#include "mif_reg.h" + +#define R4_PROCESS 0 +#define AP_PROCESS 1 +#define DELAY_NS 100 /* delay in ns*/ + +static inline void peterson_mutex_init(struct peterson_mutex *p_mutex) +{ + if (!p_mutex) { + pr_info("Mutex not declared\n"); + return; + } + + p_mutex->flag[0] = false; + p_mutex->flag[1] = false; + p_mutex->turn = 0; +} + +static inline void peterson_mutex_lock(struct peterson_mutex *p_mutex, unsigned int process) +{ + unsigned int other = 1 - process; + + p_mutex->flag[process] = true; + /* write barrier */ + smp_wmb(); + p_mutex->turn = other; + /* write barrier */ + smp_wmb(); + + while ((p_mutex->flag[other]) && (p_mutex->turn == other)) + ndelay(DELAY_NS); +} + +static inline void peterson_mutex_unlock(struct peterson_mutex *p_mutex, unsigned int process) +{ + p_mutex->flag[process] = false; + /* write barrier */ + smp_wmb(); +} +#endif diff --git a/drivers/misc/samsung/scsc/platform_mif.c b/drivers/misc/samsung/scsc/platform_mif.c new file mode 100755 index 000000000000..e2e0217388d6 --- /dev/null +++ b/drivers/misc/samsung/scsc/platform_mif.c @@ -0,0 +1,2524 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Implements interface */ + +#include "platform_mif.h" + +/* Interfaces it Uses */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef CONFIG_SOC_EXYNOS7570 +#include +#endif +#ifdef CONFIG_OF +#include +#include +#include +#endif +#include +#include +#include +#include +#ifdef CONFIG_SOC_EXYNOS7570 +#include "mif_reg.h" +#elif defined(CONFIG_SOC_EXYNOS7872) +#include "mif_reg_S5E7872.h" +#elif defined(CONFIG_SOC_EXYNOS7885) +#include "mif_reg_S5E7885.h" +#elif defined(CONFIG_SOC_EXYNOS9610) +#include "mif_reg_S5E9610.h" +#endif +#include "platform_mif_module.h" +#ifdef CONFIG_ARCH_EXYNOS +#include +#endif + +#ifdef CONFIG_SCSC_SMAPPER +#include +#include "mif_reg_smapper.h" +#endif +#ifdef CONFIG_SCSC_QOS +#include +#endif + +#if !defined(CONFIG_SOC_EXYNOS7872) && !defined(CONFIG_SOC_EXYNOS7570) \ + && !defined(CONFIG_SOC_EXYNOS7885) && !defined(CONFIG_SOC_EXYNOS9610) && !defined(CONFIG_SOC_EXYNOS9630) +#error Target processor CONFIG_SOC_EXYNOS7570 or CONFIG_SOC_EXYNOS7872 or CONFIG_SOC_EXYNOS7885 or CONFIG_SOC_EXYNOS9610 not selected +#endif + +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) +/* TODO: this will be put in a header */ +extern int exynos_acpm_set_flag(void); +#endif + +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif +/* Time to wait for CFG_REQ IRQ on 9610 */ +#define WLBT_BOOT_TIMEOUT (HZ) + +#ifdef CONFIG_OF_RESERVED_MEM +#include +#endif +static unsigned long sharedmem_base; +static size_t sharedmem_size; + +#ifdef CONFIG_SCSC_CHV_SUPPORT +static bool chv_disable_irq; +module_param(chv_disable_irq, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(chv_disable_irq, "Do not register for irq"); +#endif + +#ifdef CONFIG_SCSC_GPR4_CON_DEBUG +static u32 reg_bkp; +static bool reg_update; +static void __iomem *gpio_base; +static bool gpr4_debug; +module_param(gpr4_debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(gpr4_debug, "GPR4 PIO muxes switching to the Maxwell. Default = N. Effective on Maxwell power on"); +#endif + +static bool enable_platform_mif_arm_reset = true; +module_param(enable_platform_mif_arm_reset, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_platform_mif_arm_reset, "Enables WIFIBT ARM cores reset"); + +#ifdef CONFIG_SCSC_QOS +struct qos_table { + unsigned int freq_mif; + unsigned int freq_int; + unsigned int freq_cl0; + unsigned int freq_cl1; +}; +#endif + +struct platform_mif { + struct scsc_mif_abs interface; + struct scsc_mbox_s *mbox; + struct platform_device *pdev; + + struct device *dev; + + struct { + int irq_num; + int flags; + atomic_t irq_disabled_cnt; + } wlbt_irq[PLATFORM_MIF_NUM_IRQS]; + + /* MIF registers preserved during suspend */ + struct { + u32 irq_bit_mask; + } mif_preserve; + + /* register MBOX memory space */ + size_t reg_start; + size_t reg_size; + void __iomem *base; + +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + /* register MBOX memory space for M4 */ + size_t reg_start_m4; + size_t reg_size_m4; + void __iomem *base_m4; +#endif + /* register CMU memory space */ + struct regmap *cmu_base; +#ifdef CONFIG_SCSC_CLK20MHZ + u32 usbpll_delay; +#endif + + void __iomem *con0_base; + + /* pmu syscon regmap */ + struct regmap *pmureg; +#if defined(CONFIG_SOC_EXYNOS9610) + struct regmap *baaw_p_wlbt; + struct regmap *dbus_baaw; + struct regmap *pbus_baaw; + struct regmap *wlbt_remap; + struct regmap *boot_cfg; + + /* Signalled when CFG_REQ IRQ handled */ + struct completion cfg_ack; + + /* State of CFG_REQ handler */ + enum wlbt_boot_state { + WLBT_BOOT_IN_RESET = 0, + WLBT_BOOT_WAIT_CFG_REQ, + WLBT_BOOT_CFG_DONE, + WLBT_BOOT_CFG_ERROR + } boot_state; + +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER */ + void __iomem *smapper_base; + u8 smapper_banks; + struct { + u8 bank; + u32 ws; + bool large; + struct scsc_mif_smapper_info bank_info; + } *smapper; +#endif + /* Shared memory space - reserved memory */ + unsigned long mem_start; + size_t mem_size; + void __iomem *mem; + + /* Callback function and dev pointer mif_intr manager handler */ + void (*r4_handler)(int irq, void *data); + void *irq_dev; + /* spinlock to serialize driver access */ + spinlock_t mif_spinlock; + void (*reset_request_handler)(int irq, void *data); + void *irq_reset_request_dev; + +#ifdef CONFIG_SCSC_QOS + /* QoS table */ + struct qos_table *qos; + bool qos_enabled; +#endif + /* Suspend/resume handlers */ + int (*suspend_handler)(struct scsc_mif_abs *abs, void *data); + void (*resume_handler)(struct scsc_mif_abs *abs, void *data); + void *suspendresume_data; +}; + +extern int mx140_log_dump(void); + +#define platform_mif_from_mif_abs(MIF_ABS_PTR) container_of(MIF_ABS_PTR, struct platform_mif, interface) + +#ifdef CONFIG_SCSC_CLK20MHZ +static void __platform_mif_usbpll_claim(struct platform_mif *platform, bool wlbt); +#endif + +inline void platform_mif_reg_write(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->base + offset); +} + +inline u32 platform_mif_reg_read(struct platform_mif *platform, u16 offset) +{ + return readl(platform->base + offset); +} + +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) +inline void platform_mif_reg_write_m4(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->base_m4 + offset); +} + +inline u32 platform_mif_reg_read_m4(struct platform_mif *platform, u16 offset) +{ + return readl(platform->base_m4 + offset); +} +#endif + +#ifdef CONFIG_SCSC_SMAPPER +inline void platform_mif_reg_write_smapper(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->smapper_base + offset); +} + +inline u32 platform_mif_reg_read_smapper(struct platform_mif *platform, u16 offset) +{ + return readl(platform->smapper_base + offset); +} + +#define PLATFORM_MIF_SHIFT_SMAPPER_ADDR 11 /* From 36 bits addres to 25 bits */ +#define PLATFORM_MIF_SHIFT_SMAPPER_END 4 /* End address aligment */ + +/* Platform is responsible to give the phys mapping of the SMAPPER maps */ +static int platform_mif_smapper_get_mapping(struct scsc_mif_abs *interface, u8 *phy_map, u16 *align) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Mapping %d banks\n", platform->smapper_banks); + + if (!platform->smapper_banks) + return -EINVAL; + + for (i = 0; i < platform->smapper_banks; i++) { + if (platform->smapper[i].large) + phy_map[i] = SCSC_MIF_ABS_LARGE_BANK; + else + phy_map[i] = SCSC_MIF_ABS_SMALL_BANK; + } + + if (align) + *align = 1 << PLATFORM_MIF_SHIFT_SMAPPER_ADDR; + + return 0; +} + +static int platform_mif_smapper_get_bank_info(struct scsc_mif_abs *interface, u8 bank, struct scsc_mif_smapper_info *bank_info) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform->smapper_banks) + return -EINVAL; + + bank_info->num_entries = platform->smapper[bank].bank_info.num_entries; + bank_info->mem_range_bytes = platform->smapper[bank].bank_info.mem_range_bytes; + + return 0; +} + +static u8 platform_mif_smapper_granularity_to_bits(u32 granularity) +{ + if (granularity <= 2 * 1024) + return 0; + if (granularity <= 4 * 1024) + return 1; + if (granularity <= 8 * 1024) + return 2; + if (granularity <= 16 * 1024) + return 3; + if (granularity <= 32 * 1024) + return 4; + if (granularity <= 64 * 1024) + return 5; + if (granularity <= 128 * 1024) + return 6; + return 7; +} + +static u32 platform_mif_smapper_get_bank_base_address(struct scsc_mif_abs *interface, u8 bank) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform->smapper) + return 0; + + return platform->smapper[bank].ws; +} + +/* Configure smapper according the memory map and range */ +static void platform_mif_smapper_configure(struct scsc_mif_abs *interface, u32 granularity) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + u8 gran; + u8 nb = platform->smapper_banks; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Configure SMAPPER with granularity %d\n", granularity); + + gran = platform_mif_smapper_granularity_to_bits(granularity); + + platform_mif_reg_write_smapper(platform, SMAPPER_QCH_DISABLE, 1); + platform_mif_reg_write_smapper(platform, ORIGIN_ADDR_AR, 0); + platform_mif_reg_write_smapper(platform, ORIGIN_ADDR_AW, 0); + /* Program SMAPPER memmap */ + for (i = 0; i < nb; i++) { + /* Set ADDR_MAP_EN to 1'b0*/ + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(i), 0); + /* Set START_ADDR */ + platform_mif_reg_write_smapper(platform, START_ADDR(i), platform->smapper[i].ws); + /* Set ADDR_GRANULARITY - FIXED AT 4KB */ + platform_mif_reg_write_smapper(platform, ADDR_GRANULARITY(i), gran); + /* WLAN_ADDR_MAP operation is started */ + } + /* Set access window control (MSB 32bits Start/End address) */ + /* Remapped address should be ranged from AW_START_ADDR to AW_EN_ADDR */ + platform_mif_reg_write_smapper(platform, AW_START_ADDR, 0); + platform_mif_reg_write_smapper(platform, AW_END_ADDR, dma_get_mask(platform->dev) >> PLATFORM_MIF_SHIFT_SMAPPER_END); + smp_mb(); +} + +/* Caller is responsible of validating the phys address (alignment) */ +static int platform_mif_smapper_write_sram(struct scsc_mif_abs *interface, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + u32 rb; + + if (!platform->smapper_banks) + return -EINVAL; + + if (!platform->smapper_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "SMAPPER not enabled\n"); + return -EINVAL; + } + + /* Set ADDR_MAP_EN to 1'b0*/ + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(bank), 0); + /* Write mapping table to SRAM. Each entry consists of 25 bits MSB address to remap */ + for (i = 0; i < num_entries; i++) { + if (!addr[i]) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "ADDR IS NULL at bank %d entry %d/%d\n", bank, first_entry + i, num_entries); + return -EINVAL; + } + /* Set SRAM_WRITE_CTRL to 1'b1*/ + platform_mif_reg_write_smapper(platform, SRAM_WRITE_CTRL(bank), 1); + platform_mif_reg_write_smapper(platform, SRAM_BANK_INDEX(bank, first_entry + i), addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR); + /* check incorrect writings */ + platform_mif_reg_write_smapper(platform, SRAM_WRITE_CTRL(bank), 0); + rb = platform_mif_reg_read_smapper(platform, SRAM_BANK_INDEX(bank, first_entry + i)); + if (rb != addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "incorrect mapping detected rb 0x%x, addr 0x%x\n", rb, (u32)addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR); + return -EFAULT; + } + } + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(bank), 1); + smp_mb(); + return 0; +} + +static int platform_mif_parse_smapper(struct platform_mif *platform, struct device_node *np, u8 num_banks) +{ + /* SMAPPER parsing */ + struct device_node *np_banks; + char node_name[50]; + u32 val[2]; + u8 i; + u32 bank = 0, ws = 0, wsz = 0, ent = 0, large = 0; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "banks found: %d szof %zu\n", num_banks, sizeof(*platform->smapper)); + + platform->smapper = kmalloc_array(num_banks, sizeof(*platform->smapper), GFP_KERNEL); + + if (!platform->smapper) + return -ENOMEM; + + for (i = 0; i < num_banks; i++) { + snprintf(node_name, sizeof(node_name), "smapper_bank_%d", i); + np_banks = of_find_node_by_name(np, node_name); + if (!np_banks) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "%s: could not find smapper_bank\n", + node_name); + kfree(platform->smapper); + platform->smapper = NULL; + return -ENOENT; + } + of_property_read_u32(np_banks, "bank_num", &bank); + of_property_read_u32(np_banks, "fw_window_start", &ws); + of_property_read_u32(np_banks, "fw_window_size", &wsz); + of_property_read_u32(np_banks, "num_entries", &ent); + of_property_read_u32(np_banks, "is_large", &large); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "bank %d fw_w_start 0x%x fw_w_sz 0x%x entries %d is_large %d\n", + bank, ws, wsz, ent, large); + + platform->smapper[i].bank = (u8)bank; + platform->smapper[i].ws = ws; + platform->smapper[i].large = (bool)large; + platform->smapper[i].bank_info.num_entries = ent; + platform->smapper[i].bank_info.mem_range_bytes = wsz; + } + + /* Update the number of banks before returning */ + platform->smapper_banks = num_banks; + + of_property_read_u32_array(np, "smapper_reg", val, 2); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "smapper reg address 0x%x size 0x%x\n", val[0], val[1]); + platform->smapper_base = + devm_ioremap_nocache(platform->dev, val[0], val[1]); + + if (!platform->smapper_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping smapper register region\n"); + kfree(platform->smapper); + platform->smapper = NULL; + return -ENOENT; + } + + return 0; +} +#endif +#ifdef CONFIG_SCSC_QOS +static int platform_mif_parse_qos(struct platform_mif *platform, struct device_node *np) +{ + int len, i; + + platform->qos_enabled = false; + + len = of_property_count_u32_elems(np, "qos_table"); + if (!(len == 12)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "No qos table for wlbt, or incorrect size\n"); + return -ENOENT; + } + + platform->qos = devm_kzalloc(platform->dev, sizeof(struct qos_table) * len / 4, GFP_KERNEL); + if (!platform->qos) + return -ENOMEM; + + of_property_read_u32_array(np, "qos_table", (unsigned int *)platform->qos, len); + + for (i = 0; i < len / 4; i++) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "QoS Table[%d] mif : %u int : %u cl0 : %u cl1: %u\n", i, + platform->qos[i].freq_mif, + platform->qos[i].freq_int, + platform->qos[i].freq_cl0, + platform->qos[i].freq_cl1); + } + + platform->qos_enabled = true; + return 0; +} + +struct qos_table platform_mif_pm_qos_get_table(struct platform_mif *platform, enum scsc_qos_config config) +{ + struct qos_table table; + + switch (config) { + case SCSC_QOS_MIN: + table.freq_mif = platform->qos[0].freq_mif; + table.freq_int = platform->qos[0].freq_int; + table.freq_cl0 = platform->qos[0].freq_cl0; + table.freq_cl1 = platform->qos[0].freq_cl1; + break; + + case SCSC_QOS_MED: + table.freq_mif = platform->qos[1].freq_mif; + table.freq_int = platform->qos[1].freq_int; + table.freq_cl0 = platform->qos[1].freq_cl0; + table.freq_cl1 = platform->qos[1].freq_cl1; + break; + + case SCSC_QOS_MAX: + table.freq_mif = platform->qos[2].freq_mif; + table.freq_int = platform->qos[2].freq_int; + table.freq_cl0 = platform->qos[2].freq_cl0; + table.freq_cl1 = platform->qos[2].freq_cl1; + break; + + default: + table.freq_mif = 0; + table.freq_int = 0; + table.freq_cl0 = 0; + table.freq_cl1 = 0; + } + + return table; +} + +static int platform_mif_pm_qos_add_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + struct qos_table table; + + if (!platform) + return -ENODEV; + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + table = platform_mif_pm_qos_get_table(platform, config); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "PM QoS add request: %u. MIF %u INT %u CL0 %u CL1 %u\n", config, table.freq_mif, table.freq_int, table.freq_cl0, table.freq_cl1); + + pm_qos_add_request(&qos_req->pm_qos_req_mif, PM_QOS_BUS_THROUGHPUT, table.freq_mif); + pm_qos_add_request(&qos_req->pm_qos_req_int, PM_QOS_DEVICE_THROUGHPUT, table.freq_int); + pm_qos_add_request(&qos_req->pm_qos_req_cl0, PM_QOS_CLUSTER0_FREQ_MIN, table.freq_cl0); + pm_qos_add_request(&qos_req->pm_qos_req_cl1, PM_QOS_CLUSTER1_FREQ_MIN, table.freq_cl1); + + return 0; +} + +static int platform_mif_pm_qos_update_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + struct qos_table table; + + if (!platform) + return -ENODEV; + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + table = platform_mif_pm_qos_get_table(platform, config); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "PM QoS update request: %u. MIF %u INT %u CL0 %u CL1 %u\n", config, table.freq_mif, table.freq_int, table.freq_cl0, table.freq_cl1); + + pm_qos_update_request(&qos_req->pm_qos_req_mif, table.freq_mif); + pm_qos_update_request(&qos_req->pm_qos_req_int, table.freq_int); + pm_qos_update_request(&qos_req->pm_qos_req_cl0, table.freq_cl0); + pm_qos_update_request(&qos_req->pm_qos_req_cl1, table.freq_cl1); + + return 0; +} + +static int platform_mif_pm_qos_remove_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform) + return -ENODEV; + + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS remove request\n"); + pm_qos_remove_request(&qos_req->pm_qos_req_mif); + pm_qos_remove_request(&qos_req->pm_qos_req_int); + pm_qos_remove_request(&qos_req->pm_qos_req_cl0); + pm_qos_remove_request(&qos_req->pm_qos_req_cl1); + + return 0; +} +#endif + +static void platform_mif_irq_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; + + /* int handler not registered */ + SCSC_TAG_INFO_DEV(PLAT_MIF, NULL, "INT handler not registered\n"); +} + +static void platform_mif_irq_reset_request_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; + + /* int handler not registered */ + SCSC_TAG_INFO_DEV(PLAT_MIF, NULL, "INT reset_request handler not registered\n"); +} + +irqreturn_t platform_mif_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "INT %pS\n", platform->r4_handler); + if (platform->r4_handler != platform_mif_irq_default_handler) + platform->r4_handler(irq, platform->irq_dev); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "MIF Interrupt Handler not registered\n"); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ +irqreturn_t platform_alive_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + + return IRQ_HANDLED; +} +#endif + +irqreturn_t platform_wdog_isr(int irq, void *data) +{ + int ret = 0; + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + if (platform->reset_request_handler != platform_mif_irq_reset_request_default_handler) { + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + platform->reset_request_handler(irq, platform->irq_reset_request_dev); + } else { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WDOG Interrupt reset_request_handler not registered\n"); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Disabling unhandled WDOG IRQ.\n"); + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + atomic_inc(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt); + } +#ifdef CONFIG_SOC_EXYNOS9610 + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_RESET_REQ_CLR, WLBT_RESET_REQ_CLR); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clearing WLBT_RESET_REQ\n"); + if (ret < 0) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WLBT_CTRL_NS[WLBT_RESET_REQ_CLR]: %d\n", ret); +#else + ret = regmap_update_bits(platform->pmureg, WIFI_CTRL_NS, + WIFI_RESET_REQ_CLR, WIFI_RESET_REQ_CLR); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clearing WIFI_RESET_REQ\n"); + if (ret < 0) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WIFI_CTRL_NS[WIFI_RESET_REQ_CLR]: %d\n", ret); +#endif + return IRQ_HANDLED; +} + +#ifdef CONFIG_SOC_EXYNOS9610 +/* + * Attached array contains the replacement PMU boot code which should + * be programmed using the CBUS during the config phase. + */ +uint32_t ka_patch[] = { + /* Low temp fix 28/1 + * Maxwell142 PMU+PROC combined boot ROM + * IP Version: 0xA3 + * Major Version: 0xF, Minor Version: 0xF + * PMU ROM version: 0x4 + * PROC ROM version: 0x0 + */ + 0x90750002, + 0x11a4c218, + 0x75671191, + 0x9075e090, + 0x54b3e5e7, + 0x30f76001, + 0xb14315a2, + 0xb4b2e503, + 0xb153fb03, + 0xa90185fc, + 0xacf50774, + 0x75fdadb5, + 0xb47508a0, + 0x54b3e501, + 0x75fa7001, + 0x907500b4, + 0x78cb8018, + 0x80837982, + 0x07a075c5, + 0xb0783779, + 0xb40754e6, + 0x0b800207, + 0xc404f6d9, + 0xaf7590f5, + 0x75038000, + 0x53229090, + 0xce53eff7, + 0xd90479fe, + 0xfdce53fe, + 0xfed90c79, + 0x75fbce53, + 0x91530b92, + 0xf7ce53fd, + 0x5308f943, + 0xf922fef9, + 0xfbd8fed9, + 0x019e7522, + 0x75cfc175, + 0xc375a4c2, + 0x47c4754a, + 0x75a4c575, + 0xc7756dc6, + 0x03d27540, + 0x7510d375, + 0xca7500c9, + 0x00cb75d0, + 0x7500cc75, + 0x9b75009a, + 0x009c75c0, + 0x78009d75, + 0x12827402, + 0xc6438b80, + 0x74097802, + 0x8b8012e7, + 0x75d09075, + 0x9e750291, + 0x01a97502, + 0x00000022, +}; + +extern bool reset_failed; + +irqreturn_t platform_cfg_req_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + const u64 EXYNOS_WLBT = 0x1; + u64 ret64 = 0; + s32 ret = 0; + unsigned int ka_addr = 0x1000; + uint32_t *ka_patch_addr = ka_patch; + u32 id; + +#define CHECK(x) do { \ + int retval = (x); \ + if (retval < 0) \ + goto cfg_error; \ +} while (0) + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "disable_irq\n"); + + /* mask the irq */ + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + /* Was the CFG_REQ irq received from WLBT before we expected it? + * Typically this indicates an issue returning WLBT HW to reset. + */ + if (platform->boot_state != WLBT_BOOT_WAIT_CFG_REQ) { + u32 val; + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Spurious CFG_REQ IRQ from WLBT!\n"); + + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_NS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CTRL_S, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_S 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + reset_failed = true; /* prevent further interaction with HW */ + + return IRQ_HANDLED; + } + + /* CBUS should be ready before we get CFG_REQ, but we suspect + * CBUS is not ready yet. add some delay to see if that helps + */ + udelay(100); + + /* Set TZPC to non-secure mode */ + ret64 = exynos_smc(SMC_CMD_CONN_IF, (EXYNOS_WLBT << 32) | EXYNOS_SET_CONN_TZPC, 0, 0); + if (ret64) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to set TZPC to non-secure mode: %llu\n", ret64); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SMC_CMD_CONN_IF run successfully : %llu\n", ret64); + + /* WLBT_REMAP */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP begin\n"); + CHECK(regmap_write(platform->wlbt_remap, 0x0, WLBT_DBUS_BAAW_0_START >> 12)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP end\n"); + + /* CHIP_VERSION_ID - overload with EMA settings */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "CHIP_VERSION_ID begin\n"); + regmap_read(platform->wlbt_remap, 0x10, &id); + id &= ~CHIP_VERSION_ID_EMA_MASK; + id |= CHIP_VERSION_ID_EMA_VALUE; + CHECK(regmap_write(platform->wlbt_remap, 0x10, id)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "CHIP_VERSION_ID 0x%x end\n", id); + + /* DBUS_BAAW regions */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "DBUS_BAAW begin\n"); + CHECK(regmap_write(platform->dbus_baaw, 0x0, WLBT_DBUS_BAAW_0_START >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0x4, WLBT_DBUS_BAAW_0_END >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0x8, platform->mem_start >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0xC, WLBT_BAAW_ACCESS_CTRL)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "DBUS_BAAW end\n"); + + /* PBUS_BAAW regions */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "PBUS_BAAW begin\n"); + CHECK(regmap_write(platform->pbus_baaw, 0x0, WLBT_PBUS_BAAW_0_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x4, WLBT_PBUS_BAAW_0_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x8, WLBT_PBUS_MBOX_CP2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0xC, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x10, WLBT_PBUS_BAAW_1_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x14, WLBT_PBUS_BAAW_1_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x18, WLBT_PBUS_MBOX_SHUB2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x1C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x20, WLBT_PBUS_BAAW_2_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x24, WLBT_PBUS_BAAW_2_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x28, WLBT_PBUS_USI_CMG00_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x2C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x30, WLBT_PBUS_BAAW_3_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x34, WLBT_PBUS_BAAW_3_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x38, WLBT_PBUS_SYSREG_CMGP2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x3C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x40, WLBT_PBUS_BAAW_4_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x44, WLBT_PBUS_BAAW_4_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x48, WLBT_PBUS_GPIO_CMGP_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x4C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x50, WLBT_PBUS_BAAW_5_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x54, WLBT_PBUS_BAAW_5_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x58, WLBT_PBUS_SHUB_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x5C, WLBT_BAAW_ACCESS_CTRL)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "PBUS_BAAW end\n"); + + /* PMU boot bug workaround */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "BOOT_WLBT begin\n"); + CHECK(regmap_write(platform->boot_cfg, 0x0, 0x1)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "BOOT_WLBT done\n"); + + while (ka_patch_addr < (ka_patch + ARRAY_SIZE(ka_patch))) { + CHECK(regmap_write(platform->boot_cfg, ka_addr, *ka_patch_addr)); + ka_addr += sizeof(ka_patch[0]); + ka_patch_addr++; + } + + /* Notify PMU of configuration done */ + CHECK(regmap_write(platform->boot_cfg, 0x0, 0x0)); + + /* BOOT_CFG_ACK */ + CHECK(regmap_write(platform->boot_cfg, 0x4, 0x1)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BOOT_CFG_ACK done\n"); + + /* Delay to allow HW to clear CFG_REQ and hence de-assert IRQ, which + * it does in response to CFG_ACK + */ + udelay(100); + + /* Release ownership of MASK_PWR_REQ */ + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to clear WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + goto cfg_error; + } + + /* Mark as CFQ_REQ handled, so boot may continue */ + platform->boot_state = WLBT_BOOT_CFG_DONE; + + /* Signal triggering function that the IRQ arrived and CFG was done */ + complete(&platform->cfg_ack); + + /* Re-enable IRQ here to allow spurious interrupt to be tracked */ + enable_irq(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + return IRQ_HANDLED; +cfg_error: + platform->boot_state = WLBT_BOOT_CFG_ERROR; + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "ERROR: WLBT Config failed. WLBT will not work\n"); + complete(&platform->cfg_ack); + return IRQ_HANDLED; +} +#endif + +static void platform_mif_unregister_irq(struct platform_mif *platform) +{ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering IRQs\n"); + + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform); + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform); + /* Reset irq_disabled_cnt for WDOG IRQ since the IRQ itself is here unregistered and disabled */ + atomic_set(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt, 0); +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ + /* if ALIVE irq is required */ + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform); +#endif +#ifdef CONFIG_SOC_EXYNOS9610 + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform); +#endif +} + +static int platform_mif_register_irq(struct platform_mif *platform) +{ + int err; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering IRQs\n"); + + /* Register MBOX irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering MBOX irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform->wlbt_irq[PLATFORM_MIF_MBOX].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform_mif_isr, + platform->wlbt_irq[PLATFORM_MIF_MBOX].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register MBOX handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + + /* Register WDOG irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering WDOG irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform->wlbt_irq[PLATFORM_MIF_WDOG].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform_wdog_isr, + platform->wlbt_irq[PLATFORM_MIF_WDOG].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register WDOG handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ + /* Register ALIVE irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering ALIVE irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform->wlbt_irq[PLATFORM_MIF_ALIVE].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform_alive_isr, + platform->wlbt_irq[PLATFORM_MIF_ALIVE].flags, DRV_NAME, platform); + if (IS_ERR_VALUE(err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register ALIVE handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } +#endif + +#ifdef CONFIG_SOC_EXYNOS9610 + /* Mark as WLBT in reset before enabling IRQ to guard against spurious IRQ */ + platform->boot_state = WLBT_BOOT_IN_RESET; + smp_wmb(); /* commit before irq */ + + /* Register WB2AP_CFG_REQ irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering CFG_REQ irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform_cfg_req_isr, + platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register CFG_REQ handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } +#endif + return 0; +} + +static void platform_mif_destroy(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + platform_mif_unregister_irq(platform); +} + +static char *platform_mif_get_uid(struct scsc_mif_abs *interface) +{ + /* Avoid unused parameter error */ + (void)interface; + return "0"; +} + +/* WLBT Power domain */ +static int platform_mif_power(struct scsc_mif_abs *interface, bool power) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val = 0; + s32 ret = 0; +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "power %d\n", power); + + if (power) + val = MASK_PWR_REQ; + + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + return ret; + } +#else + /* See sequence in 8.6.6 */ + /* WIFI power on/off control If WIFI_PWRON = 1 + * and WIFI_START=1, WIFI enters to UP state. + * This bit is 0 as default value because WIFI + * should be reset at AP boot mode after Power-on Reset. + */ + if (power) + val = WIFI_PWRON; + ret = regmap_update_bits(platform->pmureg, WIFI_CTRL_NS, + WIFI_PWRON, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WIFI_CTRL_NS[WIFI_PWRON]: %d\n", ret); + return ret; + } +#endif + return 0; +} + +/* WLBT RESET */ +static int platform_mif_hold_reset(struct scsc_mif_abs *interface, bool reset) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val = 0; + s32 ret = 0; +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "reset %d\n", reset); + if (reset) + val = WLBT_RESET_SET; + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_RESET_SET, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[WLBT_RESET_SET]: %d\n", ret); + return ret; + } +#else + if (reset) + val = WIFI_RESET_SET; + /* See sequence in 8.6.6 */ + ret = regmap_update_bits(platform->pmureg, WIFI_CTRL_NS, + WIFI_RESET_SET, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WIFI_CTRL_NS[WIFI_RESET_SET]: %d\n", ret); + return ret; + } +#endif + return 0; +} + +/* WLBT START */ +static int platform_mif_start(struct scsc_mif_abs *interface, bool start) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val = 0; + s32 ret = 0; + +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "start %d\n", start); + if (start) + val = WLBT_START; + + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_S, + WLBT_START, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_S[WLBT_START]: %d\n", ret); + return ret; + } + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "update WIFI_CTRL_S[WIFI_START]: %d\n", ret); + + /* At this point WLBT should assert the CFG_REQ IRQ, so wait for it */ + if (start && + wait_for_completion_timeout(&platform->cfg_ack, WLBT_BOOT_TIMEOUT) == 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Timeout waiting for CFG_REQ IRQ\n"); + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + return -ETIMEDOUT; + } + /* only continue if CFG_REQ IRQ configured WLBT/PMU correctly */ + if (platform->boot_state == WLBT_BOOT_CFG_ERROR) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "CFG_REQ failed to configure WLBT.\n"); + return -EIO; + } +#else + if (start) + val = WIFI_START; + /* See sequence in 8.6.6 */ + ret = regmap_update_bits(platform->pmureg, WIFI_CTRL_S, + WIFI_START, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WIFI_CTRL_S[WIFI_START]: %d\n", ret); + return ret; + } +#endif + return 0; +} + +static int platform_mif_pmu_reset_release(struct scsc_mif_abs *interface) +{ + int ret = 0; + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + +#ifdef CONFIG_SOC_EXYNOS9610 + /* We're now ready for the IRQ */ + platform->boot_state = WLBT_BOOT_WAIT_CFG_REQ; + smp_wmb(); /* commit before irq */ +#endif + ret = platform_mif_power(interface, true); + if (ret) + return ret; + ret = platform_mif_hold_reset(interface, false); + if (ret) + return ret; + ret = platform_mif_start(interface, true); + if (ret) + return ret; + + return ret; +} + +int __platform_mif_9610_recover_reset(struct platform_mif *platform) +{ + unsigned long timeout; + int ret; + u32 val; + + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Attempt to recover WLBT HW reset"); + + /* + * Set CFG_ACK = 1 + * Poll CFG_REQ until REQ == 0 + * Set CFG_ACK = 0 + * Poll CENTRAL_SEQ_WLBT_STATUS SM status again + * Print WLBT_DEBUG at each step. + * (Remember to set "echo 1 > /sys/kernel/debug/exynos-rgt/vqmmc/enable" before the test). + */ + + /* CFG_ACK = 1 */ + regmap_write(platform->boot_cfg, 0x4, 0x1); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Force BOOT_CFG_ACK = 1\n"); + + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + /* TBD poll CFG_REQ status */ + udelay(250); + + /* CFG_ACK = 0 */ + regmap_write(platform->boot_cfg, 0x4, 0x0); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Force BOOT_CFG_ACK = 0\n"); + + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + /* Check WLBT_STATUS again */ + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + val &= STATES; + val >>= 16; + if (val == 0x80) { + ret = 0; /* Recovered OK */ + goto done; + } + } while (time_before(jiffies, timeout)); + ret = -ETIME; + +done: + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + if (ret == 0) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Recovered reset"); + else + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Reset not recovered"); + + /* Save log at point of failure, last to show recovery attempt */ +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_schedule_collection(SCSC_LOG_HOST_COMMON, SCSC_LOG_HOST_COMMON_RECOVER_RST); +#else + mx140_log_dump(); +#endif + return ret; +} + +static int platform_mif_pmu_reset(struct scsc_mif_abs *interface, u8 rst_case) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long timeout; + int ret; + u32 val; + + if (rst_case == 0 || rst_case > 2) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Incorrect pmu reset case %d\n", rst_case); + return -EIO; + } + +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + ret = regmap_update_bits(platform->pmureg, RESET_AHEAD_WIFI_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update RESET_ASB_WIFI_SYS_PWR_REG %d\n", ret); + return ret; + } +#elif defined(CONFIG_SOC_EXYNOS7570) + ret = regmap_update_bits(platform->pmureg, RESET_ASB_WIFI_SYS_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update RESET_ASB_WIFI_SYS_PWR_REG %d\n", ret); + return ret; + } + +#endif +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "rst_case %d\n", rst_case); + + /* Revert power control ownership to AP, as WLBT is going down (S9.6.6). */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, MASK_PWR_REQ); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + return ret; + } + + /* reset sequence as per excite implementation for Leman */ + ret = regmap_update_bits(platform->pmureg, CENTRAL_SEQ_WLBT_CONFIGURATION, + SYS_PWR_CFG_16, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update CENTRAL_SEQ_WLBT_CONFIGURATION %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, RESET_AHEAD_WLBT_SYS_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update RESET_AHEAD_WLBT_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, CLEANY_BUS_WLBT_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update CLEANY_BUS_WLBT_SYS_PWR_REG%d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, LOGIC_RESET_WLBT_SYS_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update LOGIC_RESET_WLBT_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, TCXO_GATE_WLBT_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update TCXO_GATE_WLBT_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, WLBT_DISABLE_ISO_SYS_PWR_REG, + SYS_PWR_CFG, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_DISABLE_ISO_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, WLBT_RESET_ISO_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_RESET_ISO_SYS_PWR_REG %d\n", ret); + return ret; + } + + /* rst_case is always 2 on 9610 */ + ret = platform_mif_hold_reset(interface, true); + + if (ret) + return ret; + + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + val &= STATES; + val >>= 16; + if (val == 0x80) { + /* OK. Switch CTRL_NS[MASK_PWR_REQ] ownership to FW following + * reset. WLBT PWR_REQ is cleared when it's put in reset. + * The SW PWR_REQ remains asserted, but as ownership is now FW, + * it'll be ignored. This leaves it as we found it. + */ + platform_mif_power(interface, false); + + return 0; /* OK - return */ + } + } while (time_before(jiffies, timeout)); + + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Timeout waiting for CENTRAL_SEQ_WLBT_STATUS SM status\n"); + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + +#if 0 + /* Try to recovery in case of reset failure */ + ret = __platform_mif_9610_recover_reset(platform); + if (!ret) + return 0; +#endif +#else + /* 7885, 7872, 7570 */ + ret = regmap_update_bits(platform->pmureg, CLEANY_BUS_WIFI_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update CLEANY_BUS_WIFI_SYS_PWR_REG%d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, LOGIC_RESET_WIFI_SYS_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update LOGIC_RESET_WIFI_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, TCXO_GATE_WIFI_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update TCXO_GATE_WIFI_SYS_PWR_REG %d\n", ret); + return ret; + } +#endif +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + ret = regmap_update_bits(platform->pmureg, WIFI_DISABLE_ISO_SYS_PWR_REG, + SYS_PWR_CFG, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WIFI_DISABLE_ISO_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, WIFI_RESET_ISO_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WIFI_RESET_ISO_SYS_PWR_REG %d\n", ret); + return ret; + } +#endif +#ifdef CONFIG_SOC_EXYNOS9610 + (void)platform; + (void)timeout; + (void)ret; + (void)val; +#else + /* 7885, 7872, 7570 */ + ret = regmap_update_bits(platform->pmureg, CENTRAL_SEQ_WIFI_CONFIGURATION, + SYS_PWR_CFG_16, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update CENTRAL_SEQ_WIFI_CONFIGURATION %d\n", ret); + return ret; + } + + if (rst_case == 1) + ret = platform_mif_power(interface, false); + else + ret = platform_mif_hold_reset(interface, true); + + if (ret) + return ret; + + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, CENTRAL_SEQ_WIFI_STATUS, &val); + val &= STATES; + val >>= 16; + if (val == 0x80) + return 0; + } while (time_before(jiffies, timeout)); + + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Timeout waiting for CENTRAL_SEQ_WIFI_STATUS SM status\n"); +#endif + return -ETIME; +} + +/* reset=0 - release from reset */ +/* reset=1 - hold reset */ +static int platform_mif_reset(struct scsc_mif_abs *interface, bool reset) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 ret = 0; + u32 val; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (enable_platform_mif_arm_reset || !reset) { + if (!reset) { /* Release from reset */ +#ifdef CONFIG_ARCH_EXYNOS + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SOC_VERSION: product_id 0x%x, rev 0x%x\n", + exynos_soc_info.product_id, exynos_soc_info.revision); +#endif +#ifdef CONFIG_SOC_EXYNOS9610 + (void)val; +#else + /* Enable R4 access to MIF resources */ + /* Address is fixed and its value is in the dts */ + /* It defines the base address of DRAM space which WIFI accesses. + * Default value is 0x0_6000_0000. It can configure only MSB 14-bit. + * You must align this base address with WIFI memory access size that WIFI_MEM_SIZE + * defines + */ + /* >>12 represents 4K aligment (see size)*/ + val = (platform->mem_start & 0xFFFFFC000) >> 12; + regmap_write(platform->pmureg, WIFI2AP_MEM_CONFIG1, val); + /* Size */ + /*It defines the DRAM memory size to which WLBT can + * access. + * Definition of the size has 4 KB resolution defined from + * minimum 4 KB to maximum 1GB. + * 20'b0000_0000_0000_0000_0001 = 4KB + * 20'b0000_0000_0000_0000_0010 = 8KB + * 20'b0000_0000_0001_0000_0000 = 1 MB + * 20'b0000_0000_0010_0000_0000 = 2 MB + * 20'b0000_0000_0100_0000_0000 = 4 MB (default) + * 20'b0000_0000_1000_0000_0000 = 8 MB + * 20'b0000_1000_0000_0000_0000 = 128 MB + * 20'b0100_0000_0000_0000_0000 = 1 GB + */ + /* Size is fixed and its value is in the dts */ + /* >>12 represents 4K aligment (see address)*/ + val = platform->mem_size >> 12; + regmap_write(platform->pmureg, WIFI2AP_MEM_CONFIG0, val); +#endif +#if (defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885)) && defined(CONFIG_ACPM_DVFS) + /* Immediately prior to reset release, set up ACPM + * to ensure BUCK2 gets the right voltage + */ + exynos_acpm_set_flag(); +#endif + ret = platform_mif_pmu_reset_release(interface); + } else { + /* Put back into reset */ + ret = platform_mif_pmu_reset(interface, 2); +#if !defined(CONFIG_SOC_EXYNOS9610) + /* WLBT should be stopped/powered-down at this point */ + regmap_write(platform->pmureg, WIFI2AP_MEM_CONFIG1, 0x00000); + regmap_write(platform->pmureg, WIFI2AP_MEM_CONFIG0, 0x00000); +#endif + } + } else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Not resetting ARM Cores - enable_platform_mif_arm_reset: %d\n", + enable_platform_mif_arm_reset); + return ret; +} + +static void __iomem *platform_mif_map_region(unsigned long phys_addr, size_t size) +{ + int i; + struct page **pages; + void *vmem; + + size = PAGE_ALIGN(size); + + pages = kmalloc((size >> PAGE_SHIFT) * sizeof(*pages), GFP_KERNEL); + if (!pages) + return NULL; + + /* Map NORMAL_NC pages with kernel virtual space */ + for (i = 0; i < (size >> PAGE_SHIFT); i++) { + pages[i] = phys_to_page(phys_addr); + phys_addr += PAGE_SIZE; + } + + vmem = vmap(pages, size >> PAGE_SHIFT, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + + kfree(pages); + return (void __iomem *)vmem; +} + +static void platform_mif_unmap_region(void *vmem) +{ + vunmap(vmem); +} + +static void *platform_mif_map(struct scsc_mif_abs *interface, size_t *allocated) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + + if (allocated) + *allocated = 0; + + platform->mem = + platform_mif_map_region(platform->mem_start, platform->mem_size); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Error remaping shared memory\n"); + return NULL; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Map: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + + /* Initialise MIF registers with documented defaults */ + /* MBOXes */ + for (i = 0; i < NUM_MBOX_PLAT; i++) { + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(ISSR(i)), 0x00000000); +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + platform_mif_reg_write_m4(platform, MAILBOX_WLBT_REG(ISSR(i)), 0x00000000); +#endif + } + /* MRs */ /*1's - set all as Masked */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); +#ifdef CONFIG_SOC_EXYNOS7570 + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR2), 0x0000ffff); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + platform_mif_reg_write_m4(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); +#endif + + /* CRs */ /* 1's - clear all the interrupts */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); +#ifdef CONFIG_SOC_EXYNOS7570 + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR2), 0x0000ffff); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + platform_mif_reg_write_m4(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); +#endif +#if !defined(CONFIG_SOC_EXYNOS9610) + /*Set WLBT_BOOT_TEST_RST_CFG to 0 to boot from external DRAM */ + regmap_write(platform->pmureg, WLBT_BOOT_TEST_RST_CFG, 0x00000); +#endif + /* Add more requrired initialization here: */ + +#ifdef CONFIG_SCSC_GPR4_CON_DEBUG + /* PIO muxes switching to the Maxwell subsystem */ + /* GPR4_CON (0x13750040) = 0x00666666 */ + if (gpr4_debug) { + reg_bkp = readl(gpio_base); + writel(0x00666666, gpio_base); + SCSC_TAG_WARNING_DEV(PLAT_MIF, platform->dev, "[WARNING] Changing GPR4_CON from 0x%x to 0x%x", reg_bkp, readl(gpio_base)); + reg_update = true; + } +#endif + +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_disable_irq == true) { + if (allocated) + *allocated = platform->mem_size; + return platform->mem; + } +#endif + /* register interrupts */ + if (platform_mif_register_irq(platform)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unmap: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + platform_mif_unmap_region(platform->mem); + return NULL; + } + + if (allocated) + *allocated = platform->mem_size; + /* Set the CR4 base address in Mailbox??*/ + return platform->mem; +} + +/* HERE: Not sure why mem is passed in - its stored in platform - as it should be */ +static void platform_mif_unmap(struct scsc_mif_abs *interface, void *mem) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + +#ifdef CONFIG_SCSC_GPR4_CON_DEBUG + u32 prev; + + if (gpr4_debug && reg_update) { + prev = readl(gpio_base); + writel(reg_bkp, gpio_base); + SCSC_TAG_WARNING_DEV(PLAT_MIF, platform->dev, "[WARNING] Restoring GPR4_CON from 0x%x to 0x%x", prev, readl(gpio_base)); + } + reg_update = false; +#endif + /* Avoid unused parameter error */ + (void)mem; + + /* MRs */ /*1's - set all as Masked */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); +#ifdef CONFIG_SOC_EXYNOS7570 + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR2), 0x0000ffff); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + platform_mif_reg_write_m4(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); +#endif + +#ifdef CONFIG_SCSC_CHV_SUPPORT + /* Restore PIO changed by Maxwell subsystem */ + if (chv_disable_irq == false) + /* Unregister IRQs */ + platform_mif_unregister_irq(platform); +#else + platform_mif_unregister_irq(platform); +#endif + /* CRs */ /* 1's - clear all the interrupts */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); +#ifdef CONFIG_SOC_EXYNOS7570 + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR2), 0x0000ffff); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + platform_mif_reg_write_m4(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unmap: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + platform_mif_unmap_region(platform->mem); + platform->mem = NULL; +} + +static u32 platform_mif_irq_bit_mask_status_get(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)) >> 16; + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Getting INTMR0: 0x%x\n", val); + return val; +} + +static u32 platform_mif_irq_get(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + + /* Function has to return the interrupts that are enabled *AND* not masked */ + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR0)) >> 16; + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Getting INT-INTMSR0: 0x%x\n", val); + + return val; +} + +static void platform_mif_irq_bit_set(struct scsc_mif_abs *interface, int bit_num, enum scsc_mif_abs_target target) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 reg; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } +#ifdef CONFIG_SOC_EXYNOS9610 + reg = INTGR1; + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(reg), (1 << bit_num)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTGR1: bit %d on target %d\n", bit_num, target); +#else + /* Generate INT to R4/M4 - VIC */ + if (target == SCSC_MIF_ABS_TARGET_R4) + reg = INTGR1; + else if (target == SCSC_MIF_ABS_TARGET_M4) +#ifdef CONFIG_SOC_EXYNOS7570 + reg = INTGR2; +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + reg = INTGR1; +#endif + else { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect Target %d\n", target); + return; + } +#endif +#ifdef CONFIG_SOC_EXYNOS7570 + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(reg), (1 << bit_num)); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + if (target == SCSC_MIF_ABS_TARGET_R4) + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(reg), (1 << bit_num)); + else + platform_mif_reg_write_m4(platform, MAILBOX_WLBT_REG(reg), (1 << bit_num)); +#endif + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTGR1: bit %d on target %d\n", bit_num, target); +} + +static void platform_mif_irq_bit_clear(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + /* WRITE : 1 = Clears Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), ((1 << bit_num) << 16)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTCR0: bit %d\n", bit_num); +} + +static void platform_mif_irq_bit_mask(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + unsigned long flags; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + /* WRITE : 1 = Mask Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val | ((1 << bit_num) << 16)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTMR0: 0x%x bit %d\n", val | (1 << bit_num), bit_num); +} + +static void platform_mif_irq_bit_unmask(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + unsigned long flags; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + /* WRITE : 0 = Unmask Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val & ~((1 << bit_num) << 16)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "UNMASK Setting INTMR0: 0x%x bit %d\n", val & ~((1 << bit_num) << 16), bit_num); +} + +/* Return the contents of the mask register */ +static u32 __platform_mif_irq_bit_mask_read(struct platform_mif *platform) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Read INTMR0: 0x%x\n", val); + + return val; +} + +/* Write the mask register, destroying previous contents */ +static void __platform_mif_irq_bit_mask_write(struct platform_mif *platform, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Write INTMR0: 0x%x\n", val); +} + +static void platform_mif_irq_reg_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif int handler %pS in %p %p\n", handler, platform, interface); + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform->r4_handler = handler; + platform->irq_dev = dev; + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_irq_unreg_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering mif int handler %pS\n", interface); + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform->r4_handler = platform_mif_irq_default_handler; + platform->irq_dev = NULL; + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_irq_reg_reset_request_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif reset_request int handler %pS in %p %p\n", handler, platform, interface); + platform->reset_request_handler = handler; + platform->irq_reset_request_dev = dev; + if (atomic_read(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "Default WDOG handler disabled by spurios IRQ...re-enabling.\n"); + enable_irq(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + atomic_set(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt, 0); + } +} + +static void platform_mif_irq_unreg_reset_request_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "UnRegistering mif reset_request int handler %pS\n", interface); + platform->reset_request_handler = platform_mif_irq_reset_request_default_handler; + platform->irq_reset_request_dev = NULL; +} + +static void platform_mif_suspend_reg_handler(struct scsc_mif_abs *interface, + int (*suspend)(struct scsc_mif_abs *abs, void *data), + void (*resume)(struct scsc_mif_abs *abs, void *data), + void *data) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif suspend/resume handlers in %p %p\n", platform, interface); + platform->suspend_handler = suspend; + platform->resume_handler = resume; + platform->suspendresume_data = data; +} + +static void platform_mif_suspend_unreg_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering mif suspend/resume handlers in %p %p\n", platform, interface); + platform->suspend_handler = NULL; + platform->resume_handler = NULL; + platform->suspendresume_data = NULL; +} + +static u32 *platform_mif_get_mbox_ptr(struct scsc_mif_abs *interface, u32 mbox_index) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 *addr; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "mbox_index 0x%x\n", mbox_index); + addr = platform->base + MAILBOX_WLBT_REG(ISSR(mbox_index)); + return addr; +} + +static int platform_mif_get_mifram_ref(struct scsc_mif_abs *interface, void *ptr, scsc_mifram_ref *ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return -ENOMEM; + } + + /* Check limits! */ + if (ptr >= (platform->mem + platform->mem_size)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Unable to get pointer reference\n"); + return -ENOMEM; + } + + *ref = (scsc_mifram_ref)((uintptr_t)ptr - (uintptr_t)platform->mem); + + return 0; +} + +static void *platform_mif_get_mifram_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return NULL; + } + + /* Check limits */ + if (ref >= 0 && ref < platform->mem_size) + return (void *)((uintptr_t)platform->mem + (uintptr_t)ref); + else + return NULL; +} + +static void *platform_mif_get_mifram_phy_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem_start) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return NULL; + } + + return (void *)((uintptr_t)platform->mem_start + (uintptr_t)ref); +} + +static uintptr_t platform_mif_get_mif_pfn(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + return vmalloc_to_pfn(platform->mem); +} + +static struct device *platform_mif_get_mif_device(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + return platform->dev; +} + +static void platform_mif_irq_clear(void) +{ + /* Implement if required */ +} + +static int platform_mif_read_register(struct scsc_mif_abs *interface, u64 id, u32 *val) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (id == SCSC_REG_READ_WLBT_STAT) { + regmap_read(platform->pmureg, WLBT_STAT, val); + return 0; + } + + return -EIO; +} + +static void platform_mif_dump_register(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR1))); +#ifdef CONFIG_SOC_EXYNOS7570 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR2 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR2))); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR1(M4) 0x%08x\n", platform_mif_reg_read_m4(platform, MAILBOX_WLBT_REG(INTGR1))); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR1))); +#ifdef CONFIG_SOC_EXYNOS7570 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR2 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR2))); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR1(M4) 0x%08x\n", platform_mif_reg_read_m4(platform, MAILBOX_WLBT_REG(INTCR1))); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR1))); +#ifdef CONFIG_SOC_EXYNOS7570 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR2 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR2))); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR1(M4) 0x%08x\n", platform_mif_reg_read_m4(platform, MAILBOX_WLBT_REG(INTMR1))); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR1))); +#ifdef CONFIG_SOC_EXYNOS7570 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR2 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR2))); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR1(M4) 0x%08x\n", platform_mif_reg_read_m4(platform, MAILBOX_WLBT_REG(INTSR1))); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR1))); +#ifdef CONFIG_SOC_EXYNOS7570 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR2 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR2))); +#elif defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR1(M4) 0x%08x\n", platform_mif_reg_read_m4(platform, MAILBOX_WLBT_REG(INTMSR1))); +#endif + + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_cleanup(struct scsc_mif_abs *interface) +{ +#ifdef CONFIG_SCSC_CLK20MHZ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + /* Restore USBPLL owenership to the AP so that USB driver may control it */ + __platform_mif_usbpll_claim(platform, false); +#endif +} + +static void platform_mif_restart(struct scsc_mif_abs *interface) +{ +#ifdef CONFIG_SCSC_CLK20MHZ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + /* Restore USBPLL owenership to the wlbt */ + __platform_mif_usbpll_claim(platform, true); +#endif +} + +#ifdef CONFIG_OF_RESERVED_MEM +static int __init platform_mif_wifibt_if_reserved_mem_setup(struct reserved_mem *remem) +{ + SCSC_TAG_DEBUG(PLAT_MIF, "memory reserved: mem_base=%#lx, mem_size=%zd\n", + (unsigned long)remem->base, (size_t)remem->size); + + sharedmem_base = remem->base; + sharedmem_size = remem->size; + return 0; +} +RESERVEDMEM_OF_DECLARE(wifibt_if, "exynos,wifibt_if", platform_mif_wifibt_if_reserved_mem_setup); +#endif + +#ifdef CONFIG_SCSC_CLK20MHZ +static void __platform_mif_usbpll_claim(struct platform_mif *platform, bool wlbt) +{ + + s32 ret = 0; + + if (!platform->cmu_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "USBPLL claim not enabled\n"); + return; + } + + /* Set USBPLL ownership, by setting AP2WLBT_USBPLL_WPLL_SEL */ + + if (wlbt) { + /* WLBT f/w has control */ + ret = regmap_update_bits(platform->cmu_base, USBPLL_CON1, AP2WLBT_USBPLL_WPLL_SEL, 0); + if (ret < 0) + goto error; + } else { + /* Ensure PLL runs */ + ret = regmap_update_bits(platform->cmu_base, USBPLL_CON1, AP2WLBT_USBPLL_WPLL_EN, AP2WLBT_USBPLL_WPLL_EN); + if (ret < 0) + goto error; + + /* AP has control */ + udelay(platform->usbpll_delay); + ret = regmap_update_bits(platform->cmu_base, USBPLL_CON1, AP2WLBT_USBPLL_WPLL_SEL, AP2WLBT_USBPLL_WPLL_SEL); + if (ret < 0) + goto error; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "USBPLL_CON1 assigned to %s\n", wlbt ? "WLBT" : "AP"); + return; + +error: + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Failed to update USBPLL_CON1 to %s\n", wlbt ? "WLBT" : "AP"); + +} +#endif + +struct scsc_mif_abs *platform_mif_create(struct platform_device *pdev) +{ + struct scsc_mif_abs *platform_if; + struct platform_mif *platform = + (struct platform_mif *)devm_kzalloc(&pdev->dev, sizeof(struct platform_mif), GFP_KERNEL); + int err = 0; + u8 i = 0; + struct resource *reg_res; +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + struct resource *reg_res_m4; +#endif +#ifdef CONFIG_SCSC_CLK20MHZ + /* usb pll ownership */ + const char *usbowner = NULL; + u32 usbdelay = 0; +#endif +#ifdef CONFIG_SCSC_SMAPPER + u32 smapper_banks = 0; +#endif + + if (!platform) + return NULL; + + SCSC_TAG_INFO_DEV(PLAT_MIF, &pdev->dev, "Creating MIF platform device\n"); + + platform_if = &platform->interface; + + /* initialise interface structure */ + platform_if->destroy = platform_mif_destroy; + platform_if->get_uid = platform_mif_get_uid; + platform_if->reset = platform_mif_reset; + platform_if->map = platform_mif_map; + platform_if->unmap = platform_mif_unmap; + platform_if->irq_bit_set = platform_mif_irq_bit_set; + platform_if->irq_get = platform_mif_irq_get; + platform_if->irq_bit_mask_status_get = platform_mif_irq_bit_mask_status_get; + platform_if->irq_bit_clear = platform_mif_irq_bit_clear; + platform_if->irq_bit_mask = platform_mif_irq_bit_mask; + platform_if->irq_bit_unmask = platform_mif_irq_bit_unmask; + platform_if->irq_reg_handler = platform_mif_irq_reg_handler; + platform_if->irq_unreg_handler = platform_mif_irq_unreg_handler; + platform_if->irq_reg_reset_request_handler = platform_mif_irq_reg_reset_request_handler; + platform_if->irq_unreg_reset_request_handler = platform_mif_irq_unreg_reset_request_handler; + platform_if->suspend_reg_handler = platform_mif_suspend_reg_handler; + platform_if->suspend_unreg_handler = platform_mif_suspend_unreg_handler; + platform_if->get_mbox_ptr = platform_mif_get_mbox_ptr; + platform_if->get_mifram_ptr = platform_mif_get_mifram_ptr; + platform_if->get_mifram_ref = platform_mif_get_mifram_ref; + platform_if->get_mifram_pfn = platform_mif_get_mif_pfn; + platform_if->get_mifram_phy_ptr = platform_mif_get_mifram_phy_ptr; + platform_if->get_mif_device = platform_mif_get_mif_device; + platform_if->irq_clear = platform_mif_irq_clear; + platform_if->mif_dump_registers = platform_mif_dump_register; + platform_if->mif_read_register = platform_mif_read_register; + platform_if->mif_cleanup = platform_mif_cleanup; + platform_if->mif_restart = platform_mif_restart; +#ifdef CONFIG_SCSC_SMAPPER + platform_if->mif_smapper_get_mapping = platform_mif_smapper_get_mapping; + platform_if->mif_smapper_get_bank_info = platform_mif_smapper_get_bank_info; + platform_if->mif_smapper_write_sram = platform_mif_smapper_write_sram; + platform_if->mif_smapper_configure = platform_mif_smapper_configure; + platform_if->mif_smapper_get_bank_base_address = platform_mif_smapper_get_bank_base_address; +#endif +#ifdef CONFIG_SCSC_QOS + platform_if->mif_pm_qos_add_request = platform_mif_pm_qos_add_request; + platform_if->mif_pm_qos_update_request = platform_mif_pm_qos_update_request; + platform_if->mif_pm_qos_remove_request = platform_mif_pm_qos_remove_request; +#endif + /* Update state */ + platform->pdev = pdev; + platform->dev = &pdev->dev; + + platform->r4_handler = platform_mif_irq_default_handler; + platform->irq_dev = NULL; + platform->reset_request_handler = platform_mif_irq_reset_request_default_handler; + platform->irq_reset_request_dev = NULL; + platform->suspend_handler = NULL; + platform->resume_handler = NULL; + platform->suspendresume_data = NULL; + +#ifdef CONFIG_OF_RESERVED_MEM + platform->mem_start = sharedmem_base; + platform->mem_size = sharedmem_size; +#else + /* If CONFIG_OF_RESERVED_MEM is not defined, sharedmem values should be + * parsed from the scsc_wifibt binding + */ + if (of_property_read_u32(pdev->dev.of_node, "sharedmem-base", &sharedmem_base)) { + err = -EINVAL; + goto error_exit; + } + platform->mem_start = sharedmem_base; + + if (of_property_read_u32(pdev->dev.of_node, "sharedmem-size", &sharedmem_size)) { + err = -EINVAL; + goto error_exit; + } + platform->mem_size = sharedmem_size; +#endif +#ifdef CONFIG_SCSC_SMAPPER + platform->smapper = NULL; +#endif +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + /* TZASC configuration is required for WLBT to access DRAM from Katmai onward */ + /* Base address should be 4KB aligned. This call needs proper support in EL3_MON */ + err = exynos_smc(EXYNOS_SMC_WLBT_TZASC_CMD, WLBT_TZASC, platform->mem_start, + platform->mem_size); + /* Anyway we keep on WLBT initialization even if TZASC failed to minimize disruption*/ + if (err) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "WLBT Failed to configure TZASC, err=%d. DRAM could be NOT accessible in secure mode\n", err); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WLBT TZASC configured OK\n"); +#endif + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->mem_start 0x%x platform->mem_size 0x%x\n", (u32)platform->mem_start, (u32)platform->mem_size); + if (platform->mem_start == 0) + SCSC_TAG_WARNING_DEV(PLAT_MIF, platform->dev, "platform->mem_start is 0"); + + if (platform->mem_size == 0) { + /* We return return if mem_size is 0 as it does not make any sense. + * This may be an indication of an incorrect platform device binding. + */ + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "platform->mem_size is 0"); + err = -EINVAL; + goto error_exit; + } + + /* Memory resource - Phys Address of MAILBOX_WLBT register map */ + reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!reg_res) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error getting mem resource for MAILBOX_WLBT\n"); + err = -ENOENT; + goto error_exit; + } + + platform->reg_start = reg_res->start; + platform->reg_size = resource_size(reg_res); + + platform->base = + devm_ioremap_nocache(platform->dev, reg_res->start, resource_size(reg_res)); + + if (!platform->base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping register region\n"); + err = -EBUSY; + goto error_exit; + } + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->reg_start %lx size %x base %p\n", (uintptr_t)platform->reg_start, (u32)platform->reg_size, platform->base); + +#if defined(CONFIG_SOC_EXYNOS7872) || defined(CONFIG_SOC_EXYNOS7885) + /* Memory resource for M4 MBOX bank - Phys Address of MAILBOX_WLBT register map */ + reg_res_m4 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!reg_res_m4) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Error getting mem resource for MAILBOX_WLBT\n"); + err = -ENOENT; + goto error_exit; + } + platform->reg_start_m4 = reg_res_m4->start; + platform->reg_size_m4 = resource_size(reg_res_m4); + + platform->base_m4 = + devm_ioremap_nocache(platform->dev, reg_res_m4->start, resource_size(reg_res_m4)); + + if (!platform->base_m4) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping M4 register region\n"); + err = -EBUSY; + goto error_exit; + } + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->reg_start_m4 %lx size_m4 %x base_m4 %p\n", (uintptr_t)platform->reg_start_m4, (u32)platform->reg_size_m4, platform->base_m4); +#endif + +#ifdef CONFIG_SCSC_CLK20MHZ + /* Get usbpll,owner if Maxwell has to provide 20Mhz clock to USB subsystem */ + platform->cmu_base = NULL; + if (of_property_read_string(pdev->dev.of_node, "usbpll,owner", &usbowner)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "usbpll,owner of_property does not exist or is invalid\n"); + goto cont; + } else { + if (strcasecmp(usbowner, "y")) + goto skip_usbpll; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "usbpll,owner is enabled\n"); + + /* CMU_FSYS reg map - syscon */ + platform->cmu_base = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,syscon-cmu_fsys"); + if (IS_ERR(platform->cmu_base)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "syscon regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->cmu_base)); + goto skip_usbpll; + } + + if (of_property_read_u32(pdev->dev.of_node, "usbpll,udelay", &usbdelay)) + goto skip_usbpll; + platform->usbpll_delay = usbdelay; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Mapping CMU_FSYS with %dus delay\n", usbdelay); + + goto cont; +skip_usbpll: + platform->cmu_base = NULL; + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "usbpll owner skipped\n"); + } +cont: +#endif +#ifdef CONFIG_SCSC_GPR4_CON_DEBUG + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Mapping GPR4_CON 0x13750040\n"); + gpio_base = + devm_ioremap_nocache(platform->dev, 0x13750040, 4); +#endif + + /* Get the 4 IRQ resources */ + for (i = 0; i < 4; i++) { + struct resource *irq_res; + int irqtag; + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!irq_res) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "No IRQ resource at index %d\n", i); + err = -ENOENT; + goto error_exit; + } + + if (!strcmp(irq_res->name, "MBOX")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "MBOX irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_MBOX; + } else if (!strcmp(irq_res->name, "ALIVE")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "ALIVE irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_ALIVE; + } else if (!strcmp(irq_res->name, "WDOG")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WDOG irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_WDOG; + } else if (!strcmp(irq_res->name, "CFG_REQ")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "CFG_REQ irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_CFG_REQ; + } else { + SCSC_TAG_ERR_DEV(PLAT_MIF, &pdev->dev, "Invalid irq res name: %s\n", + irq_res->name); + err = -EINVAL; + goto error_exit; + } + platform->wlbt_irq[irqtag].irq_num = irq_res->start; + platform->wlbt_irq[irqtag].flags = (irq_res->flags & IRQF_TRIGGER_MASK); + atomic_set(&platform->wlbt_irq[irqtag].irq_disabled_cnt, 0); + } + + /* PMU reg map - syscon */ + platform->pmureg = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(platform->pmureg)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "syscon regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->pmureg)); + err = -EINVAL; + goto error_exit; + } + +#ifdef CONFIG_SOC_EXYNOS9610 + /* Completion event and state used to indicate CFG_REQ IRQ occurred */ + init_completion(&platform->cfg_ack); + platform->boot_state = WLBT_BOOT_IN_RESET; + + /* BAAW_P_WLBT */ + platform->baaw_p_wlbt = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,baaw_p_wlbt-syscon-phandle"); + if (IS_ERR(platform->baaw_p_wlbt)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "baaw_p_wlbt regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->baaw_p_wlbt)); + err = -EINVAL; + goto error_exit; + } + + /* DBUS_BAAW */ + platform->dbus_baaw = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,dbus_baaw-syscon-phandle"); + if (IS_ERR(platform->dbus_baaw)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "dbus_baaw regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->dbus_baaw)); + err = -EINVAL; + goto error_exit; + } + + /* PBUS_BAAW */ + platform->pbus_baaw = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,pbus_baaw-syscon-phandle"); + if (IS_ERR(platform->pbus_baaw)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "pbus_baaw regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->pbus_baaw)); + err = -EINVAL; + goto error_exit; + } + + /* WLBT_REMAP */ + platform->wlbt_remap = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,wlbt_remap-syscon-phandle"); + if (IS_ERR(platform->wlbt_remap)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "wlbt_remap regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->wlbt_remap)); + err = -EINVAL; + goto error_exit; + } + + /* BOOT_CFG */ + platform->boot_cfg = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,boot_cfg-syscon-phandle"); + if (IS_ERR(platform->boot_cfg)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "boot_cfg regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->boot_cfg)); + err = -EINVAL; + goto error_exit; + } +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER parsing */ + if (!of_property_read_u32(pdev->dev.of_node, "smapper_num_banks", &smapper_banks)) + platform_mif_parse_smapper(platform, platform->dev->of_node, smapper_banks); + +#endif +#ifdef CONFIG_SCSC_QOS + platform_mif_parse_qos(platform, platform->dev->of_node); +#endif +#ifdef CONFIG_SCSC_CLK20MHZ + /* Assign USBPLL ownership to WLBT f/w */ + __platform_mif_usbpll_claim(platform, true); +#endif + + /* Initialize spinlock */ + spin_lock_init(&platform->mif_spinlock); + +#ifndef CONFIG_SOC_EXYNOS9610 + /* Clear WIFI_ACTIVE flag in WAKEUP_STAT */ + err = regmap_update_bits(platform->pmureg, WIFI_CTRL_NS, WIFI_ACTIVE_CLR, 1); + if (err < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WIFI_CTRL_NS[WIFI_ACTIVE_CLR]: %d\n", err); + } +#endif + return platform_if; + +error_exit: + devm_kfree(&pdev->dev, platform); + return NULL; +} + +void platform_mif_destroy_platform(struct platform_device *pdev, struct scsc_mif_abs *interface) +{ +#ifdef CONFIG_SCSC_CLK20MHZ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + /* Assign USBPLL ownership to AP */ + __platform_mif_usbpll_claim(platform, false); +#endif +} + +struct platform_device *platform_mif_get_platform_dev(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + BUG_ON(!interface || !platform); + + return platform->pdev; +} + +struct device *platform_mif_get_dev(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + BUG_ON(!interface || !platform); + + return platform->dev; +} + +/* Preserve MIF registers during suspend. + * If all users of the MIF (AP, mx140, CP, etc) release it, the registers + * will lose their values. Save the useful subset here. + * + * Assumption: the AP will not change the register values between the suspend + * and resume handlers being called! + */ +static void platform_mif_reg_save(struct platform_mif *platform) +{ + platform->mif_preserve.irq_bit_mask = __platform_mif_irq_bit_mask_read(platform); +} + +/* Restore MIF registers that may have been lost during suspend */ +static void platform_mif_reg_restore(struct platform_mif *platform) +{ + __platform_mif_irq_bit_mask_write(platform, platform->mif_preserve.irq_bit_mask); +} + +int platform_mif_suspend(struct scsc_mif_abs *interface) +{ + int r = 0; + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (platform->suspend_handler) + r = platform->suspend_handler(interface, platform->suspendresume_data); + + /* Save the MIF registers. + * This must be done last as the suspend_handler may use the MIF + */ + platform_mif_reg_save(platform); + + return r; +} + +void platform_mif_resume(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + s32 ret; + + /* Restore the MIF registers. + * This must be done first as the resume_handler may use the MIF. + */ + platform_mif_reg_restore(platform); +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clear WLBT_ACTIVE_CLR flag\n"); + /* Clear WLBT_ACTIVE_CLR flag in WLBT_CTRL_NS */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, WLBT_ACTIVE_CLR, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WLBT_CTRL_NS[WLBT_ACTIVE_CLR]: %d\n", ret); + } +#else + /* Clear WIFI_ACTIVE flag in WAKEUP_STAT */ + ret = regmap_update_bits(platform->pmureg, WIFI_CTRL_NS, WIFI_ACTIVE_CLR, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WIFI_CTRL_NS[WIFI_ACTIVE_CLR]: %d\n", ret); + } +#endif + if (platform->resume_handler) + platform->resume_handler(interface, platform->suspendresume_data); +} diff --git a/drivers/misc/samsung/scsc/platform_mif.h b/drivers/misc/samsung/scsc/platform_mif.h new file mode 100644 index 000000000000..b145cec280a8 --- /dev/null +++ b/drivers/misc/samsung/scsc/platform_mif.h @@ -0,0 +1,31 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __PLATFORM_MIF_H +#define __PLATFORM_MIF_H +#include "scsc_mif_abs.h" + +enum wlbt_irqs { + PLATFORM_MIF_MBOX, + PLATFORM_MIF_ALIVE, + PLATFORM_MIF_WDOG, +#if defined(CONFIG_SOC_EXYNOS9610) || defined(CONFIG_SOC_EXYNOS9630) + PLATFORM_MIF_CFG_REQ, +#endif + /* must be last */ + PLATFORM_MIF_NUM_IRQS +}; + +struct platform_device; + +struct scsc_mif_abs *platform_mif_create(struct platform_device *pdev); +void platform_mif_destroy_platform(struct platform_device *pdev, struct scsc_mif_abs *interface); +struct platform_device *platform_mif_get_platform_dev(struct scsc_mif_abs *interface); +struct device *platform_mif_get_dev(struct scsc_mif_abs *interface); +int platform_mif_suspend(struct scsc_mif_abs *interface); +void platform_mif_resume(struct scsc_mif_abs *interface); + +#endif diff --git a/drivers/misc/samsung/scsc/platform_mif_9610.c b/drivers/misc/samsung/scsc/platform_mif_9610.c new file mode 100644 index 000000000000..ff37b6706222 --- /dev/null +++ b/drivers/misc/samsung/scsc/platform_mif_9610.c @@ -0,0 +1,1956 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Implements interface */ + +#include "platform_mif.h" + +/* Interfaces it Uses */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#include +#endif +#include +#include +#include +#include +#include "mif_reg_S5E9610.h" +#include "platform_mif_module.h" +#ifdef CONFIG_ARCH_EXYNOS +#include +#endif + +#ifdef CONFIG_SCSC_SMAPPER +#include +#include "mif_reg_smapper.h" +#endif +#ifdef CONFIG_SCSC_QOS +#include +#endif + +#if !defined(CONFIG_SOC_EXYNOS9610) +#error Target processor CONFIG_SOC_EXYNOS9610 not selected +#endif + +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif +/* Time to wait for CFG_REQ IRQ on 9610 */ +#define WLBT_BOOT_TIMEOUT (HZ) + +#ifdef CONFIG_OF_RESERVED_MEM +#include +#endif +static unsigned long sharedmem_base; +static size_t sharedmem_size; + +#ifdef CONFIG_SCSC_CHV_SUPPORT +static bool chv_disable_irq; +module_param(chv_disable_irq, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(chv_disable_irq, "Do not register for irq"); +#endif + +static bool enable_platform_mif_arm_reset = true; +module_param(enable_platform_mif_arm_reset, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_platform_mif_arm_reset, "Enables WIFIBT ARM cores reset"); + +#ifdef CONFIG_SCSC_QOS +struct qos_table { + unsigned int freq_mif; + unsigned int freq_int; + unsigned int freq_cl0; + unsigned int freq_cl1; +}; +#endif + +struct platform_mif { + struct scsc_mif_abs interface; + struct scsc_mbox_s *mbox; + struct platform_device *pdev; + + struct device *dev; + + struct { + int irq_num; + int flags; + atomic_t irq_disabled_cnt; + } wlbt_irq[PLATFORM_MIF_NUM_IRQS]; + + /* MIF registers preserved during suspend */ + struct { + u32 irq_bit_mask; + } mif_preserve; + + /* register MBOX memory space */ + size_t reg_start; + size_t reg_size; + void __iomem *base; + + /* register CMU memory space */ + struct regmap *cmu_base; + + void __iomem *con0_base; + + /* pmu syscon regmap */ + struct regmap *pmureg; +#if defined(CONFIG_SOC_EXYNOS9610) + struct regmap *baaw_p_wlbt; + struct regmap *dbus_baaw; + struct regmap *pbus_baaw; + struct regmap *wlbt_remap; + struct regmap *boot_cfg; + + /* Signalled when CFG_REQ IRQ handled */ + struct completion cfg_ack; + + /* State of CFG_REQ handler */ + enum wlbt_boot_state { + WLBT_BOOT_IN_RESET = 0, + WLBT_BOOT_WAIT_CFG_REQ, + WLBT_BOOT_CFG_DONE, + WLBT_BOOT_CFG_ERROR + } boot_state; + +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER */ + void __iomem *smapper_base; + u8 smapper_banks; + struct { + u8 bank; + u32 ws; + bool large; + struct scsc_mif_smapper_info bank_info; + } *smapper; +#endif + /* Shared memory space - reserved memory */ + unsigned long mem_start; + size_t mem_size; + void __iomem *mem; + + /* Callback function and dev pointer mif_intr manager handler */ + void (*r4_handler)(int irq, void *data); + void *irq_dev; + /* spinlock to serialize driver access */ + spinlock_t mif_spinlock; + void (*reset_request_handler)(int irq, void *data); + void *irq_reset_request_dev; + +#ifdef CONFIG_SCSC_QOS + /* QoS table */ + struct qos_table *qos; + bool qos_enabled; +#endif + /* Suspend/resume handlers */ + int (*suspend_handler)(struct scsc_mif_abs *abs, void *data); + void (*resume_handler)(struct scsc_mif_abs *abs, void *data); + void *suspendresume_data; +}; + +extern int mx140_log_dump(void); + +#define platform_mif_from_mif_abs(MIF_ABS_PTR) container_of(MIF_ABS_PTR, struct platform_mif, interface) + +inline void platform_mif_reg_write(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->base + offset); +} + +inline u32 platform_mif_reg_read(struct platform_mif *platform, u16 offset) +{ + return readl(platform->base + offset); +} + +#ifdef CONFIG_SCSC_SMAPPER +inline void platform_mif_reg_write_smapper(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->smapper_base + offset); +} + +inline u32 platform_mif_reg_read_smapper(struct platform_mif *platform, u16 offset) +{ + return readl(platform->smapper_base + offset); +} + +#define PLATFORM_MIF_SHIFT_SMAPPER_ADDR 11 /* From 36 bits addres to 25 bits */ +#define PLATFORM_MIF_SHIFT_SMAPPER_END 4 /* End address aligment */ + +/* Platform is responsible to give the phys mapping of the SMAPPER maps */ +static int platform_mif_smapper_get_mapping(struct scsc_mif_abs *interface, u8 *phy_map, u16 *align) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Mapping %d banks\n", platform->smapper_banks); + + if (!platform->smapper_banks) + return -EINVAL; + + for (i = 0; i < platform->smapper_banks; i++) { + if (platform->smapper[i].large) + phy_map[i] = SCSC_MIF_ABS_LARGE_BANK; + else + phy_map[i] = SCSC_MIF_ABS_SMALL_BANK; + } + + if (align) + *align = 1 << PLATFORM_MIF_SHIFT_SMAPPER_ADDR; + + return 0; +} + +static int platform_mif_smapper_get_bank_info(struct scsc_mif_abs *interface, u8 bank, struct scsc_mif_smapper_info *bank_info) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform->smapper_banks) + return -EINVAL; + + bank_info->num_entries = platform->smapper[bank].bank_info.num_entries; + bank_info->mem_range_bytes = platform->smapper[bank].bank_info.mem_range_bytes; + + return 0; +} + +static u8 platform_mif_smapper_granularity_to_bits(u32 granularity) +{ + if (granularity <= 2 * 1024) + return 0; + if (granularity <= 4 * 1024) + return 1; + if (granularity <= 8 * 1024) + return 2; + if (granularity <= 16 * 1024) + return 3; + if (granularity <= 32 * 1024) + return 4; + if (granularity <= 64 * 1024) + return 5; + if (granularity <= 128 * 1024) + return 6; + return 7; +} + +static u32 platform_mif_smapper_get_bank_base_address(struct scsc_mif_abs *interface, u8 bank) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform->smapper) + return 0; + + return platform->smapper[bank].ws; +} + +/* Configure smapper according the memory map and range */ +static void platform_mif_smapper_configure(struct scsc_mif_abs *interface, u32 granularity) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + u8 gran; + u8 nb = platform->smapper_banks; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Configure SMAPPER with granularity %d\n", granularity); + + gran = platform_mif_smapper_granularity_to_bits(granularity); + + platform_mif_reg_write_smapper(platform, SMAPPER_QCH_DISABLE, 1); + platform_mif_reg_write_smapper(platform, ORIGIN_ADDR_AR, 0); + platform_mif_reg_write_smapper(platform, ORIGIN_ADDR_AW, 0); + /* Program SMAPPER memmap */ + for (i = 0; i < nb; i++) { + /* Set ADDR_MAP_EN to 1'b0*/ + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(i), 0); + /* Set START_ADDR */ + platform_mif_reg_write_smapper(platform, START_ADDR(i), platform->smapper[i].ws); + /* Set ADDR_GRANULARITY - FIXED AT 4KB */ + platform_mif_reg_write_smapper(platform, ADDR_GRANULARITY(i), gran); + /* WLAN_ADDR_MAP operation is started */ + } + /* Set access window control (MSB 32bits Start/End address) */ + /* Remapped address should be ranged from AW_START_ADDR to AW_EN_ADDR */ + platform_mif_reg_write_smapper(platform, AW_START_ADDR, 0); + platform_mif_reg_write_smapper(platform, AW_END_ADDR, dma_get_mask(platform->dev) >> PLATFORM_MIF_SHIFT_SMAPPER_END); + smp_mb(); +} + +/* Caller is responsible of validating the phys address (alignment) */ +static int platform_mif_smapper_write_sram(struct scsc_mif_abs *interface, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + u32 rb; + + if (!platform->smapper_banks) + return -EINVAL; + + if (!platform->smapper_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "SMAPPER not enabled\n"); + return -EINVAL; + } + + /* Set ADDR_MAP_EN to 1'b0*/ + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(bank), 0); + /* Write mapping table to SRAM. Each entry consists of 25 bits MSB address to remap */ + for (i = 0; i < num_entries; i++) { + if (!addr[i]) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "ADDR IS NULL at bank %d entry %d/%d\n", bank, first_entry + i, num_entries); + return -EINVAL; + } + /* Set SRAM_WRITE_CTRL to 1'b1*/ + platform_mif_reg_write_smapper(platform, SRAM_WRITE_CTRL(bank), 1); + platform_mif_reg_write_smapper(platform, SRAM_BANK_INDEX(bank, first_entry + i), addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR); + /* check incorrect writings */ + platform_mif_reg_write_smapper(platform, SRAM_WRITE_CTRL(bank), 0); + rb = platform_mif_reg_read_smapper(platform, SRAM_BANK_INDEX(bank, first_entry + i)); + if (rb != addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "incorrect mapping detected rb 0x%x, addr 0x%x\n", rb, (u32)addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR); + return -EFAULT; + } + } + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(bank), 1); + smp_mb(); + return 0; +} + +static int platform_mif_parse_smapper(struct platform_mif *platform, struct device_node *np, u8 num_banks) +{ + /* SMAPPER parsing */ + struct device_node *np_banks; + char node_name[50]; + u32 val[2]; + u8 i; + u32 bank = 0, ws = 0, wsz = 0, ent = 0, large = 0; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "banks found: %d szof %zu\n", num_banks, sizeof(*platform->smapper)); + + platform->smapper = kmalloc_array(num_banks, sizeof(*platform->smapper), GFP_KERNEL); + + if (!platform->smapper) + return -ENOMEM; + + for (i = 0; i < num_banks; i++) { + snprintf(node_name, sizeof(node_name), "smapper_bank_%d", i); + np_banks = of_find_node_by_name(np, node_name); + if (!np_banks) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "%s: could not find smapper_bank\n", + node_name); + kfree(platform->smapper); + platform->smapper = NULL; + return -ENOENT; + } + of_property_read_u32(np_banks, "bank_num", &bank); + of_property_read_u32(np_banks, "fw_window_start", &ws); + of_property_read_u32(np_banks, "fw_window_size", &wsz); + of_property_read_u32(np_banks, "num_entries", &ent); + of_property_read_u32(np_banks, "is_large", &large); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "bank %d fw_w_start 0x%x fw_w_sz 0x%x entries %d is_large %d\n", + bank, ws, wsz, ent, large); + + platform->smapper[i].bank = (u8)bank; + platform->smapper[i].ws = ws; + platform->smapper[i].large = (bool)large; + platform->smapper[i].bank_info.num_entries = ent; + platform->smapper[i].bank_info.mem_range_bytes = wsz; + } + + /* Update the number of banks before returning */ + platform->smapper_banks = num_banks; + + of_property_read_u32_array(np, "smapper_reg", val, 2); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "smapper reg address 0x%x size 0x%x\n", val[0], val[1]); + platform->smapper_base = + devm_ioremap_nocache(platform->dev, val[0], val[1]); + + if (!platform->smapper_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping smapper register region\n"); + kfree(platform->smapper); + platform->smapper = NULL; + return -ENOENT; + } + + return 0; +} +#endif +#ifdef CONFIG_SCSC_QOS +static int platform_mif_parse_qos(struct platform_mif *platform, struct device_node *np) +{ + int len, i; + + platform->qos_enabled = false; + + len = of_property_count_u32_elems(np, "qos_table"); + if (!(len == 12)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "No qos table for wlbt, or incorrect size\n"); + return -ENOENT; + } + + platform->qos = devm_kzalloc(platform->dev, sizeof(struct qos_table) * len / 4, GFP_KERNEL); + if (!platform->qos) + return -ENOMEM; + + of_property_read_u32_array(np, "qos_table", (unsigned int *)platform->qos, len); + + for (i = 0; i < len / 4; i++) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "QoS Table[%d] mif : %u int : %u cl0 : %u cl1: %u\n", i, + platform->qos[i].freq_mif, + platform->qos[i].freq_int, + platform->qos[i].freq_cl0, + platform->qos[i].freq_cl1); + } + + platform->qos_enabled = true; + return 0; +} + +struct qos_table platform_mif_pm_qos_get_table(struct platform_mif *platform, enum scsc_qos_config config) +{ + struct qos_table table; + + switch (config) { + case SCSC_QOS_MIN: + table.freq_mif = platform->qos[0].freq_mif; + table.freq_int = platform->qos[0].freq_int; + table.freq_cl0 = platform->qos[0].freq_cl0; + table.freq_cl1 = platform->qos[0].freq_cl1; + break; + + case SCSC_QOS_MED: + table.freq_mif = platform->qos[1].freq_mif; + table.freq_int = platform->qos[1].freq_int; + table.freq_cl0 = platform->qos[1].freq_cl0; + table.freq_cl1 = platform->qos[1].freq_cl1; + break; + + case SCSC_QOS_MAX: + table.freq_mif = platform->qos[2].freq_mif; + table.freq_int = platform->qos[2].freq_int; + table.freq_cl0 = platform->qos[2].freq_cl0; + table.freq_cl1 = platform->qos[2].freq_cl1; + break; + + default: + table.freq_mif = 0; + table.freq_int = 0; + table.freq_cl0 = 0; + table.freq_cl1 = 0; + } + + return table; +} + +static int platform_mif_pm_qos_add_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + struct qos_table table; + + if (!platform) + return -ENODEV; + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + table = platform_mif_pm_qos_get_table(platform, config); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "PM QoS add request: %u. MIF %u INT %u CL0 %u CL1 %u\n", config, table.freq_mif, table.freq_int, table.freq_cl0, table.freq_cl1); + + pm_qos_add_request(&qos_req->pm_qos_req_mif, PM_QOS_BUS_THROUGHPUT, table.freq_mif); + pm_qos_add_request(&qos_req->pm_qos_req_int, PM_QOS_DEVICE_THROUGHPUT, table.freq_int); + pm_qos_add_request(&qos_req->pm_qos_req_cl0, PM_QOS_CLUSTER0_FREQ_MIN, table.freq_cl0); + pm_qos_add_request(&qos_req->pm_qos_req_cl1, PM_QOS_CLUSTER1_FREQ_MIN, table.freq_cl1); + + return 0; +} + +static int platform_mif_pm_qos_update_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + struct qos_table table; + + if (!platform) + return -ENODEV; + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + table = platform_mif_pm_qos_get_table(platform, config); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "PM QoS update request: %u. MIF %u INT %u CL0 %u CL1 %u\n", config, table.freq_mif, table.freq_int, table.freq_cl0, table.freq_cl1); + + pm_qos_update_request(&qos_req->pm_qos_req_mif, table.freq_mif); + pm_qos_update_request(&qos_req->pm_qos_req_int, table.freq_int); + pm_qos_update_request(&qos_req->pm_qos_req_cl0, table.freq_cl0); + pm_qos_update_request(&qos_req->pm_qos_req_cl1, table.freq_cl1); + + return 0; +} + +static int platform_mif_pm_qos_remove_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform) + return -ENODEV; + + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS remove request\n"); + pm_qos_remove_request(&qos_req->pm_qos_req_mif); + pm_qos_remove_request(&qos_req->pm_qos_req_int); + pm_qos_remove_request(&qos_req->pm_qos_req_cl0); + pm_qos_remove_request(&qos_req->pm_qos_req_cl1); + + return 0; +} +#endif + +static void platform_mif_irq_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; + + /* int handler not registered */ + SCSC_TAG_INFO_DEV(PLAT_MIF, NULL, "INT handler not registered\n"); +} + +static void platform_mif_irq_reset_request_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; + + /* int handler not registered */ + SCSC_TAG_INFO_DEV(PLAT_MIF, NULL, "INT reset_request handler not registered\n"); +} + +irqreturn_t platform_mif_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "INT %pS\n", platform->r4_handler); + if (platform->r4_handler != platform_mif_irq_default_handler) + platform->r4_handler(irq, platform->irq_dev); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "MIF Interrupt Handler not registered\n"); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ +irqreturn_t platform_alive_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + + return IRQ_HANDLED; +} +#endif + +irqreturn_t platform_wdog_isr(int irq, void *data) +{ + int ret = 0; + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + if (platform->reset_request_handler != platform_mif_irq_reset_request_default_handler) { + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + platform->reset_request_handler(irq, platform->irq_reset_request_dev); + } else { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WDOG Interrupt reset_request_handler not registered\n"); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Disabling unhandled WDOG IRQ.\n"); + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + atomic_inc(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt); + } +#ifdef CONFIG_SOC_EXYNOS9610 + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_RESET_REQ_CLR, WLBT_RESET_REQ_CLR); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clearing WLBT_RESET_REQ\n"); + if (ret < 0) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WLBT_CTRL_NS[WLBT_RESET_REQ_CLR]: %d\n", ret); +#endif + return IRQ_HANDLED; +} + +#ifdef CONFIG_SOC_EXYNOS9610 +/* + * Attached array contains the replacement PMU boot code which should + * be programmed using the CBUS during the config phase. + */ +uint32_t ka_patch[] = { + /* Low temp fix 28/1 + * Maxwell142 PMU+PROC combined boot ROM + * IP Version: 0xA3 + * Major Version: 0xF, Minor Version: 0xF + * PMU ROM version: 0x4 + * PROC ROM version: 0x0 + */ + 0x90750002, + 0x11a4c218, + 0x75671191, + 0x9075e090, + 0x54b3e5e7, + 0x30f76001, + 0xb14315a2, + 0xb4b2e503, + 0xb153fb03, + 0xa90185fc, + 0xacf50774, + 0x75fdadb5, + 0xb47508a0, + 0x54b3e501, + 0x75fa7001, + 0x907500b4, + 0x78cb8018, + 0x80837982, + 0x07a075c5, + 0xb0783779, + 0xb40754e6, + 0x0b800207, + 0xc404f6d9, + 0xaf7590f5, + 0x75038000, + 0x53229090, + 0xce53eff7, + 0xd90479fe, + 0xfdce53fe, + 0xfed90c79, + 0x75fbce53, + 0x91530b92, + 0xf7ce53fd, + 0x5308f943, + 0xf922fef9, + 0xfbd8fed9, + 0x019e7522, + 0x75cfc175, + 0xc375a4c2, + 0x47c4754a, + 0x75a4c575, + 0xc7756dc6, + 0x03d27540, + 0x7510d375, + 0xca7500c9, + 0x00cb75d0, + 0x7500cc75, + 0x9b75009a, + 0x009c75c0, + 0x78009d75, + 0x12827402, + 0xc6438b80, + 0x74097802, + 0x8b8012e7, + 0x75d09075, + 0x9e750291, + 0x01a97502, + 0x00000022, +}; + +extern bool reset_failed; + +irqreturn_t platform_cfg_req_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + const u64 EXYNOS_WLBT = 0x1; + u64 ret64 = 0; + s32 ret = 0; + unsigned int ka_addr = 0x1000; + uint32_t *ka_patch_addr = ka_patch; + u32 id; + +#define CHECK(x) do { \ + int retval = (x); \ + if (retval < 0) \ + goto cfg_error; \ +} while (0) + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "disable_irq\n"); + + /* mask the irq */ + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + /* Was the CFG_REQ irq received from WLBT before we expected it? + * Typically this indicates an issue returning WLBT HW to reset. + */ + if (platform->boot_state != WLBT_BOOT_WAIT_CFG_REQ) { + u32 val; + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Spurious CFG_REQ IRQ from WLBT!\n"); + + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_NS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CTRL_S, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_S 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + reset_failed = true; /* prevent further interaction with HW */ + + return IRQ_HANDLED; + } + + /* CBUS should be ready before we get CFG_REQ, but we suspect + * CBUS is not ready yet. add some delay to see if that helps + */ + udelay(100); + + /* Set TZPC to non-secure mode */ + ret64 = exynos_smc(SMC_CMD_CONN_IF, (EXYNOS_WLBT << 32) | EXYNOS_SET_CONN_TZPC, 0, 0); + if (ret64) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to set TZPC to non-secure mode: %llu\n", ret64); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SMC_CMD_CONN_IF run successfully : %llu\n", ret64); + + /* WLBT_REMAP */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP begin\n"); + CHECK(regmap_write(platform->wlbt_remap, 0x0, WLBT_DBUS_BAAW_0_START >> 12)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP end\n"); + + /* CHIP_VERSION_ID - overload with EMA settings */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "CHIP_VERSION_ID begin\n"); + regmap_read(platform->wlbt_remap, 0x10, &id); + id &= ~CHIP_VERSION_ID_EMA_MASK; + id |= CHIP_VERSION_ID_EMA_VALUE; + CHECK(regmap_write(platform->wlbt_remap, 0x10, id)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "CHIP_VERSION_ID 0x%x end\n", id); + + /* DBUS_BAAW regions */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "DBUS_BAAW begin\n"); + CHECK(regmap_write(platform->dbus_baaw, 0x0, WLBT_DBUS_BAAW_0_START >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0x4, WLBT_DBUS_BAAW_0_END >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0x8, platform->mem_start >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0xC, WLBT_BAAW_ACCESS_CTRL)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "DBUS_BAAW end\n"); + + /* PBUS_BAAW regions */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "PBUS_BAAW begin\n"); + CHECK(regmap_write(platform->pbus_baaw, 0x0, WLBT_PBUS_BAAW_0_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x4, WLBT_PBUS_BAAW_0_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x8, WLBT_PBUS_MBOX_CP2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0xC, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x10, WLBT_PBUS_BAAW_1_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x14, WLBT_PBUS_BAAW_1_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x18, WLBT_PBUS_MBOX_SHUB2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x1C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x20, WLBT_PBUS_BAAW_2_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x24, WLBT_PBUS_BAAW_2_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x28, WLBT_PBUS_USI_CMG00_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x2C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x30, WLBT_PBUS_BAAW_3_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x34, WLBT_PBUS_BAAW_3_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x38, WLBT_PBUS_SYSREG_CMGP2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x3C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x40, WLBT_PBUS_BAAW_4_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x44, WLBT_PBUS_BAAW_4_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x48, WLBT_PBUS_GPIO_CMGP_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x4C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x50, WLBT_PBUS_BAAW_5_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x54, WLBT_PBUS_BAAW_5_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x58, WLBT_PBUS_SHUB_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x5C, WLBT_BAAW_ACCESS_CTRL)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "PBUS_BAAW end\n"); + + /* PMU boot bug workaround */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "BOOT_WLBT begin\n"); + CHECK(regmap_write(platform->boot_cfg, 0x0, 0x1)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "BOOT_WLBT done\n"); + + while (ka_patch_addr < (ka_patch + ARRAY_SIZE(ka_patch))) { + CHECK(regmap_write(platform->boot_cfg, ka_addr, *ka_patch_addr)); + ka_addr += sizeof(ka_patch[0]); + ka_patch_addr++; + } + + /* Notify PMU of configuration done */ + CHECK(regmap_write(platform->boot_cfg, 0x0, 0x0)); + + /* BOOT_CFG_ACK */ + CHECK(regmap_write(platform->boot_cfg, 0x4, 0x1)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BOOT_CFG_ACK done\n"); + + /* Delay to allow HW to clear CFG_REQ and hence de-assert IRQ, which + * it does in response to CFG_ACK + */ + udelay(100); + + /* Release ownership of MASK_PWR_REQ */ + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to clear WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + goto cfg_error; + } + + /* Mark as CFQ_REQ handled, so boot may continue */ + platform->boot_state = WLBT_BOOT_CFG_DONE; + + /* Signal triggering function that the IRQ arrived and CFG was done */ + complete(&platform->cfg_ack); + + /* Re-enable IRQ here to allow spurious interrupt to be tracked */ + enable_irq(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + return IRQ_HANDLED; +cfg_error: + platform->boot_state = WLBT_BOOT_CFG_ERROR; + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "ERROR: WLBT Config failed. WLBT will not work\n"); + complete(&platform->cfg_ack); + return IRQ_HANDLED; +} +#endif + +static void platform_mif_unregister_irq(struct platform_mif *platform) +{ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering IRQs\n"); + + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform); + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform); + /* Reset irq_disabled_cnt for WDOG IRQ since the IRQ itself is here unregistered and disabled */ + atomic_set(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt, 0); +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ + /* if ALIVE irq is required */ + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform); +#endif +#ifdef CONFIG_SOC_EXYNOS9610 + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform); +#endif +} + +static int platform_mif_register_irq(struct platform_mif *platform) +{ + int err; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering IRQs\n"); + + /* Register MBOX irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering MBOX irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform->wlbt_irq[PLATFORM_MIF_MBOX].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform_mif_isr, + platform->wlbt_irq[PLATFORM_MIF_MBOX].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register MBOX handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + + /* Register WDOG irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering WDOG irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform->wlbt_irq[PLATFORM_MIF_WDOG].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform_wdog_isr, + platform->wlbt_irq[PLATFORM_MIF_WDOG].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register WDOG handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ + /* Register ALIVE irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering ALIVE irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform->wlbt_irq[PLATFORM_MIF_ALIVE].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform_alive_isr, + platform->wlbt_irq[PLATFORM_MIF_ALIVE].flags, DRV_NAME, platform); + if (IS_ERR_VALUE(err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register ALIVE handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } +#endif + +#ifdef CONFIG_SOC_EXYNOS9610 + /* Mark as WLBT in reset before enabling IRQ to guard against spurious IRQ */ + platform->boot_state = WLBT_BOOT_IN_RESET; + smp_wmb(); /* commit before irq */ + + /* Register WB2AP_CFG_REQ irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering CFG_REQ irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform_cfg_req_isr, + platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register CFG_REQ handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } +#endif + return 0; +} + +static void platform_mif_destroy(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + platform_mif_unregister_irq(platform); +} + +static char *platform_mif_get_uid(struct scsc_mif_abs *interface) +{ + /* Avoid unused parameter error */ + (void)interface; + return "0"; +} + +/* WLBT Power domain */ +static int platform_mif_power(struct scsc_mif_abs *interface, bool power) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val = 0; + s32 ret = 0; +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "power %d\n", power); + + if (power) + val = MASK_PWR_REQ; + + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + return ret; + } +#endif + return 0; +} + +/* WLBT RESET */ +static int platform_mif_hold_reset(struct scsc_mif_abs *interface, bool reset) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val = 0; + s32 ret = 0; +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "reset %d\n", reset); + if (reset) + val = WLBT_RESET_SET; + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_RESET_SET, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[WLBT_RESET_SET]: %d\n", ret); + return ret; + } +#endif + return 0; +} + +/* WLBT START */ +static int platform_mif_start(struct scsc_mif_abs *interface, bool start) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val = 0; + s32 ret = 0; + +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "start %d\n", start); + if (start) + val = WLBT_START; + + /* See sequence in 9.6.6 */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_S, + WLBT_START, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_S[WLBT_START]: %d\n", ret); + return ret; + } + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "update WIFI_CTRL_S[WIFI_START]: %d\n", ret); + + /* At this point WLBT should assert the CFG_REQ IRQ, so wait for it */ + if (start && + wait_for_completion_timeout(&platform->cfg_ack, WLBT_BOOT_TIMEOUT) == 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Timeout waiting for CFG_REQ IRQ\n"); + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + return -ETIMEDOUT; + } + /* only continue if CFG_REQ IRQ configured WLBT/PMU correctly */ + if (platform->boot_state == WLBT_BOOT_CFG_ERROR) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "CFG_REQ failed to configure WLBT.\n"); + return -EIO; + } +#endif + return 0; +} + +static int platform_mif_pmu_reset_release(struct scsc_mif_abs *interface) +{ + int ret = 0; + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + +#ifdef CONFIG_SOC_EXYNOS9610 + /* We're now ready for the IRQ */ + platform->boot_state = WLBT_BOOT_WAIT_CFG_REQ; + smp_wmb(); /* commit before irq */ +#endif + ret = platform_mif_power(interface, true); + if (ret) + return ret; + ret = platform_mif_hold_reset(interface, false); + if (ret) + return ret; + ret = platform_mif_start(interface, true); + if (ret) + return ret; + + return ret; +} + +static int platform_mif_pmu_reset(struct scsc_mif_abs *interface, u8 rst_case) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long timeout; + int ret; + u32 val; + + if (rst_case == 0 || rst_case > 2) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Incorrect pmu reset case %d\n", rst_case); + return -EIO; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "rst_case %d\n", rst_case); + + /* Revert power control ownership to AP, as WLBT is going down (S9.6.6). */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, MASK_PWR_REQ); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + return ret; + } + + /* reset sequence as per excite implementation for Leman */ + ret = regmap_update_bits(platform->pmureg, CENTRAL_SEQ_WLBT_CONFIGURATION, + SYS_PWR_CFG_16, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update CENTRAL_SEQ_WLBT_CONFIGURATION %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, RESET_AHEAD_WLBT_SYS_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update RESET_AHEAD_WLBT_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, CLEANY_BUS_WLBT_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update CLEANY_BUS_WLBT_SYS_PWR_REG%d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, LOGIC_RESET_WLBT_SYS_PWR_REG, + SYS_PWR_CFG_2, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update LOGIC_RESET_WLBT_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, TCXO_GATE_WLBT_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update TCXO_GATE_WLBT_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, WLBT_DISABLE_ISO_SYS_PWR_REG, + SYS_PWR_CFG, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_DISABLE_ISO_SYS_PWR_REG %d\n", ret); + return ret; + } + + ret = regmap_update_bits(platform->pmureg, WLBT_RESET_ISO_SYS_PWR_REG, + SYS_PWR_CFG, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_RESET_ISO_SYS_PWR_REG %d\n", ret); + return ret; + } + + /* rst_case is always 2 on 9610 */ + ret = platform_mif_hold_reset(interface, true); + + if (ret) + return ret; + + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + val &= STATES; + val >>= 16; + if (val == 0x80) { + /* OK. Switch CTRL_NS[MASK_PWR_REQ] ownership to FW following + * reset. WLBT PWR_REQ is cleared when it's put in reset. + * The SW PWR_REQ remains asserted, but as ownership is now FW, + * it'll be ignored. This leaves it as we found it. + */ + platform_mif_power(interface, false); + + return 0; /* OK - return */ + } + } while (time_before(jiffies, timeout)); + + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Timeout waiting for CENTRAL_SEQ_WLBT_STATUS SM status\n"); + regmap_read(platform->pmureg, CENTRAL_SEQ_WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "CENTRAL_SEQ_WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + return -ETIME; +} + +/* reset=0 - release from reset */ +/* reset=1 - hold reset */ +static int platform_mif_reset(struct scsc_mif_abs *interface, bool reset) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 ret = 0; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (enable_platform_mif_arm_reset || !reset) { + if (!reset) { /* Release from reset */ +#ifdef CONFIG_ARCH_EXYNOS + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SOC_VERSION: product_id 0x%x, rev 0x%x\n", + exynos_soc_info.product_id, exynos_soc_info.revision); +#endif + ret = platform_mif_pmu_reset_release(interface); + } else { + /* Put back into reset */ + ret = platform_mif_pmu_reset(interface, 2); + } + } else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Not resetting ARM Cores - enable_platform_mif_arm_reset: %d\n", + enable_platform_mif_arm_reset); + return ret; +} + +static void __iomem *platform_mif_map_region(unsigned long phys_addr, size_t size) +{ + int i; + struct page **pages; + void *vmem; + + size = PAGE_ALIGN(size); + + pages = kmalloc((size >> PAGE_SHIFT) * sizeof(*pages), GFP_KERNEL); + if (!pages) + return NULL; + + /* Map NORMAL_NC pages with kernel virtual space */ + for (i = 0; i < (size >> PAGE_SHIFT); i++) { + pages[i] = phys_to_page(phys_addr); + phys_addr += PAGE_SIZE; + } + + vmem = vmap(pages, size >> PAGE_SHIFT, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + + kfree(pages); + return (void __iomem *)vmem; +} + +static void platform_mif_unmap_region(void *vmem) +{ + vunmap(vmem); +} + +static void *platform_mif_map(struct scsc_mif_abs *interface, size_t *allocated) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + + if (allocated) + *allocated = 0; + + platform->mem = + platform_mif_map_region(platform->mem_start, platform->mem_size); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Error remaping shared memory\n"); + return NULL; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Map: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + + /* Initialise MIF registers with documented defaults */ + /* MBOXes */ + for (i = 0; i < NUM_MBOX_PLAT; i++) + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(ISSR(i)), 0x00000000); + + /* MRs */ /*1's - set all as Masked */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); + /* CRs */ /* 1's - clear all the interrupts */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); + +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_disable_irq == true) { + if (allocated) + *allocated = platform->mem_size; + return platform->mem; + } +#endif + /* register interrupts */ + if (platform_mif_register_irq(platform)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unmap: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + platform_mif_unmap_region(platform->mem); + return NULL; + } + + if (allocated) + *allocated = platform->mem_size; + /* Set the CR4 base address in Mailbox??*/ + return platform->mem; +} + +/* HERE: Not sure why mem is passed in - its stored in platform - as it should be */ +static void platform_mif_unmap(struct scsc_mif_abs *interface, void *mem) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + /* Avoid unused parameter error */ + (void)mem; + + /* MRs */ /*1's - set all as Masked */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); + +#ifdef CONFIG_SCSC_CHV_SUPPORT + /* Restore PIO changed by Maxwell subsystem */ + if (chv_disable_irq == false) + /* Unregister IRQs */ + platform_mif_unregister_irq(platform); +#else + platform_mif_unregister_irq(platform); +#endif + /* CRs */ /* 1's - clear all the interrupts */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unmap: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + platform_mif_unmap_region(platform->mem); + platform->mem = NULL; +} + +static u32 platform_mif_irq_bit_mask_status_get(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)) >> 16; + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Getting INTMR0: 0x%x\n", val); + return val; +} + +static u32 platform_mif_irq_get(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + + /* Function has to return the interrupts that are enabled *AND* not masked */ + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR0)) >> 16; + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Getting INT-INTMSR0: 0x%x\n", val); + + return val; +} + +static void platform_mif_irq_bit_set(struct scsc_mif_abs *interface, int bit_num, enum scsc_mif_abs_target target) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 reg; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + +#ifdef CONFIG_SOC_EXYNOS9610 + reg = INTGR1; + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(reg), (1 << bit_num)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTGR1: bit %d on target %d\n", bit_num, target); +#endif +} + +static void platform_mif_irq_bit_clear(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + /* WRITE : 1 = Clears Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), ((1 << bit_num) << 16)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTCR0: bit %d\n", bit_num); +} + +static void platform_mif_irq_bit_mask(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + unsigned long flags; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + /* WRITE : 1 = Mask Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val | ((1 << bit_num) << 16)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTMR0: 0x%x bit %d\n", val | (1 << bit_num), bit_num); +} + +static void platform_mif_irq_bit_unmask(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + unsigned long flags; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + /* WRITE : 0 = Unmask Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val & ~((1 << bit_num) << 16)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "UNMASK Setting INTMR0: 0x%x bit %d\n", val & ~((1 << bit_num) << 16), bit_num); +} + +/* Return the contents of the mask register */ +static u32 __platform_mif_irq_bit_mask_read(struct platform_mif *platform) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Read INTMR0: 0x%x\n", val); + + return val; +} + +/* Write the mask register, destroying previous contents */ +static void __platform_mif_irq_bit_mask_write(struct platform_mif *platform, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Write INTMR0: 0x%x\n", val); +} + +static void platform_mif_irq_reg_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif int handler %pS in %p %p\n", handler, platform, interface); + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform->r4_handler = handler; + platform->irq_dev = dev; + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_irq_unreg_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering mif int handler %pS\n", interface); + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform->r4_handler = platform_mif_irq_default_handler; + platform->irq_dev = NULL; + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_irq_reg_reset_request_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif reset_request int handler %pS in %p %p\n", handler, platform, interface); + platform->reset_request_handler = handler; + platform->irq_reset_request_dev = dev; + if (atomic_read(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "Default WDOG handler disabled by spurios IRQ...re-enabling.\n"); + enable_irq(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + atomic_set(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt, 0); + } +} + +static void platform_mif_irq_unreg_reset_request_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "UnRegistering mif reset_request int handler %pS\n", interface); + platform->reset_request_handler = platform_mif_irq_reset_request_default_handler; + platform->irq_reset_request_dev = NULL; +} + +static void platform_mif_suspend_reg_handler(struct scsc_mif_abs *interface, + int (*suspend)(struct scsc_mif_abs *abs, void *data), + void (*resume)(struct scsc_mif_abs *abs, void *data), + void *data) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif suspend/resume handlers in %p %p\n", platform, interface); + platform->suspend_handler = suspend; + platform->resume_handler = resume; + platform->suspendresume_data = data; +} + +static void platform_mif_suspend_unreg_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering mif suspend/resume handlers in %p %p\n", platform, interface); + platform->suspend_handler = NULL; + platform->resume_handler = NULL; + platform->suspendresume_data = NULL; +} + +static u32 *platform_mif_get_mbox_ptr(struct scsc_mif_abs *interface, u32 mbox_index) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 *addr; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "mbox_index 0x%x\n", mbox_index); + addr = platform->base + MAILBOX_WLBT_REG(ISSR(mbox_index)); + return addr; +} + +static int platform_mif_get_mifram_ref(struct scsc_mif_abs *interface, void *ptr, scsc_mifram_ref *ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return -ENOMEM; + } + + /* Check limits! */ + if (ptr >= (platform->mem + platform->mem_size)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Unable to get pointer reference\n"); + return -ENOMEM; + } + + *ref = (scsc_mifram_ref)((uintptr_t)ptr - (uintptr_t)platform->mem); + + return 0; +} + +static void *platform_mif_get_mifram_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return NULL; + } + + /* Check limits */ + if (ref >= 0 && ref < platform->mem_size) + return (void *)((uintptr_t)platform->mem + (uintptr_t)ref); + else + return NULL; +} + +static void *platform_mif_get_mifram_phy_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem_start) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return NULL; + } + + return (void *)((uintptr_t)platform->mem_start + (uintptr_t)ref); +} + +static uintptr_t platform_mif_get_mif_pfn(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + return vmalloc_to_pfn(platform->mem); +} + +static struct device *platform_mif_get_mif_device(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + return platform->dev; +} + +static void platform_mif_irq_clear(void) +{ + /* Implement if required */ +} + +static int platform_mif_read_register(struct scsc_mif_abs *interface, u64 id, u32 *val) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (id == SCSC_REG_READ_WLBT_STAT) { + regmap_read(platform->pmureg, WLBT_STAT, val); + return 0; + } + + return -EIO; +} + +static void platform_mif_dump_register(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR1))); + + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_cleanup(struct scsc_mif_abs *interface) +{ +} + +static void platform_mif_restart(struct scsc_mif_abs *interface) +{ +} + +#ifdef CONFIG_OF_RESERVED_MEM +static int __init platform_mif_wifibt_if_reserved_mem_setup(struct reserved_mem *remem) +{ + SCSC_TAG_DEBUG(PLAT_MIF, "memory reserved: mem_base=%#lx, mem_size=%zd\n", + (unsigned long)remem->base, (size_t)remem->size); + + sharedmem_base = remem->base; + sharedmem_size = remem->size; + return 0; +} +RESERVEDMEM_OF_DECLARE(wifibt_if, "exynos,wifibt_if", platform_mif_wifibt_if_reserved_mem_setup); +#endif + +struct scsc_mif_abs *platform_mif_create(struct platform_device *pdev) +{ + struct scsc_mif_abs *platform_if; + struct platform_mif *platform = + (struct platform_mif *)devm_kzalloc(&pdev->dev, sizeof(struct platform_mif), GFP_KERNEL); + int err = 0; + u8 i = 0; + struct resource *reg_res; + +#ifdef CONFIG_SCSC_SMAPPER + u32 smapper_banks = 0; +#endif + + if (!platform) + return NULL; + + SCSC_TAG_INFO_DEV(PLAT_MIF, &pdev->dev, "Creating MIF platform device\n"); + + platform_if = &platform->interface; + + /* initialise interface structure */ + platform_if->destroy = platform_mif_destroy; + platform_if->get_uid = platform_mif_get_uid; + platform_if->reset = platform_mif_reset; + platform_if->map = platform_mif_map; + platform_if->unmap = platform_mif_unmap; + platform_if->irq_bit_set = platform_mif_irq_bit_set; + platform_if->irq_get = platform_mif_irq_get; + platform_if->irq_bit_mask_status_get = platform_mif_irq_bit_mask_status_get; + platform_if->irq_bit_clear = platform_mif_irq_bit_clear; + platform_if->irq_bit_mask = platform_mif_irq_bit_mask; + platform_if->irq_bit_unmask = platform_mif_irq_bit_unmask; + platform_if->irq_reg_handler = platform_mif_irq_reg_handler; + platform_if->irq_unreg_handler = platform_mif_irq_unreg_handler; + platform_if->irq_reg_reset_request_handler = platform_mif_irq_reg_reset_request_handler; + platform_if->irq_unreg_reset_request_handler = platform_mif_irq_unreg_reset_request_handler; + platform_if->suspend_reg_handler = platform_mif_suspend_reg_handler; + platform_if->suspend_unreg_handler = platform_mif_suspend_unreg_handler; + platform_if->get_mbox_ptr = platform_mif_get_mbox_ptr; + platform_if->get_mifram_ptr = platform_mif_get_mifram_ptr; + platform_if->get_mifram_ref = platform_mif_get_mifram_ref; + platform_if->get_mifram_pfn = platform_mif_get_mif_pfn; + platform_if->get_mifram_phy_ptr = platform_mif_get_mifram_phy_ptr; + platform_if->get_mif_device = platform_mif_get_mif_device; + platform_if->irq_clear = platform_mif_irq_clear; + platform_if->mif_dump_registers = platform_mif_dump_register; + platform_if->mif_read_register = platform_mif_read_register; + platform_if->mif_cleanup = platform_mif_cleanup; + platform_if->mif_restart = platform_mif_restart; +#ifdef CONFIG_SCSC_SMAPPER + platform_if->mif_smapper_get_mapping = platform_mif_smapper_get_mapping; + platform_if->mif_smapper_get_bank_info = platform_mif_smapper_get_bank_info; + platform_if->mif_smapper_write_sram = platform_mif_smapper_write_sram; + platform_if->mif_smapper_configure = platform_mif_smapper_configure; + platform_if->mif_smapper_get_bank_base_address = platform_mif_smapper_get_bank_base_address; +#endif +#ifdef CONFIG_SCSC_QOS + platform_if->mif_pm_qos_add_request = platform_mif_pm_qos_add_request; + platform_if->mif_pm_qos_update_request = platform_mif_pm_qos_update_request; + platform_if->mif_pm_qos_remove_request = platform_mif_pm_qos_remove_request; +#endif + /* Update state */ + platform->pdev = pdev; + platform->dev = &pdev->dev; + + platform->r4_handler = platform_mif_irq_default_handler; + platform->irq_dev = NULL; + platform->reset_request_handler = platform_mif_irq_reset_request_default_handler; + platform->irq_reset_request_dev = NULL; + platform->suspend_handler = NULL; + platform->resume_handler = NULL; + platform->suspendresume_data = NULL; + +#ifdef CONFIG_OF_RESERVED_MEM + platform->mem_start = sharedmem_base; + platform->mem_size = sharedmem_size; +#else + /* If CONFIG_OF_RESERVED_MEM is not defined, sharedmem values should be + * parsed from the scsc_wifibt binding + */ + if (of_property_read_u32(pdev->dev.of_node, "sharedmem-base", &sharedmem_base)) { + err = -EINVAL; + goto error_exit; + } + platform->mem_start = sharedmem_base; + + if (of_property_read_u32(pdev->dev.of_node, "sharedmem-size", &sharedmem_size)) { + err = -EINVAL; + goto error_exit; + } + platform->mem_size = sharedmem_size; +#endif +#ifdef CONFIG_SCSC_SMAPPER + platform->smapper = NULL; +#endif + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->mem_start 0x%x platform->mem_size 0x%x\n", + (u32)platform->mem_start, (u32)platform->mem_size); + if (platform->mem_start == 0) + SCSC_TAG_WARNING_DEV(PLAT_MIF, platform->dev, "platform->mem_start is 0"); + + if (platform->mem_size == 0) { + /* We return return if mem_size is 0 as it does not make any sense. + * This may be an indication of an incorrect platform device binding. + */ + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "platform->mem_size is 0"); + err = -EINVAL; + goto error_exit; + } + + /* Memory resource - Phys Address of MAILBOX_WLBT register map */ + reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!reg_res) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error getting mem resource for MAILBOX_WLBT\n"); + err = -ENOENT; + goto error_exit; + } + + platform->reg_start = reg_res->start; + platform->reg_size = resource_size(reg_res); + + platform->base = + devm_ioremap_nocache(platform->dev, reg_res->start, resource_size(reg_res)); + + if (!platform->base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping register region\n"); + err = -EBUSY; + goto error_exit; + } + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->reg_start %lx size %x base %p\n", + (uintptr_t)platform->reg_start, (u32)platform->reg_size, platform->base); + + /* Get the 4 IRQ resources */ + for (i = 0; i < 4; i++) { + struct resource *irq_res; + int irqtag; + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!irq_res) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "No IRQ resource at index %d\n", i); + err = -ENOENT; + goto error_exit; + } + + if (!strcmp(irq_res->name, "MBOX")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "MBOX irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_MBOX; + } else if (!strcmp(irq_res->name, "ALIVE")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "ALIVE irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_ALIVE; + } else if (!strcmp(irq_res->name, "WDOG")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WDOG irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_WDOG; + } else if (!strcmp(irq_res->name, "CFG_REQ")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "CFG_REQ irq %d flag 0x%x\n", (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_CFG_REQ; + } else { + SCSC_TAG_ERR_DEV(PLAT_MIF, &pdev->dev, "Invalid irq res name: %s\n", + irq_res->name); + err = -EINVAL; + goto error_exit; + } + platform->wlbt_irq[irqtag].irq_num = irq_res->start; + platform->wlbt_irq[irqtag].flags = (irq_res->flags & IRQF_TRIGGER_MASK); + atomic_set(&platform->wlbt_irq[irqtag].irq_disabled_cnt, 0); + } + + /* PMU reg map - syscon */ + platform->pmureg = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(platform->pmureg)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "syscon regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->pmureg)); + err = -EINVAL; + goto error_exit; + } + +#ifdef CONFIG_SOC_EXYNOS9610 + /* Completion event and state used to indicate CFG_REQ IRQ occurred */ + init_completion(&platform->cfg_ack); + platform->boot_state = WLBT_BOOT_IN_RESET; + + /* BAAW_P_WLBT */ + platform->baaw_p_wlbt = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,baaw_p_wlbt-syscon-phandle"); + if (IS_ERR(platform->baaw_p_wlbt)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "baaw_p_wlbt regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->baaw_p_wlbt)); + err = -EINVAL; + goto error_exit; + } + + /* DBUS_BAAW */ + platform->dbus_baaw = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,dbus_baaw-syscon-phandle"); + if (IS_ERR(platform->dbus_baaw)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "dbus_baaw regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->dbus_baaw)); + err = -EINVAL; + goto error_exit; + } + + /* PBUS_BAAW */ + platform->pbus_baaw = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,pbus_baaw-syscon-phandle"); + if (IS_ERR(platform->pbus_baaw)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "pbus_baaw regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->pbus_baaw)); + err = -EINVAL; + goto error_exit; + } + + /* WLBT_REMAP */ + platform->wlbt_remap = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,wlbt_remap-syscon-phandle"); + if (IS_ERR(platform->wlbt_remap)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "wlbt_remap regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->wlbt_remap)); + err = -EINVAL; + goto error_exit; + } + + /* BOOT_CFG */ + platform->boot_cfg = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,boot_cfg-syscon-phandle"); + if (IS_ERR(platform->boot_cfg)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "boot_cfg regmap lookup failed. Aborting. %ld\n", PTR_ERR(platform->boot_cfg)); + err = -EINVAL; + goto error_exit; + } +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER parsing */ + if (!of_property_read_u32(pdev->dev.of_node, "smapper_num_banks", &smapper_banks)) + platform_mif_parse_smapper(platform, platform->dev->of_node, smapper_banks); + +#endif +#ifdef CONFIG_SCSC_QOS + platform_mif_parse_qos(platform, platform->dev->of_node); +#endif + /* Initialize spinlock */ + spin_lock_init(&platform->mif_spinlock); + + return platform_if; + +error_exit: + devm_kfree(&pdev->dev, platform); + return NULL; +} + +void platform_mif_destroy_platform(struct platform_device *pdev, struct scsc_mif_abs *interface) +{ +} + +struct platform_device *platform_mif_get_platform_dev(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + BUG_ON(!interface || !platform); + + return platform->pdev; +} + +struct device *platform_mif_get_dev(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + BUG_ON(!interface || !platform); + + return platform->dev; +} + +/* Preserve MIF registers during suspend. + * If all users of the MIF (AP, mx140, CP, etc) release it, the registers + * will lose their values. Save the useful subset here. + * + * Assumption: the AP will not change the register values between the suspend + * and resume handlers being called! + */ +static void platform_mif_reg_save(struct platform_mif *platform) +{ + platform->mif_preserve.irq_bit_mask = __platform_mif_irq_bit_mask_read(platform); +} + +/* Restore MIF registers that may have been lost during suspend */ +static void platform_mif_reg_restore(struct platform_mif *platform) +{ + __platform_mif_irq_bit_mask_write(platform, platform->mif_preserve.irq_bit_mask); +} + +int platform_mif_suspend(struct scsc_mif_abs *interface) +{ + int r = 0; + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (platform->suspend_handler) + r = platform->suspend_handler(interface, platform->suspendresume_data); + + /* Save the MIF registers. + * This must be done last as the suspend_handler may use the MIF + */ + platform_mif_reg_save(platform); + + return r; +} + +void platform_mif_resume(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + s32 ret; + + /* Restore the MIF registers. + * This must be done first as the resume_handler may use the MIF. + */ + platform_mif_reg_restore(platform); +#ifdef CONFIG_SOC_EXYNOS9610 + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clear WLBT_ACTIVE_CLR flag\n"); + /* Clear WLBT_ACTIVE_CLR flag in WLBT_CTRL_NS */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, WLBT_ACTIVE_CLR, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WLBT_CTRL_NS[WLBT_ACTIVE_CLR]: %d\n", ret); + } +#endif + if (platform->resume_handler) + platform->resume_handler(interface, platform->suspendresume_data); +} diff --git a/drivers/misc/samsung/scsc/platform_mif_9630.c b/drivers/misc/samsung/scsc/platform_mif_9630.c new file mode 100644 index 000000000000..fa0f87d58580 --- /dev/null +++ b/drivers/misc/samsung/scsc/platform_mif_9630.c @@ -0,0 +1,2265 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Implements interface */ + +#include "platform_mif.h" + +/* Interfaces it Uses */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#include +#endif +#include +#include +#include +#include +#include "mif_reg_S5E9630.h" +#include "platform_mif_module.h" +#ifdef CONFIG_ARCH_EXYNOS +#include +#endif +#ifdef CONFIG_SOC_EXYNOS9630 +#include +#endif + +#ifdef CONFIG_SCSC_SMAPPER +#include +#include "mif_reg_smapper.h" +#endif +#ifdef CONFIG_SCSC_QOS +#include +#endif + +#if !defined(CONFIG_SOC_EXYNOS9630) +#error Target processor CONFIG_SOC_EXYNOS9630 not selected +#endif + +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif +/* Time to wait for CFG_REQ IRQ on 9610 */ +#define WLBT_BOOT_TIMEOUT (HZ) + +#ifdef CONFIG_OF_RESERVED_MEM +#include +#endif +static unsigned long sharedmem_base; +static size_t sharedmem_size; + +#ifdef CONFIG_SCSC_CHV_SUPPORT +static bool chv_disable_irq; +module_param(chv_disable_irq, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(chv_disable_irq, "Do not register for irq"); +#endif + +static bool enable_platform_mif_arm_reset = true; +module_param(enable_platform_mif_arm_reset, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_platform_mif_arm_reset, "Enables WIFIBT ARM cores reset"); + +static bool disable_apm_setup; +module_param(disable_apm_setup, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_apm_setup, "Disable host APM setup"); + +#ifdef CONFIG_SCSC_QOS +struct qos_table { + unsigned int freq_mif; + unsigned int freq_int; + unsigned int freq_cl0; + unsigned int freq_cl1; +}; +#endif + +struct platform_mif { + struct scsc_mif_abs interface; + struct scsc_mbox_s *mbox; + struct platform_device *pdev; + + struct device *dev; + + struct { + int irq_num; + int flags; + atomic_t irq_disabled_cnt; + } wlbt_irq[PLATFORM_MIF_NUM_IRQS]; + + /* MIF registers preserved during suspend */ + struct { + u32 irq_bit_mask; + } mif_preserve; + + /* register MBOX memory space */ + size_t reg_start; + size_t reg_size; + void __iomem *base; + + /* register CMU memory space */ + struct regmap *cmu_base; + + void __iomem *con0_base; + + /* pmu syscon regmap */ + struct regmap *pmureg; +#if defined(CONFIG_SOC_EXYNOS9630) + struct regmap *baaw_p_wlbt; + struct regmap *dbus_baaw; + struct regmap *pbus_baaw; + struct regmap *wlbt_remap; + struct regmap *boot_cfg; + + /* Signalled when CFG_REQ IRQ handled */ + struct completion cfg_ack; + + /* State of CFG_REQ handler */ + enum wlbt_boot_state { + WLBT_BOOT_IN_RESET = 0, + WLBT_BOOT_WAIT_CFG_REQ, + WLBT_BOOT_CFG_DONE, + WLBT_BOOT_CFG_ERROR + } boot_state; + +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER */ + void __iomem *smapper_base; + u8 smapper_banks; + struct { + u8 bank; + u32 ws; + bool large; + struct scsc_mif_smapper_info bank_info; + } *smapper; +#endif + /* Shared memory space - reserved memory */ + unsigned long mem_start; + size_t mem_size; + void __iomem *mem; + + /* Callback function and dev pointer mif_intr manager handler */ + void (*r4_handler)(int irq, void *data); + void *irq_dev; + /* spinlock to serialize driver access */ + spinlock_t mif_spinlock; + void (*reset_request_handler)(int irq, void *data); + void *irq_reset_request_dev; + +#ifdef CONFIG_SCSC_QOS + /* QoS table */ + struct qos_table *qos; + bool qos_enabled; +#endif + /* Suspend/resume handlers */ + int (*suspend_handler)(struct scsc_mif_abs *abs, void *data); + void (*resume_handler)(struct scsc_mif_abs *abs, void *data); + void *suspendresume_data; +}; + +static void power_supplies_on(struct platform_mif *platform); + +extern int mx140_log_dump(void); + +#define platform_mif_from_mif_abs(MIF_ABS_PTR) container_of(MIF_ABS_PTR, struct platform_mif, interface) + +inline void platform_mif_reg_write(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->base + offset); +} + +inline u32 platform_mif_reg_read(struct platform_mif *platform, u16 offset) +{ + return readl(platform->base + offset); +} + +#ifdef CONFIG_SCSC_SMAPPER +inline void platform_mif_reg_write_smapper(struct platform_mif *platform, u16 offset, u32 value) +{ + writel(value, platform->smapper_base + offset); +} + +inline u32 platform_mif_reg_read_smapper(struct platform_mif *platform, u16 offset) +{ + return readl(platform->smapper_base + offset); +} + +#define PLATFORM_MIF_SHIFT_SMAPPER_ADDR 11 /* From 36 bits addres to 25 bits */ +#define PLATFORM_MIF_SHIFT_SMAPPER_END 4 /* End address aligment */ + +/* Platform is responsible to give the phys mapping of the SMAPPER maps */ +static int platform_mif_smapper_get_mapping(struct scsc_mif_abs *interface, u8 *phy_map, u16 *align) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Mapping %d banks\n", platform->smapper_banks); + + if (!platform->smapper_banks) + return -EINVAL; + + for (i = 0; i < platform->smapper_banks; i++) { + if (platform->smapper[i].large) + phy_map[i] = SCSC_MIF_ABS_LARGE_BANK; + else + phy_map[i] = SCSC_MIF_ABS_SMALL_BANK; + } + + if (align) + *align = 1 << PLATFORM_MIF_SHIFT_SMAPPER_ADDR; + + return 0; +} + +static int platform_mif_smapper_get_bank_info(struct scsc_mif_abs *interface, u8 bank, struct scsc_mif_smapper_info *bank_info) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform->smapper_banks) + return -EINVAL; + + bank_info->num_entries = platform->smapper[bank].bank_info.num_entries; + bank_info->mem_range_bytes = platform->smapper[bank].bank_info.mem_range_bytes; + + return 0; +} + +static u8 platform_mif_smapper_granularity_to_bits(u32 granularity) +{ + if (granularity <= 2 * 1024) + return 0; + if (granularity <= 4 * 1024) + return 1; + if (granularity <= 8 * 1024) + return 2; + if (granularity <= 16 * 1024) + return 3; + if (granularity <= 32 * 1024) + return 4; + if (granularity <= 64 * 1024) + return 5; + if (granularity <= 128 * 1024) + return 6; + return 7; +} + +static u32 platform_mif_smapper_get_bank_base_address(struct scsc_mif_abs *interface, u8 bank) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform->smapper) + return 0; + + return platform->smapper[bank].ws; +} + +/* Configure smapper according the memory map and range */ +static void platform_mif_smapper_configure(struct scsc_mif_abs *interface, u32 granularity) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + u8 gran; + u8 nb = platform->smapper_banks; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Configure SMAPPER with granularity %d\n", granularity); + + gran = platform_mif_smapper_granularity_to_bits(granularity); + + platform_mif_reg_write_smapper(platform, SMAPPER_QCH_DISABLE, 1); + platform_mif_reg_write_smapper(platform, ORIGIN_ADDR_AR, 0); + platform_mif_reg_write_smapper(platform, ORIGIN_ADDR_AW, 0); + /* Program SMAPPER memmap */ + for (i = 0; i < nb; i++) { + /* Set ADDR_MAP_EN to 1'b0*/ + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(i), 0); + /* Set START_ADDR */ + platform_mif_reg_write_smapper(platform, START_ADDR(i), platform->smapper[i].ws); + /* Set ADDR_GRANULARITY - FIXED AT 4KB */ + platform_mif_reg_write_smapper(platform, ADDR_GRANULARITY(i), gran); + /* WLAN_ADDR_MAP operation is started */ + } + /* Set access window control (MSB 32bits Start/End address) */ + /* Remapped address should be ranged from AW_START_ADDR to AW_EN_ADDR */ + platform_mif_reg_write_smapper(platform, AW_START_ADDR, 0); + platform_mif_reg_write_smapper(platform, AW_END_ADDR, dma_get_mask(platform->dev) >> PLATFORM_MIF_SHIFT_SMAPPER_END); + smp_mb(); +} + +/* Caller is responsible of validating the phys address (alignment) */ +static int platform_mif_smapper_write_sram(struct scsc_mif_abs *interface, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + u32 rb; + + if (!platform->smapper_banks) + return -EINVAL; + + if (!platform->smapper_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "SMAPPER not enabled\n"); + return -EINVAL; + } + + /* Set ADDR_MAP_EN to 1'b0*/ + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(bank), 0); + /* Write mapping table to SRAM. Each entry consists of 25 bits MSB address to remap */ + for (i = 0; i < num_entries; i++) { + if (!addr[i]) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "ADDR IS NULL at bank %d entry %d/%d\n", bank, first_entry + i, num_entries); + return -EINVAL; + } + /* Set SRAM_WRITE_CTRL to 1'b1*/ + platform_mif_reg_write_smapper(platform, SRAM_WRITE_CTRL(bank), 1); + platform_mif_reg_write_smapper(platform, SRAM_BANK_INDEX(bank, first_entry + i), addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR); + /* check incorrect writings */ + platform_mif_reg_write_smapper(platform, SRAM_WRITE_CTRL(bank), 0); + rb = platform_mif_reg_read_smapper(platform, SRAM_BANK_INDEX(bank, first_entry + i)); + if (rb != addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "incorrect mapping detected rb 0x%x, addr 0x%x\n", rb, (u32)addr[i] >> PLATFORM_MIF_SHIFT_SMAPPER_ADDR); + return -EFAULT; + } + } + platform_mif_reg_write_smapper(platform, ADDR_MAP_EN(bank), 1); + smp_mb(); + return 0; +} + +static int platform_mif_parse_smapper(struct platform_mif *platform, struct device_node *np, u8 num_banks) +{ + /* SMAPPER parsing */ + struct device_node *np_banks; + char node_name[50]; + u32 val[2]; + u8 i; + u32 bank = 0, ws = 0, wsz = 0, ent = 0, large = 0; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "banks found: %d szof %zu\n", num_banks, sizeof(*platform->smapper)); + + platform->smapper = kmalloc_array(num_banks, sizeof(*platform->smapper), GFP_KERNEL); + + if (!platform->smapper) + return -ENOMEM; + + for (i = 0; i < num_banks; i++) { + snprintf(node_name, sizeof(node_name), "smapper_bank_%d", i); + np_banks = of_find_node_by_name(np, node_name); + if (!np_banks) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "%s: could not find smapper_bank\n", + node_name); + kfree(platform->smapper); + platform->smapper = NULL; + return -ENOENT; + } + of_property_read_u32(np_banks, "bank_num", &bank); + of_property_read_u32(np_banks, "fw_window_start", &ws); + of_property_read_u32(np_banks, "fw_window_size", &wsz); + of_property_read_u32(np_banks, "num_entries", &ent); + of_property_read_u32(np_banks, "is_large", &large); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "bank %d fw_w_start 0x%x fw_w_sz 0x%x entries %d is_large %d\n", + bank, ws, wsz, ent, large); + + platform->smapper[i].bank = (u8)bank; + platform->smapper[i].ws = ws; + platform->smapper[i].large = (bool)large; + platform->smapper[i].bank_info.num_entries = ent; + platform->smapper[i].bank_info.mem_range_bytes = wsz; + } + + /* Update the number of banks before returning */ + platform->smapper_banks = num_banks; + + of_property_read_u32_array(np, "smapper_reg", val, 2); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "smapper reg address 0x%x size 0x%x\n", val[0], val[1]); + platform->smapper_base = + devm_ioremap_nocache(platform->dev, val[0], val[1]); + + if (!platform->smapper_base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping smapper register region\n"); + kfree(platform->smapper); + platform->smapper = NULL; + return -ENOENT; + } + + return 0; +} +#endif +#ifdef CONFIG_SCSC_QOS +static int platform_mif_parse_qos(struct platform_mif *platform, struct device_node *np) +{ + int len, i; + + platform->qos_enabled = false; + + len = of_property_count_u32_elems(np, "qos_table"); + if (!(len == 12)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "No qos table for wlbt, or incorrect size\n"); + return -ENOENT; + } + + platform->qos = devm_kzalloc(platform->dev, sizeof(struct qos_table) * len / 4, GFP_KERNEL); + if (!platform->qos) + return -ENOMEM; + + of_property_read_u32_array(np, "qos_table", (unsigned int *)platform->qos, len); + + for (i = 0; i < len / 4; i++) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "QoS Table[%d] mif : %u int : %u cl0 : %u cl1: %u\n", i, + platform->qos[i].freq_mif, + platform->qos[i].freq_int, + platform->qos[i].freq_cl0, + platform->qos[i].freq_cl1); + } + + platform->qos_enabled = true; + return 0; +} + +struct qos_table platform_mif_pm_qos_get_table(struct platform_mif *platform, enum scsc_qos_config config) +{ + struct qos_table table; + + switch (config) { + case SCSC_QOS_MIN: + table.freq_mif = platform->qos[0].freq_mif; + table.freq_int = platform->qos[0].freq_int; + table.freq_cl0 = platform->qos[0].freq_cl0; + table.freq_cl1 = platform->qos[0].freq_cl1; + break; + + case SCSC_QOS_MED: + table.freq_mif = platform->qos[1].freq_mif; + table.freq_int = platform->qos[1].freq_int; + table.freq_cl0 = platform->qos[1].freq_cl0; + table.freq_cl1 = platform->qos[1].freq_cl1; + break; + + case SCSC_QOS_MAX: + table.freq_mif = platform->qos[2].freq_mif; + table.freq_int = platform->qos[2].freq_int; + table.freq_cl0 = platform->qos[2].freq_cl0; + table.freq_cl1 = platform->qos[2].freq_cl1; + break; + + default: + table.freq_mif = 0; + table.freq_int = 0; + table.freq_cl0 = 0; + table.freq_cl1 = 0; + } + + return table; +} + +static int platform_mif_pm_qos_add_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + struct qos_table table; + + if (!platform) + return -ENODEV; + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + table = platform_mif_pm_qos_get_table(platform, config); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "PM QoS add request: %u. MIF %u INT %u CL0 %u CL1 %u\n", config, table.freq_mif, table.freq_int, table.freq_cl0, table.freq_cl1); + + pm_qos_add_request(&qos_req->pm_qos_req_mif, PM_QOS_BUS_THROUGHPUT, table.freq_mif); + pm_qos_add_request(&qos_req->pm_qos_req_int, PM_QOS_DEVICE_THROUGHPUT, table.freq_int); + pm_qos_add_request(&qos_req->pm_qos_req_cl0, PM_QOS_CLUSTER0_FREQ_MIN, table.freq_cl0); + pm_qos_add_request(&qos_req->pm_qos_req_cl1, PM_QOS_CLUSTER1_FREQ_MIN, table.freq_cl1); + + return 0; +} + +static int platform_mif_pm_qos_update_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + struct qos_table table; + + if (!platform) + return -ENODEV; + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + table = platform_mif_pm_qos_get_table(platform, config); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "PM QoS update request: %u. MIF %u INT %u CL0 %u CL1 %u\n", config, table.freq_mif, table.freq_int, table.freq_cl0, table.freq_cl1); + + pm_qos_update_request(&qos_req->pm_qos_req_mif, table.freq_mif); + pm_qos_update_request(&qos_req->pm_qos_req_int, table.freq_int); + pm_qos_update_request(&qos_req->pm_qos_req_cl0, table.freq_cl0); + pm_qos_update_request(&qos_req->pm_qos_req_cl1, table.freq_cl1); + + return 0; +} + +static int platform_mif_pm_qos_remove_request(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (!platform) + return -ENODEV; + + + if (!platform->qos_enabled) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS not configured\n"); + return -EOPNOTSUPP; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PM QoS remove request\n"); + pm_qos_remove_request(&qos_req->pm_qos_req_mif); + pm_qos_remove_request(&qos_req->pm_qos_req_int); + pm_qos_remove_request(&qos_req->pm_qos_req_cl0); + pm_qos_remove_request(&qos_req->pm_qos_req_cl1); + + return 0; +} +#endif + +static void platform_mif_irq_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; + + /* int handler not registered */ + SCSC_TAG_INFO_DEV(PLAT_MIF, NULL, "INT handler not registered\n"); +} + +static void platform_mif_irq_reset_request_default_handler(int irq, void *data) +{ + /* Avoid unused parameter error */ + (void)irq; + (void)data; + + /* int handler not registered */ + SCSC_TAG_INFO_DEV(PLAT_MIF, NULL, "INT reset_request handler not registered\n"); +} + +irqreturn_t platform_mif_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "INT %pS\n", platform->r4_handler); + if (platform->r4_handler != platform_mif_irq_default_handler) + platform->r4_handler(irq, platform->irq_dev); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "MIF Interrupt Handler not registered\n"); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ +irqreturn_t platform_alive_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + + return IRQ_HANDLED; +} +#endif + +irqreturn_t platform_wdog_isr(int irq, void *data) +{ + int ret = 0; + struct platform_mif *platform = (struct platform_mif *)data; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + if (platform->reset_request_handler != platform_mif_irq_reset_request_default_handler) { + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + platform->reset_request_handler(irq, platform->irq_reset_request_dev); + } else { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WDOG Interrupt reset_request_handler not registered\n"); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Disabling unhandled WDOG IRQ.\n"); + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + atomic_inc(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt); + } + + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_RESET_REQ_CLR, WLBT_RESET_REQ_CLR); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clearing WLBT_RESET_REQ\n"); + if (ret < 0) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WLBT_CTRL_NS[WLBT_RESET_REQ_CLR]: %d\n", ret); + + return IRQ_HANDLED; +} + +/* + * Attached array contains the replacement PMU boot code which should + * be programmed using the CBUS during the config phase. + */ +uint32_t ka_patch[] = { + // Maxwell142 PMU+PROC combined boot ROM + // IP Version: 0xA3 + // Major Version: 0xF, Minor Version: 0xF + // PMU ROM version: 0x4 + // PROC ROM version: 0x0 + // From excite 0605 + 0xe8750002, + 0x1189113f, + 0x0e807558, + 0xe030b3e5, + 0x11a230fb, + 0xb1f50774, + 0x75fdb2b5, + 0x077400b1, + 0xadb5acf5, + 0x01b443fd, + 0xe020b3e5, + 0xfeb453fb, + 0xd880a3d2, + 0x83798278, + 0xa075d280, + 0x78377907, + 0x0754e6b0, + 0x800207b4, + 0x04f6d90b, + 0x7580f5c4, + 0x038000af, + 0x22098075, + 0x53f7f753, + 0x0479fece, + 0xce53fed9, + 0xd90c79fd, + 0xfbce53fe, + 0x53b79275, + 0x0074fd91, + 0xce5392f5, + 0xb79275f7, + 0x5302f943, + 0xf922fef9, + 0xfbd8fed9, + 0x019e7522, + 0x7501d275, + 0xc17580d5, + 0x74047880, + 0x838012c3, + 0x78c0c175, + 0x12827402, + 0x80758380, + 0x0291750d, + 0x75039375, + 0xa975029e, + 0x00002201, +}; + +//extern bool reset_failed; + +irqreturn_t platform_cfg_req_isr(int irq, void *data) +{ + struct platform_mif *platform = (struct platform_mif *)data; + u64 ret64 = 0; + const u64 EXYNOS_WLBT = 0x1; + /*s32 ret = 0;*/ + unsigned int ka_addr = 0x1000; + uint32_t *ka_patch_addr = ka_patch; + //u32 id; + +#define CHECK(x) do { \ + int retval = (x); \ + if (retval < 0) {\ + pr_err("%s failed at L%d", __FUNCTION__, __LINE__); \ + goto cfg_error; \ + } \ +} while (0) + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INT received\n"); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "disable_irq\n"); + + /* mask the irq */ + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + /* Was the CFG_REQ irq received from WLBT before we expected it? + * Typically this indicates an issue returning WLBT HW to reset. + */ + if (platform->boot_state != WLBT_BOOT_WAIT_CFG_REQ) { + u32 val; + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Spurious CFG_REQ IRQ from WLBT!\n"); + + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_NS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CTRL_S, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_S 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + + //reset_failed = true; /* prevent further interaction with HW */ + + return IRQ_HANDLED; + } + + /* Set TZPC to non-secure mode */ + /* http://caeweb/~wshi/skylark/project-skylark.sim-basic/sky/regs/pmu_conf/pmu_conf/doc/main.html + */ + ret64 = exynos_smc(SMC_CMD_CONN_IF, (EXYNOS_WLBT << 32) | EXYNOS_SET_CONN_TZPC, 0, 0); + if (ret64) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to set TZPC to non-secure mode: %llu\n", ret64); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SMC_CMD_CONN_IF run successfully : %llu\n", ret64); +#if 0 + /* WLBT_REMAP PMU_REMAP - PROC_RMP_BOOT_ADDR 0x14450400 */ + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP begin\n"); + CHECK(regmap_write(platform->wlbt_remap, 0x400, WLBT_DBUS_BAAW_0_START >> 12)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP end\n"); +#endif + + /* update EMA parameters if required in future */ + + /* BAAW1_P_WLBT */ +#if 0 + /* Not used by DRAM boot */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BAAW_P_WLBT begin\n"); + CHECK(regmap_write(platform->baaw_p_wlbt, 0x0, 0x0 >> 12)); + CHECK(regmap_write(platform->baaw_p_wlbt, 0x4, 0x1ffff + 1)); + CHECK(regmap_write(platform->baaw_p_wlbt, 0x8, 0x0 >> 12)); + CHECK(regmap_write(platform->baaw_p_wlbt, 0xC, WLBT_BAAW_ACCESS_CTRL)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BAAW_P_WLBT end\n"); +#endif + /* WLBT_REMAP PMU_REMAP - PROC_RMP_BOOT_ADDR 0x14450400 */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP begin\n"); + CHECK(regmap_write(platform->wlbt_remap, 0x400, WLBT_DBUS_BAAW_0_START >> 12)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WLBT_REMAP end\n"); + + /* DBUS_BAAW regions */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "DBUS_BAAW begin\n"); // PMU_DBUS_BAAW + + /* Shared DRAM mapping. The destination address is the location reserved + * by the kernel. + */ + CHECK(regmap_write(platform->dbus_baaw, 0x0, WLBT_DBUS_BAAW_0_START >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0x4, WLBT_DBUS_BAAW_0_END >> 12)); + CHECK(regmap_write(platform->dbus_baaw, 0x8, platform->mem_start >> 12)); // FW AP base addr >> 12 + CHECK(regmap_write(platform->dbus_baaw, 0xC, WLBT_BAAW_ACCESS_CTRL)); +#if 0 + /* Additional DRAM mappings for future use */ + CHECK(regmap_write(platform->dbus_baaw, 0x10, 0x000C0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x14, 0x000D0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x18, 0x000D0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x1C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->dbus_baaw, 0x20, 0x000D0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x24, 0x000E0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x28, 0x000E0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x2C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->dbus_baaw, 0x30, 0x000E0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x34, 0x000F0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x38, 0x000F0000)); + CHECK(regmap_write(platform->dbus_baaw, 0x3C, WLBT_BAAW_ACCESS_CTRL)); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "DBUS_BAAW end\n"); + + /* PBUS_BAAW regions */ + /* ref wlbt_if_S5E9630.c, updated for MX450 memory map */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PBUS_BAAW begin\n"); + + /* Range for CP2WLBT mailbox */ + CHECK(regmap_write(platform->pbus_baaw, 0x0, WLBT_CBUS_BAAW_0_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x4, WLBT_CBUS_BAAW_0_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x8, WLBT_PBUS_MBOX_CP2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0xC, WLBT_BAAW_ACCESS_CTRL)); + + /* Range includes AP2WLBT,APM2WLBT,GNSS2WLBT mailboxes */ + CHECK(regmap_write(platform->pbus_baaw, 0x10, WLBT_CBUS_BAAW_1_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x14, WLBT_CBUS_BAAW_1_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x18, WLBT_PBUS_MBOX_GNSS2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x1C, WLBT_BAAW_ACCESS_CTRL)); +#if 0 + /* These mappings are not yet used by WLBT FW */ + CHECK(regmap_write(platform->pbus_baaw, 0x20, WLBT_CBUS_BAAW_2_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x24, WLBT_CBUS_BAAW_2_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x28, WLBT_PBUS_GPIO_CMGP_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x2C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x30, WLBT_CBUS_BAAW_3_START >>12)); + CHECK(regmap_write(platform->pbus_baaw, 0x34, WLBT_CBUS_BAAW_3_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x38, WLBT_PBUS_SYSREG_CMGP2WLBT_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x3C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x40, WLBT_CBUS_BAAW_4_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x44, WLBT_CBUS_BAAW_4_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x48, WLBT_PBUS_USI_CMG00_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x4C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x50, WLBT_CBUS_BAAW_5_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x54, WLBT_CBUS_BAAW_5_END >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x58, WLBT_PBUS_CHUB_USICHUB0_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x5C, WLBT_BAAW_ACCESS_CTRL)); + + CHECK(regmap_write(platform->pbus_baaw, 0x60, WLBT_CBUS_BAAW_6_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x64, WLBT_CBUS_BAAW_6_START >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x68, WLBT_PBUS_CHUB_BASE >> 12)); + CHECK(regmap_write(platform->pbus_baaw, 0x6C, WLBT_BAAW_ACCESS_CTRL)); +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "PBUS_BAAW end\n"); + + /* PMU boot patch */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BOOT_WLBT begin\n"); + CHECK(regmap_write(platform->boot_cfg, 0x0, 0x1)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BOOT_WLBT done\n"); + + while (ka_patch_addr < (ka_patch + ARRAY_SIZE(ka_patch))) { + CHECK(regmap_write(platform->boot_cfg, ka_addr, *ka_patch_addr)); + ka_addr += sizeof(ka_patch[0]); + ka_patch_addr++; + } + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "KA patch done\n"); + + /* Notify PMU of configuration done */ + CHECK(regmap_write(platform->boot_cfg, 0x0, 0x0)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BOOT config done\n"); + + /* BOOT_CFG_ACK */ + CHECK(regmap_write(platform->boot_cfg, 0x4, 0x1)); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "BOOT_CFG_ACK done\n"); + + /* Delay to allow HW to clear CFG_REQ and hence de-assert IRQ, which + * it does in response to CFG_ACK + */ + //udelay(100); + + /* Release ownership of MASK_PWR_REQ */ + /* See sequence in 9.6.6 */ + /*ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + MASK_PWR_REQ, 0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to clear WLBT_CTRL_NS[MASK_PWR_REQ]: %d\n", ret); + goto cfg_error; + }*/ + + /* Mark as CFQ_REQ handled, so boot may continue */ + platform->boot_state = WLBT_BOOT_CFG_DONE; + + /* Signal triggering function that the IRQ arrived and CFG was done */ + complete(&platform->cfg_ack); + + /* Re-enable IRQ here to allow spurious interrupt to be tracked */ + enable_irq(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + /* as per wlbt_if_S5E9630.c - end */ + return IRQ_HANDLED; +cfg_error: + platform->boot_state = WLBT_BOOT_CFG_ERROR; + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "ERROR: WLBT Config failed. WLBT will not work\n"); + complete(&platform->cfg_ack); + return IRQ_HANDLED; +} + +static void platform_mif_unregister_irq(struct platform_mif *platform) +{ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering IRQs\n"); + + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform); + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform); + /* Reset irq_disabled_cnt for WDOG IRQ since the IRQ itself is here unregistered and disabled */ + atomic_set(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt, 0); +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ + /* if ALIVE irq is required */ + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform); +#endif + devm_free_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform); +} + +static int platform_mif_register_irq(struct platform_mif *platform) +{ + int err; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering IRQs\n"); + + /* Register MBOX irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering MBOX irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform->wlbt_irq[PLATFORM_MIF_MBOX].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_MBOX].irq_num, platform_mif_isr, + platform->wlbt_irq[PLATFORM_MIF_MBOX].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register MBOX handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + + /* Register WDOG irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering WDOG irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform->wlbt_irq[PLATFORM_MIF_WDOG].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num, platform_wdog_isr, + platform->wlbt_irq[PLATFORM_MIF_WDOG].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register WDOG handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + +#ifdef CONFIG_SCSC_ENABLE_ALIVE_IRQ + /* Register ALIVE irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering ALIVE irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform->wlbt_irq[PLATFORM_MIF_ALIVE].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_ALIVE].irq_num, platform_alive_isr, + platform->wlbt_irq[PLATFORM_MIF_ALIVE].flags, DRV_NAME, platform); + if (IS_ERR_VALUE(err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register ALIVE handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } +#endif + + /* Mark as WLBT in reset before enabling IRQ to guard against spurious IRQ */ + platform->boot_state = WLBT_BOOT_IN_RESET; + smp_wmb(); /* commit before irq */ + + /* Register WB2AP_CFG_REQ irq */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering CFG_REQ irq: %d flag 0x%x\n", + platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].flags); + + err = devm_request_irq(platform->dev, platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num, platform_cfg_req_isr, + platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].flags, DRV_NAME, platform); + if (IS_ERR_VALUE((unsigned long)err)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to register CFG_REQ handler: %d. Aborting.\n", err); + err = -ENODEV; + return err; + } + + /* Leave disabled until ready to handle */ + disable_irq_nosync(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + return 0; +} + +static void platform_mif_destroy(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + platform_mif_unregister_irq(platform); +} + +static char *platform_mif_get_uid(struct scsc_mif_abs *interface) +{ + /* Avoid unused parameter error */ + (void)interface; + return "0"; +} + +static void wlbt_regdump(struct platform_mif *platform) +{ + u32 val = 0; + + regmap_read(platform->pmureg, WLBT_CTRL_S, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_S 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CONFIGURATION, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CONFIGURATION 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_CTRL_NS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_IN, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_IN 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_OUT, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_OUT 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_STATUS 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_STATES, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_STATES 0x%x\n", val); + + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + +#if 0 + /* Delay to let PMU process the cfg_ack */ + udelay(10000); + regmap_read(platform->boot_cfg, 0x0, &val); + SCSC_TAG_INFO(PLAT_MIF, "BOOT_SOURCE 0x%x\n", val); + + regmap_read(platform->boot_cfg, 0x4, &val); + SCSC_TAG_INFO(PLAT_MIF, "BOOT_CFG_ACK 0x%x\n", val); +#endif +} + +/* WLBT START */ +static int platform_mif_start(struct scsc_mif_abs *interface, bool start) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + /* done as part of platform_mif_pmu_reset_release() init_done sequence */ +#if 0 + //s32 ret = 0; + if (start) + val = WLBT_START; + + /* See sequence in TODO update when available */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_S, + WLBT_START, val); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_S[WLBT_START]: %d\n", ret); + return ret; + } + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "update WIFI_CTRL_S[WLBT_START]: %d\n", ret); + +#endif + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "start %d\n", start); + /* At this point WLBT should assert the CFG_REQ IRQ, so wait for it */ + if (start && + wait_for_completion_timeout(&platform->cfg_ack, WLBT_BOOT_TIMEOUT) == 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Timeout waiting for CFG_REQ IRQ\n"); + wlbt_regdump(platform); + return -ETIMEDOUT; + } + + wlbt_regdump(platform); + + /* only continue if CFG_REQ IRQ configured WLBT/PMU correctly */ + if (platform->boot_state == WLBT_BOOT_CFG_ERROR) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "CFG_REQ failed to configure WLBT.\n"); + return -EIO; + } + return 0; +} + +static int platform_mif_pmu_reset_release(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + int ret = 0; + u32 val = 0; + u32 v = 0; + unsigned long timeout; + static bool init_done; + + /* We're now ready for the IRQ */ + platform->boot_state = WLBT_BOOT_WAIT_CFG_REQ; + smp_wmb(); /* commit before irq */ + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "on boot_state = WLBT_BOOT_WAIT_CFG_REQ\n"); + + /* INIT SEQUENCE - First WLBT boot only + * Cold reset wrt. AP power sequencer, cold reset for WLBT + */ + if (!init_done) { + /* init sequence from excite + * SetBits((uinteger)REG_WLBT_CTRL_S, 3, 0x1, 0x1); + * SetBits((uinteger)REG_WLBT_CONFIGURATION, 0, 0x1, 0x1); + * while (GetBits((uinteger)REG_WLBT_STATUS, 0, 0x1) != 0x1); + * wlbt_if_check_wlbt2ap_bus_ready(); + * SetBits((uinteger)REG_WLBT_CTRL_NS, 8, 0x1, 0x0); + * SetBits((uinteger)REG_WLBT_CTRL_NS, 7, 0x1, 0x1); + */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "init\n"); + + /* setup TZPC */ + /*SCSC_TAG_INFO(PLAT_MIF, "trying to set TZPC register via exynos_smc(SMC_CMD_CONN_IF, (EXYNOS_WLBT << 32) | EXYNOS_SET_CONN_TZPC, 0, 0)\n"); + ret64 = exynos_smc(SMC_CMD_CONN_IF, (EXYNOS_WLBT << 32) | EXYNOS_SET_CONN_TZPC, 0, 0); + if (ret64) + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to set TZPC to non-secure mode: %llu\n", ret64); + else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SMC_CMD_CONN_IF run successfully : %llu\n", ret64);*/ + + /* WLBT_CTRL_S[WLBT_START] = 1 enable */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_S, + WLBT_START, WLBT_START); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_S[WLBT_START]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CTRL_S, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CTRL_S[WLBT_START]: 0x%x\n", val); + + /* WLBT_CONFIGURATION[LOCAL_PWR_CFG] = 1 Power On */ + ret = regmap_update_bits(platform->pmureg, WLBT_CONFIGURATION, + LOCAL_PWR_CFG, 0x1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CONFIGURATION[LOCAL_PWR_CFG]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CONFIGURATION, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CONFIGURATION[LOCAL_PWR_CFG]: 0x%x\n", val); + + /* wait for power up complete WLBT_STATUS[STATUS] = 1 for Power On */ + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, WLBT_STATUS, &val); + val &= STATUS; + if (val) { + /* Power On complete */ + SCSC_TAG_INFO(PLAT_MIF, "Power On complete: WLBT_STATUS 0x%x\n", val); + /* re affirming power on by reading WLBT_STATES */ + /* STATES[7:0] = 0x10 for Power Up */ + regmap_read(platform->pmureg, WLBT_STATES, &v); + SCSC_TAG_INFO(PLAT_MIF, "Power On complete: WLBT_STATES 0x%x\n", v); + break; + } + } while (time_before(jiffies, timeout)); + + if (!val) { + regmap_read(platform->pmureg, WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "timeout waiting for power on time-out: " + "WLBT_STATUS 0x%x, WLBT_STATES 0x%x\n", val, v); + } + + /* wait for WLBT_IN[BUS_READY] = 1 for BUS READY state */ + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, WLBT_IN, &val); + val &= BUS_READY; + if (val) { + /* BUS ready indication signal -> 1: BUS READY state */ + SCSC_TAG_INFO(PLAT_MIF, "Bus Ready: WLBT_IN 0x%x\n", val); + + /* OK to break */ + break; + } + } while (time_before(jiffies, timeout)); + + if (!val) { + regmap_read(platform->pmureg, WLBT_IN, &val); + SCSC_TAG_INFO(PLAT_MIF, "timeout waiting for Bus Ready: WLBT_IN 0x%x\n", val); + } + + /* WLBT_CTRL_NS[WLBT_ACTIVE_CLR] = 0 Active interrupt clear */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_ACTIVE_CLR, 0x0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[WLBT_ACTIVE_CLR]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CTRL_NS[WLBT_ACTIVE_CLR]: 0x%x\n", val); + + /* WLBT_CTRL_NS[WLBT_ACTIVE_EN] = 1 Active interrupt enable */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_ACTIVE_EN, WLBT_ACTIVE_EN); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[WLBT_ACTIVE_EN]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CTRL_NS[WLBT_ACTIVE_EN]: 0x%x\n", val); + + init_done = true; + + goto init_code_done; + } + + /* RESET RELEASE - Subsequent WLBT reboots */ + /* wlbt_if_reset_release - from excite code + * SetBits((uinteger)REG_WLBT_CONFIGURATION, 0, 0x1, 0x1); + * while (GetBits((uinteger)REG_WLBT_STATUS, 0, 0x1) != 0x1); + * SetBits((uinteger)REG_WLBT_INT_EN, 3, 0x1, 0x1); + * SetBits((uinteger)REG_WLBT_INT_EN, 5, 0x1, 0x1); + * while (GetBits((uinteger)REG_WLBT_IN, 4, 0x1) != 0x1); + * */ + + /* Warm reset wrt. AP power sequencer, but cold reset for WLBT */ + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "release\n"); + + /* Power Down */ + ret = regmap_update_bits(platform->pmureg, WLBT_CONFIGURATION, + LOCAL_PWR_CFG, 0x1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CONFIGURATION[LOCAL_PWR_CFG]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CONFIGURATION, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CONFIGURATION[LOCAL_PWR_CFG]: 0x%x\n", val); + + /* wait for power up complete WLBT_STATUS[0] = 0 for power down */ + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, WLBT_STATUS, &val); + val &= STATUS; + if (val) { + /* Power up complete */ + SCSC_TAG_INFO(PLAT_MIF, "Power up complete: WLBT_STATUS 0x%x\n", val); + /* re affirming power down by reading WLBT_STATES */ + /* STATES[7:0] = 0x80 for Power Down */ + regmap_read(platform->pmureg, WLBT_STATES, &v); + SCSC_TAG_INFO(PLAT_MIF, "Power up complete: WLBT_STATES 0x%x\n", v); + break; + } + } while (time_before(jiffies, timeout)); + + if (!val) { + regmap_read(platform->pmureg, WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "Timeout waiting for Power up complete: " + "WLBT_STATUS 0x%x, WLBT_STATES 0x%x\n", val, v); + } + + /* enable PWR_REQ_F and TCXO_REQ_F interrupts */ + ret = regmap_update_bits(platform->pmureg, WLBT_INT_EN, + PWR_REQ_F, 0x1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_INT_EN[PWR_REQ_F]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_INT_EN, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_INT_EN[PWR_REQ_F]: 0x%x\n", val); + + ret = regmap_update_bits(platform->pmureg, WLBT_INT_EN, + TCXO_REQ_F, 0x1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_INT_EN[TCXO_REQ_F]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_INT_EN, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_INT_EN[TCXO_REQ_F]: 0x%x\n", val); + + /* wait for WLBT_IN[BUS_READY] = 0 for BUS RESET RELEASED state */ + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, WLBT_IN, &val); + val &= BUS_READY; + if (val) { + /* BUS ready indication signal -> 1: BUS RESET RELEASED Normal state */ + SCSC_TAG_INFO(PLAT_MIF, "Bus Ready: WLBT_IN 0x%x\n", val); + + /* OK to break */ + break; + } + } while (time_before(jiffies, timeout)); + + if (!val) { + regmap_read(platform->pmureg, WLBT_IN, &val); + SCSC_TAG_INFO(PLAT_MIF, "Timeout waiting for Bus Ready: WLBT_IN 0x%x != 0\n", val); + } + +init_code_done: + /* Now handle the CFG_REQ IRQ */ + enable_irq(platform->wlbt_irq[PLATFORM_MIF_CFG_REQ].irq_num); + + ret = platform_mif_start(interface, true); + if (ret) + return ret; + + return ret; +} + +static int platform_mif_pmu_reset_assert(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long timeout; + int ret; + u32 val; + + /* wlbt_if_reset_assertion() - from wlbt_if_S5E9630.c + * SetBits((uinteger)TCXO_BUF_CTRL, 2, 0x1, 0x1); + * SetBits((uinteger)TCXO_BUF_CTRL, 3, 0x1, 0x1); + * SetBits((uinteger)SYSTEM_OUT, 9, 0x1, 0x1); + * udelay(1000); + * SetBits((uinteger)REG_WLBT_INT_EN, 3, 0x1, 0x0); + * SetBits((uinteger)REG_WLBT_INT_EN, 5, 0x1, 0x0); + * SetBits((uinteger)REG_WLBT_CTRL_NS, 7, 0x1, 0x0); + * SetBits((uinteger)REG_WLBT_CONFIGURATION, 0, 0x1, 0x0); + * while (GetBits((uinteger)REG_WLBT_STATUS, 0, 0x1) != 0x0); + */ + + ret = regmap_update_bits(platform->pmureg, TCXO_BUF_CTRL, + TCXO_BUF_BIAS_EN_WLBT, TCXO_BUF_BIAS_EN_WLBT); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update TCXO_BUF_CTRL[TCXO_BUF_BIAS_EN_WLBT]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, TCXO_BUF_CTRL, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully TCXO_BUF_CTRL[TCXO_BUF_BIAS_EN_WLBT]: 0x%x\n", val & TCXO_BUF_BIAS_EN_WLBT); + + ret = regmap_update_bits(platform->pmureg, TCXO_BUF_CTRL, + TCXO_BUF_EN_WLBT, TCXO_BUF_EN_WLBT); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update TCXO_BUF_CTRL[TCXO_BUF_EN_WLBT]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, TCXO_BUF_CTRL, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully TCXO_BUF_CTRL[TCXO_BUF_EN_WLBT]: 0x%x\n", val & TCXO_BUF_EN_WLBT); + + ret = regmap_update_bits(platform->pmureg, SYSTEM_OUT, + PWRRGTON_CON, PWRRGTON_CON); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update SYSTEM_OUT[PWRRGTON_CON]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, TCXO_BUF_CTRL, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully SYSTEM_OUT[PWRRGTON_CON]: 0x%x\n", val & PWRRGTON_CON); + + udelay(1000); + + /* disable PWR_REQ_F and TCXO_REQ_F interrupts */ + ret = regmap_update_bits(platform->pmureg, WLBT_INT_EN, + PWR_REQ_F, 0x0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_INT_EN[PWR_REQ_F]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_INT_EN, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_INT_EN[PWR_REQ_F]: 0x%x\n", val); + + ret = regmap_update_bits(platform->pmureg, WLBT_INT_EN, + TCXO_REQ_F, 0x0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_INT_EN[TCXO_REQ_F]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_INT_EN, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_INT_EN[TCXO_REQ_F]: 0x%x\n", val); + + /* Active interrupt disable */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, + WLBT_ACTIVE_EN, 0x0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CTRL_NS[WLBT_ACTIVE_EN]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CTRL_NS, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CTRL_NS[WLBT_ACTIVE_EN]: 0x%x\n", val); + + /* Power Down */ + ret = regmap_write_bits(platform->pmureg, WLBT_CONFIGURATION, + LOCAL_PWR_CFG, 0x0); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to update WLBT_CONFIGURATION[LOCAL_PWR_CFG]: %d\n", ret); + return ret; + } + regmap_read(platform->pmureg, WLBT_CONFIGURATION, &val); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "updated successfully WLBT_CONFIGURATION[LOCAL_PWR_CFG]: 0x%x\n", val); + + /* Wait for power Off WLBT_STATUS[STATUS] = 0 */ + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(platform->pmureg, WLBT_STATUS, &val); + val &= STATUS; + if (val == 0) { + SCSC_TAG_INFO(PLAT_MIF, "WLBT_STATUS 0x%x\n", val); + /* re affirming power down by reading WLBT_STATES */ + /* STATES[7:0] = 0x80 for Power Down */ + regmap_read(platform->pmureg, WLBT_STATES, &val); + SCSC_TAG_INFO(PLAT_MIF, "Power down complete: WLBT_STATES 0x%x\n", val); + + return 0; /* OK - return */ + } + } while (time_before(jiffies, timeout)); + + /* Timed out */ + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Timeout waiting for WLBT_STATUS status\n"); + + regmap_read(platform->pmureg, WLBT_STATUS, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_STATUS 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_DEBUG, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_DEBUG 0x%x\n", val); + regmap_read(platform->pmureg, WLBT_STATES, &val); + SCSC_TAG_INFO(PLAT_MIF, "WLBT_STATES 0x%x\n", val); + return -ETIME; +} + +/* reset=0 - release from reset */ +/* reset=1 - hold reset */ +static int platform_mif_reset(struct scsc_mif_abs *interface, bool reset) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 ret = 0; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (enable_platform_mif_arm_reset || !reset) { + if (!reset) { /* Release from reset */ +#ifdef CONFIG_ARCH_EXYNOS + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "SOC_VERSION: product_id 0x%x, rev 0x%x\n", + exynos_soc_info.product_id, exynos_soc_info.revision); +#endif + power_supplies_on(platform); + + ret = platform_mif_pmu_reset_release(interface); + } else { + /* Put back into reset */ + ret = platform_mif_pmu_reset_assert(interface); + } + } else + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Not resetting ARM Cores - enable_platform_mif_arm_reset: %d\n", + enable_platform_mif_arm_reset); + return ret; +} + +static void __iomem *platform_mif_map_region(unsigned long phys_addr, size_t size) +{ + int i; + struct page **pages; + void *vmem; + + size = PAGE_ALIGN(size); + + pages = kmalloc((size >> PAGE_SHIFT) * sizeof(*pages), GFP_KERNEL); + if (!pages) + return NULL; + + /* Map NORMAL_NC pages with kernel virtual space */ + for (i = 0; i < (size >> PAGE_SHIFT); i++) { + pages[i] = phys_to_page(phys_addr); + phys_addr += PAGE_SIZE; + } + + vmem = vmap(pages, size >> PAGE_SHIFT, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + + kfree(pages); + return (void __iomem *)vmem; +} + +static void platform_mif_unmap_region(void *vmem) +{ + vunmap(vmem); +} + +static void *platform_mif_map(struct scsc_mif_abs *interface, size_t *allocated) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u8 i; + + if (allocated) + *allocated = 0; + + platform->mem = + platform_mif_map_region(platform->mem_start, platform->mem_size); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Error remaping shared memory\n"); + return NULL; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Map: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + + /* Initialise MIF registers with documented defaults */ + /* MBOXes */ + for (i = 0; i < NUM_MBOX_PLAT; i++) + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(ISSR(i)), 0x00000000); + + /* MRs */ /*1's - set all as Masked */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); + /* CRs */ /* 1's - clear all the interrupts */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); + +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_disable_irq == true) { + if (allocated) + *allocated = platform->mem_size; + return platform->mem; + } +#endif + /* register interrupts */ + if (platform_mif_register_irq(platform)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unmap: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + platform_mif_unmap_region(platform->mem); + return NULL; + } + + if (allocated) + *allocated = platform->mem_size; + /* Set the CR4 base address in Mailbox??*/ + return platform->mem; +} + +/* HERE: Not sure why mem is passed in - its stored in platform - as it should be */ +static void platform_mif_unmap(struct scsc_mif_abs *interface, void *mem) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + /* Avoid unused parameter error */ + (void)mem; + + /* MRs */ /*1's - set all as Masked */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR1), 0x0000ffff); + +#ifdef CONFIG_SCSC_CHV_SUPPORT + /* Restore PIO changed by Maxwell subsystem */ + if (chv_disable_irq == false) + /* Unregister IRQs */ + platform_mif_unregister_irq(platform); +#else + platform_mif_unregister_irq(platform); +#endif + /* CRs */ /* 1's - clear all the interrupts */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), 0xffff0000); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR1), 0x0000ffff); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unmap: virt %p phys %lx\n", platform->mem, (uintptr_t)platform->mem_start); + platform_mif_unmap_region(platform->mem); + platform->mem = NULL; +} + +static u32 platform_mif_irq_bit_mask_status_get(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)) >> 16; + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Getting INTMR0: 0x%x\n", val); + return val; +} + +static u32 platform_mif_irq_get(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + + /* Function has to return the interrupts that are enabled *AND* not masked */ + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR0)) >> 16; + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Getting INT-INTMSR0: 0x%x\n", val); + + return val; +} + +static void platform_mif_irq_bit_set(struct scsc_mif_abs *interface, int bit_num, enum scsc_mif_abs_target target) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 reg; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + + reg = INTGR1; + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(reg), (1 << bit_num)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTGR1: bit %d on target %d\n", bit_num, target); +} + +static void platform_mif_irq_bit_clear(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + /* WRITE : 1 = Clears Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTCR0), ((1 << bit_num) << 16)); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTCR0: bit %d\n", bit_num); +} + +static void platform_mif_irq_bit_mask(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + unsigned long flags; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + /* WRITE : 1 = Mask Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val | ((1 << bit_num) << 16)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Setting INTMR0: 0x%x bit %d\n", val | (1 << bit_num), bit_num); +} + +static void platform_mif_irq_bit_unmask(struct scsc_mif_abs *interface, int bit_num) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 val; + unsigned long flags; + + if (bit_num >= 16) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Incorrect INT number: %d\n", bit_num); + return; + } + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + /* WRITE : 0 = Unmask Interrupt */ + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val & ~((1 << bit_num) << 16)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "UNMASK Setting INTMR0: 0x%x bit %d\n", val & ~((1 << bit_num) << 16), bit_num); +} + +/* Return the contents of the mask register */ +static u32 __platform_mif_irq_bit_mask_read(struct platform_mif *platform) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + val = platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0)); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Read INTMR0: 0x%x\n", val); + + return val; +} + +/* Write the mask register, destroying previous contents */ +static void __platform_mif_irq_bit_mask_write(struct platform_mif *platform, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform_mif_reg_write(platform, MAILBOX_WLBT_REG(INTMR0), val); + spin_unlock_irqrestore(&platform->mif_spinlock, flags); + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "Write INTMR0: 0x%x\n", val); +} + +static void platform_mif_irq_reg_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif int handler %pS in %p %p\n", handler, platform, interface); + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform->r4_handler = handler; + platform->irq_dev = dev; + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_irq_unreg_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering mif int handler %pS\n", interface); + spin_lock_irqsave(&platform->mif_spinlock, flags); + platform->r4_handler = platform_mif_irq_default_handler; + platform->irq_dev = NULL; + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_irq_reg_reset_request_handler(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif reset_request int handler %pS in %p %p\n", handler, platform, interface); + platform->reset_request_handler = handler; + platform->irq_reset_request_dev = dev; + if (atomic_read(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt)) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, + "Default WDOG handler disabled by spurios IRQ...re-enabling.\n"); + enable_irq(platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_num); + atomic_set(&platform->wlbt_irq[PLATFORM_MIF_WDOG].irq_disabled_cnt, 0); + } +} + +static void platform_mif_irq_unreg_reset_request_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "UnRegistering mif reset_request int handler %pS\n", interface); + platform->reset_request_handler = platform_mif_irq_reset_request_default_handler; + platform->irq_reset_request_dev = NULL; +} + +static void platform_mif_suspend_reg_handler(struct scsc_mif_abs *interface, + int (*suspend)(struct scsc_mif_abs *abs, void *data), + void (*resume)(struct scsc_mif_abs *abs, void *data), + void *data) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Registering mif suspend/resume handlers in %p %p\n", platform, interface); + platform->suspend_handler = suspend; + platform->resume_handler = resume; + platform->suspendresume_data = data; +} + +static void platform_mif_suspend_unreg_handler(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Unregistering mif suspend/resume handlers in %p %p\n", platform, interface); + platform->suspend_handler = NULL; + platform->resume_handler = NULL; + platform->suspendresume_data = NULL; +} + +static u32 *platform_mif_get_mbox_ptr(struct scsc_mif_abs *interface, u32 mbox_index) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + u32 *addr; + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "mbox_index 0x%x\n", mbox_index); + addr = platform->base + MAILBOX_WLBT_REG(ISSR(mbox_index)); + return addr; +} + +static int platform_mif_get_mifram_ref(struct scsc_mif_abs *interface, void *ptr, scsc_mifram_ref *ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return -ENOMEM; + } + + /* Check limits! */ + if (ptr >= (platform->mem + platform->mem_size)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Unable to get pointer reference\n"); + return -ENOMEM; + } + + *ref = (scsc_mifram_ref)((uintptr_t)ptr - (uintptr_t)platform->mem); + + return 0; +} + +static void *platform_mif_get_mifram_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return NULL; + } + + /* Check limits */ + if (ref >= 0 && ref < platform->mem_size) + return (void *)((uintptr_t)platform->mem + (uintptr_t)ref); + else + return NULL; +} + +static void *platform_mif_get_mifram_phy_ptr(struct scsc_mif_abs *interface, scsc_mifram_ref ref) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + if (!platform->mem_start) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "Memory unmmaped\n"); + return NULL; + } + + return (void *)((uintptr_t)platform->mem_start + (uintptr_t)ref); +} + +static uintptr_t platform_mif_get_mif_pfn(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + return vmalloc_to_pfn(platform->mem); +} + +static struct device *platform_mif_get_mif_device(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + SCSC_TAG_DEBUG_DEV(PLAT_MIF, platform->dev, "\n"); + + return platform->dev; +} + +static void platform_mif_irq_clear(void) +{ + /* Implement if required */ +} + +static int platform_mif_read_register(struct scsc_mif_abs *interface, u64 id, u32 *val) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (id == SCSC_REG_READ_WLBT_STAT) { + regmap_read(platform->pmureg, WLBT_STAT, val); + return 0; + } + + return -EIO; +} + +static void platform_mif_dump_register(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + unsigned long flags; + + spin_lock_irqsave(&platform->mif_spinlock, flags); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTGR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTGR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTCR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTCR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTSR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTSR1))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR0 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR0))); + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "INTMSR1 0x%08x\n", platform_mif_reg_read(platform, MAILBOX_WLBT_REG(INTMSR1))); + + spin_unlock_irqrestore(&platform->mif_spinlock, flags); +} + +static void platform_mif_cleanup(struct scsc_mif_abs *interface) +{ +} + +static void platform_mif_restart(struct scsc_mif_abs *interface) +{ +} + +#ifdef CONFIG_OF_RESERVED_MEM +static int __init platform_mif_wifibt_if_reserved_mem_setup(struct reserved_mem *remem) +{ + SCSC_TAG_DEBUG(PLAT_MIF, "memory reserved: mem_base=%#lx, mem_size=%zd\n", + (unsigned long)remem->base, (size_t)remem->size); + + sharedmem_base = remem->base; + sharedmem_size = remem->size; + return 0; +} +RESERVEDMEM_OF_DECLARE(wifibt_if, "exynos,wifibt_if", platform_mif_wifibt_if_reserved_mem_setup); +#endif + +struct scsc_mif_abs *platform_mif_create(struct platform_device *pdev) +{ + struct scsc_mif_abs *platform_if; + struct platform_mif *platform = + (struct platform_mif *)devm_kzalloc(&pdev->dev, sizeof(struct platform_mif), GFP_KERNEL); + int err = 0; + u8 i = 0; + struct resource *reg_res; + +#ifdef CONFIG_SCSC_SMAPPER + u32 smapper_banks = 0; +#endif + + if (!platform) + return NULL; + + SCSC_TAG_INFO_DEV(PLAT_MIF, &pdev->dev, "Creating MIF platform device\n"); + + platform_if = &platform->interface; + + /* initialise interface structure */ + platform_if->destroy = platform_mif_destroy; + platform_if->get_uid = platform_mif_get_uid; + platform_if->reset = platform_mif_reset; + platform_if->map = platform_mif_map; + platform_if->unmap = platform_mif_unmap; + platform_if->irq_bit_set = platform_mif_irq_bit_set; + platform_if->irq_get = platform_mif_irq_get; + platform_if->irq_bit_mask_status_get = platform_mif_irq_bit_mask_status_get; + platform_if->irq_bit_clear = platform_mif_irq_bit_clear; + platform_if->irq_bit_mask = platform_mif_irq_bit_mask; + platform_if->irq_bit_unmask = platform_mif_irq_bit_unmask; + platform_if->irq_reg_handler = platform_mif_irq_reg_handler; + platform_if->irq_unreg_handler = platform_mif_irq_unreg_handler; + platform_if->irq_reg_reset_request_handler = platform_mif_irq_reg_reset_request_handler; + platform_if->irq_unreg_reset_request_handler = platform_mif_irq_unreg_reset_request_handler; + platform_if->suspend_reg_handler = platform_mif_suspend_reg_handler; + platform_if->suspend_unreg_handler = platform_mif_suspend_unreg_handler; + platform_if->get_mbox_ptr = platform_mif_get_mbox_ptr; + platform_if->get_mifram_ptr = platform_mif_get_mifram_ptr; + platform_if->get_mifram_ref = platform_mif_get_mifram_ref; + platform_if->get_mifram_pfn = platform_mif_get_mif_pfn; + platform_if->get_mifram_phy_ptr = platform_mif_get_mifram_phy_ptr; + platform_if->get_mif_device = platform_mif_get_mif_device; + platform_if->irq_clear = platform_mif_irq_clear; + platform_if->mif_dump_registers = platform_mif_dump_register; + platform_if->mif_read_register = platform_mif_read_register; + platform_if->mif_cleanup = platform_mif_cleanup; + platform_if->mif_restart = platform_mif_restart; +#ifdef CONFIG_SCSC_SMAPPER + platform_if->mif_smapper_get_mapping = platform_mif_smapper_get_mapping; + platform_if->mif_smapper_get_bank_info = platform_mif_smapper_get_bank_info; + platform_if->mif_smapper_write_sram = platform_mif_smapper_write_sram; + platform_if->mif_smapper_configure = platform_mif_smapper_configure; + platform_if->mif_smapper_get_bank_base_address = platform_mif_smapper_get_bank_base_address; +#endif +#ifdef CONFIG_SCSC_QOS + platform_if->mif_pm_qos_add_request = platform_mif_pm_qos_add_request; + platform_if->mif_pm_qos_update_request = platform_mif_pm_qos_update_request; + platform_if->mif_pm_qos_remove_request = platform_mif_pm_qos_remove_request; +#endif + /* Update state */ + platform->pdev = pdev; + platform->dev = &pdev->dev; + + platform->r4_handler = platform_mif_irq_default_handler; + platform->irq_dev = NULL; + platform->reset_request_handler = platform_mif_irq_reset_request_default_handler; + platform->irq_reset_request_dev = NULL; + platform->suspend_handler = NULL; + platform->resume_handler = NULL; + platform->suspendresume_data = NULL; + +#ifdef CONFIG_OF_RESERVED_MEM + platform->mem_start = sharedmem_base; + platform->mem_size = sharedmem_size; +#else + /* If CONFIG_OF_RESERVED_MEM is not defined, sharedmem values should be + * parsed from the scsc_wifibt binding + */ + if (of_property_read_u32(pdev->dev.of_node, "sharedmem-base", &sharedmem_base)) { + err = -EINVAL; + goto error_exit; + } + platform->mem_start = sharedmem_base; + + if (of_property_read_u32(pdev->dev.of_node, "sharedmem-size", &sharedmem_size)) { + err = -EINVAL; + goto error_exit; + } + platform->mem_size = sharedmem_size; +#endif +#ifdef CONFIG_SCSC_SMAPPER + platform->smapper = NULL; +#endif + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->mem_start 0x%x platform->mem_size 0x%x\n", + (u32)platform->mem_start, (u32)platform->mem_size); + if (platform->mem_start == 0) + SCSC_TAG_WARNING_DEV(PLAT_MIF, platform->dev, "platform->mem_start is 0"); + + if (platform->mem_size == 0) { + /* We return return if mem_size is 0 as it does not make any sense. + * This may be an indication of an incorrect platform device binding. + */ + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, "platform->mem_size is 0"); + err = -EINVAL; + goto error_exit; + } + + /* Memory resource - Phys Address of MAILBOX_WLBT register map */ + reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!reg_res) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error getting mem resource for MAILBOX_WLBT\n"); + err = -ENOENT; + goto error_exit; + } + + platform->reg_start = reg_res->start; + platform->reg_size = resource_size(reg_res); + + platform->base = + devm_ioremap_nocache(platform->dev, reg_res->start, resource_size(reg_res)); + + if (!platform->base) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Error mapping register region\n"); + err = -EBUSY; + goto error_exit; + } + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "platform->reg_start %lx size %x base %p\n", + (uintptr_t)platform->reg_start, (u32)platform->reg_size, platform->base); + + /* Get the 4 IRQ resources */ + for (i = 0; i < 4; i++) { + struct resource *irq_res; + int irqtag; + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!irq_res) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "No IRQ resource at index %d\n", i); + err = -ENOENT; + goto error_exit; + } + + if (!strcmp(irq_res->name, "MBOX")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "MBOX irq %d flag 0x%x\n", + (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_MBOX; + } else if (!strcmp(irq_res->name, "ALIVE")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "ALIVE irq %d flag 0x%x\n", + (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_ALIVE; + } else if (!strcmp(irq_res->name, "WDOG")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WDOG irq %d flag 0x%x\n", + (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_WDOG; + } else if (!strcmp(irq_res->name, "CFG_REQ")) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "CFG_REQ irq %d flag 0x%x\n", + (u32)irq_res->start, (u32)irq_res->flags); + irqtag = PLATFORM_MIF_CFG_REQ; + } else { + SCSC_TAG_ERR_DEV(PLAT_MIF, &pdev->dev, "Invalid irq res name: %s\n", + irq_res->name); + err = -EINVAL; + goto error_exit; + } + platform->wlbt_irq[irqtag].irq_num = irq_res->start; + platform->wlbt_irq[irqtag].flags = (irq_res->flags & IRQF_TRIGGER_MASK); + atomic_set(&platform->wlbt_irq[irqtag].irq_disabled_cnt, 0); + } + + /* PMU reg map - syscon */ + platform->pmureg = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(platform->pmureg)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "syscon regmap lookup failed. Aborting. %ld\n", + PTR_ERR(platform->pmureg)); + err = -EINVAL; + goto error_exit; + } + + /* Completion event and state used to indicate CFG_REQ IRQ occurred */ + init_completion(&platform->cfg_ack); + platform->boot_state = WLBT_BOOT_IN_RESET; + + /* BAAW_P_WLBT */ + platform->baaw_p_wlbt = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,baaw_p_wlbt-syscon-phandle"); + if (IS_ERR(platform->baaw_p_wlbt)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "baaw_p_wlbt regmap lookup failed. Aborting. %ld\n", + PTR_ERR(platform->baaw_p_wlbt)); + err = -EINVAL; + goto error_exit; + } + + /* DBUS_BAAW */ + platform->dbus_baaw = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,dbus_baaw-syscon-phandle"); + if (IS_ERR(platform->dbus_baaw)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "dbus_baaw regmap lookup failed. Aborting. %ld\n", + PTR_ERR(platform->dbus_baaw)); + err = -EINVAL; + goto error_exit; + } + + /* PBUS_BAAW */ + platform->pbus_baaw = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,pbus_baaw-syscon-phandle"); + if (IS_ERR(platform->pbus_baaw)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "pbus_baaw regmap lookup failed. Aborting. %ld\n", + PTR_ERR(platform->pbus_baaw)); + err = -EINVAL; + goto error_exit; + } + + /* WLBT_REMAP */ + platform->wlbt_remap = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,wlbt_remap-syscon-phandle"); + if (IS_ERR(platform->wlbt_remap)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "wlbt_remap regmap lookup failed. Aborting. %ld\n", + PTR_ERR(platform->wlbt_remap)); + err = -EINVAL; + goto error_exit; + } + + /* BOOT_CFG */ + platform->boot_cfg = syscon_regmap_lookup_by_phandle(platform->dev->of_node, + "samsung,boot_cfg-syscon-phandle"); + if (IS_ERR(platform->boot_cfg)) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "boot_cfg regmap lookup failed. Aborting. %ld\n", + PTR_ERR(platform->boot_cfg)); + err = -EINVAL; + goto error_exit; + } + +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER parsing */ + if (!of_property_read_u32(pdev->dev.of_node, "smapper_num_banks", &smapper_banks)) + platform_mif_parse_smapper(platform, platform->dev->of_node, smapper_banks); + +#endif +#ifdef CONFIG_SCSC_QOS + platform_mif_parse_qos(platform, platform->dev->of_node); +#endif + /* Initialize spinlock */ + spin_lock_init(&platform->mif_spinlock); + + return platform_if; + +error_exit: + devm_kfree(&pdev->dev, platform); + return NULL; +} + +void platform_mif_destroy_platform(struct platform_device *pdev, struct scsc_mif_abs *interface) +{ +} + +struct platform_device *platform_mif_get_platform_dev(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + BUG_ON(!interface || !platform); + + return platform->pdev; +} + +struct device *platform_mif_get_dev(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + BUG_ON(!interface || !platform); + + return platform->dev; +} + +/* Preserve MIF registers during suspend. + * If all users of the MIF (AP, mx140, CP, etc) release it, the registers + * will lose their values. Save the useful subset here. + * + * Assumption: the AP will not change the register values between the suspend + * and resume handlers being called! + */ +static void platform_mif_reg_save(struct platform_mif *platform) +{ + platform->mif_preserve.irq_bit_mask = __platform_mif_irq_bit_mask_read(platform); +} + +/* Restore MIF registers that may have been lost during suspend */ +static void platform_mif_reg_restore(struct platform_mif *platform) +{ + __platform_mif_irq_bit_mask_write(platform, platform->mif_preserve.irq_bit_mask); +} + +int platform_mif_suspend(struct scsc_mif_abs *interface) +{ + int r = 0; + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + + if (platform->suspend_handler) + r = platform->suspend_handler(interface, platform->suspendresume_data); + + /* Save the MIF registers. + * This must be done last as the suspend_handler may use the MIF + */ + platform_mif_reg_save(platform); + + return r; +} + +void platform_mif_resume(struct scsc_mif_abs *interface) +{ + struct platform_mif *platform = platform_mif_from_mif_abs(interface); + s32 ret; + + /* Restore the MIF registers. + * This must be done first as the resume_handler may use the MIF. + */ + platform_mif_reg_restore(platform); + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "Clear WLBT_ACTIVE_CLR flag\n"); + /* Clear WLBT_ACTIVE_CLR flag in WLBT_CTRL_NS */ + ret = regmap_update_bits(platform->pmureg, WLBT_CTRL_NS, WLBT_ACTIVE_CLR, 1); + if (ret < 0) { + SCSC_TAG_ERR_DEV(PLAT_MIF, platform->dev, + "Failed to Set WLBT_CTRL_NS[WLBT_ACTIVE_CLR]: %d\n", ret); + } + + if (platform->resume_handler) + platform->resume_handler(interface, platform->suspendresume_data); +} + + +/* Temporary workaround to power up slave PMIC LDOs before FW APM/WLBT signalling + * is complete + */ +static void power_supplies_on(struct platform_mif *platform) +{ + struct i2c_client i2c; + + /* HACK: Note only addr field is needed by s2mpu11_write_reg() */ + i2c.addr = 0x1; + + /* The APM IPC in FW will be used instead */ + if (disable_apm_setup) { + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WLBT LDOs firmware controlled\n"); + return; + } + + SCSC_TAG_INFO_DEV(PLAT_MIF, platform->dev, "WLBT LDOs on (PMIC i2c_addr = 0x%x)\n", i2c.addr); + + /* SLAVE PMIC + * echo 0x22 > /sys/kernel/debug/s2mpu11-regs/i2caddr + * echo 0xE0 > /sys/kernel/debug/s2mpu11-regs/i2cdata + * + * echo 0x23 > /sys/kernel/debug/s2mpu11-regs/i2caddr + * echo 0xE8 > /sys/kernel/debug/s2mpu11-regs/i2cdata + * + * echo 0x24 > /sys/kernel/debug/s2mpu11-regs/i2caddr + * echo 0xEC > /sys/kernel/debug/s2mpu11-regs/i2cdata + * + * echo 0x25 > /sys/kernel/debug/s2mpu11-regs/i2caddr + * echo 0xEC > /sys/kernel/debug/s2mpu11-regs/i2cdata + * + * echo 0x26 > /sys/kernel/debug/s2mpu11-regs/i2caddr + * echo 0xFC > /sys/kernel/debug/s2mpu11-regs/i2cdata + * + * echo 0x27 > /sys/kernel/debug/s2mpu11-regs/i2caddr + * echo 0xFC > /sys/kernel/debug/s2mpu11-regs/i2cdata + */ + + s2mpu11_write_reg(&i2c, 0x22, 0xe0); + s2mpu11_write_reg(&i2c, 0x23, 0xe8); + s2mpu11_write_reg(&i2c, 0x24, 0xec); + s2mpu11_write_reg(&i2c, 0x25, 0xec); + s2mpu11_write_reg(&i2c, 0x26, 0xfc); + s2mpu11_write_reg(&i2c, 0x27, 0xfc); +} diff --git a/drivers/misc/samsung/scsc/platform_mif_module.c b/drivers/misc/samsung/scsc/platform_mif_module.c new file mode 100644 index 000000000000..f66db18bec29 --- /dev/null +++ b/drivers/misc/samsung/scsc/platform_mif_module.c @@ -0,0 +1,252 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "platform_mif_module.h" +#include "platform_mif.h" + +/* Implements */ +#include "scsc_mif_abs.h" + +/* Variables */ +struct mif_abs_node { + struct list_head list; + struct scsc_mif_abs *mif_abs; +}; + +struct mif_driver_node { + struct list_head list; + struct scsc_mif_abs_driver *driver; +}; + +struct mif_mmap_node { + struct list_head list; + struct scsc_mif_mmap_driver *driver; +}; + +static struct platform_mif_module { + struct list_head mif_abs_list; + struct list_head mif_driver_list; + struct list_head mif_mmap_list; +} mif_module = { + .mif_abs_list = LIST_HEAD_INIT(mif_module.mif_abs_list), + .mif_driver_list = LIST_HEAD_INIT(mif_module.mif_driver_list), + .mif_mmap_list = LIST_HEAD_INIT(mif_module.mif_mmap_list), +}; + +/* Private Functions */ + +static void platform_mif_module_probe_registered_clients(struct scsc_mif_abs *mif_abs) +{ + struct mif_driver_node *mif_driver_node, *next; + bool driver_registered = false; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(mif_driver_node, next, &mif_module.mif_driver_list, list) { + mif_driver_node->driver->probe(mif_driver_node->driver, mif_abs); + driver_registered = true; + } +} + +static int platform_mif_module_probe(struct platform_device *pdev) +{ + struct mif_abs_node *mif_node; + struct scsc_mif_abs *mif_abs; + + /* TODO: ADD EARLY BOARD INITIALIZATIONS IF REQUIRED */ + /* platform_mif_init(); */ + + mif_node = kzalloc(sizeof(*mif_node), GFP_KERNEL); + if (!mif_node) + return -ENODEV; + + mif_abs = platform_mif_create(pdev); + if (!mif_abs) { + SCSC_TAG_ERR(PLAT_MIF, "Error creating platform interface\n"); + kfree(mif_node); + return -ENODEV; + } + /* Add node */ + mif_node->mif_abs = mif_abs; + list_add_tail(&mif_node->list, &mif_module.mif_abs_list); + platform_mif_module_probe_registered_clients(mif_abs); + + return 0; +} + +static int platform_mif_module_remove(struct platform_device *pdev) +{ + struct mif_abs_node *mif_node, *next; + bool match = false; + + /* Remove node */ + list_for_each_entry_safe(mif_node, next, &mif_module.mif_abs_list, list) { + if (platform_mif_get_platform_dev(mif_node->mif_abs) == pdev) { + match = true; + platform_mif_destroy_platform(pdev, mif_node->mif_abs); + list_del(&mif_node->list); + kfree(mif_node); + } + } + if (match == false) + SCSC_TAG_ERR(PLAT_MIF, "No match for given scsc_mif_abs\n"); + + return 0; +} + +static int platform_mif_module_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mif_abs_node *mif_node, *next; + int r; + + SCSC_TAG_INFO(PLAT_MIF, "\n"); + + /* Traverse mif_abs list for this platform_device to suspend */ + list_for_each_entry_safe(mif_node, next, &mif_module.mif_abs_list, list) { + if (platform_mif_get_platform_dev(mif_node->mif_abs) == pdev) { + /* Signal suspend, client can refuse */ + r = platform_mif_suspend(mif_node->mif_abs); + if (r) { + SCSC_TAG_INFO(PLAT_MIF, "%d\n", r); + return r; + } + } + } + return 0; +} + +static int platform_mif_module_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mif_abs_node *mif_node, *next; + + SCSC_TAG_INFO(PLAT_MIF, "\n"); + + /* Traverse mif_abs list for this platform_device to resume */ + list_for_each_entry_safe(mif_node, next, &mif_module.mif_abs_list, list) { + if (platform_mif_get_platform_dev(mif_node->mif_abs) == pdev) { + /* Signal resume */ + platform_mif_resume(mif_node->mif_abs); + } + } + return 0; +} + +static const struct dev_pm_ops platform_mif_pm_ops = { + .suspend = platform_mif_module_suspend, + .resume = platform_mif_module_resume, +}; + +static const struct of_device_id scsc_wifibt[] = { + { .compatible = "samsung,scsc_wifibt" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, scsc_wifibt); + +static struct platform_driver platform_mif_driver = { + .probe = platform_mif_module_probe, + .remove = platform_mif_module_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &platform_mif_pm_ops, + .of_match_table = of_match_ptr(scsc_wifibt), + }, +}; + +/* Choose when the driver should be probed */ +#if 1 +module_platform_driver(platform_mif_driver); +#else +static int platform_mif_init(void) +{ + SCSC_TAG_INFO(PLAT_MIF, "register platform driver\n"); + return platform_driver_register(&platform_mif_driver); +} +core_initcall(platform_mif_init); +#endif + +/* Public Functions */ +void scsc_mif_abs_register(struct scsc_mif_abs_driver *driver) +{ + struct mif_driver_node *mif_driver_node; + struct mif_abs_node *mif_node; + + /* Add node in driver linked list */ + mif_driver_node = kzalloc(sizeof(*mif_driver_node), GFP_KERNEL); + if (!mif_driver_node) + return; + + mif_driver_node->driver = driver; + list_add_tail(&mif_driver_node->list, &mif_module.mif_driver_list); + + /* Traverse Linked List for each mif_abs node */ + list_for_each_entry(mif_node, &mif_module.mif_abs_list, list) { + driver->probe(driver, mif_node->mif_abs); + } +} +EXPORT_SYMBOL(scsc_mif_abs_register); + +void scsc_mif_abs_unregister(struct scsc_mif_abs_driver *driver) +{ + struct mif_driver_node *mif_driver_node, *next; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(mif_driver_node, next, &mif_module.mif_driver_list, list) { + if (mif_driver_node->driver == driver) { + list_del(&mif_driver_node->list); + kfree(mif_driver_node); + } + } +} +EXPORT_SYMBOL(scsc_mif_abs_unregister); + +/* Register a mmap - debug driver - for this specific transport*/ +void scsc_mif_mmap_register(struct scsc_mif_mmap_driver *mmap_driver) +{ + struct mif_mmap_node *mif_mmap_node; + struct mif_abs_node *mif_node; + + /* Add node in driver linked list */ + mif_mmap_node = kzalloc(sizeof(*mif_mmap_node), GFP_KERNEL); + if (!mif_mmap_node) + return; + + mif_mmap_node->driver = mmap_driver; + list_add_tail(&mif_mmap_node->list, &mif_module.mif_mmap_list); + + /* Traverse Linked List for each mif_abs node */ + list_for_each_entry(mif_node, &mif_module.mif_abs_list, list) { + mmap_driver->probe(mmap_driver, mif_node->mif_abs); + } +} +EXPORT_SYMBOL(scsc_mif_mmap_register); + +/* Unregister a mmap - debug driver - for this specific transport*/ +void scsc_mif_mmap_unregister(struct scsc_mif_mmap_driver *mmap_driver) +{ + struct mif_mmap_node *mif_mmap_node, *next; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(mif_mmap_node, next, &mif_module.mif_mmap_list, list) { + if (mif_mmap_node->driver == mmap_driver) { + list_del(&mif_mmap_node->list); + kfree(mif_mmap_node); + } + } +} +EXPORT_SYMBOL(scsc_mif_mmap_unregister); + +MODULE_DESCRIPTION("SCSC Platform device Maxwell MIF abstraction"); +MODULE_AUTHOR("SCSC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/samsung/scsc/platform_mif_module.h b/drivers/misc/samsung/scsc/platform_mif_module.h new file mode 100644 index 000000000000..7e2007aac37e --- /dev/null +++ b/drivers/misc/samsung/scsc/platform_mif_module.h @@ -0,0 +1,12 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __PLATFORM_MIF_MODULE_H +#define __PLATFORM_MIF_MODULE_H + +#define DRV_NAME "scsc_wlbt" + +#endif /* __PLATFORM_MIF_MODULE_H */ diff --git a/drivers/misc/samsung/scsc/scsc_app_msg.h b/drivers/misc/samsung/scsc/scsc_app_msg.h new file mode 100644 index 000000000000..af421ee8d064 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_app_msg.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SCSC_APP_MSG_H__ +#define __SCSC_APP_MSG_H__ + + +#define BCSP_CSTOPB_MASK 0x0001 +#define BCSP_PARENB_MASK 0x0002 +#define BCSP_PAREVEN_MASK 0x0004 +#define BCSP_CRTSCTS_MASK 0x0008 + +enum { + SCSC_APP_MSG_TYPE_APP_STARTED_REPLY = 0, + SCSC_APP_MSG_TYPE_GET_DB, + SCSC_APP_MSG_TYPE_GET_DB_REPLY, + SCSC_APP_MSG_TYPE_LD_REGISTER_LOW_RATE, + SCSC_APP_MSG_TYPE_LD_REGISTER_HIGH_RATE, + SCSC_APP_MSG_TYPE_LD_REGISTER_REPLY, + SCSC_APP_MSG_TYPE_LD_UNREGISTER, + SCSC_APP_MSG_TYPE_LD_UNREGISTER_BREAK, + SCSC_APP_MSG_TYPE_LD_UNREGISTER_REPLY, + SCSC_APP_MSG_TYPE_APP_EXIT, + SCSC_APP_MSG_TYPE_APP_EXIT_REPLY, + SCSC_APP_MSG_TYPE_SET_FAST_RATE, + SCSC_APP_MSG_TYPE_SET_FAST_RATE_REPLY, +}; + +enum { + SCSC_APP_MSG_STATUS_OK = 0, + SCSC_APP_MSG_STATUS_FAILURE, +}; + + +struct scsc_app_msg_req { + __u16 type; +}; + +struct scsc_app_msg_resp { + __u16 type; + __u16 status; + __u32 len; + __u8 data[0]; +}; + +#endif /* __SCSC_APP_MSG_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_lerna.c b/drivers/misc/samsung/scsc/scsc_lerna.c new file mode 100644 index 000000000000..7818a4685327 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_lerna.c @@ -0,0 +1,276 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + + +#include +#include +#include +#include +#include /* Required for copy_to_user. */ +#include +#include + +#include + +#include "mxman.h" +#include "mxmgmt_transport_format.h" /* Required for MXMGR_MESSAGE_PAYLOAD_SIZE. */ + +#define DEVICE_NAME "lerna" +#define DEVICE_CLASS "scsc_config" +#define DEVICE_COUNT (1) + +static const void *scsc_lerna_pending; +#define SCSC_LERNA_WAIT_TIMEOUT (2000) +static DECLARE_COMPLETION(scsc_lerna_wait); + +/** + * MSMGR_MESSAGE_PAYLOAD_SIZE is not a nice power of 2, so use sizeof(msmgr_message) + * just for something more aesthetically pleasing. + */ +#define SCSC_LERNA_BUFFER_SIZE (sizeof(struct mxmgr_message)) +static uint8_t scsc_lerna_request_buffer[SCSC_LERNA_BUFFER_SIZE]; +static uint8_t scsc_lerna_response_buffer[SCSC_LERNA_BUFFER_SIZE]; + +static dev_t scsc_lerna_device_id; +static struct class *scsc_lerna_class_p; +static struct device *scsc_lerna_device_p; +static struct cdev scsc_lerna_cdev; + +static int scsc_lerna_chardev_open(struct inode *inodep, struct file *filep); +static ssize_t scsc_lerna_chardev_read(struct file *filep, char *buffer, size_t len, loff_t *offset); +static ssize_t scsc_lerna_chardev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset); +static int scsc_lerna_chardev_release(struct inode *inodep, struct file *filep); + +static struct file_operations scsc_lerna_fops = { + .open = scsc_lerna_chardev_open, + .read = scsc_lerna_chardev_read, + .write = scsc_lerna_chardev_write, + .release = scsc_lerna_chardev_release, +}; + +static atomic_t scsc_lerna_atomic; + +struct scsc_lerna_cmd_header { + uint8_t magic_number; /* Set to 0x08. */ + uint8_t cid; /* Action command identifier. */ + uint16_t payload_length; /* Payload length. 0 for value query. */ + uint16_t psid; /* PSID to query. */ + uint8_t row_index; /* Row index, or 0 for non-table querying. */ + uint8_t group_index; /* Group index, or 0 for default (group not assigned). */ +}; + +static int scsc_lerna_chardev_open(struct inode *inodep, struct file *filep) +{ + (void)inodep; + (void)filep; + + if (atomic_inc_return(&scsc_lerna_atomic) > 1) { + atomic_dec(&scsc_lerna_atomic); + /* Someone already has this open. Denied. */ + SCSC_TAG_DEBUG(LERNA, "character device busy, try again later.\n"); + return -EBUSY; + } + + SCSC_TAG_DEBUG(LERNA, "opening lerna character device.\n"); + return 0; +} + +static ssize_t scsc_lerna_chardev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) +{ + const struct scsc_lerna_cmd_header *header; + unsigned long wait_result; + ssize_t read_count; + int error_count; + + (void)filep; + (void)offset; + + wait_result = wait_for_completion_timeout(&scsc_lerna_wait, msecs_to_jiffies(SCSC_LERNA_WAIT_TIMEOUT)); + if (wait_result == 0) { + SCSC_TAG_ERR(LERNA, "read timeout; firmware not responding, or read without write.\n"); + return -ETIMEDOUT; + } + + if (!scsc_lerna_pending) { + /* Pointer is NULL, indicating that a reply hasn't been sent from firmware. */ + SCSC_TAG_DEBUG(LERNA, "pending reply is null.\n"); + return -ENOMSG; + } + + header = (const struct scsc_lerna_cmd_header *)(scsc_lerna_pending); + read_count = sizeof(struct scsc_lerna_cmd_header) + header->payload_length; + + /* Make sure there's enough space to read out the buffer. */ + if (len < read_count) { + SCSC_TAG_ERR(LERNA, "insufficient buffer space supplied for read.\n"); + return -ENOBUFS; + } + + error_count = copy_to_user(buffer, scsc_lerna_pending, read_count); + + if (error_count) { + SCSC_TAG_ERR(LERNA, "could not read from lerna character device.\n"); + return -EFAULT; + } + + SCSC_TAG_DEBUG(LERNA, "read buffer of size: %lu\n", read_count); + /* Value was read out, and is no longer considered valid. Need to write before another read. */ + scsc_lerna_pending = NULL; + return read_count; +} + +static ssize_t scsc_lerna_chardev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) +{ + SCSC_TAG_DEBUG(LERNA, "writing buffer of size: %lu\n", len); + /* At a minimum, any request (read or write) must include a command header. */ + if (len >= sizeof(struct scsc_lerna_cmd_header)) { + /* Header at least fits, but maybe a write value wants more... */ + if (len <= SCSC_LERNA_BUFFER_SIZE) { + copy_from_user(scsc_lerna_request_buffer, buffer, len); + mxman_lerna_send(NULL, scsc_lerna_request_buffer, len); + } else { + /* Message size too long, don't write anything. */ + return -EMSGSIZE; + } + } else { + return -EBADR; + } + + return len; +} + +static int scsc_lerna_chardev_release(struct inode *inodep, struct file *filep) +{ + (void)inodep; + (void)filep; + if (atomic_read(&scsc_lerna_atomic) == 0) { + SCSC_TAG_ALERT(LERNA, "character device release without open.\n"); + } else { + /* Done with the character device, release the lock on it. */ + atomic_dec(&scsc_lerna_atomic); + } + + SCSC_TAG_DEBUG(LERNA, "lerna character device closed.\n"); + return 0; +} + + +int scsc_lerna_init(void) +{ + int result; + + /** + * Reset important globals to some kind of sane value. This should be done + * whenever the module is loaded explicitly to be sure global values haven't + * been previously trashed. + */ + scsc_lerna_device_id = 0; + scsc_lerna_class_p = NULL; + scsc_lerna_device_p = NULL; + + /* Make sure to initialise the atomic used to lock char device access. */ + atomic_set(&scsc_lerna_atomic, 0); + + /** + * Allocate device id(s) for the character device. Use alloc_register_chrdev + * because this is the new way of doing things, and it will dynamically allocate + * a major number. Returns non-zero on failure. + */ + result = alloc_chrdev_region(&scsc_lerna_device_id, 0, DEVICE_COUNT, DEVICE_NAME); + if (result) { + /* Failure to register char dev, auto fail to initialise module. */ + SCSC_TAG_ALERT(LERNA, "lerna failed to register character device.\n"); + return result; + } + + scsc_lerna_class_p = class_create(THIS_MODULE, DEVICE_CLASS); + if (IS_ERR(scsc_lerna_class_p)) { + /* Could not create class, failure, remember to unregister device id(s). */ + unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); + SCSC_TAG_ALERT(LERNA, "lerna failed to create character class.\n"); + return PTR_ERR(scsc_lerna_class_p); + } + + scsc_lerna_device_p = device_create(scsc_lerna_class_p, NULL, scsc_lerna_device_id, NULL, DEVICE_NAME); + if (IS_ERR(scsc_lerna_device_p)) { + class_destroy(scsc_lerna_class_p); + unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); + SCSC_TAG_ALERT(LERNA, "lerna failed to create character device.\n"); + return PTR_ERR(scsc_lerna_device_p); + } + + /** + * At this point, the device is registered, along with class definition. The character device + * itself can now be initialised to provide the kernel with callback information for various + * actions taken on the device. + */ + cdev_init(&scsc_lerna_cdev, &scsc_lerna_fops); + scsc_lerna_cdev.owner = THIS_MODULE; + + result = cdev_add(&scsc_lerna_cdev, scsc_lerna_device_id, DEVICE_COUNT); + if (result) { + /* Failure to add character device to file system. */ + cdev_del(&scsc_lerna_cdev); + class_destroy(scsc_lerna_class_p); + unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); + SCSC_TAG_ALERT(LERNA, "lerna failed to add character device.\n"); + return result; + } + /* At this point, the cdev is live and can be used. */ + + SCSC_TAG_INFO(LERNA, "lerna intialisation complete.\n"); + return 0; /* 0 for module loaded, non-zero for module load failure. */ +} + +void scsc_lerna_deinit(void) +{ + /* Character device needs deleting. */ + cdev_del(&scsc_lerna_cdev); + + /* Destroy device. */ + device_destroy(scsc_lerna_class_p, scsc_lerna_device_id); + + /* Unregister the device class. Not sure if this means that a register earlier is required. */ + class_unregister(scsc_lerna_class_p); + + /* Destroy created class. Be careful of the order this is called in. */ + class_destroy(scsc_lerna_class_p); + + /** + * Don't forget to unregister device id(s). Major number is dynamically allocated, + * so the base id is remembered and passed along to the unregister here. + */ + unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); + + SCSC_TAG_INFO(LERNA, "lerna shutdown complete.\n"); +} + +void scsc_lerna_response(const void *message) +{ + /** + * Buffer the response from the firmware so that future messages from firmware + * don't overwrite this accidentally. This means async messages are allowed while + * waiting for the character device read from userspace, without impacting lerna's + * request/response communications. + */ + const struct scsc_lerna_cmd_header *header; + ssize_t read_count; + + if (message != NULL) { + header = (const struct scsc_lerna_cmd_header *)(message); + read_count = sizeof(struct scsc_lerna_cmd_header) + header->payload_length; + + if (read_count <= SCSC_LERNA_BUFFER_SIZE) { + memcpy(scsc_lerna_response_buffer, message, read_count); + scsc_lerna_pending = scsc_lerna_response_buffer; + } else { + SCSC_TAG_DEBUG(LERNA, "readout too large for response buffering.\n"); + /* No response possible, let the userspace application deal with it. */ + scsc_lerna_pending = NULL; + } + } + complete(&scsc_lerna_wait); +} diff --git a/drivers/misc/samsung/scsc/scsc_lerna.h b/drivers/misc/samsung/scsc/scsc_lerna.h new file mode 100644 index 000000000000..98fd963888cf --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_lerna.h @@ -0,0 +1,16 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _SCSC_LERNA_H +#define _SCSC_LERNA_H + +int scsc_lerna_init(void); + +void scsc_lerna_deinit(void); + +void scsc_lerna_response(const void *message); + +#endif /* _SCSC_LERNA_H */ diff --git a/drivers/misc/samsung/scsc/scsc_log_collector.c b/drivers/misc/samsung/scsc/scsc_log_collector.c new file mode 100644 index 000000000000..b45dffc92448 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_log_collector.c @@ -0,0 +1,538 @@ +/******************************************************************************** + * + * Copyright (c) 2016 - 2019 Samsung Electronics Co., Ltd. All rights reserved. + * + ********************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "scsc_log_collector_proc.h" +#include "scsc_log_collector_mmap.h" +#include +#include "mxlogger.h" + +#ifdef CONFIG_SCSC_WLBTD +#include "scsc_wlbtd.h" +#endif + +#define SCSC_NUM_CHUNKS_SUPPORTED 13 + +#define TO_RAM 0 +#define TO_FILE 1 +/* Add-remove supported chunks on this kernel */ +static u8 chunk_supported_sbl[SCSC_NUM_CHUNKS_SUPPORTED] = { + SCSC_LOG_CHUNK_SYNC, + SCSC_LOG_CHUNK_IMP, + SCSC_LOG_CHUNK_MXL, + SCSC_LOG_CHUNK_UDI, + SCSC_LOG_CHUNK_BT_HCF, + SCSC_LOG_CHUNK_WLAN_HCF, + SCSC_LOG_CHUNK_HIP4_SAMPLER, + SCSC_LOG_RESERVED_COMMON, + SCSC_LOG_RESERVED_BT, + SCSC_LOG_RESERVED_WLAN, + SCSC_LOG_RESERVED_RADIO, + SCSC_LOG_MINIMOREDUMP, + SCSC_LOG_CHUNK_LOGRING, +}; + +static int scsc_log_collector_collect(enum scsc_log_reason reason, u16 reason_code); + +static atomic_t in_collection; + +/* Collect logs in an intermediate buffer to be collected at later time (mmap or wq) */ +static bool collect_to_ram = true; +module_param(collect_to_ram, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(collect_to_ram, "Collect buffer in ram"); + +static char collection_dir_buf[256] = "/data/vendor/log/wifi"; +module_param_string(collection_target_directory, collection_dir_buf, sizeof(collection_dir_buf), S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(collection_target_directory, "Specify collection target directory"); + +static bool sable_collection_off; +static int sable_collection_off_set_param_cb(const char *val, + const struct kernel_param *kp) +{ + bool nval; + + if (!val || strtobool(val, &nval)) + return -EINVAL; + + if (sable_collection_off ^ nval) { + sable_collection_off = nval; + mxlogger_set_enabled_status(!sable_collection_off); + pr_info("Sable Log Collection is now %sABLED.\n", + sable_collection_off ? "DIS" : "EN"); + } + return 0; +} + +/** + * As described in struct kernel_param+ops the _get method: + * -> returns length written or -errno. Buffer is 4k (ie. be short!) + */ +static int sable_collection_off_get_param_cb(char *buffer, + const struct kernel_param *kp) +{ + return sprintf(buffer, "%c", sable_collection_off ? 'Y' : 'N'); +} + +static struct kernel_param_ops sable_collection_off_ops = { + .set = sable_collection_off_set_param_cb, + .get = sable_collection_off_get_param_cb, +}; +module_param_cb(sable_collection_off, &sable_collection_off_ops, NULL, 0644); +MODULE_PARM_DESC(sable_collection_off, "Disable SABLE Log Collection. This will inhibit also MXLOGGER"); + +struct scsc_log_client { + struct list_head list; + struct scsc_log_collector_client *collect_client; +}; +static struct scsc_log_collector_list { struct list_head list; } scsc_log_collector_list = { + .list = LIST_HEAD_INIT(scsc_log_collector_list.list) +}; + +struct scsc_log_status { + struct file *fp; + loff_t pos; + bool in_collection; + char fapi_ver[SCSC_LOG_FAPI_VERSION_SIZE]; + + unsigned char *buf; + struct workqueue_struct *collection_workq; + struct work_struct collect_work; + enum scsc_log_reason collect_reason; + u16 reason_code; + struct mutex collection_serial; + bool observer_present; +} log_status; + +static DEFINE_MUTEX(log_mutex); + +static void collection_worker(struct work_struct *work) +{ + struct scsc_log_status *ls; + + ls = container_of(work, struct scsc_log_status, collect_work); + if (!ls) + return; + pr_info("SCSC running scheduled Log Collection - collect reason:%d reason code:%d\n", + ls->collect_reason, ls->reason_code); + scsc_log_collector_collect(ls->collect_reason, ls->reason_code); + atomic_set(&in_collection, 0); +} + +/* Module init */ +int __init scsc_log_collector(void) +{ + pr_info("Log Collector Init\n"); + + log_status.in_collection = false; + log_status.collection_workq = create_workqueue("log_collector"); + if (log_status.collection_workq) + INIT_WORK(&log_status.collect_work, collection_worker); + /* Update mxlogger status on init.*/ + pr_info("Sable Log Collection is now %sABLED.\n", + sable_collection_off ? "DIS" : "EN"); + mxlogger_set_enabled_status(!sable_collection_off); + + /* Create the buffer on the constructor */ + log_status.buf = vzalloc(SCSC_LOG_COLLECT_MAX_SIZE); + if (IS_ERR_OR_NULL(log_status.buf)) { + pr_err("open allocating memmory err = %ld\n", PTR_ERR(log_status.buf)); + log_status.buf = NULL; + } + + mutex_init(&log_status.collection_serial); + + scsc_log_collect_proc_create(); + scsc_log_collector_mmap_create(); + return 0; +} + +void __exit scsc_log_collector_exit(void) +{ + if (log_status.buf) + vfree(log_status.buf); + + scsc_log_collect_proc_remove(); + if (log_status.collection_workq) { + flush_workqueue(log_status.collection_workq); + destroy_workqueue(log_status.collection_workq); + log_status.collection_workq = NULL; + } + + pr_info("Log Collect Unloaded\n"); +} + +module_init(scsc_log_collector); +module_exit(scsc_log_collector_exit); + +static bool scsc_is_chunk_supported(u8 type) +{ + u8 i; + + for (i = 0; i < SCSC_NUM_CHUNKS_SUPPORTED; i++) { + if (type == chunk_supported_sbl[i]) + return true; + } + + return false; +} + +static int scsc_log_collector_compare(void *priv, struct list_head *A, struct list_head *B) +{ + struct scsc_log_client *a = list_entry(A, typeof(*a), list); + struct scsc_log_client *b = list_entry(B, typeof(*b), list); + + if (a->collect_client->type < b->collect_client->type) + return -1; + else + return 1; +} + +int scsc_log_collector_register_client(struct scsc_log_collector_client *collect_client) +{ + struct scsc_log_client *lc; + + if (!scsc_is_chunk_supported(collect_client->type)) { + pr_info("Type not supported: %d\n", collect_client->type); + return -EIO; + } + + mutex_lock(&log_mutex); + lc = kzalloc(sizeof(*lc), GFP_KERNEL); + if (!lc) { + mutex_unlock(&log_mutex); + return -ENOMEM; + } + + lc->collect_client = collect_client; + list_add_tail(&lc->list, &scsc_log_collector_list.list); + + /* Sort the list */ + list_sort(NULL, &scsc_log_collector_list.list, scsc_log_collector_compare); + + pr_info("Registered client: %s\n", collect_client->name); + mutex_unlock(&log_mutex); + return 0; +} +EXPORT_SYMBOL(scsc_log_collector_register_client); + +int scsc_log_collector_unregister_client(struct scsc_log_collector_client *collect_client) +{ + struct scsc_log_client *lc, *next; + bool match = false; + + /* block any attempt of unregistering while a collection is in progres */ + mutex_lock(&log_mutex); + list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) { + if (lc->collect_client == collect_client) { + match = true; + list_del(&lc->list); + kfree(lc); + } + } + + if (match == false) + pr_err("FATAL, no match for given scsc_log_collector_client\n"); + + pr_info("Unregistered client: %s\n", collect_client->name); + mutex_unlock(&log_mutex); + + return 0; +} +EXPORT_SYMBOL(scsc_log_collector_unregister_client); + + +unsigned char *scsc_log_collector_get_buffer(void) +{ + return log_status.buf; +} + +static int __scsc_log_collector_write_to_ram(char __user *buf, size_t count, u8 align) +{ + if (!log_status.in_collection || !log_status.buf) + return -EIO; + + if (log_status.pos + count > SCSC_LOG_COLLECT_MAX_SIZE) { + pr_err("Write will exceed SCSC_LOG_COLLECT_MAX_SIZE. Abort write\n"); + return -ENOMEM; + } + + log_status.pos = (log_status.pos + align - 1) & ~(align - 1); + /* Write buf to RAM */ + memcpy(log_status.buf + log_status.pos, buf, count); + + log_status.pos += count; + + return 0; +} + +static int __scsc_log_collector_write_to_file(char __user *buf, size_t count, u8 align) +{ + int ret = 0; + + if (!log_status.in_collection) + return -EIO; + + if (log_status.pos + count > SCSC_LOG_COLLECT_MAX_SIZE) { + pr_err("Write will exceed SCSC_LOG_COLLECT_MAX_SIZE. Abort write\n"); + return -ENOMEM; + } + + log_status.pos = (log_status.pos + align - 1) & ~(align - 1); + /* Write buf to file */ + ret = vfs_write(log_status.fp, buf, count, &log_status.pos); + if (ret < 0) { + pr_err("write file error, err = %d\n", ret); + return ret; + } + return 0; +} + +int scsc_log_collector_write(char __user *buf, size_t count, u8 align) +{ + if (collect_to_ram) + return __scsc_log_collector_write_to_ram(buf, count, align); + else + return __scsc_log_collector_write_to_file(buf, count, align); +} +EXPORT_SYMBOL(scsc_log_collector_write); + +#define align_chunk(ppos) (((ppos) + (SCSC_LOG_CHUNK_ALIGN - 1)) & \ + ~(SCSC_LOG_CHUNK_ALIGN - 1)) + +static int __scsc_log_collector_collect(enum scsc_log_reason reason, u16 reason_code, u8 buffer) +{ + struct scsc_log_client *lc, *next; + mm_segment_t old_fs; + char memdump_path[128]; + int ret = 0; + char version_fw[SCSC_LOG_FW_VERSION_SIZE] = {0}; + char version_host[SCSC_LOG_HOST_VERSION_SIZE] = {0}; + u32 mem_pos, temp_pos, chunk_size; + ktime_t start; + u8 num_chunks = 0; + u16 first_chunk_pos = SCSC_LOG_OFFSET_FIRST_CHUNK; + struct scsc_log_sbl_header sbl_header; + struct scsc_log_chunk_header chk_header; + u8 j; + bool sbl_is_valid = false; + + mutex_lock(&log_mutex); + + pr_info("Log collection triggered %s reason_code 0x%x\n", + scsc_get_trigger_str((int)reason), reason_code); + + start = ktime_get(); + + if (buffer == TO_FILE) { + snprintf(memdump_path, sizeof(memdump_path), "%s/%s.sbl", + collection_dir_buf, scsc_get_trigger_str((int)reason)); + + /* change to KERNEL_DS address limit */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + log_status.fp = filp_open(memdump_path, O_CREAT | O_WRONLY | O_SYNC | O_TRUNC, 0664); + if (IS_ERR(log_status.fp)) { + pr_err("open file error, err = %ld\n", PTR_ERR(log_status.fp)); + mutex_unlock(&log_mutex); + return PTR_ERR(log_status.fp); + } + } else if (!log_status.buf) { + pr_err("RAM buffer not created. Aborting dump\n"); + mutex_unlock(&log_mutex); + return -ENOMEM; + } + + log_status.in_collection = true; + /* Position index to start of the first chunk */ + log_status.pos = SCSC_LOG_OFFSET_FIRST_CHUNK; + + /* Call client init callbacks if any */ + list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) { + if (lc->collect_client && lc->collect_client->collect_init) + lc->collect_client->collect_init(lc->collect_client); + } + /* Traverse all the clients from the list.. Those would start calling scsc_log_collector_write!!*/ + /* Create chunk */ + list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) { + if (lc->collect_client) { + num_chunks++; + /* Create Chunk */ + /* Store current post */ + temp_pos = log_status.pos; + /* Make room for chunck header */ + log_status.pos += SCSC_CHUNK_HEADER_SIZE; + /* Execute clients callbacks */ + if (lc->collect_client->collect(lc->collect_client, 0)) + goto exit; + /* Write chunk headers */ + /* Align log_status.pos */ + mem_pos = log_status.pos = align_chunk(log_status.pos); + chunk_size = log_status.pos - temp_pos - SCSC_CHUNK_HEADER_SIZE; + /* rewind pos */ + log_status.pos = temp_pos; + /* Write chunk header */ + memcpy(chk_header.magic, "CHK", 3); + chk_header.type = (char)lc->collect_client->type; + chk_header.chunk_size = chunk_size; + scsc_log_collector_write((char *)&chk_header, sizeof(struct scsc_log_chunk_header), 1); + /* restore position for next chunk */ + log_status.pos = mem_pos; + } + } + /* Callbacks to clients have finished at this point. */ + /* Write file header */ + /* Move position to start of file */ + log_status.pos = 0; + /* Write header */ + memset(&sbl_header, 0, sizeof(sbl_header)); + memcpy(sbl_header.magic, "SCSC", 4); + sbl_header.version_major = SCSC_LOG_HEADER_VERSION_MAJOR; + sbl_header.version_minor = SCSC_LOG_HEADER_VERSION_MINOR; + sbl_header.num_chunks = num_chunks; + sbl_header.trigger = reason; + sbl_header.reason_code = reason_code; + sbl_header.observer = log_status.observer_present; + sbl_header.offset_data = first_chunk_pos; + mxman_get_fw_version(version_fw, SCSC_LOG_FW_VERSION_SIZE); + memcpy(sbl_header.fw_version, version_fw, SCSC_LOG_FW_VERSION_SIZE); + mxman_get_driver_version(version_host, SCSC_LOG_HOST_VERSION_SIZE); + memcpy(sbl_header.host_version, version_host, SCSC_LOG_HOST_VERSION_SIZE); + memcpy(sbl_header.fapi_version, log_status.fapi_ver, SCSC_LOG_FAPI_VERSION_SIZE); + + memset(sbl_header.supported_chunks, SCSC_LOG_CHUNK_INVALID, SCSC_SUPPORTED_CHUNKS_HEADER); + for (j = 0; j < SCSC_NUM_CHUNKS_SUPPORTED; j++) + sbl_header.supported_chunks[j] = chunk_supported_sbl[j]; + + scsc_log_collector_write((char *)&sbl_header, sizeof(struct scsc_log_sbl_header), 1); + + if (buffer == TO_FILE) { + /* Sync file from filesystem to physical media */ + ret = vfs_fsync(log_status.fp, 0); + if (ret < 0) { + pr_err("sync file error, error = %d\n", ret); + goto exit; + } + } + + sbl_is_valid = true; +exit: + if (buffer == TO_FILE) { + /* close file before return */ + if (!IS_ERR(log_status.fp)) + filp_close(log_status.fp, current->files); + + /* restore previous address limit */ + set_fs(old_fs); + } + + log_status.in_collection = false; + + list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) { + if (lc->collect_client && lc->collect_client->collect_end) + lc->collect_client->collect_end(lc->collect_client); + } + + pr_info("Calling sable collection\n"); + +#ifdef CONFIG_SCSC_WLBTD + if (sbl_is_valid) + call_wlbtd_sable((u8)reason, reason_code); +#endif + pr_info("Log collection end. Took: %lld\n", ktime_to_ns(ktime_sub(ktime_get(), start))); + + mutex_unlock(&log_mutex); + + return ret; +} + +static int scsc_log_collector_collect(enum scsc_log_reason reason, u16 reason_code) +{ + int ret = -1; + + if (sable_collection_off) { + pr_info("Sable Log collection is currently DISABLED (sable_collection_off=Y).\n"); + pr_info("Ignoring incoming Sable Collection request with Reason=%d.\n", reason); + return ret; + } + + if (collect_to_ram) + ret = __scsc_log_collector_collect(reason, reason_code, TO_RAM); + else + ret = __scsc_log_collector_collect(reason, reason_code, TO_FILE); + + return ret; +} + +void scsc_log_collector_schedule_collection(enum scsc_log_reason reason, u16 reason_code) +{ + + if (log_status.collection_workq) { + mutex_lock(&log_status.collection_serial); + pr_info("Log collection Schedule"); + + /* Serialize with previous work if the reason is a FW panic */ + if (reason == SCSC_LOG_FW_PANIC) + flush_work(&log_status.collect_work); + else if (atomic_read(&in_collection)) { + pr_info("Log collection %s reason_code 0x%x rejected. Collection already scheduled\n", + scsc_get_trigger_str((int)reason), reason_code); + mutex_unlock(&log_status.collection_serial); + return; + } + log_status.collect_reason = reason; + log_status.reason_code = reason_code; + if (!queue_work(log_status.collection_workq, &log_status.collect_work)) { + pr_info("Log collection %s reason_code 0x%x queue_work error\n", + scsc_get_trigger_str((int)reason), reason_code); + mutex_unlock(&log_status.collection_serial); + return; + } + atomic_set(&in_collection, 1); + pr_info("Log collection Scheduled"); + + /* If dumping a FW panic (i.e. collecting a moredump), we need + * to wait for the collection to finish before returning. + */ + if (reason == SCSC_LOG_FW_PANIC) + flush_work(&log_status.collect_work); + + mutex_unlock(&log_status.collection_serial); + + } else { + pr_err("Log Collection Workqueue NOT available...aborting scheduled collection.\n"); + } +} +EXPORT_SYMBOL(scsc_log_collector_schedule_collection); + +void scsc_log_collector_write_fapi(char __user *buf, size_t len) +{ + if (len > SCSC_LOG_FAPI_VERSION_SIZE) + len = SCSC_LOG_FAPI_VERSION_SIZE; + memcpy(log_status.fapi_ver, buf, len); +} +EXPORT_SYMBOL(scsc_log_collector_write_fapi); + +void scsc_log_collector_is_observer(bool observer) +{ + log_status.observer_present = observer; +} +EXPORT_SYMBOL(scsc_log_collector_is_observer); + +MODULE_DESCRIPTION("SCSC Log collector"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/misc/samsung/scsc/scsc_log_collector_mmap.c b/drivers/misc/samsung/scsc/scsc_log_collector_mmap.c new file mode 100644 index 000000000000..c255807fa51b --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_log_collector_mmap.c @@ -0,0 +1,141 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "scsc_log_collector_mmap.h" + +#define DEVICE_NAME "scsc_log_collector" +#define N_MINORS 1 + +struct class *scsc_log_collector_class; +struct cdev scsc_log_collector_dev[N_MINORS]; +dev_t dev_num; + +static int scsc_log_collector_mmap_open(struct inode *inode, struct file *filp) +{ + pr_info("scsc_log_collector_mmap_open\n"); + return 0; +} + +static int scsc_log_collector_release(struct inode *inode, struct file *filp) +{ + pr_info("scsc_log_collector_release\n"); + return 0; +} + +static int scsc_log_collector_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + unsigned char *buf; + + if (size > SCSC_LOG_COLLECT_MAX_SIZE) + return -EINVAL; + if (offset > SCSC_LOG_COLLECT_MAX_SIZE - size) + return -EINVAL; + + buf = scsc_log_collector_get_buffer(); + if (!buf) { + pr_err("No buffer mapped\n"); + return -ENOMEM; + } + + pos = (unsigned long)buf + offset; + + pr_info("scsc_log_collector_mmap size:%lu offset %ld\n", size, offset); + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +static const struct file_operations scsc_log_collector_mmap_fops = { + .owner = THIS_MODULE, + .open = scsc_log_collector_mmap_open, + .mmap = scsc_log_collector_mmap, + .release = scsc_log_collector_release, +}; + +int scsc_log_collector_mmap_create(void) +{ + struct device *dev; + int i; + int ret; + dev_t curr_dev; + + /* Request the kernel for N_MINOR devices */ + ret = alloc_chrdev_region(&dev_num, 0, N_MINORS, "scsc_log_collector"); + if (ret) { + pr_err("alloc_chrdev_region failed"); + goto error; + } + + /* Create a class : appears at /sys/class */ + scsc_log_collector_class = class_create(THIS_MODULE, "scsc_log_collector_class"); + if (IS_ERR(scsc_log_collector_class)) { + ret = PTR_ERR(scsc_log_collector_class); + goto error_class; + } + + /* Initialize and create each of the device(cdev) */ + for (i = 0; i < N_MINORS; i++) { + /* Associate the cdev with a set of file_operations */ + cdev_init(&scsc_log_collector_dev[i], &scsc_log_collector_mmap_fops); + + ret = cdev_add(&scsc_log_collector_dev[i], dev_num, 1); + if (ret) + pr_err("cdev_add failed"); + + scsc_log_collector_dev[i].owner = THIS_MODULE; + /* Build up the current device number. To be used further */ + dev = device_create(scsc_log_collector_class, NULL, dev_num, NULL, "scsc_log_collector_%d", i); + if (IS_ERR(dev)) { + pr_err("device_create failed"); + ret = PTR_ERR(dev); + cdev_del(&scsc_log_collector_dev[i]); + continue; + } + curr_dev = MKDEV(MAJOR(dev_num), MINOR(dev_num) + i); + } + + return 0; + +error_class: + unregister_chrdev_region(dev_num, N_MINORS); +error: + return 0; +} + +int scsc_log_collector_mmap_destroy(void) +{ + int i; + + device_destroy(scsc_log_collector_class, dev_num); + for (i = 0; i < N_MINORS; i++) + cdev_del(&scsc_log_collector_dev[i]); + class_destroy(scsc_log_collector_class); + unregister_chrdev_region(dev_num, N_MINORS); + return 0; +} diff --git a/drivers/misc/samsung/scsc/scsc_log_collector_mmap.h b/drivers/misc/samsung/scsc/scsc_log_collector_mmap.h new file mode 100644 index 000000000000..df1eefeb308a --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_log_collector_mmap.h @@ -0,0 +1,12 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef SCSC_LOG_COLLECT_MMAP_H +#define SCSC_LOG_COLLECT_MMAP_H + +int scsc_log_collector_mmap_create(void); +int scsc_log_collector_mmap_destroy(void); +#endif /* SCSC_log_collect_MMAP_H */ diff --git a/drivers/misc/samsung/scsc/scsc_log_collector_proc.c b/drivers/misc/samsung/scsc/scsc_log_collector_proc.c new file mode 100644 index 000000000000..9523eb22554c --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_log_collector_proc.c @@ -0,0 +1,96 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include "scsc_log_collector_proc.h" + +static struct proc_dir_entry *procfs_dir; + +static int log_collect_procfs_open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = LOG_COLLECT_PDE_DATA(inode); + return 0; +} + +LOG_COLLECT_PROCFS_RW_FILE_OPS(trigger_collection); + +static ssize_t log_collect_procfs_trigger_collection_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + /* Avoid unused parameter error */ + (void)file; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t log_collect_procfs_trigger_collection_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + char val; + + /* check that only one digit is passed */ + if (count != 2) + pr_err("%s: Incorrect argument length\n", __func__); + + if (copy_from_user(&val, user_buf, 1)) + return -EFAULT; + + if (val == '1') { + pr_info("%s: Userland has triggered log collection\n", __func__); + scsc_log_collector_schedule_collection(SCSC_LOG_USER, SCSC_LOG_USER_REASON_PROC); + } else if (val == '2') { + pr_info("%s: Dumpstate/dumpsys has triggered log collection\n", __func__); + scsc_log_collector_schedule_collection(SCSC_LOG_DUMPSTATE, SCSC_LOG_DUMPSTATE_REASON); + } else { + pr_err("%s: Incorrect argument\n", __func__); + } + return count; +} + +static const char *procdir = "driver/scsc_log_collect"; + +#define LOG_COLLECT_DIRLEN 128 + +int scsc_log_collect_proc_create(void) +{ + char dir[LOG_COLLECT_DIRLEN]; + struct proc_dir_entry *parent; + + (void)snprintf(dir, sizeof(dir), "%s", procdir); + parent = proc_mkdir(dir, NULL); + if (parent) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = NULL; +#endif + procfs_dir = parent; + + LOG_COLLECT_PROCFS_ADD_FILE(NULL, trigger_collection, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + } else { + pr_err("failed to create /proc dir\n"); + return -EINVAL; + } + + return 0; +} + +void scsc_log_collect_proc_remove(void) +{ + if (procfs_dir) { + char dir[LOG_COLLECT_DIRLEN]; + + LOG_COLLECT_PROCFS_REMOVE_FILE(trigger_collection, procfs_dir); + (void)snprintf(dir, sizeof(dir), "%s", procdir); + remove_proc_entry(dir, NULL); + procfs_dir = NULL; + } +} diff --git a/drivers/misc/samsung/scsc/scsc_log_collector_proc.h b/drivers/misc/samsung/scsc/scsc_log_collector_proc.h new file mode 100644 index 000000000000..22321edb7fc2 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_log_collector_proc.h @@ -0,0 +1,77 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* + * Chip Manager /proc interface + */ +#include +#include +#include + +#ifndef SCSC_LOG_COLLECT_PROC_H +#define SCSC_LOG_COLLECT_PROC_H + +#ifndef AID_MX +#define AID_MX 0444 +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define LOG_COLLECT_PDE_DATA(inode) PDE_DATA(inode) +#else +#define LOG_COLLECT_PDE_DATA(inode) (PDE(inode)->data) +#endif + +#define LOG_COLLECT_PROCFS_RW_FILE_OPS(name) \ + static ssize_t log_collect_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static ssize_t log_collect_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations log_collect_procfs_ ## name ## _fops = { \ + .read = log_collect_procfs_ ## name ## _read, \ + .write = log_collect_procfs_ ## name ## _write, \ + .open = log_collect_procfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define LOG_COLLECT_PROCFS_SET_UID_GID(_entry) \ + do { \ + kuid_t proc_kuid = KUIDT_INIT(AID_MX); \ + kgid_t proc_kgid = KGIDT_INIT(AID_MX); \ + proc_set_user(_entry, proc_kuid, proc_kgid); \ + } while (0) +#else +#define LOG_COLLECT_PROCFS_SET_UID_GID(entry) \ + do { \ + (entry)->uid = AID_MX; \ + (entry)->gid = AID_MX; \ + } while (0) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define LOG_COLLECT_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &log_collect_procfs_ ## name ## _fops, _sdev); \ + LOG_COLLECT_PROCFS_SET_UID_GID(entry); \ + } while (0) +#else +#define LOG_COLLECT_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = create_proc_entry(# name, mode, parent); \ + if (entry) { \ + entry->proc_fops = &log_collect_procfs_ ## name ## _fops; \ + entry->data = _sdev; \ + LOG_COLLECT_PROCFS_SET_UID_GID(entry); \ + } \ + } while (0) +#endif + +#define LOG_COLLECT_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent) + +int scsc_log_collect_proc_create(void); +void scsc_log_collect_proc_remove(void); + +#endif /* SCSC_log_collect__PROC_H */ diff --git a/drivers/misc/samsung/scsc/scsc_logring_common.h b/drivers/misc/samsung/scsc/scsc_logring_common.h new file mode 100644 index 000000000000..488e532aa50a --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_common.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#ifndef __SCSC_LOGRING_COMMON_H__ +#define __SCSC_LOGRING_COMMON_H__ + +enum scsc_log_level { + SCSC_MIN_DBG = 0, + SCSC_EMERG = SCSC_MIN_DBG, + SCSC_ALERT, + SCSC_CRIT, + SCSC_ERR, + SCSC_WARNING, + SCSC_NOTICE, + SCSC_INFO, + SCSC_DEBUG, + SCSC_DBG1 = SCSC_DEBUG, /* 7 */ + SCSC_DBG2, + SCSC_DBG3, + SCSC_DBG4, /* 10 */ + SCSC_FULL_DEBUG +}; + +#define SCSC_SOH 0x01 +#define DEFAULT_DBGLEVEL SCSC_INFO /* KERN_INFO */ +#define DEFAULT_DROPLEVEL SCSC_FULL_DEBUG /* DBG4 + 1 */ +#define DEFAULT_ALL_DISABLED -1 +#define DEFAULT_DROP_ALL 0 +#define DEFAULT_REDIRECT_DROPLVL SCSC_DEBUG +#define DEFAULT_NO_REDIRECT 0 +#define DEFAULT_TBUF_SZ 4096 + +/** + * Nested macros needed to force expansion of 'defval' + * before stringification takes place. Allows for ONE level + * of indirection specifying params. + */ +#define SCSC_MODPARAM_DESC(kparam, descr, eff, defval) \ + __SCSC_MODPARAM_DESC(kparam, descr, eff, defval) + +#define __SCSC_MODPARAM_DESC(kparam, descr, eff, defval) \ + MODULE_PARM_DESC(kparam, " "descr " Effective @"eff " default=" # defval ".") + + +#endif /* __SCSC_LOGRING_COMMON_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_logring_debugfs.c b/drivers/misc/samsung/scsc/scsc_logring_debugfs.c new file mode 100644 index 000000000000..18493f0297d6 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_debugfs.c @@ -0,0 +1,706 @@ +/****************************************************************************** + * + * Copyright (c) 2016-2017 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ + +#include +#include "scsc_logring_main.h" +#include "scsc_logring_debugfs.h" + +static int scsc_max_records_per_read = SCSC_DEFAULT_MAX_RECORDS_PER_READ; +module_param(scsc_max_records_per_read, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_max_records_per_read, + "Number of records a reader can try to get in a shot. 0 is infinite", + "run-time", SCSC_DEFAULT_MAX_RECORDS_PER_READ); + +static int scsc_double_buffer_sz = DEFAULT_TBUF_SZ; +module_param(scsc_double_buffer_sz, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_double_buffer_sz, + "Determines the size of the per-reader allocted double buffer.", + "run-time", DEFAULT_TBUF_SZ); + +/** + * BIG NOTE on DOUBLE BUFFERING. + * + * In order to extract data from the ring buffer, protected by spinlocks, + * to user space we use a double buffer: data is so finally copied to + * userspace from a temporary double buffer, after having copied into it + * ALL the desired content and after all the spinlocks have been released. + * In order to avoid use of an additional mutex to protect such temporary + * buffer from multiple readers access we use a oneshot throwaway buffer + * dedicated to each reader and allocated at opening time. + * The most straightforward way to do this thing would have been to simply + * allocate such buffer inside the read method and throw it away on exit: + * this is what underlying printk mechanism does via a simple kmalloc. + * BUT we decided INSTEAD to use this buffer ALSO as a sort of caching + * area for each reader in order to cope with under-sized user read-request; + * basically no matter what the user has asked in term of size of the read + * request we'll ALWAYS RETRIEVE multiple of whole records from the ring, + * one record being the minimum internal ring-read-request this way. + * So no matter if the user ask for a few bytes, less than the next record + * size, we'll retrieve ONE WHOLE record from the ring into the double buffer: + * this way on the next read request we'll have already a cached copy of the + * record and we could deal with it inside the read callback without the + * need to access the ring anymore for such record. + * The main reason for this is that if we had instead accessed the ring and + * retrieved ONLY a fraction of the record, on the next request we could NOT + * be able to provide the remaining part of the record because, being the ring + * an overwriting buffer, it could have wrap in the meantime and we could have + * simply lost that data: this condition would have lead us to return to + * user partial truncated records when we hit this overwrap condition. + * Following instead the approach of WHOLE records retrieval we can instead be + * sure to always retrieve fully correct records, despite being vulnerable + * anyway to loss of data (whole records) while reading if fast writers + * overwrite our data. (since we'll never ever want to slow down and starve a + * writer.) + */ +static struct dentry *scsc_debugfs_root; +static atomic_t scsc_debugfs_root_refcnt; +static char *global_fmt_string = "%s"; + +/** + * Generic open/close calls to use with every logring debugfs file. + * Any file in debugfs has an underlying associated ring buffer: + * opening ANY of these with O_TRUNC leads to ring_buffer truncated + * to zero len. + */ +static int debugfile_open(struct inode *ino, struct file *filp) +{ + struct scsc_ibox *i = NULL; + + if (!filp->private_data) { + i = kzalloc(sizeof(*i), GFP_KERNEL); + if (!i) + return -EFAULT; + i->rb = ino->i_private; + filp->private_data = i; + } else { + i = filp->private_data; + } + /* tbuf sz is now runtime-configurable so try a few fallback methods */ + i->tbuf = kmalloc(scsc_double_buffer_sz, GFP_KERNEL); + /* Making sure we fallback to a safe size DEFAULT_TBUF_SZ */ + if (!i->tbuf) { + i->tbuf = vmalloc(scsc_double_buffer_sz); + pr_err("LogRing: FAILED tbuf allocation of %d bytes...retried vmalloc()...\n", + scsc_double_buffer_sz); + if (!i->tbuf) { + scsc_double_buffer_sz = DEFAULT_TBUF_SZ; + pr_err("LogRing: FAILED tbuf vmalloc...using DEFAULT %d bytes size.\n", + scsc_double_buffer_sz); + i->tbuf = kmalloc(scsc_double_buffer_sz, GFP_KERNEL); + if (!i->tbuf) { + pr_err("LogRing: FAILED DEFINITELY allocation...aborting\n"); + kfree(i); + return -ENOMEM; + } + } else { + i->tbuf_vm = true; + } + } + i->tsz = scsc_double_buffer_sz; + pr_info("LogRing: Allocated per-reader tbuf of %d bytes\n", + scsc_double_buffer_sz); + /* Truncate when attempting to write RO files samlog and samsg */ + if (filp->f_flags & (O_WRONLY | O_RDWR) && + filp->f_flags & O_TRUNC) { + unsigned long flags; + + raw_spin_lock_irqsave(&i->rb->lock, flags); + scsc_ring_truncate(i->rb); + raw_spin_unlock_irqrestore(&i->rb->lock, flags); + pr_info("LogRing Truncated to zerolen\n"); + return -EACCES; + } + return 0; +} + +static int debugfile_release(struct inode *ino, struct file *filp) +{ + struct scsc_ibox *i = NULL; + + if (!filp->private_data) + return -EFAULT; + i = filp->private_data; + if (!i->tbuf_vm) + kfree(i->tbuf); + else + vfree(i->tbuf); + i->tbuf = NULL; + + /* Were we using a snapshot ? Free it.*/ + if (i->saved_live_rb) { + vfree(i->rb->buf); + kfree(i->rb); + } + /* Being paranoid... */ + filp->private_data = NULL; + kfree(i); + return 0; +} + +/** + * Initialize references for subsequent cached reads: in fact if + * data retrieved from the ring was more than the count-bytes required by + * the caller of this read, we can keep such data stored in tbuf and provide + * it to this same reader on its next read-call. + * + * @i: contains references useful to this reader + * @retrieved_bytes: how many bytes have been stored in tbuf + * @count: a pointer to the count bytes required by this reader + * for this call. We'll manipulate this to return an + * appropriate number of bytes. + */ +static inline +size_t init_cached_read(struct scsc_ibox *i, + size_t retrieved_bytes, size_t *count) +{ + if (retrieved_bytes <= *count) { + *count = retrieved_bytes; + } else { + i->t_off = *count; + i->t_used = retrieved_bytes - *count; + i->cached_reads += *count; + } + + return 0; +} + +/** + * Here we'll serve to user space the next available chunk of + * record directly from the tbuf double buffer without + * accessing the ring anymore. + * + * @i: contains references useful to this reader + * @count: a pointer to the count bytes required by this reader + * for this call. We'll manipulate this to return an + * appropriate number of bytes. + */ +static inline +size_t process_cached_read_data(struct scsc_ibox *i, size_t *count) +{ + size_t offset = 0; + + offset = i->t_off; + if (i->t_used <= *count) { + /* this was the last chunk cached */ + *count = i->t_used; + i->t_off = 0; + i->t_used = 0; + } else { + i->t_off += *count; + i->t_used -= *count; + i->cached_reads += *count; + } + + return offset; +} + +/** + * This file operation read from the ring using common routines, starting its + * read from head: in other words it immediately blocks waiting for some data to + * arrive. As soon as some data arrives and head moves away, the freshly + * available data is returned to userspace up to the required size , and this + * call goes back to sleeping waiting for more data. + * + * NOTE + * ---- + * The need to copy_to_user imposes the use of a temp buffer tbuf which is used + * as a double buffer: being allocated to this reader on open() we do NOT need + * any additional form of mutual exclusion. + * Moreover we use such buffer here as an area to cache the retrieved records: + * if the retrieved record size is bigger than the count bytes required by user + * we'll return less data at first and then deal with the following requests + * pumping data directly from the double buffer without accessing the ring. + */ +static ssize_t samsg_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + unsigned long flags; + loff_t current_head = 0; + struct scsc_ibox *i = NULL; + size_t off = 0; + size_t retrieved_bytes = 0; + + if (!filp->private_data || !access_ok(VERIFY_WRITE, ubuf, count)) + return -ENOMEM; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + /* open() assures us that this private data is certainly non-NULL */ + i = filp->private_data; + if (!i->t_used) { + raw_spin_lock_irqsave(&i->rb->lock, flags); + current_head = *f_pos ? i->f_pos : i->rb->head; + while (current_head == i->rb->head) { + raw_spin_unlock_irqrestore(&i->rb->lock, flags); + if (wait_event_interruptible(i->rb->wq, + current_head != i->rb->head)) + return -ERESTARTSYS; + raw_spin_lock_irqsave(&i->rb->lock, flags); + } + retrieved_bytes = read_next_records(i->rb, + scsc_max_records_per_read, + ¤t_head, i->tbuf, i->tsz); + /* We MUST keep track of the the last known READ record + * in order to keep going from the same place on the next + * read call coming from the same userspace process... + * ...this could NOT necessarily be the HEAD at the end of this + * read if we asked for few records. + * So we must annotate the really last read record got back, + * returned in current_head, inside i->f_pos in order to have a + * reference for the next read call by the same reader. + */ + i->f_pos = current_head; + raw_spin_unlock_irqrestore(&i->rb->lock, flags); + /* ANYWAY we could have got back more data from the ring (ONLY + * multiple of whole records) than required by usersapce. + */ + off = init_cached_read(i, retrieved_bytes, &count); + } else { + /* Serve this read-request directly from cached data without + * accessing the ring + */ + off = process_cached_read_data(i, &count); + } + if (copy_to_user(ubuf, i->tbuf + off, count)) + return -EFAULT; + *f_pos += count; + return count; +} + +/** + * This seek op assumes let userspace believe that it's dealing with a regular + * plain file, so f_pos is modified accordingly (linearly till the maximum + * number SCSC_LOGGED_BYTES is reached); in fact it's up to + * the read/write ops to properly 'cast' this value to a modulus value as + * required by the underlying ring buffer. This operates only on samlog. + */ +loff_t debugfile_llseek(struct file *filp, loff_t off, int whence) +{ + loff_t newpos, maxpos; + struct scsc_ibox *i = NULL; + unsigned long flags; + + if (!filp->private_data) + return -EFAULT; + i = filp->private_data; + raw_spin_lock_irqsave(&i->rb->lock, flags); + maxpos = SCSC_LOGGED_BYTES(i->rb) >= 1 ? + SCSC_LOGGED_BYTES(i->rb) - 1 : 0; + raw_spin_unlock_irqrestore(&i->rb->lock, flags); + switch (whence) { + case 0: /* SEEK_SET */ + newpos = (off <= maxpos) ? off : maxpos; + break; + case 1: /* SEEK_CUR */ + newpos = (filp->f_pos + off <= maxpos) ? + filp->f_pos + off : maxpos; + break; + case 2: /* SEEK_END */ + newpos = maxpos; + break; + default: /* can't happen */ + return -EINVAL; + } + if (newpos < 0) + return -EINVAL; + filp->f_pos = newpos; + return newpos; +} + +static int samsg_open(struct inode *ino, struct file *filp) +{ + int ret; + + ret = debugfile_open(ino, filp); +#ifdef CONFIG_SCSC_MXLOGGER + if (!ret) + scsc_service_register_observer(NULL, "LOGRING"); +#endif + return ret; +} + +static int samsg_release(struct inode *ino, struct file *filp) +{ +#ifdef CONFIG_SCSC_MXLOGGER + scsc_service_unregister_observer(NULL, "LOGRING"); +#endif + + return debugfile_release(ino, filp); +} + +const struct file_operations samsg_fops = { + .owner = THIS_MODULE, + .open = samsg_open, + .read = samsg_read, + .release = samsg_release, +}; + +/** + * This is the samlog open and it is used by samlog read-process to grab + * a per-reader dedicated static snapshot of the ring, in order to be able + * then to fetch records from a static immutable image of the ring buffer, + * without the need to stop the ring in the meantime. + * This way samlog dumps exactly a snapshot at-a-point-in-time of the ring + * limiting at the same time the contention with the writers: ring is + * 'spinlocked' ONLY durng the snapshot phase. + * Being the snapshot buffer big as the ring we use a vmalloc to limit + * possibility of failures (especially on non-AOSP builds). + * If such vmalloc allocation fails we then quietly keep on using the old + * method that reads directly from the live buffer. + */ +static int debugfile_open_snapshot(struct inode *ino, struct file *filp) +{ + int ret; + + ret = debugfile_open(ino, filp); + /* if regular debug_file_open has gone through, attempt snapshot */ + if (!ret) { + /* filp && filp->private_data NON-NULL by debugfile_open */ + void *snap_buf; + size_t snap_sz; + struct scsc_ibox *i = filp->private_data; + + /* This is read-only...no spinlocking needed */ + snap_sz = i->rb->bsz + i->rb->ssz; + /* Allocate here to minimize lock time... */ + snap_buf = vmalloc(snap_sz); + if (snap_buf) { + struct scsc_ring_buffer *snap_rb; + char snap_name[RNAME_SZ] = "snapshot"; + unsigned long flags; + + snprintf(snap_name, RNAME_SZ, "%s_snap", i->rb->name); + /* lock while snapshot is taken */ + raw_spin_lock_irqsave(&i->rb->lock, flags); + snap_rb = scsc_ring_get_snapshot(i->rb, snap_buf, snap_sz, snap_name); + raw_spin_unlock_irqrestore(&i->rb->lock, flags); + if (snap_rb) { + /* save real ring and swap into the snap_shot */ + i->saved_live_rb = i->rb; + i->rb = snap_rb; + } else { + vfree(snap_buf); + snap_buf = NULL; + } + } + + /* Warns when not possible to use a snapshot */ + if (!snap_buf) + pr_warn("LogRing: no snapshot available, samlog dump from live ring.\n"); + } + + return ret; +} + +/** + * samlog_read - Reads from the ring buffer the required number of bytes + * starting from the start of the ring. It is usually used to dump the + * whole ring buffer taking a snapshot at-a-point-in-time. + * + * If it had been possible at opening time to take a static snapshot of + * the ring, this routine will fetch records from such a snapshot without + * the need to lock the ring; if instead no snapshot was taken it reverts + * to the usual locked-access pattern. + * + * This function as a usual .read fops returns the number of bytes + * effectively read, and this could: + * - equal the required count bytes + * - be less than the required bytes if: less data WAS available + * (since we only GOT whole records at time from the ring) + * Returning less bytes usually triggers the userapp to reissue the syscall + * to complete the read up to the originaly required number of bytes. + * - be ZERO if NO more data available..this causes the reading userspace + * process to stop reading usually. + */ +static ssize_t samlog_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + struct scsc_ibox *i = NULL; + size_t off = 0, retrieved_bytes = 0; + + if (!filp->private_data) + return -EFAULT; + i = filp->private_data; + if (!i->t_used) { + unsigned long flags; + + /* Lock ONLY if NOT using a snapshot */ + if (!i->saved_live_rb) + raw_spin_lock_irqsave(&i->rb->lock, flags); + /* On first read from userspace f_pos will be ZERO and in this + * case we'll want to trigger a read from the very beginning of + * ring (tail) and set i->f_pos accordingly. + * Internal RING API returns in i->f_pos the next record to + * read: when reading process has wrapped over you'll get back + * an f_pos ZERO as next read. + */ + if (*f_pos == 0) + i->f_pos = i->rb->tail; + retrieved_bytes = read_next_records(i->rb, + scsc_max_records_per_read, + &i->f_pos, i->tbuf, i->tsz); + if (!i->saved_live_rb) + raw_spin_unlock_irqrestore(&i->rb->lock, flags); + /* ANYWAY we could have got back more data from the ring (ONLY + * multiple of whole records) than required by userspace. + */ + off = init_cached_read(i, retrieved_bytes, &count); + } else { + /* Serve this read-request directly from cached data without + * accessing the ring + */ + off = process_cached_read_data(i, &count); + } + if (copy_to_user(ubuf, i->tbuf + off, count)) + return -EFAULT; + *f_pos += count; + return count; +} + +const struct file_operations samlog_fops = { + .owner = THIS_MODULE, + .open = debugfile_open_snapshot, + .read = samlog_read, + .llseek = debugfile_llseek, + .release = debugfile_release, +}; + +static int statfile_open(struct inode *ino, struct file *filp) +{ + if (!filp->private_data) + filp->private_data = ino->i_private; + if (!filp->private_data) + return -EFAULT; + return 0; +} + +static int statfile_release(struct inode *ino, struct file *filp) +{ + if (!filp->private_data) + filp->private_data = ino->i_private; + if (!filp->private_data) + return -EFAULT; + return 0; +} + + +/* A simple read to dump some stats about the ring buffer. */ +static ssize_t statfile_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + unsigned long flags; + size_t bsz = 0; + loff_t head = 0, tail = 0, used = 0, max_chunk = 0, logged = 0, + last = 0; + int slen = 0, records = 0, wraps = 0, oos = 0; + u64 written = 0; + char statstr[STATSTR_SZ] = {}; + struct scsc_ring_buffer *rb = filp->private_data; + + raw_spin_lock_irqsave(&rb->lock, flags); + bsz = rb->bsz; + head = rb->head; + tail = rb->tail; + last = rb->last; + written = rb->written; + records = rb->records; + wraps = rb->wraps; + oos = rb->oos; + used = SCSC_USED_BYTES(rb); + max_chunk = SCSC_RING_FREE_BYTES(rb); + logged = SCSC_LOGGED_BYTES(rb); + raw_spin_unlock_irqrestore(&rb->lock, flags); + + slen = snprintf(statstr, STATSTR_SZ, + "sz:%zd used:%lld free:%lld logged:%lld records:%d\nhead:%lld tail:%lld last:%lld written:%lld wraps:%d oos:%d\n", + bsz, used, max_chunk, logged, records, + head, tail, last, written, wraps, oos); + if (slen >= 0 && *f_pos < slen) { + count = (count <= slen - *f_pos) ? count : (slen - *f_pos); + if (copy_to_user(ubuf, statstr + *f_pos, count)) + return -EFAULT; + *f_pos += count; + } else + count = 0; + return count; +} + +const struct file_operations stat_fops = { + .owner = THIS_MODULE, + .open = statfile_open, + .read = statfile_read, + .release = statfile_release, +}; + +/** + * This implement samwrite interface to INJECT log lines into the ring from + * user space. The support, thought as an aid for testing mainly, is + * minimal, so the interface allows only for simple %s format string injection. + */ +static int samwritefile_open(struct inode *ino, struct file *filp) +{ + if (!filp->private_data) { + struct write_config *wc = + kzalloc(sizeof(struct write_config), GFP_KERNEL); + if (wc) { + wc->fmt = global_fmt_string; + wc->buf_sz = SAMWRITE_BUFSZ; + } + filp->private_data = wc; + } + if (!filp->private_data) + return -EFAULT; + return 0; +} + + +static int samwritefile_release(struct inode *ino, struct file *filp) +{ + kfree(filp->private_data); + filp->private_data = NULL; + return 0; +} + +/** + * User injected string content is pushed to the ring as simple %s fmt string + * content using the TEST_ME tag. Default debuglevel (6 - INFO)will be used. + */ +static ssize_t samwritefile_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *f_pos) +{ + ssize_t written_bytes = 0; + struct write_config *wc = filp->private_data; + + if (wc) { + /* wc->buf is null terminated as it's kzalloc'ed */ + count = count < wc->buf_sz ? count : wc->buf_sz - 1; + if (copy_from_user(wc->buf, ubuf, count)) + return -EINVAL; + written_bytes = scsc_printk_tag(NO_ECHO_PRK, TEST_ME, + wc->fmt, wc->buf); + /* Handle the case where the message is filtered out by + * droplevel filters...zero is returned BUT we do NOT want + * the applications to keep trying...it's NOT a transient + * error...at least till someone changes droplevels. + */ + if (!written_bytes) { + pr_info("samwrite wrote 0 bytes...droplevels filtering ?\n"); + return -EPERM; + } + /* Returned written bytes should be normalized since + * lower level functions returns the number of bytes + * effectively written including the prepended header + * file... IF, when required to write n, we return n+X, + * some applications could behave badly trying to access + * file at *fpos=n+X next time, ending up in a regular + * EFAULT error anyway. + */ + if (written_bytes > count) + written_bytes = count; + *f_pos += written_bytes; + } + + return written_bytes; +} + +const struct file_operations samwrite_fops = { + .owner = THIS_MODULE, + .open = samwritefile_open, + .write = samwritefile_write, + .release = samwritefile_release, +}; + +/** + * Initializes debugfs support build the proper debugfs file dentries: + * - entries in debugfs are created under /sys/kernel/debugfs/scsc/@name/ + * - using the provided rb ring buffer as underlying ring buffer, storing it + * into inode ptr for future retrieval (via open) + * - registers the proper fops + */ +void __init *samlog_debugfs_init(const char *root_name, void *rb) +{ + struct scsc_debugfs_info *di = NULL; + + if (!rb || !root_name) + return NULL; + di = kmalloc(sizeof(*di), GFP_KERNEL); + if (!di) + return NULL; + if (!scsc_debugfs_root) { + /* I could have multiple rings debugfs entry all rooted at + * the same /sys/kernel/debug/scsc/...so such entry could + * already exist. + */ + scsc_debugfs_root = debugfs_create_dir(SCSC_DEBUGFS_ROOT, NULL); + if (!scsc_debugfs_root) + goto no_root; + } + di->rootdir = scsc_debugfs_root; + di->bufdir = debugfs_create_dir(root_name, di->rootdir); + if (!di->bufdir) + goto no_buf; + atomic_inc(&scsc_debugfs_root_refcnt); + /* Saving ring ref @rb to Inode */ + di->samsgfile = debugfs_create_file(SCSC_SAMSG_FNAME, 0444, + di->bufdir, rb, &samsg_fops); + if (!di->samsgfile) + goto no_samsg; + /* Saving ring ref @rb to Inode */ + di->samlogfile = debugfs_create_file(SCSC_SAMLOG_FNAME, 0444, + di->bufdir, rb, &samlog_fops); + if (!di->samlogfile) + goto no_samlog; + di->statfile = debugfs_create_file(SCSC_STAT_FNAME, 0444, + di->bufdir, rb, &stat_fops); + if (!di->statfile) + goto no_statfile; + + di->samwritefile = debugfs_create_file(SCSC_SAMWRITE_FNAME, 0220, + di->bufdir, NULL, + &samwrite_fops); + if (!di->samwritefile) + goto no_samwrite; + + pr_info("Samlog Debugfs Initialized\n"); + return di; + +no_samwrite: + debugfs_remove(di->statfile); +no_statfile: + debugfs_remove(di->samlogfile); +no_samlog: + debugfs_remove(di->samsgfile); +no_samsg: + debugfs_remove(di->bufdir); + atomic_dec(&scsc_debugfs_root_refcnt); +no_buf: + if (!atomic_read(&scsc_debugfs_root_refcnt)) { + debugfs_remove(scsc_debugfs_root); + scsc_debugfs_root = NULL; + } +no_root: + kfree(di); + return NULL; +} + +void __exit samlog_debugfs_exit(void **priv) +{ + struct scsc_debugfs_info **di = NULL; + + if (!priv) + return; + di = (struct scsc_debugfs_info **)priv; + if (di && *di) { + debugfs_remove_recursive(scsc_debugfs_root); + atomic_dec(&scsc_debugfs_root_refcnt); + if (!atomic_read(&scsc_debugfs_root_refcnt)) { + debugfs_remove(scsc_debugfs_root); + scsc_debugfs_root = NULL; + } + kfree(*di); + *di = NULL; + } + pr_info("Debugfs Cleaned Up\n"); +} diff --git a/drivers/misc/samsung/scsc/scsc_logring_debugfs.h b/drivers/misc/samsung/scsc/scsc_logring_debugfs.h new file mode 100644 index 000000000000..9aab70f7c5cc --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_debugfs.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * + * Copyright (c) 2016-2017 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ + +#ifndef _SCSC_LOGRING_DEBUGFS_H_ +#define _SCSC_LOGRING_DEBUGFS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "scsc_logring_ring.h" + +#define STATSTR_SZ 256 +#define SCSC_DEBUGFS_ROOT "scsc" +#define SCSC_SAMSG_FNAME "samsg" +#define SCSC_SAMLOG_FNAME "samlog" +#define SCSC_STAT_FNAME "stat" +#define SCSC_SAMWRITE_FNAME "samwrite" + +#define SAMWRITE_BUFSZ 2048 +#define SCSC_DEFAULT_MAX_RECORDS_PER_READ 1 + +struct scsc_ibox { + struct scsc_ring_buffer *rb; + char *tbuf; + size_t tsz; + bool tbuf_vm; + size_t t_off; + size_t t_used; + size_t cached_reads; + loff_t f_pos; + struct scsc_ring_buffer *saved_live_rb; +}; + +struct scsc_debugfs_info { + struct dentry *rootdir; + struct dentry *bufdir; + struct dentry *samsgfile; + struct dentry *samlogfile; + struct dentry *statfile; + struct dentry *samwritefile; +}; + +struct write_config { + char *fmt; + size_t buf_sz; + char buf[SAMWRITE_BUFSZ]; +}; + +void *samlog_debugfs_init(const char *name, void *rb) __init; +void samlog_debugfs_exit(void **priv) __exit; + +#endif /* _SCSC_LOGRING_DEBUGFS_H_ */ diff --git a/drivers/misc/samsung/scsc/scsc_logring_main.c b/drivers/misc/samsung/scsc/scsc_logring_main.c new file mode 100644 index 000000000000..9b623c4529f3 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_main.c @@ -0,0 +1,663 @@ +/******************************************************************************** + * + * Copyright (c) 2016 - 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ********************************************************************************/ +#include "scsc_logring_main.h" +#include "scsc_logring_ring.h" +#include "scsc_logring_debugfs.h" +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +/* Global module parameters */ +static int cached_enable; +static int enable = DEFAULT_ENABLE_LOGRING; +static bool initialized; +#ifndef CONFIG_SCSC_STATIC_RING_SIZE +static int ringsize = DEFAULT_RING_BUFFER_SZ; +#else +static int ringsize = CONFIG_SCSC_STATIC_RING_SIZE; +#endif +static int prepend_header = DEFAULT_ENABLE_HEADER; +static int default_dbglevel = DEFAULT_DBGLEVEL; +static int scsc_droplevel_wlbt = DEFAULT_DROPLEVEL; +static int scsc_droplevel_all = DEFAULT_ALL_DISABLED; +static int scsc_droplevel_atomic = DEFAULT_DROPLEVEL; +static int scsc_redirect_to_printk_droplvl = DEFAULT_REDIRECT_DROPLVL; +static int scsc_reset_all_droplevels_to; + +struct scsc_ring_buffer *the_ringbuf; + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static int logring_collect(struct scsc_log_collector_client *collect_client, size_t size); + +struct scsc_log_collector_client logring_collect_client = { + .name = "Logring", + .type = SCSC_LOG_CHUNK_LOGRING, + .collect_init = NULL, + .collect = logring_collect, + .collect_end = NULL, + .prv = NULL, +}; +#endif + +/* Module init and ring buffer allocation */ +int __init samlog_init(void) +{ + struct scsc_ring_buffer *rb = NULL; + + pr_debug("Samlog Init\n"); + if (!enable) { + pr_info("Samlog: module disabled...NOT starting.\n"); + return 0; + } + if (the_ringbuf != NULL) { + pr_info("Samlog: Ring:%s already initialized...skipping.\n", + the_ringbuf->name); + return 0; + } + /* Check for power of two compliance with std Kernel func */ + if (!is_power_of_2(ringsize)) { + ringsize = DEFAULT_RING_BUFFER_SZ; + pr_info("Samlog: scsc_logring.ringsize MUST be power-of-two. Using default: %d\n", + ringsize); + } + rb = alloc_ring_buffer(ringsize, BASE_SPARE_SZ, DEBUGFS_RING0_ROOT); + + if (!rb) + goto tfail; + rb->private = samlog_debugfs_init(rb->name, rb); + if (!rb->private) + pr_info("Samlog: Cannot Initialize DebugFS.\n"); +#ifndef CONFIG_SCSC_STATIC_RING_SIZE + pr_info("scsc_logring:: Allocated ring buffer of size %zd bytes.\n", + rb->bsz); +#else + pr_info("scsc_logring:: Allocated STATIC ring buffer of size %zd bytes.\n", + rb->bsz); +#endif + the_ringbuf = rb; + initialized = true; + pr_info("Samlog Loaded.\n"); + scsc_printk_tag(FORCE_PRK, NO_TAG, "Samlog Started.\n"); + scsc_printk_tag(NO_ECHO_PRK, NO_TAG, + "Allocated ring buffer of size %zd bytes at %p - %p\n", + rb->bsz, virt_to_phys((const volatile void *)rb->buf), + virt_to_phys((const volatile void *)(rb->buf + rb->bsz))); + scsc_printk_tag(NO_ECHO_PRK, NO_TAG, + "Using THROWAWAY DYNAMIC per-reader buffer.\n"); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_register_client(&logring_collect_client); +#endif + return 0; + +tfail: + pr_err("Samlog Initialization Failed. LogRing disabled.\n"); + return -ENODEV; +} + +void __exit samlog_exit(void) +{ + if (!the_ringbuf) { + pr_err("Cannot UNLOAD ringbuf\n"); + return; + } + if (the_ringbuf && the_ringbuf->private) + samlog_debugfs_exit(&the_ringbuf->private); + initialized = false; + free_ring_buffer(the_ringbuf); + the_ringbuf = NULL; +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_unregister_client(&logring_collect_client); +#endif + pr_info("Samlog Unloaded\n"); +} + +module_init(samlog_init); +module_exit(samlog_exit); + +module_param(enable, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(enable, "Enable/Disable scsc_logring as a whole.", + "load-time", DEFAULT_ENABLE_LOGRING); + +#ifndef CONFIG_SCSC_STATIC_RING_SIZE +module_param(ringsize, int, S_IRUGO); +SCSC_MODPARAM_DESC(ringsize, + "Ring buffer size. Available ONLY if ring is NOT statically allocated.", + "run-time", DEFAULT_RING_BUFFER_SZ); +#endif + +module_param(prepend_header, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(prepend_header, "Enable/disable header prepending. ", + "run-time", DEFAULT_ENABLE_HEADER); + +module_param(default_dbglevel, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(default_dbglevel, + "The default debug level assigned to messages when NOT explicitly specified.", + "run-time", DEFAULT_DBGLEVEL); + +module_param(scsc_droplevel_wlbt, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_droplevel_wlbt, + "Droplevels for the 'no_tag/wlbt' tag family.", "run-time", + DEFAULT_DROP_ALL); + +module_param(scsc_droplevel_all, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_droplevel_all, + "This droplevel overrides any other, if set to a value >= 0", + "run-time", DEFAULT_ALL_DISABLED); + +module_param(scsc_redirect_to_printk_droplvl, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_redirect_to_printk_droplvl, + "Echoing messages up to the specified loglevel also to kernel standard ring buffer.", + "run-time", DEFAULT_REDIRECT_DROPLVL); + +module_param(scsc_droplevel_atomic, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_droplevel_atomic, + "This droplevel is applied to logmsg emitted in atomic context.", + "run-time", DEFAULT_KEEP_ALL); + +/** + * This macro code has been freely 'inspired' (read copied) from the + * slsi_ original/old debug.c implementaion: it takes care to register + * a new TAG with the debug subsystem including a module param to + * dynamically configure the droplevel for the specified tag at runtime. + * + * ALL debug is ON by default at FULL DEBUG LEVEL. + * There are THREE intended exceptions to this that instead stick to + * level 7: PLAT_MIF MBULK and MXLOG_TRANS tags. + * + * NOTE_CREATING_TAGS: when adding a tag here REMEMBER to add it also + * where required, taking care to maintain the same ordering. + * (Search 4 NOTE_CREATING_TAGS) + */ + +/** + * This macros define module parameters used to configure per-subsystem + * filtering, and assign a default DROPLEVEL. + * + * NOTE THAT the default DROPLEVEL indicates the default value FROM which + * the system will start to discard messages, so as an example: + * + * - if set to SCSC_DBG1 (7) every message with a dbglvl >= 7 will be discarded + * - if set to SCSC_FULL_DEBUG (11) every message is logged + * + * Name, Default DROPLEVEL , FilterTag + */ +ADD_DEBUG_MODULE_PARAM(binary, SCSC_FULL_DEBUG, BINARY); +ADD_DEBUG_MODULE_PARAM(bin_wifi_ctrl_rx, SCSC_FULL_DEBUG, BIN_WIFI_CTRL_RX); +ADD_DEBUG_MODULE_PARAM(bin_wifi_data_rx, SCSC_FULL_DEBUG, BIN_WIFI_DATA_RX); +ADD_DEBUG_MODULE_PARAM(bin_wifi_ctrl_tx, SCSC_FULL_DEBUG, BIN_WIFI_CTRL_TX); +ADD_DEBUG_MODULE_PARAM(bin_wifi_data_tx, SCSC_FULL_DEBUG, BIN_WIFI_DATA_TX); +ADD_DEBUG_MODULE_PARAM(wifi_rx, SCSC_FULL_DEBUG, WIFI_RX); +ADD_DEBUG_MODULE_PARAM(wifi_tx, SCSC_FULL_DEBUG, WIFI_TX); +ADD_DEBUG_MODULE_PARAM(bt_common, SCSC_FULL_DEBUG, BT_COMMON); +ADD_DEBUG_MODULE_PARAM(bt_h4, SCSC_FULL_DEBUG, BT_H4); +ADD_DEBUG_MODULE_PARAM(bt_fw, SCSC_FULL_DEBUG, BT_FW); +ADD_DEBUG_MODULE_PARAM(bt_rx, SCSC_FULL_DEBUG, BT_RX); +ADD_DEBUG_MODULE_PARAM(bt_tx, SCSC_FULL_DEBUG, BT_TX); +ADD_DEBUG_MODULE_PARAM(cpktbuff, SCSC_DBG4, CPKTBUFF); +ADD_DEBUG_MODULE_PARAM(fw_load, SCSC_FULL_DEBUG, FW_LOAD); +ADD_DEBUG_MODULE_PARAM(fw_panic, SCSC_FULL_DEBUG, FW_PANIC); +ADD_DEBUG_MODULE_PARAM(gdb_trans, SCSC_DBG1, GDB_TRANS); +ADD_DEBUG_MODULE_PARAM(mif, SCSC_FULL_DEBUG, MIF); +ADD_DEBUG_MODULE_PARAM(clk20, SCSC_FULL_DEBUG, CLK20); +ADD_DEBUG_MODULE_PARAM(clk20_test, SCSC_FULL_DEBUG, CLK20_TEST); +ADD_DEBUG_MODULE_PARAM(fm, SCSC_FULL_DEBUG, FM); +ADD_DEBUG_MODULE_PARAM(fm_test, SCSC_FULL_DEBUG, FM_TEST); +ADD_DEBUG_MODULE_PARAM(mx_file, SCSC_FULL_DEBUG, MX_FILE); +ADD_DEBUG_MODULE_PARAM(mx_fw, SCSC_FULL_DEBUG, MX_FW); +ADD_DEBUG_MODULE_PARAM(mx_sampler, SCSC_FULL_DEBUG, MX_SAMPLER); +ADD_DEBUG_MODULE_PARAM(mxlog_trans, SCSC_DBG1, MXLOG_TRANS); +ADD_DEBUG_MODULE_PARAM(mxman, SCSC_FULL_DEBUG, MXMAN); +ADD_DEBUG_MODULE_PARAM(mxman_test, SCSC_FULL_DEBUG, MXMAN_TEST); +ADD_DEBUG_MODULE_PARAM(mxmgt_trans, SCSC_FULL_DEBUG, MXMGT_TRANS); +ADD_DEBUG_MODULE_PARAM(mx_mmap, SCSC_DBG1, MX_MMAP); +ADD_DEBUG_MODULE_PARAM(mx_proc, SCSC_FULL_DEBUG, MX_PROC); +ADD_DEBUG_MODULE_PARAM(panic_mon, SCSC_FULL_DEBUG, PANIC_MON); +ADD_DEBUG_MODULE_PARAM(pcie_mif, SCSC_FULL_DEBUG, PCIE_MIF); +ADD_DEBUG_MODULE_PARAM(plat_mif, SCSC_DBG1, PLAT_MIF); +ADD_DEBUG_MODULE_PARAM(kic_common, SCSC_FULL_DEBUG, KIC_COMMON); +ADD_DEBUG_MODULE_PARAM(wlbtd, SCSC_FULL_DEBUG, WLBTD); +ADD_DEBUG_MODULE_PARAM(wlog, SCSC_DEBUG, WLOG); +ADD_DEBUG_MODULE_PARAM(lerna, SCSC_FULL_DEBUG, LERNA); +ADD_DEBUG_MODULE_PARAM(mxcfg, SCSC_FULL_DEBUG, MX_CFG); +#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY +ADD_DEBUG_MODULE_PARAM(init_deinit, SCSC_FULL_DEBUG, SLSI_INIT_DEINIT); +ADD_DEBUG_MODULE_PARAM(netdev, SCSC_DBG4, SLSI_NETDEV); +ADD_DEBUG_MODULE_PARAM(cfg80211, SCSC_FULL_DEBUG, SLSI_CFG80211); +ADD_DEBUG_MODULE_PARAM(mlme, SCSC_FULL_DEBUG, SLSI_MLME); +ADD_DEBUG_MODULE_PARAM(summary_frames, SCSC_FULL_DEBUG, SLSI_SUMMARY_FRAMES); +ADD_DEBUG_MODULE_PARAM(hydra, SCSC_FULL_DEBUG, SLSI_HYDRA); +ADD_DEBUG_MODULE_PARAM(tx, SCSC_FULL_DEBUG, SLSI_TX); +ADD_DEBUG_MODULE_PARAM(rx, SCSC_FULL_DEBUG, SLSI_RX); +ADD_DEBUG_MODULE_PARAM(udi, SCSC_DBG4, SLSI_UDI); +ADD_DEBUG_MODULE_PARAM(wifi_fcq, SCSC_DBG4, SLSI_WIFI_FCQ); +ADD_DEBUG_MODULE_PARAM(hip, SCSC_FULL_DEBUG, SLSI_HIP); +ADD_DEBUG_MODULE_PARAM(hip_init_deinit, SCSC_FULL_DEBUG, SLSI_HIP_INIT_DEINIT); +ADD_DEBUG_MODULE_PARAM(hip_fw_dl, SCSC_FULL_DEBUG, SLSI_HIP_FW_DL); +ADD_DEBUG_MODULE_PARAM(hip_sdio_op, SCSC_FULL_DEBUG, SLSI_HIP_SDIO_OP); +ADD_DEBUG_MODULE_PARAM(hip_ps, SCSC_FULL_DEBUG, SLSI_HIP_PS); +ADD_DEBUG_MODULE_PARAM(hip_th, SCSC_FULL_DEBUG, SLSI_HIP_TH); +ADD_DEBUG_MODULE_PARAM(hip_fh, SCSC_FULL_DEBUG, SLSI_HIP_FH); +ADD_DEBUG_MODULE_PARAM(hip_sig, SCSC_FULL_DEBUG, SLSI_HIP_SIG); +ADD_DEBUG_MODULE_PARAM(func_trace, SCSC_FULL_DEBUG, SLSI_FUNC_TRACE); +ADD_DEBUG_MODULE_PARAM(test, SCSC_FULL_DEBUG, SLSI_TEST); +ADD_DEBUG_MODULE_PARAM(src_sink, SCSC_FULL_DEBUG, SLSI_SRC_SINK); +ADD_DEBUG_MODULE_PARAM(fw_test, SCSC_DBG4, SLSI_FW_TEST); +ADD_DEBUG_MODULE_PARAM(rx_ba, SCSC_FULL_DEBUG, SLSI_RX_BA); +ADD_DEBUG_MODULE_PARAM(tdls, SCSC_FULL_DEBUG, SLSI_TDLS); +ADD_DEBUG_MODULE_PARAM(gscan, SCSC_FULL_DEBUG, SLSI_GSCAN); +ADD_DEBUG_MODULE_PARAM(mbulk, SCSC_DBG1, SLSI_MBULK); +ADD_DEBUG_MODULE_PARAM(smapper, SCSC_DBG1, SLSI_SMAPPER); +ADD_DEBUG_MODULE_PARAM(flowc, SCSC_FULL_DEBUG, SLSI_FLOWC); +#endif +ADD_DEBUG_MODULE_PARAM(test_me, SCSC_FULL_DEBUG, TEST_ME); + +/* Extend this list when you add ADD_DEBUG_MODULE_PARAM, above. + * You must also extend "enum scsc_logring_tags" + */ +int *scsc_droplevels[] = { + &scsc_droplevel_binary, + &scsc_droplevel_bin_wifi_ctrl_rx, + &scsc_droplevel_bin_wifi_data_rx, + &scsc_droplevel_bin_wifi_ctrl_tx, + &scsc_droplevel_bin_wifi_data_tx, + &scsc_droplevel_wlbt, + &scsc_droplevel_wifi_rx, + &scsc_droplevel_wifi_tx, + &scsc_droplevel_bt_common, + &scsc_droplevel_bt_h4, + &scsc_droplevel_bt_fw, + &scsc_droplevel_bt_rx, + &scsc_droplevel_bt_tx, + &scsc_droplevel_cpktbuff, + &scsc_droplevel_fw_load, + &scsc_droplevel_fw_panic, + &scsc_droplevel_gdb_trans, + &scsc_droplevel_mif, + &scsc_droplevel_clk20, + &scsc_droplevel_clk20_test, + &scsc_droplevel_fm, + &scsc_droplevel_fm_test, + &scsc_droplevel_mx_file, + &scsc_droplevel_mx_fw, + &scsc_droplevel_mx_sampler, + &scsc_droplevel_mxlog_trans, + &scsc_droplevel_mxman, + &scsc_droplevel_mxman_test, + &scsc_droplevel_mxmgt_trans, + &scsc_droplevel_mx_mmap, + &scsc_droplevel_mx_proc, + &scsc_droplevel_panic_mon, + &scsc_droplevel_pcie_mif, + &scsc_droplevel_plat_mif, + &scsc_droplevel_kic_common, + &scsc_droplevel_wlbtd, + &scsc_droplevel_wlog, + &scsc_droplevel_lerna, + &scsc_droplevel_mxcfg, +#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY + &scsc_droplevel_init_deinit, + &scsc_droplevel_netdev, + &scsc_droplevel_cfg80211, + &scsc_droplevel_mlme, + &scsc_droplevel_summary_frames, + &scsc_droplevel_hydra, + &scsc_droplevel_tx, + &scsc_droplevel_rx, + &scsc_droplevel_udi, + &scsc_droplevel_wifi_fcq, + &scsc_droplevel_hip, + &scsc_droplevel_hip_init_deinit, + &scsc_droplevel_hip_fw_dl, + &scsc_droplevel_hip_sdio_op, + &scsc_droplevel_hip_ps, + &scsc_droplevel_hip_th, + &scsc_droplevel_hip_fh, + &scsc_droplevel_hip_sig, + &scsc_droplevel_func_trace, + &scsc_droplevel_test, + &scsc_droplevel_src_sink, + &scsc_droplevel_fw_test, + &scsc_droplevel_rx_ba, + &scsc_droplevel_tdls, + &scsc_droplevel_gscan, + &scsc_droplevel_mbulk, + &scsc_droplevel_flowc, + &scsc_droplevel_smapper, +#endif + &scsc_droplevel_test_me, /* Must be last */ +}; + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static int logring_collect(struct scsc_log_collector_client *collect_client, size_t size) +{ + int ret = 0, saved_droplevel; + + if (!the_ringbuf) + return 0; + + /** + * Inhibit logring during collection overriding with scsc_droplevel_all + */ + saved_droplevel = scsc_droplevel_all; + scsc_droplevel_all = DEFAULT_DROP_ALL; + + /* Write buffer */ + ret = scsc_log_collector_write(the_ringbuf->buf, the_ringbuf->bsz, 1); + + scsc_droplevel_all = saved_droplevel; + + return ret; +} +#endif + +static int scsc_reset_all_droplevels_to_set_param_cb(const char *val, + const struct kernel_param *kp) +{ + int ret = 0, i = 0; + long rval = 0; + + if (!val) + return -EINVAL; + ret = kstrtol(val, 10, &rval); + if (!ret) { + if (rval < 0) + return -EINVAL; + scsc_droplevel_wlbt = (int)rval; + for (i = 0; i < ARRAY_SIZE(scsc_droplevels); i++) + *scsc_droplevels[i] = (int)rval; + scsc_reset_all_droplevels_to = (int)rval; + scsc_printk_tag(FORCE_PRK, NO_TAG, + KERN_INFO"Reset ALL droplevels to %d\n", + scsc_reset_all_droplevels_to); + } + return ret; +} + +static struct kernel_param_ops scsc_reset_droplvl_ops = { + .set = scsc_reset_all_droplevels_to_set_param_cb, + .get = NULL, +}; +module_param_cb(scsc_reset_all_droplevels_to, &scsc_reset_droplvl_ops, + NULL, 0200); +MODULE_PARM_DESC(scsc_reset_all_droplevels_to, + "Reset ALL droplevels to the requested value. Effective @run-time."); + + +/* SCSC_PRINTK API and Helpers */ +static inline int get_debug_level(const char *fmt) +{ + int level; + + if (fmt && *fmt == SCSC_SOH && *(fmt + 1)) + level = *(fmt + 1) - '0'; + else + level = default_dbglevel; + return level; +} + +static inline void drop_message_level_macro(const char *fmt, char **msg) +{ + if (fmt && *fmt == SCSC_SOH && *(fmt + 1)) { + if (msg) + *msg = (char *)(fmt + 2); + } else if (msg) { + *msg = (char *)fmt; + } +} + +/** + * The workhorse function that receiving a droplevel to enforce, and a pair + * format_string/va_list decides what to do: + * - drop + * OR + * - insert into ring buffer accounting for wrapping + * + * ... then wakes up any waiting reading process + */ +static inline int _scsc_printk(int level, int tag, + const char *fmt, va_list args) +{ + int written = 0; + char *msg_head = NULL; + + if (!initialized || !enable || !fmt || + ((scsc_droplevel_all < 0 && level >= *scsc_droplevels[tag]) || + (scsc_droplevel_all >= 0 && level >= scsc_droplevel_all))) + return written; + drop_message_level_macro(fmt, &msg_head); + written = push_record_string(the_ringbuf, tag, level, + prepend_header, msg_head, args); + return written; +} + +/** + * Embeds the filtering behaviour towards std kenrel ring buffer for + * non binary stuff, and decides what to do based on current user config. + * Note that printk redirect doplevel filter is now completely disjoint + * from normal LogRing droplevel filters. + */ +static inline +void handle_klogbuf_out_string(int level, struct device *dev, int tag, + const char *fmt, va_list args, int force) +{ + if (IS_PRINTK_REDIRECT_ALLOWED(force, level, tag)) { + if (!dev) + vprintk_emit(0, level, NULL, 0, fmt, args); + else + dev_vprintk_emit(level, dev, fmt, args); + } +} + +const char *map2kern[] = { + KERN_EMERG, + KERN_ALERT, + KERN_CRIT, + KERN_ERR, + KERN_WARNING, + KERN_NOTICE, + KERN_INFO, + KERN_DEBUG +}; + +/** + * Embeds the filtering behaviour towards std kenrel ring buffer for + * BINARY stuff, and decides what to do based on current user config. + * Note that printk redirect doplevel filter is now completely disjoint + * from normal LogRing droplevel filters. + */ +static inline +void handle_klogbuf_out_binary(int level, int tag, const void *start, + size_t len, int force) +{ + if (IS_PRINTK_REDIRECT_ALLOWED(force, level, tag)) { + if (level < SCSC_MIN_DBG || level >= ARRAY_SIZE(map2kern)) + level = ARRAY_SIZE(map2kern) - 1; + print_hex_dump(map2kern[level], SCSC_PREFIX"SCSC_HEX->|", + DUMP_PREFIX_NONE, 16, 1, start, len, false); + } +} + +/* + * scsc_printk - it's main API entry to the event logging mechanism. Prints + * the specified format string to the underlying ring_buffer, injecting + * timestamp and context information at the start of the line while + * classifying and filtering the message suing the specified TAG identifier. + * + * This function assumes that you'll never write a line longer than + * BASE_SPARE_SZ bytes; if this limit is obeyed any input string is correctly + * wrapped when placed at the end of the buffer. Any longer line will be + * trucated. + * This function recognize Kernel style debug level KERN_, checking the FIRST + * byte for ASCII SOH in order to recognize if some printk style kernel debug + * level has been specified. + * SO you can use KERN_INFO KERN_ERR etc etc INLINE macros to specify the + * desired debug level: that will be checked against the droplevel specified. + * If NOT specified a default debug level is assigned following what specified + * in module parameter default_dbglevel. + * + * It's usually NOT used directly but through the means of utility macros that + * can be easily compiled out in production builds. + */ +int scsc_printk_tag(int force, int tag, const char *fmt, ...) +{ + int ret = 0, level = 0; + va_list args; + + /* Cannot use BINARY tag with strings logging */ + if (tag < NO_TAG || tag > MAX_TAG) + return ret; + level = get_debug_level(fmt); + if ((in_interrupt() && level >= scsc_droplevel_atomic)) + return ret; + va_start(args, fmt); + handle_klogbuf_out_string(level, NULL, tag, fmt, args, force); + va_end(args); + /* restart varargs */ + va_start(args, fmt); + ret = _scsc_printk(level, tag, fmt, args); + va_end(args); + return ret; +} +EXPORT_SYMBOL(scsc_printk_tag); + + +/** + * This is a variation on the main API that allows to specify loglevel + * by number. + */ +int scsc_printk_tag_lvl(int tag, int level, const char *fmt, ...) +{ + int ret = 0; + va_list args; + + /* Cannot use BINARY tag with strings logging */ + if (tag < NO_TAG || tag > MAX_TAG) + return ret; + if ((in_interrupt() && level >= scsc_droplevel_atomic)) + return ret; + va_start(args, fmt); + handle_klogbuf_out_string(level, NULL, tag, fmt, args, NO_FORCE_PRK); + va_end(args); + /* restart varargs */ + va_start(args, fmt); + ret = _scsc_printk(level, tag, fmt, args); + va_end(args); + return ret; +} +EXPORT_SYMBOL(scsc_printk_tag_lvl); + + +/** + * This is a variation on the main API that allows to specify a + * struct device reference. + */ +int scsc_printk_tag_dev(int force, int tag, struct device *dev, + const char *fmt, ...) +{ + int ret = 0, level = 0; + va_list args; + + /* Cannot use BINARY tag with strings logging */ + if (tag < NO_TAG || tag > MAX_TAG) + return ret; + level = get_debug_level(fmt); + if ((in_interrupt() && level >= scsc_droplevel_atomic)) + return ret; + va_start(args, fmt); + handle_klogbuf_out_string(level, dev, tag, fmt, args, force); + va_end(args); + /* restart varargs */ + va_start(args, fmt); + ret = _scsc_printk(level, tag, fmt, args); + va_end(args); + return ret; +} +EXPORT_SYMBOL(scsc_printk_tag_dev); + +/** + * This is a variation on the main API that allows to specify a + * struct device reference and an explicit numerical debug level. + */ +int scsc_printk_tag_dev_lvl(int force, int tag, struct device *dev, + int level, const char *fmt, ...) +{ + int ret = 0; + va_list args; + + /* Cannot use BINARY tag with strings logging */ + if (tag < NO_TAG || tag > MAX_TAG) + return ret; + if ((in_interrupt() && level >= scsc_droplevel_atomic)) + return ret; + va_start(args, fmt); + handle_klogbuf_out_string(level, dev, tag, fmt, args, force); + va_end(args); + /* restart varargs */ + va_start(args, fmt); + ret = _scsc_printk(level, tag, fmt, args); + va_end(args); + return ret; +} +EXPORT_SYMBOL(scsc_printk_tag_dev_lvl); + +/** + * This is a variation on the main API used to push binary blob into the ring. + */ +int scsc_printk_bin(int force, int tag, int dlev, const void *start, size_t len) +{ + int ret = 0; + + /* Cannot use NON BINARY tag with strings logging + * or NULLs start/len + */ + if (!start || !len || tag < FIRST_BIN_TAG || tag > LAST_BIN_TAG) + return ret; + dlev = (dlev >= 0) ? dlev : default_dbglevel; + if ((in_interrupt() && dlev >= scsc_droplevel_atomic)) + return ret; + handle_klogbuf_out_binary(dlev, tag, start, len, force); + /* consider proper tag droplevel */ + if (!initialized || !enable || !start || + (scsc_droplevel_all < 0 && *scsc_droplevels[tag] <= dlev) || + (scsc_droplevel_all >= 0 && scsc_droplevel_all <= dlev)) + return ret; + ret = push_record_blob(the_ringbuf, tag, dlev, + prepend_header, start, len); + return ret; +} +EXPORT_SYMBOL(scsc_printk_bin); + +/* + * This is a very basic mechanism to have implement the dynamic switch + * for one user (currently WLAN). If multiple users are + * required to use the dynamic logring switch, a new registration + * mechanism based on requests and use_count should be implemented to avoid one service + * re-enabling logring when some other has requested not to do so. + */ +int scsc_logring_enable(bool logging_enable) +{ + scsc_printk_tag(FORCE_PRK, NO_TAG, "scsc_logring %s\n", logging_enable ? "enable" : "disable"); + /* User has requested to disable logring */ + if (!logging_enable && enable) { + cached_enable = true; + enable = 0; + scsc_printk_tag(FORCE_PRK, NO_TAG, "Logring disabled\n"); + } else if (logging_enable && cached_enable) { + cached_enable = false; + enable = 1; + scsc_printk_tag(FORCE_PRK, NO_TAG, "Logring re-enabled\n"); + } else { + scsc_printk_tag(FORCE_PRK, NO_TAG, "Ignored\n"); + } + return 0; +} +EXPORT_SYMBOL(scsc_logring_enable); + + +MODULE_DESCRIPTION("SCSC Event Logger"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/misc/samsung/scsc/scsc_logring_main.h b/drivers/misc/samsung/scsc/scsc_logring_main.h new file mode 100644 index 000000000000..85afd4e7b3a6 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_main.h @@ -0,0 +1,34 @@ +/******************************************************************************** + * + * Copyright (c) 2016 - 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + ********************************************************************************/ + +#ifndef _SCSC_LOGRING_MAIN_H_ +#define _SCSC_LOGRING_MAIN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scsc_logring_common.h" + +#define ADD_DEBUG_MODULE_PARAM(tagname, default_level, filter) \ + int scsc_droplevel_ ## tagname = default_level; \ + module_param(scsc_droplevel_ ## tagname, int, S_IRUGO | S_IWUSR); \ + SCSC_MODPARAM_DESC(scsc_droplevel_ ## tagname, \ + "Droplevels for the '" # tagname "' family.", \ + "run-time", default_level) + +#define IS_PRINTK_REDIRECT_ALLOWED(ff, level, tag) \ + ((ff) == FORCE_PRK || \ + ((ff) != NO_ECHO_PRK && (level) < scsc_redirect_to_printk_droplvl)) + +#endif /* _SCSC_LOGRING_MAIN_H_ */ diff --git a/drivers/misc/samsung/scsc/scsc_logring_ring.c b/drivers/misc/samsung/scsc/scsc_logring_ring.c new file mode 100644 index 000000000000..432eb6e24ec9 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_ring.c @@ -0,0 +1,831 @@ +/**************************************************************************** + * + * Copyright (c) 2016-2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include "scsc_logring_ring.h" + +#ifdef CONFIG_SCSC_STATIC_RING_SIZE +static char a_ring[CONFIG_SCSC_STATIC_RING_SIZE + BASE_SPARE_SZ] __aligned(4); +#endif + +static int scsc_decode_binary_len = DEFAULT_BIN_DECODE_LEN; +module_param(scsc_decode_binary_len, int, S_IRUGO | S_IWUSR); +SCSC_MODPARAM_DESC(scsc_decode_binary_len, + "When reading a binary record dump these bytes-len in ASCII human readable form when reading", + "run-time", DEFAULT_BIN_DECODE_LEN); + +/* + * NOTE_CREATING_TAGS: when adding a tag string here REMEMBER to add + * it also where required, taking care to maintain the same ordering. + * (Search 4 NOTE_CREATING_TAGS) + */ +const char *tagstr[] = { + "binary", + "bin_wifi_ctrl_rx", + "bin_wifi_data_rx", + "bin_wifi_ctrl_tx", + "bin_wifi_data_tx", + "wlbt", /* this is the generic one...NO_TAG */ + "wifi_rx", + "wifi_tx", + "bt_common", + "bt_h4", + "bt_fw", + "bt_rx", + "bt_tx", + "cpktbuff", + "fw_load", + "fw_panic", + "gdb_trans", + "mif", + "clk20", + "clk20_test", + "fm", + "fm_test", + "mx_file", + "mx_fw", + "mx_sampler", + "mxlog_trans", + "mxman", + "mxman_test", + "mxmgt_trans", + "mx_mmap", + "mx_proc", + "panic_mon", + "pcie_mif", + "plat_mif", + "kic_common", + "wlbtd", + "wlog", +#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY + "init_deinit", + "netdev", + "cfg80211", + "mlme", + "summary_frames", + "hydra", + "tx", + "rx", + "udi", + "wifi_fcq", + "hip", + "hip_init_deinit", + "hip_fw_dl", + "hip_sdio_op", + "hip_ps", + "hip_th", + "hip_fh", + "hip_sig", + "func_trace", + "test", + "src_sink", + "fw_test", + "rx_ba", + "tdls", + "gscan", + "mbulk", + "flowc", + "smapper", +#endif + "test_me" +}; + +/** + * Calculate and returns the CRC32 for the provided record and record pos. + * Before calculating the CRC32 the crc field is temporarily substituted + * with the 32 LSB record relative starting position. + * Assumes the rec ptr area-validity has been checked upstream in the + * caller chain. + * We SKIP the fixed blob of the SYNC field that is placed ahead of + * CRC field. + * Assumes the related ring buffer is currently atomically accessed by + * caller. MUST NOT SLEEP. + */ +static inline uint32_t get_calculated_crc(struct scsc_ring_record *rec, + loff_t pos) +{ + uint32_t calculated_crc = 0; + uint32_t saved_crc = 0; + + saved_crc = rec->crc; + rec->crc = (uint32_t)pos; + /* we skip the fixed sync calculating crc */ + calculated_crc = + crc32_le(~0, (unsigned char const *)&rec->crc, + SCSC_CRC_RINGREC_SZ); + rec->crc = saved_crc; + return calculated_crc; +} + +/** + * Checks for record CRC sanity. + * Assumes the related ring buffer is currently atomically accessed by + * caller. MUST NOT SLEEP. + */ +static inline bool is_record_crc_valid(struct scsc_ring_record *rec, + loff_t pos) +{ + uint32_t calculated_crc = 0; + + calculated_crc = get_calculated_crc(rec, pos); + return calculated_crc == rec->crc; +} + +/** + * Calculate the proper CRC and set it into the crc field + * Assumes the related ring buffer is currently atomically accessed by + * caller. MUST NOT SLEEP. + */ +static inline void finalize_record_crc(struct scsc_ring_record *rec, + loff_t pos) +{ + uint32_t calculated_crc = 0; + + if (!rec) + return; + rec->crc = (uint32_t)pos; + calculated_crc = + crc32_le(~0, (unsigned char const *)&rec->crc, + SCSC_CRC_RINGREC_SZ); + rec->crc = calculated_crc; +} + +/** + * This function analyzes the pos provided relative to the provided + * ring, just to understand if it can be safely dereferenced. + * Assumes RING is already locked. + */ +static inline bool is_ring_pos_safe(struct scsc_ring_buffer *rb, + loff_t pos) +{ + if (!rb || pos > rb->bsz || pos < 0) + return false; + /* NOT Wrapped */ + if (rb->head > rb->tail && pos > rb->head) + return false; + /* Wrapped... */ + if (rb->head < rb->tail && + (pos > rb->head && pos < rb->tail)) + return false; + return true; +} + +/** + * This sanitizes record header before using it. + * It must be in the proper area related to head and tail and + * the CRC must fit the header. + */ +static inline bool is_ring_read_pos_valid(struct scsc_ring_buffer *rb, + loff_t pos) +{ + if (!is_ring_pos_safe(rb, pos)) + goto oos; + /* We do not check for SYNC before CRC since most of the time + * you are NOT OutOfSync and so you MUST check CRC anyway. + * It will be useful only for resync. + * At last...Check CRC ... doing this check LAST avoids the risk of + * dereferencing an already dangling pos pointer. + */ + if (!is_record_crc_valid(SCSC_GET_REC(rb, pos), pos)) + goto oos; + return true; +oos: + if (rb) + rb->oos++; + return false; +} + + +/** + * Buid a header into the provided buffer, + * and append the optional trail string + */ +static inline +int build_header(char *buf, int blen, struct scsc_ring_record *r, + const char *trail) +{ + int written = 0; + struct timeval tval = {}; + + tval = ns_to_timeval(r->nsec); + written = scnprintf(buf, blen, + "<%d>[%6lu.%06ld] [c%d] [%c] [%s] :: %s", + r->lev, tval.tv_sec, tval.tv_usec, + r->core, (char)r->ctx, tagstr[r->tag], + (trail) ? : ""); + return written; +} + + +/** + * We're going to overwrite something writing from the head toward the tail + * so we must search for the next tail far enough from head in oder not to be + * overwritten: that will be our new tail after the wrap over. + */ +static inline +loff_t find_next_tail_far_enough_from_start(struct scsc_ring_buffer *rb, + loff_t start, int len) +{ + loff_t new_tail = rb->tail; + + while (start + len >= new_tail && new_tail != rb->last) { + new_tail = SCSC_GET_NEXT_REC_ENTRY_POS(rb, new_tail); + rb->records--; + } + if (start + len >= new_tail) { + new_tail = 0; + rb->records--; + } + return new_tail; +} + +/** + * This handles the just plain append of a record to head without + * any need of wrapping or overwriting current tail + * You can provide two buffer here: the second, hbuf, is optional + * and will be written first. This is to account for the binary case + * in which the record data are written at first into the spare area + * (like we do with var strings, BUT then the bulk of binary data is + * written directly in place into the ring without double copies. + */ +static inline +void scsc_ring_buffer_plain_append(struct scsc_ring_buffer *rb, + const char *srcbuf, int slen, + const char *hbuf, int hlen) +{ + /* empty condition is special case */ + if (rb->records) + rb->head += SCSC_GET_SLOT_LEN(rb, rb->head); + if (hbuf) + memcpy(SCSC_GET_HEAD_PTR(rb), hbuf, hlen); + else + hlen = 0; + memcpy(SCSC_GET_HEAD_PTR(rb) + hlen, srcbuf, slen); + finalize_record_crc((struct scsc_ring_record *)SCSC_GET_HEAD_PTR(rb), + rb->head); + rb->records++; + if (rb->head > rb->last) + rb->last = rb->head; +} + + +/** + * This handles the case in which appending current record must account + * for overwriting: this sitiation can happen at the end of ring if we do NOT + * have enough space for the current record, or in any place when the buffer + * has wrapped, head is before tail and there's not enough space to write + * between current head and tail. + */ +static inline +void scsc_ring_buffer_overlap_append(struct scsc_ring_buffer *rb, + const char *srcbuf, int slen, + const char *hbuf, int hlen) +{ + if (rb->head < rb->tail && + slen + hlen < rb->bsz - SCSC_GET_NEXT_SLOT_POS(rb, rb->head)) + rb->head += SCSC_GET_SLOT_LEN(rb, rb->head); + else { + rb->last = rb->head; + rb->head = 0; + rb->tail = 0; + rb->wraps++; + } + rb->tail = + find_next_tail_far_enough_from_start(rb, rb->head, slen + hlen); + if (hbuf) + memcpy(SCSC_GET_HEAD_PTR(rb), hbuf, hlen); + else + hlen = 0; + memcpy(SCSC_GET_HEAD_PTR(rb) + hlen, srcbuf, slen); + finalize_record_crc((struct scsc_ring_record *)SCSC_GET_HEAD_PTR(rb), + rb->head); + rb->records++; + if (rb->head > rb->last) + rb->last = rb->head; +} + + +/** + * This uses the spare area to prepare the record descriptor and to expand + * the format string into the spare area in order to get the final lenght of + * the whole record+data. Data is pre-pended with a header representing the + * data hold in binary form in the record descriptor. + * This data duplication helps when we'll read back a record holding string + * data, we won't have to build the header on the fly during the read. + */ +static inline +int tag_writer_string(char *spare, int tag, int lev, + int prepend_header, const char *msg_head, va_list args) +{ + int written; + char bheader[SCSC_HBUF_LEN] = {}; + struct scsc_ring_record *rrec; + + /* Fill record in place */ + rrec = (struct scsc_ring_record *)spare; + SCSC_FILL_RING_RECORD(rrec, tag, lev); + if (prepend_header) + build_header(bheader, SCSC_HBUF_LEN, rrec, NULL); + written = scnprintf(SCSC_GET_REC_BUF(spare), + BASE_SPARE_SZ - SCSC_RINGREC_SZ, "%s", bheader); + /** + * NOTE THAT + * --------- + * vscnprintf retvalue is the number of characters which have been + * written into the @buf NOT including the trailing '\0'. + * If @size is == 0 the function returns 0. + * Here we enforce a line lenght limit equal to + * BASE_SPARE_SZ - SCSC_RINGREC_SZ. + */ + written += vscnprintf(SCSC_GET_REC_BUF(spare) + written, + BASE_SPARE_SZ - SCSC_RINGREC_SZ - written, + msg_head, args); + /* complete record metadata */ + rrec->len = written; + return written; +} + +/** + * A ring API function to push variable length format string into the buffer + * After the record has been created and pushed into the ring any process + * waiting on the related waiting queue is awakened. + */ +int push_record_string(struct scsc_ring_buffer *rb, int tag, int lev, + int prepend_header, const char *msg_head, va_list args) +{ + int rec_len = 0; + loff_t free_bytes; + unsigned long flags; + + /* Prepare ring_record and header if needed */ + raw_spin_lock_irqsave(&rb->lock, flags); + rec_len = tag_writer_string(rb->spare, tag, lev, prepend_header, + msg_head, args); + /* Line too long anyway drop */ + if (rec_len >= BASE_SPARE_SZ - SCSC_RINGREC_SZ) { + raw_spin_unlock_irqrestore(&rb->lock, flags); + return 0; + } + free_bytes = SCSC_RING_FREE_BYTES(rb); + /** + * Evaluate if it's a trivial append or if we must account for + * any overwrap. Note that we do NOT truncate record across ring + * boundaries, if a record does NOT fit at the end of buffer, + * we'll write it from start directly. + */ + if (rec_len + SCSC_RINGREC_SZ < free_bytes) + scsc_ring_buffer_plain_append(rb, rb->spare, + SCSC_RINGREC_SZ + rec_len, + NULL, 0); + else + scsc_ring_buffer_overlap_append(rb, rb->spare, + SCSC_RINGREC_SZ + rec_len, + NULL, 0); + rb->written += rec_len; + raw_spin_unlock_irqrestore(&rb->lock, flags); + /* WAKEUP EVERYONE WAITING ON THIS BUFFER */ + wake_up_interruptible(&rb->wq); + return rec_len; +} + +/* This simply builds up a record descriptor for a binary entry. */ +static inline +int tag_writer_binary(char *spare, int tag, int lev, size_t hexlen) +{ + struct scsc_ring_record *rrec; + + rrec = (struct scsc_ring_record *)spare; + SCSC_FILL_RING_RECORD(rrec, tag, lev); + rrec->len = hexlen; + + return hexlen; +} + +/** + * A ring API function to push binary data into the ring buffer. Binary data + * is copied from the start/len specified location. + * After the record has been created and pushed into the ring any process + * waiting on the related waiting queue is awakened. + */ +int push_record_blob(struct scsc_ring_buffer *rb, int tag, int lev, + int prepend_header, const void *start, size_t len) +{ + loff_t free_bytes; + unsigned long flags; + + if (len > SCSC_MAX_BIN_BLOB_SZ) + len = SCSC_MAX_BIN_BLOB_SZ; + /* Prepare ring_record and header if needed */ + raw_spin_lock_irqsave(&rb->lock, flags); + memset(rb->spare, 0x00, rb->ssz); + tag_writer_binary(rb->spare, tag, lev, len); + free_bytes = SCSC_RING_FREE_BYTES(rb); + if (len + SCSC_RINGREC_SZ < free_bytes) + scsc_ring_buffer_plain_append(rb, start, len, + rb->spare, SCSC_RINGREC_SZ); + else + scsc_ring_buffer_overlap_append(rb, start, len, + rb->spare, SCSC_RINGREC_SZ); + rb->written += len; + raw_spin_unlock_irqrestore(&rb->lock, flags); + /* WAKEUP EVERYONE WAITING ON THIS BUFFER */ + wake_up_interruptible(&rb->wq); + return len; +} + +/* A simple reader used to retrieve a string from the record + * It always return ONE WHOLE RECORD if it fits the provided tbuf OR NOTHING. + */ +static inline +size_t tag_reader_string(char *tbuf, struct scsc_ring_buffer *rb, + int start_rec, size_t tsz) +{ + size_t max_chunk = SCSC_GET_REC_LEN(SCSC_GET_PTR(rb, start_rec)); + + if (max_chunk <= tsz) + memcpy(tbuf, SCSC_GET_REC_BUF(rb->buf + start_rec), max_chunk); + else + max_chunk = 0; + return max_chunk; +} + +/* + * Helper to dump binary data in ASCII readable form up to + * scsc_decode_binary_len bytes: when such modparam is set to -1 + * this will dump all the available data. Data is dumped onto the + * output buffer with an endianity that conforms to the data as + * dumped by the print_hex_dump() kernel standard facility. + */ +static inline +int binary_hexdump(char *tbuf, int tsz, struct scsc_ring_record *rrec, + int start, int dlen) +{ + int i, j, bytepos; + unsigned char *blob = SCSC_GET_REC_BUF(rrec); + char *hmap = "0123456789abcdef"; + + /** + * Scan the buffer reversing endianity when appropriate and + * producing ASCII human readable output while obeying chosen + * maximum decoden_len dlen. + */ + for (j = start, i = 0; j < tsz && i < rrec->len && i < dlen; i += 4) { + bytepos = (rrec->len - i - 1 >= 3) ? 3 : rrec->len - i - 1; + /* Reverse endianity to little only on 4-byte boundary */ + if (bytepos == 3) { + for (; bytepos >= 0; bytepos--) { + if (i + bytepos >= dlen) + continue; + tbuf[j++] = hmap[blob[i + bytepos] >> 4 & 0x0f]; + tbuf[j++] = hmap[blob[i + bytepos] & 0x0f]; + } + } else { + int bb; + + /** + * Trailing bytes NOT aligned on a 4-byte boundary + * should be decoded maintaining the original endianity. + * This way we obtain a binary output perfectly equal + * to the one generated by the original UDI tools. + */ + for (bb = 0; bb <= bytepos; bb++) { + if (i + bb >= dlen) + break; + tbuf[j++] = hmap[blob[i + bb] >> 4 & 0x0f]; + tbuf[j++] = hmap[blob[i + bb] & 0x0f]; + } + } + } + return j; +} + +/** + * A reader used to dump binary records: this function first of all + * builds a proper human readable header to identify the record with the + * usual debuglevel and timestamps and then DUMPS some of the binary blob + * in ASCII human readable form: how much is dumped depends on the module + * param scsc_decode_binary_len (default 8 bytes). + * ANYWAY ONLY ONE WHOLE RECORD IS DUMPED OR NOTHING IF IT DOES NOT FIT + * THE PROVIDED DESTINATION BUFFER TBUF. + */ +static inline +size_t tag_reader_binary(char *tbuf, struct scsc_ring_buffer *rb, + int start_rec, size_t tsz) +{ + size_t written; + int declen = scsc_decode_binary_len; + struct scsc_ring_record *rrec; + char bheader[SCSC_HBUF_LEN] = {}; + char binfo[SCSC_BINFO_LEN] = {}; + size_t max_chunk; + + rrec = (struct scsc_ring_record *)SCSC_GET_PTR(rb, start_rec); + if (declen < 0 || declen > rrec->len) + declen = rrec->len; + if (declen) + snprintf(binfo, SCSC_BINFO_LEN, "HEX[%d/%d]: ", + declen, rrec->len); + written = build_header(bheader, SCSC_HBUF_LEN, rrec, + declen ? binfo : ""); + /* Account for byte decoding: two ASCII char for each byte */ + max_chunk = written + (declen * 2); + if (max_chunk <= tsz) { + memcpy(tbuf, bheader, written); + if (declen) + written = binary_hexdump(tbuf, tsz - written, + rrec, written, declen); + tbuf[written] = '\n'; + written++; + } else { + written = 0; + } + return written; +} + +/** + * This is a utility function to read from the specified ring_buffer + * up to 'tsz' amount of data starting from position record 'start_rec'. + * This function reads ONLY UP TO ONE RECORD and returns the effective + * amount of data bytes read; it invokes the proper tag_reader_* helper + * depending on the specific record is handling. + * Data is copied to a TEMP BUFFER provided by user of this function, + * IF AND ONLY IF a whole record CAN fit into the space available in the + * destination buffer, otherwise record is NOT copied and 0 is returned. + * This function DOES NOT SLEEP. + * Caller IS IN CHARGE to SOLVE any sync issue on provided tbuf and + * underlying ring buffer. + * + * @tbuf: a temp buffer destination for the read data + * @rb: the ring_buffer to use. + * @start_rec: the record from which to start expressed as a record + * starting position. + * @tsz: the available space in tbuf + * @return size_t: returns the bytes effectively read. + */ +static inline size_t +_read_one_whole_record(void *tbuf, struct scsc_ring_buffer *rb, + int start_rec, size_t tsz) +{ + if (SCSC_GET_REC_TAG(SCSC_GET_PTR(rb, start_rec)) > LAST_BIN_TAG) + return tag_reader_string(tbuf, rb, start_rec, tsz); + else + return tag_reader_binary(tbuf, rb, start_rec, tsz); +} + + +/** + * This just inject a string into the buffer to signal we've gone + * OUT OF SYNC due to Ring WRAPPING too FAST, noting how many bytes + * we resynced. + */ +static inline size_t mark_out_of_sync(char *tbuf, size_t tsz, + int resynced_bytes) +{ + size_t written = 0; + struct timeval tval = {}; + + tval = ns_to_timeval(local_clock()); + /* We should write something even if truncated ... */ + written = scnprintf(tbuf, tsz, + "<7>[%6lu.%06ld] [c%d] [P] [OOS] :: [[[ OUT OF SYNC -- RESYNC'ED BYTES %d ]]]\n", + tval.tv_sec, tval.tv_usec, smp_processor_id(), + resynced_bytes); + return written; +} + +/** + * Attempt resync searching for SYNC pattern and verifying CRC. + * ASSUMES that the invalid_pos provided is anyway safe to access, since + * it should be checked by the caller in advance. + * The amount of resynced bytes are not necessarily the number of bytes + * effectively lost....they could be much more...imagine the ring had + * overwrap multiple times before detecting OUT OF SYNC. + */ +static inline loff_t reader_resync(struct scsc_ring_buffer *rb, + loff_t invalid_pos, int *resynced_bytes) +{ + int bytes = 0; + loff_t sync_pos = rb->head; + struct scsc_ring_record *candidate = SCSC_GET_REC(rb, invalid_pos); + + *resynced_bytes = 0; + /* Walking thorugh the ring in search of the sync one byte at time */ + while (invalid_pos != rb->head && + !SCSC_IS_REC_SYNC_VALID(candidate)) { + invalid_pos = (invalid_pos < rb->last) ? + (invalid_pos + sizeof(u8)) : 0; + bytes += sizeof(u8); + candidate = SCSC_GET_REC(rb, invalid_pos); + } + if (invalid_pos == rb->head || + (SCSC_IS_REC_SYNC_VALID(candidate) && + is_record_crc_valid(candidate, invalid_pos))) { + sync_pos = invalid_pos; + *resynced_bytes = bytes; + } + return sync_pos; +} + +/** + * An Internal API ring function to retrieve into the provided tbuf + * up to N WHOLE RECORDS starting from *next_rec. + * It STOPS collecting records if: + * - NO MORE RECORDS TO READ: last_read_record record is head + * - NO MORE SPACE: on provided destination tbuf to collect + * one more WHOLE record + * - MAX NUMBER OF REQUIRED RECORDS READ: if max_recs was passed in + * as ZERO it means read as much as you can till head is reached. + * + * If at start it detects and OUT OF SYNC, so that next_rec is + * NO MORE pointing to a valid record, it tries to RE-SYNC on next + * GOOD KNOWN record or to HEAD as last resource and injects into + * the user buffer an OUT OF SYNC marker record. + * + * ASSUMES proper locking and syncing ALREADY inplace...does NOT SLEEP. + */ +size_t read_next_records(struct scsc_ring_buffer *rb, int max_recs, + loff_t *last_read_rec, void *tbuf, size_t tsz) +{ + size_t bytes_read = 0, last_read = -1; + int resynced_bytes = 0, records = 0; + loff_t next_rec = 0; + + /* Nothing to read...simply return 0 causing reader to exit */ + if (*last_read_rec == rb->head) + return bytes_read; + if (!is_ring_read_pos_valid(rb, *last_read_rec)) { + if (is_ring_pos_safe(rb, *last_read_rec)) { + /* Try to resync from *last_read_rec INVALID POS */ + next_rec = reader_resync(rb, *last_read_rec, + &resynced_bytes); + } else { + /* Skip to head...ONLY safe place known in tis case. */ + resynced_bytes = 0; + next_rec = rb->head; + } + bytes_read += mark_out_of_sync(tbuf, tsz, resynced_bytes); + } else { + /* next to read....we're surely NOT already at rb->head here */ + next_rec = (*last_read_rec != rb->last) ? + SCSC_GET_NEXT_SLOT_POS(rb, *last_read_rec) : 0; + } + do { + /* Account for last read */ + last_read = bytes_read; + bytes_read += + _read_one_whole_record(tbuf + bytes_read, rb, + next_rec, tsz - bytes_read); + /* Did a WHOLE record fit into available tbuf ? */ + if (bytes_read != last_read) { + records++; + *last_read_rec = next_rec; + if (*last_read_rec != rb->head) + next_rec = (next_rec != rb->last) ? + SCSC_GET_NEXT_SLOT_POS(rb, next_rec) : 0; + } + } while (*last_read_rec != rb->head && + last_read != bytes_read && + (!max_recs || records <= max_recs)); + + return bytes_read; +} + +/** + * This function returns a static snapshot of the ring that can be used + * for further processing using usual records operations. + * + * It returns a freshly allocated scsc_ring_buffer descriptor whose + * internal references are exactly the same as the original buffer + * being snapshot, and with all the sync machinery re-initialized. + * Even if the current use-case does NOT make any use of spinlocks and + * waitqueues in the snapshot image, we provide an initialized instance + * in order to be safe for future (mis-)usage. + * + * It also takes care to copy the content of original ring buffer into + * the new snapshot image (including the spare area) using the provided + * pre-allocated snap_buf. + * + * Assumes ring is already spinlocked. + * + * @rb: the original buffer to snapshot + * @snap_buf: the pre-allocated ring-buffer area to use for copying records + * @snap_sz: pre-allocated area including spare + * @snap_name: a human readable descriptor + */ +struct scsc_ring_buffer *scsc_ring_get_snapshot(const struct scsc_ring_buffer *rb, + void *snap_buf, size_t snap_sz, + char *snap_name) +{ + struct scsc_ring_buffer *snap_rb = NULL; + + if (!rb || !snap_buf || !snap_name || snap_sz != rb->bsz + rb->ssz) + return snap_rb; + + /* Here we hold a lock starving writers...try to be quick using + * GFP_ATOMIC since scsc_ring_buffer is small enough (144 bytes) + */ + snap_rb = kzalloc(sizeof(*rb), GFP_ATOMIC); + if (!snap_rb) + return snap_rb; + + /* Copy original buffer content on provided snap_buf */ + if (memcpy(snap_buf, rb->buf, snap_sz)) { + snap_rb->bsz = rb->bsz; + snap_rb->ssz = rb->ssz; + snap_rb->head = rb->head; + snap_rb->tail = rb->tail; + snap_rb->last = rb->last; + snap_rb->written = rb->written; + snap_rb->records = rb->records; + snap_rb->wraps = rb->wraps; + /* this is related to reads so must be re-init */ + snap_rb->oos = 0; + strncpy(snap_rb->name, snap_name, RNAME_SZ - 1); + /* Link the copies */ + snap_rb->buf = snap_buf; + snap_rb->spare = snap_rb->buf + snap_rb->bsz; + /* cleanup spare */ + memset(snap_rb->spare, 0x00, snap_rb->ssz); + /* Re-init snapshot copies of sync tools */ + raw_spin_lock_init(&snap_rb->lock); + init_waitqueue_head(&snap_rb->wq); + } else { + kfree(snap_rb); + snap_rb = NULL; + } + + return snap_rb; +} + +/* Assumes ring is already spinlocked. */ +void scsc_ring_truncate(struct scsc_ring_buffer *rb) +{ + rb->head = 0; + rb->tail = 0; + rb->records = 0; + rb->written = 0; + rb->wraps = 0; + rb->last = 0; + memset(rb->buf + rb->head, 0x00, SCSC_RINGREC_SZ); +} + +/** + * alloc_ring_buffer - Allocates and initializes a basic ring buffer, + * including a basic spare area where to handle strings-splitting when + * buffer wraps. Basic spinlock/mutex init takes place here too. + * + * @bsz: the size of the ring buffer to allocate in bytes + * @ssz: the size of the spare area to allocate in bytes + * @name: a name for this ring buffer + */ +struct scsc_ring_buffer __init *alloc_ring_buffer(size_t bsz, size_t ssz, + const char *name) +{ + struct scsc_ring_buffer *rb = kmalloc(sizeof(*rb), GFP_KERNEL); + + if (!rb) + return NULL; + rb->bsz = bsz; + rb->ssz = ssz; +#ifndef CONFIG_SCSC_STATIC_RING_SIZE + rb->buf = kzalloc(rb->bsz + rb->ssz, GFP_KERNEL); + if (!rb->buf) { + kfree(rb); + return NULL; + } +#else + rb->buf = a_ring; +#endif + rb->head = 0; + rb->tail = 0; + rb->last = 0; + rb->written = 0; + rb->records = 0; + rb->wraps = 0; + rb->oos = 0; + rb->spare = rb->buf + rb->bsz; + memset(rb->name, 0x00, RNAME_SZ); + strncpy(rb->name, name, RNAME_SZ - 1); + raw_spin_lock_init(&rb->lock); + init_waitqueue_head(&rb->wq); + + return rb; +} + +/* + * free_ring_buffer - Free the ring what else... + * ...does NOT account for spinlocks existence currently + * + * @rb: a pointer to the ring buffer to free + */ +void free_ring_buffer(struct scsc_ring_buffer *rb) +{ + if (!rb) + return; +#ifndef CONFIG_SCSC_STATIC_RING_SIZE + kfree(rb->buf); +#endif + kfree(rb); +} diff --git a/drivers/misc/samsung/scsc/scsc_logring_ring.h b/drivers/misc/samsung/scsc/scsc_logring_ring.h new file mode 100644 index 000000000000..5d230b5f0bb2 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_logring_ring.h @@ -0,0 +1,244 @@ +/****************************************************************************** + * + * Copyright (c) 2016-2017 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ + +#ifndef _SCSC_LOGRING_RING_H_ +#define _SCSC_LOGRING_RING_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "scsc_logring_common.h" + +#define SCSC_BINFO_LEN 32 +#define SCSC_HBUF_LEN 128 +/* A safe size to enforce on ingressing binary blobs; this accounts + * for possible binary expansion while reading, in order to fit the reader + * DEFAULT_TBUF_SZ in any possible case: this way we avoid to have truncated + * data also on read while dumping big binary blobs. + */ +#define SCSC_MAX_BIN_BLOB_SZ 1920 +/** + * This spare area is used to prepare a logging entry before pushing it into + * the ring and so it's the maximum length allowed for a log entry. + * When this change (hugely) you should check the size of len field + * in the following struct scsc_ring_record. + */ +#define BASE_SPARE_SZ 2048 +#define RNAME_SZ 16 +#define DEFAULT_RING_BUFFER_SZ 1048576 +#define DEFAULT_ENABLE_HEADER 1 +#define DEFAULT_ENABLE_LOGRING 1 +/* The default len, in bytes, of the binary blob to decode in ASCII + * Human readable form. -1 means DECODE EVERYTHING ! + */ +#define DEFAULT_BIN_DECODE_LEN -1 +#define DEBUGFS_ROOT "/sys/kernel/debug" +#define DEBUGFS_RING0_ROOT "ring0" + +/** + * Our ring buffer is allocated simply as a bunch of contiguos bytes. + * Data is stored as a contiguos stream of concatenated records, each one + * starting with a record descriptor of type scsc_ring_record: data content + * is then appended to the record descriptor; in this way we can account + * for different types of content, pushing the TAG describing the content + * into the record descriptor itself, being then able to operate differently + * on read depending on the type of content. + * The tail and head references 'points' to the start of the first (oldest) + * and the last (newest) record: any write will happen after the end + * of the current head: these references in fact points to the starting byte + * of the record descriptor modulus the ring size (they're NOT abosolute + * pointers). Since our 'metadata' is embedded inside the ring itself (like + * printk does) we never write variable string content in place BUT use + * instead the spare area (allocated contiguosly at the end of the ring) + * to expand the provided format string and then memcpy the content to + * the final position after having properly updated the record descriptors + * and eventually moved away the tail when overwriting. + * Moreover we never wrap a record across the ring boundary: if there's NOT + * enough space at the end of the ring, we simply place it at the start. + * Moreover this structs holds a kcache reference to allocate temporary + * buffers to use when double buffering is needed, a spinlock_t for + * protection and a wait_queue_t for blocking I/O. + * + * @buf: the ring-buffer itself starts here + * @spare: start of spare area (buf[bsz]) + * @name: a simple named identifier + * @bsz: ring size + * @ssz: size of spare (fixed at BASE_SPARE_SZ) + * @head: newest record written (first byte)...next write after it + * @tail: odelst record written (first byte)...full dump read will start + * from here + * @last: the last record before the end of the ring. + * @records: the number of records + * @written: a general progressive counter of total bytes written into + * the ring + * @lock: a spinlock_t to protetc concurrent access + * @wq: a wait queue where to put sleeping processes waiting for input. + * They're woken up at the end os scsc_printk(). + * @refc: a reference counter...currently unused. + * @private: useful to hold some user provided data (used to hold debugfs + * initdata related to this ring) + * @kcache: a reference to a kmem_cache created at initialization time + * to get fresh temporary buffers on the fly when copying to user and in + * need of a double buffer + */ +struct scsc_ring_buffer { + char *buf; + char *spare; + char name[RNAME_SZ]; + size_t bsz; + size_t ssz; + loff_t head; + loff_t tail; + loff_t last; + int records; + int wraps; + int oos; + u64 written; + raw_spinlock_t lock; + wait_queue_head_t wq; + atomic_t refc; + void *private; +}; + +/** + * Our ring buffer is now built concatenating entries prepended by a record + * that describes the content itself. This will allow us to store different + * types of data (NOT only string) and to interpret it. + * Each record is described by this struct that is laid out in front of the + * effective content: + * + * | SYNC | CRC | tag | len | lev | ctx | core | nsec | sync = SYNC_MAGIC; \ + (r)->crc = 0; \ + (r)->nsec = local_clock(); \ + (r)->tag = tag; \ + (r)->len = 0; \ + (r)->lev = lev; \ + (r)->ctx = ((in_interrupt()) ? \ + ((in_softirq()) ? 'S' : 'I') : 'P'); \ + (r)->core = smp_processor_id(); \ + } while (0) + + +#define SCSC_RINGREC_SZ (sizeof(struct scsc_ring_record)) +#define SCSC_CRC_RINGREC_SZ (SCSC_RINGREC_SZ - sizeof(SYNC_MAGIC)) + +#define SCSC_IS_RING_IN_USE(ring) \ + ((atomic_read(&((struct scsc_ring_buffer *)(ring))->refc)) != 0) + +#define SCSC_GET_RING_REFC(ring) \ + atomic_inc(&((struct scsc_ring_buffer *)(ring))->refc) + +#define SCSC_PUT_RING_REFC(ring) \ + atomic_dec(&((struct scsc_ring_buffer *)(ring))->refc) + + +#define SCSC_GET_REC_BUF(p) (((char *)(p)) + SCSC_RINGREC_SZ) + +#define SCSC_GET_REC_LEN(recp) (((struct scsc_ring_record *)(recp))->len) + +#define SCSC_GET_REC_TAG(recp) (((struct scsc_ring_record *)(recp))->tag) + +#define SCSC_GET_REC_CRC(recp) (((struct scsc_ring_record *)(recp))->crc) + +#define SCSC_GET_PTR(ring, pos) ((ring)->buf + (pos)) + +#define SCSC_GET_REC(ring, pos) \ + ((struct scsc_ring_record *)(SCSC_GET_PTR((ring), (pos)))) + +#define SCSC_IS_REC_SYNC_VALID(recp) ((recp)->sync == SYNC_MAGIC) + +#define SCSC_GET_HEAD_PTR(ring) SCSC_GET_PTR((ring), (ring)->head) + +#define SCSC_GET_NEXT_FREE_SLOT_PTR(ring) \ + (SCSC_GET_HEAD_PTR((ring)) + SCSC_RINGREC_SZ + \ + SCSC_GET_REC_LEN(SCSC_GET_HEAD_PTR(ring))) + +#define SCSC_GET_SLOT_LEN(ring, pos) \ + (((SCSC_GET_REC_LEN(SCSC_GET_PTR((ring), (pos)))) != 0) ? \ + (SCSC_RINGREC_SZ + SCSC_GET_REC_LEN(SCSC_GET_PTR((ring), (pos)))) : 0) + +#define SCSC_GET_NEXT_SLOT_POS(ring, pos) \ + ((pos) + SCSC_GET_SLOT_LEN((ring), (pos))) + +#define SCSC_RING_FREE_BYTES(rb) \ + (((rb)->head >= (rb)->tail) ? \ + ((rb)->bsz - SCSC_GET_NEXT_SLOT_POS(rb, rb->head)) : \ + ((rb)->tail - SCSC_GET_NEXT_SLOT_POS(rb, rb->head))) + +#define SCSC_USED_BYTES(rb) ((rb)->bsz - SCSC_RING_FREE_BYTES(rb)) + +#define SCSC_LOGGED_BYTES(rb) (SCSC_USED_BYTES(rb) - \ + ((rb)->records * SCSC_RINGREC_SZ)) + +#define SCSC_GET_NEXT_REC_ENTRY_POS(ring, rpos) \ + (rpos + SCSC_RINGREC_SZ + \ + SCSC_GET_REC_LEN(SCSC_GET_PTR((ring), (rpos)))) + +/* Ring buffer API */ +struct scsc_ring_buffer *alloc_ring_buffer(size_t bsz, size_t ssz, + const char *name) __init; +void free_ring_buffer(struct scsc_ring_buffer *rb); +void scsc_ring_truncate(struct scsc_ring_buffer *rb); +int push_record_string(struct scsc_ring_buffer *rb, int tag, int lev, + int prepend_header, const char *msg_head, va_list args); +int push_record_blob(struct scsc_ring_buffer *rb, int tag, int lev, + int prepend_header, const void *start, size_t len); +size_t read_next_records(struct scsc_ring_buffer *rb, int max_recs, + loff_t *last_read_rec, void *tbuf, size_t tsz); +struct scsc_ring_buffer *scsc_ring_get_snapshot(const struct scsc_ring_buffer *rb, + void *snap_buf, size_t snap_sz, + char *snap_name); +#endif /* _SCSC_LOGRING_RING_H_ */ diff --git a/drivers/misc/samsung/scsc/scsc_loopback.c b/drivers/misc/samsung/scsc/scsc_loopback.c new file mode 100644 index 000000000000..27f7072edca3 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_loopback.c @@ -0,0 +1,48 @@ +/** + * Loopback Protocol (Implementation) + */ + +#include +#include + +#include + +#include "scsc_loopback.h" + +/*****************************************************************************/ + +/** + * Handle data received on port by sending it back. + */ +static void scsc_loopback_port_recv( + struct scsc_mport *port, + const unsigned char *data, + size_t count) +{ + os_trace_dbg("%s: @%p, count %zu", __func__, port, count); + + scsc_mport_emit(port, data, count); +} + +static const struct scsc_mport_ops scsc_loopback_port_ops = { + scsc_loopback_port_recv +}; + +/*****************************************************************************/ + +void scsc_loopback_init(struct scsc_loopback *loopback) +{ + os_trace_dbg("%s: @%p", __func__, loopback); + + scsc_mport_init(&loopback->port, &scsc_loopback_port_ops); +} + +void scsc_loopback_deinit(struct scsc_loopback *loopback) +{ +} + +struct scsc_mport *scsc_loopback_get_port( + struct scsc_loopback *loopback) +{ + return &loopback->port; +} diff --git a/drivers/misc/samsung/scsc/scsc_loopback.h b/drivers/misc/samsung/scsc/scsc_loopback.h new file mode 100644 index 000000000000..10ddf67c069f --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_loopback.h @@ -0,0 +1,24 @@ +#ifndef __HCI_LOOPBACK_H +#define __HCI_LOOPBACK_H +/** + * Loopback Protocol (Interface) + * + * Bounces anything send straight back. + */ + +#include "scsc_mport.h" + +/*****************************************************************************/ + +struct scsc_loopback { + struct scsc_mport port; +}; + +/*****************************************************************************/ + +void scsc_loopback_init(struct scsc_loopback *loopback); +void scsc_loopback_deinit(struct scsc_loopback *loopback); + +struct scsc_mport *scsc_loopback_get_port(struct scsc_loopback *loopback); + +#endif /* __HCI_LOOPBACK_H */ diff --git a/drivers/misc/samsung/scsc/scsc_mif_abs.h b/drivers/misc/samsung/scsc/scsc_mif_abs.h new file mode 100644 index 000000000000..3f40635e8c0c --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_mif_abs.h @@ -0,0 +1,234 @@ +/**************************************************************************** +* +* Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved +* +****************************************************************************/ + +#ifndef __SCSC_MIF_ABS_H +#define __SCSC_MIF_ABS_H + +#ifdef CONFIG_SCSC_QOS +#include +#endif +#include +#include +#include + +struct device; + +/* To R4/M4 */ +enum scsc_mif_abs_target { + SCSC_MIF_ABS_TARGET_R4 = 0, + SCSC_MIF_ABS_TARGET_M4 = 1, +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + SCSC_MIF_ABS_TARGET_M4_1 = 2 +#endif +}; + +#ifdef CONFIG_SCSC_SMAPPER +#define SCSC_MIF_SMAPPER_MAX_BANKS 32 + +struct scsc_mif_smapper_info { + u32 num_entries; + u32 mem_range_bytes; +}; + +enum scsc_mif_abs_bank_type { + SCSC_MIF_ABS_NO_BANK = 0, + SCSC_MIF_ABS_SMALL_BANK = 1, + SCSC_MIF_ABS_LARGE_BANK = 2 +}; +#endif + +#ifdef CONFIG_SCSC_QOS +struct scsc_mifqos_request { + struct pm_qos_request pm_qos_req_mif; + struct pm_qos_request pm_qos_req_int; + struct pm_qos_request pm_qos_req_cl0; + struct pm_qos_request pm_qos_req_cl1; +}; +#endif + +#define SCSC_REG_READ_WLBT_STAT 0 + +/** + * Abstraction of the Maxwell "Memory Interface" aka MIF. + * + * There will be at least two implementations of this + * interface - The native AXI one and a PCIe based emulation. + * + * A reference to an interface will be passed to the + * scsc_mx driver when the system startsup. + */ +struct scsc_mif_abs { +/** + * Destroy this interface. + * + * This should be called when the underlying device is + * removed. + */ + void (*destroy)(struct scsc_mif_abs *interface); + /* Return an unique id for this host, and prefreabrly identifies specific device (example pcie0, pcie1) */ + char *(*get_uid)(struct scsc_mif_abs *interface); +/** + * Controls the hardware "reset" state of the Maxwell + * subsystem. + * + * Setting reset=TRUE places the subsystem in its low + * power "reset" state. This function is called + * by the Maxwell Manager near the end of the subsystem + * shutdown process, before "unmapping" the interface. + * + * Setting reset=FALSE release the subsystem reset state. + * The subystem will then start its cold boot sequence. This + * function is called + * by the Subsystem Manager near the end of the subsystem + * startup process after installing the maxwell firmware and + * other resources in MIF RAM. + */ + int (*reset)(struct scsc_mif_abs *interface, bool reset); +/** + * This function maps the Maxwell interface hardware (MIF + * DRAM) into kernel memory space. + * + * Amount of memory allocated must be defined and returned + * on (*allocated) by the abstraction layer implemenation. + * + * This returns kernel-space pointer to the start of the + * shared MIF DRAM. The Maxwell Manager will load firmware + * to this location and configure the MIF Heap Manager to + * manage any unused memory at the end of the DRAM region. + * + * The scsc_mx driver should call this when the Maxwell + * subsystem is required by any service client. + * + * The mailbox, irq and dram functions are only usable once + * this call has returned. HERE: Should we rename this to + * "open" and return a handle to these conditional methods? + */ + void *(*map)(struct scsc_mif_abs *interface, size_t *allocated); +/** + * The inverse of "map". Should be called once the maxwell + * subsystem is no longer required and has been placed into + * "reset" state (see reset method). + */ + void (*unmap)(struct scsc_mif_abs *interface, void *mem); + +/** + * The Mailbox pointer returned can be used for direct access + * to the hardware register for efficiency. + * The pointer is guaranteed to remain valid between map and unmap calls. + * HERE: If we are not assuming AP v R4 same-endianess then this + * should be explicitly leu32 or u8[4] (or something equivalent). + */ + u32 *(*get_mbox_ptr)(struct scsc_mif_abs *interface, u32 mbox_index); +/** + * Incoming MIF Interrupt Hardware Controls + */ + /** Get the incoming interrupt source mask */ + u32 (*irq_bit_mask_status_get)(struct scsc_mif_abs *interface); + + /** Get the incoming interrupt pending (waiting *AND* not masked) mask */ + u32 (*irq_get)(struct scsc_mif_abs *interface); + + void (*irq_bit_clear)(struct scsc_mif_abs *interface, int bit_num); + void (*irq_bit_mask)(struct scsc_mif_abs *interface, int bit_num); + void (*irq_bit_unmask)(struct scsc_mif_abs *interface, int bit_num); + +/** + * Outgoing MIF Interrupt Hardware Controls + */ + void (*irq_bit_set)(struct scsc_mif_abs *interface, int bit_num, enum scsc_mif_abs_target target); + +/** + * Register handler for the interrupt from the + * MIF Interrupt Hardware. + * + * This is used by the MIF Interrupt Manager to + * register a handler that demultiplexes the + * individual interrupt sources (MIF Interrupt Bits) + * to source-specific handlers. + */ + void (*irq_reg_handler)(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev); + void (*irq_unreg_handler)(struct scsc_mif_abs *interface); + + /* Clear HW interrupt line */ + void (*irq_clear)(void); + void (*irq_reg_reset_request_handler)(struct scsc_mif_abs *interface, void (*handler)(int irq, void *data), void *dev); + void (*irq_unreg_reset_request_handler)(struct scsc_mif_abs *interface); + +/** + * Install suspend/resume handlers for the MIF abstraction driver + */ + void (*suspend_reg_handler)(struct scsc_mif_abs *abs, + int (*suspend)(struct scsc_mif_abs *abs, void *data), + void (*resume)(struct scsc_mif_abs *abs, void *data), + void *data); + void (*suspend_unreg_handler)(struct scsc_mif_abs *abs); + +/** + * Return kernel-space pointer to MIF ram. + * The pointer is guaranteed to remain valid between map and unmap calls. + */ + void *(*get_mifram_ptr)(struct scsc_mif_abs *interface, scsc_mifram_ref ref); +/* Maps kernel-space pointer to MIF RAM to portable reference */ + int (*get_mifram_ref)(struct scsc_mif_abs *interface, void *ptr, scsc_mifram_ref *ref); + +/* Return physical page frame number corresponding to the physical addres to which + * the virtual address is mapped . Needed in mmap file operations*/ + uintptr_t (*get_mifram_pfn)(struct scsc_mif_abs *interface); + +/** + * Return physical address from MIF ram address. + */ + void *(*get_mifram_phy_ptr)(struct scsc_mif_abs *interface, scsc_mifram_ref ref); +/** Return a kernel device associated 1:1 with the Maxwell instance. + * This is published only for the purpose of associating service drivers with a Maxwell instance + * for logging purposes. Clients should not make any assumptions about the device type. + * In some configurations this may be the associated host-interface device (AXI/PCIe), + * but this may change in future. + */ + struct device *(*get_mif_device)(struct scsc_mif_abs *interface); + + + void (*mif_dump_registers)(struct scsc_mif_abs *interface); + void (*mif_cleanup)(struct scsc_mif_abs *interface); + void (*mif_restart)(struct scsc_mif_abs *interface); + +#ifdef CONFIG_SCSC_SMAPPER +/* SMAPPER */ + int (*mif_smapper_get_mapping)(struct scsc_mif_abs *interface, u8 *phy_map, u16 *align); + int (*mif_smapper_get_bank_info)(struct scsc_mif_abs *interface, u8 bank, struct scsc_mif_smapper_info *bank_info); + int (*mif_smapper_write_sram)(struct scsc_mif_abs *interface, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr); + void (*mif_smapper_configure)(struct scsc_mif_abs *interface, u32 granularity); + u32 (*mif_smapper_get_bank_base_address)(struct scsc_mif_abs *interface, u8 bank); +#endif +#ifdef CONFIG_SCSC_QOS + int (*mif_pm_qos_add_request)(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config); + int (*mif_pm_qos_update_request)(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req, enum scsc_qos_config config); + int (*mif_pm_qos_remove_request)(struct scsc_mif_abs *interface, struct scsc_mifqos_request *qos_req); +#endif + int (*mif_read_register)(struct scsc_mif_abs *interface, u64 id, u32 *val); +}; + +struct device; + +struct scsc_mif_abs_driver { + char *name; + void (*probe)(struct scsc_mif_abs_driver *abs_driver, struct scsc_mif_abs *abs); + void (*remove)(struct scsc_mif_abs *abs); +}; + +extern void scsc_mif_abs_register(struct scsc_mif_abs_driver *driver); +extern void scsc_mif_abs_unregister(struct scsc_mif_abs_driver *driver); + +/* mmap-debug driver */ +struct scsc_mif_mmap_driver { + char *name; + void (*probe)(struct scsc_mif_mmap_driver *mmap_driver, struct scsc_mif_abs *abs); + void (*remove)(struct scsc_mif_abs *abs); +}; + +extern void scsc_mif_mmap_register(struct scsc_mif_mmap_driver *mmap_driver); +extern void scsc_mif_mmap_unregister(struct scsc_mif_mmap_driver *mmap_driver); +#endif diff --git a/drivers/misc/samsung/scsc/scsc_mx_impl.c b/drivers/misc/samsung/scsc/scsc_mx_impl.c new file mode 100644 index 000000000000..a4928e145c15 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_mx_impl.c @@ -0,0 +1,225 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include "scsc_mx_impl.h" +#include "mifintrbit.h" +#include "miframman.h" +#include "mifmboxman.h" +#ifdef CONFIG_SCSC_SMAPPER +#include "mifsmapper.h" +#endif +#ifdef CONFIG_SCSC_QOS +#include "mifqos.h" +#endif +#include "mifproc.h" +#include "mxman.h" +#include "mxproc.h" +#include "srvman.h" +#include "mxmgmt_transport.h" +#include "gdb_transport.h" +#include "mxlog.h" +#include "mxlogger.h" +#include "panicmon.h" +#include "mxlog_transport.h" +#include "suspendmon.h" + +#include "scsc/api/bt_audio.h" +#include "mxfwconfig.h" +#ifdef CONFIG_SCSC_WLBTD +#include "scsc_wlbtd.h" +#endif + +struct scsc_mx { + struct scsc_mif_abs *mif_abs; + struct mifintrbit intr; + struct miframman ram; + struct miframman ram2; + struct mifmboxman mbox; + struct mifabox mifabox; +#ifdef CONFIG_SCSC_SMAPPER + struct mifsmapper smapper; +#endif +#ifdef CONFIG_SCSC_QOS + struct mifqos qos; +#endif + struct mifproc proc; + struct mxman mxman; + struct srvman srvman; + struct mxmgmt_transport mxmgmt_transport; + struct gdb_transport gdb_transport_r4; + struct gdb_transport gdb_transport_m4; +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT + struct gdb_transport gdb_transport_m4_1; +#endif + int users; + struct mxlog mxlog; + struct mxlogger mxlogger; + struct panicmon panicmon; + struct mxlog_transport mxlog_transport; + struct suspendmon suspendmon; + struct mxfwconfig mxfwconfig; +}; + + +struct scsc_mx *scsc_mx_create(struct scsc_mif_abs *mif) +{ + struct scsc_mx *mx; + + mx = kzalloc(sizeof(*mx), GFP_KERNEL); + if (!mx) + return NULL; + + mx->mif_abs = mif; + + mifintrbit_init(&mx->intr, mif); + + mifmboxman_init(&mx->mbox); + suspendmon_init(&mx->suspendmon, mx); + mxman_init(&mx->mxman, mx); + srvman_init(&mx->srvman, mx); + mifproc_create_proc_dir(mx->mif_abs); +#ifdef CONFIG_SCSC_WLBTD + scsc_wlbtd_init(); +#endif + SCSC_TAG_DEBUG(MXMAN, "Hurray Maxwell is here with %p\n", mx); + return mx; +} + +void scsc_mx_destroy(struct scsc_mx *mx) +{ + SCSC_TAG_DEBUG(MXMAN, "\n"); + BUG_ON(mx == NULL); + mifintrbit_deinit(&mx->intr); + mifmboxman_deinit(scsc_mx_get_mboxman(mx)); +#ifdef CONFIG_SCSC_SMAPPER + mifsmapper_deinit(scsc_mx_get_smapper(mx)); +#endif + suspendmon_deinit(scsc_mx_get_suspendmon(mx)); + mifproc_remove_proc_dir(); + srvman_deinit(&mx->srvman); + mxman_deinit(&mx->mxman); +#ifdef CONFIG_SCSC_WLBTD + scsc_wlbtd_deinit(); +#endif + kfree(mx); + SCSC_TAG_DEBUG(MXMAN, "OK\n"); +} + +struct scsc_mif_abs *scsc_mx_get_mif_abs(struct scsc_mx *mx) +{ + return mx->mif_abs; +} + +struct mifintrbit *scsc_mx_get_intrbit(struct scsc_mx *mx) +{ + return &mx->intr; +} + +struct miframman *scsc_mx_get_ramman(struct scsc_mx *mx) +{ + return &mx->ram; +} + +struct miframman *scsc_mx_get_ramman2(struct scsc_mx *mx) +{ + return &mx->ram2; +} + +struct mifabox *scsc_mx_get_aboxram(struct scsc_mx *mx) +{ + return &mx->mifabox; +} + +struct mifmboxman *scsc_mx_get_mboxman(struct scsc_mx *mx) +{ + return &mx->mbox; +} + +#ifdef CONFIG_SCSC_SMAPPER +struct mifsmapper *scsc_mx_get_smapper(struct scsc_mx *mx) +{ + return &mx->smapper; +} +#endif + +#ifdef CONFIG_SCSC_QOS +struct mifqos *scsc_mx_get_qos(struct scsc_mx *mx) +{ + return &mx->qos; +} +#endif + +struct device *scsc_mx_get_device(struct scsc_mx *mx) +{ + return mx->mif_abs->get_mif_device(mx->mif_abs); +} +EXPORT_SYMBOL_GPL(scsc_mx_get_device); /* TODO: export a top-level API for this */ + +struct mxman *scsc_mx_get_mxman(struct scsc_mx *mx) +{ + return &mx->mxman; +} + +struct srvman *scsc_mx_get_srvman(struct scsc_mx *mx) +{ + return &mx->srvman; +} + +struct mxmgmt_transport *scsc_mx_get_mxmgmt_transport(struct scsc_mx *mx) +{ + return &mx->mxmgmt_transport; +} + +struct gdb_transport *scsc_mx_get_gdb_transport_r4(struct scsc_mx *mx) +{ + return &mx->gdb_transport_r4; +} + +struct gdb_transport *scsc_mx_get_gdb_transport_m4(struct scsc_mx *mx) +{ + return &mx->gdb_transport_m4; +} + +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT +struct gdb_transport *scsc_mx_get_gdb_transport_m4_1(struct scsc_mx *mx) +{ + return &mx->gdb_transport_m4_1; +} +#endif + +struct mxlog *scsc_mx_get_mxlog(struct scsc_mx *mx) +{ + return &mx->mxlog; +} + +struct panicmon *scsc_mx_get_panicmon(struct scsc_mx *mx) +{ + return &mx->panicmon; +} + +struct mxlog_transport *scsc_mx_get_mxlog_transport(struct scsc_mx *mx) +{ + return &mx->mxlog_transport; +} + +struct mxlogger *scsc_mx_get_mxlogger(struct scsc_mx *mx) +{ + return &mx->mxlogger; +} + +struct suspendmon *scsc_mx_get_suspendmon(struct scsc_mx *mx) +{ + return &mx->suspendmon; +} + +struct mxfwconfig *scsc_mx_get_mxfwconfig(struct scsc_mx *mx) +{ + return &mx->mxfwconfig; +} + diff --git a/drivers/misc/samsung/scsc/scsc_mx_impl.h b/drivers/misc/samsung/scsc/scsc_mx_impl.h new file mode 100644 index 000000000000..d4df3466c36f --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_mx_impl.h @@ -0,0 +1,63 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _CORE_H_ +#define _CORE_H_ + +#include +#include "scsc_mif_abs.h" + +struct device; +struct scsc_mx; +struct mifintrbit; +struct miframman; +struct mifmboxman; +struct mxman; +struct srvman; +struct mxmgmt_transport; +struct mxproc; +struct mxfwconfig; + +struct scsc_mx *scsc_mx_create(struct scsc_mif_abs *mif); +void scsc_mx_destroy(struct scsc_mx *mx); +struct scsc_mif_abs *scsc_mx_get_mif_abs(struct scsc_mx *mx); +struct mifintrbit *scsc_mx_get_intrbit(struct scsc_mx *mx); +struct mifmuxman *scsc_mx_get_muxman(struct scsc_mx *mx); +struct miframman *scsc_mx_get_ramman(struct scsc_mx *mx); +struct miframman *scsc_mx_get_ramman2(struct scsc_mx *mx); +struct mifabox *scsc_mx_get_aboxram(struct scsc_mx *mx); +struct mifmboxman *scsc_mx_get_mboxman(struct scsc_mx *mx); +#ifdef CONFIG_SCSC_SMAPPER +struct mifsmapper *scsc_mx_get_smapper(struct scsc_mx *mx); +#endif +#ifdef CONFIG_SCSC_QOS +struct mifqos *scsc_mx_get_qos(struct scsc_mx *mx); +#endif +struct device *scsc_mx_get_device(struct scsc_mx *mx); +struct mxman *scsc_mx_get_mxman(struct scsc_mx *mx); +struct srvman *scsc_mx_get_srvman(struct scsc_mx *mx); +struct mxproc *scsc_mx_get_mxproc(struct scsc_mx *mx); +struct mxmgmt_transport *scsc_mx_get_mxmgmt_transport(struct scsc_mx *mx); +struct gdb_transport *scsc_mx_get_gdb_transport_r4(struct scsc_mx *mx); +struct gdb_transport *scsc_mx_get_gdb_transport_m4(struct scsc_mx *mx); +#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT +struct gdb_transport *scsc_mx_get_gdb_transport_m4_1(struct scsc_mx *mx); +#endif +struct mxlog *scsc_mx_get_mxlog(struct scsc_mx *mx); +struct mxlog_transport *scsc_mx_get_mxlog_transport(struct scsc_mx *mx); +struct mxlogger *scsc_mx_get_mxlogger(struct scsc_mx *mx); +struct panicmon *scsc_mx_get_panicmon(struct scsc_mx *mx); +struct suspendmon *scsc_mx_get_suspendmon(struct scsc_mx *mx); +struct mxfwconfig *scsc_mx_get_mxfwconfig(struct scsc_mx *mx); + +int mx140_file_download_fw(struct scsc_mx *mx, void *dest, size_t dest_size, u32 *fw_image_size); +int mx140_request_file(struct scsc_mx *mx, char *path, const struct firmware **firmp); +int mx140_release_file(struct scsc_mx *mx, const struct firmware *firmp); +int mx140_basedir_file(struct scsc_mx *mx); +int mx140_exe_path(struct scsc_mx *mx, char *path, size_t len, const char *bin); +int mx140_file_select_fw(struct scsc_mx *mx, u32 suffix); +bool mx140_file_supported_hw(struct scsc_mx *mx, u32 hw_ver); +#endif diff --git a/drivers/misc/samsung/scsc/scsc_mx_module.c b/drivers/misc/samsung/scsc/scsc_mx_module.c new file mode 100755 index 000000000000..0b4c01b24e78 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_mx_module.c @@ -0,0 +1,200 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "scsc_mif_abs.h" +#include "scsc_mx_impl.h" +#ifdef CONFIG_SCSC_WLBTD +#include "scsc_wlbtd.h" +#endif + +#define SCSC_MX_CORE_MODDESC "mx140 Core Driver" + +struct clients_node { + struct list_head list; + struct scsc_mx_module_client *module_client; +}; + +struct mx_node { + struct list_head list; + struct scsc_mx *mx; +}; + +static struct mx_module { + struct list_head clients_list; + struct list_head mx_list; +} mx_module = { + .clients_list = LIST_HEAD_INIT(mx_module.clients_list), + .mx_list = LIST_HEAD_INIT(mx_module.mx_list) +}; + +static void scsc_mx_module_probe_registered_clients(struct scsc_mx *new_mx) +{ + bool client_registered = false; + struct clients_node *client_node, *client_next; + + /* Traverse Linked List for each mif_driver node */ + list_for_each_entry_safe(client_node, client_next, &mx_module.clients_list, list) { + client_node->module_client->probe(client_node->module_client, new_mx, SCSC_MODULE_CLIENT_REASON_HW_PROBE); + client_registered = true; + } + if (client_registered == false) + SCSC_TAG_INFO(MXMAN, "No clients registered\n"); +} + +static void scsc_mx_module_probe(struct scsc_mif_abs_driver *abs_driver, struct scsc_mif_abs *mif_abs) +{ + struct scsc_mx *new_mx; + struct mx_node *mx_node; + + /* Avoid unused parm error */ + (void)abs_driver; + + mx_node = kzalloc(sizeof(*mx_node), GFP_KERNEL); + if (!mx_node) + return; + /* Create new mx instance */ + new_mx = scsc_mx_create(mif_abs); + if (!new_mx) { + kfree(mx_node); + SCSC_TAG_ERR(MXMAN, "Error allocating new_mx\n"); + return; + } + /* Add instance in mx_node linked list */ + mx_node->mx = new_mx; + + list_add_tail(&mx_node->list, &mx_module.mx_list); + + scsc_mx_module_probe_registered_clients(new_mx); +} + +static void scsc_mx_module_remove(struct scsc_mif_abs *abs) +{ + bool match = false; + struct mx_node *mx_node, *next; + + /* Traverse Linked List for each mx node */ + list_for_each_entry_safe(mx_node, next, &mx_module.mx_list, list) { + /* If there is a match, call destroy */ + if (scsc_mx_get_mif_abs(mx_node->mx) == abs) { + match = true; + scsc_mx_destroy(mx_node->mx); + list_del(&mx_node->list); + kfree(mx_node); + } + } + if (match == false) + SCSC_TAG_ERR(MXMAN, "FATAL, no match for given scsc_mif_abs\n"); +} + +static struct scsc_mif_abs_driver mx_module_mif_if = { + .name = "mx140 driver", + .probe = scsc_mx_module_probe, + .remove = scsc_mx_module_remove, +}; + +static int __init scsc_mx_module_init(void) +{ + SCSC_TAG_INFO(MXMAN, SCSC_MX_CORE_MODDESC " scsc_release %d.%d.%d.%d\n", + SCSC_RELEASE_PRODUCT, + SCSC_RELEASE_ITERATION, + SCSC_RELEASE_CANDIDATE, + SCSC_RELEASE_POINT); + + scsc_mif_abs_register(&mx_module_mif_if); + return 0; +} + +static void __exit scsc_mx_module_exit(void) +{ + struct mx_node *mx_node, *next_mx; + + /* Traverse Linked List for each mx node */ + list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list) { + scsc_mx_destroy(mx_node->mx); + list_del(&mx_node->list); + kfree(mx_node); + } + + scsc_mif_abs_unregister(&mx_module_mif_if); + + SCSC_TAG_INFO(MXMAN, SCSC_MX_CORE_MODDESC " unloaded\n"); +} + +/** + * Reset all registered service drivers by first calling the remove callback and + * then the probe callback. This function is used during recovery operations, + * where the chip has been reset as part of the recovery and the service drivers + * has to do the same. + */ +int scsc_mx_module_reset(void) +{ + struct clients_node *clients_node; + struct mx_node *mx_node, *next_mx; + + /* Traverse Linked List and call registered removed callbacks */ + list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list) + list_for_each_entry(clients_node, &mx_module.clients_list, list) + clients_node->module_client->remove(clients_node->module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_RECOVERY); + + /* Traverse Linked List and call registered probed callbacks */ + list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list) + list_for_each_entry(clients_node, &mx_module.clients_list, list) + clients_node->module_client->probe(clients_node->module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_RECOVERY); + + return 0; +} +EXPORT_SYMBOL(scsc_mx_module_reset); + +int scsc_mx_module_register_client_module(struct scsc_mx_module_client *module_client) +{ + struct clients_node *module_client_node; + struct mx_node *mx_node; + + /* Add node in modules linked list */ + module_client_node = kzalloc(sizeof(*module_client_node), GFP_KERNEL); + if (!module_client_node) + return -ENOMEM; + + module_client_node->module_client = module_client; + list_add_tail(&module_client_node->list, &mx_module.clients_list); + + /* Traverse Linked List for each mx node */ + list_for_each_entry(mx_node, &mx_module.mx_list, list) { + module_client->probe(module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_HW_PROBE); + } + return 0; +} +EXPORT_SYMBOL(scsc_mx_module_register_client_module); + +void scsc_mx_module_unregister_client_module(struct scsc_mx_module_client *module_client) +{ + struct clients_node *client_node, *client_next; + struct mx_node *mx_node, *next_mx; + + /* Traverse Linked List for each client_list */ + list_for_each_entry_safe(client_node, client_next, &mx_module.clients_list, list) { + if (client_node->module_client == module_client) { + list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list) { + module_client->remove(module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_HW_REMOVE); + } + list_del(&client_node->list); + kfree(client_node); + } + } +} +EXPORT_SYMBOL(scsc_mx_module_unregister_client_module); + +module_init(scsc_mx_module_init); +module_exit(scsc_mx_module_exit); + +MODULE_DESCRIPTION(SCSC_MX_CORE_MODDESC); +MODULE_AUTHOR("SCSC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/samsung/scsc/scsc_service.c b/drivers/misc/samsung/scsc/scsc_service.c new file mode 100755 index 000000000000..d9cd4e079aca --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_service.c @@ -0,0 +1,1110 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#ifdef CONFIG_ANDROID +#include +#endif +#include +#include + +#include "mxman.h" +#include "scsc_mx_impl.h" +#include "mifintrbit.h" +#include "miframman.h" +#include "mifmboxman.h" +#ifdef CONFIG_SCSC_SMAPPER +#include "mifsmapper.h" +#endif +#ifdef CONFIG_SCSC_QOS +#include "mifqos.h" +#endif +#include "mxlogger.h" +#include "srvman.h" +#include "servman_messages.h" +#include "mxmgmt_transport.h" + +static ulong sm_completion_timeout_ms = 1000; +module_param(sm_completion_timeout_ms, ulong, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(sm_completion_timeout_ms, "Timeout Service Manager start/stop (ms) - default 1000. 0 = infinite"); + +#define SCSC_MIFRAM_INVALID_REF -1 +#define SCSC_MX_SERVICE_RECOVERY_TIMEOUT 20000 /* 20 seconds */ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)) +#define reinit_completion(completion) INIT_COMPLETION(*(completion)) +#endif + +struct scsc_service { + struct list_head list; + struct scsc_mx *mx; + enum scsc_service_id id; + struct scsc_service_client *client; + struct completion sm_msg_start_completion; + struct completion sm_msg_stop_completion; +}; + +void srvman_init(struct srvman *srvman, struct scsc_mx *mx) +{ + SCSC_TAG_INFO(MXMAN, "\n"); + srvman->mx = mx; + INIT_LIST_HEAD(&srvman->service_list); + mutex_init(&srvman->service_list_mutex); + mutex_init(&srvman->api_access_mutex); + +#ifdef CONFIG_ANDROID + wake_lock_init(&srvman->sm_wake_lock, WAKE_LOCK_SUSPEND, "srvman_wakelock"); +#endif +} + +void srvman_deinit(struct srvman *srvman) +{ + struct scsc_service *service, *next; + + SCSC_TAG_INFO(MXMAN, "\n"); + list_for_each_entry_safe(service, next, &srvman->service_list, list) { + list_del(&service->list); + kfree(service); + } + mutex_destroy(&srvman->api_access_mutex); + mutex_destroy(&srvman->service_list_mutex); + +#ifdef CONFIG_ANDROID + wake_lock_destroy(&srvman->sm_wake_lock); +#endif +} + +void srvman_set_error(struct srvman *srvman) +{ + struct scsc_service *service; + + SCSC_TAG_INFO(MXMAN, "\n"); + srvman->error = true; + mutex_lock(&srvman->service_list_mutex); + list_for_each_entry(service, &srvman->service_list, list) { + complete(&service->sm_msg_start_completion); + complete(&service->sm_msg_stop_completion); + } + mutex_unlock(&srvman->service_list_mutex); +} + +void srvman_clear_error(struct srvman *srvman) +{ + SCSC_TAG_INFO(MXMAN, "\n"); + srvman->error = false; +} + +static int wait_for_sm_msg_start_cfm(struct scsc_service *service) +{ + int r; + + if (0 == sm_completion_timeout_ms) { + /* Zero implies infinite wait, for development use only. + * r = -ERESTARTSYS if interrupted (e.g. Ctrl-C), 0 if completed + */ + r = wait_for_completion_interruptible(&service->sm_msg_start_completion); + if (r == -ERESTARTSYS) { + /* Paranoid sink of any pending event skipped by the interrupted wait */ + r = wait_for_completion_timeout(&service->sm_msg_start_completion, HZ / 2); + if (r == 0) { + SCSC_TAG_ERR(MXMAN, "timed out\n"); + return -ETIMEDOUT; + } + } + return r; + } + r = wait_for_completion_timeout(&service->sm_msg_start_completion, msecs_to_jiffies(sm_completion_timeout_ms)); + if (r == 0) { + SCSC_TAG_ERR(MXMAN, "timeout\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int wait_for_sm_msg_stop_cfm(struct scsc_service *service) +{ + int r; + + if (0 == sm_completion_timeout_ms) { + /* Zero implies infinite wait, for development use only. + * r = -ERESTARTSYS if interrupted (e.g. Ctrl-C), 0 if completed + */ + r = wait_for_completion_interruptible(&service->sm_msg_stop_completion); + if (r == -ERESTARTSYS) { + /* Paranoid sink of any pending event skipped by the interrupted wait */ + r = wait_for_completion_timeout(&service->sm_msg_stop_completion, HZ / 2); + if (r == 0) { + SCSC_TAG_ERR(MXMAN, "timed out\n"); + return -ETIMEDOUT; + } + } + return r; + } + r = wait_for_completion_timeout(&service->sm_msg_stop_completion, msecs_to_jiffies(sm_completion_timeout_ms)); + if (r == 0) { + SCSC_TAG_ERR(MXMAN, "timeout\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int send_sm_msg_start_blocking(struct scsc_service *service, scsc_mifram_ref ref) +{ + struct scsc_mx *mx = service->mx; + struct mxmgmt_transport *mxmgmt_transport = scsc_mx_get_mxmgmt_transport(mx); + int r; + struct sm_msg_packet message = { .service_id = service->id, + .msg = SM_MSG_START_REQ, + .optional_data = ref }; + + reinit_completion(&service->sm_msg_start_completion); + + /* Send to FW in MM stream */ + mxmgmt_transport_send(mxmgmt_transport, MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, &message, sizeof(message)); + r = wait_for_sm_msg_start_cfm(service); + if (r) { + SCSC_TAG_ERR(MXMAN, "wait_for_sm_msg_start_cfm() failed: r=%d\n", r); + + /* Report the error in order to get a moredump. Avoid auto-recovering this type of failure */ + if (mxman_recovery_disabled()) + scsc_mx_service_service_failed(service, "SM_MSG_START_CFM timeout"); + } + return r; +} + +static int send_sm_msg_stop_blocking(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct mxman *mxman = scsc_mx_get_mxman(mx); + struct mxmgmt_transport *mxmgmt_transport = scsc_mx_get_mxmgmt_transport(mx); + int r; + struct sm_msg_packet message = { .service_id = service->id, + .msg = SM_MSG_STOP_REQ, + .optional_data = 0 }; + + if (mxman->mxman_state == MXMAN_STATE_FAILED) + return 0; + + reinit_completion(&service->sm_msg_stop_completion); + + /* Send to FW in MM stream */ + mxmgmt_transport_send(mxmgmt_transport, MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, &message, sizeof(message)); + r = wait_for_sm_msg_stop_cfm(service); + if (r) + SCSC_TAG_ERR(MXMAN, "wait_for_sm_msg_stop_cfm() for service=%p service->id=%d failed: r=%d\n", service, service->id, r); + return r; +} + +/* + * Receive handler for messages from the FW along the maxwell management transport + */ +static void srv_message_handler(const void *message, void *data) +{ + struct srvman *srvman = (struct srvman *)data; + struct scsc_service *service; + const struct sm_msg_packet *msg = message; + bool found = false; + + mutex_lock(&srvman->service_list_mutex); + list_for_each_entry(service, &srvman->service_list, list) { + if (service->id == msg->service_id) { + found = true; + break; + } + } + if (!found) { + SCSC_TAG_ERR(MXMAN, "No service for msg->service_id=%d", msg->service_id); + mutex_unlock(&srvman->service_list_mutex); + return; + } + /* Forward the message to the applicable service to deal with */ + switch (msg->msg) { + case SM_MSG_START_CFM: + SCSC_TAG_INFO(MXMAN, "Received SM_MSG_START_CFM message service=%p with service_id=%d from the firmware\n", + service, msg->service_id); + complete(&service->sm_msg_start_completion); + break; + case SM_MSG_STOP_CFM: + SCSC_TAG_INFO(MXMAN, "Received SM_MSG_STOP_CFM message for service=%p with service_id=%d from the firmware\n", + service, msg->service_id); + complete(&service->sm_msg_stop_completion); + break; + default: + /* HERE: Unknown message, raise fault */ + SCSC_TAG_WARNING(MXMAN, "Received unknown message for service=%p with service_id=%d from the firmware: msg->msg=%d\n", + service, msg->msg, msg->service_id); + break; + } + mutex_unlock(&srvman->service_list_mutex); +} + +int scsc_mx_service_start(struct scsc_service *service, scsc_mifram_ref ref) +{ + struct scsc_mx *mx = service->mx; + struct srvman *srvman = scsc_mx_get_srvman(mx); + struct mxman *mxman = scsc_mx_get_mxman(service->mx); + int r; + struct timeval tval = {}; + + SCSC_TAG_INFO(MXMAN, "%d\n", service->id); +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_run) + return 0; +#endif + mutex_lock(&srvman->api_access_mutex); +#ifdef CONFIG_ANDROID + wake_lock(&srvman->sm_wake_lock); +#endif + if (srvman->error) { + tval = ns_to_timeval(mxman->last_panic_time); + SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure scsc_panic_code=0x%x happened at [%6lu.%06ld]\n", + mxman->scsc_panic_code, tval.tv_sec, tval.tv_usec); + + /* Print the last panic record to help track ancient failures */ + mxman_show_last_panic(mxman); + +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + return -EILSEQ; + } + + r = send_sm_msg_start_blocking(service, ref); + if (r) { + SCSC_TAG_ERR(MXMAN, "send_sm_msg_start_blocking() failed: r=%d\n", r); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + return r; + } + +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + return 0; +} +EXPORT_SYMBOL(scsc_mx_service_start); + +int scsc_mx_list_services(struct mxman *mxman_p, char *buf, const size_t bufsz) +{ + struct scsc_service *service, *next; + int pos = 0; + struct srvman *srvman_p = scsc_mx_get_srvman(mxman_p->mx); + + list_for_each_entry_safe(service, next, &srvman_p->service_list, list) { + switch (service->id) { + case SCSC_SERVICE_ID_NULL: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "null"); + break; + case SCSC_SERVICE_ID_WLAN: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "wlan"); + break; + case SCSC_SERVICE_ID_BT: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "bt"); + break; + case SCSC_SERVICE_ID_ANT: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "ant"); + break; + case SCSC_SERVICE_ID_R4DBG: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "r4dbg"); + break; + case SCSC_SERVICE_ID_ECHO: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "echo"); + break; + case SCSC_SERVICE_ID_DBG_SAMPLER: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "dbg sampler"); + break; + case SCSC_SERVICE_ID_CLK20MHZ: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "clk20mhz"); + break; + case SCSC_SERVICE_ID_FM: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "fm"); + break; + case SCSC_SERVICE_ID_INVALID: + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "invalid"); + break; + } + } + return pos; +} +EXPORT_SYMBOL(scsc_mx_list_services); + +int scsc_mx_service_stop(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct srvman *srvman = scsc_mx_get_srvman(mx); + struct mxman *mxman = scsc_mx_get_mxman(service->mx); + int r; + struct timeval tval = {}; + + SCSC_TAG_INFO(MXMAN, "%d\n", service->id); +#ifdef CONFIG_SCSC_CHV_SUPPORT + if (chv_run) + return 0; +#endif + mutex_lock(&srvman->api_access_mutex); +#ifdef CONFIG_ANDROID + wake_lock(&srvman->sm_wake_lock); +#endif + if (srvman->error) { + tval = ns_to_timeval(mxman->last_panic_time); + SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure scsc_panic_code=0x%x happened at [%6lu.%06ld]\n", + mxman->scsc_panic_code, tval.tv_sec, tval.tv_usec); + + /* Print the last panic record to help track ancient failures */ + mxman_show_last_panic(mxman); + +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + + /* Return a special status to allow caller recovery logic to know + * that there will never be a recovery + */ + if (mxman_recovery_disabled()) { + SCSC_TAG_ERR(MXMAN, "recovery disabled, return -EPERM (%d)\n", -EPERM); + return -EPERM; /* failed due to prior failure, recovery disabled */ + } else { + return -EILSEQ; /* operation rejected due to prior failure */ + } + } + + r = send_sm_msg_stop_blocking(service); + if (r) { + SCSC_TAG_ERR(MXMAN, "send_sm_msg_stop_blocking() failed: r=%d\n", r); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + return -EIO; /* operation failed */ + } + +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + return 0; +} +EXPORT_SYMBOL(scsc_mx_service_stop); + + +/* Returns 0 if Suspend succeeded, otherwise return error */ +int srvman_suspend_services(struct srvman *srvman) +{ + int ret = 0; + struct scsc_service *service; + + SCSC_TAG_INFO(MXMAN, "\n"); + mutex_lock(&srvman->service_list_mutex); + list_for_each_entry(service, &srvman->service_list, list) { + if (service->client->suspend) { + ret = service->client->suspend(service->client); + /* If any service returns error message and call resume callbacks */ + if (ret) { + list_for_each_entry(service, &srvman->service_list, list) { + if (service->client->resume) + service->client->resume(service->client); + } + SCSC_TAG_INFO(MXMAN, "Service client suspend failure ret: %d\n", ret); + mutex_unlock(&srvman->service_list_mutex); + return ret; + } + } + } + + mutex_unlock(&srvman->service_list_mutex); + SCSC_TAG_INFO(MXMAN, "OK\n"); + return 0; +} + +/* Returns always 0. Extend API and return value if required */ +int srvman_resume_services(struct srvman *srvman) +{ + struct scsc_service *service; + + SCSC_TAG_INFO(MXMAN, "\n"); + mutex_lock(&srvman->service_list_mutex); + list_for_each_entry(service, &srvman->service_list, list) { + if (service->client->resume) + service->client->resume(service->client); + } + + mutex_unlock(&srvman->service_list_mutex); + SCSC_TAG_INFO(MXMAN, "OK\n"); + + return 0; +} + +void srvman_freeze_services(struct srvman *srvman) +{ + struct scsc_service *service; + struct mxman *mxman = scsc_mx_get_mxman(srvman->mx); + + SCSC_TAG_INFO(MXMAN, "\n"); + mutex_lock(&srvman->service_list_mutex); + list_for_each_entry(service, &srvman->service_list, list) { + service->client->stop_on_failure(service->client); + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) + reinit_completion(&mxman->recovery_completion); +#else + INIT_COMPLETION(mxman->recovery_completion); +#endif + mutex_unlock(&srvman->service_list_mutex); + SCSC_TAG_INFO(MXMAN, "OK\n"); +} + +void srvman_unfreeze_services(struct srvman *srvman, u16 scsc_panic_code) +{ + struct scsc_service *service; + + SCSC_TAG_INFO(MXMAN, "\n"); + mutex_lock(&srvman->service_list_mutex); + list_for_each_entry(service, &srvman->service_list, list) { + service->client->failure_reset(service->client, scsc_panic_code); + } + mutex_unlock(&srvman->service_list_mutex); + SCSC_TAG_INFO(MXMAN, "OK\n"); +} + +/** Signal a failure detected by the Client. This will trigger the systemwide + * failure handling procedure: _All_ Clients will be called back via + * their stop_on_failure() handler as a side-effect. + */ +void scsc_mx_service_service_failed(struct scsc_service *service, const char *reason) +{ + struct scsc_mx *mx = service->mx; + struct srvman *srvman = scsc_mx_get_srvman(mx); + u16 host_panic_code; + + host_panic_code = (SCSC_PANIC_CODE_HOST << 15) | (service->id << SCSC_SYSERR_HOST_SERVICE_SHIFT); + + srvman_set_error(srvman); + switch (service->id) { + case SCSC_SERVICE_ID_WLAN: + SCSC_TAG_INFO(MXMAN, "WLAN: %s\n", ((reason != NULL) ? reason : "")); + break; + case SCSC_SERVICE_ID_BT: + SCSC_TAG_INFO(MXMAN, "BT: %s\n", ((reason != NULL) ? reason : "")); + break; + default: + SCSC_TAG_INFO(MXMAN, "service id %d failed\n", service->id); + break; + + } + + SCSC_TAG_INFO(MXMAN, "Reporting host hang code 0x%02x\n", host_panic_code); + + mxman_fail(scsc_mx_get_mxman(mx), host_panic_code, reason); +} +EXPORT_SYMBOL(scsc_mx_service_service_failed); + + +int scsc_mx_service_close(struct scsc_service *service) +{ + struct mxman *mxman = scsc_mx_get_mxman(service->mx); + struct scsc_mx *mx = service->mx; + struct srvman *srvman = scsc_mx_get_srvman(mx); + bool empty; + struct timeval tval = {}; + + SCSC_TAG_INFO(MXMAN, "%d\n", service->id); + + mutex_lock(&srvman->api_access_mutex); +#ifdef CONFIG_ANDROID + wake_lock(&srvman->sm_wake_lock); +#endif + + if (srvman->error) { + tval = ns_to_timeval(mxman->last_panic_time); + SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure scsc_panic_code=0x%x happened at [%6lu.%06ld]\n", + mxman->scsc_panic_code, tval.tv_sec, tval.tv_usec); + + /* Print the last panic record to help track ancient failures */ + mxman_show_last_panic(mxman); + + mutex_unlock(&srvman->api_access_mutex); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + + /* Return a special status when recovery is disabled, to allow + * calling recovery logic to be aware that recovery is disabled, + * hence not wait for recovery events. + */ + if (mxman_recovery_disabled()) { + SCSC_TAG_ERR(MXMAN, "recovery disabled, return -EPERM (%d)\n", -EPERM); + return -EPERM; /* rejected due to prior failure, recovery disabled */ + } else { + return -EIO; + } + } + + /* remove the service from the list and deallocate the service memory */ + mutex_lock(&srvman->service_list_mutex); + list_del(&service->list); + empty = list_empty(&srvman->service_list); + mutex_unlock(&srvman->service_list_mutex); + if (empty) { + /* unregister channel handler */ + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, + NULL, NULL); + } + + kfree(service); + mxman_close(mxman); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + return 0; +} +EXPORT_SYMBOL(scsc_mx_service_close); + +/* Consider move to a public scsc_mx interface */ +struct scsc_service *scsc_mx_service_open(struct scsc_mx *mx, enum scsc_service_id id, struct scsc_service_client *client, int *status) +{ + int ret; + struct scsc_service *service; + struct srvman *srvman = scsc_mx_get_srvman(mx); + struct mxman *mxman = scsc_mx_get_mxman(mx); + bool empty; + struct timeval tval = {}; + + SCSC_TAG_INFO(MXMAN, "%d\n", id); + + mutex_lock(&srvman->api_access_mutex); +#ifdef CONFIG_ANDROID + wake_lock(&srvman->sm_wake_lock); +#endif + if (srvman->error) { + tval = ns_to_timeval(mxman->last_panic_time); + SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure scsc_panic_code=0x%x happened at [%6lu.%06ld]\n", + mxman->scsc_panic_code, tval.tv_sec, tval.tv_usec); + /* Print the last panic record to help track ancient failures */ + mxman_show_last_panic(mxman); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + *status = -EILSEQ; + return NULL; + } + + if (mxman->mxman_state == MXMAN_STATE_FAILED) { + int r; + + mutex_unlock(&srvman->api_access_mutex); + r = wait_for_completion_timeout(&mxman->recovery_completion, + msecs_to_jiffies(SCSC_MX_SERVICE_RECOVERY_TIMEOUT)); + if (r == 0) { + SCSC_TAG_ERR(MXMAN, "Recovery timeout\n"); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + *status = -EIO; + return NULL; + } + + mutex_lock(&srvman->api_access_mutex); + } + + service = kmalloc(sizeof(struct scsc_service), GFP_KERNEL); + if (service) { + /* MaxwellManager Should allocate Mem and download FW */ + ret = mxman_open(mxman); + if (ret) { + kfree(service); +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + *status = ret; + return NULL; + } + /* Initialise service struct here */ + service->mx = mx; + service->id = id; + service->client = client; + init_completion(&service->sm_msg_start_completion); + init_completion(&service->sm_msg_stop_completion); + mutex_lock(&srvman->service_list_mutex); + empty = list_empty(&srvman->service_list); + mutex_unlock(&srvman->service_list_mutex); + if (empty) + mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, + &srv_message_handler, srvman); + mutex_lock(&srvman->service_list_mutex); + list_add_tail(&service->list, &srvman->service_list); + mutex_unlock(&srvman->service_list_mutex); + } else + *status = -ENOMEM; + +#ifdef CONFIG_ANDROID + wake_unlock(&srvman->sm_wake_lock); +#endif + mutex_unlock(&srvman->api_access_mutex); + + return service; +} +EXPORT_SYMBOL(scsc_mx_service_open); + +struct scsc_bt_audio_abox *scsc_mx_service_get_bt_audio_abox(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct mifabox *ptr; + + ptr = scsc_mx_get_aboxram(mx); + + return ptr->aboxram; +} + +struct mifabox *scsc_mx_service_get_aboxram(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct mifabox *ptr; + + ptr = scsc_mx_get_aboxram(mx); + + return ptr; +} + +/** + * Allocate a contiguous block of SDRAM accessible to Client Driver + * + * When allocation fails, beside returning -ENOMEM, the IN-param 'ref' + * is cleared to an INVALID value that can be safely fed to the companion + * function scsc_mx_service_mifram_free(). + */ +int scsc_mx_service_mifram_alloc_extended(struct scsc_service *service, size_t nbytes, scsc_mifram_ref *ref, u32 align, uint32_t flags) +{ + struct scsc_mx *mx = service->mx; + void *mem; + int ret; + struct miframman *ramman; + + if (flags & MIFRAMMAN_MEM_POOL_GENERIC) { + ramman = scsc_mx_get_ramman(mx); + } else if (flags & MIFRAMMAN_MEM_POOL_LOGGING) { + ramman = scsc_mx_get_ramman2(mx); + } else { + SCSC_TAG_ERR(MXMAN, "Unsupported flags value: %d", flags); + *ref = SCSC_MIFRAM_INVALID_REF; + return -ENOMEM; + } + + mem = miframman_alloc(ramman, nbytes, align, service->id); + if (!mem) { + SCSC_TAG_ERR(MXMAN, "miframman_alloc() failed\n"); + *ref = SCSC_MIFRAM_INVALID_REF; + return -ENOMEM; + } + + SCSC_TAG_DEBUG(MXMAN, "Allocated mem %p\n", mem); + + /* Transform native pointer and get mifram_ref type */ + ret = scsc_mx_service_mif_ptr_to_addr(service, mem, ref); + if (ret) { + SCSC_TAG_ERR(MXMAN, "scsc_mx_service_mif_ptr_to_addr() failed: ret=%d", ret); + miframman_free(ramman, mem); + *ref = SCSC_MIFRAM_INVALID_REF; + } else { + SCSC_TAG_DEBUG(MXMAN, "mem %p ref %d\n", mem, *ref); + } + return ret; +} +EXPORT_SYMBOL(scsc_mx_service_mifram_alloc_extended); + +int scsc_mx_service_mifram_alloc(struct scsc_service *service, size_t nbytes, scsc_mifram_ref *ref, u32 align) +{ + return scsc_mx_service_mifram_alloc_extended(service, nbytes, ref, align, MIFRAMMAN_MEM_POOL_GENERIC); +} +EXPORT_SYMBOL(scsc_mx_service_mifram_alloc); + +/** Free a contiguous block of SDRAM */ +void scsc_mx_service_mifram_free_extended(struct scsc_service *service, scsc_mifram_ref ref, uint32_t flags) +{ + struct scsc_mx *mx = service->mx; + void *mem; + struct miframman *ramman; + + if (flags & MIFRAMMAN_MEM_POOL_GENERIC) { + ramman = scsc_mx_get_ramman(mx); + } else if (flags & MIFRAMMAN_MEM_POOL_LOGGING) { + ramman = scsc_mx_get_ramman2(mx); + } else { + SCSC_TAG_ERR(MXMAN, "Unsupported flags value: %d", flags); + return; + } + + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + + SCSC_TAG_DEBUG(MXMAN, "**** Freeing %p\n", mem); + + miframman_free(ramman, mem); +} +EXPORT_SYMBOL(scsc_mx_service_mifram_free_extended); + +void scsc_mx_service_mifram_free(struct scsc_service *service, scsc_mifram_ref ref) +{ + scsc_mx_service_mifram_free_extended(service, ref, MIFRAMMAN_MEM_POOL_GENERIC); +} +EXPORT_SYMBOL(scsc_mx_service_mifram_free); + +/* MIF ALLOCATIONS */ +bool scsc_mx_service_alloc_mboxes(struct scsc_service *service, int n, int *first_mbox_index) +{ + struct scsc_mx *mx = service->mx; + + return mifmboxman_alloc_mboxes(scsc_mx_get_mboxman(mx), n, first_mbox_index); +} +EXPORT_SYMBOL(scsc_mx_service_alloc_mboxes); + +void scsc_service_free_mboxes(struct scsc_service *service, int n, int first_mbox_index) +{ + struct scsc_mx *mx = service->mx; + + return mifmboxman_free_mboxes(scsc_mx_get_mboxman(mx), first_mbox_index, n); +} +EXPORT_SYMBOL(scsc_service_free_mboxes); + +u32 *scsc_mx_service_get_mbox_ptr(struct scsc_service *service, int mbox_index) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif_abs, mbox_index); +} +EXPORT_SYMBOL(scsc_mx_service_get_mbox_ptr); + +int scsc_service_mifintrbit_bit_mask_status_get(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->irq_bit_mask_status_get(mif_abs); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_bit_mask_status_get); + +int scsc_service_mifintrbit_get(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->irq_get(mif_abs); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_get); + +void scsc_service_mifintrbit_bit_set(struct scsc_service *service, int which_bit, enum scsc_mifintr_target dir) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->irq_bit_set(mif_abs, which_bit, (enum scsc_mif_abs_target)dir); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_bit_set); + +void scsc_service_mifintrbit_bit_clear(struct scsc_service *service, int which_bit) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->irq_bit_clear(mif_abs, which_bit); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_bit_clear); + +void scsc_service_mifintrbit_bit_mask(struct scsc_service *service, int which_bit) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->irq_bit_mask(mif_abs, which_bit); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_bit_mask); + +void scsc_service_mifintrbit_bit_unmask(struct scsc_service *service, int which_bit) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->irq_bit_unmask(mif_abs, which_bit); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_bit_unmask); + +int scsc_service_mifintrbit_alloc_fromhost(struct scsc_service *service, enum scsc_mifintr_target dir) +{ + struct scsc_mx *mx = service->mx; + + return mifintrbit_alloc_fromhost(scsc_mx_get_intrbit(mx), (enum scsc_mif_abs_target)dir); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_alloc_fromhost); + +int scsc_service_mifintrbit_free_fromhost(struct scsc_service *service, int which_bit, enum scsc_mifintr_target dir) +{ + struct scsc_mx *mx = service->mx; + + return mifintrbit_free_fromhost(scsc_mx_get_intrbit(mx), which_bit, (enum scsc_mif_abs_target)dir); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_free_fromhost); + +int scsc_service_mifintrbit_register_tohost(struct scsc_service *service, void (*handler)(int irq, void *data), void *data) +{ + struct scsc_mx *mx = service->mx; + + SCSC_TAG_DEBUG(MXMAN, "Registering %pS\n", handler); + + return mifintrbit_alloc_tohost(scsc_mx_get_intrbit(mx), handler, data); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_register_tohost); + +int scsc_service_mifintrbit_unregister_tohost(struct scsc_service *service, int which_bit) +{ + struct scsc_mx *mx = service->mx; + + SCSC_TAG_DEBUG(MXMAN, "Deregistering int for bit %d\n", which_bit); + return mifintrbit_free_tohost(scsc_mx_get_intrbit(mx), which_bit); +} +EXPORT_SYMBOL(scsc_service_mifintrbit_unregister_tohost); + +void *scsc_mx_service_mif_addr_to_ptr(struct scsc_service *service, scsc_mifram_ref ref) +{ + struct scsc_mx *mx = service->mx; + + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + return mif_abs->get_mifram_ptr(mif_abs, ref); +} +EXPORT_SYMBOL(scsc_mx_service_mif_addr_to_ptr); + +void *scsc_mx_service_mif_addr_to_phys(struct scsc_service *service, scsc_mifram_ref ref) +{ + struct scsc_mx *mx = service->mx; + + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + if (mif_abs->get_mifram_phy_ptr) + return mif_abs->get_mifram_phy_ptr(mif_abs, ref); + else + return NULL; +} +EXPORT_SYMBOL(scsc_mx_service_mif_addr_to_phys); + +int scsc_mx_service_mif_ptr_to_addr(struct scsc_service *service, void *mem_ptr, scsc_mifram_ref *ref) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + /* Transform native pointer and get mifram_ref type */ + if (mif_abs->get_mifram_ref(mif_abs, mem_ptr, ref)) { + SCSC_TAG_ERR(MXMAN, "ooops somethig went wrong"); + return -ENOMEM; + } + + return 0; +} +EXPORT_SYMBOL(scsc_mx_service_mif_ptr_to_addr); + +int scsc_mx_service_mif_dump_registers(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + struct scsc_mif_abs *mif_abs; + + mif_abs = scsc_mx_get_mif_abs(mx); + + /* Dump registers */ + mif_abs->mif_dump_registers(mif_abs); + + return 0; +} +EXPORT_SYMBOL(scsc_mx_service_mif_dump_registers); + +struct device *scsc_service_get_device(struct scsc_service *service) +{ + return scsc_mx_get_device(service->mx); +} +EXPORT_SYMBOL(scsc_service_get_device); + +struct device *scsc_service_get_device_by_mx(struct scsc_mx *mx) +{ + return scsc_mx_get_device(mx); +} +EXPORT_SYMBOL(scsc_service_get_device_by_mx); + +/* Force a FW panic for test purposes only */ +int scsc_service_force_panic(struct scsc_service *service) +{ + struct mxman *mxman = scsc_mx_get_mxman(service->mx); + + SCSC_TAG_INFO(MXMAN, "%d\n", service->id); + + return mxman_force_panic(mxman); +} +EXPORT_SYMBOL(scsc_service_force_panic); + +#ifdef CONFIG_SCSC_SMAPPER +u16 scsc_service_get_alignment(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_get_alignment(scsc_mx_get_smapper(mx)); +} + +int scsc_service_mifsmapper_alloc_bank(struct scsc_service *service, bool large_bank, u32 entry_size, u16 *entries) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_alloc_bank(scsc_mx_get_smapper(mx), large_bank, entry_size, entries); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_alloc_bank); + +void scsc_service_mifsmapper_configure(struct scsc_service *service, u32 granularity) +{ + struct scsc_mx *mx = service->mx; + + mifsmapper_configure(scsc_mx_get_smapper(mx), granularity); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_configure); + +int scsc_service_mifsmapper_write_sram(struct scsc_service *service, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_write_sram(scsc_mx_get_smapper(mx), bank, num_entries, first_entry, addr); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_write_sram); + +int scsc_service_mifsmapper_get_entries(struct scsc_service *service, u8 bank, u8 num_entries, u8 *entries) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_get_entries(scsc_mx_get_smapper(mx), bank, num_entries, entries); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_get_entries); + +int scsc_service_mifsmapper_free_entries(struct scsc_service *service, u8 bank, u8 num_entries, u8 *entries) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_free_entries(scsc_mx_get_smapper(mx), bank, num_entries, entries); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_free_entries); + +int scsc_service_mifsmapper_free_bank(struct scsc_service *service, u8 bank) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_free_bank(scsc_mx_get_smapper(mx), bank); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_free_bank); + +u32 scsc_service_mifsmapper_get_bank_base_address(struct scsc_service *service, u8 bank) +{ + struct scsc_mx *mx = service->mx; + + return mifsmapper_get_bank_base_address(scsc_mx_get_smapper(mx), bank); +} +EXPORT_SYMBOL(scsc_service_mifsmapper_get_bank_base_address); +#endif + +#ifdef CONFIG_SCSC_QOS +int scsc_service_pm_qos_add_request(struct scsc_service *service, enum scsc_qos_config config) +{ + struct scsc_mx *mx = service->mx; + + mifqos_add_request(scsc_mx_get_qos(mx), service->id, config); + + return 0; +} +EXPORT_SYMBOL(scsc_service_pm_qos_add_request); + +int scsc_service_pm_qos_update_request(struct scsc_service *service, enum scsc_qos_config config) +{ + struct scsc_mx *mx = service->mx; + + mifqos_update_request(scsc_mx_get_qos(mx), service->id, config); + + return 0; +} +EXPORT_SYMBOL(scsc_service_pm_qos_update_request); + +int scsc_service_pm_qos_remove_request(struct scsc_service *service) +{ + struct scsc_mx *mx = service->mx; + + if (!mx) + return -EIO; + + mifqos_remove_request(scsc_mx_get_qos(mx), service->id); + + return 0; +} +EXPORT_SYMBOL(scsc_service_pm_qos_remove_request); +#endif +#ifdef CONFIG_SCSC_MXLOGGER +/* If there is no service/mxman associated, register the observer as global (will affect all the mx instanes)*/ +/* Users of these functions should ensure that the registers/unregister functions are balanced (i.e. if observer is registed as global, + * it _has_ to unregister as global) */ +int scsc_service_register_observer(struct scsc_service *service, char *name) +{ + struct scsc_mx *mx; + + if (!service) + return mxlogger_register_global_observer(name); + + mx = service->mx; + + if (!mx) + return -EIO; + + return mxlogger_register_observer(scsc_mx_get_mxlogger(mx), name); +} +EXPORT_SYMBOL(scsc_service_register_observer); + +/* If there is no service/mxman associated, unregister the observer as global (will affect all the mx instanes)*/ +int scsc_service_unregister_observer(struct scsc_service *service, char *name) +{ + struct scsc_mx *mx; + + if (!service) + return mxlogger_unregister_global_observer(name); + + mx = service->mx; + + if (!mx) + return -EIO; + + return mxlogger_unregister_observer(scsc_mx_get_mxlogger(mx), name); +} +EXPORT_SYMBOL(scsc_service_unregister_observer); +#endif diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger.h b/drivers/misc/samsung/scsc/scsc_wifilogger.h new file mode 100644 index 000000000000..4eb6769b27a3 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger.h @@ -0,0 +1,199 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef _SCSC_WIFILOGGER_H_ +#define _SCSC_WIFILOGGER_H_ +/** + * Internal Reference docs for WiFi-Logger subsystem + * + * SC-507043-SW -- Android Wi-Fi Logger architecture + * SC-507780-DD -- Android Enhanced Logging + * WiFiLogger Core Driver Requirements and Design + * + * This is the CONSUMER API as implemented by scsc_wifilogger driver: + * the framework and WiFi HAL are the final consumer of WiFi logger provided + * data but this API is directly used by our driver NetLink layer to + * configure and start/stop Android Enhanced Logging - WiFi Logger. + * + * Workflow is as follows: + * + * - framework invokes wifi_logger.h exported methods implemented by WiFi HAL + * - WiFi HAL wifi_logger module translates wifi_logger.h requests into + * NetLink vendor messages dispatched to out driver + * - our SCSC netlink layer driver translates back NetLink received messages + * into invokations of methods exported by this driver into the current + * header file + * - this driver, manages all the basic ring operations, providing: + * + this consumer API used to configure and start/stop the data-consuming + * reader-process that pushes data up to the framework through the NetLink + * channel + * + a producer API that will be used to push data/record into the rings + * + all the machinery needed to create and manage multiple rings + * + * As a consequence this file's types and methods definitions closely resembles + * the interface and types defined in: + * + * hardware/libhardware_legacy/include/hardware_legacy/wifi_logger.h + * hardware/libhardware_legacy/include/hardware_legacy/wifi_hal.h + * + * Some function arguments, deemed un-needed in the driver layer, have been + * removed from function prototypes, and all the names have been prefixed + * with "scsc_". + * + * Types' definitions are splitted into scsc_wifilogger_types.h dedicated + * header since they will be used also by core implementation. + * + */ +#include "scsc_wifilogger_types.h" + +/** + * API to collect a firmware version string. + * - Caller is responsible to allocate / free a buffer to retrieve firmware + * version info. + * - Max string will be at most 256 bytes. + */ +wifi_error scsc_wifi_get_firmware_version(char *buffer, int buffer_size); + +/** + * API to collect a driver version string. + * - Caller is responsible to allocate / free a buffer to retrieve driver + * version info. + * - Max string will be at most 256 bytes. + */ +wifi_error scsc_wifi_get_driver_version(char *buffer, int buffer_size); + +/** + * API to get the status of all ring buffers supported by driver. + * - Caller is responsible to allocate / free ring buffer status. + * - Maximum no of ring buffer would be 10. + */ +wifi_error scsc_wifi_get_ring_buffers_status(u32 *num_rings, + struct scsc_wifi_ring_buffer_status *status); + +/** + * API to retrieve the current supportive features. + * - An integer variable is enough to have bit mapping info by caller. + */ +wifi_error scsc_wifi_get_logger_supported_feature_set(unsigned int *support); + + +/** + * API to set/reset the log handler for getting ring data + * - Only a single instance of log handler can be instantiated for each + * ring buffer. + */ +wifi_error scsc_wifi_set_log_handler(on_ring_buffer_data handler, void *ctx); +wifi_error scsc_wifi_reset_log_handler(void); + +/** + * API to set/reset the alert handler for the alert case in Wi-Fi Chip + * - Only a single instance of alert handler can be instantiated. + */ +wifi_error scsc_wifi_set_alert_handler(on_alert handler, void *ctx); +wifi_error scsc_wifi_reset_alert_handler(void); + +/* API for framework to indicate driver has to upload and drain all data + * of a given ring + */ +wifi_error scsc_wifi_get_ring_data(char *ring_name); + +/** + * API to trigger the debug collection. + * Unless his API is invoked - logging is not triggered. + * - Verbose_level 0 corresponds to no collection, + * and it makes log handler stop by no more events from driver. + * - Verbose_level 1 correspond to normal log level, with minimal user impact. + * This is the default value. + * - Verbose_level 2 are enabled when user is lazily trying to reproduce + * a problem, wifi performances and power can be impacted but device should + * not otherwise be significantly impacted. + * - Verbose_level 3+ are used when trying to actively debug a problem. + * + * ring_name represent the name of the ring for which data + * collection shall start. + * + * flags: TBD parameter used to enable/disable specific events + * on a ring + * max_interval: maximum interval in seconds for driver to + * invoke on_ring_buffer_data, + * ignore if zero + * min_data_size: minimum data size in buffer for driver to + * invoke on_ring_buffer_data, + * ignore if zero + */ +wifi_error scsc_wifi_start_logging(u32 verbose_level, u32 flags, u32 max_interval_sec, + u32 min_data_size, char *ring_name); + +/** + * API to collect a firmware memory dump for a given iface by async memdump event. + * - Triggered by Alerthandler, esp. when FW problem or FW health check happens + * - Caller is responsible to store fw dump data into a local, + * e.g., /data/misc/wifi/alertdump-1.bin + */ +wifi_error scsc_wifi_get_firmware_memory_dump(on_firmware_memory_dump handler, void *ctx); + +/** + * API to collect driver state. + * + * Framework will call this API soon before or after (but not + * concurrently with) wifi_get_firmware_memory_dump(). Capturing + * firmware and driver dumps is intended to help identify + * inconsistent state between these components. + * + * - In response to this call, HAL implementation should make one or + * more calls to callbacks.on_driver_memory_dump(). Framework will + * copy data out of the received |buffer|s, and concatenate the + * contents thereof. + * - HAL implemention will indicate completion of the driver memory + * dump by returning from this call. + */ +wifi_error scsc_wifi_get_driver_memory_dump(on_driver_memory_dump handler, void *ctx); + +/** + * API to start packet fate monitoring. + * - Once stared, monitoring should remain active until HAL is unloaded. + * - When HAL is unloaded, all packet fate buffers should be cleared. + */ +wifi_error scsc_wifi_start_pkt_fate_monitoring(void); + +/** + * API to retrieve fates of outbound packets. + * - HAL implementation should fill |tx_report_bufs| with fates of + * _first_ min(n_requested_fates, actual packets) frames + * transmitted for the most recent association. The fate reports + * should follow the same order as their respective packets. + * - HAL implementation may choose (but is not required) to include + * reports for management frames. + * - Packets reported by firmware, but not recognized by driver, + * should be included. However, the ordering of the corresponding + * reports is at the discretion of HAL implementation. + * - Framework may call this API multiple times for the same association. + * - Framework will ensure |n_requested_fates <= MAX_FATE_LOG_LEN|. + * - Framework will allocate and free the referenced storage. + */ +wifi_error scsc_wifi_get_tx_pkt_fates(wifi_tx_report *tx_report_bufs, + size_t n_requested_fates, + size_t *n_provided_fates); + +/** + * API to retrieve fates of inbound packets. + * - HAL implementation should fill |rx_report_bufs| with fates of + * _first_ min(n_requested_fates, actual packets) frames + * received for the most recent association. The fate reports + * should follow the same order as their respective packets. + * - HAL implementation may choose (but is not required) to include + * reports for management frames. + * - Packets reported by firmware, but not recognized by driver, + * should be included. However, the ordering of the corresponding + * reports is at the discretion of HAL implementation. + * - Framework may call this API multiple times for the same association. + * - Framework will ensure |n_requested_fates <= MAX_FATE_LOG_LEN|. + * - Framework will allocate and free the referenced storage. + */ +wifi_error scsc_wifi_get_rx_pkt_fates(wifi_rx_report *rx_report_bufs, + size_t n_requested_fates, + size_t *n_provided_fates); + +#endif /*_SCSC_WIFILOGGER_H_*/ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_api.c b/drivers/misc/samsung/scsc/scsc_wifilogger_api.c new file mode 100644 index 000000000000..03647524be82 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_api.c @@ -0,0 +1,261 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#include +#include + +#include "scsc_wifilogger.h" +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger_internal.h" +#include "scsc_wifilogger_ring_pktfate.h" + +wifi_error scsc_wifi_get_firmware_version(char *buffer, int buffer_size) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + mxman_get_fw_version(buffer, buffer_size); + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_firmware_version); + +wifi_error scsc_wifi_get_driver_version(char *buffer, int buffer_size) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + mxman_get_driver_version(buffer, buffer_size); + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_driver_version); + +wifi_error scsc_wifi_get_ring_buffers_status(u32 *num_rings, + struct scsc_wifi_ring_buffer_status *status) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + if (!scsc_wifilogger_get_rings_status(num_rings, status)) + return WIFI_ERROR_UNINITIALIZED; + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_ring_buffers_status); + +wifi_error scsc_wifi_get_logger_supported_feature_set(unsigned int *support) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + if (!support) + return WIFI_ERROR_INVALID_ARGS; + *support = scsc_wifilogger_get_features(); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_logger_supported_feature_set); + +wifi_error scsc_wifi_set_log_handler(on_ring_buffer_data handler, void *ctx) +{ + struct scsc_wifi_logger *wl = NULL; + + SCSC_TAG_DEBUG(WLOG, "\n"); + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "Cannot register log_handler on UNINITIALIZED WiFi Logger.\n"); + return WIFI_ERROR_UNINITIALIZED; + } + if (!handler) { + SCSC_TAG_ERR(WLOG, + "Cannot register NULL log_handler for WiFi Logger.\n"); + return WIFI_ERROR_INVALID_ARGS; + } + + mutex_lock(&wl->lock); + if (wl->on_ring_buffer_data_cb) { + SCSC_TAG_ERR(WLOG, + "Log handler already registered...request ignored.\n"); + mutex_unlock(&wl->lock); + return WIFI_SUCCESS; + } + wl->on_ring_buffer_data_cb = handler; + wl->on_ring_buffer_ctx = ctx; + mutex_unlock(&wl->lock); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_set_log_handler); + +wifi_error scsc_wifi_reset_log_handler(void) +{ + struct scsc_wifi_logger *wl = NULL; + + SCSC_TAG_DEBUG(WLOG, "\n"); + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "Cannot reset log_handler on UNINITIALIZED WiFi Logger.\n"); + return WIFI_ERROR_UNINITIALIZED; + } + mutex_lock(&wl->lock); + wl->on_ring_buffer_data_cb = NULL; + wl->on_ring_buffer_ctx = NULL; + mutex_unlock(&wl->lock); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_reset_log_handler); + +wifi_error scsc_wifi_set_alert_handler(on_alert handler, void *ctx) +{ + struct scsc_wifi_logger *wl = NULL; + + SCSC_TAG_DEBUG(WLOG, "\n"); + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "Cannot register alert_handler on UNINITIALIZED WiFi Logger.\n"); + return WIFI_ERROR_UNINITIALIZED; + } + if (!handler) { + SCSC_TAG_ERR(WLOG, + "Cannot register NULL alert_handler for WiFi Logger.\n"); + return WIFI_ERROR_INVALID_ARGS; + } + + mutex_lock(&wl->lock); + if (wl->on_alert_cb) { + SCSC_TAG_ERR(WLOG, + "Alert handler already registered...request ignored.\n"); + mutex_unlock(&wl->lock); + return WIFI_SUCCESS; + } + wl->on_alert_cb = handler; + wl->on_alert_ctx = ctx; + mutex_unlock(&wl->lock); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_set_alert_handler); + +wifi_error scsc_wifi_reset_alert_handler(void) +{ + struct scsc_wifi_logger *wl = NULL; + + SCSC_TAG_DEBUG(WLOG, "\n"); + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "Cannot reset alert_handler on UNINITIALIZED WiFi Logger.\n"); + return WIFI_ERROR_UNINITIALIZED; + } + mutex_lock(&wl->lock); + wl->on_alert_cb = NULL; + wl->on_alert_ctx = NULL; + mutex_unlock(&wl->lock); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_reset_alert_handler); + +wifi_error scsc_wifi_get_ring_data(char *ring_name) +{ + struct scsc_wlog_ring *r; + struct scsc_wifi_logger *wl = NULL; + + SCSC_TAG_DEBUG(WLOG, "\n"); + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "Cannot drain ring %s on UNINITIALIZED WiFi Logger.\n", + ring_name); + return WIFI_ERROR_UNINITIALIZED; + } + + mutex_lock(&wl->lock); + if (!wl->on_ring_buffer_data_cb) + SCSC_TAG_WARNING(WLOG, + "NO log-handler registered. Discarding data while draining ring: %s\n", + ring_name); + mutex_unlock(&wl->lock); + + r = scsc_wifilogger_get_ring_from_name(ring_name); + if (!r) { + SCSC_TAG_ERR(WLOG, + "Ring %s NOT found. Cannot drain.\n", + ring_name); + return WIFI_ERROR_NOT_AVAILABLE; + } + + scsc_wlog_drain_whole_ring(r); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_ring_data); + +wifi_error scsc_wifi_start_logging(u32 verbose_level, u32 flags, u32 max_interval_sec, + u32 min_data_size, char *ring_name) +{ + struct scsc_wlog_ring *r; + + SCSC_TAG_DEBUG(WLOG, "\n"); + r = scsc_wifilogger_get_ring_from_name(ring_name); + if (!r) { + SCSC_TAG_ERR(WLOG, + "Ring %s NOT found. Cannot start logging\n", + ring_name); + return WIFI_ERROR_NOT_AVAILABLE; + } + + return scsc_wlog_start_logging(r, verbose_level, flags, + max_interval_sec, min_data_size); +} +EXPORT_SYMBOL(scsc_wifi_start_logging); + +wifi_error scsc_wifi_get_firmware_memory_dump(on_firmware_memory_dump handler, void *ctx) +{ + char buf[] = "Full FW memory dump NOT available.\n"; + + SCSC_TAG_DEBUG(WLOG, "\n"); + handler(buf, sizeof(buf), ctx); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_firmware_memory_dump); + +wifi_error scsc_wifi_get_driver_memory_dump(on_driver_memory_dump handler, void *ctx) +{ + char buf[] = "Full DRIVER memory dump NOT available.\n"; + + SCSC_TAG_DEBUG(WLOG, "\n"); + handler(buf, sizeof(buf), ctx); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_driver_memory_dump); + +wifi_error scsc_wifi_start_pkt_fate_monitoring(void) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + scsc_wifilogger_ring_pktfate_start_monitoring(); + + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_start_pkt_fate_monitoring); + +wifi_error scsc_wifi_get_tx_pkt_fates(wifi_tx_report *tx_report_bufs, + size_t n_requested_fates, + size_t *n_provided_fates) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + scsc_wifilogger_ring_pktfate_get_fates(TX_FATE, tx_report_bufs, + n_requested_fates, n_provided_fates); + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_tx_pkt_fates); + +wifi_error scsc_wifi_get_rx_pkt_fates(wifi_rx_report *rx_report_bufs, + size_t n_requested_fates, + size_t *n_provided_fates) +{ + SCSC_TAG_DEBUG(WLOG, "\n"); + scsc_wifilogger_ring_pktfate_get_fates(RX_FATE, rx_report_bufs, + n_requested_fates, n_provided_fates); + return WIFI_SUCCESS; +} +EXPORT_SYMBOL(scsc_wifi_get_rx_pkt_fates); diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_core.c b/drivers/misc/samsung/scsc/scsc_wifilogger_core.c new file mode 100644 index 000000000000..24afb406ea78 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_core.c @@ -0,0 +1,470 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +/* Uses */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Implements */ +#include "scsc_wifilogger_core.h" + +static atomic_t next_ring_id; + +static void wlog_drain_worker(struct work_struct *work) +{ + struct scsc_wlog_ring *r; + + r = container_of(work, struct scsc_wlog_ring, drain_work); + + if (r && r->ops.drain_ring) + r->ops.drain_ring(r, r->flushing ? r->st.rb_byte_size : DEFAULT_DRAIN_CHUNK_SZ(r)); +} + +static void drain_timer_callback(unsigned long data) +{ + struct scsc_wlog_ring *r = (struct scsc_wlog_ring *)data; + + SCSC_TAG_DBG4(WLOG, "TIMER DRAIN : %p\n", r); + /* we should kick the workqueue here...no sleep */ + queue_work(r->drain_workq, &r->drain_work); + + if (r->st.verbose_level && r->max_interval_sec) { + mod_timer(&r->drain_timer, + jiffies + msecs_to_jiffies(r->max_interval_sec * 1000)); + SCSC_TAG_DBG4(WLOG, "TIMER RELOADED !!!\n"); + } +} + +static int wlog_ring_init(struct scsc_wlog_ring *r) +{ + /* Allocate buffer and spare area */ + r->buf = kzalloc(r->st.rb_byte_size + MAX_RECORD_SZ, GFP_KERNEL); + if (!r->buf) + return -ENOMEM; + r->drain_sz = DRAIN_BUF_SZ; + r->drain_buf = kzalloc(r->drain_sz, GFP_KERNEL); + if (!r->drain_buf) { + kfree(r->buf); + return -ENOMEM; + } + mutex_init(&r->drain_lock); + + r->drain_workq = create_workqueue("wifilogger"); + INIT_WORK(&r->drain_work, wlog_drain_worker); + setup_timer(&r->drain_timer, drain_timer_callback, (unsigned long)r); + + r->st.ring_id = atomic_read(&next_ring_id); + atomic_inc(&next_ring_id); + + SCSC_TAG_DBG3(WLOG, "Workers initialized for ring[%p]: %s\n", + r, r->st.name); + + return 0; +} + +static void wlog_ring_finalize(struct scsc_wlog_ring *r) +{ + if (!r) + return; + + cancel_work_sync(&r->drain_work); + del_timer_sync(&r->drain_timer); + destroy_workqueue(r->drain_workq); + + r->initialized = false; + kfree(r->drain_buf); + kfree(r->buf); + r->buf = NULL; +} + +static wifi_error wlog_get_ring_status(struct scsc_wlog_ring *r, + struct scsc_wifi_ring_buffer_status *status) +{ + if (!r || !status) + return WIFI_ERROR_INVALID_ARGS; + //TODO locking SRCU ? + *status = r->st; + + return WIFI_SUCCESS; +} + +static int wlog_read_records(struct scsc_wlog_ring *r, u8 *buf, + size_t blen, u32 *records, + struct scsc_wifi_ring_buffer_status *status) +{ + u16 read_bytes = 0, rec_sz = 0; + u32 got_records = 0, req_records = -1; + + if (scsc_wlog_ring_is_flushing(r)) + return 0; + + /** + * req_records has been loaded with a max u32 value by default + * on purpose...if a max number of records is provided in records + * update req_records accordingly + */ + if (records) + req_records = *records; + /** + * We have ONLY ONE READER at any time that consumes data, impersonated + * here by the drain_ring drainer callback, whose read-ops are ensured + * atomic by the drain_lock mutex: this will guard against races + * between the periodic-drain worker and the threshold-drain procedure + * triggered by the write itself. + * + * But we want also to guard against any direct read_record invokation + * like in test rings via debugfs so we add a read spinlock: this last + * won't lead to any contention here anyway most of the time in a + * real scenario so the same reason we don't need either any irqsave + * spinlock version....so latency also is not impacted. + */ + raw_spin_lock(&r->rlock); + while (!scsc_wlog_is_ring_empty(r) && got_records < req_records) { + rec_sz = REC_SZ(r, RPOS(r)); + if (read_bytes + rec_sz > blen) + break; + /** + * Rollover is transparent on read...last written material in + * spare is still there... + */ + memcpy(buf + read_bytes, REC_START(r, RPOS(r)), rec_sz); + read_bytes += rec_sz; + r->st.read_bytes += rec_sz; + got_records++; + } + if (status) + *status = r->st; + raw_spin_unlock(&r->rlock); + + if (records) + *records = got_records; + SCSC_TAG_DBG4(WLOG, "BytesRead:%d -- RecordsRead:%d\n", + read_bytes, got_records); + + return read_bytes; +} + +static int wlog_default_ring_drainer(struct scsc_wlog_ring *r, size_t drain_sz) +{ + int rval = 0, drained_bytes = 0; + size_t chunk_sz = drain_sz <= r->drain_sz ? drain_sz : r->drain_sz; + struct scsc_wifi_ring_buffer_status ring_status = {}; + + /* An SRCU on callback here would better */ + mutex_lock(&r->drain_lock); + do { + /* drain ... consumes data */ + rval = r->ops.read_records(r, r->drain_buf, chunk_sz, NULL, &ring_status); + /* and push...if any callback defined */ + if (!r->flushing) { + mutex_lock(&r->wl->lock); + if (rval > 0 && r->wl->on_ring_buffer_data_cb) { + SCSC_TAG_DEBUG(WLOG, + "Invoking registered log_handler:%p to drain %d bytes\n", + r->wl->on_ring_buffer_data_cb, rval); + r->wl->on_ring_buffer_data_cb(r->st.name, r->drain_buf, rval, + &ring_status, r->wl->on_ring_buffer_ctx); + SCSC_TAG_DBG4(WLOG, "Callback processed %d bytes\n", rval); + } + mutex_unlock(&r->wl->lock); + } + drained_bytes += rval; + } while (rval && drained_bytes <= drain_sz); + SCSC_TAG_DBG3(WLOG, "%s %d bytes\n", (r->flushing) ? "Flushed" : "Drained", + drained_bytes); + + /* Execute flush if required... */ + if (r->flushing) { + unsigned long flags; + + /* Inhibit writers momentarily */ + raw_spin_lock_irqsave(&r->wlock, flags); + r->dropped = 0; + r->st.written_records = 0; + r->st.read_bytes = r->st.written_bytes = 0; + r->flushing = false; + raw_spin_unlock_irqrestore(&r->wlock, flags); + SCSC_TAG_INFO(WLOG, "Ring '%s' flushed.\n", r->st.name); + } + mutex_unlock(&r->drain_lock); + + return drained_bytes; +} + +/** + * A generic write that takes care to build the final payload created + * concatenating: + * - the common record-header + * - an optionally provided ring_hdr + * - the provided payload buf + * + * The optional header is passed down as a separate parameters to avoid + * unnecessary intermediate copies: this function will copy all the bits + * in place directly into the proper calculated ring position. + * + * By design a read-end-point is always provided by the framework + * (in terms of netlink channels towards the WiFi-HAL) so we spawn a + * configurable reader-worker upon start of logging, and the same reader + * is also invoked when ring is running out of space: for these reasons + * the ring is meant NOT to overwrite itself ever. + * + * If NO periodic reader is spawned NOR a min_data_size threshold was + * specified to force kick the periodic drainer, we could just end-up + * filling up the ring: in that case we just drop and account for it. + * + * Data is drained and pushed periodically upstream using the + * on_ring_buffer_data_cb if any provided and periodic drain was + * configured. + * + * @r: the referenced ring + * @buf: payload + * @blen: payload_sz + * @ring_hdr: upper-layer-record-header + * @hlen: upper-layer-record-header length + * @verbose_level: loglevel for this message (to be checked against) + * @timestamp: a providewd timestamp (if any). If zero a timestamp will be + * calculated. + * + * Final injected record will be composed as follows: + * + * |common_hdr|ring_hdr|buf| + * + * where the common header is compued and filled in by this function, and the + * provided additional upper-layer header ring_hdr could be not provided. + * + * THIS BASIC RING OPERATION IS THE WORKHORSE USED BY THE PRODUCER API IMPLEMENTED + * BY REAL RINGS, AND AS SUCH COULD BE INVOKED FROM ANY CONTEXTS...SO IT MUST NOT SLEEP. + */ +static int wlog_write_record(struct scsc_wlog_ring *r, u8 *buf, size_t blen, + void *ring_hdr, size_t hlen, u32 verbose_level, u64 timestamp) +{ + u8 *start = NULL; + u16 chunk_sz; + unsigned long flags; + + if (scsc_wlog_ring_is_flushing(r)) + return 0; + + /* Just drop messages above configured verbose level. 0 is disabled */ + if (!scsc_wlog_is_message_allowed(r, verbose_level)) + return 0; + + //TODO Account for missing timestamp + chunk_sz = sizeof(struct scsc_wifi_ring_buffer_entry) + hlen + blen; + if (chunk_sz > MAX_RECORD_SZ) { + SCSC_TAG_WARNING(WLOG, "Dropping record exceeding %d bytes\n", + chunk_sz); + return 0; + } + + raw_spin_lock_irqsave(&r->wlock, flags); + /** + * Are there enough data to drain ? + * if so...drain...queueing work.... + * if not (min_data_size == 0) just do nothing + */ + if (!r->drop_on_full && r->min_data_size && + AVAIL_BYTES(r) >= r->min_data_size) + queue_work(r->drain_workq, &r->drain_work); + /** + * If no min_data_size was specified, NOR a periodic read-worker + * was configured (i.e. max_interval_sec == 0), we could end up + * filling up the ring...in that case just drop...accounting for it. + * + * This is the case when packet_fate rings fills up... + */ + if (!CAN_FIT(r, chunk_sz)) { + SCSC_TAG_DBG4(WLOG, "[%s]:: dropped %zd bytes\n", + r->st.name, blen + hlen); + r->dropped += blen + hlen; + raw_spin_unlock_irqrestore(&r->wlock, flags); + return 0; + } + + start = REC_START(r, WPOS(r)); + REC_HEADER_FILL(start, hlen + blen, timestamp, (u8)r->st.flags, r->type); + start += sizeof(struct scsc_wifi_ring_buffer_entry); + if (hlen) { + memcpy(start, ring_hdr, hlen); + start += hlen; + } + if (blen) + memcpy(start, buf, blen); + /* Account for rollover using spare area at end of ring... */ + if (start + blen > BUF_END(r)) + memcpy(BUF_START(r), BUF_END(r), start + blen - BUF_END(r)); + r->st.written_bytes += chunk_sz; + r->st.written_records++; + raw_spin_unlock_irqrestore(&r->wlock, flags); + + return chunk_sz; +} + +static int wlog_default_ring_config_change(struct scsc_wlog_ring *r, + u32 verbose_level, u32 flags, + u32 max_interval_sec, + u32 min_data_size) +{ + u32 old_interval_sec; + + SCSC_TAG_DEBUG(WLOG, "Ring: %s -- configuration change.\n", + r->st.name); + + r->min_data_size = min_data_size; + old_interval_sec = r->max_interval_sec; + r->max_interval_sec = max_interval_sec; + + if (r->state == RING_STATE_SUSPEND && r->st.verbose_level) { + /* Restarting timeri where required ... + * it will take care to queue_work back. + */ + if (r->max_interval_sec) + mod_timer(&r->drain_timer, + jiffies + msecs_to_jiffies(r->max_interval_sec * 1000)); + r->state = RING_STATE_ACTIVE; + SCSC_TAG_INFO(WLOG, "ACTIVATED ring: %s\n", r->st.name); + } else if (r->state == RING_STATE_ACTIVE && !r->st.verbose_level) { + /* Stop timer, cancel pending work */ + del_timer_sync(&r->drain_timer); + cancel_work_sync(&r->drain_work); + r->state = RING_STATE_SUSPEND; + SCSC_TAG_INFO(WLOG, "SUSPENDED ring: %s\n", r->st.name); + } else if (r->state == RING_STATE_ACTIVE) { + if (old_interval_sec != r->max_interval_sec) { + if (!r->max_interval_sec) + del_timer_sync(&r->drain_timer); + else + mod_timer(&r->drain_timer, + jiffies + msecs_to_jiffies(r->max_interval_sec * 1000)); + } + SCSC_TAG_INFO(WLOG, "RECONFIGURED ring: %s\n", r->st.name); + } + + return 0; +} + +static wifi_error wlog_start_logging(struct scsc_wlog_ring *r, + u32 verbose_level, u32 flags, + u32 max_interval_sec, + u32 min_data_size) +{ + if (!r) + return WIFI_ERROR_INVALID_ARGS; + + scsc_wlog_ring_change_verbosity(r, verbose_level); + wlog_default_ring_config_change(r, verbose_level, flags, + max_interval_sec, min_data_size); + + return WIFI_SUCCESS; +} + +static struct scsc_wlog_ring_ops default_ring_ops = { + .init = NULL, + .finalize = NULL, + .get_ring_status = wlog_get_ring_status, + .read_records = wlog_read_records, + .write_record = wlog_write_record, + .loglevel_change = NULL, + .drain_ring = wlog_default_ring_drainer, + .start_logging = wlog_start_logging, +}; + +void scsc_wlog_ring_destroy(struct scsc_wlog_ring *r) +{ + if (!r || r->registered) { + SCSC_TAG_ERR(WLOG, "Cannot destroy ring r:%p\n", r); + return; + } + /* If initialized call custom finalizer at first..reverse order */ + if (r->initialized && r->ops.finalize) + r->ops.finalize(r); + wlog_ring_finalize(r); + kfree(r); +} + +struct scsc_wlog_ring *scsc_wlog_ring_create(char *ring_name, u32 flags, + u8 type, u32 size, + unsigned int features_mask, + init_cb init, finalize_cb fini, + void *priv) +{ + struct scsc_wlog_ring *r = NULL; + + WARN_ON(!ring_name || !size); + + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) + return r; + r->type = type; + r->st.flags = flags; + r->st.rb_byte_size = size; + if (snprintf(r->st.name, RING_NAME_SZ - 1, "%s", ring_name) >= RING_NAME_SZ) + SCSC_TAG_WARNING(WLOG, "Ring name too long...truncated to: %s\n", + r->st.name); + /* Setup defaults and configure init finalize if any provided */ + memcpy(&r->ops, &default_ring_ops, sizeof(struct scsc_wlog_ring_ops)); + r->ops.init = init; + r->ops.finalize = fini; + r->priv = priv; + /* Basic common initialization is called first */ + if (wlog_ring_init(r)) { + SCSC_TAG_ERR(WLOG, + "Wi-Fi Logger Ring %s basic initialization failed.\n", + r->st.name); + kfree(r); + return NULL; + } + if (r->ops.init) { + if (r->ops.init(r)) { + SCSC_TAG_DBG4(WLOG, + "Ring %s custom init completed\n", + r->st.name); + } else { + SCSC_TAG_ERR(WLOG, + "Ring %s custom init FAILED !\n", + r->st.name); + scsc_wlog_ring_destroy(r); + return NULL; + } + } + r->features_mask = features_mask; + raw_spin_lock_init(&r->rlock); + raw_spin_lock_init(&r->wlock); + r->initialized = true; + SCSC_TAG_DEBUG(WLOG, "Ring '%s' initialized.\n", r->st.name); + + return r; +} + +int scsc_wlog_register_loglevel_change_cb(struct scsc_wlog_ring *r, + int (*callback)(struct scsc_wlog_ring *r, u32 new_loglevel)) +{ + if (!callback) + r->ops.loglevel_change = NULL; + else + r->ops.loglevel_change = callback; + + return 0; +} + +int scsc_wlog_drain_whole_ring(struct scsc_wlog_ring *r) +{ + SCSC_TAG_INFO(WLOG, "Draining whole ring %s\n", r->st.name); + return r->ops.drain_ring(r, r->st.rb_byte_size); +} + +void scsc_wlog_flush_ring(struct scsc_wlog_ring *r) +{ + r->flushing = true; + /* kick the workq...which will take care of flushing */ + queue_work(r->drain_workq, &r->drain_work); +} diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_core.h b/drivers/misc/samsung/scsc/scsc_wifilogger_core.h new file mode 100644 index 000000000000..fab28cb8e5f9 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_core.h @@ -0,0 +1,270 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef _SCSC_WIFILOGGER_CORE_H_ +#define _SCSC_WIFILOGGER_CORE_H_ +/** + * @file + * + * Implements a basic ring abstraction to be used as a common foundation + * upon which all the Wi-Fi Logger real rings are built. + * + * It will provide: + * + * - the basic record header common to all rings' flavours as defined in + * scsc_wifilogger_types.h::struct scsc_wifi_ring_buffer_entry + * + * | entry | flags | type | timestamp | + * + * - a set of common basic rings' methods: default_ring_ops + * + * - a common periodic worker used to periodically drain the rings using + * one of the above operations (when configured to do so) + * + * General Ring Architecture + * ------------------------- + * The ring is constituted by a buffer of contiguos memory of specified + * size followed by a spare area of MAX_RECORD_SZ; this latter area is used + * when a record would not fit the physical end of the ring buffer and would + * be going to wrap around: in a such a case we simply write down the record + * content and let it spill over into the spare area; we'll then take care + * to copy the overflown part from the spare area into the start of the + * physical buffer. For this reason a limit of MAX_RECORD_SZ length is + * enforced on write. + * + * Ring status is maintained inside that + * is a well known and defined structure defined in scsc_wifilogger_types.h; + * such structure format is expected by Framework itself when it queries for + * ring status using Wi-Fi HAL. + * Such structure contains also the @read_bytes and @written_bytes counters + * needed for all the ring mechanics based on modulo-ring-size aritmethic. + * Modulo arithmethic is achieved without using the % operator itself so ring + * is expected to be of power-of-two size. + * + * Ring's basic operations are defined as follows: + * - MULTIPLE concurrent writers are expected (and handled) + * + * - Only ONE active reader is expected at any time: such a reader could act + * out of the periodic reader worker or triggered by a write operation. + * + * - each ring read-behavior is configured by two params: + * + min_data_size: the minimum amount of available data that should trigger + * a read. Ignored if zero. + * + max_interval_sec: periodic-reader interval in seconds. Ignored if zero. + * + * NOTE THAT if both the above params are configured as zero, no periodic or + * threshold reading process will be performed and, in absence of any kind of + * polling-read mechanism, the ring would finally FILL-UP: in such a case all + * the data received once the ring is full will be DROPPED. + * This behavior fits the pkt_fate use case scenario. + */ + +#include "scsc_wifilogger_types.h" +#include "scsc_wifilogger_internal.h" + +#define DRAIN_BUF_SZ 4096 + +#define BUF_SZ(r) ((r)->st.rb_byte_size) +#define BUF_START(r) ((r)->buf) +#define BUF_END(r) ((r)->buf + BUF_SZ(r)) +#define AVAIL_BYTES(r) ((r)->st.written_bytes - (r)->st.read_bytes) + +/** + * Avoid % when calculating ring-relative position + * Ring SIZE MUST BE A POWER OF TWO....currently is enforced in + * WiFi-Logger code since there's no way (API) to set the rings' sizes. + */ +#define RPOS(r) \ + ((r)->st.read_bytes & (BUF_SZ(r) - 1)) + +#define WPOS(r) \ + ((r)->st.written_bytes & (BUF_SZ(r) - 1)) + +#define WPOS_INC(r, bytes) \ + (((r)->st.written_bytes + (bytes)) & (BUF_SZ(r) - 1)) + +#define REC_PAYLOAD_SZ(r, pos) \ + (*((u16 *)((r)->buf + (pos)))) + +#define REC_SZ(r, pos) \ + (REC_PAYLOAD_SZ(r, pos) + sizeof(struct scsc_wifi_ring_buffer_entry)) + +#define REC_START(r, pos) ((r)->buf + (pos)) + +#define IS_EMPTY(r) \ + ((r)->st.read_bytes == (r)->st.written_bytes) + +#define CAN_FIT(r, bytes) \ + (bytes < BUF_SZ(r) && \ + (IS_EMPTY(r) || \ + (WPOS(r) < RPOS(r) && WPOS(r) + (bytes) < RPOS(r)) || \ + (WPOS(r) > RPOS(r) && (WPOS(r) + (bytes) < BUF_SZ(r) || WPOS_INC(r, bytes) < RPOS(r))))) + +#define REC_HEADER_FILL(ptr, payload_sz, rtimestamp, rflags, rtype) \ + do { \ + struct scsc_wifi_ring_buffer_entry *h = \ + (struct scsc_wifi_ring_buffer_entry *)(ptr); \ + \ + h->entry_size = (payload_sz); \ + h->flags |= (rflags) | RING_BUFFER_ENTRY_FLAGS_HAS_TIMESTAMP; \ + h->type = (rtype); \ + h->timestamp = (rtimestamp) ?: local_clock();\ + } while (0) + +#define MINIMUM_DRAIN_CHUNK_BYTES 1024 +#define DEFAULT_DRAIN_CHUNK_SZ(r) ((r)->st.rb_byte_size / 2) +#define FORCE_DRAIN_CHUNK_SZ(r) ((r)->st.rb_byte_size / 4) +#define MAX_RECORD_SZ 8192 + +enum { + RING_STATE_SUSPEND, + RING_STATE_ACTIVE +}; + +struct scsc_wlog_ring; + +typedef bool (*init_cb)(struct scsc_wlog_ring *r); +typedef bool (*finalize_cb)(struct scsc_wlog_ring *r); + +struct scsc_wlog_ring_ops { + init_cb init; + finalize_cb finalize; + + wifi_error (*get_ring_status)(struct scsc_wlog_ring *r, + struct scsc_wifi_ring_buffer_status *status); + int (*read_records)(struct scsc_wlog_ring *r, u8 *buf, size_t blen, + u32 *records, struct scsc_wifi_ring_buffer_status *status); + int (*write_record)(struct scsc_wlog_ring *r, u8 *buf, size_t blen, + void *hdr, size_t hlen, u32 verbose_level, u64 timestamp); + int (*loglevel_change)(struct scsc_wlog_ring *r, u32 new_loglevel); + int (*drain_ring)(struct scsc_wlog_ring *r, size_t drain_sz); + wifi_error (*start_logging)(struct scsc_wlog_ring *r, u32 verbose_level, + u32 flags, u32 max_interval_sec, + u32 min_data_size); +}; + +struct scsc_wlog_ring { + bool initialized; + bool registered; + bool flushing; + bool drop_on_full; + u8 state; + + u8 *buf; + unsigned int features_mask; + u8 type; + u32 min_data_size; + u32 max_interval_sec; + u32 dropped; + u32 *verbosity; + raw_spinlock_t rlock, wlock; + struct scsc_wifi_ring_buffer_status st; + + u8 *drain_buf; + size_t drain_sz; + struct mutex drain_lock; + struct timer_list drain_timer; + struct work_struct drain_work; + struct workqueue_struct *drain_workq; + + struct scsc_wlog_ring_ops ops; + + void *priv; + + struct scsc_wifi_logger *wl; +}; + +struct scsc_wlog_ring *scsc_wlog_ring_create(char *ring_name, u32 flags, + u8 type, u32 size, + unsigned int features_mask, + init_cb init, finalize_cb fini, + void *priv); + +void scsc_wlog_ring_destroy(struct scsc_wlog_ring *r); + +int scsc_wlog_register_loglevel_change_cb(struct scsc_wlog_ring *r, + int (*callback)(struct scsc_wlog_ring *r, + u32 new_loglevel)); + +int scsc_wlog_drain_whole_ring(struct scsc_wlog_ring *r); + +static inline bool scsc_wlog_is_ring_empty(struct scsc_wlog_ring *r) +{ + return r->st.written_bytes == r->st.read_bytes; +} + +static inline wifi_error scsc_wlog_get_ring_status(struct scsc_wlog_ring *r, + struct scsc_wifi_ring_buffer_status *status) +{ + if (!r) + return WIFI_ERROR_INVALID_ARGS; + + return r->ops.get_ring_status(r, status); +} + +static inline bool scsc_wlog_is_message_allowed(struct scsc_wlog_ring *r, u32 verbose_level) +{ + return r->st.verbose_level && verbose_level <= r->st.verbose_level; +} + +static inline int scsc_wlog_read_records(struct scsc_wlog_ring *r, u8 *buf, size_t blen) +{ + return r->ops.read_records(r, buf, blen, NULL, NULL); +} + +static inline int scsc_wlog_read_max_records(struct scsc_wlog_ring *r, u8 *buf, + size_t blen, u32 *max_records) +{ + return r->ops.read_records(r, buf, blen, max_records, NULL); +} + +static inline int scsc_wlog_write_record(struct scsc_wlog_ring *r, u8 *buf, size_t blen, + void *hdr, size_t hlen, u32 verbose_level, u64 timestamp) +{ + return r->ops.write_record(r, buf, blen, hdr, hlen, verbose_level, timestamp); +} + +static inline wifi_error scsc_wlog_start_logging(struct scsc_wlog_ring *r, + u32 verbose_level, u32 flags, + u32 max_interval_sec, u32 min_data_size) +{ + return r->ops.start_logging(r, verbose_level, flags, max_interval_sec, min_data_size); +} + +static inline void scsc_wlog_ring_set_drop_on_full(struct scsc_wlog_ring *r) +{ + r->drop_on_full = true; +} + +static inline void scsc_wlog_register_verbosity_reference(struct scsc_wlog_ring *r, u32 *verbose_ref) +{ + r->verbosity = verbose_ref; +} + +static inline bool scsc_wlog_ring_is_flushing(struct scsc_wlog_ring *r) +{ + if (!r->flushing) + return false; + + SCSC_TAG_DBG4(WLOG, "Ring is flushing..abort pending read/write\n"); + return true; +} + +static inline void scsc_wlog_ring_change_verbosity(struct scsc_wlog_ring *r, u32 verbose_level) +{ + if (r->st.verbose_level != verbose_level) { + if (r->ops.loglevel_change) + r->ops.loglevel_change(r, verbose_level); + r->st.verbose_level = verbose_level; + if (r->verbosity) + *r->verbosity = r->st.verbose_level; + SCSC_TAG_INFO(WLOG, "Ring: %s -- verbose_level changed to: %d\n", + r->st.name, r->st.verbose_level); + } +} + +void scsc_wlog_flush_ring(struct scsc_wlog_ring *r); + +#endif /*_SCSC_WIFI_LOGGER_CORE_H_*/ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.c b/drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.c new file mode 100644 index 000000000000..7ab5ff025864 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.c @@ -0,0 +1,439 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#include +#include "scsc_wifilogger_debugfs.h" +#include "scsc_wifilogger_ring_pktfate.h" + +struct dentry *scsc_wlog_debugfs_global_root; + +int dfs_open(struct inode *ino, struct file *filp) +{ + if (!filp->private_data) { + filp->private_data = ino->i_private; + if (!filp->private_data) + return -EFAULT; + } + return 0; +} + +int dfs_release(struct inode *ino, struct file *filp) +{ + return 0; +} + +#define SCSC_RING_TEST_STAT_SZ 512 + +static ssize_t dfs_stats_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + int slen = 0; + char statstr[SCSC_RING_TEST_STAT_SZ] = {}; + struct scsc_ring_test_object *rto = filp->private_data; + + slen = snprintf(statstr, SCSC_RING_TEST_STAT_SZ, + "[%s]:: len:%d state:%d verbose:%d min_data_size:%d max_interval_sec:%d drop_on_full:%d\n" + "\tunread:%d written:%d read:%d written_records:%d dropped:%d buf:%p\n", + rto->r->st.name, rto->r->st.rb_byte_size, rto->r->state, + rto->r->st.verbose_level, rto->r->min_data_size, + rto->r->max_interval_sec, rto->r->drop_on_full, + rto->r->st.written_bytes - rto->r->st.read_bytes, + rto->r->st.written_bytes, rto->r->st.read_bytes, + rto->r->st.written_records, rto->r->dropped, rto->r->buf); + if (slen >= 0 && *f_pos < slen) { + count = (count <= slen - *f_pos) ? count : (slen - *f_pos); + if (copy_to_user(ubuf, statstr + *f_pos, count)) + return -EFAULT; + *f_pos += count; + } else { + count = 0; + } + return count; +} + +const struct file_operations stats_fops = { + .owner = THIS_MODULE, + .open = dfs_open, + .read = dfs_stats_read, + .release = dfs_release, +}; + +#ifdef CONFIG_SCSC_WIFILOGGER_TEST +static int dfs_read_record_open(struct inode *ino, struct file *filp) +{ + int ret; + struct scsc_ring_test_object *rto; + + ret = dfs_open(ino, filp); + if (ret) + return ret; + + rto = filp->private_data; + if (!mutex_trylock(&rto->readers_lock)) { + SCSC_TAG_ERR(WLOG, + "Failed to get readers mutex...ONLY one reader allowed !!!\n"); + dfs_release(ino, filp); + return -EPERM; + } + /* NO Log handler here...only raise verbosity */ + scsc_wifi_start_logging(1, 0x00, 0, 8192, rto->r->st.name); + + return ret; +} + +static int dfs_read_record_release(struct inode *ino, struct file *filp) +{ + struct scsc_ring_test_object *rto = filp->private_data; + + /* Stop logging ... verbosity 0 */ + scsc_wifi_start_logging(0, 0x00, 0, 8192, rto->r->st.name); + mutex_unlock(&rto->readers_lock); + SCSC_TAG_DEBUG(WLOG, "Readers mutex released.\n"); + + return dfs_release(ino, filp); +} + +static ssize_t dfs_read_record(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + int ret; + struct scsc_ring_test_object *rto; + + if (!filp->private_data) + return -EINVAL; + rto = filp->private_data; + + while (scsc_wlog_is_ring_empty(rto->r)) { + if (wait_event_interruptible(rto->rw_wq, + !scsc_wlog_is_ring_empty(rto->r))) + return -ERESTARTSYS; + } + ret = scsc_wlog_read_records(rto->r, rto->rbuf, rto->bsz); + count = ret <= count ? ret : count; + if (copy_to_user(ubuf, rto->rbuf, count)) + return -EFAULT; + *f_pos += count; + + return count; +} + +const struct file_operations read_record_fops = { + .owner = THIS_MODULE, + .open = dfs_read_record_open, + .read = dfs_read_record, + .release = dfs_read_record_release, +}; + +static void on_ring_test_data_cb(char *ring_name, char *buf, int bsz, + struct scsc_wifi_ring_buffer_status *status, + void *ctx) +{ + struct scsc_ring_test_object *a_rto, *head_rto = ctx; + + a_rto = kzalloc(sizeof(*a_rto), GFP_KERNEL); + if (!a_rto) + return; + a_rto->rbuf = kmalloc(bsz, GFP_KERNEL); + if (!a_rto->rbuf) { + kfree(a_rto); + return; + } + /* copy and pass over into a list to simulate a channel */ + memcpy(a_rto->rbuf, buf, bsz); + a_rto->bsz = bsz; + list_add_tail(&a_rto->elem, &head_rto->elem); + wake_up_interruptible(&head_rto->drain_wq); +} + +static int dfs_read_open(struct inode *ino, struct file *filp) +{ + int ret; + struct scsc_ring_test_object *rto; + + ret = dfs_open(ino, filp); + if (ret) + return ret; + + /* Filp private data NOW contains rto */ + rto = filp->private_data; + if (!mutex_trylock(&rto->readers_lock)) { + SCSC_TAG_ERR(WLOG, + "Failed to get readers mutex...ONLY one reader allowed !!!\n"); + dfs_release(ino, filp); + return -EPERM; + } + + SCSC_TAG_DEBUG(WLOG, + "DebugFS Read opened...setting handlers...starting logging on: %s\n", + rto->r->st.name); + scsc_wifi_set_log_handler(on_ring_test_data_cb, rto); + scsc_wifi_start_logging(1, 0x00, 2, 8192, rto->r->st.name); + + return ret; +} + +static ssize_t dfs_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + size_t ret_count = 0; + struct list_head *pos = NULL, *tlist = NULL; + struct scsc_ring_test_object *head_rto, *a_rto; + + if (!filp->private_data) + return -EINVAL; + head_rto = filp->private_data; + + while (list_empty(&head_rto->elem)) { + if (wait_event_interruptible(head_rto->drain_wq, !list_empty(&head_rto->elem))) + return -ERESTARTSYS; + } + + list_for_each_safe(pos, tlist, &head_rto->elem) { + a_rto = list_entry(pos, struct scsc_ring_test_object, elem); + SCSC_TAG_DEBUG(WLOG, "Processing list item: %p\n", a_rto); + if (!a_rto || ret_count + a_rto->bsz >= count) { + SCSC_TAG_DEBUG(WLOG, "BREAK OUT on:%p\n", a_rto); + list_del(pos); + if (a_rto) { + kfree(a_rto->rbuf); + kfree(a_rto); + } + break; + } + if (copy_to_user(ubuf + ret_count, a_rto->rbuf, a_rto->bsz)) + return -EFAULT; + ret_count += a_rto->bsz; + list_del(pos); + kfree(a_rto->rbuf); + kfree(a_rto); + } + *f_pos += ret_count; + + return count; +} + +static int dfs_read_release(struct inode *ino, struct file *filp) +{ + int ret; + struct scsc_ring_test_object *head_rto, *a_rto; + + head_rto = filp->private_data; + if (head_rto) { + struct list_head *pos = NULL, *tlist = NULL; + + list_for_each_safe(pos, tlist, &head_rto->elem) { + a_rto = list_entry(pos, struct scsc_ring_test_object, elem); + list_del(pos); + if (a_rto) { + kfree(a_rto->rbuf); + kfree(a_rto); + a_rto = NULL; + } + } + } + + ret = dfs_read_record_release(ino, filp); + scsc_wifi_reset_log_handler(); + + return ret; +} + +const struct file_operations read_fops = { + .owner = THIS_MODULE, + .open = dfs_read_open, + .read = dfs_read, + .release = dfs_read_release, +}; + +#define BUF_LEN 16 +static ssize_t dfs_verbose_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + char buf[BUF_LEN] = {}; + struct scsc_ring_test_object *rto; + + if (!filp->private_data) + return -EINVAL; + rto = filp->private_data; + + count = snprintf(buf, BUF_LEN, "%d\n", rto->r->st.verbose_level); + if (copy_to_user(ubuf, buf, count)) + return -EFAULT; + /* emit EOF after having spitted the value once */ + count = !*f_pos ? count : 0; + *f_pos += count; + + return count; +} + +static ssize_t dfs_verbose_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *f_pos) +{ + char buf[BUF_LEN] = {}; + size_t written; + unsigned long verb; + struct scsc_ring_test_object *rto; + + if (!filp->private_data) + return -EINVAL; + rto = filp->private_data; + + count = count < BUF_LEN ? count : BUF_LEN - 1; + if (copy_from_user(buf, ubuf, count)) + return -EINVAL; + if (!kstrtoul((const char *)buf, 10, &verb)) + scsc_wlog_ring_change_verbosity(rto->r, verb); + written = strlen(buf); + *f_pos += written; + + SCSC_TAG_DEBUG(WLOG, "Changed verbosity on ring %s to %d\n", + rto->r->st.name, rto->r->st.verbose_level); + + switch (rto->r->st.verbose_level) { + case 10: + SCSC_TAG_DEBUG(WLOG, "Ring '%s' -- RING FULL DRAIN !\n", + rto->r->st.name); + scsc_wifi_get_ring_data(rto->r->st.name); + break; + case 20: + scsc_wlog_flush_ring(rto->r); + SCSC_TAG_DEBUG(WLOG, "Ring '%s' -- RING FLUSH !\n", + rto->r->st.name); + break; + case 30: + scsc_wlog_ring_set_drop_on_full(rto->r); + SCSC_TAG_DEBUG(WLOG, "Ring '%s' -- RING SET DROP ON FULL !\n", + rto->r->st.name); + break; + case 40: + if (rto->r->features_mask & WIFI_LOGGER_PACKET_FATE_SUPPORTED) { + scsc_wifilogger_ring_pktfate_start_monitoring(); + SCSC_TAG_DEBUG(WLOG, "PKTFATE MONITORING STARTED !\n"); + } + break; + case 50: + { + int i, num_rings = 10; + struct scsc_wifi_ring_buffer_status status[10]; + + scsc_wifilogger_get_rings_status(&num_rings, status); + SCSC_TAG_INFO(WLOG, "Returned rings: %d\n", num_rings); + for (i = 0; i < num_rings; i++) + SCSC_TAG_INFO(WLOG, "Retrieved ring: %s\n", status[i].name); + } + break; + default: + break; + } + + return written; +} + +const struct file_operations verbosity_fops = { + .owner = THIS_MODULE, + .open = dfs_open, + .read = dfs_verbose_read, + .write = dfs_verbose_write, + .release = dfs_release, +}; + +#endif /* CONFIG_SCSC_WIFILOGGER_TEST */ + +/*** Public ***/ + +struct scsc_ring_test_object *init_ring_test_object(struct scsc_wlog_ring *r) +{ + struct scsc_ring_test_object *rto; + + rto = kzalloc(sizeof(*rto), GFP_KERNEL); + if (!rto) + return rto; + +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + rto->bsz = MAX_RECORD_SZ; + rto->rbuf = kzalloc(rto->bsz, GFP_KERNEL); + if (!rto->rbuf) { + kfree(rto); + return NULL; + } + rto->wbuf = kzalloc(rto->bsz, GFP_KERNEL); + if (!rto->wbuf) { + kfree(rto->rbuf); + kfree(rto); + return NULL; + } + /* used by on_ring_data_cb simulation test */ + INIT_LIST_HEAD(&rto->elem); + init_waitqueue_head(&rto->drain_wq); + init_waitqueue_head(&rto->rw_wq); + mutex_init(&rto->readers_lock); +#endif + rto->r = r; + + return rto; +} + +void *scsc_wlog_register_debugfs_entry(const char *ring_name, + const char *fname, + const struct file_operations *fops, + void *rto, + struct scsc_wlog_debugfs_info *di) +{ + if (!ring_name || !fname || !fops || !rto || !di) + return NULL; + + /* create root debugfs dirs if they not already exists */ + if (!di->rootdir) { + if (scsc_wlog_debugfs_global_root) { + di->rootdir = scsc_wlog_debugfs_global_root; + } else { + di->rootdir = debugfs_create_dir(SCSC_DEBUGFS_ROOT_DIRNAME, NULL); + scsc_wlog_debugfs_global_root = di->rootdir; + } + if (!di->rootdir) + goto no_rootdir; + } + + if (!di->ringdir) { + di->ringdir = debugfs_create_dir(ring_name, di->rootdir); + if (!di->ringdir) + goto no_ringdir; + } + + /* Saving ring ref @r to Inode */ + debugfs_create_file(fname, 0664, di->ringdir, rto, fops); + + return di; + +no_ringdir: +no_rootdir: + SCSC_TAG_ERR(WLOG, "Failed WiFiLogger Debugfs basic initialization\n"); + return NULL; +} + +void scsc_wifilogger_debugfs_remove_top_dir_recursive(void) +{ + if (!scsc_wlog_debugfs_global_root) + return; + + debugfs_remove_recursive(scsc_wlog_debugfs_global_root); + + SCSC_TAG_INFO(WLOG, "Wi-Fi Logger Debugfs Cleaned Up\n"); +} + +void scsc_register_common_debugfs_entries(char *ring_name, void *rto, + struct scsc_wlog_debugfs_info *di) +{ + scsc_wlog_register_debugfs_entry(ring_name, "stats", + &stats_fops, rto, di); +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + scsc_wlog_register_debugfs_entry(ring_name, "verbose_level", + &verbosity_fops, rto, di); + scsc_wlog_register_debugfs_entry(ring_name, "read_record", + &read_record_fops, rto, di); + scsc_wlog_register_debugfs_entry(ring_name, "read", + &read_fops, rto, di); +#endif +} diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.h b/drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.h new file mode 100644 index 000000000000..6689f756e025 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_debugfs.h @@ -0,0 +1,67 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef _SCSC_WIFILOGGER_DEBUGFS_H_ +#define _SCSC_WIFILOGGER_DEBUGFS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger.h" + +#define SCSC_DEBUGFS_ROOT "/sys/kernel/debug/wifilogger" +#define SCSC_DEBUGFS_ROOT_DIRNAME "wifilogger" + +extern struct dentry *scsc_wlog_debugfs_global_root; + +struct scsc_wlog_debugfs_info { + struct dentry *rootdir; + struct dentry *ringdir; +}; + +struct scsc_ring_test_object { + struct scsc_wlog_ring *r; +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + char *rbuf; + char *wbuf; + size_t bsz; + struct list_head elem; + wait_queue_head_t drain_wq; + wait_queue_head_t rw_wq; + struct mutex readers_lock; +#endif +}; + +void *scsc_wlog_register_debugfs_entry(const char *ring_name, + const char *fname, + const struct file_operations *fops, + void *rto, + struct scsc_wlog_debugfs_info *di); + +struct scsc_ring_test_object *init_ring_test_object(struct scsc_wlog_ring *r); + +void scsc_wifilogger_debugfs_remove_top_dir_recursive(void); + +void scsc_register_common_debugfs_entries(char *ring_name, void *rto, + struct scsc_wlog_debugfs_info *di); + +int dfs_open(struct inode *ino, struct file *filp); + +int dfs_release(struct inode *ino, struct file *filp); + +#endif /* _SCSC_WIFILOGGER_DEBUGFS_H_ */ + diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_internal.c b/drivers/misc/samsung/scsc/scsc_wifilogger_internal.c new file mode 100644 index 000000000000..0f8bd1b29aaa --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_internal.c @@ -0,0 +1,232 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include "scsc_wifilogger_internal.h" + +static struct scsc_wifi_logger *wifi_logger; + +struct scsc_wifi_logger *scsc_wifilogger_get_handle(void) +{ + return wifi_logger; +} + +bool scsc_wifilogger_init(void) +{ + struct scsc_wifi_logger *wl = NULL; + + wl = kzalloc(sizeof(*wl), GFP_KERNEL); + if (!wl) { + SCSC_TAG_ERR(WLOG, + "Failed to allocate scsc_wifilogger data. Abort.\n"); + return false; + } + + mutex_init(&wl->lock); + wl->initialized = true; + wifi_logger = wl; + + return true; +} + +static int scsc_wifilogger_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + int ret = NOTIFY_DONE; + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, "WiFi Logger NOT initialized !\n"); + return NOTIFY_BAD; + } + + switch (event) { + case SCSC_FW_EVENT_FAILURE: + break; + case SCSC_FW_EVENT_MOREDUMP_COMPLETE: + { + char *panic_record_dump = data; + + SCSC_TAG_INFO(WLOG, "Notification received: MOREDUMP COMPLETED.\n"); + SCSC_TAG_DEBUG(WLOG, "PANIC DUMP RX\n-------------------------\n\n%s\n", + panic_record_dump); + if (wl->on_alert_cb) { + wl->on_alert_cb(panic_record_dump, + PANIC_RECORD_DUMP_BUFFER_SZ, 0, wl->on_alert_ctx); + SCSC_TAG_DEBUG(WLOG, "Alert handler -- processed %d bytes @%p\n", + PANIC_RECORD_DUMP_BUFFER_SZ, panic_record_dump); + } + ret = NOTIFY_OK; + break; + } + default: + ret = NOTIFY_BAD; + break; + } + + return ret; +} + +static struct notifier_block firmware_nb = { + .notifier_call = scsc_wifilogger_notifier, +}; + +bool scsc_wifilogger_fw_alert_init(void) +{ + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, "WiFi Logger NOT initialized !\n"); + return false; + } + + wl->features_mask |= WIFI_LOGGER_MEMORY_DUMP_SUPPORTED | WIFI_LOGGER_DRIVER_DUMP_SUPPORTED; + if (!mxman_register_firmware_notifier(&firmware_nb)) + wl->features_mask |= WIFI_LOGGER_WATCHDOG_TIMER_SUPPORTED; + + return true; +} + +unsigned int scsc_wifilogger_get_features(void) +{ + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, "WiFi Logger NOT initialized !\n"); + return 0; + } + + return wl->features_mask; +} + +bool scsc_wifilogger_get_rings_status(u32 *num_rings, + struct scsc_wifi_ring_buffer_status *status) +{ + int i, j = 0; + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, "WiFi Logger NOT initialized !\n"); + *num_rings = 0; + return false; + } + + for (i = 0; i < *num_rings && i < MAX_WIFI_LOGGER_RINGS; i++) + if (wl->rings[i] && wl->rings[i]->registered) + scsc_wlog_get_ring_status(wl->rings[i], &status[j++]); + *num_rings = j; + + return true; +} + +struct scsc_wlog_ring *scsc_wifilogger_get_ring_from_name(char *name) +{ + int i; + struct scsc_wlog_ring *r = NULL; + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "WiFi Logger NOT initialized..cannot find ring !\n"); + return r; + } + + mutex_lock(&wl->lock); + for (i = 0; i < MAX_WIFI_LOGGER_RINGS; i++) { + if (wl->rings[i] && + !strncmp(name, wl->rings[i]->st.name, RING_NAME_SZ)) { + if (wl->rings[i]->initialized && + wl->rings[i]->registered) + r = wl->rings[i]; + break; + } + } + mutex_unlock(&wl->lock); + + return r; +} + +bool scsc_wifilogger_register_ring(struct scsc_wlog_ring *r) +{ + int pos; + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "WiFi Logger NOT initialized..cannot register ring !\n"); + return false; + } + /** + * Calculate ring position in array from unique ring_id: + * there can be multiple distinct rings supporting the same + * feature....like pkt_fate_tx/rx + */ + pos = r->st.ring_id % MAX_WIFI_LOGGER_RINGS; + mutex_lock(&wl->lock); + if (wl->rings[pos]) { + SCSC_TAG_ERR(WLOG, + "Ring %s already registered on position %d. Abort\n", + wl->rings[pos]->st.name, pos); + mutex_unlock(&wl->lock); + return false; + } + SCSC_TAG_DEBUG(WLOG, "Registering ring %s as position %d\n", + r->st.name, pos); + wl->rings[pos] = r; + wl->features_mask |= r->features_mask; + r->wl = wl; + r->registered = true; + mutex_unlock(&wl->lock); + SCSC_TAG_INFO(WLOG, "Ring '%s' registered\n", r->st.name); + + return true; +} + +void scsc_wifilogger_destroy(void) +{ + int i; + struct scsc_wlog_ring *r = NULL; + struct scsc_wifi_logger *wl = NULL; + + wl = scsc_wifilogger_get_handle(); + if (!wl || !wl->initialized) { + SCSC_TAG_ERR(WLOG, + "WiFi Logger NOT initialized..cannot destroy!\n"); + return; + } + + mxman_unregister_firmware_notifier(&firmware_nb); + /* Remove DebufgFS hooks at first... */ + scsc_wifilogger_debugfs_remove_top_dir_recursive(); + + mutex_lock(&wl->lock); + for (i = 0; i < MAX_WIFI_LOGGER_RINGS; i++) { + if (wl->rings[i]) { + r = wl->rings[i]; + scsc_wlog_ring_change_verbosity(r, WLOG_NONE); + r->registered = false; + wl->rings[i] = NULL; + if (r->initialized) + scsc_wlog_ring_destroy(r); + } + } + wl->features_mask = 0; + mutex_unlock(&wl->lock); + kfree(wl); + wifi_logger = NULL; +} diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_internal.h b/drivers/misc/samsung/scsc/scsc_wifilogger_internal.h new file mode 100644 index 000000000000..0aa5db90c40c --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_internal.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef _SCSC_WIFILOGGER_INTERNAL_H_ +#define _SCSC_WIFILOGGER_INTERNAL_H_ + +#include +#include +#include +#include +#include + +#include "scsc_wifilogger_types.h" +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger_debugfs.h" + +struct scsc_wifi_logger { + bool initialized; + struct mutex lock; + unsigned int features_mask; + struct scsc_wlog_ring *rings[MAX_WIFI_LOGGER_RINGS]; + + /** + * There is only one log_handler and alert_handler registered + * to be used across all rings: moreover just one instance of + * these handlers can exist per-ring. + */ + + /* log_handler callback registered by framework */ + void (*on_ring_buffer_data_cb)(char *ring_name, char *buffer, + int buffer_size, + struct scsc_wifi_ring_buffer_status *status, + void *ctx); + /* alert_handler callback registered by framework */ + void (*on_alert_cb)(char *buffer, int buffer_size, int err_code, void *ctx); + + void *on_ring_buffer_ctx; + void *on_alert_ctx; +}; + +bool scsc_wifilogger_init(void); +void scsc_wifilogger_destroy(void); +bool scsc_wifilogger_fw_alert_init(void); +struct scsc_wifi_logger *scsc_wifilogger_get_handle(void); +unsigned int scsc_wifilogger_get_features(void); +bool scsc_wifilogger_register_ring(struct scsc_wlog_ring *r); +struct scsc_wlog_ring *scsc_wifilogger_get_ring_from_name(char *name); +bool scsc_wifilogger_get_rings_status(u32 *num_rings, + struct scsc_wifi_ring_buffer_status *status); + +#endif /* _SCSC_WIFILOGGER_INTERNAL_H_ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_module.c b/drivers/misc/samsung/scsc/scsc_wifilogger_module.c new file mode 100644 index 000000000000..ad2f84cdafbb --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_module.c @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +/** + * Internal Reference docs for WiFi-Logger subsystem + * + * SC-507043-SW -- Android Wi-Fi Logger architecture + * SC-507780-DD -- Android Enhanced Logging + * WiFiLogger Core Driver Requirements and Design + */ +#include "scsc_wifilogger_module.h" + +static int __init scsc_wifilogger_module_init(void) +{ + if (scsc_wifilogger_init()) { + scsc_wifilogger_ring_connectivity_init(); + scsc_wifilogger_ring_wakelock_init(); + scsc_wifilogger_ring_pktfate_init(); +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + scsc_wifilogger_ring_test_init(); +#endif + scsc_wifilogger_fw_alert_init(); + } else { + SCSC_TAG_ERR(WLOG, "Module init failed\n"); + return -ENOMEM; + } + + SCSC_TAG_INFO(WLOG, "Wi-Fi Logger subsystem initialized.\n"); + + return 0; +} + +static void __exit scsc_wifilogger_module_exit(void) +{ + scsc_wifilogger_destroy(); + + SCSC_TAG_INFO(WLOG, "Wi-Fi Logger subsystem unloaded.\n"); +} + +module_init(scsc_wifilogger_module_init); +module_exit(scsc_wifilogger_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Samsung SLSI"); +MODULE_DESCRIPTION("Android Wi-Fi Logger module"); diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_module.h b/drivers/misc/samsung/scsc/scsc_wifilogger_module.h new file mode 100644 index 000000000000..4d8792755ab9 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_module.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ + +#ifndef _SCSC_WIFILOGGER_MODULE_H_ +#define _SCSC_WIFILOGGER_MODULE_H_ + +#include +#include +#include +#include +#include +#include + +#include "scsc_wifilogger_internal.h" + +#include "scsc_wifilogger_ring_connectivity.h" +#include "scsc_wifilogger_ring_wakelock.h" +#include "scsc_wifilogger_ring_pktfate.h" + +#ifdef CONFIG_SCSC_WIFILOGGER_TEST +#include "scsc_wifilogger_ring_test.h" +#endif + +#endif /* _SCSC_WIFILOGGER_MODULE_H_ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.c b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.c new file mode 100644 index 000000000000..923bf3243b40 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.c @@ -0,0 +1,121 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + *****************************************************************************/ +/* Implements */ +#include "scsc_wifilogger_ring_connectivity.h" + +/* Uses */ +#include +#include "scsc_wifilogger_ring_connectivity_api.h" +#include "scsc_wifilogger_internal.h" + +static struct scsc_wlog_ring *the_ring; + +u32 cring_lev; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS +#include "scsc_wifilogger_debugfs.h" + +static struct scsc_wlog_debugfs_info di; + +#endif /* CONFIG_SCSC_WIFILOGGER_DEBUGFS */ + +bool scsc_wifilogger_ring_connectivity_init(void) +{ + struct scsc_wlog_ring *r = NULL; +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + struct scsc_ring_test_object *rto; +#endif + + r = scsc_wlog_ring_create(WLOGGER_RCONNECT_NAME, + RING_BUFFER_ENTRY_FLAGS_HAS_BINARY, + ENTRY_TYPE_CONNECT_EVENT, 32768 * 8, + WIFI_LOGGER_CONNECT_EVENT_SUPPORTED, + NULL, NULL, NULL); + + if (!r) { + SCSC_TAG_ERR(WLOG, "Failed to CREATE WiFiLogger ring: %s\n", + WLOGGER_RCONNECT_NAME); + return false; + } + scsc_wlog_register_verbosity_reference(r, &cring_lev); + + if (!scsc_wifilogger_register_ring(r)) { + SCSC_TAG_ERR(WLOG, "Failed to REGISTER WiFiLogger ring: %s\n", + WLOGGER_RCONNECT_NAME); + scsc_wlog_ring_destroy(r); + return false; + } + the_ring = r; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + rto = init_ring_test_object(the_ring); + if (rto) + scsc_register_common_debugfs_entries(the_ring->st.name, rto, &di); +#endif + + return true; +} + +/**** Producer API ******/ + +int scsc_wifilogger_ring_connectivity_fw_event(wlog_verbose_level lev, u16 fw_event_id, + u64 fw_timestamp, void *fw_bulk_data, size_t fw_blen) +{ + struct scsc_wifi_ring_buffer_driver_connectivity_event event_item; + + if (!the_ring) + return 0; + + SCSC_TAG_DEBUG(WLOG, "EL -- RX MLME_EVENT_LOG_INFO - event_id[%d] @0x%x\n", + fw_event_id, fw_timestamp); + event_item.event = fw_event_id; + + return scsc_wlog_write_record(the_ring, fw_bulk_data, fw_blen, &event_item, + sizeof(event_item), lev, fw_timestamp); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_connectivity_fw_event); + +int scsc_wifilogger_ring_connectivity_driver_event(wlog_verbose_level lev, + u16 driver_event_id, unsigned int tag_count, ...) +{ + int i; + u64 timestamp; + va_list ap; + u8 tlvs[MAX_TLVS_SZ]; + size_t tlvs_sz = 0; + struct scsc_tlv_log *tlv = NULL; + struct scsc_wifi_ring_buffer_driver_connectivity_event event_item; + + if (!the_ring) + return 0; + + timestamp = local_clock(); + + SCSC_TAG_DEBUG(WLOG, "EL -- RX Driver CONNECTIVITY EVENT - event_id[%d] @0x%x\n", + driver_event_id, timestamp); + + event_item.event = driver_event_id; + va_start(ap, tag_count); + for (i = 0; i < tag_count && + tlvs_sz + sizeof(*tlv) < MAX_TLVS_SZ; i++) { + tlv = (struct scsc_tlv_log *)(tlvs + tlvs_sz); + tlv->tag = (u16)va_arg(ap, int); + tlv->length = (u16)va_arg(ap, int); + if (tlvs_sz + sizeof(*tlv) + tlv->length >= MAX_TLVS_SZ) { + WARN(true, + "TLVs container too small [%d]....truncating event's tags !\n", + MAX_TLVS_SZ); + break; + } + memcpy(&tlv->value, va_arg(ap, u8 *), tlv->length); + tlvs_sz += sizeof(*tlv) + tlv->length; + } + va_end(ap); + + return scsc_wlog_write_record(the_ring, tlvs, tlvs_sz, &event_item, + sizeof(event_item), lev, timestamp); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_connectivity_driver_event); diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.h new file mode 100644 index 000000000000..06c3106ff39e --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_CONNECTIVITY_H__ +#define __SCSC_WIFILOGGER_RING_CONNECTIVITY_H__ + +#include +#include +#include +#include +#include + +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger.h" + +#define MAX_TLVS_SZ 1024 +#define WLOGGER_RCONNECT_NAME "connectivity" + +/** + * A local mirror for this ring's current verbose level: + * avoids func-call and minimizes impact when ring is disabled + */ +extern u32 cring_lev; + +bool scsc_wifilogger_ring_connectivity_init(void); + +int scsc_wifilogger_ring_connectivity_driver_event(wlog_verbose_level lev, + u16 driver_event_id, unsigned int tag_count, ...); + +int scsc_wifilogger_ring_connectivity_fw_event(wlog_verbose_level lev, u16 fw_event_id, + u64 fw_timestamp, void *fw_bulk_data, size_t fw_blen); + +#endif /* __SCSC_WIFILOGGER_RING_CONNECTIVITY_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity_api.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity_api.h new file mode 100644 index 000000000000..ca038ad3dd8f --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_connectivity_api.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_CONNECTIVITY_API_H__ +#define __SCSC_WIFILOGGER_RING_CONNECTIVITY_API_H__ +/** Android Enhanced Logging + * + * CONNECTIVITY RING -- Public Producer API + * + * This ring collects a number of events originated by FW and Driver; given the different + * format of the provided payload between FW and Driver the API is splitted in two classes: + * once to be invoked on fw events and the other on driver events. + */ + +#ifdef CONFIG_SCSC_WIFILOGGER + +#include "scsc_wifilogger_ring_connectivity.h" +/** + * DRIVER-Produced Connectivity Events + * + * @lev: chosen verbosity level + * @driver_event_id: id of the event reported by the driver + * @tag_count: number of TLV-TRIPLETS constituting the variadic portion of the call. + * Provided TLV triplets are composed as follows: + * - Types(Tag) are defined in scsc_wifilogger_types.h + * - Length in bytes of value + * - Value is a POINTER to the area holding the Length bytes to copy + * + * This function will take care to build the needed inner record-header and push the + * log material to the related ring, adding also a proper driver-built timestamp. + * + * An example invokation on Association Request received at driver: + * + * SCSC_WLOG_DRIVER_EVENT(WLOG_NORMAL, WIFI_EVENT_ASSOCIATION_REQUESTED, 3, + * WIFI_TAG_BSSID, ETH_ALEN, sme->bssid, + * WIFI_TAG_SSID, sme->ssid_len, sme->ssid, + * WIFI_TAG_CHANNEL, sizeof(u16), &sme->channel->center_freq); + * + * BE AWARE THAT tag_count parameeters expects the NUMBER of TRIPLETS + * NOT the number of variadic params. + */ +#define SCSC_WLOG_DRIVER_EVENT(lev, driver_event_id, tag_count, tag_args...) \ + do { \ + if (cring_lev && (lev) <= cring_lev) \ + scsc_wifilogger_ring_connectivity_driver_event((lev), (driver_event_id), \ + (tag_count), tag_args); \ + } while (0) + +/** + * FW-Produced Connectivity Events + * + * @lev: chosen verbosity level + * @fw_event_id: id of the event as provided in the field + * MLME-EVENT-LOG.indication[Event] + * @fw_timestamp: timestamp of the event as provided in the field + * MLME-EVENT-LOG.indication[TSF Time] + * @fw_bulk_data: the bulk data contained in the MLME signal. + * "The bulk data shall contain TLV encoded parameters for that event" + * @fw_blen: the length of the above bulk_data + * + * This function will take care to build the needed inner record-header and push the + * log material to the related ring. + */ +#define SCSC_WLOG_FW_EVENT(lev, fw_event_id, fw_timestamp, fw_bulk_data, fw_blen) \ + do { \ + if (cring_lev && (lev) <= cring_lev) \ + scsc_wifilogger_ring_connectivity_fw_event((lev), (fw_event_id), (fw_timestamp), \ + (fw_bulk_data), (fw_blen)); \ + } while (0) +#else + +#define SCSC_WLOG_DRIVER_EVENT(lev, driver_event_id, tag_count, tag_args...) do {} while (0) +#define SCSC_WLOG_FW_EVENT(lev, fw_event_id, fw_timestamp, fw_bulk_data, fw_blen) do {} while (0) + +#endif /* CONFIG_SCSC_WIFILOGGER */ + +#endif /* __SCSC_WIFILOGGER_RING_CONNECTIVITY_API_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.c b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.c new file mode 100644 index 000000000000..a133d39c62db --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.c @@ -0,0 +1,284 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + *****************************************************************************/ +/* Implements */ +#include "scsc_wifilogger_ring_pktfate.h" + +/* Uses */ +#include +#include "scsc_wifilogger_internal.h" + +static bool pktfate_monitor_started; + +static wifi_tx_report txr; +static wifi_rx_report rxr; +static struct scsc_wlog_ring *fate_ring_tx; +static struct scsc_wlog_ring *fate_ring_rx; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS +#include "scsc_wifilogger_debugfs.h" +#include "scsc_wifilogger.h" + +static struct scsc_wlog_debugfs_info di_tx, di_rx; + +#ifdef CONFIG_SCSC_WIFILOGGER_TEST +static ssize_t dfs_read_fates(struct file *filp, char __user *ubuf, + size_t count, loff_t *f_pos) +{ + size_t got_sz, n_provided_fates = 0, n_requested_fates; + struct scsc_ring_test_object *rto; + wifi_tx_report *tx_report_bufs = NULL; + wifi_rx_report *rx_report_bufs = NULL; + void *srcbuf = NULL; + + if (!filp->private_data) + return -EINVAL; + rto = filp->private_data; + + if (!strncmp(rto->r->st.name, WLOGGER_RFATE_TX_NAME, RING_NAME_SZ - 1)) { + n_requested_fates = count / sizeof(wifi_tx_report); + tx_report_bufs = vmalloc(sizeof(wifi_tx_report) * n_requested_fates); + if (tx_report_bufs) + scsc_wifi_get_tx_pkt_fates(tx_report_bufs, n_requested_fates, + &n_provided_fates); + got_sz = sizeof(wifi_tx_report) * n_provided_fates; + srcbuf = tx_report_bufs; + } else { + n_requested_fates = count / sizeof(wifi_rx_report); + rx_report_bufs = vmalloc(sizeof(wifi_rx_report) * n_requested_fates); + if (rx_report_bufs) + scsc_wifi_get_rx_pkt_fates(rx_report_bufs, n_requested_fates, + &n_provided_fates); + got_sz = sizeof(wifi_rx_report) * n_provided_fates; + srcbuf = rx_report_bufs; + } + SCSC_TAG_DEBUG(WLOG, "Ring '%s'...asked for %d fates....GOT %d\n", + rto->r->st.name, n_requested_fates, n_provided_fates); + if (copy_to_user(ubuf, srcbuf, got_sz)) + return -EFAULT; + *f_pos += got_sz; + + return got_sz; +} + +const struct file_operations get_fates_fops = { + .owner = THIS_MODULE, + .open = dfs_open, + .read = dfs_read_fates, + .release = dfs_release, +}; +#endif /* CONFIG_SCSC_WIFILOGGER_TEST */ + +#endif /* CONFIG_SCSC_WIFILOGGER_DEBUGFS */ + +bool is_pktfate_monitor_started(void) +{ + return pktfate_monitor_started; +} + +bool scsc_wifilogger_ring_pktfate_init(void) +{ + struct scsc_wlog_ring *r = NULL; +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + struct scsc_ring_test_object *rto_tx, *rto_rx; +#endif + + r = scsc_wlog_ring_create(WLOGGER_RFATE_TX_NAME, + RING_BUFFER_ENTRY_FLAGS_HAS_BINARY, + ENTRY_TYPE_PKT, 32768 * 4, + WIFI_LOGGER_PACKET_FATE_SUPPORTED, + NULL, NULL, NULL); + if (!r) { + SCSC_TAG_ERR(WLOG, "Failed to CREATE WiFiLogger ring: %s\n", + WLOGGER_RFATE_TX_NAME); + return false; + } + fate_ring_tx = r; + + r = scsc_wlog_ring_create(WLOGGER_RFATE_RX_NAME, + RING_BUFFER_ENTRY_FLAGS_HAS_BINARY, + ENTRY_TYPE_PKT, 32768 * 4, + WIFI_LOGGER_PACKET_FATE_SUPPORTED, + NULL, NULL, NULL); + if (!r) { + SCSC_TAG_ERR(WLOG, "Failed to CREATE WiFiLogger ring: %s\n", + WLOGGER_RFATE_RX_NAME); + scsc_wlog_ring_destroy(fate_ring_tx); + return false; + } + fate_ring_rx = r; + + if (!scsc_wifilogger_register_ring(fate_ring_tx)) { + SCSC_TAG_ERR(WLOG, "Failed to REGISTER WiFiLogger ring: %s\n", + fate_ring_tx->st.name); + scsc_wlog_ring_destroy(fate_ring_tx); + scsc_wlog_ring_destroy(fate_ring_rx); + return false; + } + if (!scsc_wifilogger_register_ring(fate_ring_rx)) { + SCSC_TAG_ERR(WLOG, "Failed to REGISTER WiFiLogger ring: %s\n", + fate_ring_rx->st.name); + scsc_wlog_ring_destroy(fate_ring_tx); + scsc_wlog_ring_destroy(fate_ring_rx); + return false; + } + + // Just in case framework invokes with min_data_size != 0 + scsc_wlog_ring_set_drop_on_full(fate_ring_tx); + scsc_wlog_ring_set_drop_on_full(fate_ring_rx); + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + /* The test object is shared between all the debugfs entries + * belonging to the same ring. + */ + rto_tx = init_ring_test_object(fate_ring_tx); + if (rto_tx) { + scsc_register_common_debugfs_entries(fate_ring_tx->st.name, rto_tx, &di_tx); +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + scsc_wlog_register_debugfs_entry(fate_ring_tx->st.name, "get_fates", + &get_fates_fops, rto_tx, &di_tx); +#endif + } + + rto_rx = init_ring_test_object(fate_ring_rx); + if (rto_rx) { + scsc_register_common_debugfs_entries(fate_ring_rx->st.name, rto_rx, &di_rx); +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + scsc_wlog_register_debugfs_entry(fate_ring_rx->st.name, "get_fates", + &get_fates_fops, rto_rx, &di_rx); +#endif + } +#endif + + return true; +} + +void scsc_wifilogger_ring_pktfate_start_monitoring(void) +{ + if (!fate_ring_rx || !fate_ring_tx) + return; + + /* Just in case */ + scsc_wlog_flush_ring(fate_ring_tx); + scsc_wlog_start_logging(fate_ring_tx, WLOG_DEBUG, 0, 0, 0); + scsc_wlog_flush_ring(fate_ring_rx); + scsc_wlog_start_logging(fate_ring_rx, WLOG_DEBUG, 0, 0, 0); + pktfate_monitor_started = true; + + SCSC_TAG_INFO(WLOG, "PacketFate monitor started.\n"); +} + +/**** Producer API ******/ +void scsc_wifilogger_ring_pktfate_new_assoc(void) +{ + SCSC_TAG_INFO(WLOG, "New Association started...flushing PacketFate rings.\n"); + scsc_wlog_flush_ring(fate_ring_tx); + scsc_wlog_flush_ring(fate_ring_rx); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_pktfate_new_assoc); + +void scsc_wifilogger_ring_pktfate_get_fates(int fate, void *report_bufs, + size_t n_requested_fates, + size_t *n_provided_fates) +{ + struct scsc_wlog_ring *r; + u32 n_req_fates = (u32)n_requested_fates; + size_t blen; + + r = (fate == TX_FATE) ? fate_ring_tx : fate_ring_rx; + if (fate == TX_FATE) + blen = sizeof(wifi_tx_report) * n_req_fates; + else + blen = sizeof(wifi_rx_report) * n_req_fates; + scsc_wlog_read_max_records(r, report_bufs, blen, &n_req_fates); + *n_provided_fates = n_req_fates; + + SCSC_TAG_INFO(WLOG, "[%s]:: GET %s pkt_fates -- Requested:%zd - Got:%zd\n", + r->st.name, + (fate == TX_FATE) ? "TX" : "RX", n_requested_fates, *n_provided_fates); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_pktfate_get_fates); + +/** + * Here we're Just saving the egressing ETH frame for now with the + * provided initial fate (which is fixed to TX_PKT_FATE_DRV_QUEUED). + * In a full pktfate implementation as required by WiFi-Logger we + * should track down this eth frame using the host_tag and then + * account for the final fate of the frame looking at Debugging + * Information Element provided in subsequent UnidataTx.confirm, BUT + * such confirm as of now does NOT provide any Debugging Element NOR + * any additional interesting information related to the packet fates + * defined. + */ +void scsc_wifilogger_ring_pktfate_log_tx_frame(wifi_tx_packet_fate fate, + u16 htag, void *frame, + size_t len, bool ma_unitdata) +{ + if (len > MAX_FRAME_LEN_ETHERNET) { + SCSC_TAG_WARNING(WLOG, "pktfate TX:: dropped unplausible length frame.\n"); + return; + } + + if (ma_unitdata) + len = len <= MAX_UNITDATA_LOGGED_SZ ? len : MAX_UNITDATA_LOGGED_SZ; + + txr.fate = fate; + txr.frame_inf.payload_type = FRAME_TYPE_ETHERNET_II; + txr.frame_inf.frame_len = len; + txr.frame_inf.driver_timestamp_usec = ktime_to_ns(ktime_get_boottime()); + txr.frame_inf.firmware_timestamp_usec = 0; + memcpy(&txr.frame_inf.frame_content, frame, len); + //TODO MD5 checksum using Kernel Crypto API + memset(&txr.md5_prefix, 0x00, MD5_PREFIX_LEN); + /** + * We have to waste a lot of space storing the frame in a full-sized + * frame_content array, even if the frame size is much smaller, because + * the wifi-logger API (get_tx/rx_pkt_fates) reports multiple of struct + * wifi_tx/rx_packet_fates and does NOT return the effectively read bytes. + * The real size (that we cannot use) being: + * + * real_txr_sz = sizeof(txr) - sizeof(txr.frame_inf.frame_content) + len; + * + * frame_len field is anyway provided to recognize the actual end of frame. + */ + scsc_wlog_write_record(fate_ring_tx, NULL, 0, &txr, sizeof(txr), WLOG_DEBUG, 0); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_pktfate_log_tx_frame); + +void scsc_wifilogger_ring_pktfate_log_rx_frame(wifi_rx_packet_fate fate, u16 du_desc, + void *frame, size_t len, bool ma_unitdata) +{ + if ((du_desc == SCSC_DUD_ETHERNET_FRAME && len > MAX_FRAME_LEN_ETHERNET) || + (du_desc == SCSC_DUD_80211_FRAME && len > MAX_FRAME_LEN_80211_MGMT)) { + SCSC_TAG_WARNING(WLOG, "pktfate RX:: dropped unplausible length frame.\n"); + return; + } + + if (ma_unitdata) + len = len <= MAX_UNITDATA_LOGGED_SZ ? len : MAX_UNITDATA_LOGGED_SZ; + + rxr.fate = fate; + rxr.frame_inf.payload_type = du_desc == SCSC_DUD_ETHERNET_FRAME ? FRAME_TYPE_ETHERNET_II : + (du_desc == SCSC_DUD_80211_FRAME ? FRAME_TYPE_80211_MGMT : FRAME_TYPE_UNKNOWN); + rxr.frame_inf.frame_len = len; + rxr.frame_inf.driver_timestamp_usec = ktime_to_ns(ktime_get_boottime()); + rxr.frame_inf.firmware_timestamp_usec = 0; + memcpy(&rxr.frame_inf.frame_content, frame, len); + //TODO MD5 checksum using Kernel Crypto API + memset(&rxr.md5_prefix, 0x00, MD5_PREFIX_LEN); + /** + * We have to waste a lot of space storing the frame in a full-sized + * frame_content array, even if the frame size is much smaller, because + * the wifi-logger API (get_tx/rx_pkt_fates) reports multiple of struct + * wifi_tx/rx_packet_fates and does NOT return the effectively read bytes. + * The real size (that we cannot use) being: + * + * real_txr_sz = sizeof(txr) - sizeof(txr.frame_inf.frame_content) + len; + * + * frame_len field is anyway provided to recognize the actual end of frame. + */ + scsc_wlog_write_record(fate_ring_rx, NULL, 0, &rxr, sizeof(rxr), WLOG_DEBUG, 0); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_pktfate_log_rx_frame); diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.h new file mode 100644 index 000000000000..dfea593d6b5a --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_PKTFATE_H__ +#define __SCSC_WIFILOGGER_RING_PKTFATE_H__ + +#include +#include +#include +#include +#include + +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger.h" + +#define TX_FATE 0 +#define RX_FATE 1 +#define WLOGGER_RFATE_TX_NAME "pkt_fate_tx" +#define WLOGGER_RFATE_RX_NAME "pkt_fate_rx" +#define MAX_UNITDATA_LOGGED_SZ 100 + +#define SCSC_DUD_ETHERNET_FRAME 0 +#define SCSC_DUD_80211_FRAME 1 +#define SCSC_DUD_MLME 2 + +bool is_pktfate_monitor_started(void); +bool scsc_wifilogger_ring_pktfate_init(void); +void scsc_wifilogger_ring_pktfate_start_monitoring(void); +void scsc_wifilogger_ring_pktfate_new_assoc(void); +void scsc_wifilogger_ring_pktfate_log_tx_frame(wifi_tx_packet_fate fate, + u16 htag, void *pkt, + size_t len, bool ma_unitdata); +void scsc_wifilogger_ring_pktfate_log_rx_frame(wifi_rx_packet_fate fate, u16 du_desc, + void *frame, size_t len, bool ma_unitdata); +void scsc_wifilogger_ring_pktfate_get_fates(int fate, void *report_bufs, + size_t n_requested_fates, + size_t *n_provided_fates); +#endif /* __SCSC_WIFILOGGER_RING_PKTFATE_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate_api.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate_api.h new file mode 100644 index 000000000000..dcfe4911b581 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_pktfate_api.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_PKTFATE_API_H__ +#define __SCSC_WIFILOGGER_RING_PKTFATE_API_H__ + +/** Android Enhanced Logging + * + * PKTFATE RING -- Public Producer API + * + */ + +#ifdef CONFIG_SCSC_WIFILOGGER +#include "scsc_wifilogger_ring_pktfate.h" + +#define SCSC_WLOG_PKTFATE_NEW_ASSOC() \ + do { \ + if (is_pktfate_monitor_started()) \ + scsc_wifilogger_ring_pktfate_new_assoc(); \ + } while (0) + +#define SCSC_WLOG_PKTFATE_LOG_TX_DATA_FRAME(htag, frame, flen) \ + do { \ + if (is_pktfate_monitor_started()) \ + scsc_wifilogger_ring_pktfate_log_tx_frame(TX_PKT_FATE_DRV_QUEUED, (htag), \ + (void *)(frame), (flen), true); \ + } while (0) + +#define SCSC_WLOG_PKTFATE_LOG_RX_DATA_FRAME(du_desc, frame, flen) \ + do { \ + if (is_pktfate_monitor_started() && \ + ((du_desc) == SCSC_DUD_ETHERNET_FRAME || (du_desc) == SCSC_DUD_80211_FRAME)) \ + scsc_wifilogger_ring_pktfate_log_rx_frame(RX_PKT_FATE_DRV_QUEUED, (du_desc), \ + (void *)(frame), (flen), true); \ + } while (0) + +#define SCSC_WLOG_PKTFATE_LOG_TX_CTRL_FRAME(htag, frame, flen) \ + do { \ + if (is_pktfate_monitor_started()) \ + scsc_wifilogger_ring_pktfate_log_tx_frame(TX_PKT_FATE_DRV_QUEUED, (htag), \ + (void *)(frame), (flen), false); \ + } while (0) + +#define SCSC_WLOG_PKTFATE_LOG_RX_CTRL_FRAME(frame, flen) \ + do { \ + if (is_pktfate_monitor_started()) \ + scsc_wifilogger_ring_pktfate_log_rx_frame(RX_PKT_FATE_DRV_QUEUED, SCSC_DUD_MLME, \ + (void *)(frame), (flen), false); \ + } while (0) + +#else + +#define SCSC_WLOG_PKTFATE_NEW_ASSOC() do {} while (0) +#define SCSC_WLOG_PKTFATE_LOG_TX_DATA_FRAME(htag, skb_mac, skb_hlen) do {} while (0) +#define SCSC_WLOG_PKTFATE_LOG_TX_CTRL_FRAME(htag, skb_mac, skb_hlen) do {} while (0) +#define SCSC_WLOG_PKTFATE_LOG_RX_DATA_FRAME(du_desc, skb_mac, skb_hlen) do {} while (0) +#define SCSC_WLOG_PKTFATE_LOG_RX_CTRL_FRAME(frame, flen) do {} while (0) + +#endif /* CONFIG_SCSC_WIFILOGGER */ + +#endif /* __SCSC_WIFILOGGER_RING_PKTFATE_API_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.c b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.c new file mode 100644 index 000000000000..818b16de7510 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.c @@ -0,0 +1,135 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + *****************************************************************************/ +/* Implements */ +#include "scsc_wifilogger_ring_test.h" + +/* Uses */ +#include "scsc_wifilogger_internal.h" + +static u32 seq; +static struct scsc_wlog_ring *the_ring; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS +#include "scsc_wifilogger_debugfs.h" + +static struct scsc_wlog_debugfs_info di; +#endif /* CONFIG_SCSC_WIFILOGGER_DEBUGFS */ + +static int ring_test_loglevel_change_cb(struct scsc_wlog_ring *r, + u32 new_loglevel) +{ + SCSC_TAG_DEBUG(WLOG, + "==>> TEST RING SETTING CUSTOM LogLevel for ring: %s -- to:%d\n", + r->st.name, new_loglevel); + + return 0; +} + +bool ring_test_custom_init(struct scsc_wlog_ring *r) +{ + SCSC_TAG_DEBUG(WLOG, "Custom init for ring:%s\n", r->st.name); + + return true; +} + +bool ring_test_custom_fini(struct scsc_wlog_ring *r) +{ + SCSC_TAG_DEBUG(WLOG, "Custom fini for ring:%s\n", r->st.name); + + return true; +} + +/* Producer API */ +int scsc_wifilogger_ring_test_write(char *buf, size_t blen) +{ + struct tring_hdr thdr; + + if (!the_ring) + return 0; + + thdr.seq = seq++; + thdr.fake = 666 & seq; + + return scsc_wlog_write_record(the_ring, buf, blen, &thdr, sizeof(thdr), WLOG_NORMAL, 0); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_test_write); + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS +static ssize_t dfs_record_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *f_pos) +{ + ssize_t written; + struct scsc_ring_test_object *rto; + + if (!filp->private_data) + return -EINVAL; + rto = filp->private_data; + + count = count < rto->bsz ? count : rto->bsz; + if (copy_from_user(rto->wbuf, ubuf, count)) + return -EINVAL; + written = scsc_wifilogger_ring_test_write(rto->wbuf, count); + if (!written && count) + return -EAGAIN; + wake_up_interruptible(&rto->rw_wq); + *f_pos += written; + + return written; +} + +const struct file_operations write_record_fops = { + .owner = THIS_MODULE, + .open = dfs_open, + .write = dfs_record_write, + .release = dfs_release, +}; +#endif + +bool scsc_wifilogger_ring_test_init(void) +{ + struct scsc_wlog_ring *r = NULL; +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + struct scsc_ring_test_object *rto; +#endif + + r = scsc_wlog_ring_create(WLOGGER_RTEST_NAME, + RING_BUFFER_ENTRY_FLAGS_HAS_BINARY, + ENTRY_TYPE_DATA, 32768, + WIFI_LOGGER_SCSC_TEST_RING_SUPPORTED, + ring_test_custom_init, ring_test_custom_fini, + NULL); + + if (!r) { + SCSC_TAG_ERR(WLOG, "Failed to CREATE WiFiLogger ring: %s\n", + WLOGGER_RTEST_NAME); + return false; + } + /* Registering custom loglevel change callback */ + scsc_wlog_register_loglevel_change_cb(r, ring_test_loglevel_change_cb); + + if (!scsc_wifilogger_register_ring(r)) { + SCSC_TAG_ERR(WLOG, "Failed to REGISTER WiFiLogger ring: %s\n", + WLOGGER_RTEST_NAME); + scsc_wlog_ring_destroy(r); + return false; + } + the_ring = r; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + /* This test object is shared between all the debugfs entries + * belonging to this ring. + */ + rto = init_ring_test_object(the_ring); + if (rto) { + scsc_register_common_debugfs_entries(the_ring->st.name, rto, &di); + /* A write is specific to the ring...*/ + scsc_wlog_register_debugfs_entry(the_ring->st.name, "write_record", + &write_record_fops, rto, &di); + } +#endif + + return true; +} diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.h new file mode 100644 index 000000000000..2020d846e28e --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_test.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_TEST_H__ +#define __SCSC_WIFILOGGER_RING_TEST_H__ + +#include +#include +#include +#include +#include + +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger.h" + +#define WLOGGER_RTEST_NAME "test" + +struct tring_hdr { + u32 seq; + u64 fake; +} __packed; + +bool scsc_wifilogger_ring_test_init(void); + +int scsc_wifilogger_ring_test_write(char *buf, size_t blen); +#endif /* __SCSC_WIFILOGGER_RING_TEST_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.c b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.c new file mode 100644 index 000000000000..919314f19408 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.c @@ -0,0 +1,81 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + *****************************************************************************/ +/* Implements */ +#include "scsc_wifilogger_ring_wakelock.h" + +/* Uses */ +#include "scsc_wifilogger_ring_wakelock_api.h" +#include "scsc_wifilogger_internal.h" + +static struct scsc_wlog_ring *the_ring; +u32 wring_lev; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS +#include "scsc_wifilogger_debugfs.h" + +static struct scsc_wlog_debugfs_info di; + +#endif /* CONFIG_SCSC_WIFILOGGER_DEBUGFS */ + +bool scsc_wifilogger_ring_wakelock_init(void) +{ + struct scsc_wlog_ring *r = NULL; +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + struct scsc_ring_test_object *rto = NULL; +#endif + + r = scsc_wlog_ring_create(WLOGGER_RWAKELOCK_NAME, + RING_BUFFER_ENTRY_FLAGS_HAS_BINARY, + ENTRY_TYPE_WAKE_LOCK, 65536, + WIFI_LOGGER_WAKE_LOCK_SUPPORTED, + NULL, NULL, NULL); + + if (!r) { + SCSC_TAG_ERR(WLOG, "Failed to CREATE WiFiLogger ring: %s\n", + WLOGGER_RWAKELOCK_NAME); + return false; + } + scsc_wlog_register_verbosity_reference(r, &wring_lev); + + if (!scsc_wifilogger_register_ring(r)) { + SCSC_TAG_ERR(WLOG, "Failed to REGISTER WiFiLogger ring: %s\n", + WLOGGER_RWAKELOCK_NAME); + scsc_wlog_ring_destroy(r); + return false; + } + the_ring = r; + +#ifdef CONFIG_SCSC_WIFILOGGER_DEBUGFS + rto = init_ring_test_object(the_ring); + if (rto) + scsc_register_common_debugfs_entries(the_ring->st.name, rto, &di); +#endif + + return true; +} + +/**** Producer API ******/ + +int scsc_wifilogger_ring_wakelock_action(u32 verbose_level, int status, + char *wl_name, int reason) +{ + u64 timestamp; + struct scsc_wake_lock_event wl_event; + + if (!the_ring) + return 0; + + timestamp = local_clock(); + SCSC_TAG_DBG4(WLOG, "EL -- WAKELOCK[%s] - status:%d reason:%d @0x%x\n", + wl_name, status, reason, timestamp); + + wl_event.status = status; + wl_event.reason = reason; + return scsc_wlog_write_record(the_ring, wl_name, strlen(wl_name), &wl_event, + sizeof(wl_event), verbose_level, timestamp); +} +EXPORT_SYMBOL(scsc_wifilogger_ring_wakelock_action); + diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.h new file mode 100644 index 000000000000..fef9a87c0179 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_WAKELOCK_H__ +#define __SCSC_WIFILOGGER_RING_WAKELOCK_H__ + +#include +#include + +#include "scsc_wifilogger_core.h" +#include "scsc_wifilogger.h" + +#define WLOGGER_RWAKELOCK_NAME "wakelock" + +extern u32 wring_lev; + +int scsc_wifilogger_ring_wakelock_action(u32 verbose_level, int status, + char *wl_name, int reason); + + +bool scsc_wifilogger_ring_wakelock_init(void); +#endif /* __SCSC_WIFILOGGER_RING_WAKELOCK_H__ */ + diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock_api.h b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock_api.h new file mode 100644 index 000000000000..d4b38ea63e01 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_ring_wakelock_api.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RING_WAKELOCK_API_H__ +#define __SCSC_WIFILOGGER_RING_WAKELOCK_API_H__ + +/** + * Android Enhanced Logging + * + * WAKELOCK EVENTS RING -- Public Producer API + * + */ +enum { + WL_TAKEN = 0, + WL_RELEASED +}; + +enum { + WL_REASON_TX = 0, + WL_REASON_RX, + WL_REASON_ROAM +}; + +#ifdef CONFIG_SCSC_WIFILOGGER + +#include "scsc_wifilogger_ring_wakelock.h" + +#define SCSC_WLOG_WAKELOCK(lev, status, wl_name, reason) \ + do { \ + if (wring_lev && (lev) <= wring_lev) \ + scsc_wifilogger_ring_wakelock_action((lev), (status), (wl_name), (reason)); \ + } while (0) + +#else + +#define SCSC_WLOG_WAKELOCK(lev, status, wl_name, reason) do {} while (0) + +#endif + +#endif /* __SCSC_WIFILOGGER_RING_WAKELOCK_API_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_rings.h b/drivers/misc/samsung/scsc/scsc_wifilogger_rings.h new file mode 100644 index 000000000000..c3792a5e9abb --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_rings.h @@ -0,0 +1,15 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef __SCSC_WIFILOGGER_RINGS_H__ +#define __SCSC_WIFILOGGER_RINGS_H__ + +#include "scsc_wifilogger_types.h" +/* This file groups and exports all rings Producer APIs */ +#include "scsc_wifilogger_ring_connectivity_api.h" +#include "scsc_wifilogger_ring_wakelock_api.h" +#include "scsc_wifilogger_ring_pktfate_api.h" + +#endif /* __SCSC_WIFILOGGER_RINGS_H__ */ diff --git a/drivers/misc/samsung/scsc/scsc_wifilogger_types.h b/drivers/misc/samsung/scsc/scsc_wifilogger_types.h new file mode 100755 index 000000000000..20142a052b78 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wifilogger_types.h @@ -0,0 +1,435 @@ +/****************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + ******************************************************************************/ +#ifndef _SCSC_WIFILOGGER_TYPES_H_ +#define _SCSC_WIFILOGGER_TYPES_H_ +/** + * These types are derived from definitions in: + * + * hardware/libhardware_legacy/include/hardware_legacy/wifi_logger.h + * hardware/libhardware_legacy/include/hardware_legacy/wifi_hal.h + * + * Descriptive comments are in wifi_logger.h original file. + * Here we avoided using typedef that are in contrast with Kernel + * coding style though. + */ +#include + +#define LOGGER_MAJOR_VERSION 1 +#define LOGGER_MINOR_VERSION 0 +#define LOGGER_MICRO_VERSION 0 + +/** + * Be aware that there can be multiple distinct rings, registered + * with different names but supporting the same feature: an example + * being pkt_fate_tx and pkt_fate_rx. Rings are registered by ring_id. + */ +#ifndef CONFIG_SCSC_WIFILOGGER_TEST_RING +#define MAX_WIFI_LOGGER_RINGS 10 +#else +#define MAX_WIFI_LOGGER_RINGS 11 +#endif + +typedef enum { + WIFI_SUCCESS = 0, + WIFI_ERROR_NONE = 0, + WIFI_ERROR_UNKNOWN = -1, + WIFI_ERROR_UNINITIALIZED = -2, + WIFI_ERROR_NOT_SUPPORTED = -3, + WIFI_ERROR_NOT_AVAILABLE = -4, /* Not available right now, but try later */ + WIFI_ERROR_INVALID_ARGS = -5, + WIFI_ERROR_INVALID_REQUEST_ID = -6, + WIFI_ERROR_TIMED_OUT = -7, + WIFI_ERROR_TOO_MANY_REQUESTS = -8, /* Too many instances of this request */ + WIFI_ERROR_OUT_OF_MEMORY = -9, + WIFI_ERROR_BUSY = -10, +} wifi_error; + +/* Verbosity */ +typedef enum { + WLOG_NONE = 0, + WLOG_NORMAL = 1, + WLOG_LAZY = 2, + WLOG_DEBUG = 3, +} wlog_verbose_level; + +/* Feature set */ +enum { + WIFI_LOGGER_MEMORY_DUMP_SUPPORTED = (1 << (0)), + WIFI_LOGGER_PER_PACKET_TX_RX_STATUS_SUPPORTED = (1 << (1)), + WIFI_LOGGER_CONNECT_EVENT_SUPPORTED = (1 << (2)), + WIFI_LOGGER_POWER_EVENT_SUPPORTED = (1 << (3)), + WIFI_LOGGER_WAKE_LOCK_SUPPORTED = (1 << (4)), + WIFI_LOGGER_VERBOSE_SUPPORTED = (1 << (5)), + WIFI_LOGGER_WATCHDOG_TIMER_SUPPORTED = (1 << (6)), + WIFI_LOGGER_DRIVER_DUMP_SUPPORTED = (1 << (7)), + WIFI_LOGGER_PACKET_FATE_SUPPORTED = (1 << (8)), +#ifdef CONFIG_SCSC_WIFILOGGER_TEST + WIFI_LOGGER_SCSC_TEST_RING_SUPPORTED = (1 << (9)), +#endif +}; + +enum { + RING_BUFFER_ENTRY_FLAGS_HAS_BINARY = (1 << (0)), + RING_BUFFER_ENTRY_FLAGS_HAS_TIMESTAMP = (1 << (1)) +}; + +#define RING_NAME_SZ 32 + +struct scsc_wifi_ring_buffer_status { + u8 name[RING_NAME_SZ]; + u32 flags; + int ring_id; /* unique integer representing the ring */ + u32 rb_byte_size; /* total memory size allocated for the buffer */ + u32 verbose_level; /* verbose level for ring buffer */ + u32 written_bytes; /* number of bytes that was written to the + * buffer by driver, monotonously increasing + * integer + */ + u32 read_bytes; /* number of bytes that was read from the buffer + * by user land, monotonously increasing integer + */ + u32 written_records; /* number of records that was written to the + * buffer by driver, monotonously increasing + * integer + */ +}; + +typedef void (*on_ring_buffer_data)(char *ring_name, char *buffer, int buffer_size, + struct scsc_wifi_ring_buffer_status *status, void *ctx); +typedef void (*on_alert)(char *buffer, int buffer_size, int err_code, void *ctx); + +enum { + ENTRY_TYPE_CONNECT_EVENT = 1, + ENTRY_TYPE_PKT, + ENTRY_TYPE_WAKE_LOCK, + ENTRY_TYPE_POWER_EVENT, + ENTRY_TYPE_DATA +}; + +struct scsc_wifi_ring_buffer_entry { + u16 entry_size; /* the size of payload excluding the header. */ + u8 flags; + u8 type; + u64 timestamp; /* present if has_timestamp bit is set. */ +} __packed; + +/* set if binary entries are present */ +#define WIFI_RING_BUFFER_FLAG_HAS_BINARY_ENTRIES 0x00000001 +/* set if ascii entries are present */ +#define WIFI_RING_BUFFER_FLAG_HAS_ASCII_ENTRIES 0x00000002 + +/* Below events refer to the wifi_connectivity_event ring and shall be supported */ +#define WIFI_EVENT_ASSOCIATION_REQUESTED 0 // driver receives association command from kernel +#define WIFI_EVENT_AUTH_COMPLETE 1 +#define WIFI_EVENT_ASSOC_COMPLETE 2 +#define WIFI_EVENT_FW_AUTH_STARTED 3 // fw event indicating auth frames are sent +#define WIFI_EVENT_FW_ASSOC_STARTED 4 // fw event indicating assoc frames are sent +#define WIFI_EVENT_FW_RE_ASSOC_STARTED 5 // fw event indicating reassoc frames are sent +#define WIFI_EVENT_DRIVER_SCAN_REQUESTED 6 +#define WIFI_EVENT_DRIVER_SCAN_RESULT_FOUND 7 +#define WIFI_EVENT_DRIVER_SCAN_COMPLETE 8 +#define WIFI_EVENT_G_SCAN_STARTED 9 +#define WIFI_EVENT_G_SCAN_COMPLETE 10 +#define WIFI_EVENT_DISASSOCIATION_REQUESTED 11 +#define WIFI_EVENT_RE_ASSOCIATION_REQUESTED 12 +#define WIFI_EVENT_ROAM_REQUESTED 13 +#define WIFI_EVENT_BEACON_RECEIVED 14 // received beacon from AP (event enabled + // only in verbose mode) +#define WIFI_EVENT_ROAM_SCAN_STARTED 15 // firmware has triggered a roam scan (not g-scan) +#define WIFI_EVENT_ROAM_SCAN_COMPLETE 16 // firmware has completed a roam scan (not g-scan) +#define WIFI_EVENT_ROAM_SEARCH_STARTED 17 // firmware has started searching for roam + // candidates (with reason =xx) +#define WIFI_EVENT_ROAM_SEARCH_STOPPED 18 // firmware has stopped searching for roam +// candidates (with reason =xx) +#define WIFI_EVENT_CHANNEL_SWITCH_ANOUNCEMENT 20 // received channel switch anouncement from AP +#define WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_START 21 // fw start transmit eapol frame, with + // EAPOL index 1-4 +#define WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_STOP 22 // fw gives up eapol frame, with rate, + // success/failure and number retries +#define WIFI_EVENT_DRIVER_EAPOL_FRAME_TRANSMIT_REQUESTED 23 // kernel queue EAPOL for transmission + // in driver with EAPOL index 1-4 +#define WIFI_EVENT_FW_EAPOL_FRAME_RECEIVED 24 // with rate, regardless of the fact that + // EAPOL frame is accepted or rejected by fw +#define WIFI_EVENT_DRIVER_EAPOL_FRAME_RECEIVED 26 // with rate, and eapol index, driver has + // received EAPOL frame and will queue it up + // to wpa_supplicant +#define WIFI_EVENT_BLOCK_ACK_NEGOTIATION_COMPLETE 27 // with success/failure, parameters +#define WIFI_EVENT_BT_COEX_BT_SCO_START 28 +#define WIFI_EVENT_BT_COEX_BT_SCO_STOP 29 +#define WIFI_EVENT_BT_COEX_BT_SCAN_START 30 // for paging/scan etc., when BT starts transmiting + // twice per BT slot +#define WIFI_EVENT_BT_COEX_BT_SCAN_STOP 31 +#define WIFI_EVENT_BT_COEX_BT_HID_START 32 +#define WIFI_EVENT_BT_COEX_BT_HID_STOP 33 +#define WIFI_EVENT_ROAM_AUTH_STARTED 34 // fw sends auth frame in roaming to next candidate +#define WIFI_EVENT_ROAM_AUTH_COMPLETE 35 // fw receive auth confirm from ap +#define WIFI_EVENT_ROAM_ASSOC_STARTED 36 // firmware sends assoc/reassoc frame in + // roaming to next candidate +#define WIFI_EVENT_ROAM_ASSOC_COMPLETE 37 // firmware receive assoc/reassoc confirm from ap +#define WIFI_EVENT_G_SCAN_STOP 38 // firmware sends stop G_SCAN +#define WIFI_EVENT_G_SCAN_CYCLE_STARTED 39 // firmware indicates G_SCAN scan cycle started +#define WIFI_EVENT_G_SCAN_CYCLE_COMPLETED 40 // firmware indicates G_SCAN scan cycle completed +#define WIFI_EVENT_G_SCAN_BUCKET_STARTED 41 // firmware indicates G_SCAN scan start + // for a particular bucket +#define WIFI_EVENT_G_SCAN_BUCKET_COMPLETED 42 // firmware indicates G_SCAN scan completed for + // for a particular bucket +#define WIFI_EVENT_G_SCAN_RESULTS_AVAILABLE 43 // Event received from firmware about G_SCAN scan +// results being available +#define WIFI_EVENT_G_SCAN_CAPABILITIES 44 // Event received from firmware with G_SCAN + // capabilities +#define WIFI_EVENT_ROAM_CANDIDATE_FOUND 45 // Event received from firmware when eligible + // candidate is found +#define WIFI_EVENT_ROAM_SCAN_CONFIG 46 // Event received from firmware when roam scan + // configuration gets enabled or disabled +#define WIFI_EVENT_AUTH_TIMEOUT 47 // firmware/driver timed out authentication +#define WIFI_EVENT_ASSOC_TIMEOUT 48 // firmware/driver timed out association +#define WIFI_EVENT_MEM_ALLOC_FAILURE 49 // firmware/driver encountered allocation failure +#define WIFI_EVENT_DRIVER_PNO_ADD 50 // driver added a PNO network in firmware +#define WIFI_EVENT_DRIVER_PNO_REMOVE 51 // driver removed a PNO network in firmware +#define WIFI_EVENT_DRIVER_PNO_NETWORK_FOUND 52 // driver received PNO networks + // found indication from firmware +#define WIFI_EVENT_DRIVER_PNO_SCAN_REQUESTED 53 // driver triggered a scan for PNO networks +#define WIFI_EVENT_DRIVER_PNO_SCAN_RESULT_FOUND 54 // driver received scan results + // of PNO networks +#define WIFI_EVENT_DRIVER_PNO_SCAN_COMPLETE 55 // driver updated scan results from + // PNO networks to cfg80211 + +/** + * Parameters of wifi logger events are TLVs + * Event parameters tags are defined as: + */ +#define WIFI_TAG_VENDOR_SPECIFIC 0 // take a byte stream as parameter +#define WIFI_TAG_BSSID 1 // takes a 6 bytes MAC address as parameter +#define WIFI_TAG_ADDR 2 // takes a 6 bytes MAC address as parameter +#define WIFI_TAG_SSID 3 // takes a 32 bytes SSID address as parameter +#define WIFI_TAG_STATUS 4 // takes an integer as parameter +#define WIFI_TAG_CHANNEL_SPEC 5 // takes one or more wifi_channel_spec as parameter +#define WIFI_TAG_WAKE_LOCK_EVENT 6 // takes a wake_lock_event struct as parameter +#define WIFI_TAG_ADDR1 7 // takes a 6 bytes MAC address as parameter +#define WIFI_TAG_ADDR2 8 // takes a 6 bytes MAC address as parameter +#define WIFI_TAG_ADDR3 9 // takes a 6 bytes MAC address as parameter +#define WIFI_TAG_ADDR4 10 // takes a 6 bytes MAC address as parameter +#define WIFI_TAG_TSF 11 // take a 64 bits TSF value as parameter +#define WIFI_TAG_IE 12 // take one or more specific 802.11 IEs parameter, + // IEs are in turn indicated in TLV format as per + // 802.11 spec +#define WIFI_TAG_INTERFACE 13 // take interface name as parameter +#define WIFI_TAG_REASON_CODE 14 // take a reason code as per 802.11 as parameter +#define WIFI_TAG_RATE_MBPS 15 // take a wifi rate in 0.5 mbps +#define WIFI_TAG_REQUEST_ID 16 // take an integer as parameter +#define WIFI_TAG_BUCKET_ID 17 // take an integer as parameter +#define WIFI_TAG_GSCAN_PARAMS 18 // takes a wifi_scan_cmd_params struct as parameter +#define WIFI_TAG_GSCAN_CAPABILITIES 19 // takes a wifi_gscan_capabilities struct as parameter +#define WIFI_TAG_SCAN_ID 20 // take an integer as parameter +#define WIFI_TAG_RSSI 21 // take an integer as parameter +#define WIFI_TAG_CHANNEL 22 // take an integer as parameter +#define WIFI_TAG_LINK_ID 23 // take an integer as parameter +#define WIFI_TAG_LINK_ROLE 24 // take an integer as parameter +#define WIFI_TAG_LINK_STATE 25 // take an integer as parameter +#define WIFI_TAG_LINK_TYPE 26 // take an integer as parameter +#define WIFI_TAG_TSCO 27 // take an integer as parameter +#define WIFI_TAG_RSCO 28 // take an integer as parameter +#define WIFI_TAG_EAPOL_MESSAGE_TYPE 29 // take an integer as parameter + // M1-1, M2-2, M3-3, M4-4 + +struct scsc_tlv_log { + u16 tag; + u16 length; // length of value + u8 value[0]; +} __packed; + +struct scsc_wifi_ring_buffer_driver_connectivity_event { + u16 event; + struct scsc_tlv_log tlvs[0]; +} __packed; + +/** + * Ring buffer name for power events ring. note that power event are extremely frequents + * and thus should be stored in their own ring/file so as not to clobber connectivity events. + */ +struct scsc_wake_lock_event { + int status; // 0 taken, 1 released + int reason; // reason why this wake lock is taken + char name[0]; // null terminated +} __packed; + +struct scsc_wifi_power_event { + u16 event; + struct scsc_tlv_log tlvs[0]; +} __packed; + +#define PER_PACKET_ENTRY_FLAGS_DIRECTION_TX 1 /* 0: TX, 1: RX */ +#define PER_PACKET_ENTRY_FLAGS_TX_SUCCESS 2 /* pkt TX or RX/decrypt successfully */ +#define PER_PACKET_ENTRY_FLAGS_80211_HEADER 4 /* full 802.11 header or 802.3 header */ +#define PER_PACKET_ENTRY_FLAGS_PROTECTED 8 /* whether packet was encrypted */ + +struct scsc_wifi_ring_per_packet_status_entry { + u8 flags; + u8 tid; + u16 MCS; + u8 rssi; + u8 num_retries; + u16 last_transmit_rate; + u16 link_layer_transmit_sequence; + u64 firmware_entry_timestamp; + u64 start_contention_timestamp; + u64 transmit_success_timestamp; + u8 data[0]; +} __packed; + +typedef void (*on_driver_memory_dump)(char *buffer, int buffer_size, void *ctx); +typedef void (*on_firmware_memory_dump)(char *buffer, int buffer_size, void *ctx); + +/* packet fate logs */ + +#define MD5_PREFIX_LEN 4 +#define MAX_FATE_LOG_LEN 32 +#define MAX_FRAME_LEN_ETHERNET 1518 +#define MAX_FRAME_LEN_80211_MGMT 2352 // 802.11-2012 Fig. 8-34 + +typedef enum { + // Sent over air and ACKed. + TX_PKT_FATE_ACKED, + + // Sent over air but not ACKed. (Normal for broadcast/multicast.) + TX_PKT_FATE_SENT, + + // Queued within firmware, but not yet sent over air. + TX_PKT_FATE_FW_QUEUED, + + // Dropped by firmware as invalid. E.g. bad source address, bad checksum, + // or invalid for current state. + TX_PKT_FATE_FW_DROP_INVALID, + + // Dropped by firmware due to lack of buffer space. + TX_PKT_FATE_FW_DROP_NOBUFS, + + // Dropped by firmware for any other reason. Includes frames that + // were sent by driver to firmware, but unaccounted for by + // firmware. + TX_PKT_FATE_FW_DROP_OTHER, + + // Queued within driver, not yet sent to firmware. + TX_PKT_FATE_DRV_QUEUED, + + // Dropped by driver as invalid. E.g. bad source address, or + // invalid for current state. + TX_PKT_FATE_DRV_DROP_INVALID, + + // Dropped by driver due to lack of buffer space. + TX_PKT_FATE_DRV_DROP_NOBUFS, + + // Dropped by driver for any other reason. + TX_PKT_FATE_DRV_DROP_OTHER, +} wifi_tx_packet_fate; + +typedef enum { + // Valid and delivered to network stack (e.g., netif_rx()). + RX_PKT_FATE_SUCCESS, + + // Queued within firmware, but not yet sent to driver. + RX_PKT_FATE_FW_QUEUED, + + // Dropped by firmware due to host-programmable filters. + RX_PKT_FATE_FW_DROP_FILTER, + + // Dropped by firmware as invalid. E.g. bad checksum, decrypt failed, + // or invalid for current state. + RX_PKT_FATE_FW_DROP_INVALID, + + // Dropped by firmware due to lack of buffer space. + RX_PKT_FATE_FW_DROP_NOBUFS, + + // Dropped by firmware for any other reason. + RX_PKT_FATE_FW_DROP_OTHER, + + // Queued within driver, not yet delivered to network stack. + RX_PKT_FATE_DRV_QUEUED, + + // Dropped by driver due to filter rules. + RX_PKT_FATE_DRV_DROP_FILTER, + + // Dropped by driver as invalid. E.g. not permitted in current state. + RX_PKT_FATE_DRV_DROP_INVALID, + + // Dropped by driver due to lack of buffer space. + RX_PKT_FATE_DRV_DROP_NOBUFS, + + // Dropped by driver for any other reason. + RX_PKT_FATE_DRV_DROP_OTHER, +} wifi_rx_packet_fate; + +typedef enum { + FRAME_TYPE_UNKNOWN, + FRAME_TYPE_ETHERNET_II, + FRAME_TYPE_80211_MGMT, +} frame_type; + +struct scsc_frame_info { + // The type of MAC-layer frame that this frame_info holds. + // - For data frames, use FRAME_TYPE_ETHERNET_II. + // - For management frames, use FRAME_TYPE_80211_MGMT. + // - If the type of the frame is unknown, use FRAME_TYPE_UNKNOWN. + frame_type payload_type; + + // The number of bytes included in |frame_content|. If the frame + // contents are missing (e.g. RX frame dropped in firmware), + // |frame_len| should be set to 0. + size_t frame_len; + + // Host clock when this frame was received by the driver (either + // outbound from the host network stack, or inbound from the + // firmware). + // - The timestamp should be taken from a clock which includes time + // the host spent suspended (e.g. ktime_get_boottime()). + // - If no host timestamp is available (e.g. RX frame was dropped in + // firmware), this field should be set to 0. + u32 driver_timestamp_usec; + + // Firmware clock when this frame was received by the firmware + // (either outbound from the host, or inbound from a remote + // station). + // - The timestamp should be taken from a clock which includes time + // firmware spent suspended (if applicable). + // - If no firmware timestamp is available (e.g. TX frame was + // dropped by driver), this field should be set to 0. + // - Consumers of |frame_info| should _not_ assume any + // synchronization between driver and firmware clocks. + u32 firmware_timestamp_usec; + + // Actual frame content. + // - Should be provided for TX frames originated by the host. + // - Should be provided for RX frames received by the driver. + // - Optionally provided for TX frames originated by firmware. (At + // discretion of HAL implementation.) + // - Optionally provided for RX frames dropped in firmware. (At + // discretion of HAL implementation.) + // - If frame content is not provided, |frame_len| should be set + // to 0. + union { + char ethernet_ii_bytes[MAX_FRAME_LEN_ETHERNET]; + char ieee_80211_mgmt_bytes[MAX_FRAME_LEN_80211_MGMT]; + } frame_content; +}; + +typedef struct { + // Prefix of MD5 hash of |frame_inf.frame_content|. If frame + // content is not provided, prefix of MD5 hash over the same data + // that would be in frame_content, if frame content were provided. + char md5_prefix[MD5_PREFIX_LEN]; + wifi_tx_packet_fate fate; + struct scsc_frame_info frame_inf; +} __packed wifi_tx_report; + +typedef struct { + // Prefix of MD5 hash of |frame_inf.frame_content|. If frame + // content is not provided, prefix of MD5 hash over the same data + // that would be in frame_content, if frame content were provided. + char md5_prefix[MD5_PREFIX_LEN]; + wifi_rx_packet_fate fate; + struct scsc_frame_info frame_inf; +} __packed wifi_rx_report; +#endif /* _SCSC_WIFILOGGER_TYPES_H_ */ diff --git a/drivers/misc/samsung/scsc/scsc_wlbtd.c b/drivers/misc/samsung/scsc/scsc_wlbtd.c new file mode 100755 index 000000000000..cc2100a056ed --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wlbtd.c @@ -0,0 +1,704 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include + +#include "scsc_wlbtd.h" + +#define MAX_TIMEOUT 30000 /* in milisecounds */ +#define WRITE_FILE_TIMEOUT 1000 /* in milisecounds */ + +/* completion to indicate when EVENT_* is done */ +static DECLARE_COMPLETION(event_done); +static DECLARE_COMPLETION(fw_panic_done); +static DECLARE_COMPLETION(write_file_done); +static DEFINE_MUTEX(write_file_lock); + +static DEFINE_MUTEX(build_type_lock); +static char *build_type; +static DEFINE_MUTEX(sable_lock); + +static struct wake_lock wlbtd_wakelock; + +/* module parameter controlling recovery handling */ +extern int disable_recovery_handling; + +const char *response_code_to_str(int response_code) +{ + switch (response_code) { + case SCSC_WLBTD_ERR_PARSE_FAILED: + return "SCSC_WLBTD_ERR_PARSE_FAILED"; + case SCSC_WLBTD_FW_PANIC_TAR_GENERATED: + return "SCSC_WLBTD_FW_PANIC_TAR_GENERATED"; + case SCSC_WLBTD_FW_PANIC_ERR_SCRIPT_FILE_NOT_FOUND: + return "SCSC_WLBTD_FW_PANIC_ERR_SCRIPT_FILE_NOT_FOUND"; + case SCSC_WLBTD_FW_PANIC_ERR_NO_DEV: + return "SCSC_WLBTD_FW_PANIC_ERR_NO_DEV"; + case SCSC_WLBTD_FW_PANIC_ERR_MMAP: + return "SCSC_WLBTD_FW_PANIC_ERR_MMAP"; + case SCSC_WLBTD_FW_PANIC_ERR_SABLE_FILE: + return "SCSC_WLBTD_FW_PANIC_ERR_SABLE_FILE"; + case SCSC_WLBTD_FW_PANIC_ERR_TAR: + return "SCSC_WLBTD_FW_PANIC_ERR_TAR"; + case SCSC_WLBTD_OTHER_SBL_GENERATED: + return "SCSC_WLBTD_OTHER_SBL_GENERATED"; + case SCSC_WLBTD_OTHER_TAR_GENERATED: + return "SCSC_WLBTD_OTHER_TAR_GENERATED"; + case SCSC_WLBTD_OTHER_ERR_SCRIPT_FILE_NOT_FOUND: + return "SCSC_WLBTD_OTHER_ERR_SCRIPT_FILE_NOT_FOUND"; + case SCSC_WLBTD_OTHER_ERR_NO_DEV: + return "SCSC_WLBTD_OTHER_ERR_NO_DEV"; + case SCSC_WLBTD_OTHER_ERR_MMAP: + return "SCSC_WLBTD_OTHER_ERR_MMAP"; + case SCSC_WLBTD_OTHER_ERR_SABLE_FILE: + return "SCSC_WLBTD_OTHER_ERR_SABLE_FILE"; + case SCSC_WLBTD_OTHER_ERR_TAR: + return "SCSC_WLBTD_OTHER_ERR_TAR"; + case SCSC_WLBTD_OTHER_IGNORE_TRIGGER: + return "SCSC_WLBTD_OTHER_IGNORE_TRIGGER"; + default: + SCSC_TAG_ERR(WLBTD, "UNKNOWN response_code %d", response_code); + return "UNKNOWN response_code"; + } +} + +/** + * This callback runs whenever the socket receives messages. + */ +static int msg_from_wlbtd_cb(struct sk_buff *skb, struct genl_info *info) +{ + int status = 0; + + if (info->attrs[1]) + SCSC_TAG_INFO(WLBTD, "ATTR_STR: %s\n", + (char *)nla_data(info->attrs[1])); + + if (info->attrs[2]) { + status = *((__u32 *)nla_data(info->attrs[2])); + if (status) + SCSC_TAG_ERR(WLBTD, "ATTR_INT: %u\n", status); + } + + complete(&event_done); + + return 0; +} + +static int msg_from_wlbtd_sable_cb(struct sk_buff *skb, struct genl_info *info) +{ + int status = 0; + const char *data = (const char *)nla_data(info->attrs[1]); + + if (info->attrs[1]) + SCSC_TAG_INFO(WLBTD, "%s\n", data); + + if (info->attrs[2]) { + status = nla_get_u16(info->attrs[2]); + SCSC_TAG_ERR(WLBTD, "%s\n", response_code_to_str(status)); + } + + /* completion cases : + * 1) FW_PANIC_TAR_GENERATED + * for trigger scsc_log_fw_panic only one response from wlbtd when + * tar done + * ---> complete fw_panic_done + * 2) for all other triggers, we get 2 responses + * a) OTHER_SBL_GENERATED + * Once .sbl is written + * ---> complete event_done + * b) OTHER_TAR_GENERATED + * 2nd time when sable tar is done + * IGNORE this response and Don't complete + * 3) OTHER_IGNORE_TRIGGER + * When we get rapid requests for SABLE generation, + * to serialise while processing current request, + * we ignore requests other than "fw_panic" in wlbtd and + * send a msg "ignoring" back to kernel. + * ---> complete event_done + * 4) FW_PANIC_ERR_* and OTHER_ERR_* + * when something failed, file not found, mmap failed, etc. + * ---> complete the completion with waiter(s) based on if it was + * a fw_panic trigger or other trigger + * 5) ERR_PARSE_FAILED + * When msg parsing fails, wlbtd doesn't know the trigger type + * ---> complete the completion with waiter(s) + */ + + switch (status) { + case SCSC_WLBTD_ERR_PARSE_FAILED: + if (!completion_done(&fw_panic_done)) { + SCSC_TAG_INFO(WLBTD, "completing fw_panic_done\n"); + complete(&fw_panic_done); + } + if (!completion_done(&event_done)) { + SCSC_TAG_INFO(WLBTD, "completing event_done\n"); + complete(&event_done); + } + break; + case SCSC_WLBTD_FW_PANIC_TAR_GENERATED: + case SCSC_WLBTD_FW_PANIC_ERR_TAR: + case SCSC_WLBTD_FW_PANIC_ERR_SCRIPT_FILE_NOT_FOUND: + case SCSC_WLBTD_FW_PANIC_ERR_NO_DEV: + case SCSC_WLBTD_FW_PANIC_ERR_MMAP: + case SCSC_WLBTD_FW_PANIC_ERR_SABLE_FILE: + if (!completion_done(&fw_panic_done)) { + SCSC_TAG_INFO(WLBTD, "completing fw_panic_done\n"); + complete(&fw_panic_done); + } + break; + case SCSC_WLBTD_OTHER_TAR_GENERATED: + /* ignore */ + break; + case SCSC_WLBTD_OTHER_SBL_GENERATED: + case SCSC_WLBTD_OTHER_ERR_TAR: + case SCSC_WLBTD_OTHER_ERR_SCRIPT_FILE_NOT_FOUND: + case SCSC_WLBTD_OTHER_ERR_NO_DEV: + case SCSC_WLBTD_OTHER_ERR_MMAP: + case SCSC_WLBTD_OTHER_ERR_SABLE_FILE: + case SCSC_WLBTD_OTHER_IGNORE_TRIGGER: + if (!completion_done(&event_done)) { + SCSC_TAG_INFO(WLBTD, "completing event_done\n"); + complete(&event_done); + } + break; + default: + SCSC_TAG_ERR(WLBTD, "UNKNOWN reponse from WLBTD\n"); + } + + return 0; +} + +static int msg_from_wlbtd_build_type_cb(struct sk_buff *skb, struct genl_info *info) +{ + if (!info->attrs[1]) { + SCSC_TAG_WARNING(WLBTD, "info->attrs[1] = NULL\n"); + return -1; + } + + if (!nla_len(info->attrs[1])) { + SCSC_TAG_WARNING(WLBTD, "nla_len = 0\n"); + return -1; + } + + mutex_lock(&build_type_lock); + if (build_type) { + SCSC_TAG_WARNING(WLBTD, "ro.build.type = %s\n", build_type); + mutex_unlock(&build_type_lock); + return 0; + } + /* nla_len includes trailing zero. Tested.*/ + build_type = kmalloc(info->attrs[1]->nla_len, GFP_KERNEL); + if (!build_type) { + SCSC_TAG_WARNING(WLBTD, "kmalloc failed: build_type = NULL\n"); + mutex_unlock(&build_type_lock); + return -1; + } + memcpy(build_type, (char *)nla_data(info->attrs[1]), info->attrs[1]->nla_len); + SCSC_TAG_WARNING(WLBTD, "ro.build.type = %s\n", build_type); + mutex_unlock(&build_type_lock); + return 0; + +} + +static int msg_from_wlbtd_write_file_cb(struct sk_buff *skb, struct genl_info *info) +{ + if (info->attrs[3]) + SCSC_TAG_INFO(WLBTD, "%s\n", (char *)nla_data(info->attrs[3])); + + complete(&write_file_done); + return 0; +} + +/** + * Here you can define some constraints for the attributes so Linux will + * validate them for you. + */ +static struct nla_policy policies[] = { + [ATTR_STR] = { .type = NLA_STRING, }, + [ATTR_INT] = { .type = NLA_U32, }, +}; + +static struct nla_policy policy_sable[] = { + [ATTR_INT] = { .type = NLA_U16, }, + [ATTR_INT8] = { .type = NLA_U8, }, +}; + +static struct nla_policy policies_build_type[] = { + [ATTR_STR] = { .type = NLA_STRING, }, +}; + +static struct nla_policy policy_write_file[] = { + [ATTR_PATH] = { .type = NLA_STRING, }, + [ATTR_CONTENT] = { .type = NLA_STRING, }, +}; + + +/** + * Actual message type definition. + */ +const struct genl_ops scsc_ops[] = { + { + .cmd = EVENT_SCSC, + .flags = 0, + .policy = policies, + .doit = msg_from_wlbtd_cb, + .dumpit = NULL, + }, + { + .cmd = EVENT_SYSTEM_PROPERTY, + .flags = 0, + .policy = policies_build_type, + .doit = msg_from_wlbtd_build_type_cb, + .dumpit = NULL, + }, + { + .cmd = EVENT_SABLE, + .flags = 0, + .policy = policy_sable, + .doit = msg_from_wlbtd_sable_cb, + .dumpit = NULL, + }, + { + .cmd = EVENT_WRITE_FILE, + .flags = 0, + .policy = policy_write_file, + .doit = msg_from_wlbtd_write_file_cb, + .dumpit = NULL, + }, + +}; + +/* The netlink family */ +static struct genl_family scsc_nlfamily = { + .id = 0, /* Don't bother with a hardcoded ID */ + .name = "scsc_mdp_family", /* Have users key off the name instead */ + .hdrsize = 0, /* No private header */ + .version = 1, + .maxattr = __ATTR_MAX, + .module = THIS_MODULE, + .ops = scsc_ops, + .n_ops = ARRAY_SIZE(scsc_ops), + .mcgrps = scsc_mcgrp, + .n_mcgrps = ARRAY_SIZE(scsc_mcgrp), +}; + +int scsc_wlbtd_get_and_print_build_type(void) +{ + struct sk_buff *skb; + void *msg; + int rc = 0; + + SCSC_TAG_DEBUG(WLBTD, "start\n"); + wake_lock(&wlbtd_wakelock); + + /* check if the value wasn't cached yet */ + mutex_lock(&build_type_lock); + if (build_type) { + SCSC_TAG_WARNING(WLBTD, "ro.build.type = %s\n", build_type); + SCSC_TAG_DEBUG(WLBTD, "sync end\n"); + mutex_unlock(&build_type_lock); + goto done; + } + mutex_unlock(&build_type_lock); + skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!skb) { + SCSC_TAG_ERR(WLBTD, "Failed to construct message\n"); + goto error; + } + + SCSC_TAG_INFO(WLBTD, "create message\n"); + msg = genlmsg_put(skb, + 0, // PID is whatever + 0, // Sequence number (don't care) + &scsc_nlfamily, // Pointer to family struct + 0, // Flags + EVENT_SYSTEM_PROPERTY // Generic netlink command + ); + if (!msg) { + SCSC_TAG_ERR(WLBTD, "Failed to create message\n"); + goto error; + } + rc = nla_put_string(skb, ATTR_STR, "ro.build.type"); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_string failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + genlmsg_end(skb, msg); + + SCSC_TAG_INFO(WLBTD, "finalize & send msg\n"); + rc = genlmsg_multicast_allns(&scsc_nlfamily, skb, 0, 0, GFP_KERNEL); + + if (rc) { + SCSC_TAG_ERR(WLBTD, "failed to send message. rc = %d\n", rc); + goto error; + } + + SCSC_TAG_DEBUG(WLBTD, "async end\n"); +done: + wake_unlock(&wlbtd_wakelock); + return rc; + +error: + if (rc == -ESRCH) { + /* If no one registered to scsc_mdp_mcgrp (e.g. in case wlbtd + * is not running) genlmsg_multicast_allns returns -ESRCH. + * Ignore and return. + */ + SCSC_TAG_WARNING(WLBTD, "WLBTD not running ?\n"); + wake_unlock(&wlbtd_wakelock); + return rc; + } + /* free skb */ + nlmsg_free(skb); + wake_unlock(&wlbtd_wakelock); + return -1; +} + +int wlbtd_write_file(const char *file_path, const char *file_content) +{ +#ifdef CONFIG_SCSC_WRITE_INFO_FILE_WLBTD + struct sk_buff *skb; + void *msg; + int rc = 0; + unsigned long completion_jiffies = 0; + unsigned long max_timeout_jiffies = msecs_to_jiffies(WRITE_FILE_TIMEOUT); + + SCSC_TAG_DEBUG(WLBTD, "start\n"); + + mutex_lock(&write_file_lock); + wake_lock(&wlbtd_wakelock); + + skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!skb) { + SCSC_TAG_ERR(WLBTD, "Failed to construct message\n"); + goto error; + } + + SCSC_TAG_INFO(WLBTD, "create message to write %s\n", file_path); + msg = genlmsg_put(skb, + 0, // PID is whatever + 0, // Sequence number (don't care) + &scsc_nlfamily, // Pointer to family struct + 0, // Flags + EVENT_WRITE_FILE// Generic netlink command + ); + if (!msg) { + SCSC_TAG_ERR(WLBTD, "Failed to create message\n"); + goto error; + } + + SCSC_TAG_DEBUG(WLBTD, "add values to msg\n"); + rc = nla_put_string(skb, ATTR_PATH, file_path); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_u32 failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + + rc = nla_put_string(skb, ATTR_CONTENT, file_content); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_string failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + + genlmsg_end(skb, msg); + + SCSC_TAG_INFO(WLBTD, "finalize & send msg\n"); + /* genlmsg_multicast_allns() frees skb */ + rc = genlmsg_multicast_allns(&scsc_nlfamily, skb, 0, 0, GFP_KERNEL); + + if (rc) { + if (rc == -ESRCH) { + /* If no one registered to scsc_mcgrp (e.g. in case + * wlbtd is not running) genlmsg_multicast_allns + * returns -ESRCH. Ignore and return. + */ + SCSC_TAG_WARNING(WLBTD, "WLBTD not running ?\n"); + goto done; + } + SCSC_TAG_ERR(WLBTD, "Failed to send message. rc = %d\n", rc); + goto done; + } + + SCSC_TAG_INFO(WLBTD, "waiting for completion\n"); + /* wait for script to finish */ + completion_jiffies = wait_for_completion_timeout(&write_file_done, + max_timeout_jiffies); + + if (completion_jiffies == 0) + SCSC_TAG_ERR(WLBTD, "wait for completion timed out !\n"); + else { + completion_jiffies = jiffies_to_msecs(max_timeout_jiffies - completion_jiffies); + + SCSC_TAG_INFO(WLBTD, "written %s in %dms\n", file_path, + completion_jiffies ? completion_jiffies : 1); + } + + /* reinit so completion can be re-used */ + reinit_completion(&write_file_done); + + SCSC_TAG_DEBUG(WLBTD, "end\n"); +done: + wake_unlock(&wlbtd_wakelock); + mutex_unlock(&write_file_lock); + return rc; + +error: + /* free skb */ + nlmsg_free(skb); + + wake_unlock(&wlbtd_wakelock); + mutex_unlock(&write_file_lock); + return -1; +#else /* CONFIG_SCSC_WRITE_INFO_FILE_WLBTD */ + return 0; /* stub */ +#endif +} +EXPORT_SYMBOL(wlbtd_write_file); + +int call_wlbtd_sable(u8 trigger_code, u16 reason_code) +{ + struct sk_buff *skb; + void *msg; + int rc = 0; + unsigned long completion_jiffies = 0; + unsigned long max_timeout_jiffies = msecs_to_jiffies(MAX_TIMEOUT); + + mutex_lock(&sable_lock); + wake_lock(&wlbtd_wakelock); + + SCSC_TAG_INFO(WLBTD, "start:trigger - %s\n", + scsc_get_trigger_str((int)trigger_code)); + + skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!skb) { + SCSC_TAG_ERR(WLBTD, "Failed to construct message\n"); + goto error; + } + + SCSC_TAG_DEBUG(WLBTD, "create message\n"); + msg = genlmsg_put(skb, + 0, // PID is whatever + 0, // Sequence number (don't care) + &scsc_nlfamily, // Pointer to family struct + 0, // Flags + EVENT_SABLE // Generic netlink command + ); + if (!msg) { + SCSC_TAG_ERR(WLBTD, "Failed to create message\n"); + goto error; + } + SCSC_TAG_DEBUG(WLBTD, "add values to msg\n"); + rc = nla_put_u16(skb, ATTR_INT, reason_code); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_u16 failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + + rc = nla_put_u8(skb, ATTR_INT8, trigger_code); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_u8 failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + + genlmsg_end(skb, msg); + + SCSC_TAG_DEBUG(WLBTD, "finalize & send msg\n"); + /* genlmsg_multicast_allns() frees skb */ + rc = genlmsg_multicast_allns(&scsc_nlfamily, skb, 0, 0, GFP_KERNEL); + + if (rc) { + if (rc == -ESRCH) { + /* If no one registered to scsc_mcgrp (e.g. in case + * wlbtd is not running) genlmsg_multicast_allns + * returns -ESRCH. Ignore and return. + */ + SCSC_TAG_WARNING(WLBTD, "WLBTD not running ?\n"); + goto done; + } + SCSC_TAG_ERR(WLBTD, "Failed to send message. rc = %d\n", rc); + goto done; + } + + SCSC_TAG_INFO(WLBTD, "waiting for completion\n"); + + /* wait for script to finish */ + if (trigger_code == SCSC_LOG_FW_PANIC) + completion_jiffies = wait_for_completion_timeout(&fw_panic_done, + max_timeout_jiffies); + else + completion_jiffies = wait_for_completion_timeout(&event_done, + max_timeout_jiffies); + + if (completion_jiffies) { + completion_jiffies = max_timeout_jiffies - completion_jiffies; + SCSC_TAG_INFO(WLBTD, "sable generated in %dms\n", + (int)jiffies_to_msecs(completion_jiffies) ? : 1); + } else + SCSC_TAG_ERR(WLBTD, "wait for completion timed out for %s\n", + scsc_get_trigger_str((int)trigger_code)); + + /* reinit so completion can be re-used */ + if (trigger_code == SCSC_LOG_FW_PANIC) + reinit_completion(&fw_panic_done); + else + reinit_completion(&event_done); + + SCSC_TAG_INFO(WLBTD, " end:trigger - %s\n", + scsc_get_trigger_str((int)trigger_code)); + +done: + wake_unlock(&wlbtd_wakelock); + mutex_unlock(&sable_lock); + return rc; + +error: + /* free skb */ + nlmsg_free(skb); + wake_unlock(&wlbtd_wakelock); + mutex_unlock(&sable_lock); + + return -1; +} +EXPORT_SYMBOL(call_wlbtd_sable); + +int call_wlbtd(const char *script_path) +{ + struct sk_buff *skb; + void *msg; + int rc = 0; + unsigned long completion_jiffies = 0; + unsigned long max_timeout_jiffies = msecs_to_jiffies(MAX_TIMEOUT); + + SCSC_TAG_DEBUG(WLBTD, "start\n"); + + wake_lock(&wlbtd_wakelock); + + skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!skb) { + SCSC_TAG_ERR(WLBTD, "Failed to construct message\n"); + goto error; + } + + SCSC_TAG_INFO(WLBTD, "create message to run %s\n", script_path); + msg = genlmsg_put(skb, + 0, // PID is whatever + 0, // Sequence number (don't care) + &scsc_nlfamily, // Pointer to family struct + 0, // Flags + EVENT_SCSC // Generic netlink command + ); + if (!msg) { + SCSC_TAG_ERR(WLBTD, "Failed to create message\n"); + goto error; + } + + SCSC_TAG_DEBUG(WLBTD, "add values to msg\n"); + rc = nla_put_u32(skb, ATTR_INT, 9); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_u32 failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + + rc = nla_put_string(skb, ATTR_STR, script_path); + if (rc) { + SCSC_TAG_ERR(WLBTD, "nla_put_string failed. rc = %d\n", rc); + genlmsg_cancel(skb, msg); + goto error; + } + + genlmsg_end(skb, msg); + + SCSC_TAG_INFO(WLBTD, "finalize & send msg\n"); + /* genlmsg_multicast_allns() frees skb */ + rc = genlmsg_multicast_allns(&scsc_nlfamily, skb, 0, 0, GFP_KERNEL); + + if (rc) { + if (rc == -ESRCH) { + /* If no one registered to scsc_mcgrp (e.g. in case + * wlbtd is not running) genlmsg_multicast_allns + * returns -ESRCH. Ignore and return. + */ + SCSC_TAG_WARNING(WLBTD, "WLBTD not running ?\n"); + goto done; + } + SCSC_TAG_ERR(WLBTD, "Failed to send message. rc = %d\n", rc); + goto done; + } + + SCSC_TAG_INFO(WLBTD, "waiting for completion\n"); + + /* wait for script to finish */ + completion_jiffies = wait_for_completion_timeout(&event_done, + max_timeout_jiffies); + + if (completion_jiffies) { + + completion_jiffies = max_timeout_jiffies - completion_jiffies; + SCSC_TAG_INFO(WLBTD, "done in %dms\n", + (int)jiffies_to_msecs(completion_jiffies) ? : 1); + } else + SCSC_TAG_ERR(WLBTD, "wait for completion timed out !\n"); + + /* reinit so completion can be re-used */ + reinit_completion(&event_done); + + SCSC_TAG_DEBUG(WLBTD, "end\n"); + +done: + wake_unlock(&wlbtd_wakelock); + return rc; + +error: + /* free skb */ + nlmsg_free(skb); + wake_unlock(&wlbtd_wakelock); + + return -1; +} +EXPORT_SYMBOL(call_wlbtd); + +int scsc_wlbtd_init(void) +{ + int r = 0; + + wake_lock_init(&wlbtd_wakelock, WAKE_LOCK_SUSPEND, "wlbtd_wl"); + init_completion(&event_done); + init_completion(&fw_panic_done); + init_completion(&write_file_done); + + /* register the family so that wlbtd can bind */ + r = genl_register_family(&scsc_nlfamily); + if (r) { + SCSC_TAG_ERR(WLBTD, "Failed to register family. (%d)\n", r); + return -1; + } + + return r; +} + +int scsc_wlbtd_deinit(void) +{ + int ret = 0; + + /* unregister family */ + ret = genl_unregister_family(&scsc_nlfamily); + if (ret) { + SCSC_TAG_ERR(WLBTD, "genl_unregister_family failed (%d)\n", + ret); + return -1; + } + kfree(build_type); + build_type = NULL; + wake_lock_destroy(&wlbtd_wakelock); + + return ret; +} diff --git a/drivers/misc/samsung/scsc/scsc_wlbtd.h b/drivers/misc/samsung/scsc/scsc_wlbtd.h new file mode 100644 index 000000000000..5176ecd659b1 --- /dev/null +++ b/drivers/misc/samsung/scsc/scsc_wlbtd.h @@ -0,0 +1,83 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include +#include +#include + +/* module parameter value to indicate control of recovery via .memdump.info file */ +#define MEMDUMP_FILE_FOR_RECOVERY 2 +/* content of .memdump.info file indicating to panic kernel */ +#define MEMDUMP_FILE_KERNEL_PANIC 3 + +/** + * Attributes are fields of data your messages will contain. + * The designers of Netlink really want you to use these instead of just dumping + * data to the packet payload. + */ +enum attributes { + /* The first one has to be a throwaway empty attribute */ + ATTR_UNSPEC, + + ATTR_STR, + ATTR_INT, + ATTR_PATH, + ATTR_CONTENT, + ATTR_INT8, + + /* This must be last! */ + __ATTR_MAX, +}; + +/** + * Message type codes. + */ +enum events { + /* must be first */ + EVENT_UNSPEC, + + EVENT_SCSC, + EVENT_SYSTEM_PROPERTY, + EVENT_WRITE_FILE, + EVENT_SABLE, + /* This must be last! */ + __EVENT_MAX, +}; + +enum scsc_wlbtd_response_codes { + /* NOTE: keep the enum in sync with userspace wlbtd */ + /* parse failed */ + SCSC_WLBTD_ERR_PARSE_FAILED, + + /* fw_panic trigger */ + SCSC_WLBTD_FW_PANIC_TAR_GENERATED, + SCSC_WLBTD_FW_PANIC_ERR_SCRIPT_FILE_NOT_FOUND, + SCSC_WLBTD_FW_PANIC_ERR_NO_DEV, + SCSC_WLBTD_FW_PANIC_ERR_MMAP, + SCSC_WLBTD_FW_PANIC_ERR_SABLE_FILE, + SCSC_WLBTD_FW_PANIC_ERR_TAR, + + /* other triggers */ + SCSC_WLBTD_OTHER_SBL_GENERATED, + SCSC_WLBTD_OTHER_TAR_GENERATED, + SCSC_WLBTD_OTHER_ERR_SCRIPT_FILE_NOT_FOUND, + SCSC_WLBTD_OTHER_ERR_NO_DEV, + SCSC_WLBTD_OTHER_ERR_MMAP, + SCSC_WLBTD_OTHER_ERR_SABLE_FILE, + SCSC_WLBTD_OTHER_ERR_TAR, + SCSC_WLBTD_OTHER_IGNORE_TRIGGER, +}; + +static const struct genl_multicast_group scsc_mcgrp[] = { + { .name = "scsc_mdp_grp", }, +}; + +int scsc_wlbtd_init(void); +int scsc_wlbtd_deinit(void); +int call_wlbtd(const char *script_path); +int wlbtd_write_file(const char *path, const char *content); +int call_wlbtd_sable(u8 trigger_code, u16 reason_code); +int scsc_wlbtd_get_and_print_build_type(void); diff --git a/drivers/misc/samsung/scsc/servman_messages.h b/drivers/misc/samsung/scsc/servman_messages.h new file mode 100644 index 000000000000..8fb4527e670d --- /dev/null +++ b/drivers/misc/samsung/scsc/servman_messages.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef SERVMAN_MESSAGES_H__ +#define SERVMAN_MESSAGES_H__ + +#include +/** + * Maxwell Service Management Messages. + * + * TODO: common defn with host, generated. + */ +enum { + SM_MSG_START_REQ, + SM_MSG_START_CFM, + SM_MSG_STOP_REQ, + SM_MSG_STOP_CFM, +} sm_msg; + +/* Transport format for service management messages across the + * Maxwell management transport. + * + * TODO: common defn with host, generated. + */ +struct sm_msg_packet { + uint8_t service_id; + uint8_t msg; + scsc_mifram_ref optional_data; +} __packed; + + +#endif /* SERVMAN_MESSAGES_H__ */ diff --git a/drivers/misc/samsung/scsc/srvman.h b/drivers/misc/samsung/scsc/srvman.h new file mode 100755 index 000000000000..03d213e1ca43 --- /dev/null +++ b/drivers/misc/samsung/scsc/srvman.h @@ -0,0 +1,37 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _SRVMAN_H +#define _SRVMAN_H + +#ifdef CONFIG_ANDROID +#include +#endif + +struct srvman; + +void srvman_init(struct srvman *srvman, struct scsc_mx *mx); +int srvman_suspend_services(struct srvman *srvman); +int srvman_resume_services(struct srvman *srvman); +void srvman_freeze_services(struct srvman *srvman); +void srvman_unfreeze_services(struct srvman *srvman, u16 scsc_panic_code); +void srvman_set_error(struct srvman *srvman); +void srvman_clear_error(struct srvman *srvman); +void srvman_deinit(struct srvman *srvman); + +struct srvman { + struct scsc_mx *mx; + struct list_head service_list; + struct mutex service_list_mutex; + struct mutex api_access_mutex; + bool error; +#ifdef CONFIG_ANDROID + struct wake_lock sm_wake_lock; +#endif +}; + + +#endif diff --git a/drivers/misc/samsung/scsc/suspendmon.c b/drivers/misc/samsung/scsc/suspendmon.c new file mode 100644 index 000000000000..b0c27ed28a56 --- /dev/null +++ b/drivers/misc/samsung/scsc/suspendmon.c @@ -0,0 +1,53 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +#include + +#include "suspendmon.h" +#include "scsc_mif_abs.h" +#include "mxman.h" + +static int suspendmon_suspend(struct scsc_mif_abs *mif, void *data) +{ + struct suspendmon *suspendmon = (struct suspendmon *)data; + + SCSC_TAG_DEBUG(MXMAN, "suspendmon=%p suspendmon->mx=%p mxman=%p\n", + suspendmon, suspendmon->mx, scsc_mx_get_mxman(suspendmon->mx)); + + return mxman_suspend(scsc_mx_get_mxman(suspendmon->mx)); +} + +static void suspendmon_resume(struct scsc_mif_abs *mif, void *data) +{ + struct suspendmon *suspendmon = (struct suspendmon *)data; + + SCSC_TAG_DEBUG(MXMAN, "suspendmon=%p suspendmon->mx=%p mxman=%p\n", + suspendmon, suspendmon->mx, scsc_mx_get_mxman(suspendmon->mx)); + + mxman_resume(scsc_mx_get_mxman(suspendmon->mx)); +} + +void suspendmon_init(struct suspendmon *suspendmon, struct scsc_mx *mx) +{ + struct scsc_mif_abs *mif; + + suspendmon->mx = mx; + mif = scsc_mx_get_mif_abs(mx); + + /* register callbacks with mif abstraction */ + if (mif->suspend_reg_handler) + mif->suspend_reg_handler(mif, suspendmon_suspend, suspendmon_resume, (void *)suspendmon); +} + +void suspendmon_deinit(struct suspendmon *suspendmon) +{ + struct scsc_mif_abs *mif; + + mif = scsc_mx_get_mif_abs(suspendmon->mx); + + if (mif->suspend_unreg_handler) + mif->suspend_unreg_handler(mif); +} diff --git a/drivers/misc/samsung/scsc/suspendmon.h b/drivers/misc/samsung/scsc/suspendmon.h new file mode 100644 index 000000000000..d68e99e66284 --- /dev/null +++ b/drivers/misc/samsung/scsc/suspendmon.h @@ -0,0 +1,23 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. + * + ****************************************************************************/ + +/* Baroque suspend/resume handler registration interface */ + +#ifndef _SUSPENDMON_H +#define _SUSPENDMON_H + +#include "mxman.h" + +struct suspendmon; + +void suspendmon_init(struct suspendmon *suspendmon, struct scsc_mx *mx); +void suspendmon_deinit(struct suspendmon *suspendmon); + +struct suspendmon { + struct scsc_mx *mx; +}; + +#endif /* _SUSPENDMON_H */ diff --git a/drivers/misc/samsung/scsc_bt/Kconfig b/drivers/misc/samsung/scsc_bt/Kconfig new file mode 100644 index 000000000000..9be9a00cd86e --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/Kconfig @@ -0,0 +1,29 @@ +config SCSC_BT + tristate "SCSC MX BT support" + depends on SCSC_CORE + +config SCSC_BT_BLUEZ + default N + tristate "SCSC MX BlueZ support" + depends on SCSC_CORE + +config SCSC_ANT + default N + tristate "SCSC MX ANT support" + depends on SCSC_CORE + ---help--- + This module adds support for using a ANT character device. + +config SCSC_BT_ADDRESS_IN_FILE + default N + tristate "SCSC MX BT support" + depends on SCSC_CORE + ---help--- + Allow BT address to be read from a file + +config SCSC_BT_ADDRESS_FILENAME + string "BT address filename" + depends on SCSC_CORE + default "/mnt/vendor/efs/bluetooth/bt_addr" + ---help--- + Select the named BT address override file. diff --git a/drivers/misc/samsung/scsc_bt/Makefile b/drivers/misc/samsung/scsc_bt/Makefile new file mode 100644 index 000000000000..c6ae27153ad2 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/Makefile @@ -0,0 +1,31 @@ +# Needed since this subdir is symlinked in the main Kernel tree +# without this our samsung subdir is NOT cleaned. +clean-files := *.o *.ko + +# +# Maxwell BT +obj-$(CONFIG_SCSC_BT) += scsc_bt.o +scsc_bt-y += scsc_bt_module.o scsc_shm.o scsc_avdtp_detect.o +scsc_bt-$(CONFIG_SCSC_BT_BLUEZ) += scsc_bluez.o +scsc_bt-$(CONFIG_SCSC_ANT) += scsc_ant.o + +ccflags-y += $(CONFIG_SAMSUNG_MAXWELL_EXTRA) +## See sibling scsc/ Makefile for an explanation of the reasons of +## the following ifeq/else +ifeq ($(CONFIG_SCSC_LOGRING), m) +ccflags-y += -DCONFIG_SCSC_PRINTK +else +ccflags-$(CONFIG_SCSC_LOGRING) += -DCONFIG_SCSC_PRINTK +endif + +ifeq ($(CONFIG_SCSC_BT_BLUEZ), m) +ccflags-y += -DCONFIG_SCSC_BT_BLUEZ +else +ccflags-$(CONFIG_SCSC_BT_BLUEZ) += -DCONFIG_SCSC_BT_BLUEZ +endif + +ifeq ($(CONFIG_SCSC_ANT), m) +ccflags-y += -DCONFIG_SCSC_ANT +else +ccflags-$(CONFIG_SCSC_ANT) += -DCONFIG_SCSC_ANT +endif diff --git a/drivers/misc/samsung/scsc_bt/scsc_ant.c b/drivers/misc/samsung/scsc_bt/scsc_ant.c new file mode 100644 index 000000000000..6b10c57e26fb --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_ant.c @@ -0,0 +1,676 @@ +/**************************************************************************** + * + * Copyright (c) 2017 Samsung Electronics Co., Ltd + * + ****************************************************************************/ + +/* MX BT shared memory interface */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "scsc_bt_priv.h" +#include "scsc_shm.h" +#include "scsc_bt_hci.h" + +static u8 ant_write_buffer[ASMHCP_BUFFER_SIZE]; + +static void scsc_ant_shm_irq_handler(int irqbit, void *data) +{ + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(ant_service.service, irqbit); + + ant_service.interrupt_count++; + + /* Wake the reader operation */ + if (ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_write != + ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_read || + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_write != + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_read || + atomic_read(&ant_service.error_count) != 0 || + ant_service.asmhcp_protocol->header.panic_deathbed_confession) { + ant_service.interrupt_read_count++; + + wake_lock_timeout(&ant_service.read_wake_lock, HZ); + wake_up(&ant_service.read_wait); + } + + if (ant_service.asmhcp_protocol->header.mailbox_data_driv_ctr_write == + ant_service.asmhcp_protocol->header.mailbox_data_driv_ctr_read && + ant_service.asmhcp_protocol->header.mailbox_cmd_driv_ctr_write == + ant_service.asmhcp_protocol->header.mailbox_cmd_driv_ctr_read) { + ant_service.interrupt_write_count++; + + if (wake_lock_active(&ant_service.write_wake_lock)) { + ant_service.write_wake_unlock_count++; + wake_unlock(&ant_service.write_wake_lock); + } + } +} + +/* Assign firmware/host interrupts */ +static void scsc_ant_shm_init_interrupt(void) +{ + /* To-host f/w IRQ allocations and ISR registrations */ + ant_service.asmhcp_protocol->header.bg_to_ap_int_src = + scsc_service_mifintrbit_register_tohost(ant_service.service, scsc_ant_shm_irq_handler, NULL); + + /* From-host f/w IRQ allocations */ + ant_service.asmhcp_protocol->header.ap_to_bg_int_src = + scsc_service_mifintrbit_alloc_fromhost(ant_service.service, SCSC_MIFINTR_TARGET_R4); + + SCSC_TAG_DEBUG(BT_COMMON, "Registered to-host IRQ bit %d, from-host IRQ bit %d\n", + ant_service.asmhcp_protocol->header.bg_to_ap_int_src, + ant_service.asmhcp_protocol->header.ap_to_bg_int_src); +} + +static ssize_t scsc_shm_ant_cmd_write(const unsigned char *data, size_t count) +{ + /* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */ + uint32_t tr_read = ant_service.asmhcp_protocol->header.mailbox_cmd_driv_ctr_read; + uint32_t tr_write = ant_service.asmhcp_protocol->header.mailbox_cmd_driv_ctr_write; + + struct ASMHCP_TD_CONTROL *td = &ant_service.asmhcp_protocol->cmd_driver_controller_transfer_ring[tr_write]; + /* Temp vars */ + SCSC_TAG_DEBUG(BT_H4, "ANT_COMMAND_PKT (len=%zu, read=%u, write=%u)\n", + count, tr_read, tr_write); + + /* Index out of bounds check */ + if (tr_read >= ASMHCP_TRANSFER_RING_CMD_SIZE || tr_write >= ASMHCP_TRANSFER_RING_CMD_SIZE) { + SCSC_TAG_ERR(BT_H4, + "ANT_COMMAND_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", + tr_read, tr_write); + atomic_inc(&ant_service.error_count); + return -EIO; + } + + /* Does the transfer ring have room for an entry */ + if (BSMHCP_HAS_ROOM(tr_write, tr_read, ASMHCP_TRANSFER_RING_CMD_SIZE)) { + /* Fill the transfer descriptor with the ANT command data */ + memcpy(td->data, data, count); + td->length = (u16)count; + + /* Ensure the wake lock is acquired */ + if (!wake_lock_active(&ant_service.write_wake_lock)) { + ant_service.write_wake_lock_count++; + wake_lock(&ant_service.write_wake_lock); + } + + /* Increase the write pointer */ + BSMHCP_INCREASE_INDEX(tr_write, ASMHCP_TRANSFER_RING_CMD_SIZE); + ant_service.asmhcp_protocol->header.mailbox_cmd_driv_ctr_write = tr_write; + + /* Memory barrier to ensure out-of-order execution is completed */ + mmiowb(); + + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set( + ant_service.service, + ant_service.asmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } else { + /* Transfer ring full. Only happens if the user attempt to send more ANT command packets than + * available credits + */ + count = 0; + } + + return count; +} + +static ssize_t scsc_shm_ant_data_write(const unsigned char *data, size_t count) +{ + /* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */ + uint32_t tr_read = ant_service.asmhcp_protocol->header.mailbox_data_driv_ctr_read; + uint32_t tr_write = ant_service.asmhcp_protocol->header.mailbox_data_driv_ctr_write; + + /* Temp vars */ + struct ASMHCP_TD_CONTROL *td = &ant_service.asmhcp_protocol->data_driver_controller_transfer_ring[tr_write]; + + SCSC_TAG_DEBUG(BT_H4, "ANT_DATA_PKT (len=%zu, read=%u, write=%u)\n", + count, tr_read, tr_write); + + /* Index out of bounds check */ + if (tr_read >= ASMHCP_TRANSFER_RING_DATA_SIZE || tr_write >= ASMHCP_TRANSFER_RING_DATA_SIZE) { + SCSC_TAG_ERR( + BT_H4, + "ANT_DATA_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", + tr_read, tr_write); + atomic_inc(&ant_service.error_count); + return -EIO; + } + + /* Does the transfer ring have room for an entry */ + if (BSMHCP_HAS_ROOM(tr_write, tr_read, ASMHCP_TRANSFER_RING_DATA_SIZE)) { + /* Fill the transfer descriptor with the ANT command data */ + memcpy(td->data, data, count); + td->length = (u16)count; + + /* Ensure the wake lock is acquired */ + if (!wake_lock_active(&ant_service.write_wake_lock)) { + ant_service.write_wake_lock_count++; + wake_lock(&ant_service.write_wake_lock); + } + + /* Increase the write pointer */ + BSMHCP_INCREASE_INDEX(tr_write, ASMHCP_TRANSFER_RING_DATA_SIZE); + ant_service.asmhcp_protocol->header.mailbox_data_driv_ctr_write = tr_write; + + /* Memory barrier to ensure out-of-order execution is completed */ + mmiowb(); + + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set( + ant_service.service, + ant_service.asmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } + else + /* Transfer ring full */ + count = 0; + + return count; +} + +static ssize_t scsc_ant_copy_td_to_buffer(char __user *buf, size_t len, struct ASMHCP_TD_CONTROL *td) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + size_t copy_len = 0; + + SCSC_TAG_DEBUG(BT_H4, "td (length=%u), len=%zu, read_offset=%zu\n", + td->length, len, ant_service.read_offset); + + /* Has the header been copied to userspace (aka is this the start of the copy operation) */ + if (ant_service.read_offset < ANT_HEADER_LENGTH) { + /* Calculate the amount of data that can be transferred */ + copy_len = min(ANT_HEADER_LENGTH - ant_service.read_offset, len); + + if (td->data[1] + ANT_HEADER_LENGTH + 1 != td->length) { + SCSC_TAG_ERR(BT_H4, "Firmware sent invalid ANT cmd/data\n"); + atomic_inc(&ant_service.error_count); + ret = -EFAULT; + } + /* Copy the ANT header to the userspace buffer */ + ret = copy_to_user(buf, &td->data[ant_service.read_offset], copy_len); + if (ret == 0) { + /* All good - Update our consumed information */ + consumed = copy_len; + ant_service.read_offset += copy_len; + SCSC_TAG_DEBUG(BT_H4, + "copied header: read_offset=%zu, consumed=%zu, ret=%zd, len=%zu, copy_len=%zu\n", + ant_service.read_offset, consumed, ret, len, copy_len); + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + /* Can more data be put into the userspace buffer */ + if (ret == 0 && ant_service.read_offset >= ANT_HEADER_LENGTH && (len - consumed)) { + /* Calculate the amount of data that can be transferred */ + copy_len = min((td->length - ant_service.read_offset), (len - consumed)); + + /* Copy the data to the user buffer */ + ret = copy_to_user(&buf[consumed], &td->data[ant_service.read_offset], copy_len); + if (ret == 0) { + /* All good - Update our consumed information */ + ant_service.read_offset += copy_len; + consumed += copy_len; + + /* Have all data been copied to the userspace buffer */ + if (ant_service.read_offset == td->length) { + /* All good - read operation is completed */ + ant_service.read_offset = 0; + ant_service.read_operation = ANT_READ_OP_NONE; + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + SCSC_TAG_DEBUG(BT_H4, "read_offset=%zu, consumed=%zu, ret=%zd, len=%zu, copy_len=%zu\n", + ant_service.read_offset, consumed, ret, len, copy_len); + + return ret == 0 ? consumed : ret; +} + +static ssize_t scsc_ant_cmd_read(char __user *buf, size_t len) +{ + ssize_t ret = 0; + + /* Temp vars */ + if (ant_service.mailbox_cmd_ctr_driv_read != ant_service.mailbox_cmd_ctr_driv_write) { + struct ASMHCP_PROTOCOL *ap = ant_service.asmhcp_protocol; + struct ASMHCP_TD_CONTROL *td = &ap->cmd_controller_driver_transfer_ring + [ant_service.mailbox_cmd_ctr_driv_read]; + + ret = scsc_ant_copy_td_to_buffer(buf, len, td); + } + + return ret; +} + +static ssize_t scsc_ant_data_read(char __user *buf, size_t len) +{ + ssize_t ret = 0; + + if (ant_service.mailbox_data_ctr_driv_read != ant_service.mailbox_data_ctr_driv_write) { + struct ASMHCP_PROTOCOL *ap = ant_service.asmhcp_protocol; + struct ASMHCP_TD_CONTROL *td = &ap->data_controller_driver_transfer_ring + [ant_service.mailbox_data_ctr_driv_read]; + + ret = scsc_ant_copy_td_to_buffer(buf, len, td); + } + + return ret; +} + +static ssize_t scsc_bt_shm_ant_read_data(char __user *buf, size_t len) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + + while (ant_service.read_operation == ANT_READ_OP_NONE && + ret == 0 && + ant_service.mailbox_data_ctr_driv_read != ant_service.mailbox_data_ctr_driv_write) { + /* Start a data copy to userspace */ + ant_service.read_operation = ANT_READ_OP_DATA; + ant_service.read_index = ant_service.mailbox_data_ctr_driv_read; + ret = scsc_ant_data_read(&buf[consumed], len - consumed); + if (ret > 0) { + /* All good - Update our consumed information */ + consumed += ret; + ret = 0; + + /* Update the index if all the data could be copied to the userspace buffer + * otherwise stop processing the data + */ + if (ant_service.read_operation == ANT_READ_OP_NONE) + BSMHCP_INCREASE_INDEX(ant_service.mailbox_data_ctr_driv_read, + ASMHCP_TRANSFER_RING_DATA_SIZE); + else + break; + } + } + + return ret == 0 ? consumed : ret; +} + +static ssize_t scsc_bt_shm_ant_read_cmd(char __user *buf, size_t len) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + + while (ant_service.read_operation == ANT_READ_OP_NONE && + ret == 0 && + ant_service.mailbox_cmd_ctr_driv_read != ant_service.mailbox_cmd_ctr_driv_write) { + /* Start a cmd copy to userspace */ + ant_service.read_operation = ANT_READ_OP_CMD; + ant_service.read_index = ant_service.mailbox_cmd_ctr_driv_read; + ret = scsc_ant_cmd_read(&buf[consumed], len - consumed); + if (ret > 0) { + /* All good - Update our consumed information */ + consumed += ret; + ret = 0; + + /* Update the index if all the data could be copied to the userspace buffer + * otherwise stop processing the cmds + */ + if (ant_service.read_operation == ANT_READ_OP_NONE) + BSMHCP_INCREASE_INDEX(ant_service.mailbox_cmd_ctr_driv_read, + ASMHCP_TRANSFER_RING_CMD_SIZE); + else + break; + } + } + + return ret == 0 ? consumed : ret; +} + +static ssize_t scsc_shm_ant_read_continue(char __user *buf, size_t len) +{ + ssize_t ret = 0; + + /* Is a cmd read operation ongoing */ + if (ant_service.read_operation == ANT_READ_OP_CMD) { + SCSC_TAG_DEBUG(BT_H4, "ANT_READ_OP_CMD\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_ant_cmd_read(buf, len); + if (ant_service.read_operation == ANT_READ_OP_NONE) + /* All done - increase the read pointer and continue */ + if (ant_service.read_operation == ANT_READ_OP_NONE) + BSMHCP_INCREASE_INDEX(ant_service.mailbox_cmd_ctr_driv_read, + ASMHCP_TRANSFER_RING_CMD_SIZE); + /* Is a data read operation ongoing */ + } else if (ant_service.read_operation == ANT_READ_OP_DATA) { + SCSC_TAG_DEBUG(BT_H4, "ANT_READ_OP_DATA\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_ant_data_read(buf, len); + if (ant_service.read_operation == ANT_READ_OP_NONE) + /* All done - increase the read pointer and continue */ + BSMHCP_INCREASE_INDEX(ant_service.mailbox_data_ctr_driv_read, ASMHCP_TRANSFER_RING_DATA_SIZE); + } + + return ret; +} + +ssize_t scsc_shm_ant_read(struct file *file, char __user *buf, size_t len, loff_t *offset) +{ + ssize_t consumed = 0; + ssize_t ret = 0; + ssize_t res; + bool gen_bg_int = false; + + /* Only 1 reader is allowed */ + if (atomic_inc_return(&ant_service.ant_readers) != 1) { + atomic_dec(&ant_service.ant_readers); + return -EIO; + } + + /* Has en error been detect then just return with an error */ + if (atomic_read(&ant_service.error_count) != 0) { + atomic_dec(&ant_service.ant_readers); + return -EIO; + } + + /* Update the cached variables with the non-cached variables */ + ant_service.mailbox_cmd_ctr_driv_write = ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_write; + ant_service.mailbox_data_ctr_driv_write = ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_write; + + /* put the remaining data from the transfer ring into the available userspace buffer */ + if (ant_service.read_operation != ANT_READ_OP_NONE) { + ret = scsc_shm_ant_read_continue(buf, len); + /* Update the consumed variable in case a operation was ongoing */ + if (ret > 0) { + consumed = ret; + ret = 0; + } + } + + /* Main loop - Can only be entered when no operation is present on entering this function + * or no hardware error has been detected. It loops until data has been placed in the + * userspace buffer or an error has been detected + */ + while (atomic_read(&ant_service.error_count) == 0 && consumed == 0) { + /* Does any of the read/write pairs differs */ + if (ant_service.mailbox_data_ctr_driv_read == ant_service.mailbox_data_ctr_driv_write && + ant_service.mailbox_cmd_ctr_driv_read == ant_service.mailbox_cmd_ctr_driv_write && + atomic_read(&ant_service.error_count) == 0 && + ant_service.asmhcp_protocol->header.panic_deathbed_confession == 0) { + /* Don't wait if in NONBLOCK mode */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + /* All read/write pairs are identical - wait for the firmware. The conditional + * check is used to verify that a read/write pair has actually changed + */ + ret = wait_event_interruptible(bt_service.read_wait, + (ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_write != + ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_read || + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_write != + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_read || + atomic_read(&ant_service.error_count) != 0 || + ant_service.asmhcp_protocol->header.panic_deathbed_confession)); + + /* Has an error been detected elsewhere in the driver then just return from this function */ + if (atomic_read(&ant_service.error_count) != 0) + break; + + /* Any failures is handled by the userspace application */ + if (ret) + break; + + /* Refresh our write indexes before starting to process the protocol */ + ant_service.mailbox_cmd_ctr_driv_write = + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_write; + ant_service.mailbox_data_ctr_driv_write = + ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_write; + } + + /* First: process any pending cmd that needs to be sent to userspace */ + res = scsc_bt_shm_ant_read_cmd(&buf[consumed], len - consumed); + if (res > 0) + consumed += res; + else + ret = res; + + /* Second: process any pending data that needs to be sent to userspace */ + res = scsc_bt_shm_ant_read_data(&buf[consumed], len - consumed); + if (res > 0) + consumed += res; + else + ret = res; + } + + /* If anything was read, generate the appropriate interrupt(s) */ + if (ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_read != ant_service.mailbox_cmd_ctr_driv_read || + ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_read != ant_service.mailbox_data_ctr_driv_read) + gen_bg_int = true; + + /* Update the read index for all transfer rings */ + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_read = ant_service.mailbox_cmd_ctr_driv_read; + ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_read = ant_service.mailbox_data_ctr_driv_read; + + /* Ensure the data is updating correctly in memory */ + mmiowb(); + + if (gen_bg_int) + scsc_service_mifintrbit_bit_set(ant_service.service, + ant_service.asmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + + /* Decrease the ant readers counter */ + atomic_dec(&ant_service.ant_readers); + + return ret == 0 ? consumed : ret; +} + +ssize_t scsc_shm_ant_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + size_t length; + size_t ant_pkt_len; + ssize_t written = 0; + ssize_t ret = 0; + size_t pkt_count = 0; + + SCSC_TAG_DEBUG(BT_H4, "enter\n"); + + UNUSED(file); + UNUSED(offset); + + /* Only 1 writer is allowed */ + if (atomic_inc_return(&ant_service.ant_writers) != 1) { + SCSC_TAG_DEBUG(BT_H4, "only one reader allowed\n"); + atomic_dec(&ant_service.ant_writers); + return -EIO; + } + + /* Has en error been detect then just return with an error */ + if (atomic_read(&ant_service.error_count) != 0) { + SCSC_TAG_DEBUG(BT_H4, "error has occured\n"); + atomic_dec(&ant_service.ant_writers); + return -EIO; + } + + while (written != count && ret == 0) { + length = min(count - written, sizeof(ant_write_buffer) - ant_service.ant_write_offset); + SCSC_TAG_DEBUG(BT_H4, "count: %zu, length: %zu, ant_write_offset: %zu, written:%zu, size:%zu\n", + count, length, ant_service.ant_write_offset, + written - (pkt_count * 2), sizeof(ant_write_buffer)); + + /* Is there room in the temp buffer */ + if (length == 0) { + SCSC_TAG_ERR(BT_H4, "no room in the buffer\n"); + atomic_inc(&ant_service.error_count); + ret = -EIO; + break; + } + + /* Copy the userspace data to the target buffer */ + ret = copy_from_user(&ant_write_buffer[ant_service.ant_write_offset], &buf[written], length); + + if (ret == 0) { + /* Is the message a data message? */ + if (ant_write_buffer[0] == ANT_DATA_MSG) { + /* Extract the data packet length */ + ant_pkt_len = ant_write_buffer[1] + ANT_HEADER_LENGTH + 1; + + /* Is it a complete packet available */ + if (ant_pkt_len <= (length + ant_service.ant_write_offset)) { + /* Transfer the packet to the ANT data transfer ring */ + ret = scsc_shm_ant_data_write(&ant_write_buffer[2], ant_pkt_len - 2); + if (ret >= 0) { + written += (ant_pkt_len - ant_service.ant_write_offset); + pkt_count += 1; + ant_service.ant_write_offset = 0; + ret = 0; + } + } else { + /* Still needing data to have the complete packet */ + SCSC_TAG_WARNING(BT_H4, + "missing data (need=%zu, got=%zu)\n", + ant_pkt_len, (length - ant_service.ant_write_offset)); + written += length; + ant_service.ant_write_offset += (u32) length; + } + /* Is the message a command message? */ + } else if (ant_write_buffer[0] == ANT_COMMAND_MSG) { + /* Extract the ANT command packet length */ + ant_pkt_len = ant_write_buffer[1] + ANT_HEADER_LENGTH + 1; + + /* Is it a complete packet available */ + if ((ant_pkt_len) <= (length + ant_service.ant_write_offset)) { + /* Transfer the packet to the ANT command transfer ring */ + ret = scsc_shm_ant_cmd_write(&ant_write_buffer[2], ant_pkt_len - 2); + if (ret >= 0) { + written += (ant_pkt_len - ant_service.ant_write_offset); + pkt_count += 1; + ant_service.ant_write_offset = 0; + ret = 0; + } + } else { + /* Still needing data to have the complete packet */ + SCSC_TAG_WARNING(BT_H4, + "missing data (need=%zu, got=%zu)\n", + (ant_pkt_len), (length + ant_service.ant_write_offset)); + written += length; + ant_service.ant_write_offset += (u32) length; + } + /* Is there less data than a header then just wait for more */ + } else if (length <= ANT_HEADER_LENGTH) { + ant_service.ant_write_offset += length; + written += length; + /* Header is unknown - unable to proceed */ + } else { + atomic_inc(&ant_service.error_count); + ret = -EIO; + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_from_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + SCSC_TAG_DEBUG(BT_H4, "ant_write_offset=%zu, ret=%zu, written=%zu\n", + ant_service.ant_write_offset, ret, written - (pkt_count * 2)); + + /* Decrease the ant readers counter */ + atomic_dec(&ant_service.ant_writers); + + return ret == 0 ? written : ret; +} + +unsigned int scsc_shm_ant_poll(struct file *file, poll_table *wait) +{ + /* Add the wait queue to the polling queue */ + poll_wait(file, &ant_service.read_wait, wait); + + if (atomic_read(&ant_service.error_count) != 0) + return POLLERR; + + /* Has en error been detect then just return with an error */ + if (ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_write != + ant_service.asmhcp_protocol->header.mailbox_data_ctr_driv_read || + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_write != + ant_service.asmhcp_protocol->header.mailbox_cmd_ctr_driv_read) { + SCSC_TAG_DEBUG(BT_H4, "queue(s) changed\n"); + return POLLIN | POLLRDNORM; /* readeable */ + } + + SCSC_TAG_DEBUG(BT_H4, "no change\n"); + + return (atomic_read(&ant_service.error_count) != 0) ? POLLERR : POLLOUT; +} + +/* Initialise the shared memory interface for ANT */ +int scsc_ant_shm_init(void) +{ + /* Get kmem pointer to the shared memory ref */ + ant_service.asmhcp_protocol = scsc_mx_service_mif_addr_to_ptr(ant_service.service, ant_service.asmhcp_ref); + if (ant_service.asmhcp_protocol == NULL) { + SCSC_TAG_ERR(BT_COMMON, "couldn't map kmem to shm_ref 0x%08x\n", (u32)ant_service.asmhcp_ref); + return -ENOMEM; + } + + /* Clear the protocol shared memory area */ + memset(ant_service.asmhcp_protocol, 0, sizeof(*ant_service.asmhcp_protocol)); + ant_service.asmhcp_protocol->header.magic_value = ASMHCP_PROTOCOL_MAGICVALUE; + ant_service.mailbox_data_ctr_driv_read = 0; + ant_service.mailbox_data_ctr_driv_write = 0; + ant_service.mailbox_cmd_ctr_driv_read = 0; + ant_service.mailbox_cmd_ctr_driv_write = 0; + ant_service.read_index = 0; + + /* Initialise the interrupt handlers */ + scsc_ant_shm_init_interrupt(); + + return 0; +} + +/* Terminate the shared memory interface for ANT, stopping its thread. + * + * Note: The service must be stopped prior to calling this function. + * The shared memory can only be released after calling this function. + */ +void scsc_ant_shm_exit(void) +{ + /* Release IRQs */ + if (ant_service.asmhcp_protocol != NULL) { + scsc_service_mifintrbit_unregister_tohost( + ant_service.service, + ant_service.asmhcp_protocol->header.bg_to_ap_int_src); + + scsc_service_mifintrbit_free_fromhost( + ant_service.service, + ant_service.asmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } + + /* Clear all control structures */ + ant_service.asmhcp_protocol = NULL; +} diff --git a/drivers/misc/samsung/scsc_bt/scsc_avdtp_detect.c b/drivers/misc/samsung/scsc_bt/scsc_avdtp_detect.c new file mode 100644 index 000000000000..c7418efd1f57 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_avdtp_detect.c @@ -0,0 +1,807 @@ +/**************************************************************************** + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + ****************************************************************************/ + +/* Before submitting new changes to this file please make sure to run the module tests to verify + * that the change didn't break anything. Also, make sure to write new tests that captures the + * change. The module tests can be found in "vendor/samsung_slsi/scsc_tools/kernel_unit_test/" + * from where there are run with "make". If needed its git project nane is: + * "Connectivity/Android/platform/vendor/samsung_slsi/scsc_tools/kernel_unit_test" */ + +/* MX BT shared memory interface */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "scsc_bt_priv.h" +#include "scsc_shm.h" +/** + * Coex AVDTP detection. + * + * Strategy: + * + * - On the L2CAP signaling CID, look for connect requests with the AVDTP PSM + * + * - Assume the first AVDTP connection is the signaling channel. + * (AVDTP 1.3, section 5.4.6 "Transport and Signaling Channel Establishment") + * + * - If a signaling channel exists, assume the next connection is the streaming channel + * + * - If a streaming channel exists, look for AVDTP start, suspend, abort and close signals + * -- When one of these is found, signal the FW with updated acl_id and cid + * + * - If the ACL is torn down, make sure to clean up. + * + * */ + +#define IS_VALID_CID_CONN_RESP(is_tx, avdtp, data) ((is_tx && avdtp->dst_cid == HCI_L2CAP_SOURCE_CID(data)) || \ + (!is_tx && avdtp->src_cid == HCI_L2CAP_SOURCE_CID(data))) + + +#define IS_VALID_CID_DISCONNECT_REQ(is_tx, avdtp, data) ((is_tx && avdtp.src_cid == HCI_L2CAP_SOURCE_CID(data) && \ + avdtp.dst_cid == HCI_L2CAP_RSP_DEST_CID(data)) || \ + (!is_tx && avdtp.src_cid == HCI_L2CAP_RSP_DEST_CID(data) && \ + avdtp.dst_cid == HCI_L2CAP_SOURCE_CID(data))) + +#define STORE_DETECTED_CID_CONN_REQ(is_tx, avdtp, data) \ + do { \ + if (is_tx) { \ + avdtp->src_cid = HCI_L2CAP_SOURCE_CID(data); \ + } else { \ + avdtp->dst_cid = HCI_L2CAP_SOURCE_CID(data); \ + } \ + } while (0) + +#define STORE_DETECTED_CID_CONN_RESP(is_tx, avdtp, data) \ + do { \ + if (is_tx) { \ + avdtp->src_cid = HCI_L2CAP_RSP_DEST_CID(data); \ + } else { \ + avdtp->dst_cid = HCI_L2CAP_RSP_DEST_CID(data); \ + } \ + } while (0) + +#ifdef ENABLE_MODULE_TESTS_DEBUG +#undef SCSC_TAG_DEBUG +#define SCSC_TAG_DEBUG(tag, fmt, ...) \ + fprintf(stdout, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__) +#endif + +/* Forward declarations */ +void scsc_avdtp_detect_reset(struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci, + bool reset_signal, + bool reset_signal_ongoing, + bool reset_stream, + bool reset_stream_ongoing, + bool reset_local_seids, + bool reset_remote_seids); + +/* Simple list traversal to find an existing AVDTP detection from a hci connection handle. If + * not found, the function returns NULL + */ +static struct scsc_bt_avdtp_detect_hci_connection *scsc_avdtp_detect_search_hci_connection( + u16 hci_connection_handle) +{ + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci; + + avdtp_hci = bt_service.avdtp_detect.connections; + while (avdtp_hci) { + if (avdtp_hci->hci_connection_handle == hci_connection_handle) { + /* Found it */ + break; + } + avdtp_hci = avdtp_hci->next; + } + return avdtp_hci; +} + +/* Find existing detection for a given connection handle. If provided as argument, a new detection + * will be created if it doesn't exist. The returned element is locked and must be unlocked before + * moving to another context. + */ +static struct scsc_bt_avdtp_detect_hci_connection *scsc_avdtp_detect_find_or_create_hci_connection( + u16 hci_connection_handle, + bool create) +{ + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci; + + spin_lock(&bt_service.avdtp_detect.lock); + + avdtp_hci = scsc_avdtp_detect_search_hci_connection(hci_connection_handle); + if (avdtp_hci) + spin_lock(&avdtp_hci->lock); + + /* Unlock the list again and check if a new element must be created. If that is the case, + * malloc the new memory and lock the list afterwards such that others are not spinning for + * some undefined amount of time. The trade-off in this construction is that the memory + * potentially is allocated twice in rare situations, and the list must therefore be locked + * and searched again before inserting. + */ + spin_unlock(&bt_service.avdtp_detect.lock); + + /* Check if the existing detection was found. If not create it */ + if (!avdtp_hci && create) { + avdtp_hci = kmalloc(sizeof(struct scsc_bt_avdtp_detect_hci_connection), GFP_KERNEL); + if (avdtp_hci) { + struct scsc_bt_avdtp_detect_hci_connection *head; + struct scsc_bt_avdtp_detect_hci_connection *recheck_avdtp_hci; + + memset(avdtp_hci, 0, sizeof(struct scsc_bt_avdtp_detect_hci_connection)); + avdtp_hci->signal.type = BT_AVDTP_CONN_TYPE_SIGNAL; + avdtp_hci->stream.type = BT_AVDTP_CONN_TYPE_STREAM; + avdtp_hci->ongoing.incoming_signal.type = BT_AVDTP_CONN_TYPE_SIGNAL; + avdtp_hci->ongoing.outgoing_signal.type = BT_AVDTP_CONN_TYPE_SIGNAL; + avdtp_hci->ongoing.incoming_stream.type = BT_AVDTP_CONN_TYPE_STREAM; + avdtp_hci->ongoing.outgoing_stream.type = BT_AVDTP_CONN_TYPE_STREAM; + avdtp_hci->signal.state = BT_AVDTP_STATE_IDLE_SIGNALING; + avdtp_hci->signal.src_cid = 0; + avdtp_hci->signal.dst_cid = 0; + avdtp_hci->hci_connection_handle = hci_connection_handle; + scsc_avdtp_detect_reset(avdtp_hci, false, true, true, true, true, true); + + /* The element is ready for insertion into the list. Recheck the list to make sure that + * the hci handle hasn't been detected meanwhile. + */ + spin_lock(&bt_service.avdtp_detect.lock); + + recheck_avdtp_hci = scsc_avdtp_detect_search_hci_connection(hci_connection_handle); + if (recheck_avdtp_hci == NULL) { + /* Insert into list */ + spin_lock_init(&avdtp_hci->lock); + spin_lock(&avdtp_hci->lock); + head = bt_service.avdtp_detect.connections; + bt_service.avdtp_detect.connections = avdtp_hci; + avdtp_hci->next = head; + spin_unlock(&bt_service.avdtp_detect.lock); + } else { + /* The element was already present. Free the allocated memory and return the found + * element. + */ + spin_lock(&recheck_avdtp_hci->lock); + spin_unlock(&bt_service.avdtp_detect.lock); + kfree(avdtp_hci); + avdtp_hci = NULL; + avdtp_hci = recheck_avdtp_hci; + } + } + } + return avdtp_hci; +} + +/* Find an existing l2cap connection struct. Works for both signal and stream since their internal + * structures are equal */ +static struct scsc_bt_avdtp_detect_connection *scsc_avdtp_detect_find_l2cap_connection(struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci, + enum scsc_bt_avdtp_detect_conn_req_direction_enum direction) +{ + struct scsc_bt_avdtp_detect_connection *avdtp_l2cap = NULL; + + /* Return either signal or stream l2cap connection */ + if (avdtp_hci) { + /* Check if there is already a signal connection and return the ongoing stream */ + if (avdtp_hci->signal.state == BT_AVDTP_STATE_COMPLETE_SIGNALING) { + if (direction == BT_AVDTP_CONN_REQ_DIR_OUTGOING) + avdtp_l2cap = &avdtp_hci->ongoing.outgoing_stream; + else + avdtp_l2cap = &avdtp_hci->ongoing.incoming_stream; + } else { + /* If not, use the ongoing signal */ + if (direction == BT_AVDTP_CONN_REQ_DIR_OUTGOING) + avdtp_l2cap = &avdtp_hci->ongoing.outgoing_signal; + else + avdtp_l2cap = &avdtp_hci->ongoing.incoming_signal; + } + } + return avdtp_l2cap; +} + +/* Handle CONNECTION_REQUEST and detect signal or stream connections. The function handles both RX and TX, where + * connect requests in TX direction is mapped to "outgoing". RX direction is mapped to "incoming" */ +static void scsc_bt_avdtp_detect_connection_conn_req_handling(uint16_t hci_connection_handle, + const unsigned char *data, + uint16_t length, + bool is_tx) +{ + struct scsc_bt_avdtp_detect_connection *avdtp_l2cap = NULL; + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci = NULL; + + /* Ignore everything else than the PSM */ + if (HCI_L2CAP_CON_REQ_PSM(data) == L2CAP_AVDTP_PSM) { + // Check if there is already an detection for the given hci handle + avdtp_hci = scsc_avdtp_detect_find_or_create_hci_connection( + hci_connection_handle, + true); + + avdtp_l2cap = scsc_avdtp_detect_find_l2cap_connection(avdtp_hci, + is_tx ? BT_AVDTP_CONN_REQ_DIR_OUTGOING : BT_AVDTP_CONN_REQ_DIR_INCOMING); + + if (avdtp_hci && avdtp_l2cap) { + if (avdtp_l2cap->state == BT_AVDTP_STATE_IDLE_SIGNALING) { + + /* AVDTP signal channel was detected - store dst_cid or src_cid depending on the transmit + * direction, and store the connection_handle. */ + STORE_DETECTED_CID_CONN_REQ(is_tx, avdtp_l2cap, data); + avdtp_l2cap->state = BT_AVDTP_STATE_PENDING_SIGNALING; + SCSC_TAG_DEBUG(BT_H4, "Signaling dst CID: 0x%04X, src CID: 0x%04X, aclid: 0x%04X (tx=%u)\n", + avdtp_l2cap->dst_cid, + avdtp_l2cap->src_cid, + avdtp_hci->hci_connection_handle, + is_tx); + } else if (avdtp_l2cap->state == BT_AVDTP_STATE_IDLE_STREAMING && + avdtp_hci->signal.state == BT_AVDTP_STATE_COMPLETE_SIGNALING) { + + /* AVDTP stream channel was detected - store dst_cid or src_cid depending on the transmit + * direction. */ + STORE_DETECTED_CID_CONN_REQ(is_tx, avdtp_l2cap, data); + avdtp_l2cap->state = BT_AVDTP_STATE_PENDING_STREAMING; + SCSC_TAG_DEBUG(BT_H4, "Streaming dst CID: 0x%04X, src CID: 0x%04X, aclid: 0x%04X (%u)\n", + avdtp_l2cap->dst_cid, + avdtp_l2cap->src_cid, + avdtp_hci->hci_connection_handle, + is_tx); + } + } + if (avdtp_hci) + spin_unlock(&avdtp_hci->lock); + } +} + +/* Handle CONNECTION_REPSONS and detect signal or stream connections. The function handles both RX and TX, where + * connect requests in TX direction is mapped to "incoming". RX direction is mapped to "outgoing" */ +static void scsc_bt_avdtp_detect_connection_conn_resp_handling(uint16_t hci_connection_handle, + const unsigned char *data, + uint16_t length, + bool is_tx) +{ + + /* Check if there is already a signal connection */ + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci = + scsc_avdtp_detect_find_or_create_hci_connection(hci_connection_handle, false); + struct scsc_bt_avdtp_detect_connection *avdtp_l2cap = + scsc_avdtp_detect_find_l2cap_connection(avdtp_hci, + is_tx ? BT_AVDTP_CONN_REQ_DIR_INCOMING : BT_AVDTP_CONN_REQ_DIR_OUTGOING); + /* Only consider RSP on expected connection handle */ + if (avdtp_hci && avdtp_l2cap) { + if (HCI_L2CAP_CON_RSP_RESULT(data) == HCI_L2CAP_CON_RSP_RESULT_SUCCESS) { + if (IS_VALID_CID_CONN_RESP(is_tx, avdtp_l2cap, data) && + avdtp_l2cap->state == BT_AVDTP_STATE_PENDING_SIGNALING) { + + /* If we were waiting to complete an AVDTP signal detection - store the dst_cid or src_cid depending + * on the transmit direction */ + STORE_DETECTED_CID_CONN_RESP(is_tx, avdtp_l2cap, data); + avdtp_l2cap->state = BT_AVDTP_STATE_COMPLETE_SIGNALING; + + /* Switch to use "signal" and delete "ongoing" since the AVDTP signaling has now been + * detected */ + avdtp_hci->signal = *avdtp_l2cap; + scsc_avdtp_detect_reset(avdtp_hci, false, true, false, false, false, false); + SCSC_TAG_DEBUG(BT_H4, "Signaling dst CID: 0x%04X, src CID: 0x%04X, aclid: 0x%04X (tx=%u)\n", + avdtp_hci->signal.dst_cid, + avdtp_hci->signal.src_cid, + avdtp_hci->hci_connection_handle, + is_tx); + + } else if (IS_VALID_CID_CONN_RESP(is_tx, avdtp_l2cap, data) && + avdtp_l2cap->state == BT_AVDTP_STATE_PENDING_STREAMING) { + + /* If we were waiting to complete an AVDTP stream detection - store the dst_cid or src_cid depending + * on the transmit direction */ + STORE_DETECTED_CID_CONN_RESP(is_tx, avdtp_l2cap, data); + avdtp_l2cap->state = BT_AVDTP_STATE_COMPLETE_STREAMING; + + /* Switch to use "stream". If both an incoming and outgoing connection response was "expected" + * the first one wins. */ + avdtp_hci->stream = *avdtp_l2cap; + scsc_avdtp_detect_reset(avdtp_hci, false, false, false, true, false, false); + + SCSC_TAG_DEBUG(BT_H4, "Streaming dst CID: 0x%04X, src CID: 0x%04X, aclid: 0x%04X (tx=%u)\n", + avdtp_hci->stream.dst_cid, + avdtp_hci->stream.src_cid, + avdtp_hci->hci_connection_handle, + is_tx); + } + } else if (HCI_L2CAP_CON_RSP_RESULT(data) >= HCI_L2CAP_CON_RSP_RESULT_REFUSED) { + /* In case of a CONN_REFUSED the existing CIDs must be cleaned up such that the detection is ready + * for a new connection request */ + if (IS_VALID_CID_CONN_RESP(is_tx, avdtp_l2cap, data) && + avdtp_l2cap->state == BT_AVDTP_STATE_PENDING_SIGNALING) { + avdtp_l2cap->dst_cid = avdtp_l2cap->src_cid = 0; + avdtp_l2cap->state = BT_AVDTP_STATE_IDLE_SIGNALING; + + } else if (IS_VALID_CID_CONN_RESP(is_tx, avdtp_l2cap, data) && + avdtp_l2cap->state == BT_AVDTP_STATE_PENDING_STREAMING) { + + /* Connection refused on streaming connect request. Reset dst_cid and src_cid, and + * reset the state to IDLE such that new connection requests can be detected */ + avdtp_l2cap->dst_cid = avdtp_l2cap->src_cid = 0; + avdtp_l2cap->state = BT_AVDTP_STATE_IDLE_STREAMING; + + } + } + } + if (avdtp_hci) + spin_unlock(&avdtp_hci->lock); +} + +/* Handle DISCONNECT_REQUEST and remove all current detections on the specific CIDs */ +static bool scsc_bt_avdtp_detect_connection_disconnect_req_handling(uint16_t hci_connection_handle, + const unsigned char *data, + uint16_t length, + bool is_tx) +{ + bool result = false; + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci = + scsc_avdtp_detect_find_or_create_hci_connection(hci_connection_handle, false); + + if (avdtp_hci) { + if (avdtp_hci->signal.state == BT_AVDTP_STATE_COMPLETE_SIGNALING && + IS_VALID_CID_DISCONNECT_REQ(is_tx, avdtp_hci->signal, data)) { + + /* Disconnect the current registered signaling and streaming AVDTP connection */ + scsc_avdtp_detect_reset(avdtp_hci, true, true, true, true, true, true); + + /* The detection was removed and it can therefore not be unlocked */ + avdtp_hci = NULL; + + SCSC_TAG_DEBUG(BT_H4, "Signaling src CID disconnected (aclid: 0x%04X) (TX=%u)\n", + hci_connection_handle, + is_tx); + } else if (avdtp_hci->stream.state == BT_AVDTP_STATE_COMPLETE_STREAMING && + IS_VALID_CID_DISCONNECT_REQ(is_tx, avdtp_hci->stream, data)) { + + /* Disconnect the current registered streaming AVDTP connection */ + scsc_avdtp_detect_reset(avdtp_hci, false, false, true, true, false, false); + + SCSC_TAG_DEBUG(BT_H4, "Streaming src CID disconnected (aclid: 0x%04X) (TX=%u)\n", + hci_connection_handle, + is_tx); + result = true; + } + if (avdtp_hci) + spin_unlock(&avdtp_hci->lock); + } + return result; +} + +/* Detects if there is any of the L2CAP codes of interrest, and returns true of the FW should be signalled a change */ +static bool scsc_bt_avdtp_detect_connection_rxtx(uint16_t hci_connection_handle, const unsigned char *data, uint16_t length, bool is_tx) +{ + uint8_t code = 0; + if (length < AVDTP_DETECT_MIN_DATA_LENGTH) { + SCSC_TAG_DEBUG(BT_H4, "Ignoring L2CAP signal, length %u)\n", length); + return false; + } + + code = HCI_L2CAP_CODE(data); + switch (code) { + + case L2CAP_CODE_CONNECT_REQ: + { + /* Handle connection request */ + scsc_bt_avdtp_detect_connection_conn_req_handling(hci_connection_handle, data, length, is_tx); + break; + } + case L2CAP_CODE_CONNECT_RSP: + { + if (length < AVDTP_DETECT_MIN_DATA_LENGTH_CON_RSP) { + SCSC_TAG_WARNING(BT_H4, "Ignoring L2CAP CON RSP in short packet, length %u)\n", length); + return false; + } + /* Handle connection response */ + scsc_bt_avdtp_detect_connection_conn_resp_handling(hci_connection_handle, data, length, is_tx); + break; + } + case L2CAP_CODE_DISCONNECT_REQ: + { + /* Handle disconnect request */ + return scsc_bt_avdtp_detect_connection_disconnect_req_handling(hci_connection_handle, data, length, is_tx); + break; + } + default: + break; + } + return false; +} + +/* Check if there are any SEIDs from the discover that are SINK, and store them as SINK candidates */ +static void scsc_avdtp_detect_check_discover_for_snk_seids(uint16_t hci_connection_handle, + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci, + const unsigned char *data, + uint16_t length, + bool is_tx) +{ + uint16_t i = 0; + uint16_t n_seid_info = (length - HCI_L2CAP_CONF_SEID_OFFSET) / HCI_L2CAP_CONF_SEID_INFO_SIZE; + struct scsc_bt_avdtp_detect_snk_seid *seid = NULL; + + /* Remove potential existing SEID infos on the either local or remote */ + scsc_avdtp_detect_reset(avdtp_hci, false, false, false, false, is_tx, !is_tx); + for (i = 0; i < n_seid_info; i++) { + /* Only consider SEID if it's type is SINK. This means that for TX, look for TSEP equal to SNK. + * For RX look for TSEP equal to SRC, since this would result in our side being SNK */ + if ((is_tx && HCI_L2CAP_CONF_TSEP(data, i) == HCI_L2CAP_CONF_TSEP_SNK) || + (!is_tx && HCI_L2CAP_CONF_TSEP(data, i) == HCI_L2CAP_CONF_TSEP_SRC)) { + + if (avdtp_hci) + spin_unlock(&avdtp_hci->lock); + + seid = kmalloc(sizeof(struct scsc_bt_avdtp_detect_snk_seid), GFP_KERNEL); + + avdtp_hci = scsc_avdtp_detect_find_or_create_hci_connection(hci_connection_handle, + false); + + if (avdtp_hci && seid) { + memset(seid, 0, sizeof(struct scsc_bt_avdtp_detect_snk_seid)); + seid->seid = HCI_L2CAP_CONF_SEID(data, i); + SCSC_TAG_DEBUG(BT_H4, "Storing seid=%u as candidate for SINK, aclid: 0x%04X\n", + seid->seid, + avdtp_hci->hci_connection_handle); + /* Store the information on either local or remote */ + if (is_tx) { + if (avdtp_hci->tsep_detect.local_snk_seids) + seid->next = avdtp_hci->tsep_detect.local_snk_seids; + avdtp_hci->tsep_detect.local_snk_seids = seid; + } else { + if (avdtp_hci->tsep_detect.remote_snk_seids) + seid->next = avdtp_hci->tsep_detect.remote_snk_seids; + avdtp_hci->tsep_detect.remote_snk_seids = seid; + } + } else + kfree(seid); + } + } +} + +/* Check if the set configuration matches any of the SINK candidates */ +static void scsc_avdtp_detect_match_set_conf_seid_with_discover(struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci, + const unsigned char *data, + bool is_tx) +{ + struct scsc_bt_avdtp_detect_snk_seid *seid_info; + uint8_t candidate = 0; + + /* Default to SRC */ + avdtp_hci->tsep_detect.tsep = BT_AVDTP_TSEP_SRC; + + if (is_tx) { + seid_info = avdtp_hci->tsep_detect.local_snk_seids; + candidate = avdtp_hci->tsep_detect.local_snk_seid_candidate; + } else { + seid_info = avdtp_hci->tsep_detect.remote_snk_seids; + candidate = avdtp_hci->tsep_detect.remote_snk_seid_candidate; + } + + while (seid_info) { + if (seid_info->seid == candidate) { + /* SINK was detected */ + avdtp_hci->tsep_detect.tsep = BT_AVDTP_TSEP_SNK; + break; + } + seid_info = seid_info->next; + } + /* Clear the canditate SEID since it has now been checked */ + avdtp_hci->tsep_detect.local_snk_seid_candidate = 0; + avdtp_hci->tsep_detect.remote_snk_seid_candidate = 0; + SCSC_TAG_DEBUG(BT_H4, "TSEP for active stream, snk=%d, aclid=0x%04X\n", + avdtp_hci->tsep_detect.tsep, + avdtp_hci->hci_connection_handle); +} + +/* Detects if the AVDTP signal leads to a state change that the FW should know */ +static uint8_t scsc_avdtp_detect_signaling_rxtx(uint16_t hci_connection_handle, + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci, + const unsigned char *data, + uint16_t length, + bool is_tx) +{ + u8 signal_id = AVDTP_SIGNAL_ID(data); + u8 message_type = AVDTP_MESSAGE_TYPE(data); + + SCSC_TAG_DEBUG(BT_H4, "id: 0x%02X, type: 0x%02X)\n", signal_id, message_type); + + if (message_type == AVDTP_MESSAGE_TYPE_RSP_ACCEPT) { + if (signal_id == AVDTP_SIGNAL_ID_START) + return AVDTP_DETECT_SIGNALING_ACTIVE; + else if (signal_id == AVDTP_SIGNAL_ID_OPEN) + return AVDTP_DETECT_SIGNALING_OPEN; + else if (signal_id == AVDTP_SIGNAL_ID_CLOSE || signal_id == AVDTP_SIGNAL_ID_SUSPEND || + signal_id == AVDTP_SIGNAL_ID_ABORT) + return AVDTP_DETECT_SIGNALING_INACTIVE; + else if (signal_id == AVDTP_SIGNAL_ID_DISCOVER) { + /* Check the discover signal for potential SNK candidate SEIDs */ + scsc_avdtp_detect_check_discover_for_snk_seids(hci_connection_handle, + avdtp_hci, data, length, is_tx); + } else if (signal_id == AVDTP_SIGNAL_ID_SET_CONF) { + /* Check if the SEID from set config matches a SNK SEID */ + scsc_avdtp_detect_match_set_conf_seid_with_discover(avdtp_hci, data, is_tx); + } + } else if (message_type == AVDTP_MESSAGE_TYPE_CMD) { + if (signal_id == AVDTP_SIGNAL_ID_SET_CONF) { + if (is_tx) + avdtp_hci->tsep_detect.remote_snk_seid_candidate = HCI_L2CAP_SET_CONF_ACP_SEID(data); + else + avdtp_hci->tsep_detect.local_snk_seid_candidate = HCI_L2CAP_SET_CONF_ACP_SEID(data); + SCSC_TAG_DEBUG(BT_H4, "Set configuration was detected; local_seid_candidate=%u, remote_seid_candidate=%u (aclid: 0x%04X)\n", + avdtp_hci->tsep_detect.local_snk_seid_candidate, + avdtp_hci->tsep_detect.remote_snk_seid_candidate, + avdtp_hci->hci_connection_handle); + } + } else if (message_type == AVDTP_MESSAGE_TYPE_GENERAL_REJECT || message_type == AVDTP_MESSAGE_TYPE_RSP_REJECT) { + if (signal_id == AVDTP_SIGNAL_ID_SET_CONF) { + if (is_tx) { + if (avdtp_hci->tsep_detect.local_snk_seid_candidate) + avdtp_hci->tsep_detect.local_snk_seid_candidate = 0; + } else { + if (avdtp_hci->tsep_detect.remote_snk_seid_candidate) + avdtp_hci->tsep_detect.remote_snk_seid_candidate = 0; + } + } + } + return AVDTP_DETECT_SIGNALING_IGNORE; +} + +/* Public function that hooks into scsc_shm.c. It pass the provided data on the proper functions such that + * the AVDTP can be detected. If any state change is detected, the FW is signalled */ +void scsc_avdtp_detect_rxtx(u16 hci_connection_handle, const unsigned char *data, uint16_t length, bool is_tx) +{ + /* Look for AVDTP connections */ + bool avdtp_gen_bg_int = false; + uint16_t cid_to_fw = 0; + bool is_sink = false; + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci = NULL; + bool avdtp_open = false; + + /* Look for AVDTP connections */ + if (HCI_L2CAP_RX_CID((const unsigned char *)(data)) == L2CAP_SIGNALING_CID) { + if (scsc_bt_avdtp_detect_connection_rxtx(hci_connection_handle, data, length, is_tx)) { + avdtp_gen_bg_int = true; + + avdtp_hci = scsc_avdtp_detect_find_or_create_hci_connection( + hci_connection_handle, + false); + + if (avdtp_hci) { + cid_to_fw = avdtp_hci->stream.dst_cid; + is_sink = avdtp_hci->tsep_detect.tsep == BT_AVDTP_TSEP_SNK; + } + } + } else { + /* Check if we have detected any signal on the connection handle */ + avdtp_hci = scsc_avdtp_detect_find_or_create_hci_connection( + hci_connection_handle, + false); + + if (avdtp_hci) { + if (avdtp_hci->signal.state == BT_AVDTP_STATE_COMPLETE_SIGNALING && + length >= AVDTP_DETECT_MIN_AVDTP_LENGTH && + ((is_tx && avdtp_hci->signal.dst_cid != 0 && + avdtp_hci->signal.dst_cid == HCI_L2CAP_RX_CID((const unsigned char *)(data))) || + (!is_tx && avdtp_hci->signal.src_cid != 0 && + avdtp_hci->signal.src_cid == HCI_L2CAP_RX_CID((const unsigned char *)(data))))) { + /* Signaling has been detected on the given CID and hci_connection_handle */ + uint8_t result = scsc_avdtp_detect_signaling_rxtx(hci_connection_handle, + avdtp_hci, data, length, is_tx); + + if (result != AVDTP_DETECT_SIGNALING_IGNORE) { + avdtp_gen_bg_int = true; + if (result != AVDTP_DETECT_SIGNALING_INACTIVE) + cid_to_fw = avdtp_hci->stream.dst_cid; + if (result == AVDTP_DETECT_SIGNALING_OPEN) + avdtp_open = true; + is_sink = avdtp_hci->tsep_detect.tsep == BT_AVDTP_TSEP_SNK; + } + + } + } + } + + if (avdtp_hci) + spin_unlock(&avdtp_hci->lock); + + if (avdtp_gen_bg_int) { + if (bt_service.bsmhcp_protocol->header.firmware_features & BSMHCP_FEATURE_AVDTP_TRANSFER_RING) { + uint32_t flags = 0; + + if (avdtp_hci && avdtp_hci->tsep_detect.tsep == BT_AVDTP_TSEP_SNK) + flags |= AVDTP_SNK_FLAG_TD_MASK; + if (avdtp_open) + flags |= AVDTP_OPEN_FLAG_TD_MASK; + scsc_bt_shm_h4_avdtp_detect_write(flags, cid_to_fw, hci_connection_handle); + } else { + /* Legacy communication between driver and FW. This was replaced by a transfer ring but + * the implementation is kept for backward compability + */ + u8 msg_counter = AVDTP_GET_MESSAGE_COUNT( + bt_service.bsmhcp_protocol->header.avdtp_detect_stream_id); + msg_counter++; + msg_counter &= 0x3; + + bt_service.bsmhcp_protocol->header.avdtp_detect_stream_id = cid_to_fw | + (hci_connection_handle << 16) | + (msg_counter << 28); + bt_service.bsmhcp_protocol->header.avdtp_detect_stream_id |= AVDTP_SIGNAL_FLAG_MASK; + if (is_sink) + bt_service.bsmhcp_protocol->header.avdtp_detect_stream_id |= AVDTP_SNK_FLAG_MASK; + SCSC_TAG_DEBUG( + BT_H4, + "Found AVDTP signal. msgid: 0x%02X, aclid: 0x%04X, cid: 0x%04X, streamid: 0x%08X\n", + msg_counter, + hci_connection_handle, + cid_to_fw, + bt_service.bsmhcp_protocol->header.avdtp_detect_stream_id); + mmiowb(); + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } + } +} + +/* Used to reset the different AVDTP detections */ +void scsc_avdtp_detect_reset(struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci, + bool reset_signal, + bool reset_signal_ongoing, + bool reset_stream, + bool reset_stream_ongoing, + bool reset_local_seids, + bool reset_remote_seids) +{ + if (reset_signal_ongoing) { + avdtp_hci->ongoing.outgoing_signal.state = BT_AVDTP_STATE_IDLE_SIGNALING; + avdtp_hci->ongoing.outgoing_signal.src_cid = 0; + avdtp_hci->ongoing.outgoing_signal.dst_cid = 0; + avdtp_hci->ongoing.incoming_signal.state = BT_AVDTP_STATE_IDLE_SIGNALING; + avdtp_hci->ongoing.incoming_signal.src_cid = 0; + avdtp_hci->ongoing.incoming_signal.dst_cid = 0; + } + if (reset_stream) { + avdtp_hci->stream.state = BT_AVDTP_STATE_IDLE_STREAMING; + avdtp_hci->stream.src_cid = 0; + avdtp_hci->stream.dst_cid = 0; + } + if (reset_stream_ongoing) { + avdtp_hci->ongoing.outgoing_stream.state = BT_AVDTP_STATE_IDLE_STREAMING; + avdtp_hci->ongoing.outgoing_stream.src_cid = 0; + avdtp_hci->ongoing.outgoing_stream.dst_cid = 0; + avdtp_hci->ongoing.incoming_stream.state = BT_AVDTP_STATE_IDLE_STREAMING; + avdtp_hci->ongoing.incoming_stream.src_cid = 0; + avdtp_hci->ongoing.incoming_stream.dst_cid = 0; + } + if (reset_local_seids) { + struct scsc_bt_avdtp_detect_snk_seid *seid = avdtp_hci->tsep_detect.local_snk_seids; + + while (seid) { + struct scsc_bt_avdtp_detect_snk_seid *next = seid->next; + + kfree(seid); + seid = next; + } + avdtp_hci->tsep_detect.local_snk_seids = NULL; + avdtp_hci->tsep_detect.local_snk_seid_candidate = 0; + } + if (reset_remote_seids) { + struct scsc_bt_avdtp_detect_snk_seid *seid = avdtp_hci->tsep_detect.remote_snk_seids; + + while (seid) { + struct scsc_bt_avdtp_detect_snk_seid *next = seid->next; + + kfree(seid); + seid = next; + } + avdtp_hci->tsep_detect.remote_snk_seids = NULL; + avdtp_hci->tsep_detect.remote_snk_seid_candidate = 0; + } + if (reset_local_seids && reset_remote_seids) + avdtp_hci->tsep_detect.tsep = BT_AVDTP_TSEP_SRC; + + if (reset_signal) { + struct scsc_bt_avdtp_detect_hci_connection *prev; + /* Unlock the mutex to keep the order of lock/unlock between the connection list + * and the individual elements + */ + spin_unlock(&avdtp_hci->lock); + spin_lock(&bt_service.avdtp_detect.lock); + /* The element could have been deleted at this point by another thread before the mutext + * on the list was taken. Therefore re-check. + */ + if (avdtp_hci) { + prev = bt_service.avdtp_detect.connections; + + if (prev && prev != avdtp_hci) { + /* The element was not the head of the list. Search for the previous element */ + while (prev) { + if (prev->next == avdtp_hci) { + /* Remove the element from the list */ + prev->next = avdtp_hci->next; + break; + } + prev = prev->next; + } + } else { + bt_service.avdtp_detect.connections = avdtp_hci->next; + } + /* Lock to make sure that no-one reads from it. Since it has been removed from the list + * unlocking it again will not make another thread read it since it cannot be found + */ + spin_lock(&avdtp_hci->lock); + spin_unlock(&bt_service.avdtp_detect.lock); + spin_unlock(&avdtp_hci->lock); + kfree(avdtp_hci); + avdtp_hci = NULL; + } else + spin_unlock(&bt_service.avdtp_detect.lock); + } +} + +/* Used to reset all current or ongoing detections for a given hci_connection_handle. This can e.g. + * used if the link is lost */ +bool scsc_avdtp_detect_reset_connection_handle(uint16_t hci_connection_handle) +{ + bool reset_anything = false; + struct scsc_bt_avdtp_detect_hci_connection *avdtp_hci = + scsc_avdtp_detect_find_or_create_hci_connection(hci_connection_handle, false); + + /* Check already established connections */ + if (avdtp_hci) { + scsc_avdtp_detect_reset(avdtp_hci, true, true, true, true, true, true); + /* No need to unlock the detection since it has been removed */ + reset_anything = true; + } + return reset_anything; +} + +void scsc_avdtp_detect_exit(void) +{ + struct scsc_bt_avdtp_detect_hci_connection *head; + + /* Lock the detection list and find the head */ + spin_lock(&bt_service.avdtp_detect.lock); + head = bt_service.avdtp_detect.connections; + + while (head) { + spin_lock(&head->lock); + /* Clear the remote and local seids lists on head */ + scsc_avdtp_detect_reset(head, false, false, false, false, true, true); + + /* Update the head to bypass the current element */ + bt_service.avdtp_detect.connections = head->next; + + spin_unlock(&bt_service.avdtp_detect.lock); + + /* Free the used memory */ + spin_unlock(&head->lock); + kfree(head); + head = NULL; + + /* Update the head variable */ + spin_lock(&bt_service.avdtp_detect.lock); + head = bt_service.avdtp_detect.connections; + } + + spin_unlock(&bt_service.avdtp_detect.lock); + + /* The avdtp_detect has now been restored and doesn't contain other information + * than its two locks + */ +} diff --git a/drivers/misc/samsung/scsc_bt/scsc_bluez.c b/drivers/misc/samsung/scsc_bt/scsc_bluez.c new file mode 100755 index 000000000000..1c58f95c19f0 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_bluez.c @@ -0,0 +1,337 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + * BT BlueZ interface + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../../../bluetooth/h4_recv.h" + +#include + +static struct hci_dev *hdev; +static struct device *dev_ref; +static const struct file_operations *bt_fs; +static struct workqueue_struct *wq; +static struct work_struct open_worker; +static struct work_struct close_worker; +static struct work_struct read_work; +static u8 receive_buffer[1024]; +static bool terminate_read; +static atomic_t *error_count_ref; +static wait_queue_head_t *read_wait_ref; +static struct file s_file; + +static struct platform_device *slsi_btz_pdev; +static struct rfkill *btz_rfkill; + +static const struct h4_recv_pkt scsc_recv_pkts[] = { + { H4_RECV_ACL, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = hci_recv_frame }, +}; + +static void slsi_bt_fs_read_func(struct work_struct *work) +{ + int ret; + struct sk_buff *skb = NULL; + + while ((ret = bt_fs->read(&s_file, receive_buffer, sizeof(receive_buffer), NULL)) >= 0) { + if (terminate_read) + break; + + if (ret > 0) { + skb = h4_recv_buf(hdev, skb, receive_buffer, + ret, scsc_recv_pkts, + ARRAY_SIZE(scsc_recv_pkts)); + + if (IS_ERR(skb)) { + SCSC_TAG_ERR(BT_COMMON, "corrupted event packet\n"); + hdev->stat.err_rx++; + break; + } + hdev->stat.byte_rx += ret; + } + } + + SCSC_TAG_INFO(BT_COMMON, "BT BlueZ: Exiting %s\n", __func__); +} + +static int slsi_bt_open(struct hci_dev *hdev) +{ + int err; + + SCSC_TAG_INFO(BT_COMMON, "enter\n"); + + err = bt_fs->open(NULL, NULL); + + if (0 == err) { + terminate_read = false; + if (wq == NULL) { + wq = create_singlethread_workqueue("slsi_bt_bluez_wq"); + INIT_WORK(&read_work, slsi_bt_fs_read_func); + } + queue_work(wq, &read_work); + } + + SCSC_TAG_INFO(BT_COMMON, "done\n"); + + return err; +} + +static int slsi_bt_close(struct hci_dev *hdev) +{ + int ret; + + SCSC_TAG_INFO(BT_COMMON, "terminating reader thread\n"); + + terminate_read = true; + + if (error_count_ref != NULL) + atomic_inc(error_count_ref); + + if (read_wait_ref != NULL) + wake_up(read_wait_ref); + + cancel_work_sync(&read_work); + + SCSC_TAG_INFO(BT_COMMON, "releasing service\n"); + + ret = bt_fs->release(NULL, NULL); + + if (wq != NULL) { + destroy_workqueue(wq); + wq = NULL; + } + + SCSC_TAG_INFO(BT_COMMON, "done\n"); + + return ret; +} + +static int slsi_bt_flush(struct hci_dev *hdev) +{ + return 0; +} + +static int slsi_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + int ret; + + SCSC_TAG_DEBUG(BT_H4, "sending frame(data=%p, len=%u)\n", skb->data, skb->len); + + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + + ret = bt_fs->write(NULL, skb->data, skb->len, NULL); + if (ret >= 0) { + kfree_skb(skb); + + /* Update HCI stat counters */ + hdev->stat.byte_tx += skb->len; + + switch (hci_skb_pkt_type(skb)) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } + } + + return ret; +} + +static void slsi_bt_open_worker(struct work_struct *work) +{ + int err; + + if (!hdev) { + hdev = hci_alloc_dev(); + if (!hdev) { + SCSC_TAG_ERR(BT_COMMON, "failed to allocate hci device\n"); + return; + } + + hdev->bus = HCI_VIRTUAL; + hdev->dev_type = HCI_BREDR; + + SET_HCIDEV_DEV(hdev, dev_ref); + + hdev->open = slsi_bt_open; + hdev->close = slsi_bt_close; + hdev->flush = slsi_bt_flush; + hdev->send = slsi_bt_send_frame; + + err = hci_register_dev(hdev); + if (err < 0) { + SCSC_TAG_ERR(BT_COMMON, "failed to register hci device (err: %d)\n", err); + hci_free_dev(hdev); + hdev = NULL; + } + } +} + +static void slsi_bt_close_worker(struct work_struct *work) +{ + if (hdev) { + hci_unregister_dev(hdev); + hci_free_dev(hdev); + hdev = NULL; + } +} + +void slsi_bt_notify_probe(struct device *dev, + const struct file_operations *fs, + atomic_t *error_count, + wait_queue_head_t *read_wait) +{ + bt_fs = fs; + error_count_ref = error_count; + read_wait_ref = read_wait; + dev_ref = dev; + + SCSC_TAG_INFO(BT_COMMON, "SLSI BT BlueZ probe\n"); +} + +void slsi_bt_notify_remove(void) +{ + error_count_ref = NULL; + read_wait_ref = NULL; + dev_ref = NULL; +} + +static int slsi_bt_power_control_set_param_cb(const char *buffer, + const struct kernel_param *kp) +{ + int ret; + u32 value; + + ret = kstrtou32(buffer, 0, &value); + if (!ret) { + if (value && dev_ref) { + INIT_WORK(&open_worker, slsi_bt_open_worker); + schedule_work(&open_worker); + } else if (0 == value) { + INIT_WORK(&close_worker, slsi_bt_close_worker); + schedule_work(&close_worker); + } + } + + return ret; +} + +static int slsi_bt_power_control_get_param_cb(char *buffer, + const struct kernel_param *kp) +{ + return sprintf(buffer, "%u\n", (hdev != NULL ? 1 : 0)); +} + +static struct kernel_param_ops slsi_bt_power_control_ops = { + .set = slsi_bt_power_control_set_param_cb, + .get = slsi_bt_power_control_get_param_cb, +}; + +module_param_cb(power_control, &slsi_bt_power_control_ops, NULL, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(power_control, + "Enables/disable BlueZ registration"); + +static int btz_rfkill_set(void *data, bool blocked) +{ + if (!blocked && dev_ref) { + INIT_WORK(&open_worker, slsi_bt_open_worker); + schedule_work(&open_worker); + } else if (blocked) { + INIT_WORK(&close_worker, slsi_bt_close_worker); + schedule_work(&close_worker); + } + + return 0; +} + +static const struct rfkill_ops btz_rfkill_ops = { + .set_block = btz_rfkill_set, +}; + +static int __init slsi_bluez_init(void) +{ + int ret; + + slsi_btz_pdev = platform_device_alloc("scsc-bluez", -1); + if (!slsi_btz_pdev) { + return -ENOMEM; + } + + ret = platform_device_add(slsi_btz_pdev); + if (ret) { + goto err_slsi_btz_pdev; + } + + btz_rfkill = rfkill_alloc("scsc-bluez-rfkill", &slsi_btz_pdev->dev, + RFKILL_TYPE_BLUETOOTH, &btz_rfkill_ops, NULL); + if (!btz_rfkill) { + goto err_btz_rfkill_alloc; + } + + rfkill_init_sw_state(btz_rfkill, 1); + + ret = rfkill_register(btz_rfkill); + if (ret) { + goto err_btz_rfkill_reg; + } + + return 0; + +err_btz_rfkill_reg: + rfkill_destroy(btz_rfkill); + +err_btz_rfkill_alloc: + platform_device_del(slsi_btz_pdev); + +err_slsi_btz_pdev: + platform_device_put(slsi_btz_pdev); + + return ret; +} + +static void __exit slsi_bluez_exit(void) +{ + platform_device_unregister(slsi_btz_pdev); + rfkill_unregister(btz_rfkill); + rfkill_destroy(btz_rfkill); +} + +module_init(slsi_bluez_init); +module_exit(slsi_bluez_exit); + +MODULE_DESCRIPTION("SCSC BT Bluez"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/misc/samsung/scsc_bt/scsc_bt_hci.h b/drivers/misc/samsung/scsc_bt/scsc_bt_hci.h new file mode 100755 index 000000000000..02215bf0a277 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_bt_hci.h @@ -0,0 +1,112 @@ +/**************************************************************************** + * + * Internal BT driver HCI decoder + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + ****************************************************************************/ + +#ifndef __SCSC_BT_HCI_H +#define __SCSC_BT_HCI_H + +#define HCI_EVENT_HEADER_LENGTH (2) + +#define HCI_EV_INQUIRY_COMPLETE ((u8)0x01) +#define HCI_EV_INQUIRY_RESULT ((u8)0x02) +#define HCI_EV_CONN_COMPLETE ((u8)0x03) +#define HCI_EV_CONN_REQUEST ((u8)0x04) +#define HCI_EV_DISCONNECT_COMPLETE ((u8)0x05) +#define HCI_EV_AUTH_COMPLETE ((u8)0x06) +#define HCI_EV_REMOTE_NAME_REQ_COMPLETE ((u8)0x07) +#define HCI_EV_ENCRYPTION_CHANGE ((u8)0x08) +#define HCI_EV_CHANGE_CONN_LINK_KEY_COMPLETE ((u8)0x09) +#define HCI_EV_MASTER_LINK_KEY_COMPLETE ((u8)0x0A) +#define HCI_EV_READ_REM_SUPP_FEATURES_COMPLETE ((u8)0x0B) +#define HCI_EV_READ_REMOTE_VER_INFO_COMPLETE ((u8)0x0C) +#define HCI_EV_QOS_SETUP_COMPLETE ((u8)0x0D) +#define HCI_EV_COMMAND_COMPLETE ((u8)0x0E) +#define HCI_EV_COMMAND_STATUS ((u8)0x0F) +#define HCI_EV_HARDWARE_ERROR ((u8)0x10) +#define HCI_EV_FLUSH_OCCURRED ((u8)0x11) +#define HCI_EV_ROLE_CHANGE ((u8)0x12) +#define HCI_EV_NUMBER_COMPLETED_PKTS ((u8)0x13) +#define HCI_EV_MODE_CHANGE ((u8)0x14) +#define HCI_EV_RETURN_LINK_KEYS ((u8)0x15) +#define HCI_EV_PIN_CODE_REQ ((u8)0x16) +#define HCI_EV_LINK_KEY_REQ ((u8)0x17) +#define HCI_EV_LINK_KEY_NOTIFICATION ((u8)0x18) +#define HCI_EV_LOOPBACK_COMMAND ((u8)0x19) +#define HCI_EV_DATA_BUFFER_OVERFLOW ((u8)0x1A) +#define HCI_EV_MAX_SLOTS_CHANGE ((u8)0x1B) +#define HCI_EV_READ_CLOCK_OFFSET_COMPLETE ((u8)0x1C) +#define HCI_EV_CONN_PACKET_TYPE_CHANGED ((u8)0x1D) +#define HCI_EV_QOS_VIOLATION ((u8)0x1E) +#define HCI_EV_PAGE_SCAN_MODE_CHANGE ((u8)0x1F) +#define HCI_EV_PAGE_SCAN_REP_MODE_CHANGE ((u8)0x20) +/* 1.2 Events */ +#define HCI_EV_FLOW_SPEC_COMPLETE ((u8)0x21) +#define HCI_EV_INQUIRY_RESULT_WITH_RSSI ((u8)0x22) +#define HCI_EV_READ_REM_EXT_FEATURES_COMPLETE ((u8)0x23) +#define HCI_EV_FIXED_ADDRESS ((u8)0x24) +#define HCI_EV_ALIAS_ADDRESS ((u8)0x25) +#define HCI_EV_GENERATE_ALIAS_REQ ((u8)0x26) +#define HCI_EV_ACTIVE_ADDRESS ((u8)0x27) +#define HCI_EV_ALLOW_PRIVATE_PAIRING ((u8)0x28) +#define HCI_EV_ALIAS_ADDRESS_REQ ((u8)0x29) +#define HCI_EV_ALIAS_NOT_RECOGNISED ((u8)0x2A) +#define HCI_EV_FIXED_ADDRESS_ATTEMPT ((u8)0x2B) +#define HCI_EV_SYNC_CONN_COMPLETE ((u8)0x2C) +#define HCI_EV_SYNC_CONN_CHANGED ((u8)0x2D) + +/* 2.1 Events */ +#define HCI_EV_SNIFF_SUB_RATE ((u8)0x2E) +#define HCI_EV_EXTENDED_INQUIRY_RESULT ((u8)0x2F) +#define HCI_EV_ENCRYPTION_KEY_REFRESH_COMPLETE ((u8)0x30) +#define HCI_EV_IO_CAPABILITY_REQUEST ((u8)0x31) +#define HCI_EV_IO_CAPABILITY_RESPONSE ((u8)0x32) +#define HCI_EV_USER_CONFIRMATION_REQUEST ((u8)0x33) +#define HCI_EV_USER_PASSKEY_REQUEST ((u8)0x34) +#define HCI_EV_REMOTE_OOB_DATA_REQUEST ((u8)0x35) +#define HCI_EV_SIMPLE_PAIRING_COMPLETE ((u8)0x36) +#define HCI_EV_LST_CHANGE ((u8)0x38) +#define HCI_EV_ENHANCED_FLUSH_COMPLETE ((u8)0x39) +#define HCI_EV_USER_PASSKEY_NOTIFICATION ((u8)0x3B) +#define HCI_EV_KEYPRESS_NOTIFICATION ((u8)0x3C) +#define HCI_EV_REM_HOST_SUPPORTED_FEATURES ((u8)0x3D) +#define HCI_EV_ULP ((u8)0x3E) + +/* TCC + CSB Events */ +#define HCI_EV_TRIGGERED_CLOCK_CAPTURE ((u8)0x4E) +#define HCI_EV_SYNCHRONIZATION_TRAIN_COMPLETE ((u8)0x4F) +#define HCI_EV_SYNCHRONIZATION_TRAIN_RECEIVED ((u8)0x50) +#define HCI_EV_CSB_RECEIVE ((u8)0x51) +#define HCI_EV_CSB_TIMEOUT ((u8)0x52) +#define HCI_EV_TRUNCATED_PAGE_COMPLETE ((u8)0x53) +#define HCI_EV_SLAVE_PAGE_RESPONSE_TIMEOUT ((u8)0x54) +#define HCI_EV_CSB_CHANNEL_MAP_CHANGE ((u8)0x55) +#define HCI_EV_INQUIRY_RESPONSE_NOTIFICATION ((u8)0x56) + +/* 4.1 Events */ +#define HCI_EV_AUTHENTICATED_PAYLOAD_TIMEOUT_EXPIRED ((u8)0x57) + +/* ULP Sub-opcodes */ +#define HCI_EV_ULP_CONNECTION_COMPLETE ((u8)0x01) +#define HCI_EV_ULP_ADVERTISING_REPORT ((u8)0x02) +#define HCI_EV_ULP_CONNECTION_UPDATE_COMPLETE ((u8)0x03) +#define HCI_EV_ULP_READ_REMOTE_USED_FEATURES_COMPLETE ((u8)0x04) +#define HCI_EV_ULP_LONG_TERM_KEY_REQUEST ((u8)0x05) +#define HCI_EV_ULP_REMOTE_CONNECTION_PARAMETER_REQUEST ((u8)0x06) +#define HCI_EV_ULP_DATA_LENGTH_CHANGE ((u8)0x07) +#define HCI_EV_ULP_READ_LOCAL_P256_PUB_KEY_COMPLETE ((u8)0x08) +#define HCI_EV_ULP_GENERATE_DHKEY_COMPLETE ((u8)0x09) +#define HCI_EV_ULP_ENHANCED_CONNECTION_COMPLETE ((u8)0x0A) +#define HCI_EV_ULP_DIRECT_ADVERTISING_REPORT ((u8)0x0B) +#define HCI_EV_ULP_PHY_UPDATE_COMPLETE ((u8)0x0C) +/* The subevent code of ULP_USED_CHANNEL_SELECTION_EVENT shall be updated + when it is defined in the spec. + Assign it as 0x0D temporarily. */ +#define HCI_EV_ULP_USED_CHANNEL_SELECTION ((u8)0x0D) + +#define HCI_EV_DECODE(entry) case entry: ret = #entry; break + +#endif /* __SCSC_BT_HCI_H */ diff --git a/drivers/misc/samsung/scsc_bt/scsc_bt_module.c b/drivers/misc/samsung/scsc_bt/scsc_bt_module.c new file mode 100755 index 000000000000..1e799dfe4940 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_bt_module.c @@ -0,0 +1,2161 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + * BT driver entry point + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARCH_EXYNOS +#include +#endif + +#include +#include +#include +#include + +#include "scsc_bt_priv.h" +#include "../scsc/scsc_mx_impl.h" + +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +#define SCSC_MODDESC "SCSC MX BT Driver" +#define SCSC_MODAUTH "Samsung Electronics Co., Ltd" +#define SCSC_MODVERSION "-devel" + +#define SLSI_BT_SERVICE_CLOSE_RETRY 60 +#define SLSI_BT_SERVICE_STOP_RECOVERY_TIMEOUT 20000 +#define SLSI_BT_SERVICE_STOP_RECOVERY_DISABLED_TIMEOUT 2000 + +#define SCSC_ANT_MAX_TIMEOUT (20*HZ) + +#ifdef CONFIG_SCSC_ANT +static DECLARE_WAIT_QUEUE_HEAD(ant_recovery_complete_queue); +#endif + +static DEFINE_MUTEX(bt_start_mutex); +static DEFINE_MUTEX(bt_audio_mutex); +#ifdef CONFIG_SCSC_ANT +static DEFINE_MUTEX(ant_start_mutex); +#endif + +static int bt_recovery_in_progress; +#ifdef CONFIG_SCSC_ANT +static int ant_recovery_in_progress; +#endif + +static int recovery_timeout = SLSI_BT_SERVICE_STOP_RECOVERY_TIMEOUT; + +struct scsc_common_service common_service; +struct scsc_bt_service bt_service; +#ifdef CONFIG_SCSC_ANT +struct scsc_ant_service ant_service; +#endif + +static int service_start_count; +#ifdef CONFIG_SCSC_ANT +static int ant_service_start_count; +#endif + +static u64 bluetooth_address; +#ifdef CONFIG_ARCH_EXYNOS +static char bluetooth_address_fallback[] = "00:00:00:00:00:00"; +#endif +static u32 bt_info_trigger; +static u32 bt_info_interrupt; +static u32 firmware_control; +static bool firmware_control_reset = true; +static u32 firmware_mxlog_filter; +static bool disable_service; + +/* Audio */ +static struct device *audio_device; +static bool audio_device_probed; +static struct scsc_bt_audio bt_audio; + +module_param(bluetooth_address, ullong, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(bluetooth_address, + "Bluetooth address"); + +#ifdef CONFIG_ARCH_EXYNOS +module_param_string(bluetooth_address_fallback, bluetooth_address_fallback, + sizeof(bluetooth_address_fallback), 0444); +MODULE_PARM_DESC(bluetooth_address_fallback, + "Bluetooth address as proposed by the driver"); +#endif + +module_param(service_start_count, int, S_IRUGO); +MODULE_PARM_DESC(service_start_count, + "Track how many times the BT service has been started"); +#ifdef CONFIG_SCSC_ANT +module_param(ant_service_start_count, int, 0444); +MODULE_PARM_DESC(ant_service_start_count, + "Track how many times the ANT service has been started"); +#endif + +module_param(firmware_control, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(firmware_control, "Control how the firmware behaves"); + +module_param(firmware_control_reset, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(firmware_control_reset, + "Controls the resetting of the firmware_control variable"); + +module_param(disable_service, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_service, + "Disables service startup"); + +/* + * Service event callbacks called from mx-core when things go wrong + */ +static void bt_stop_on_failure(struct scsc_service_client *client) +{ + UNUSED(client); + + SCSC_TAG_ERR(BT_COMMON, "\n"); + + reinit_completion(&bt_service.recovery_probe_complete); + bt_recovery_in_progress = 1; + + atomic_inc(&bt_service.error_count); + + /* Zero the shared memory on error. The A-Box does not stop using this + * memory immediately as designed. To prevent noise during recovery we zero the + * shared memory before freeing it + */ + mutex_lock(&bt_audio_mutex); + + if (bt_service.abox_ref != 0 && bt_audio.abox_virtual) { + memset(bt_audio.abox_virtual->abox_to_bt_streaming_if_data, 0, SCSC_BT_AUDIO_ABOX_DATA_SIZE); + memset(bt_audio.abox_virtual->bt_to_abox_streaming_if_data, 0, SCSC_BT_AUDIO_ABOX_DATA_SIZE); + } + + mutex_unlock(&bt_audio_mutex); +} + +static void bt_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + UNUSED(client); + UNUSED(scsc_panic_code); + + SCSC_TAG_ERR(BT_COMMON, "\n"); + + wake_up(&bt_service.read_wait); +} + +#ifdef CONFIG_SCSC_ANT +static void ant_stop_on_failure(struct scsc_service_client *client) +{ + UNUSED(client); + + SCSC_TAG_ERR(BT_COMMON, "\n"); + + reinit_completion(&ant_service.recovery_probe_complete); + ant_recovery_in_progress = 1; + + atomic_inc(&ant_service.error_count); + + /* Let the ANT stack call poll() to be notified about the reset asap */ + wake_up(&ant_service.read_wait); +} +#endif + +#ifdef CONFIG_SCSC_ANT +static void ant_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + UNUSED(client); + UNUSED(scsc_panic_code); + + SCSC_TAG_ERR(BT_COMMON, "\n"); + + wake_up(&ant_service.read_wait); +} +#endif + +static void scsc_bt_shm_irq_handler(int irqbit, void *data) +{ + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(bt_service.service, irqbit); + + bt_info_interrupt++; + + wake_up(&bt_service.info_wait); +} + +static struct scsc_service_client mx_bt_client = { + .stop_on_failure = bt_stop_on_failure, + .failure_reset = bt_failure_reset, +}; + +#ifdef CONFIG_SCSC_ANT +static struct scsc_service_client mx_ant_client = { + .stop_on_failure = ant_stop_on_failure, + .failure_reset = ant_failure_reset, +}; +#endif + +static void slsi_sm_bt_service_cleanup_interrupts(void) +{ + u16 int_src = bt_service.bsmhcp_protocol->header.info_bg_to_ap_int_src; + + SCSC_TAG_DEBUG(BT_COMMON, + "unregister firmware information interrupts\n"); + + scsc_service_mifintrbit_unregister_tohost(bt_service.service, int_src); + scsc_service_mifintrbit_free_fromhost(bt_service.service, + bt_service.bsmhcp_protocol->header.info_ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); +} + +static int slsi_sm_bt_service_cleanup_stop_service(void) +{ + int ret; + + /* Stop service first, then it's safe to release shared memory + resources */ + ret = scsc_mx_service_stop(bt_service.service); + if (ret) { + SCSC_TAG_ERR(BT_COMMON, + "scsc_mx_service_stop failed err: %d\n", ret); + if (0 == atomic_read(&bt_service.error_count)) { + scsc_mx_service_service_failed(bt_service.service, "BT service stop failed"); + SCSC_TAG_DEBUG(BT_COMMON, + "force service fail complete\n"); + } + return -EIO; + } + + return 0; +} + +static int slsi_bt_audio_probe(void) +{ + phys_addr_t paddr; + size_t size; + + if (audio_device == NULL || bt_audio.dev_iommu_map == NULL) { + SCSC_TAG_ERR(BT_COMMON, "failed audio_device %p bt_audio.dev_iommu_map %p\n", + audio_device, bt_audio.dev_iommu_map); + return -EFAULT; + } + + paddr = (phys_addr_t)bt_audio.abox_physical; + size = PAGE_ALIGN(sizeof(*bt_audio.abox_physical)); + + SCSC_TAG_DEBUG(BT_COMMON, "paddr %p size %zu\n", paddr, size); + + return bt_audio.dev_iommu_map(audio_device, paddr, size); +} + +/* Note A-Box memory should only be unmapped when A-Box driver is finished with it */ +static void slsi_bt_audio_remove(void) +{ + size_t size; + + if (audio_device == NULL || bt_audio.dev_iommu_unmap == NULL || bt_audio.abox_physical == NULL) + return; + + size = PAGE_ALIGN(sizeof(*bt_audio.abox_physical)); + bt_audio.dev_iommu_unmap(audio_device, size); +} + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static int bt_hcf_collect(struct scsc_log_collector_client *collect_client, size_t size) +{ + struct scsc_bt_hcf_collection *hcf_collect = (struct scsc_bt_hcf_collection *) collect_client->prv; + int ret = 0; + + if (hcf_collect == NULL) + return ret; + + SCSC_TAG_DEBUG(BT_COMMON, "Collecting BT config file\n"); + ret = scsc_log_collector_write(hcf_collect->hcf, hcf_collect->hcf_size, 1); + + return ret; +} + +struct scsc_log_collector_client bt_collect_hcf_client = { + .name = "bt_hcf", + .type = SCSC_LOG_CHUNK_BT_HCF, + .collect_init = NULL, + .collect = bt_hcf_collect, + .collect_end = NULL, + .prv = NULL, +}; +#endif + +static int slsi_sm_bt_service_cleanup(bool allow_service_stop) +{ + SCSC_TAG_DEBUG(BT_COMMON, "enter (service=%p)\n", bt_service.service); + + if (NULL != bt_service.service) { + SCSC_TAG_DEBUG(BT_COMMON, "stopping debugging thread\n"); + + /* If slsi_sm_bt_service_cleanup_stop_service fails, then let + recovery do the rest of the deinit later. */ + if (!bt_recovery_in_progress && allow_service_stop) + if (slsi_sm_bt_service_cleanup_stop_service() < 0) { + SCSC_TAG_DEBUG(BT_COMMON, "slsi_sm_bt_service_cleanup_stop_service failed. Recovery has been triggered\n"); + goto done_error; + } + + /* Service is stopped - ensure polling function is existed */ + SCSC_TAG_DEBUG(BT_COMMON, "wake reader/poller thread\n"); + wake_up_interruptible(&bt_service.read_wait); + + /* Unregister firmware information interrupts */ + if (bt_service.bsmhcp_protocol) + slsi_sm_bt_service_cleanup_interrupts(); + + /* Shut down the shared memory interface */ + SCSC_TAG_DEBUG(BT_COMMON, + "cleanup protocol structure and main interrupts\n"); + scsc_bt_shm_exit(); + + /* Cleanup AVDTP detections */ + SCSC_TAG_DEBUG(BT_COMMON, + "cleanup ongoing avdtp detections\n"); + scsc_avdtp_detect_exit(); + + mutex_lock(&bt_audio_mutex); + if (audio_device) { + bt_audio.dev = NULL; + bt_audio.abox_virtual = NULL; + bt_audio.abox_physical = NULL; + } + mutex_unlock(&bt_audio_mutex); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Deinit HCF log collection */ + scsc_log_collector_unregister_client(&bt_collect_hcf_client); + bt_collect_hcf_client.prv = NULL; + + if (bt_service.hcf_collection.hcf) { + /* Reset HCF pointer - memory will be freed later */ + bt_service.hcf_collection.hcf_size = 0; + bt_service.hcf_collection.hcf = NULL; + } +#endif + + /* Release the shared memory */ + SCSC_TAG_DEBUG(BT_COMMON, + "free memory allocated in the shared DRAM pool\n"); + if (bt_service.config_ref != 0) { + scsc_mx_service_mifram_free(bt_service.service, + bt_service.config_ref); + bt_service.config_ref = 0; + } + if (bt_service.bsmhcp_ref != 0) { + scsc_mx_service_mifram_free(bt_service.service, + bt_service.bsmhcp_ref); + bt_service.bsmhcp_ref = 0; + } + if (bt_service.bhcs_ref != 0) { + scsc_mx_service_mifram_free(bt_service.service, + bt_service.bhcs_ref); + bt_service.bhcs_ref = 0; + } + + SCSC_TAG_DEBUG(BT_COMMON, "closing service...\n"); + if (0 != scsc_mx_service_close(bt_service.service)) { + int retry_counter, r; + + SCSC_TAG_DEBUG(BT_COMMON, + "scsc_mx_service_close failed\n"); + + /** + * Error handling in progress - try and close again + * later. The service close call shall remain blocked + * until close service is successful. Will try up to + * 30 seconds. + */ + for (retry_counter = 0; + SLSI_BT_SERVICE_CLOSE_RETRY > retry_counter; + retry_counter++) { + msleep(500); + r = scsc_mx_service_close(bt_service.service); + if (r == 0) { + SCSC_TAG_DEBUG(BT_COMMON, + "scsc_mx_service_close closed after %d attempts\n", + retry_counter + 1); + break; + } + } + + if (retry_counter + 1 == SLSI_BT_SERVICE_CLOSE_RETRY) + SCSC_TAG_ERR(BT_COMMON, "scsc_mx_service_close failed %d times\n", + SLSI_BT_SERVICE_CLOSE_RETRY); + } + bt_service.service = NULL; + + SCSC_TAG_DEBUG(BT_COMMON, + "notify the KIC subsystem of the shutdown\n"); + slsi_kic_system_event( + slsi_kic_system_event_category_deinitialisation, + slsi_kic_system_events_bt_off, + 0); + } + + atomic_set(&bt_service.error_count, 0); + + /* Release write wake lock if held */ + if (wake_lock_active(&bt_service.write_wake_lock)) { + bt_service.write_wake_unlock_count++; + wake_unlock(&bt_service.write_wake_lock); + } + + SCSC_TAG_DEBUG(BT_COMMON, "complete\n"); + return 0; + +done_error: + return -EIO; +} + +#ifdef CONFIG_SCSC_ANT +static int slsi_sm_ant_service_cleanup_stop_service(void) +{ + int ret; + + /* Stop service first, then it's safe to release shared memory + * resources + */ + ret = scsc_mx_service_stop(ant_service.service); + if (ret) { + SCSC_TAG_ERR(BT_COMMON, + "scsc_mx_service_stop failed err: %d\n", ret); + if (atomic_read(&ant_service.error_count) == 0) { + scsc_mx_service_service_failed(ant_service.service, "ANT service stop failed"); + SCSC_TAG_DEBUG(BT_COMMON, + "force service fail complete\n"); + return -EIO; + } + } + + return 0; +} +#endif + +#ifdef CONFIG_SCSC_ANT +static int slsi_sm_ant_service_cleanup(bool allow_service_stop) +{ + SCSC_TAG_DEBUG(BT_COMMON, "enter (service=%p)\n", ant_service.service); + + if (ant_service.service != NULL) { + SCSC_TAG_DEBUG(BT_COMMON, "stopping debugging thread\n"); + + /* If slsi_sm_ant_service_cleanup_stop_service fails, then let + * recovery do the rest of the deinit later. + **/ + if (!ant_recovery_in_progress && allow_service_stop) + if (slsi_sm_ant_service_cleanup_stop_service() < 0) { + SCSC_TAG_DEBUG(BT_COMMON, + "slsi_sm_ant_service_cleanup_stop_service failed. Recovery has been triggered\n"); + goto done_error; + } + + /* Service is stopped - ensure polling function is existed */ + SCSC_TAG_DEBUG(BT_COMMON, "wake reader/poller thread\n"); + wake_up_interruptible(&ant_service.read_wait); + + /* Shut down the shared memory interface */ + SCSC_TAG_DEBUG(BT_COMMON, + "cleanup protocol structure and main interrupts\n"); + scsc_ant_shm_exit(); + + /* Release the shared memory */ + SCSC_TAG_DEBUG(BT_COMMON, + "free memory allocated in the shared DRAM pool\n"); + if (ant_service.config_ref != 0) { + scsc_mx_service_mifram_free(ant_service.service, + ant_service.config_ref); + ant_service.config_ref = 0; + } + if (ant_service.asmhcp_ref != 0) { + scsc_mx_service_mifram_free(ant_service.service, + ant_service.asmhcp_ref); + ant_service.asmhcp_ref = 0; + } + if (ant_service.bhcs_ref != 0) { + scsc_mx_service_mifram_free(ant_service.service, + ant_service.bhcs_ref); + ant_service.bhcs_ref = 0; + } + + SCSC_TAG_DEBUG(BT_COMMON, "closing ant service...\n"); + if (scsc_mx_service_close(ant_service.service) != 0) { + int retry_counter, r; + + SCSC_TAG_DEBUG(BT_COMMON, + "scsc_mx_service_close failed\n"); + + /** + * Error handling in progress - try and close again + * later. The service close call shall remain blocked + * until close service is successful. Will try up to + * 30 seconds. + */ + for (retry_counter = 0; + retry_counter < SLSI_BT_SERVICE_CLOSE_RETRY; + retry_counter++) { + msleep(500); + r = scsc_mx_service_close(ant_service.service); + if (r == 0) { + SCSC_TAG_DEBUG(BT_COMMON, + "scsc_mx_service_close closed after %d attempts\n", + retry_counter + 1); + break; + } + } + + if (retry_counter + 1 == SLSI_BT_SERVICE_CLOSE_RETRY) + SCSC_TAG_ERR(BT_COMMON, + "scsc_mx_service_close failed %d times\n", + SLSI_BT_SERVICE_CLOSE_RETRY); + } + ant_service.service = NULL; + + SCSC_TAG_DEBUG(BT_COMMON, + "notify the KIC subsystem of the shutdown\n"); + slsi_kic_system_event( + slsi_kic_system_event_category_deinitialisation, + slsi_kic_system_events_ant_off, + 0); + } + + atomic_set(&ant_service.error_count, 0); + + SCSC_TAG_DEBUG(BT_COMMON, "complete\n"); + return 0; + +done_error: + return -EIO; +} +#endif + +static int setup_bhcs(struct scsc_service *service, + struct BHCS *bhcs, + uint32_t protocol_ref, + uint32_t protocol_length, + scsc_mifram_ref *config_ref, + scsc_mifram_ref *bhcs_ref) +{ + int err = 0; + unsigned char *conf_ptr; + const struct firmware *firm = NULL; + /* Fill the configuration information */ + bhcs->version = BHCS_VERSION; + bhcs->bsmhcp_protocol_offset = protocol_ref; + bhcs->bsmhcp_protocol_length = protocol_length; + bhcs->configuration_offset = 0; + bhcs->configuration_length = 0; + bhcs->bluetooth_address_lap = 0; + bhcs->bluetooth_address_uap = 0; + bhcs->bluetooth_address_nap = 0; + + /* Request the configuration file */ + SCSC_TAG_DEBUG(BT_COMMON, + "loading configuration: " SCSC_BT_CONF "\n"); + err = mx140_file_request_conf(common_service.maxwell_core, + &firm, "bluetooth", SCSC_BT_CONF); + if (err) { + /* Not found - just silently ignore this */ + SCSC_TAG_DEBUG(BT_COMMON, "configuration not found\n"); + *config_ref = 0; + } else if (firm && firm->size) { + SCSC_TAG_DEBUG(BT_COMMON, + "configuration size = %zu\n", firm->size); + + /* Allocate a region for the data */ + err = scsc_mx_service_mifram_alloc(service, + firm->size, + config_ref, + BSMHCP_ALIGNMENT); + if (err) { + SCSC_TAG_WARNING(BT_COMMON, "mifram alloc failed\n"); + mx140_file_release_conf(common_service.maxwell_core, firm); + return -EINVAL; + } + + /* Map the region to a memory pointer */ + conf_ptr = scsc_mx_service_mif_addr_to_ptr(service, + *config_ref); + if (conf_ptr == NULL) { + SCSC_TAG_ERR(BT_COMMON, + "couldn't map kmem to bhcs_ref 0x%08x\n", + (u32)*bhcs_ref); + mx140_file_release_conf(common_service.maxwell_core, firm); + return -EINVAL; + } + + /* Copy the configuration data to the shared memory area */ + memcpy(conf_ptr, firm->data, firm->size); + bhcs->configuration_offset = *config_ref; + bhcs->configuration_length = firm->size; + + /* Relase the configuration information */ + mx140_file_release_conf(common_service.maxwell_core, firm); + firm = NULL; + } else { + /* Empty configuration - just silently ignore this */ + SCSC_TAG_DEBUG(BT_COMMON, "empty configuration\n"); + *config_ref = 0; + + /* Relase the configuration information */ + mx140_file_release_conf(common_service.maxwell_core, firm); + firm = NULL; + } + +#ifdef CONFIG_ARCH_EXYNOS + bhcs->bluetooth_address_nap = + (exynos_soc_info.unique_id & 0x000000FFFF00) >> 8; + bhcs->bluetooth_address_uap = + (exynos_soc_info.unique_id & 0x0000000000FF); + bhcs->bluetooth_address_lap = + (exynos_soc_info.unique_id & 0xFFFFFF000000) >> 24; +#endif + + if (bluetooth_address) { + SCSC_TAG_INFO(BT_COMMON, + "using stack supplied Bluetooth address\n"); + bhcs->bluetooth_address_nap = + (bluetooth_address & 0xFFFF00000000) >> 32; + bhcs->bluetooth_address_uap = + (bluetooth_address & 0x0000FF000000) >> 24; + bhcs->bluetooth_address_lap = + (bluetooth_address & 0x000000FFFFFF); + } + +#ifdef SCSC_BT_ADDR + /* Request the Bluetooth address file */ + SCSC_TAG_DEBUG(BT_COMMON, + "loading Bluetooth address configuration file: " + SCSC_BT_ADDR "\n"); + err = mx140_request_file(common_service.maxwell_core, SCSC_BT_ADDR, &firm); + if (err) { + /* Not found - just silently ignore this */ + SCSC_TAG_DEBUG(BT_COMMON, "Bluetooth address not found\n"); + } else if (firm && firm->size) { + u32 u[SCSC_BT_ADDR_LEN]; + +#ifdef CONFIG_SCSC_BT_BLUEZ + /* Convert the data into a native format */ + if (sscanf(firm->data, "%04x %02X %06x", + &u[0], &u[1], &u[2]) + == SCSC_BT_ADDR_LEN) { + bhcs->bluetooth_address_lap = u[2]; + bhcs->bluetooth_address_uap = u[1]; + bhcs->bluetooth_address_nap = u[0]; + } else + SCSC_TAG_WARNING(BT_COMMON, + "data size incorrect = %zu\n", firm->size); +#else + /* Convert the data into a native format */ + if (sscanf(firm->data, "%02X:%02X:%02X:%02X:%02X:%02X", + &u[0], &u[1], &u[2], &u[3], &u[4], &u[5]) + == SCSC_BT_ADDR_LEN) { + bhcs->bluetooth_address_lap = + (u[3] << 16) | (u[4] << 8) | u[5]; + bhcs->bluetooth_address_uap = u[2]; + bhcs->bluetooth_address_nap = (u[0] << 8) | u[1]; + } else + SCSC_TAG_WARNING(BT_COMMON, + "data size incorrect = %zu\n", firm->size); +#endif + /* Relase the configuration information */ + mx140_release_file(common_service.maxwell_core, firm); + firm = NULL; + } else { + SCSC_TAG_DEBUG(BT_COMMON, "empty Bluetooth address\n"); + mx140_release_file(common_service.maxwell_core, firm); + firm = NULL; + } +#endif + +#ifdef CONFIG_SCSC_DEBUG + SCSC_TAG_DEBUG(BT_COMMON, "Bluetooth address: %04X:%02X:%06X\n", + bhcs->bluetooth_address_nap, + bhcs->bluetooth_address_uap, + bhcs->bluetooth_address_lap); + + /* Always print Bluetooth Address in Kernel log */ + printk(KERN_INFO "Bluetooth address: %04X:%02X:%06X\n", + bhcs->bluetooth_address_nap, + bhcs->bluetooth_address_uap, + bhcs->bluetooth_address_lap); +#endif /* CONFIG_SCSC_DEBUG */ + + return err; +} + +/* Start the BT service */ +int slsi_sm_bt_service_start(void) +{ + int err = 0; + struct BHCS *bhcs; + + ++service_start_count; + + /* Lock the start/stop procedures to handle multiple application + * starting the sercice + */ + mutex_lock(&bt_start_mutex); + + if (disable_service) { + SCSC_TAG_WARNING(BT_COMMON, "service disabled\n"); + mutex_unlock(&bt_start_mutex); + return -EBUSY; + } + + /* Has probe been called */ + if (bt_recovery_in_progress) { + SCSC_TAG_WARNING(BT_COMMON, "recovery in progress\n"); + mutex_unlock(&bt_start_mutex); + return -EFAULT; + } + + /* Has probe been called */ + if (common_service.maxwell_core == NULL) { + SCSC_TAG_WARNING(BT_COMMON, "service probe not arrived\n"); + mutex_unlock(&bt_start_mutex); + return -EFAULT; + } + + /* Is this the first service to enter */ + if (atomic_inc_return(&bt_service.service_users) > 1) { + SCSC_TAG_WARNING(BT_COMMON, "service already opened\n"); + mutex_unlock(&bt_start_mutex); + return 0; + } + + /* Open service - will download FW - will set MBOX0 with Starting + * address + */ + SCSC_TAG_DEBUG(BT_COMMON, + "open Bluetooth service id %d opened %d times\n", + SCSC_SERVICE_ID_BT, service_start_count); + wake_lock(&bt_service.service_wake_lock); + bt_service.service = scsc_mx_service_open(common_service.maxwell_core, + SCSC_SERVICE_ID_BT, + &mx_bt_client, + &err); + if (!bt_service.service) { + SCSC_TAG_WARNING(BT_COMMON, "service open failed %d\n", err); + err = -EINVAL; + goto exit; + } + + /* Shorter completion timeout if autorecovery is disabled, as it will + * never be signalled. + */ + if (mxman_recovery_disabled()) + recovery_timeout = SLSI_BT_SERVICE_STOP_RECOVERY_DISABLED_TIMEOUT; + else + recovery_timeout = SLSI_BT_SERVICE_STOP_RECOVERY_TIMEOUT; + + /* Get shared memory region for the configuration structure from + * the MIF + */ + SCSC_TAG_DEBUG(BT_COMMON, "allocate mifram regions\n"); + err = scsc_mx_service_mifram_alloc(bt_service.service, + sizeof(struct BHCS), + &bt_service.bhcs_ref, + BSMHCP_ALIGNMENT); + if (err) { + SCSC_TAG_WARNING(BT_COMMON, "mifram alloc failed\n"); + err = -EINVAL; + goto exit; + } + + /* Get shared memory region for the protocol structure from the MIF */ + err = scsc_mx_service_mifram_alloc(bt_service.service, + sizeof(struct BSMHCP_PROTOCOL), + &bt_service.bsmhcp_ref, + BSMHCP_ALIGNMENT); + if (err) { + SCSC_TAG_WARNING(BT_COMMON, "mifram alloc failed\n"); + err = -EINVAL; + goto exit; + } + + /* The A-Box driver must have registered before reaching this point + * otherwise there is no audio routing + */ + if (audio_device != NULL) { + /* Get shared memory region for the A-Box structure from the MIF. + * The allocated memory is aligned to 4kB, but this is going to work + * only if the physical start address of the 4MB region is aligned + * to 4kB (which maybe will be always the case). + */ + + /* On 9610, do not unmap previously mapped memory from IOMMU. + * It may still be used by A-Box. + */ + + err = scsc_mx_service_mif_ptr_to_addr(bt_service.service, + scsc_mx_service_get_bt_audio_abox(bt_service.service), + &bt_service.abox_ref); + if (err) { + SCSC_TAG_WARNING(BT_COMMON, "scsc_mx_service_mif_ptr_to_addr failed\n"); + err = -EINVAL; + goto exit; + } + /* irrespective of the technical definition of probe - wrt to memory allocation it has been */ + + bt_audio.abox_virtual = (struct scsc_bt_audio_abox *) + scsc_mx_service_mif_addr_to_ptr( + bt_service.service, + bt_service.abox_ref); + + memset(bt_audio.abox_virtual, 0, sizeof(struct scsc_bt_audio_abox)); + + bt_audio.abox_virtual->magic_value = SCSC_BT_AUDIO_ABOX_MAGIC_VALUE; + bt_audio.abox_virtual->version_major = SCSC_BT_AUDIO_ABOX_VERSION_MAJOR; + bt_audio.abox_virtual->version_minor = SCSC_BT_AUDIO_ABOX_VERSION_MINOR; + bt_audio.abox_virtual->abox_to_bt_streaming_if_0_size = SCSC_BT_AUDIO_ABOX_IF_0_SIZE; + bt_audio.abox_virtual->bt_to_abox_streaming_if_0_size = SCSC_BT_AUDIO_ABOX_IF_0_SIZE; + bt_audio.abox_virtual->abox_to_bt_streaming_if_1_size = SCSC_BT_AUDIO_ABOX_IF_1_SIZE; + bt_audio.abox_virtual->abox_to_bt_streaming_if_1_offset = SCSC_BT_AUDIO_ABOX_IF_0_SIZE; + bt_audio.abox_virtual->bt_to_abox_streaming_if_1_size = SCSC_BT_AUDIO_ABOX_IF_1_SIZE; + bt_audio.abox_virtual->bt_to_abox_streaming_if_1_offset = SCSC_BT_AUDIO_ABOX_IF_0_SIZE; + + /* Resolve the physical address of the structure */ + bt_audio.abox_physical = (struct scsc_bt_audio_abox *)scsc_mx_service_mif_addr_to_phys( + bt_service.service, + bt_service.abox_ref); + + + bt_audio.dev = bt_service.dev; + } + + /* Map the configuration pointer */ + bhcs = (struct BHCS *) scsc_mx_service_mif_addr_to_ptr( + bt_service.service, + bt_service.bhcs_ref); + if (bhcs == NULL) { + SCSC_TAG_ERR(BT_COMMON, + "couldn't map kmem to bhcs_ref 0x%08x\n", + (u32)bt_service.bhcs_ref); + err = -ENOMEM; + goto exit; + } + + SCSC_TAG_INFO(BT_COMMON, + "regions (bhcs_ref=0x%08x, bsmhcp_ref=0x%08x, config_ref=0x%08x, abox_ref=0x%08x)\n", + bt_service.bhcs_ref, + bt_service.bsmhcp_ref, + bt_service.config_ref, + bt_service.abox_ref); + SCSC_TAG_INFO(BT_COMMON, "version=%u\n", BHCS_VERSION); + + err = setup_bhcs(bt_service.service, + bhcs, + bt_service.bsmhcp_ref, + sizeof(struct BSMHCP_PROTOCOL), + &bt_service.config_ref, + &bt_service.bhcs_ref); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Save the binary BT config ref and register for + * log collector to collect the hcf file + */ + if (bhcs->configuration_length > 0) { + bt_service.hcf_collection.hcf = + scsc_mx_service_mif_addr_to_ptr(bt_service.service, + bt_service.config_ref); + bt_service.hcf_collection.hcf_size = bhcs->configuration_length; + bt_collect_hcf_client.prv = &bt_service.hcf_collection; + scsc_log_collector_register_client(&bt_collect_hcf_client); + } +#endif + + if (err == -EINVAL) + goto exit; + + /* Initialise the shared-memory interface */ + err = scsc_bt_shm_init(); + if (err) { + SCSC_TAG_ERR(BT_COMMON, "scsc_bt_shm_init err %d\n", err); + err = -EINVAL; + goto exit; + } + + bt_service.bsmhcp_protocol->header.info_ap_to_bg_int_src = + scsc_service_mifintrbit_alloc_fromhost(bt_service.service, + SCSC_MIFINTR_TARGET_R4); + bt_service.bsmhcp_protocol->header.info_bg_to_ap_int_src = + scsc_service_mifintrbit_register_tohost(bt_service.service, + scsc_bt_shm_irq_handler, NULL); + bt_service.bsmhcp_protocol->header.mxlog_filter = firmware_mxlog_filter; + bt_service.bsmhcp_protocol->header.firmware_control = firmware_control; + bt_service.bsmhcp_protocol->header.abox_offset = bt_service.abox_ref; + bt_service.bsmhcp_protocol->header.abox_length = sizeof(struct scsc_bt_audio_abox); + + SCSC_TAG_DEBUG(BT_COMMON, + "firmware_control=0x%08x, firmware_control_reset=%u\n", + firmware_control, firmware_control_reset); + + if (firmware_control_reset) + firmware_control = 0; + + /* Start service last - after setting up shared memory resources */ + SCSC_TAG_DEBUG(BT_COMMON, "starting Bluetooth service\n"); + err = scsc_mx_service_start(bt_service.service, bt_service.bhcs_ref); + if (err) { + SCSC_TAG_ERR(BT_COMMON, "scsc_mx_service_start err %d\n", err); + err = -EINVAL; + } else { + SCSC_TAG_DEBUG(BT_COMMON, "Bluetooth service running\n"); + slsi_kic_system_event( + slsi_kic_system_event_category_initialisation, + slsi_kic_system_events_bt_on, 0); + + mutex_lock(&bt_audio_mutex); + if (audio_device && !audio_device_probed) { + err = slsi_bt_audio_probe(); + + audio_device_probed = true; + } + mutex_unlock(&bt_audio_mutex); + } + + if (bt_service.bsmhcp_protocol->header.firmware_features & + BSMHCP_FEATURE_M4_INTERRUPTS) + SCSC_TAG_DEBUG(BT_COMMON, "features enabled: M4_INTERRUPTS\n"); + +exit: + if (err) { + if (slsi_sm_bt_service_cleanup(false) == 0) + atomic_dec(&bt_service.service_users); + } + + wake_unlock(&bt_service.service_wake_lock); + mutex_unlock(&bt_start_mutex); + return err; +} + +#ifdef CONFIG_SCSC_ANT +/* Start the ANT service */ +int slsi_sm_ant_service_start(void) +{ + int err = 0; + struct BHCS *bhcs; + + ++ant_service_start_count; + + /* Lock the start/stop procedures to handle multiple application + * starting the sercice + */ + mutex_lock(&ant_start_mutex); + + if (disable_service) { + SCSC_TAG_WARNING(BT_COMMON, "service disabled\n"); + mutex_unlock(&ant_start_mutex); + return -EBUSY; + } + + /* Has probe been called */ + if (common_service.maxwell_core == NULL) { + SCSC_TAG_WARNING(BT_COMMON, "service probe not arrived\n"); + mutex_unlock(&ant_start_mutex); + return -EFAULT; + } + + /* Is this the first service to enter */ + if (atomic_inc_return(&ant_service.service_users) > 1) { + SCSC_TAG_WARNING(BT_COMMON, "service already opened\n"); + mutex_unlock(&ant_start_mutex); + return 0; + } + + /* Open service - will download FW - will set MBOX0 with Starting + * address + */ + SCSC_TAG_DEBUG(BT_COMMON, + "open ANT service id %d opened %d times\n", + SCSC_SERVICE_ID_ANT, ant_service_start_count); + + wake_lock(&ant_service.service_wake_lock); + ant_service.service = scsc_mx_service_open(common_service.maxwell_core, + SCSC_SERVICE_ID_ANT, + &mx_ant_client, + &err); + if (!ant_service.service) { + SCSC_TAG_WARNING(BT_COMMON, "ant service open failed %d\n", err); + err = -EINVAL; + goto exit; + } + + /* Shorter completion timeout if autorecovery is disabled, as it will + * never be signalled. + */ + if (mxman_recovery_disabled()) + recovery_timeout = SLSI_BT_SERVICE_STOP_RECOVERY_DISABLED_TIMEOUT; + else + recovery_timeout = SLSI_BT_SERVICE_STOP_RECOVERY_TIMEOUT; + + + /* Get shared memory region for the configuration structure from + * the MIF + */ + SCSC_TAG_DEBUG(BT_COMMON, "allocate mifram regions\n"); + err = scsc_mx_service_mifram_alloc(ant_service.service, + sizeof(struct BHCS), + &ant_service.bhcs_ref, + BSMHCP_ALIGNMENT); + if (err) { + SCSC_TAG_WARNING(BT_COMMON, "mifram alloc failed\n"); + err = -EINVAL; + goto exit; + } + + /* Get shared memory region for the protocol structure from the MIF */ + err = scsc_mx_service_mifram_alloc(ant_service.service, + sizeof(struct ASMHCP_PROTOCOL), + &ant_service.asmhcp_ref, + BSMHCP_ALIGNMENT); + if (err) { + SCSC_TAG_WARNING(BT_COMMON, "mifram alloc failed\n"); + err = -EINVAL; + goto exit; + } + + /* Map the configuration pointer */ + bhcs = (struct BHCS *) scsc_mx_service_mif_addr_to_ptr( + ant_service.service, + ant_service.bhcs_ref); + if (bhcs == NULL) { + SCSC_TAG_ERR(BT_COMMON, + "couldn't map kmem to bhcs_ref 0x%08x\n", + (u32)ant_service.bhcs_ref); + err = -ENOMEM; + goto exit; + } + + SCSC_TAG_INFO(BT_COMMON, + "regions (bhcs_ref=0x%08x, bsmhcp_ref=0x%08x, config_ref=0x%08x)\n", + ant_service.bhcs_ref, + ant_service.asmhcp_ref, + ant_service.config_ref); + SCSC_TAG_INFO(BT_COMMON, "version=%u\n", BHCS_VERSION); + + err = setup_bhcs(ant_service.service, + bhcs, + ant_service.asmhcp_ref, + sizeof(struct ASMHCP_PROTOCOL), + &ant_service.config_ref, + &ant_service.bhcs_ref); + + if (err == -EINVAL) + goto exit; + + /* Initialise the shared-memory interface */ + err = scsc_ant_shm_init(); + if (err) { + SCSC_TAG_ERR(BT_COMMON, "scsc_ant_shm_init err %d\n", err); + err = -EINVAL; + goto exit; + } + + ant_service.asmhcp_protocol->header.mxlog_filter = firmware_mxlog_filter; + ant_service.asmhcp_protocol->header.firmware_control = firmware_control; + + SCSC_TAG_DEBUG(BT_COMMON, + "firmware_control=0x%08x, firmware_control_reset=%u\n", + firmware_control, firmware_control_reset); + + if (firmware_control_reset) + firmware_control = 0; + + /* Start service last - after setting up shared memory resources */ + SCSC_TAG_DEBUG(BT_COMMON, "starting ANT service\n"); + err = scsc_mx_service_start(ant_service.service, ant_service.bhcs_ref); + if (err) { + SCSC_TAG_ERR(BT_COMMON, "scsc_mx_service_start err %d\n", err); + err = -EINVAL; + } else { + SCSC_TAG_DEBUG(BT_COMMON, "Ant service running\n"); + slsi_kic_system_event( + slsi_kic_system_event_category_initialisation, + slsi_kic_system_events_ant_on, 0); + } + +exit: + if (err) { + if (slsi_sm_ant_service_cleanup(false) == 0) + atomic_dec(&ant_service.service_users); + } + + wake_unlock(&ant_service.service_wake_lock); + mutex_unlock(&ant_start_mutex); + return err; +} +#endif + +/* Stop the BT service */ +static int slsi_sm_bt_service_stop(void) +{ + SCSC_TAG_INFO(BT_COMMON, "bt service users %u\n", atomic_read(&bt_service.service_users)); + + if (1 < atomic_read(&bt_service.service_users)) { + atomic_dec(&bt_service.service_users); + } else if (1 == atomic_read(&bt_service.service_users)) { + if (slsi_sm_bt_service_cleanup(true) == 0) + atomic_dec(&bt_service.service_users); + else + return -EIO; + } + + return 0; +} + +#ifdef CONFIG_SCSC_ANT +/* Stop the ANT service */ +static int slsi_sm_ant_service_stop(void) +{ + SCSC_TAG_INFO(BT_COMMON, "ant service users %u\n", atomic_read(&ant_service.service_users)); + + if (atomic_read(&ant_service.service_users) > 1) { + atomic_dec(&ant_service.service_users); + } else if (atomic_read(&ant_service.service_users) == 1) { + if (slsi_sm_ant_service_cleanup(true) == 0) + atomic_dec(&ant_service.service_users); + else + return -EIO; + } + + return 0; +} +#endif + +static int scsc_bt_h4_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + SCSC_TAG_INFO(BT_COMMON, "(h4_users=%u)\n", bt_service.h4_users ? 1 : 0); + + if (!bt_service.h4_users) { + ret = slsi_sm_bt_service_start(); + if (0 == ret) + bt_service.h4_users = true; + } else { + ret = -EBUSY; + } + + return ret; +} + +static int scsc_bt_h4_release(struct inode *inode, struct file *file) +{ + SCSC_TAG_INFO(BT_COMMON, "\n"); + + mutex_lock(&bt_start_mutex); + wake_lock(&bt_service.service_wake_lock); + if (!bt_recovery_in_progress) { + if (slsi_sm_bt_service_stop() == -EIO) + goto recovery; + + /* Clear all control structures */ + bt_service.read_offset = 0; + bt_service.read_operation = 0; + bt_service.read_index = 0; + bt_service.h4_write_offset = 0; + + bt_service.h4_users = false; + + /* The recovery flag can be set in case of crossing release and + * recovery signaling. It's safe to check the flag here since + * the bt_start_mutex guarantees that the remove/probe callbacks + * will be called after the mutex is released. Jump to the + * normal recovery path. + */ + if (bt_recovery_in_progress) + goto recovery; + + wake_unlock(&bt_service.service_wake_lock); + mutex_unlock(&bt_start_mutex); + } else { + int ret; +recovery: + complete_all(&bt_service.recovery_release_complete); + wake_unlock(&bt_service.service_wake_lock); + mutex_unlock(&bt_start_mutex); + + ret = wait_for_completion_timeout(&bt_service.recovery_probe_complete, + msecs_to_jiffies(recovery_timeout)); + if (ret == 0) + SCSC_TAG_INFO(BT_COMMON, "recovery_probe_complete timeout\n"); + } + + return 0; +} + +#ifdef CONFIG_SCSC_ANT +static int scsc_ant_release(struct inode *inode, struct file *file) +{ + SCSC_TAG_INFO(BT_COMMON, "\n"); + + mutex_lock(&ant_start_mutex); + wake_lock(&ant_service.service_wake_lock); + if (!ant_recovery_in_progress) { + if (slsi_sm_ant_service_stop() == -EIO) + goto recovery; + + /* Clear all control structures */ + ant_service.read_offset = 0; + ant_service.read_operation = 0; + ant_service.read_index = 0; + ant_service.ant_write_offset = 0; + + ant_service.ant_users = false; + + /* The recovery flag can be set in case of crossing release and + * recovery signaling. It's safe to check the flag here since + * the bt_start_mutex guarantees that the remove/probe callbacks + * will be called after the mutex is released. Jump to the + * normal recovery path. + */ + if (ant_recovery_in_progress) + goto recovery; + + wake_unlock(&ant_service.service_wake_lock); + mutex_unlock(&ant_start_mutex); + } else { + int ret; +recovery: + complete_all(&ant_service.recovery_release_complete); + wake_unlock(&ant_service.service_wake_lock); + mutex_unlock(&ant_start_mutex); + + ret = wait_for_completion_timeout(&ant_service.recovery_probe_complete, + msecs_to_jiffies(recovery_timeout)); + if (ret == 0) + SCSC_TAG_INFO(BT_COMMON, "recovery_probe_complete timeout\n"); + } + + return 0; +} +#endif + +#ifdef CONFIG_SCSC_ANT +static int scsc_ant_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + SCSC_TAG_INFO(BT_COMMON, "(ant_users=%u)\n", ant_service.ant_users ? 1 : 0); + + if (ant_recovery_in_progress) { + SCSC_TAG_WARNING(BT_COMMON, "recovery in progress\n"); + wait_event_interruptible_timeout(ant_recovery_complete_queue, + ant_recovery_in_progress == 0, + SCSC_ANT_MAX_TIMEOUT); + if (ant_recovery_in_progress) { + SCSC_TAG_WARNING(BT_COMMON, "recovery timeout, aborting\n"); + return -EFAULT; + } + } + if (!ant_service.ant_users) { + ret = slsi_sm_ant_service_start(); + if (ret == 0) + ant_service.ant_users = true; + } else { + ret = -EBUSY; + } + + return ret; +} +#endif + +static long scsc_default_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + UNUSED(file); + UNUSED(cmd); + UNUSED(arg); + + switch (cmd) { + case TCGETS: + SCSC_TAG_DEBUG(BT_COMMON, "TCGETS (arg=%lu)\n", arg); + break; + case TCSETS: + SCSC_TAG_DEBUG(BT_COMMON, "TCSETS (arg=%lu)\n", arg); + break; + default: + SCSC_TAG_DEBUG(BT_COMMON, + "trapped ioctl in virtual tty device, cmd %d arg %lu\n", + cmd, arg); + break; + } + + return 0; +} + +static int scsc_bt_trigger_recovery(void *priv, + enum slsi_kic_test_recovery_type type) +{ + int err = 0; + + SCSC_TAG_INFO(BT_COMMON, "forcing panic\n"); + + mutex_lock(&bt_start_mutex); + + if (0 < atomic_read(&bt_service.service_users) && + bt_service.bsmhcp_protocol) { + SCSC_TAG_INFO(BT_COMMON, "trashing magic value\n"); + + if (slsi_kic_test_recovery_type_service_stop_panic == type) + bt_service.bsmhcp_protocol->header.firmware_control = + BSMHCP_CONTROL_STOP_PANIC; + else if (slsi_kic_test_recovery_type_service_start_panic == + type) + firmware_control = BSMHCP_CONTROL_START_PANIC; + else + bt_service.bsmhcp_protocol->header.magic_value = 0; + + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } else { + if (slsi_kic_test_recovery_type_service_stop_panic == type) + firmware_control = BSMHCP_CONTROL_STOP_PANIC; + else if (slsi_kic_test_recovery_type_service_start_panic == + type) + firmware_control = BSMHCP_CONTROL_START_PANIC; + else + err = -EFAULT; + } + + mutex_unlock(&bt_start_mutex); + + return err; +} + +#ifdef CONFIG_SCSC_ANT +static int scsc_ant_trigger_recovery(void *priv, + enum slsi_kic_test_recovery_type type) +{ + int err = 0; + + SCSC_TAG_INFO(BT_COMMON, "forcing panic\n"); + + mutex_lock(&ant_start_mutex); + + if (atomic_read(&ant_service.service_users) > 0 && + ant_service.asmhcp_protocol) { + SCSC_TAG_INFO(BT_COMMON, "trashing magic value\n"); + + if (slsi_kic_test_recovery_type_service_stop_panic == type) + ant_service.asmhcp_protocol->header.firmware_control = + BSMHCP_CONTROL_STOP_PANIC; + else if (slsi_kic_test_recovery_type_service_start_panic == + type) + firmware_control = BSMHCP_CONTROL_START_PANIC; + else + ant_service.asmhcp_protocol->header.magic_value = 0; + + scsc_service_mifintrbit_bit_set(ant_service.service, + ant_service.asmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } else { + if (slsi_kic_test_recovery_type_service_stop_panic == type) + firmware_control = BSMHCP_CONTROL_STOP_PANIC; + else if (slsi_kic_test_recovery_type_service_start_panic == + type) + firmware_control = BSMHCP_CONTROL_START_PANIC; + else + err = -EFAULT; + } + + mutex_unlock(&ant_start_mutex); + + return err; +} +#endif + +static const struct file_operations scsc_bt_shm_fops = { + .owner = THIS_MODULE, + .open = scsc_bt_h4_open, + .release = scsc_bt_h4_release, + .read = scsc_bt_shm_h4_read, + .write = scsc_bt_shm_h4_write, + .poll = scsc_bt_shm_h4_poll, + .unlocked_ioctl = scsc_default_ioctl, +}; + +#ifdef CONFIG_SCSC_ANT +static const struct file_operations scsc_ant_shm_fops = { + .owner = THIS_MODULE, + .open = scsc_ant_open, + .release = scsc_ant_release, + .read = scsc_shm_ant_read, + .write = scsc_shm_ant_write, + .poll = scsc_shm_ant_poll, +}; +#endif + +static struct slsi_kic_bt_ops scsc_bt_kic_ops = { + .trigger_recovery = scsc_bt_trigger_recovery +}; + +#ifdef CONFIG_SCSC_ANT +static struct slsi_kic_ant_ops scsc_ant_kic_ops = { + .trigger_recovery = scsc_ant_trigger_recovery +}; +#endif + +/* A new MX instance is available */ +void slsi_bt_service_probe(struct scsc_mx_module_client *module_client, + struct scsc_mx *mx, + enum scsc_module_client_reason reason) +{ + /* Note: mx identifies the instance */ + SCSC_TAG_INFO(BT_COMMON, + "BT service probe (%s %p)\n", module_client->name, mx); + + mutex_lock(&bt_start_mutex); + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !bt_recovery_in_progress) { + SCSC_TAG_INFO(BT_COMMON, + "BT service probe recovery, but no recovery in progress\n"); + goto done; + } + + bt_service.dev = scsc_mx_get_device(mx); + common_service.maxwell_core = mx; + + get_device(bt_service.dev); + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && bt_recovery_in_progress) { + complete_all(&bt_service.recovery_probe_complete); + bt_recovery_in_progress = 0; + } + + slsi_bt_notify_probe(bt_service.dev, + &scsc_bt_shm_fops, + &bt_service.error_count, + &bt_service.read_wait); + +done: + mutex_unlock(&bt_start_mutex); +} + +/* The MX instance is now unavailable */ +static void slsi_bt_service_remove(struct scsc_mx_module_client *module_client, + struct scsc_mx *mx, + enum scsc_module_client_reason reason) +{ + SCSC_TAG_INFO(BT_COMMON, + "BT service remove (%s %p)\n", module_client->name, mx); + + mutex_lock(&bt_start_mutex); + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !bt_recovery_in_progress) { + SCSC_TAG_INFO(BT_COMMON, + "BT service remove recovery, but no recovery in progress\n"); + goto done; + } + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && bt_recovery_in_progress) { + mutex_unlock(&bt_start_mutex); + + /* Wait forever for recovery_release_complete, as it will + * arrive even if autorecovery is disabled. + */ + SCSC_TAG_INFO(BT_COMMON, "wait for recovery_release_complete\n"); + wait_for_completion(&bt_service.recovery_release_complete); + reinit_completion(&bt_service.recovery_release_complete); + + mutex_lock(&bt_start_mutex); + if (slsi_sm_bt_service_stop() == -EIO) + SCSC_TAG_INFO(BT_COMMON, "Service stop or close failed during recovery.\n"); + + bt_service.h4_users = false; + + /* Clear all control structures */ + bt_service.read_offset = 0; + bt_service.read_operation = 0; + bt_service.read_index = 0; + bt_service.h4_write_offset = 0; + } + + slsi_bt_notify_remove(); + put_device(bt_service.dev); + common_service.maxwell_core = NULL; + +done: + mutex_unlock(&bt_start_mutex); + + SCSC_TAG_INFO(BT_COMMON, + "BT service remove complete (%s %p)\n", module_client->name, mx); +} + +/* BT service driver registration interface */ +static struct scsc_mx_module_client bt_driver = { + .name = "BT driver", + .probe = slsi_bt_service_probe, + .remove = slsi_bt_service_remove, +}; + +#ifdef CONFIG_SCSC_ANT +/* A new MX instance is available */ +void slsi_ant_service_probe(struct scsc_mx_module_client *module_client, + struct scsc_mx *mx, + enum scsc_module_client_reason reason) +{ + /* Note: mx identifies the instance */ + SCSC_TAG_INFO(BT_COMMON, + "ANT service probe (%s %p)\n", module_client->name, mx); + + mutex_lock(&ant_start_mutex); + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !ant_recovery_in_progress) { + SCSC_TAG_INFO(BT_COMMON, + "ANT service probe recovery, but no recovery in progress\n"); + goto done; + } + + ant_service.dev = scsc_mx_get_device(mx); + common_service.maxwell_core = mx; + + get_device(ant_service.dev); + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && ant_recovery_in_progress) { + complete_all(&ant_service.recovery_probe_complete); + ant_recovery_in_progress = 0; + } + +done: + mutex_unlock(&ant_start_mutex); + wake_up_interruptible(&ant_recovery_complete_queue); +} +#endif + +#ifdef CONFIG_SCSC_ANT +/* The MX instance is now unavailable */ +static void slsi_ant_service_remove(struct scsc_mx_module_client *module_client, + struct scsc_mx *mx, + enum scsc_module_client_reason reason) +{ + SCSC_TAG_INFO(BT_COMMON, + "ANT service remove (%s %p)\n", module_client->name, mx); + + mutex_lock(&ant_start_mutex); + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !ant_recovery_in_progress) { + SCSC_TAG_INFO(BT_COMMON, + "ANT service remove recovery, but no recovery in progress\n"); + goto done; + } + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && ant_recovery_in_progress) { + int ret; + + mutex_unlock(&ant_start_mutex); + + /* Wait full duration for recovery_release_complete, as it will + * arrive even if autorecovery is disabled. + */ + ret = wait_for_completion_timeout(&ant_service.recovery_release_complete, + msecs_to_jiffies(SLSI_BT_SERVICE_STOP_RECOVERY_TIMEOUT)); + reinit_completion(&ant_service.recovery_release_complete); + if (ret == 0) + SCSC_TAG_INFO(BT_COMMON, "recovery_release_complete timeout\n"); + + mutex_lock(&ant_start_mutex); + if (slsi_sm_ant_service_stop() == -EIO) + SCSC_TAG_INFO(BT_COMMON, "Service stop or close failed during recovery.\n"); + + ant_service.ant_users = false; + + /* Clear all control structures */ + ant_service.read_offset = 0; + ant_service.read_operation = 0; + ant_service.read_index = 0; + ant_service.ant_write_offset = 0; + } + + put_device(ant_service.dev); + common_service.maxwell_core = NULL; + +done: + mutex_unlock(&ant_start_mutex); + + SCSC_TAG_INFO(BT_COMMON, + "ANT service remove complete (%s %p)\n", module_client->name, mx); +} +#endif + +#ifdef CONFIG_SCSC_ANT +/* ANT service driver registration interface */ +static struct scsc_mx_module_client ant_driver = { + .name = "ANT driver", + .probe = slsi_ant_service_probe, + .remove = slsi_ant_service_remove, +}; +#endif + +static void slsi_bt_service_proc_show_firmware(struct seq_file *m) +{ + struct BSMHCP_FW_INFO *info = + &bt_service.bsmhcp_protocol->information; + int res; + u32 index; + u32 user_defined_count = info->user_defined_count; + + bt_info_trigger++; + + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.info_ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + + res = wait_event_interruptible_timeout(bt_service.info_wait, + bt_info_trigger == bt_info_interrupt, + 2*HZ); + + seq_printf(m, " r4_from_ap_interrupt_count = %u\n", + info->r4_from_ap_interrupt_count); + seq_printf(m, " m4_from_ap_interrupt_count = %u\n", + info->m4_from_ap_interrupt_count); + seq_printf(m, " r4_to_ap_interrupt_count = %u\n", + info->r4_to_ap_interrupt_count); + seq_printf(m, " m4_to_ap_interrupt_count = %u\n\n", + info->m4_to_ap_interrupt_count); + seq_printf(m, " bt_deep_sleep_time_total = %u\n", + info->bt_deep_sleep_time_total); + seq_printf(m, " bt_deep_sleep_wakeup_duration = %u\n\n", + info->bt_deep_sleep_wakeup_duration); + seq_printf(m, " sched_n_messages = %u\n\n", + info->sched_n_messages); + seq_printf(m, " user_defined_count = %u\n\n", + info->user_defined_count); + + if (user_defined_count > BSMHCP_FW_INFO_USER_DEFINED_COUNT) + user_defined_count = BSMHCP_FW_INFO_USER_DEFINED_COUNT; + + for (index = 0; index < user_defined_count; index++) + seq_printf(m, " user%02u = 0x%08x (%u)\n", + index, info->user_defined[index], info->user_defined[index]); + + if (user_defined_count) + seq_puts(m, "\n"); + + seq_printf(m, " bt_info_trigger = %u\n", + bt_info_trigger); + seq_printf(m, " bt_info_interrupt = %u\n\n", + bt_info_interrupt); + seq_printf(m, " result = %d\n", res); +} + +static int slsi_bt_service_proc_show(struct seq_file *m, void *v) +{ + char allocated_text[BSMHCP_DATA_BUFFER_TX_ACL_SIZE + 1]; + char processed_text[BSMHCP_TRANSFER_RING_EVT_SIZE + 1]; + size_t index; + struct scsc_bt_avdtp_detect_hci_connection *cur = bt_service.avdtp_detect.connections; + + seq_puts(m, "Driver statistics:\n"); + seq_printf(m, " write_wake_lock_count = %zu\n", + bt_service.write_wake_lock_count); + seq_printf(m, " write_wake_unlock_count = %zu\n\n", + bt_service.write_wake_unlock_count); + + seq_printf(m, " mailbox_hci_evt_read = %u\n", + bt_service.mailbox_hci_evt_read); + seq_printf(m, " mailbox_hci_evt_write = %u\n", + bt_service.mailbox_hci_evt_write); + seq_printf(m, " mailbox_acl_rx_read = %u\n", + bt_service.mailbox_acl_rx_read); + seq_printf(m, " mailbox_acl_rx_write = %u\n", + bt_service.mailbox_acl_rx_write); + seq_printf(m, " mailbox_acl_free_read = %u\n", + bt_service.mailbox_acl_free_read); + seq_printf(m, " mailbox_acl_free_read_scan = %u\n", + bt_service.mailbox_acl_free_read_scan); + seq_printf(m, " mailbox_acl_free_write = %u\n", + bt_service.mailbox_acl_free_write); + + seq_printf(m, " hci_event_paused = %u\n", + bt_service.hci_event_paused); + seq_printf(m, " acldata_paused = %u\n\n", + bt_service.acldata_paused); + + seq_printf(m, " interrupt_count = %zu\n", + bt_service.interrupt_count); + seq_printf(m, " interrupt_read_count = %zu\n", + bt_service.interrupt_read_count); + seq_printf(m, " interrupt_write_count = %zu\n", + bt_service.interrupt_write_count); + + for (index = 0; index < BSMHCP_DATA_BUFFER_TX_ACL_SIZE; index++) + allocated_text[index] = bt_service.allocated[index] ? '1' : '0'; + allocated_text[BSMHCP_DATA_BUFFER_TX_ACL_SIZE] = 0; + + for (index = 0; index < BSMHCP_TRANSFER_RING_EVT_SIZE; index++) + processed_text[index] = bt_service.processed[index] ? '1' : '0'; + processed_text[BSMHCP_DATA_BUFFER_TX_ACL_SIZE] = 0; + + seq_printf(m, " allocated_count = %u\n", + bt_service.allocated_count); + seq_printf(m, " freed_count = %u\n", + bt_service.freed_count); + seq_printf(m, " allocated = %s\n", + allocated_text); + seq_printf(m, " processed = %s\n\n", + processed_text); + + while (cur) { + seq_printf(m, " avdtp_hci_connection_handle = %u\n\n", + cur->hci_connection_handle); + seq_printf(m, " avdtp_signaling_src_cid = %u\n", + cur->signal.src_cid); + seq_printf(m, " avdtp_signaling_dst_cid = %u\n", + cur->signal.dst_cid); + seq_printf(m, " avdtp_streaming_src_cid = %u\n", + cur->stream.src_cid); + seq_printf(m, " avdtp_streaming_dst_cid = %u\n", + cur->stream.dst_cid); + cur = cur->next; + } + seq_puts(m, "Firmware statistics:\n"); + + mutex_lock(&bt_start_mutex); + + if (NULL != bt_service.service) { + if (bt_service.bsmhcp_protocol->header.firmware_features & + BSMHCP_FEATURE_FW_INFORMATION) { + slsi_bt_service_proc_show_firmware(m); + } else + seq_puts(m, + " Firmware does not provide this information\n"); + } else + seq_puts(m, + " Error: bluetooth service is currently disabled\n"); + + mutex_unlock(&bt_start_mutex); + + return 0; +} + +static int slsi_bt_service_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, slsi_bt_service_proc_show, NULL); +} + +static const struct file_operations scsc_bt_procfs_fops = { + .owner = THIS_MODULE, + .open = slsi_bt_service_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int scsc_mxlog_filter_set_param_cb(const char *buffer, + const struct kernel_param *kp) +{ + int ret; + u32 value; + + ret = kstrtou32(buffer, 0, &value); + if (!ret) { + firmware_mxlog_filter = value; + + mutex_lock(&bt_start_mutex); + if (bt_service.service) { + bt_service.bsmhcp_protocol->header.mxlog_filter = + firmware_mxlog_filter; + + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } + mutex_unlock(&bt_start_mutex); + } + + return ret; +} + +static int scsc_mxlog_filter_get_param_cb(char *buffer, + const struct kernel_param *kp) +{ + return sprintf(buffer, "filter=0x%08x\n", firmware_mxlog_filter); +} + +static struct kernel_param_ops scsc_mxlog_filter_ops = { + .set = scsc_mxlog_filter_set_param_cb, + .get = scsc_mxlog_filter_get_param_cb, +}; + +module_param_cb(mxlog_filter, &scsc_mxlog_filter_ops, NULL, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mxlog_filter, + "Set the filter for MX log in the Bluetooth firmware"); + +static int scsc_force_crash_set_param_cb(const char *buffer, + const struct kernel_param *kp) +{ + int ret; + u32 value; + + ret = kstrtou32(buffer, 0, &value); + if (!ret && value == 0xDEADDEAD) { + mutex_lock(&bt_start_mutex); + if (bt_service.service) { + atomic_inc(&bt_service.error_count); + wake_up(&bt_service.read_wait); + } + mutex_unlock(&bt_start_mutex); + } + + return ret; +} + +static struct kernel_param_ops scsc_force_crash_ops = { + .set = scsc_force_crash_set_param_cb, + .get = NULL, +}; + +module_param_cb(force_crash, &scsc_force_crash_ops, NULL, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(force_crash, + "Forces a crash of the Bluetooth driver"); + + +phys_addr_t scsc_bt_audio_get_paddr_buf(bool tx) +{ + if (bt_audio.abox_physical) { + struct scsc_bt_audio_abox *abox_physical; + void *ptr; + + abox_physical = bt_audio.abox_physical; + if (tx) + ptr = abox_physical->bt_to_abox_streaming_if_data; + else + ptr = abox_physical->abox_to_bt_streaming_if_data; + + return (phys_addr_t)ptr; + } else + return 0; +} +EXPORT_SYMBOL(scsc_bt_audio_get_paddr_buf); + +unsigned int scsc_bt_audio_get_rate(int id) +{ + if (!bt_audio.abox_virtual) + return 0; + + switch (id) { + case 0: + return bt_audio.abox_virtual->streaming_if_0_sample_rate; + case 1: + return bt_audio.abox_virtual->streaming_if_1_sample_rate; + default: + return 0; + } +} +EXPORT_SYMBOL(scsc_bt_audio_get_rate); + +int scsc_bt_audio_register(struct device *dev, + int (*dev_iommu_map)(struct device *, phys_addr_t, size_t), + void (*dev_iommu_unmap)(struct device *, size_t)) +{ + int ret = 0; + + mutex_lock(&bt_audio_mutex); + + if (audio_device != NULL || dev == NULL || + dev_iommu_map == NULL || dev_iommu_unmap == NULL) { + SCSC_TAG_ERR(BT_COMMON, + "failed audio_device %p dev %p dev_iommu_map %p dev_iommu_unmap %p\n", + audio_device, dev, dev_iommu_map, dev_iommu_unmap); + ret = -EINVAL; + } else { + audio_device = dev; + bt_audio.dev_iommu_map = dev_iommu_map; + bt_audio.dev_iommu_unmap = dev_iommu_unmap; + } + + mutex_unlock(&bt_audio_mutex); + + return ret; +} +EXPORT_SYMBOL(scsc_bt_audio_register); + +int scsc_bt_audio_unregister(struct device *dev) +{ + int ret = 0; + + mutex_lock(&bt_audio_mutex); + + if (audio_device != NULL && dev == audio_device) { + + /* Unmap ringbuffer IOMMU now that A-Box is finished with it, + * but for safety don't allow this if BT is running. + * + * In practice, A-Box driver only unregisters if platform + * driver unloads at shutdown, so it would be safe to leave the + * memmory mapped. + */ + if (atomic_read(&bt_service.service_users) == 0 && audio_device_probed) + slsi_bt_audio_remove(); + + bt_audio.dev = NULL; + bt_audio.abox_virtual = NULL; + bt_audio.abox_physical = NULL; + bt_audio.dev_iommu_map = NULL; + bt_audio.dev_iommu_unmap = NULL; + audio_device = NULL; + audio_device_probed = false; + } else + ret = -EINVAL; + + mutex_unlock(&bt_audio_mutex); + + return ret; +} +EXPORT_SYMBOL(scsc_bt_audio_unregister); + +/******* Module entry/exit point ********/ +static int __init scsc_bt_module_init(void) +{ + int ret; + struct proc_dir_entry *procfs_dir; + + SCSC_TAG_INFO(BT_COMMON, "%s %s (C) %s\n", + SCSC_MODDESC, SCSC_MODVERSION, SCSC_MODAUTH); + + memset(&bt_service, 0, sizeof(bt_service)); +#ifdef CONFIG_SCSC_ANT + memset(&ant_service, 0, sizeof(ant_service)); +#endif + + init_waitqueue_head(&bt_service.read_wait); + init_waitqueue_head(&bt_service.info_wait); + + wake_lock_init(&bt_service.read_wake_lock, + WAKE_LOCK_SUSPEND, + "bt_read_wake_lock"); + wake_lock_init(&bt_service.write_wake_lock, + WAKE_LOCK_SUSPEND, + "bt_write_wake_lock"); + wake_lock_init(&bt_service.service_wake_lock, + WAKE_LOCK_SUSPEND, + "bt_service_wake_lock"); + +#ifdef CONFIG_SCSC_ANT + init_waitqueue_head(&ant_service.read_wait); + + wake_lock_init(&ant_service.read_wake_lock, + WAKE_LOCK_SUSPEND, + "ant_read_wake_lock"); + wake_lock_init(&ant_service.write_wake_lock, + WAKE_LOCK_SUSPEND, + "ant_write_wake_lock"); + wake_lock_init(&ant_service.service_wake_lock, + WAKE_LOCK_SUSPEND, + "ant_service_wake_lock"); +#endif + + procfs_dir = proc_mkdir("driver/scsc_bt", NULL); + if (NULL != procfs_dir) { + proc_create_data("stats", S_IRUSR | S_IRGRP, + procfs_dir, &scsc_bt_procfs_fops, NULL); + } + + ret = alloc_chrdev_region(&bt_service.device, 0, + SCSC_TTY_MINORS, "scsc_char"); + if (ret) { + SCSC_TAG_ERR(BT_COMMON, "error alloc_chrdev_region %d\n", ret); + return ret; + } + + common_service.class = class_create(THIS_MODULE, "scsc_char"); + if (IS_ERR(common_service.class)) { + ret = PTR_ERR(common_service.class); + goto error; + } + + cdev_init(&bt_service.h4_cdev, &scsc_bt_shm_fops); + ret = cdev_add(&bt_service.h4_cdev, + MKDEV(MAJOR(bt_service.device), MINOR(0)), 1); + if (ret) { + SCSC_TAG_ERR(BT_COMMON, + "cdev_add failed for device %s\n", + SCSC_H4_DEVICE_NAME); + bt_service.h4_cdev.dev = 0; + goto error; + } + + bt_service.h4_device = device_create(common_service.class, + NULL, + bt_service.h4_cdev.dev, + NULL, + SCSC_H4_DEVICE_NAME); + if (bt_service.h4_device == NULL) { + cdev_del(&bt_service.h4_cdev); + ret = -EFAULT; + goto error; + } + + init_completion(&bt_service.recovery_probe_complete); + init_completion(&bt_service.recovery_release_complete); + +#ifdef CONFIG_SCSC_ANT + ret = alloc_chrdev_region(&ant_service.device, 0, + SCSC_TTY_MINORS, "scsc_ant_char"); + if (ret) { + SCSC_TAG_ERR(BT_COMMON, "error alloc_chrdev_region %d\n", ret); + return ret; + } + + cdev_init(&ant_service.ant_cdev, &scsc_ant_shm_fops); + ret = cdev_add(&ant_service.ant_cdev, + MKDEV(MAJOR(ant_service.device), MINOR(0)), 1); + if (ret) { + SCSC_TAG_ERR(BT_COMMON, + "cdev_add failed for device %s\n", + SCSC_ANT_DEVICE_NAME); + ant_service.ant_cdev.dev = 0; + goto error; + } + + ant_service.ant_device = device_create(common_service.class, + NULL, + ant_service.ant_cdev.dev, + NULL, + SCSC_ANT_DEVICE_NAME); + if (ant_service.ant_device == NULL) { + cdev_del(&ant_service.ant_cdev); + ret = -EFAULT; + goto error; + } + + init_completion(&ant_service.recovery_probe_complete); + init_completion(&ant_service.recovery_release_complete); +#endif + + /* Register KIC interface */ + slsi_kic_bt_ops_register(NULL, &scsc_bt_kic_ops); + + /* Register with MX manager */ + scsc_mx_module_register_client_module(&bt_driver); + +#ifdef CONFIG_SCSC_ANT +/* Register KIC interface */ + slsi_kic_ant_ops_register(NULL, &scsc_ant_kic_ops); + SCSC_TAG_DEBUG(BT_COMMON, "Register the KIC interface, %p\n", + &scsc_ant_kic_ops); + + /* Register with MX manager */ + scsc_mx_module_register_client_module(&ant_driver); +#endif + + SCSC_TAG_DEBUG(BT_COMMON, "dev=%u class=%p\n", + bt_service.device, common_service.class); + + spin_lock_init(&bt_service.avdtp_detect.lock); + spin_lock_init(&bt_service.avdtp_detect.fw_write_lock); + +#ifdef CONFIG_ARCH_EXYNOS + sprintf(bluetooth_address_fallback, "%02X:%02X:%02X:%02X:%02X:%02X", + (exynos_soc_info.unique_id & 0x000000FF0000) >> 16, + (exynos_soc_info.unique_id & 0x00000000FF00) >> 8, + (exynos_soc_info.unique_id & 0x0000000000FF) >> 0, + (exynos_soc_info.unique_id & 0xFF0000000000) >> 40, + (exynos_soc_info.unique_id & 0x00FF00000000) >> 32, + (exynos_soc_info.unique_id & 0x0000FF000000) >> 24); +#endif + +#ifdef CONFIG_SCSC_ANT + SCSC_TAG_DEBUG(BT_COMMON, "dev=%u class=%p\n", + ant_service.device, common_service.class); +#endif + + return 0; + +error: + SCSC_TAG_ERR(BT_COMMON, "error class_create bt device\n"); + unregister_chrdev_region(bt_service.device, SCSC_TTY_MINORS); + +#ifdef CONFIG_SCSC_ANT + SCSC_TAG_ERR(BT_COMMON, "error class_create ant device\n"); + unregister_chrdev_region(ant_service.device, SCSC_TTY_MINORS); +#endif + + return ret; +} + + +static void __exit scsc_bt_module_exit(void) +{ + SCSC_TAG_INFO(BT_COMMON, "\n"); + + wake_lock_destroy(&bt_service.write_wake_lock); + wake_lock_destroy(&bt_service.read_wake_lock); + wake_lock_destroy(&bt_service.service_wake_lock); + complete_all(&bt_service.recovery_probe_complete); + complete_all(&bt_service.recovery_release_complete); + +#ifdef CONFIG_SCSC_ANT + wake_lock_destroy(&ant_service.write_wake_lock); + wake_lock_destroy(&ant_service.read_wake_lock); + wake_lock_destroy(&ant_service.service_wake_lock); + complete_all(&ant_service.recovery_probe_complete); + complete_all(&ant_service.recovery_release_complete); +#endif + + slsi_kic_bt_ops_unregister(&scsc_bt_kic_ops); + + /* Register with MX manager */ + scsc_mx_module_unregister_client_module(&bt_driver); + + if (bt_service.h4_device) { + device_destroy(common_service.class, bt_service.h4_cdev.dev); + bt_service.h4_device = NULL; + } + + cdev_del(&bt_service.h4_cdev); + + unregister_chrdev_region(bt_service.device, SCSC_TTY_MINORS); + +#ifdef CONFIG_SCSC_ANT + slsi_kic_ant_ops_unregister(&scsc_ant_kic_ops); + + /* Register with MX manager */ + scsc_mx_module_unregister_client_module(&ant_driver); + + if (ant_service.ant_device) { + device_destroy(common_service.class, ant_service.ant_cdev.dev); + ant_service.ant_device = NULL; + } + + cdev_del(&ant_service.ant_cdev); + + unregister_chrdev_region(ant_service.device, SCSC_TTY_MINORS); +#endif + + SCSC_TAG_INFO(BT_COMMON, "exit, module unloaded\n"); +} + +module_init(scsc_bt_module_init); +module_exit(scsc_bt_module_exit); + +MODULE_DESCRIPTION(SCSC_MODDESC); +MODULE_AUTHOR(SCSC_MODAUTH); +MODULE_LICENSE("GPL"); +MODULE_VERSION(SCSC_MODVERSION); diff --git a/drivers/misc/samsung/scsc_bt/scsc_bt_priv.h b/drivers/misc/samsung/scsc_bt/scsc_bt_priv.h new file mode 100755 index 000000000000..42cc2f682959 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_bt_priv.h @@ -0,0 +1,450 @@ +/**************************************************************************** + * + * Internal BT driver definitions + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + ****************************************************************************/ + +#ifndef __SCSC_BT_PRIV_H +#define __SCSC_BT_PRIV_H + +#include +#include +#include +#include + +#include "scsc_shm.h" + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + +/** + * Size of temporary buffer (on stack) for peeking at HCI/H4 + * packet header held in FIFO. + * + * Must be big enough to decode the + * length of any HCI packet type. + * + * For ACL that is 1 h4 header + 2 ACL handle + 2 ACL data size + */ +#define H4DMUX_HEADER_HCI (1 + 3) /* CMD, SCO */ +#define H4DMUX_HEADER_ACL (1 + 4) /* ACL */ + +#define HCI_COMMAND_PKT (1) +#define HCI_ACLDATA_PKT (2) +#define HCI_EVENT_PKT (4) + +#define ACLDATA_HEADER_SIZE (4) +#define L2CAP_HEADER_SIZE (4) + +#define HCI_ACL_DATA_FLAGS(data) ((*(data + 1)) & 0xf0) +#define HCI_ACL_DATA_CON_HDL(data) ((u16)(*(data + 0) | ((*(data + 1)) & 0x0f) << 8)) +#define HCI_ACL_DATA_LENGTH(data) ((u16)(*(data + 2) | (*(data + 3)) << 8)) +#define HCI_L2CAP_LENGTH(data) ((u16)(*(data + 4) | (*(data + 5)) << 8)) +#define HCI_L2CAP_CID(data) ((u16)(*(data + 6) | (*(data + 7)) << 8)) + +#define HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS_EVENT (0x13) +#define HCI_EVENT_HARDWARE_ERROR_EVENT (0x10) + +#define SCSC_BT_CONF "bt.hcf" +#ifdef CONFIG_SCSC_BT_BLUEZ +#define SCSC_BT_ADDR "/csa/bluetooth/.bd_addr" +#define SCSC_BT_ADDR_LEN (3) +#elif defined CONFIG_SCSC_BT_ADDRESS_IN_FILE +#define SCSC_BT_ADDR CONFIG_SCSC_BT_ADDRESS_FILENAME +#define SCSC_BT_ADDR_LEN (6) +#endif + +#define SCSC_H4_DEVICE_NAME "scsc_h4_0" +#define SCSC_ANT_DEVICE_NAME "scsc_ant_0" + +#define ANT_COMMAND_MSG 0x0C +#define ANT_DATA_MSG 0x0E +#define ANT_HEADER_LENGTH 1 + +#define SCSC_BT_CONNECTION_INFO_MAX (0x1000) +#define SCSC_BT_ACL_RAW_MASK (0xF000) +#define SCSC_BT_ACL_RAW (0x2000) +#define SCSC_BT_ACL_HANDLE_MASK (0x0FFF) + +#define SCSC_TTY_MINORS (8) + +enum scsc_bt_shm_thread_flags; + +enum scsc_bt_read_op { + BT_READ_OP_NONE, + BT_READ_OP_HCI_EVT, + BT_READ_OP_HCI_EVT_ERROR, + BT_READ_OP_ACL_DATA, + BT_READ_OP_ACL_CREDIT, + BT_READ_OP_IQ_REPORT, + BT_READ_OP_STOP +}; + +enum scsc_ant_read_op { + ANT_READ_OP_NONE, + ANT_READ_OP_CMD, + ANT_READ_OP_DATA +}; + +struct scsc_bt_connection_info { + u8 state; + u16 length; + u16 l2cap_cid; +}; + +#define CONNECTION_NONE (0) +#define CONNECTION_ACTIVE (1) +#define CONNECTION_DISCONNECTED (2) + +enum bt_link_type_enum { + BT_LINK_TYPE_SCO = 0, + BT_LINK_TYPE_ACL = 1, + BT_LINK_TYPE_SETUP_ID = 2, + BT_LINK_TYPE_SETUP_FHS = 3, + BT_LINK_TYPE_ESCO = 4, + BT_LINK_TYPE_ACL_23 = 5, + BT_LINK_TYPE_ESCO_23 = 6, + BT_LINK_TYPE_ANTPLUS = 7, + MAX_BT_LINK_TYPE = 7 +}; + +enum scsc_bt_avdtp_detect_state_enum { + BT_AVDTP_STATE_IDLE_SIGNALING, + BT_AVDTP_STATE_PENDING_SIGNALING, + BT_AVDTP_STATE_COMPLETE_SIGNALING, + BT_AVDTP_STATE_IDLE_STREAMING, + BT_AVDTP_STATE_PENDING_STREAMING, + BT_AVDTP_STATE_COMPLETE_STREAMING, +}; + +enum scsc_bt_avdtp_detect_conn_req_direction_enum { + BT_AVDTP_CONN_REQ_DIR_INCOMING, + BT_AVDTP_CONN_REQ_DIR_OUTGOING, +}; + +enum scsc_bt_avdtp_detect_type { + BT_AVDTP_CONN_TYPE_SIGNAL = 0, + BT_AVDTP_CONN_TYPE_STREAM = 1, +}; + +struct scsc_bt_avdtp_detect_connection { + enum scsc_bt_avdtp_detect_type type; + enum scsc_bt_avdtp_detect_state_enum state; + u16 src_cid; + u16 dst_cid; +}; + +struct scsc_bt_avdtp_detect_ongoing { + struct scsc_bt_avdtp_detect_connection incoming_signal; + struct scsc_bt_avdtp_detect_connection outgoing_signal; + struct scsc_bt_avdtp_detect_connection incoming_stream; + struct scsc_bt_avdtp_detect_connection outgoing_stream; +}; + +enum scsc_bt_avdtp_detect_tsep { + BT_AVDTP_TSEP_SRC = 0, + BT_AVDTP_TSEP_SNK = 1, +}; + +struct scsc_bt_avdtp_detect_src_snk { + enum scsc_bt_avdtp_detect_tsep tsep; + struct scsc_bt_avdtp_detect_snk_seid *local_snk_seids; + struct scsc_bt_avdtp_detect_snk_seid *remote_snk_seids; + uint16_t local_snk_seid_candidate; + uint16_t remote_snk_seid_candidate; +}; + +struct scsc_bt_avdtp_detect_snk_seid { + uint8_t seid; + struct scsc_bt_avdtp_detect_snk_seid *next; +}; + +struct scsc_bt_avdtp_detect_hci_connection { + struct scsc_bt_avdtp_detect_ongoing ongoing; + u16 hci_connection_handle; + struct scsc_bt_avdtp_detect_connection signal; + struct scsc_bt_avdtp_detect_connection stream; + struct scsc_bt_avdtp_detect_src_snk tsep_detect; + struct scsc_bt_avdtp_detect_hci_connection *next; + spinlock_t lock; +}; + +struct scsc_bt_avdtp_detect { + struct scsc_bt_avdtp_detect_hci_connection *connections; + spinlock_t lock; + spinlock_t fw_write_lock; +}; + +struct scsc_common_service { + struct scsc_mx *maxwell_core; + struct class *class; +}; + +extern struct scsc_common_service common_service; + +#ifdef CONFIG_SCSC_LOG_COLLECTION +struct scsc_bt_hcf_collection { + void *hcf; + u32 hcf_size; +}; +#endif + +struct scsc_bt_service { + dev_t device; + struct scsc_service *service; + struct device *dev; + + struct cdev h4_cdev; + struct device *h4_device; + struct file *h4_file; + bool h4_users; + atomic_t h4_readers; + atomic_t h4_writers; + size_t h4_write_offset; + + atomic_t error_count; + atomic_t service_users; + + scsc_mifram_ref bhcs_ref; /* Bluetooth host configuration service reference */ + scsc_mifram_ref bsmhcp_ref; /* Bluetooth shared memory host controller protocol reference */ + scsc_mifram_ref config_ref; /* Bluetooth configuration reference */ + scsc_mifram_ref abox_ref; /* A-Box reference */ + struct BSMHCP_PROTOCOL *bsmhcp_protocol; /* Bluetooth shared memory host controller protocol pointer */ + size_t read_offset; + enum scsc_bt_read_op read_operation; + u32 read_index; + wait_queue_head_t read_wait; + + wait_queue_head_t info_wait; + + int last_alloc; /* Cached previous alloc index to aid search */ + u8 allocated[BSMHCP_DATA_BUFFER_TX_ACL_SIZE]; + u32 allocated_count; + u32 freed_count; + bool processed[BSMHCP_TRANSFER_RING_EVT_SIZE]; + + struct scsc_bt_connection_info connection_handle_list[SCSC_BT_CONNECTION_INFO_MAX]; + bool hci_event_paused; + bool acldata_paused; + + struct wake_lock read_wake_lock; + struct wake_lock write_wake_lock; + struct wake_lock service_wake_lock; + size_t write_wake_lock_count; + size_t write_wake_unlock_count; + + size_t interrupt_count; + size_t interrupt_read_count; + size_t interrupt_write_count; + + u32 mailbox_hci_evt_read; + u32 mailbox_hci_evt_write; + u32 mailbox_acl_rx_read; + u32 mailbox_acl_rx_write; + u32 mailbox_acl_free_read; + u32 mailbox_acl_free_read_scan; + u32 mailbox_acl_free_write; + u32 mailbox_iq_report_read; + u32 mailbox_iq_report_write; + + struct scsc_bt_avdtp_detect avdtp_detect; + struct completion recovery_release_complete; + struct completion recovery_probe_complete; + +#ifdef CONFIG_SCSC_LOG_COLLECTION + struct scsc_bt_hcf_collection hcf_collection; +#endif +}; + +extern struct scsc_bt_service bt_service; + +/* IQ reporting */ +#define HCI_IQ_REPORTING_MAX_NUM_SAMPLES (82) +/** + * The driver supports both reception of LE Connection IQ Report + * and LE Connectionless IQ report event: + * + * The largest hci event is LE Connection IQ Report, which + * constitutes of: + * + * - Hci packet type : 1 octet + * - Event code : 1 octet + * - Param total Length : 1 octet + * - Subevent code : 1 octet + * - Connection hdl : 2 octets + * - RX_PHY : 1 octet + * - Data_channel_idx : 1 octet + * - RSSI : 2 octets + * - RSSI antenna id : 1 octet + * - cte type : 1 octet + * - Slot durations : 1 octet + * - packet_status : 1 octet + * - event_counter : 2 octets + * - Sample count : 1 octet + ********************************* + * Total : 17 octets + * + * The maximum hci event size in bytes is: + * (17 + (number of samples * 2 (both I and Q))) + * + */ +#define HCI_IQ_REPORT_MAX_LEN (17 + (2 * HCI_IQ_REPORTING_MAX_NUM_SAMPLES)) +#define HCI_LE_CONNECTIONLESS_IQ_REPORT_EVENT_SUB_CODE (0x15) +#define HCI_LE_CONNECTION_IQ_REPORT_EVENT_SUB_CODE (0x16) + + +struct scsc_ant_service { + dev_t device; + struct scsc_service *service; + struct device *dev; + + struct cdev ant_cdev; + struct device *ant_device; + struct file *ant_file; + bool ant_users; + atomic_t ant_readers; + atomic_t ant_writers; + size_t ant_write_offset; + + atomic_t error_count; + atomic_t service_users; + + /* Bluetooth host configuration service reference */ + scsc_mifram_ref bhcs_ref; + /* Ant shared memory host controller protocol reference */ + scsc_mifram_ref asmhcp_ref; + /* Bluetooth configuration reference */ + scsc_mifram_ref config_ref; + + /* Ant shared memory host controller protocol pointer */ + struct ASMHCP_PROTOCOL *asmhcp_protocol; + size_t read_offset; + enum scsc_ant_read_op read_operation; + u32 read_index; + wait_queue_head_t read_wait; + + struct wake_lock read_wake_lock; + struct wake_lock write_wake_lock; + struct wake_lock service_wake_lock; + size_t write_wake_lock_count; + size_t write_wake_unlock_count; + + size_t interrupt_count; + size_t interrupt_read_count; + size_t interrupt_write_count; + + u32 mailbox_data_ctr_driv_read; + u32 mailbox_data_ctr_driv_write; + u32 mailbox_cmd_ctr_driv_read; + u32 mailbox_cmd_ctr_driv_write; + + struct completion recovery_release_complete; + struct completion recovery_probe_complete; +}; + +extern struct scsc_ant_service ant_service; +/* Coex avdtp detection */ + +/* The buffers passed for inspection begin at the L2CAP basic header, as does the length + * passed in the function calls */ +#define AVDTP_DETECT_MIN_DATA_LENGTH (12) /* We always want to look for the SRC CID */ +#define AVDTP_DETECT_MIN_DATA_LENGTH_CON_RSP (16) /* For CON RSP, we want the result, too */ +#define AVDTP_DETECT_MIN_AVDTP_LENGTH (6) /* Basic L2CAP header + 2 AVDTP octets as min */ + +#define HCI_ACL_PACKET_BOUNDARY_START_FLUSH (2) + +/* Can't use HCI_L2CAP_CID(data), since that assumes 4 bytes of HCI header, which has been stripped + * for the calls to the avdtp detection functions */ +#define HCI_L2CAP_RX_CID(data) ((u16)(*(data + 2) | (*(data + 3)) << 8)) + +#define HCI_L2CAP_CODE(data) ((u8)(*(data + 4))) +#define HCI_L2CAP_CON_REQ_PSM(data) ((u16)(*(data + 8) | (*(data + 9)) << 8)) +/* Valid for at least connection request/response and disconnection request */ +#define HCI_L2CAP_SOURCE_CID(data) ((u16)(*(data + 10) | (*(data + 11)) << 8)) +/* Valid for at least connection and disconnection responses */ +#define HCI_L2CAP_RSP_DEST_CID(data) ((u16)(*(data + 8) | (*(data + 9)) << 8)) +#define HCI_L2CAP_CON_RSP_RESULT(data) ((u16)(*(data + 12) | (*(data + 13)) << 8)) +#define HCI_L2CAP_CON_RSP_RESULT_SUCCESS (0x0000) +#define HCI_L2CAP_CON_RSP_RESULT_REFUSED (0x0002) + + +#define HCI_L2CAP_CONF_SEID_OFFSET 6 +#define HCI_L2CAP_CONF_TSEP_OFFSET 7 +#define HCI_L2CAP_CONF_SEID_INFO_SIZE 2 +#define HCI_L2CAP_CONF_SEID(data, index) (((u8)(*(data + index * HCI_L2CAP_CONF_SEID_INFO_SIZE + HCI_L2CAP_CONF_SEID_OFFSET)) >> 2) & 0x3F) +#define HCI_L2CAP_CONF_TSEP_SRC 0 +#define HCI_L2CAP_CONF_TSEP_SNK 1 +#define HCI_L2CAP_CONF_TSEP(data, index) (((u8)(*(data + index * HCI_L2CAP_CONF_SEID_INFO_SIZE + HCI_L2CAP_CONF_TSEP_OFFSET)) >> 3) & 0x1) +#define HCI_L2CAP_SET_CONF_ACP_SEID_OFFSET 6 +#define HCI_L2CAP_SET_CONF_ACP_SEID(data) (((u8)(*(data + HCI_L2CAP_SET_CONF_ACP_SEID_OFFSET)) >> 2) & 0x3F) + + +#define L2CAP_AVDTP_PSM 0x0019 +#define L2CAP_SIGNALING_CID 0x0001 +#define L2CAP_CODE_CONNECT_REQ 0x02 +#define L2CAP_CODE_CONNECT_RSP 0x03 +#define L2CAP_CODE_CONFIGURE_REQ 0x04 +#define L2CAP_CODE_DISCONNECT_REQ 0x06 +#define L2CAP_CODE_DISCONNECT_RSP 0x07 + +#define AVDTP_MESSAGE_TYPE_OFFSET 4 /* Assuming only single packet type */ +#define AVDTP_MESSAGE_TYPE_MASK 0x03 +#define AVDTP_MESSAGE_TYPE(data) ((u8)(*(data + AVDTP_MESSAGE_TYPE_OFFSET)) & AVDTP_MESSAGE_TYPE_MASK) +#define AVDTP_MESSAGE_TYPE_CMD 0x00 +#define AVDTP_MESSAGE_TYPE_GENERAL_REJECT 0x01 +#define AVDTP_MESSAGE_TYPE_RSP_ACCEPT 0x02 +#define AVDTP_MESSAGE_TYPE_RSP_REJECT 0x03 + +#define AVDTP_SIGNAL_ID_OFFSET 5 /* Assuming only single packet type */ +#define AVDTP_SIGNAL_ID_MASK 0x1F +#define AVDTP_SIGNAL_ID(data) ((u8)(*(data + AVDTP_SIGNAL_ID_OFFSET)) & AVDTP_SIGNAL_ID_MASK) + +#define AVDTP_SIGNAL_ID_DISCOVER 0x01 +#define AVDTP_SIGNAL_ID_SET_CONF 0x03 +#define AVDTP_SIGNAL_ID_OPEN 0x06 +#define AVDTP_SIGNAL_ID_START 0x07 +#define AVDTP_SIGNAL_ID_CLOSE 0x08 +#define AVDTP_SIGNAL_ID_SUSPEND 0x09 +#define AVDTP_SIGNAL_ID_ABORT 0x0A + +#define AVDTP_SIGNAL_FLAG_MASK (0x80000000) +#define AVDTP_SNK_FLAG_MASK (0x40000000) +#define AVDTP_MESSAGE_COUNT_MASK (0x30000000) +#define AVDTP_GET_MESSAGE_COUNT(data) ((data & AVDTP_MESSAGE_COUNT_MASK) >> 28) + +#define AVDTP_SNK_FLAG_TD_MASK (0x00000001) +#define AVDTP_OPEN_FLAG_TD_MASK (0x00000002) + +extern uint16_t avdtp_signaling_src_cid; +extern uint16_t avdtp_signaling_dst_cid; +extern uint16_t avdtp_streaming_src_cid; +extern uint16_t avdtp_streaming_dst_cid; +extern uint16_t avdtp_hci_connection_handle; + +#define AVDTP_DETECT_SIGNALING_IGNORE 0 +#define AVDTP_DETECT_SIGNALING_ACTIVE 1 +#define AVDTP_DETECT_SIGNALING_INACTIVE 2 +#define AVDTP_DETECT_SIGNALING_OPEN 3 + +void scsc_avdtp_detect_rxtx(u16 hci_connection_handle, const unsigned char *data, uint16_t length, bool is_tx); +bool scsc_avdtp_detect_reset_connection_handle(uint16_t hci_connection_handle); +bool scsc_bt_shm_h4_avdtp_detect_write(uint32_t flags, + uint16_t l2cap_cid, + uint16_t hci_connection_handle); +void scsc_avdtp_detect_exit(void); + +#ifdef CONFIG_SCSC_BT_BLUEZ +void slsi_bt_notify_probe(struct device *dev, + const struct file_operations *fs, + atomic_t *error_count, + wait_queue_head_t *read_wait); +void slsi_bt_notify_remove(void); +#else +#define slsi_bt_notify_probe(dev, fs, error_count, read_wait) +#define slsi_bt_notify_remove() +#endif + +#endif /* __SCSC_BT_PRIV_H */ diff --git a/drivers/misc/samsung/scsc_bt/scsc_shm.c b/drivers/misc/samsung/scsc_bt/scsc_shm.c new file mode 100755 index 000000000000..e1786b37aaab --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_shm.c @@ -0,0 +1,1593 @@ +/**************************************************************************** + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + ****************************************************************************/ + +/* MX BT shared memory interface */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "scsc_bt_priv.h" +#include "scsc_shm.h" +#include "scsc_bt_hci.h" + +struct hci_credit_entry { + u16 hci_connection_handle; + u16 credits; +}; + +static u8 h4_write_buffer[BSMHCP_ACL_PACKET_SIZE + H4DMUX_HEADER_ACL]; +static u8 h4_acl_header[5]; +static u8 h4_hci_event_ncp_header[4 + BSMHCP_TRANSFER_RING_ACL_COUNT * sizeof(struct hci_credit_entry)]; +static u32 h4_hci_event_ncp_header_len = 8; +static struct hci_credit_entry *h4_hci_credit_entries = (struct hci_credit_entry *) &h4_hci_event_ncp_header[4]; +static u8 h4_hci_event_hardware_error[4] = { HCI_EVENT_PKT, HCI_EVENT_HARDWARE_ERROR_EVENT, 1, 0 }; +static u8 h4_iq_report_evt[HCI_IQ_REPORT_MAX_LEN]; +static u32 h4_iq_report_evt_len; + + +static void scsc_bt_shm_irq_handler(int irqbit, void *data) +{ + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(bt_service.service, irqbit); + + /* Ensure irq bit is cleared before reading the mailbox indexes */ + mb(); + + bt_service.interrupt_count++; + + /* Wake the reader operation */ + if (bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write != + bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read || + bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write != + bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read || + bt_service.bsmhcp_protocol->header.mailbox_acl_free_write != + bt_service.bsmhcp_protocol->header.mailbox_acl_free_read || + bt_service.bsmhcp_protocol->header.mailbox_iq_report_write != + bt_service.bsmhcp_protocol->header.mailbox_iq_report_read || + 0 != atomic_read(&bt_service.error_count) || + bt_service.bsmhcp_protocol->header.panic_deathbed_confession) { + bt_service.interrupt_read_count++; + + wake_lock_timeout(&bt_service.read_wake_lock, HZ); + wake_up(&bt_service.read_wait); + } + + if (bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_write == + bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_read && + bt_service.bsmhcp_protocol->header.mailbox_acl_tx_write == + bt_service.bsmhcp_protocol->header.mailbox_acl_tx_read) { + bt_service.interrupt_write_count++; + + if (wake_lock_active(&bt_service.write_wake_lock)) { + bt_service.write_wake_unlock_count++; + wake_unlock(&bt_service.write_wake_lock); + } + } +} + +/* Assign firmware/host interrupts */ +static void scsc_bt_shm_init_interrupt(void) +{ + /* To-host f/w IRQ allocations and ISR registrations */ + bt_service.bsmhcp_protocol->header.bg_to_ap_int_src = scsc_service_mifintrbit_register_tohost(bt_service.service, scsc_bt_shm_irq_handler, NULL); + bt_service.bsmhcp_protocol->header.fg_to_ap_int_src = scsc_service_mifintrbit_register_tohost(bt_service.service, scsc_bt_shm_irq_handler, NULL); + + /* From-host f/w IRQ allocations */ + bt_service.bsmhcp_protocol->header.ap_to_bg_int_src = scsc_service_mifintrbit_alloc_fromhost(bt_service.service, SCSC_MIFINTR_TARGET_R4); + bt_service.bsmhcp_protocol->header.ap_to_fg_int_src = scsc_service_mifintrbit_alloc_fromhost(bt_service.service, SCSC_MIFINTR_TARGET_R4); + bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src = scsc_service_mifintrbit_alloc_fromhost(bt_service.service, SCSC_MIFINTR_TARGET_M4); + + SCSC_TAG_DEBUG(BT_COMMON, "Registered to-host IRQ bits %d:%d:%d, from-host IRQ bits %d:%d\n", + bt_service.bsmhcp_protocol->header.bg_to_ap_int_src, + bt_service.bsmhcp_protocol->header.fg_to_ap_int_src, + bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, + bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, + bt_service.bsmhcp_protocol->header.ap_to_fg_int_src); +} + +bool scsc_bt_shm_h4_avdtp_detect_write(uint32_t flags, + uint16_t l2cap_cid, + uint16_t hci_connection_handle) +{ + uint32_t tr_read; + uint32_t tr_write; + struct BSMHCP_TD_AVDTP *td; + + spin_lock(&bt_service.avdtp_detect.fw_write_lock); + + /* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */ + tr_read = bt_service.bsmhcp_protocol->header.mailbox_avdtp_read; + tr_write = bt_service.bsmhcp_protocol->header.mailbox_avdtp_write; + + td = &bt_service.bsmhcp_protocol->avdtp_transfer_ring[tr_write]; + + SCSC_TAG_DEBUG(BT_H4, + "AVDTP_DETECT_PKT (flags: 0x%08X, cid: 0x%04X, handle: 0x%04X, read=%u, write=%u)\n", + flags, + l2cap_cid, + hci_connection_handle, + tr_read, + tr_write); + + /* Index out of bounds check */ + if (tr_read >= BSMHCP_TRANSFER_RING_AVDTP_SIZE || tr_write >= BSMHCP_TRANSFER_RING_AVDTP_SIZE) { + spin_unlock(&bt_service.avdtp_detect.fw_write_lock); + SCSC_TAG_ERR(BT_H4, + "AVDTP_DETECT_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", + tr_read, + tr_write); + atomic_inc(&bt_service.error_count); + return false; + } + + /* Does the transfer ring have room for an entry */ + if (BSMHCP_HAS_ROOM(tr_write, tr_read, BSMHCP_TRANSFER_RING_AVDTP_SIZE)) { + /* Fill the transfer descriptor with the AVDTP data */ + td->flags = flags; + td->l2cap_cid = l2cap_cid; + td->hci_connection_handle = hci_connection_handle; + + /* Ensure the wake lock is acquired */ + if (!wake_lock_active(&bt_service.write_wake_lock)) { + bt_service.write_wake_lock_count++; + wake_lock(&bt_service.write_wake_lock); + } + + /* Increate the write pointer */ + BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_AVDTP_SIZE); + bt_service.bsmhcp_protocol->header.mailbox_avdtp_write = tr_write; + + spin_unlock(&bt_service.avdtp_detect.fw_write_lock); + + /* Memory barrier to ensure out-of-order execution is completed */ + mmiowb(); + + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set( + bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, + SCSC_MIFINTR_TARGET_R4); + } else { + /* Transfer ring full */ + spin_unlock(&bt_service.avdtp_detect.fw_write_lock); + SCSC_TAG_ERR(BT_H4, + "AVDTP_DETECT_PKT - No more room for messages (tr_read=%u, tr_write=%u)\n", + tr_read, + tr_write); + scsc_service_force_panic(bt_service.service); + return false; + } + return true; +} + + +static ssize_t scsc_bt_shm_h4_hci_cmd_write(const unsigned char *data, size_t count) +{ + /* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */ + uint32_t tr_read = bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_read; + uint32_t tr_write = bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_write; +#ifdef CONFIG_SCSC_PRINTK + uint16_t op_code = *(uint16_t *) data; +#endif + + /* Temp vars */ + struct BSMHCP_TD_CONTROL *td = &bt_service.bsmhcp_protocol->hci_cmd_transfer_ring[tr_write]; + + SCSC_TAG_DEBUG(BT_H4, "HCI_COMMAND_PKT (op_code=0x%04x, len=%zu, read=%u, write=%u)\n", op_code, count, tr_read, tr_write); + + /* Index out of bounds check */ + if (tr_read >= BSMHCP_TRANSFER_RING_CMD_SIZE || tr_write >= BSMHCP_TRANSFER_RING_CMD_SIZE) { + SCSC_TAG_ERR(BT_H4, "HCI_COMMAND_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", tr_read, tr_write); + atomic_inc(&bt_service.error_count); + return -EIO; + } + + /* Does the transfer ring have room for an entry */ + if (BSMHCP_HAS_ROOM(tr_write, tr_read, BSMHCP_TRANSFER_RING_CMD_SIZE)) { + /* Fill the transfer descriptor with the HCI command data */ + memcpy(td->data, data, count); + td->length = (u16)count; + + /* Ensure the wake lock is acquired */ + if (!wake_lock_active(&bt_service.write_wake_lock)) { + bt_service.write_wake_lock_count++; + wake_lock(&bt_service.write_wake_lock); + } + + /* Increate the write pointer */ + BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_CMD_SIZE); + bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_write = tr_write; + + /* Memory barrier to ensure out-of-order execution is completed */ + mmiowb(); + + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, SCSC_MIFINTR_TARGET_R4); + } else + /* Transfer ring full. Only happens if the user attempt to send more HCI command packets than + * available credits */ + count = 0; + + return count; +} + +static ssize_t scsc_bt_shm_h4_acl_write(const unsigned char *data, size_t count) +{ + /* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */ + uint32_t tr_read = bt_service.bsmhcp_protocol->header.mailbox_acl_tx_read; + uint32_t tr_write = bt_service.bsmhcp_protocol->header.mailbox_acl_tx_write; + + /* Temp vars */ + struct BSMHCP_TD_ACL_TX_DATA *td = &bt_service.bsmhcp_protocol->acl_tx_data_transfer_ring[tr_write]; + int acldata_buf_index = -1; + u16 l2cap_length; + u32 i; + size_t payload_len = count - ACLDATA_HEADER_SIZE; + + /* Index out of bounds check */ + if (tr_read >= BSMHCP_TRANSFER_RING_ACL_SIZE || tr_write >= BSMHCP_TRANSFER_RING_ACL_SIZE) { + SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", tr_read, tr_write); + atomic_inc(&bt_service.error_count); + return -EIO; + } + + /* Allocate a data slot */ + for (i = 0; i < BSMHCP_DATA_BUFFER_TX_ACL_SIZE; i++) { + /* Wrap the offset index around the buffer max */ + if (++bt_service.last_alloc == BSMHCP_DATA_BUFFER_TX_ACL_SIZE) + bt_service.last_alloc = 0; + /* Claim a free slot */ + if (bt_service.allocated[bt_service.last_alloc] == 0) { + bt_service.allocated[bt_service.last_alloc] = 1; + acldata_buf_index = bt_service.last_alloc; + bt_service.allocated_count++; + break; + } + } + + /* Is a buffer available to hold the data */ + if (acldata_buf_index < 0) { + SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - No buffers available\n"); + atomic_inc(&bt_service.error_count); + return -EIO; + } + + /* Does the transfer ring have room for an entry */ + if (BSMHCP_HAS_ROOM(tr_write, tr_read, BSMHCP_TRANSFER_RING_ACL_SIZE)) { + /* Extract the ACL data header and L2CAP header and fill it into the transfer descriptor */ + td->buffer_index = (uint8_t)acldata_buf_index; + td->flags = HCI_ACL_DATA_FLAGS(data); + td->hci_connection_handle = HCI_ACL_DATA_CON_HDL(data); + td->length = (u16)payload_len; + + if ((td->flags & BSMHCP_ACL_BC_FLAG_BCAST_ACTIVE) || + (td->flags & BSMHCP_ACL_BC_FLAG_BCAST_ALL)) { + SCSC_TAG_DEBUG(BT_H4, + "Setting broadcast handle (hci_connection_handle=0x%03x)\n", + td->hci_connection_handle); + bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_ACTIVE; + } + + /* Is this a packet marked with the start flag */ + if ((td->flags & BSMHCP_ACL_PB_FLAG_MASK) == BSMHCP_ACL_PB_FLAG_START_NONFLUSH || + (td->flags & BSMHCP_ACL_PB_FLAG_MASK) == BSMHCP_ACL_PB_FLAG_START_FLUSH) { + + /* Extract the L2CAP payload length and connection identifier */ + td->l2cap_cid = HCI_L2CAP_CID(data); + + /* data+4 to skip the HCI header, to align offsets with the rx detection. The "true" argument is to tell + * the detection that this is TX */ + scsc_avdtp_detect_rxtx(td->hci_connection_handle, data+4, td->length, true); + + l2cap_length = HCI_L2CAP_LENGTH(data); + + SCSC_TAG_DEBUG(BT_TX, "ACL[START] (len=%u, buffer=%u, credits=%u, l2cap_cid=0x%04x, l2cap_length=%u)\n", + td->length, acldata_buf_index, + BSMHCP_DATA_BUFFER_TX_ACL_SIZE - (bt_service.allocated_count - bt_service.freed_count), + HCI_L2CAP_CID(data), l2cap_length); + + if (l2cap_length == payload_len - L2CAP_HEADER_SIZE) + /* Mark it with the END flag if packet length matches the L2CAP payload length */ + td->flags |= BSMHCP_ACL_L2CAP_FLAG_END; + else if (l2cap_length < payload_len - L2CAP_HEADER_SIZE) { + /* Mark it with the END flag if packet length is greater than the L2CAP payload length + and generate a warning notifying that this is incorrect according to the specification. + This is allowed to support the BITE tester. */ + SCSC_TAG_WARNING(BT_H4, "ACL_DATA_PKT - H4 ACL payload length > L2CAP Length (payload_len=%zu, l2cap_length=%u)\n", + payload_len - L2CAP_HEADER_SIZE, l2cap_length); + td->flags |= BSMHCP_ACL_L2CAP_FLAG_END; + } else if (l2cap_length > (payload_len - L2CAP_HEADER_SIZE)) { + /* This is only a fragment of the packet. Save the remaining number of octets required + * to complete the packet */ + bt_service.connection_handle_list[td->hci_connection_handle].length = (u16)(l2cap_length - payload_len + L2CAP_HEADER_SIZE); + bt_service.connection_handle_list[td->hci_connection_handle].l2cap_cid = HCI_L2CAP_CID(data); + } else { + /* The packet is larger than the L2CAP payload length - protocol error */ + SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - L2CAP Length Error (l2cap_length=%u, payload_len=%zu)\n", + l2cap_length, payload_len - L2CAP_HEADER_SIZE); + atomic_inc(&bt_service.error_count); + return -EIO; + } + } else if ((td->flags & BSMHCP_ACL_PB_FLAG_MASK) == BSMHCP_ACL_PB_FLAG_CONT) { + /* Set the L2CAP connection identifer set by the start packet */ + td->l2cap_cid = bt_service.connection_handle_list[td->hci_connection_handle].l2cap_cid; + + SCSC_TAG_DEBUG(BT_TX, "ACL[CONT] (len=%u, buffer=%u, credits=%u, l2cap_cid=0x%04x, length=%u)\n", + td->length, acldata_buf_index, + BSMHCP_DATA_BUFFER_TX_ACL_SIZE - (bt_service.allocated_count - bt_service.freed_count), + bt_service.connection_handle_list[td->hci_connection_handle].l2cap_cid, + bt_service.connection_handle_list[td->hci_connection_handle].length); + + /* Does this packet complete the L2CAP frame */ + if (bt_service.connection_handle_list[td->hci_connection_handle].length == payload_len) { + /* The L2CAP frame is complete. mark it with the END flag */ + td->flags |= BSMHCP_ACL_L2CAP_FLAG_END; + + /* Set the remaining length to zero */ + bt_service.connection_handle_list[td->hci_connection_handle].length = 0; + } else if (bt_service.connection_handle_list[td->hci_connection_handle].length < payload_len) { + /* Mark it with the END flag if packet length is greater than the L2CAP missing payload length + and generate a warning notifying that this is incorrect according to the specification. + This is allowed to support the BITE tester. */ + SCSC_TAG_WARNING(BT_H4, "ACL_DATA_PKT - H4 ACL payload length > L2CAP Missing Length (payload_len=%zu, missing=%u)\n", + payload_len, bt_service.connection_handle_list[td->hci_connection_handle].length); + td->flags |= BSMHCP_ACL_L2CAP_FLAG_END; + /* Set the remaining length to zero */ + bt_service.connection_handle_list[td->hci_connection_handle].length = 0; + } else if (bt_service.connection_handle_list[td->hci_connection_handle].length > payload_len) + /* This is another fragment of the packet. Save the remaining number of octets required + * to complete the packet */ + bt_service.connection_handle_list[td->hci_connection_handle].length -= (u16)payload_len; + else if (bt_service.connection_handle_list[td->hci_connection_handle].length < payload_len) { + /* The packet is larger than the L2CAP payload length - protocol error */ + SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - L2CAP Length Error (missing=%u, payload_len=%zu)\n", + bt_service.connection_handle_list[td->hci_connection_handle].length, payload_len); + atomic_inc(&bt_service.error_count); + return -EIO; + } + } else { + /* Reserved flags set - report it as an error */ + SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - Flag set to reserved\n"); + atomic_inc(&bt_service.error_count); + return -EIO; + } + + SCSC_TAG_DEBUG(BT_H4, "ACL_DATA_PKT (len=%zu, read=%u, write=%u, slot=%u, flags=0x%04x, handle=0x%03x, l2cap_cid=0x%04x, missing=%u)\n", + payload_len, tr_read, tr_write, acldata_buf_index, td->flags >> 4, td->hci_connection_handle, td->l2cap_cid, + bt_service.connection_handle_list[td->hci_connection_handle].length); + + /* Ensure the wake lock is acquired */ + if (!wake_lock_active(&bt_service.write_wake_lock)) { + bt_service.write_wake_lock_count++; + wake_lock(&bt_service.write_wake_lock); + } + + /* Copy the ACL packet into the targer buffer */ + memcpy(&bt_service.bsmhcp_protocol->acl_tx_buffer[acldata_buf_index][0], &data[ACLDATA_HEADER_SIZE], payload_len); + /* Increate the write pointer */ + BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_ACL_SIZE); + bt_service.bsmhcp_protocol->header.mailbox_acl_tx_write = tr_write; + + /* Memory barrier to ensure out-of-order execution is completed */ + mmiowb(); + + if (bt_service.bsmhcp_protocol->header.firmware_features & BSMHCP_FEATURE_M4_INTERRUPTS) + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, SCSC_MIFINTR_TARGET_M4); + else + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_fg_int_src, SCSC_MIFINTR_TARGET_R4); + } else { + /* Transfer ring full. Only happens if the user attempt to send more ACL data packets than + * available credits */ + SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - No room in transfer ring (tr_write=%u, tr_read=%u)\n", + tr_write, tr_read); + atomic_inc(&bt_service.error_count); + count = -EIO; + } + + return count; +} + +#ifdef CONFIG_SCSC_PRINTK +static const char *scsc_hci_evt_decode_event_code(u8 hci_event_code, u8 hci_ulp_sub_code) +{ + const char *ret = "NA"; + + switch (hci_event_code) { + HCI_EV_DECODE(HCI_EV_INQUIRY_COMPLETE); + HCI_EV_DECODE(HCI_EV_INQUIRY_RESULT); + HCI_EV_DECODE(HCI_EV_CONN_COMPLETE); + HCI_EV_DECODE(HCI_EV_CONN_REQUEST); + HCI_EV_DECODE(HCI_EV_DISCONNECT_COMPLETE); + HCI_EV_DECODE(HCI_EV_AUTH_COMPLETE); + HCI_EV_DECODE(HCI_EV_REMOTE_NAME_REQ_COMPLETE); + HCI_EV_DECODE(HCI_EV_ENCRYPTION_CHANGE); + HCI_EV_DECODE(HCI_EV_CHANGE_CONN_LINK_KEY_COMPLETE); + HCI_EV_DECODE(HCI_EV_MASTER_LINK_KEY_COMPLETE); + HCI_EV_DECODE(HCI_EV_READ_REM_SUPP_FEATURES_COMPLETE); + HCI_EV_DECODE(HCI_EV_READ_REMOTE_VER_INFO_COMPLETE); + HCI_EV_DECODE(HCI_EV_QOS_SETUP_COMPLETE); + HCI_EV_DECODE(HCI_EV_COMMAND_COMPLETE); + HCI_EV_DECODE(HCI_EV_COMMAND_STATUS); + HCI_EV_DECODE(HCI_EV_HARDWARE_ERROR); + HCI_EV_DECODE(HCI_EV_FLUSH_OCCURRED); + HCI_EV_DECODE(HCI_EV_ROLE_CHANGE); + HCI_EV_DECODE(HCI_EV_NUMBER_COMPLETED_PKTS); + HCI_EV_DECODE(HCI_EV_MODE_CHANGE); + HCI_EV_DECODE(HCI_EV_RETURN_LINK_KEYS); + HCI_EV_DECODE(HCI_EV_PIN_CODE_REQ); + HCI_EV_DECODE(HCI_EV_LINK_KEY_REQ); + HCI_EV_DECODE(HCI_EV_LINK_KEY_NOTIFICATION); + HCI_EV_DECODE(HCI_EV_LOOPBACK_COMMAND); + HCI_EV_DECODE(HCI_EV_DATA_BUFFER_OVERFLOW); + HCI_EV_DECODE(HCI_EV_MAX_SLOTS_CHANGE); + HCI_EV_DECODE(HCI_EV_READ_CLOCK_OFFSET_COMPLETE); + HCI_EV_DECODE(HCI_EV_CONN_PACKET_TYPE_CHANGED); + HCI_EV_DECODE(HCI_EV_QOS_VIOLATION); + HCI_EV_DECODE(HCI_EV_PAGE_SCAN_MODE_CHANGE); + HCI_EV_DECODE(HCI_EV_PAGE_SCAN_REP_MODE_CHANGE); + HCI_EV_DECODE(HCI_EV_FLOW_SPEC_COMPLETE); + HCI_EV_DECODE(HCI_EV_INQUIRY_RESULT_WITH_RSSI); + HCI_EV_DECODE(HCI_EV_READ_REM_EXT_FEATURES_COMPLETE); + HCI_EV_DECODE(HCI_EV_FIXED_ADDRESS); + HCI_EV_DECODE(HCI_EV_ALIAS_ADDRESS); + HCI_EV_DECODE(HCI_EV_GENERATE_ALIAS_REQ); + HCI_EV_DECODE(HCI_EV_ACTIVE_ADDRESS); + HCI_EV_DECODE(HCI_EV_ALLOW_PRIVATE_PAIRING); + HCI_EV_DECODE(HCI_EV_ALIAS_ADDRESS_REQ); + HCI_EV_DECODE(HCI_EV_ALIAS_NOT_RECOGNISED); + HCI_EV_DECODE(HCI_EV_FIXED_ADDRESS_ATTEMPT); + HCI_EV_DECODE(HCI_EV_SYNC_CONN_COMPLETE); + HCI_EV_DECODE(HCI_EV_SYNC_CONN_CHANGED); + HCI_EV_DECODE(HCI_EV_SNIFF_SUB_RATE); + HCI_EV_DECODE(HCI_EV_EXTENDED_INQUIRY_RESULT); + HCI_EV_DECODE(HCI_EV_ENCRYPTION_KEY_REFRESH_COMPLETE); + HCI_EV_DECODE(HCI_EV_IO_CAPABILITY_REQUEST); + HCI_EV_DECODE(HCI_EV_IO_CAPABILITY_RESPONSE); + HCI_EV_DECODE(HCI_EV_USER_CONFIRMATION_REQUEST); + HCI_EV_DECODE(HCI_EV_USER_PASSKEY_REQUEST); + HCI_EV_DECODE(HCI_EV_REMOTE_OOB_DATA_REQUEST); + HCI_EV_DECODE(HCI_EV_SIMPLE_PAIRING_COMPLETE); + HCI_EV_DECODE(HCI_EV_LST_CHANGE); + HCI_EV_DECODE(HCI_EV_ENHANCED_FLUSH_COMPLETE); + HCI_EV_DECODE(HCI_EV_USER_PASSKEY_NOTIFICATION); + HCI_EV_DECODE(HCI_EV_KEYPRESS_NOTIFICATION); + HCI_EV_DECODE(HCI_EV_REM_HOST_SUPPORTED_FEATURES); + HCI_EV_DECODE(HCI_EV_TRIGGERED_CLOCK_CAPTURE); + HCI_EV_DECODE(HCI_EV_SYNCHRONIZATION_TRAIN_COMPLETE); + HCI_EV_DECODE(HCI_EV_SYNCHRONIZATION_TRAIN_RECEIVED); + HCI_EV_DECODE(HCI_EV_CSB_RECEIVE); + HCI_EV_DECODE(HCI_EV_CSB_TIMEOUT); + HCI_EV_DECODE(HCI_EV_TRUNCATED_PAGE_COMPLETE); + HCI_EV_DECODE(HCI_EV_SLAVE_PAGE_RESPONSE_TIMEOUT); + HCI_EV_DECODE(HCI_EV_CSB_CHANNEL_MAP_CHANGE); + HCI_EV_DECODE(HCI_EV_INQUIRY_RESPONSE_NOTIFICATION); + HCI_EV_DECODE(HCI_EV_AUTHENTICATED_PAYLOAD_TIMEOUT_EXPIRED); + case HCI_EV_ULP: + { + switch (hci_ulp_sub_code) { + HCI_EV_DECODE(HCI_EV_ULP_CONNECTION_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_ADVERTISING_REPORT); + HCI_EV_DECODE(HCI_EV_ULP_CONNECTION_UPDATE_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_READ_REMOTE_USED_FEATURES_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_LONG_TERM_KEY_REQUEST); + HCI_EV_DECODE(HCI_EV_ULP_REMOTE_CONNECTION_PARAMETER_REQUEST); + HCI_EV_DECODE(HCI_EV_ULP_DATA_LENGTH_CHANGE); + HCI_EV_DECODE(HCI_EV_ULP_READ_LOCAL_P256_PUB_KEY_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_GENERATE_DHKEY_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_ENHANCED_CONNECTION_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_DIRECT_ADVERTISING_REPORT); + HCI_EV_DECODE(HCI_EV_ULP_PHY_UPDATE_COMPLETE); + HCI_EV_DECODE(HCI_EV_ULP_USED_CHANNEL_SELECTION); + } + break; + } + } + + return ret; +} +#endif + +static ssize_t scsc_iq_report_evt_read(char __user *buf, size_t len) +{ + ssize_t consumed = 0; + ssize_t ret = 0; + + /* Calculate the amount of data that can be transferred */ + len = min(h4_iq_report_evt_len - bt_service.read_offset, len); + + SCSC_TAG_DEBUG(BT_H4, "SCSC_IQ_REPORT_EVT_READ: td(h4_iq_len=%u offset=%u)\n", + h4_iq_report_evt_len, + bt_service.read_offset); + + /* Copy the data to the user buffer */ + ret = copy_to_user(buf, &h4_iq_report_evt[bt_service.read_offset], len); + if (ret == 0) { + /* All good - Update our consumed information */ + bt_service.read_offset += len; + consumed = len; + + SCSC_TAG_DEBUG(BT_H4, "SCSC_IQ_REPORT_EVT_READ: (offset=%u consumed: %u)\n", + bt_service.read_offset, + consumed); + + /* Have all data been copied to the userspace buffer */ + if (bt_service.read_offset == h4_iq_report_evt_len) { + /* All good - read operation is completed */ + bt_service.read_offset = 0; + bt_service.read_operation = BT_READ_OP_NONE; + } + } else { + SCSC_TAG_ERR(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + + return ret == 0 ? consumed : ret; +} + +static ssize_t scsc_hci_evt_read(char __user *buf, size_t len) +{ + struct BSMHCP_TD_HCI_EVT *td = &bt_service.bsmhcp_protocol->hci_evt_transfer_ring[bt_service.read_index]; + u8 h4_hci_event_header = HCI_EVENT_PKT; + ssize_t consumed = 0; + ssize_t ret = 0; + + SCSC_TAG_DEBUG(BT_H4, "td (length=%u, hci_connection_handle=0x%03x, event_type=%u), len=%zu, read_offset=%zu\n", + td->length, td->hci_connection_handle, td->event_type, len, bt_service.read_offset); + + /* Is this the start of the copy operation */ + if (0 == bt_service.read_offset) { + SCSC_TAG_DEBUG(BT_RX, "HCI Event [type=%s (0x%02x), length=%u]\n", + scsc_hci_evt_decode_event_code(td->data[0], td->data[2]), td->data[0], td->data[1]); + + if (td->data[1] + HCI_EVENT_HEADER_LENGTH != td->length) { + SCSC_TAG_ERR(BT_H4, "Firmware sent invalid HCI event\n"); + atomic_inc(&bt_service.error_count); + ret = -EFAULT; + } + + /* Store the H4 header in the user buffer */ + ret = copy_to_user(buf, &h4_hci_event_header, sizeof(h4_hci_event_header)); + if (0 == ret) { + /* All good - Update our consumed information */ + consumed = sizeof(h4_hci_event_header); + bt_service.read_offset = sizeof(h4_hci_event_header); + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + /* Can more data be put into the userspace buffer */ + if (0 == ret && (len - consumed)) { + /* Calculate the amount of data that can be transferred */ + len = min((td->length - (bt_service.read_offset - sizeof(h4_hci_event_header))), (len - consumed)); + + /* Copy the data to the user buffer */ + ret = copy_to_user(&buf[consumed], &td->data[bt_service.read_offset - sizeof(h4_hci_event_header)], len); + if (0 == ret) { + /* All good - Update our consumed information */ + bt_service.read_offset += len; + consumed += len; + + /* Have all data been copied to the userspace buffer */ + if (bt_service.read_offset == (sizeof(h4_hci_event_header) + td->length)) { + /* All good - read operation is completed */ + bt_service.read_offset = 0; + bt_service.read_operation = BT_READ_OP_NONE; + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + return 0 == ret ? consumed : ret; +} + +static ssize_t scsc_hci_evt_error_read(char __user *buf, size_t len) +{ + ssize_t ret; + ssize_t consumed = 0; + + /* Calculate the amount of data that can be transferred */ + len = min(sizeof(h4_hci_event_hardware_error) - bt_service.read_offset, len); + + /* Copy the data to the user buffer */ + ret = copy_to_user(buf, &h4_hci_event_hardware_error[bt_service.read_offset], len); + if (0 == ret) { + /* All good - Update our consumed information */ + bt_service.read_offset += len; + consumed = len; + + /* Have all data been copied to the userspace buffer */ + if (bt_service.read_offset == sizeof(h4_hci_event_hardware_error)) { + /* All good - read operation is completed */ + bt_service.read_offset = 0; + bt_service.read_operation = BT_READ_OP_NONE; + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + + return 0 == ret ? consumed : ret; +} + +static ssize_t scsc_acl_read(char __user *buf, size_t len) +{ + struct BSMHCP_TD_ACL_RX *td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.read_index]; + ssize_t consumed = 0; + size_t copy_len = 0; + ssize_t ret = 0; + + SCSC_TAG_DEBUG(BT_H4, "td (length=%u, hci_connection_handle=0x%03x, packet_boundary=%u, broadcast_flag=%u), len=%zu, read_offset=%zu\n", + td->length, td->hci_connection_handle, td->packet_boundary, td->broadcast_flag, len, bt_service.read_offset); + + /* Has the header been copied to userspace */ + if (bt_service.read_offset < sizeof(h4_acl_header)) { + /* Calculate the amount of data that can be transferred */ + copy_len = min(sizeof(h4_acl_header) - bt_service.read_offset, len); + + /* Fully generate the H4 header + ACL data header regardless of the available amount of user memory */ + h4_acl_header[0] = HCI_ACLDATA_PKT; + h4_acl_header[1] = td->hci_connection_handle & 0x00ff; + h4_acl_header[2] = ((td->hci_connection_handle & 0x0f00) >> 8) | ((td->packet_boundary & 0x03) << 4) | ((td->broadcast_flag & 0x03) << 6); + h4_acl_header[3] = td->length & 0x00ff; + h4_acl_header[4] = (td->length & 0xff00) >> 8; + + /* Copy the H4 header + ACL data header to the userspace buffer */ + ret = copy_to_user(buf, &h4_acl_header[bt_service.read_offset], copy_len); + if (0 == ret) { + /* All good - Update our consumed information */ + consumed = copy_len; + bt_service.read_offset += copy_len; + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + /* Can more data be put into the userspace buffer */ + if (0 == ret && bt_service.read_offset >= sizeof(h4_acl_header) && (len - consumed)) { + /* Calculate the amount of data that can be transferred */ + copy_len = min((td->length - (bt_service.read_offset - sizeof(h4_acl_header))), (len - consumed)); + + /* Copy the data to the user buffer */ + ret = copy_to_user(&buf[consumed], &td->data[bt_service.read_offset - sizeof(h4_acl_header)], copy_len); + if (0 == ret) { + /* All good - Update our consumed information */ + bt_service.read_offset += copy_len; + consumed += copy_len; + + /* Have all data been copied to the userspace buffer */ + if (bt_service.read_offset == (sizeof(h4_acl_header) + td->length)) { + /* All good - read operation is completed */ + bt_service.read_offset = 0; + bt_service.read_operation = BT_READ_OP_NONE; + + /* Only supported on start packet*/ + if (td->packet_boundary == HCI_ACL_PACKET_BOUNDARY_START_FLUSH) + /* The "false" argument is to tell the detection that this is RX */ + scsc_avdtp_detect_rxtx(td->hci_connection_handle, td->data, td->length, false); + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + SCSC_TAG_DEBUG(BT_H4, "read_offset=%zu, consumed=%zu, ret=%zd, len=%zu, copy_len=%zu\n", + bt_service.read_offset, consumed, ret, len, copy_len); + + return 0 == ret ? consumed : ret; +} + +static ssize_t scsc_acl_credit(char __user *buf, size_t len) +{ +#ifdef CONFIG_SCSC_PRINTK + struct BSMHCP_TD_ACL_TX_FREE *td = &bt_service.bsmhcp_protocol->acl_tx_free_transfer_ring[bt_service.read_index]; +#endif + ssize_t consumed = 0; + ssize_t ret = 0; + + SCSC_TAG_DEBUG(BT_H4, "td (hci_connection_handle=0x%03x, buffer_index=%u), len=%zu, read_offset=%zu\n", + td->hci_connection_handle & SCSC_BT_ACL_HANDLE_MASK, + td->buffer_index, len, bt_service.read_offset); + + /* Calculate the amount of data that can be transferred */ + len = min(h4_hci_event_ncp_header_len - bt_service.read_offset, len); + + /* Copy the data to the user buffer */ + ret = copy_to_user(buf, &h4_hci_event_ncp_header[bt_service.read_offset], len); + if (0 == ret) { + /* All good - Update our consumed information */ + bt_service.read_offset += len; + consumed = len; + + /* Have all data been copied to the userspace buffer */ + if (bt_service.read_offset == h4_hci_event_ncp_header_len) { + /* All good - read operation is completed */ + bt_service.read_offset = 0; + bt_service.read_operation = BT_READ_OP_NONE; + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret); + ret = -EACCES; + } + + return 0 == ret ? consumed : ret; +} + +static ssize_t scsc_bt_shm_h4_read_continue(char __user *buf, size_t len) +{ + ssize_t ret = 0; + + /* Is a HCI event read operation ongoing */ + if (BT_READ_OP_HCI_EVT == bt_service.read_operation) { + SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_HCI_EVT\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_hci_evt_read(buf, len); + if (BT_READ_OP_NONE == bt_service.read_operation) + /* All done - increase the read pointer and continue + * unless this was an out-of-order read for the queue + * sync helper */ + if (bt_service.read_index == bt_service.mailbox_hci_evt_read) + BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE); + /* Is a ACL data read operation ongoing */ + } else if (BT_READ_OP_ACL_DATA == bt_service.read_operation) { + SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_ACL_DATA\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_acl_read(buf, len); + if (BT_READ_OP_NONE == bt_service.read_operation) + /* All done - increase the read pointer and continue */ + BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); + /* Is a ACL credit update operation ongoing */ + } else if (BT_READ_OP_ACL_CREDIT == bt_service.read_operation) { + SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_ACL_CREDIT\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_acl_credit(buf, len); + } else if (bt_service.read_operation == BT_READ_OP_IQ_REPORT) { + SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_IQ_REPORT\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_iq_report_evt_read(buf, len); + if (bt_service.read_operation == BT_READ_OP_NONE) + /* All done - increase the read pointer and continue */ + BSMHCP_INCREASE_INDEX(bt_service.mailbox_iq_report_read, BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE); + } else if (BT_READ_OP_HCI_EVT_ERROR == bt_service.read_operation) { + SCSC_TAG_ERR(BT_H4, "BT_READ_OP_HCI_EVT_ERROR\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_hci_evt_error_read(buf, len); + if (BT_READ_OP_NONE == bt_service.read_operation) + /* All done - set the stop condition */ + bt_service.read_operation = BT_READ_OP_STOP; + } else if (BT_READ_OP_STOP == bt_service.read_operation) + ret = -EIO; + + return ret; +} + +static ssize_t scsc_bt_shm_h4_read_iq_report_evt(char __user *buf, size_t len) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + + if (bt_service.read_operation == BT_READ_OP_NONE && + bt_service.mailbox_iq_report_read != bt_service.mailbox_iq_report_write) { + struct BSMHCP_TD_IQ_REPORTING_EVT *td = + &bt_service.bsmhcp_protocol->iq_reporting_transfer_ring[bt_service.mailbox_iq_report_read]; + u32 index = 0; + u32 j = 0; + u32 i; + + memset(h4_iq_report_evt, 0, sizeof(h4_iq_report_evt)); + h4_iq_report_evt_len = 0; + + h4_iq_report_evt[index++] = HCI_EVENT_PKT; + h4_iq_report_evt[index++] = 0x3E; + index++; /* Leaving room for total length of params */ + h4_iq_report_evt[index++] = td->subevent_code; + + if (td->subevent_code == HCI_LE_CONNECTIONLESS_IQ_REPORT_EVENT_SUB_CODE) { + /* LE Connectionless IQ Report Event*/ + h4_iq_report_evt[index++] = td->sync_handle & 0xFF; + h4_iq_report_evt[index++] = (td->sync_handle >> 8) & 0xFF; + } else if (td->subevent_code == HCI_LE_CONNECTION_IQ_REPORT_EVENT_SUB_CODE) { + /* LE connection IQ Report Event */ + h4_iq_report_evt[index++] = td->connection_handle & 0xFF; + h4_iq_report_evt[index++] = (td->connection_handle >> 8) & 0xFF; + h4_iq_report_evt[index++] = td->rx_phy; + + } + h4_iq_report_evt[index++] = td->channel_index; + h4_iq_report_evt[index++] = td->rssi & 0xFF; + h4_iq_report_evt[index++] = (td->rssi >> 8) & 0xFF; + h4_iq_report_evt[index++] = td->rssi_antenna_id; + h4_iq_report_evt[index++] = td->cte_type; + h4_iq_report_evt[index++] = td->slot_durations; + h4_iq_report_evt[index++] = td->packet_status; + h4_iq_report_evt[index++] = td->event_count & 0xFF; + h4_iq_report_evt[index++] = (td->event_count >> 8) & 0xFF; + h4_iq_report_evt[index++] = td->sample_count; + + /* Total length of hci event */ + h4_iq_report_evt_len = index + (2 * td->sample_count); + + /* Total length of hci event parameters */ + h4_iq_report_evt[2] = h4_iq_report_evt_len - 3; + + for (i = 0; i < td->sample_count; i++) { + h4_iq_report_evt[index + i] = td->data[j++]; + h4_iq_report_evt[(index + td->sample_count) + i] = td->data[j++]; + } + + bt_service.read_operation = BT_READ_OP_IQ_REPORT; + bt_service.read_index = bt_service.mailbox_iq_report_read; + + ret = scsc_iq_report_evt_read(&buf[consumed], len - consumed); + if (ret > 0) { + /* All good - Update our consumed information */ + consumed += ret; + ret = 0; + + /** + * Update the index if all the data could be copied to the userspace + * buffer otherwise stop processing the HCI events + */ + if (bt_service.read_operation == BT_READ_OP_NONE) + BSMHCP_INCREASE_INDEX(bt_service.mailbox_iq_report_read, + BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE); + } + } + + return ret == 0 ? consumed : ret; +} + +static ssize_t scsc_bt_shm_h4_read_hci_evt(char __user *buf, size_t len) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + + while (BT_READ_OP_NONE == bt_service.read_operation && 0 == ret && !bt_service.hci_event_paused && bt_service.mailbox_hci_evt_read != bt_service.mailbox_hci_evt_write) { + struct BSMHCP_TD_HCI_EVT *td = &bt_service.bsmhcp_protocol->hci_evt_transfer_ring[bt_service.mailbox_hci_evt_read]; + + /* This event has already been processed - skip it */ + if (bt_service.processed[bt_service.mailbox_hci_evt_read]) { + bt_service.processed[bt_service.mailbox_hci_evt_read] = false; + BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE); + continue; + } + + /* A connection event has been detected by the firmware */ + if (td->event_type & BSMHCP_EVENT_TYPE_CONNECTED) { + /* Sanity check of the HCI connection handle */ + if (td->hci_connection_handle >= SCSC_BT_CONNECTION_INFO_MAX) { + SCSC_TAG_ERR(BT_H4, "connection handle is beyond max (hci_connection_handle=0x%03x)\n", + td->hci_connection_handle); + atomic_inc(&bt_service.error_count); + break; + } + + SCSC_TAG_DEBUG(BT_H4, "connected (hci_connection_handle=0x%03x, state=%u)\n", + td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state); + + /* Update the connection table to mark it as active */ + bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_ACTIVE; + bt_service.connection_handle_list[td->hci_connection_handle].length = 0; + + /* ACL data processing can now continue */ + bt_service.acldata_paused = false; + + /* A disconnection event has been detected by the firmware */ + } else if (td->event_type & BSMHCP_EVENT_TYPE_DISCONNECTED) { + SCSC_TAG_DEBUG(BT_H4, "disconnected (hci_connection_handle=0x%03x, state=%u)\n", + td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state); + + /* If this ACL connection had an avdtp stream, mark it gone and interrupt the bg */ + if (scsc_avdtp_detect_reset_connection_handle(td->hci_connection_handle)) + mmiowb(); + + /* If the connection is marked as active the ACL disconnect packet hasn't yet arrived */ + if (CONNECTION_ACTIVE == bt_service.connection_handle_list[td->hci_connection_handle].state) { + /* Pause the HCI event procssing until the ACL disconnect packet arrives */ + bt_service.hci_event_paused = true; + break; + } + + /* Firmware does not have more ACL data - Mark the connection as inactive */ + bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_NONE; + } + + /* Start a HCI event copy to userspace */ + bt_service.read_operation = BT_READ_OP_HCI_EVT; + bt_service.read_index = bt_service.mailbox_hci_evt_read; + ret = scsc_hci_evt_read(&buf[consumed], len - consumed); + if (ret > 0) { + /* All good - Update our consumed information */ + consumed += ret; + ret = 0; + + /* Update the index if all the data could be copied to the userspace buffer otherwise stop processing the HCI events */ + if (BT_READ_OP_NONE == bt_service.read_operation) + BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE); + else + break; + } + } + + return 0 == ret ? consumed : ret; +} + +/** + * Start the acl data to userspace copy + * + * Acl processing should be stopped if either unable to read a complete packet + * or a complete packet is read and BlueZ is enabled + * + * @param[out] ret result of read operations written to here + * @param[in,out] consumed read bytes added to this + * + * @return true if ACL data processing should stop + */ +static bool scsc_bt_shm_h4_acl_start_copy(char __user *buf, + size_t len, + ssize_t *ret, + ssize_t *consumed) +{ + bt_service.read_operation = BT_READ_OP_ACL_DATA; + bt_service.read_index = bt_service.mailbox_acl_rx_read; + *ret = scsc_acl_read(&buf[*consumed], len - *consumed); + if (*ret <= 0) + return *ret < 0; /* Break the loop for errors */ + + /* Update our consumed information */ + *consumed += *ret; + *ret = 0; + + /* Stop processing if all the data could not be copied to userspace */ + if (bt_service.read_operation != BT_READ_OP_NONE) + return true; + + BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); + + return false; +} + +static ssize_t scsc_bt_shm_h4_read_acl_data(char __user *buf, size_t len) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + + while (bt_service.read_operation == BT_READ_OP_NONE && + !bt_service.acldata_paused && + bt_service.mailbox_acl_rx_read != bt_service.mailbox_acl_rx_write) { + struct BSMHCP_TD_ACL_RX *td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.mailbox_acl_rx_read]; + + /* Bypass packet inspection and connection handling for data dump */ + if (SCSC_BT_ACL_RAW == (td->hci_connection_handle & SCSC_BT_ACL_RAW_MASK)) { + if (scsc_bt_shm_h4_acl_start_copy(buf, len, &ret, &consumed)) + break; + } + + /* Sanity check of the HCI connection handle */ + if (td->hci_connection_handle >= SCSC_BT_CONNECTION_INFO_MAX) { + SCSC_TAG_ERR(BT_H4, "connection handle is beyond max (hci_connection_handle=0x%03x)\n", + td->hci_connection_handle); + atomic_inc(&bt_service.error_count); + break; + } + + /* Only process ACL data if the connection is marked active aka a HCI connection complete event has arrived */ + if (CONNECTION_ACTIVE == bt_service.connection_handle_list[td->hci_connection_handle].state) { + /* Is this the final packet for the indicated ACL connection */ + if (td->disconnected) { + SCSC_TAG_DEBUG(BT_H4, "ACL disconnected (hci_connection_handle=0x%03x, state=%u)\n", + td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state); + + /* Update the connection table to mark it as disconnected */ + bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_DISCONNECTED; + + /* Clear the HCI event processing to allow for the HCI disconnect event to be transferred to userspace */ + bt_service.hci_event_paused = false; + + /* Update the read pointer */ + BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); + } else { + if (scsc_bt_shm_h4_acl_start_copy(buf, len, &ret, &consumed)) + break; + } + /* If the connection state is inactive the HCI connection complete information hasn't yet arrived. Stop processing ACL data */ + } else if (CONNECTION_NONE == bt_service.connection_handle_list[td->hci_connection_handle].state) { + SCSC_TAG_DEBUG(BT_H4, "ACL empty (hci_connection_handle=0x%03x, state=%u)\n", + td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state); + bt_service.acldata_paused = true; + /* If the connection state is disconnection the firmware sent ACL after the ACL disconnect packet which is an FW error */ + } else { + SCSC_TAG_ERR(BT_H4, "ACL data received after disconnected indication\n"); + atomic_inc(&bt_service.error_count); + break; + } + } + + return 0 == ret ? consumed : ret; +} + +static ssize_t scsc_bt_shm_h4_read_acl_credit(char __user *buf, size_t len) +{ + ssize_t ret = 0; + ssize_t consumed = 0; + + if (BT_READ_OP_NONE == bt_service.read_operation && 0 == ret && + bt_service.mailbox_acl_free_read != bt_service.mailbox_acl_free_write) { + u32 entries = 0; + u32 index; + + memset(h4_hci_event_ncp_header, 0, sizeof(h4_hci_event_ncp_header)); + + while (bt_service.mailbox_acl_free_read != bt_service.mailbox_acl_free_write) { + struct BSMHCP_TD_ACL_TX_FREE *td = + &bt_service.bsmhcp_protocol->acl_tx_free_transfer_ring[bt_service.mailbox_acl_free_read]; + uint16_t sanitized_conn_handle = td->hci_connection_handle & SCSC_BT_ACL_HANDLE_MASK; + + if (bt_service.connection_handle_list[sanitized_conn_handle].state == CONNECTION_ACTIVE) { + for (index = 0; index < BSMHCP_TRANSFER_RING_ACL_COUNT; index++) { + if (0 == h4_hci_credit_entries[index].hci_connection_handle) { + h4_hci_credit_entries[index].hci_connection_handle = + td->hci_connection_handle; + h4_hci_credit_entries[index].credits = 1; + entries++; + break; + } else if ((h4_hci_credit_entries[index].hci_connection_handle & + SCSC_BT_ACL_HANDLE_MASK) == sanitized_conn_handle) { + h4_hci_credit_entries[index].hci_connection_handle = + td->hci_connection_handle; + h4_hci_credit_entries[index].credits++; + break; + } + } + } else + SCSC_TAG_WARNING(BT_H4, + "No active connection ((hci_connection_handle=0x%03x)\n", + sanitized_conn_handle); + + BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_free_read, BSMHCP_TRANSFER_RING_ACL_SIZE); + } + + if (entries) { + /* Fill the number of completed packets data into the temp buffer */ + h4_hci_event_ncp_header[0] = HCI_EVENT_PKT; + h4_hci_event_ncp_header[1] = HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS_EVENT; + h4_hci_event_ncp_header[2] = 1 + (4 * entries); /* Parameter length */ + h4_hci_event_ncp_header[3] = entries; /* Number_of_Handles */ + h4_hci_event_ncp_header_len = 4 + (4 * entries); + + /* Start a ACL credit copy to userspace */ + bt_service.read_operation = BT_READ_OP_ACL_CREDIT; + bt_service.read_index = bt_service.mailbox_acl_free_read; + ret = scsc_acl_credit(&buf[consumed], len - consumed); + if (ret > 0) { + /* Update our consumed information */ + consumed += ret; + ret = 0; + } + } + } + + return 0 == ret ? consumed : ret; +} + +ssize_t scsc_bt_shm_h4_queue_sync_helper(char __user *buf, size_t len) +{ + ssize_t ret = 0; + bool found = false; + u32 mailbox_hci_evt_read = bt_service.mailbox_hci_evt_read; + + /* If both the HCI event transfer ring and ACL data transfer ring has been + * paused the entire HCI event transfer ring is scanned for the presence + * of the connected indication. Once present this is transferred to the host + * stack and marked as processed. This will unlock the hci event processing */ + while (bt_service.hci_event_paused && bt_service.acldata_paused) { + struct BSMHCP_TD_ACL_RX *acl_td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.mailbox_acl_rx_read]; + + while (mailbox_hci_evt_read != bt_service.mailbox_hci_evt_write) { + struct BSMHCP_TD_HCI_EVT *td = &bt_service.bsmhcp_protocol->hci_evt_transfer_ring[mailbox_hci_evt_read]; + + if (td->event_type & BSMHCP_EVENT_TYPE_CONNECTED && acl_td->hci_connection_handle == td->hci_connection_handle) { + /* Update the connection table to mark it as active */ + bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_ACTIVE; + bt_service.connection_handle_list[td->hci_connection_handle].length = 0; + + /* ACL data processing can now continue */ + bt_service.acldata_paused = false; + + /* Mark the event as processed */ + bt_service.processed[mailbox_hci_evt_read] = true; + + /* Indicate the event have been found */ + found = true; + + /* Start a HCI event copy to userspace */ + bt_service.read_operation = BT_READ_OP_HCI_EVT; + bt_service.read_index = mailbox_hci_evt_read; + ret = scsc_hci_evt_read(buf, len); + break; + } + + BSMHCP_INCREASE_INDEX(mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE); + } + + if (!found) { + ret = wait_event_interruptible_timeout(bt_service.read_wait, + ((mailbox_hci_evt_read != bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write || + 0 != atomic_read(&bt_service.error_count) || + bt_service.bsmhcp_protocol->header.panic_deathbed_confession)), HZ); + if (0 == ret) { + SCSC_TAG_ERR(BT_H4, "firmware didn't send the connected event within the given timeframe\n"); + atomic_inc(&bt_service.error_count); + break; + } else if (1 != ret) { + SCSC_TAG_INFO(BT_H4, "user interrupt\n"); + break; + } + } + } + + return ret; +} + +ssize_t scsc_bt_shm_h4_read(struct file *file, char __user *buf, size_t len, loff_t *offset) +{ + ssize_t consumed = 0; + ssize_t ret = 0; + ssize_t res; + bool gen_bg_int = false; + bool gen_fg_int = false; + + if (len == 0) + return 0; + + /* Only 1 reader is allowed */ + if (1 != atomic_inc_return(&bt_service.h4_readers)) { + atomic_dec(&bt_service.h4_readers); + return -EIO; + } + + /* Update the cached variables with the non-cached variables */ + bt_service.mailbox_hci_evt_write = bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write; + bt_service.mailbox_acl_rx_write = bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write; + bt_service.mailbox_acl_free_write = bt_service.bsmhcp_protocol->header.mailbox_acl_free_write; + bt_service.mailbox_iq_report_write = bt_service.bsmhcp_protocol->header.mailbox_iq_report_write; + + /* Only generate the HCI hardware error event if any pending operation has been completed + * and the event hasn't already neen sent. This check assume the main while loop will exit + * on a completed operation in the next section */ + if (0 != atomic_read(&bt_service.error_count) && BT_READ_OP_NONE == bt_service.read_operation) + bt_service.read_operation = BT_READ_OP_HCI_EVT_ERROR; + + /* put the remaining data from the transfer ring into the available userspace buffer */ + if (BT_READ_OP_NONE != bt_service.read_operation) { + ret = scsc_bt_shm_h4_read_continue(buf, len); + /* Update the consumed variable in case a operation was ongoing */ + if (0 < ret) { + consumed = ret; + ret = 0; + } + } + + /* Main loop - Can only be entered when no operation is present on entering this function + * or no hardware error has been detected. It loops until data has been placed in the + * userspace buffer or an error has been detected */ + while (0 == atomic_read(&bt_service.error_count) && 0 == consumed) { + /* If both the HCI event processing and ACL data processing has been disabled this function + * helps exit this condition by scanning the HCI event queue for the connection established + * event and return it to userspace */ + ret = scsc_bt_shm_h4_queue_sync_helper(buf, len); + if (ret > 0) { + consumed = ret; + break; + } + + /* Does any of the read/write pairs differs */ + if ((bt_service.mailbox_hci_evt_read == bt_service.mailbox_hci_evt_write || bt_service.hci_event_paused) && + (bt_service.mailbox_acl_rx_read == bt_service.mailbox_acl_rx_write || bt_service.acldata_paused) && + bt_service.mailbox_acl_free_read == bt_service.mailbox_acl_free_write && + bt_service.mailbox_iq_report_read == bt_service.mailbox_iq_report_write && + 0 == atomic_read(&bt_service.error_count) && + 0 == bt_service.bsmhcp_protocol->header.panic_deathbed_confession) { + /* Don't wait if in NONBLOCK mode */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + /* All read/write pairs are identical - wait for the firmware. The conditional + * check is used to verify that a read/write pair has actually changed */ + ret = wait_event_interruptible( + bt_service.read_wait, + ((bt_service.mailbox_hci_evt_read != + bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write && + !bt_service.hci_event_paused) || + (bt_service.mailbox_acl_rx_read != + bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write && + !bt_service.acldata_paused) || + (bt_service.mailbox_acl_free_read != + bt_service.bsmhcp_protocol->header.mailbox_acl_free_write) || + (bt_service.mailbox_iq_report_read != + bt_service.bsmhcp_protocol->header.mailbox_iq_report_write) || + atomic_read(&bt_service.error_count) != 0 || + bt_service.bsmhcp_protocol->header.panic_deathbed_confession)); + + /* Has an error been detected elsewhere in the driver then just return from this function */ + if (0 != atomic_read(&bt_service.error_count)) + break; + + /* Any failures is handled by the userspace application */ + if (ret) + break; + + /* Refresh our write indexes before starting to process the protocol */ + bt_service.mailbox_hci_evt_write = bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write; + bt_service.mailbox_acl_rx_write = bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write; + bt_service.mailbox_acl_free_write = bt_service.bsmhcp_protocol->header.mailbox_acl_free_write; + bt_service.mailbox_iq_report_write = bt_service.bsmhcp_protocol->header.mailbox_iq_report_write; + } + + SCSC_TAG_DEBUG(BT_H4, "hci_evt_read=%u, hci_evt_write=%u, acl_rx_read=%u,acl_rx_write=%u\n", + bt_service.mailbox_hci_evt_read, + bt_service.mailbox_hci_evt_write, + bt_service.mailbox_acl_rx_read, + bt_service.mailbox_acl_rx_write); + + SCSC_TAG_DEBUG(BT_H4, "acl_free_read=%u, acl_free_write=%u, iq_report_read=%u iq_report_write=%u\n", + bt_service.mailbox_acl_free_read, + bt_service.mailbox_acl_free_write, + bt_service.mailbox_iq_report_read, + bt_service.mailbox_iq_report_write); + + SCSC_TAG_DEBUG(BT_H4, "read_operation=%u, hci_event_paused=%u, acldata_paused=%u\n", + bt_service.read_operation, bt_service.hci_event_paused, + bt_service.acldata_paused); + + while (bt_service.mailbox_acl_free_read_scan != bt_service.mailbox_acl_free_write) { + struct BSMHCP_TD_ACL_TX_FREE *td = &bt_service.bsmhcp_protocol->acl_tx_free_transfer_ring[bt_service.mailbox_acl_free_read_scan]; + + /* Free the buffer in the allocation table */ + if (td->buffer_index < BSMHCP_DATA_BUFFER_TX_ACL_SIZE) { + bt_service.allocated[td->buffer_index] = 0; + bt_service.freed_count++; + + SCSC_TAG_DEBUG(BT_TX, "ACL[CREDIT] (index=%u, buffer=%u, credits=%u)\n", + bt_service.mailbox_acl_free_read_scan, + td->buffer_index, + BSMHCP_DATA_BUFFER_TX_ACL_SIZE - (bt_service.allocated_count - bt_service.freed_count)); + } + + BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_free_read_scan, BSMHCP_TRANSFER_RING_ACL_SIZE); + } + + /* First: process any pending HCI event that needs to be sent to userspace */ + res = scsc_bt_shm_h4_read_hci_evt(&buf[consumed], len - consumed); + if (res < 0) { + ret = res; + break; + } + consumed += res; + + /* Second: process any pending ACL data that needs to be sent to userspace */ + res = scsc_bt_shm_h4_read_acl_data(&buf[consumed], len - consumed); + if (res < 0) { + ret = res; + break; + } + consumed += res; + + /* Third: process any pending ACL data that needs to be sent to userspace */ + res = scsc_bt_shm_h4_read_acl_credit(&buf[consumed], len - consumed); + if (res < 0) { + ret = res; + break; + } + consumed += res; + + res = scsc_bt_shm_h4_read_iq_report_evt(&buf[consumed], len - consumed); + if (res < 0) { + ret = res; + break; + } + consumed += res; + } + + if (0 == ret && 0 == consumed) { + if (0 != atomic_read(&bt_service.error_count) && BT_READ_OP_NONE == bt_service.read_operation) + bt_service.read_operation = BT_READ_OP_HCI_EVT_ERROR; + + if (BT_READ_OP_HCI_EVT_ERROR == bt_service.read_operation) { + SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_HCI_EVT_ERROR\n"); + + /* Copy data into the userspace buffer */ + ret = scsc_hci_evt_error_read(buf, len); + if (ret > 0) { + consumed += ret; + ret = 0; + } + + if (BT_READ_OP_NONE == bt_service.read_operation) + /* All done - set the stop condition */ + bt_service.read_operation = BT_READ_OP_STOP; + } + } + + /* If anything was read, generate the appropriate interrupt(s) */ + if (bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read != + bt_service.mailbox_hci_evt_read) + gen_bg_int = true; + + if (bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read != + bt_service.mailbox_acl_rx_read || + bt_service.bsmhcp_protocol->header.mailbox_acl_free_read != + bt_service.mailbox_acl_free_read) + gen_fg_int = true; + + if (bt_service.bsmhcp_protocol->header.mailbox_iq_report_read != + bt_service.mailbox_iq_report_read) + gen_fg_int = true; + + + /* Update the read index for all transfer rings */ + bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read = bt_service.mailbox_hci_evt_read; + bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read = bt_service.mailbox_acl_rx_read; + bt_service.bsmhcp_protocol->header.mailbox_acl_free_read = bt_service.mailbox_acl_free_read; + bt_service.bsmhcp_protocol->header.mailbox_iq_report_read = bt_service.mailbox_iq_report_read; + + /* Ensure the data is updating correctly in memory */ + mmiowb(); + + if (gen_bg_int) + scsc_service_mifintrbit_bit_set(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, SCSC_MIFINTR_TARGET_R4); + + if (gen_fg_int) { + if (bt_service.bsmhcp_protocol->header.firmware_features & BSMHCP_FEATURE_M4_INTERRUPTS) + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, SCSC_MIFINTR_TARGET_M4); + else + /* Trigger the interrupt in the mailbox */ + scsc_service_mifintrbit_bit_set(bt_service.service, + bt_service.bsmhcp_protocol->header.ap_to_fg_int_src, SCSC_MIFINTR_TARGET_R4); + } + + if (BT_READ_OP_STOP != bt_service.read_operation) + SCSC_TAG_DEBUG(BT_H4, "hci_evt_read=%u, acl_rx_read=%u, acl_free_read=%u, read_operation=%u, consumed=%zd, ret=%zd\n", + bt_service.mailbox_hci_evt_read, bt_service.mailbox_acl_rx_read, bt_service.mailbox_acl_free_read, bt_service.read_operation, consumed, ret); + + /* Decrease the H4 readers counter */ + atomic_dec(&bt_service.h4_readers); + + return 0 == ret ? consumed : ret; +} + +ssize_t scsc_bt_shm_h4_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + size_t length; + size_t hci_pkt_len; + ssize_t written = 0; + ssize_t ret = 0; + + SCSC_TAG_DEBUG(BT_H4, "enter\n"); + + UNUSED(file); + UNUSED(offset); + + /* Only 1 writer is allowed */ + if (1 != atomic_inc_return(&bt_service.h4_writers)) { + atomic_dec(&bt_service.h4_writers); + return -EIO; + } + + /* Has en error been detect then just return with an error */ + if (0 != atomic_read(&bt_service.error_count)) { + /* SCSC_TAG_WARNING(BT_H4, "firmware panicked or protocol error (error_count=%u)\n", atomic_read(&bt_service.error_count));*/ + atomic_dec(&bt_service.h4_writers); + return -EIO; + } + + while (written != count && 0 == ret) { + length = min(count - written, sizeof(h4_write_buffer) - bt_service.h4_write_offset); + SCSC_TAG_DEBUG(BT_H4, "count: %zu, length: %zu, h4_write_offset: %zu, written:%zu, size:%zu\n", + count, length, bt_service.h4_write_offset, written, sizeof(h4_write_buffer)); + + /* Is there room in the temp buffer */ + if (0 == length) { + SCSC_TAG_ERR(BT_H4, "no room in the buffer\n"); + atomic_inc(&bt_service.error_count); + ret = -EIO; + break; + } + + /* Copy the userspace data to the target buffer */ + ret = copy_from_user(&h4_write_buffer[bt_service.h4_write_offset], &buf[written], length); + if (0 == ret) { + /* Is there enough data to include a HCI command header and is the type a HCI_COMMAND_PKT */ + if ((length + bt_service.h4_write_offset) >= H4DMUX_HEADER_HCI && HCI_COMMAND_PKT == h4_write_buffer[0]) { + /* Extract the HCI command packet length */ + hci_pkt_len = h4_write_buffer[3] + 3; + + /* Is it a complete packet available */ + if ((hci_pkt_len + 1) <= (length + bt_service.h4_write_offset)) { + /* Transfer the packet to the HCI command transfer ring */ + ret = scsc_bt_shm_h4_hci_cmd_write(&h4_write_buffer[1], hci_pkt_len); + if (ret >= 0) { + written += ((hci_pkt_len + 1) - bt_service.h4_write_offset); + bt_service.h4_write_offset = 0; + ret = 0; + } + } else { + /* Still needing data to have the complete packet */ + SCSC_TAG_WARNING(BT_H4, "missing data (need=%zu, got=%zu)\n", (hci_pkt_len + 1), (length + bt_service.h4_write_offset)); + written += length; + bt_service.h4_write_offset += (u32) length; + } + /* Is there enough data to include a ACL data header and is the type a HCI_ACLDATA_PKT */ + } else if ((length + bt_service.h4_write_offset) >= H4DMUX_HEADER_ACL && HCI_ACLDATA_PKT == h4_write_buffer[0]) { + /* Extract the ACL data packet length */ + hci_pkt_len = (h4_write_buffer[3] | (h4_write_buffer[4] << 8)); + + /* Sanity check on the packet length */ + if (hci_pkt_len > BSMHCP_ACL_PACKET_SIZE) { + SCSC_TAG_ERR(BT_H4, "ACL packet length is larger than read buffer size specifies (%zu > %u)\n", hci_pkt_len, BSMHCP_ACL_PACKET_SIZE); + atomic_inc(&bt_service.error_count); + ret = -EIO; + break; + } + + /* Is it a complete packet available */ + if ((hci_pkt_len + 5) <= (length + bt_service.h4_write_offset)) { + /* Transfer the packet to the ACL data transfer ring */ + ret = scsc_bt_shm_h4_acl_write(&h4_write_buffer[1], hci_pkt_len + 4); + if (ret >= 0) { + written += ((hci_pkt_len + 5) - bt_service.h4_write_offset); + bt_service.h4_write_offset = 0; + ret = 0; + } + } else { + /* Still needing data to have the complete packet */ + SCSC_TAG_WARNING(BT_H4, "missing data (need=%zu, got=%zu)\n", (hci_pkt_len + 5), (length - bt_service.h4_write_offset)); + written += length; + bt_service.h4_write_offset += (u32) length; + } + /* Is there less data than a header then just wait for more */ + } else if (length <= 5) { + bt_service.h4_write_offset += length; + written += length; + /* Header is unknown - unable to proceed */ + } else { + atomic_inc(&bt_service.error_count); + ret = -EIO; + } + } else { + SCSC_TAG_WARNING(BT_H4, "copy_from_user returned: %zu\n", ret); + ret = -EACCES; + } + } + + SCSC_TAG_DEBUG(BT_H4, "h4_write_offset=%zu, ret=%zu, written=%zu\n", + bt_service.h4_write_offset, ret, written); + + /* Decrease the H4 readers counter */ + atomic_dec(&bt_service.h4_writers); + + return 0 == ret ? written : ret; +} + +unsigned scsc_bt_shm_h4_poll(struct file *file, poll_table *wait) +{ + /* Add the wait queue to the polling queue */ + poll_wait(file, &bt_service.read_wait, wait); + + /* Has en error been detect then just return with an error */ + if (bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write != + bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read || + bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write != + bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read || + bt_service.bsmhcp_protocol->header.mailbox_acl_free_write != + bt_service.bsmhcp_protocol->header.mailbox_acl_free_read || + bt_service.bsmhcp_protocol->header.mailbox_iq_report_write != + bt_service.bsmhcp_protocol->header.mailbox_iq_report_read || + (bt_service.read_operation != BT_READ_OP_NONE && + bt_service.read_operation != BT_READ_OP_STOP) || + ((BT_READ_OP_STOP != bt_service.read_operation) && + (0 != atomic_read(&bt_service.error_count) || + bt_service.bsmhcp_protocol->header.panic_deathbed_confession))) { + SCSC_TAG_DEBUG(BT_H4, "queue(s) changed\n"); + return POLLIN | POLLRDNORM; /* readeable */ + } + + SCSC_TAG_DEBUG(BT_H4, "no change\n"); + + return POLLOUT; /* writeable */ +} + +/* Initialise the shared memory interface */ +int scsc_bt_shm_init(void) +{ + /* Get kmem pointer to the shared memory ref */ + bt_service.bsmhcp_protocol = scsc_mx_service_mif_addr_to_ptr(bt_service.service, bt_service.bsmhcp_ref); + if (bt_service.bsmhcp_protocol == NULL) { + SCSC_TAG_ERR(BT_COMMON, "couldn't map kmem to shm_ref 0x%08x\n", (u32)bt_service.bsmhcp_ref); + return -ENOMEM; + } + + /* Clear the protocol shared memory area */ + memset(bt_service.bsmhcp_protocol, 0, sizeof(*bt_service.bsmhcp_protocol)); + bt_service.bsmhcp_protocol->header.magic_value = BSMHCP_PROTOCOL_MAGICVALUE; + bt_service.mailbox_hci_evt_read = 0; + bt_service.mailbox_acl_rx_read = 0; + bt_service.mailbox_acl_free_read = 0; + bt_service.mailbox_acl_free_read_scan = 0; + bt_service.mailbox_iq_report_read = 0; + bt_service.read_index = 0; + bt_service.allocated_count = 0; + + /* Initialise the interrupt handlers */ + scsc_bt_shm_init_interrupt(); + + return 0; +} + +/* Terminate the shared memory interface, stopping its thread. + * + * Note: The service must be stopped prior to calling this function. + * The shared memory can only be released after calling this function. + */ +void scsc_bt_shm_exit(void) +{ + /* Release IRQs */ + if (bt_service.bsmhcp_protocol != NULL) { + scsc_service_mifintrbit_unregister_tohost(bt_service.service, bt_service.bsmhcp_protocol->header.bg_to_ap_int_src); + scsc_service_mifintrbit_unregister_tohost(bt_service.service, bt_service.bsmhcp_protocol->header.fg_to_ap_int_src); + + scsc_service_mifintrbit_free_fromhost(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, SCSC_MIFINTR_TARGET_R4); + scsc_service_mifintrbit_free_fromhost(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_fg_int_src, SCSC_MIFINTR_TARGET_R4); + scsc_service_mifintrbit_free_fromhost(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, SCSC_MIFINTR_TARGET_M4); + } + + /* Clear all control structures */ + bt_service.last_alloc = 0; + bt_service.hci_event_paused = false; + bt_service.acldata_paused = false; + bt_service.bsmhcp_protocol = NULL; + + memset(bt_service.allocated, 0, sizeof(bt_service.allocated)); + memset(bt_service.connection_handle_list, 0, sizeof(bt_service.connection_handle_list)); +} + diff --git a/drivers/misc/samsung/scsc_bt/scsc_shm.h b/drivers/misc/samsung/scsc_bt/scsc_shm.h new file mode 100644 index 000000000000..6b98e7d8fdb8 --- /dev/null +++ b/drivers/misc/samsung/scsc_bt/scsc_shm.h @@ -0,0 +1,37 @@ +/**************************************************************************** + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * + ****************************************************************************/ + +/* Shared memory interface API */ + +#ifndef __SCSC_SHM_H__ +#define __SCSC_SHM_H__ +#include +#include + +/* Bluetooth specific functions */ +int scsc_bt_shm_init(void); +void scsc_bt_shm_exit(void); +ssize_t scsc_bt_shm_h4_read(struct file *file, + char __user *buf, + size_t len, + loff_t *offset); +ssize_t scsc_bt_shm_h4_write(struct file *file, + const char __user *buf, + size_t len, loff_t *offset); +unsigned scsc_bt_shm_h4_poll(struct file *file, poll_table *wait); + +/* ANT specific functions */ +int scsc_ant_shm_init(void); +void scsc_ant_shm_exit(void); +ssize_t scsc_shm_ant_read(struct file *file, + char __user *buf, + size_t len, + loff_t *offset); +ssize_t scsc_shm_ant_write(struct file *file, + const char __user *buf, + size_t len, loff_t *offset); +unsigned int scsc_shm_ant_poll(struct file *file, poll_table *wait); +#endif diff --git a/drivers/net/wireless/scsc/Kconfig b/drivers/net/wireless/scsc/Kconfig new file mode 100755 index 000000000000..05e3b2099d3f --- /dev/null +++ b/drivers/net/wireless/scsc/Kconfig @@ -0,0 +1,264 @@ +config SCSC_WLAN + tristate "Driver for Samsung SCSC Maxwell Wifi" + depends on SCSC_CORE + depends on CFG80211 + +if SCSC_WLAN != n +config SCSC_WLAN_KEY_MGMT_OFFLOAD + bool "Enable roam offload for SCSC WLAN" + default y + ---help--- + This option enables roam offload for SCSC WLAN. + +config SCSC_WLAN_HIP4_PROFILING + bool "Enable HIP4 profiling" + default y + depends on SCSC_WLAN + ---help--- + This option enables HIP4 profiling + +config SCSC_WLAN_DEBUG + bool "Enable debug output from the SCSC Wifi driver" + depends on SCSC_WLAN + select SCSC_WLAN_HIP4_PROFILING + ---help--- + This option enables debug support for the SCSC wifi chipset. + +config SCSC_WLAN_SG + bool "Enable SCSC WLAN Scatter-gather and GSO support" + depends on SCSC_WLAN + default y + ---help--- + This option enables scatter-gather and GSO feature + +config SCSC_WLAN_SKB_TRACKING + bool "Enable debug memory tracking of skb data in the driver" + ---help--- + This option enables tracking of all skb data in the driver.. + This will affect the performance! + +config SCSC_WLAN_RX_NAPI + bool "Enable use of net device napi rx polling api" + default y + ---help--- + This option enables the drivers use of the napi api + +config SCSC_WLAN_RX_NAPI_GRO + bool "Enable use of net device napi rx GRO" + depends on SCSC_WLAN_RX_NAPI + default y + ---help--- + This option enables the drivers use of the napi Generic Receive Offload + +config SCSC_WLAN_ANDROID + bool "Android specific support" + default y + ---help--- + Enable support for Android specific aspects of the driver. + +config SCSC_WLAN_STA_ONLY + bool "Support WLAN STA only (no P2P/AP/IBSS)" + default n + ---help--- + Enable only basic STA mode for debugging purposes + +config SCSC_WLAN_KIC_OPS + bool "Wi-Fi service driver provides OPS for KIC to use for various tasks" + depends on SAMSUNG_KIC + default y + ---help--- + Provide Wi-Fi service driver OPS, which KIC can use for triggering Wi-Fi related events. + +config SCSC_WLAN_GSCAN_ENABLE + bool "Enable GSCAN in SCSC wlan driver" + default y + ---help--- + This option enables GSCAN in SCSC wlan driver. + +config SCSC_WLAN_WES_NCHO + bool "Enable NCHO/WES feature" + default y + ---help--- + This option enables the NCHO/WES feature + +config SCSC_WLAN_MUTEX_DEBUG + bool "Enable Mutex debugging mechanism in slsi wlan driver" + default y + ---help--- + This option enables the Mutex debug reports in slsi wlan driver. + +config CONFIG_SCSC_WLAN_BLOCK_IPV6 + bool "Block IPv6" + default n + ---help--- + This option blocks IPv6 packets. + +config CONFIG_SCSC_WLAN_DISABLE_NAT_KA + bool "Disable NAT" + default n + ---help--- + This option disables NAT. + +config SCSC_WLAN_HANG_TEST + bool "Test HANG Event" + default y + ---help--- + This option facilitates triggering of HANG Event + to reset WLAN. + +config SCSC_WLAN_NAT_KEEPALIVE_DISABLE + bool "Disable NAT KeepAlive" + default n + ---help--- + This option disables the NAT KeepAlive + Offload Feature. + +config SCSC_WLAN_ENHANCED_LOGGING + bool "Enable Enhanced Logging" + depends on SCSC_WIFILOGGER + default y + ---help--- + This option enables the Enhanced Logging + Feature. + +config SCSC_WLAN_ACM_API + bool "Provide TSF read API" + default n + ---help--- + This option provides API to read TSF. + +endif + +config SCSC_WLAN_MAC_ADDRESS_FILENAME + string "MAC address filename" + default "/efs/wifi/.mac.info" + ---help--- + Select the programmed MAC address file. + +config SCSC_WLAN_MAX_INTERFACES + int "Max number of virtual interfaces supported" + range 1 3 + default 3 + ---help--- + The driver structures are sized to support this + number of interfaces. + +config SCSC_WLAN_AP_INFO_FILE + bool "Create .softap.info file" + default n + ---help--- + The option enables the driver to create .softap.info + file in user space and fill information in it. + +config SCSC_WLAN_WIFI_SHARING + bool "Wifi Sharing Support" + default n + ---help--- + This option tells if wifi sharing is supported or not. + +config SCSC_WLAN_SAE_CONFIG + bool "Wpa3 Support" + default n + ---help--- + This option tells if wpa3 is supported or not. + +config SCSC_WLAN_ENABLE_MAC_RANDOMISATION + bool "Wifi Mac Randomization Support" + default n + ---help--- + This option tells if mac randomization is supported or not. + +config SCSC_WLAN_SINGLE_ANTENNA + depends on SCSC_WLAN_WIFI_SHARING + bool "Single Antenna Supported" + default y + ---help--- + This option tells if there is support for single + antenna or dual antenna. + +config SCSC_AP_INTERFACE_NAME + string "AP net device interface name" + default "wlan1" + ---help--- + AP net device interface name. + +config SCSC_WLAN_ACS_ENABLE + bool "ACS Support" + default y + ---help--- + This option tells if automatic channel selection is + supported or not. + +config SCSC_AP_INTERFACE_NAME + string "AP net device interface name" + default "wlan1" + ---help--- + AP net device interface name. + +config SCSC_WLAN_MHS_STATIC_INTERFACE + bool "Static AP Interface Support" + default n + ---help--- + This option tells if AP interface has been created + statically or dynamically. + is enabled or not. + +config SCSC_WLAN_PRIORITISE_IMP_FRAMES + bool "Change priority of important frames such as DNS, MDNS, TCP SYN" + default y + ---help--- + The option enables the driver to prioritise important frames + (such as DNS, MDNS, TCP SYN) so that they are sent ahead of others. + +config SCSC_WIFI_NAN_ENABLE + bool "Enable WiFi NAN" + default n + ---help--- + This option tells whether WiFi NAN is enabled or not. + +config SCSC_WLAN_ENHANCED_PKT_FILTER + bool "Enable enhanced packet filtering in suspend" + default n + ---help--- + This option tells if UDP packet filtering in suspend is + supported or not. + +config SCSC_WLAN_SET_NUM_ANTENNAS + bool "Enable configuring of number of antennas" + default n + ---help--- + This option tells whether configuring of number of antennas is enabled or not. + +config SCSC_ENHANCED_PACKET_STATS + bool "Enable enhanced packet stats" + default n + ---help--- + This option tells whether enhanced packet stats collection + is enabled or not. + +config SCSC_WLAN_LOW_LATENCY_MODE + bool "Support enabling/disabling roaming as per latency mode" + default n + ---help--- + This option tells whether support is provided for enabling + or disabling roaming as per the latency mode. + +config SCSC_WLAN_SET_PREFERRED_ANTENNA + bool "Enable the driver to set preferred antenna" + default y + ---help--- + This option enables the driver to set the preferred antenna. + +config SLSI_WLAN_STA_FWD_BEACON + bool "Forwarding beacon support" + default n + ---help--- + This option tells if beacon forwarding in STA mode is enabled or not. + When it is enabled, Driver deliver beacon info of connected AP to Supplicant. + +config SCSC_WLAN_STA_ENHANCED_ARP_DETECT + bool "Enable enhanced arp detection" + default n + ---help--- + This option tells whether enhanced arp detection + is enabled or not. diff --git a/drivers/net/wireless/scsc/Makefile b/drivers/net/wireless/scsc/Makefile new file mode 100755 index 000000000000..8abcb20895ae --- /dev/null +++ b/drivers/net/wireless/scsc/Makefile @@ -0,0 +1,127 @@ +# ---------------------------------------------------------------------------- +# FILE: Makefile +# +# PURPOSE: +# Build instructions for SCSC Wlan driver. +# +# Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved +# ---------------------------------------------------------------------------- + +# Needed since this subdir is symlinked in the main Kernel tree +# without this our samsung subdir is NOT cleaned. +clean-files := *.o *.ko + +obj-$(CONFIG_SCSC_WLAN) += scsc_wlan.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += hip4.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += mbulk.o + +# ---------------------------------------------------------------------------- +# HIP4 sampler +# ---------------------------------------------------------------------------- +ifeq ($(CONFIG_SCSC_WLAN_HIP4_PROFILING),y) +scsc_wlan-$(CONFIG_SCSC_WLAN) += hip4_sampler.o +endif + +scsc_wlan-$(CONFIG_SCSC_SMAPPER) += hip4_smapper.o + +# Upper driver +scsc_wlan-$(CONFIG_SCSC_WLAN) += dev.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += cfg80211_ops.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += netif.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += rx.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += tx.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += ba.o + +# SAPs +scsc_wlan-$(CONFIG_SCSC_WLAN) += sap_mlme.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += sap_ma.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += sap_dbg.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += sap_test.o + +# ---------------------------------------------------------------------------- +# Common Driver Files +# ---------------------------------------------------------------------------- +scsc_wlan-$(CONFIG_SCSC_WLAN) += mib.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += mib_text_convert.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += debug.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += debug_frame.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += procfs.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += mgt.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += mlme.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += udi.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += log_clients.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += src_sink.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += fw_test.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += cac.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += scsc_wifi_fcq.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += ioctl.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += wakelock.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += traffic_monitor.o + +# ---------------------------------------------------------------------------- +# ACM +# ---------------------------------------------------------------------------- +ifeq ($(CONFIG_SCSC_WLAN_ACM_API),y) +scsc_wlan-$(CONFIG_SCSC_WLAN) += acm_api.o +endif + +# ---------------------------------------------------------------------------- +# Building for Hardware +# ---------------------------------------------------------------------------- +scsc_wlan-$(CONFIG_SCSC_WLAN) += hip.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += cm_if.o + +ifeq ($(CONFIG_SCSC_WLAN_KIC_OPS),y) +scsc_wlan-$(CONFIG_SCSC_WLAN) += kic.o +endif +ccflags-$(CONFIG_SCSC_WLAN_KIC_OPS) += -DCONFIG_SCSC_WLAN_KIC_OPS + +ifeq ($(CONFIG_SCSC_WLAN_GSCAN_ENABLE),y) +scsc_wlan-$(CONFIG_SCSC_WLAN) += nl80211_vendor.o +ccflags-y += -DCONFIG_SCSC_WLAN_GSCAN_ENABLE +endif +ifeq ($(CONFIG_SCSC_WIFI_NAN_ENABLE),y) +scsc_wlan-$(CONFIG_SCSC_WLAN) += nl80211_vendor_nan.o +scsc_wlan-$(CONFIG_SCSC_WLAN) += mlme_nan.o +endif + +ifeq ($(CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD),y) +ccflags-y += -DCONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD +endif + +# --------------------------------------------------------------------------- +# Include +# --------------------------------------------------------------------------- +# TODO: This is only required because scsc_wifilogger is in the wrong driver directory +ccflags-y += -Idrivers/misc/samsung/scsc + +# ---------------------------------------------------------------------------- +# Wlan configuration +# ---------------------------------------------------------------------------- +ccflags-$(CONFIG_SCSC_WLAN_DEBUG) += -DCONFIG_SCSC_WLAN_DEBUG +ccflags-$(CONFIG_SCSC_WLAN_SKB_TRACKING) += -DCONFIG_SCSC_WLAN_SKB_TRACKING +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_TRACE +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_DATA_PLANE_PROFILE_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_DATA_PLANE_PROFILE_TRACE +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_TX_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_TX_TRACE +ccflags-$(CONFIG_SCSC_WLAN_HIP_PSCHED_AMSDU) += -DCONFIG_SCSC_WLAN_HIP_PSCHED_AMSDU +ccflags-$(CONFIG_SCSC_WLAN_RX_NAPI) += -DCONFIG_SCSC_WLAN_RX_NAPI +ccflags-$(CONFIG_SCSC_WLAN_RX_NAPI_GRO) += -DCONFIG_SCSC_WLAN_RX_NAPI_GRO +ccflags-$(CONFIG_SCSC_WLAN_HIP_SUPPORT_SCATTER_GATHER_API) += -DCONFIG_SCSC_WLAN_HIP_SUPPORT_SCATTER_GATHER_API +ccflags-$(CONFIG_SCSC_WLAN_WES_NCHO) += -DCONFIG_SCSC_WLAN_WES_NCHO +ccflags-$(CONFIG_SCSC_WLAN_MUTEX_DEBUG) += -DCONFIG_SCSC_WLAN_MUTEX_DEBUG +ccflags-$(CONFIG_SCSC_WLAN_BLOCK_IPV6) += -DCONFIG_SCSC_WLAN_BLOCK_IPV6 +ccflags-$(CONFIG_SCSC_WLAN_DISABLE_NAT_KA) += -DCONFIG_SCSC_WLAN_DISABLE_NAT_KA + +ccflags-y += $(CONFIG_SAMSUNG_MAXWELL_EXTRA) +ccflags-y += -DCONFIG_SCSC_WLAN_MAX_INTERFACES=$(CONFIG_SCSC_WLAN_MAX_INTERFACES) + +# Android specific build options +ccflags-$(CONFIG_SCSC_WLAN_ANDROID) += -DCONFIG_SCSC_WLAN_ANDROID + +## See drivers/misc/samsung/scsc/ Makefile for an explanation of the reasons +## of the following ifeq/else +ifeq ($(CONFIG_SCSC_LOGRING), m) +ccflags-y += -DCONFIG_SCSC_PRINTK +else +ccflags-$(CONFIG_SCSC_LOGRING) += -DCONFIG_SCSC_PRINTK +endif diff --git a/drivers/net/wireless/scsc/acm_api.c b/drivers/net/wireless/scsc/acm_api.c new file mode 100755 index 000000000000..d64ee26ac611 --- /dev/null +++ b/drivers/net/wireless/scsc/acm_api.c @@ -0,0 +1,74 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include "mlme.h" +#include "mib.h" +#include "debug.h" +/* PSID Hdr(4) + VLDATA Hdr(1) + TSF(8) * + padding(1)*/ +#define TSF_RESP_SIZE (14) + +unsigned long long SDA_getTsf(const unsigned char if_id) +{ + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif = NULL; + struct slsi_dev *sdev = NULL; + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + /* Alloc buffer in stack to avoid possible scheduling when malloc.*/ + u8 res_payload[TSF_RESP_SIZE] = { 0 }; + int r = 0; + int rx_length = 0; + unsigned long long ret = 0; + + read_lock(&dev_base_lock); + dev = first_net_device(&init_net); + while (dev) { + if (if_id == 0 && memcmp(dev->name, "wlan0", 5) == 0) + break; + else if (if_id == 1 && memcmp(dev->name, "p2p0", 4) == 0) + break; + dev = next_net_device(dev); + } + read_unlock(&dev_base_lock); + + if (!dev) { + SLSI_ERR(sdev, "dev is null\n"); + return 0; + } + ndev_vif = netdev_priv(dev); + if (!ndev_vif) { + SLSI_ERR(sdev, "ndev_vif is null\n"); + return 0; + } + sdev = ndev_vif->sdev; + if (!sdev) { + SLSI_ERR(sdev, "sdev is null\n"); + return 0; + } + + SLSI_DBG3(sdev, SLSI_MLME, "\n"); + slsi_mib_encode_get(&mibreq, SLSI_PSID_UNIFI_CURRENT_TSF_TIME, 0); + mibrsp.dataLength = sizeof(res_payload); + mibrsp.data = res_payload; + + r = slsi_mlme_get(sdev, dev, mibreq.data, mibreq.dataLength, mibrsp.data, mibrsp.dataLength, &rx_length); + + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_length; + if (rx_length == 0) { + SLSI_ERR(sdev, "Mib decode error\n"); + return 0; + } + slsi_mib_decodeInt64(&mibrsp.data[4], &ret); + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + return 0; + } + return ret; +} diff --git a/drivers/net/wireless/scsc/ba.c b/drivers/net/wireless/scsc/ba.c new file mode 100755 index 000000000000..3f95fa1833e5 --- /dev/null +++ b/drivers/net/wireless/scsc/ba.c @@ -0,0 +1,616 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "debug.h" +#include "dev.h" +#include "ba.h" +#include "mgt.h" + +/* Age value for frames in MPDU reorder buffer */ +static int ba_mpdu_reorder_age_timeout = 150; /* 150 milli seconds */ +module_param(ba_mpdu_reorder_age_timeout, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ba_mpdu_reorder_age_timeout, "Timeout (in ms) before a BA frame in Reorder buffer is passed to upper layers"); + +#define BA_WINDOW_BOUNDARY 2048 + +#define SN_TO_INDEX(__ba_session_rx, __sn) (((__sn - __ba_session_rx->start_sn) & 0xFFF) % __ba_session_rx->buffer_size) + +#define ADVANCE_EXPECTED_SN(__ba_session_rx) \ + { \ + __ba_session_rx->expected_sn++; \ + __ba_session_rx->expected_sn &= 0xFFF; \ + } + +#define FREE_BUFFER_SLOT(__ba_session_rx, __index) \ + { \ + __ba_session_rx->occupied_slots--; \ + __ba_session_rx->buffer[__index].active = false; \ + } + +#define IS_SN_LESS(sn1, sn2) ((((sn1) - (sn2)) & 0xFFF) > BA_WINDOW_BOUNDARY) + +void slsi_rx_ba_init(struct slsi_dev *sdev) +{ + int i; + + for (i = 0; i < SLSI_MAX_RX_BA_SESSIONS; i++) + slsi_spinlock_create(&sdev->rx_ba_buffer_pool[i].ba_lock); + + slsi_spinlock_create(&sdev->rx_ba_buffer_pool_lock); +} + +static struct slsi_ba_session_rx *slsi_rx_ba_alloc_buffer(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct slsi_ba_session_rx *buffer = NULL; + int i; + + SLSI_NET_DBG3(dev, SLSI_RX_BA, "RX BA buffer pool status: %d,%d,%d,%d,%d,%d,%d,%d\n", + sdev->rx_ba_buffer_pool[0].used, sdev->rx_ba_buffer_pool[1].used, sdev->rx_ba_buffer_pool[2].used, + sdev->rx_ba_buffer_pool[3].used, sdev->rx_ba_buffer_pool[4].used, sdev->rx_ba_buffer_pool[5].used, + sdev->rx_ba_buffer_pool[6].used, sdev->rx_ba_buffer_pool[7].used); + + slsi_spinlock_lock(&sdev->rx_ba_buffer_pool_lock); + for (i = 0; i < SLSI_MAX_RX_BA_SESSIONS; i++) { + if (!sdev->rx_ba_buffer_pool[i].used) { + sdev->rx_ba_buffer_pool[i].used = true; + buffer = &sdev->rx_ba_buffer_pool[i]; + break; + } + } + slsi_spinlock_unlock(&sdev->rx_ba_buffer_pool_lock); + + if (!buffer) + SLSI_NET_ERR(dev, "No free RX BA buffer\n"); + + return buffer; +} + +static void slsi_rx_ba_free_buffer(struct net_device *dev, struct slsi_peer *peer, int tid) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + + slsi_spinlock_lock(&sdev->rx_ba_buffer_pool_lock); + if (peer && peer->ba_session_rx[tid]) { + peer->ba_session_rx[tid]->used = false; + peer->ba_session_rx[tid] = NULL; + } + slsi_spinlock_unlock(&sdev->rx_ba_buffer_pool_lock); + + SLSI_NET_DBG3(dev, SLSI_RX_BA, "RX BA buffer pool status: %d,%d,%d,%d,%d,%d,%d,%d\n", + sdev->rx_ba_buffer_pool[0].used, sdev->rx_ba_buffer_pool[1].used, sdev->rx_ba_buffer_pool[2].used, + sdev->rx_ba_buffer_pool[3].used, sdev->rx_ba_buffer_pool[4].used, sdev->rx_ba_buffer_pool[5].used, + sdev->rx_ba_buffer_pool[6].used, sdev->rx_ba_buffer_pool[7].used); +} + +/* This code - slsi_ba_process_complete() + * is called in the data workqueue context with the + * netdev_vif mutex held. + */ +void slsi_ba_process_complete(struct net_device *dev, bool from_ba_timer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *skb; + + while ((skb = slsi_skb_dequeue(&ndev_vif->ba_complete)) != NULL) + slsi_rx_data_deliver_skb(ndev_vif->sdev, dev, skb, from_ba_timer); +} + +static void slsi_ba_signal_process_complete(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + struct slsi_dev *sdev = ndev_vif->sdev; + u32 conf_hip4_ver = 0; +#endif + + atomic_set(&ndev_vif->ba_flush, 1); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + if (conf_hip4_ver == 5) + slsi_skb_schedule_work(&ndev_vif->rx_data); +#else + slsi_skb_schedule_work(&ndev_vif->rx_data); +#endif +} + +static void ba_add_frame_to_ba_complete(struct net_device *dev, struct slsi_ba_frame_desc *frame_desc) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + slsi_skb_queue_tail(&ndev_vif->ba_complete, frame_desc->signal); +} + +static void ba_update_expected_sn(struct net_device *dev, + struct slsi_ba_session_rx *ba_session_rx, u16 sn) +{ + u32 i, j; + u16 gap; + + gap = (sn - ba_session_rx->expected_sn) & 0xFFF; + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Proccess the frames up to new expected_sn = %d gap = %d\n", sn, gap); + + for (j = 0; j < gap && j < ba_session_rx->buffer_size; j++) { + i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn); + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Proccess the slot index = %d\n", i); + if (ba_session_rx->buffer[i].active) { + ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]); + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Proccess the frame at index = %d expected_sn = %d\n", i, ba_session_rx->expected_sn); + FREE_BUFFER_SLOT(ba_session_rx, i); + } else { + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Empty slot at index = %d\n", i); + } + ADVANCE_EXPECTED_SN(ba_session_rx); + } + ba_session_rx->expected_sn = sn; +} + +static void ba_complete_ready_sequence(struct net_device *dev, + struct slsi_ba_session_rx *ba_session_rx) +{ + int i; + + i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn); + while (ba_session_rx->buffer[i].active) { + ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]); + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Completed stored frame (expected_sn=%d) at i = %d\n", + ba_session_rx->expected_sn, i); + FREE_BUFFER_SLOT(ba_session_rx, i); + ADVANCE_EXPECTED_SN(ba_session_rx); + i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn); + } +} + +static void ba_scroll_window(struct net_device *dev, + struct slsi_ba_session_rx *ba_session_rx, u16 sn) +{ + if (((sn - ba_session_rx->expected_sn) & 0xFFF) <= BA_WINDOW_BOUNDARY) { + ba_update_expected_sn(dev, ba_session_rx, sn); + ba_complete_ready_sequence(dev, ba_session_rx); + } +} + +static int ba_consume_frame_or_get_buffer_index(struct net_device *dev, struct slsi_peer *peer, + struct slsi_ba_session_rx *ba_session_rx, u16 sn, struct slsi_ba_frame_desc *frame_desc, bool *stop_timer) +{ + int i; + u16 sn_temp; +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + struct netdev_vif *ndev_vif = netdev_priv(dev); +#endif + + *stop_timer = false; + + if (((sn - ba_session_rx->expected_sn) & 0xFFF) <= BA_WINDOW_BOUNDARY) { + /* Once we are in BA window, set the flag for BA trigger */ + if (!ba_session_rx->trigger_ba_after_ssn) + ba_session_rx->trigger_ba_after_ssn = true; + + sn_temp = ba_session_rx->expected_sn + ba_session_rx->buffer_size; + SLSI_NET_DBG4(dev, SLSI_RX_BA, "New frame: sn=%d\n", sn); + + if (!(((sn - sn_temp) & 0xFFF) > BA_WINDOW_BOUNDARY)) { + u16 new_expected_sn; + + SLSI_NET_DBG2(dev, SLSI_RX_BA, "Frame is out of window\n"); + sn_temp = (sn - ba_session_rx->buffer_size) & 0xFFF; + if (ba_session_rx->timer_on) + *stop_timer = true; + new_expected_sn = (sn_temp + 1) & 0xFFF; + ba_update_expected_sn(dev, ba_session_rx, new_expected_sn); + } + + i = -1; + if (sn == ba_session_rx->expected_sn) { + SLSI_NET_DBG4(dev, SLSI_RX_BA, "sn = ba_session_rx->expected_sn = %d\n", sn); + if (ba_session_rx->timer_on) + *stop_timer = true; + ADVANCE_EXPECTED_SN(ba_session_rx); + ba_add_frame_to_ba_complete(dev, frame_desc); + } else { + i = SN_TO_INDEX(ba_session_rx, sn); + SLSI_NET_DBG4(dev, SLSI_RX_BA, "sn (%d) != ba_session_rx->expected_sn(%d), i = %d\n", sn, ba_session_rx->expected_sn, i); + if (ba_session_rx->buffer[i].active) { + SLSI_NET_DBG3(dev, SLSI_RX_BA, "free frame at i = %d\n", i); + i = -1; + slsi_kfree_skb(frame_desc->signal); + } + } + if (!IS_SN_LESS(sn, ba_session_rx->highest_received_sn)) + ba_session_rx->highest_received_sn = sn; + } else { + i = -1; + if (!ba_session_rx->trigger_ba_after_ssn) { + SLSI_NET_DBG3(dev, SLSI_RX_BA, "frame before ssn, pass it up: sn=%d\n", sn); + ba_add_frame_to_ba_complete(dev, frame_desc); + } else { + if (slsi_is_tdls_peer(dev, peer)) { + /* Don't drop old frames in TDLS AMPDU-reordering for interoperability with third party devices. + * When the TDLS link is established the peer sends few packets with AP's sequence number. + * BA reorder logic updates the expected sequence number. After that peer sends packets with + * starting sequence number negotiated in BA (0). But those frames are getting dropped here. + * Because of this TCP traffic does not work and TDLS link is getting disconnected. + */ + SLSI_NET_DBG1(dev, SLSI_RX_BA, "tdls: forward old frame: sn=%d, expected_sn=%d\n", sn, ba_session_rx->expected_sn); + ba_add_frame_to_ba_complete(dev, frame_desc); + } else { + /* this frame is deemed as old. But it may so happen that the reorder process did not wait long + * enough for this frame and moved to new window. So check here that the current frame still lies in + * originators transmit window by comparing it with highest sequence number received from originator. + * + * If it lies in the window pass the frame to next process else discard the frame here. + */ + if (IS_SN_LESS(ba_session_rx->highest_received_sn, (((sn + ba_session_rx->buffer_size) & 0xFFF) - 1))) { + SLSI_NET_DBG4(dev, SLSI_RX_BA, "old frame, but still in window: sn=%d, highest_received_sn=%d\n", sn, ba_session_rx->highest_received_sn); + ba_add_frame_to_ba_complete(dev, frame_desc); + } else { + SLSI_NET_DBG1(dev, SLSI_RX_BA, "old frame, drop: sn=%d, expected_sn=%d\n", sn, ba_session_rx->expected_sn); +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (ndev_vif->enhanced_arp_detect_enabled) + slsi_fill_enhanced_arp_out_of_order_drop_counter(ndev_vif, + frame_desc->signal); +#endif + slsi_kfree_skb(frame_desc->signal); + } + } + } + } + return i; +} + +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE +static void slsi_ba_aging_timeout_handler(struct timer_list *t) +#else +static void slsi_ba_aging_timeout_handler(unsigned long data) +#endif +{ +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + struct slsi_ba_session_rx *ba_session_rx = from_timer(ba_session_rx, t, ba_age_timer); +#else + struct slsi_ba_session_rx *ba_session_rx = (struct slsi_ba_session_rx *)data; +#endif + u8 i, j; + u8 gap = 1; + u16 temp_sn; + struct net_device *dev = ba_session_rx->dev; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + u32 conf_hip4_ver = 0; +#endif + + SLSI_NET_DBG3(dev, SLSI_RX_BA, "\n"); + + slsi_spinlock_lock(&ba_session_rx->ba_lock); + + ba_session_rx->timer_on = false; + + if (ba_session_rx->active && ba_session_rx->occupied_slots) { + /* expected sequence has not arrived so start searching from next + * sequence number until a frame is available and determine the gap. + * Release all the frames upto next hole from the reorder buffer. + */ + temp_sn = (ba_session_rx->expected_sn + 1) & 0xFFF; + + for (j = 0; j < ba_session_rx->buffer_size; j++) { + i = SN_TO_INDEX(ba_session_rx, temp_sn); + + if (ba_session_rx->buffer[i].active) { + while (gap--) + ADVANCE_EXPECTED_SN(ba_session_rx); + + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Completed stored frame (expected_sn=%d) at i = %d\n", ba_session_rx->expected_sn, i); + ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]); + FREE_BUFFER_SLOT(ba_session_rx, i); + ADVANCE_EXPECTED_SN(ba_session_rx); + ba_complete_ready_sequence(dev, ba_session_rx); + break; + } + /* advance temp sequence number and frame gap */ + temp_sn = (temp_sn + 1) & 0xFFF; + gap++; + } + + /* Check for next hole in the buffer, if hole exists create the timer for next missing frame */ + if (ba_session_rx->occupied_slots) { + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Timer start\n"); + mod_timer(&ba_session_rx->ba_age_timer, jiffies + msecs_to_jiffies(ba_mpdu_reorder_age_timeout)); + ba_session_rx->timer_on = true; + } + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + /* Process the data now marked as completed */ +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + if (conf_hip4_ver == 4) + slsi_ba_process_complete(dev, true); + else + slsi_ba_signal_process_complete(dev); +#else + slsi_ba_signal_process_complete(dev); +#endif + } else { + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + } +} + +int slsi_ba_process_frame(struct net_device *dev, struct slsi_peer *peer, + struct sk_buff *skb, u16 sequence_number, u16 tid) +{ + int i; + struct slsi_ba_session_rx *ba_session_rx = peer->ba_session_rx[tid]; + struct slsi_ba_frame_desc frame_desc; + bool stop_timer = false; + + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Got frame(sn=%d)\n", sequence_number); + + if (WARN_ON(tid > FAPI_PRIORITY_QOS_UP7)) { + SLSI_NET_ERR(dev, "tid=%d\n", tid); + return -EINVAL; + } + + if (!ba_session_rx) + return -EINVAL; + + slsi_spinlock_lock(&ba_session_rx->ba_lock); + + if (!ba_session_rx->active) { + SLSI_NET_ERR(dev, "No BA session exists\n"); + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + return -EINVAL; + } + + frame_desc.signal = skb; + frame_desc.sn = sequence_number; + frame_desc.active = true; + + i = ba_consume_frame_or_get_buffer_index(dev, peer, ba_session_rx, sequence_number, &frame_desc, &stop_timer); + if (i >= 0) { + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Store frame(sn=%d) at i = %d\n", sequence_number, i); + ba_session_rx->buffer[i] = frame_desc; + ba_session_rx->occupied_slots++; + } else { + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Frame consumed - sn = %d\n", sequence_number); + } + + ba_complete_ready_sequence(dev, ba_session_rx); + + /* Timer decision: + * + * If the timer is not running (timer_on=false) + * Start the timer if there are holes (occupied_slots!=0) + * + * If the timer is running (timer_on=true) + * Stop the timer if + * There are no holes (occupied_slots=0) + * Restart the timer if + * stop_timer=true and there are holes (occupied_slots!=0) + * Leave the timer running (do nothing) if + * stop_timer=false and there are holes (occupied_slots!=0) + */ + + if (!ba_session_rx->timer_on) { + if (ba_session_rx->occupied_slots) { + stop_timer = false; + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Timer start\n"); + mod_timer(&ba_session_rx->ba_age_timer, jiffies + msecs_to_jiffies(ba_mpdu_reorder_age_timeout)); + ba_session_rx->timer_on = true; + } + } else if (!ba_session_rx->occupied_slots) { + stop_timer = true; + } else if (stop_timer) { + stop_timer = false; + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Timer restart\n"); + mod_timer(&ba_session_rx->ba_age_timer, jiffies + msecs_to_jiffies(ba_mpdu_reorder_age_timeout)); + ba_session_rx->timer_on = true; + } + + if (stop_timer) { + ba_session_rx->timer_on = false; + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + SLSI_NET_DBG4(dev, SLSI_RX_BA, "Timer stop\n"); + del_timer_sync(&ba_session_rx->ba_age_timer); + } else { + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + } + slsi_ba_signal_process_complete(dev); + return 0; +} + +bool slsi_ba_check(struct slsi_peer *peer, u16 tid) +{ + if (tid > FAPI_PRIORITY_QOS_UP7) + return false; + if (!peer->ba_session_rx[tid]) + return false; + + return peer->ba_session_rx[tid]->active; +} + +static void __slsi_rx_ba_stop(struct net_device *dev, struct slsi_ba_session_rx *ba_session_rx) +{ + u8 i, j; + + SLSI_NET_DBG1(dev, SLSI_RX_BA, "Stopping BA session: tid = %d\n", ba_session_rx->tid); + + if (WARN_ON(!ba_session_rx->active)) { + SLSI_NET_ERR(dev, "No BA session exists\n"); + return; + } + + for (i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn), j = 0; + j < ba_session_rx->buffer_size; i++, j++) { + i %= ba_session_rx->buffer_size; + if (ba_session_rx->buffer[i].active) { + ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]); + SLSI_NET_DBG3(dev, SLSI_RX_BA, "Completed stored frame at i = %d\n", i); + FREE_BUFFER_SLOT(ba_session_rx, i); + } + } + ba_session_rx->active = false; +} + +static void slsi_rx_ba_stop_lock_held(struct net_device *dev, struct slsi_ba_session_rx *ba_session_rx) +{ + __slsi_rx_ba_stop(dev, ba_session_rx); + if (ba_session_rx->timer_on) { + ba_session_rx->timer_on = false; + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + del_timer_sync(&ba_session_rx->ba_age_timer); + slsi_spinlock_lock(&ba_session_rx->ba_lock); + } +} + +static void slsi_rx_ba_stop_lock_unheld(struct net_device *dev, struct slsi_ba_session_rx *ba_session_rx) +{ + slsi_spinlock_lock(&ba_session_rx->ba_lock); + __slsi_rx_ba_stop(dev, ba_session_rx); + if (ba_session_rx->timer_on) { + ba_session_rx->timer_on = false; + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + del_timer_sync(&ba_session_rx->ba_age_timer); + } else { + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + } +} + +void slsi_rx_ba_stop_all(struct net_device *dev, struct slsi_peer *peer) +{ + int i; + + for (i = 0; i < NUM_BA_SESSIONS_PER_PEER; i++) + if (peer->ba_session_rx[i] && peer->ba_session_rx[i]->active) { + slsi_rx_ba_stop_lock_unheld(dev, peer->ba_session_rx[i]); + slsi_rx_ba_free_buffer(dev, peer, i); + } +} + +static int slsi_rx_ba_start(struct net_device *dev, + struct slsi_ba_session_rx *ba_session_rx, + u16 tid, u16 buffer_size, u16 start_sn) +{ + SLSI_NET_DBG1(dev, SLSI_RX_BA, "Request to start a new BA session tid=%d buffer_size=%d start_sn=%d\n", + tid, buffer_size, start_sn); + + if (WARN_ON((!buffer_size) || (buffer_size > SLSI_BA_BUFFER_SIZE_MAX))) { + SLSI_NET_ERR(dev, "Invalid window size: buffer_size=%d\n", buffer_size); + return -EINVAL; + } + + slsi_spinlock_lock(&ba_session_rx->ba_lock); + + if (ba_session_rx->active) { + SLSI_NET_DBG1(dev, SLSI_RX_BA, "BA session already exists\n"); + + if ((ba_session_rx->buffer_size == buffer_size) && + (ba_session_rx->expected_sn == start_sn)) { + SLSI_NET_DBG1(dev, SLSI_RX_BA, + "BA session tid=%d already exists. The parameters match so keep the existing session\n", + tid); + + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + + return 0; + } + SLSI_NET_DBG1(dev, SLSI_RX_BA, "Parameters don't match so stop the existing BA session: tid=%d\n", tid); + slsi_rx_ba_stop_lock_held(dev, ba_session_rx); + } + + ba_session_rx->dev = dev; + ba_session_rx->buffer_size = buffer_size; + ba_session_rx->start_sn = start_sn; + ba_session_rx->expected_sn = start_sn; + ba_session_rx->highest_received_sn = 0; + ba_session_rx->trigger_ba_after_ssn = false; + ba_session_rx->tid = tid; + ba_session_rx->timer_on = false; +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + timer_setup(&ba_session_rx->ba_age_timer, slsi_ba_aging_timeout_handler, 0); +#else + ba_session_rx->ba_age_timer.function = slsi_ba_aging_timeout_handler; + ba_session_rx->ba_age_timer.data = (unsigned long)ba_session_rx; + init_timer(&ba_session_rx->ba_age_timer); +#endif + + ba_session_rx->active = true; + SLSI_NET_DBG1(dev, SLSI_RX_BA, "Started a new BA session tid=%d buffer_size=%d start_sn=%d\n", + tid, buffer_size, start_sn); + + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + slsi_ba_signal_process_complete(dev); + + return 0; +} + +static void slsi_ba_process_error(struct net_device *dev, + struct slsi_ba_session_rx *ba_session_rx, u16 sequence_number) +{ + slsi_spinlock_lock(&ba_session_rx->ba_lock); + + if (WARN_ON(!ba_session_rx->active)) { + SLSI_NET_ERR(dev, "No BA session exists\n"); + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + return; + } + + ba_scroll_window(dev, ba_session_rx, sequence_number); + + slsi_spinlock_unlock(&ba_session_rx->ba_lock); + slsi_ba_signal_process_complete(dev); +} + +void slsi_handle_blockack(struct net_device *dev, struct slsi_peer *peer, + u16 vif, u8 *peer_qsta_address, u16 parameter_set, u16 sequence_number, + u16 reason_code, u16 direction) +{ + struct slsi_ba_session_rx *ba_session_rx; + u16 user_priority = (parameter_set >> 2) & 0x000F; + u16 buffer_size = (parameter_set >> 6) & 0x03FF; + + SLSI_UNUSED_PARAMETER(vif); + SLSI_UNUSED_PARAMETER(peer_qsta_address); + + if (WARN_ON(user_priority > FAPI_PRIORITY_QOS_UP7)) { + SLSI_NET_ERR(dev, "Invalid user_priority=%d\n", user_priority); + return; + } + + switch (direction) { + case FAPI_DIRECTION_TRANSMIT: + break; + case FAPI_DIRECTION_RECEIVE: + ba_session_rx = peer->ba_session_rx[user_priority]; + + switch (reason_code) { + case FAPI_REASONCODE_START: + if (!peer->ba_session_rx[user_priority]) + peer->ba_session_rx[user_priority] = slsi_rx_ba_alloc_buffer(dev); + + if (peer->ba_session_rx[user_priority]) + if (slsi_rx_ba_start(dev, peer->ba_session_rx[user_priority], user_priority, buffer_size, sequence_number) != 0) + slsi_rx_ba_free_buffer(dev, peer, user_priority); + break; + case FAPI_REASONCODE_END: + if (ba_session_rx) { + slsi_rx_ba_stop_lock_unheld(dev, ba_session_rx); + slsi_rx_ba_free_buffer(dev, peer, user_priority); + } + break; + case FAPI_REASONCODE_UNSPECIFIED_REASON: + if (ba_session_rx) + slsi_ba_process_error(dev, ba_session_rx, sequence_number); + break; + default: + SLSI_NET_ERR(dev, "Invalid value: reason_code=%d\n", reason_code); + break; + } + break; + default: + SLSI_NET_ERR(dev, "Invalid value: direction=%d\n", direction); + break; + } +} diff --git a/drivers/net/wireless/scsc/ba.h b/drivers/net/wireless/scsc/ba.h new file mode 100755 index 000000000000..af442233ad7a --- /dev/null +++ b/drivers/net/wireless/scsc/ba.h @@ -0,0 +1,26 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_BA_H__ +#define __SLSI_BA_H__ + +#include "dev.h" + +void slsi_handle_blockack(struct net_device *dev, struct slsi_peer *peer, + u16 vif, u8 *peer_qsta_address, u16 parameter_set, u16 sequence_number, + u16 reason_code, u16 direction); + +int slsi_ba_process_frame(struct net_device *dev, struct slsi_peer *peer, + struct sk_buff *skb, u16 sequence_number, u16 tid); + +void slsi_ba_process_complete(struct net_device *dev, bool from_ba_timer); + +bool slsi_ba_check(struct slsi_peer *peer, u16 tid); + +void slsi_rx_ba_stop_all(struct net_device *dev, struct slsi_peer *peer); + +void slsi_rx_ba_init(struct slsi_dev *sdev); +#endif diff --git a/drivers/net/wireless/scsc/cac.c b/drivers/net/wireless/scsc/cac.c new file mode 100755 index 000000000000..70abc0471978 --- /dev/null +++ b/drivers/net/wireless/scsc/cac.c @@ -0,0 +1,1487 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "cac.h" + +static struct cac_tspec *tspec_list; +static int tspec_list_next_id; +static u8 dialog_token_next; + +/* Define the meta-data info for tspec_fields */ +static struct tspec_field tspec_fields[] = { + { "traffic_type", 0, 1, 0x1, 0 }, + { "tsid", 0, 1, 0xF, 1 }, + { "direction", 0, 1, 0x3, 5 }, + { "access_policy", 0, 1, 0x3, 7 }, /* WMM - always set to 1 */ + { "aggregation", 0, 1, 0x1, 9 }, /* WMM - not supported */ + { "psb", 0, 1, 0x1, 10 }, + { "user_priority", 0, 1, 0x7, 11 }, + { "tsinfo_ack_policy", 0, 1, 0x3, 14 }, /* WMM - not supported */ + { "schedule", 0, 1, 0x1, 16 }, /* WMM - not supported */ + { "nominal_msdu_size", 0, 0, 2, OFFSETOF(nominal_msdu_size) }, + { "max_msdu_size", 0, 0, 2, OFFSETOF(maximum_msdu_size) }, + { "min_service_interval", 0, 0, 4, OFFSETOF(minimum_service_interval) }, + { "max_service_interval", 0, 0, 4, OFFSETOF(maximum_service_interval) }, + { "inactivity_interval", 0, 0, 4, OFFSETOF(inactivity_interval) }, + { "suspension_interval", 0, 0, 4, OFFSETOF(suspension_interval) }, + { "service_start_time", 0, 0, 4, OFFSETOF(service_start_time) }, + { "min_data_rate", 0, 0, 4, OFFSETOF(minimum_data_rate) }, + { "mean_data_rate", 0, 0, 4, OFFSETOF(mean_data_rate) }, + { "peak_data_rate", 0, 0, 4, OFFSETOF(peak_data_rate) }, + { "max_burst_size", 0, 0, 4, OFFSETOF(maximum_burst_size) }, + { "delay_bound", 0, 0, 4, OFFSETOF(delay_bound) }, + { "min_phy_rate", 0, 0, 4, OFFSETOF(minimum_phy_rate) }, + { "surplus_bw_allowance", 0, 0, 2, + OFFSETOF(surplus_bandwidth_allowance) }, + { "medium_time", 0, 0, 2, OFFSETOF(medium_time) }, +}; + +/* Define the OUI type data for the corresponding IE's */ +static const u8 TSRS_OUI_TYPE[] = { 0x00, 0x40, 0x96, 0x08 }; +static const u8 EBW_OUI_TYPE[] = { 0x00, 0x40, 0x96, 0x0F }; + +static const int NUM_TSPEC_FIELDS = sizeof(tspec_fields) / sizeof(struct tspec_field); +static u32 previous_msdu_lifetime = MAX_TRANSMIT_MSDU_LIFETIME_NOT_VALID; +static u8 ccx_status = BSS_CCX_DISABLED; + +static void cac_set_ric_ie(struct slsi_dev *sdev, struct net_device *netdev); +static int cac_get_rde_tspec_ie(struct slsi_dev *sdev, u8 *assoc_rsp_ie, int assoc_rsp_ie_len, const u8 **tspec_ie_arr); +/* Name: strtoint + * Desc: Converts a string to a decimal or hexadecimal integer + * s: the string to be converted + * res: pointer to the calculated integer + * return: 0 (success), 1(failure) + */ +static int strtoint(const char *s, int *res) +{ + int base = 10; + + if (strlen(s) > 2) + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + base = 16; + return kstrtoint(s, base, res); +} + +/* Name: find_tspec_entry + * Desc: Finds a tspec entry in the list of tspecs (tspec_list) + * according to tspec id and status (accepted or not accepted) + * id: the tspec id + * accepted: 1 : accepted by AP, 0: new or rejected by AP + * return: pointer to the tspec struct or NULL if a tspec doesn't exist + */ +static struct cac_tspec *find_tspec_entry(int id, int accepted) +{ + struct cac_tspec *itr; + + itr = tspec_list; + while (itr != NULL) { + if ((itr->id == id) && (itr->accepted == accepted)) + break; + itr = itr->next; + } + return itr; +} + +/* Name: cac_query_tspec_field + * Desc: Get the value of a tspec's field. + * sdev: pointer to the slsi_dev struct + * entry: pointer to the tspec + * field: the field name + * value: poinet to the field value + * return: 0 (success), -1 (failure) + */ +static int cac_query_tspec_field(struct slsi_dev *sdev, struct cac_tspec *entry, const char *field, u32 *value) +{ + int i; + u32 tsinfo; + u8 mask; + u8 *pos; + + if ((entry == NULL) || (field == NULL) || (value == NULL)) + return -1; + + for (i = 0; i < NUM_TSPEC_FIELDS; i++) + if (strcasecmp(field, tspec_fields[i].name) == 0) + break; + if (i >= NUM_TSPEC_FIELDS) { + SLSI_ERR(sdev, "CAC: Invalid TSPEC config field\n"); + return -1; + } + if (tspec_fields[i].is_tsinfo_field) { + mask = tspec_fields[i].size; + tsinfo = CAC_GET_LE24(&entry->tspec.ts_info[0]) & TSINFO_MASK; + *value = (tsinfo >> tspec_fields[i].offset) & mask; + } else { + pos = (u8 *)(&entry->tspec) + tspec_fields[i].offset; + if (tspec_fields[i].size == 1) + *value = (*pos & 0xFF); + else if (tspec_fields[i].size == 2) + *value = CAC_GET_LE16(pos); + else + *value = CAC_GET_LE32(pos); + } + + return 0; +} + +/* Name: get_netdev_for_station + * Desc: Get the pointer to net_device struct with vif_type == FAPI_VIFTYPE_STATION + * sdev: pointer to the slsi_dev struct + * return: pointer to the net_device struct or NULL if the it doesn't exist + */ +static struct net_device *get_netdev_for_station(struct slsi_dev *sdev) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + s32 vif; + + for (vif = 1; vif <= CONFIG_SCSC_WLAN_MAX_INTERFACES; vif++) { + dev = slsi_get_netdev_locked(sdev, vif); + if (!dev) + continue; + ndev_vif = netdev_priv(dev); + if (!ndev_vif) + continue; + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION && + ndev_vif->iftype == NL80211_IFTYPE_STATION) + return dev; + } + return NULL; +} + +/* Name: add_ebw_ie + * Desc: Add ebw ie + * buf: pointer to buf that the ie is going to added + * buf_len: the byte length of the ie + * tsid: tspec id + * return: length of bytes that were added + */ +static int add_ebw_ie(u8 *buf, size_t buf_len, u8 tsid) +{ + u8 *pos; + + if ((buf == NULL) || (buf_len < 8)) + return -1; + + pos = buf; + *pos++ = WLAN_EID_VENDOR_SPECIFIC; /* element id */ + *pos++ = 6; /* length */ + memcpy(pos, EBW_OUI_TYPE, sizeof(EBW_OUI_TYPE)); + pos += sizeof(EBW_OUI_TYPE); + *pos++ = tsid; + *pos++ = 0; + + return pos - buf; +} + +/* Name: add_tsrs_ie + * Desc: Add tsrs_ie + * buf: pointer to buf that the ie is going to added + * buf_len: the byte length of the ie + * tsid: tspec id + * rates: list of rates that are supported + * num_rates: number of rates that are supported + * return: length of bytes that were added + */ +static int add_tsrs_ie(u8 *buf, size_t buf_len, u8 tsid, + u8 rates[CCX_MAX_NUM_RATES], int num_rates) +{ + u8 *pos; + size_t ie_len = (size_t)(7 + num_rates); + int i; + + if ((buf == NULL) || (buf_len < ie_len) || (rates == NULL) || + (num_rates > CCX_MAX_NUM_RATES)) + return -1; + + pos = buf; + memset(pos, 0, ie_len); + *pos++ = WLAN_EID_VENDOR_SPECIFIC; /* element id */ + *pos++ = ie_len - 2; /* length */ + memcpy(pos, TSRS_OUI_TYPE, sizeof(TSRS_OUI_TYPE)); + pos += sizeof(TSRS_OUI_TYPE); + *pos++ = tsid; + for (i = 0; i < num_rates; i++) + *pos++ = rates[i]; + + return pos - buf; +} + +/* Name: bss_get_ie + * Desc: Get the buffer of an IE that is included in a bss + * bss: pointer to the cfg80211_bss struct + * ie: the IE id that is going to be extracted + * return: pointer to the start of the IE buffer + */ +static const u8 *bss_get_ie(struct cfg80211_bss *bss, u8 ie) +{ + const u8 *pos; + u8 ies_len, ies_cur_len; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)) + pos = (const u8 *)(bss->ies); + ies_len = (u8)bss->ies->len; +#else + pos = (const u8 *)(bss->information_elements); + ies_len = (u8)bss->len_information_elements; +#endif + ies_cur_len = 1; + + while (ies_cur_len <= ies_len) { + if (pos[0] == ie) + return pos; + + pos += 2 + pos[1]; + ies_cur_len++; + } + + return NULL; +} + +/* Name: bss_get_bit_rates + * Desc: Get the buffer of an IE that is included in a bss + * bss: pointer to the cfg80211_bss struct + * rates: the rates that are supported + * return: 0 (succes), -1 (failure) + */ +static int bss_get_bit_rates(struct cfg80211_bss *bss, u8 **rates) +{ + const u8 *ie, *ie2; + int i, j; + unsigned int len; + u8 *r; + + ie = bss_get_ie(bss, WLAN_EID_SUPP_RATES); + ie2 = bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES); + + len = (ie ? ie[1] : 0) + (ie2 ? ie2[1] : 0); + + if (!len) + return -1; + + r = kmalloc(len, GFP_KERNEL); + if (!r) + return -1; + + for (i = 0; ie && i < ie[1]; i++) + r[i] = ie[i + 2] & 0x7f; + + for (j = 0; ie2 && j < ie2[1]; j++) + r[i + j] = ie2[j + 2] & 0x7f; + + *rates = r; + return len; +} + +/* Name: cac_send_addts + * Desc: Build and send the ADDTS action frame + * sdev: pointer to the slsi_dev struct + * id: the tspec id that is going to be included in the ADDTS action frame + * ebw: 1 (add ebw IE), 0 (don't add ebw IE) + * return: 0 (succes), -1 (failure) + */ +static int cac_send_addts(struct slsi_dev *sdev, int id, int ebw) +{ + struct action_addts_req *req; + size_t extra_ie_len = 50; + int ie_len = 0; + size_t req_len; + struct cac_tspec *entry; + u8 tsid, i; + u8 *rates; + u8 rate = 0; + u8 *pos; + int num_rates; + struct netdev_vif *ndev_vif; + struct net_device *netdev; + u16 host_tag = slsi_tx_mgmt_host_tag(sdev); + struct ieee80211_hdr *hdr; + u8 *buf = NULL; + u8 *bssid; + u8 r = 0; + + entry = find_tspec_entry(id, 0); + if (entry == NULL) { + SLSI_ERR(sdev, "CAC-ADDTS: Invalid TSPEC ID\n"); + return -1; + } + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + netdev = get_netdev_for_station(sdev); + if (netdev == NULL) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + ndev_vif = netdev_priv(netdev); + if ((ndev_vif == NULL) || (ndev_vif->sta.sta_bss == NULL)) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_ERR(sdev, "CAC-ADDTS: Not connected, can't send ADDTS\n"); + r = -1; + goto exit; + } + bssid = ndev_vif->sta.sta_bss->bssid; + if (entry->accepted) { + SLSI_ERR(sdev, "CAC-ADDTS: TSPEC already accepted\n"); + r = -1; + goto exit; + } + + buf = kmalloc(IEEE80211_HEADER_SIZE + sizeof(*req) + extra_ie_len, GFP_KERNEL); + if (buf == NULL) { + SLSI_ERR(sdev, "CAC-ADDTS: Failed to allocate ADDTS request\n"); + r = -1; + goto exit; + } + + hdr = (struct ieee80211_hdr *)buf; + hdr->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, IEEE80211_STYPE_ACTION); + SLSI_ETHER_COPY(hdr->addr1, bssid); + SLSI_ETHER_COPY(hdr->addr2, sdev->hw_addr); + SLSI_ETHER_COPY(hdr->addr3, bssid); + + req = (struct action_addts_req *)(buf + IEEE80211_HEADER_SIZE); + req->hdr.category = WLAN_CATEGORY_WMM; + req->hdr.action = WMM_ACTION_CODE_ADDTS_REQ; + if (dialog_token_next == 0) + dialog_token_next++; + req->hdr.dialog_token = dialog_token_next++; + req->hdr.status_code = 0; + tsid = (CAC_GET_LE24(req->tspec.ts_info) >> 1) & 0xF; + + /* Find the value of PSB in TSPEC. If PSB is unspecified; fill the + * value from UAPSD value stored in Peer structure for the AC + */ + if (entry->psb_specified == 0) { + struct slsi_peer *peer; + u32 priority; + + peer = slsi_get_peer_from_qs(sdev, netdev, SLSI_STA_PEER_QUEUESET); + if (!peer) { + SLSI_ERR(sdev, "CAC-ADDTS: no Peer found\n"); + r = -1; + goto exit_free_buf; + } + + cac_query_tspec_field(sdev, entry, "user_priority", &priority); + if (peer->uapsd & BIT(slsi_frame_priority_to_ac_queue(priority))) + entry->tspec.ts_info[1] |= 0x04; + } + memcpy(&req->tspec, &entry->tspec, sizeof(entry->tspec)); + req_len = sizeof(*req); + pos = (u8 *)(req + 1); + entry->ebw = ebw ? 1 : 0; + + if (ebw) { + ie_len += add_ebw_ie(pos, extra_ie_len, tsid); + if (ie_len <= 0) + SLSI_ERR(sdev, "CAC-ADDTS: Failed to add EBW IE\n"); + } + + /* Add tsrs IE in case of ccx enabled bss */ + if (ccx_status == BSS_CCX_ENABLED) { + num_rates = bss_get_bit_rates(ndev_vif->sta.sta_bss, &rates); + if (num_rates <= 0) + rate = 12; /* Default to 6Mbps */ + else { + for (i = 0; i < num_rates; i++) + if ((rates[i] > rate) && (rates[i] <= 48)) + rate = rates[i]; + kfree(rates); + } + + do { + /* if the nominal rate is equal to minimum_phy_rate + * don't add the tsrs_ie + */ + if ((rate * TSRS_RATE_PER_UNIT) == req->tspec.minimum_phy_rate) + break; + + if ((rate * TSRS_RATE_PER_UNIT) > req->tspec.minimum_phy_rate) { + ie_len += add_tsrs_ie(pos + ie_len, extra_ie_len - ie_len, + tsid, &rate, 1); + if (ie_len <= 0) { + SLSI_ERR(sdev, "CAC-ADDTS: Failed to add TSRS IE\n"); + r = -1; + goto exit_free_buf; + } + } else { /* only the "<" case is possible */ + SLSI_ERR(sdev, "CAC-ADDTS: BSS rate too low\n"); + r = -1; + goto exit_free_buf; + } + } while (0); + } + + if (slsi_mlme_send_frame_mgmt(sdev, netdev, buf, (IEEE80211_HEADER_SIZE + req_len + ie_len), + FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, + host_tag, 0, sdev->fw_dwell_time, 0) != 0) { + SLSI_ERR(sdev, "CAC-ADDTS: Failed to send ADDTS request\n"); + r = -1; + goto exit_free_buf; + } + entry->dialog_token = req->hdr.dialog_token; + +exit_free_buf: + kfree(buf); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return r; +} + +/* Name: cac_send_delts + * Desc: Build and send the DELTS action frame + * sdev: pointer to the slsi_dev struct + * id: the tspec id that is going the DELTS action frame to send for + * return: 0 (succes), -1 (failure) + */ +static int cac_send_delts(struct slsi_dev *sdev, int id) +{ + struct action_delts_req *req; + struct cac_tspec *entry; + size_t req_len; + u32 priority; + int rc; + struct netdev_vif *ndev_vif; + struct net_device *netdev; + u16 host_tag = slsi_tx_mgmt_host_tag(sdev); + struct ieee80211_hdr *hdr; + u8 *buf = NULL; + u8 *bssid; + u8 r = 0; + struct slsi_peer *stapeer; + + entry = find_tspec_entry(id , 1); + if (entry == NULL) { + SLSI_ERR(sdev, "CAC-DELTS: no TSPEC has been established for tsid=%d\n", id); + return -1; + } + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + netdev = get_netdev_for_station(sdev); + if (netdev == NULL) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + ndev_vif = netdev_priv(netdev); + if ((ndev_vif == NULL) || (ndev_vif->sta.sta_bss == NULL)) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_ERR(sdev, "CAC-DELTS: Not connected, can't send DELTS\n"); + r = -1; + goto exit; + } + + stapeer = slsi_get_peer_from_qs(sdev, netdev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!stapeer)) { + r = -1; + goto exit; + } + + bssid = ndev_vif->sta.sta_bss->bssid; + buf = kmalloc(24 + sizeof(*req), GFP_KERNEL); + if (buf == NULL) { + SLSI_ERR(sdev, "CAC-DELTS: Failed to allocate DELTS request\n"); + r = -1; + goto exit; + } + hdr = (struct ieee80211_hdr *)buf; + hdr->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, IEEE80211_STYPE_ACTION); + SLSI_ETHER_COPY(hdr->addr1, bssid); + SLSI_ETHER_COPY(hdr->addr2, sdev->hw_addr); + SLSI_ETHER_COPY(hdr->addr3, bssid); + req = (struct action_delts_req *)(buf + 24); + req_len = sizeof(*req); + req->hdr.category = WLAN_CATEGORY_WMM; + req->hdr.action = WMM_ACTION_CODE_DELTS; + req->hdr.dialog_token = 0; + req->hdr.status_code = 0; + memcpy(&req->tspec, &entry->tspec, sizeof(entry->tspec)); + + /* TODO_HARDMAC: If PMF is negotiated over the link, the host shall not + * issue this primitive before pairwise keys have been installed in F/W . + */ + if (slsi_mlme_send_frame_mgmt(sdev, netdev, buf, (IEEE80211_HEADER_SIZE + req_len), FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, 0, 0, 0) != 0) { + SLSI_ERR(sdev, "CAC-DELTS: Failed to send DELTS request\n"); + r = -1; + goto exit_free_buf; + } + rc = cac_query_tspec_field(sdev, entry, "user_priority", &priority); + if (rc != 0) { + SLSI_ERR(sdev, "CAC-DELTS: Error in reading priority from tspec!\n"); + r = -1; + goto exit_free_buf; + } + + if (slsi_mlme_del_traffic_parameters(sdev, netdev, priority) != 0) { + SLSI_ERR(sdev, "CAC-DELTS: Failed to send DELTS request\n"); + r = -1; + goto exit_free_buf; + } + + /* BlockAck Control Req was previously used to enable blockack for VO & VI. This + * signal is removed and expected to be replaced with MIBs - not able to see + * through the haze yet!. Need to take approp. action when the cloud clears. + * Historical Data: + * if the DELTS request is for UP = 4 or 5 then generate a + * MLME-BLOCKACK-CONTROL.request so that no BlockAck is negotiated + * on AC_VI. And leave AC_BE enabled + */ + + entry->accepted = 0; /* DELTS sent successfully */ + sdev->tspec_error_code = 0; + stapeer->tspec_established &= ~BIT(priority); + /* update RIC in add_info_elements for assoc req */ + cac_set_ric_ie(sdev, netdev); + + if (ccx_status == BSS_CCX_ENABLED && previous_msdu_lifetime != MAX_TRANSMIT_MSDU_LIFETIME_NOT_VALID) + if (slsi_send_max_transmit_msdu_lifetime(sdev, netdev, previous_msdu_lifetime) != 0) { + SLSI_ERR(sdev, "CAC-DELTS: slsi_send_max_msdu_lifetime failed"); + goto exit_free_buf; + } +exit_free_buf: + kfree(buf); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return r; +} + +/* Name: cac_create_tspec + * Desc: Create a tspec entry and added it to the tspec list + * sdev: pointer to the slsi_dev struct + * id: the id of the tspec that is included in DELTS action frame + * return: 0 (succes), -1 (failure) + */ +static int cac_create_tspec(struct slsi_dev *sdev, char *args) +{ + struct cac_tspec *entry; + int id; + u8 tid_auto_done = 0; + struct netdev_vif *ndev_vif; + struct net_device *netdev; + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + netdev = get_netdev_for_station(sdev); + if (netdev == NULL) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + ndev_vif = netdev_priv(netdev); + if (ndev_vif == NULL) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_ERR(sdev, "CAC-ADDTS: Not connected, can't create TSPEC\n"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -1; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + if (args == NULL) { + /* No input for tid, so we use the auto increment*/ + if (tspec_list_next_id <= 7) { + id = tspec_list_next_id++; + } else { + id = 0; + tspec_list_next_id = 0; + tspec_list_next_id++; + } + tid_auto_done = 1; + } + + if ((!tid_auto_done) && (strtoint(args, &id) < 0)) { + /* Invalid input for tid, so we use the auto increment*/ + if (tspec_list_next_id <= 7) { + id = tspec_list_next_id++; + } else { + id = 0; + tspec_list_next_id = 0; + tspec_list_next_id++; + } + } + + if (id < TSID_MIN || id > TSID_MAX) { + SLSI_ERR(sdev, "CAC: Invalid TSID =%d, must be in range 0-7\n", id); + return -1; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry == NULL) { + SLSI_ERR(sdev, "CAC: Failed to allocate TSPEC\n"); + return -1; + } + + entry->id = id; + entry->tspec.eid = WLAN_EID_VENDOR_SPECIFIC; + entry->tspec.length = sizeof(entry->tspec) - sizeof(entry->tspec.eid) - sizeof(entry->tspec.length); + CAC_PUT_BE24(entry->tspec.oui, WLAN_OUI_MICROSOFT); + entry->tspec.oui_type = WLAN_OUI_TYPE_MICROSOFT_WMM; + entry->tspec.oui_subtype = WMM_OUI_SUBTYPE_TSPEC_ELEMENT; + entry->tspec.version = WMM_VERSION; + entry->accepted = 0; + entry->psb_specified = 0; + /* Setting the 7th bit of ts info to 1, as its a fixed reserved bit. */ + entry->tspec.ts_info[0] = 0x80; + + entry->next = tspec_list; + tspec_list = entry; + SLSI_DBG1(sdev, SLSI_MLME, "CAC: Created TSPEC entry for id =%d\n", id); + + return entry->id; +} + +/* Name: cac_delete_tspec + * Desc: delete a tspec from the list of the tspecs + * sdev: pointer to the slsi_dev struct + * id: the id of the tspec that will be deleted + * return: 0 (succes), -1 (failure) + */ +static int cac_delete_tspec(struct slsi_dev *sdev, int id) +{ + struct cac_tspec *itr; + struct cac_tspec *prev; + + itr = tspec_list; + prev = NULL; + while (itr != NULL) { + if (itr->id == id) { + if (prev) + prev->next = itr->next; + else + tspec_list = itr->next; + + if (itr->accepted) + cac_send_delts(sdev, itr->id); + + SLSI_DBG3(sdev, SLSI_MLME, "CAC: TSPEC entry deleted for id =%d\n", id); + kfree(itr); + + return 0; + } + prev = itr; + itr = itr->next; + } + SLSI_ERR(sdev, "CAC: Couldn't find TSPEC with id %d for deletion", id); + + return -1; +} + +/* Name: cac_delete_tspec_by_state + * Desc: delete a tspec from the list of the tspecs based on id and state + * sdev: pointer to the slsi_dev struct + * id: the id of the tspec that will be deleted + * accepted: 0 - not yet accepted by AP, 1- accepted by AP + * return: 0 (succes), -1 (failure) + */ +static int cac_delete_tspec_by_state(struct slsi_dev *sdev, int id, int accepted) +{ + struct cac_tspec *itr; + struct cac_tspec *prev; + + itr = tspec_list; + prev = NULL; + while (itr != NULL) { + if ((itr->id == id) && (itr->accepted == accepted)) { + if (prev) + prev->next = itr->next; + else + tspec_list = itr->next; + + SLSI_DBG3(sdev, SLSI_MLME, "CAC: Deleting TSPEC 0x%p with ID %d (accepted =%d)\n", itr, id, accepted); + kfree(itr); + return 0; + } + prev = itr; + itr = itr->next; + } + SLSI_ERR(sdev, "CAC: Couldn't find TSPEC with ID %d (accepted =%d)\n", id, accepted); + + return -1; +} + +/* Name: cac_config_tspec + * Desc: Set a field's value of a tspec + * sdev: pointer to the slsi_dev struct + * id: the id of the tspec that will be configured + * field: the field name that will be changed + * value: the value of the field + * return: 0 (succes), -1 (failure) + */ +static int cac_config_tspec(struct slsi_dev *sdev, int id, const char *field, u32 value) +{ + struct cac_tspec *entry; + int i; + u32 max = 0xFFFFFFFF; + u32 tsinfo; + u8 mask; + u8 *pos; + + if (field == NULL) + return -1; + + entry = find_tspec_entry(id, 0); + if (entry == NULL) { + SLSI_ERR(sdev, "CAC: Invalid TSPEC ID\n"); + return -1; + } + + for (i = 0; i < NUM_TSPEC_FIELDS; i++) + if (strcasecmp(field, tspec_fields[i].name) == 0) + break; + if (i >= NUM_TSPEC_FIELDS) { + SLSI_ERR(sdev, "CAC: Invalid TSPEC config field\n"); + return -1; + } + if (tspec_fields[i].read_only) { + SLSI_ERR(sdev, "CAC: TSPEC field is read-only\n"); + return -1; + } + if (tspec_fields[i].is_tsinfo_field) { + mask = tspec_fields[i].size; + if (strcasecmp(field, "psb") == 0) { + if (value <= mask) + entry->psb_specified = 1; + else + return 0; + } + if (value > mask) { + SLSI_ERR(sdev, "CAC: TSPEC config value exceeded maximum for %s\n", tspec_fields[i].name); + return -1; + } + + tsinfo = CAC_GET_LE24(&entry->tspec.ts_info[0]); + tsinfo &= ~(u32)(mask << tspec_fields[i].offset); + tsinfo |= (u32)((value & mask) << tspec_fields[i].offset); + CAC_PUT_LE24(entry->tspec.ts_info, tsinfo); + } else { + if (tspec_fields[i].size < 4) + max = ((1 << (tspec_fields[i].size * 8)) - 1); + + if (value > max) { + SLSI_ERR(sdev, "CAC: TSPEC config value exceeded maximumfor %s\n", tspec_fields[i].name); + return -1; + } + + pos = (u8 *)(&entry->tspec) + tspec_fields[i].offset; + if (tspec_fields[i].size == 1) + *pos = (value & 0xFF); + else if (tspec_fields[i].size == 2) + CAC_PUT_LE16(pos, value); + else + CAC_PUT_LE32(pos, value); + } + + return 0; +} + +/* Name: cac_ctrl_create_tspec + * Desc: public function to create tspec + * sdev: pointer to the slsi_dev struct + * return: tspec id + */ +int cac_ctrl_create_tspec(struct slsi_dev *sdev, char *args) +{ + int id; + + id = cac_create_tspec(sdev, args); + if (id < 0) + return -1; + + return id; +} + +/* Name: cac_ctrl_delete_tspec + * Desc: public function to delete tspec + * sdev: pointer to the slsi_dev struct + * args:pointer to a buffer that contains the agrs for deleting tspec from the list + * return: 0 (succes), -1 (failure) + */ +int cac_ctrl_delete_tspec(struct slsi_dev *sdev, char *args) +{ + int id; + + if (strtoint(args, &id) < 0) { + SLSI_ERR(sdev, "CAC-DELETE-TSPEC: Invalid TSPEC ID\n"); + return -1; + } + + if (cac_delete_tspec(sdev, id) < 0) + return -1; + + return 0; +} + +/* Name: cac_ctrl_config_tspec + * Desc: public function to configure a tspec + * sdev: pointer to the slsi_dev struct + * args: pointer to a buffer that contains the agrs for tspec configuration + * return: 0 (succes), -1 (failure) + */ +int cac_ctrl_config_tspec(struct slsi_dev *sdev, char *args) +{ + char *id; + char *field; + char *value; + int tspec_id; + u32 val; + + id = args; + field = strchr(id, ' '); + if (field == NULL) { + SLSI_ERR(sdev, "CAC: field string is NULL\n"); + return -1; + } + *field++ = '\0'; + value = strchr(field, ' '); + if (value == NULL) { + SLSI_ERR(sdev, "CAC: field value is NULL\n"); + return -1; + } + *value++ = '\0'; + + if (strtoint(id, &tspec_id) < 0) { + SLSI_ERR(sdev, "CAC: Conversion error for tspecid\n"); + return -1; + } + + if (strtoint(value, &val) < 0) { + SLSI_ERR(sdev, "CAC: Conversion error for tspecid value\n"); + return -1; + } + + if (cac_config_tspec(sdev, tspec_id, field, val) < 0) + return -1; + + return 0; +} + +/* Name: cac_ctrl_send_addts + * Desc: public function to send ADDTS action frame + * sdev: pointer to the slsi_dev struct + * args: buffer that contains the agrs for ADDTS request + * return: 0 (succes), -1 (failure) + */ +int cac_ctrl_send_addts(struct slsi_dev *sdev, char *args) +{ + char *id_str; + char *ebw_str; + int id; + int ebw = 0; + + if (args == NULL) + return -1; + + id_str = args; + ebw_str = strchr(id_str, ' '); + if (ebw_str != NULL) { + *ebw_str++ = '\0'; + if (!strncmp(ebw_str, "ebw", 3)) + ebw = 1; + } + if (strtoint(id_str, &id) < 0) { + SLSI_ERR(sdev, "CAC: Conversion error for tspecid value\n"); + return -1; + } + if (cac_send_addts(sdev, id, ebw) < 0) + return -1; + + return 0; +} + +/* Name: cac_ctrl_send_delts + * Desc: public function to send DELTS action frame + * sdev: pointer to the slsi_dev struct + * args: buffer that contains the agrs for DELTS request + * return: 0 (succes), -1 (failure) + */ +int cac_ctrl_send_delts(struct slsi_dev *sdev, char *args) +{ + int id; + + if (args == NULL) + return -1; + + if (strtoint(args, &id) < 0) { + SLSI_ERR(sdev, "CAC: Invalid TSPEC ID\n"); + return -1; + } + if (cac_send_delts(sdev, id) < 0) + return -1; + + return 0; +} + +/* Name: cac_process_delts_req + * Desc: process a DELTS request + * sdev: pointer to the slsi_dev struct + * req: buffer of the DELTS request + * return: 0 (succes), -1 (failure) + */ +static void cac_process_delts_req(struct slsi_dev *sdev, struct net_device *netdev, struct action_delts_req *req) +{ + struct netdev_vif *ndev_vif = netdev_priv(netdev); + struct cac_tspec *itr; + u32 priority; + int rc; + struct slsi_peer *stapeer; + u8 tid; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED) || (ndev_vif->sta.sta_bss == NULL)) { + SLSI_ERR(sdev, "CAC: Not connected, Unexpected DELTS request\n"); + return; + } + + stapeer = slsi_get_peer_from_qs(sdev, netdev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!stapeer)) + return; + + tid = (CAC_GET_LE24(req->tspec.ts_info) >> 1) & 0xF; + SLSI_DBG1(sdev, SLSI_MLME, "CAC: TID in delts request =%d\n", tid); + + itr = find_tspec_entry(tid, 1); + if (itr == NULL) { + SLSI_ERR(sdev, "CAC: No matching TSPEC found\n"); + return; + } + + rc = cac_query_tspec_field(sdev, itr, "user_priority", &priority); + if (rc != 0) { + SLSI_ERR(sdev, "CAC: Missing priority from TSPEC!\n"); + return; + } + + if (slsi_mlme_del_traffic_parameters(sdev, netdev, priority) != 0) { + SLSI_ERR(sdev, "CAC: Failed to send DEL-TRAFFIC_PARAMETERS request\n"); + return; + } + + /* BlockAck Control Req was previously used to enable blockack for VO & VI. This + * signal is removed and expected to be replaced with MIBs - not able to see + * through the haze yet!. Need to take approp. action when the cloud clears. + * Historical Data: + * if the DELTS request is for UP = 4 or 5 then generate a + * MLME-BLOCKACK-CONTROL.request so that no BlockAck is negotiated + * on AC_VI. And leave AC_BE enabled + */ + + itr->accepted = 0; /* del traffic parameters sent successfully */ + stapeer->tspec_established &= ~BIT(priority); + SLSI_DBG1(sdev, SLSI_MLME, "tspec_established =%x\n", stapeer->tspec_established); + /* update RIC in add_info_elements for assoc req */ + cac_set_ric_ie(sdev, netdev); + + if (ccx_status == BSS_CCX_ENABLED && previous_msdu_lifetime != MAX_TRANSMIT_MSDU_LIFETIME_NOT_VALID) + if (slsi_send_max_transmit_msdu_lifetime(sdev, netdev, previous_msdu_lifetime) != 0) { + SLSI_ERR(sdev, "CAC: slsi_send_max_msdu_lifetime failed"); + return; + } +} + +/* Name: cac_find_edca_ie + * Desc: Finds the edca IE in the ADDTS response action frame + * sdev: pointer to the slsi_dev struct + * ie: buffer of the edca IE + * tsid: the tsid that is included in the edca IE + * lifetime: the lifetime value that is included in the edca IE + * return: 0 (succes), -1 (failure) + */ +static int cac_find_edca_ie(const u8 *ie, size_t ie_len, u8 *tsid, u16 *lifetime) +{ + const u8 *pos = ie; + + if ((ie == NULL) || (ie_len < 9) || + (tsid == NULL) || (lifetime == NULL)) + return -1; + + pos = cfg80211_find_vendor_ie(WLAN_OUI_CISCO, WLAN_OUI_TYPE_CISCO_EDCA, ie, ie_len); + if (pos && (pos + 9 <= ie + ie_len)) { + *tsid = pos[6]; + *lifetime = CAC_GET_LE16(&pos[7]); + return 0; + } + + return -1; +} + +/* Name: cac_process_addts_rsp + * Desc: parsing of the addts response + * sdev: pointer to the slsi_dev struct + * rsp: the buffer of the ADDTS response received + * ie_len: the length of the buffer + * return: 0 (succes), -1 (failure) + */ +static void cac_process_addts_rsp(struct slsi_dev *sdev, struct net_device *netdev, struct action_addts_rsp *rsp, const u8 *ie, size_t ie_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(netdev); + struct cac_tspec *itr, *entry; + struct wmm_tspec_element *tspec; + u32 priority, prev_priority; + int rc; + u8 tsid; + u16 msdu_lifetime; + struct slsi_peer *peer; + u16 medium_time; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_DBG1(sdev, SLSI_MLME, "\n"); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED) || (ndev_vif->sta.sta_bss == NULL)) { + SLSI_ERR(sdev, "CAC: Not connected, INVALID state for ADDTS response\n"); + return; + } + + peer = slsi_get_peer_from_qs(sdev, netdev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!peer)) + return; + + itr = tspec_list; + while (itr != NULL) { + if (itr->dialog_token == rsp->hdr.dialog_token) { + itr->dialog_token = 0; /*reset the dialog token to avoid any incorrect matches if AP send incorrect value*/ + break; + } + itr = itr->next; + } + if (itr == NULL) { + SLSI_ERR(sdev, "CAC: No matching TSPEC found for ADDTS response\n"); + return; + } + + if (rsp->hdr.status_code != ADDTS_STATUS_ACCEPTED) { + SLSI_ERR(sdev, "CAC: TSPEC rejected (status=0x%02X)", rsp->hdr.status_code); + cac_delete_tspec_by_state(sdev, itr->id , 0); + return; + } + + if ((ccx_status == BSS_CCX_ENABLED) && cac_find_edca_ie(ie, ie_len, &tsid, &msdu_lifetime) != 0) + msdu_lifetime = MSDU_LIFETIME_DEFAULT; + + tspec = (struct wmm_tspec_element *)(rsp + 1); + medium_time = tspec->medium_time; + + rc = cac_query_tspec_field(sdev, itr, "user_priority", &priority); + SLSI_DBG1(sdev, SLSI_MLME, "CAC: Priority for current tspec id %d=%d\n", itr->id, priority); + + if (peer->tspec_established == 0) + goto set_params; + + SLSI_DBG1(sdev, SLSI_MLME, "TSPEC already established\n"); + + /* TSPEC is already established . Check if it is for same UP / UP mapping to same AC + * If same UP (or UP mapping to same AC) : set params with modified values + * If not, set traffic params for this priority (new AC) + */ + switch (priority) { + /*AC_BK*/ + case FAPI_PRIORITY_QOS_UP1: + case FAPI_PRIORITY_QOS_UP2: + if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP1)) + prev_priority = FAPI_PRIORITY_QOS_UP1; + else if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP2)) + prev_priority = FAPI_PRIORITY_QOS_UP2; + else + goto set_params; + break; + + /*AC_BE*/ + case FAPI_PRIORITY_QOS_UP0: + case FAPI_PRIORITY_QOS_UP3: + if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP0)) + prev_priority = FAPI_PRIORITY_QOS_UP0; + else if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP3)) + prev_priority = FAPI_PRIORITY_QOS_UP3; + else + goto set_params; + break; + + /*AC_VI*/ + case FAPI_PRIORITY_QOS_UP4: + case FAPI_PRIORITY_QOS_UP5: + if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP4)) + prev_priority = FAPI_PRIORITY_QOS_UP4; + else if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP5)) + prev_priority = FAPI_PRIORITY_QOS_UP5; + else + goto set_params; + break; + + /*AC_VO*/ + case FAPI_PRIORITY_QOS_UP6: + case FAPI_PRIORITY_QOS_UP7: + if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP6)) + prev_priority = FAPI_PRIORITY_QOS_UP6; + else if (peer->tspec_established & BIT(FAPI_PRIORITY_QOS_UP7)) + prev_priority = FAPI_PRIORITY_QOS_UP7; + else + goto set_params; + break; + /* invalid*/ + default: + SLSI_ERR(sdev, "CAC: Invalid UP in the request\n"); + return; + } + + /* Look for TSPEC entry for initial request */ + entry = find_tspec_entry(itr->id, 1); + if (entry) { /*same TID*/ + cac_query_tspec_field(sdev, entry, "user_priority", &prev_priority); + SLSI_DBG1(sdev, SLSI_MLME, "CAC: Modify TSPEC (prev_priority =%d)\n", prev_priority); + /* On receiving the new medium time (second ADDTS Response) , driver shall issue + * mlme-set-traffic-parameters.request with the received medium time. + * Use UP from old entry so FW can replace the medium time + * Delete the old entry in host, and replace UP in new entry. + */ + cac_delete_tspec_by_state(sdev, entry->id, 1); + if (priority != prev_priority) { + itr->tspec.ts_info[1] &= ~(7 << 3) ; /*clear the value*/ + itr->tspec.ts_info[1] |= prev_priority << 3 ; /*set the value*/ + priority = prev_priority; + } + + } else { + /* Two distinct TSes are being admitted, so the driver needs to add both allocated medium time + * The UP must be set to the same value of the first mlme-set-traffic-parameters.request so that + * the FW replaces the current medium time with the new medium time. + */ + SLSI_DBG1(sdev, SLSI_MLME, "CAC: Modify TSPEC for different TID\n"); + entry = tspec_list; + while (entry != NULL) { + if ((entry->accepted) && ((entry->tspec.ts_info[1] >> 3 & 0x07) == prev_priority)) { /*initial TS entry for same priority*/ + medium_time += entry->tspec.medium_time; + priority = prev_priority; + break; + } + entry = entry->next; + } + if (entry == NULL) { + SLSI_ERR(sdev, "CAC: Failed to find entry for prev established TSPEC!!\n"); + return; + } + } + +set_params: + SLSI_DBG1(sdev, SLSI_MLME, "sending traffic params tid [%d]", itr->id); + if (slsi_mlme_set_traffic_parameters(sdev, netdev, priority, medium_time, tspec->minimum_data_rate, ndev_vif->sta.sta_bss->bssid) != 0) { + SLSI_ERR(sdev, "CAC: Failed to send SET_TRAFFIC_PARAMETERS request\n"); + return; + } + + /*update the TSPEC with medium_time allocated by AP*/ + itr->tspec.medium_time = medium_time; + + /* BlockAck Control Req was previously used to enable blockack for VO & VI. This + * signal is removed and expected to be replaced with MIBs - not able to see + * through the haze yet!. Need to take approp. action when the cloud clears. + * Historical Data: + * Currently the firmware autonomously negotiates BlockAck agreement for AC_BE. + * It is required for WMM-AC certification to use BlockAck for AC_VI. + * So if a TSPEC for AC_VI (UP = 5 0r 4) is successfully negotiated, the host + * generates an MLME-BLOCKACK-CONTROL.request, identifying that a BlockAck for the + * corresponding Priority (direction set to Any) should be enabled, i.e. the F/W + * will accept a downlink requested BlockAck Request, and will try to set-up an + * uplink BlockAck Request for that priority (TID). + * Bits for AC_BE should always be set + * For WMM-AC certification, if the EDCA parameters for both VO and VI are same + * during association and both are ACM = 1, then don't use BlockAck for AC_VI. + */ + + /* Add store in MIB the msdu_lifetime value in case of ccx enabled bss */ + if (ccx_status == BSS_CCX_ENABLED) { + if ((slsi_read_max_transmit_msdu_lifetime(sdev, netdev, &previous_msdu_lifetime)) != 0) { + previous_msdu_lifetime = MAX_TRANSMIT_MSDU_LIFETIME_NOT_VALID; + SLSI_ERR(sdev, "CAC: slsi_read_max_msdu_lifetime failed"); + return; + } + + if (slsi_send_max_transmit_msdu_lifetime(sdev, netdev, msdu_lifetime) != 0) { + SLSI_ERR(sdev, "CAC: slsi_send_max_msdu_lifetime failed"); + return; + } + } + + itr->accepted = 1; /* add_tspec accepted by AP*/ + sdev->tspec_error_code = 0; /* add_tspec response received */ + peer->tspec_established |= BIT(priority); + /* update RIC in add_info_elements for assoc req */ + cac_set_ric_ie(sdev, netdev); +} + +/* Name: cac_rx_wmm_action + * Desc: Get the action frame received and call the corresponding process routine + * sdev: pointer to the slsi_dev struct + * data: buffer to the action frame received + * len: the length in bytes of the action frame + */ +void cac_rx_wmm_action(struct slsi_dev *sdev, struct net_device *netdev, struct ieee80211_mgmt *data, size_t len) +{ + struct ieee80211_mgmt *mgmt = data; + struct action_addts_rsp *addts; + + if ((sdev == NULL) || (data == NULL) || (netdev == NULL) || (len == 0)) + return; + + if (mgmt->u.action.u.wme_action.action_code == WMM_ACTION_CODE_ADDTS_RESP) { + addts = (struct action_addts_rsp *)&mgmt->u.action; + cac_process_addts_rsp(sdev, netdev, addts, mgmt->u.action.u.wme_action.variable, len - sizeof(*addts) + 1); + } else if (mgmt->u.action.u.wme_action.action_code == WMM_ACTION_CODE_DELTS) { + cac_process_delts_req(sdev, netdev, (struct action_delts_req *)&mgmt->u.action); + } +} + +/* Name: cac_get_active_tspecs + * Desc: + * tspecs: the list of active tspecs + * return: 0 (succes), -1 (failure) + */ +int cac_get_active_tspecs(struct cac_activated_tspec **tspecs) +{ + struct cac_tspec *itr = tspec_list; + int count = 0; + int i = 0; + + if (tspecs == NULL) + return -1; + + while (itr != NULL) { + if (itr->accepted) + count++; + itr = itr->next; + } + *tspecs = kmalloc_array((size_t)count, sizeof(struct cac_activated_tspec), GFP_KERNEL); + itr = tspec_list; + while (itr != NULL) { + if (itr->accepted) { + tspecs[i]->ebw = itr->ebw; + memcpy(&tspecs[i]->tspec, &itr->tspec, sizeof(itr->tspec)); + i++; + } + itr = itr->next; + } + + return count; +} + +/********************************************************* + * call cac_delete_tspec_list to delete all tspecs + * when the device is disconnecting + */ +/* Name: cac_delete_tspec_list + * Desc: + * sdev: pointer to the slsi_dev struct + * return: None + */ +void cac_delete_tspec_list(struct slsi_dev *sdev) +{ + struct cac_tspec *itr = tspec_list; + struct cac_tspec *temp = NULL; + + SLSI_UNUSED_PARAMETER(sdev); + + while (itr != NULL) { + itr->accepted = 0; + itr->dialog_token = 0; + temp = itr; + itr = itr->next; + kfree(temp); + } + tspec_list = NULL; +} + +void cac_deactivate_tspecs(struct slsi_dev *sdev) +{ + struct cac_tspec *itr = tspec_list; + + SLSI_UNUSED_PARAMETER(sdev); + + while (itr) { + itr->accepted = 0; + itr->dialog_token = 0; + itr = itr->next; + } +} + +static void cac_set_ric_ie(struct slsi_dev *sdev, struct net_device *netdev) +{ + struct cac_tspec *itr = tspec_list; + int tspec_count = 0; + int buf_len = 0; + u8 *buff, *add_info_ies; + struct wmm_tspec_element *tspec_ie; + int i = 0; + struct netdev_vif *ndev_vif = netdev_priv(netdev); + + while (itr) { + if (itr->accepted) + tspec_count++; + itr = itr->next; + } + + if (tspec_count == 0) { + slsi_mlme_add_info_elements(sdev, netdev, FAPI_PURPOSE_ASSOCIATION_REQUEST, + ndev_vif->sta.assoc_req_add_info_elem, + ndev_vif->sta.assoc_req_add_info_elem_len); + return; + } + + /* RDE (6 bytes), WMM TSPEC * tspec_count bytes*/ + buf_len = 6 + (sizeof(struct wmm_tspec_element) * tspec_count); + buf_len += ndev_vif->sta.assoc_req_add_info_elem_len; + add_info_ies = kmalloc(buf_len, GFP_KERNEL); + if (!add_info_ies) { + SLSI_ERR(sdev, "malloc fail. size:%d\n", buf_len); + return; + } + memcpy(add_info_ies, ndev_vif->sta.assoc_req_add_info_elem, ndev_vif->sta.assoc_req_add_info_elem_len); + + buff = add_info_ies + ndev_vif->sta.assoc_req_add_info_elem_len; + buff[0] = WLAN_EID_RIC_DATA; + buff[1] = 4; + buff[2] = 0; /* random identifier */ + /* buff[3]: resource desc count update after filling TSPEC */ + buff[4] = 0; /* buff[4]-buff[5] status code. set to success */ + buff[5] = 0; + + itr = tspec_list; + i = 0; + while (itr) { + if (itr->accepted) { + tspec_ie = (struct wmm_tspec_element *)&buff[6 + i * sizeof(struct wmm_tspec_element)]; + memcpy(tspec_ie, &itr->tspec, sizeof(struct wmm_tspec_element)); + ((struct wmm_tspec_element *)tspec_ie)->medium_time = 0; + i++; + } + itr = itr->next; + } + buff[3] = i; + slsi_mlme_add_info_elements(sdev, netdev, FAPI_PURPOSE_ASSOCIATION_REQUEST, add_info_ies, buf_len); + kfree(add_info_ies); +} + +static int cac_get_rde_tspec_ie(struct slsi_dev *sdev, u8 *assoc_rsp_ie, int assoc_rsp_ie_len, const u8 **tspec_ie_arr) +{ + const u8 *ie; + u16 status; + int tspec_count = 0, i = 0; + + ie = assoc_rsp_ie; + + /* Find total number of RDE TSPEC */ + while (ie && (assoc_rsp_ie_len > ie - assoc_rsp_ie)) { + ie = cfg80211_find_ie(WLAN_EID_RIC_DATA, ie, assoc_rsp_ie_len - (ie - assoc_rsp_ie)); + if (!ie) + break; + status = CAC_GET_LE16(&ie[4]); + if (status != 0) + continue; + + tspec_count += ie[3]; /* TSPEC descriptor count */ + ie = ie + ie[1]; + } + + /* limit WMM TSPEC count to TSID_MAX */ + if (tspec_count > TSID_MAX) { + SLSI_DBG1(sdev, SLSI_MLME, "received %d TSPEC but can accommodate only %d\n", tspec_count, TSID_MAX); + tspec_count = TSID_MAX; + } + + /* Get all WMM TSPEC IE pointers */ + ie = cfg80211_find_ie(WLAN_EID_RIC_DATA, assoc_rsp_ie, assoc_rsp_ie_len); + while (i < tspec_count && ie) { + ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WMM, ie, + assoc_rsp_ie_len - (ie - assoc_rsp_ie)); + if (!ie) + break; + /* re-assoc-res can contain wmm parameter IE and wmm TSPEC IE. + * we want wmm TSPEC Element) + */ + if (ie[1] > 6 && ie[6] == WMM_OUI_SUBTYPE_TSPEC_ELEMENT) { + tspec_ie_arr[i] = ie; + i++; + } + ie += ie[1]; + } + + return i; +} + +void cac_update_roam_traffic_params(struct slsi_dev *sdev, struct net_device *dev) +{ + const u8 *tspec_ie_arr[TSID_MAX]; + int assoc_rsp_tspec_count, i; + u32 priority; + struct cac_tspec *itr; + struct wmm_tspec_element *assoc_rsp_tspec; + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_DBG3(sdev, SLSI_MLME, "\n"); + + /* Roamed to new AP. TSPEC admitted to previous AP are no more valid. + * Set all TSPEC to not admitted + */ + cac_deactivate_tspecs(sdev); + + if (!peer) { + SLSI_ERR(sdev, "AP peer entry not found\n"); + return; + } + + /* Find all the admitted TSPECs in assoc resp. */ + assoc_rsp_tspec_count = cac_get_rde_tspec_ie(sdev, peer->assoc_resp_ie->data, + peer->assoc_resp_ie->len, tspec_ie_arr); + + SLSI_DBG3(sdev, SLSI_MLME, "assoc_rsp_tspec_count:%d\n", assoc_rsp_tspec_count); + + if (!assoc_rsp_tspec_count) + return; + + /* update the admitted TSPECs from assoc resp and set traffic params in FW.*/ + for (i = 0; i < assoc_rsp_tspec_count; i++) { + assoc_rsp_tspec = (struct wmm_tspec_element *)tspec_ie_arr[i]; + SLSI_DBG3(sdev, SLSI_MLME, "rsp_tspec:[%d] ts: [%x|%x|%x] medium time[%x]\n", i, + assoc_rsp_tspec->ts_info[0], assoc_rsp_tspec->ts_info[1], assoc_rsp_tspec->ts_info[2], + assoc_rsp_tspec->medium_time); + + itr = find_tspec_entry((assoc_rsp_tspec->ts_info[0] & 0x1E) >> 1, 0); + if (!itr) { + SLSI_DBG3(sdev, SLSI_MLME, "tspec entry not found\n"); + continue; + } + + itr->tspec.medium_time = assoc_rsp_tspec->medium_time; + itr->tspec.minimum_data_rate = assoc_rsp_tspec->minimum_data_rate; + itr->accepted = 1; + cac_query_tspec_field(sdev, itr, "user_priority", &priority); + peer->tspec_established |= BIT(priority); + SLSI_DBG3(sdev, SLSI_MLME, "tspec admitted id[%d]\n", itr->id); + slsi_mlme_set_traffic_parameters(sdev, dev, priority, assoc_rsp_tspec->medium_time, + assoc_rsp_tspec->minimum_data_rate, ndev_vif->sta.sta_bss->bssid); + } +} + diff --git a/drivers/net/wireless/scsc/cac.h b/drivers/net/wireless/scsc/cac.h new file mode 100755 index 000000000000..a8851eb4b455 --- /dev/null +++ b/drivers/net/wireless/scsc/cac.h @@ -0,0 +1,182 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef CAC_H +#define CAC_H + +#include +#include "dev.h" +#include "debug.h" +#include "mlme.h" +#include "mgt.h" + +/* management */ +#define WLAN_OUI_CISCO 0x004096 /* Cisco systems OUI */ +#define WLAN_OUI_TYPE_CISCO_EDCA 0x09 + +#define WMM_OUI_SUBTYPE_INFORMATION_ELEMENT 0 +#define WMM_OUI_SUBTYPE_PARAMETER_ELEMENT 1 +#define WMM_OUI_SUBTYPE_TSPEC_ELEMENT 2 +#define WMM_VERSION 1 +#define WMM_ACTION_CODE_ADDTS_REQ 0 +#define WMM_ACTION_CODE_ADDTS_RESP 1 +#define WMM_ACTION_CODE_DELTS 2 +#define WMM_ADDTS_STATUS_ADMISSION_ACCEPTED 0 +#define WMM_ADDTS_STATUS_INVALID_PARAMETERS 1 +/* 2 - Reserved */ +#define WMM_ADDTS_STATUS_REFUSED 3 +/* 4-255 - Reserved */ + +/* WMM TSPEC Direction Field Values */ +#define WMM_TSPEC_DIRECTION_UPLINK 0 +#define WMM_TSPEC_DIRECTION_DOWNLINK 1 +/* 2 - Reserved */ +#define WMM_TSPEC_DIRECTION_BI_DIRECTIONAL 3 + +/* WMM TSPEC PSB Field Values */ +#define WMM_TSPEC_PSB_UNSPECIFIED 2 + +#define ADDTS_STATUS_ACCEPTED 0x00 +#define ADDTS_STATUS_INVALID_PARAM 0x01 +#define ADDTS_STATUS_REFUSED 0x03 +#define ADDTS_STATUS_DELAY 0x2F +#define ADDTS_STATUS_UNSPECIFIED 0xC8 +#define ADDTS_STATUS_POLICY_CONFIG 0xC9 +#define ADDTS_STATUS_ASSOC_DENIED 0xCA +#define ADDTS_STATUS_INVALID_PARAM2 0xCB + +#define TSINFO_MASK 0x00FFFFFF + +#define CCX_MAX_NUM_RATES 8 + +#define TSID_MIN 0 +#define TSID_MAX 7 + +#define TSRS_RATE_PER_UNIT 500000 +#define IEEE80211_HEADER_SIZE 24 + +#define MAX_TRANSMIT_MSDU_LIFETIME_NOT_VALID -1 +#define BSS_CCX_DISABLED 0 +#define BSS_CCX_ENABLED 1 + +/* Macros for handling unaligned memory accesses */ +#define CAC_GET_LE16(a) ((u16)(((a)[1] << 8) | (a)[0])) +#define CAC_PUT_LE16(a, val) \ + do { \ + (a)[1] = ((u16)(val)) >> 8; \ + (a)[0] = ((u16)(val)) & 0xff; \ + } while (0) +#define CAC_PUT_BE24(a, val) \ + do { \ + (a)[0] = (u8)((((u32)(val)) >> 16) & 0xff); \ + (a)[1] = (u8)((((u32)(val)) >> 8) & 0xff); \ + (a)[2] = (u8)(((u32)(val)) & 0xff); \ + } while (0) +#define CAC_GET_LE24(a) ((((u32)(a)[2]) << 16) | (((u32)(a)[1]) << 8) | ((u32)(a)[0])) +#define CAC_PUT_LE24(a, val) \ + do { \ + (a)[2] = (u8)((((u32)(val)) >> 16) & 0xff); \ + (a)[1] = (u8)((((u32)(val)) >> 8) & 0xff); \ + (a)[0] = (u8)(((u32)(val)) & 0xff); \ + } while (0) +#define CAC_GET_LE32(a) ((((u32)(a)[3]) << 24) | (((u32)(a)[2]) << 16) | \ + (((u32)(a)[1]) << 8) | ((u32)(a)[0])) +#define CAC_PUT_LE32(a, val) \ + do { \ + (a)[3] = (u8)((((u32)(val)) >> 24) & 0xff); \ + (a)[2] = (u8)((((u32)(val)) >> 16) & 0xff); \ + (a)[1] = (u8)((((u32)(val)) >> 8) & 0xff); \ + (a)[0] = (u8)(((u32)(val)) & 0xff); \ + } while (0) + +#define IEEE80211_FC(type, stype) (u16)(type | stype) + +/* WMM TSPEC Element */ +struct wmm_tspec_element { + char eid; /* 221 = 0xdd */ + u8 length; /* 6 + 55 = 61 */ + u8 oui[3]; /* 00:50:f2 */ + u8 oui_type; /* 2 */ + u8 oui_subtype; /* 2 */ + u8 version; /* 1 */ + /* WMM TSPEC body (55 octets): */ + u8 ts_info[3]; + u16 nominal_msdu_size; + u16 maximum_msdu_size; + u32 minimum_service_interval; + u32 maximum_service_interval; + u32 inactivity_interval; + u32 suspension_interval; + u32 service_start_time; + u32 minimum_data_rate; + u32 mean_data_rate; + u32 peak_data_rate; + u32 maximum_burst_size; + u32 delay_bound; + u32 minimum_phy_rate; + u16 surplus_bandwidth_allowance; + u16 medium_time; +} __packed; + +#define MSDU_LIFETIME_DEFAULT 512 + +struct cac_activated_tspec { + struct wmm_tspec_element tspec; + int ebw; +}; + +struct tspec_field { + const char *name; + int read_only; + int is_tsinfo_field; + u8 size; + u32 offset; +}; + +struct cac_tspec { + struct cac_tspec *next; + int id; + struct wmm_tspec_element tspec; + u8 psb_specified; + int ebw; + int accepted; + u8 dialog_token; +}; + +#define OFFSETOF(m) ((size_t)&((struct wmm_tspec_element *)0)->m) + +struct wmm_action_hdr { + u8 category; + u8 action; + u8 dialog_token; + u8 status_code; +} __packed; + +struct action_addts_req { + struct wmm_action_hdr hdr; + struct wmm_tspec_element tspec; +} __packed; + +struct action_addts_rsp { + struct wmm_action_hdr hdr; +} __packed; + +struct action_delts_req { + struct wmm_action_hdr hdr; + struct wmm_tspec_element tspec; +} __packed; +/* prototypes for public functions */ +int cac_ctrl_create_tspec(struct slsi_dev *sdev, char *args); +int cac_ctrl_config_tspec(struct slsi_dev *sdev, char *args); +int cac_ctrl_send_addts(struct slsi_dev *sdev, char *args); +int cac_ctrl_send_delts(struct slsi_dev *sdev, char *args); +int cac_update_local_tspec(struct slsi_dev *sdev, u16 msdu_lifetime, struct wmm_tspec_element *tspec); +int cac_get_active_tspecs(struct cac_activated_tspec **tspecs); +void cac_delete_tspec_list(struct slsi_dev *sdev); +int cac_ctrl_delete_tspec(struct slsi_dev *sdev, char *args); +void cac_rx_wmm_action(struct slsi_dev *sdev, struct net_device *netdev, struct ieee80211_mgmt *data, size_t len); +void cac_update_roam_traffic_params(struct slsi_dev *sdev, struct net_device *dev); +#endif /* CAC_H */ diff --git a/drivers/net/wireless/scsc/cfg80211_ops.c b/drivers/net/wireless/scsc/cfg80211_ops.c new file mode 100755 index 000000000000..417d3b987d70 --- /dev/null +++ b/drivers/net/wireless/scsc/cfg80211_ops.c @@ -0,0 +1,3557 @@ +/*************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include "dev.h" +#include "cfg80211_ops.h" +#include "debug.h" +#include "mgt.h" +#include "mlme.h" +#include "netif.h" +#include "unifiio.h" +#include "mib.h" + +#ifdef CONFIG_ANDROID +#include "scsc_wifilogger_rings.h" +#endif +#include "nl80211_vendor.h" + +#define SLSI_MAX_CHAN_2G_BAND 14 + +static uint keep_alive_period = SLSI_P2PGO_KEEP_ALIVE_PERIOD_SEC; +module_param(keep_alive_period, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(keep_alive_period, "default is 10 seconds"); + +static bool slsi_is_mhs_active(struct slsi_dev *sdev) +{ + struct net_device *mhs_dev = sdev->netdev_ap; + struct netdev_vif *ndev_vif; + bool ret; + + if (mhs_dev) { + ndev_vif = netdev_priv(mhs_dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + ret = ndev_vif->is_available; + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return ret; + } + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) +struct wireless_dev *slsi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) +struct wireless_dev *slsi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +struct wireless_dev *slsi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ +#else +struct net_device *slsi_add_ virtual_intf(struct wiphy *wiphy, + char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ +#endif + + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif = NULL; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 11, 0)) + SLSI_UNUSED_PARAMETER(flags); +#endif + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Intf name:%s, type:%d, macaddr:%pM\n", name, type, params->macaddr); + if (slsi_is_mhs_active(sdev)) { + SLSI_ERR(sdev, "MHS is active. cannot add new interface\n"); + return ERR_PTR(-EOPNOTSUPP); + } + dev = slsi_dynamic_interface_create(wiphy, name, type, params); + if (!dev) + goto exit_with_error; + ndev_vif = netdev_priv(dev); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + return &ndev_vif->wdev; +#else + return dev; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ + +exit_with_error: + return ERR_PTR(-ENODEV); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct net_device *dev = wdev->netdev; + +#else +int slsi_del_virtual_intf(struct wiphy *wiphy, struct net_device *dev) +{ +#endif + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + + if (WARN_ON(!dev)) + return -EINVAL; + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Dev name:%s\n", dev->name); + + slsi_stop_net_dev(sdev, dev); + slsi_netif_remove_rtlnl_locked(sdev, dev); + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + if (dev == sdev->netdev_ap) + rcu_assign_pointer(sdev->netdev_ap, NULL); + if (!sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]) + rcu_assign_pointer(sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN], sdev->netdev_ap); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) +int slsi_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, + struct vif_params *params) +{ +#else +int slsi_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ +#endif + + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 11, 0)) + SLSI_UNUSED_PARAMETER(flags); +#endif + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "type:%u, iftype:%d\n", type, ndev_vif->iftype); + + if (WARN_ON(ndev_vif->activated)) { + r = -EINVAL; + goto exit; + } + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: +#ifdef CONFIG_SCSC_WLAN_DEBUG + case NL80211_IFTYPE_MONITOR: +#endif + ndev_vif->iftype = type; + dev->ieee80211_ptr->iftype = type; + if (params) + dev->ieee80211_ptr->use_4addr = params->use_4addr; + break; + default: + r = -EINVAL; + break; + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_add_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_index, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + int r = 0; + u16 key_type = FAPI_KEYTYPE_GROUP; + + if (WARN_ON(pairwise && !mac_addr)) + return -EINVAL; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "(key_index:%d, pairwise:%d, address:%pM, cipher:0x%.8X, key_len:%d," + "vif_type:%d)\n", key_index, pairwise, mac_addr, params->cipher, params->key_len, + ndev_vif->vif_type); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "vif not active\n"); + goto exit; + } + + if (params->cipher == WLAN_CIPHER_SUITE_PMK) { + r = slsi_mlme_set_pmk(sdev, dev, params->key, params->key_len); + goto exit; + } + + if (mac_addr && pairwise) { + /* All Pairwise Keys will have a peer record. */ + peer = slsi_get_peer_from_mac(sdev, dev, mac_addr); + if (peer) + mac_addr = peer->address; + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + /* Sta Group Key will use the peer address */ + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (peer) + mac_addr = peer->address; + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && !pairwise) + /* AP Group Key will use the Interface address */ + mac_addr = dev->dev_addr; + else { + r = -EINVAL; + goto exit; + } + + /*Treat WEP key as pairwise key*/ + if ((ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + ((params->cipher == WLAN_CIPHER_SUITE_WEP40) || + (params->cipher == WLAN_CIPHER_SUITE_WEP104)) && peer) { + u8 bc_mac_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "WEP Key: store key\n"); + r = slsi_mlme_set_key(sdev, dev, key_index, FAPI_KEYTYPE_WEP, bc_mac_addr, params); + if (r == FAPI_RESULTCODE_SUCCESS) { + /* if static ip is set before connection, after setting keys enable powersave. */ + if (ndev_vif->ipaddress) + slsi_mlme_powermgt(sdev, dev, ndev_vif->set_power_mode); + } else { + SLSI_NET_ERR(dev, "Error adding WEP key\n"); + } + goto exit; + } + + if (pairwise) { + key_type = FAPI_KEYTYPE_PAIRWISE; + if (WARN_ON(!peer)) { + r = -EINVAL; + goto exit; + } + } else if (params->cipher == WLAN_CIPHER_SUITE_AES_CMAC) { + key_type = FAPI_KEYTYPE_IGTK; + } + + if (WARN(!mac_addr, "mac_addr not defined\n")) { + r = -EINVAL; + goto exit; + } + if (!((ndev_vif->vif_type == FAPI_VIFTYPE_AP) && (key_index == 4))) { + r = slsi_mlme_set_key(sdev, dev, key_index, key_type, mac_addr, params); + if (r) { + SLSI_NET_ERR(dev, "error in adding key (key_type: %d)\n", key_type); + goto exit; + } + } + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + ndev_vif->sta.eap_hosttag = 0xFFFF; + /* if static IP is set before connection, after setting keys enable powersave. */ + if (ndev_vif->ipaddress) + slsi_mlme_powermgt(sdev, dev, ndev_vif->set_power_mode); + } + + if (key_type == FAPI_KEYTYPE_GROUP) { + ndev_vif->sta.group_key_set = true; + ndev_vif->ap.cipher = params->cipher; + } else if (key_type == FAPI_KEYTYPE_PAIRWISE) { + if (peer) + peer->pairwise_key_set = true; + } + + if (peer) { + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + if (pairwise) { + if (params->cipher == WLAN_CIPHER_SUITE_SMS4) { /*WAPI */ + slsi_mlme_connect_resp(sdev, dev); + slsi_set_packet_filters(sdev, dev); + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + } + } + + if (ndev_vif->sta.gratuitous_arp_needed) { + ndev_vif->sta.gratuitous_arp_needed = false; + slsi_send_gratuitous_arp(sdev, dev); + } + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && pairwise) { + slsi_mlme_connected_resp(sdev, dev, peer->aid); + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + peer->connected_state = SLSI_STA_CONN_STATE_CONNECTED; + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) + ndev_vif->ap.p2p_gc_keys_set = true; + } + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_del_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_index, bool pairwise, const u8 *mac_addr) +{ + SLSI_UNUSED_PARAMETER(wiphy); + SLSI_UNUSED_PARAMETER(key_index); + SLSI_UNUSED_PARAMETER(pairwise); + SLSI_UNUSED_PARAMETER(mac_addr); + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_DELETEKEYS.request\n"); + return -EOPNOTSUPP; + } + + return 0; +} + +int slsi_get_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_index, bool pairwise, const u8 *mac_addr, + void *cookie, + void (*callback)(void *cookie, struct key_params *)) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct key_params params; + +#define SLSI_MAX_KEY_SIZE 8 /*used only for AP case, so WAPI not considered*/ + u8 key_seq[SLSI_MAX_KEY_SIZE] = { 0 }; + int r = 0; + + SLSI_UNUSED_PARAMETER(mac_addr); + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_GET_KEY_SEQUENCE.request\n"); + return -EOPNOTSUPP; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "(key_index:%d, pairwise:%d, mac_addr:%pM, vif_type:%d)\n", key_index, + pairwise, mac_addr, ndev_vif->vif_type); + + if (!ndev_vif->activated) { + SLSI_NET_ERR(dev, "vif not active\n"); + r = -EINVAL; + goto exit; + } + + /* The get_key call is expected only for AP vif with Group Key type */ + if (FAPI_VIFTYPE_AP != ndev_vif->vif_type) { + SLSI_NET_ERR(dev, "Invalid vif type: %d\n", ndev_vif->vif_type); + r = -EINVAL; + goto exit; + } + if (pairwise) { + SLSI_NET_ERR(dev, "Invalid key type\n"); + r = -EINVAL; + goto exit; + } + + memset(¶ms, 0, sizeof(params)); + /* Update params with sequence number, key field would be updated NULL */ + params.key = NULL; + params.key_len = 0; + params.cipher = ndev_vif->ap.cipher; + if (!((ndev_vif->vif_type == FAPI_VIFTYPE_AP) && (key_index == 4))) { + r = slsi_mlme_get_key(sdev, dev, key_index, FAPI_KEYTYPE_GROUP, key_seq, ¶ms.seq_len); + + if (!r) { + params.seq = key_seq; + callback(cookie, ¶ms); + } + } +#undef SLSI_MAX_KEY_SIZE +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +static size_t slsi_strip_wsc_p2p_ie(const u8 *src_ie, size_t src_ie_len, u8 *dest_ie, bool strip_wsc, bool strip_p2p) +{ + const u8 *ie; + const u8 *next_ie; + size_t dest_ie_len = 0; + + if (!dest_ie || !(strip_p2p || strip_wsc)) + return dest_ie_len; + + for (ie = src_ie; (ie - src_ie) < src_ie_len; ie = next_ie) { + next_ie = ie + ie[1] + 2; + + if (ie[0] == WLAN_EID_VENDOR_SPECIFIC && ie[1] > 4) { + int i; + unsigned int oui = 0; + + for (i = 0; i < 4; i++) + oui = (oui << 8) | ie[5 - i]; + + if (strip_wsc && (oui == SLSI_WPS_OUI_PATTERN)) + continue; + if (strip_p2p && (oui == SLSI_P2P_OUI_PATTERN)) + continue; + } + + if (next_ie - src_ie <= src_ie_len) { + memcpy(dest_ie + dest_ie_len, ie, ie[1] + 2); + dest_ie_len += ie[1] + 2; + } + } + + return dest_ie_len; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct net_device *dev = request->wdev->netdev; + +#else +int slsi_scan(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_scan_request *request) +{ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ + + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + u16 scan_type = FAPI_SCANTYPE_FULL_SCAN; + int r = 0; + u8 *scan_ie; + size_t scan_ie_len; + bool strip_wsc = false; + bool strip_p2p = false; + struct ieee80211_channel *channels[64]; + int i, chan_count = 0; + struct cfg80211_scan_info info = {.aborted = false}; +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + u8 mac_addr_mask[ETH_ALEN] = {0xFF}; +#endif + + if (WARN_ON(!request->wdev)) + return -EINVAL; + if (WARN_ON(!dev)) + return -EINVAL; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_WARN(dev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + /* Reject scan request if Group Formation is in progress */ + if (sdev->p2p_state == P2P_ACTION_FRAME_TX_RX) { + SLSI_NET_INFO(dev, "Scan received in P2P Action Frame Tx/Rx state - Reject\n"); + return -EBUSY; + } + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) { + SLSI_NET_INFO(dev, "Rejecting scan request as last scan is still running\n"); + r = -EBUSY; + goto exit; + } +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + if (ndev_vif->is_wips_running && (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + int ret = 0; + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "Scan invokes DRIVER_BCN_ABORT\n"); + ret = slsi_mlme_set_forward_beacon(sdev, dev, FAPI_ACTION_STOP); + + if (!ret) { + ret = slsi_send_forward_beacon_abort_vendor_event(sdev, + SLSI_FORWARD_BEACON_ABORT_REASON_SCANNING); + } + } +#endif + SLSI_NET_DBG3(dev, SLSI_CFG80211, "channels:%d, ssids:%d, ie_len:%d, vif_index:%d\n", request->n_channels, + request->n_ssids, (int)request->ie_len, ndev_vif->ifnum); + + for (i = 0; i < request->n_channels; i++) + channels[i] = request->channels[i]; + chan_count = request->n_channels; + + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + if (sdev->initial_scan) { + sdev->initial_scan = false; + scan_type = FAPI_SCANTYPE_INITIAL_SCAN; + } + ndev_vif->unsync.slsi_p2p_continuous_fullscan = false; + } + + /* Update scan timing for P2P social channels scan.*/ + if ((request->ie) && + cfg80211_find_vendor_ie(WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P, request->ie, request->ie_len) && + request->ssids && SLSI_IS_P2P_SSID(request->ssids[0].ssid, request->ssids[0].ssid_len)) { + /* In supplicant during joining procedure the P2P GO scan + * with GO's operating channel comes on P2P device. Hence added the + * check for n_channels as 1 + */ + if (!ndev_vif->drv_in_p2p_procedure) { + if (delayed_work_pending(&ndev_vif->unsync.unset_channel_expiry_work)) { + cancel_delayed_work(&ndev_vif->unsync.unset_channel_expiry_work); + slsi_mlme_unset_channel_req(sdev, dev); + ndev_vif->driver_channel = 0; + } + } + if (request->n_channels == SLSI_P2P_SOCIAL_CHAN_COUNT || request->n_channels == 1) { + scan_type = FAPI_SCANTYPE_P2P_SCAN_SOCIAL; + ndev_vif->unsync.slsi_p2p_continuous_fullscan = false; + } else if (request->n_channels > SLSI_P2P_SOCIAL_CHAN_COUNT) { + if (!ndev_vif->unsync.slsi_p2p_continuous_fullscan) { + scan_type = FAPI_SCANTYPE_P2P_SCAN_FULL; + ndev_vif->unsync.slsi_p2p_continuous_fullscan = true; + } else { + int count = 0, chann = 0; + + scan_type = FAPI_SCANTYPE_P2P_SCAN_SOCIAL; + ndev_vif->unsync.slsi_p2p_continuous_fullscan = false; + for (i = 0; i < request->n_channels; i++) { + chann = channels[i]->hw_value & 0xFF; + if (chann == 1 || chann == 6 || chann == 11) { + channels[count] = request->channels[i]; + count++; + } + } + chan_count = count; + } + } + } + + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif) && (request->ie)) { + const u8 *ie; + + /* check HS2 related bits in extended capabilties (interworking, WNM,QoS Map, BSS transition) and set in MIB*/ + r = slsi_mlme_set_hs2_ext_cap(sdev, dev, request->ie, request->ie_len); + if (r) + goto exit; + + /* Supplicant adds wsc and p2p in Station scan at the end of scan request ie. + * for non-wps case remove both wps and p2p IEs + * for wps case remove only p2p IE + */ + + ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPS, request->ie, request->ie_len); + if (ie && ie[1] > SLSI_WPS_REQUEST_TYPE_POS && + ie[SLSI_WPS_REQUEST_TYPE_POS] == SLSI_WPS_REQUEST_TYPE_ENROLEE_INFO_ONLY) + strip_wsc = true; + + ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P, request->ie, request->ie_len); + if (ie) + strip_p2p = true; + } + + if (strip_wsc || strip_p2p) { + scan_ie = kmalloc(request->ie_len, GFP_KERNEL); + if (!scan_ie) { + SLSI_NET_INFO(dev, "Out of memory for scan IEs\n"); + r = -ENOMEM; + goto exit; + } + scan_ie_len = slsi_strip_wsc_p2p_ie(request->ie, request->ie_len, scan_ie, strip_wsc, strip_p2p); + } else { + scan_ie = (u8 *)request->ie; + scan_ie_len = request->ie_len; + } + + /* Flush out any outstanding single scan timeout work */ + cancel_delayed_work(&ndev_vif->scan_timeout_work); + + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = false; + slsi_purge_scan_results(ndev_vif, SLSI_SCAN_HW_ID); + +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { + if (sdev->fw_mac_randomization_enabled) { + memcpy(sdev->scan_mac_addr, request->mac_addr, ETH_ALEN); + r = slsi_set_mac_randomisation_mask(sdev, request->mac_addr_mask); + if (!r) + sdev->scan_addr_set = 1; + } else { + SLSI_NET_INFO(dev, "Mac Randomization is not enabled in Firmware\n"); + sdev->scan_addr_set = 0; + } + } else +#endif + if (sdev->scan_addr_set) { + memset(mac_addr_mask, 0xFF, ETH_ALEN); + r = slsi_set_mac_randomisation_mask(sdev, mac_addr_mask); + sdev->scan_addr_set = 0; + } +#endif + + r = slsi_mlme_add_scan(sdev, + dev, + scan_type, + FAPI_REPORTMODE_REAL_TIME, + request->n_ssids, + request->ssids, + chan_count, + channels, + NULL, + scan_ie, + scan_ie_len, + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan); + + if (r != 0) { + if (r > 0) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Nothing to be done\n"); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + cfg80211_scan_done(request, &info); +#else + cfg80211_scan_done(request, false); +#endif + r = 0; + } else { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "add_scan error: %d\n", r); + r = -EIO; + } + } else { + ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req = request; + + /* if delayed work is already scheduled, queue delayed work fails. So set + * requeue_timeout_work flag to enqueue delayed work in the timeout handler + */ + if (queue_delayed_work(sdev->device_wq, &ndev_vif->scan_timeout_work, + msecs_to_jiffies(SLSI_FW_SCAN_DONE_TIMEOUT_MSEC))) + ndev_vif->scan[SLSI_SCAN_HW_ID].requeue_timeout_work = false; + else + ndev_vif->scan[SLSI_SCAN_HW_ID].requeue_timeout_work = true; + + /* Update State only for scan in Device role */ + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif) && (!SLSI_IS_P2P_GROUP_STATE(sdev))) { + if (scan_type == FAPI_SCANTYPE_P2P_SCAN_SOCIAL) + SLSI_P2P_STATE_CHANGE(sdev, P2P_SCANNING); + } else if (!SLSI_IS_VIF_INDEX_P2P(ndev_vif) && scan_ie_len) { + kfree(ndev_vif->probe_req_ies); + ndev_vif->probe_req_ies = kmalloc(request->ie_len, GFP_KERNEL); + if (!ndev_vif->probe_req_ies) /* Don't fail, continue as it would still work */ + ndev_vif->probe_req_ie_len = 0; + else { + ndev_vif->probe_req_ie_len = scan_ie_len; + memcpy(ndev_vif->probe_req_ies, scan_ie, scan_ie_len); + } + } + } + if (strip_p2p || strip_wsc) + kfree(scan_ie); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; +} + +int slsi_sched_scan_start(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *request) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r; + u8 *scan_ie; + size_t scan_ie_len; + bool strip_wsc = false; + bool strip_p2p = false; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_ADD_SCAN.request\n"); + return -EOPNOTSUPP; + } + + /* Allow sched_scan only on wlan0. For P2PCLI interface, sched_scan might get requested following a + * wlan0 scan and its results being shared to sibling interfaces. Reject sched_scan for other interfaces. + */ + if (!SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + SLSI_NET_INFO(dev, "Scheduled scan req received on vif %d - Reject\n", ndev_vif->ifnum); + return -EINVAL; + } + + /* Unlikely to get a schedule scan while Group formation is in progress. + * In case it is requested, it will be rejected. + */ + if (sdev->p2p_state == P2P_ACTION_FRAME_TX_RX) { + SLSI_NET_INFO(dev, "Scheduled scan req received in P2P Action Frame Tx/Rx state - Reject\n"); + return -EBUSY; + } + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "channels:%d, ssids:%d, ie_len:%d, vif_index:%d\n", request->n_channels, + request->n_ssids, (int)request->ie_len, ndev_vif->ifnum); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].sched_req) { + r = -EBUSY; + goto exit; + } + + if (request->ie) { + const u8 *ie; + /* Supplicant adds wsc and p2p in Station scan at the end of scan request ie. + * Remove both wps and p2p IEs. + * Scheduled scan is not used for wsc, So no need to check for wsc request type + */ + + ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPS, request->ie, request->ie_len); + if (ie) + strip_wsc = true; + + ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P, request->ie, request->ie_len); + if (ie) + strip_p2p = true; + } + + if (strip_wsc || strip_p2p) { + scan_ie = kmalloc(request->ie_len, GFP_KERNEL); + if (!scan_ie) { + SLSI_NET_INFO(dev, "Out of memory for scan IEs\n"); + r = -ENOMEM; + goto exit; + } + scan_ie_len = slsi_strip_wsc_p2p_ie(request->ie, request->ie_len, scan_ie, strip_wsc, strip_p2p); + } else { + scan_ie = (u8 *)request->ie; + scan_ie_len = request->ie_len; + } + + slsi_purge_scan_results(ndev_vif, SLSI_SCAN_SCHED_ID); + r = slsi_mlme_add_sched_scan(sdev, dev, request, scan_ie, scan_ie_len); + + if (strip_p2p || strip_wsc) + kfree(scan_ie); + + if (r != 0) { + if (r > 0) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Nothing to be done\n"); + cfg80211_sched_scan_stopped(wiphy, request->reqid); + r = 0; + } else { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "add_scan error: %d\n", r); + r = -EIO; + } + } else { + ndev_vif->scan[SLSI_SCAN_SCHED_ID].sched_req = request; + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) +int slsi_sched_scan_stop(struct wiphy *wiphy, struct net_device *dev, u64 reqid) +{ +#else +int slsi_sched_scan_stop(struct wiphy *wiphy, struct net_device *dev) +{ +#endif + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r = 0; + + SLSI_UNUSED_PARAMETER(reqid); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + SLSI_NET_DBG1(dev, SLSI_CFG80211, "vif_index:%d", ndev_vif->ifnum); + if (!ndev_vif->scan[SLSI_SCAN_SCHED_ID].sched_req) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "No sched scan req\n"); + goto exit; + } + + r = slsi_mlme_del_scan(sdev, dev, (ndev_vif->ifnum << 8 | SLSI_SCAN_SCHED_ID), false); + + ndev_vif->scan[SLSI_SCAN_SCHED_ID].sched_req = NULL; + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; +} + +static void slsi_abort_hw_scan(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Abort on-going scan, vif_index:%d," + "ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req:%p\n", ndev_vif->ifnum, + ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) { + (void)slsi_mlme_del_scan(sdev, dev, ndev_vif->ifnum << 8 | SLSI_SCAN_HW_ID, false); + slsi_scan_complete(sdev, dev, SLSI_SCAN_HW_ID, false); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); +} + +int slsi_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct netdev_vif *ndev_p2p_vif; + u8 device_address[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + int r = 0; + u16 capability = WLAN_CAPABILITY_ESS; + struct slsi_peer *peer; + u16 prev_vif_type; + u32 action_frame_bmap; + struct net_device *p2p_dev; + const u8 *bssid; + struct ieee80211_channel *channel; + const u8 *connected_ssid = NULL; + u8 peer_address[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + u16 center_freq = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_CONNECT.request\n"); + return -EOPNOTSUPP; + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + capability = sme->privacy ? IEEE80211_PRIVACY_ON : IEEE80211_PRIVACY_OFF; +#else + if (sme->privacy) + capability |= WLAN_CAPABILITY_PRIVACY; +#endif + + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { + SLSI_WARN(sdev, "device not started yet (device_state:%d)\n", sdev->device_state); + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + return -EINVAL; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (ndev_vif->sta.sta_bss) + SLSI_ETHER_COPY(peer_address, ndev_vif->sta.sta_bss->bssid); + + center_freq = sme->channel ? sme->channel->center_freq : 0; + + SLSI_NET_INFO(dev, "%.*s Freq=%d vifStatus=%d CurrBssid:%pM NewBssid:%pM Qinfo:%d ieLen:%d\n", + (int)sme->ssid_len, sme->ssid, center_freq, ndev_vif->sta.vif_status, + peer_address, sme->bssid, sdev->device_config.qos_info, (int)sme->ie_len); + +#ifdef CONFIG_SCSC_WIFILOGGER + SCSC_WLOG_PKTFATE_NEW_ASSOC(); + if (sme->bssid) { + SCSC_WLOG_DRIVER_EVENT(WLOG_NORMAL, WIFI_EVENT_ASSOCIATION_REQUESTED, 3, + WIFI_TAG_BSSID, ETH_ALEN, sme->bssid, + WIFI_TAG_SSID, sme->ssid_len, sme->ssid, + WIFI_TAG_CHANNEL, sizeof(u16), ¢er_freq); + // ?? WIFI_TAG_VENDOR_SPECIFIC, sizeof(RSSE), RSSE); + } +#endif + + if (SLSI_IS_HS2_UNSYNC_VIF(ndev_vif)) { + slsi_wlan_unsync_vif_deactivate(sdev, dev, true); + } else if (SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + SLSI_NET_ERR(dev, "Connect requested on incorrect vif\n"); + goto exit_with_error; + } + + if (WARN_ON(!sme->ssid)) + goto exit_with_error; + + if (WARN_ON(sme->ssid_len > IEEE80211_MAX_SSID_LEN)) + goto exit_with_error; + + if (sme->bssid) { + if ((SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) && (sdev->p2p_state == P2P_GROUP_FORMED_CLI)) { + p2p_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2PX_SWLAN); + if (p2p_dev) { + ndev_p2p_vif = netdev_priv(p2p_dev); + if (ndev_p2p_vif->sta.sta_bss) { + if (SLSI_ETHER_EQUAL(ndev_p2p_vif->sta.sta_bss->bssid, sme->bssid)) { + SLSI_NET_ERR(dev, "Connect Request Rejected\n"); + goto exit_with_error; + } + } + } + } + } + + if ((ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + /*reassociation*/ + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!peer)) + goto exit_with_error; + + if (!sme->bssid) { + SLSI_NET_ERR(dev, "Require bssid in reassoc but received null\n"); + goto exit_with_error; + } + if (!memcmp(peer->address, sme->bssid, ETH_ALEN)) { /*same bssid*/ + r = slsi_mlme_reassociate(sdev, dev); + if (r) { + SLSI_NET_ERR(dev, "Failed to reassociate : %d\n", r); + } else { + ndev_vif->sta.vif_status = SLSI_VIF_STATUS_CONNECTING; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + } + goto exit; + } else { /*different bssid*/ + connected_ssid = cfg80211_find_ie(WLAN_EID_SSID, ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len); + + if (!connected_ssid) { + SLSI_NET_ERR(dev, "Require ssid in roam but received null\n"); + goto exit_with_error; + } + + if (!memcmp(&connected_ssid[2], sme->ssid, connected_ssid[1])) { /*same ssid*/ + if (!sme->channel) { + SLSI_NET_ERR(dev, "Roaming has been rejected, as sme->channel is null\n"); + goto exit_with_error; + } + r = slsi_mlme_roam(sdev, dev, sme->bssid, sme->channel->center_freq); + if (r) { + SLSI_NET_ERR(dev, "Failed to roam : %d\n", r); + goto exit_with_error; + } + goto exit; + } else { + SLSI_NET_ERR(dev, "Connected but received connect to new ESS, without disconnect"); + goto exit_with_error; + } + } + } + /* Sta started case */ + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (SLSI_IS_VIF_INDEX_MHS(sdev, ndev_vif)) + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) { + SLSI_NET_ERR(dev, "Iftype: %d\n", ndev_vif->iftype); + goto exit_with_error; + } +#endif /*wifi sharing*/ + if (WARN_ON(ndev_vif->activated)) { + SLSI_NET_ERR(dev, "Vif is activated: %d\n", ndev_vif->activated); + goto exit_with_error; + } + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION && + ndev_vif->sta.vif_status != SLSI_VIF_STATUS_UNSPECIFIED) { + SLSI_NET_ERR(dev, "VIF status: %d\n", ndev_vif->sta.vif_status); + goto exit_with_error; + } + prev_vif_type = ndev_vif->vif_type; + + prev_vif_type = ndev_vif->vif_type; + switch (ndev_vif->iftype) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + ndev_vif->iftype = NL80211_IFTYPE_STATION; + dev->ieee80211_ptr->iftype = NL80211_IFTYPE_STATION; + action_frame_bmap = SLSI_STA_ACTION_FRAME_BITMAP; +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + if (sdev->device_config.wes_mode) + action_frame_bmap |= SLSI_ACTION_FRAME_VENDOR_SPEC; +#endif + break; + case NL80211_IFTYPE_P2P_CLIENT: + slsi_p2p_group_start_remove_unsync_vif(sdev); + p2p_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2P); + if (p2p_dev) + SLSI_ETHER_COPY(device_address, p2p_dev->dev_addr); + action_frame_bmap = SLSI_ACTION_FRAME_PUBLIC; + break; + default: + SLSI_NET_ERR(dev, "Invalid Device Type: %d\n", ndev_vif->iftype); + goto exit_with_error; + } + + /* Initial Roaming checks done - assign vif type */ + ndev_vif->vif_type = FAPI_VIFTYPE_STATION; + + channel = sme->channel; + bssid = sme->bssid; + ndev_vif->sta.sta_bss = cfg80211_get_bss(wiphy, + sme->channel, + sme->bssid, + sme->ssid, + sme->ssid_len, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + IEEE80211_BSS_TYPE_ANY, +#else + capability, +#endif + capability); + if (!ndev_vif->sta.sta_bss) { + struct cfg80211_ssid ssid; + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "BSS info is not available - Perform scan\n"); + ssid.ssid_len = sme->ssid_len; + memcpy(ssid.ssid, sme->ssid, ssid.ssid_len); + r = slsi_mlme_connect_scan(sdev, dev, 1, &ssid, sme->channel); + if (r) { + SLSI_NET_ERR(dev, "slsi_mlme_connect_scan failed\n"); + goto exit; + } + ndev_vif->sta.sta_bss = cfg80211_get_bss(wiphy, + sme->channel, + sme->bssid, + sme->ssid, + sme->ssid_len, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + IEEE80211_BSS_TYPE_ANY, +#else + capability, +#endif + capability); + if (!ndev_vif->sta.sta_bss) { + SLSI_NET_ERR(dev, "cfg80211_get_bss(%.*s, %pM) Not found\n", (int)sme->ssid_len, sme->ssid, sme->bssid); + /*Set previous status in case of failure */ + ndev_vif->vif_type = prev_vif_type; + r = -ENOENT; + goto exit; + } + channel = ndev_vif->sta.sta_bss->channel; + bssid = ndev_vif->sta.sta_bss->bssid; + } else { + channel = ndev_vif->sta.sta_bss->channel; + bssid = ndev_vif->sta.sta_bss->bssid; + } + + ndev_vif->channel_type = NL80211_CHAN_NO_HT; + ndev_vif->chan = channel; + + if (slsi_mlme_add_vif(sdev, dev, dev->dev_addr, device_address) != 0) { + SLSI_NET_ERR(dev, "slsi_mlme_add_vif failed\n"); + goto exit_with_bss; + } + if (slsi_vif_activated(sdev, dev) != 0) { + SLSI_NET_ERR(dev, "slsi_vif_activated failed\n"); + goto exit_with_vif; + } + if (slsi_mlme_register_action_frame(sdev, dev, action_frame_bmap, action_frame_bmap) != 0) { + SLSI_NET_ERR(dev, "Action frame registration failed for bitmap value %d\n", action_frame_bmap); + goto exit_with_vif; + } + + r = slsi_set_boost(sdev, dev); + if (r != 0) + SLSI_NET_ERR(dev, "Rssi Boost set failed: %d\n", r); + + /* add_info_elements with Probe Req IEs. Proceed even if confirm fails for add_info as it would + * still work if the fw pre-join scan does not include the vendor IEs + */ + if (ndev_vif->probe_req_ies) { + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) { + if (sme->crypto.wpa_versions == 2) + ndev_vif->delete_probe_req_ies = true; /* Stored Probe Req can be deleted at vif + * deletion after WPA2 association + */ + else + /* Retain stored Probe Req at vif deletion until WPA2 connection to allow Probe req */ + ndev_vif->delete_probe_req_ies = false; + } else { + ndev_vif->delete_probe_req_ies = true; /* Delete stored Probe Req at vif deletion for STA */ + } + (void)slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_PROBE_REQUEST, ndev_vif->probe_req_ies, + ndev_vif->probe_req_ie_len); + } + + /* Sometimes netif stack takes more time to initialize and any packet + * sent to stack would be dropped. This behavior is random in nature, + * so start the netif stack before sending out the connect req, it shall + * give enough time to netstack to initialize. + */ + netif_carrier_on(dev); + ndev_vif->sta.vif_status = SLSI_VIF_STATUS_CONNECTING; + +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG + if (sme->auth_type == NL80211_AUTHTYPE_SAE && (sme->flags & CONNECT_REQ_EXTERNAL_AUTH_SUPPORT)) { + const u8 *rsn; + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "N AKM Suites: : %1d\n", sme->crypto.n_akm_suites); + ndev_vif->sta.crypto.wpa_versions = 3; + rsn = cfg80211_find_ie(WLAN_EID_RSN, sme->ie, sme->ie_len); + if (rsn) { + int pos; + + pos = 7 + 2 + (rsn[8] * 4) + 2; + ndev_vif->sta.crypto.akm_suites[0] = ((rsn[pos + 4] << 24) | (rsn[pos + 3] << 16) | (rsn[pos + 2] << 8) | (rsn[pos + 1])); + } + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "RSN IE: : %1d\n", ndev_vif->sta.crypto.akm_suites[0]); + } else { + ndev_vif->sta.crypto.wpa_versions = 0; + } +#endif + + r = slsi_mlme_connect(sdev, dev, sme, channel, bssid); + if (r != 0) { + ndev_vif->sta.is_wps = false; + SLSI_NET_ERR(dev, "connect failed: %d\n", r); + netif_carrier_off(dev); + goto exit_with_vif; + } + + peer = slsi_peer_add(sdev, dev, (u8 *)bssid, SLSI_STA_PEER_QUEUESET + 1); + ndev_vif->sta.resp_id = 0; + + if (!peer) + goto exit_with_error; + + goto exit; + +exit_with_vif: + slsi_mlme_del_vif(sdev, dev); + slsi_vif_deactivated(sdev, dev); +exit_with_bss: + if (ndev_vif->sta.sta_bss) { + slsi_cfg80211_put_bss(wiphy, ndev_vif->sta.sta_bss); + ndev_vif->sta.sta_bss = NULL; + } +exit_with_error: + r = -EINVAL; +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + return r; +} + +int slsi_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + int r = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "reason: %d, vif_index = %d, vif_type = %d\n", reason_code, + ndev_vif->ifnum, ndev_vif->vif_type); + + /* Assuming that the time it takes the firmware to disconnect is not significant + * as this function holds the locks until the MLME-DISCONNECT-IND comes back. + * Unless the MLME-DISCONNECT-CFM fails. + */ + if (!ndev_vif->activated) { + r = 0; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) + cfg80211_disconnected(dev, reason_code, NULL, 0, false, GFP_KERNEL); +#else + cfg80211_disconnected(dev, reason_code, NULL, 0, GFP_KERNEL); +#endif + SLSI_NET_INFO(dev, "Vif is already Deactivated\n"); + goto exit; + } + + peer = ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET]; + +#ifdef CONFIG_SCSC_WIFILOGGER + SCSC_WLOG_DRIVER_EVENT(WLOG_NORMAL, WIFI_EVENT_DISASSOCIATION_REQUESTED, 2, + WIFI_TAG_BSSID, ETH_ALEN, peer->address, + WIFI_TAG_REASON_CODE, sizeof(u16), &reason_code); +#endif + + switch (ndev_vif->vif_type) { + case FAPI_VIFTYPE_STATION: + { + slsi_reset_throughput_stats(dev); + /* Disconnecting spans several host firmware interactions so track the status + * so that the Host can ignore connect related signaling eg. MLME-CONNECT-IND + * now that it has triggered a disconnect. + */ + ndev_vif->sta.vif_status = SLSI_VIF_STATUS_DISCONNECTING; + + netif_carrier_off(dev); + if (peer->valid) + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + + /* MLME-DISCONNECT_CFM only means that the firmware has accepted the request it has not yet + * disconnected. Completion of the disconnect is indicated by MLME-DISCONNECT-IND, so have + * to wait for that before deleting the VIF. Also any new activities eg. connect can not yet + * be started on the VIF until the disconnection is completed. So the MLME function also handles + * waiting for the MLME-DISCONNECT-IND (if the CFM is successful) + */ + + r = slsi_mlme_disconnect(sdev, dev, peer->address, reason_code, true); + if (r != 0) + SLSI_NET_ERR(dev, "Disconnection returned with failure\n"); + /* Even if we fail to disconnect cleanly, tidy up. */ + r = slsi_handle_disconnect(sdev, dev, peer->address, 0); + + break; + } + default: + SLSI_NET_WARN(dev, "Invalid - vif type:%d, device type:%d)\n", ndev_vif->vif_type, ndev_vif->iftype); + r = -EINVAL; + break; + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_set_default_key(struct wiphy *wiphy, struct net_device *dev, + u8 key_index, bool unicast, bool multicast) +{ + SLSI_UNUSED_PARAMETER(wiphy); + SLSI_UNUSED_PARAMETER(dev); + SLSI_UNUSED_PARAMETER(key_index); + SLSI_UNUSED_PARAMETER(unicast); + SLSI_UNUSED_PARAMETER(multicast); + /* Key is set in add_key. Nothing to do here */ + return 0; +} + +int slsi_config_default_mgmt_key(struct wiphy *wiphy, + struct net_device *dev, + u8 key_index) +{ + SLSI_UNUSED_PARAMETER(wiphy); + SLSI_UNUSED_PARAMETER(key_index); + SLSI_UNUSED_PARAMETER(dev); + + return 0; +} + +int slsi_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r = 0; + + SLSI_DBG1(sdev, SLSI_CFG80211, "slsi_set_wiphy_parms Frag Threshold = %d, RTS Threshold = %d", + wiphy->frag_threshold, wiphy->rts_threshold); + + if ((changed & WIPHY_PARAM_FRAG_THRESHOLD) && (wiphy->frag_threshold != -1)) { + r = slsi_set_uint_mib(sdev, NULL, SLSI_PSID_DOT11_FRAGMENTATION_THRESHOLD, wiphy->frag_threshold); + if (r != 0) { + SLSI_ERR(sdev, "Setting FRAG_THRESHOLD failed\n"); + return r; + } + } + + if ((changed & WIPHY_PARAM_RTS_THRESHOLD) && (wiphy->rts_threshold != -1)) { + r = slsi_set_uint_mib(sdev, NULL, SLSI_PSID_DOT11_RTS_THRESHOLD, wiphy->rts_threshold); + if (r != 0) { + SLSI_ERR(sdev, "Setting RTS_THRESHOLD failed\n"); + return r; + } + } + + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, int mbm) +#else +int slsi_set_tx_power(struct wiphy *wiphy, + enum nl80211_tx_power_setting type, int mbm) +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + SLSI_UNUSED_PARAMETER(wdev); + SLSI_UNUSED_PARAMETER(type); +#endif + SLSI_UNUSED_PARAMETER(mbm); + SLSI_UNUSED_PARAMETER(sdev); + + r = -EOPNOTSUPP; + + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, int *dbm) +#else +int slsi_get_tx_power(struct wiphy *wiphy, int *dbm) +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + SLSI_UNUSED_PARAMETER(wdev); +#endif + SLSI_UNUSED_PARAMETER(dbm); + SLSI_UNUSED_PARAMETER(sdev); + + r = -EOPNOTSUPP; + + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) +int slsi_del_station(struct wiphy *wiphy, struct net_device *dev, + struct station_del_parameters *del_params) +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) +int slsi_del_station(struct wiphy *wiphy, struct net_device *dev, + const u8 *mac) +#else +int slsi_del_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac) +#endif +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + int r = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) + const u8 *mac = del_params->mac; +#endif + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "%pM, vifType:%d, vifIndex:%d vifActivated:%d ap.p2p_gc_keys_set = %d\n", + mac, ndev_vif->vif_type, ndev_vif->ifnum, ndev_vif->activated, ndev_vif->ap.p2p_gc_keys_set); + + /* Function is called by cfg80211 before the VIF is added */ + if (!ndev_vif->activated) + goto exit; + + if (FAPI_VIFTYPE_AP != ndev_vif->vif_type) { + r = -EINVAL; + goto exit; + } + /* MAC with NULL value will come in case of flushing VLANS . Ignore this.*/ + if (!mac) { + goto exit; + } else if (is_broadcast_ether_addr(mac)) { + int i = 0; + + while (i < SLSI_PEER_INDEX_MAX) { + peer = ndev_vif->peer_sta_record[i]; + if (peer && peer->valid) { + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + } + ++i; + } + + /* Note AP :: mlme_disconnect_request with broadcast mac address is + * not required. Other third party devices don't support this. Conclusively, + * BIP support is not present with AP + */ + + /* Free WPA and WMM IEs if present */ + slsi_clear_cached_ies(&ndev_vif->ap.cache_wpa_ie, &ndev_vif->ap.wpa_ie_len); + slsi_clear_cached_ies(&ndev_vif->ap.cache_wmm_ie, &ndev_vif->ap.wmm_ie_len); + + netif_carrier_off(dev); + + /* All STA related packets and info should already have been flushed */ + slsi_mlme_del_vif(sdev, dev); + slsi_vif_deactivated(sdev, dev); + ndev_vif->ipaddress = cpu_to_be32(0); + + if (ndev_vif->ap.p2p_gc_keys_set) { + slsi_wakeunlock(&sdev->wlan_wl); + ndev_vif->ap.p2p_gc_keys_set = false; + } + } else { + peer = slsi_get_peer_from_mac(sdev, dev, mac); + if (peer) { /* To handle race condition when disconnect_req is sent before procedure_strted_ind and before mlme-connected_ind*/ + if (peer->connected_state == SLSI_STA_CONN_STATE_CONNECTING) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "SLSI_STA_CONN_STATE_CONNECTING : mlme-disconnect-req dropped at driver\n"); + goto exit; + } + if (peer->is_wps) { + /* To inter-op with Intel STA in P2P cert need to discard the deauth after successful WPS handshake as a P2P GO */ + SLSI_NET_INFO(dev, "DISCONNECT after WPS : mlme-disconnect-req dropped at driver\n"); + goto exit; + } + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + r = slsi_mlme_disconnect(sdev, dev, peer->address, WLAN_REASON_DEAUTH_LEAVING, true); + if (r != 0) + SLSI_NET_ERR(dev, "Disconnection returned with failure\n"); + /* Even if we fail to disconnect cleanly, tidy up. */ + r = slsi_handle_disconnect(sdev, dev, peer->address, WLAN_REASON_DEAUTH_LEAVING); + } + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) +int slsi_get_station(struct wiphy *wiphy, struct net_device *dev, + const u8 *mac, struct station_info *sinfo) +#else +int slsi_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo) +#endif +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + int r = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + r = -EINVAL; + goto exit; + } + + peer = slsi_get_peer_from_mac(sdev, dev, mac); + if (!peer) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "%pM : Not Found\n", mac); + r = -EINVAL; + goto exit; + } + + if (((ndev_vif->iftype == NL80211_IFTYPE_STATION && !(ndev_vif->sta.roam_in_progress)) || + ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT)) { + /*Read MIB and fill into the peer.sinfo*/ + r = slsi_mlme_get_sinfo_mib(sdev, dev, peer); + if (r) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "failed to read Station Info Error:%d\n", r); + goto exit; + } + } + + *sinfo = peer->sinfo; + sinfo->generation = ndev_vif->cfg80211_sinfo_generation; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + SLSI_NET_DBG1(dev, SLSI_CFG80211, "%pM, tx:%d, txbytes:%llu, rx:%d, rxbytes:%llu tx_fail:%d tx_retry:%d\n", + mac, + peer->sinfo.tx_packets, + peer->sinfo.tx_bytes, + peer->sinfo.rx_packets, + peer->sinfo.rx_bytes, + peer->sinfo.tx_failed, + peer->sinfo.tx_retries); +#else + SLSI_NET_DBG1(dev, SLSI_CFG80211, "%pM, tx:%d, txbytes:%d, rx:%d, rxbytes:%d tx_fail:%d tx_retry:%d\n", + mac, + peer->sinfo.tx_packets, + peer->sinfo.tx_bytes, + peer->sinfo.rx_packets, + peer->sinfo.rx_bytes, + peer->sinfo.tx_failed, + peer->sinfo.tx_retries); +#endif + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, + bool enabled, int timeout) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = -EINVAL; + u16 pwr_mode = enabled ? FAPI_POWERMANAGEMENTMODE_POWER_SAVE : FAPI_POWERMANAGEMENTMODE_ACTIVE_MODE; + + SLSI_UNUSED_PARAMETER(timeout); + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_POWERMGT.request\n"); + return -EOPNOTSUPP; + } + + if (slsi_is_rf_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, RF test does not support.\n"); + return -EOPNOTSUPP; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "enabled:%d, vif_type:%d, vif_index:%d\n", enabled, ndev_vif->vif_type, + ndev_vif->ifnum); + + if ((ndev_vif->activated) && (ndev_vif->vif_type == FAPI_VIFTYPE_STATION)) { + ndev_vif->set_power_mode = pwr_mode; + r = slsi_mlme_powermgt(sdev, dev, pwr_mode); + } else { + r = 0; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 18)) +int slsi_tdls_oper(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, enum nl80211_tdls_operation oper) +#else +int slsi_tdls_oper(struct wiphy *wiphy, struct net_device *dev, u8 *peer, enum nl80211_tdls_operation oper) +#endif +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "oper:%d, vif_type:%d, vif_index:%d\n", oper, ndev_vif->vif_type, + ndev_vif->ifnum); + + if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) + return -ENOTSUPP; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((!ndev_vif->activated) || SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + r = -ENOTSUPP; + goto exit; + } + + switch (oper) { + case NL80211_TDLS_DISCOVERY_REQ: + SLSI_NET_DBG1(dev, SLSI_CFG80211, "NL80211_TDLS_DISCOVERY_REQ\n"); + r = slsi_mlme_tdls_action(sdev, dev, peer, FAPI_TDLSACTION_DISCOVERY, 0, 0); + break; + case NL80211_TDLS_SETUP: + r = slsi_mlme_tdls_action(sdev, dev, peer, FAPI_TDLSACTION_SETUP, 0, 0); + break; + case NL80211_TDLS_TEARDOWN: + r = slsi_mlme_tdls_action(sdev, dev, peer, FAPI_TDLSACTION_TEARDOWN, 0, 0); + break; + default: + r = -EOPNOTSUPP; + goto exit; + } +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) +int slsi_set_qos_map(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_qos_map *qos_map) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + int r = 0; + + /* Cleaning up is inherently taken care by driver */ + if (!qos_map) + return r; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + r = -EINVAL; + goto exit; + } + + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + r = -EINVAL; + goto exit; + } + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "Set QoS Map\n"); + peer = ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET]; + if (!peer || !peer->valid) { + r = -EINVAL; + goto exit; + } + + memcpy(&peer->qos_map, qos_map, sizeof(struct cfg80211_qos_map)); + peer->qos_map_set = true; + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_DEBUG +int slsi_set_monitor_channel(struct wiphy *wiphy, struct cfg80211_chan_def *chandef) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev; + struct netdev_vif *ndev_vif; + + SLSI_DBG1(sdev, SLSI_CFG80211, "channel (freq:%u)\n", chandef->chan->center_freq); + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, SLSI_NET_INDEX_WLAN); + if (!dev) { + SLSI_ERR(sdev, "netdev No longer exists\n"); + rcu_read_unlock(); + return -EINVAL; + } + ndev_vif = netdev_priv(dev); + rcu_read_unlock(); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (slsi_test_sap_configure_monitor_mode(sdev, dev, chandef) != 0) { + SLSI_ERR(sdev, "set Monitor channel failed\n"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EINVAL; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return 0; +} +#endif +#endif +int slsi_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wow) +{ + SLSI_UNUSED_PARAMETER(wow); + + return 0; +} + +int slsi_resume(struct wiphy *wiphy) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + + /* Scheduling the IO thread */ +/* (void)slsi_hip_run_bh(sdev); */ + SLSI_UNUSED_PARAMETER(sdev); + + return 0; +} + +int slsi_set_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa) +{ + SLSI_UNUSED_PARAMETER(wiphy); + SLSI_UNUSED_PARAMETER(dev); + SLSI_UNUSED_PARAMETER(pmksa); + return 0; +} + +int slsi_del_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa) +{ + SLSI_UNUSED_PARAMETER(wiphy); + SLSI_UNUSED_PARAMETER(dev); + SLSI_UNUSED_PARAMETER(pmksa); + return 0; +} + +int slsi_flush_pmksa(struct wiphy *wiphy, struct net_device *dev) +{ + SLSI_UNUSED_PARAMETER(wiphy); + SLSI_UNUSED_PARAMETER(dev); + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, + u64 *cookie) +{ + struct net_device *dev = wdev->netdev; + +#else +int slsi_remain_on_channel(struct wiphy *wiphy, + struct net_device *dev, + struct ieee80211_channel *chan, + enum nl80211_channel_type channel_type, + unsigned int duration, + u64 *cookie) +{ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { + SLSI_WARN(sdev, "device not started yet (device_state:%d)\n", sdev->device_state); + goto exit_with_error; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "channel freq = %d, duration = %d, vif_type = %d, vif_index = %d," + "sdev->p2p_state = %s\n", chan->center_freq, duration, ndev_vif->vif_type, ndev_vif->ifnum, + slsi_p2p_state_text(sdev->p2p_state)); + if (!SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + SLSI_NET_ERR(dev, "Invalid vif type\n"); + goto exit_with_error; + } + + if (SLSI_IS_P2P_GROUP_STATE(sdev)) { + slsi_assign_cookie_id(cookie, &ndev_vif->unsync.roc_cookie); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_ready_on_channel(wdev, *cookie, chan, duration, GFP_KERNEL); + cfg80211_remain_on_channel_expired(wdev, *cookie, chan, GFP_KERNEL); +#else + cfg80211_ready_on_channel(dev, *cookie, chan, channel_type, duration, GFP_KERNEL); + cfg80211_remain_on_channel_expired(dev, *cookie, chan, channel_type, GFP_KERNEL); +#endif + goto exit; + } + + /* Unsync vif will be required, cancel any pending work of its deletion */ + cancel_delayed_work(&ndev_vif->unsync.del_vif_work); + + /* Ideally, there should not be any ROC work pending. However, supplicant can send back to back ROC in a race scenario as below. + * If action frame is received while P2P social scan, the response frame tx is delayed till scan completes. After scan completion, + * frame tx is done and ROC is started. Upon frame tx status, supplicant sends another ROC without cancelling the previous one. + */ + cancel_delayed_work(&ndev_vif->unsync.roc_expiry_work); + + if (delayed_work_pending(&ndev_vif->unsync.unset_channel_expiry_work)) + cancel_delayed_work(&ndev_vif->unsync.unset_channel_expiry_work); + + /* If action frame tx is in progress and ROC comes, then it would mean action frame tx was done in ROC and + * frame tx ind is awaited, don't change state. Also allow back to back ROC in case it comes. + */ + if ((sdev->p2p_state == P2P_ACTION_FRAME_TX_RX) || (sdev->p2p_state == P2P_LISTENING)) { + goto exit_with_roc; + } + + /* Unsync vif activation: Possible P2P state at this point is P2P_IDLE_NO_VIF or P2P_IDLE_VIF_ACTIVE */ + if (sdev->p2p_state == P2P_IDLE_NO_VIF) { + if (slsi_p2p_vif_activate(sdev, dev, chan, duration, true) != 0) + goto exit_with_error; + } else if (sdev->p2p_state == P2P_IDLE_VIF_ACTIVE) { + /* Configure Probe Response IEs in firmware if they have changed */ + if (ndev_vif->unsync.ies_changed) { + u16 purpose = FAPI_PURPOSE_PROBE_RESPONSE; + + if (slsi_mlme_add_info_elements(sdev, dev, purpose, ndev_vif->unsync.probe_rsp_ies, ndev_vif->unsync.probe_rsp_ies_len) != 0) { + SLSI_NET_ERR(dev, "Probe Rsp IEs setting failed\n"); + goto exit_with_vif; + } + ndev_vif->unsync.ies_changed = false; + } + /* Channel Setting - Don't set if already on same channel */ + if (ndev_vif->driver_channel != chan->hw_value) { + if (slsi_mlme_set_channel(sdev, dev, chan, SLSI_FW_CHANNEL_DURATION_UNSPECIFIED, 0, 0) != 0) { + SLSI_NET_ERR(dev, "Channel setting failed\n"); + goto exit_with_vif; + } else { + ndev_vif->chan = chan; + ndev_vif->driver_channel = chan->hw_value; + } + } + } else { + SLSI_NET_ERR(dev, "Driver in incorrect P2P state (%s)", slsi_p2p_state_text(sdev->p2p_state)); + goto exit_with_error; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 9)) + ndev_vif->channel_type = channel_type; +#endif + + SLSI_P2P_STATE_CHANGE(sdev, P2P_LISTENING); + +exit_with_roc: + /* Cancel remain on channel is sent to the supplicant 10ms before the duration + *This is to avoid the race condition of supplicant sending cancel remain on channel and + *drv sending cancel_remain on channel because of roc expiry. + *This race condition causes delay to the next p2p search + */ + queue_delayed_work(sdev->device_wq, &ndev_vif->unsync.roc_expiry_work, + msecs_to_jiffies(duration - SLSI_P2P_ROC_EXTRA_MSEC)); + + slsi_assign_cookie_id(cookie, &ndev_vif->unsync.roc_cookie); + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Cookie = 0x%llx\n", *cookie); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_ready_on_channel(wdev, *cookie, chan, duration, GFP_KERNEL); +#else + cfg80211_ready_on_channel(dev, *cookie, chan, channel_type, duration, GFP_KERNEL); +#endif + + goto exit; + +exit_with_vif: + slsi_p2p_vif_deactivate(sdev, dev, true); +exit_with_error: + r = -EINVAL; +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + u64 cookie) +{ + struct net_device *dev = wdev->netdev; + +#else +int slsi_cancel_remain_on_channel(struct wiphy *wiphy, + struct net_device *dev, + u64 cookie) +{ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Cookie = 0x%llx, vif_type = %d, vif_index = %d, sdev->p2p_state = %s," + "ndev_vif->ap.p2p_gc_keys_set = %d, ndev_vif->unsync.roc_cookie = 0x%llx\n", cookie, + ndev_vif->vif_type, ndev_vif->ifnum, slsi_p2p_state_text(sdev->p2p_state), + ndev_vif->ap.p2p_gc_keys_set, ndev_vif->unsync.roc_cookie); + + if (!SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + SLSI_NET_ERR(dev, "Invalid vif type\n"); + r = -EINVAL; + goto exit; + } + + if (!((sdev->p2p_state == P2P_LISTENING) || (sdev->p2p_state == P2P_ACTION_FRAME_TX_RX))) { + goto exit; + } + + if (sdev->p2p_state == P2P_ACTION_FRAME_TX_RX && ndev_vif->mgmt_tx_data.exp_frame != SLSI_P2P_PA_INVALID) { + /* Reset the expected action frame as procedure got completed */ + SLSI_INFO(sdev, "Action frame (%s) was not received\n", slsi_p2p_pa_subtype_text(ndev_vif->mgmt_tx_data.exp_frame)); + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + } + + cancel_delayed_work(&ndev_vif->unsync.roc_expiry_work); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_remain_on_channel_expired(&ndev_vif->wdev, ndev_vif->unsync.roc_cookie, ndev_vif->chan, GFP_KERNEL); +#else + cfg80211_remain_on_channel_expired(ndev_vif->wdev.netdev, ndev_vif->unsync.roc_cookie, + ndev_vif->chan, ndev_vif->channel_type, GFP_KERNEL); +#endif + if (!ndev_vif->drv_in_p2p_procedure) { + if (delayed_work_pending(&ndev_vif->unsync.unset_channel_expiry_work)) + cancel_delayed_work(&ndev_vif->unsync.unset_channel_expiry_work); + queue_delayed_work(sdev->device_wq, &ndev_vif->unsync.unset_channel_expiry_work, + msecs_to_jiffies(SLSI_P2P_UNSET_CHANNEL_EXTRA_MSEC)); + } + /* Queue work to delete unsync vif */ + slsi_p2p_queue_unsync_vif_del_work(ndev_vif, SLSI_P2P_UNSYNC_VIF_EXTRA_MSEC); + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_change_bss(struct wiphy *wiphy, struct net_device *dev, + struct bss_parameters *params) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int r = 0; + + SLSI_UNUSED_PARAMETER(params); + SLSI_UNUSED_PARAMETER(sdev); + + return r; +} + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 5, 0)) +int slsi_set_channel(struct wiphy *wiphy, struct net_device *dev, + struct ieee80211_channel *chan, + enum nl80211_channel_type channel_type) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + + SLSI_UNUSED_PARAMETER(sdev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "channel_type:%u, freq:%u, vif_index:%d, vif_type:%d\n", channel_type, + chan->center_freq, ndev_vif->ifnum, ndev_vif->vif_type); + if (WARN_ON(ndev_vif->activated)) { + r = -EINVAL; + goto exit; + } + + ndev_vif->channel_type = channel_type; + ndev_vif->chan = chan; + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} +#endif /* (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 5, 0)) */ + +static void slsi_ap_start_obss_scan(struct slsi_dev *sdev, struct net_device *dev, struct netdev_vif *ndev_vif) +{ + struct cfg80211_ssid ssids; + struct ieee80211_channel *channel; + int n_ssids = 1, n_channels = 1, i; + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "channel %u\n", ndev_vif->chan->hw_value); + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + ssids.ssid_len = 0; + for (i = 0; i < IEEE80211_MAX_SSID_LEN; i++) + ssids.ssid[i] = 0x00; /* Broadcast SSID */ + + channel = ieee80211_get_channel(sdev->wiphy, ndev_vif->chan->center_freq); + + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = true; + (void)slsi_mlme_add_scan(sdev, + dev, + FAPI_SCANTYPE_OBSS_SCAN, + FAPI_REPORTMODE_REAL_TIME, + n_ssids, + &ssids, + n_channels, + &channel, + NULL, + NULL, /* No IEs */ + 0, + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan /* Wait for scan_done_ind */); + + slsi_ap_obss_scan_done_ind(dev, ndev_vif); + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = false; + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); +} + +static int slsi_ap_start_validate(struct net_device *dev, struct slsi_dev *sdev, struct cfg80211_ap_settings *settings) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + SLSI_NET_ERR(dev, "AP start requested on incorrect vif\n"); + goto exit_with_error; + } + + if (!settings->ssid_len || !settings->ssid) { + SLSI_NET_ERR(dev, "SSID not provided\n"); + goto exit_with_error; + } + + if (!settings->beacon.head_len || !settings->beacon.head) { + SLSI_NET_ERR(dev, "Beacon not provided\n"); + goto exit_with_error; + } + + if (!settings->beacon_interval) { + SLSI_NET_ERR(dev, "Beacon Interval not provided\n"); + goto exit_with_error; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + ndev_vif->chandef = &settings->chandef; + ndev_vif->chan = ndev_vif->chandef->chan; +#endif + if (WARN_ON(!ndev_vif->chan)) + goto exit_with_error; + + if (WARN_ON(ndev_vif->activated)) + goto exit_with_error; + + if (WARN_ON((ndev_vif->iftype != NL80211_IFTYPE_AP) && (ndev_vif->iftype != NL80211_IFTYPE_P2P_GO))) + goto exit_with_error; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if ((ndev_vif->chan->hw_value <= 14) && (!sdev->fw_2g_40mhz_enabled) && + (ndev_vif->chandef->width == NL80211_CHAN_WIDTH_40)) { + SLSI_NET_ERR(dev, "Configuration error: 40 MHz on 2.4 GHz is not supported. Channel_no: %d Channel_width: %d\n", ndev_vif->chan->hw_value, slsi_get_chann_info(sdev, ndev_vif->chandef)); + goto exit_with_error; + } +#else + if ((ndev_vif->chan->hw_value <= 14) && (!sdev->fw_2g_40mhz_enabled) && + (ndev_vif->channel_type > NL80211_CHAN_HT20)) { + SLSI_NET_ERR(dev, "Configuration error: 40 MHz on 2.4 GHz is not supported. Channel_no: %d Channel_width: %d\n", ndev_vif->chan->hw_value, slsi_get_chann_info(sdev, ndev_vif->channel_type)); + goto exit_with_error; + } +#endif + + return 0; + +exit_with_error: + return -EINVAL; +} + +static int slsi_get_max_bw_mhz(struct slsi_dev *sdev, u16 prim_chan_cf) +{ + int i; + struct ieee80211_regdomain *regd = sdev->device_config.domain_info.regdomain; + + if (!regd) { + SLSI_WARN(sdev, "NO regdomain info\n"); + return 0; + } + + for (i = 0; i < regd->n_reg_rules; i++) { + if ((regd->reg_rules[i].freq_range.start_freq_khz / 1000 <= prim_chan_cf - 10) && + (regd->reg_rules[i].freq_range.end_freq_khz / 1000 >= prim_chan_cf + 10)) + return regd->reg_rules[i].freq_range.max_bandwidth_khz / 1000; + } + + SLSI_WARN(sdev, "Freq(%d) not found in regdomain\n", prim_chan_cf); + return 0; +} + +int slsi_start_ap(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ap_settings *settings) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct net_device *wlan_dev; + u8 device_address[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + int r = 0; + const u8 *wpa_ie_pos = NULL; + size_t wpa_ie_len = 0; + const u8 *wmm_ie_pos = NULL; + size_t wmm_ie_len = 0; + const u8 *country_ie = NULL; + char alpha2[SLSI_COUNTRY_CODE_LEN]; + bool append_vht_ies = false; + const u8 *ie; +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + int wifi_sharing_channel_switched = 0; + struct netdev_vif *ndev_sta_vif; + int invalid_channel = 0; +#endif + int skip_indoor_check_for_wifi_sharing = 0; + u8 *ds_params_ie = NULL; + struct ieee80211_mgmt *mgmt; + u16 beacon_ie_head_len; + u8 *ht_operation_ie = NULL; + struct ieee80211_channel *channel = NULL; + int indoor_channel = 0; + int i; + u32 chan_flags; + u16 center_freq; + + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { + SLSI_WARN(sdev, "device not started yet (device_state:%d)\n", sdev->device_state); + r = -EINVAL; + goto exit_with_start_stop_mutex; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + /* Abort any ongoing wlan scan. */ + wlan_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + if (wlan_dev) + slsi_abort_hw_scan(sdev, wlan_dev); + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "AP frequency received: %d\n", settings->chandef.chan->center_freq); + mgmt = (struct ieee80211_mgmt *)settings->beacon.head; + beacon_ie_head_len = settings->beacon.head_len - ((u8 *)mgmt->u.beacon.variable - (u8 *)mgmt); +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + ndev_sta_vif = netdev_priv(wlan_dev); + if (SLSI_IS_VIF_INDEX_MHS(sdev, ndev_vif)) { + SLSI_MUTEX_LOCK(ndev_sta_vif->vif_mutex); + if ((ndev_sta_vif->activated) && (ndev_sta_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_sta_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTING || + ndev_sta_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + invalid_channel = slsi_select_wifi_sharing_ap_channel(wiphy, dev, settings, sdev, + &wifi_sharing_channel_switched); + skip_indoor_check_for_wifi_sharing = 1; + if (invalid_channel) { + SLSI_NET_ERR(dev, "Rejecting AP start req at host (invalid channel)\n"); + SLSI_MUTEX_UNLOCK(ndev_sta_vif->vif_mutex); + r = -EINVAL; + goto exit_with_vif_mutex; + } + } + SLSI_MUTEX_UNLOCK(ndev_sta_vif->vif_mutex); + } +#endif + + memset(&ndev_vif->ap, 0, sizeof(ndev_vif->ap)); + /* Initialise all allocated peer structures to remove old data. */ + /*slsi_netif_init_all_peers(sdev, dev);*/ + + /* Reg domain changes */ + country_ie = cfg80211_find_ie(WLAN_EID_COUNTRY, settings->beacon.tail, settings->beacon.tail_len); + if (country_ie) { + country_ie += 2; + memcpy(alpha2, country_ie, SLSI_COUNTRY_CODE_LEN); + if (memcmp(sdev->device_config.domain_info.regdomain->alpha2, alpha2, SLSI_COUNTRY_CODE_LEN - 1) != 0) { + if (slsi_set_country_update_regd(sdev, alpha2, SLSI_COUNTRY_CODE_LEN) != 0) { + r = -EINVAL; + goto exit_with_vif_mutex; + } + } + } + if (!skip_indoor_check_for_wifi_sharing && sdev->band_5g_supported && + ((settings->chandef.chan->center_freq / 1000) == 5)) { + channel = ieee80211_get_channel(sdev->wiphy, settings->chandef.chan->center_freq); + if (!channel) { + SLSI_ERR(sdev, "Invalid frequency %d used to start AP. Channel not found\n", + settings->chandef.chan->center_freq); + r = -EINVAL; + goto exit_with_vif_mutex; + } + if (ndev_vif->iftype != NL80211_IFTYPE_P2P_GO) { + if ((channel->flags) & (IEEE80211_CHAN_INDOOR_ONLY)) { + chan_flags = (IEEE80211_CHAN_INDOOR_ONLY | IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_DISABLED | +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 10, 13) + IEEE80211_CHAN_PASSIVE_SCAN +#else + IEEE80211_CHAN_NO_IR +#endif + ); + + for (i = 0; i < wiphy->bands[NL80211_BAND_5GHZ]->n_channels; i++) { + if (!(wiphy->bands[NL80211_BAND_5GHZ]->channels[i].flags & chan_flags)) { + center_freq = wiphy->bands[NL80211_BAND_5GHZ]->channels[i].center_freq; + settings->chandef.chan = ieee80211_get_channel(wiphy, center_freq); + settings->chandef.center_freq1 = center_freq; + SLSI_DBG1(sdev, SLSI_CFG80211, "ap valid frequency:%d,chan_flags:%x\n", + center_freq, + wiphy->bands[NL80211_BAND_5GHZ]->channels[i].flags); + indoor_channel = 1; + break; + } + } + if (indoor_channel == 0) { + SLSI_ERR(sdev, "No valid channel found to start the AP"); + r = -EINVAL; + goto exit_with_vif_mutex; + } + } + } + } + + r = slsi_ap_start_validate(dev, sdev, settings); + if (r != 0) + goto exit_with_vif_mutex; + + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) { + slsi_p2p_group_start_remove_unsync_vif(sdev); + SLSI_ETHER_COPY(device_address, dev->dev_addr); + if (keep_alive_period != SLSI_P2PGO_KEEP_ALIVE_PERIOD_SEC) + if (slsi_set_uint_mib(sdev, NULL, SLSI_PSID_UNIFI_MLMEGO_KEEP_ALIVE_TIMEOUT, + keep_alive_period) != 0) { + SLSI_NET_ERR(dev, "P2PGO Keep Alive MIB set failed"); + r = -EINVAL; + goto exit_with_vif_mutex; + } + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Channel: %d, Maximum bandwidth: %d\n", ndev_vif->chandef->chan->hw_value, + slsi_get_max_bw_mhz(sdev, ndev_vif->chandef->chan->center_freq)); + /* 11ac configuration (5GHz and VHT) */ + if ((ndev_vif->chandef->chan->hw_value >= 36) && (ndev_vif->chandef->chan->hw_value < 165) && + (sdev->fw_vht_enabled) && sdev->allow_switch_80_mhz && + (slsi_get_max_bw_mhz(sdev, ndev_vif->chandef->chan->center_freq) >= 80)) { + u16 oper_chan = ndev_vif->chandef->chan->hw_value; + append_vht_ies = true; + ndev_vif->chandef->width = NL80211_CHAN_WIDTH_80; + + SLSI_NET_DBG1(dev, SLSI_MLME, "5 GHz- Include VHT\n"); + if ((oper_chan >= 36) && (oper_chan <= 48)) + ndev_vif->chandef->center_freq1 = ieee80211_channel_to_frequency(42, NL80211_BAND_5GHZ); + else if ((oper_chan >= 149) && (oper_chan <= 161)) + ndev_vif->chandef->center_freq1 = ieee80211_channel_to_frequency(155, NL80211_BAND_5GHZ); +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + /* In wifi sharing case, AP can start on STA channel even though it is DFS channel*/ + if (wifi_sharing_channel_switched == 1) { + if ((oper_chan >= 52) && (oper_chan <= 64)) + ndev_vif->chandef->center_freq1 = ieee80211_channel_to_frequency(58, + NL80211_BAND_5GHZ); + else if ((oper_chan >= 100) && (oper_chan <= 112)) + ndev_vif->chandef->center_freq1 = ieee80211_channel_to_frequency(106, + NL80211_BAND_5GHZ); + else if ((oper_chan >= 116) && (oper_chan <= 128)) + ndev_vif->chandef->center_freq1 = ieee80211_channel_to_frequency(122, + NL80211_BAND_5GHZ); + else if ((oper_chan >= 132) && (oper_chan <= 144)) + ndev_vif->chandef->center_freq1 = ieee80211_channel_to_frequency(138, + NL80211_BAND_5GHZ); + } +#endif + } else if (sdev->fw_ht_enabled && sdev->allow_switch_40_mhz && + slsi_get_max_bw_mhz(sdev, ndev_vif->chandef->chan->center_freq) >= 40 && + ((ndev_vif->chandef->chan->hw_value < 165 && ndev_vif->chandef->chan->hw_value >= 36) || + (ndev_vif->chandef->chan->hw_value < 12 && sdev->fw_2g_40mhz_enabled && + ndev_vif->iftype == NL80211_IFTYPE_P2P_GO))) { + /* HT40 configuration (5GHz/2GHz and HT) */ + u16 oper_chan = ndev_vif->chandef->chan->hw_value; + u8 bw_40_minus_channels[] = { 40, 48, 153, 161, 5, 6, 7, 8, 9, 10, 11 }; +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + u8 bw_40_minus_dfs_channels[] = { 144, 136, 128, 120, 112, 104, 64, 56 }; +#endif + u8 ch; + + ndev_vif->chandef->width = NL80211_CHAN_WIDTH_40; + ndev_vif->chandef->center_freq1 = ndev_vif->chandef->chan->center_freq + 10; + for (ch = 0; ch < ARRAY_SIZE(bw_40_minus_channels); ch++) + if (oper_chan == bw_40_minus_channels[ch]) { + ndev_vif->chandef->center_freq1 = ndev_vif->chandef->chan->center_freq - 10; + break; + } + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (wifi_sharing_channel_switched == 1) { + for (ch = 0; ch < ARRAY_SIZE(bw_40_minus_dfs_channels); ch++) + if (oper_chan == bw_40_minus_dfs_channels[ch]) { + ndev_vif->chandef->center_freq1 = ndev_vif->chandef->chan->center_freq - 10; + break; + } + } +#endif + } +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (slsi_check_channelization(sdev, ndev_vif->chandef, wifi_sharing_channel_switched) != 0) { +#else + if (slsi_check_channelization(sdev, ndev_vif->chandef, 0) != 0) { +#endif +#else + if (slsi_check_channelization(sdev, ndev_vif->channel_type) != 0) { +#endif + r = -EINVAL; + goto exit_with_vif_mutex; + } + + if (ndev_vif->iftype == NL80211_IFTYPE_AP) { + /* Legacy AP */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (ndev_vif->chandef->width == NL80211_CHAN_WIDTH_20) +#else + if (ndev_vif->channel_type == NL80211_CHAN_HT20) +#endif + slsi_ap_start_obss_scan(sdev, dev, ndev_vif); + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (ndev_vif->chandef->width <= NL80211_CHAN_WIDTH_20) { + /* Enable LDPC, SGI20 and SGI40 for both SoftAP & P2PGO if firmware supports */ + if (cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, settings->beacon.tail, settings->beacon.tail_len)) { + u8 enforce_ht_cap1 = sdev->fw_ht_cap[0] & (IEEE80211_HT_CAP_LDPC_CODING | + IEEE80211_HT_CAP_SGI_20); + u8 enforce_ht_cap2 = sdev->fw_ht_cap[1] & (IEEE80211_HT_CAP_RX_STBC >> 8); + + slsi_modify_ies(dev, WLAN_EID_HT_CAPABILITY, (u8 *)settings->beacon.tail, + settings->beacon.tail_len, 2, enforce_ht_cap1); + slsi_modify_ies(dev, WLAN_EID_HT_CAPABILITY, (u8 *)settings->beacon.tail, + settings->beacon.tail_len, 3, enforce_ht_cap2); + } + } else if (cfg80211_chandef_valid(ndev_vif->chandef)) { + u8 *ht_operation_ie; + u8 sec_chan_offset = 0; + u8 ch; + u8 bw_40_minus_channels[] = { 40, 48, 153, 161, 5, 6, 7, 8, 9, 10, 11 }; + + ht_operation_ie = (u8 *)cfg80211_find_ie(WLAN_EID_HT_OPERATION, settings->beacon.tail, + settings->beacon.tail_len); + if (!ht_operation_ie) { + SLSI_NET_ERR(dev, "HT Operation IE is not passed by wpa_supplicant"); + r = -EINVAL; + goto exit_with_vif_mutex; + } + + sec_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + for (ch = 0; ch < ARRAY_SIZE(bw_40_minus_channels); ch++) + if (bw_40_minus_channels[ch] == ndev_vif->chandef->chan->hw_value) { + sec_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + } + + /* Change HT Information IE subset 1 */ + ht_operation_ie += 3; + *(ht_operation_ie) |= sec_chan_offset; + *(ht_operation_ie) |= IEEE80211_HT_PARAM_CHAN_WIDTH_ANY; + + /* For 80MHz, Enable HT Capabilities : Support 40MHz Channel Width, SGI20 and SGI40 + * for AP (both softAp as well as P2P GO), if firmware supports. + */ + if (cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, settings->beacon.tail, + settings->beacon.tail_len)) { + u8 enforce_ht_cap1 = sdev->fw_ht_cap[0] & (IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_LDPC_CODING); + u8 enforce_ht_cap2 = sdev->fw_ht_cap[1] & (IEEE80211_HT_CAP_RX_STBC >> 8); + + slsi_modify_ies(dev, WLAN_EID_HT_CAPABILITY, (u8 *)settings->beacon.tail, + settings->beacon.tail_len, 2, enforce_ht_cap1); + slsi_modify_ies(dev, WLAN_EID_HT_CAPABILITY, (u8 *)settings->beacon.tail, + settings->beacon.tail_len, 3, enforce_ht_cap2); + } + } +#endif + + if (indoor_channel == 1 +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + || (wifi_sharing_channel_switched == 1) +#endif + ) { + slsi_modify_ies_on_channel_switch(dev, settings, ds_params_ie, ht_operation_ie, mgmt, beacon_ie_head_len); + } + ndev_vif->vif_type = FAPI_VIFTYPE_AP; + + if (slsi_mlme_add_vif(sdev, dev, dev->dev_addr, device_address) != 0) { + SLSI_NET_ERR(dev, "slsi_mlme_add_vif failed\n"); + r = -EINVAL; + goto exit_with_vif_mutex; + } + + if (slsi_vif_activated(sdev, dev) != 0) { + SLSI_NET_ERR(dev, "slsi_vif_activated failed\n"); + goto exit_with_vif; + } + + /* Extract the WMM and WPA IEs from settings->beacon.tail - This is sent in add_info_elements and shouldn't be included in start_req + * Cache IEs to be used in later add_info_elements_req. The IEs would be freed during AP stop + */ + wpa_ie_pos = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPA, settings->beacon.tail, settings->beacon.tail_len); + if (wpa_ie_pos) { + wpa_ie_len = (size_t)(*(wpa_ie_pos + 1) + 2); /* For 0xdd (1) and Tag Length (1) */ + SLSI_NET_DBG2(dev, SLSI_CFG80211, "WPA IE found: Length = %zu\n", wpa_ie_len); + SLSI_EC_GOTO(slsi_cache_ies(wpa_ie_pos, wpa_ie_len, &ndev_vif->ap.cache_wpa_ie, &ndev_vif->ap.wpa_ie_len), r, exit_with_vif); + } + + wmm_ie_pos = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WMM, settings->beacon.tail, settings->beacon.tail_len); + if (wmm_ie_pos) { + wmm_ie_len = (size_t)(*(wmm_ie_pos + 1) + 2); + SLSI_NET_DBG2(dev, SLSI_CFG80211, "WMM IE found: Length = %zu\n", wmm_ie_len); + SLSI_EC_GOTO(slsi_cache_ies(wmm_ie_pos, wmm_ie_len, &ndev_vif->ap.cache_wmm_ie, &ndev_vif->ap.wmm_ie_len), r, exit_with_vif); + } + + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + + /* Set Vendor specific IEs (WPA, WMM, WPS, P2P) for Beacon, Probe Response and Association Response + * The Beacon and Assoc Rsp IEs can include Extended Capability (WLAN_EID_EXT_CAPAB) IE when supported. + * Some other IEs (like internetworking, etc) can also come if supported. + * The add_info should include only vendor specific IEs and other IEs should be removed if supported in future. + */ + if ((wmm_ie_pos) || (wpa_ie_pos) || (settings->beacon.beacon_ies_len > 0 && settings->beacon.beacon_ies)) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Add info elements for beacon\n"); + SLSI_EC_GOTO(slsi_ap_prepare_add_info_ies(ndev_vif, settings->beacon.beacon_ies, settings->beacon.beacon_ies_len), r, exit_with_vif); + SLSI_EC_GOTO(slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_BEACON, ndev_vif->ap.add_info_ies, ndev_vif->ap.add_info_ies_len), r, exit_with_vif); + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + } + + if ((wmm_ie_pos) || (wpa_ie_pos) || (settings->beacon.proberesp_ies_len > 0 && settings->beacon.proberesp_ies)) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Add info elements for probe response\n"); + SLSI_EC_GOTO(slsi_ap_prepare_add_info_ies(ndev_vif, settings->beacon.proberesp_ies, settings->beacon.proberesp_ies_len), r, exit_with_vif); + SLSI_EC_GOTO(slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_PROBE_RESPONSE, ndev_vif->ap.add_info_ies, ndev_vif->ap.add_info_ies_len), r, exit_with_vif); + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + } + + if ((wmm_ie_pos) || (wpa_ie_pos) || (settings->beacon.assocresp_ies_len > 0 && settings->beacon.assocresp_ies)) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Add info elements for assoc response\n"); + SLSI_EC_GOTO(slsi_ap_prepare_add_info_ies(ndev_vif, settings->beacon.assocresp_ies, settings->beacon.assocresp_ies_len), r, exit_with_vif); + SLSI_EC_GOTO(slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_ASSOCIATION_RESPONSE, ndev_vif->ap.add_info_ies, ndev_vif->ap.add_info_ies_len), r, exit_with_vif); + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + } + + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) { + u32 af_bmap_active = SLSI_ACTION_FRAME_PUBLIC; + u32 af_bmap_suspended = SLSI_ACTION_FRAME_PUBLIC; + + r = slsi_mlme_register_action_frame(sdev, dev, af_bmap_active, af_bmap_suspended); + if (r != 0) { + SLSI_NET_ERR(dev, "slsi_mlme_register_action_frame failed: resultcode = %d\n", r); + goto exit_with_vif; + } + } + + if (append_vht_ies) { + ndev_vif->ap.mode = SLSI_80211_MODE_11AC; + } else if (cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, settings->beacon.tail, settings->beacon.tail_len) && + cfg80211_find_ie(WLAN_EID_HT_OPERATION, settings->beacon.tail, settings->beacon.tail_len)) { + ndev_vif->ap.mode = SLSI_80211_MODE_11N; + } else { + ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, settings->beacon.tail, settings->beacon.tail_len); + if (ie) + ndev_vif->ap.mode = slsi_get_supported_mode(ie); + } + + r = slsi_mlme_start(sdev, dev, dev->dev_addr, settings, wpa_ie_pos, wmm_ie_pos, append_vht_ies); + + if ((indoor_channel == 1) +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + || (wifi_sharing_channel_switched == 1) +#endif +#ifdef CONFIG_SCSC_WLAN_ACS_ENABLE + || (sdev->acs_channel_switched == true) +#endif + ) + cfg80211_ch_switch_notify(dev, &settings->chandef); + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (r == 0) + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Soft Ap started on frequency: %d\n", + settings->chandef.chan->center_freq); + if (SLSI_IS_VIF_INDEX_MHS(sdev, ndev_vif)) + ndev_vif->chan = settings->chandef.chan; +#endif + if (r != 0) { + SLSI_NET_ERR(dev, "Start ap failed: resultcode = %d frequency = %d\n", r, + settings->chandef.chan->center_freq); + goto exit_with_vif; + } else if (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) { + SLSI_P2P_STATE_CHANGE(sdev, P2P_GROUP_FORMED_GO); + } +#ifdef CONFIG_SCSC_WLAN_SET_NUM_ANTENNAS + if (ndev_vif->iftype == NL80211_IFTYPE_AP) { + /* Don't care results. */ + slsi_set_num_antennas(dev, 1 /*SISO*/); + } +#endif + ndev_vif->ap.beacon_interval = settings->beacon_interval; + ndev_vif->ap.ssid_len = settings->ssid_len; + memcpy(ndev_vif->ap.ssid, settings->ssid, settings->ssid_len); + + netif_carrier_on(dev); + + if (ndev_vif->ipaddress != cpu_to_be32(0)) + /* Static IP is assigned already */ + slsi_ip_address_changed(sdev, dev, ndev_vif->ipaddress); + + r = slsi_read_disconnect_ind_timeout(sdev, SLSI_PSID_UNIFI_DISCONNECT_TIMEOUT); + if (r != 0) + sdev->device_config.ap_disconnect_ind_timeout = *sdev->sig_wait_cfm_timeout; + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "slsi_read_disconnect_ind_timeout: timeout = %d", sdev->device_config.ap_disconnect_ind_timeout); + goto exit_with_vif_mutex; +exit_with_vif: + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + slsi_mlme_del_vif(sdev, dev); + slsi_vif_deactivated(sdev, dev); + r = -EINVAL; +exit_with_vif_mutex: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit_with_start_stop_mutex: + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + return r; +} + +int slsi_change_beacon(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_beacon_data *info) +{ + SLSI_UNUSED_PARAMETER(info); + + return -EOPNOTSUPP; +} + +int slsi_stop_ap(struct wiphy *wiphy, struct net_device *dev) +{ + slsi_reset_throughput_stats(dev); + + return 0; +} + +static int slsi_p2p_group_mgmt_tx(const struct ieee80211_mgmt *mgmt, struct wiphy *wiphy, + struct net_device *dev, struct ieee80211_channel *chan, + unsigned int wait, const u8 *buf, size_t len, + bool dont_wait_for_ack, u64 *cookie) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif; + struct net_device *netdev; + int subtype = slsi_p2p_get_public_action_subtype(mgmt); + int r = 0; + u32 host_tag = slsi_tx_mgmt_host_tag(sdev); + u16 freq = 0; + u32 dwell_time = SLSI_FORCE_SCHD_ACT_FRAME_MSEC; + u16 data_unit_desc = FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME; + + if (sdev->p2p_group_exp_frame != SLSI_P2P_PA_INVALID) { + SLSI_NET_ERR(dev, "sdev->p2p_group_exp_frame : %d\n", sdev->p2p_group_exp_frame); + return -EINVAL; + } + netdev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2PX_SWLAN); + ndev_vif = netdev_priv(netdev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Sending Action frame (%s) on p2p group vif (%d), vif_index = %d," + "vif_type = %d, chan->hw_value = %d, ndev_vif->chan->hw_value = %d, wait = %d," + "sdev->p2p_group_exp_frame = %d\n", slsi_p2p_pa_subtype_text(subtype), ndev_vif->activated, + ndev_vif->ifnum, ndev_vif->vif_type, chan->hw_value, ndev_vif->chan->hw_value, wait, + ndev_vif->chan->hw_value); + + if (!((ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) || (ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT))) + goto exit_with_error; + + if (chan->hw_value != ndev_vif->chan->hw_value) { + freq = SLSI_FREQ_HOST_TO_FW(chan->center_freq); + dwell_time = wait; + } + + /* Incase of GO dont wait for resp/cfm packets for go-negotiation.*/ + if (subtype != SLSI_P2P_PA_GO_NEG_RSP) + sdev->p2p_group_exp_frame = slsi_p2p_get_exp_peer_frame_subtype(subtype); + + r = slsi_mlme_send_frame_mgmt(sdev, netdev, buf, len, data_unit_desc, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, freq, dwell_time * 1000, 0); + if (r) + goto exit_with_lock; + slsi_assign_cookie_id(cookie, &ndev_vif->mgmt_tx_cookie); + r = slsi_set_mgmt_tx_data(ndev_vif, *cookie, host_tag, buf, len); /* If error then it is returned in exit */ + goto exit_with_lock; + +exit_with_error: + r = -EINVAL; +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +/* Handle mgmt_tx callback for P2P modes */ +static int slsi_p2p_mgmt_tx(const struct ieee80211_mgmt *mgmt, struct wiphy *wiphy, + struct net_device *dev, struct netdev_vif *ndev_vif, + struct ieee80211_channel *chan, unsigned int wait, + const u8 *buf, size_t len, bool dont_wait_for_ack, u64 *cookie) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + + if (ieee80211_is_action(mgmt->frame_control)) { + u16 host_tag = slsi_tx_mgmt_host_tag(sdev); + int subtype = slsi_p2p_get_public_action_subtype(mgmt); + u8 exp_peer_frame; + u32 dwell_time = 0; + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Action frame (%s), unsync_vif_active (%d)\n", slsi_p2p_pa_subtype_text(subtype), ndev_vif->activated); + + if (subtype == SLSI_P2P_PA_INVALID) { + SLSI_NET_ERR(dev, "Invalid Action frame subtype\n"); + goto exit_with_error; + } + + /* Check if unsync vif is available */ + if (sdev->p2p_state == P2P_IDLE_NO_VIF) + if (slsi_p2p_vif_activate(sdev, dev, chan, wait, false) != 0) + goto exit_with_error; + + /* Clear Probe Response IEs if vif was already present with a different channel */ + if (ndev_vif->driver_channel != chan->hw_value) { + if (slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_PROBE_RESPONSE, NULL, 0) != 0) + SLSI_NET_ERR(dev, "Clearing Probe Response IEs failed for unsync vif\n"); + slsi_unsync_vif_set_probe_rsp_ie(ndev_vif, NULL, 0); + + if (slsi_mlme_set_channel(sdev, dev, chan, SLSI_FW_CHANNEL_DURATION_UNSPECIFIED, 0, 0) != 0) + goto exit_with_vif; + else { + ndev_vif->chan = chan; + ndev_vif->driver_channel = chan->hw_value; + } + } + + /* Check if peer frame response is expected */ + exp_peer_frame = slsi_p2p_get_exp_peer_frame_subtype(subtype); + + if (exp_peer_frame != SLSI_P2P_PA_INVALID) { + if ((subtype == SLSI_P2P_PA_GO_NEG_RSP) && (slsi_p2p_get_go_neg_rsp_status(dev, mgmt) != SLSI_P2P_STATUS_CODE_SUCCESS)) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "GO_NEG_RSP Tx, peer response not expected\n"); + exp_peer_frame = SLSI_P2P_PA_INVALID; + } else { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Peer response expected with action frame (%s)\n", + slsi_p2p_pa_subtype_text(exp_peer_frame)); + + if (ndev_vif->mgmt_tx_data.exp_frame != SLSI_P2P_PA_INVALID) + (void)slsi_set_mgmt_tx_data(ndev_vif, 0, 0, NULL, 0); + + /* Change Force Schedule Duration as peer response is expected */ + if (wait) + dwell_time = wait; + else + dwell_time = SLSI_FORCE_SCHD_ACT_FRAME_MSEC; + } + } + + slsi_assign_cookie_id(cookie, &ndev_vif->mgmt_tx_cookie); + + /* Send the action frame, transmission status indication would be received later */ + if (slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, 0, dwell_time * 1000, 0) != 0) + goto exit_with_vif; + if (subtype == SLSI_P2P_PA_GO_NEG_CFM) + ndev_vif->drv_in_p2p_procedure = false; + else if ((subtype == SLSI_P2P_PA_GO_NEG_REQ) || (subtype == SLSI_P2P_PA_PROV_DISC_REQ)) + ndev_vif->drv_in_p2p_procedure = true; + /* If multiple frames are requested for tx, only the info of first frame would be stored */ + if (ndev_vif->mgmt_tx_data.host_tag == 0) { + unsigned int n_wait = 0; + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Store mgmt frame tx data for cookie = 0x%llx\n", *cookie); + + ret = slsi_set_mgmt_tx_data(ndev_vif, *cookie, host_tag, buf, len); + if (ret != 0) + goto exit_with_vif; + ndev_vif->mgmt_tx_data.exp_frame = exp_peer_frame; + + SLSI_P2P_STATE_CHANGE(sdev, P2P_ACTION_FRAME_TX_RX); + if ((exp_peer_frame == SLSI_P2P_PA_GO_NEG_RSP) || (exp_peer_frame == SLSI_P2P_PA_GO_NEG_CFM)) + /* Retain vif for larger duration that wpa_supplicant asks to wait, + * during GO-Negotiation to allow peer to retry GO neg in bad radio condition. + * Some of phones retry GO-Negotiation after 2 seconds + */ + n_wait = SLSI_P2P_NEG_PROC_UNSYNC_VIF_RETAIN_DURATION; + else if (exp_peer_frame != SLSI_P2P_PA_INVALID) + /* If a peer response is expected queue work to retain vif till wait time else the work will be handled in mgmt_tx_cancel_wait */ + n_wait = wait + SLSI_P2P_MGMT_TX_EXTRA_MSEC; + if (n_wait) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "retain unsync vif for duration (%d) msec\n", n_wait); + slsi_p2p_queue_unsync_vif_del_work(ndev_vif, n_wait); + } + } else { + /* Already a frame Tx is in progress, send immediate tx_status as success. Sending immediate tx status should be ok + * as supplicant is in another procedure and so these frames would be mostly only response frames. + */ + WARN_ON(sdev->p2p_state != P2P_ACTION_FRAME_TX_RX); + + if (!dont_wait_for_ack) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Send immediate tx_status (cookie = 0x%llx)\n", *cookie); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) + cfg80211_mgmt_tx_status(&ndev_vif->wdev, *cookie, buf, len, true, GFP_KERNEL); +#else + cfg80211_mgmt_tx_status(dev, *cookie, buf, len, true, GFP_KERNEL); +#endif + } + } + goto exit; + } + + /* Else send failure for unexpected management frame */ + SLSI_NET_ERR(dev, "Drop Tx frame: Unexpected Management frame\n"); + goto exit_with_error; + +exit_with_vif: + if (sdev->p2p_state != P2P_LISTENING) + slsi_p2p_vif_deactivate(sdev, dev, true); +exit_with_error: + ret = -EINVAL; +exit: + return ret; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) +int slsi_mgmt_tx_cancel_wait(struct wiphy *wiphy, + struct wireless_dev *wdev, + u64 cookie) +{ + struct net_device *dev = wdev->netdev; + +#else +int slsi_mgmt_tx_cancel_wait(struct wiphy *wiphy, + struct net_device *dev, + u64 cookie) +{ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) */ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "iface_num = %d, cookie = 0x%llx, vif_index = %d, vif_type = %d," + "sdev->p2p_state = %d, ndev_vif->mgmt_tx_data.cookie = 0x%llx, sdev->p2p_group_exp_frame = %d," + "sdev->wlan_unsync_vif_state = %d\n", ndev_vif->ifnum, cookie, + ndev_vif->vif_type, sdev->p2p_state, ndev_vif->mgmt_tx_data.cookie, + sdev->p2p_group_exp_frame, sdev->wlan_unsync_vif_state); + + /* If device was in frame tx_rx state, clear mgmt tx data and change state */ + if ((sdev->p2p_state == P2P_ACTION_FRAME_TX_RX) && (ndev_vif->mgmt_tx_data.cookie == cookie)) { + if (ndev_vif->mgmt_tx_data.exp_frame != SLSI_P2P_PA_INVALID) + (void)slsi_mlme_reset_dwell_time(sdev, dev); + + (void)slsi_set_mgmt_tx_data(ndev_vif, 0, 0, NULL, 0); + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + + if (delayed_work_pending(&ndev_vif->unsync.roc_expiry_work)) { + SLSI_P2P_STATE_CHANGE(sdev, P2P_LISTENING); + } else { + slsi_p2p_queue_unsync_vif_del_work(ndev_vif, SLSI_P2P_UNSYNC_VIF_EXTRA_MSEC); + SLSI_P2P_STATE_CHANGE(ndev_vif->sdev, P2P_IDLE_VIF_ACTIVE); + } + } else if ((SLSI_IS_P2P_GROUP_STATE(sdev)) && (sdev->p2p_group_exp_frame != SLSI_P2P_PA_INVALID)) { + /* acquire mutex lock if it is not group net dev */ + slsi_clear_offchannel_data(sdev, (!SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif)) ? true : false); + } else if ((sdev->wlan_unsync_vif_state == WLAN_UNSYNC_VIF_TX) && (ndev_vif->mgmt_tx_data.cookie == cookie)) { + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_VIF_ACTIVE; + cancel_delayed_work(&ndev_vif->unsync.hs2_del_vif_work); + queue_delayed_work(sdev->device_wq, &ndev_vif->unsync.hs2_del_vif_work, msecs_to_jiffies(SLSI_HS2_UNSYNC_VIF_EXTRA_MSEC)); + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +void slsi_mgmt_frame_register(struct wiphy *wiphy, + struct wireless_dev *wdev, + u16 frame_type, bool reg) +{ + struct net_device *dev = wdev->netdev; + +#else +void slsi_mgmt_frame_register(struct wiphy *wiphy, + struct net_device *dev, + u16 frame_type, bool reg) +{ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) */ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + SLSI_UNUSED_PARAMETER(frame_type); + SLSI_UNUSED_PARAMETER(reg); +#endif + + if (WARN_ON(!dev)) + return; + + SLSI_UNUSED_PARAMETER(sdev); +} + +static int slsi_wlan_mgmt_tx(struct slsi_dev *sdev, struct net_device *dev, + struct ieee80211_channel *chan, unsigned int wait, + const u8 *buf, size_t len, bool dont_wait_for_ack, u64 *cookie) +{ + u32 host_tag = slsi_tx_mgmt_host_tag(sdev); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)buf; + + if (!ieee80211_is_auth(mgmt->frame_control)) + slsi_wlan_dump_public_action_subtype(sdev, mgmt, true); + if (!ndev_vif->activated) { + r = slsi_wlan_unsync_vif_activate(sdev, dev, chan, wait); + if (r) + return r; + + r = slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, 0, wait * 1000, 0); + if (r) + goto exit_with_vif; + + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_VIF_TX; + queue_delayed_work(sdev->device_wq, &ndev_vif->unsync.hs2_del_vif_work, msecs_to_jiffies(wait)); + } else { + /* vif is active*/ + if (ieee80211_is_auth(mgmt->frame_control)) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Transmit on the current frequency\n"); + r = slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, + FAPI_MESSAGETYPE_IEEE80211_MGMT, host_tag, 0, wait * 1000, 0); + if (r) + return r; + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_UNSYNCHRONISED) { + cancel_delayed_work(&ndev_vif->unsync.hs2_del_vif_work); + /*even if we fail to cancel the delayed work, we shall go ahead and send action frames*/ + if (ndev_vif->driver_channel != chan->hw_value) { + r = slsi_mlme_set_channel(sdev, dev, chan, SLSI_FW_CHANNEL_DURATION_UNSPECIFIED, 0, 0); + if (r) + goto exit_with_vif; + else { + ndev_vif->driver_channel = chan->hw_value; + } + } + SLSI_NET_DBG1(dev, SLSI_CFG80211, "HS2 vif is active ,send GAS (ANQP) request on channel freq = %d\n", chan->center_freq); + r = slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, 0, wait * 1000, 0); + if (r) + goto exit_with_vif; + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_VIF_TX; + queue_delayed_work(sdev->device_wq, &ndev_vif->unsync.hs2_del_vif_work, msecs_to_jiffies(wait)); + } else if (ndev_vif->chan->hw_value == chan->hw_value) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "STA VIF is active on same channel, send GAS (ANQP) request on channel freq %d\n", chan->center_freq); + r = slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, 0, wait * 1000, 0); + if (r) + return r; + } else { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "STA VIF is active on a different channel, send GAS (ANQP) request on channel freq %d\n", chan->center_freq); + r = slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, SLSI_FREQ_HOST_TO_FW(chan->center_freq), wait * 1000, 0); + if (r) + return r; + } + } + + slsi_assign_cookie_id(cookie, &ndev_vif->mgmt_tx_cookie); + slsi_set_mgmt_tx_data(ndev_vif, *cookie, host_tag, buf, len); + return r; + +exit_with_vif: + slsi_wlan_unsync_vif_deactivate(sdev, dev, true); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) +int slsi_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, + u64 *cookie) +{ +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) +int slsi_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *chan, bool offchan, + unsigned int wait, const u8 *buf, size_t len, bool no_cck, bool dont_wait_for_ack, u64 *cookie) +{ + struct net_device *dev = wdev->netdev; + +#else +int slsi_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, + struct ieee80211_channel *chan, bool offchan, + enum nl80211_channel_type channel_type, + bool channel_type_valid, unsigned int wait, + const u8 *buf, size_t len, bool no_cck, + bool dont_wait_for_ack, u64 *cookie) +{ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) */ + + /* Note to explore for AP ::All public action frames which come to host should be handled properly + * Additionally, if PMF is negotiated over the link, the host shall not issue "mlme-send-frame.request" + * primitive for action frames before the pairwise keys have been installed in F/W. Presently, for + * SoftAP with PMF support, there is no scenario in which slsi_mlme_send_frame will be called for + * action frames for VIF TYPE = AP. + */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + struct net_device *dev = wdev->netdev; + struct ieee80211_channel *chan = params->chan; + bool offchan = params->offchan; + unsigned int wait = params->wait; + const u8 *buf = params->buf; + size_t len = params->len; + bool no_cck = params->no_cck; + bool dont_wait_for_ack = params->dont_wait_for_ack; +#endif + + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *)buf; + int r = 0; + + SLSI_UNUSED_PARAMETER(offchan); + SLSI_UNUSED_PARAMETER(no_cck); + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { + SLSI_WARN(sdev, "device not started yet (device_state:%d)\n", sdev->device_state); + r = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!(ieee80211_is_auth(mgmt->frame_control))) { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Mgmt Frame Tx: iface_num = %d, channel = %d, wait = %d, noAck = %d," + "offchannel = %d, mgmt->frame_control = %d, vif_type = %d\n", ndev_vif->ifnum, chan->hw_value, + wait, dont_wait_for_ack, offchan, mgmt->frame_control, ndev_vif->vif_type); + } else { + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Received Auth Frame"); + } + + if (!(ieee80211_is_mgmt(mgmt->frame_control))) { + SLSI_NET_ERR(dev, "Drop Tx frame: Not a Management frame\n"); + r = -EINVAL; + goto exit; + } + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif) || (ndev_vif->iftype == NL80211_IFTYPE_AP && (ieee80211_is_auth(mgmt->frame_control)))) { + r = slsi_wlan_mgmt_tx(SDEV_FROM_WIPHY(wiphy), dev, chan, wait, buf, len, dont_wait_for_ack, cookie); + goto exit; + } + + /*P2P*/ + + /* Drop Probe Responses which can come in P2P Device and P2P Group role */ + if (ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Ideally supplicant doesn't expect Tx status for Probe Rsp. Send tx status just in case it requests ack */ + if (!dont_wait_for_ack) { + slsi_assign_cookie_id(cookie, &ndev_vif->mgmt_tx_cookie); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, GFP_KERNEL); +#else + cfg80211_mgmt_tx_status(dev, *cookie, buf, len, true, GFP_KERNEL); +#endif + } + goto exit; + } + + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + /* Check whether STA scan is running or not. If yes, then abort the STA scan */ + slsi_abort_sta_scan(sdev); + if (SLSI_IS_P2P_GROUP_STATE(sdev)) + r = slsi_p2p_group_mgmt_tx(mgmt, wiphy, dev, chan, wait, buf, len, dont_wait_for_ack, cookie); + else + r = slsi_p2p_mgmt_tx(mgmt, wiphy, dev, ndev_vif, chan, wait, buf, len, dont_wait_for_ack, cookie); + } else if (SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif)) + if (chan->hw_value == ndev_vif->chan->hw_value) { + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + u16 host_tag = slsi_tx_mgmt_host_tag(sdev); + + r = slsi_mlme_send_frame_mgmt(sdev, dev, buf, len, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, 0, 0, 0); + if (r) { + SLSI_NET_ERR(dev, "Failed to send action frame, r = %d\n", r); + goto exit; + } + slsi_assign_cookie_id(cookie, &ndev_vif->mgmt_tx_cookie); + r = slsi_set_mgmt_tx_data(ndev_vif, *cookie, host_tag, buf, len); + } +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + return r; +} + +/* cw = (2^n -1). But WMM IE needs value n. */ +u8 slsi_get_ecw(int cw) +{ + int ecw = 0; + + cw = cw + 1; + do { + cw = cw >> 1; + ecw++; + } while (cw); + return ecw - 1; +} + +int slsi_set_txq_params(struct wiphy *wiphy, struct net_device *ndev, + struct ieee80211_txq_params *params) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(ndev); + struct slsi_wmm_parameter_element *wmm_ie = &ndev_vif->ap.wmm_ie; + int r = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) + int ac = params->ac; +#else + int ac = params->queue; +#endif + /* Index remapping for AC from nl80211_ac enum to slsi_ac_index_wmm enum (index to be used in the IE). + * Kernel version less than 3.5.0 doesn't support nl80211_ac enum hence not using the nl80211_ac enum. + * Eg. NL80211_AC_VO (index value 0) would be remapped to AC_VO (index value 3). + * Don't change the order of array elements. + */ + u8 ac_index_map[4] = { AC_VO, AC_VI, AC_BE, AC_BK }; + int ac_remapped = ac_index_map[ac]; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_NET_DBG2(ndev, SLSI_CFG80211, " ac= %x, ac_remapped = %d aifs = %d, cmin=%x cmax = %x, txop = %x," + "vif_index = %d vif_type = %d", ac, ac_remapped, params->aifs, params->cwmin, params->cwmax, + params->txop, ndev_vif->ifnum, ndev_vif->vif_type); + + if (ndev_vif->activated) { + wmm_ie->ac[ac_remapped].aci_aifsn = (ac_remapped << 5) | (params->aifs & 0x0f); + wmm_ie->ac[ac_remapped].ecw = ((slsi_get_ecw(params->cwmax)) << 4) | ((slsi_get_ecw(params->cwmin)) & 0x0f); + wmm_ie->ac[ac_remapped].txop_limit = cpu_to_le16(params->txop); + if (ac == 3) { + wmm_ie->eid = SLSI_WLAN_EID_VENDOR_SPECIFIC; + wmm_ie->len = 24; + wmm_ie->oui[0] = 0x00; + wmm_ie->oui[1] = 0x50; + wmm_ie->oui[2] = 0xf2; + wmm_ie->oui_type = WLAN_OUI_TYPE_MICROSOFT_WMM; + wmm_ie->oui_subtype = 1; + wmm_ie->version = 1; + wmm_ie->qos_info = 0; + wmm_ie->reserved = 0; + r = slsi_mlme_add_info_elements(sdev, ndev, FAPI_PURPOSE_LOCAL, (const u8 *)wmm_ie, sizeof(struct slsi_wmm_parameter_element)); + if (r) + SLSI_NET_ERR(ndev, "Error sending TX Queue Parameters for AP error = %d", r); + } + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG +int slsi_synchronised_response(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_external_auth_params *params) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + r = slsi_mlme_synchronised_response(sdev, dev, params); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +static int slsi_update_ft_ies(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_update_ft_ies_params *ftie) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) + r = slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_ASSOCIATION_REQUEST, ftie->ie, ftie->ie_len); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_set_mac_acl(struct wiphy *wiphy, struct net_device *dev, + const struct cfg80211_acl_data *params) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_SET_ACL.request\n"); + return -EOPNOTSUPP; + } + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (FAPI_VIFTYPE_AP != ndev_vif->vif_type) { + SLSI_NET_ERR(dev, "Invalid vif type: %d\n", ndev_vif->vif_type); + r = -EINVAL; + goto exit; + } + SLSI_NET_DBG2(dev, SLSI_CFG80211, "ACL:: Policy: %d Number of stations: %d\n", params->acl_policy, params->n_acl_entries); + r = slsi_mlme_set_acl(sdev, dev, ndev_vif->ifnum, params); + if (r != 0) + SLSI_NET_ERR(dev, "mlme_set_acl_req returned with CFM failure\n"); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} +#endif + +static struct cfg80211_ops slsi_ops = { + .add_virtual_intf = slsi_add_virtual_intf, + .del_virtual_intf = slsi_del_virtual_intf, + .change_virtual_intf = slsi_change_virtual_intf, + + .scan = slsi_scan, + .connect = slsi_connect, + .disconnect = slsi_disconnect, + + .add_key = slsi_add_key, + .del_key = slsi_del_key, + .get_key = slsi_get_key, + .set_default_key = slsi_set_default_key, + .set_default_mgmt_key = slsi_config_default_mgmt_key, + + .set_wiphy_params = slsi_set_wiphy_params, + + .del_station = slsi_del_station, + .get_station = slsi_get_station, + .set_tx_power = slsi_set_tx_power, + .get_tx_power = slsi_get_tx_power, + .set_power_mgmt = slsi_set_power_mgmt, + + .suspend = slsi_suspend, + .resume = slsi_resume, + + .set_pmksa = slsi_set_pmksa, + .del_pmksa = slsi_del_pmksa, + .flush_pmksa = slsi_flush_pmksa, + + .remain_on_channel = slsi_remain_on_channel, + .cancel_remain_on_channel = slsi_cancel_remain_on_channel, + + .change_bss = slsi_change_bss, +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 5, 0)) + .set_channel = slsi_set_channel, +#endif /* (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 5, 0)) */ + + .start_ap = slsi_start_ap, + .change_beacon = slsi_change_beacon, + .stop_ap = slsi_stop_ap, + + .sched_scan_start = slsi_sched_scan_start, + .sched_scan_stop = slsi_sched_scan_stop, + + .mgmt_frame_register = slsi_mgmt_frame_register, + .mgmt_tx = slsi_mgmt_tx, + .mgmt_tx_cancel_wait = slsi_mgmt_tx_cancel_wait, + .set_txq_params = slsi_set_txq_params, +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG + .external_auth = slsi_synchronised_response, +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + .set_mac_acl = slsi_set_mac_acl, + .update_ft_ies = slsi_update_ft_ies, +#endif + .tdls_oper = slsi_tdls_oper, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) +#ifdef CONFIG_SCSC_WLAN_DEBUG + .set_monitor_channel = slsi_set_monitor_channel, +#endif + .set_qos_map = slsi_set_qos_map +#endif +}; + +#define RATE_LEGACY(_rate, _hw_value, _flags) { \ + .bitrate = (_rate), \ + .hw_value = (_hw_value), \ + .flags = (_flags), \ +} + +#define CHAN2G(_freq, _idx) { \ + .band = NL80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 17, \ +} + +#define CHAN5G(_freq, _idx) { \ + .band = NL80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 17, \ +} + +static struct ieee80211_channel slsi_2ghz_channels[] = { + CHAN2G(2412, 1), + CHAN2G(2417, 2), + CHAN2G(2422, 3), + CHAN2G(2427, 4), + CHAN2G(2432, 5), + CHAN2G(2437, 6), + CHAN2G(2442, 7), + CHAN2G(2447, 8), + CHAN2G(2452, 9), + CHAN2G(2457, 10), + CHAN2G(2462, 11), + CHAN2G(2467, 12), + CHAN2G(2472, 13), + CHAN2G(2484, 14), +}; + +static struct ieee80211_rate slsi_11g_rates[] = { + RATE_LEGACY(10, 1, 0), + RATE_LEGACY(20, 2, IEEE80211_RATE_SHORT_PREAMBLE), + RATE_LEGACY(55, 3, IEEE80211_RATE_SHORT_PREAMBLE), + RATE_LEGACY(110, 6, IEEE80211_RATE_SHORT_PREAMBLE), + RATE_LEGACY(60, 4, 0), + RATE_LEGACY(90, 5, 0), + RATE_LEGACY(120, 7, 0), + RATE_LEGACY(180, 8, 0), + RATE_LEGACY(240, 9, 0), + RATE_LEGACY(360, 10, 0), + RATE_LEGACY(480, 11, 0), + RATE_LEGACY(540, 12, 0), +}; + +static struct ieee80211_channel slsi_5ghz_channels[] = { + /* _We_ call this UNII 1 */ + CHAN5G(5180, 36), + CHAN5G(5200, 40), + CHAN5G(5220, 44), + CHAN5G(5240, 48), + /* UNII 2 */ + CHAN5G(5260, 52), + CHAN5G(5280, 56), + CHAN5G(5300, 60), + CHAN5G(5320, 64), + /* "Middle band" */ + CHAN5G(5500, 100), + CHAN5G(5520, 104), + CHAN5G(5540, 108), + CHAN5G(5560, 112), + CHAN5G(5580, 116), + CHAN5G(5600, 120), + CHAN5G(5620, 124), + CHAN5G(5640, 128), + CHAN5G(5660, 132), + CHAN5G(5680, 136), + CHAN5G(5700, 140), + CHAN5G(5720, 144), + /* UNII 3 */ + CHAN5G(5745, 149), + CHAN5G(5765, 153), + CHAN5G(5785, 157), + CHAN5G(5805, 161), + CHAN5G(5825, 165), +}; + +/* note fw_rate_idx_to_host_11a_idx[] below must change if this table changes */ + +static struct ieee80211_rate wifi_11a_rates[] = { + RATE_LEGACY(60, 4, 0), + RATE_LEGACY(90, 5, 0), + RATE_LEGACY(120, 7, 0), + RATE_LEGACY(180, 8, 0), + RATE_LEGACY(240, 9, 0), + RATE_LEGACY(360, 10, 0), + RATE_LEGACY(480, 11, 0), + RATE_LEGACY(540, 12, 0), +}; + +static struct ieee80211_sta_ht_cap slsi_ht_cap = { + .ht_supported = true, + .cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_LDPC_CODING | + IEEE80211_HT_CAP_RX_STBC | + IEEE80211_HT_CAP_GRN_FLD | + IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_SGI_40, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_4, + .mcs = { + .rx_mask = { 0xff, 0, }, + .rx_highest = cpu_to_le16(0), + .tx_params = 0, + }, +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +struct ieee80211_sta_vht_cap slsi_vht_cap = { + .vht_supported = true, + .cap = IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991 | + IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + (5 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT), + .vht_mcs = { + .rx_mcs_map = cpu_to_le16(0xfffe), + .rx_highest = cpu_to_le16(0), + .tx_mcs_map = cpu_to_le16(0xfffe), + .tx_highest = cpu_to_le16(0), + }, +}; +#endif + +struct ieee80211_supported_band slsi_band_2ghz = { + .channels = slsi_2ghz_channels, + .band = NL80211_BAND_2GHZ, + .n_channels = ARRAY_SIZE(slsi_2ghz_channels), + .bitrates = slsi_11g_rates, + .n_bitrates = ARRAY_SIZE(slsi_11g_rates), +}; + +struct ieee80211_supported_band slsi_band_5ghz = { + .channels = slsi_5ghz_channels, + .band = NL80211_BAND_5GHZ, + .n_channels = ARRAY_SIZE(slsi_5ghz_channels), + .bitrates = wifi_11a_rates, + .n_bitrates = ARRAY_SIZE(wifi_11a_rates), +}; + +static const u32 slsi_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_SMS4, + WLAN_CIPHER_SUITE_PMK +}; + +static const struct ieee80211_txrx_stypes + ieee80211_default_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_AP] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) + }, + [NL80211_IFTYPE_STATION] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) + }, +}; + +/* Interface combinations supported by driver */ +static struct ieee80211_iface_limit iface_limits[] = { +#ifdef CONFIG_SCSC_WLAN_STA_ONLY + /* Basic STA-only */ + { + .max = CONFIG_SCSC_WLAN_MAX_INTERFACES, + .types = BIT(NL80211_IFTYPE_STATION), + }, +#else + /* AP mode: # AP <= 1 on channel = 1 */ + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP), + }, + /* STA and P2P mode: #STA <= 1, #{P2P-client,P2P-GO} <= 1 on two channels */ + /* For P2P, the device mode and group mode is first started as STATION and then changed. + * Similarly it is changed to STATION on group removal. Hence set maximum interfaces for STATION. + */ + { + .max = CONFIG_SCSC_WLAN_MAX_INTERFACES, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), + }, + /* ADHOC mode: #ADHOC <= 1 on channel = 1 */ + { + .max = 1, + .types = BIT(NL80211_IFTYPE_ADHOC), + }, +#endif +}; + +static struct ieee80211_regdomain slsi_regdomain = { + .reg_rules = { + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + REG_RULE(0, 0, 0, 0, 0, 0), + } +}; + +static struct ieee80211_iface_combination iface_comb[] = { + { + .limits = iface_limits, + .n_limits = ARRAY_SIZE(iface_limits), + .num_different_channels = 2, + .max_interfaces = CONFIG_SCSC_WLAN_MAX_INTERFACES, + }, +}; + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) +static struct cfg80211_wowlan slsi_wowlan_config = { + .any = true, +}; +#endif +#endif + +struct slsi_dev *slsi_cfg80211_new(struct device *dev) +{ + struct wiphy *wiphy; + struct slsi_dev *sdev = NULL; + + SLSI_DBG1_NODEV(SLSI_CFG80211, "wiphy_new()\n"); + wiphy = wiphy_new(&slsi_ops, sizeof(struct slsi_dev)); + if (!wiphy) { + SLSI_ERR_NODEV("wiphy_new() failed"); + return NULL; + } + + sdev = (struct slsi_dev *)wiphy->priv; + + sdev->wiphy = wiphy; + + set_wiphy_dev(wiphy, dev); + + /* Allow changing of the netns, if NOT set then no changes are allowed */ + wiphy->flags |= WIPHY_FLAG_NETNS_OK; + wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + wiphy->flags |= WIPHY_FLAG_CONTROL_PORT_PROTOCOL; + + /* wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM; + * + * Whilst the firmware does support roaming the driver MUST NOT advertise it + * as the supplicant will NOT send the BSSID and frequency information in the + * connect cfg80211 op. + * If the driver advertises FW_ROAM then the supplicant expects it to perform + * any scans required to find an appropriate AP and will only pass the SSID + */ + + wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | + WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | + WIPHY_FLAG_AP_UAPSD; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + wiphy->max_acl_mac_addrs = SLSI_AP_PEER_CONNECTIONS_MAX; +#endif + + wiphy->privid = sdev; + + wiphy->interface_modes = +#ifdef CONFIG_SCSC_WLAN_STA_ONLY + BIT(NL80211_IFTYPE_STATION); +#else + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | +#ifdef CONFIG_SCSC_WLAN_DEBUG + BIT(NL80211_IFTYPE_MONITOR) | +#endif + BIT(NL80211_IFTYPE_ADHOC); +#endif + slsi_band_2ghz.ht_cap = slsi_ht_cap; + slsi_band_5ghz.ht_cap = slsi_ht_cap; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + slsi_band_5ghz.vht_cap = slsi_vht_cap; +#endif + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + wiphy->bands[NL80211_BAND_2GHZ] = &slsi_band_2ghz; + wiphy->bands[NL80211_BAND_5GHZ] = &slsi_band_5ghz; + + memset(&sdev->device_config, 0, sizeof(struct slsi_dev_config)); + sdev->device_config.band_5G = &slsi_band_5ghz; + sdev->device_config.band_2G = &slsi_band_2ghz; + sdev->device_config.domain_info.regdomain = &slsi_regdomain; + + wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + wiphy->max_remain_on_channel_duration = 5000; /* 5000 msec */ + + wiphy->cipher_suites = slsi_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(slsi_cipher_suites); + + wiphy->mgmt_stypes = ieee80211_default_mgmt_stypes; + + /* Driver interface combinations */ + wiphy->n_iface_combinations = ARRAY_SIZE(iface_comb); + wiphy->iface_combinations = iface_comb; + + /* Basic scan parameters */ + wiphy->max_scan_ssids = 10; + wiphy->max_scan_ie_len = 2048; + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 11, 0)) + /* Scheduled scanning support */ + wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN; +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) + /* Parameters for Scheduled Scanning Support */ + wiphy->max_sched_scan_reqs = 1; + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI); +#endif + + /* Match the maximum number of SSIDs that could be requested from wpa_supplicant */ + wiphy->max_sched_scan_ssids = 16; + + /* To get a list of SSIDs rather than just the wildcard SSID need to support match sets */ + wiphy->max_match_sets = 16; + + wiphy->max_sched_scan_ie_len = 2048; + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) + wiphy->wowlan = NULL; + wiphy->wowlan_config = &slsi_wowlan_config; +#endif +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + wiphy->regulatory_flags |= (REGULATORY_STRICT_REG | + REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS); +#endif +#ifndef CONFIG_SCSC_WLAN_STA_ONLY + /* P2P flags */ + wiphy->flags |= WIPHY_FLAG_OFFCHAN_TX; + + /* Enable Probe response offloading w.r.t WPS and P2P */ + wiphy->probe_resp_offload |= + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; + + /* TDLS support */ + wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; +#endif + /* Mac Randomization */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + wiphy->features |= NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR; +#endif +#endif +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG + wiphy->features |= NL80211_FEATURE_SAE; +#endif + return sdev; +} + +int slsi_cfg80211_register(struct slsi_dev *sdev) +{ + SLSI_DBG1(sdev, SLSI_CFG80211, "wiphy_register()\n"); + return wiphy_register(sdev->wiphy); +} + +void slsi_cfg80211_unregister(struct slsi_dev *sdev) +{ +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) + sdev->wiphy->wowlan = NULL; + sdev->wiphy->wowlan_config = NULL; +#endif +#endif + SLSI_DBG1(sdev, SLSI_CFG80211, "wiphy_unregister()\n"); + wiphy_unregister(sdev->wiphy); +} + +void slsi_cfg80211_free(struct slsi_dev *sdev) +{ + SLSI_DBG1(sdev, SLSI_CFG80211, "wiphy_free()\n"); + wiphy_free(sdev->wiphy); +} + +void slsi_cfg80211_update_wiphy(struct slsi_dev *sdev) +{ + /* update supported Bands */ + if (sdev->band_5g_supported) { + sdev->wiphy->bands[NL80211_BAND_5GHZ] = &slsi_band_5ghz; + sdev->device_config.band_5G = &slsi_band_5ghz; + } else { + sdev->wiphy->bands[NL80211_BAND_5GHZ] = NULL; + sdev->device_config.band_5G = NULL; + } + + /* update HT features */ + if (sdev->fw_ht_enabled) { + slsi_ht_cap.ht_supported = true; + slsi_ht_cap.cap = le16_to_cpu(*(u16 *)sdev->fw_ht_cap); + slsi_ht_cap.ampdu_density = (sdev->fw_ht_cap[2] & IEEE80211_HT_AMPDU_PARM_DENSITY) >> IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT; + slsi_ht_cap.ampdu_factor = sdev->fw_ht_cap[2] & IEEE80211_HT_AMPDU_PARM_FACTOR; + } else { + slsi_ht_cap.ht_supported = false; + } + slsi_band_2ghz.ht_cap = slsi_ht_cap; + slsi_band_5ghz.ht_cap = slsi_ht_cap; + + /* update VHT features */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (sdev->fw_vht_enabled) { + slsi_vht_cap.vht_supported = true; + slsi_vht_cap.cap = le32_to_cpu(*(u32 *)sdev->fw_vht_cap); + } else { + slsi_vht_cap.vht_supported = false; + } + slsi_band_5ghz.vht_cap = slsi_vht_cap; +#endif + + SLSI_INFO(sdev, "BANDS SUPPORTED -> 2.4:'%c' 5:'%c'\n", sdev->wiphy->bands[NL80211_BAND_2GHZ] ? 'Y' : 'N', + sdev->wiphy->bands[NL80211_BAND_5GHZ] ? 'Y' : 'N'); + SLSI_INFO(sdev, "HT/VHT SUPPORTED -> HT:'%c' VHT:'%c'\n", sdev->fw_ht_enabled ? 'Y' : 'N', + sdev->fw_vht_enabled ? 'Y' : 'N'); + SLSI_INFO(sdev, "HT -> cap:0x%04x ampdu_density:%d ampdu_factor:%d\n", slsi_ht_cap.cap, slsi_ht_cap.ampdu_density, slsi_ht_cap.ampdu_factor); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + SLSI_INFO(sdev, "VHT -> cap:0x%08x\n", slsi_vht_cap.cap); +#endif +} diff --git a/drivers/net/wireless/scsc/cfg80211_ops.h b/drivers/net/wireless/scsc/cfg80211_ops.h new file mode 100755 index 000000000000..3e6c6ae4e5bc --- /dev/null +++ b/drivers/net/wireless/scsc/cfg80211_ops.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_CFG80211_OPS_H__ +#define __SLSI_CFG80211_OPS_H__ + +#include + +struct slsi_dev; + +#define SDEV_FROM_WIPHY(wiphy) ((struct slsi_dev *)(wiphy)->priv) +#define WLAN_CIPHER_SUITE_PMK 0x00904C00 + +#define SLSI_WPS_REQUEST_TYPE_POS 15 +#define SLSI_WPS_REQUEST_TYPE_ENROLEE_INFO_ONLY 0x00 +#define SLSI_WPS_OUI_PATTERN 0x04F25000 +#define SLSI_P2P_OUI_PATTERN 0x099a6f50 +#define SLSI_VENDOR_OUI_AND_TYPE_LEN 4 + +struct slsi_dev *slsi_cfg80211_new(struct device *dev); +int slsi_cfg80211_register(struct slsi_dev *sdev); +void slsi_cfg80211_unregister(struct slsi_dev *sdev); +void slsi_cfg80211_free(struct slsi_dev *sdev); +void slsi_cfg80211_update_wiphy(struct slsi_dev *sdev); +#endif /*__SLSI_CFG80211_OPS_H__*/ diff --git a/drivers/net/wireless/scsc/cm_if.c b/drivers/net/wireless/scsc/cm_if.c new file mode 100755 index 000000000000..43fc3de3ed25 --- /dev/null +++ b/drivers/net/wireless/scsc/cm_if.c @@ -0,0 +1,706 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + * Chip Manager interface + * + ****************************************************************************/ + +#include "mgt.h" +#include "dev.h" +#include "debug.h" +#include "scsc_wifi_cm_if.h" +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING +#include "hip4_sampler.h" +#endif + +#include +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +static bool EnableTestMode; +module_param(EnableTestMode, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(EnableTestMode, "Enable WlanLite test mode driver."); + +static BLOCKING_NOTIFIER_HEAD(slsi_wlan_notifier); + +static bool EnableRfTestMode; +module_param(EnableRfTestMode, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(EnableRfTestMode, "Enable RF test mode driver."); + +static struct mutex slsi_start_mutex; +static int recovery_in_progress; +static u16 latest_scsc_panic_code; + + +/* TODO: Would be good to get this removed - use module_client? */ +struct slsi_cm_ctx { + struct slsi_dev *sdev; +}; + +/* Only one wlan service instance is assumed for now. */ +static struct slsi_cm_ctx cm_ctx; + +static void slsi_hip_block_bh(struct slsi_dev *sdev); + +int slsi_wlan_service_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&slsi_wlan_notifier, nb); +} + +int slsi_wlan_service_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&slsi_wlan_notifier, nb); +} + +static int wlan_suspend(struct scsc_service_client *client) +{ + struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client); + + SLSI_INFO_NODEV("Nofity registered functions\n"); + blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_SUSPEND, sdev); + + return 0; +} + +static int wlan_resume(struct scsc_service_client *client) +{ + struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client); + + SLSI_INFO_NODEV("Nofity registered functions\n"); + blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_RESUME, sdev); + + return 0; +} + +static void wlan_stop_on_failure(struct scsc_service_client *client) +{ + int state; + struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client); + + SLSI_INFO_NODEV("\n"); + + mutex_lock(&slsi_start_mutex); + recovery_in_progress = 1; + sdev->recovery_status = 1; + state = atomic_read(&sdev->cm_if.cm_if_state); + if (state != SCSC_WIFI_CM_IF_STATE_STOPPED) { + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_BLOCKED); + sdev->fail_reported = true; + + /* If next state is stopped, then don't signal recovery since + * the Android framework won't/shouldn't restart (supplicant + * stop and start). + */ + if (sdev->recovery_next_state != SCSC_WIFI_CM_IF_STATE_STOPPING) { + slsi_hip_block_bh(sdev); + + /* Stop wlan operations. Send event to registered parties */ + mutex_unlock(&slsi_start_mutex); + SLSI_INFO_NODEV("Nofity registered functions\n"); + blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_STOP, sdev); + mutex_lock(&slsi_start_mutex); + } + } else { + SLSI_INFO_NODEV("Wi-Fi service driver not started\n"); + } + + mutex_unlock(&slsi_start_mutex); +} + +static void wlan_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code) +{ + SLSI_INFO_NODEV("\n"); + latest_scsc_panic_code = scsc_panic_code; +} + +int slsi_check_rf_test_mode(void) +{ + struct file *fp = NULL; +#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 + char *filepath = "/data/vendor/conn/.psm.info"; +#else + char *filepath = "/data/misc/conn/.psm.info"; +#endif + char power_val = 0; + + fp = filp_open(filepath, O_RDONLY, 0); + if (IS_ERR(fp) || (!fp)) { + pr_err("%s is not exist.\n", filepath); + return -ENOENT; /* -2 */ + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + kernel_read(fp, &power_val, 1, &fp->f_pos); +#else + kernel_read(fp, fp->f_pos, &power_val, 1); +#endif + /* if power_val is 0, it means rf_test mode by rf. */ + if (power_val == '0') { + pr_err("*#rf# is enabled.\n"); + EnableRfTestMode = 1; + } else { + pr_err("*#rf# is disabled.\n"); + EnableRfTestMode = 0; + } + + if (fp) + filp_close(fp, NULL); + + return 0; +} + +/* WLAN service driver registration + * ================================ + */ +void slsi_wlan_service_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + struct slsi_dev *sdev; + struct device *dev; + struct scsc_service_client mx_wlan_client; +#ifdef CONFIG_SCSC_LOG_COLLECTION + char buf[SCSC_LOG_FAPI_VERSION_SIZE]; +#endif + + SLSI_UNUSED_PARAMETER(module_client); + + SLSI_INFO_NODEV("WLAN service probe\n"); + + mutex_lock(&slsi_start_mutex); + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress) + goto done; + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY) { + SLSI_INFO_NODEV("Probe recovery\n"); + sdev = cm_ctx.sdev; + recovery_in_progress = 0; + sdev->fail_reported = false; + sdev->recovery_status = 0; + sdev->mlme_blocked = false; + complete_all(&sdev->recovery_completed); + } else { + /* Register callbacks */ + mx_wlan_client.stop_on_failure = wlan_stop_on_failure; + mx_wlan_client.failure_reset = wlan_failure_reset; + mx_wlan_client.suspend = wlan_suspend; + mx_wlan_client.resume = wlan_resume; + + dev = scsc_service_get_device_by_mx(mx); + + /* The mutex must be released at this point since the attach + * process may call various functions including + * slsi_sm_wlan_service_start and slsi_sm_wlan_service_open, which will + * claim the same mutex. + */ + mutex_unlock(&slsi_start_mutex); + sdev = slsi_dev_attach(dev, mx, &mx_wlan_client); + mutex_lock(&slsi_start_mutex); + if (!sdev) { + SLSI_ERR_NODEV("WLAN attach failed - slsi_dev_attach\n"); + goto done; + } + + cm_ctx.sdev = sdev; /* TODO: For now. */ + + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBING); + get_device(dev); + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + hip4_sampler_create(sdev, mx); +#endif +#ifdef CONFIG_SCSC_LOG_COLLECTION + memset(buf, 0, SCSC_LOG_FAPI_VERSION_SIZE); + /* Write FAPI VERSION to collector header */ + /* IMPORTANT - Do not change the formatting as User space tooling is parsing the string + * to read SAP fapi versions. + */ + snprintf(buf, SCSC_LOG_FAPI_VERSION_SIZE, "ma:%u.%u, mlme:%u.%u, debug:%u.%u, test:%u.%u", + FAPI_MAJOR_VERSION(FAPI_DATA_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_DATA_SAP_VERSION), + FAPI_MAJOR_VERSION(FAPI_CONTROL_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_CONTROL_SAP_VERSION), + FAPI_MAJOR_VERSION(FAPI_DEBUG_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_DEBUG_SAP_VERSION), + FAPI_MAJOR_VERSION(FAPI_TEST_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_TEST_SAP_VERSION)); + + scsc_log_collector_write_fapi(buf, SCSC_LOG_FAPI_VERSION_SIZE); +#endif + } + + if (reason != SCSC_MODULE_CLIENT_REASON_RECOVERY) + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBED); + +done: + mutex_unlock(&slsi_start_mutex); +} + +/* service_clean_up_locked expects the slsi_start_mutex mutex to be claimed when + * service_clean_up_locked is called. + */ +static void service_clean_up_locked(struct slsi_dev *sdev) +{ + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_REMOVING); + put_device(sdev->dev); + + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_REMOVED); + + sdev->maxwell_core = NULL; + + /* The mutex must be released at this point since the tear down + * process will call various functions including + * slsi_sm_wlan_service_stop and slsi_sm_wlan_service_close, which will + * claim the same mutex. + */ + mutex_unlock(&slsi_start_mutex); + slsi_dev_detach(sdev); + mutex_lock(&slsi_start_mutex); +} + +static void slsi_wlan_service_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason) +{ + struct slsi_dev *sdev; + int state; + + SLSI_UNUSED_PARAMETER(mx); + SLSI_UNUSED_PARAMETER(module_client); + + sdev = cm_ctx.sdev; + if (!sdev) { + SLSI_INFO_NODEV("no sdev\n"); + return; + } + + if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress) { + SLSI_INFO_NODEV("WLAN service remove - recovery. Service not active.\n"); + } else if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery_in_progress) { + int r; + + SLSI_INFO_NODEV("WLAN service remove - recovery\n"); + + /* Only indicate if the next state is not stopping. The recovery + * handling won't have any affect if the framework is closing + * anyway. + */ + if (sdev->recovery_next_state != SCSC_WIFI_CM_IF_STATE_STOPPING) { + SLSI_INFO_NODEV("Nofity registered functions\n"); + blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_FAILURE_RESET, sdev); + } + + mutex_lock(&slsi_start_mutex); + /** + * If there was a request to stop during the recovery, then do + * not sent a hang - just stop here. The Wi-Fi service driver is + * ready to be turned on again. Let the service_stop complete. + */ + complete_all(&sdev->recovery_remove_completion); + if (sdev->recovery_next_state == SCSC_WIFI_CM_IF_STATE_STOPPING) { + SLSI_INFO_NODEV("Recovery - next state stopping\n"); + } else { + SLSI_INFO_NODEV("Calling slsi_send_hanged_vendor_event with latest_scsc_panic_code=0x%x\n", + latest_scsc_panic_code); + if (slsi_send_hanged_vendor_event(sdev, latest_scsc_panic_code) < 0) + SLSI_ERR(sdev, "Failed to send hang event\n"); + + /* Complete any pending ctrl signals, which will prevent + * the hang event from being processed. + */ + complete_all(&sdev->sig_wait.completion); + } + + mutex_unlock(&slsi_start_mutex); + r = wait_for_completion_timeout(&sdev->recovery_stop_completion, + msecs_to_jiffies(sdev->recovery_timeout)); + if (r == 0) + SLSI_INFO(sdev, "recovery_stop_completion timeout\n"); + + mutex_lock(&slsi_start_mutex); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) + reinit_completion(&sdev->recovery_stop_completion); +#else + /*This is how the macro is used in the older verion.*/ + INIT_COMPLETION(sdev->recovery_stop_completion); +#endif + mutex_unlock(&slsi_start_mutex); + + } else { + SLSI_INFO_NODEV("WLAN service remove\n"); + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + hip4_sampler_destroy(sdev, mx); +#endif + + mutex_lock(&slsi_start_mutex); + state = atomic_read(&sdev->cm_if.cm_if_state); + if (state != SCSC_WIFI_CM_IF_STATE_STARTED && + state != SCSC_WIFI_CM_IF_STATE_PROBED && + state != SCSC_WIFI_CM_IF_STATE_STOPPED && + state != SCSC_WIFI_CM_IF_STATE_BLOCKED) { + mutex_unlock(&slsi_start_mutex); + SLSI_INFO_NODEV("state-event error %d\n", state); + return; + } + + service_clean_up_locked(sdev); + mutex_unlock(&slsi_start_mutex); + } +} + +/* Block future HIP runs through the hip_switch */ +static void slsi_hip_block_bh(struct slsi_dev *sdev) +{ + SLSI_WARN(sdev, "HIP state set to #SLSI_HIP_STATE_BLOCKED#\n"); + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_BLOCKED); +} + +struct scsc_mx_module_client wlan_driver = { + .name = "WLAN driver", + .probe = slsi_wlan_service_probe, + .remove = slsi_wlan_service_remove, +}; + +int slsi_sm_service_driver_register(void) +{ + struct slsi_cm_ctx *ctx = &cm_ctx; + + memset(ctx, 0, sizeof(*ctx)); + mutex_init(&slsi_start_mutex); + scsc_mx_module_register_client_module(&wlan_driver); + + return 0; +} + +void slsi_sm_service_driver_unregister(void) +{ + scsc_mx_module_unregister_client_module(&wlan_driver); +} + +/* start/stop wlan service + * ======================= + */ +void slsi_sm_service_failed(struct slsi_dev *sdev, const char *reason) +{ + int state; + + mutex_lock(&slsi_start_mutex); + + state = atomic_read(&sdev->cm_if.cm_if_state); + if (state != SCSC_WIFI_CM_IF_STATE_STARTED && + state != SCSC_WIFI_CM_IF_STATE_STOPPING) { + mutex_unlock(&slsi_start_mutex); + SLSI_INFO(sdev, "State %d - ignoring event\n", state); + return; + } + + /* Limit the volume of error reports to the core */ + if (!sdev->fail_reported) { + /* This log may be scraped by test systems */ + SLSI_ERR(sdev, "scsc_wifibt: FATAL ERROR: %s\n", reason); + + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_BLOCKED); + slsi_hip_block_bh(sdev); + scsc_mx_service_service_failed(sdev->service, reason); + scsc_mx_service_mif_dump_registers(sdev->service); + sdev->fail_reported = true; + } + + mutex_unlock(&slsi_start_mutex); +} + +/* Is production test mode enabled? */ +bool slsi_is_test_mode_enabled(void) +{ + return EnableTestMode; +} + +/* Is production rf test mode enabled? */ +bool slsi_is_rf_test_mode_enabled(void) +{ + return EnableRfTestMode; +} + +#define SLSI_SM_WLAN_SERVICE_RECOVERY_COMPLETED_TIMEOUT 20000 +#define SLSI_SM_WLAN_SERVICE_RECOVERY_DISABLED_TIMEOUT 2000 + +int slsi_sm_wlan_service_open(struct slsi_dev *sdev) +{ + int err = 0; + int state; + + mutex_lock(&slsi_start_mutex); + state = atomic_read(&sdev->cm_if.cm_if_state); + if (state != SCSC_WIFI_CM_IF_STATE_PROBED && + state != SCSC_WIFI_CM_IF_STATE_STOPPED) { + SLSI_INFO(sdev, "State-event error %d\n", state); + err = -EINVAL; + goto exit; + } + + sdev->recovery_timeout = mxman_recovery_disabled() ? SLSI_SM_WLAN_SERVICE_RECOVERY_DISABLED_TIMEOUT : SLSI_SM_WLAN_SERVICE_RECOVERY_COMPLETED_TIMEOUT; + + /* Open service - will download FW - will set MBOX0 with Starting address */ + SLSI_INFO(sdev, "Open WLAN service\n"); + sdev->service = scsc_mx_service_open(sdev->maxwell_core, SCSC_SERVICE_ID_WLAN, &sdev->mx_wlan_client, &err); + if (!sdev->service) { + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBED); + SLSI_WARN(sdev, "Service open failed\n"); + err = -EINVAL; + goto exit; + } + +exit: + mutex_unlock(&slsi_start_mutex); + return err; +} + +#if defined(CONFIG_SLUB_DEBUG_ON) || defined(CONFIG_DEBUG_PREEMPT) || defined(CONFIG_DEBUG_RT_MUTEXES) || \ + defined(CONFIG_DEBUG_SPINLOCK) && defined(CONFIG_DEBUG_MUTEXES) && defined(CONFIG_DEBUG_LOCK_ALLOC) || \ + defined(CONFIG_DEBUG_LOCK_ALLOC) && defined(CONFIG_DEBUG_ATOMIC_SLEEP) && defined(CONFIG_DEBUG_LIST) +#define KERNEL_DEBUG_OPTIONS_ENABLED +#endif + +int slsi_sm_wlan_service_start(struct slsi_dev *sdev) +{ + struct slsi_hip4 *hip = &sdev->hip4_inst; + scsc_mifram_ref ref; + int err = 0; + int err2 = 0; + int state; + + mutex_lock(&slsi_start_mutex); + state = atomic_read(&sdev->cm_if.cm_if_state); + SLSI_INFO(sdev, + "Recovery -- Status:%d In_Progress:%d -- cm_if_state:%d\n", + sdev->recovery_status, recovery_in_progress, state); + if (state != SCSC_WIFI_CM_IF_STATE_PROBED && + state != SCSC_WIFI_CM_IF_STATE_STOPPED) { + SLSI_INFO(sdev, "State-event error %d\n", state); + mutex_unlock(&slsi_start_mutex); + return -EINVAL; + } + + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STARTING); + +#ifdef KERNEL_DEBUG_OPTIONS_ENABLED + SLSI_WARN(sdev, "Kernel config debug options are enabled. This might impact the throughput performance.\n"); +#endif + + /* Get RAM from the MIF */ + SLSI_INFO(sdev, "Allocate mifram\n"); + +#ifdef CONFIG_SCSC_PCIE + err = scsc_mx_service_mifram_alloc(sdev->service, 1.5 * 1024 * 1024, &sdev->hip4_inst.hip_ref, 4096); +#else + err = scsc_mx_service_mifram_alloc(sdev->service, 2 * 1024 * 1024, &sdev->hip4_inst.hip_ref, 4096); +#endif + if (err) { + SLSI_WARN(sdev, "scsc_mx_service_mifram_alloc failed err: %d\n", err); + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); + mutex_unlock(&slsi_start_mutex); + return err; + } + + SLSI_INFO(sdev, "Start HIP\n"); + err = slsi_hip_start(sdev); + if (err) { + SLSI_WARN(sdev, "slsi_hip_start failed err: %d\n", err); + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); + slsi_hip_stop(sdev); + mutex_unlock(&slsi_start_mutex); + return err; + } + + err = scsc_mx_service_mif_ptr_to_addr(sdev->service, hip->hip_control, &ref); + if (err) { + SLSI_WARN(sdev, "scsc_mx_service_mif_ptr_to_addr failed err: %d\n", err); + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); + slsi_hip_stop(sdev); + mutex_unlock(&slsi_start_mutex); + return err; + } + + SLSI_INFO(sdev, "Starting WLAN service\n"); + err = scsc_mx_service_start(sdev->service, ref); + if (err) { + SLSI_WARN(sdev, "scsc_mx_service_start failed err: %d\n", err); + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); + slsi_hip_stop(sdev); + mutex_unlock(&slsi_start_mutex); + return err; + } + err = slsi_hip_setup(sdev); + if (err) { + SLSI_WARN(sdev, "slsi_hip_setup failed err: %d\n", err); + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); + SLSI_INFO_NODEV("Stopping WLAN service\n"); + err2 = scsc_mx_service_stop(sdev->service); + if (err2) + SLSI_INFO(sdev, "scsc_mx_service_stop failed err2: %d\n", err2); + slsi_hip_stop(sdev); + mutex_unlock(&slsi_start_mutex); + return err; + } + /* Service has started, inform SAP versions to the registered SAPs */ + err = slsi_hip_sap_setup(sdev); + if (err) { + SLSI_WARN(sdev, "slsi_hip_sap_setup failed err: %d\n", err); + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); + SLSI_INFO_NODEV("Stopping WLAN service\n"); + err2 = scsc_mx_service_stop(sdev->service); + if (err2) + SLSI_INFO(sdev, "scsc_mx_service_stop failed err2: %d\n", err2); + slsi_hip_stop(sdev); + mutex_unlock(&slsi_start_mutex); + return err; + } + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STARTED); + mutex_unlock(&slsi_start_mutex); + return 0; +} + +static void __slsi_sm_wlan_service_stop_wait_locked(struct slsi_dev *sdev) +{ + int r; + + mutex_unlock(&slsi_start_mutex); + r = wait_for_completion_timeout(&sdev->recovery_remove_completion, + msecs_to_jiffies(sdev->recovery_timeout)); + if (r == 0) + SLSI_INFO(sdev, "recovery_remove_completion timeout\n"); + + mutex_lock(&slsi_start_mutex); + sdev->recovery_next_state = SCSC_WIFI_CM_IF_STATE_STOPPED; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) + reinit_completion(&sdev->recovery_remove_completion); +#else + /*This is how the macro is used in the older verion.*/ + INIT_COMPLETION(sdev->recovery_remove_completion); +#endif +} + +void slsi_sm_wlan_service_stop(struct slsi_dev *sdev) +{ + int cm_if_state; + int err = 0; + + mutex_lock(&slsi_start_mutex); + cm_if_state = atomic_read(&sdev->cm_if.cm_if_state); + SLSI_INFO(sdev, + "Recovery -- Status:%d In_Progress:%d -- cm_if_state:%d\n", + sdev->recovery_status, recovery_in_progress, cm_if_state); + + if (cm_if_state == SCSC_WIFI_CM_IF_STATE_BLOCKED) { + __slsi_sm_wlan_service_stop_wait_locked(sdev); + + /* If the wait hasn't timed out, the recovery remove completion + * will have completed properly and the cm_if_state will be + * set to stopped here. If the probe hasn't fired for some reason + * try and do a service_stop regardless, since that's all we can + * do in this situation; hence skip the state check. + */ + goto skip_state_check; + } + + if (cm_if_state != SCSC_WIFI_CM_IF_STATE_STARTED && + cm_if_state != SCSC_WIFI_CM_IF_STATE_REMOVED && + cm_if_state != SCSC_WIFI_CM_IF_STATE_PROBED) { + SLSI_INFO(sdev, "Service not started or incorrect state %d\n", + cm_if_state); + goto exit; + } + + /** + * Note that the SCSC_WIFI_CM_IF_STATE_STOPPING state will inhibit + * auto-recovery mechanism, so be careful not to abuse it: as an + * example if panic happens on start or stop we don't want to + * un-necessarily pass by STOPPING in order to have a successful + * recovery in such a situation. + */ + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPING); +skip_state_check: + SLSI_INFO_NODEV("Stopping WLAN service\n"); + err = scsc_mx_service_stop(sdev->service); + if (err == -EILSEQ) { + /* scsc_mx_service_stop failed since there's a recovery in + * progress, so just wait for it to complete and try again. + */ + SLSI_INFO(sdev, "scsc_mx_service_stop failed err: %d\n", err); + __slsi_sm_wlan_service_stop_wait_locked(sdev); + goto skip_state_check; + } else if (err == -EIO) { + char reason[80]; + + SLSI_INFO(sdev, "scsc_mx_service_stop failed err: %d\n", err); + + /* scsc_mx_service_stop since there was no respons from firmware + * to the stop request. Generate a host initiated panic to reset + * the chip and wait for it to complete. + */ + sdev->recovery_next_state = SCSC_WIFI_CM_IF_STATE_STOPPING; + snprintf(reason, sizeof(reason), "WLAN scsc_mx_service_stop failed"); + + mutex_unlock(&slsi_start_mutex); + slsi_sm_service_failed(sdev, reason); + mutex_lock(&slsi_start_mutex); + + __slsi_sm_wlan_service_stop_wait_locked(sdev); + } else if (err == -EPERM) { + /* Special case when recovery is disabled, otherwise the driver + * will wait forever for recovery that never comes + */ + SLSI_INFO(sdev, "refused due to previous failure, recovery is disabled: %d\n", err); + } else if (err != 0) { + SLSI_INFO(sdev, "scsc_mx_service_stop failed, unknown err: %d\n", err); + } + + atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED); +exit: + mutex_unlock(&slsi_start_mutex); +} + +#define SLSI_SM_WLAN_SERVICE_CLOSE_RETRY 60 +void slsi_sm_wlan_service_close(struct slsi_dev *sdev) +{ + int cm_if_state, r; + + mutex_lock(&slsi_start_mutex); + cm_if_state = atomic_read(&sdev->cm_if.cm_if_state); + if (cm_if_state != SCSC_WIFI_CM_IF_STATE_STOPPED) { + SLSI_INFO(sdev, "Service not stopped\n"); + goto exit; + } + + SLSI_INFO_NODEV("Closing WLAN service\n"); + scsc_mx_service_mifram_free(sdev->service, sdev->hip4_inst.hip_ref); + r = scsc_mx_service_close(sdev->service); + if (r == -EIO) { + int retry_counter; + + /** + * Error handling in progress - try and close again later. + * The service close call shall remain blocked until close + * service is successful. Try up to 30 seconds. + */ + for (retry_counter = 0; + SLSI_SM_WLAN_SERVICE_CLOSE_RETRY > retry_counter; + retry_counter++) { + msleep(500); + r = scsc_mx_service_close(sdev->service); + if (r == 0) { + SLSI_INFO(sdev, "scsc_mx_service_close closed after %d attempts\n", + retry_counter + 1); + break; + } + } + + if (retry_counter + 1 == SLSI_SM_WLAN_SERVICE_CLOSE_RETRY) + SLSI_ERR(sdev, "scsc_mx_service_close failed %d times\n", + SLSI_SM_WLAN_SERVICE_CLOSE_RETRY); + } else if (r == -EPERM) { + SLSI_ERR(sdev, "scsc_mx_service_close - recovery is disabled (%d)\n", r); + } + + if (recovery_in_progress) + complete_all(&sdev->recovery_stop_completion); +exit: + mutex_unlock(&slsi_start_mutex); +} diff --git a/drivers/net/wireless/scsc/const.h b/drivers/net/wireless/scsc/const.h new file mode 100755 index 000000000000..470c1dca472c --- /dev/null +++ b/drivers/net/wireless/scsc/const.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef _SLSI_CONST_H__ +#define _SLSI_CONST_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Max number of peers */ +#define SLSI_TDLS_PEER_CONNECTIONS_MAX 4 +#define SLSI_AP_PEER_CONNECTIONS_MAX 10 +/* When SLSI_ADHOC_PEER_CONNECTIONS_MAX is increased to 32, + * method of intiliazing peer_sta_record[] should be carefully changed + * from the present way of static allocation to a dynamic allocation or + * a VIF specific initialization or both. + */ +#define SLSI_ADHOC_PEER_CONNECTIONS_MAX 16 + +/* Max number of indexes */ +#define SLSI_TDLS_PEER_INDEX_MIN 2 +#define SLSI_TDLS_PEER_INDEX_MAX 15 +#define SLSI_PEER_INDEX_MIN 1 +#define SLSI_PEER_INDEX_MAX 16 + +#define SLSI_STA_PEER_QUEUESET 0 + +#define SLSI_BA_TRAFFIC_STREAM_MAX 8 +#define SLSI_BA_BUFFER_SIZE_MAX 64 + +/* Until LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 1) the IE Value for + * HT OPERATION was incorrectly defined as HT INFORMATION + */ +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 7, 0)) +#ifndef WLAN_EID_HT_INFORMATION +#define WLAN_EID_HT_INFORMATION 61 +#endif + +#define WLAN_EID_HT_OPERATION WLAN_EID_HT_INFORMATION +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _SLSI_CONST_H__ */ diff --git a/drivers/net/wireless/scsc/debug.c b/drivers/net/wireless/scsc/debug.c new file mode 100755 index 000000000000..a3907a060bcf --- /dev/null +++ b/drivers/net/wireless/scsc/debug.c @@ -0,0 +1,437 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include + +#include "debug.h" +#include "procfs.h" +#include "utils.h" + +#ifndef CONFIG_SCSC_DEBUG_COMPATIBILITY +const int SLSI_INIT_DEINIT; +const int SLSI_NETDEV = 1; +const int SLSI_CFG80211 = 2; +const int SLSI_MLME = 3; +const int SLSI_SUMMARY_FRAMES = 4; +const int SLSI_HYDRA = 5; +const int SLSI_TX = 6; +const int SLSI_RX = 7; +const int SLSI_UDI = 8; + +const int SLSI_WIFI_FCQ = 9; + +const int SLSI_HIP = 10; +const int SLSI_HIP_INIT_DEINIT = 11; +const int SLSI_HIP_FW_DL = 12; +const int SLSI_HIP_SDIO_OP = 13; +const int SLSI_HIP_PS = 14; +const int SLSI_HIP_TH = 15; +const int SLSI_HIP_FH = 16; +const int SLSI_HIP_SIG = 17; + +const int SLSI_FUNC_TRACE = 18; +const int SLSI_TEST = 19; /* Unit test logging */ +const int SLSI_SRC_SINK = 20; +const int SLSI_FW_TEST = 21; +const int SLSI_RX_BA = 22; + +const int SLSI_TDLS = 23; +const int SLSI_GSCAN = 24; +const int SLSI_MBULK = 25; +const int SLSI_FLOWC = 26; +const int SLSI_SMAPPER = 27; +#endif + +static int slsi_dbg_set_param_cb(const char *val, const struct kernel_param *kp); +static int slsi_dbg_get_param_cb(char *buffer, const struct kernel_param *kp); + +static struct kernel_param_ops param_ops_log = { + .set = slsi_dbg_set_param_cb, + .get = slsi_dbg_get_param_cb, +}; + +#define ADD_DEBUG_MODULE_PARAM(name, default_level, filter) \ + int slsi_dbg_lvl_ ## name = default_level; \ + module_param_cb(slsi_dbg_lvl_ ## name, ¶m_ops_log, (void *)&filter, S_IRUGO | S_IWUSR); \ + MODULE_PARM_DESC(slsi_dbg_lvl_ ## name, " Debug levels (0~4) for the " # name " module (0 = off) default=" # default_level) + +#ifndef CONFIG_SCSC_DEBUG_COMPATIBILITY +/* Name, Default, Filter */ +ADD_DEBUG_MODULE_PARAM(init_deinit, 3, SLSI_INIT_DEINIT); +ADD_DEBUG_MODULE_PARAM(netdev, 2, SLSI_NETDEV); +ADD_DEBUG_MODULE_PARAM(cfg80211, 1, SLSI_CFG80211); +ADD_DEBUG_MODULE_PARAM(mlme, 2, SLSI_MLME); +ADD_DEBUG_MODULE_PARAM(summary_frames, 0, SLSI_SUMMARY_FRAMES); +ADD_DEBUG_MODULE_PARAM(hydra, 0, SLSI_HYDRA); +ADD_DEBUG_MODULE_PARAM(tx, 0, SLSI_TX); +ADD_DEBUG_MODULE_PARAM(rx, 0, SLSI_RX); +ADD_DEBUG_MODULE_PARAM(udi, 2, SLSI_UDI); + +ADD_DEBUG_MODULE_PARAM(wifi_fcq, 0, SLSI_WIFI_FCQ); + +ADD_DEBUG_MODULE_PARAM(hip, 0, SLSI_HIP); +ADD_DEBUG_MODULE_PARAM(hip_init_deinit, 0, SLSI_HIP_INIT_DEINIT); +ADD_DEBUG_MODULE_PARAM(hip_fw_dl, 0, SLSI_HIP_FW_DL); +ADD_DEBUG_MODULE_PARAM(hip_sdio_op, 0, SLSI_HIP_SDIO_OP); +ADD_DEBUG_MODULE_PARAM(hip_ps, 0, SLSI_HIP_PS); +ADD_DEBUG_MODULE_PARAM(hip_th, 0, SLSI_HIP_TH); +ADD_DEBUG_MODULE_PARAM(hip_fh, 0, SLSI_HIP_FH); +ADD_DEBUG_MODULE_PARAM(hip_sig, 0, SLSI_HIP_SIG); + +ADD_DEBUG_MODULE_PARAM(func_trace, 0, SLSI_FUNC_TRACE); +ADD_DEBUG_MODULE_PARAM(test, 0, SLSI_TEST); +ADD_DEBUG_MODULE_PARAM(src_sink, 0, SLSI_SRC_SINK); +ADD_DEBUG_MODULE_PARAM(fw_test, 0, SLSI_FW_TEST); +ADD_DEBUG_MODULE_PARAM(rx_ba, 0, SLSI_RX_BA); + +ADD_DEBUG_MODULE_PARAM(tdls, 2, SLSI_TDLS); +ADD_DEBUG_MODULE_PARAM(gscan, 3, SLSI_GSCAN); +ADD_DEBUG_MODULE_PARAM(mbulk, 0, SLSI_MBULK); +ADD_DEBUG_MODULE_PARAM(flowc, 0, SLSI_FLOWC); +ADD_DEBUG_MODULE_PARAM(smapper, 0, SLSI_SMAPPER); + +int slsi_dbg_lvl_all; /* Override all debug modules */ + +int *slsi_dbg_filters[] = { + &slsi_dbg_lvl_init_deinit, + &slsi_dbg_lvl_netdev, + &slsi_dbg_lvl_cfg80211, + &slsi_dbg_lvl_mlme, + &slsi_dbg_lvl_summary_frames, + &slsi_dbg_lvl_hydra, + &slsi_dbg_lvl_tx, + &slsi_dbg_lvl_rx, + &slsi_dbg_lvl_udi, + + &slsi_dbg_lvl_wifi_fcq, + + &slsi_dbg_lvl_hip, + &slsi_dbg_lvl_hip_init_deinit, + &slsi_dbg_lvl_hip_fw_dl, + &slsi_dbg_lvl_hip_sdio_op, + &slsi_dbg_lvl_hip_ps, + &slsi_dbg_lvl_hip_th, + &slsi_dbg_lvl_hip_fh, + &slsi_dbg_lvl_hip_sig, + + &slsi_dbg_lvl_func_trace, + &slsi_dbg_lvl_test, + &slsi_dbg_lvl_src_sink, + &slsi_dbg_lvl_fw_test, + &slsi_dbg_lvl_rx_ba, + + &slsi_dbg_lvl_tdls, + &slsi_dbg_lvl_gscan, + &slsi_dbg_lvl_mbulk, + &slsi_dbg_lvl_flowc, + &slsi_dbg_lvl_smapper, +}; +#else +int slsi_dbg_lvl_compat_all; +module_param(slsi_dbg_lvl_compat_all, int, S_IRUGO | S_IWUSR); + +int *slsi_dbg_filters[] = { + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, + &slsi_dbg_lvl_compat_all, +}; + +int slsi_dbg_lvl_all; /* Override all debug modules */ +#endif + +const int SLSI_DF_MAX = (sizeof(slsi_dbg_filters) / sizeof(slsi_dbg_filters[0])); + +const int SLSI_OVERRIDE_ALL_FILTER = -1; /* This is not a log module but merely a filter option */ + +/* Convert a string containing a decimal value to an integer */ +static int slsi_decstr_to_int(const char *dec_str, int *res) +{ + int tmp_res = 0; + int sign = 0; + const char *tmp_char = dec_str; + + sign = (*tmp_char == '-') ? -1 : ((*tmp_char == '+') ? 1 : 0); + if (sign != 0) + tmp_char++; + + while (*tmp_char) { + if (*tmp_char == '\n') + break; + if ((*tmp_char < '0') || (*tmp_char > '9')) + return -1; + tmp_res = tmp_res * 10 + (*tmp_char - '0'); + tmp_char++; + } + + *res = (sign < 0) ? (-tmp_res) : tmp_res; + return 0; +} + +static int slsi_dbg_set_param_cb(const char *val, const struct kernel_param *kp) +{ + int new_val; + int filter; + + if (slsi_decstr_to_int(val, &new_val) < 0) { + pr_info("%s: failed to convert %s to int\n", __func__, val); + return -1; + } + filter = *((int *)(kp->arg)); + + if (filter < -1 || filter >= SLSI_DF_MAX) { + pr_info("%s: filter %d out of range\n", __func__, filter); + return -1; + } + + if (filter == SLSI_OVERRIDE_ALL_FILTER) { + if (new_val == -1) { + pr_info("Override does not take effect because slsi_dbg_lvl_all=%d\n", new_val); + } else { + int i; + + pr_info("Setting all debug modules to level %d\n", new_val); + for (i = 0; i < SLSI_DF_MAX; i++) + *slsi_dbg_filters[i] = new_val; + + slsi_dbg_lvl_all = new_val; + } + } else { + pr_info("Setting debug module %d to level %d\n", filter, new_val); + *slsi_dbg_filters[filter] = new_val; + } + + return 0; +} + +static int slsi_dbg_get_param_cb(char *buffer, const struct kernel_param *kp) +{ +#define KERN_PARAM_OPS_MAX_BUF_SIZE (4 * 1024) + int filter; + int val = 0; + + filter = *((int *)(kp->arg)); + + if (filter == SLSI_OVERRIDE_ALL_FILTER) + val = slsi_dbg_lvl_all; + else if (filter < 0 || filter >= SLSI_DF_MAX) + pr_info("%s: filter %d out of range\n", __func__, filter); + else + val = *slsi_dbg_filters[filter]; + + return snprintf(buffer, KERN_PARAM_OPS_MAX_BUF_SIZE, "%i", val); +} + +module_param_cb(slsi_dbg_lvl_all, ¶m_ops_log, (void *)&SLSI_OVERRIDE_ALL_FILTER, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(slsi_dbg_lvl_all, "Override debug level (0~4) for all the log modules (-1 = do not override) default=0"); + +#ifdef CONFIG_SCSC_WLAN_SKB_TRACKING +struct slsi_skb_tracker { + spinlock_t lock; + struct list_head tracked; + u32 tracked_count; + u32 tracked_count_max; +}; + +struct slsi_tracked_skb { + struct list_head entry; + const char *file; + int line; + struct sk_buff *skb; +}; + +static struct slsi_skb_tracker skb_tracker; + +void slsi_dbg_track_skb_init(void) +{ + SLSI_DBG4_NODEV(SLSI_TEST, "\n"); + memset(&skb_tracker, 0x00, sizeof(skb_tracker)); + spin_lock_init(&skb_tracker.lock); + INIT_LIST_HEAD(&skb_tracker.tracked); +} + +void slsi_dbg_track_skb_reset(void) +{ + SLSI_DBG4_NODEV(SLSI_TEST, "\n"); + skb_tracker.tracked_count = 0; + skb_tracker.tracked_count_max = 0; +} + +bool slsi_dbg_track_skb_marker_f(struct sk_buff *skb, const char *file, int line) +{ + struct slsi_tracked_skb *t; + struct list_head *pos, *q; + bool r = true; + unsigned long flags; + + if (!skb) + return r; + + spin_lock_irqsave(&skb_tracker.lock, flags); + list_for_each_safe(pos, q, &skb_tracker.tracked) { + t = list_entry(pos, struct slsi_tracked_skb, entry); + if (t->skb == skb) { + SLSI_DBG4_NODEV(SLSI_TEST, "Marker 0x%p: %s:%d\n", skb, file, line); + t->file = file; + t->line = line; + goto exit; + } + } + WARN_ON(1); + SLSI_ERR_NODEV("SKB Not Tracked: %p: %s:%d\n", skb, file, line); + r = false; +exit: + spin_unlock_irqrestore(&skb_tracker.lock, flags); + return r; +} + +void slsi_dbg_track_skb_f(struct sk_buff *skb, gfp_t flags, const char *file, int line) +{ + struct slsi_tracked_skb *t = kmalloc(sizeof(*t), flags); + unsigned long flags_irq; + + if (!t) + return; + + t->file = file; + t->line = line; + t->skb = skb_get(skb); /* Add a reference to the skb */ + SLSI_DBG4_NODEV(SLSI_TEST, "track SKB: 0x%p: %s:%d\n", skb, file, line); + spin_lock_irqsave(&skb_tracker.lock, flags_irq); + list_add(&t->entry, &skb_tracker.tracked); + skb_tracker.tracked_count++; + if (skb_tracker.tracked_count > skb_tracker.tracked_count_max) + skb_tracker.tracked_count_max = skb_tracker.tracked_count; + spin_unlock_irqrestore(&skb_tracker.lock, flags_irq); +} + +bool slsi_dbg_untrack_skb_f(struct sk_buff *skb, const char *file, int line) +{ + struct slsi_tracked_skb *t; + struct list_head *pos, *q; + bool r = true; + unsigned long flags; + + if (!skb) + return r; + + spin_lock_irqsave(&skb_tracker.lock, flags); + list_for_each_safe(pos, q, &skb_tracker.tracked) { + t = list_entry(pos, struct slsi_tracked_skb, entry); + if (t->skb == skb) { + SLSI_DBG4_NODEV(SLSI_TEST, "un-track SKB: 0x%p: %s:%d\n", skb, file, line); + list_del(pos); + kfree_skb(t->skb); /* Free the reference we took */ + kfree(t); + skb_tracker.tracked_count--; + goto exit; + } + } + WARN_ON(1); + SLSI_ERR_NODEV("SKB Not Tracked: %p: %s:%d", skb, file, line); + r = false; +exit: + spin_unlock_irqrestore(&skb_tracker.lock, flags); + return r; +} + +void slsi_dbg_track_skb_report(void) +{ + struct slsi_tracked_skb *t; + struct list_head *pos, *q; + unsigned long flags; + + spin_lock_irqsave(&skb_tracker.lock, flags); + SLSI_INFO_NODEV("Tracked Count Current: %d\n", skb_tracker.tracked_count); + SLSI_INFO_NODEV("Tracked Count Max : %d\n", skb_tracker.tracked_count_max); + list_for_each_safe(pos, q, &skb_tracker.tracked) { + t = list_entry(pos, struct slsi_tracked_skb, entry); + if (skb_shared(t->skb)) + SLSI_ERR_NODEV("SKB Leak: 0x%p: %s:%d\n", t->skb, t->file, t->line); + else + SLSI_ERR_NODEV("SKB Not Untracked: 0x%p: %s:%d\n", t->skb, t->file, t->line); + list_del(pos); + kfree_skb(t->skb); /* Free the reference we took */ + kfree(t); + skb_tracker.tracked_count--; + } + INIT_LIST_HEAD(&skb_tracker.tracked); + spin_unlock_irqrestore(&skb_tracker.lock, flags); +} +#endif diff --git a/drivers/net/wireless/scsc/debug.h b/drivers/net/wireless/scsc/debug.h new file mode 100644 index 000000000000..b659e2163afa --- /dev/null +++ b/drivers/net/wireless/scsc/debug.h @@ -0,0 +1,283 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#include +#include "dev.h" +#include + +/* Logging modules + * ======================= + */ + +#ifndef CONFIG_SCSC_DEBUG_COMPATIBILITY +extern const int SLSI_INIT_DEINIT; +extern const int SLSI_NETDEV; +extern const int SLSI_CFG80211; +extern const int SLSI_MLME; +extern const int SLSI_SUMMARY_FRAMES; +extern const int SLSI_HYDRA; +extern const int SLSI_TX; +extern const int SLSI_RX; +extern const int SLSI_UDI; + +extern const int SLSI_WIFI_FCQ; + +extern const int SLSI_HIP; +extern const int SLSI_HIP_INIT_DEINIT; +extern const int SLSI_HIP_FW_DL; +extern const int SLSI_HIP_SDIO_OP; +extern const int SLSI_HIP_PS; +extern const int SLSI_HIP_TH; +extern const int SLSI_HIP_FH; +extern const int SLSI_HIP_SIG; + +extern const int SLSI_FUNC_TRACE; +extern const int SLSI_TEST; +extern const int SLSI_SRC_SINK; +extern const int SLSI_FW_TEST; +extern const int SLSI_RX_BA; + +extern const int SLSI_TDLS; +extern const int SLSI_GSCAN; +extern const int SLSI_MBULK; +extern const int SLSI_FLOWC; +extern const int SLSI_SMAPPER; +#endif /* CONFIG_SCSC_DEBUG_COMPATIBILITY */ + +extern int *slsi_dbg_filters[]; + +#ifndef pr_warn +#define pr_warn pr_warning +#endif + +/*---------------------------*/ + +/** + * debug logging functions + * ======================= + */ + +#define SLSI_EWI_NODEV_LABEL "ieee80211 phy.: " +#define SLSI_EWI_DEV(sdev) (likely((sdev) && ((sdev)->wiphy)) ? &((sdev)->wiphy->dev) : NULL) +#define SLSI_EWI_NET_DEV(ndev) (likely(ndev) ? SLSI_EWI_DEV(((struct netdev_vif *)netdev_priv(ndev))->sdev) : NULL) +#define SLSI_EWI_NET_NAME(ndev) (likely(ndev) ? netdev_name(ndev) : NULL) + +#define SLSI_EWI(output, sdev, label, fmt, arg ...) output(SLSI_EWI_DEV(sdev), SCSC_PREFIX label ": %s: " fmt, __func__, ## arg) +#define SLSI_EWI_NET(output, ndev, label, fmt, arg ...) output(SLSI_EWI_NET_DEV(ndev), SCSC_PREFIX "%s: " label ": %s: " fmt, SLSI_EWI_NET_NAME(ndev), __func__, ## arg) +#define SLSI_EWI_NODEV(output, label, fmt, arg ...) output(SLSI_EWI_NODEV_LABEL SCSC_PREFIX label ": %s: " fmt, __func__, ## arg) + +#define SLSI_EWI_HEX(output, klevel, sdev, label, p, len, fmt, arg ...) \ + do { \ + SLSI_EWI(output, sdev, label, fmt, ## arg); \ + print_hex_dump(klevel, SCSC_PREFIX, DUMP_PREFIX_OFFSET, 16, 1, p, len, 0); \ + } while (0) + +#define SLSI_EWI_HEX_NET(output, klevel, dev, label, p, len, fmt, arg ...) \ + do { \ + SLSI_EWI_NET(output, dev, label, fmt, ## arg); \ + print_hex_dump(klevel, SCSC_PREFIX, DUMP_PREFIX_OFFSET, 16, 1, p, len, 0); \ + } while (0) + +#define SLSI_EWI_HEX_NODEV(output, klevel, label, p, len, fmt, arg ...) \ + do { \ + SLSI_EWI_NODEV(output, label, fmt, ## arg); \ + print_hex_dump(klevel, SCSC_PREFIX, DUMP_PREFIX_OFFSET, 16, 1, p, len, 0); \ + } while (0) + +#define SLSI_ERR(sdev, fmt, arg ...) SLSI_EWI(dev_err, sdev, "E", fmt, ## arg) +#define SLSI_WARN(sdev, fmt, arg ...) SLSI_EWI(dev_warn, sdev, "W", fmt, ## arg) +#define SLSI_INFO(sdev, fmt, arg ...) SLSI_EWI(dev_info, sdev, "I", fmt, ## arg) + +#define SLSI_NET_ERR(ndev, fmt, arg ...) SLSI_EWI_NET(dev_err, ndev, "E", fmt, ## arg) +#define SLSI_NET_WARN(ndev, fmt, arg ...) SLSI_EWI_NET(dev_warn, ndev, "W", fmt, ## arg) +#define SLSI_NET_INFO(ndev, fmt, arg ...) SLSI_EWI_NET(dev_info, ndev, "I", fmt, ## arg) + +#define SLSI_ERR_NODEV(fmt, arg ...) SLSI_EWI_NODEV(pr_err, "E", fmt, ## arg) +#define SLSI_WARN_NODEV(fmt, arg ...) SLSI_EWI_NODEV(pr_warn, "W", fmt, ## arg) +#define SLSI_INFO_NODEV(fmt, arg ...) SLSI_EWI_NODEV(pr_info, "I", fmt, ## arg) + +#define SLSI_ERR_HEX(sdev, p, len, fmt, arg ...) SLSI_EWI_HEX(dev_err, KERN_ERR, sdev, "E", p, len, fmt, ## arg) +#define SLSI_WARN_HEX(sdev, p, len, fmt, arg ...) SLSI_EWI_HEX(dev_warn, KERN_WARN, sdev, "W", p, len, fmt, ## arg) +#define SLSI_INFO_HEX(sdev, p, len, fmt, arg ...) SLSI_EWI_HEX(dev_info, KERN_INFO, sdev, "I", p, len, fmt, ## arg) + +#define SLSI_ERR_HEX_NODEV(p, len, fmt, arg ...) SLSI_EWI_HEX_NODEV(pr_err, KERN_ERR, "E", p, len, fmt, ## arg) +#define SLSI_WARN_HEX_NODEV(p, len, fmt, arg ...) SLSI_EWI_HEX_NODEV(pr_warn, KERN_WARN, "W", p, len, fmt, ## arg) +#define SLSI_INFO_HEX_NODEV(p, len, fmt, arg ...) SLSI_EWI_HEX_NODEV(pr_info, KERN_INFO, "I", p, len, fmt, ## arg) + +#ifdef CONFIG_SCSC_WLAN_DEBUG + +#define SLSI_DBG(sdev, filter, dbg_lvl, fmt, arg ...) \ + do { \ + if (unlikely((dbg_lvl) <= *slsi_dbg_filters[filter])) { \ + SLSI_EWI(dev_info, sdev, # dbg_lvl, fmt, ## arg); \ + } \ + } while (0) + +#define SLSI_DBG_NET(ndev, filter, dbg_lvl, fmt, arg ...) \ + do { \ + if (unlikely((dbg_lvl) <= *slsi_dbg_filters[filter])) { \ + SLSI_EWI_NET(dev_info, ndev, # dbg_lvl, fmt, ## arg); \ + } \ + } while (0) + +#define SLSI_DBG_NODEV(filter, dbg_lvl, fmt, arg ...) \ + do { \ + if (unlikely((dbg_lvl) <= *slsi_dbg_filters[filter])) { \ + SLSI_EWI_NODEV(pr_info, # dbg_lvl, fmt, ## arg); \ + } \ + } while (0) + +#define SLSI_DBG1(sdev, filter, fmt, arg ...) SLSI_DBG(sdev, filter, 1, fmt, ## arg) +#define SLSI_DBG2(sdev, filter, fmt, arg ...) SLSI_DBG(sdev, filter, 2, fmt, ## arg) +#define SLSI_DBG3(sdev, filter, fmt, arg ...) SLSI_DBG(sdev, filter, 3, fmt, ## arg) +#define SLSI_DBG4(sdev, filter, fmt, arg ...) SLSI_DBG(sdev, filter, 4, fmt, ## arg) + +#define SLSI_NET_DBG1(ndev, filter, fmt, arg ...) SLSI_DBG_NET(ndev, filter, 1, fmt, ## arg) +#define SLSI_NET_DBG2(ndev, filter, fmt, arg ...) SLSI_DBG_NET(ndev, filter, 2, fmt, ## arg) +#define SLSI_NET_DBG3(ndev, filter, fmt, arg ...) SLSI_DBG_NET(ndev, filter, 3, fmt, ## arg) +#define SLSI_NET_DBG4(ndev, filter, fmt, arg ...) SLSI_DBG_NET(ndev, filter, 4, fmt, ## arg) + +#define SLSI_DBG1_NODEV(filter, fmt, arg ...) SLSI_DBG_NODEV(filter, 1, fmt, ## arg) +#define SLSI_DBG2_NODEV(filter, fmt, arg ...) SLSI_DBG_NODEV(filter, 2, fmt, ## arg) +#define SLSI_DBG3_NODEV(filter, fmt, arg ...) SLSI_DBG_NODEV(filter, 3, fmt, ## arg) +#define SLSI_DBG4_NODEV(filter, fmt, arg ...) SLSI_DBG_NODEV(filter, 4, fmt, ## arg) + +/* Prints LOG_ENTRY if the condition evaluates to TRUE otherwise nothing is printed. */ +#define LOG_CONDITIONALLY(condition, LOG_ENTRY) \ + do { \ + if (unlikely(condition)) \ + LOG_ENTRY; \ + } while (0) + +/* Returns TRUE if the flag is set otherwise returns FALSE. */ +#define LOG_BOOL_FLAG(flag) \ + (flag) ? "TRUE" : "FALSE" + +#define SLSI_DBG_HEX_OUT(sdev, filter, dbg_lvl, p, len, fmt, arg ...) \ + do { \ + if (unlikely((dbg_lvl) <= *slsi_dbg_filters[filter])) { \ + SLSI_EWI_HEX(dev_info, KERN_INFO, sdev, # dbg_lvl, p, len, fmt, ## arg); \ + } \ + } while (0) + +#define SLSI_DBG_HEX_OUT_NET(sdev, filter, dbg_lvl, p, len, fmt, arg ...) \ + do { \ + if (unlikely((dbg_lvl) <= *slsi_dbg_filters[filter])) { \ + SLSI_EWI_HEX_NET(dev_info, KERN_INFO, dev, # dbg_lvl, p, len, fmt, ## arg); \ + } \ + } while (0) + +#define SLSI_DBG_HEX_OUT_NODEV(filter, dbg_lvl, p, len, fmt, arg ...) \ + do { \ + if (unlikely((dbg_lvl) <= *slsi_dbg_filters[filter])) { \ + SLSI_EWI_HEX_NODEV(pr_info, KERN_INFO, # dbg_lvl, p, len, fmt, ## arg); \ + } \ + } while (0) + +#define SLSI_DBG_HEX(sdev, filter, p, len, fmt, arg ...) SLSI_DBG_HEX_OUT(sdev, filter, 4, p, len, fmt, ## arg) +#define SLSI_NET_DBG_HEX(dev, filter, p, len, fmt, arg ...) SLSI_DBG_HEX_OUT_NET(dev, filter, 4, p, len, fmt, ## arg) +#define SLSI_DBG_HEX_NODEV(filter, p, len, fmt, arg ...) SLSI_DBG_HEX_OUT_NODEV(filter, 4, p, len, fmt, ## arg) + +#define FUNC_ENTER(sdev) SLSI_DBG4(sdev, SLSI_FUNC_TRACE, "--->\n") +#define FUNC_EXIT(sdev) SLSI_DBG4(sdev, SLSI_FUNC_TRACE, "<---\n") + +#define FUNC_ENTER_NODEV() SLSI_DBG4_NODEV(SLSI_FUNC_TRACE, "--->\n") +#define FUNC_EXIT_NODEV() SLSI_DBG4_NODEV(SLSI_FUNC_TRACE, "<---\n") + +void slsi_debug_frame(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, const char *prefix); +#else /* CONFIG_SCSC_WLAN_DEBUG */ + +#define SLSI_DBG1(sdev, filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG2(sdev, filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG3(sdev, filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG4(sdev, filter, fmt, arg ...) do {} while (0) +#define SLSI_NET_DBG1(dev, filter, fmt, arg ...) do {} while (0) +#define SLSI_NET_DBG2(dev, filter, fmt, arg ...) do {} while (0) +#define SLSI_NET_DBG3(dev, filter, fmt, arg ...) do {} while (0) +#define SLSI_NET_DBG4(dev, filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG1_NODEV(filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG2_NODEV(filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG3_NODEV(filter, fmt, arg ...) do {} while (0) +#define SLSI_DBG4_NODEV(filter, fmt, arg ...) do {} while (0) + +#define LOG_CONDITIONALLY(condition, LOG_ENTRY) do {} while (0) +#define LOG_BOOL_FLAG(flag) do {} while (0) + +#define SLSI_DBG_HEX(sdev, filter, p, len, fmt, arg ...) do {} while (0) +#define SLSI_NET_DBG_HEX(dev, filter, p, len, fmt, arg ...) do {} while (0) +#define SLSI_DBG_HEX_NODEV(filter, p, len, fmt, arg ...) do {} while (0) + +#define FUNC_ENTER(sdev) +#define FUNC_EXIT(sdev) + +#define FUNC_ENTER_NODEV() +#define FUNC_EXIT_NODEV() + +static inline void slsi_debug_frame(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, const char *prefix) +{ + (void)sdev; /* unused */ + (void)dev; /* unused */ + (void)skb; /* unused */ + (void)prefix; /* unused */ +} + +#endif /* CONFIG_SCSC_WLAN_DEBUG */ + +#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY + +#undef SLSI_ERR +#undef SLSI_WARN +#undef SLSI_INFO +#define SLSI_ERR(sdev, fmt, arg ...) SCSC_ERR_SDEV(sdev, fmt, ## arg) +#define SLSI_WARN(sdev, fmt, arg ...) SCSC_WARNING_SDEV(sdev, fmt, ## arg) +#define SLSI_INFO(sdev, fmt, arg ...) SCSC_INFO_SDEV(sdev, fmt, ## arg) + +#undef SLSI_NET_ERR +#undef SLSI_NET_WARN +#undef SLSI_NET_INFO +#define SLSI_NET_ERR(ndev, fmt, arg ...) SCSC_ERR_NDEV(ndev, fmt, ## arg) +#define SLSI_NET_WARN(ndev, fmt, arg ...) SCSC_WARNING_NDEV(ndev, fmt, ## arg) +#define SLSI_NET_INFO(ndev, fmt, arg ...) SCSC_INFO_NDEV(ndev, fmt, ## arg) + +#undef SLSI_ERR_NODEV +#undef SLSI_WARN_NODEV +#undef SLSI_INFO_NODEV +#define SLSI_ERR_NODEV(fmt, arg ...) SCSC_ERR(fmt, ## arg) +#define SLSI_WARN_NODEV(fmt, arg ...) SCSC_WARNING(fmt, ## arg) +#define SLSI_INFO_NODEV(fmt, arg ...) SCSC_INFO(fmt, ## arg) + +#undef SLSI_DBG1 +#undef SLSI_DBG2 +#undef SLSI_DBG3 +#undef SLSI_DBG4 +#define SLSI_DBG1(sdev, filter, fmt, arg ...) SCSC_TAG_DBG1_SDEV(sdev, filter, fmt, ## arg) +#define SLSI_DBG2(sdev, filter, fmt, arg ...) SCSC_TAG_DBG2_SDEV(sdev, filter, fmt, ## arg) +#define SLSI_DBG3(sdev, filter, fmt, arg ...) SCSC_TAG_DBG3_SDEV(sdev, filter, fmt, ## arg) +#define SLSI_DBG4(sdev, filter, fmt, arg ...) SCSC_TAG_DBG4_SDEV(sdev, filter, fmt, ## arg) + +#undef SLSI_NET_DBG1 +#undef SLSI_NET_DBG2 +#undef SLSI_NET_DBG3 +#undef SLSI_NET_DBG4 +#define SLSI_NET_DBG1(ndev, filter, fmt, arg ...) SCSC_TAG_DBG1_NDEV(ndev, filter, fmt, ## arg) +#define SLSI_NET_DBG2(ndev, filter, fmt, arg ...) SCSC_TAG_DBG2_NDEV(ndev, filter, fmt, ## arg) +#define SLSI_NET_DBG3(ndev, filter, fmt, arg ...) SCSC_TAG_DBG3_NDEV(ndev, filter, fmt, ## arg) +#define SLSI_NET_DBG4(ndev, filter, fmt, arg ...) SCSC_TAG_DBG4_NDEV(ndev, filter, fmt, ## arg) + +#undef SLSI_DBG1_NODEV +#undef SLSI_DBG2_NODEV +#undef SLSI_DBG3_NODEV +#undef SLSI_DBG4_NODEV +#define SLSI_DBG1_NODEV(filter, fmt, arg ...) SCSC_TAG_DBG1(filter, fmt, ## arg) +#define SLSI_DBG2_NODEV(filter, fmt, arg ...) SCSC_TAG_DBG2(filter, fmt, ## arg) +#define SLSI_DBG3_NODEV(filter, fmt, arg ...) SCSC_TAG_DBG3(filter, fmt, ## arg) +#define SLSI_DBG4_NODEV(filter, fmt, arg ...) SCSC_TAG_DBG4(filter, fmt, ## arg) + +#endif /* CONFIG_SCSC_DEBUG_COMPATIBILITY */ +#endif diff --git a/drivers/net/wireless/scsc/debug_frame.c b/drivers/net/wireless/scsc/debug_frame.c new file mode 100755 index 000000000000..eee4efa8ecc3 --- /dev/null +++ b/drivers/net/wireless/scsc/debug_frame.c @@ -0,0 +1,1120 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include + +#include "debug.h" +#include "fapi.h" +#include "const.h" +#include "mgt.h" + +#ifdef CONFIG_SCSC_WLAN_DEBUG + +/* frame decoding debug level */ +static int slsi_debug_summary_frame = 3; +module_param(slsi_debug_summary_frame, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(slsi_debug_summary_frame, "Debug level (0: disable, 1: mgmt only (no scan), 2: mgmt and imp frames, 3: all"); + +struct slsi_decode_entry { + const char *name; + void (*decode_fn)(u8 *frame, u16 frame_length, char *result, size_t result_length); +}; + +struct slsi_decode_snap { + const u8 snap[8]; + const char *name; + size_t (*decode_fn)(u8 *frame, u16 frame_length, char *result, size_t result_length); +}; + +struct slsi_value_name_decode { + const u16 value; + const char *name; + size_t (*decode_fn)(u8 *frame, u16 frame_length, char *result, size_t result_length); +}; + +static size_t slsi_decode_basic_ie_info(u8 *ies, u16 ies_length, char *result, size_t result_length) +{ + size_t size_written = 0; + const u8 *ssid = cfg80211_find_ie(WLAN_EID_SSID, ies, ies_length); + const u8 *rsn = cfg80211_find_ie(WLAN_EID_RSN, ies, ies_length); + const u8 *wpa = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPA, ies, ies_length); + const u8 *wps = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPS, ies, ies_length); + const u8 *htop = cfg80211_find_ie(WLAN_EID_HT_OPERATION, ies, ies_length); + const u8 *country = cfg80211_find_ie(WLAN_EID_COUNTRY, ies, ies_length); + + if (htop && htop[1]) { + size_written += snprintf(result + size_written, result_length - size_written, " channel:%d", htop[2]); + } else { + const u8 *ds = cfg80211_find_ie(WLAN_EID_DS_PARAMS, ies, ies_length); + + if (ds) + size_written += snprintf(result + size_written, result_length - size_written, " channel:%d", ds[2]); + } + + if (ssid) { + if (ssid[1]) + size_written += snprintf(result + size_written, result_length - size_written, " %.*s", ssid[1], (char *)&ssid[2]); + else + size_written += snprintf(result + size_written, result_length - size_written, " "); + } + + if (country) + size_written += snprintf(result + size_written, result_length - size_written, " country:%c%c%c", country[2], country[3], country[4]); + if (wpa) + size_written += snprintf(result + size_written, result_length - size_written, " wpa"); + if (rsn) + size_written += snprintf(result + size_written, result_length - size_written, " wpa2"); + if (wps) + size_written += snprintf(result + size_written, result_length - size_written, " wps"); + return size_written; +} + +static void slsi_decode_frame_ies_only(u8 offset, u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t str_len; + + result[0] = '('; + str_len = slsi_decode_basic_ie_info(frame + offset, + frame_length - offset, + result + 1, + result_length - 1); + result[1 + str_len] = ')'; + result[2 + str_len] = '\0'; +} + +static size_t slsi_decode_frame_leu16(u8 *frame, u16 frame_length, char *result, size_t result_length, const char *name) +{ + u16 value = frame[0] | frame[1] << 8; + + SLSI_UNUSED_PARAMETER(frame_length); + + return snprintf(result, result_length, "%s:%u", name, value); +} + +#define SLSI_ASSOC_REQ_IE_OFFSET 4 +static void slsi_decode_assoc_req(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + slsi_decode_frame_ies_only(SLSI_ASSOC_REQ_IE_OFFSET, frame, frame_length, result, result_length); +} + +#define SLSI_ASSOC_RSP_STATUS_OFFSET 2 +#define SLSI_ASSOC_RSP_IE_OFFSET 6 +static void slsi_decode_assoc_rsp(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t str_len = 0; + + result[str_len++] = '('; + str_len += slsi_decode_frame_leu16(frame + SLSI_ASSOC_RSP_STATUS_OFFSET, + frame_length - SLSI_ASSOC_RSP_STATUS_OFFSET, + result + str_len, + result_length - str_len, + "status"); + str_len += slsi_decode_basic_ie_info(frame + SLSI_ASSOC_RSP_IE_OFFSET, + frame_length - SLSI_ASSOC_RSP_IE_OFFSET, + result + str_len, + result_length - str_len); + result[str_len++] = ')'; + result[str_len] = '\0'; +} + +#define SLSI_DEAUTH_REASON_OFFSET 0 +static void slsi_decode_deauth(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t str_len = 0; + + result[str_len++] = '('; + str_len += slsi_decode_frame_leu16(frame + SLSI_DEAUTH_REASON_OFFSET, + frame_length - SLSI_DEAUTH_REASON_OFFSET, + result + str_len, + result_length - str_len, + "reason_code"); + result[str_len++] = ')'; + result[str_len] = '\0'; +} + +#define SLSI_AUTH_ALGO_OFFSET 0 +#define SLSI_AUTH_SEQ_OFFSET 2 +#define SLSI_AUTH_STATUS_OFFSET 4 +static void slsi_decode_auth(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t str_len = 0; + + result[str_len++] = '('; + str_len += slsi_decode_frame_leu16(frame + SLSI_AUTH_ALGO_OFFSET, + frame_length - SLSI_AUTH_ALGO_OFFSET, + result + str_len, + result_length - str_len, + "algo"); + result[str_len++] = ' '; + str_len += slsi_decode_frame_leu16(frame + SLSI_AUTH_SEQ_OFFSET, + frame_length - SLSI_AUTH_SEQ_OFFSET, + result + str_len, + result_length - str_len, + "seq"); + result[str_len++] = ' '; + str_len += slsi_decode_frame_leu16(frame + SLSI_AUTH_STATUS_OFFSET, + frame_length - SLSI_AUTH_STATUS_OFFSET, + result + str_len, + result_length - str_len, + "status"); + result[str_len++] = ' '; + result[str_len++] = ')'; + result[str_len] = '\0'; +} + +#define SLSI_REASSOC_IE_OFFSET 10 +static void slsi_decode_reassoc_req(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + slsi_decode_frame_ies_only(SLSI_REASSOC_IE_OFFSET, frame, frame_length, result, result_length); +} + +#define SLSI_BEACON_IE_OFFSET 12 +static void slsi_decode_beacon(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + slsi_decode_frame_ies_only(SLSI_BEACON_IE_OFFSET, frame, frame_length, result, result_length); +} + +#define SLSI_PROBEREQ_IE_OFFSET 0 +static void slsi_decode_probe_req(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + slsi_decode_frame_ies_only(SLSI_PROBEREQ_IE_OFFSET, frame, frame_length, result, result_length); +} + +#define SLSI_ACTION_BLOCK_ACK_ADDBA_REQ 0 +#define SLSI_ACTION_BLOCK_ACK_ADDBA_RSP 1 +#define SLSI_ACTION_BLOCK_ACK_DELBA 2 +static size_t slsi_decode_action_blockack(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u8 action = frame[1]; + + SLSI_UNUSED_PARAMETER(frame_length); + + switch (action) { + case SLSI_ACTION_BLOCK_ACK_ADDBA_REQ: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->ADDBAReq(token:%u)", token); + } + case SLSI_ACTION_BLOCK_ACK_ADDBA_RSP: + { + u8 token = frame[2]; + u16 status = frame[3] | frame[4] << 8; + + return snprintf(result, result_length, "->ADDBARsp(token:%u, status:%u)", token, status); + } + case SLSI_ACTION_BLOCK_ACK_DELBA: + { + u16 reason_code = frame[4] | frame[5] << 8; + + return snprintf(result, result_length, "->DELBA(reason_code:%u)", reason_code); + } + default: + return snprintf(result, result_length, "->Action(%u)", action); + } +} + +#define SLSI_ACTION_PUBLIC_DISCOVERY_RSP 14 + +static size_t slsi_decode_action_public(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u8 action = frame[1]; + + SLSI_UNUSED_PARAMETER(frame_length); + + switch (action) { + case SLSI_ACTION_PUBLIC_DISCOVERY_RSP: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->DiscoveryRsp(token:%u)", token); + } + default: + return snprintf(result, result_length, "->Action(%u)", action); + } +} + +#define SLSI_ACTION_TDLS_SETUP_REQ 0 +#define SLSI_ACTION_TDLS_SETUP_RSP 1 +#define SLSI_ACTION_TDLS_SETUP_CFM 2 +#define SLSI_ACTION_TDLS_TEARDOWN 3 +#define SLSI_ACTION_TDLS_PEER_TRAFFIC_IND 4 +#define SLSI_ACTION_TDLS_CHANNEL_SWITCH_REQ 5 +#define SLSI_ACTION_TDLS_CHANNEL_SWITCH_RSP 6 +#define SLSI_ACTION_TDLS_PEER_PSM_REQ 7 +#define SLSI_ACTION_TDLS_PEER_PSM_RSP 8 +#define SLSI_ACTION_TDLS_PEER_TRAFFIC_RSP 9 +#define SLSI_ACTION_TDLS_DISCOVERY_REQ 10 + +static size_t slsi_decode_action_tdls(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u8 action = frame[1]; + + SLSI_UNUSED_PARAMETER(frame_length); + + switch (action) { + case SLSI_ACTION_TDLS_SETUP_REQ: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->SetupReq(token:%u)", token); + } + case SLSI_ACTION_TDLS_SETUP_RSP: + { + u16 status = frame[2] | frame[3] << 8; + u8 token = frame[4]; + + return snprintf(result, result_length, "->SetupRsp(token:%u, status:%u)", token, status); + } + case SLSI_ACTION_TDLS_SETUP_CFM: + { + u16 status = frame[2] | frame[3] << 8; + u8 token = frame[4]; + + return snprintf(result, result_length, "->SetupCfm(token:%u, status:%u)", token, status); + } + case SLSI_ACTION_TDLS_TEARDOWN: + { + u16 reason = frame[2] | frame[3] << 8; + + return snprintf(result, result_length, "->SetupCfm(reason:%u)", reason); + } + case SLSI_ACTION_TDLS_PEER_TRAFFIC_IND: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->PeerTrafficInd(token:%u)", token); + } + case SLSI_ACTION_TDLS_CHANNEL_SWITCH_REQ: + { + u8 channel = frame[2]; + + return snprintf(result, result_length, "->ChannelSwitchReq(channel:%u)", channel); + } + case SLSI_ACTION_TDLS_CHANNEL_SWITCH_RSP: + { + u16 status = frame[2] | frame[3] << 8; + + return snprintf(result, result_length, "->ChannelSwitchRsp(status:%u)", status); + } + case SLSI_ACTION_TDLS_PEER_PSM_REQ: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->PeerPSMReq(token:%u)", token); + } + case SLSI_ACTION_TDLS_PEER_PSM_RSP: + { + u8 token = frame[2]; + u16 status = frame[3] | frame[4] << 8; + + return snprintf(result, result_length, "->PeerPSMRsp(token:%u, status:%u)", token, status); + } + case SLSI_ACTION_TDLS_PEER_TRAFFIC_RSP: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->PeerTrafficRsp(token:%u)", token); + } + case SLSI_ACTION_TDLS_DISCOVERY_REQ: + { + u8 token = frame[2]; + + return snprintf(result, result_length, "->DiscoveryReq(token:%u)", token); + } + default: + return snprintf(result, result_length, "->Action(%u)", action); + } +} + +static const struct slsi_value_name_decode action_categories[] = { + { 3, "BlockAck", slsi_decode_action_blockack }, + { 0, "SpectrumManagement", NULL }, + { 1, "QoS", NULL }, + { 2, "DLS", NULL }, + { 4, "Public", slsi_decode_action_public }, + { 5, "RadioMeasurement", NULL }, + { 6, "FastBSSTransition", NULL }, + { 7, "HT", NULL }, + { 8, "SAQuery", NULL }, + { 9, "ProtectedDualOfPublicAction", NULL }, + { 12, "TDLS", slsi_decode_action_tdls }, + { 17, "ReservedWFA", NULL }, + { 126, "VendorSpecificProtected", NULL }, + { 127, "VendorSpecific", NULL }, + { 132, "Public(error)", slsi_decode_action_public }, +}; + +#define SLSI_ACTION_CAT_BLOCK_ACK 3 +static void slsi_decode_action(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u8 category = frame[0]; + u32 i; + + for (i = 0; i < ARRAY_SIZE(action_categories); i++) + if (action_categories[i].value == category) { + int size_written = snprintf(result, result_length, "->%s", action_categories[i].name); + + if (action_categories[i].decode_fn) + action_categories[i].decode_fn(frame, frame_length, result + size_written, result_length - size_written); + return; + } + snprintf(result, result_length, "->category:%u", category); +} + +const char *slsi_arp_opcodes[] = { + "Reserved", + "REQUEST", + "REPLY", + "request Reverse", + "reply Reverse", + "DRARP-Request", + "DRARP-Reply", + "DRARP-Error", + "InARP-Request", + "InARP-Reply", + "ARP-NAK", + "MARS-Request", + "MARS-Multi", + "MARS-MServ", + "MARS-Join", + "MARS-Leave", + "MARS-NAK", + "MARS-Unserv", + "MARS-SJoin", + "MARS-SLeave", + "MARS-Grouplist-Request", + "MARS-Grouplist-Reply", + "MARS-Redirect-Map", + "MAPOS-UNARP", + "OP_EXP1", + "OP_EXP2", +}; + +static size_t slsi_decode_arp(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + /* u16 htype = frame[0] << 8 | frame[1]; + * u16 proto = frame[2] << 8 | frame[3]; + * u8 hlen = frame[4]; + * u8 plen = frame[5]; + */ + u16 opcode = frame[6] << 8 | frame[7]; + u8 *sha = &frame[8]; + u8 *spa = &frame[14]; + u8 *tha = &frame[18]; + u8 *tpa = &frame[24]; + + SLSI_UNUSED_PARAMETER(frame_length); + + if (opcode < ARRAY_SIZE(slsi_arp_opcodes)) + return snprintf(result, result_length, "->%s(sha:%.2X:%.2X:%.2X:%.2X:%.2X:%.2X, spa:%u.%u.%u.%u, tha:%.2X:%.2X:%.2X:%.2X:%.2X:%.2X, tpa:%u.%u.%u.%u)", + slsi_arp_opcodes[opcode], + sha[0], sha[1], sha[2], sha[3], sha[4], sha[5], + spa[0], spa[1], spa[2], spa[3], + tha[0], tha[1], tha[2], tha[3], tha[4], tha[5], + tpa[0], tpa[1], tpa[2], tpa[3]); + else + return snprintf(result, result_length, "->(opcode:%u)", opcode); +} + +static const struct slsi_value_name_decode slsi_decode_eapol_packet_types[] = { + { 1, "Identity", NULL }, + { 2, "Notification", NULL }, + { 3, "Nak", NULL }, + { 4, "MD5Challenge", NULL }, + { 5, "OneTimePassword", NULL }, + { 6, "GenericTokenCard", NULL }, + { 9, "RSA Public Key Authentication", NULL }, + { 10, "DSS Unilateral", NULL }, + { 11, "KEA", NULL }, + { 12, "KEA-VALIDATE", NULL }, + { 13, "EAP-TLS", NULL }, + { 14, "Defender Token (AXENT)", NULL }, + { 15, "RSA Security SecurID EAP", NULL }, + { 16, "Arcot Systems EAP", NULL }, + { 17, "EAP-Cisco Wireless", NULL }, + { 18, "EAP-SIM", NULL }, + { 19, "SRP-SHA1 Part 1", NULL }, + { 21, "EAP-TTLS", NULL }, + { 22, "Remote Access Service", NULL }, + { 23, "EAP-AKA", NULL }, + { 24, "EAP-3Com Wireless", NULL }, + { 25, "PEAP", NULL }, + { 26, "MS-EAP-Authentication", NULL }, + { 27, "MAKE", NULL }, + { 28, "CRYPTOCard", NULL }, + { 29, "EAP-MSCHAP-V2", NULL }, + { 30, "DynamID", NULL }, + { 31, "Rob EAP", NULL }, + { 32, "EAP-POTP", NULL }, + { 33, "MS-Authentication-TLV", NULL }, + { 34, "SentriNET", NULL }, + { 35, "EAP-Actiontec Wireless", NULL }, + { 36, "Cogent Systems Biometrics Authentication EAP", NULL }, + { 37, "AirFortress EAP", NULL }, + { 38, "EAP-HTTP Digest", NULL }, + { 39, "SecureSuite EAP", NULL }, + { 40, "DeviceConnect EAP", NULL }, + { 41, "EAP-SPEKE", NULL }, + { 42, "EAP-MOBAC", NULL }, + { 43, "EAP-FAST", NULL }, + { 44, "ZLXEAP", NULL }, + { 45, "EAP-Link", NULL }, + { 46, "EAP-PAX", NULL }, + { 47, "EAP-PSK", NULL }, + { 48, "EAP-SAKE", NULL }, + { 49, "EAP-IKEv2", NULL }, + { 50, "EAP-AKA", NULL }, + { 51, "EAP-GPSK", NULL }, + { 52, "EAP-pwd", NULL }, + { 53, "EAP-EKE V1", NULL }, + { 254, "WPS", NULL } +}; + +static size_t slsi_decode_eapol_packet(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + static const char *const slsi_decode_eapol_packet_codes[] = { + "", + "Request", + "Response", + "Success", + "Failure", + }; + + size_t size_written = 0; + u32 i; + u8 code = frame[0]; + u8 id = frame[1]; + u16 length = frame[2] << 8 | frame[3]; + const char *code_str = ""; + + SLSI_UNUSED_PARAMETER(frame_length); + + if (code >= 1 && code <= 4) + code_str = slsi_decode_eapol_packet_codes[code]; + + if (length > 4 && (code == 1 || code == 2)) { + u8 type = frame[4]; + + for (i = 0; i < ARRAY_SIZE(slsi_decode_eapol_packet_types); i++) + if (slsi_decode_eapol_packet_types[i].value == type) { + size_written += snprintf(result, result_length, ":%s:%s id:%u", slsi_decode_eapol_packet_types[i].name, code_str, id); + return size_written; + } + size_written += snprintf(result, result_length, ":type:%u: %s id:%u", type, code_str, id); + } else { + size_written += snprintf(result, result_length, ":%s id:%u length:%u", code_str, id, length); + } + return size_written; +} + +static const struct slsi_value_name_decode slsi_eapol_packet_type[] = { + { 0, "EapPacket", slsi_decode_eapol_packet }, + { 1, "EapolStart", NULL }, + { 2, "EapolLogoff", NULL }, + { 3, "EapolKey", NULL } +}; + +static size_t slsi_decode_eapol(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t size_written = 0; + u8 packet_type = frame[1]; + u16 length = frame[2] << 8 | frame[3]; + + SLSI_UNUSED_PARAMETER(frame_length); + + if (packet_type < ARRAY_SIZE(slsi_eapol_packet_type)) { + size_written += snprintf(result, result_length, "->%s", slsi_eapol_packet_type[packet_type].name); + if (slsi_eapol_packet_type[packet_type].decode_fn) + size_written += slsi_eapol_packet_type[packet_type].decode_fn(frame + 4, length, result + size_written, result_length - size_written); + return size_written; + } else { + return snprintf(result, result_length, "->packet_type:%u", packet_type); + } +} + +static size_t slsi_decode_tdls(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u8 payload_type = frame[0]; + + if (payload_type == 2) { + slsi_decode_action(frame + 1, frame_length - 1, result, result_length); + return 0; + } else { + return snprintf(result, result_length, "->Unknown(payload:%u", payload_type); + } +} + +static size_t slsi_decode_ipv4_icmp_echo(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u16 id = frame[0] << 8 | frame[1]; + u16 seq = frame[2] << 8 | frame[3]; + + SLSI_UNUSED_PARAMETER(frame_length); + + return snprintf(result, result_length, " id:%u seq:%u", id, seq); +} + +static const struct slsi_value_name_decode slsi_ipv4_icmp_types[] = { + { 0, "EchoReply", slsi_decode_ipv4_icmp_echo }, + { 8, "Echo ", slsi_decode_ipv4_icmp_echo }, + { 3, "Destination Unreachable Ack", NULL }, + { 4, "Source Quench", NULL }, + { 5, "Redirect", NULL }, + { 6, "Alternate Host Address", NULL }, + { 9, "Router Advertisement", NULL }, + { 10, "Router Selection", NULL }, + { 11, "Time Exceeded", NULL }, + { 12, "Parameter Problem", NULL }, + { 13, "Timestamp", NULL }, + { 14, "Timestamp Reply", NULL }, + { 15, "Information Request", NULL }, + { 16, "Information Reply", NULL }, + { 17, "Address Mask Request", NULL }, + { 18, "Address Mask Reply", NULL }, + { 19, "Reserved (for Security)", NULL }, + { 30, "Traceroute", NULL }, + { 31, "Datagram Conversion Error", NULL }, + { 32, "Mobile Host Redirect", NULL }, + { 33, "IPv6 Where-Are-You", NULL }, + { 34, "IPv6 I-Am-Here", NULL }, + { 35, "Mobile Registration Request", NULL }, + { 36, "Mobile Registration Reply", NULL }, + { 39, "SKIP", NULL }, + { 40, "Photuris", NULL }, + { 253, "RFC3692-style Experiment 1", NULL }, + { 254, "RFC3692-style Experiment 2", NULL } +}; + +static size_t slsi_decode_ipv4_icmp(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t size_written = 0; + u32 i; + u8 type = frame[0]; + u8 code = frame[1]; + + for (i = 0; i < ARRAY_SIZE(slsi_ipv4_icmp_types); i++) + if (slsi_ipv4_icmp_types[i].value == type) { + size_written += snprintf(result, result_length, "->%s(code:%u)", slsi_ipv4_icmp_types[i].name, code); + if (slsi_ipv4_icmp_types[i].decode_fn) + size_written += slsi_ipv4_icmp_types[i].decode_fn(frame + 4, frame_length - 4, result + size_written, result_length - size_written); + return size_written; + } + return snprintf(result, result_length, "->type(%u)", type); +} + +static const struct slsi_value_name_decode slsi_ipv4_udp_bootp_dhcp_option53[] = { + { 1, "DHCP_DISCOVER", NULL }, + { 2, "DHCP_OFFER", NULL }, + { 3, "DHCP_REQUEST", NULL }, + { 4, "DHCP_DECLINE", NULL }, + { 5, "DHCP_ACK", NULL }, + { 6, "DHCP_NAK", NULL }, + { 7, "DHCP_RELEASE", NULL }, + { 8, "DHCP_INFORM", NULL }, +}; + +#define SLSI_IPV4_UDP_BOOTP_CIADDR_OFFSET 16 +#define SLSI_IPV4_UDP_BOOTP_YIADDR_OFFSET 20 +#define SLSI_IPV4_UDP_BOOTP_GIADDR_OFFSET 24 +#define SLSI_IPV4_UDP_BOOTP_MAGIC_OFFSET 236 +static size_t slsi_decode_ipv4_udp_bootp(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u32 i; + u8 *ciaddr = &frame[SLSI_IPV4_UDP_BOOTP_CIADDR_OFFSET]; + u8 *yiaddr = &frame[SLSI_IPV4_UDP_BOOTP_YIADDR_OFFSET]; + u8 *giaddr = &frame[SLSI_IPV4_UDP_BOOTP_GIADDR_OFFSET]; + u8 *magic = &frame[SLSI_IPV4_UDP_BOOTP_MAGIC_OFFSET]; + + if (magic[0] == 0x63 && magic[1] == 0x82 && magic[2] == 0x53 && magic[3] == 0x63) { + u8 *p = &frame[SLSI_IPV4_UDP_BOOTP_MAGIC_OFFSET + 4]; + + while (p < p + frame_length) { + u8 option = p[0]; + u8 option_length = p[1]; + + if (option == 53 && option_length == 1) { + for (i = 0; i < ARRAY_SIZE(slsi_ipv4_udp_bootp_dhcp_option53); i++) + if (slsi_ipv4_udp_bootp_dhcp_option53[i].value == p[2]) + return snprintf(result, result_length, "->%s(ci:%u.%u.%u.%u yi:%u.%u.%u.%u gi:%u.%u.%u.%u)", + slsi_ipv4_udp_bootp_dhcp_option53[i].name, + ciaddr[0], ciaddr[1], ciaddr[2], ciaddr[3], + yiaddr[0], yiaddr[1], yiaddr[2], yiaddr[3], + giaddr[0], giaddr[1], giaddr[2], giaddr[3]); + return snprintf(result, result_length, "->option53(%u ci:%u.%u.%u.%u yi:%u.%u.%u.%u gi:%u.%u.%u.%u)", + p[2], + ciaddr[0], ciaddr[1], ciaddr[2], ciaddr[3], + yiaddr[0], yiaddr[1], yiaddr[2], yiaddr[3], + giaddr[0], giaddr[1], giaddr[2], giaddr[3]); + } + if (option == 0) + break; + p = p + 2 + option_length; + } + } + return 0; +} + +static const struct slsi_value_name_decode slsi_ipv4_udp_ports[] = { + { 53, "DNS", NULL }, + { 67, "Bootp", slsi_decode_ipv4_udp_bootp }, + { 68, "Bootp", slsi_decode_ipv4_udp_bootp }, +}; + +static size_t slsi_decode_ipv4_udp(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + /* 0 7 8 15 16 23 24 31 + * +--------+--------+--------+--------+ + * | Source | Destination | + * | Port | Port | + * +--------+--------+--------+--------+ + * | | | + * | Length | Checksum | + * +--------+--------+--------+--------+ + * | + * | data octets ... + * +--------------------- ... + */ + size_t size_written = 0; + u32 i; + u16 sport = frame[0] << 8 | frame[1]; + u16 dport = frame[2] << 8 | frame[3]; + u16 length = frame[4] << 8 | frame[5]; + + /*u16 chksum = frame[6] << 8 | frame[7];*/ + + for (i = 0; i < ARRAY_SIZE(slsi_ipv4_udp_ports); i++) + if (slsi_ipv4_udp_ports[i].value == dport || slsi_ipv4_udp_ports[i].value == sport) { + size_written += snprintf(result, result_length, "->%s", slsi_ipv4_udp_ports[i].name); + if (slsi_ipv4_udp_ports[i].decode_fn) + size_written += slsi_ipv4_udp_ports[i].decode_fn(frame + 8, length, result + size_written, result_length - size_written); + else + size_written += snprintf(result + size_written, result_length - size_written, "(dport:%u, size:%u)", dport, frame_length - 8); + return size_written; + } + return snprintf(result, result_length, "(dport:%u, size:%u)", dport, frame_length - 8); +} + +static size_t slsi_decode_ipv4_tcp(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + /* TCP Header Format + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Acknowledgment Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Data | |U|A|P|R|S|F| | + * | Offset| Reserved |R|C|S|S|Y|I| Window | + * | | |G|K|H|T|N|N| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Checksum | Urgent Pointer | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options | Padding | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + u16 dport = frame[2] << 8 | frame[3]; + u8 flags = frame[13]; + bool fin = flags & 0x01; + bool syn = flags & 0x02; + bool rst = flags & 0x04; + bool psh = flags & 0x08; + bool ack = flags & 0x10; + bool urg = flags & 0x20; + + return snprintf(result, result_length, "(dport:%u%s%s%s%s%s%s size:%u)", + dport, + fin ? " FIN" : "", + syn ? " SYN" : "", + rst ? " RST" : "", + psh ? " PSH" : "", + ack ? " ACK" : "", + urg ? " URG" : "", + frame_length - 24); +} + +#define SLSI_IPV4_PROTO_ICMP 1 +#define SLSI_IPV4_PROTO_IGMP 2 +#define SLSI_IPV4_PROTO_TCP 6 +#define SLSI_IPV4_PROTO_UDP 17 +static size_t slsi_decode_ipv4(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + size_t size_written = 0; + u16 ip_data_offset = 20; + /*u8 version = frame[0] >> 4; */ + u8 hlen = frame[0] & 0x0F; + /*u8 tos = frame[1]; */ + /*u16 len = frame[2] << 8 | frame[3]; */ + /*u16 id = frame[4] << 8 | frame[5]; */ + /*u16 flags_foff = frame[6] << 8 | frame[7]; */ + /*u8 ttl = frame[8]; */ + u8 proto = frame[9]; + /*u16 cksum = frame[10] << 8 | frame[11]; */ + u8 *src_ip = &frame[12]; + u8 *dest_ip = &frame[16]; + + if (hlen > 5) + ip_data_offset += (hlen - 5) * 4; + + size_written += snprintf(result + size_written, result_length - size_written, "(s:%u.%u.%u.%u d:%u.%u.%u.%u)", + src_ip[0], src_ip[1], src_ip[2], src_ip[3], + dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3]); + + switch (proto) { + case SLSI_IPV4_PROTO_TCP: + size_written += snprintf(result + size_written, result_length - size_written, "->TCP"); + size_written += slsi_decode_ipv4_tcp(frame + ip_data_offset, + frame_length - ip_data_offset, + result + size_written, + result_length - size_written); + break; + case SLSI_IPV4_PROTO_UDP: + size_written += snprintf(result + size_written, result_length - size_written, "->UDP"); + size_written += slsi_decode_ipv4_udp(frame + ip_data_offset, + frame_length - ip_data_offset, + result + size_written, + result_length - size_written); + break; + case SLSI_IPV4_PROTO_ICMP: + size_written += snprintf(result + size_written, result_length - size_written, "->ICMP"); + size_written += slsi_decode_ipv4_icmp(frame + ip_data_offset, + frame_length - ip_data_offset, + result + size_written, + result_length - size_written); + break; + case SLSI_IPV4_PROTO_IGMP: + size_written += snprintf(result + size_written, result_length - size_written, "->IGMP"); + break; + default: + size_written += snprintf(result + size_written, result_length - size_written, "->proto:%u", proto); + break; + } + return size_written; +} + +static const struct slsi_decode_snap snap_types[] = { + { { 0x08, 0x00 }, "IpV4", slsi_decode_ipv4 }, + { { 0x08, 0x06 }, "Arp", slsi_decode_arp }, + { { 0x88, 0x8e }, "Eapol", slsi_decode_eapol }, + { { 0x89, 0x0d }, NULL, slsi_decode_tdls }, + { { 0x86, 0xdd }, "IpV6", NULL }, + { { 0x88, 0xb4 }, "Wapi", NULL }, +}; + +static void slsi_decode_proto_data(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(snap_types); i++) + if (memcmp(snap_types[i].snap, frame, 2) == 0) { + int slen = 0; + + if (snap_types[i].name) + slen = snprintf(result, result_length, "->%s", snap_types[i].name); + if (snap_types[i].decode_fn) + slen += snap_types[i].decode_fn(frame + 2, frame_length - 2, result + slen, result_length - slen); + return; + } + + snprintf(result, result_length, "(proto:0x%.2X%.2X)", frame[0], frame[1]); +} + +static void slsi_decode_80211_data(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + return slsi_decode_proto_data(frame + 6, frame_length - 6, result, result_length); +} + +static const struct slsi_decode_entry frame_types[4][16] = { + { + { "AssocReq", slsi_decode_assoc_req }, + { "AssocRsp", slsi_decode_assoc_rsp }, + { "ReassocReq", slsi_decode_reassoc_req }, + { "ReassocRsp", slsi_decode_assoc_rsp }, /* Same as Assoc Req Frame*/ + { "ProbeReq", slsi_decode_probe_req }, + { "ProbeRsp", slsi_decode_beacon }, /* Same as Beacon Frame */ + { "TimingAdv", NULL }, + { "Reserved", NULL }, + { "Beacon ", slsi_decode_beacon }, + { "Atim", NULL }, + { "Disassoc", slsi_decode_deauth }, /* Same as Deauth Frame */ + { "Auth", slsi_decode_auth }, + { "Deauth", slsi_decode_deauth }, + { "Action", slsi_decode_action }, + { "ActionNoAck", slsi_decode_action }, + { "Reserved", NULL } + }, + { + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "BlockAckReq", NULL }, + { "BlockAck", NULL }, + { "PsPoll", NULL }, + { "RTS", NULL }, + { "CTS", NULL }, + { "Ack", NULL }, + { "CF-End", NULL }, + { "CF-End+Ack", NULL } + }, + { + { "Data", slsi_decode_80211_data }, + { "Data+CF-Ack", slsi_decode_80211_data }, + { "Data+CF-Poll", slsi_decode_80211_data }, + { "Data+CF-Ack+Poll", slsi_decode_80211_data }, + { "Null", NULL }, + { "CF-Ack", NULL }, + { "CF-Poll", NULL }, + { "CF-Ack+Poll", NULL }, + { "QosData", slsi_decode_80211_data }, + { "QosData+CF-Ack", slsi_decode_80211_data }, + { "QosData+CF-Poll", slsi_decode_80211_data }, + { "QosData+CF-Ack+Poll", slsi_decode_80211_data }, + { "QosNull", NULL }, + { "Reserved", NULL }, + { "QosCF-Poll", NULL }, + { "QosCF-Ack+Poll", NULL } + }, + { + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + { "Reserved", NULL }, + } +}; + +static bool slsi_decode_80211_frame(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)frame; + u16 fc_cpu = cpu_to_le16(hdr->frame_control); + int ftype_idx = (fc_cpu & 0xf) >> 2; + const struct slsi_decode_entry *entry; + int hdrlen; + int slen; + + /* Only decode Management Frames at Level 1 */ + if (slsi_debug_summary_frame == 1 && ftype_idx != 0) + return false; + + /* Filter Scanning at the debug level 3 and above as it can be noisy with large scan results */ + if (slsi_debug_summary_frame < 3 && + (ieee80211_is_probe_req(fc_cpu) || ieee80211_is_probe_resp(fc_cpu) || ieee80211_is_beacon(fc_cpu))) + return false; + + entry = &frame_types[ftype_idx][(fc_cpu >> 4) & 0xf]; + hdrlen = ieee80211_hdrlen(hdr->frame_control); + slen = snprintf(result, result_length, entry->name); + + if (entry->decode_fn) + entry->decode_fn(frame + hdrlen, frame_length - hdrlen, result + slen, result_length - slen); + return true; +} + +static bool slsi_decode_l3_frame(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + int slen; + + /* Only decode Management Frames at Level 1 */ + if (slsi_debug_summary_frame == 1) + return false; + + /* Only decode high important frames e.g. EAPOL, ARP, DHCP at Level 2 */ + if (slsi_debug_summary_frame == 2) { + struct ethhdr *ehdr = (struct ethhdr *)frame; + u16 eth_type = be16_to_cpu(ehdr->h_proto); + + switch (eth_type) { + case ETH_P_IP: + if (slsi_is_dhcp_packet(frame) == SLSI_TX_IS_NOT_DHCP) + return false; + break; + /* Fall through; process EAPOL, WAPI and ARP frames */ + case ETH_P_PAE: + case ETH_P_WAI: + case ETH_P_ARP: + break; + default: + /* return for all other frames */ + return false; + } + } + slen = snprintf(result, result_length, "eth"); + slsi_decode_proto_data(frame + 12, frame_length - 12, result + slen, result_length - slen); + return true; +} + +static bool slsi_decode_amsdu_subframe(u8 *frame, u16 frame_length, char *result, size_t result_length) +{ + int slen; + + /* Only decode Management Frames at Level 1 */ + if (slsi_debug_summary_frame == 1) + return false; + + /* Only decode high important frames e.g. EAPOL, ARP, DHCP at Level 2 */ + if (slsi_debug_summary_frame == 2) { + struct msduhdr *msdu_hdr = (struct msduhdr *)frame; + u16 eth_type = be16_to_cpu(msdu_hdr->type); + + switch (eth_type) { + case ETH_P_IP: + /* slsi_is_dhcp_packet() decodes the frame as Ethernet frame so + * pass a offset (difference between MSDU header and ethernet header) + * to frames so it reads at the right offset + */ + if (slsi_is_dhcp_packet(frame + 8) == SLSI_TX_IS_NOT_DHCP) + return false; + break; + /* Fall through; process EAPOL, WAPI and ARP frames */ + case ETH_P_PAE: + case ETH_P_WAI: + case ETH_P_ARP: + break; + default: + /* return for all other frames */ + return false; + } + } + slen = snprintf(result, result_length, "eth"); + slsi_decode_proto_data(frame + 20, frame_length - 20, result + slen, result_length - slen); + return true; +} + +static inline bool slsi_debug_frame_ratelimited(void) +{ + static DEFINE_RATELIMIT_STATE(_rs, (5 * HZ), 200); + + if (__ratelimit(&_rs)) + return true; + return false; +} + +/* NOTE: dev can be NULL */ +void slsi_debug_frame(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, const char *prefix) +{ + char frame_info[384]; + u8 *frame = fapi_get_data(skb); + u16 len = fapi_get_datalen(skb); + u8 *dst = NULL; + u8 *src = NULL; + u16 frametype = 0xFFFF; + bool print = false; + u16 id = fapi_get_sigid(skb); + u16 vif = fapi_get_vif(skb); + s16 rssi = 0; + + if (!slsi_debug_summary_frame) + return; + + if (!len) + return; + + switch (id) { + case MA_UNITDATA_REQ: + case MA_UNITDATA_IND: + if (!slsi_debug_frame_ratelimited()) /* Limit the Data output to stop too much spam at high data rates */ + return; + break; + default: + break; + } + + frame_info[0] = '\0'; + switch (id) { + case MA_UNITDATA_REQ: + frametype = fapi_get_u16(skb, u.ma_unitdata_req.data_unit_descriptor); + break; + case MA_UNITDATA_IND: + frametype = fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor); + break; + case MLME_SEND_FRAME_REQ: + frametype = fapi_get_u16(skb, u.mlme_send_frame_req.data_unit_descriptor); + break; + case MLME_RECEIVED_FRAME_IND: + frametype = fapi_get_u16(skb, u.mlme_received_frame_ind.data_unit_descriptor); + break; + case MLME_SCAN_IND: + frametype = FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME; + rssi = fapi_get_s16(skb, u.mlme_scan_ind.rssi); + vif = fapi_get_u16(skb, u.mlme_scan_ind.scan_id) >> 8; + break; + case MLME_CONNECT_CFM: + case MLME_CONNECT_IND: + case MLME_PROCEDURE_STARTED_IND: + case MLME_CONNECTED_IND: + case MLME_REASSOCIATE_IND: + case MLME_ROAMED_IND: + frametype = FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME; + break; + default: + return; + } + + switch (frametype) { + case FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME: + { + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)frame; + + dst = hdr->addr1; + src = hdr->addr2; + print = slsi_decode_80211_frame(frame, len, frame_info, sizeof(frame_info)); + break; + } + case FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME: + { + struct ethhdr *ehdr = (struct ethhdr *)frame; + + dst = ehdr->h_dest; + src = ehdr->h_source; + print = slsi_decode_l3_frame(frame, len, frame_info, sizeof(frame_info)); + break; + } + case FAPI_DATAUNITDESCRIPTOR_AMSDU_SUBFRAME: + { + struct ethhdr *ehdr = (struct ethhdr *)frame; + + dst = ehdr->h_dest; + src = ehdr->h_source; + print = slsi_decode_amsdu_subframe(frame, len, frame_info, sizeof(frame_info)); + break; + } + default: + return; + } + if (print) { +#ifdef CONFIG_SCSC_WLAN_SKB_TRACKING + SLSI_DBG4(sdev, SLSI_SUMMARY_FRAMES, "%-5s: 0x%p %s(vif:%u rssi:%-3d, s:%pM d:%pM)->%s\n", + dev ? netdev_name(dev) : "", skb, prefix, vif, rssi, src, dst, frame_info); +#else + SLSI_DBG4(sdev, SLSI_SUMMARY_FRAMES, "%-5s: %s(vif:%u rssi:%-3d, s:%pM d:%pM)->%s\n", + dev ? netdev_name(dev) : "", prefix, vif, rssi, src, dst, frame_info); +#endif + } +} + +#endif /* CONFIG_SCSC_WLAN_DEBUG */ diff --git a/drivers/net/wireless/scsc/dev.c b/drivers/net/wireless/scsc/dev.c new file mode 100755 index 000000000000..72591471d5b7 --- /dev/null +++ b/drivers/net/wireless/scsc/dev.c @@ -0,0 +1,596 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "dev.h" +#include "hip.h" +#include "mgt.h" +#include "debug.h" +#include "udi.h" +#include "hip_bh.h" +#include "cfg80211_ops.h" +#include "netif.h" +#include "procfs.h" +#include "ba.h" +#include "nl80211_vendor.h" + +#include "sap_mlme.h" +#include "sap_ma.h" +#include "sap_dbg.h" +#include "sap_test.h" + +#ifdef CONFIG_SCSC_WLAN_KIC_OPS +#include "kic.h" +#endif + +char *slsi_mib_file = "wlan.hcf"; +module_param_named(mib_file, slsi_mib_file, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mib_file, "mib data filename"); + +char *slsi_mib_file2 = "wlan_sw.hcf"; +module_param_named(mib_file2, slsi_mib_file2, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mib_file2, "sw mib data filename"); + +static char *local_mib_file = "localmib.hcf"; +module_param(local_mib_file, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(local_mib_file, "local mib data filename (Optional extra mib values)"); + +static char *maddr_file = "mac.txt"; +module_param(maddr_file, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(maddr_file, "mac address filename"); + +static bool term_udi_users = true; +module_param(term_udi_users, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(term_udi_users, "Try to terminate UDI user space users (applications) connected on the cdev (0, 1)"); + +static int sig_wait_cfm_timeout = 6000; +module_param(sig_wait_cfm_timeout, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(sig_wait_cfm_timeout, "Signal wait timeout in milliseconds (default: 3000)"); + +static bool lls_disabled; +module_param(lls_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(lls_disabled, "Disable LLS: to disable LLS set 1"); + +static bool gscan_disabled = 0; +module_param(gscan_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(gscan_disabled, "Disable gscan: to disable gscan set 1"); + +static bool llslogs_disabled; +module_param(llslogs_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(llslogs_disabled, "Disable llslogs: to disable llslogs set 1"); + +static bool epno_disabled; +module_param(epno_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(epno_disabled, "Disable ePNO: to disable ePNO set 1.\nNote: for ePNO to work gscan should be enabled"); + +static bool vo_vi_block_ack_disabled; +module_param(vo_vi_block_ack_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(vo_vi_block_ack_disabled, "Disable VO VI Block Ack logic added for WMM AC Cert : 5.1.4"); + +static int max_scan_result_count = 200; +module_param(max_scan_result_count, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_scan_result_count, "Max scan results to be reported"); +static bool rtt_disabled = 1; +module_param(rtt_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(rtt_disabled, "Disable rtt: to disable rtt set 1"); + +static bool nan_disabled; +module_param(nan_disabled, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(nan_disabled, "Disable NAN: to disable NAN set 1."); + +bool slsi_dev_gscan_supported(void) +{ + return !gscan_disabled; +} + +bool slsi_dev_rtt_supported(void) +{ + return !rtt_disabled; +} + +bool slsi_dev_llslogs_supported(void) +{ + return !llslogs_disabled; +} + +bool slsi_dev_lls_supported(void) +{ + return !lls_disabled; +} + +bool slsi_dev_epno_supported(void) +{ + return !epno_disabled; +} + +bool slsi_dev_vo_vi_block_ack(void) +{ + return vo_vi_block_ack_disabled; +} + +int slsi_dev_get_scan_result_count(void) +{ + return max_scan_result_count; +} + +int slsi_dev_nan_supported(struct slsi_dev *sdev) +{ +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + if (sdev) + return sdev->nan_enabled && !nan_disabled; + return false; +#else + return false; +#endif +} + +static int slsi_dev_inetaddr_changed(struct notifier_block *nb, unsigned long data, void *arg) +{ + struct slsi_dev *sdev = container_of(nb, struct slsi_dev, inetaddr_notifier); + struct in_ifaddr *ifa = arg; + struct net_device *dev = ifa->ifa_dev->dev; + struct wireless_dev *wdev = dev->ieee80211_ptr; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + if (!wdev) + return NOTIFY_DONE; + + if (wdev->wiphy != sdev->wiphy) + return NOTIFY_DONE; + + if (data == NETDEV_DOWN) { + SLSI_NET_DBG2(dev, SLSI_NETDEV, "Returning 0 for NETDEV_DOWN event\n"); + return 0; + } + + SLSI_NET_INFO(dev, "IP: %pI4\n", &ifa->ifa_address); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif) && wake_lock_active(&sdev->wlan_wl_roam)) { + SLSI_NET_DBG2(dev, SLSI_NETDEV, "Releasing the roaming wakelock\n"); + wake_unlock(&sdev->wlan_wl_roam); + /* If upper layers included wps ie in connect but the actually + * connection is not for wps, reset the wps flag. + */ + if (ndev_vif->sta.is_wps) { + SLSI_NET_DBG1(dev, SLSI_NETDEV, + "is_wps set but not wps connection.\n"); + ndev_vif->sta.is_wps = false; + } + } +#endif + slsi_ip_address_changed(sdev, dev, ifa->ifa_address); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return 0; +} + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 +static int slsi_dev_inet6addr_changed(struct notifier_block *nb, unsigned long data, void *arg) +{ + struct slsi_dev *sdev = container_of(nb, struct slsi_dev, inet6addr_notifier); + struct inet6_ifaddr *ifa = arg; + struct net_device *dev = ifa->idev->dev; + struct wireless_dev *wdev = dev->ieee80211_ptr; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + (void)data; /* unused */ + + if (!wdev) + return NOTIFY_DONE; + + if (wdev->wiphy != sdev->wiphy) + return NOTIFY_DONE; + + SLSI_NET_INFO(dev, "IPv6: %pI6\n", &ifa->addr.s6_addr); + + slsi_spinlock_lock(&ndev_vif->ipv6addr_lock); + memcpy(&ndev_vif->ipv6address, &ifa->addr, sizeof(struct in6_addr)); + slsi_spinlock_unlock(&ndev_vif->ipv6addr_lock); + + return 0; +} +#endif + +void slsi_regd_init(struct slsi_dev *sdev) +{ + struct ieee80211_regdomain *slsi_world_regdom_custom = sdev->device_config.domain_info.regdomain; + struct ieee80211_reg_rule reg_rules[] = { + /* Channel 1 - 11*/ + REG_RULE(2412 - 10, 2462 + 10, 40, 0, 20, 0), +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + /* Channel 12 - 13 NO_IR*/ + REG_RULE(2467 - 10, 2472 + 10, 40, 0, 20, NL80211_RRF_NO_IR), +#endif + /* Channel 36 - 48 */ + REG_RULE(5180 - 10, 5240 + 10, 80, 0, 20, 0), + /* Channel 52 - 64 */ + REG_RULE(5260 - 10, 5320 + 10, 80, 0, 20, NL80211_RRF_DFS), + /* Channel 100 - 140 */ + REG_RULE(5500 - 10, 5700 + 10, 80, 0, 20, NL80211_RRF_DFS), + /* Channel 149 - 165 */ + REG_RULE(5745 - 10, 5825 + 10, 80, 0, 20, 0), + }; + + int i; + + SLSI_DBG1_NODEV(SLSI_INIT_DEINIT, "regulatory init\n"); + slsi_world_regdom_custom->n_reg_rules = 6; + for (i = 0; i < slsi_world_regdom_custom->n_reg_rules; i++) + slsi_world_regdom_custom->reg_rules[i] = reg_rules[i]; + + /* Country code '00' indicates world regulatory domain */ + slsi_world_regdom_custom->alpha2[0] = '0'; + slsi_world_regdom_custom->alpha2[1] = '0'; + + wiphy_apply_custom_regulatory(sdev->wiphy, slsi_world_regdom_custom); +} + +struct slsi_dev *slsi_dev_attach(struct device *dev, struct scsc_mx *core, struct scsc_service_client *mx_wlan_client) +{ + struct slsi_dev *sdev; + int i; + + SLSI_DBG1_NODEV(SLSI_INIT_DEINIT, "Add Device\n"); + + sdev = slsi_cfg80211_new(dev); + if (!sdev) { + SLSI_ERR_NODEV("No sdev\n"); + return NULL; + } + + sdev->mlme_blocked = false; + + SLSI_MUTEX_INIT(sdev->netdev_add_remove_mutex); + mutex_init(&sdev->netdev_remove_mutex); + SLSI_MUTEX_INIT(sdev->start_stop_mutex); + SLSI_MUTEX_INIT(sdev->device_config_mutex); + SLSI_MUTEX_INIT(sdev->logger_mutex); + + sdev->dev = dev; + sdev->maxwell_core = core; + memcpy(&sdev->mx_wlan_client, mx_wlan_client, sizeof(struct scsc_service_client)); + + sdev->fail_reported = false; + sdev->p2p_certif = false; + sdev->allow_switch_40_mhz = true; + sdev->allow_switch_80_mhz = true; + sdev->mib[0].mib_file_name = slsi_mib_file; + sdev->mib[1].mib_file_name = slsi_mib_file2; + sdev->local_mib.mib_file_name = local_mib_file; + sdev->maddr_file_name = maddr_file; + sdev->device_config.qos_info = -1; + sdev->acs_channel_switched = false; + memset(&sdev->chip_info_mib, 0xFF, sizeof(struct slsi_chip_info_mib)); + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + sdev->num_5g_restricted_channels = 0; +#endif + +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + sdev->device_config.okc_mode = 0; + sdev->device_config.wes_mode = 0; + sdev->device_config.roam_scan_mode = 0; +#endif + + slsi_log_clients_init(sdev); + + slsi_wakelock_init(&sdev->wlan_wl, "wlan"); + slsi_wakelock_init(&sdev->wlan_wl_mlme, "wlan_mlme"); + slsi_wakelock_init(&sdev->wlan_wl_ma, "wlan_ma"); +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID + wake_lock_init(&sdev->wlan_wl_roam, WAKE_LOCK_SUSPEND, "wlan_roam"); +#endif + sdev->recovery_next_state = 0; + init_completion(&sdev->recovery_remove_completion); + init_completion(&sdev->recovery_stop_completion); + init_completion(&sdev->recovery_completed); + sdev->recovery_status = 0; + + sdev->term_udi_users = &term_udi_users; + sdev->sig_wait_cfm_timeout = &sig_wait_cfm_timeout; + slsi_sig_send_init(&sdev->sig_wait); + + for (i = 0; i < SLSI_LLS_AC_MAX; i++) + atomic_set(&sdev->tx_host_tag[i], ((1 << 2) | i)); + + if (slsi_skb_work_init(sdev, NULL, &sdev->rx_dbg_sap, "slsi_wlan_rx_dbg_sap", slsi_rx_dbg_sap_work) != 0) + goto err_if; + + if (slsi_netif_init(sdev) != 0) { + SLSI_ERR(sdev, "Can not create the network interface\n"); + goto err_ctrl_wq_init; + } + + if (slsi_hip_init(sdev, dev) != 0) { + SLSI_ERR(sdev, "slsi_hip_init() Failed\n"); + goto err_netif_init; + } + + if (slsi_udi_node_init(sdev, dev) != 0) { + SLSI_ERR(sdev, "failed to init UDI\n"); + goto err_hip_init; + } + + slsi_create_proc_dir(sdev); + slsi_traffic_mon_clients_init(sdev); + + /* update regulatory domain */ + slsi_regd_init(sdev); + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + slsi_nl80211_vendor_init(sdev); +#endif + + if (slsi_cfg80211_register(sdev) != 0) { + SLSI_ERR(sdev, "failed to register with cfg80211\n"); + goto err_udi_proc_init; + } + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + sdev->inet6addr_notifier.notifier_call = slsi_dev_inet6addr_changed; + if (register_inet6addr_notifier(&sdev->inet6addr_notifier) != 0) { + SLSI_ERR(sdev, "failed to register inet6addr_notifier\n"); + goto err_cfg80211_registered; + } +#endif + + sdev->inetaddr_notifier.notifier_call = slsi_dev_inetaddr_changed; + if (register_inetaddr_notifier(&sdev->inetaddr_notifier) != 0) { + SLSI_ERR(sdev, "failed to register inetaddr_notifier\n"); +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + unregister_inet6addr_notifier(&sdev->inet6addr_notifier); +#endif + goto err_cfg80211_registered; + } + +#ifdef SLSI_TEST_DEV + slsi_init_netdev_mac_addr(sdev); +#endif + slsi_rx_ba_init(sdev); + + if (slsi_netif_register(sdev, sdev->netdev[SLSI_NET_INDEX_WLAN]) != 0) { + SLSI_ERR(sdev, "failed to register with wlan netdev\n"); + goto err_inetaddr_registered; + } +#ifdef CONFIG_SCSC_WLAN_STA_ONLY + SLSI_ERR(sdev, "CONFIG_SCSC_WLAN_STA_ONLY: not registering p2p netdev\n"); +#else + if (slsi_netif_register(sdev, sdev->netdev[SLSI_NET_INDEX_P2P]) != 0) { + SLSI_ERR(sdev, "failed to register with p2p netdev\n"); + goto err_wlan_registered; + } +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) + if (slsi_netif_register(sdev, sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]) != 0) { + SLSI_ERR(sdev, "failed to register with p2px_wlan1 netdev\n"); + goto err_p2p_registered; + } + rcu_assign_pointer(sdev->netdev_ap, sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]); +#endif +#endif +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + if (slsi_netif_register(sdev, sdev->netdev[SLSI_NET_INDEX_NAN]) != 0) { + SLSI_ERR(sdev, "failed to register with NAN netdev\n"); +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) + goto err_p2px_wlan_registered; +#else + goto err_p2p_registered; +#endif +#else + goto err_p2p_registered; +#endif + } +#endif +#endif +#ifdef CONFIG_SCSC_WLAN_KIC_OPS + if (wifi_kic_register(sdev) < 0) + SLSI_ERR(sdev, "failed to register Wi-Fi KIC ops\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + sdev->enhanced_pkt_filter_enabled = true; +#endif + sdev->device_state = SLSI_DEVICE_STATE_STOPPED; + sdev->current_tspec_id = -1; + sdev->tspec_error_code = -1; + + /* Driver workqueue used to queue work in different modes (STA/P2P/HS2) */ + sdev->device_wq = alloc_ordered_workqueue("slsi_wlan_wq", 0); + if (!sdev->device_wq) { + SLSI_ERR(sdev, "Cannot allocate workqueue\n"); +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + goto err_nan_registered; +#else +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) + goto err_p2px_wlan_registered; +#else + goto err_p2p_registered; +#endif +#else + goto err_p2p_registered; +#endif +#endif + } + return sdev; + +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 +err_nan_registered: + slsi_netif_remove(sdev, sdev->netdev[SLSI_NET_INDEX_NAN]); +#endif + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) +err_p2px_wlan_registered: + slsi_netif_remove(sdev, sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]); + rcu_assign_pointer(sdev->netdev_ap, NULL); +#endif +#endif + +err_p2p_registered: + slsi_netif_remove(sdev, sdev->netdev[SLSI_NET_INDEX_P2P]); + +err_wlan_registered: + slsi_netif_remove(sdev, sdev->netdev[SLSI_NET_INDEX_WLAN]); + +err_inetaddr_registered: + unregister_inetaddr_notifier(&sdev->inetaddr_notifier); +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + unregister_inet6addr_notifier(&sdev->inet6addr_notifier); +#endif + +err_cfg80211_registered: + slsi_cfg80211_unregister(sdev); + +err_udi_proc_init: + slsi_traffic_mon_clients_deinit(sdev); + slsi_remove_proc_dir(sdev); + slsi_udi_node_deinit(sdev); + +err_hip_init: + slsi_hip_deinit(sdev); + +err_netif_init: + slsi_netif_deinit(sdev); + +err_ctrl_wq_init: + slsi_skb_work_deinit(&sdev->rx_dbg_sap); + +err_if: + slsi_wakelock_exit(&sdev->wlan_wl); + slsi_wakelock_exit(&sdev->wlan_wl_mlme); + slsi_wakelock_exit(&sdev->wlan_wl_ma); +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID + wake_lock_destroy(&sdev->wlan_wl_roam); +#endif + slsi_cfg80211_free(sdev); + return NULL; +} + +void slsi_dev_detach(struct slsi_dev *sdev) +{ + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Remove Device\n"); + + slsi_stop(sdev); + +#ifdef CONFIG_SCSC_WLAN_KIC_OPS + wifi_kic_unregister(); +#endif + complete_all(&sdev->sig_wait.completion); + complete_all(&sdev->recovery_remove_completion); + complete_all(&sdev->recovery_stop_completion); + complete_all(&sdev->recovery_completed); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Unregister inetaddr_notifier\n"); + unregister_inetaddr_notifier(&sdev->inetaddr_notifier); + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Unregister inet6addr_notifier\n"); + unregister_inet6addr_notifier(&sdev->inet6addr_notifier); +#endif + + WARN_ON(!sdev->device_wq); + if (sdev->device_wq) + flush_workqueue(sdev->device_wq); + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + slsi_nl80211_vendor_deinit(sdev); +#endif + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Unregister netif\n"); + slsi_netif_remove_all(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Unregister cfg80211\n"); + slsi_cfg80211_unregister(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Remove proc entries\n"); + slsi_remove_proc_dir(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "De-initialise the Traffic monitor\n"); + slsi_traffic_mon_clients_deinit(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "De-initialise the UDI\n"); + slsi_log_clients_terminate(sdev); + slsi_udi_node_deinit(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "De-initialise Hip\n"); + slsi_hip_deinit(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "De-initialise netif\n"); + slsi_netif_deinit(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "De-initialise Regulatory\n"); + slsi_regd_deinit(sdev); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Stop Work Queues\n"); + slsi_skb_work_deinit(&sdev->rx_dbg_sap); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Clean up wakelock\n"); + slsi_wakelock_exit(&sdev->wlan_wl); + slsi_wakelock_exit(&sdev->wlan_wl_mlme); + slsi_wakelock_exit(&sdev->wlan_wl_ma); +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID + wake_lock_destroy(&sdev->wlan_wl_roam); +#endif + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Free cfg80211\n"); + slsi_cfg80211_free(sdev); + + slsi_dbg_track_skb_report(); + slsi_dbg_track_skb_reset(); +} + +int __init slsi_dev_load(void) +{ + SLSI_INFO_NODEV("Loading Maxwell Wi-Fi driver\n"); + + slsi_dbg_track_skb_init(); + + if (slsi_udi_init()) + SLSI_INFO_NODEV("Failed to init udi - continuing\n"); + + if (slsi_sm_service_driver_register()) + SLSI_INFO_NODEV("slsi_sm_service_driver_register failed - continuing\n"); + + /* Register SAPs */ + sap_mlme_init(); + sap_ma_init(); + sap_dbg_init(); + sap_test_init(); + +/* Always create devnode if TW Android P on */ +#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 + slsi_create_sysfs_macaddr(); +#endif + SLSI_INFO_NODEV("--- Maxwell Wi-Fi driver loaded successfully ---\n"); + return 0; +} + +void __exit slsi_dev_unload(void) +{ + SLSI_INFO_NODEV("Unloading Maxwell Wi-Fi driver\n"); + +#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 + slsi_destroy_sysfs_macaddr(); +#endif + /* Unregister SAPs */ + sap_mlme_deinit(); + sap_ma_deinit(); + sap_dbg_deinit(); + sap_test_deinit(); + + slsi_sm_service_driver_unregister(); + + slsi_udi_deinit(); + + SLSI_INFO_NODEV("--- Maxwell Wi-Fi driver unloaded successfully ---\n"); +} + +module_init(slsi_dev_load); +module_exit(slsi_dev_unload); + +MODULE_DESCRIPTION("mx140 Wi-Fi Driver"); +MODULE_AUTHOR("SLSI"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/net/wireless/scsc/dev.h b/drivers/net/wireless/scsc/dev.h new file mode 100755 index 000000000000..b31c107c0070 --- /dev/null +++ b/drivers/net/wireless/scsc/dev.h @@ -0,0 +1,1294 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_DEVICE_H__ +#define __SLSI_DEVICE_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "fapi.h" +#include "const.h" +#include "utils.h" +#include "hip.h" +#include "log_clients.h" +#include "src_sink.h" +#include "scsc_wifi_fcq.h" +#include "scsc_wifi_cm_if.h" +#include "hip4.h" +#include "nl80211_vendor.h" +#include "traffic_monitor.h" + +#define FAPI_MAJOR_VERSION(v) (((v) >> 8) & 0xFF) +#define FAPI_MINOR_VERSION(v) ((v) & 0xFF) + +/* Modes for CMDGETBSSINFO and CMDGETSTAINFO */ +#define SLSI_80211_MODE_11B 0 +#define SLSI_80211_MODE_11G 1 +#define SLSI_80211_MODE_11N 2 +#define SLSI_80211_MODE_11A 3 +#define SLSI_80211_MODE_11AC 4 + +#define SLSI_FW_API_RATE_HT_SELECTOR_FIELD 0xc000 +#define SLSI_FW_API_RATE_NON_HT_SELECTED 0x4000 +#define SLSI_FW_API_RATE_HT_SELECTED 0x8000 +#define SLSI_FW_API_RATE_VHT_SELECTED 0xc000 + +#define SLSI_FW_API_RATE_VHT_MCS_FIELD 0x000F +#define SLSI_FW_API_RATE_HT_MCS_FIELD 0x003F +#define SLSI_FW_API_RATE_INDEX_FIELD 0x1fff +#define SLSI_FW_API_RATE_VHT_NSS_FIELD 0x0070 +#define SLSI_FW_API_RATE_HT_NSS_FIELD 0x0040 + +#define SLSI_FW_API_RATE_BW_FIELD 0x0600 +#define SLSI_FW_API_RATE_BW_40MHZ 0x0200 +#define SLSI_FW_API_RATE_BW_20MHZ 0x0000 + +#define SLSI_FW_API_RATE_SGI 0x0100 +#define SLSI_FW_API_RATE_GF 0x0080 + +/* indices: 3= BW20->idx_0, BW40->idx_1, BW80->idx_2. + * 2= noSGI->idx_0, SGI->idx_1 + * 10= mcs index + * rate units 100kbps + * This table for single stream Nss=1and does not include 160MHz BW and 80+80MHz BW. + */ +static const u16 slsi_rates_table[3][2][10] = { + { /* BW20 */ + { /* no SGI */ + 65, 130, 195, 260, 390, 520, 585, 650, 780, 0 + }, + { /* SGI */ + 72, 144, 217, 289, 433, 578, 650, 722, 867, 0 + } + }, + { /* BW40 */ + { /* no SGI */ + 135, 270, 405, 540, 810, 1080, 1215, 1350, 1620, 1800 + }, + { /* SGI */ + 150, 300, 450, 600, 900, 1200, 1350, 1500, 1800, 2000 + } + }, + { /* BW80 */ + { /* no SGI */ + 293, 585, 878, 1170, 1755, 2340, 2633, 2925, 3510, 3900 + }, + { /* SGI */ + 325, 650, 975, 1300, 1950, 2600, 2925, 3250, 3900, 4333 + } + } +}; +/* MSDU subframe Header */ +struct msduhdr { + unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ + unsigned char h_source[ETH_ALEN]; /* source ether addr */ + __be16 len; /* MSDU Subframe length */ + unsigned char dsap; /* DSAP field - SNAP 0xaa */ + unsigned char ssap; /* SSAP field - SNAP 0xaa */ + unsigned char ui; /* Control field: U, func UI - 0x03 */ + unsigned char oui[3]; /* Organization Code - 0x000000 */ + __be16 type; /* Type - 0x0800 (IPv4)*/ +} __attribute__((packed)); + +static inline void ethr_ii_to_subframe_msdu(struct sk_buff *skb) +{ + struct ethhdr *ehdr; + struct msduhdr msduh; + + ehdr = eth_hdr(skb); + ether_addr_copy(msduh.h_dest, ehdr->h_dest); + ether_addr_copy(msduh.h_source, ehdr->h_source); + /* adjust packet length */ + msduh.len = cpu_to_be16(skb->len - 6); + msduh.dsap = 0xaa; + msduh.ssap = 0xaa; + msduh.ui = 0x03; + memset(msduh.oui, 0x0, 3); + msduh.type = ehdr->h_proto; + (void)skb_push(skb, sizeof(struct msduhdr) - sizeof(struct ethhdr)); + /* update SKB mac_header to point to start of MSDU header */ + skb->mac_header -= (sizeof(struct msduhdr) - sizeof(struct ethhdr)); + memcpy(skb->data, &msduh, sizeof(struct msduhdr)); +} + +#define SLSI_TX_PROCESS_ID_MIN (0xC001) +#define SLSI_TX_PROCESS_ID_MAX (0xCF00) +#define SLSI_TX_PROCESS_ID_UDI_MIN (0xCF01) +#define SLSI_TX_PROCESS_ID_UDI_MAX (0xCFFE) + +/* There are no wakelocks in kernel/supplicant/hostapd. + * So keep the platform active for some time after receiving any data packet. + * This timeout value can be fine-tuned based on the test results. + */ +#define SLSI_RX_WAKELOCK_TIME (200) +#define MAX_BA_BUFFER_SIZE 64 +#define NUM_BA_SESSIONS_PER_PEER 8 +#define MAX_CHANNEL_LIST 20 +#define SLSI_MAX_RX_BA_SESSIONS (8) +#define SLSI_STA_ACTION_FRAME_BITMAP (SLSI_ACTION_FRAME_PUBLIC | SLSI_ACTION_FRAME_WMM | SLSI_ACTION_FRAME_WNM |\ + SLSI_ACTION_FRAME_QOS | SLSI_ACTION_FRAME_PROTECTED_DUAL |\ + SLSI_ACTION_FRAME_RADIO_MEASUREMENT) + +/* Default value for MIB SLSI_PSID_UNIFI_DISCONNECT_TIMEOUT + 1 sec*/ +#define SLSI_DEFAULT_AP_DISCONNECT_IND_TIMEOUT 3000 + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)) +#define WLAN_EID_VHT_CAPABILITY 191 +#define WLAN_EID_VHT_OPERATION 192 +#endif + +#define NUM_COUNTRY (300) + +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG +#define SLSI_MUTEX_INIT(slsi_mutex__) \ + { \ + (slsi_mutex__).owner = NULL; \ + mutex_init(&(slsi_mutex__).mutex); \ + (slsi_mutex__).valid = true; \ + } + +#define SLSI_MUTEX_LOCK(slsi_mutex_to_lock) \ + { \ + (slsi_mutex_to_lock).line_no_before = __LINE__; \ + (slsi_mutex_to_lock).file_name_before = __FILE__; \ + mutex_lock(&(slsi_mutex_to_lock).mutex); \ + (slsi_mutex_to_lock).owner = current; \ + (slsi_mutex_to_lock).line_no_after = __LINE__; \ + (slsi_mutex_to_lock).file_name_after = __FILE__; \ + (slsi_mutex_to_lock).function = __func__; \ + } + +#define SLSI_MUTEX_UNLOCK(slsi_mutex_to_unlock) \ + { \ + (slsi_mutex_to_unlock).owner = NULL; \ + mutex_unlock(&(slsi_mutex_to_unlock).mutex); \ + } +#define SLSI_MUTEX_IS_LOCKED(slsi_mutex__) mutex_is_locked(&(slsi_mutex__).mutex) + +struct slsi_mutex { + bool valid; + u32 line_no_before; + const u8 *file_name_before; + /* a std mutex */ + struct mutex mutex; + u32 line_no_after; + const u8 *file_name_after; + const u8 *function; + struct task_struct *owner; +}; + +#else +#define SLSI_MUTEX_INIT(mutex__) mutex_init(&(mutex__)) +#define SLSI_MUTEX_LOCK(mutex_to_lock) mutex_lock(&(mutex_to_lock)) +#define SLSI_MUTEX_UNLOCK(mutex_to_unlock) mutex_unlock(&(mutex_to_unlock)) +#define SLSI_MUTEX_IS_LOCKED(mutex__) mutex_is_locked(&(mutex__)) +#endif + +#define OS_UNUSED_PARAMETER(x) ((void)(x)) + +#define SLSI_HOST_TAG_TRAFFIC_QUEUE(htag) (htag & 0x00000003) + +/* For each mlme-req a mlme-cfm is expected to be received from the + * firmware. The host is not allowed to send another mlme-req until + * the mlme-cfm is received. + * + * However there are also instances where we need to wait for an mlme-ind + * following a mlme-req/cfm exchange. One example of this is the disconnect + * sequence: + * mlme-disconnect-req - host requests disconnection + * mlme-disconnect-cfm - firmware accepts disconnection request but hasn't + * disconnected yet. + * mlme-disconnect-ind - firmware reports final result of disconnection + * + * Assuming that waiting for the mlme-ind following on from the mlme-req/cfm + * is ok. + */ +struct slsi_sig_send { + /* a std spinlock */ + spinlock_t send_signal_lock; +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex mutex; +#else + /* a std mutex */ + struct mutex mutex; +#endif + struct completion completion; + + u16 process_id; + u16 req_id; + u16 cfm_id; + u16 ind_id; + struct sk_buff *cfm; + struct sk_buff *ind; + struct sk_buff *mib_error; +}; + +static inline void slsi_sig_send_init(struct slsi_sig_send *sig_send) +{ + spin_lock_init(&sig_send->send_signal_lock); + sig_send->req_id = 0; + sig_send->cfm_id = 0; + sig_send->process_id = SLSI_TX_PROCESS_ID_MIN; + SLSI_MUTEX_INIT(sig_send->mutex); + init_completion(&sig_send->completion); +} + +struct slsi_ba_frame_desc { + bool active; + struct sk_buff *signal; + u16 sn; +}; + +struct slsi_ba_session_rx { + bool active; + bool used; + void *vif; + struct slsi_ba_frame_desc buffer[MAX_BA_BUFFER_SIZE]; + u16 buffer_size; + u16 occupied_slots; + u16 expected_sn; + u16 start_sn; + u16 highest_received_sn; + bool trigger_ba_after_ssn; + u8 tid; + + /* Aging timer parameters */ + bool timer_on; + struct timer_list ba_age_timer; + struct slsi_spinlock ba_lock; + struct net_device *dev; +}; + +#define SLSI_TID_MAX (16) +#define SLSI_AMPDU_F_INITIATED (0x0001) +#define SLSI_AMPDU_F_CREATED (0x0002) +#define SLSI_AMPDU_F_OPERATIONAL (0x0004) + +#define SLSI_SCAN_HW_ID 0 +#define SLSI_SCAN_SCHED_ID 1 +#define SLSI_SCAN_MAX 3 + +#define SLSI_SCAN_SSID_MAP_MAX 10 /* Arbitrary value */ +#define SLSI_SCAN_SSID_MAP_EXPIRY_AGE 2 /* If hidden bss not found these many scan cycles, remove map. Arbitrary value*/ +#define SLSI_FW_SCAN_DONE_TIMEOUT_MSEC (15 * 1000) + +struct slsi_scan_result { + u8 bssid[ETH_ALEN]; + u8 hidden; + int rssi; + struct sk_buff *probe_resp; + struct sk_buff *beacon; + struct slsi_scan_result *next; + int band; +}; + +/* Per Interface Scan Data + * Access protected by: cfg80211_lock + */ +struct slsi_scan { + /* When a Scan is running this not NULL. */ + struct cfg80211_scan_request *scan_req; + struct slsi_acs_request *acs_request; + struct cfg80211_sched_scan_request *sched_req; + bool requeue_timeout_work; + + /* Indicates if the scan req is blocking. i.e, waiting until scan_done_ind received */ + bool is_blocking_scan; + + struct slsi_scan_result *scan_results; /* head for scan_results list*/ +}; + +struct slsi_ssid_map { + u8 bssid[ETH_ALEN]; + u8 ssid[32]; + u8 ssid_len; + u8 age; + int band; +}; + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +struct slsi_enhanced_arp_counters { + u16 arp_req_count_from_netdev; + u16 arp_req_count_to_lower_mac; + u16 arp_req_rx_count_by_lower_mac; + u16 arp_req_count_tx_success; + u16 arp_rsp_rx_count_by_lower_mac; + u16 arp_rsp_rx_count_by_upper_mac; + u16 arp_rsp_count_to_netdev; + u16 arp_rsp_count_out_of_order_drop; + u16 ap_link_active; + bool is_duplicate_addr_detected; +}; +#endif + +struct slsi_peer { + /* Flag MUST be set last when creating a record and immediately when removing. + * Otherwise another process could test the flag and start using the data. + */ + bool valid; + u8 address[ETH_ALEN]; + + /* Presently connected_state is used only for AP/GO mode*/ + u8 connected_state; + u16 aid; + /* Presently is_wps is used only in P2P GO mode */ + bool is_wps; + u16 capabilities; + bool qos_enabled; + u8 queueset; + struct scsc_wifi_fcq_data_qset data_qs; + struct scsc_wifi_fcq_ctrl_q ctrl_q; + + bool authorized; + bool pairwise_key_set; + + /* Needed for STA/AP VIF */ + struct sk_buff *assoc_ie; + struct sk_buff_head buffered_frames; + /* Needed for STA VIF */ + struct sk_buff *assoc_resp_ie; + + /* bitmask that keeps the status of acm bit for each AC + * bit 7 6 5 4 3 2 1 0 + * | | | | | | | | + * vo vo vi vi be bk bk be + */ + u8 wmm_acm; + /* bitmask that keeps the status of tspec establishment for each priority + * bit 7 6 5 4 3 2 1 0 + * | | | | | | | | + * p7 p6 p5 p4 p3 p2 p1 p0 + */ + u8 tspec_established; + u8 uapsd; + + /* TODO_HARDMAC: + * Q: Can we obtain stats from the firmware? + * Yes - then this is NOT needed and we can just get from the firmware when requested. + * No - How much can we get from the PSCHED? + */ + struct station_info sinfo; + /* rate limit for peer sinfo mib reads */ + struct ratelimit_state sinfo_mib_get_rs; + struct slsi_ba_session_rx *ba_session_rx[NUM_BA_SESSIONS_PER_PEER]; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + /* qos map configured at peer end*/ + bool qos_map_set; + struct cfg80211_qos_map qos_map; +#endif +}; + +/* Used to update vif type on vif deactivation indicating vif is no longer available */ +#define SLSI_VIFTYPE_UNSPECIFIED 0xFFFF + +struct slsi_vif_mgmt_tx { + u64 cookie; /* Cookie assigned by Host for the tx mgmt frame */ + u16 host_tag; /* Host tag for the tx mgmt frame */ + const u8 *buf; /* Buffer - Mgmt frame requested for tx */ + size_t buf_len; /* Buffer length */ + u8 exp_frame; /* Next expected Public action frame subtype from peer */ +}; + +struct slsi_wmm_ac { + u8 aci_aifsn; + u8 ecw; + u16 txop_limit; +} __packed; + +/* struct slsi_wmm_parameter_element + * + * eid - Vendor Specific + * len - Remaining Length of IE + * oui - Microsoft OUI + * oui_type - WMM + * oui_subtype - Param IE + * version - 1 + * qos_info - Qos + * reserved - + * ac - BE,BK,VI,VO + */ +struct slsi_wmm_parameter_element { + u8 eid; + u8 len; + u8 oui[3]; + u8 oui_type; + u8 oui_subtype; + u8 version; + u8 qos_info; + u8 reserved; + struct slsi_wmm_ac ac[4]; +} __packed; + +#define SLSI_MIN_FILTER_ID 0x80 /* Start of filter range reserved for host */ + +/* for AP */ +#define SLSI_AP_ALL_IPV6_PKTS_FILTER_ID 0x80 + +/* filter IDs for filters installed by driver */ +#ifdef CONFIG_SCSC_WLAN_BLOCK_IPV6 + +enum slsi_filter_id { + SLSI_LOCAL_ARP_FILTER_ID = SLSI_MIN_FILTER_ID, /* 0x80 */ + SLSI_ALL_BC_MC_FILTER_ID, /* 0x81 */ + SLSI_PROXY_ARP_FILTER_ID, /* 0x82 */ + SLSI_ALL_IPV6_PKTS_FILTER_ID, /* 0x83 */ +#ifndef CONFIG_SCSC_WLAN_DISABLE_NAT_KA + SLSI_NAT_IPSEC_FILTER_ID, /* 0x84 */ +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + SLSI_OPT_OUT_ALL_FILTER_ID, /* 0x85 */ + SLSI_OPT_IN_TCP4_FILTER_ID, /* 0x86 */ + SLSI_OPT_IN_TCP6_FILTER_ID, /* 0x87 */ +#endif + SLSI_REGD_MC_FILTER_ID, /* 0x88 */ +}; +#else + +/* for STA */ +enum slsi_filter_id { + SLSI_LOCAL_ARP_FILTER_ID = SLSI_MIN_FILTER_ID, /* 0x80 */ + SLSI_ALL_BC_MC_FILTER_ID, /* 0x81 */ + SLSI_PROXY_ARP_FILTER_ID, /* 0x82 */ + SLSI_LOCAL_NS_FILTER_ID, /* 0x83 */ + SLSI_PROXY_ARP_NA_FILTER_ID, /* 0x84 */ +#ifndef CONFIG_SCSC_WLAN_DISABLE_NAT_KA + SLSI_NAT_IPSEC_FILTER_ID, /* 0x85 */ +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + SLSI_OPT_OUT_ALL_FILTER_ID, /* 0x86 */ + SLSI_OPT_IN_TCP4_FILTER_ID, /* 0x87 */ + SLSI_OPT_IN_TCP6_FILTER_ID, /* 0x88 */ +#endif + SLSI_REGD_MC_FILTER_ID, /* 0x89 */ +}; + +#endif + +#define SLSI_MAX_PKT_FILTERS 16 + +#ifndef CONFIG_SCSC_WLAN_DISABLE_NAT_KA +/* default config */ +#define SLSI_MC_ADDR_ENTRY_MAX (SLSI_MIN_FILTER_ID + SLSI_MAX_PKT_FILTERS - SLSI_REGD_MC_FILTER_ID) +#else +#define SLSI_MC_ADDR_ENTRY_MAX (SLSI_MIN_FILTER_ID + SLSI_MAX_PKT_FILTERS - SLSI_REGD_MC_FILTER_ID + 1) +#endif + +/* Values for vif_status field + * + * Used to indicate the status of an activated VIF, to help resolve + * conflicting activities with indications from the firmware eg. + * cfg80211 triggers a disconnection before a STA completes its + * connection to an AP. + */ +#define SLSI_VIF_STATUS_UNSPECIFIED 0 +#define SLSI_VIF_STATUS_CONNECTING 1 +#define SLSI_VIF_STATUS_CONNECTED 2 +#define SLSI_VIF_STATUS_DISCONNECTING 3 + +/*From wifi_offload.h (N_AVAIL_ID=3)*/ +#define SLSI_MAX_KEEPALIVE_ID 3 + +struct slsi_last_connected_bss { + u8 address[ETH_ALEN]; + int antenna_mode; + int rssi; + int mode; + int passpoint_version; + int snr; + int noise_level; + u16 bandwidth; + u16 roaming_count; + u16 channel_freq; + u16 tx_data_rate; + u8 roaming_akm; + u8 kv; + u32 kvie; + bool mimo_used; +}; + +struct slsi_vif_sta { + /* Only valid when the VIF is activated */ + u8 vif_status; + bool is_wps; + u16 eap_hosttag; + u16 m4_host_tag; + u16 keepalive_host_tag[SLSI_MAX_KEEPALIVE_ID]; + + struct sk_buff *roam_mlme_procedure_started_ind; + + /* This id is used to find out which response (connect resp/roamed resp/reassoc resp) + * is to be sent once M4 is transmitted successfully + */ + u16 resp_id; + bool gratuitous_arp_needed; + + /* regd multicast address*/ + u8 regd_mc_addr_count; + u8 regd_mc_addr[SLSI_MC_ADDR_ENTRY_MAX][ETH_ALEN]; + bool group_key_set; + struct sk_buff *mlme_scan_ind_skb; + bool roam_in_progress; + int tdls_peer_sta_records; + bool tdls_enabled; + struct cfg80211_bss *sta_bss; + u8 *assoc_req_add_info_elem; + u8 assoc_req_add_info_elem_len; + + /* List of seen ESS and Freq associated with them */ + struct list_head network_map; + + struct slsi_wmm_ac wmm_ac[4]; + bool nd_offload_enabled; + + /*This structure is used to store last disconnected bss info and valid even when vif is deactivated. */ + struct slsi_last_connected_bss last_connected_bss; + struct cfg80211_crypto_settings crypto; + + /* Variable to indicate if roamed_ind needs to be dropped in driver, to maintain roam synchronization. */ + atomic_t drop_roamed_ind; +}; + +struct slsi_vif_unsync { + struct delayed_work roc_expiry_work; /* Work on ROC duration expiry */ + struct delayed_work del_vif_work; /* Work on unsync vif retention timeout */ + struct delayed_work hs2_del_vif_work; /* Work on HS2 unsync vif retention timeout */ + struct delayed_work unset_channel_expiry_work; /*unset channel after a timer */ + u64 roc_cookie; /* Cookie id for ROC */ + u8 *probe_rsp_ies; /* Probe response IEs to be configured in firmware */ + size_t probe_rsp_ies_len; /* Probe response IE length */ + bool ies_changed; /* To indicate if Probe Response IEs have changed from that previously stored */ + bool listen_offload; /* To indicate if Listen Offload is started */ + bool slsi_p2p_continuous_fullscan; +}; + +struct slsi_last_disconnected_sta { + u8 address[ETH_ALEN]; + u32 rx_retry_packets; + u32 rx_bc_mc_packets; + u16 capabilities; + int bandwidth; + int antenna_mode; + int rssi; + int mode; + u16 tx_data_rate; + bool mimo_used; + u16 reason; + int support_mode; +}; + +struct slsi_vif_ap { + struct slsi_wmm_parameter_element wmm_ie; + struct slsi_last_disconnected_sta last_disconnected_sta; + u8 *cache_wmm_ie; + u8 *cache_wpa_ie; + u8 *add_info_ies; + size_t wmm_ie_len; + size_t wpa_ie_len; + size_t add_info_ies_len; + bool p2p_gc_keys_set; /* Used in GO mode to identify that a CLI has connected after WPA2 handshake */ + bool privacy; /* Used for port enabling based on the open/secured AP configuration */ + bool qos_enabled; + int beacon_interval; /* Beacon interval in AP/GO mode */ + int mode; + bool non_ht_bss_present; /* Non HT BSS observed in HT20 OBSS scan */ + struct scsc_wifi_fcq_data_qset group_data_qs; + u32 cipher; + u16 channel_freq; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 ssid_len; +}; + +#define SLSI_NAN_MAX_PUBLISH_ID 16 +#define SLSI_NAN_MAX_SUBSCRIBE_ID 16 + +struct slsi_vif_nan { + u32 publish_id_map; + u32 subscribe_id_map; +}; + +#define TCP_ACK_SUPPRESSION_RECORDS_MAX 16 +#define TCP_ACK_SUPPRESSION_RECORD_UNUSED_TIMEOUT 10 /* in seconds */ + +#define TCP_ACK_SUPPRESSION_OPTIONS_OFFSET 20 +#define TCP_ACK_SUPPRESSION_OPTION_EOL 0 +#define TCP_ACK_SUPPRESSION_OPTION_NOP 1 +#define TCP_ACK_SUPPRESSION_OPTION_MSS 2 +#define TCP_ACK_SUPPRESSION_OPTION_WINDOW 3 +#define TCP_ACK_SUPPRESSION_OPTION_SACK 5 + +#define SLSI_IS_VIF_CHANNEL_5G(ndev_vif) (((ndev_vif)->chan) ? ((ndev_vif)->chan->hw_value > 14) : 0) + +struct slsi_tcp_ack_s { + u32 daddr; + u32 dport; + u32 saddr; + u32 sport; + struct sk_buff_head list; + u8 window_multiplier; + u16 mss; + u32 ack_seq; + u16 slow_start_count; + u8 count; + u8 max; + u8 age; + struct timer_list timer; + struct slsi_spinlock lock; + bool state; + ktime_t last_sent; + bool tcp_slow_start; + + /* TCP session throughput monitor */ + u16 hysteresis; + u32 last_tcp_rate; + ktime_t last_sample_time; + u32 last_ack_seq; + u64 num_bytes; +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + u8 stream_id; + u8 rx_window_scale; +#endif +}; + +struct slsi_tcp_ack_stats { + u32 tack_acks; + u32 tack_suppressed; + u32 tack_sent; + u32 tack_max; + u32 tack_timeout; + u32 tack_dacks; + u32 tack_sacks; + u32 tack_delay_acks; + u32 tack_low_window; + u32 tack_nocache; + u32 tack_norecord; + u32 tack_hasdata; + u32 tack_psh; + u32 tack_dropped; + u32 tack_ktime; + u32 tack_lastrecord; + u32 tack_searchrecord; + u32 tack_ece; +}; + +struct netdev_vif { + struct slsi_dev *sdev; + struct wireless_dev wdev; + atomic_t is_registered; /* Has the net dev been registered */ + bool is_available; /* Has the net dev been opened AND is usable */ + bool is_fw_test; /* Is the device in use as a test device via UDI */ +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + bool is_wips_running; +#endif + /* Structure can be accessed by cfg80211 ops, procfs/ioctls and as a result + * of receiving MLME indications e.g. MLME-CONNECT-IND that can affect the + * status of the interface eg. STA connect failure will delete the VIF. + */ +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex vif_mutex; +#else + /* a std mutex */ + struct mutex vif_mutex; +#endif + struct slsi_sig_send sig_wait; + + struct slsi_skb_work rx_data; + struct slsi_skb_work rx_mlme; + u16 ifnum; + enum nl80211_iftype iftype; + enum nl80211_channel_type channel_type; + struct ieee80211_channel *chan; + u16 driver_channel; + bool drv_in_p2p_procedure; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + struct cfg80211_chan_def *chandef; +#endif + + /* NOTE: The Address is a __be32 + * It needs converting to pass to the FW + * But not for the Arp or trace %pI4 + */ + __be32 ipaddress; + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + struct in6_addr ipv6address; + struct slsi_spinlock ipv6addr_lock; +#endif + struct net_device_stats stats; + u32 rx_packets[SLSI_LLS_AC_MAX]; + u32 tx_packets[SLSI_LLS_AC_MAX]; + u32 tx_no_ack[SLSI_LLS_AC_MAX]; +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex scan_mutex; + struct slsi_mutex scan_result_mutex; +#else + /* a std mutex */ + struct mutex scan_mutex; + struct mutex scan_result_mutex; + +#endif + struct slsi_scan scan[SLSI_SCAN_MAX]; + + struct slsi_src_sink_params src_sink_params; + u16 power_mode; + u16 set_power_mode; + + bool activated; /* VIF is created in firmware and ready to use */ + u16 vif_type; + struct slsi_spinlock peer_lock; + int peer_sta_records; + struct slsi_peer *peer_sta_record[SLSI_ADHOC_PEER_CONNECTIONS_MAX]; + + /* Used to populate the cfg80211 station_info structure generation variable. + * This number should increase every time the list of stations changes + * i.e. when a station is added or removed, so that userspace can tell + * whether it got a consistent snapshot. + */ + int cfg80211_sinfo_generation; + + /* Block Ack MPDU Re-order */ + struct sk_buff_head ba_complete; + atomic_t ba_flush; + + u64 mgmt_tx_cookie; /* Cookie id for mgmt tx */ + struct slsi_vif_mgmt_tx mgmt_tx_data; + struct delayed_work scan_timeout_work; /* Work on scan timeout */ + bool delete_probe_req_ies; /* Delete probe request stored at probe_req_ies, if + * connected for WAP2 at mlme_del_vif or in all cases + * if STA + */ + u8 *probe_req_ies; + size_t probe_req_ie_len; + + struct slsi_vif_unsync unsync; + struct slsi_vif_sta sta; + struct slsi_vif_ap ap; + struct slsi_vif_nan nan; + + /* TCP ack suppression. */ + struct slsi_tcp_ack_s *last_tcp_ack; + struct slsi_tcp_ack_s ack_suppression[TCP_ACK_SUPPRESSION_RECORDS_MAX]; + struct slsi_tcp_ack_stats tcp_ack_stats; + /* traffic monitor */ + ktime_t last_timer_time; + u32 report_time; + u32 num_bytes_tx_per_timer; + u32 num_bytes_rx_per_timer; + u32 num_bytes_tx_per_sec; + u32 num_bytes_rx_per_sec; + u32 throughput_tx; + u32 throughput_rx; + u32 throughput_tx_bps; + u32 throughput_rx_bps; +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + bool enhanced_arp_detect_enabled; + struct slsi_enhanced_arp_counters enhanced_arp_stats; + u8 target_ip_addr[4]; + int enhanced_arp_host_tag[5]; +#endif +}; + +struct slsi_802_11d_reg_domain { + u8 *countrylist; + struct ieee80211_regdomain *regdomain; + int country_len; +}; + +struct slsi_apf_capabilities { + u16 version; + u16 max_length; +}; + +#ifdef CONFIG_SCSC_WLAN_WES_NCHO +struct slsi_wes_mode_roam_scan_channels { + int n; + u8 channels[MAX_CHANNEL_LIST]; +}; +#endif + +struct slsi_dev_config { + /* Supported Freq Band (Dynamic) + * Set via the freq_band procfs + */ +#define SLSI_FREQ_BAND_AUTO 0 +#define SLSI_FREQ_BAND_5GHZ 1 +#define SLSI_FREQ_BAND_2GHZ 2 + int supported_band; + + struct ieee80211_supported_band *band_5G; + struct ieee80211_supported_band *band_2G; + + /* current user suspend mode + * Set via the suspend_mode procfs + * 0 : not suspended + * 1 : suspended + */ + int user_suspend_mode; + + /* Rx filtering rule + * Set via the rx_filter_num procfs + * 0: Unicast, 1: Broadcast, 2:Multicast IPv4, 3: Multicast IPv6 + */ + int rx_filter_num; + + /* Rx filter rule enabled + * Set via the rx_filter_start & rx_filter_stop procfs + */ + bool rx_filter_rule_started; + + /* AP Auto channel Selection */ +#define SLSI_NO_OF_SCAN_CHANLS_FOR_AUTO_CHAN_MAX 14 + int ap_auto_chan; + + /*QoS capability for a non-AP Station*/ + int qos_info; +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + /* NCHO OKC mode */ + int okc_mode; + + /*NCHO WES mode */ + int wes_mode; + + int roam_scan_mode; + + /*WES mode roam scan channels*/ + struct slsi_wes_mode_roam_scan_channels wes_roam_scan_list; +#endif + struct slsi_802_11d_reg_domain domain_info; + + int ap_disconnect_ind_timeout; + + u8 host_state; + + int rssi_boost_5g; + int rssi_boost_2g; + bool disable_ch12_ch13; + bool fw_enhanced_arp_detect_supported; + bool fw_apf_supported; + struct slsi_apf_capabilities apf_cap; +}; + +#define SLSI_DEVICE_STATE_ATTACHING 0 +#define SLSI_DEVICE_STATE_STOPPED 1 +#define SLSI_DEVICE_STATE_STARTING 2 +#define SLSI_DEVICE_STATE_STARTED 3 +#define SLSI_DEVICE_STATE_STOPPING 4 + +#define SLSI_NET_INDEX_WLAN 1 +#define SLSI_NET_INDEX_P2P 2 +#define SLSI_NET_INDEX_P2PX_SWLAN 3 +#define SLSI_NET_INDEX_NAN 4 + +/* States used during P2P operations */ +enum slsi_p2p_states { + P2P_IDLE_NO_VIF, /* Initial state - Unsync vif is not present */ + P2P_IDLE_VIF_ACTIVE, /* Unsync vif is present but no P2P procedure in progress */ + P2P_SCANNING, /* P2P SOCIAL channel (1,6,11) scan in progress. Not used for P2P full scan */ + P2P_LISTENING, /* P2P Listen (ROC) in progress */ + P2P_ACTION_FRAME_TX_RX, /* P2P Action frame Tx in progress or waiting for a peer action frame Rx (i.e. in response to the Tx frame) */ + P2P_GROUP_FORMED_CLI, /* P2P Group Formed - CLI role */ + P2P_GROUP_FORMED_GO, /* P2P Group Formed - GO role */ + /* NOTE: In P2P_LISTENING state if frame transmission is requested to driver then a peer response is ideally NOT expected. + * This is an assumption based on the fact that FIND would be stopped prior to group formation/connection. + * If driver were to receive a peer frame in P2P_LISTENING state then it would most probably be a REQUEST frame and the supplicant would respond to it. + * Hence the driver should get only RESPONSE frames for transmission in P2P_LISTENING state. + */ +}; + +enum slsi_wlan_state { + WLAN_UNSYNC_NO_VIF = 0, /* Initial state - Unsync vif is not present */ + WLAN_UNSYNC_VIF_ACTIVE, /* Unsync vif is activated but no wlan procedure in progress */ + WLAN_UNSYNC_VIF_TX /* Unsync vif is activated and wlan procedure in progress */ +}; + +/* Wakelock timeouts */ +#define SLSI_WAKELOCK_TIME_MSEC_EAPOL (1000) + +struct slsi_chip_info_mib { + u16 chip_version; +}; + +struct slsi_plat_info_mib { + u16 plat_build; +}; + +/* P2P States in text format for debug purposes */ +static inline char *slsi_p2p_state_text(u8 state) +{ + switch (state) { + case P2P_IDLE_NO_VIF: + return "P2P_IDLE_NO_VIF"; + case P2P_IDLE_VIF_ACTIVE: + return "P2P_IDLE_VIF_ACTIVE"; + case P2P_SCANNING: + return "P2P_SCANNING"; + case P2P_LISTENING: + return "P2P_LISTENING"; + case P2P_ACTION_FRAME_TX_RX: + return "P2P_ACTION_FRAME_TX_RX"; + case P2P_GROUP_FORMED_CLI: + return "P2P_GROUP_FORMED_CLI"; + case P2P_GROUP_FORMED_GO: + return "P2P_GROUP_FORMED_GO"; + default: + return "UNKNOWN"; + } +} + +#define SLSI_WLAN_MAX_HCF_PLATFORM_LEN (128) + +struct slsi_dev_mib_info { + char *mib_file_name; + unsigned int mib_hash; + + /* Cached File MIB Configuration values from User Space */ + u8 *mib_data; + u32 mib_len; + char platform[SLSI_WLAN_MAX_HCF_PLATFORM_LEN]; +}; + +#define SLSI_WLAN_MAX_MIB_FILE 2 /* Number of WLAN HCFs to load */ + +#ifdef CONFIG_SCSC_LOG_COLLECTION +struct slsi_dev_mib_collect_file { + char file_name[32]; + u16 len; + u8 *data; +} __packed; + +struct slsi_dev_mib_collect { + bool enabled; + /* Serialize writers/readers */ + spinlock_t in_collection; + char num_files; + /* +1 represents local_mib */ + struct slsi_dev_mib_collect_file file[SLSI_WLAN_MAX_MIB_FILE + 1]; +}; + +#endif + +struct slsi_dev { + /* Devices */ + struct device *dev; + struct wiphy *wiphy; + + struct slsi_hip hip; /* HIP bookkeeping block */ + struct slsi_hip4 hip4_inst; /* The handler to parse to HIP */ + + struct scsc_wifi_cm_if cm_if; /* cm_if bookkeeping block */ + struct scsc_mx *maxwell_core; + struct scsc_service_client mx_wlan_client; + struct scsc_service *service; + struct slsi_chip_info_mib chip_info_mib; + struct slsi_plat_info_mib plat_info_mib; + u16 reg_dom_version; + +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex netdev_add_remove_mutex; +#else + /* a std mutex */ + struct mutex netdev_add_remove_mutex; +#endif + /* mutex to protect dynamic netdev removal */ + struct mutex netdev_remove_mutex; + int netdev_up_count; + struct net_device __rcu *netdev[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1]; /* 0 is reserved */ + struct net_device __rcu *netdev_ap; + u8 netdev_addresses[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1][ETH_ALEN]; /* 0 is reserved */ + + int device_state; + + /* BoT */ + atomic_t in_pause_state; + + /* Locking used to control Starting and stopping the chip */ +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex start_stop_mutex; +#else + /* a std mutex */ + struct mutex start_stop_mutex; +#endif + /* UDI Logging */ + struct slsi_log_clients log_clients; + void *uf_cdev; + + /* ProcFS */ + int procfs_instance; + struct proc_dir_entry *procfs_dir; + + /* Configuration */ + u8 hw_addr[ETH_ALEN]; + struct slsi_dev_mib_info mib[SLSI_WLAN_MAX_MIB_FILE]; + struct slsi_dev_mib_info local_mib; +#ifdef CONFIG_SCSC_LOG_COLLECTION + struct slsi_dev_mib_collect collect_mib; +#endif + char *maddr_file_name; + bool *term_udi_users; /* Try to terminate UDI users during unload */ + int *sig_wait_cfm_timeout; + + struct slsi_wake_lock wlan_wl; + struct slsi_wake_lock wlan_wl_mlme; + struct slsi_wake_lock wlan_wl_ma; +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID + struct wake_lock wlan_wl_roam; +#endif + struct slsi_sig_send sig_wait; + struct slsi_skb_work rx_dbg_sap; + atomic_t tx_host_tag[SLSI_LLS_AC_MAX]; +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex device_config_mutex; +#else + /* a std mutex */ + struct mutex device_config_mutex; +#endif + struct slsi_dev_config device_config; + + struct notifier_block inetaddr_notifier; +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + struct notifier_block inet6addr_notifier; +#endif + + struct workqueue_struct *device_wq; /* Driver Workqueue */ + enum slsi_p2p_states p2p_state; /* Store current P2P operation */ + + enum slsi_wlan_state wlan_unsync_vif_state; /* Store current sate of unsync wlan vif */ + + int current_tspec_id; + int tspec_error_code; + u8 p2p_group_exp_frame; /* Next expected Public action frame subtype from peer */ + bool initial_scan; + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + struct slsi_gscan *gscan; + struct slsi_gscan_result *gscan_hash_table[SLSI_GSCAN_HASH_TABLE_SIZE]; + int num_gscan_results; + int buffer_threshold; + int buffer_consumed; + struct slsi_bucket bucket[SLSI_GSCAN_MAX_BUCKETS]; + struct list_head hotlist_results; + bool epno_active; +#endif +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + u8 scan_mac_addr[6]; + bool scan_addr_set; +#endif +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + int minor_prof; +#endif + struct slsi_ba_session_rx rx_ba_buffer_pool[SLSI_MAX_RX_BA_SESSIONS]; + struct slsi_spinlock rx_ba_buffer_pool_lock; + bool fail_reported; + bool p2p_certif; /* Set to true to idenitfy p2p_certification testing is going on*/ + bool mlme_blocked; /* When true do not send mlme signals to FW */ + atomic_t debug_inds; + int recovery_next_state; + struct completion recovery_remove_completion; + struct completion recovery_stop_completion; + struct completion recovery_completed; + int recovery_status; + struct slsi_ssid_map ssid_map[SLSI_SCAN_SSID_MAP_MAX]; + bool band_5g_supported; + int supported_2g_channels[14]; + int supported_5g_channels[25]; + int enabled_channel_count; + bool fw_ht_enabled; + u8 fw_ht_cap[4]; /* HT capabilities is 21 bytes but host is never intersted in last 17 bytes*/ + bool fw_vht_enabled; + u8 fw_vht_cap[4]; +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + u8 wifi_sharing_5ghz_channel[8]; + int valid_5g_freq[25]; + int wifi_sharing_5g_restricted_channels[25]; + int num_5g_restricted_channels; +#endif + bool fw_2g_40mhz_enabled; + bool nan_enabled; + u16 assoc_result_code; /* Status of latest association in STA mode */ + bool allow_switch_40_mhz; /* Used in AP cert to disable HT40 when not configured */ + bool allow_switch_80_mhz; /* Used in AP cert to disable VHT when not configured */ +#ifdef CONFIG_SCSC_WLAN_AP_INFO_FILE + /* Parameters in '/data/vendor/conn/.softap.info' */ + bool dualband_concurrency; + u32 softap_max_client; +#endif + u32 fw_dwell_time; + int lls_num_radio; + +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + struct slsi_mutex logger_mutex; +#else + /* a std mutex */ + struct mutex logger_mutex; +#endif + struct slsi_traffic_mon_clients traffic_mon_clients; + /*Store vif index corresponding to rtt id for FTM*/ + u16 rtt_vif[8]; + bool acs_channel_switched; + int recovery_timeout; /* ms autorecovery completion timeout */ +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + bool fw_mac_randomization_enabled; +#endif + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + bool enhanced_pkt_filter_enabled; +#endif +}; + +/* Compact representation of channels a ESS has been seen on + * This is sized correctly for the Channels we currently support, + * 2.4Ghz Channels 1 - 14 + * 5 Ghz Channels Uni1, Uni2 and Uni3 + */ +struct slsi_roaming_network_map_entry { + struct list_head list; + unsigned long last_seen_jiffies; /* Timestamp of the last time we saw this ESS */ + struct cfg80211_ssid ssid; /* SSID of the ESS */ + u8 initial_bssid[ETH_ALEN]; /* Bssid of the first ap seen in this ESS */ + bool only_one_ap_seen; /* Has more than one AP for this ESS been seen */ + u16 channels_24_ghz; /* 2.4 Ghz Channels Bit Map */ + /* 5 Ghz Channels Bit Map + * channels_5_ghz & 0x000000FF = 4 Uni1 Channels + * channels_5_ghz & 0x00FFFF00 = 15 Uni2 Channels + * channels_5_ghz & 0xFF000000 = 5 Uni3 Channels + */ + u32 channels_5_ghz; +}; + +#define LLC_SNAP_HDR_LEN 8 +struct llc_snap_hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + u16 snap_type; +} __packed; + +void slsi_rx_data_deliver_skb(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, bool from_ba_timer); +void slsi_rx_dbg_sap_work(struct work_struct *work); +void slsi_rx_netdev_data_work(struct work_struct *work); +void slsi_rx_netdev_mlme_work(struct work_struct *work); +int slsi_rx_enqueue_netdev_mlme(struct slsi_dev *sdev, struct sk_buff *skb, u16 vif); +void slsi_rx_scan_pass_to_cfg80211(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_buffered_frames(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer); +int slsi_rx_blocking_signals(struct slsi_dev *sdev, struct sk_buff *skb); +void slsi_scan_complete(struct slsi_dev *sdev, struct net_device *dev, u16 scan_id, bool aborted); + +void slsi_tx_pause_queues(struct slsi_dev *sdev); +void slsi_tx_unpause_queues(struct slsi_dev *sdev); +int slsi_tx_control(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +int slsi_tx_data(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +int slsi_tx_data_lower(struct slsi_dev *sdev, struct sk_buff *skb); +bool slsi_is_test_mode_enabled(void); +bool slsi_is_rf_test_mode_enabled(void); +int slsi_check_rf_test_mode(void); +void slsi_regd_deinit(struct slsi_dev *sdev); +void slsi_init_netdev_mac_addr(struct slsi_dev *sdev); +bool slsi_dev_lls_supported(void); +bool slsi_dev_gscan_supported(void); +bool slsi_dev_epno_supported(void); +bool slsi_dev_vo_vi_block_ack(void); +int slsi_dev_get_scan_result_count(void); +bool slsi_dev_llslogs_supported(void); +int slsi_dev_nan_supported(struct slsi_dev *sdev); +void slsi_regd_init(struct slsi_dev *sdev); +bool slsi_dev_rtt_supported(void); + +static inline u16 slsi_tx_host_tag(struct slsi_dev *sdev, enum slsi_traffic_q tq) +{ + /* host_tag: + * bit 0,1 = trafficqueue identifier + * bit 2-15 = incremental number + * So increment by 4 to get bit 2-15 a incremental sequence + */ + return (u16)atomic_add_return(4, &sdev->tx_host_tag[tq]); +} + +static inline u16 slsi_tx_mgmt_host_tag(struct slsi_dev *sdev) +{ + /* Doesn't matter which traffic queue host tag is selected.*/ + return slsi_tx_host_tag(sdev, 0); +} + +static inline struct net_device *slsi_get_netdev_rcu(struct slsi_dev *sdev, u16 ifnum) +{ + WARN_ON(!rcu_read_lock_held()); + if (ifnum > CONFIG_SCSC_WLAN_MAX_INTERFACES) { + /* WARN(1, "ifnum:%d", ifnum); WARN() is used like this to avoid Coverity Error */ + return NULL; + } + return rcu_dereference(sdev->netdev[ifnum]); +} + +static inline struct net_device *slsi_get_netdev_locked(struct slsi_dev *sdev, u16 ifnum) +{ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->netdev_add_remove_mutex)); + if (ifnum > CONFIG_SCSC_WLAN_MAX_INTERFACES) { + WARN(1, "ifnum:%d", ifnum); /* WARN() is used like this to avoid Coverity Error */ + return NULL; + } + return sdev->netdev[ifnum]; +} + +static inline struct net_device *slsi_get_netdev(struct slsi_dev *sdev, u16 ifnum) +{ + struct net_device *dev; + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + dev = slsi_get_netdev_locked(sdev, ifnum); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + return dev; +} + +static inline int slsi_get_supported_mode(const u8 *peer_ie) +{ + const u8 *peer_ie_data; + u8 peer_ie_len; + int i; + int supported_rate; + + peer_ie_len = peer_ie[1]; + peer_ie_data = &peer_ie[2]; + for (i = 0; i < peer_ie_len; i++) { + supported_rate = ((peer_ie_data[i] & 0x7F) / 2); + if (supported_rate > 11) + return SLSI_80211_MODE_11G; + } + return SLSI_80211_MODE_11B; +} + +/* Names of full mode HCF files */ +extern char *slsi_mib_file; +extern char *slsi_mib_file2; + +#endif diff --git a/drivers/net/wireless/scsc/fapi.h b/drivers/net/wireless/scsc/fapi.h new file mode 100644 index 000000000000..ffc57d9c0784 --- /dev/null +++ b/drivers/net/wireless/scsc/fapi.h @@ -0,0 +1,4252 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Note: this is an auto-generated file. */ + +#ifndef _FAPI_H__ +#define _FAPI_H__ + +#include +#include +#include "utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_SCSC_SMAPPER +struct slsi_skb_cb { + u32 sig_length; + u32 data_length; + u32 frame_format; + u32 colour; +}; + +static inline struct slsi_skb_cb *slsi_skb_cb_get(struct sk_buff *skb) +{ + return (struct slsi_skb_cb *)skb->cb; +} + +static inline struct slsi_skb_cb *slsi_skb_cb_init(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct slsi_skb_cb) > sizeof(skb->cb)); + + memset(skb->cb, 0, sizeof(struct slsi_skb_cb)); + return slsi_skb_cb_get(skb); +} +#endif + +#define FAPI_SIG_TYPE_MASK 0x0F00 +#define FAPI_SIG_TYPE_REQ 0x0000 +#define FAPI_SIG_TYPE_CFM 0x0100 +#define FAPI_SIG_TYPE_RES 0x0200 +#define FAPI_SIG_TYPE_IND 0x0300 + +#define FAPI_SAP_TYPE_MASK 0xF000 +#define FAPI_SAP_TYPE_MA 0x1000 +#define FAPI_SAP_TYPE_MLME 0x2000 +#define FAPI_SAP_TYPE_DEBUG 0x8000 +#define FAPI_SAP_TYPE_TEST 0x9000 + +#define FAPI_DEBUG_SAP_ENG_VERSION 0x0001 +#define FAPI_DATA_SAP_ENG_VERSION 0x0001 +#define FAPI_CONTROL_SAP_ENG_VERSION 0x0006 +#define FAPI_TEST_SAP_ENG_VERSION 0x000e +#define FAPI_DEBUG_SAP_VERSION 0x0d03 +#define FAPI_CONTROL_SAP_VERSION 0x0e00 +#define FAPI_TEST_SAP_VERSION 0x0e00 +#define FAPI_DATA_SAP_VERSION 0x0e00 + +#define FAPI_ACLPOLICY_BLACKLIST 0x0000 +#define FAPI_ACLPOLICY_WHITELIST 0x0001 + +#define FAPI_ACTION_STOP 0x0000 +#define FAPI_ACTION_START 0x0001 + +#define FAPI_APFFILTERMODE_SUSPEND 0x0001 +#define FAPI_APFFILTERMODE_ACTIVE 0x0002 + +#define FAPI_AUTHENTICATIONTYPE_OPEN_SYSTEM 0x0000 +#define FAPI_AUTHENTICATIONTYPE_SHARED_KEY 0x0001 +#define FAPI_AUTHENTICATIONTYPE_SAE 0x0003 +#define FAPI_AUTHENTICATIONTYPE_LEAP 0x0080 + +#define FAPI_BANDWIDTH_20_MHZ 0x0 +#define FAPI_BANDWIDTH_40_MHZ 0x1 +#define FAPI_BANDWIDTH_80_MHZ 0x2 +#define FAPI_BANDWIDTH_160_MHZ 0x3 + +#define FAPI_BLOCKACKPOLICY_DELAYED_BLOCKACK 0x0 +#define FAPI_BLOCKACKPOLICY_IMMEDIATE_BLOCKACK 0x1 + +#define FAPI_BULKDATADESCRIPTOR_INLINE 0x0000 +#define FAPI_BULKDATADESCRIPTOR_SMAPPER 0x0001 + +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ 0x00 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ 0x01 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ 0x02 + +#define FAPI_CONNECTIONTYPE_WLAN_INFRASTRUCTURE 0x0000 +#define FAPI_CONNECTIONTYPE_P2P_OPERATION 0x0001 +#define FAPI_CONNECTIONTYPE_NAN_FURTHER_SERVICE_SLOT 0x0004 +#define FAPI_CONNECTIONTYPE_WLAN_RANGING 0x0005 + +#define FAPI_CWSTARTFLAGS_NONE 0x0000 +#define FAPI_CWSTARTFLAGS_SCAN_CHANNEL 0x0001 + +#define FAPI_CWTYPE_SINE 0x0000 +#define FAPI_CWTYPE_RAMP 0x0001 +#define FAPI_CWTYPE_TWO_TONE 0x0002 +#define FAPI_CWTYPE_DC 0x0003 +#define FAPI_CWTYPE_PRN 0x0004 + +#define FAPI_DATARATE_11B20_1MBPS 0x0001 +#define FAPI_DATARATE_11B20_2MBPS 0x0002 +#define FAPI_DATARATE_11B20_5M5BPS 0x0005 +#define FAPI_DATARATE_11B20_11MBPS 0x000b +#define FAPI_DATARATE_11A20_6MBPS 0x2006 +#define FAPI_DATARATE_11A20_9MBPS 0x2009 +#define FAPI_DATARATE_11A20_12MBPS 0x200c +#define FAPI_DATARATE_11A20_18MBPS 0x2012 +#define FAPI_DATARATE_11A20_24MBPS 0x2018 +#define FAPI_DATARATE_11A20_36MBPS 0x2024 +#define FAPI_DATARATE_11A20_48MBPS 0x2030 +#define FAPI_DATARATE_11A20_54MBPS 0x2036 +#define FAPI_DATARATE_11N20_6M5BPS 0x4000 +#define FAPI_DATARATE_11N20_13MBPS 0x4001 +#define FAPI_DATARATE_11N20_19M5BPS 0x4002 +#define FAPI_DATARATE_11N20_26MBPS 0x4003 +#define FAPI_DATARATE_11N20_39MBPS 0x4004 +#define FAPI_DATARATE_11N20_52MBPS 0x4005 +#define FAPI_DATARATE_11N20_58M5BPS 0x4006 +#define FAPI_DATARATE_11N20_65MBPS 0x4007 +#define FAPI_DATARATE_11N20_7M2BPS_SGI 0x4040 +#define FAPI_DATARATE_11N20_14M4BPS_SGI 0x4041 +#define FAPI_DATARATE_11N20_21M7BPS_SGI 0x4042 +#define FAPI_DATARATE_11N20_28M9BPS_SGI 0x4043 +#define FAPI_DATARATE_11N20_43M3BPS_SGI 0x4044 +#define FAPI_DATARATE_11N20_57M8BPS_SGI 0x4045 +#define FAPI_DATARATE_11N20_65MBPS_SGI 0x4046 +#define FAPI_DATARATE_11N20_72M2BPS_SGI 0x4047 +#define FAPI_DATARATE_11N20_13MBPS_NSS2 0x4088 +#define FAPI_DATARATE_11N20_26MBPS_NSS2 0x4089 +#define FAPI_DATARATE_11N20_39MBPS_NSS2 0x408a +#define FAPI_DATARATE_11N20_52MBPS_NSS2 0x408b +#define FAPI_DATARATE_11N20_78MBPS_NSS2 0x408c +#define FAPI_DATARATE_11N20_104MBPS_NSS2 0x408d +#define FAPI_DATARATE_11N20_117MBPS_NSS2 0x408e +#define FAPI_DATARATE_11N20_130MBPS_NSS2 0x408f +#define FAPI_DATARATE_11N20_14M4BPS_SGI_NSS2 0x40c8 +#define FAPI_DATARATE_11N20_28M9BPS_SGI_NSS2 0x40c9 +#define FAPI_DATARATE_11N20_43M3BPS_SGI_NSS2 0x40ca +#define FAPI_DATARATE_11N20_57M8BPS_SGI_NSS2 0x40cb +#define FAPI_DATARATE_11N20_86M7BPS_SGI_NSS2 0x40cc +#define FAPI_DATARATE_11N20_115M6BPS_SGI_NSS2 0x40cd +#define FAPI_DATARATE_11N20_130MBPS_SGI_NSS2 0x40ce +#define FAPI_DATARATE_11N20_144M4BPS_SGI_NSS2 0x40cf +#define FAPI_DATARATE_11N40_13M5BPS 0x4400 +#define FAPI_DATARATE_11N40_27MBPS 0x4401 +#define FAPI_DATARATE_11N40_40M5BPS 0x4402 +#define FAPI_DATARATE_11N40_54MBPS 0x4403 +#define FAPI_DATARATE_11N40_81MBPS 0x4404 +#define FAPI_DATARATE_11N40_108MBPS 0x4405 +#define FAPI_DATARATE_11N40_121M5BPS 0x4406 +#define FAPI_DATARATE_11N40_135MBPS 0x4407 +#define FAPI_DATARATE_11N40_6MBPS 0x4420 +#define FAPI_DATARATE_11N40_15MBPS_SGI 0x4440 +#define FAPI_DATARATE_11N40_30MBPS_SGI 0x4441 +#define FAPI_DATARATE_11N40_45MBPS_SGI 0x4442 +#define FAPI_DATARATE_11N40_60MBPS_SGI 0x4443 +#define FAPI_DATARATE_11N40_90MBPS_SGI 0x4444 +#define FAPI_DATARATE_11N40_120MBPS_SGI 0x4445 +#define FAPI_DATARATE_11N40_135MBPS_SGI 0x4446 +#define FAPI_DATARATE_11N40_150MBPS_SGI 0x4447 +#define FAPI_DATARATE_11N40_6M7BPS_SGI 0x4460 +#define FAPI_DATARATE_11N40_27MBPS_NSS2 0x4488 +#define FAPI_DATARATE_11N40_54MBPS_NSS2 0x4489 +#define FAPI_DATARATE_11N40_81MBPS_NSS2 0x448a +#define FAPI_DATARATE_11N40_108MBPS_NSS2 0x448b +#define FAPI_DATARATE_11N40_162MBPS_NSS2 0x448c +#define FAPI_DATARATE_11N40_216MBPS_NSS2 0x448d +#define FAPI_DATARATE_11N40_243MBPS_NSS2 0x448e +#define FAPI_DATARATE_11N40_270MBPS_NSS2 0x448f +#define FAPI_DATARATE_11N40_30MBPS_SGI_NSS2 0x44c8 +#define FAPI_DATARATE_11N40_60MBPS_SGI_NSS2 0x44c9 +#define FAPI_DATARATE_11N40_90MBPS_SGI_NSS2 0x44ca +#define FAPI_DATARATE_11N40_120MBPS_SGI_NSS2 0x44cb +#define FAPI_DATARATE_11N40_180MBPS_SGI_NSS2 0x44cc +#define FAPI_DATARATE_11N40_240MBPS_SGI_NSS2 0x44cd +#define FAPI_DATARATE_11N40_270MBPS_SGI_NSS2 0x44ce +#define FAPI_DATARATE_11N40_300MBPS_SGI_NSS2 0x44cf +#define FAPI_DATARATE_11AC20_6M5BPS 0x6000 +#define FAPI_DATARATE_11AC20_13MBPS 0x6001 +#define FAPI_DATARATE_11AC20_19M5BPS 0x6002 +#define FAPI_DATARATE_11AC20_26MBPS 0x6003 +#define FAPI_DATARATE_11AC20_39MBPS 0x6004 +#define FAPI_DATARATE_11AC20_52MBPS 0x6005 +#define FAPI_DATARATE_11AC20_58M5BPS 0x6006 +#define FAPI_DATARATE_11AC20_65MBPS 0x6007 +#define FAPI_DATARATE_11AC20_78MBPS 0x6008 +#define FAPI_DATARATE_11AC20_7M2BPS_SGI 0x6040 +#define FAPI_DATARATE_11AC20_14M4BPS_SGI 0x6041 +#define FAPI_DATARATE_11AC20_21M7BPS_SGI 0x6042 +#define FAPI_DATARATE_11AC20_28M9BPS_SGI 0x6043 +#define FAPI_DATARATE_11AC20_43M3BPS_SGI 0x6044 +#define FAPI_DATARATE_11AC20_57M8BPS_SGI 0x6045 +#define FAPI_DATARATE_11AC20_65MBPS_SGI 0x6046 +#define FAPI_DATARATE_11AC20_72M2BPS_SGI 0x6047 +#define FAPI_DATARATE_11AC20_86M7BPS_SGI 0x6048 +#define FAPI_DATARATE_11AC20_13MBPS_NSS2 0x6080 +#define FAPI_DATARATE_11AC20_26MBPS_NSS2 0x6081 +#define FAPI_DATARATE_11AC20_39MBPS_NSS2 0x6082 +#define FAPI_DATARATE_11AC20_52MBPS_NSS2 0x6083 +#define FAPI_DATARATE_11AC20_78MBPS_NSS2 0x6084 +#define FAPI_DATARATE_11AC20_104MBPS_NSS2 0x6085 +#define FAPI_DATARATE_11AC20_117MBPS_NSS2 0x6086 +#define FAPI_DATARATE_11AC20_130MBPS_NSS2 0x6087 +#define FAPI_DATARATE_11AC20_156MBPS_NSS2 0x6088 +#define FAPI_DATARATE_11AC20_14M4BPS_SGI_NSS2 0x60c0 +#define FAPI_DATARATE_11AC20_28M9BPS_SGI_NSS2 0x60c1 +#define FAPI_DATARATE_11AC20_43M3BPS_SGI_NSS2 0x60c2 +#define FAPI_DATARATE_11AC20_57M8BPS_SGI_NSS2 0x60c3 +#define FAPI_DATARATE_11AC20_86M7BPS_SGI_NSS2 0x60c4 +#define FAPI_DATARATE_11AC20_115M6BPS_SGI_NSS2 0x60c5 +#define FAPI_DATARATE_11AC20_130MBPS_SGI_NSS2 0x60c6 +#define FAPI_DATARATE_11AC20_144M4BPS_SGI_NSS2 0x60c7 +#define FAPI_DATARATE_11AC20_173M3BPS_SGI_NSS2 0x60c8 +#define FAPI_DATARATE_11AC40_13M5BPS 0x6400 +#define FAPI_DATARATE_11AC40_27MBPS 0x6401 +#define FAPI_DATARATE_11AC40_40M5BPS 0x6402 +#define FAPI_DATARATE_11AC40_54MBPS 0x6403 +#define FAPI_DATARATE_11AC40_81MBPS 0x6404 +#define FAPI_DATARATE_11AC40_108MBPS 0x6405 +#define FAPI_DATARATE_11AC40_121M5BPS 0x6406 +#define FAPI_DATARATE_11AC40_135MBPS 0x6407 +#define FAPI_DATARATE_11AC40_162MBPS 0x6408 +#define FAPI_DATARATE_11AC40_180MBPS 0x6409 +#define FAPI_DATARATE_11AC40_15MBPS_SGI 0x6440 +#define FAPI_DATARATE_11AC40_30MBPS_SGI 0x6441 +#define FAPI_DATARATE_11AC40_45MBPS_SGI 0x6442 +#define FAPI_DATARATE_11AC40_60MBPS_SGI 0x6443 +#define FAPI_DATARATE_11AC40_90MBPS_SGI 0x6444 +#define FAPI_DATARATE_11AC40_120MBPS_SGI 0x6445 +#define FAPI_DATARATE_11AC40_135MBPS_SGI 0x6446 +#define FAPI_DATARATE_11AC40_150MBPS_SGI 0x6447 +#define FAPI_DATARATE_11AC40_180MBPS_SGI 0x6448 +#define FAPI_DATARATE_11AC40_200MBPS_SGI 0x6449 +#define FAPI_DATARATE_11AC40_27MBPS_NSS2 0x6480 +#define FAPI_DATARATE_11AC40_54MBPS_NSS2 0x6481 +#define FAPI_DATARATE_11AC40_81MBPS_NSS2 0x6482 +#define FAPI_DATARATE_11AC40_108MBPS_NSS2 0x6483 +#define FAPI_DATARATE_11AC40_162MBPS_NSS2 0x6484 +#define FAPI_DATARATE_11AC40_216MBPS_NSS2 0x6485 +#define FAPI_DATARATE_11AC40_243MBPS_NSS2 0x6486 +#define FAPI_DATARATE_11AC40_270MBPS_NSS2 0x6487 +#define FAPI_DATARATE_11AC40_324MBPS_NSS2 0x6488 +#define FAPI_DATARATE_11AC40_360MBPS_NSS2 0x6489 +#define FAPI_DATARATE_11AC40_30MBPS_SGI_NSS2 0x64c0 +#define FAPI_DATARATE_11AC40_60MBPS_SGI_NSS2 0x64c1 +#define FAPI_DATARATE_11AC40_90MBPS_SGI_NSS2 0x64c2 +#define FAPI_DATARATE_11AC40_120MBPS_SGI_NSS2 0x64c3 +#define FAPI_DATARATE_11AC40_180MBPS_SGI_NSS2 0x64c4 +#define FAPI_DATARATE_11AC40_240MBPS_SGI_NSS2 0x64c5 +#define FAPI_DATARATE_11AC40_270MBPS_SGI_NSS2 0x64c6 +#define FAPI_DATARATE_11AC40_300MBPS_SGI_NSS2 0x64c7 +#define FAPI_DATARATE_11AC40_360MBPS_SGI_NSS2 0x64c8 +#define FAPI_DATARATE_11AC40_400MBPS_SGI_NSS2 0x64c9 +#define FAPI_DATARATE_11AC80_29M3BPS 0x6800 +#define FAPI_DATARATE_11AC80_58M5BPS 0x6801 +#define FAPI_DATARATE_11AC80_87M8BPS 0x6802 +#define FAPI_DATARATE_11AC80_117MBPS 0x6803 +#define FAPI_DATARATE_11AC80_175M5BPS 0x6804 +#define FAPI_DATARATE_11AC80_234MBPS 0x6805 +#define FAPI_DATARATE_11AC80_263M3BPS 0x6806 +#define FAPI_DATARATE_11AC80_292M5BPS 0x6807 +#define FAPI_DATARATE_11AC80_351MBPS 0x6808 +#define FAPI_DATARATE_11AC80_390MBPS 0x6809 +#define FAPI_DATARATE_11AC80_32M5BPS_SGI 0x6840 +#define FAPI_DATARATE_11AC80_65MBPS_SGI 0x6841 +#define FAPI_DATARATE_11AC80_97M5BPS_SGI 0x6842 +#define FAPI_DATARATE_11AC80_130MBPS_SGI 0x6843 +#define FAPI_DATARATE_11AC80_195MBPS_SGI 0x6844 +#define FAPI_DATARATE_11AC80_260MBPS_SGI 0x6845 +#define FAPI_DATARATE_11AC80_292M5BPS_SGI 0x6846 +#define FAPI_DATARATE_11AC80_325MBPS_SGI 0x6847 +#define FAPI_DATARATE_11AC80_390MBPS_SGI 0x6848 +#define FAPI_DATARATE_11AC80_433M3BPS_SGI 0x6849 +#define FAPI_DATARATE_11AC80_58M5BPS_NSS2 0x6880 +#define FAPI_DATARATE_11AC80_117MBPS_NSS2 0x6881 +#define FAPI_DATARATE_11AC80_175M5BPS_NSS2 0x6882 +#define FAPI_DATARATE_11AC80_234MBPS_NSS2 0x6883 +#define FAPI_DATARATE_11AC80_351MBPS_NSS2 0x6884 +#define FAPI_DATARATE_11AC80_468MBPS_NSS2 0x6885 +#define FAPI_DATARATE_11AC80_526M5BPS_NSS2 0x6886 +#define FAPI_DATARATE_11AC80_585MBPS_NSS2 0x6887 +#define FAPI_DATARATE_11AC80_702MBPS_NSS2 0x6888 +#define FAPI_DATARATE_11AC80_780MBPS_NSS2 0x6889 +#define FAPI_DATARATE_11AC80_65MBPS_SGI_NSS2 0x68c0 +#define FAPI_DATARATE_11AC80_130MBPS_SGI_NSS2 0x68c1 +#define FAPI_DATARATE_11AC80_195MBPS_SGI_NSS2 0x68c2 +#define FAPI_DATARATE_11AC80_260MBPS_SGI_NSS2 0x68c3 +#define FAPI_DATARATE_11AC80_390MBPS_SGI_NSS2 0x68c4 +#define FAPI_DATARATE_11AC80_520MBPS_SGI_NSS2 0x68c5 +#define FAPI_DATARATE_11AC80_585MBPS_SGI_NSS2 0x68c6 +#define FAPI_DATARATE_11AC80_650MBPS_SGI_NSS2 0x68c7 +#define FAPI_DATARATE_11AC80_780MBPS_SGI_NSS2 0x68c8 +#define FAPI_DATARATE_11AC80_866M7BPS_SGI_NSS2 0x68c9 +#define FAPI_DATARATE_CTR_TOTAL 0xe000 +#define FAPI_DATARATE_CTR_NO_ERROR 0xe001 +#define FAPI_DATARATE_CTR_CRC_ERROR 0xe002 +#define FAPI_DATARATE_CTR_BAD_SIGNAL 0xe003 +#define FAPI_DATARATE_CTR_STBC 0xe004 +#define FAPI_DATARATE_CTR_DUPLICATE 0xe005 +#define FAPI_DATARATE_CTR_ERROR 0xe006 +#define FAPI_DATARATE_CTR_LDPC 0xe007 +#define FAPI_DATARATE_CTR_BEAMFORMED 0xe008 + +#define FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME 0x0000 +#define FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME 0x0001 +#define FAPI_DATAUNITDESCRIPTOR_AMSDU_SUBFRAME 0x0002 +#define FAPI_DATAUNITDESCRIPTOR_AMSDU 0x0003 +#define FAPI_DATAUNITDESCRIPTOR_TCP_ACK 0x0004 + +#define FAPI_DEVICEROLE_INFRASTRUCTURE_STATION 0x0001 +#define FAPI_DEVICEROLE_P2P_GO 0x0002 +#define FAPI_DEVICEROLE_P2P_DEVICE 0x0003 +#define FAPI_DEVICEROLE_P2P_CLIENT 0x0004 + +#define FAPI_DEVICESTATE_IDLE 0 +#define FAPI_DEVICESTATE_RX_RUNNING 1 +#define FAPI_DEVICESTATE_TX_RUNNING 2 +#define FAPI_DEVICESTATE_CW_RUNNING 3 +#define FAPI_DEVICESTATE_BIST_RUNNING 4 + +#define FAPI_DFSREGULATORY_UNKNOWN 0x0000 +#define FAPI_DFSREGULATORY_FCC 0x0001 +#define FAPI_DFSREGULATORY_ETSI 0x0002 +#define FAPI_DFSREGULATORY_JAPAN 0x0003 +#define FAPI_DFSREGULATORY_GLOBAL 0x0004 +#define FAPI_DFSREGULATORY_CHINA 0x0006 + +#define FAPI_DIRECTION_TRANSMIT 0x0000 +#define FAPI_DIRECTION_RECEIVE 0x0001 + +#define FAPI_ENDPOINT_HOSTIO 0x0001 +#define FAPI_ENDPOINT_DPLP 0x0002 + +#define FAPI_EPNOPOLICY_HIDDEN 0x0001 +#define FAPI_EPNOPOLICY_A_BAND 0x0002 +#define FAPI_EPNOPOLICY_G_BAND 0x0004 +#define FAPI_EPNOPOLICY_STRICT_MATCH 0x0008 +#define FAPI_EPNOPOLICY_SAME_NETWORK 0x0010 +#define FAPI_EPNOPOLICY_AUTH_OPEN 0x0100 +#define FAPI_EPNOPOLICY_AUTH_PSK 0x0200 +#define FAPI_EPNOPOLICY_AUTH_EAPOL 0x0400 + +#define FAPI_EVENT_WIFI_EVENT_ASSOCIATION_REQUESTED 0x0000 +#define FAPI_EVENT_WIFI_EVENT_AUTH_COMPLETE 0x0001 +#define FAPI_EVENT_WIFI_EVENT_ASSOC_COMPLETE 0x0002 +#define FAPI_EVENT_WIFI_EVENT_FW_AUTH_STARTED 0x0003 +#define FAPI_EVENT_WIFI_EVENT_FW_ASSOC_STARTED 0x0004 +#define FAPI_EVENT_WIFI_EVENT_FW_RE_ASSOC_STARTED 0x0005 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_SCAN_REQUESTED 0x0006 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_SCAN_RESULT_FOUND 0x0007 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_SCAN_COMPLETE 0x0008 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_STARTED 0x0009 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_COMPLETE 0x000a +#define FAPI_EVENT_WIFI_EVENT_DISASSOCIATION_REQUESTED 0x000b +#define FAPI_EVENT_WIFI_EVENT_RE_ASSOCIATION_REQUESTED 0x000c +#define FAPI_EVENT_WIFI_EVENT_ROAM_REQUESTED 0x000d +#define FAPI_EVENT_WIFI_EVENT_BEACON_RECEIVED 0x000e +#define FAPI_EVENT_WIFI_EVENT_ROAM_SCAN_STARTED 0x000f +#define FAPI_EVENT_WIFI_EVENT_ROAM_SCAN_COMPLETE 0x0010 +#define FAPI_EVENT_WIFI_EVENT_ROAM_SEARCH_STARTED 0x0011 +#define FAPI_EVENT_WIFI_EVENT_ROAM_SEARCH_STOPPED 0x0012 +#define FAPI_EVENT_WIFI_EVENT_CHANNEL_SWITCH_ANOUNCEMENT 0x0014 +#define FAPI_EVENT_WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_START 0x0015 +#define FAPI_EVENT_WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_STOP 0x0016 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_EAPOL_FRAME_TRANSMIT_REQUESTED 0x0017 +#define FAPI_EVENT_WIFI_EVENT_FW_EAPOL_FRAME_RECEIVED 0x0018 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_EAPOL_FRAME_RECEIVED 0x001a +#define FAPI_EVENT_WIFI_EVENT_BLOCK_ACK_NEGOTIATION_COMPLETE 0x001b +#define FAPI_EVENT_WIFI_EVENT_BT_COEX_BT_SCO_START 0x001c +#define FAPI_EVENT_WIFI_EVENT_BT_COEX_BT_SCO_STOP 0x001d +#define FAPI_EVENT_WIFI_EVENT_BT_COEX_BT_SCAN_START 0x001e +#define FAPI_EVENT_WIFI_EVENT_BT_COEX_BT_SCAN_STOP 0x001f +#define FAPI_EVENT_WIFI_EVENT_BT_COEX_BT_HID_START 0x0020 +#define FAPI_EVENT_WIFI_EVENT_BT_COEX_BT_HID_STOP 0x0021 +#define FAPI_EVENT_WIFI_EVENT_ROAM_AUTH_STARTED 0x0022 +#define FAPI_EVENT_WIFI_EVENT_ROAM_AUTH_COMPLETE 0x0023 +#define FAPI_EVENT_WIFI_EVENT_ROAM_ASSOC_STARTED 0x0024 +#define FAPI_EVENT_WIFI_EVENT_ROAM_ASSOC_COMPLETE 0x0025 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_STOP 0x0026 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_CYCLE_STARTED 0x0027 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_CYCLE_COMPLETED 0x0028 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_BUCKET_STARTED 0x0029 +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_BUCKET_COMPLETED 0x002a +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_RESULTS_AVAILABLE 0x002b +#define FAPI_EVENT_WIFI_EVENT_G_SCAN_CAPABILITIES 0x002c +#define FAPI_EVENT_WIFI_EVENT_ROAM_CANDIDATE_FOUND 0x002d +#define FAPI_EVENT_WIFI_EVENT_ROAM_SCAN_CONFIG 0x002e +#define FAPI_EVENT_WIFI_EVENT_AUTH_TIMEOUT 0x002f +#define FAPI_EVENT_WIFI_EVENT_ASSOC_TIMEOUT 0x0030 +#define FAPI_EVENT_WIFI_EVENT_MEM_ALLOC_FAILURE 0x0031 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_PNO_ADD 0x0032 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_PNO_REMOVE 0x0033 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_PNO_NETWORK_FOUND 0x0034 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_PNO_SCAN_REQUESTED 0x0035 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_PNO_SCAN_RESULT_FOUND 0x0036 +#define FAPI_EVENT_WIFI_EVENT_DRIVER_PNO_SCAN_COMPLETE 0x0037 +#define FAPI_EVENT_WIFI_EVENT_BLACKOUT_START 0x0064 +#define FAPI_EVENT_WIFI_EVENT_BLACKOUT_STOP 0x0065 +#define FAPI_EVENT_WIFI_EVENT_NAN_PUBLISH_TERMINATED 0x0100 +#define FAPI_EVENT_WIFI_EVENT_NAN_SUBSCRIBE_TERMINATED 0x0101 +#define FAPI_EVENT_WIFI_EVENT_NAN_MATCH_EXPIRED 0x0102 +#define FAPI_EVENT_WIFI_EVENT_NAN_ADDRESS_CHANGED 0x0103 +#define FAPI_EVENT_WIFI_EVENT_NAN_CLUSTER_STARTED 0x0104 +#define FAPI_EVENT_WIFI_EVENT_NAN_CLUSTER_JOINED 0x0105 +#define FAPI_EVENT_WIFI_EVENT_NAN_TRANSMIT_FOLLOWUP 0x0106 + +#define FAPI_HIDDENSSID_NOT_HIDDEN 0x0000 +#define FAPI_HIDDENSSID_HIDDEN_ZERO_LENGTH 0x0001 +#define FAPI_HIDDENSSID_HIDDEN_ZERO_DATA 0x0002 + +#define FAPI_HOSTSTATE_LCD_ACTIVE 0x0001 +#define FAPI_HOSTSTATE_CELLULAR_ACTIVE 0x0002 +#define FAPI_HOSTSTATE_SAR_ACTIVE 0x0004 +#define FAPI_HOSTSTATE_GRIP_ACTIVE 0x0040 +#define FAPI_HOSTSTATE_LOW_LATENCY_ACTIVE 0x0080 + +#define FAPI_HT_NON_HT_RATE 0x1 +#define FAPI_HT_HT_RATE 0x2 +#define FAPI_HT_VHT_RATE 0x3 + +#define FAPI_KEYTYPE_GROUP 0x0000 +#define FAPI_KEYTYPE_PAIRWISE 0x0001 +#define FAPI_KEYTYPE_WEP 0x0002 +#define FAPI_KEYTYPE_IGTK 0x0003 +#define FAPI_KEYTYPE_PMK 0x0004 +#define FAPI_KEYTYPE_FIRST_ILLEGAL 0x0005 + +#define FAPI_MESSAGETYPE_EAP_MESSAGE 0x0001 +#define FAPI_MESSAGETYPE_EAPOL_KEY_M123 0x0002 +#define FAPI_MESSAGETYPE_EAPOL_KEY_M4 0x0003 +#define FAPI_MESSAGETYPE_ARP 0x0004 +#define FAPI_MESSAGETYPE_DHCP 0x0005 +#define FAPI_MESSAGETYPE_NEIGHBOR_DISCOVERY 0x0006 +#define FAPI_MESSAGETYPE_WAI_MESSAGE 0x0007 +#define FAPI_MESSAGETYPE_ANY_OTHER 0x0008 +#define FAPI_MESSAGETYPE_IEEE80211_ACTION 0x0010 +#define FAPI_MESSAGETYPE_IEEE80211_MGMT 0x0011 + +#define FAPI_MODE_SOURCE 0x0001 +#define FAPI_MODE_SINK 0x0002 +#define FAPI_MODE_LOOPBACK 0x0003 + +#define FAPI_NANAVAILABILITYDURATION_16MS 0x00 +#define FAPI_NANAVAILABILITYDURATION_32MS 0x01 +#define FAPI_NANAVAILABILITYDURATION_64MS 0x02 + +#define FAPI_NANOPERATIONCONTROL_MAC_ADDRESS_EVENT 0x0002 +#define FAPI_NANOPERATIONCONTROL_START_CLUSTER_EVENT 0x0004 +#define FAPI_NANOPERATIONCONTROL_JOINED_CLUSTER_EVENT 0x0008 + +#define FAPI_NANSDFCONTROL_PUBLISH_END_EVENT 0x0001 +#define FAPI_NANSDFCONTROL_SUBSCRIBE_END_EVENT 0x0002 +#define FAPI_NANSDFCONTROL_MATCH_EXPIRED_EVENT 0x0004 +#define FAPI_NANSDFCONTROL_RECEIVED_FOLLOWUP_EVENT 0x0008 +#define FAPI_NANSDFCONTROL_DISABLE_FOLLOWUP_TRANSMIT_STATUS 0x0010 + +#define FAPI_NSSTYPE_NSS_ONE_STREAM 0x0000 +#define FAPI_NSSTYPE_NSS_TWO_STREAMS 0x0001 +#define FAPI_NSSTYPE_NSS_THREE_STREAMS 0x0002 +#define FAPI_NSSTYPE_NSS_FOUR_STREAMS 0x0003 +#define FAPI_NSSTYPE_NSS_FIVE_STREAMS 0x0004 +#define FAPI_NSSTYPE_NSS_SIX_STREAMS 0x0005 +#define FAPI_NSSTYPE_NSS_SEVEN_STREAMS 0x0006 +#define FAPI_NSSTYPE_NSS_EIGHT_STREAMS 0x0007 + +#define FAPI_PACKETFILTERMODE_OPT_OUT 0x01 +#define FAPI_PACKETFILTERMODE_OPT_IN 0x02 +#define FAPI_PACKETFILTERMODE_OPT_OUT_SLEEP 0x04 +#define FAPI_PACKETFILTERMODE_OPT_IN_SLEEP 0x08 + +#define FAPI_PMALLOCAREA_PMALLOC_STATS 0x0000 +#define FAPI_PMALLOCAREA_PMALLOC_FSM_STATS 0x0001 +#define FAPI_PMALLOCAREA_HOSTIO_SIG_SIZES 0x0002 + +#define FAPI_POWERMANAGEMENTMODE_ACTIVE_MODE 0x0000 +#define FAPI_POWERMANAGEMENTMODE_POWER_SAVE 0x0001 + +#define FAPI_PRIMARYCHANNELPOSITION_P0 0x00 +#define FAPI_PRIMARYCHANNELPOSITION_P1 0x01 +#define FAPI_PRIMARYCHANNELPOSITION_P2 0x02 +#define FAPI_PRIMARYCHANNELPOSITION_P3 0x03 +#define FAPI_PRIMARYCHANNELPOSITION_P4 0x04 +#define FAPI_PRIMARYCHANNELPOSITION_P5 0x05 +#define FAPI_PRIMARYCHANNELPOSITION_P6 0x06 +#define FAPI_PRIMARYCHANNELPOSITION_P7 0x07 +#define FAPI_PRIMARYCHANNELPOSITION_NOT_APPLICABLE 0x08 + +#define FAPI_PRIORITY_QOS_UP0 0x0000 +#define FAPI_PRIORITY_QOS_UP1 0x0001 +#define FAPI_PRIORITY_QOS_UP2 0x0002 +#define FAPI_PRIORITY_QOS_UP3 0x0003 +#define FAPI_PRIORITY_QOS_UP4 0x0004 +#define FAPI_PRIORITY_QOS_UP5 0x0005 +#define FAPI_PRIORITY_QOS_UP6 0x0006 +#define FAPI_PRIORITY_QOS_UP7 0x0007 +#define FAPI_PRIORITY_CONTENTION 0x8000 + +#define FAPI_PROCEDURETYPE_UNKNOWN 0x0000 +#define FAPI_PROCEDURETYPE_CONNECTION_STARTED 0x0001 +#define FAPI_PROCEDURETYPE_DEVICE_DISCOVERED 0x0002 +#define FAPI_PROCEDURETYPE_ROAMING_STARTED 0x0003 + +#define FAPI_PROCESSOR_DEFAULT 0x0 +#define FAPI_PROCESSOR_CHIPCPU 0x1 +#define FAPI_PROCESSOR_HOSTCPU 0x3 + +#define FAPI_PROTOCOL_UDP 0x0001 +#define FAPI_PROTOCOL_TCP 0x0002 + +#define FAPI_PURPOSE_BEACON 0x0001 +#define FAPI_PURPOSE_PROBE_RESPONSE 0x0002 +#define FAPI_PURPOSE_ASSOCIATION_RESPONSE 0x0004 +#define FAPI_PURPOSE_LOCAL 0x0008 +#define FAPI_PURPOSE_ASSOCIATION_REQUEST 0x0010 +#define FAPI_PURPOSE_PROBE_REQUEST 0x0020 + +#define FAPI_RADIOBITMAP_RADIO_0 0x0001 +#define FAPI_RADIOBITMAP_RADIO_1 0x0002 + +#define FAPI_REASONCODE_RESERVED 0x0000 +#define FAPI_REASONCODE_UNSPECIFIED_REASON 0x0001 +#define FAPI_REASONCODE_DEAUTHENTICATED_INVALID_AUTHENTICATION 0x0002 +#define FAPI_REASONCODE_DEAUTHENTICATED_LEAVING 0x0003 +#define FAPI_REASONCODE_DEAUTHENTICATED_NO_MORE_STATIONS 0x0005 +#define FAPI_REASONCODE_DEAUTHENTICATED_INVALID_CLASS_2_FRAME 0x0006 +#define FAPI_REASONCODE_DEAUTHENTICATED_INVALID_CLASS_3_FRAME 0x0007 +#define FAPI_REASONCODE_DEAUTHENTICATED_REASON_INVALID_IE 0x000d +#define FAPI_REASONCODE_DEAUTHENTICATED_4_WAY_HANDSHAKE_TIMEOUT 0x000f +#define FAPI_REASONCODE_DEAUTHENTICATED_GROUP_HANDSHAKE_TIMEOUT 0x0010 +#define FAPI_REASONCODE_DEAUTHENTICATED_HANDSHAKE_ELEMENT_MISMATCH 0x0011 +#define FAPI_REASONCODE_DEAUTHENTICATED_REASON_INVALID_RSNE 0x0014 +#define FAPI_REASONCODE_DEAUTHENTICATED_802_1_X_AUTH_FAILED 0x0017 +#define FAPI_REASONCODE_TDLS_PEER_UNREACHABLE 0x0019 +#define FAPI_REASONCODE_TDLS_TEARDOWN_UNSPECIFIED_REASON 0x001a +#define FAPI_REASONCODE_QOS_UNSPECIFIED_REASON 0x0020 +#define FAPI_REASONCODE_QOS_EXCESSIVE_NOT_ACK 0x0022 +#define FAPI_REASONCODE_QOS_TXOP_LIMIT_EXCEEDED 0x0023 +#define FAPI_REASONCODE_QSTA_LEAVING 0x0024 +#define FAPI_REASONCODE_END 0x0025 +#define FAPI_REASONCODE_UNKNOWN 0x0026 +#define FAPI_REASONCODE_TIMEOUT 0x0027 +#define FAPI_REASONCODE_KEEP_ALIVE_FAILURE 0x0028 +#define FAPI_REASONCODE_START 0x0029 +#define FAPI_REASONCODE_DEAUTHENTICATED_REASON_INVALID_PMKID 0x0031 +#define FAPI_REASONCODE_INVALID_PMKID 0x0049 +#define FAPI_REASONCODE_SYNCHRONISATION_LOSS 0x8003 +#define FAPI_REASONCODE_SECURITY_REQUIRED 0x8004 +#define FAPI_REASONCODE_ROAMING_FAILURE_LINK_LOSS_NO_CANDIDATE 0x8005 +#define FAPI_REASONCODE_HOTSPOT_MAX_CLIENT_REACHED 0x8006 +#define FAPI_REASONCODE_CHANNEL_SWITCH_FAILURE 0x8007 +#define FAPI_REASONCODE_REPORTING_ABORTED_SCANNING 0x8008 +#define FAPI_REASONCODE_REPORTING_ABORTED_ROAMING 0x8009 +#define FAPI_REASONCODE_NAN_SERVICE_TERMINATED_TIMEOUT 0x9001 +#define FAPI_REASONCODE_NAN_SERVICE_TERMINATED_USER_REQUEST 0x9002 +#define FAPI_REASONCODE_NAN_SERVICE_TERMINATED_COUNT_REACHED 0x9003 +#define FAPI_REASONCODE_NAN_SERVICE_TERMINATED_DISCOVERY_SHUTDOWN 0x9004 +#define FAPI_REASONCODE_NAN_TRANSMIT_FOLLOWUP_SUCCESS 0x9006 +#define FAPI_REASONCODE_NAN_TRANSMIT_FOLLOWUP_FAILURE 0x9007 +#define FAPI_REASONCODE_NDP_ACCEPTED 0x9008 +#define FAPI_REASONCODE_NDP_REJECTED 0x9009 + +#define FAPI_REPORTMODE_RESERVED 0x0001 +#define FAPI_REPORTMODE_END_OF_SCAN_CYCLE 0x0002 +#define FAPI_REPORTMODE_REAL_TIME 0x0004 +#define FAPI_REPORTMODE_NO_BATCH 0x0008 + +#define FAPI_RESULTCODE_SUCCESS 0x0000 +#define FAPI_RESULTCODE_UNSPECIFIED_FAILURE 0x0001 +#define FAPI_RESULTCODE_INVALID_PARAMETERS 0x0026 +#define FAPI_RESULTCODE_REJECTED_INVALID_IE 0x0028 +#define FAPI_RESULTCODE_NOT_ALLOWED 0x0030 +#define FAPI_RESULTCODE_NOT_PRESENT 0x0031 +#define FAPI_RESULTCODE_TRANSMISSION_FAILURE 0x004f +#define FAPI_RESULTCODE_TOO_MANY_SIMULTANEOUS_REQUESTS 0x8001 +#define FAPI_RESULTCODE_BSS_ALREADY_STARTED_OR_JOINED 0x8002 +#define FAPI_RESULTCODE_NOT_SUPPORTED 0x8003 +#define FAPI_RESULTCODE_INVALID_STATE 0x8004 +#define FAPI_RESULTCODE_INSUFFICIENT_RESOURCE 0x8006 +#define FAPI_RESULTCODE_INVALID_VIRTUAL_INTERFACE_INDEX 0x800a +#define FAPI_RESULTCODE_HOST_REQUEST_SUCCESS 0x800b +#define FAPI_RESULTCODE_HOST_REQUEST_FAILED 0x800c +#define FAPI_RESULTCODE_INVALID_FREQUENCY 0x800e +#define FAPI_RESULTCODE_PROBE_TIMEOUT 0x800f +#define FAPI_RESULTCODE_AUTH_TIMEOUT 0x8010 +#define FAPI_RESULTCODE_ASSOC_TIMEOUT 0x8011 +#define FAPI_RESULTCODE_ASSOC_ABORT 0x8012 +#define FAPI_RESULTCODE_AUTH_NO_ACK 0x8013 +#define FAPI_RESULTCODE_ASSOC_NO_ACK 0x8014 +#define FAPI_RESULTCODE_AUTH_FAILED_CODE 0x8100 +#define FAPI_RESULTCODE_ASSOC_FAILED_CODE 0x8200 +#define FAPI_RESULTCODE_INVALID_TLV_VALUE 0x9000 +#define FAPI_RESULTCODE_NAN_PROTOCOL_FAILURE 0x9001 +#define FAPI_RESULTCODE_NAN_INVALID_PUBLISH_SUBSCRIBE_ID 0x9002 +#define FAPI_RESULTCODE_NAN_INVALID_REQUESTOR_INSTANCE_ID 0x9003 +#define FAPI_RESULTCODE_UNSUPPORTED_CONCURRENCY 0x9004 +#define FAPI_RESULTCODE_NAN_INVALID_NDP_ID 0x9005 +#define FAPI_RESULTCODE_NAN_INVALID_PEER_ID 0x9006 +#define FAPI_RESULTCODE_NAN_NO_OTA_ACK 0x9007 +#define FAPI_RESULTCODE_NAN_INVALID_AVAILABILITY 0x9008 +#define FAPI_RESULTCODE_NAN_IMMUTABLE_UNACCEPTABLE 0x9009 +#define FAPI_RESULTCODE_NAN_REJECTED_SECURITY_POLICY 0x900a +#define FAPI_RESULTCODE_NDP_REJECTED 0x900b +#define FAPI_RESULTCODE_NDL_UNACCEPTABLE 0x900c +#define FAPI_RESULTCODE_NDL_FAILED_SCHEDULE 0x900d + +#define FAPI_RTTBANDWIDTH_20MHZ 0x0004 +#define FAPI_RTTBANDWIDTH_40MHZ 0x0008 +#define FAPI_RTTBANDWIDTH_80MHZ 0x0010 +#define FAPI_RTTBANDWIDTH_160MHZ 0x0020 + +#define FAPI_RTTPREAMBLE_LEGACY 0x0001 +#define FAPI_RTTPREAMBLE_HT 0x0002 +#define FAPI_RTTPREAMBLE_VHT 0x0004 + +#define FAPI_RTTSTATUS_SUCCESS 0x0000 +#define FAPI_RTTSTATUS_UNSPECIFIED_FAILURE 0x0001 +#define FAPI_RTTSTATUS_FAIL_NO_RESPONSE 0x0002 +#define FAPI_RTTSTATUS_FAIL_REJECTED 0x0003 +#define FAPI_RTTSTATUS_FAIL_NOT_SCHEDULED 0x0004 +#define FAPI_RTTSTATUS_FAIL_TIMEOUT 0x0005 +#define FAPI_RTTSTATUS_FAIL_INCORRECT_CHANNEL 0x0006 +#define FAPI_RTTSTATUS_FAIL_FTM_NOT_SUPPORTED 0x0007 +#define FAPI_RTTSTATUS_FAIL_MEASUREMENT_ABORTED 0x0008 +#define FAPI_RTTSTATUS_FAIL_INVALID_TIME_STAMP 0x0009 +#define FAPI_RTTSTATUS_FAIL_NO_FTM_RECEIVED 0x000a +#define FAPI_RTTSTATUS_FAIL_BURST_NOT_SCHEDULED 0x000b +#define FAPI_RTTSTATUS_FAIL_BUSY_TRY_LATER 0x000c +#define FAPI_RTTSTATUS_FAIL_INVALID_REQUEST 0x000d +#define FAPI_RTTSTATUS_FAIL_FTM_PARAMETER_OVERRIDE 0x000f + +#define FAPI_RTTTYPE_ONE_SIDED 0x0001 +#define FAPI_RTTTYPE_TWO_SIDED 0x0002 + +#define FAPI_RULEFLAG_NO_IR 0x0001 +#define FAPI_RULEFLAG_DFS 0x0002 +#define FAPI_RULEFLAG_NO_OFDM 0x0004 +#define FAPI_RULEFLAG_NO_INDOOR 0x0008 +#define FAPI_RULEFLAG_NO_OUTDOOR 0x0010 + +#define FAPI_RXSTARTFLAGS_NONE 0x0000 +#define FAPI_RXSTARTFLAGS_SCAN_CHANNEL 0x0001 +#define FAPI_RXSTARTFLAGS_FILTERING 0x0002 +#define FAPI_RXSTARTFLAGS_BEAMFORMING 0x0004 +#define FAPI_RXSTARTFLAGS_ACK 0x0008 +#define FAPI_RXSTARTFLAGS_LP_MODE 0x0010 +#define FAPI_RXSTARTFLAGS_CHAN_RSSI 0x0020 +#define FAPI_RXSTARTFLAGS_DISABLE_EXTERNAL_LNA 0x0040 + +#define FAPI_SCANPOLICY_PASSIVE 0x01 +#define FAPI_SCANPOLICY_TEST_MODE 0x02 +#define FAPI_SCANPOLICY_ANY_RA 0x04 +#define FAPI_SCANPOLICY_2_4GHZ 0x08 +#define FAPI_SCANPOLICY_5GHZ 0x10 +#define FAPI_SCANPOLICY_NON_DFS 0x20 +#define FAPI_SCANPOLICY_DFS 0x40 +#define FAPI_SCANPOLICY_ON_CHANNEL 0x80 + +#define FAPI_SCANTYPE_INITIAL_SCAN 0x0001 +#define FAPI_SCANTYPE_FULL_SCAN 0x0002 +#define FAPI_SCANTYPE_SCHEDULED_SCAN 0x0003 +#define FAPI_SCANTYPE_P2P_SCAN_FULL 0x0004 +#define FAPI_SCANTYPE_P2P_SCAN_SOCIAL 0x0005 +#define FAPI_SCANTYPE_OBSS_SCAN 0x0006 +#define FAPI_SCANTYPE_AP_AUTO_CHANNEL_SELECTION 0x0007 +#define FAPI_SCANTYPE_GSCAN 0x0009 +#define FAPI_SCANTYPE_MEASUREMENT_SCAN 0x000a +#define FAPI_SCANTYPE_SOFT_NEIGHBOUR_ROAMING_SCAN 0x000b +#define FAPI_SCANTYPE_SOFT_CACHED_ROAMING_SCAN 0x000c +#define FAPI_SCANTYPE_SOFT_ALL_ROAMING_SCAN 0x000d +#define FAPI_SCANTYPE_HARD_NEIGHBOUR_ROAMING_SCAN 0x000e +#define FAPI_SCANTYPE_HARD_CACHED_ROAMING_SCAN 0x000f +#define FAPI_SCANTYPE_HARD_ALL_ROAMING_SCAN 0x0010 +#define FAPI_SCANTYPE_OBSS_SCAN_INTERNAL 0x0011 +#define FAPI_SCANTYPE_NAN_SCAN 0x0012 +#define FAPI_SCANTYPE_FTM_NEIGHBOUR_SCAN 0x0013 +#define FAPI_SCANTYPE_FIRST_ILLEGAL 0x0014 + +#define FAPI_STATSSTOPBITMAP_STATS_RADIO 0x0001 +#define FAPI_STATSSTOPBITMAP_STATS_RADIO_CCA 0x0002 +#define FAPI_STATSSTOPBITMAP_STATS_RADIO_CHANNELS 0x0004 +#define FAPI_STATSSTOPBITMAP_STATS_RADIO_SCAN 0x0008 +#define FAPI_STATSSTOPBITMAP_STATS_IFACE 0x0010 +#define FAPI_STATSSTOPBITMAP_STATS_IFACE_TXRATE 0x0020 +#define FAPI_STATSSTOPBITMAP_STATS_IFACE_AC 0x0040 +#define FAPI_STATSSTOPBITMAP_STATS_IFACE_CONTENSION 0x0080 + +#define FAPI_TDLSACTION_DISCOVERY 0x0000 +#define FAPI_TDLSACTION_SETUP 0x0001 +#define FAPI_TDLSACTION_TEARDOWN 0x0002 +/*todo*/ +#define FAPI_TDLSACTION_CHANNEL_SWITCH 0x0003 + +#define FAPI_TDLSEVENT_CONNECTED 0x0001 +#define FAPI_TDLSEVENT_DISCONNECTED 0x0002 +#define FAPI_TDLSEVENT_DISCOVERED 0x0003 + +#define FAPI_TRANSMISSIONSTATUS_SUCCESSFUL 0x0000 +#define FAPI_TRANSMISSIONSTATUS_RETRY_LIMIT 0x0001 +#define FAPI_TRANSMISSIONSTATUS_TX_LIFETIME 0x0002 +#define FAPI_TRANSMISSIONSTATUS_NO_BSS 0x0003 +#define FAPI_TRANSMISSIONSTATUS_EXCESSIVE_DATA_LENGTH 0x0004 +#define FAPI_TRANSMISSIONSTATUS_UNAVAILABLE_KEY_MAPPING 0x0005 +#define FAPI_TRANSMISSIONSTATUS_UNSPECIFIED_FAILURE 0x0006 + +#define FAPI_TXDATATYPE_DATA_WORD 0x0000 +#define FAPI_TXDATATYPE_DATA_RANDOM 0x0001 + +#define FAPI_TXREADFLAGS_NONE 0x0000 +#define FAPI_TXREADFLAGS_FRAME_COUNTING 0x0001 +#define FAPI_TXREADFLAGS_THERMAL_CUTOUT 0x0002 + +#define FAPI_TXSETPARAMSFLAGS_NONE 0x0000 +#define FAPI_TXSETPARAMSFLAGS_ACK 0x0001 +#define FAPI_TXSETPARAMSFLAGS_DUPLICATE_80 0x0002 +#define FAPI_TXSETPARAMSFLAGS_DUPLICATE_40 0x0004 +#define FAPI_TXSETPARAMSFLAGS_DEAFEN_RX 0x0008 +#define FAPI_TXSETPARAMSFLAGS_CS 0x0010 +#define FAPI_TXSETPARAMSFLAGS_SCAN_CHANNEL 0x0020 +#define FAPI_TXSETPARAMSFLAGS_SHORT_PREAMBLE 0x0040 +#define FAPI_TXSETPARAMSFLAGS_DISABLE_SCRAMBLER 0x0080 +#define FAPI_TXSETPARAMSFLAGS_LDPC 0x0100 +#define FAPI_TXSETPARAMSFLAGS_STBC 0x0200 +#define FAPI_TXSETPARAMSFLAGS_DISABLE_SPREADER 0x0400 +#define FAPI_TXSETPARAMSFLAGS_GREENFIELD_PREAMBLE 0x0800 +#define FAPI_TXSETPARAMSFLAGS_RX_LOW_POWER 0x1000 +#define FAPI_TXSETPARAMSFLAGS_IBSS_FRAMES 0x2000 +#define FAPI_TXSETPARAMSFLAGS_BEAMFORMING 0x4000 +#define FAPI_TXSETPARAMSFLAGS_DISABLE_EXTERNAL_LNA 0x8000 + +#define FAPI_TYPEOFAIRPOWER_EIRP 0x00 +#define FAPI_TYPEOFAIRPOWER_TPO 0x01 +#define FAPI_TYPEOFAIRPOWER_RAW 0x02 + +#define FAPI_USAGE_NO_USE 0x0 +#define FAPI_USAGE_USE 0x1 + +#define FAPI_VIFRANGE_VIF_INDEX_MIN 0x0001 +#define FAPI_VIFRANGE_VIF_INDEX_MAX 0x0008 + +#define FAPI_VIFTYPE_UNSYNCHRONISED 0x0000 +#define FAPI_VIFTYPE_STATION 0x0002 +#define FAPI_VIFTYPE_AP 0x0003 +#define FAPI_VIFTYPE_WLANLITE 0x0004 +#define FAPI_VIFTYPE_NAN 0x0005 +#define FAPI_VIFTYPE_DISCOVERY 0x0006 +#define FAPI_VIFTYPE_PRECONNECT 0x0007 +#define FAPI_VIFTYPE_NANDATAPATH 0x0008 +#define FAPI_VIFTYPE_MONITOR 0x0010 +#define FAPI_VIFTYPE_SCAN 0x0020 +#define FAPI_VIFTYPE_OFFCHANNEL 0x0021 +#define FAPI_VIFTYPE_RANGE 0x0022 + +#define FAPI_WIFILOGGINGPARAMS_BEACON_PERIOD 0xf000 +#define FAPI_WIFILOGGINGPARAMS_BLACKOUT_ID 0xf001 +#define FAPI_WIFILOGGINGPARAMS_BLACKOUT_SOURCE 0xf002 +#define FAPI_WIFILOGGINGPARAMS_BLACKOUT_TYPE 0xf003 +#define FAPI_WIFILOGGINGPARAMS_BLOCKACK_PARAMETER_SET 0xf004 +#define FAPI_WIFILOGGINGPARAMS_DIRECTION 0xf005 +#define FAPI_WIFILOGGINGPARAMS_DUTY_CYCLE 0xf006 +#define FAPI_WIFILOGGINGPARAMS_EAPOL_KEY_TYPE 0xf007 +#define FAPI_WIFILOGGINGPARAMS_FRAME 0xf008 +#define FAPI_WIFILOGGINGPARAMS_LOCAL_DURATION 0xf009 +#define FAPI_WIFILOGGINGPARAMS_PERIOD 0xf00a +#define FAPI_WIFILOGGINGPARAMS_REASON 0xf00b +#define FAPI_WIFILOGGINGPARAMS_REPORT_MODE 0xf00c +#define FAPI_WIFILOGGINGPARAMS_RETRY_COUNT 0xf00d +#define FAPI_WIFILOGGINGPARAMS_SCAN_TYPE 0xf00e +#define FAPI_WIFILOGGINGPARAMS_SECONDARY_CHANNEL_OFFSET 0xf00f +#define FAPI_WIFILOGGINGPARAMS_SEQUENCE_NUMBER 0xf010 +#define FAPI_WIFILOGGINGPARAMS_SNIFF_ACL 0xf011 +#define FAPI_WIFILOGGINGPARAMS_TEMPORAL_KEYS_REQUIRED 0xf012 +#define FAPI_WIFILOGGINGPARAMS_VIF_ID 0xf013 + +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P0 0x0000 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P1 0x0001 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P2 0x0002 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P3 0x0003 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P4 0x0004 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P5 0x0005 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P6 0x0006 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_P7 0x0007 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_20MHZ_PRIMARYCHANNELPOSITION_NOT_APPLICABLE 0x0008 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P0 0x0100 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P1 0x0101 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P2 0x0102 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P3 0x0103 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P4 0x0104 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P5 0x0105 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P6 0x0106 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_P7 0x0107 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_40MHZ_PRIMARYCHANNELPOSITION_NOT_APPLICABLE 0x0108 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P0 0x0200 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P1 0x0201 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P2 0x0202 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P3 0x0203 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P4 0x0204 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P5 0x0205 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P6 0x0206 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_P7 0x0207 +#define FAPI_CHANNELBANDWIDTH_BANDWIDTH_80MHZ_PRIMARYCHANNELPOSITION_NOT_APPLICABLE 0x0208 + +#define MA_UNITDATA_REQ 0x1000 +#define MA_SPARE_1_REQ 0x1002 +#define MA_SPARE_2_REQ 0x1003 +#define MA_SPARE_3_REQ 0x1004 +#define MA_SPARE_SIGNAL_1_REQ 0x1005 +#define MA_SPARE_SIGNAL_2_REQ 0x1006 +#define MA_SPARE_SIGNAL_3_REQ 0x1007 +#define MA_UNITDATA_CFM 0x1100 +#define MA_SPARE_1_CFM 0x1102 +#define MA_SPARE_2_CFM 0x1103 +#define MA_SPARE_3_CFM 0x1104 +#define MA_SPARE_SIGNAL_1_CFM 0x1105 +#define MA_SPARE_SIGNAL_2_CFM 0x1106 +#define MA_SPARE_SIGNAL_3_CFM 0x1107 +#define MA_SPARE_1_RES 0x1200 +#define MA_SPARE_2_RES 0x1201 +#define MA_SPARE_3_RES 0x1202 +#define MA_SPARE_SIGNAL_1_RES 0x1203 +#define MA_SPARE_SIGNAL_2_RES 0x1204 +#define MA_SPARE_SIGNAL_3_RES 0x1205 +#define MA_UNITDATA_IND 0x1300 +#define MA_BLOCKACK_IND 0x1301 +#define MA_SPARE_1_IND 0x1302 +#define MA_SPARE_2_IND 0x1303 +#define MA_SPARE_3_IND 0x1304 +#define MA_SPARE_SIGNAL_1_IND 0x1305 +#define MA_SPARE_SIGNAL_2_IND 0x1306 +#define MA_SPARE_SIGNAL_3_IND 0x1307 +#define MLME_GET_REQ 0x2001 +#define MLME_SET_REQ 0x2002 +#define MLME_POWERMGT_REQ 0x2003 +#define MLME_ADD_INFO_ELEMENTS_REQ 0x2004 +#define MLME_ADD_SCAN_REQ 0x2005 +#define MLME_DEL_SCAN_REQ 0x2006 +#define MLME_ADD_VIF_REQ 0x2007 +#define MLME_DEL_VIF_REQ 0x2008 +#define MLME_START_REQ 0x2009 +#define MLME_SET_CHANNEL_REQ 0x200a +#define MLME_CONNECT_REQ 0x200b +#define MLME_REASSOCIATE_REQ 0x200c +#define MLME_ROAM_REQ 0x200d +#define MLME_DISCONNECT_REQ 0x200e +#define MLME_REGISTER_ACTION_FRAME_REQ 0x200f +#define MLME_SEND_FRAME_REQ 0x2010 +#define MLME_RESET_DWELL_TIME_REQ 0x2011 +#define MLME_SET_TRAFFIC_PARAMETERS_REQ 0x2012 +#define MLME_DEL_TRAFFIC_PARAMETERS_REQ 0x2013 +#define MLME_SET_PACKET_FILTER_REQ 0x2014 +#define MLME_SET_IP_ADDRESS_REQ 0x2015 +#define MLME_SET_ACL_REQ 0x2016 +#define MLME_SETKEYS_REQ 0x2018 +#define MLME_GET_KEY_SEQUENCE_REQ 0x201a +#define MLME_SET_PMK_REQ 0x201c +#define MLME_SET_CACHED_CHANNELS_REQ 0x201f +#define MLME_SET_WHITELIST_SSID_REQ 0x2020 +#define MLME_TDLS_ACTION_REQ 0x2021 +#define MLME_CHANNEL_SWITCH_REQ 0x2022 +#define MLME_MONITOR_RSSI_REQ 0x2023 +#define MLME_START_LINK_STATISTICS_REQ 0x2024 +#define MLME_STOP_LINK_STATISTICS_REQ 0x2025 +#define MLME_SET_PNO_LIST_REQ 0x2027 +#define MLME_HOST_STATE_REQ 0x2028 +#define MLME_ADD_RANGE_REQ 0x2029 +#define MLME_DEL_RANGE_REQ 0x202a +#define MLME_SET_NOA_REQ 0x202b +#define MLME_SET_CTWINDOW_REQ 0x202c +#define MLME_NAN_START_REQ 0x202d +#define MLME_NAN_CONFIG_REQ 0x202e +#define MLME_NAN_PUBLISH_REQ 0x202f +#define MLME_NAN_SUBSCRIBE_REQ 0x2030 +#define MLME_NAN_FOLLOWUP_REQ 0x2031 +#define MLME_UNSET_CHANNEL_REQ 0x2032 +#define MLME_SET_COUNTRY_REQ 0x2033 +#define MLME_FORWARD_BEACON_REQ 0x2034 +#define MLME_NDP_SETUP_REQ 0x2035 +#define MLME_NDP_SETUP_RESPONSE_REQ 0x2036 +#define MLME_NDP_TERMINATE_REQ 0x2037 +#define MLME_NAN_ADD_RANGE_REQ 0x2038 +#define MLME_NAN_DEL_RANGE_REQ 0x2039 +#define MLME_SPARE_4_REQ 0x203a +#define MLME_SPARE_5_REQ 0x203b +#define MLME_SPARE_6_REQ 0x203c +#define MLME_INSTALL_APF_REQ 0x203d +#define MLME_READ_APF_REQ 0x203e +#define MLME_SET_NSS_REQ 0x203f +#define MLME_ARP_DETECT_REQ 0x2040 +#define MLME_SPARE_SIGNAL_1_REQ 0x2041 +#define MLME_SPARE_SIGNAL_2_REQ 0x2042 +#define MLME_SPARE_SIGNAL_3_REQ 0x2043 +#define MLME_GET_CFM 0x2101 +#define MLME_SET_CFM 0x2102 +#define MLME_POWERMGT_CFM 0x2103 +#define MLME_ADD_INFO_ELEMENTS_CFM 0x2104 +#define MLME_ADD_SCAN_CFM 0x2105 +#define MLME_DEL_SCAN_CFM 0x2106 +#define MLME_ADD_VIF_CFM 0x2107 +#define MLME_DEL_VIF_CFM 0x2108 +#define MLME_START_CFM 0x2109 +#define MLME_SET_CHANNEL_CFM 0x210a +#define MLME_CONNECT_CFM 0x210b +#define MLME_REASSOCIATE_CFM 0x210c +#define MLME_ROAM_CFM 0x210d +#define MLME_DISCONNECT_CFM 0x210e +#define MLME_REGISTER_ACTION_FRAME_CFM 0x210f +#define MLME_SEND_FRAME_CFM 0x2110 +#define MLME_RESET_DWELL_TIME_CFM 0x2111 +#define MLME_SET_TRAFFIC_PARAMETERS_CFM 0x2112 +#define MLME_DEL_TRAFFIC_PARAMETERS_CFM 0x2113 +#define MLME_SET_PACKET_FILTER_CFM 0x2114 +#define MLME_SET_IP_ADDRESS_CFM 0x2115 +#define MLME_SET_ACL_CFM 0x2116 +#define MLME_SETKEYS_CFM 0x2118 +#define MLME_GET_KEY_SEQUENCE_CFM 0x211a +#define MLME_SET_PMK_CFM 0x211c +#define MLME_SET_CACHED_CHANNELS_CFM 0x211f +#define MLME_SET_WHITELIST_SSID_CFM 0x2120 +#define MLME_TDLS_ACTION_CFM 0x2121 +#define MLME_CHANNEL_SWITCH_CFM 0x2122 +#define MLME_MONITOR_RSSI_CFM 0x2123 +#define MLME_START_LINK_STATISTICS_CFM 0x2124 +#define MLME_STOP_LINK_STATISTICS_CFM 0x2125 +#define MLME_SET_PNO_LIST_CFM 0x2127 +#define MLME_HOST_STATE_CFM 0x2128 +#define MLME_ADD_RANGE_CFM 0x2129 +#define MLME_DEL_RANGE_CFM 0x212a +#define MLME_SET_NOA_CFM 0x212b +#define MLME_SET_CTWINDOW_CFM 0x212c +#define MLME_NAN_START_CFM 0x212d +#define MLME_NAN_CONFIG_CFM 0x212e +#define MLME_NAN_PUBLISH_CFM 0x212f +#define MLME_NAN_SUBSCRIBE_CFM 0x2130 +#define MLME_NAN_FOLLOWUP_CFM 0x2131 +#define MLME_UNSET_CHANNEL_CFM 0x2132 +#define MLME_SET_COUNTRY_CFM 0x2133 +#define MLME_FORWARD_BEACON_CFM 0x2134 +#define MLME_NDP_SETUP_CFM 0x2135 +#define MLME_NDP_SETUP_RESPONSE_CFM 0x2136 +#define MLME_NDP_TERMINATE_CFM 0x2137 +#define MLME_NAN_ADD_RANGE_CFM 0x2138 +#define MLME_NAN_DEL_RANGE_CFM 0x2139 +#define MLME_SPARE_4_CFM 0x213a +#define MLME_SPARE_5_CFM 0x213b +#define MLME_SPARE_6_CFM 0x213c +#define MLME_INSTALL_APF_CFM 0x213d +#define MLME_READ_APF_CFM 0x213e +#define MLME_SET_NSS_CFM 0x213f +#define MLME_ARP_DETECT_CFM 0x2140 +#define MLME_SPARE_SIGNAL_1_CFM 0x2141 +#define MLME_SPARE_SIGNAL_2_CFM 0x2142 +#define MLME_SPARE_SIGNAL_3_CFM 0x2143 +#define MLME_CONNECT_RES 0x2200 +#define MLME_CONNECTED_RES 0x2201 +#define MLME_REASSOCIATE_RES 0x2202 +#define MLME_ROAMED_RES 0x2203 +#define MLME_TDLS_PEER_RES 0x2204 +#define MLME_SYNCHRONISED_RES 0x2205 +#define MLME_SPARE_2_RES 0x2206 +#define MLME_SPARE_3_RES 0x2207 +#define MLME_SPARE_4_RES 0x2208 +#define MLME_SPARE_SIGNAL_1_RES 0x2209 +#define MLME_SPARE_SIGNAL_2_RES 0x220a +#define MLME_SPARE_SIGNAL_3_RES 0x220b +#define MLME_SCAN_IND 0x2300 +#define MLME_SCAN_DONE_IND 0x2301 +#define MLME_LISTEN_END_IND 0x2302 +#define MLME_CONNECT_IND 0x2303 +#define MLME_CONNECTED_IND 0x2304 +#define MLME_REASSOCIATE_IND 0x2305 +#define MLME_ROAM_IND 0x2306 +#define MLME_ROAMED_IND 0x2307 +#define MLME_DISCONNECT_IND 0x2308 +#define MLME_DISCONNECTED_IND 0x2309 +#define MLME_PROCEDURE_STARTED_IND 0x230a +#define MLME_MIC_FAILURE_IND 0x230b +#define MLME_FRAME_TRANSMISSION_IND 0x230c +#define MLME_RECEIVED_FRAME_IND 0x230d +#define MLME_TDLS_PEER_IND 0x230f +#define MLME_RSSI_REPORT_IND 0x2312 +#define MLME_AC_PRIORITY_UPDATE_IND 0x2313 +#define MLME_RANGE_IND 0x2314 +#define MLME_RANGE_DONE_IND 0x2315 +#define MLME_EVENT_LOG_IND 0x2316 +#define MLME_NAN_EVENT_IND 0x2317 +#define MLME_NAN_SERVICE_IND 0x2318 +#define MLME_NAN_FOLLOWUP_IND 0x2319 +#define MLME_CHANNEL_SWITCHED_IND 0x231a +#define MLME_SYNCHRONISED_IND 0x231b +#define MLME_BEACON_REPORTING_EVENT_IND 0x231c +#define MLME_SPARE_3_IND 0x231d +#define MLME_SPARE_4_IND 0x231e +#define MLME_NDP_SETUP_IND 0x231f +#define MLME_NDP_REQUESTED_IND 0x2320 +#define MLME_NDP_SETUP_RESPONSE_IND 0x2321 +#define MLME_NDP_TERMINATED_IND 0x2322 +#define MLME_NAN_ADD_RANGE_IND 0x2323 +#define MLME_SPARE_5_IND 0x2324 +#define MLME_SPARE_SIGNAL_1_IND 0x2325 +#define MLME_SPARE_SIGNAL_2_IND 0x2326 +#define MLME_SPARE_SIGNAL_3_IND 0x2327 +#define DEBUG_SPARE_1_REQ 0x8000 +#define DEBUG_SPARE_2_REQ 0x8001 +#define DEBUG_SPARE_3_REQ 0x8002 +#define DEBUG_SPARE_SIGNAL_1_REQ 0x8003 +#define DEBUG_SPARE_SIGNAL_2_REQ 0x8004 +#define DEBUG_SPARE_SIGNAL_3_REQ 0x8005 +#define DEBUG_SPARE_1_CFM 0x8100 +#define DEBUG_SPARE_2_CFM 0x8101 +#define DEBUG_SPARE_3_CFM 0x8102 +#define DEBUG_SPARE_SIGNAL_1_CFM 0x8103 +#define DEBUG_SPARE_SIGNAL_2_CFM 0x8104 +#define DEBUG_SPARE_SIGNAL_3_CFM 0x8105 +#define DEBUG_SPARE_1_RES 0x8200 +#define DEBUG_SPARE_2_RES 0x8201 +#define DEBUG_SPARE_3_RES 0x8202 +#define DEBUG_SPARE_SIGNAL_1_RES 0x8203 +#define DEBUG_SPARE_SIGNAL_2_RES 0x8204 +#define DEBUG_SPARE_SIGNAL_3_RES 0x8205 +#define DEBUG_WORD12IND 0x8301 +#define DEBUG_FAULT_IND 0x8302 +#define DEBUG_WORDS_IND 0x8303 +#define DEBUG_SPARE_2_IND 0x8304 +#define DEBUG_SPARE_3_IND 0x8305 +#define DEBUG_SPARE_4_IND 0x8306 +#define DEBUG_SPARE_SIGNAL_1_IND 0x8307 +#define DEBUG_SPARE_SIGNAL_2_IND 0x8308 +#define DEBUG_SPARE_SIGNAL_3_IND 0x8309 +#define TEST_BLOCK_REQUESTS_REQ 0x9000 +#define TEST_PANIC_REQ 0x9001 +#define TEST_SUSPEND_REQ 0x9002 +#define TEST_RESUME_REQ 0x9003 +#define RADIO_LOGGING_REQ 0x9004 +#define WLANLITE_CW_START_REQ 0x9005 +#define WLANLITE_CW_STOP_REQ 0x9006 +#define WLANLITE_TX_SET_PARAMS_REQ 0x9007 +#define WLANLITE_TX_START_REQ 0x9008 +#define WLANLITE_TX_READ_REQ 0x9009 +#define WLANLITE_TX_STOP_REQ 0x900a +#define WLANLITE_RX_START_REQ 0x900b +#define WLANLITE_RX_READ_REQ 0x900c +#define WLANLITE_RX_STOP_REQ 0x900d +#define WLANLITE_STATUS_REQ 0x900e +#define TEST_PMALLOC_REQ 0x900f +#define TEST_CONFIGURE_MONITOR_MODE_REQ 0x9010 +#define TEST_CHECK_FW_ALIVE_REQ 0x9012 +#define DEBUG_GENERIC_REQ 0x9013 +#define DEBUG_PKT_SINK_START_REQ 0x9014 +#define DEBUG_PKT_SINK_STOP_REQ 0x9015 +#define DEBUG_PKT_SINK_REPORT_REQ 0x9016 +#define DEBUG_PKT_GEN_START_REQ 0x9017 +#define DEBUG_PKT_GEN_STOP_REQ 0x9018 +#define DEBUG_PKT_GEN_REPORT_REQ 0x9019 +#define WLANLITE_RADIO_SELECT_REQ 0x901a +#define TEST_HIP_TESTER_START_REQ 0x901b +#define TEST_HIP_TESTER_STOP_REQ 0x901c +#define TEST_HIP_TESTER_SET_PARAMS_REQ 0x901d +#define TEST_HIP_TESTER_REPORT_REQ 0x901e +#define TEST_BIST_GET_TX_GAIN_REQ 0x901f +#define TEST_SPARE_1_REQ 0x9020 +#define TEST_SPARE_2_REQ 0x9021 +#define TEST_SPARE_3_REQ 0x9022 +#define TEST_SPARE_SIGNAL_1_REQ 0x9023 +#define TEST_SPARE_SIGNAL_2_REQ 0x9024 +#define TEST_SPARE_SIGNAL_3_REQ 0x9025 +#define RADIO_LOGGING_CFM 0x9100 +#define WLANLITE_CW_START_CFM 0x9101 +#define WLANLITE_TX_SET_PARAMS_CFM 0x9102 +#define WLANLITE_CW_STOP_CFM 0x9103 +#define WLANLITE_TX_START_CFM 0x9104 +#define WLANLITE_TX_READ_CFM 0x9105 +#define WLANLITE_TX_STOP_CFM 0x9106 +#define WLANLITE_RX_START_CFM 0x9107 +#define WLANLITE_RX_READ_CFM 0x9108 +#define WLANLITE_RX_STOP_CFM 0x9109 +#define WLANLITE_STATUS_CFM 0x910a +#define TEST_PMALLOC_CFM 0x910b +#define TEST_CONFIGURE_MONITOR_MODE_CFM 0x910c +#define TEST_CHECK_FW_ALIVE_CFM 0x910e +#define TEST_SUSPEND_CFM 0x910f +#define TEST_RESUME_CFM 0x9110 +#define DEBUG_GENERIC_CFM 0x9111 +#define WLANLITE_RADIO_SELECT_CFM 0x9112 +#define TEST_HIP_TESTER_START_CFM 0x9113 +#define TEST_HIP_TESTER_STOP_CFM 0x9114 +#define TEST_HIP_TESTER_SET_PARAMS_CFM 0x9115 +#define TEST_BIST_GET_TX_GAIN_CFM 0x9116 +#define TEST_SPARE_1_CFM 0x9117 +#define TEST_SPARE_2_CFM 0x9118 +#define TEST_SPARE_3_CFM 0x9119 +#define TEST_SPARE_SIGNAL_1_CFM 0x911a +#define TEST_SPARE_SIGNAL_2_CFM 0x911b +#define TEST_SPARE_SIGNAL_3_CFM 0x911c +#define TEST_SPARE_1_RES 0x9200 +#define TEST_SPARE_2_RES 0x9201 +#define TEST_SPARE_3_RES 0x9202 +#define TEST_SPARE_SIGNAL_1_RES 0x9203 +#define TEST_SPARE_SIGNAL_2_RES 0x9204 +#define TEST_SPARE_SIGNAL_3_RES 0x9205 +#define RADIO_LOGGING_IND 0x9300 +#define DEBUG_GENERIC_IND 0x9301 +#define DEBUG_PKT_SINK_REPORT_IND 0x9302 +#define DEBUG_PKT_GEN_REPORT_IND 0x9303 +#define TEST_HIP_TESTER_REPORT_IND 0x9304 +#define TEST_SPARE_1_IND 0x9305 +#define TEST_SPARE_2_IND 0x9306 +#define TEST_SPARE_3_IND 0x9307 +#define TEST_SPARE_SIGNAL_1_IND 0x9308 +#define TEST_SPARE_SIGNAL_2_IND 0x9309 +#define TEST_SPARE_SIGNAL_3_IND 0x930a + +struct fapi_signal_header { + __le16 id; + __le16 receiver_pid; + __le16 sender_pid; + __le32 fw_reference; +} __packed; + +struct fapi_vif_signal_header { + __le16 id; + __le16 receiver_pid; + __le16 sender_pid; + __le32 fw_reference; + __le16 vif; +} __packed; + +struct fapi_signal { + __le16 id; + __le16 receiver_pid; + __le16 sender_pid; + __le32 fw_reference; + + union { + struct { + __le16 vif; + __le16 host_tag; + __le16 priority; + __le16 peer_index; + __le16 data_unit_descriptor; + __le16 bulk_data_descriptor; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_unitdata_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_3_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_3_req; + struct { + __le16 vif; + __le16 transmission_status; + __le16 host_tag; + __le16 sequence_number; + __le16 peer_index; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_unitdata_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_3_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_3_cfm; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_3_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_3_res; + struct { + __le16 vif; + __le16 data_unit_descriptor; + __le16 sequence_number; + __le16 priority; + __le16 peer_index; + __le16 proprieraty_information_length; + __le16 bulk_data_descriptor; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_unitdata_ind; + struct { + __le16 vif; + u8 peer_qsta_address[ETH_ALEN]; + __le16 sequence_number; + __le16 reason_code; + __le16 blockack_parameter_set; + __le16 direction; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_blockack_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_1_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_1_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed ma_spare_signal_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_get_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_req; + struct { + __le16 vif; + __le16 power_management_mode; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_powermgt_req; + struct { + __le16 vif; + __le16 purpose; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_info_elements_req; + struct { + __le16 vif; + __le16 scan_id; + __le16 scan_type; + u8 device_address[ETH_ALEN]; + __le16 report_mode_bitmap; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_scan_req; + struct { + __le16 vif; + __le16 scan_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_scan_req; + struct { + __le16 vif; + u8 interface_address[ETH_ALEN]; + __le16 virtual_interface_type; + u8 device_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_vif_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_vif_req; + struct { + __le16 vif; + u8 bssid[ETH_ALEN]; + __le16 beacon_period; + __le16 dtim_period; + __le16 capability_information; + __le16 authentication_type; + __le16 hidden_ssid; + __le16 channel_frequency; + __le16 channel_information; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_start_req; + struct { + __le16 vif; + __le16 availability_duration; + __le16 availability_interval; + __le16 count; + __le16 channel_frequency; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_channel_req; + struct { + __le16 vif; + u8 bssid[ETH_ALEN]; + __le16 authentication_type; + __le16 channel_frequency; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_connect_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_reassociate_req; + struct { + __le16 vif; + u8 bssid[ETH_ALEN]; + __le16 channel_frequency; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_roam_req; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le16 reason_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_disconnect_req; + struct { + __le16 vif; + __le32 action_frame_category_bitmap_active; + __le32 action_frame_category_bitmap_suspended; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_register_action_frame_req; + struct { + __le16 vif; + __le16 host_tag; + __le16 data_unit_descriptor; + __le16 message_type; + __le16 channel_frequency; + __le32 dwell_time; + __le32 period; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_send_frame_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_reset_dwell_time_req; + struct { + __le16 vif; + __le16 user_priority; + __le16 medium_time; + __le16 minimum_data_rate; + u8 peer_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_traffic_parameters_req; + struct { + __le16 vif; + __le16 user_priority; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_traffic_parameters_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_packet_filter_req; + struct { + __le16 vif; + __le16 ip_version; + u8 multicast_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_ip_address_req; + struct { + __le16 vif; + __le16 entries; + __le16 acl_policy; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_acl_req; + struct { + __le16 vif; + __le16 length; + __le16 key_id; + __le16 key_type; + u8 address[ETH_ALEN]; + __le16 sequence_number[8]; + __le32 cipher_suite_selector; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_setkeys_req; + struct { + __le16 vif; + __le16 key_id; + __le16 key_type; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_get_key_sequence_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_pmk_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_cached_channels_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_whitelist_ssid_req; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le16 tdls_action; + __le16 channel_frequency; + __le16 channel_information; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_tdls_action_req; + struct { + __le16 vif; + __le16 channel_frequency; + __le16 channel_information; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_channel_switch_req; + struct { + __le16 vif; + __le16 low_rssi_threshold; + __le16 high_rssi_threshold; + __le16 rssi_monitoring_enabled; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_monitor_rssi_req; + struct { + __le16 vif; + __le16 mpdu_size_threshold; + __le16 aggressive_statistics_gathering_enabled; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_start_link_statistics_req; + struct { + __le16 vif; + __le16 statistics_stop_bitmap; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_stop_link_statistics_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_pno_list_req; + struct { + __le16 vif; + __le16 host_state; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_host_state_req; + struct { + __le16 vif; + __le16 rtt_id; + u8 device_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_range_req; + struct { + __le16 vif; + __le16 rtt_id; + __le16 entries; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_range_req; + struct { + __le16 vif; + __le16 request_id; + __le16 noa_count; + __le32 interval; + __le32 duration; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_noa_req; + struct { + __le16 vif; + __le16 ctwindow; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_ctwindow_req; + struct { + __le16 vif; + __le16 operatein5gband; + __le16 hopcountmax; + __le16 nan_operation_control_flags; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_start_req; + struct { + __le16 vif; + __le16 nan_operation_control_flags; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_config_req; + struct { + __le16 vif; + __le16 publish_id; + __le16 nan_sdf_flags; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_publish_req; + struct { + __le16 vif; + __le16 subscribe_id; + __le16 nan_sdf_flags; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_subscribe_req; + struct { + __le16 vif; + __le16 publish_subscribe_id; + __le16 peer_id; + __le16 nan_sdf_flags; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_followup_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_unset_channel_req; + struct { + __le16 vif; + __le16 country_code; + __le16 dfs_regulatory_domain; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_country_req; + struct { + __le16 vif; + __le16 wips_action; + __le16 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_forward_beacon_req; + struct { + __le16 vif; + __le16 instance_id; + __le16 peer_id; + u8 local_ndp_interface_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_setup_req; + struct { + __le16 vif; + __le16 instance_id; + __le16 reason_code; + u8 local_ndp_interface_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_setup_response_req; + struct { + __le16 vif; + __le16 instance_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_terminate_req; + struct { + __le16 vif; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_add_range_req; + struct { + __le16 vif; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_del_range_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_4_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_5_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_6_req; + struct { + __le16 vif; + __le16 filter_mode; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_install_apf_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_read_apf_req; + struct { + __le16 vif; + __le16 rx_nss; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_nss_req; + struct { + __le16 vif; + __le16 arp_detect_action; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_arp_detect_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_3_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_get_cfm; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_powermgt_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_info_elements_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 scan_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_scan_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 scan_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_scan_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_vif_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_vif_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_start_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_channel_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_connect_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_reassociate_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_roam_cfm; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_disconnect_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_register_action_frame_cfm; + struct { + __le16 vif; + __le16 host_tag; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_send_frame_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_reset_dwell_time_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_traffic_parameters_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_traffic_parameters_cfm; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_packet_filter_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_ip_address_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_acl_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_setkeys_cfm; + struct { + __le16 vif; + __le16 sequence_number[8]; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_get_key_sequence_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_pmk_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_cached_channels_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_whitelist_ssid_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_tdls_action_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_channel_switch_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_monitor_rssi_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_start_link_statistics_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_stop_link_statistics_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_pno_list_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_host_state_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_add_range_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_del_range_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_noa_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_ctwindow_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_start_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_config_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_publish_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_subscribe_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_followup_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_unset_channel_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_country_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_forward_beacon_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_setup_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_setup_response_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_terminate_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_add_range_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_del_range_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_4_cfm; + struct { + __le16 result_code; + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_5_cfm; + struct { + __le16 result_code; + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_6_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_install_apf_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_read_apf_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_set_nss_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_arp_detect_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_3_cfm; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_connect_res; + struct { + __le16 vif; + __le16 peer_index; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_connected_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_reassociate_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_roamed_res; + struct { + __le16 vif; + __le16 peer_index; + __le16 tdls_event; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_tdls_peer_res; + struct { + __le16 vif; + __le16 result_code; + u8 bssid[ETH_ALEN]; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_synchronised_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_3_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_4_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_3_res; + struct { + __le16 vif; + __le16 channel_frequency; + __le16 rssi; + __le16 scan_id; + __le16 hotlisted_ap; + __le16 preferrednetwork_ap; + __le16 anqp_elements_length; + __le16 network_block_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_scan_ind; + struct { + __le16 vif; + __le16 scan_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_scan_done_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_listen_end_ind; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_connect_ind; + struct { + __le16 vif; + __le16 peer_index; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_connected_ind; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_reassociate_ind; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_roam_ind; + struct { + __le16 vif; + __le16 temporal_keys_required; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_roamed_ind; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_disconnect_ind; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le16 reason_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_disconnected_ind; + struct { + __le16 vif; + __le16 procedure_type; + __le16 peer_index; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_procedure_started_ind; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le16 key_type; + __le16 key_id; + __le16 key_sequence_number[8]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_mic_failure_ind; + struct { + __le16 vif; + __le16 host_tag; + __le16 transmission_status; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_frame_transmission_ind; + struct { + __le16 vif; + __le16 data_unit_descriptor; + __le16 channel_frequency; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_received_frame_ind; + struct { + __le16 vif; + u8 peer_sta_address[ETH_ALEN]; + __le16 peer_index; + __le16 tdls_event; + __le16 reason_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_tdls_peer_ind; + struct { + __le16 vif; + u8 bssid[ETH_ALEN]; + __le16 rssi; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_rssi_report_ind; + struct { + __le16 vif; + __le16 ac_priority; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ac_priority_update_ind; + struct { + __le16 vif; + __le16 rtt_id; + __le16 entries; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_range_ind; + struct { + __le16 vif; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_range_done_ind; + struct { + __le16 vif; + __le16 event; + u8 timestamp[8]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_event_log_ind; + struct { + __le16 vif; + __le16 event; + __le16 identifier; + u8 address_or_identifier[ETH_ALEN]; + __le16 reason_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_event_ind; + struct { + __le16 vif; + __le16 publish_subscribe_id; + __le16 peer_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_service_ind; + struct { + __le16 vif; + __le16 publish_subscribe_id; + __le16 peer_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_followup_ind; + struct { + __le16 vif; + __le16 channel_frequency; + __le16 channel_information; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_channel_switched_ind; + struct { + __le16 vif; + __le16 rssi; + __le16 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_synchronised_ind; + struct { + __le16 vif; + __le16 abort_reason; + __le16 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_beacon_reporting_event_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_4_ind; + struct { + __le16 vif; + __le16 instance_id; + u8 peer_ndp_interface_address[ETH_ALEN]; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_setup_ind; + struct { + __le16 vif; + __le16 instance_id; + __le16 publish_subscribe_id; + u8 peer_ndp_interface_address[ETH_ALEN]; + __le16 security_required; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_requested_ind; + struct { + __le16 vif; + __le16 instance_id; + u8 peer_ndp_interface_address[ETH_ALEN]; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_setup_response_ind; + struct { + __le16 vif; + __le16 instance_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_ndp_terminated_ind; + struct { + __le16 vif; + __le16 rtt_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_nan_add_range_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_5_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_1_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed mlme_spare_signal_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_3_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_3_req; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_3_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_3_cfm; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_3_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_3_res; + struct { + __le16 vif; + __le16 module_id; + __le16 module_sub_id; + __le32 timestamp; + __le16 debug_words[12]; + __le16 sequence_number; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_word12_ind; + struct { + __le16 vif; + __le16 faultid; + __le16 count; + __le32 timestamp; + __le32 arg; + __le16 cpu; + __le16 sequence_number; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_fault_ind; + struct { + __le16 vif; + __le16 module_id; + __le16 module_sub_id; + __le32 timestamp; + __le16 sequence_number; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_words_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_4_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_1_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_spare_signal_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_block_requests_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_panic_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_suspend_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_resume_req; + struct { + __le32 logging_source; + __le32 logging_frequency; + __le32 capture_stream; + __le32 trigger_mode; + __le32 delay; + __le32 buffer_size; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed radio_logging_req; + struct { + __le16 freq; + __le16 power; + __le16 flags; + __le16 type; + __le16 amplitude; + __le32 freq1; + __le32 freq2; + __le16 phase; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_cw_start_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_cw_stop_req; + struct { + __le16 freq; + __le16 rate; + __le16 channel_information; + __le16 power; + __le16 length; + __le32 interval; + __le16 flags; + __le16 aid; + __le16 distance_to_band_edge_half_mhz; + __le16 regulatory_domain; + __le16 spare_0; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_set_params_req; + struct { + __le32 num_frames_to_send; + __le16 data_type; + __le16 data_param; + u8 dest_addr[ETH_ALEN]; + u8 src_addr[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 num_mpdus_per_ampdu; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_start_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_read_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_stop_req; + struct { + __le16 freq; + __le16 channel_information; + __le16 flags; + u8 mac_addr[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 aid; + __le16 num_mpdus_per_ampdu; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_rx_start_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_rx_read_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_rx_stop_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_status_req; + struct { + __le16 alloc_area; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_pmalloc_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_configure_monitor_mode_req; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_check_fw_alive_req; + struct { + __le16 vif; + __le16 debug_words[8]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_generic_req; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 interval; + __le16 packets_per_interval; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_sink_start_req; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_sink_stop_req; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 report_interval; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_sink_report_req; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 interval; + __le16 size; + __le16 packets_per_interval; + __le32 ipv4destination_address; + __le16 packets_per_interrupt; + __le16 use_streaming; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_gen_start_req; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_gen_stop_req; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 report_interval; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_gen_report_req; + struct { + __le16 radio_bitmap; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_radio_select_req; + struct { + __le16 vif; + __le16 mode; + __le16 end_point; + __le16 protocol; + __le32 interval; + __le16 packets_per_interval; + __le16 packets_size; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_start_req; + struct { + __le16 vif; + __le16 stream_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_stop_req; + struct { + __le16 vif; + __le16 stream_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_set_params_req; + struct { + __le16 vif; + __le16 stream_id; + __le32 report_interval; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_report_req; + struct { + __le16 freq; + __le16 tx_gain; + __le16 rx_gain; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_bist_get_tx_gain_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_3_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_1_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_2_req; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_3_req; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed radio_logging_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_cw_start_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_set_params_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_cw_stop_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_start_cfm; + struct { + __le16 result_code; + __le16 flags; + __le32 ctr_frames_left_to_send; + __le32 transmission_back_off; + __le16 wanted_power_target; + __le16 final_power_target; + __le16 oob_constraint; + __le16 last_trim_pa_temperature; + __le16 current_pa_temperature; + __le16 last_trim_ambient_temperature; + __le16 current_ambient_temperature; + __le16 temp_power_adjust; + __le32 ctr_frames_success; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_read_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_tx_stop_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_rx_start_cfm; + struct { + __le16 result_code; + __le32 freq_offset_cur; + __le32 freq_offset_avg; + __le16 rssi_cur; + __le16 rssi_avg; + __le16 rssi_min; + __le16 rssi_max; + __le16 snr_cur; + __le16 snr_avg; + __le16 snr_min; + __le16 snr_max; + __le32 interval; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_rx_read_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_rx_stop_cfm; + struct { + __le16 result_code; + __le16 device_state; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_status_cfm; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_pmalloc_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_configure_monitor_mode_cfm; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_check_fw_alive_cfm; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_suspend_cfm; + struct { + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_resume_cfm; + struct { + __le16 vif; + __le16 debug_words[8]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_generic_cfm; + struct { + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed wlanlite_radio_select_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 stream_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_start_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 stream_id; + __le32 duration; + __le32 count; + __le32 failed_count; + __le32 octets; + __le32 mbps; + __le16 idle_ratio; + __le16 int_latency; + __le32 tester_reserved1; + __le32 tester_reserved2; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_stop_cfm; + struct { + __le16 vif; + __le16 result_code; + __le16 stream_id; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_set_params_cfm; + struct { + __le16 result_code; + __le32 gain; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_bist_get_tx_gain_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_3_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_1_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_2_cfm; + struct { + __le16 vif; + __le16 result_code; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_3_cfm; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_3_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_1_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_2_res; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_3_res; + struct { + __le32 sequence_number; + __le32 more_data; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed radio_logging_ind; + struct { + __le16 vif; + __le16 debug_words[8]; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_generic_ind; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 duration; + __le32 received_packets; + __le32 received_octets; + __le32 kbps; + __le16 idle_ratio; + __le16 int_latency; + __le16 free_kbytes; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_sink_report_ind; + struct { + __le16 vif; + __le16 end_point; + __le16 direction; + __le32 duration; + __le32 received_packets; + __le32 failed_count; + __le32 received_octets; + __le32 kbps; + __le16 idle_ratio; + __le16 int_latency; + __le16 free_kbytes; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed debug_pkt_gen_report_ind; + struct { + __le16 vif; + __le16 stream_id; + __le32 duration; + __le32 count; + __le32 failed_count; + __le32 octets; + __le32 mbps; + __le16 idle_ratio; + __le16 int_latency; + __le32 tester_reserved1; + __le32 tester_reserved2; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_hip_tester_report_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_1_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_3_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_1_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_2_ind; + struct { + __le16 vif; + __le32 spare_1; + __le32 spare_2; + __le32 spare_3; + u8 dr[0]; + } __packed test_spare_signal_3_ind; + } u; +} __packed; + +static inline struct sk_buff *fapi_alloc_f(size_t sig_size, size_t data_size, u16 id, u16 vif, const char *file, int line) +{ + struct sk_buff *skb = slsi_alloc_skb_f(sig_size + data_size, GFP_ATOMIC, file, line); + struct fapi_vif_signal_header *header; + + WARN_ON(sig_size < sizeof(struct fapi_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 fapi_vif_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; +} + +#define fapi_sig_size(mp_name) ((u16)offsetof(struct fapi_signal, u.mp_name.dr)) +#define fapi_alloc(mp_name, mp_id, mp_vif, mp_datalen) fapi_alloc_f(fapi_sig_size(mp_name), mp_datalen, mp_id, mp_vif, __FILE__, __LINE__) +#define fapi_get_buff(mp_skb, mp_name) (((struct fapi_signal *)(mp_skb)->data)->mp_name) +#define fapi_get_u16(mp_skb, mp_name) le16_to_cpu(((struct fapi_signal *)(mp_skb)->data)->mp_name) +#define fapi_get_u32(mp_skb, mp_name) le32_to_cpu(((struct fapi_signal *)(mp_skb)->data)->mp_name) +/*todo*/ +#define fapi_get_u64(mp_skb, mp_name) le64_to_cpu(((struct fapi_signal *)(mp_skb)->data)->mp_name) +#define fapi_set_u16(mp_skb, mp_name, mp_value) (((struct fapi_signal *)(mp_skb)->data)->mp_name = cpu_to_le16(mp_value)) +#define fapi_set_u32(mp_skb, mp_name, mp_value) (((struct fapi_signal *)(mp_skb)->data)->mp_name = cpu_to_le32(mp_value)) +#define fapi_get_s16(mp_skb, mp_name) ((s16)le16_to_cpu(((struct fapi_signal *)(mp_skb)->data)->mp_name)) +#define fapi_get_s32(mp_skb, mp_name) ((s32)le32_to_cpu(((struct fapi_signal *)(mp_skb)->data)->mp_name)) +#define fapi_set_s16(mp_skb, mp_name, mp_value) (((struct fapi_signal *)(mp_skb)->data)->mp_name = cpu_to_le16((u16)mp_value)) +#define fapi_set_s32(mp_skb, mp_name, mp_value) (((struct fapi_signal *)(mp_skb)->data)->mp_name = cpu_to_le32((u32)mp_value)) +#define fapi_set_memcpy(mp_skb, mp_name, mp_value) memcpy(((struct fapi_signal *)(mp_skb)->data)->mp_name, mp_value, sizeof(((struct fapi_signal *)(mp_skb)->data)->mp_name)) +#define fapi_set_memset(mp_skb, mp_name, mp_value) memset(((struct fapi_signal *)(mp_skb)->data)->mp_name, mp_value, sizeof(((struct fapi_signal *)(mp_skb)->data)->mp_name)) + +/* Helper to get and set high/low 16 bits from u32 signals */ +#define fapi_get_high16_u32(mp_skb, mp_name) ((fapi_get_u32((mp_skb), mp_name) & 0xffff0000) >> 16) +#define fapi_set_high16_u32(mp_skb, mp_name, mp_value) fapi_set_u32((mp_skb), mp_name, (fapi_get_u32((mp_skb), mp_name) & 0xffff) | ((mp_value) << 16)) +#define fapi_get_low16_u32(mp_skb, mp_name) (fapi_get_u32((mp_skb), mp_name) & 0xffff) +#define fapi_set_low16_u32(mp_skb, mp_name, mp_value) fapi_set_u32((mp_skb), mp_name, (fapi_get_u32((mp_skb), mp_name) & 0xffff0000) | (mp_value)) + +/* Helper to get signal and data */ +#define fapi_get_sigid(mp_skb) le16_to_cpu(((struct fapi_signal *)(mp_skb)->data)->id) +#define fapi_get_siglen(mp_skb) (slsi_skb_cb_get(mp_skb)->sig_length) +#define fapi_get_datalen(mp_skb) (slsi_skb_cb_get(mp_skb)->data_length - slsi_skb_cb_get(mp_skb)->sig_length) +#define fapi_get_data(mp_skb) (mp_skb->data + fapi_get_siglen(mp_skb)) +#define fapi_get_vif(mp_skb) le16_to_cpu(((struct fapi_vif_signal_header *)(mp_skb)->data)->vif) + +/* Helper to get the struct ieee80211_mgmt from the data */ +#define fapi_get_mgmt(mp_skb) ((struct ieee80211_mgmt *)fapi_get_data(mp_skb)) +#define fapi_get_mgmtlen(mp_skb) fapi_get_datalen(mp_skb) + +static inline u8 *fapi_append_data(struct sk_buff *skb, const u8 *data, size_t data_len) +{ + u8 *p; + + if (WARN_ON(skb_tailroom(skb) < data_len)) + return NULL; + + p = skb_put(skb, data_len); + slsi_skb_cb_get(skb)->data_length += data_len; + if (data) + memcpy(p, data, data_len); + return p; +} + +static inline bool fapi_is_mlme(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SAP_TYPE_MASK) == FAPI_SAP_TYPE_MLME; +} + +static inline bool fapi_is_ma(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SAP_TYPE_MASK) == FAPI_SAP_TYPE_MA; +} + +static inline bool fapi_is_debug(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SAP_TYPE_MASK) == FAPI_SAP_TYPE_DEBUG; +} + +static inline bool fapi_is_test(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SAP_TYPE_MASK) == FAPI_SAP_TYPE_TEST; +} + +static inline bool fapi_is_req(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SIG_TYPE_MASK) == FAPI_SIG_TYPE_REQ; +} + +static inline bool fapi_is_cfm(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SIG_TYPE_MASK) == FAPI_SIG_TYPE_CFM; +} + +static inline bool fapi_is_res(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SIG_TYPE_MASK) == FAPI_SIG_TYPE_RES; +} + +static inline bool fapi_is_ind(struct sk_buff *skb) +{ + return (fapi_get_sigid(skb) & FAPI_SIG_TYPE_MASK) == FAPI_SIG_TYPE_IND; +} + +static inline u16 fapi_get_expected_size_table(struct sk_buff *skb, const u16 fapi_size_table[], size_t table_size, u16 start_id) +{ + u16 id = fapi_get_sigid(skb); + + if (id < start_id) + return 0; + + if (id - start_id >= (u16)table_size) + return 0; + + return fapi_size_table[id - start_id]; +} + +static inline u16 fapi_get_expected_size(struct sk_buff *skb) +{ + static const u16 fapi_ma_req_size_table[] = { + fapi_sig_size(ma_unitdata_req), + 0, + fapi_sig_size(ma_spare_1_req), + fapi_sig_size(ma_spare_2_req), + fapi_sig_size(ma_spare_3_req), + fapi_sig_size(ma_spare_signal_1_req), + fapi_sig_size(ma_spare_signal_2_req), + fapi_sig_size(ma_spare_signal_3_req), + }; + + static const u16 fapi_mlme_req_size_table[] = { + fapi_sig_size(mlme_get_req), + fapi_sig_size(mlme_set_req), + fapi_sig_size(mlme_powermgt_req), + fapi_sig_size(mlme_add_info_elements_req), + fapi_sig_size(mlme_add_scan_req), + fapi_sig_size(mlme_del_scan_req), + fapi_sig_size(mlme_add_vif_req), + fapi_sig_size(mlme_del_vif_req), + fapi_sig_size(mlme_start_req), + fapi_sig_size(mlme_set_channel_req), + fapi_sig_size(mlme_connect_req), + fapi_sig_size(mlme_reassociate_req), + fapi_sig_size(mlme_roam_req), + fapi_sig_size(mlme_disconnect_req), + fapi_sig_size(mlme_register_action_frame_req), + fapi_sig_size(mlme_send_frame_req), + fapi_sig_size(mlme_reset_dwell_time_req), + fapi_sig_size(mlme_set_traffic_parameters_req), + fapi_sig_size(mlme_del_traffic_parameters_req), + fapi_sig_size(mlme_set_packet_filter_req), + fapi_sig_size(mlme_set_ip_address_req), + fapi_sig_size(mlme_set_acl_req), + 0, + fapi_sig_size(mlme_setkeys_req), + 0, + fapi_sig_size(mlme_get_key_sequence_req), + 0, + fapi_sig_size(mlme_set_pmk_req), + 0, + 0, + fapi_sig_size(mlme_set_cached_channels_req), + fapi_sig_size(mlme_set_whitelist_ssid_req), + fapi_sig_size(mlme_tdls_action_req), + fapi_sig_size(mlme_channel_switch_req), + fapi_sig_size(mlme_monitor_rssi_req), + fapi_sig_size(mlme_start_link_statistics_req), + fapi_sig_size(mlme_stop_link_statistics_req), + 0, + fapi_sig_size(mlme_set_pno_list_req), + fapi_sig_size(mlme_host_state_req), + fapi_sig_size(mlme_add_range_req), + fapi_sig_size(mlme_del_range_req), + fapi_sig_size(mlme_set_noa_req), + fapi_sig_size(mlme_set_ctwindow_req), + fapi_sig_size(mlme_nan_start_req), + fapi_sig_size(mlme_nan_config_req), + fapi_sig_size(mlme_nan_publish_req), + fapi_sig_size(mlme_nan_subscribe_req), + fapi_sig_size(mlme_nan_followup_req), + fapi_sig_size(mlme_unset_channel_req), + fapi_sig_size(mlme_set_country_req), + fapi_sig_size(mlme_forward_beacon_req), + fapi_sig_size(mlme_ndp_setup_req), + fapi_sig_size(mlme_ndp_setup_response_req), + fapi_sig_size(mlme_ndp_terminate_req), + fapi_sig_size(mlme_nan_add_range_req), + fapi_sig_size(mlme_nan_del_range_req), + fapi_sig_size(mlme_spare_4_req), + fapi_sig_size(mlme_spare_5_req), + fapi_sig_size(mlme_spare_6_req), + fapi_sig_size(mlme_install_apf_req), + fapi_sig_size(mlme_read_apf_req), + fapi_sig_size(mlme_set_nss_req), + fapi_sig_size(mlme_arp_detect_req), + fapi_sig_size(mlme_spare_signal_1_req), + fapi_sig_size(mlme_spare_signal_2_req), + fapi_sig_size(mlme_spare_signal_3_req), + }; + + static const u16 fapi_debug_req_size_table[] = { + fapi_sig_size(debug_spare_1_req), + fapi_sig_size(debug_spare_2_req), + fapi_sig_size(debug_spare_3_req), + fapi_sig_size(debug_spare_signal_1_req), + fapi_sig_size(debug_spare_signal_2_req), + fapi_sig_size(debug_spare_signal_3_req), + }; + + static const u16 fapi_test_req_size_table[] = { + fapi_sig_size(test_block_requests_req), + fapi_sig_size(test_panic_req), + fapi_sig_size(test_suspend_req), + fapi_sig_size(test_resume_req), + fapi_sig_size(radio_logging_req), + fapi_sig_size(wlanlite_cw_start_req), + fapi_sig_size(wlanlite_cw_stop_req), + fapi_sig_size(wlanlite_tx_set_params_req), + fapi_sig_size(wlanlite_tx_start_req), + fapi_sig_size(wlanlite_tx_read_req), + fapi_sig_size(wlanlite_tx_stop_req), + fapi_sig_size(wlanlite_rx_start_req), + fapi_sig_size(wlanlite_rx_read_req), + fapi_sig_size(wlanlite_rx_stop_req), + fapi_sig_size(wlanlite_status_req), + fapi_sig_size(test_pmalloc_req), + fapi_sig_size(test_configure_monitor_mode_req), + 0, + fapi_sig_size(test_check_fw_alive_req), + fapi_sig_size(debug_generic_req), + fapi_sig_size(debug_pkt_sink_start_req), + fapi_sig_size(debug_pkt_sink_stop_req), + fapi_sig_size(debug_pkt_sink_report_req), + fapi_sig_size(debug_pkt_gen_start_req), + fapi_sig_size(debug_pkt_gen_stop_req), + fapi_sig_size(debug_pkt_gen_report_req), + fapi_sig_size(wlanlite_radio_select_req), + fapi_sig_size(test_hip_tester_start_req), + fapi_sig_size(test_hip_tester_stop_req), + fapi_sig_size(test_hip_tester_set_params_req), + fapi_sig_size(test_hip_tester_report_req), + fapi_sig_size(test_bist_get_tx_gain_req), + fapi_sig_size(test_spare_1_req), + fapi_sig_size(test_spare_2_req), + fapi_sig_size(test_spare_3_req), + fapi_sig_size(test_spare_signal_1_req), + fapi_sig_size(test_spare_signal_2_req), + fapi_sig_size(test_spare_signal_3_req), + }; + + static const u16 fapi_ma_cfm_size_table[] = { + fapi_sig_size(ma_unitdata_cfm), + 0, + fapi_sig_size(ma_spare_1_cfm), + fapi_sig_size(ma_spare_2_cfm), + fapi_sig_size(ma_spare_3_cfm), + fapi_sig_size(ma_spare_signal_1_cfm), + fapi_sig_size(ma_spare_signal_2_cfm), + fapi_sig_size(ma_spare_signal_3_cfm), + }; + + static const u16 fapi_mlme_cfm_size_table[] = { + fapi_sig_size(mlme_get_cfm), + fapi_sig_size(mlme_set_cfm), + fapi_sig_size(mlme_powermgt_cfm), + fapi_sig_size(mlme_add_info_elements_cfm), + fapi_sig_size(mlme_add_scan_cfm), + fapi_sig_size(mlme_del_scan_cfm), + fapi_sig_size(mlme_add_vif_cfm), + fapi_sig_size(mlme_del_vif_cfm), + fapi_sig_size(mlme_start_cfm), + fapi_sig_size(mlme_set_channel_cfm), + fapi_sig_size(mlme_connect_cfm), + fapi_sig_size(mlme_reassociate_cfm), + fapi_sig_size(mlme_roam_cfm), + fapi_sig_size(mlme_disconnect_cfm), + fapi_sig_size(mlme_register_action_frame_cfm), + fapi_sig_size(mlme_send_frame_cfm), + fapi_sig_size(mlme_reset_dwell_time_cfm), + fapi_sig_size(mlme_set_traffic_parameters_cfm), + fapi_sig_size(mlme_del_traffic_parameters_cfm), + fapi_sig_size(mlme_set_packet_filter_cfm), + fapi_sig_size(mlme_set_ip_address_cfm), + fapi_sig_size(mlme_set_acl_cfm), + 0, + fapi_sig_size(mlme_setkeys_cfm), + 0, + fapi_sig_size(mlme_get_key_sequence_cfm), + 0, + fapi_sig_size(mlme_set_pmk_cfm), + 0, + 0, + fapi_sig_size(mlme_set_cached_channels_cfm), + fapi_sig_size(mlme_set_whitelist_ssid_cfm), + fapi_sig_size(mlme_tdls_action_cfm), + fapi_sig_size(mlme_channel_switch_cfm), + fapi_sig_size(mlme_monitor_rssi_cfm), + fapi_sig_size(mlme_start_link_statistics_cfm), + fapi_sig_size(mlme_stop_link_statistics_cfm), + 0, + fapi_sig_size(mlme_set_pno_list_cfm), + fapi_sig_size(mlme_host_state_cfm), + fapi_sig_size(mlme_add_range_cfm), + fapi_sig_size(mlme_del_range_cfm), + fapi_sig_size(mlme_set_noa_cfm), + fapi_sig_size(mlme_set_ctwindow_cfm), + fapi_sig_size(mlme_nan_start_cfm), + fapi_sig_size(mlme_nan_config_cfm), + fapi_sig_size(mlme_nan_publish_cfm), + fapi_sig_size(mlme_nan_subscribe_cfm), + fapi_sig_size(mlme_nan_followup_cfm), + fapi_sig_size(mlme_unset_channel_cfm), + fapi_sig_size(mlme_set_country_cfm), + fapi_sig_size(mlme_forward_beacon_cfm), + fapi_sig_size(mlme_ndp_setup_cfm), + fapi_sig_size(mlme_ndp_setup_response_cfm), + fapi_sig_size(mlme_ndp_terminate_cfm), + fapi_sig_size(mlme_nan_add_range_cfm), + fapi_sig_size(mlme_nan_del_range_cfm), + fapi_sig_size(mlme_spare_4_cfm), + fapi_sig_size(mlme_spare_5_cfm), + fapi_sig_size(mlme_spare_6_cfm), + fapi_sig_size(mlme_install_apf_cfm), + fapi_sig_size(mlme_read_apf_cfm), + fapi_sig_size(mlme_set_nss_cfm), + fapi_sig_size(mlme_arp_detect_cfm), + fapi_sig_size(mlme_spare_signal_1_cfm), + fapi_sig_size(mlme_spare_signal_2_cfm), + fapi_sig_size(mlme_spare_signal_3_cfm), + }; + + static const u16 fapi_debug_cfm_size_table[] = { + fapi_sig_size(debug_spare_1_cfm), + fapi_sig_size(debug_spare_2_cfm), + fapi_sig_size(debug_spare_3_cfm), + fapi_sig_size(debug_spare_signal_1_cfm), + fapi_sig_size(debug_spare_signal_2_cfm), + fapi_sig_size(debug_spare_signal_3_cfm), + }; + + static const u16 fapi_test_cfm_size_table[] = { + fapi_sig_size(radio_logging_cfm), + fapi_sig_size(wlanlite_cw_start_cfm), + fapi_sig_size(wlanlite_tx_set_params_cfm), + fapi_sig_size(wlanlite_cw_stop_cfm), + fapi_sig_size(wlanlite_tx_start_cfm), + fapi_sig_size(wlanlite_tx_read_cfm), + fapi_sig_size(wlanlite_tx_stop_cfm), + fapi_sig_size(wlanlite_rx_start_cfm), + fapi_sig_size(wlanlite_rx_read_cfm), + fapi_sig_size(wlanlite_rx_stop_cfm), + fapi_sig_size(wlanlite_status_cfm), + fapi_sig_size(test_pmalloc_cfm), + fapi_sig_size(test_configure_monitor_mode_cfm), + 0, + fapi_sig_size(test_check_fw_alive_cfm), + fapi_sig_size(test_suspend_cfm), + fapi_sig_size(test_resume_cfm), + fapi_sig_size(debug_generic_cfm), + fapi_sig_size(wlanlite_radio_select_cfm), + fapi_sig_size(test_hip_tester_start_cfm), + fapi_sig_size(test_hip_tester_stop_cfm), + fapi_sig_size(test_hip_tester_set_params_cfm), + fapi_sig_size(test_bist_get_tx_gain_cfm), + fapi_sig_size(test_spare_1_cfm), + fapi_sig_size(test_spare_2_cfm), + fapi_sig_size(test_spare_3_cfm), + fapi_sig_size(test_spare_signal_1_cfm), + fapi_sig_size(test_spare_signal_2_cfm), + fapi_sig_size(test_spare_signal_3_cfm), + }; + + static const u16 fapi_ma_ind_size_table[] = { + fapi_sig_size(ma_unitdata_ind), + fapi_sig_size(ma_blockack_ind), + fapi_sig_size(ma_spare_1_ind), + fapi_sig_size(ma_spare_2_ind), + fapi_sig_size(ma_spare_3_ind), + fapi_sig_size(ma_spare_signal_1_ind), + fapi_sig_size(ma_spare_signal_2_ind), + fapi_sig_size(ma_spare_signal_3_ind), + }; + + static const u16 fapi_mlme_ind_size_table[] = { + fapi_sig_size(mlme_scan_ind), + fapi_sig_size(mlme_scan_done_ind), + fapi_sig_size(mlme_listen_end_ind), + fapi_sig_size(mlme_connect_ind), + fapi_sig_size(mlme_connected_ind), + fapi_sig_size(mlme_reassociate_ind), + fapi_sig_size(mlme_roam_ind), + fapi_sig_size(mlme_roamed_ind), + fapi_sig_size(mlme_disconnect_ind), + fapi_sig_size(mlme_disconnected_ind), + fapi_sig_size(mlme_procedure_started_ind), + fapi_sig_size(mlme_mic_failure_ind), + fapi_sig_size(mlme_frame_transmission_ind), + fapi_sig_size(mlme_received_frame_ind), + 0, + fapi_sig_size(mlme_tdls_peer_ind), + 0, + 0, + fapi_sig_size(mlme_rssi_report_ind), + fapi_sig_size(mlme_ac_priority_update_ind), + fapi_sig_size(mlme_range_ind), + fapi_sig_size(mlme_range_done_ind), + fapi_sig_size(mlme_event_log_ind), + fapi_sig_size(mlme_nan_event_ind), + fapi_sig_size(mlme_nan_service_ind), + fapi_sig_size(mlme_nan_followup_ind), + fapi_sig_size(mlme_channel_switched_ind), + fapi_sig_size(mlme_synchronised_ind), + fapi_sig_size(mlme_beacon_reporting_event_ind), + fapi_sig_size(mlme_spare_3_ind), + fapi_sig_size(mlme_spare_4_ind), + fapi_sig_size(mlme_ndp_setup_ind), + fapi_sig_size(mlme_ndp_requested_ind), + fapi_sig_size(mlme_ndp_setup_response_ind), + fapi_sig_size(mlme_ndp_terminated_ind), + fapi_sig_size(mlme_nan_add_range_ind), + fapi_sig_size(mlme_spare_5_ind), + fapi_sig_size(mlme_spare_signal_1_ind), + fapi_sig_size(mlme_spare_signal_2_ind), + fapi_sig_size(mlme_spare_signal_3_ind), + }; + + static const u16 fapi_debug_ind_size_table[] = { + fapi_sig_size(debug_word12_ind), + fapi_sig_size(debug_fault_ind), + fapi_sig_size(debug_words_ind), + fapi_sig_size(debug_spare_2_ind), + fapi_sig_size(debug_spare_3_ind), + fapi_sig_size(debug_spare_4_ind), + fapi_sig_size(debug_spare_signal_1_ind), + fapi_sig_size(debug_spare_signal_2_ind), + fapi_sig_size(debug_spare_signal_3_ind), + }; + + static const u16 fapi_test_ind_size_table[] = { + fapi_sig_size(radio_logging_ind), + fapi_sig_size(debug_generic_ind), + fapi_sig_size(debug_pkt_sink_report_ind), + fapi_sig_size(debug_pkt_gen_report_ind), + fapi_sig_size(test_hip_tester_report_ind), + fapi_sig_size(test_spare_1_ind), + fapi_sig_size(test_spare_2_ind), + fapi_sig_size(test_spare_3_ind), + fapi_sig_size(test_spare_signal_1_ind), + fapi_sig_size(test_spare_signal_2_ind), + fapi_sig_size(test_spare_signal_3_ind), + }; + + static const u16 fapi_mlme_res_size_table[] = { + fapi_sig_size(mlme_connect_res), + fapi_sig_size(mlme_connected_res), + fapi_sig_size(mlme_reassociate_res), + fapi_sig_size(mlme_roamed_res), + fapi_sig_size(mlme_tdls_peer_res), + fapi_sig_size(mlme_synchronised_res), + fapi_sig_size(mlme_spare_2_res), + fapi_sig_size(mlme_spare_3_res), + fapi_sig_size(mlme_spare_4_res), + fapi_sig_size(mlme_spare_signal_1_res), + fapi_sig_size(mlme_spare_signal_2_res), + fapi_sig_size(mlme_spare_signal_3_res), + }; + + static const u16 fapi_ma_res_size_table[] = { + fapi_sig_size(ma_spare_1_res), + fapi_sig_size(ma_spare_2_res), + fapi_sig_size(ma_spare_3_res), + fapi_sig_size(ma_spare_signal_1_res), + fapi_sig_size(ma_spare_signal_2_res), + fapi_sig_size(ma_spare_signal_3_res), + }; + + static const u16 fapi_debug_res_size_table[] = { + fapi_sig_size(debug_spare_1_res), + fapi_sig_size(debug_spare_2_res), + fapi_sig_size(debug_spare_3_res), + fapi_sig_size(debug_spare_signal_1_res), + fapi_sig_size(debug_spare_signal_2_res), + fapi_sig_size(debug_spare_signal_3_res), + }; + + static const u16 fapi_test_res_size_table[] = { + fapi_sig_size(test_spare_1_res), + fapi_sig_size(test_spare_2_res), + fapi_sig_size(test_spare_3_res), + fapi_sig_size(test_spare_signal_1_res), + fapi_sig_size(test_spare_signal_2_res), + fapi_sig_size(test_spare_signal_3_res), + }; + + if (fapi_is_mlme(skb) && fapi_is_req(skb)) + return fapi_get_expected_size_table(skb, fapi_mlme_req_size_table, ARRAY_SIZE(fapi_mlme_req_size_table), MLME_GET_REQ); + else if (fapi_is_mlme(skb) && fapi_is_cfm(skb)) + return fapi_get_expected_size_table(skb, fapi_mlme_cfm_size_table, ARRAY_SIZE(fapi_mlme_cfm_size_table), MLME_GET_CFM); + else if (fapi_is_mlme(skb) && fapi_is_res(skb)) + return fapi_get_expected_size_table(skb, fapi_mlme_res_size_table, ARRAY_SIZE(fapi_mlme_res_size_table), MLME_CONNECT_RES); + else if (fapi_is_mlme(skb) && fapi_is_ind(skb)) + return fapi_get_expected_size_table(skb, fapi_mlme_ind_size_table, ARRAY_SIZE(fapi_mlme_ind_size_table), MLME_SCAN_IND); + else if (fapi_is_ma(skb) && fapi_is_req(skb)) + return fapi_get_expected_size_table(skb, fapi_ma_req_size_table, ARRAY_SIZE(fapi_ma_req_size_table), MA_UNITDATA_REQ); + else if (fapi_is_ma(skb) && fapi_is_cfm(skb)) + return fapi_get_expected_size_table(skb, fapi_ma_cfm_size_table, ARRAY_SIZE(fapi_ma_cfm_size_table), MA_UNITDATA_CFM); + else if (fapi_is_ma(skb) && fapi_is_ind(skb)) + return fapi_get_expected_size_table(skb, fapi_ma_ind_size_table, ARRAY_SIZE(fapi_ma_ind_size_table), MA_UNITDATA_IND); + else if (fapi_is_debug(skb) && fapi_is_req(skb)) + return fapi_get_expected_size_table(skb, fapi_debug_req_size_table, ARRAY_SIZE(fapi_debug_req_size_table), DEBUG_GENERIC_REQ); + else if (fapi_is_debug(skb) && fapi_is_cfm(skb)) + return fapi_get_expected_size_table(skb, fapi_debug_cfm_size_table, ARRAY_SIZE(fapi_debug_cfm_size_table), DEBUG_GENERIC_CFM); + else if (fapi_is_debug(skb) && fapi_is_ind(skb)) + return fapi_get_expected_size_table(skb, fapi_debug_ind_size_table, ARRAY_SIZE(fapi_debug_ind_size_table), DEBUG_WORD12IND); + else if (fapi_is_test(skb) && fapi_is_req(skb)) + return fapi_get_expected_size_table(skb, fapi_test_req_size_table, ARRAY_SIZE(fapi_test_req_size_table), TEST_BLOCK_REQUESTS_REQ); + else if (fapi_is_test(skb) && fapi_is_cfm(skb)) + return fapi_get_expected_size_table(skb, fapi_test_cfm_size_table, ARRAY_SIZE(fapi_test_cfm_size_table), RADIO_LOGGING_CFM); + else if (fapi_is_test(skb) && fapi_is_ind(skb)) + return fapi_get_expected_size_table(skb, fapi_test_ind_size_table, ARRAY_SIZE(fapi_test_ind_size_table), RADIO_LOGGING_IND); + else if (fapi_is_mlme(skb) && fapi_is_res(skb)) + return fapi_get_expected_size_table(skb, fapi_ma_res_size_table, ARRAY_SIZE(fapi_ma_res_size_table), MA_SPARE_SIGNAL_1_RES); + else if (fapi_is_mlme(skb) && fapi_is_res(skb)) + return fapi_get_expected_size_table(skb, fapi_debug_res_size_table, ARRAY_SIZE(fapi_debug_res_size_table), DEBUG_SPARE_SIGNAL_1_RES); + else if (fapi_is_mlme(skb) && fapi_is_res(skb)) + return fapi_get_expected_size_table(skb, fapi_test_res_size_table, ARRAY_SIZE(fapi_test_res_size_table), TEST_SPARE_SIGNAL_1_RES); + + return 0; +} + +#ifdef __cplusplus +} +#endif +#endif /* _FAPI_H__ */ diff --git a/drivers/net/wireless/scsc/fw_test.c b/drivers/net/wireless/scsc/fw_test.c new file mode 100755 index 000000000000..ea99e0e0b51a --- /dev/null +++ b/drivers/net/wireless/scsc/fw_test.c @@ -0,0 +1,971 @@ +/***************************************************************************** + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ +#include +#include +#include "dev.h" +#include "fapi.h" +#include "fw_test.h" +#include "debug.h" +#include "mgt.h" +#include "mlme.h" +#include "netif.h" +#include "ba.h" +#include "sap_mlme.h" + +static void slsi_fw_test_save_frame(struct slsi_dev *sdev, struct slsi_fw_test *fwtest, struct sk_buff *saved_skbs[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1], struct sk_buff *skb, bool udi_header) +{ + u16 vif; + + skb = slsi_skb_copy(skb, GFP_KERNEL); + + if (udi_header) + skb_pull(skb, sizeof(struct udi_msg_t)); + + vif = fapi_get_vif(skb); + + SLSI_DBG3(sdev, SLSI_FW_TEST, "sig:0x%.4X, vif:%d\n", fapi_get_sigid(skb), vif); + slsi_debug_frame(sdev, NULL, skb, "SAVE"); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + slsi_kfree_skb(saved_skbs[vif]); + saved_skbs[vif] = skb; + slsi_spinlock_unlock(&fwtest->fw_test_lock); +} + +static void slsi_fw_test_process_frame(struct slsi_dev *sdev, struct slsi_fw_test *fwtest, struct sk_buff *skb, bool udi_header) +{ + u16 vif; + + skb = slsi_skb_copy(skb, GFP_KERNEL); + + if (udi_header) + skb_pull(skb, sizeof(struct udi_msg_t)); + + vif = fapi_get_vif(skb); + + SLSI_DBG3(sdev, SLSI_FW_TEST, "sig:0x%.4X, vif:%d\n", fapi_get_sigid(skb), vif); + slsi_debug_frame(sdev, NULL, skb, "PROCESS"); + + slsi_skb_work_enqueue(&fwtest->fw_test_work, skb); +} + +int slsi_fw_test_signal(struct slsi_dev *sdev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + u16 vif = fapi_get_vif(skb); + + /* Atleast one write to via the UDI interface */ + fwtest->fw_test_enabled = true; + SLSI_DBG3(sdev, SLSI_FW_TEST, "0x%p: sig:0x%.4X, vif:%d\n", skb, fapi_get_sigid(skb), vif); + + if (WARN(vif > CONFIG_SCSC_WLAN_MAX_INTERFACES, "vif(%d) > CONFIG_SCSC_WLAN_MAX_INTERFACES", vif)) + return -EINVAL; + + switch (fapi_get_sigid(skb)) { + case MLME_ADD_VIF_REQ: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Save MLME_ADD_VIF_REQ(0x%.4X, vif:%d)\n", skb, fapi_get_sigid(skb), vif); + slsi_fw_test_save_frame(sdev, fwtest, fwtest->mlme_add_vif_req, skb, false); + slsi_fw_test_process_frame(sdev, fwtest, skb, false); + break; + case MLME_CONNECT_REQ: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Save MLME_CONNECT_REQ(0x%.4X, vif:%d)\n", skb, fapi_get_sigid(skb), vif); + slsi_fw_test_save_frame(sdev, fwtest, fwtest->mlme_connect_req, skb, false); + break; + case MLME_DEL_VIF_REQ: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Save MLME_DEL_VIF_REQ(0x%.4X, vif:%d)\n", skb, fapi_get_sigid(skb), vif); + slsi_fw_test_process_frame(sdev, fwtest, skb, false); + break; + default: + return 0; + } + + return 0; +} + +int slsi_fw_test_signal_with_udi_header(struct slsi_dev *sdev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct udi_msg_t *udi_msg = (struct udi_msg_t *)skb->data; + struct fapi_vif_signal_header *fapi_header = (struct fapi_vif_signal_header *)(skb->data + sizeof(struct udi_msg_t)); + + if (!fwtest->fw_test_enabled) + return 0; + + SLSI_DBG3(sdev, SLSI_FW_TEST, "0x%p: sig:0x%.4X, vif:%d\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + + if (udi_msg->direction == SLSI_LOG_DIRECTION_TO_HOST) { + switch (le16_to_cpu(fapi_header->id)) { + case MLME_DISCONNECT_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_DISCONNECT_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_DISCONNECTED_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_DISCONNECTED_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_CONNECT_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_CONNECT_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_CONNECTED_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_CONNECTED_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_ROAMED_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_ROAMED_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_TDLS_PEER_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_TDLS_PEER_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_CONNECT_CFM: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Save MLME_CONNECT_CFM(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_save_frame(sdev, fwtest, fwtest->mlme_connect_cfm, skb, true); + break; + case MLME_PROCEDURE_STARTED_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Save MLME_PROCEDURE_STARTED_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_save_frame(sdev, fwtest, fwtest->mlme_procedure_started_ind, skb, true); + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_PROCEDURE_STARTED_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + case MLME_START_CFM: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MLME_START_CFM(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + sdev->device_config.ap_disconnect_ind_timeout = SLSI_DEFAULT_AP_DISCONNECT_IND_TIMEOUT; + break; + case MA_BLOCKACK_IND: + SLSI_DBG2(sdev, SLSI_FW_TEST, "0x%p: Process MA_BLOCKACK_IND(0x%.4X, vif:%d)\n", skb, le16_to_cpu(fapi_header->id), le16_to_cpu(fapi_header->vif)); + slsi_fw_test_process_frame(sdev, fwtest, skb, true); + break; + default: + break; + } + } + + return 0; +} + +static void slsi_fw_test_connect_station_roam(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + struct sk_buff *mlme_procedure_started_ind; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Station Connect(vif:%d) Roam\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->is_fw_test, "!is_fw_test")) + return; + + if (WARN(!ndev_vif->activated, "Not Activated")) + return; + + if (WARN(ndev_vif->vif_type != FAPI_VIFTYPE_STATION, "Not Station Vif")) + return; + + if (WARN(!peer, "peer not found")) + return; + + slsi_spinlock_lock(&fwtest->fw_test_lock); + mlme_procedure_started_ind = fwtest->mlme_procedure_started_ind[ndev_vif->ifnum]; + fwtest->mlme_procedure_started_ind[ndev_vif->ifnum] = NULL; + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(!mlme_procedure_started_ind, "mlme_procedure_started_ind not found")) + return; + + slsi_rx_ba_stop_all(dev, peer); + + SLSI_ETHER_COPY(peer->address, mgmt->bssid); + slsi_peer_update_assoc_req(sdev, dev, peer, mlme_procedure_started_ind); + slsi_peer_update_assoc_rsp(sdev, dev, peer, slsi_skb_copy(skb, GFP_KERNEL)); +} + +static void slsi_fw_test_connect_start_station(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + struct sk_buff *ind; + struct slsi_peer *peer; + u8 bssid[ETH_ALEN]; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Station Connect Start(vif:%d)\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->is_fw_test, "!is_fw_test")) + return; + if (WARN(ndev_vif->activated, "Already Activated")) + return; + + slsi_spinlock_lock(&fwtest->fw_test_lock); + req = fwtest->mlme_connect_req[ndev_vif->ifnum]; + cfm = fwtest->mlme_connect_cfm[ndev_vif->ifnum]; + ind = fwtest->mlme_procedure_started_ind[ndev_vif->ifnum]; + if (req) + SLSI_ETHER_COPY(bssid, fapi_get_buff(req, u.mlme_connect_req.bssid)); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(!req, "mlme_connect_req Not found")) + return; + if (WARN(!cfm, "mlme_connect_cfm Not found")) + return; + + ndev_vif->iftype = NL80211_IFTYPE_STATION; + dev->ieee80211_ptr->iftype = NL80211_IFTYPE_STATION; + ndev_vif->vif_type = FAPI_VIFTYPE_STATION; + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d slsi_vif_activated\n", ndev_vif->ifnum); + if (WARN(slsi_vif_activated(sdev, dev) != 0, "slsi_vif_activated() Failed")) + return; + + peer = slsi_peer_add(sdev, dev, bssid, SLSI_STA_PEER_QUEUESET + 1); + if (WARN(!peer, "slsi_peer_add(%pM) Failed", bssid)) { + slsi_vif_deactivated(sdev, dev); + return; + } + + slsi_peer_update_assoc_req(sdev, dev, peer, slsi_skb_copy(skb, GFP_KERNEL)); +} + +static void slsi_fw_test_connect_station(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + struct sk_buff *ind; + struct slsi_peer *peer; + u16 result; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Station Connect(vif:%d)\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->is_fw_test, "!is_fw_test")) + return; + + result = fapi_get_u16(skb, u.mlme_connect_ind.result_code); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + req = fwtest->mlme_connect_req[ndev_vif->ifnum]; + cfm = fwtest->mlme_connect_cfm[ndev_vif->ifnum]; + ind = fwtest->mlme_procedure_started_ind[ndev_vif->ifnum]; + fwtest->mlme_connect_req[ndev_vif->ifnum] = NULL; + fwtest->mlme_connect_cfm[ndev_vif->ifnum] = NULL; + fwtest->mlme_procedure_started_ind[ndev_vif->ifnum] = NULL; + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(!req, "mlme_connect_req Not found")) + goto exit; + if (WARN(!cfm, "mlme_connect_cfm Not found")) + goto exit; + if (FAPI_RESULTCODE_SUCCESS == result && + WARN(!ind, "mlme_procedure_started_ind Not found")) + goto exit; + if (FAPI_RESULTCODE_SUCCESS != result) + goto exit; + + if (WARN(!ndev_vif->activated, "Not Activated")) + return; + + peer = slsi_get_peer_from_mac(sdev, dev, fapi_get_buff(req, u.mlme_connect_req.bssid)); + if (WARN(!peer, "slsi_get_peer_from_mac(%pM) Failed", fapi_get_buff(req, u.mlme_connect_req.bssid))) + goto exit; + + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + netif_carrier_on(dev); + +exit: + slsi_kfree_skb(req); + slsi_kfree_skb(cfm); + slsi_kfree_skb(ind); +} + +static void slsi_fw_test_started_network(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 result = fapi_get_u16(skb, u.mlme_start_cfm.result_code); + + SLSI_UNUSED_PARAMETER(fwtest); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Start Network(vif:%d)\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->is_fw_test, "!is_fw_test")) + return; + if (WARN(ndev_vif->activated, "Already Activated")) + return; + + ndev_vif->iftype = NL80211_IFTYPE_AP; + dev->ieee80211_ptr->iftype = NL80211_IFTYPE_AP; + ndev_vif->vif_type = FAPI_VIFTYPE_AP; + + if (WARN(slsi_vif_activated(sdev, dev) != 0, "slsi_vif_activated() Failed")) + return; + + if (FAPI_RESULTCODE_SUCCESS == result) + netif_carrier_on(dev); +} + +static void slsi_fw_test_stop_network(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_UNUSED_PARAMETER(fwtest); + SLSI_UNUSED_PARAMETER(skb); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (!ndev_vif->is_fw_test) + return; + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Stopping Network(vif:%d)\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->activated, "Not Activated")) + return; + + netif_carrier_off(dev); + slsi_vif_deactivated(sdev, dev); +} + +static void slsi_fw_test_connect_start_ap(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + u16 peer_index; + + SLSI_UNUSED_PARAMETER(fwtest); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Network Peer Connect Start(vif:%d)\n", ndev_vif->ifnum); + WARN(!ndev_vif->is_fw_test, "!is_fw_test"); + + if (WARN(!ndev_vif->activated, "Not Activated")) + return; + + if (WARN_ON(!ieee80211_is_assoc_req(mgmt->frame_control) && + !ieee80211_is_reassoc_req(mgmt->frame_control))) + return; + peer_index = fapi_get_u16(skb, u.mlme_procedure_started_ind.peer_index); + + peer = slsi_peer_add(sdev, dev, mgmt->sa, peer_index); + if (WARN_ON(!peer)) + return; + + slsi_peer_update_assoc_req(sdev, dev, peer, slsi_skb_copy(skb, GFP_KERNEL)); + peer->connected_state = SLSI_STA_CONN_STATE_CONNECTING; +} + +static void slsi_fw_test_connected_network(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + u16 peer_index = fapi_get_u16(skb, u.mlme_connected_ind.peer_index); + + SLSI_UNUSED_PARAMETER(fwtest); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Network Peer Connect(vif:%d, peer_index:%d)\n", ndev_vif->ifnum, peer_index); + WARN(!ndev_vif->is_fw_test, "!is_fw_test"); + + if (WARN(!ndev_vif->activated, "Not Activated")) + return; + + if (WARN_ON(peer_index > SLSI_PEER_INDEX_MAX)) + return; + + peer = slsi_get_peer_from_qs(sdev, dev, peer_index - 1); + if (WARN(!peer, "Peer(peer_index:%d) Not Found", peer_index)) + return; + + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + peer->connected_state = SLSI_STA_CONN_STATE_CONNECTED; + + slsi_rx_buffered_frames(sdev, dev, peer); +} + +/* Setup the NetDev / Peers based on the saved frames */ +static void slsi_fw_test_procedure_started_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_STATION; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "ProceedureStarted(vif:%d)\n", ndev_vif->ifnum); + + if (fapi_get_u16(skb, u.mlme_procedure_started_ind.procedure_type) != FAPI_PROCEDURETYPE_CONNECTION_STARTED) { + slsi_kfree_skb(skb); + return; + } + + /* Set up the VIF and Data plane ready to go BUT do not open the control port */ + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Start UDI test NetDevice(vif:%d)\n", ndev_vif->ifnum); + if (WARN(!add_vif_req, "fwtest->mlme_add_vif_req[ndev_vif->ifnum] == NULL")) + goto out; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + case FAPI_VIFTYPE_STATION: + slsi_fw_test_connect_start_station(sdev, dev, fwtest, skb); + break; + case FAPI_VIFTYPE_AP: + slsi_fw_test_connect_start_ap(sdev, dev, fwtest, skb); + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d virtual_interface_type:%d NOT SUPPORTED\n", ndev_vif->ifnum, viftype); + break; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +out: + slsi_kfree_skb(skb); +} + +/* Setup the NetDev / Peers based on the saved frames */ +static void slsi_fw_test_connect_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_STATION; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Network Peer Connect(vif:%d)\n", ndev_vif->ifnum); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Start UDI test NetDevice(vif:%d)\n", ndev_vif->ifnum); + if (WARN(!add_vif_req, "fwtest->mlme_add_vif_req[ndev_vif->ifnum] == NULL")) + goto out; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + case FAPI_VIFTYPE_STATION: + slsi_fw_test_connect_station(sdev, dev, fwtest, skb); + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d virtual_interface_type:%d NOT SUPPORTED\n", ndev_vif->ifnum, viftype); + break; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +out: + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_connected_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_STATION; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Connected(vif:%d)\n", ndev_vif->ifnum); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(!add_vif_req, "fwtest->mlme_add_vif_req[ndev_vif->ifnum] == NULL")) + goto out; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + case FAPI_VIFTYPE_AP: + slsi_fw_test_connected_network(sdev, dev, fwtest, skb); + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d virtual_interface_type:%d NOT SUPPORTED\n", ndev_vif->ifnum, viftype); + break; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +out: + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_roamed_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_STATION; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Roamed(vif:%d)\n", ndev_vif->ifnum); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(!add_vif_req, "fwtest->mlme_add_vif_req[ndev_vif->ifnum] == NULL")) + goto out; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + case FAPI_VIFTYPE_STATION: + slsi_fw_test_connect_station_roam(sdev, dev, fwtest, skb); + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d virtual_interface_type:%d NOT SUPPORTED\n", ndev_vif->ifnum, viftype); + break; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +out: + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_disconnect_station(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + + SLSI_UNUSED_PARAMETER(fwtest); + SLSI_UNUSED_PARAMETER(skb); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (!ndev_vif->is_fw_test) + return; + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Station Disconnect(vif:%d)\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->activated, "Not Activated")) + return; + + netif_carrier_off(dev); + if (peer) + slsi_peer_remove(sdev, dev, peer); + slsi_vif_deactivated(sdev, dev); +} + +static void slsi_fw_test_disconnect_network(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + /* Find the peer based on MAC address, mlme-disconnect-ind and mlme-disconnected-ind + * both have the MAC address in the same position. + */ + struct slsi_peer *peer = slsi_get_peer_from_mac(sdev, dev, fapi_get_buff(skb, u.mlme_disconnect_ind.peer_sta_address)); + + SLSI_UNUSED_PARAMETER(fwtest); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (!ndev_vif->is_fw_test) + return; + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Network Peer Disconnect(vif:%d)\n", ndev_vif->ifnum); + + if (peer) + slsi_peer_remove(sdev, dev, peer); +} + +static void slsi_fw_test_disconnected_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_STATION; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(!add_vif_req, "fwtest->mlme_add_vif_req[ndev_vif->ifnum] == NULL")) + goto out; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + case FAPI_VIFTYPE_STATION: + slsi_fw_test_disconnect_station(sdev, dev, fwtest, skb); + break; + case FAPI_VIFTYPE_AP: + slsi_fw_test_disconnect_network(sdev, dev, fwtest, skb); + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d virtual_interface_type:%d NOT SUPPORTED\n", ndev_vif->ifnum, viftype); + break; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +out: + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_tdls_event_connected(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct slsi_peer *peer = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 peer_index = fapi_get_u16(skb, u.mlme_tdls_peer_ind.peer_index); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + ndev_vif->sta.tdls_enabled = true; + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "TDLS connect (vif:%d, peer_index:%d, mac:%pM)\n", fapi_get_vif(skb), peer_index, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address)); + + if ((ndev_vif->sta.tdls_peer_sta_records) + 1 > SLSI_TDLS_PEER_CONNECTIONS_MAX) { + SLSI_NET_ERR(dev, "max TDLS limit reached (peer_index:%d)\n", peer_index); + goto out; + } + + if (peer_index < SLSI_TDLS_PEER_INDEX_MIN || peer_index > SLSI_TDLS_PEER_INDEX_MAX) { + SLSI_NET_ERR(dev, "incorrect index (peer_index:%d)\n", peer_index); + goto out; + } + + peer = slsi_peer_add(sdev, dev, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address), peer_index); + if (!peer) { + SLSI_NET_ERR(dev, "peer add failed\n"); + goto out; + } + + /* QoS is mandatory for TDLS - enable QoS for TDLS peer by default */ + peer->qos_enabled = true; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + + /* move TDLS packets from STA Q to TDLS Q */ + slsi_tdls_move_packets(sdev, dev, ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET], peer, true); + +out: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +static void slsi_fw_test_tdls_event_disconnected(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct slsi_peer *peer = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_NET_DBG1(dev, SLSI_MLME, "TDLS dis-connect (vif:%d, mac:%pM)\n", ndev_vif->ifnum, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address)); + + peer = slsi_get_peer_from_mac(sdev, dev, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address)); + if (!peer || (peer->aid == 0)) { + WARN_ON(!peer || (peer->aid == 0)); + SLSI_NET_DBG1(dev, SLSI_MLME, "can't find peer by MAC address\n"); + goto out; + } + + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + + /* move TDLS packets from TDLS Q to STA Q */ + slsi_tdls_move_packets(sdev, dev, ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET], peer, false); + slsi_peer_remove(sdev, dev, peer); + +out: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +static void slsi_fw_test_tdls_peer_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 vif_type = 0; + u16 tdls_event; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + if (WARN(!ndev_vif->activated, "Not Activated")) { + slsi_kfree_skb(skb); + return; + } + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + vif_type = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + if (WARN(vif_type != FAPI_VIFTYPE_STATION, "Not STA VIF")) { + slsi_kfree_skb(skb); + return; + } + + tdls_event = fapi_get_u16(skb, u.mlme_tdls_peer_ind.tdls_event); + SLSI_NET_DBG1(dev, SLSI_MLME, "TDLS peer(vif:%d tdls_event:%d)\n", ndev_vif->ifnum, tdls_event); + switch (tdls_event) { + case FAPI_TDLSEVENT_CONNECTED: + slsi_fw_test_tdls_event_connected(sdev, dev, skb); + break; + case FAPI_TDLSEVENT_DISCONNECTED: + slsi_fw_test_tdls_event_disconnected(sdev, dev, skb); + break; + case FAPI_TDLSEVENT_DISCOVERED: + /* nothing to do */ + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d tdls_event:%d not supported\n", ndev_vif->ifnum, tdls_event); + break; + } + slsi_kfree_skb(skb); +} + +/* Setup the NetDev */ +static void slsi_fw_test_start_cfm(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_UNSYNCHRONISED; + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Network Start(vif:%d)\n", ndev_vif->ifnum); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "Start UDI test NetDevice(vif:%d)\n", ndev_vif->ifnum); + if (WARN(!add_vif_req, "fwtest->mlme_add_vif_req[ndev_vif->ifnum] == NULL")) + goto out; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + case FAPI_VIFTYPE_AP: + slsi_fw_test_started_network(sdev, dev, fwtest, skb); + break; + default: + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "vif:%d virtual_interface_type:%d NOT SUPPORTED\n", ndev_vif->ifnum, viftype); + break; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +out: + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_add_vif_req(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_UNUSED_PARAMETER(sdev); + SLSI_UNUSED_PARAMETER(fwtest); + + SLSI_DBG1(sdev, SLSI_FW_TEST, "Mark UDI test NetDevice(vif:%d)\n", fapi_get_vif(skb)); + ndev_vif->is_fw_test = true; + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_del_vif_req(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *add_vif_req; + u16 viftype = FAPI_VIFTYPE_UNSYNCHRONISED; + + SLSI_DBG1(sdev, SLSI_FW_TEST, "Unmark UDI test NetDevice(vif:%d)\n", fapi_get_vif(skb)); + + slsi_spinlock_lock(&fwtest->fw_test_lock); + add_vif_req = fwtest->mlme_add_vif_req[ndev_vif->ifnum]; + if (add_vif_req) + viftype = fapi_get_u16(add_vif_req, u.mlme_add_vif_req.virtual_interface_type); + slsi_kfree_skb(fwtest->mlme_add_vif_req[ndev_vif->ifnum]); + slsi_kfree_skb(fwtest->mlme_connect_req[ndev_vif->ifnum]); + slsi_kfree_skb(fwtest->mlme_connect_cfm[ndev_vif->ifnum]); + slsi_kfree_skb(fwtest->mlme_procedure_started_ind[ndev_vif->ifnum]); + + fwtest->mlme_add_vif_req[ndev_vif->ifnum] = NULL; + fwtest->mlme_connect_req[ndev_vif->ifnum] = NULL; + fwtest->mlme_connect_cfm[ndev_vif->ifnum] = NULL; + fwtest->mlme_procedure_started_ind[ndev_vif->ifnum] = NULL; + slsi_spinlock_unlock(&fwtest->fw_test_lock); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + switch (viftype) { + /* As there is no specific MLME primitive for shutting down the network + * perform an actions on the MLME-DEL-VIF. + */ + case FAPI_VIFTYPE_AP: + slsi_fw_test_stop_network(sdev, dev, fwtest, skb); + break; + default: + if (ndev_vif->is_fw_test && ndev_vif->activated) { + netif_carrier_off(dev); + slsi_vif_deactivated(sdev, dev); + } + break; + } + ndev_vif->is_fw_test = false; + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + slsi_kfree_skb(skb); +} + +static void slsi_fw_test_ma_blockack_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_fw_test *fwtest, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + if (!ndev_vif->is_fw_test) { + slsi_kfree_skb(skb); + return; + } + + SLSI_NET_DBG1(dev, SLSI_FW_TEST, "MA Block Ack Indication(vif:%d)\n", ndev_vif->ifnum); + slsi_rx_blockack_ind(sdev, dev, skb); +} + +void slsi_fw_test_work(struct work_struct *work) +{ + struct slsi_fw_test *fw_test = container_of(work, struct slsi_fw_test, fw_test_work.work); + struct slsi_dev *sdev = fw_test->sdev; + struct sk_buff *skb = slsi_skb_work_dequeue(&fw_test->fw_test_work); + struct net_device *dev; + + while (skb) { + u16 vif = fapi_get_vif(skb); + + SLSI_DBG3(sdev, SLSI_FW_TEST, "0x%p: Signal:0x%.4X, vif:%d\n", skb, fapi_get_sigid(skb), vif); + + if (WARN(!vif, "!vif")) { + slsi_kfree_skb(skb); + skb = slsi_skb_work_dequeue(&fw_test->fw_test_work); + continue; + } + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + dev = slsi_get_netdev_locked(sdev, vif); + if (!dev) { + /* Just ignore the signal. This is valid in some error testing scenarios*/ + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + slsi_kfree_skb(skb); + skb = slsi_skb_work_dequeue(&fw_test->fw_test_work); + continue; + } + + switch (fapi_get_sigid(skb)) { + case MLME_PROCEDURE_STARTED_IND: + slsi_fw_test_procedure_started_ind(sdev, dev, fw_test, skb); + break; + case MLME_CONNECT_IND: + slsi_fw_test_connect_ind(sdev, dev, fw_test, skb); + break; + case MLME_ROAMED_IND: + slsi_fw_test_roamed_ind(sdev, dev, fw_test, skb); + break; + case MLME_CONNECTED_IND: + slsi_fw_test_connected_ind(sdev, dev, fw_test, skb); + break; + case MLME_DISCONNECT_IND: + case MLME_DISCONNECTED_IND: + slsi_fw_test_disconnected_ind(sdev, dev, fw_test, skb); + break; + case MLME_TDLS_PEER_IND: + slsi_fw_test_tdls_peer_ind(sdev, dev, fw_test, skb); + break; + case MLME_START_CFM: + slsi_fw_test_start_cfm(sdev, dev, fw_test, skb); + break; + case MLME_ADD_VIF_REQ: + slsi_fw_test_add_vif_req(sdev, dev, fw_test, skb); + break; + case MLME_DEL_VIF_REQ: + slsi_fw_test_del_vif_req(sdev, dev, fw_test, skb); + break; + case MA_BLOCKACK_IND: + slsi_fw_test_ma_blockack_ind(sdev, dev, fw_test, skb); + break; + default: + WARN(1, "Unhandled Signal"); + slsi_kfree_skb(skb); + break; + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + skb = slsi_skb_work_dequeue(&fw_test->fw_test_work); + } +} + +void slsi_fw_test_init(struct slsi_dev *sdev, struct slsi_fw_test *fwtest) +{ + SLSI_DBG1(sdev, SLSI_FW_TEST, "\n"); + memset(fwtest, 0x00, sizeof(struct slsi_fw_test)); + fwtest->sdev = sdev; + slsi_spinlock_create(&fwtest->fw_test_lock); + slsi_skb_work_init(sdev, NULL, &fwtest->fw_test_work, "slsi_wlan_fw_test", slsi_fw_test_work); +} + +void slsi_fw_test_deinit(struct slsi_dev *sdev, struct slsi_fw_test *fwtest) +{ + int i; + + SLSI_UNUSED_PARAMETER(sdev); + + SLSI_DBG1(sdev, SLSI_FW_TEST, "\n"); + fwtest->fw_test_enabled = false; + slsi_skb_work_deinit(&fwtest->fw_test_work); + slsi_spinlock_lock(&fwtest->fw_test_lock); + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + slsi_kfree_skb(fwtest->mlme_add_vif_req[i]); + slsi_kfree_skb(fwtest->mlme_connect_req[i]); + slsi_kfree_skb(fwtest->mlme_connect_cfm[i]); + slsi_kfree_skb(fwtest->mlme_procedure_started_ind[i]); + + fwtest->mlme_add_vif_req[i] = NULL; + fwtest->mlme_connect_req[i] = NULL; + fwtest->mlme_connect_cfm[i] = NULL; + fwtest->mlme_procedure_started_ind[i] = NULL; + } + slsi_spinlock_unlock(&fwtest->fw_test_lock); + memset(fwtest, 0x00, sizeof(struct slsi_fw_test)); +} diff --git a/drivers/net/wireless/scsc/fw_test.h b/drivers/net/wireless/scsc/fw_test.h new file mode 100755 index 000000000000..fa88d84a3383 --- /dev/null +++ b/drivers/net/wireless/scsc/fw_test.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_FW_TEST_H__ +#define __SLSI_FW_TEST_H__ + +#include "dev.h" + +struct slsi_fw_test { + struct slsi_dev *sdev; + bool fw_test_enabled; + struct slsi_skb_work fw_test_work; + struct slsi_spinlock fw_test_lock; + struct sk_buff *mlme_add_vif_req[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1]; + struct sk_buff *mlme_connect_req[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1]; + struct sk_buff *mlme_connect_cfm[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1]; + struct sk_buff *mlme_procedure_started_ind[CONFIG_SCSC_WLAN_MAX_INTERFACES + 1]; /* TODO_HARDMAC : Per AID as well as per vif */ +}; + +void slsi_fw_test_init(struct slsi_dev *sdev, struct slsi_fw_test *fwtest); +void slsi_fw_test_deinit(struct slsi_dev *sdev, struct slsi_fw_test *fwtest); +int slsi_fw_test_signal(struct slsi_dev *sdev, struct slsi_fw_test *fwtest, struct sk_buff *skb); +int slsi_fw_test_signal_with_udi_header(struct slsi_dev *sdev, struct slsi_fw_test *fwtest, struct sk_buff *skb); + +#endif /*__SLSI_FW_TEST_H__*/ diff --git a/drivers/net/wireless/scsc/hip.c b/drivers/net/wireless/scsc/hip.c new file mode 100755 index 000000000000..1d957a82b0f6 --- /dev/null +++ b/drivers/net/wireless/scsc/hip.c @@ -0,0 +1,303 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include + +#include "hip.h" +#include "debug.h" +#include "procfs.h" +#include "sap.h" +#ifdef CONFIG_SCSC_SMAPPER +#include "hip4_smapper.h" +#endif + +/* SAP implementations container. Local and static to hip */ +static struct hip_sap { + struct sap_api *sap[SAP_TOTAL]; +} hip_sap_cont; + +/* Register SAP with HIP layer */ +int slsi_hip_sap_register(struct sap_api *sap_api) +{ + u8 class = sap_api->sap_class; + + if (class >= SAP_TOTAL) + return -ENODEV; + + hip_sap_cont.sap[class] = sap_api; + + return 0; +} + +/* UNregister SAP with HIP layer */ +int slsi_hip_sap_unregister(struct sap_api *sap_api) +{ + u8 class = sap_api->sap_class; + + if (class >= SAP_TOTAL) + return -ENODEV; + + hip_sap_cont.sap[class] = NULL; + + return 0; +} + +int slsi_hip_sap_setup(struct slsi_dev *sdev) +{ + /* Execute callbacks to intorm Supported version */ + u16 version = 0; + u32 conf_hip4_ver = 0; + + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + + /* We enforce that all the SAPs are registered at this point */ + if ((!hip_sap_cont.sap[SAP_MLME]) || (!hip_sap_cont.sap[SAP_MA]) || + (!hip_sap_cont.sap[SAP_DBG]) || (!hip_sap_cont.sap[SAP_TST])) + return -ENODEV; + + if (hip_sap_cont.sap[SAP_MLME]->sap_version_supported) { + if (conf_hip4_ver == 4) + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_mlme_ver); + if (conf_hip4_ver == 5) + version = scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_mlme_ver); + if (hip_sap_cont.sap[SAP_MLME]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + if (hip_sap_cont.sap[SAP_MA]->sap_version_supported) { + if (conf_hip4_ver == 4) + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_ma_ver); + if (conf_hip4_ver == 5) + version = scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_ma_ver); + if (hip_sap_cont.sap[SAP_MA]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + if (hip_sap_cont.sap[SAP_DBG]->sap_version_supported) { + if (conf_hip4_ver == 4) + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_debug_ver); + if (conf_hip4_ver == 5) + version = scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_debug_ver); + if (hip_sap_cont.sap[SAP_DBG]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + if (hip_sap_cont.sap[SAP_TST]->sap_version_supported) { + if (conf_hip4_ver == 4) + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_test_ver); + if (conf_hip4_ver == 5) + version = scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_test_ver); + if (hip_sap_cont.sap[SAP_TST]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + /* Success */ + return 0; +} + +static int slsi_hip_service_notifier(struct notifier_block *nb, unsigned long event, void *data) +{ + struct slsi_dev *sdev = (struct slsi_dev *)data; + int i; + + if (!sdev) + return NOTIFY_BAD; + + /* We enforce that all the SAPs are registered at this point */ + if ((!hip_sap_cont.sap[SAP_MLME]) || (!hip_sap_cont.sap[SAP_MA]) || + (!hip_sap_cont.sap[SAP_DBG]) || (!hip_sap_cont.sap[SAP_TST])) + return NOTIFY_BAD; + + /* Check whether any sap is interested in the notifications */ + for (i = 0; i < SAP_TOTAL; i++) + if (hip_sap_cont.sap[i]->sap_notifier) { + if (hip_sap_cont.sap[i]->sap_notifier(sdev, event)) + return NOTIFY_BAD; + } + + switch (event) { + case SCSC_WIFI_STOP: + SLSI_INFO(sdev, "Freeze HIP4\n"); + mutex_lock(&sdev->hip.hip_mutex); + hip4_freeze(&sdev->hip4_inst); + mutex_unlock(&sdev->hip.hip_mutex); + break; + + case SCSC_WIFI_FAILURE_RESET: + SLSI_INFO(sdev, "Set HIP4 up again\n"); + mutex_lock(&sdev->hip.hip_mutex); + hip4_setup(&sdev->hip4_inst); + mutex_unlock(&sdev->hip.hip_mutex); + break; + + case SCSC_WIFI_SUSPEND: + SLSI_INFO(sdev, "Suspend HIP4\n"); + mutex_lock(&sdev->hip.hip_mutex); + hip4_suspend(&sdev->hip4_inst); + mutex_unlock(&sdev->hip.hip_mutex); + break; + + case SCSC_WIFI_RESUME: + SLSI_INFO(sdev, "Resume HIP4\n"); + mutex_lock(&sdev->hip.hip_mutex); + hip4_resume(&sdev->hip4_inst); + mutex_unlock(&sdev->hip.hip_mutex); + break; + + default: + SLSI_INFO(sdev, "Unknown event code %lu\n", event); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block cm_nb = { + .notifier_call = slsi_hip_service_notifier, +}; + +int slsi_hip_init(struct slsi_dev *sdev, struct device *dev) +{ + SLSI_UNUSED_PARAMETER(dev); + + memset(&sdev->hip4_inst, 0, sizeof(sdev->hip4_inst)); + + sdev->hip.sdev = sdev; + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_STOPPED); + mutex_init(&sdev->hip.hip_mutex); + + /* Register with the service notifier to receiver + * asynchronus messages such as SCSC_WIFI_STOP(Freeze), SCSC_WIFI_FAILURE_RESET i + */ + slsi_wlan_service_notifier_register(&cm_nb); + + return 0; +} + +void slsi_hip_deinit(struct slsi_dev *sdev) +{ + slsi_wlan_service_notifier_unregister(&cm_nb); + mutex_destroy(&sdev->hip.hip_mutex); +} + +int slsi_hip_start(struct slsi_dev *sdev) +{ + if (!sdev->maxwell_core) { + SLSI_ERR(sdev, "Maxwell core does not exist\n"); + return -EINVAL; + } + + SLSI_DBG4(sdev, SLSI_HIP_INIT_DEINIT, "[1/3]. Update HIP state (SLSI_HIP_STATE_STARTING)\n"); + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_STARTING); + + SLSI_DBG4(sdev, SLSI_HIP_INIT_DEINIT, "[2/3]. Initialise HIP\n"); + if (hip4_init(&sdev->hip4_inst)) { + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_STOPPED); + SLSI_ERR(sdev, "hip4_init failed\n"); + return -EINVAL; + } + + SLSI_DBG4(sdev, SLSI_HIP_INIT_DEINIT, "[3/3]. Update HIP state (SLSI_HIP_STATE_STARTED)\n"); + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_STARTED); + + return 0; +} + +/* SAP rx proxy */ +int slsi_hip_rx(struct slsi_dev *sdev, struct sk_buff *skb) +{ + u16 pid; + + /* We enforce that all the SAPs are registered at this point */ + if ((!hip_sap_cont.sap[SAP_MLME]) || (!hip_sap_cont.sap[SAP_MA]) || + (!hip_sap_cont.sap[SAP_DBG]) || (!hip_sap_cont.sap[SAP_TST])) + return -ENODEV; + + /* Here we push a copy of the bare RECEIVED skb data also to the + * logring as a binary record. + * Note that bypassing UDI subsystem as a whole means we are losing: + * UDI filtering / UDI Header INFO / UDI QueuesFrames Throttling / + * UDI Skb Asynchronous processing + * We keep split DATA/CTRL path. + */ + if (fapi_is_ma(skb)) + SCSC_BIN_TAG_DEBUG(BIN_WIFI_DATA_RX, skb->data, skb->len); + else + SCSC_BIN_TAG_DEBUG(BIN_WIFI_CTRL_RX, skb->data, skb->len); + /* Udi test : If pid in UDI range then pass to UDI and ignore */ + slsi_log_clients_log_signal_fast(sdev, &sdev->log_clients, skb, SLSI_LOG_DIRECTION_TO_HOST); + pid = fapi_get_u16(skb, receiver_pid); + if (pid >= SLSI_TX_PROCESS_ID_UDI_MIN && pid <= SLSI_TX_PROCESS_ID_UDI_MAX) { + slsi_kfree_skb(skb); + return 0; + } + + if (fapi_is_ma(skb)) + return hip_sap_cont.sap[SAP_MA]->sap_handler(sdev, skb); + + if (fapi_is_mlme(skb)) + return hip_sap_cont.sap[SAP_MLME]->sap_handler(sdev, skb); + + if (fapi_is_debug(skb)) + return hip_sap_cont.sap[SAP_DBG]->sap_handler(sdev, skb); + + if (fapi_is_test(skb)) + return hip_sap_cont.sap[SAP_TST]->sap_handler(sdev, skb); + + return -EIO; +} + +/* Only DATA plane will look at the returning FB to account BoT */ +int slsi_hip_tx_done(struct slsi_dev *sdev, u16 colour) +{ + return hip_sap_cont.sap[SAP_MA]->sap_txdone(sdev, colour); +} + +int slsi_hip_setup(struct slsi_dev *sdev) +{ + /* Setup hip4 after initialization */ + return hip4_setup(&sdev->hip4_inst); +} + +#ifdef CONFIG_SCSC_SMAPPER +int slsi_hip_consume_smapper_entry(struct slsi_dev *sdev, struct sk_buff *skb) +{ + return hip4_smapper_consume_entry(sdev, &sdev->hip4_inst, skb); +} + +struct sk_buff *slsi_hip_get_skb_from_smapper(struct slsi_dev *sdev, struct sk_buff *skb) +{ + return hip4_smapper_get_skb(sdev, &sdev->hip4_inst, skb); +} + +void *slsi_hip_get_skb_data_from_smapper(struct slsi_dev *sdev, struct sk_buff *skb) +{ + return hip4_smapper_get_skb_data(sdev, &sdev->hip4_inst, skb); +} +#endif + +int slsi_hip_stop(struct slsi_dev *sdev) +{ + mutex_lock(&sdev->hip.hip_mutex); + SLSI_DBG4(sdev, SLSI_HIP_INIT_DEINIT, "Update HIP state (SLSI_HIP_STATE_STOPPING)\n"); + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_STOPPING); + + hip4_deinit(&sdev->hip4_inst); + + SLSI_DBG4(sdev, SLSI_HIP_INIT_DEINIT, "Update HIP state (SLSI_HIP_STATE_STOPPED)\n"); + atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_STOPPED); + + mutex_unlock(&sdev->hip.hip_mutex); + return 0; +} diff --git a/drivers/net/wireless/scsc/hip.h b/drivers/net/wireless/scsc/hip.h new file mode 100644 index 000000000000..b8dc04a91bbd --- /dev/null +++ b/drivers/net/wireless/scsc/hip.h @@ -0,0 +1,79 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_HIP_H__ +#define __SLSI_HIP_H__ + +#include +#include +#include + +struct slsi_dev; + +/* This structure describes the chip and HIP core lib + * information that exposed to the OS layer. + */ +struct slsi_card_info { + u16 chip_id; + u32 fw_build; + u16 fw_hip_version; + u32 sdio_block_size; +}; + +/* HIP States: + * STOPPED : (default) state, avoid running the HIP + * STARTING : HIP is being initialised, avoid running the HIP + * STARTED : HIP cycles can run + * STOPPING : HIP is being de-initialised, avoid running the HIP + * BLOCKED : HIP TX CMD53 failure or WLAN subsystem crashed indication from Hydra, + * avoid running the HIP + */ +enum slsi_hip_state { + SLSI_HIP_STATE_STOPPED, + SLSI_HIP_STATE_STARTING, + SLSI_HIP_STATE_STARTED, + SLSI_HIP_STATE_STOPPING, + SLSI_HIP_STATE_BLOCKED +}; + +struct slsi_hip { + struct slsi_dev *sdev; + struct slsi_card_info card_info; + /* a std mutex */ + struct mutex hip_mutex; + + /* refer to enum slsi_hip_state */ + atomic_t hip_state; +}; + +#define SLSI_HIP_PARAM_SLOT_COUNT 2 + +int slsi_hip_init(struct slsi_dev *sdev, struct device *dev); +void slsi_hip_deinit(struct slsi_dev *sdev); + +int slsi_hip_start(struct slsi_dev *sdev); +int slsi_hip_setup(struct slsi_dev *sdev); +int slsi_hip_consume_smapper_entry(struct slsi_dev *sdev, struct sk_buff *skb); +void *slsi_hip_get_skb_data_from_smapper(struct slsi_dev *sdev, struct sk_buff *skb); +struct sk_buff *slsi_hip_get_skb_from_smapper(struct slsi_dev *sdev, struct sk_buff *skb); +int slsi_hip_stop(struct slsi_dev *sdev); + +/* Forward declaration */ +struct sap_api; +struct sk_buff; + +/* Register SAP with HIP layer */ +int slsi_hip_sap_register(struct sap_api *sap_api); +/* Unregister SAP with HIP layer */ +int slsi_hip_sap_unregister(struct sap_api *sap_api); +/* SAP rx proxy */ +int slsi_hip_rx(struct slsi_dev *sdev, struct sk_buff *skb); +/* SAP setup once we receive SAP versions */ +int slsi_hip_sap_setup(struct slsi_dev *sdev); +/* Allow the SAP to act on a buffer in the free list. */ +int slsi_hip_tx_done(struct slsi_dev *sdev, u16 colour); + +#endif diff --git a/drivers/net/wireless/scsc/hip4.c b/drivers/net/wireless/scsc/hip4.c new file mode 100644 index 000000000000..6d7a6732b025 --- /dev/null +++ b/drivers/net/wireless/scsc/hip4.c @@ -0,0 +1,2894 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_SCSC_WLAN_RX_NAPI +#include +#include +#endif + +#include "hip4.h" +#include "mbulk.h" +#include "dev.h" +#include "hip4_sampler.h" + +#ifdef CONFIG_ANDROID +#include "scsc_wifilogger_rings.h" +#endif + +#include "debug.h" + +static bool hip4_system_wq; +module_param(hip4_system_wq, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_system_wq, "Use system wq instead of named workqueue. (default: N)"); + +#ifdef CONFIG_SCSC_LOGRING +static bool hip4_dynamic_logging = true; +module_param(hip4_dynamic_logging, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_dynamic_logging, "Dynamic logging, logring is disabled if tput > hip4_qos_med_tput_in_mbps. (default: Y)"); + +static int hip4_dynamic_logging_tput_in_mbps = 150; +module_param(hip4_dynamic_logging_tput_in_mbps, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_dynamic_logging_tput_in_mbps, "throughput (in Mbps) to apply dynamic logring logging"); +#endif + +#ifdef CONFIG_SCSC_QOS +static bool hip4_qos_enable = true; +module_param(hip4_qos_enable, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_qos_enable, "enable HIP4 PM QoS. (default: Y)"); + +static int hip4_qos_max_tput_in_mbps = 300; +module_param(hip4_qos_max_tput_in_mbps, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_qos_max_tput_in_mbps, "throughput (in Mbps) to apply Max PM QoS"); + +static int hip4_qos_med_tput_in_mbps = 150; +module_param(hip4_qos_med_tput_in_mbps, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_qos_med_tput_in_mbps, "throughput (in Mbps) to apply Median PM QoS"); +#endif + +#ifdef CONFIG_SCSC_SMAPPER +static bool hip4_smapper_enable; +module_param(hip4_smapper_enable, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_smapper_enable, "enable HIP4 SMAPPER. (default: Y)"); +static bool hip4_smapper_is_enabled; +#endif + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI +/* run NAPI poll on a specific CPU (preferably a big CPU if online) */ +static int napi_select_cpu; /* CPU number */ +module_param(napi_select_cpu, int, 0644); +MODULE_PARM_DESC(napi_select_cpu, "select a specific CPU to execute NAPI poll"); +#endif + +static int max_buffered_frames = 10000; +module_param(max_buffered_frames, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_buffered_frames, "Maximum number of frames to buffer in the driver"); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI +static ktime_t intr_received_fb; +static ktime_t bh_init_fb; +static ktime_t bh_end_fb; +static ktime_t intr_received_ctrl; +static ktime_t bh_init_ctrl; +static ktime_t bh_end_ctrl; +static ktime_t intr_received_data; +static ktime_t bh_init_data; +static ktime_t bh_end_data; +#endif +static ktime_t intr_received; +static ktime_t bh_init; +static ktime_t bh_end; + +static ktime_t wdt; +static ktime_t send; +static ktime_t closing; + +enum rw { + widx, + ridx, +}; + +static u8 hip4_read_index(struct slsi_hip4 *hip, u32 q, enum rw r_w); + +/* Q mapping V3 - V4 */ +/*offset of F/W owned indices */ +#define FW_OWN_OFS (64) +/** + * HIP queue indices layout in the scoreboard (SC-505612-DD). v3 + * + * 3 2 1 0 + * +-----------------------------------+ + * +0 | Q3R | Q2R | Q1W | Q0W | Owned by the host + * +-----------------------------------+ + * +4 | | | Q5W | Q4R | Owned by the host + * +-----------------------------------+ + * + * +-----------------------------------+ + * +64 | Q3W | Q2W | Q1R | Q0R | Owned by the F/W + * +-----------------------------------+ + * +68 | | | Q5R | Q4W | Owned by the F/W + * +-----------------------------------+ + * + * The queue indcies which owned by the host are only writable by the host. + * F/W can only read them. And vice versa. + */ +static int q_idx_layout[6][2] = { + { 0, FW_OWN_OFS + 0}, /* mif_q_fh_ctl : 0 */ + { 1, FW_OWN_OFS + 1}, /* mif_q_fh_dat : 1 */ + { FW_OWN_OFS + 2, 2}, /* mif_q_fh_rfb : 2 */ + { FW_OWN_OFS + 3, 3}, /* mif_q_th_ctl : 3 */ + { FW_OWN_OFS + 4, 4}, /* mif_q_th_dat : 4 */ + { 5, FW_OWN_OFS + 5} /* mif_q_th_rfb : 5 */ +}; + +/*offset of F/W owned VIF Status */ +#define FW_OWN_VIF (96) +/** + * HIP Pause state VIF. v4. 2 bits per PEER + * + * +-----------------------------------+ + * +96 | VIF[0] Peers [15-1] | Owned by the F/W + * +-----------------------------------+ + * +100 | VIF[0] Peers [31-16] | Owned by the F/W + * +-----------------------------------+ + * +104 | VIF[1] Peers [15-1] | Owned by the F/W + * +-----------------------------------+ + * +108 | VIF[1] Peers [31-16] | Owned by the F/W + * +-----------------------------------+ + * +112 | VIF[2] Peers [15-1] | Owned by the F/W + * +-----------------------------------+ + * +116 | VIF[2] Peers [31-16] | Owned by the F/W + * +-----------------------------------+ + * +120 | VIF[3] Peers [15-1] | Owned by the F/W + * +-----------------------------------+ + * +124 | VIF[3] Peers [31-16] | Owned by the F/W + * +-----------------------------------+ + * + */ + +/* MAX_STORM. Max Interrupts allowed when platform is in suspend */ +#define MAX_STORM 5 + +/* Timeout for Wakelocks in HIP */ +#define SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS (1000) + +#ifdef CONFIG_SCSC_WLAN_DEBUG + +static u64 histogram_1; +static u64 histogram_2; +static u64 histogram_3; +static u64 histogram_4; +static u64 histogram_5; +static u64 histogram_6; +static u64 max_jitter; + +#define HISTO_1 1000 /* 1 us */ +#define HISTO_2 10000 /* 10 us */ +#define HISTO_3 100000 /* 100 us */ +#define HISTO_4 1000000 /* 1ms */ +#define HISTO_5 10000000 /* 10ms */ + +static u64 histogram_1_data; +static u64 histogram_2_data; +static u64 histogram_3_data; +static u64 histogram_4_data; +static u64 histogram_5_data; +static u64 histogram_6_data; +static u64 max_data; + +#define HISTO_1_DATA 50 /* 50 th data packets */ +#define HISTO_2_DATA 100/* 100 th data packets */ +#define HISTO_3_DATA 150/* 150 th data packets */ +#define HISTO_4_DATA 200/* 200 th data packets */ +#define HISTO_5_DATA 250/* 250 th data packets */ + +/* MAX_HISTORY_RECORDS should be power of two */ +#define MAX_HISTORY_RECORDS 32 + +#define FH 0 +#define TH 1 + +struct hip4_history { + bool dir; + u32 signal; + u32 cnt; + ktime_t last_time; +} hip4_signal_history[MAX_HISTORY_RECORDS]; + +static u32 history_record; + +/* This function should be called from atomic context */ +static void hip4_history_record_add(bool dir, u32 signal_id) +{ + struct hip4_history record; + + record = hip4_signal_history[history_record]; + + if (record.signal == signal_id && record.dir == dir) { + /* If last signal and direction is the same, increment counter */ + record.last_time = ktime_get(); + record.cnt += 1; + hip4_signal_history[history_record] = record; + return; + } + + history_record = (history_record + 1) & (MAX_HISTORY_RECORDS - 1); + + record = hip4_signal_history[history_record]; + record.dir = dir; + record.signal = signal_id; + record.cnt = 1; + record.last_time = ktime_get(); + hip4_signal_history[history_record] = record; +} + +#define HIP4_HISTORY(in_seq_file, m, fmt, arg ...) \ + do { \ + if (in_seq_file) \ + seq_printf(m, fmt, ## arg); \ + else \ + SLSI_ERR_NODEV(fmt, ## arg); \ + } while (0) + +static void hip4_history_record_print(bool in_seq_file, struct seq_file *m) +{ + struct hip4_history record; + u32 i, pos; + ktime_t old; + + old = ktime_set(0, 0); + + /* Start with the Next record to print history in order */ + pos = (history_record + 1) & (MAX_HISTORY_RECORDS - 1); + + HIP4_HISTORY(in_seq_file, m, "dir\t signal\t cnt\t last_time(ns) \t\t gap(ns)\n"); + HIP4_HISTORY(in_seq_file, m, "-----------------------------------------------------------------------------\n"); + for (i = 0; i < MAX_HISTORY_RECORDS; i++) { + record = hip4_signal_history[pos]; + /*next pos*/ + if (record.cnt) { + HIP4_HISTORY(in_seq_file, m, "%s\t 0x%04x\t %d\t %lld \t%lld\n", record.dir ? "<--TH" : "FH-->", + record.signal, record.cnt, ktime_to_ns(record.last_time), ktime_to_ns(ktime_sub(record.last_time, old))); + } + old = record.last_time; + pos = (pos + 1) & (MAX_HISTORY_RECORDS - 1); + } +} + +static int hip4_proc_show_history(struct seq_file *m, void *v) +{ + hip4_history_record_print(true, m); + return 0; +} + +static int hip4_proc_history_open(struct inode *inode, struct file *file) +{ + return single_open(file, hip4_proc_show_history, PDE_DATA(inode)); +} + +static const struct file_operations hip4_procfs_history_fops = { + .owner = THIS_MODULE, + .open = hip4_proc_history_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int hip4_proc_show(struct seq_file *m, void *v) +{ + struct slsi_hip4 *hip = m->private; + struct hip4_hip_control *hip_control; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + u8 i; + + u32 conf_hip4_ver = 0; + void *hip_ptr; + + if (!hip->hip_priv) { + seq_puts(m, "HIP4 not active\n"); + return 0; + } + + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + /* Check if the version is supported. And get the index */ + /* This is hardcoded and may change in future versions */ + if (conf_hip4_ver != 4 && conf_hip4_ver != 3) { + SLSI_ERR_NODEV("FW Version %d not supported or Hip has not been set up\n", conf_hip4_ver); + return 0; + } + + /* hip_ref contains the reference of the start of shared memory allocated for WLAN */ + /* hip_ptr is the kernel address of hip_ref*/ + hip_ptr = scsc_mx_service_mif_addr_to_ptr(sdev->service, hip->hip_ref); + /* Get hip_control pointer on shared memory */ + hip_control = (struct hip4_hip_control *)(hip_ptr + + HIP4_WLAN_CONFIG_OFFSET); + + seq_puts(m, "-----------------------------------------\n"); + seq_puts(m, "HIP4 CONFIG:\n"); + seq_puts(m, "-----------------------------------------\n"); + seq_printf(m, "config kernel addr = %p\n", hip_control); + if (conf_hip4_ver == 4) { + seq_printf(m, "hip4_version_4 addr = 0x%p\n", &hip_control->config_v4); + seq_printf(m, "magic_number = 0x%x\n", hip_control->config_v4.magic_number); + seq_printf(m, "hip_config_ver = 0x%x\n", hip_control->config_v4.hip_config_ver); + seq_printf(m, "config_len = 0x%x\n", hip_control->config_v4.config_len); + seq_printf(m, "compat_flag = 0x%x\n", hip_control->config_v4.compat_flag); + seq_printf(m, "sap_mlme_ver = 0x%x\n", hip_control->config_v4.sap_mlme_ver); + seq_printf(m, "sap_ma_ver = 0x%x\n", hip_control->config_v4.sap_ma_ver); + seq_printf(m, "sap_debug_ver = 0x%x\n", hip_control->config_v4.sap_debug_ver); + seq_printf(m, "sap_test_ver = 0x%x\n", hip_control->config_v4.sap_test_ver); + seq_printf(m, "fw_build_id = 0x%x\n", hip_control->config_v4.fw_build_id); + seq_printf(m, "fw_patch_id = 0x%x\n", hip_control->config_v4.fw_patch_id); + seq_printf(m, "unidat_req_headroom = 0x%x\n", hip_control->config_v4.unidat_req_headroom); + seq_printf(m, "unidat_req_tailroom = 0x%x\n", hip_control->config_v4.unidat_req_tailroom); + seq_printf(m, "bulk_buffer_align = 0x%x\n", hip_control->config_v4.bulk_buffer_align); + seq_printf(m, "host_cache_line = 0x%x\n", hip_control->config_v4.host_cache_line); + seq_printf(m, "host_buf_loc = 0x%x\n", hip_control->config_v4.host_buf_loc); + seq_printf(m, "host_buf_sz = 0x%x\n", hip_control->config_v4.host_buf_sz); + seq_printf(m, "fw_buf_loc = 0x%x\n", hip_control->config_v4.fw_buf_loc); + seq_printf(m, "fw_buf_sz = 0x%x\n", hip_control->config_v4.fw_buf_sz); + seq_printf(m, "mib_buf_loc = 0x%x\n", hip_control->config_v4.mib_loc); + seq_printf(m, "mib_buf_sz = 0x%x\n", hip_control->config_v4.mib_sz); + seq_printf(m, "log_config_loc = 0x%x\n", hip_control->config_v4.log_config_loc); + seq_printf(m, "log_config_sz = 0x%x\n", hip_control->config_v4.log_config_sz); + seq_printf(m, "mif_fh_int_n = 0x%x\n", hip_control->config_v4.mif_fh_int_n); + seq_printf(m, "mif_th_int_n[FH_CTRL] = 0x%x\n", hip_control->config_v4.mif_th_int_n[HIP4_MIF_Q_FH_CTRL]); + seq_printf(m, "mif_th_int_n[FH_DAT] = 0x%x\n", hip_control->config_v4.mif_th_int_n[HIP4_MIF_Q_FH_DAT]); + seq_printf(m, "mif_th_int_n[FH_RFB] = 0x%x\n", hip_control->config_v4.mif_th_int_n[HIP4_MIF_Q_FH_RFB]); + seq_printf(m, "mif_th_int_n[TH_CTRL] = 0x%x\n", hip_control->config_v4.mif_th_int_n[HIP4_MIF_Q_TH_CTRL]); + seq_printf(m, "mif_th_int_n[TH_DAT] = 0x%x\n", hip_control->config_v4.mif_th_int_n[HIP4_MIF_Q_TH_DAT]); + seq_printf(m, "mif_th_int_n[TH_RFB] = 0x%x\n", hip_control->config_v4.mif_th_int_n[HIP4_MIF_Q_TH_RFB]); + seq_printf(m, "scbrd_loc = 0x%x\n", hip_control->config_v4.scbrd_loc); + seq_printf(m, "q_num = 0x%x\n", hip_control->config_v4.q_num); + seq_printf(m, "q_len = 0x%x\n", hip_control->config_v4.q_len); + seq_printf(m, "q_idx_sz = 0x%x\n", hip_control->config_v4.q_idx_sz); + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + seq_printf(m, "q_loc[%d] = 0x%x\n", i, hip_control->config_v4.q_loc[i]); + } else if (conf_hip4_ver == 5) { + seq_printf(m, "hip4_version_5 addr = 0x%p\n", &hip_control->config_v5); + seq_printf(m, "magic_number = 0x%x\n", hip_control->config_v5.magic_number); + seq_printf(m, "hip_config_ver = 0x%x\n", hip_control->config_v5.hip_config_ver); + seq_printf(m, "config_len = 0x%x\n", hip_control->config_v5.config_len); + seq_printf(m, "compat_flag = 0x%x\n", hip_control->config_v5.compat_flag); + seq_printf(m, "sap_mlme_ver = 0x%x\n", hip_control->config_v5.sap_mlme_ver); + seq_printf(m, "sap_ma_ver = 0x%x\n", hip_control->config_v5.sap_ma_ver); + seq_printf(m, "sap_debug_ver = 0x%x\n", hip_control->config_v5.sap_debug_ver); + seq_printf(m, "sap_test_ver = 0x%x\n", hip_control->config_v5.sap_test_ver); + seq_printf(m, "fw_build_id = 0x%x\n", hip_control->config_v5.fw_build_id); + seq_printf(m, "fw_patch_id = 0x%x\n", hip_control->config_v5.fw_patch_id); + seq_printf(m, "unidat_req_headroom = 0x%x\n", hip_control->config_v5.unidat_req_headroom); + seq_printf(m, "unidat_req_tailroom = 0x%x\n", hip_control->config_v5.unidat_req_tailroom); + seq_printf(m, "bulk_buffer_align = 0x%x\n", hip_control->config_v5.bulk_buffer_align); + seq_printf(m, "host_cache_line = 0x%x\n", hip_control->config_v5.host_cache_line); + seq_printf(m, "host_buf_loc = 0x%x\n", hip_control->config_v5.host_buf_loc); + seq_printf(m, "host_buf_sz = 0x%x\n", hip_control->config_v5.host_buf_sz); + seq_printf(m, "fw_buf_loc = 0x%x\n", hip_control->config_v5.fw_buf_loc); + seq_printf(m, "fw_buf_sz = 0x%x\n", hip_control->config_v5.fw_buf_sz); + seq_printf(m, "mib_buf_loc = 0x%x\n", hip_control->config_v5.mib_loc); + seq_printf(m, "mib_buf_sz = 0x%x\n", hip_control->config_v5.mib_sz); + seq_printf(m, "log_config_loc = 0x%x\n", hip_control->config_v5.log_config_loc); + seq_printf(m, "log_config_sz = 0x%x\n", hip_control->config_v5.log_config_sz); + seq_printf(m, "mif_fh_int_n = 0x%x\n", hip_control->config_v5.mif_fh_int_n); + seq_printf(m, "mif_th_int_n = 0x%x\n", hip_control->config_v5.mif_th_int_n); + seq_printf(m, "scbrd_loc = 0x%x\n", hip_control->config_v5.scbrd_loc); + seq_printf(m, "q_num = 0x%x\n", hip_control->config_v5.q_num); + seq_printf(m, "q_len = 0x%x\n", hip_control->config_v5.q_len); + seq_printf(m, "q_idx_sz = 0x%x\n", hip_control->config_v5.q_idx_sz); + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + seq_printf(m, "q_loc[%d] = 0x%x\n", i, hip_control->config_v5.q_loc[i]); + } + seq_puts(m, "\n-----------------------------------------\n"); + seq_puts(m, "HIP4 SCOREBOARD INDEXES:\n"); + seq_puts(m, "-----------------------------------------\n"); + seq_printf(m, "ktime start %lld (ns)\n", ktime_to_ns(hip->hip_priv->stats.start)); + seq_printf(m, "ktime now %lld (ns)\n\n", ktime_to_ns(ktime_get())); + + seq_printf(m, "rx_intr_tohost 0x%x\n", hip->hip_priv->intr_tohost); + seq_printf(m, "rx_intr_fromhost 0x%x\n\n", hip->hip_priv->intr_fromhost); + + /* HIP statistics */ + seq_printf(m, "HIP IRQs: %u\n", atomic_read(&hip->hip_priv->stats.irqs)); + seq_printf(m, "HIP IRQs spurious: %u\n", atomic_read(&hip->hip_priv->stats.spurious_irqs)); + seq_printf(m, "FW debug-inds: %u\n\n", atomic_read(&sdev->debug_inds)); + + seq_puts(m, "Queue\tIndex\tFrames\n"); + seq_puts(m, "-----\t-----\t------\n"); + /* Print scoreboard */ + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) { + seq_printf(m, "Q%dW\t0x%x\t\n", i, hip4_read_index(hip, i, widx)); + seq_printf(m, "Q%dR\t0x%x\t%d\n", i, hip4_read_index(hip, i, ridx), hip->hip_priv->stats.q_num_frames[i]); + } + seq_puts(m, "\n"); + return 0; +} + +static int hip4_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, hip4_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations hip4_procfs_stats_fops = { + .owner = THIS_MODULE, + .open = hip4_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int hip4_proc_jitter_show(struct seq_file *m, void *v) +{ + seq_puts(m, "Values in ns\n"); + seq_printf(m, "<%d(ns)\t\t\t\t %lld\n", HISTO_1, histogram_1); + seq_printf(m, "%d(ns)-%d(ns)\t\t\t %lld\n", HISTO_1, HISTO_2, histogram_2); + seq_printf(m, "%d(ns)-%d(ns)\t\t\t %lld\n", HISTO_2, HISTO_3, histogram_3); + seq_printf(m, "%d(ns)-%d(ns)\t\t\t %lld\n", HISTO_3, HISTO_4, histogram_4); + seq_printf(m, "%d(ns)-%d(ns)\t\t %lld\n", HISTO_4, HISTO_5, histogram_5); + seq_printf(m, ">%d(ns)\t\t\t\t %lld\n", HISTO_5, histogram_6); + seq_printf(m, "max jitter(ns)\t\t\t\t %lld\n", max_jitter); + seq_puts(m, "--------------------------\n"); + seq_puts(m, "Packets in TH DATA Q\n"); + seq_printf(m, "<%d\t\t%lld\n", HISTO_1_DATA, histogram_1_data); + seq_printf(m, "%d-%d\t\t%lld\n", HISTO_1_DATA, HISTO_2_DATA, histogram_2_data); + seq_printf(m, "%d-%d\t\t%lld\n", HISTO_2_DATA, HISTO_3_DATA, histogram_3_data); + seq_printf(m, "%d-%d\t\t%lld\n", HISTO_3_DATA, HISTO_4_DATA, histogram_4_data); + seq_printf(m, "%d-%d\t\t%lld\n", HISTO_4_DATA, HISTO_5_DATA, histogram_5_data); + seq_printf(m, ">%d\t\t%lld\n", HISTO_5_DATA, histogram_6_data); + seq_printf(m, "max data\t%lld\n", max_data); + return 0; +} + +static int hip4_proc_jitter_open(struct inode *inode, struct file *file) +{ + return single_open(file, hip4_proc_jitter_show, PDE_DATA(inode)); +} + +static ssize_t hip4_proc_jitter_clear(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + SLSI_INFO_NODEV("Clear Histogram\n"); + + histogram_1 = 0; + histogram_2 = 0; + histogram_3 = 0; + histogram_4 = 0; + histogram_5 = 0; + histogram_6 = 0; + max_jitter = 0; + + histogram_1_data = 0; + histogram_2_data = 0; + histogram_3_data = 0; + histogram_4_data = 0; + histogram_5_data = 0; + histogram_6_data = 0; + max_data = 0; + + return count; +} + +static const struct file_operations hip4_procfs_jitter_fops = { + .owner = THIS_MODULE, + .open = hip4_proc_jitter_open, + .write = hip4_proc_jitter_clear, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) +static inline ktime_t ktime_add_ms(const ktime_t kt, const u64 msec) +{ + return ktime_add_ns(kt, msec * NSEC_PER_MSEC); +} +#endif + +#define FB_NO_SPC_NUM_RET 100 +#define FB_NO_SPC_SLEEP_MS 10 +#define FB_NO_SPC_DELAY_US 1000 + +/* Update scoreboard index */ +/* Function can be called from BH context */ +static void hip4_update_index(struct slsi_hip4 *hip, u32 q, enum rw r_w, u8 value) +{ + struct hip4_priv *hip_priv = hip->hip_priv; + + write_lock_bh(&hip_priv->rw_scoreboard); + if (hip->hip_priv->version == 5 || hip->hip_priv->version == 4) { + *((u8 *)(hip->hip_priv->scbrd_base + q_idx_layout[q][r_w])) = value; + } else { + SLSI_ERR_NODEV("Incorrect version\n"); + goto error; + } + + /* Memory barrier when updating shared mailbox/memory */ + smp_wmb(); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, q, r_w, value, 0); +error: + write_unlock_bh(&hip_priv->rw_scoreboard); +} + +/* Read scoreboard index */ +/* Function can be called from BH context */ +static u8 hip4_read_index(struct slsi_hip4 *hip, u32 q, enum rw r_w) +{ + struct hip4_priv *hip_priv = hip->hip_priv; + u32 value = 0; + + read_lock_bh(&hip_priv->rw_scoreboard); + if (hip->hip_priv->version == 5 || hip->hip_priv->version == 4) { + value = *((u8 *)(hip->hip_priv->scbrd_base + q_idx_layout[q][r_w])); + } else { + SLSI_ERR_NODEV("Incorrect version\n"); + goto error; + } + + /* Memory barrier when reading shared mailbox/memory */ + smp_rmb(); +error: + read_unlock_bh(&hip_priv->rw_scoreboard); + return value; +} + +static void hip4_dump_dbg(struct slsi_hip4 *hip, struct mbulk *m, struct sk_buff *skb, struct scsc_service *service) +{ + unsigned int i = 0; + scsc_mifram_ref ref; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; + + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + + if (conf_hip4_ver == 4) { + SLSI_ERR_NODEV("intr_tohost_fb 0x%x\n", hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_RFB]); + SLSI_ERR_NODEV("intr_tohost_ctrl 0x%x\n", hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_CTRL]); + SLSI_ERR_NODEV("intr_tohost_dat 0x%x\n", hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); + } else { + SLSI_ERR_NODEV("intr_tohost 0x%x\n", hip->hip_priv->intr_tohost); + } +#else + SLSI_ERR_NODEV("intr_tohost 0x%x\n", hip->hip_priv->intr_tohost); +#endif + SLSI_ERR_NODEV("intr_fromhost 0x%x\n", hip->hip_priv->intr_fromhost); + + /* Print scoreboard */ + for (i = 0; i < 6; i++) { + SLSI_ERR_NODEV("Q%dW 0x%x\n", i, hip4_read_index(hip, i, widx)); + SLSI_ERR_NODEV("Q%dR 0x%x\n", i, hip4_read_index(hip, i, ridx)); + } + + if (service) + scsc_mx_service_mif_dump_registers(service); + + if (m && service) { + if (scsc_mx_service_mif_ptr_to_addr(service, m, &ref)) + return; + SLSI_ERR_NODEV("m: %p 0x%x\n", m, ref); + print_hex_dump(KERN_ERR, SCSC_PREFIX "mbulk ", DUMP_PREFIX_NONE, 16, 1, m, sizeof(struct mbulk), 0); + } + if (m && mbulk_has_signal(m)) + print_hex_dump(KERN_ERR, SCSC_PREFIX "sig ", DUMP_PREFIX_NONE, 16, 1, mbulk_get_signal(m), + MBULK_SEG_SIG_BUFSIZE(m), 0); + if (skb) + print_hex_dump(KERN_ERR, SCSC_PREFIX "skb ", DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len > 0xff ? 0xff : skb->len, 0); + + SLSI_ERR_NODEV("time: wdt %lld\n", ktime_to_ns(wdt)); + SLSI_ERR_NODEV("time: send %lld\n", ktime_to_ns(send)); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + if (conf_hip4_ver == 4) { + SLSI_ERR_NODEV("time: intr_fb %lld\n", ktime_to_ns(intr_received_fb)); + SLSI_ERR_NODEV("time: bh_init_fb %lld\n", ktime_to_ns(bh_init_fb)); + SLSI_ERR_NODEV("time: bh_end_fb %lld\n", ktime_to_ns(bh_end_fb)); + SLSI_ERR_NODEV("time: intr_ctrl %lld\n", ktime_to_ns(intr_received_ctrl)); + SLSI_ERR_NODEV("time: bh_init_ctrl %lld\n", ktime_to_ns(bh_init_ctrl)); + SLSI_ERR_NODEV("time: bh_end_ctrl %lld\n", ktime_to_ns(bh_end_ctrl)); + SLSI_ERR_NODEV("time: intr_data %lld\n", ktime_to_ns(intr_received_data)); + SLSI_ERR_NODEV("time: bh_init_data %lld\n", ktime_to_ns(bh_init_data)); + SLSI_ERR_NODEV("time: bh_end_data %lld\n", ktime_to_ns(bh_end_data)); + } else { + SLSI_ERR_NODEV("time: intr %lld\n", ktime_to_ns(intr_received)); + SLSI_ERR_NODEV("time: bh_init %lld\n", ktime_to_ns(bh_init)); + SLSI_ERR_NODEV("time: bh_end %lld\n", ktime_to_ns(bh_end)); + } +#else + SLSI_ERR_NODEV("time: intr %lld\n", ktime_to_ns(intr_received)); + SLSI_ERR_NODEV("time: bh_init %lld\n", ktime_to_ns(bh_init)); + SLSI_ERR_NODEV("time: bh_end %lld\n", ktime_to_ns(bh_end)); +#endif + SLSI_ERR_NODEV("time: closing %lld\n", ktime_to_ns(closing)); +#ifdef CONFIG_SCSC_WLAN_DEBUG + /* Discard noise if it is a mbulk/skb issue */ + if (!skb && !m) + hip4_history_record_print(false, NULL); +#endif +} + +/* Transform skb to mbulk (fapi_signal + payload) */ +static struct mbulk *hip4_skb_to_mbulk(struct hip4_priv *hip, struct sk_buff *skb, bool ctrl_packet) +{ + struct mbulk *m = NULL; + void *sig = NULL, *b_data = NULL; + size_t payload = 0; + u8 pool_id = ctrl_packet ? MBULK_POOL_ID_CTRL : MBULK_POOL_ID_DATA; + u8 headroom = 0, tailroom = 0; + enum mbulk_class clas = ctrl_packet ? MBULK_CLASS_FROM_HOST_CTL : MBULK_CLASS_FROM_HOST_DAT; + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb); +#ifdef CONFIG_SCSC_WLAN_SG + u32 linear_data; + u32 offset; + u8 i; +#endif + + payload = skb->len - cb->sig_length; + + /* Get headroom/tailroom */ + headroom = hip->unidat_req_headroom; + tailroom = hip->unidat_req_tailroom; + + /* Allocate mbulk */ + if (payload > 0) { + /* If signal include payload, add headroom and tailroom */ + m = mbulk_with_signal_alloc_by_pool(pool_id, cb->colour, clas, cb->sig_length + 4, + payload + headroom + tailroom); + if (!m) + return NULL; + if (!mbulk_reserve_head(m, headroom)) + return NULL; + } else { + /* If it is only a signal do not add headroom */ + m = mbulk_with_signal_alloc_by_pool(pool_id, cb->colour, clas, cb->sig_length + 4, 0); + if (!m) + return NULL; + } + + /* Get signal handler */ + sig = mbulk_get_signal(m); + if (!sig) { + mbulk_free_virt_host(m); + return NULL; + } + + /* Copy signal */ + /* 4Bytes offset is required for FW fapi header */ + memcpy(sig + 4, skb->data, cb->sig_length); + + /* Copy payload */ + /* If the signal has payload memcpy the data */ + if (payload > 0) { + /* Get head pointer */ + b_data = mbulk_dat_rw(m); + if (!b_data) { + mbulk_free_virt_host(m); + return NULL; + } + +#ifdef CONFIG_SCSC_WLAN_SG + /* The amount of non-paged data at skb->data can be calculated as skb->len - skb->data_len. + * Helper routine: skb_headlen() . + */ + linear_data = skb_headlen(skb) - cb->sig_length; + + offset = 0; + /* Copy the linear data */ + if (linear_data > 0) { + /* Copy the linear payload skipping the signal data */ + memcpy(b_data, skb->data + cb->sig_length, linear_data); + offset = linear_data; + } + + /* Traverse fragments and copy in to linear DRAM memory */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *frag = NULL; + void *frag_va_data; + unsigned int frag_size; + + frag = &skb_shinfo(skb)->frags[i]; + WARN_ON(!frag); + if (!frag) + continue; + frag_va_data = skb_frag_address_safe(frag); + WARN_ON(!frag_va_data); + if (!frag_va_data) + continue; + frag_size = skb_frag_size(frag); + /* Copy the fragmented data */ + memcpy(b_data + offset, frag_va_data, frag_size); + offset += frag_size; + } + + /* Check whether the driver should perform the checksum */ + if (skb->ip_summed == CHECKSUM_PARTIAL) { + SLSI_DBG3_NODEV(SLSI_HIP, "CHECKSUM_PARTIAL. Driver performing checksum\n"); + if (skb->protocol == htons(ETH_P_IP)) { + struct ethhdr *mach = (struct ethhdr *)b_data; + struct iphdr *iph = (struct iphdr *)((char *)b_data + sizeof(*mach)); + unsigned int len = payload - sizeof(*mach) - (iph->ihl << 2); + + if (iph->protocol == IPPROTO_TCP) { + struct tcphdr *th = (struct tcphdr *)((char *)b_data + sizeof(*mach) + + (iph->ihl << 2)); + th->check = 0; + th->check = csum_tcpudp_magic(iph->saddr, iph->daddr, len, + IPPROTO_TCP, + csum_partial((char *)th, len, 0)); + SLSI_DBG3_NODEV(SLSI_HIP, "th->check 0x%x\n", ntohs(th->check)); + } else if (iph->protocol == IPPROTO_UDP) { + struct udphdr *uh = (struct udphdr *)((char *)b_data + sizeof(*mach) + + (iph->ihl << 2)); + uh->check = 0; + uh->check = csum_tcpudp_magic(iph->saddr, iph->daddr, len, + IPPROTO_UDP, + csum_partial((char *)uh, len, 0)); + SLSI_DBG3_NODEV(SLSI_HIP, "uh->check 0x%x\n", ntohs(uh->check)); + } + } + } +#else + /* Copy payload skipping the signal data */ + memcpy(b_data, skb->data + cb->sig_length, payload); +#endif + mbulk_append_tail(m, payload); + } + m->flag |= MBULK_F_OBOUND; + +#ifdef CONFIG_SCSC_SMAPPER + /* Clear smapper field */ + cb->skb_addr = NULL; +#endif + return m; +} + +/* Transform mbulk to skb (fapi_signal + payload) */ +static struct sk_buff *hip4_mbulk_to_skb(struct scsc_service *service, struct hip4_priv *hip_priv, struct mbulk *m, scsc_mifram_ref *to_free, bool atomic) +{ + struct slsi_skb_cb *cb; + struct mbulk *next_mbulk[MBULK_MAX_CHAIN]; + struct sk_buff *skb = NULL; + scsc_mifram_ref ref; + scsc_mifram_ref m_chain_next; + u8 free = 0; + u8 i = 0, j = 0; + u8 *p; + size_t bytes_to_alloc = 0; + + /* Get the mif ref pointer, check for incorrect mbulk */ + if (scsc_mx_service_mif_ptr_to_addr(service, m, &ref)) { + SLSI_ERR_NODEV("mbulk address conversion failed\n"); + return NULL; + } + + /* Track mbulk that should be freed */ + to_free[free++] = ref; + + bytes_to_alloc += m->sig_bufsz - 4; + bytes_to_alloc += m->len; + + /* Detect Chained mbulk to start building the chain */ + if ((MBULK_SEG_IS_CHAIN_HEAD(m)) && (MBULK_SEG_IS_CHAINED(m))) { + m_chain_next = mbulk_chain_next(m); + if (!m_chain_next) { + SLSI_ERR_NODEV("Mbulk is set MBULK_F_CHAIN_HEAD and MBULK_F_CHAIN but m_chain_next is NULL\n"); + goto cont; + } + while (1) { + /* increase number mbulks in chain */ + i++; + /* Get next_mbulk kernel address space pointer */ + next_mbulk[i - 1] = scsc_mx_service_mif_addr_to_ptr(service, m_chain_next); + if (!next_mbulk[i - 1]) { + SLSI_ERR_NODEV("First Mbulk is set as MBULK_F_CHAIN but next_mbulk is NULL\n"); + return NULL; + } + /* Track mbulk to be freed */ + to_free[free++] = m_chain_next; + bytes_to_alloc += next_mbulk[i - 1]->len; + if (MBULK_SEG_IS_CHAINED(next_mbulk[i - 1])) { + /* continue traversing the chain */ + m_chain_next = mbulk_chain_next(next_mbulk[i - 1]); + if (!m_chain_next) + break; + + if (i >= MBULK_MAX_CHAIN) { + SLSI_ERR_NODEV("Max number of chained MBULK reached\n"); + return NULL; + } + } else { + break; + } + } + } + +cont: + if (atomic) + skb = slsi_alloc_skb(bytes_to_alloc, GFP_ATOMIC); + else { + spin_unlock_bh(&hip_priv->rx_lock); + skb = slsi_alloc_skb(bytes_to_alloc, GFP_KERNEL); + spin_lock_bh(&hip_priv->rx_lock); + } + if (!skb) { + SLSI_ERR_NODEV("Error allocating skb %d bytes\n", bytes_to_alloc); + return NULL; + } + + cb = slsi_skb_cb_init(skb); + cb->sig_length = m->sig_bufsz - 4; + /* fapi_data_append adds to the data_length */ + cb->data_length = cb->sig_length; + + p = mbulk_get_signal(m); + if (!p) { + SLSI_ERR_NODEV("No signal in Mbulk\n"); + print_hex_dump(KERN_ERR, SCSC_PREFIX "mbulk ", DUMP_PREFIX_NONE, 16, 1, m, sizeof(struct mbulk), 0); + slsi_kfree_skb(skb); + return NULL; + } + /* Remove 4Bytes offset coming from FW */ + p += 4; + + /* Don't need to copy the 4Bytes header coming from the FW */ + memcpy(skb_put(skb, cb->sig_length), p, cb->sig_length); + + if (m->len) + fapi_append_data(skb, mbulk_dat_r(m), m->len); + for (j = 0; j < i; j++) + fapi_append_data(skb, mbulk_dat_r(next_mbulk[j]), next_mbulk[j]->len); + + return skb; +} + +/* Add signal reference (offset in shared memory) in the selected queue */ +/* This function should be called in atomic context. Callers should supply proper locking mechanism */ +static int hip4_q_add_signal(struct slsi_hip4 *hip, enum hip4_hip_q_conf conf, scsc_mifram_ref phy_m, struct scsc_service *service) +{ + struct hip4_hip_control *ctrl = hip->hip_control; + struct hip4_priv *hip_priv = hip->hip_priv; + u8 idx_w; + u8 idx_r; + + /* Read the current q write pointer */ + idx_w = hip4_read_index(hip, conf, widx); + /* Read the current q read pointer */ + idx_r = hip4_read_index(hip, conf, ridx); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, widx, idx_w, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, ridx, idx_r, 1); + + /* Queueu is full */ + if (idx_r == ((idx_w + 1) & (MAX_NUM - 1))) + return -ENOSPC; + + /* Update array */ + ctrl->q[conf].array[idx_w] = phy_m; + /* Memory barrier before updating shared mailbox */ + smp_wmb(); + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, phy_m, conf); +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip->hip_priv->stats.q_num_frames[conf] = hip->hip_priv->stats.q_num_frames[conf] + 1; +#endif + + /* Increase index */ + idx_w++; + idx_w &= (MAX_NUM - 1); + + /* Update the scoreboard */ + hip4_update_index(hip, conf, widx, idx_w); + + send = ktime_get(); + scsc_service_mifintrbit_bit_set(service, hip_priv->intr_fromhost, SCSC_MIFINTR_TARGET_R4); + + return 0; +} + +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE +static void hip4_watchdog(struct timer_list *t) +#else +static void hip4_watchdog(unsigned long data) +#endif +{ +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + struct hip4_priv *priv = from_timer(priv, t, watchdog); + struct slsi_hip4 *hip = priv->hip; +#else + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; +#endif + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + struct scsc_service *service; + ktime_t intr_ov; + unsigned long flags; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + bool retrigger_watchdog = true; + u32 conf_hip4_ver = 0; +#endif + + if (!hip || !sdev || !sdev->service || !hip->hip_priv) + return; + + spin_lock_irqsave(&hip->hip_priv->watchdog_lock, flags); + if (!atomic_read(&hip->hip_priv->watchdog_timer_active)) + goto exit; + + wdt = ktime_get(); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + + if (conf_hip4_ver == 4) { + /* if intr_received > wdt skip as intr has been unblocked */ + if (test_and_clear_bit(HIP4_MIF_Q_FH_RFB, hip->hip_priv->irq_bitmap)) { + intr_ov = ktime_add_ms(intr_received_fb, jiffies_to_msecs(HZ)); + if ((ktime_compare(intr_ov, wdt) < 0)) + retrigger_watchdog = false; + } + if (test_and_clear_bit(HIP4_MIF_Q_TH_CTRL, hip->hip_priv->irq_bitmap)) { + intr_ov = ktime_add_ms(intr_received_ctrl, jiffies_to_msecs(HZ)); + if ((ktime_compare(intr_ov, wdt) < 0)) + retrigger_watchdog = false; + } + if (test_and_clear_bit(HIP4_MIF_Q_TH_DAT, hip->hip_priv->irq_bitmap)) { + intr_ov = ktime_add_ms(intr_received_data, jiffies_to_msecs(HZ)); + if ((ktime_compare(intr_ov, wdt) < 0)) + retrigger_watchdog = false; + } + if (retrigger_watchdog) { + wdt = ktime_set(0, 0); + /* Retrigger WDT to check flags again in the future */ + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ / 2); + goto exit; + } + } else { + /* if intr_received > wdt skip as intr has been unblocked */ + if (ktime_compare(intr_received, wdt) > 0) { + wdt = ktime_set(0, 0); + goto exit; + } + + intr_ov = ktime_add_ms(intr_received, jiffies_to_msecs(HZ)); + + /* Check that wdt is > 1 HZ intr */ + if (!(ktime_compare(intr_ov, wdt) < 0)) { + wdt = ktime_set(0, 0); + /* Retrigger WDT to check flags again in the future */ + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ / 2); + goto exit; + } + } +#else + /* if intr_received > wdt skip as intr has been unblocked */ + if (ktime_compare(intr_received, wdt) > 0) { + wdt = ktime_set(0, 0); + goto exit; + } + + intr_ov = ktime_add_ms(intr_received, jiffies_to_msecs(HZ)); + + /* Check that wdt is > 1 HZ intr */ + if (!(ktime_compare(intr_ov, wdt) < 0)) { + wdt = ktime_set(0, 0); + /* Retrigger WDT to check flags again in the future */ + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ / 2); + goto exit; + } +#endif + + /* Unlock irq to avoid __local_bh_enable_ip warning */ + spin_unlock_irqrestore(&hip->hip_priv->watchdog_lock, flags); + hip4_dump_dbg(hip, NULL, NULL, sdev->service); + spin_lock_irqsave(&hip->hip_priv->watchdog_lock, flags); + + service = sdev->service; + + SLSI_INFO_NODEV("Hip4 watchdog triggered\n"); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + if (conf_hip4_ver == 4) { + for (u8 i = 0; i < MIF_HIP_CFG_Q_NUM; i++) { + if (hip->hip_priv->intr_tohost_mul[i] == MIF_NO_IRQ) + continue; + if (scsc_service_mifintrbit_bit_mask_status_get(service) & (1 << hip->hip_priv->intr_tohost_mul[i])) { + /* Interrupt might be pending! */ + SLSI_INFO_NODEV("%d: Interrupt Masked. Unmask to restart Interrupt processing\n", i); + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost_mul[i]); + } + } + } else { + if (scsc_service_mifintrbit_bit_mask_status_get(service) & (1 << hip->hip_priv->intr_tohost)) { + /* Interrupt might be pending! */ + SLSI_INFO_NODEV("Interrupt Masked. Unmask to restart Interrupt processing\n"); + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); + } + } +#else + if (scsc_service_mifintrbit_bit_mask_status_get(service) & (1 << hip->hip_priv->intr_tohost)) { + /* Interrupt might be pending! */ + SLSI_INFO_NODEV("Interrupt Masked. Unmask to restart Interrupt processing\n"); + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); + } +#endif +exit: + spin_unlock_irqrestore(&hip->hip_priv->watchdog_lock, flags); +} + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI +static void hip4_wq_fb(struct work_struct *data) +{ + struct hip4_priv *hip_priv = container_of(data, struct hip4_priv, intr_wq_fb); + struct slsi_hip4 *hip; + struct hip4_hip_control *ctrl; + struct scsc_service *service; + struct slsi_dev *sdev; + bool no_change = true; + u8 idx_r; + u8 idx_w; + scsc_mifram_ref ref; + void *mem; + + if (!hip_priv || !hip_priv->hip) { + SLSI_ERR_NODEV("hip_priv or hip_priv->hip is Null\n"); + return; + } + + hip = hip_priv->hip; + ctrl = hip->hip_control; + + if (!ctrl) { + SLSI_ERR_NODEV("hip->hip_control is Null\n"); + return; + } + sdev = container_of(hip, struct slsi_dev, hip4_inst); + + if (!sdev || !sdev->service) { + SLSI_ERR_NODEV("sdev or sdev->service is Null\n"); + return; + } + + spin_lock_bh(&hip_priv->rx_lock); + service = sdev->service; + SCSC_HIP4_SAMPLER_INT_BH(hip->hip_priv->minor, 2); + bh_init_fb = ktime_get(); + clear_bit(HIP4_MIF_Q_FH_RFB, hip->hip_priv->irq_bitmap); + + idx_r = hip4_read_index(hip, HIP4_MIF_Q_FH_RFB, ridx); + idx_w = hip4_read_index(hip, HIP4_MIF_Q_FH_RFB, widx); + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + if (idx_r != idx_w) { + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, widx, idx_w, 1); + } +#endif + while (idx_r != idx_w) { + struct mbulk *m; + u16 colour; + + no_change = false; + ref = ctrl->q[HIP4_MIF_Q_FH_RFB].array[idx_r]; +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_FH_RFB); +#endif + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + m = (struct mbulk *)mem; + + if (!m) { + SLSI_ERR_NODEV("FB: Mbulk is NULL\n"); + goto consume_fb_mbulk; + } + /* colour is defined as: */ + /* u16 register bits: + * 0 - do not use + * [2:1] - vif + * [7:3] - peer_index + * [10:8] - ac queue + */ + colour = ((m->clas & 0xc0) << 2) | (m->pid & 0xfe); + /* Account ONLY for data RFB */ + if ((m->pid & 0x1) == MBULK_POOL_ID_DATA) { +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + SCSC_HIP4_SAMPLER_VIF_PEER(hip->hip_priv->minor, 0, (colour & 0x6) >> 1, (colour & 0xf8) >> 3); + /* to profile round-trip */ + { + u16 host_tag; + u8 *get_host_tag; + /* This is a nasty way of getting the host_tag without involving mbulk processing + * This hostag value should also be include in the cb descriptor which goes to + * mbulk descriptor (no room left at the moment) + */ + get_host_tag = (u8 *)m; + host_tag = get_host_tag[37] << 8 | get_host_tag[36]; + SCSC_HIP4_SAMPLER_PKT_TX_FB(hip->hip_priv->minor, host_tag); + } +#endif + /* Ignore return value */ + slsi_hip_tx_done(sdev, colour); + } + mbulk_free_virt_host(m); +consume_fb_mbulk: + /* Increase index */ + idx_r++; + idx_r &= (MAX_NUM - 1); + hip4_update_index(hip, HIP4_MIF_Q_FH_RFB, ridx, idx_r); + } + + if (no_change) + atomic_inc(&hip->hip_priv->stats.spurious_irqs); + + if (!atomic_read(&hip->hip_priv->closing)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + scsc_service_mifintrbit_bit_unmask(sdev->service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_RFB]); + } + SCSC_HIP4_SAMPLER_INT_OUT_BH(hip->hip_priv->minor, 2); + +#ifdef CONFIG_ANDROID + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock_tx)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock_tx); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock_tx", WL_REASON_RX); + } +#endif + bh_end_fb = ktime_get(); + spin_unlock_bh(&hip_priv->rx_lock); +} + +static void hip4_irq_handler_fb(int irq, void *data) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + SCSC_HIP4_SAMPLER_INT(hip->hip_priv->minor, 2); + intr_received_fb = ktime_get(); + +#ifdef CONFIG_ANDROID + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock_tx)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock_tx, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock_tx", WL_REASON_RX); + } +#endif + + if (!atomic_read(&hip->hip_priv->watchdog_timer_active)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 1); + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ); + } + set_bit(HIP4_MIF_Q_FH_RFB, hip->hip_priv->irq_bitmap); + + scsc_service_mifintrbit_bit_mask(sdev->service, irq); + if (hip4_system_wq) + schedule_work(&hip->hip_priv->intr_wq_fb); + else + queue_work(hip->hip_priv->hip4_workq, &hip->hip_priv->intr_wq_fb); + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(sdev->service, irq); + SCSC_HIP4_SAMPLER_INT_OUT(hip->hip_priv->minor, 2); +} + +static void hip4_wq_ctrl(struct work_struct *data) +{ + struct hip4_priv *hip_priv = container_of(data, struct hip4_priv, intr_wq_ctrl); + struct slsi_hip4 *hip; + struct hip4_hip_control *ctrl; + struct scsc_service *service; + struct slsi_dev *sdev; + u8 retry; + bool no_change = true; + u8 idx_r; + u8 idx_w; + scsc_mifram_ref ref; + void *mem; + struct mbulk *m; +#if defined(CONFIG_SCSC_WLAN_DEBUG) || defined(CONFIG_SCSC_WLAN_HIP4_PROFILING) + int id; +#endif + + if (!hip_priv || !hip_priv->hip) { + SLSI_ERR_NODEV("hip_priv or hip_priv->hip is Null\n"); + return; + } + + hip = hip_priv->hip; + ctrl = hip->hip_control; + + if (!ctrl) { + SLSI_ERR_NODEV("hip->hip_control is Null\n"); + return; + } + sdev = container_of(hip, struct slsi_dev, hip4_inst); + + if (!sdev || !sdev->service) { + SLSI_ERR_NODEV("sdev or sdev->service is Null\n"); + return; + } + + spin_lock_bh(&hip_priv->rx_lock); + service = sdev->service; + SCSC_HIP4_SAMPLER_INT_BH(hip->hip_priv->minor, 1); + bh_init_ctrl = ktime_get(); + clear_bit(HIP4_MIF_Q_TH_CTRL, hip->hip_priv->irq_bitmap); + + idx_r = hip4_read_index(hip, HIP4_MIF_Q_TH_CTRL, ridx); + idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_CTRL, widx); + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + if (idx_r != idx_w) { + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, widx, idx_w, 1); + } +#endif + while (idx_r != idx_w) { + struct sk_buff *skb; + /* TODO: currently the max number to be freed is 2. In future + * implementations (i.e. AMPDU) this number may be bigger + * list of mbulks to be freedi + */ + scsc_mifram_ref to_free[MBULK_MAX_CHAIN + 1] = { 0 }; + u8 i = 0; + + no_change = false; + /* Catch-up with idx_w */ + ref = ctrl->q[HIP4_MIF_Q_TH_CTRL].array[idx_r]; +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_TH_CTRL); +#endif + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + m = (struct mbulk *)(mem); + /* Process Control Signal */ + skb = hip4_mbulk_to_skb(service, hip_priv, m, to_free, false); + if (!skb) { + SLSI_ERR_NODEV("Ctrl: Error parsing or allocating skb\n"); + hip4_dump_dbg(hip, m, skb, service); + goto consume_ctl_mbulk; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (m->flag & MBULK_F_WAKEUP) { + SLSI_INFO(sdev, "WIFI wakeup by MLME frame 0x%x:\n", fapi_get_sigid(skb)); + SCSC_BIN_TAG_INFO(BINARY, skb->data, skb->len > 128 ? 128 : skb->len); + } +#else + if (m->flag & MBULK_F_WAKEUP) + SLSI_INFO(sdev, "WIFI wakeup by MLME frame 0x%x\n", fapi_get_sigid(skb)); +#endif + +#if defined(CONFIG_SCSC_WLAN_DEBUG) || defined(CONFIG_SCSC_WLAN_HIP4_PROFILING) + id = fapi_get_sigid(skb); +#endif +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + /* log control signal, not unidata not debug */ + if (fapi_is_mlme(skb)) + SCSC_HIP4_SAMPLER_SIGNAL_CTRLRX(hip_priv->minor, (id & 0xff00) >> 8, id & 0xff); +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip4_history_record_add(TH, id); +#endif + if (slsi_hip_rx(sdev, skb) < 0) { + SLSI_ERR_NODEV("Ctrl: Error detected slsi_hip_rx\n"); + hip4_dump_dbg(hip, m, skb, service); + slsi_kfree_skb(skb); + } +consume_ctl_mbulk: + /* Increase index */ + idx_r++; + idx_r &= (MAX_NUM - 1); + + /* Go through the list of references to free */ + while ((ref = to_free[i++])) { + /* Set the number of retries */ + retry = FB_NO_SPC_NUM_RET; + /* return to the firmware */ + while (hip4_q_add_signal(hip, HIP4_MIF_Q_TH_RFB, ref, service) && retry > 0) { + SLSI_WARN_NODEV("Ctrl: Not enough space in FB, retry: %d/%d\n", retry, FB_NO_SPC_NUM_RET); + spin_unlock_bh(&hip_priv->rx_lock); + msleep(FB_NO_SPC_SLEEP_MS); + spin_lock_bh(&hip_priv->rx_lock); + retry--; + if (retry == 0) + SLSI_ERR_NODEV("Ctrl: FB has not been freed for %d ms\n", FB_NO_SPC_NUM_RET * FB_NO_SPC_SLEEP_MS); + SCSC_HIP4_SAMPLER_QFULL(hip_priv->minor, HIP4_MIF_Q_TH_RFB); + } + } + hip4_update_index(hip, HIP4_MIF_Q_TH_CTRL, ridx, idx_r); + } + + if (no_change) + atomic_inc(&hip->hip_priv->stats.spurious_irqs); + + if (!atomic_read(&hip->hip_priv->closing)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + scsc_service_mifintrbit_bit_unmask(sdev->service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_CTRL]); + } + SCSC_HIP4_SAMPLER_INT_OUT_BH(hip->hip_priv->minor, 1); +#ifdef CONFIG_ANDROID + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock_ctrl)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock_ctrl); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock_ctrl", WL_REASON_RX); + } +#endif + bh_end_ctrl = ktime_get(); + spin_unlock_bh(&hip_priv->rx_lock); +} + +static void hip4_irq_handler_ctrl(int irq, void *data) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + SCSC_HIP4_SAMPLER_INT(hip->hip_priv->minor, 1); + intr_received_ctrl = ktime_get(); + +#ifdef CONFIG_ANDROID + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock_ctrl)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock_ctrl, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock_ctrl", WL_REASON_RX); + } +#endif + + if (!atomic_read(&hip->hip_priv->watchdog_timer_active)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 1); + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ); + } + set_bit(HIP4_MIF_Q_TH_CTRL, hip->hip_priv->irq_bitmap); + + scsc_service_mifintrbit_bit_mask(sdev->service, irq); + + if (hip4_system_wq) + schedule_work(&hip->hip_priv->intr_wq_ctrl); + else + queue_work(hip->hip_priv->hip4_workq, &hip->hip_priv->intr_wq_ctrl); + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(sdev->service, irq); + SCSC_HIP4_SAMPLER_INT_OUT(hip->hip_priv->minor, 1); +} + +static int hip4_napi_poll(struct napi_struct *napi, int budget) +{ + struct hip4_priv *hip_priv = container_of(napi, struct hip4_priv, napi); + struct slsi_hip4 *hip; + struct hip4_hip_control *ctrl; + struct scsc_service *service; + struct slsi_dev *sdev; + +#ifdef CONFIG_SCSC_WLAN_DEBUG + int id; +#endif + u8 idx_r; + u8 idx_w; + scsc_mifram_ref ref; + void *mem; + struct mbulk *m; + u8 retry; + int work_done = 0; + + if (!hip_priv || !hip_priv->hip) { + SLSI_ERR_NODEV("hip_priv or hip_priv->hip is Null\n"); + return 0; + } + + hip = hip_priv->hip; + ctrl = hip->hip_control; + + if (!ctrl) { + SLSI_ERR_NODEV("hip->hip_control is Null\n"); + return 0; + } + sdev = container_of(hip, struct slsi_dev, hip4_inst); + + if (!sdev || !sdev->service) { + SLSI_ERR_NODEV("sdev or sdev->service is Null\n"); + return 0; + } + + spin_lock_bh(&hip_priv->rx_lock); + SCSC_HIP4_SAMPLER_INT_BH(hip->hip_priv->minor, 0); + if (ktime_compare(bh_init_data, bh_end_data) <= 0) { + bh_init_data = ktime_get(); + if (!atomic_read(&hip->hip_priv->closing)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + } + } + clear_bit(HIP4_MIF_Q_TH_DAT, hip->hip_priv->irq_bitmap); + + idx_r = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, ridx); + idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, widx); + + service = sdev->service; + + SLSI_DBG4(sdev, SLSI_RX, "todo:%d\n", (idx_w - idx_r) & 0xff); + if (idx_r == idx_w) { + SLSI_DBG4(sdev, SLSI_RX, "nothing to do, NAPI Complete\n"); + bh_end_data = ktime_get(); + napi_complete(napi); + if (!atomic_read(&hip->hip_priv->closing)) { + /* Nothing more to drain, unmask interrupt */ + scsc_service_mifintrbit_bit_unmask(sdev->service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); + } +#ifdef CONFIG_ANDROID + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock_data)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock_data); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock_data", WL_REASON_RX); + } +#endif + goto end; + } + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + if (idx_r != idx_w) { + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, widx, idx_w, 1); + } +#endif + + while (idx_r != idx_w) { + struct sk_buff *skb; + /* TODO: currently the max number to be freed is 2. In future + * implementations (i.e. AMPDU) this number may be bigger + */ + /* list of mbulks to be freed */ + scsc_mifram_ref to_free[MBULK_MAX_CHAIN + 1] = { 0 }; + u8 i = 0; + + /* Catch-up with idx_w */ + ref = ctrl->q[HIP4_MIF_Q_TH_DAT].array[idx_r]; +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_TH_DAT); +#endif + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + m = (struct mbulk *)(mem); + skb = hip4_mbulk_to_skb(service, hip_priv, m, to_free, true); + if (!skb) { + SLSI_ERR_NODEV("Dat: Error parsing or allocating skb\n"); + hip4_dump_dbg(hip, m, skb, service); + goto consume_dat_mbulk; + } +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (m->flag & MBULK_F_WAKEUP) { + SLSI_INFO(sdev, "WIFI wakeup by DATA frame:\n"); + SCSC_BIN_TAG_INFO(BINARY, skb->data, skb->len > 128 ? 128 : skb->len); + } +#else + if (m->flag & MBULK_F_WAKEUP) { + SLSI_INFO(sdev, "WIFI wakeup by DATA frame:\n"); + SCSC_BIN_TAG_INFO(BINARY, skb->data, fapi_get_siglen(skb) + ETH_HLEN); + } +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG + id = fapi_get_sigid(skb); + hip4_history_record_add(TH, id); +#endif + if (slsi_hip_rx(sdev, skb) < 0) { + SLSI_ERR_NODEV("Dat: Error detected slsi_hip_rx\n"); + hip4_dump_dbg(hip, m, skb, service); + slsi_kfree_skb(skb); + } +consume_dat_mbulk: + /* Increase index */ + idx_r++; + idx_r &= (MAX_NUM - 1); + + while ((ref = to_free[i++])) { + /* Set the number of retries */ + retry = FB_NO_SPC_NUM_RET; + while (hip4_q_add_signal(hip, HIP4_MIF_Q_TH_RFB, ref, service) && retry > 0) { + SLSI_WARN_NODEV("Dat: Not enough space in FB, retry: %d/%d\n", retry, FB_NO_SPC_NUM_RET); + udelay(FB_NO_SPC_DELAY_US); + retry--; + + if (retry == 0) + SLSI_ERR_NODEV("Dat: FB has not been freed for %d us\n", FB_NO_SPC_NUM_RET * FB_NO_SPC_DELAY_US); +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + SCSC_HIP4_SAMPLER_QFULL(hip_priv->minor, HIP4_MIF_Q_TH_RFB); +#endif + } + } + + work_done++; + if (budget == work_done) { + /* We have consumed all the bugdet */ + break; + } + } + + hip4_update_index(hip, HIP4_MIF_Q_TH_DAT, ridx, idx_r); + + if (work_done < budget) { + SLSI_DBG4(sdev, SLSI_RX, "NAPI Complete\n"); + bh_end_data = ktime_get(); + napi_complete(napi); + if (!atomic_read(&hip->hip_priv->closing)) { + /* Nothing more to drain, unmask interrupt */ + scsc_service_mifintrbit_bit_unmask(sdev->service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); + } +#ifdef CONFIG_ANDROID + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock_data)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock_data); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock_data", WL_REASON_RX); + } +#endif + } +end: + SLSI_DBG4(sdev, SLSI_RX, "work done:%d\n", work_done); + SCSC_HIP4_SAMPLER_INT_OUT_BH(hip->hip_priv->minor, 0); + spin_unlock_bh(&hip_priv->rx_lock); + return work_done; +} + +static void hip4_napi_schedule(void *data) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + + napi_schedule(&hip->hip_priv->napi); +} + +static void hip4_irq_data_tasklet(unsigned long data) +{ + /* TODO: NAPI - formalize the CPU selection code */ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + int err = -EINVAL; + + if (cpu_online(napi_select_cpu)) + err = smp_call_function_single(napi_select_cpu, hip4_napi_schedule, hip, 0); + + if (err) + napi_schedule(&hip->hip_priv->napi); +} + +static void hip4_irq_handler_dat(int irq, void *data) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + if (!hip || !sdev || !sdev->service || !hip->hip_priv) + return; + + SCSC_HIP4_SAMPLER_INT(hip->hip_priv->minor, 0); + intr_received_data = ktime_get(); + +#ifdef CONFIG_ANDROID + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock_data)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock_data, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock_data", WL_REASON_RX); + } +#endif + if (!atomic_read(&hip->hip_priv->watchdog_timer_active)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 1); + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ); + } + set_bit(HIP4_MIF_Q_TH_DAT, hip->hip_priv->irq_bitmap); + + if (napi_select_cpu) + tasklet_schedule(&hip->hip_priv->intr_tasklet); + else + napi_schedule(&hip->hip_priv->napi); + + /* Mask interrupt to avoid interrupt storm and let BH run */ + scsc_service_mifintrbit_bit_mask(sdev->service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(sdev->service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); + SCSC_HIP4_SAMPLER_INT_OUT(hip->hip_priv->minor, 0); +} + +#endif /* #ifdef CONFIG_SCSC_WLAN_RX_NAPI */ + +static bool slsi_check_rx_flowcontrol(struct slsi_dev *sdev) +{ + struct netdev_vif *ndev_vif; + int qlen = 0; + + ndev_vif = netdev_priv(sdev->netdev[SLSI_NET_INDEX_WLAN]); + if (ndev_vif) + qlen = skb_queue_len(&ndev_vif->rx_data.queue); + + if (!mutex_trylock(&sdev->netdev_remove_mutex)) + goto evaluate; + +#if defined(SLSI_NET_INDEX_P2PX_SWLAN) + if (sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]) { + ndev_vif = netdev_priv(sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]); + if (ndev_vif) + qlen += skb_queue_len(&ndev_vif->rx_data.queue); + } +#elif defined(SLSI_NET_INDEX_P2PX) + if (sdev->netdev[SLSI_NET_INDEX_P2PX]) { + ndev_vif = netdev_priv(sdev->netdev[SLSI_NET_INDEX_P2PX]); + if (ndev_vif) + qlen += skb_queue_len(&ndev_vif->rx_data.queue); + } +#endif + mutex_unlock(&sdev->netdev_remove_mutex); + +evaluate: + if (qlen > max_buffered_frames) { + SLSI_DBG1_NODEV(SLSI_HIP, "max qlen reached: %d\n", qlen); + return true; + } + SLSI_DBG3_NODEV(SLSI_HIP, "qlen %d\n", qlen); + + return false; +} + +/* Worqueue: Lower priority, run in process context. Can run simultaneously on + * different CPUs + */ +static void hip4_wq(struct work_struct *data) +{ + struct hip4_priv *hip_priv = container_of(data, struct hip4_priv, intr_wq); + struct slsi_hip4 *hip = hip_priv->hip; + struct hip4_hip_control *ctrl = hip->hip_control; + scsc_mifram_ref ref; + void *mem; + struct mbulk *m; + u8 idx_r; + u8 idx_w; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + struct scsc_service *service; + bool no_change = true; + u8 retry; + u32 packets_total; + bool rx_flowcontrol = false; + +#if defined(CONFIG_SCSC_WLAN_HIP4_PROFILING) || defined(CONFIG_SCSC_WLAN_DEBUG) + int id; +#endif + + if (!sdev || !sdev->service) { + WARN_ON(1); + return; + } + + service = sdev->service; + + atomic_set(&hip->hip_priv->in_rx, 1); + if (slsi_check_rx_flowcontrol(sdev)) + rx_flowcontrol = true; + + atomic_set(&hip->hip_priv->in_rx, 2); + spin_lock_bh(&hip_priv->rx_lock); + atomic_set(&hip->hip_priv->in_rx, 3); + + bh_init = ktime_get(); + +#ifdef CONFIG_SCSC_WLAN_DEBUG + /* Compute jitter */ + { + u64 jitter; + + jitter = ktime_to_ns(ktime_sub(bh_init, intr_received)); + + if (jitter <= HISTO_1) + histogram_1++; + else if (jitter > HISTO_1 && jitter <= HISTO_2) + histogram_2++; + else if (jitter > HISTO_2 && jitter <= HISTO_3) + histogram_3++; + else if (jitter > HISTO_3 && jitter <= HISTO_4) + histogram_4++; + else if (jitter > HISTO_4 && jitter <= HISTO_5) + histogram_5++; + else + histogram_6++; + + if (jitter > max_jitter) + max_jitter = jitter; + } +#endif + idx_r = hip4_read_index(hip, HIP4_MIF_Q_FH_RFB, ridx); + idx_w = hip4_read_index(hip, HIP4_MIF_Q_FH_RFB, widx); + + if (idx_r != idx_w) { + no_change = false; + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, widx, idx_w, 1); + } + + SCSC_HIP4_SAMPLER_INT_BH(hip_priv->minor, 2); + while (idx_r != idx_w) { + struct mbulk *m; + u16 colour; + + ref = ctrl->q[HIP4_MIF_Q_FH_RFB].array[idx_r]; + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_FH_RFB); +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip->hip_priv->stats.q_num_frames[HIP4_MIF_Q_FH_RFB] = hip->hip_priv->stats.q_num_frames[HIP4_MIF_Q_FH_RFB] + 1; +#endif + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + m = (struct mbulk *)mem; + if (!m) { + SLSI_ERR_NODEV("FB: Mbulk is NULL 0x%x\n", ref); + goto consume_fb_mbulk; + } + /* colour is defined as: */ + /* u16 register bits: + * 0 - do not use + * [2:1] - vif + * [7:3] - peer_index + * [10:8] - ac queue + */ + colour = ((m->clas & 0xc0) << 2) | (m->pid & 0xfe); + /* Account ONLY for data RFB */ + if ((m->pid & 0x1) == MBULK_POOL_ID_DATA) { +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + SCSC_HIP4_SAMPLER_VIF_PEER(hip->hip_priv->minor, 0, (colour & 0x6) >> 1, (colour & 0xf8) >> 3); + /* to profile round-trip */ + { + u16 host_tag; + u8 *get_host_tag; + /* This is a nasty way of getting the host_tag without involving mbulk processing + * This hostag value should also be include in the cb descriptor which goes to + * mbulk descriptor (no room left at the moment) + */ + get_host_tag = (u8 *)m; + host_tag = get_host_tag[37] << 8 | get_host_tag[36]; + SCSC_HIP4_SAMPLER_PKT_TX_FB(hip->hip_priv->minor, host_tag); + } +#endif + /* Ignore return value */ + slsi_hip_tx_done(sdev, colour); + } + mbulk_free_virt_host(m); +consume_fb_mbulk: + /* Increase index */ + idx_r++; + idx_r &= (MAX_NUM - 1); + hip4_update_index(hip, HIP4_MIF_Q_FH_RFB, ridx, idx_r); + } + SCSC_HIP4_SAMPLER_INT_OUT_BH(hip_priv->minor, 2); + + atomic_set(&hip->hip_priv->in_rx, 4); + + idx_r = hip4_read_index(hip, HIP4_MIF_Q_TH_CTRL, ridx); + idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_CTRL, widx); + + if (idx_r != idx_w) { + no_change = false; + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, widx, idx_w, 1); + } + + SCSC_HIP4_SAMPLER_INT_BH(hip_priv->minor, 1); + while (idx_r != idx_w) { + struct sk_buff *skb; + /* TODO: currently the max number to be freed is 2. In future + * implementations (i.e. AMPDU) this number may be bigger + * list of mbulks to be freedi + */ + scsc_mifram_ref to_free[MBULK_MAX_CHAIN + 1] = { 0 }; + u8 i = 0; + + /* Catch-up with idx_w */ + ref = ctrl->q[HIP4_MIF_Q_TH_CTRL].array[idx_r]; + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_TH_CTRL); +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip->hip_priv->stats.q_num_frames[HIP4_MIF_Q_TH_CTRL] = hip->hip_priv->stats.q_num_frames[HIP4_MIF_Q_TH_CTRL] + 1; +#endif + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + m = (struct mbulk *)(mem); + if (!m) { + SLSI_ERR_NODEV("Ctrl: Mbulk is NULL 0x%x\n", ref); + goto consume_ctl_mbulk; + } + /* Process Control Signal */ + skb = hip4_mbulk_to_skb(service, hip_priv, m, to_free, false); + if (!skb) { + SLSI_ERR_NODEV("Ctrl: Error parsing or allocating skb\n"); + hip4_dump_dbg(hip, m, skb, service); + goto consume_ctl_mbulk; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (m->flag & MBULK_F_WAKEUP) { + SLSI_INFO(sdev, "WIFI wakeup by MLME frame 0x%x:\n", fapi_get_sigid(skb)); + SCSC_BIN_TAG_INFO(BINARY, skb->data, skb->len > 128 ? 128 : skb->len); + } +#else + if (m->flag & MBULK_F_WAKEUP) + SLSI_INFO(sdev, "WIFI wakeup by MLME frame 0x%x\n", fapi_get_sigid(skb)); +#endif + +#if defined(CONFIG_SCSC_WLAN_HIP4_PROFILING) || defined(CONFIG_SCSC_WLAN_DEBUG) + id = fapi_get_sigid(skb); +#endif +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + /* log control signal, not unidata not debug */ + if (fapi_is_mlme(skb)) + SCSC_HIP4_SAMPLER_SIGNAL_CTRLRX(hip_priv->minor, (id & 0xff00) >> 8, id & 0xff); +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip4_history_record_add(TH, id); +#endif + if (slsi_hip_rx(sdev, skb) < 0) { + SLSI_ERR_NODEV("Ctrl: Error detected slsi_hip_rx\n"); + hip4_dump_dbg(hip, m, skb, service); + slsi_kfree_skb(skb); + } +consume_ctl_mbulk: + /* Increase index */ + idx_r++; + idx_r &= (MAX_NUM - 1); + + /* Go through the list of references to free */ + while ((ref = to_free[i++])) { + /* Set the number of retries */ + retry = FB_NO_SPC_NUM_RET; + /* return to the firmware */ + while (hip4_q_add_signal(hip, HIP4_MIF_Q_TH_RFB, ref, service) && retry > 0) { + SLSI_WARN_NODEV("Ctrl: Not enough space in FB, retry: %d/%d\n", retry, FB_NO_SPC_NUM_RET); + spin_unlock_bh(&hip_priv->rx_lock); + msleep(FB_NO_SPC_SLEEP_MS); + spin_lock_bh(&hip_priv->rx_lock); + retry--; + if (retry == 0) + SLSI_ERR_NODEV("Ctrl: FB has not been freed for %d ms\n", FB_NO_SPC_NUM_RET * FB_NO_SPC_SLEEP_MS); + SCSC_HIP4_SAMPLER_QFULL(hip_priv->minor, HIP4_MIF_Q_TH_RFB); + } + } + hip4_update_index(hip, HIP4_MIF_Q_TH_CTRL, ridx, idx_r); + } + + SCSC_HIP4_SAMPLER_INT_OUT_BH(hip_priv->minor, 1); + + if (rx_flowcontrol) + goto skip_data_q; + + atomic_set(&hip->hip_priv->in_rx, 5); + + idx_r = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, ridx); + idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, widx); + + if (idx_r != idx_w) { + packets_total = 0; + no_change = false; + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, widx, idx_w, 1); +#ifdef CONFIG_SCSC_WLAN_DEBUG + /* Compute DAT histogram */ + { + u8 num_packets = (idx_w - idx_r) % 256; + + if (num_packets <= HISTO_1_DATA) + histogram_1_data++; + else if (num_packets > HISTO_1_DATA && num_packets <= HISTO_2_DATA) + histogram_2_data++; + else if (num_packets > HISTO_2_DATA && num_packets <= HISTO_3_DATA) + histogram_3_data++; + else if (num_packets > HISTO_3_DATA && num_packets <= HISTO_4_DATA) + histogram_4_data++; + else if (num_packets > HISTO_4_DATA && num_packets <= HISTO_5_DATA) + histogram_5_data++; + else + histogram_6_data++; + if (num_packets > max_data) + max_data = num_packets; + } +#endif + } + + SCSC_HIP4_SAMPLER_INT_BH(hip_priv->minor, 0); + while (idx_r != idx_w) { + struct sk_buff *skb; + /* TODO: currently the max number to be freed is 2. In future + * implementations (i.e. AMPDU) this number may be bigger + */ + /* list of mbulks to be freed */ + scsc_mifram_ref to_free[MBULK_MAX_CHAIN + 1] = { 0 }; + u8 i = 0; + + packets_total++; + /* Catch-up with idx_w */ + ref = ctrl->q[HIP4_MIF_Q_TH_DAT].array[idx_r]; + SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_TH_DAT); +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip->hip_priv->stats.q_num_frames[HIP4_MIF_Q_TH_DAT] = hip->hip_priv->stats.q_num_frames[HIP4_MIF_Q_TH_DAT] + 1; +#endif + mem = scsc_mx_service_mif_addr_to_ptr(service, ref); + m = (struct mbulk *)(mem); + if (!m) { + SLSI_ERR_NODEV("Dat: Mbulk is NULL 0x%x\n", ref); + goto consume_dat_mbulk; + } + + skb = hip4_mbulk_to_skb(service, hip_priv, m, to_free, false); + if (!skb) { + SLSI_ERR_NODEV("Dat: Error parsing or allocating skb\n"); + hip4_dump_dbg(hip, m, skb, service); + goto consume_dat_mbulk; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (m->flag & MBULK_F_WAKEUP) { + SLSI_INFO(sdev, "WIFI wakeup by DATA frame:\n"); + SCSC_BIN_TAG_INFO(BINARY, skb->data, skb->len > 128 ? 128 : skb->len); + } +#else + if (m->flag & MBULK_F_WAKEUP) { + SLSI_INFO(sdev, "WIFI wakeup by DATA frame:\n"); + SCSC_BIN_TAG_INFO(BINARY, skb->data, fapi_get_siglen(skb) + ETH_HLEN); + } +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG + id = fapi_get_sigid(skb); + hip4_history_record_add(TH, id); +#endif + if (slsi_hip_rx(sdev, skb) < 0) { + SLSI_ERR_NODEV("Dat: Error detected slsi_hip_rx\n"); + hip4_dump_dbg(hip, m, skb, service); + slsi_kfree_skb(skb); + } +consume_dat_mbulk: + /* Increase index */ + idx_r++; + idx_r &= (MAX_NUM - 1); + + /* Go through the list of references to free */ + while ((ref = to_free[i++])) { + /* Set the number of retries */ + retry = FB_NO_SPC_NUM_RET; + /* return to the firmware */ + while (hip4_q_add_signal(hip, HIP4_MIF_Q_TH_RFB, ref, service) && retry > 0) { + SLSI_WARN_NODEV("Dat: Not enough space in FB, retry: %d/%d\n", retry, FB_NO_SPC_NUM_RET); + spin_unlock_bh(&hip_priv->rx_lock); + msleep(FB_NO_SPC_SLEEP_MS); + spin_lock_bh(&hip_priv->rx_lock); + retry--; + if (retry == 0) + SLSI_ERR_NODEV("Dat: FB has not been freed for %d ms\n", FB_NO_SPC_NUM_RET * FB_NO_SPC_SLEEP_MS); + SCSC_HIP4_SAMPLER_QFULL(hip_priv->minor, HIP4_MIF_Q_TH_RFB); + } + } + + hip4_update_index(hip, HIP4_MIF_Q_TH_DAT, ridx, idx_r); + + /* read again the write index */ + if ((idx_r == idx_w) && (packets_total < HIP4_POLLING_MAX_PACKETS)) { + u8 old_idx = idx_w; + + idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, widx); + if (idx_w != old_idx) { + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, ridx, idx_r, 1); + SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, widx, idx_w, 1); + } + } + } + SCSC_HIP4_SAMPLER_INT_OUT_BH(hip_priv->minor, 0); + + if (no_change) + atomic_inc(&hip->hip_priv->stats.spurious_irqs); + +skip_data_q: + if (!atomic_read(&hip->hip_priv->closing)) { + /* Reset status variable. DO THIS BEFORE UNMASKING!!!*/ + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); + } + +#ifdef CONFIG_ANDROID + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock", WL_REASON_RX); + } +#endif + bh_end = ktime_get(); + atomic_set(&hip->hip_priv->in_rx, 0); + spin_unlock_bh(&hip_priv->rx_lock); +} + +/* IRQ handler for hip4. The function runs in Interrupt context, so all the + * asssumptions related to interrupt should be applied (sleep, fast,...) + */ +static void hip4_irq_handler(int irq, void *data) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + (void)irq; /* unused */ + + if (!hip || !sdev || !sdev->service || !hip->hip_priv) + return; + + SCSC_HIP4_SAMPLER_INT(hip->hip_priv->minor, 0); + SCSC_HIP4_SAMPLER_INT(hip->hip_priv->minor, 1); + SCSC_HIP4_SAMPLER_INT(hip->hip_priv->minor, 2); + if (!atomic_read(&hip->hip_priv->rx_ready)) + goto end; + + intr_received = ktime_get(); + +#ifdef CONFIG_ANDROID + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock", WL_REASON_RX); + } +#endif + /* if wd timer is active system might be in trouble as it should be + * cleared in the BH. Ignore updating the timer + */ + if (!atomic_read(&hip->hip_priv->watchdog_timer_active)) { + atomic_set(&hip->hip_priv->watchdog_timer_active, 1); + mod_timer(&hip->hip_priv->watchdog, jiffies + HZ); + } else { + SLSI_ERR_NODEV("INT triggered while WDT is active\n"); + SLSI_ERR_NODEV("bh_init %lld\n", ktime_to_ns(bh_init)); + SLSI_ERR_NODEV("bh_end %lld\n", ktime_to_ns(bh_end)); + SLSI_ERR_NODEV("hip4_wq work_busy %d\n", work_busy(&hip->hip_priv->intr_wq)); + SLSI_ERR_NODEV("hip4_priv->in_rx %d\n", atomic_read(&hip->hip_priv->in_rx)); + } + /* If system is not in suspend, mask interrupt to avoid interrupt storm and let BH run */ + if (!atomic_read(&hip->hip_priv->in_suspend)) { + scsc_service_mifintrbit_bit_mask(sdev->service, hip->hip_priv->intr_tohost); + hip->hip_priv->storm_count = 0; + } else if (++hip->hip_priv->storm_count >= MAX_STORM) { + /* A MAX_STORM number of interrupts has been received + * when platform was in suspend. This indicates FW interrupt activity + * that should resume the hip4, so it is safe to mask to avoid + * interrupt storm. + */ + hip->hip_priv->storm_count = 0; + scsc_service_mifintrbit_bit_mask(sdev->service, hip->hip_priv->intr_tohost); + } + + atomic_inc(&hip->hip_priv->stats.irqs); + + if (hip4_system_wq) + schedule_work(&hip->hip_priv->intr_wq); + else + queue_work(hip->hip_priv->hip4_workq, &hip->hip_priv->intr_wq); +end: + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(sdev->service, hip->hip_priv->intr_tohost); + SCSC_HIP4_SAMPLER_INT_OUT(hip->hip_priv->minor, 0); + SCSC_HIP4_SAMPLER_INT_OUT(hip->hip_priv->minor, 1); + SCSC_HIP4_SAMPLER_INT_OUT(hip->hip_priv->minor, 2); +} + +#ifdef CONFIG_SCSC_QOS +static void hip4_pm_qos_work(struct work_struct *data) +{ + struct hip4_priv *hip_priv = container_of(data, struct hip4_priv, pm_qos_work); + struct slsi_hip4 *hip = hip_priv->hip; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + u8 state; + + if (!sdev || !sdev->service) { + WARN_ON(1); + return; + } + + SLSI_DBG1(sdev, SLSI_HIP, "update to state %d\n", hip_priv->pm_qos_state); + spin_lock_bh(&hip_priv->pm_qos_lock); + state = hip_priv->pm_qos_state; + spin_unlock_bh(&hip_priv->pm_qos_lock); + scsc_service_pm_qos_update_request(sdev->service, state); +} + +static void hip4_traffic_monitor_cb(void *client_ctx, u32 state, u32 tput_tx, u32 tput_rx) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)client_ctx; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + if (!sdev) + return; + + spin_lock_bh(&hip->hip_priv->pm_qos_lock); + SLSI_DBG1(sdev, SLSI_HIP, "event (state:%u, tput_tx:%u bps, tput_rx:%u bps)\n", state, tput_tx, tput_rx); + if (state == TRAFFIC_MON_CLIENT_STATE_HIGH) + hip->hip_priv->pm_qos_state = SCSC_QOS_MAX; + else if (state == TRAFFIC_MON_CLIENT_STATE_MID) + hip->hip_priv->pm_qos_state = SCSC_QOS_MED; + else + hip->hip_priv->pm_qos_state = SCSC_QOS_DISABLED; + + spin_unlock_bh(&hip->hip_priv->pm_qos_lock); + + schedule_work(&hip->hip_priv->pm_qos_work); +} +#endif + +#ifdef CONFIG_SCSC_LOGRING +static void hip4_traffic_monitor_logring_cb(void *client_ctx, u32 state, u32 tput_tx, u32 tput_rx) +{ + struct hip4_priv *hip_priv = (struct hip4_priv *)client_ctx; + struct slsi_hip4 *hip = hip_priv->hip; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + if (!sdev) + return; + + SLSI_DBG1(sdev, SLSI_HIP, "event (state:%u, tput_tx:%u bps, tput_rx:%u bps)\n", state, tput_tx, tput_rx); + if (state == TRAFFIC_MON_CLIENT_STATE_HIGH || state == TRAFFIC_MON_CLIENT_STATE_MID) { + if (hip4_dynamic_logging) + scsc_logring_enable(false); + } else { + scsc_logring_enable(true); + } +} +#endif + +int hip4_init(struct slsi_hip4 *hip) +{ + void *hip_ptr; + struct hip4_hip_control *hip_control; + struct scsc_service *service; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + scsc_mifram_ref ref, ref_scoreboard; + int i; + int ret; + u32 total_mib_len; + u32 mib_file_offset; + + if (!sdev || !sdev->service) + return -EINVAL; + + hip->hip_priv = kzalloc(sizeof(*hip->hip_priv), GFP_ATOMIC); + if (!hip->hip_priv) + return -ENOMEM; + + SLSI_INFO_NODEV("HIP4_WLAN_CONFIG_SIZE (%d)\n", HIP4_WLAN_CONFIG_SIZE); + SLSI_INFO_NODEV("HIP4_WLAN_MIB_SIZE (%d)\n", HIP4_WLAN_MIB_SIZE); + SLSI_INFO_NODEV("HIP4_WLAN_TX_DAT_SIZE (%d)\n", HIP4_WLAN_TX_DAT_SIZE); + SLSI_INFO_NODEV("HIP4_WLAN_TX_CTL_SIZE (%d)\n", HIP4_WLAN_TX_CTL_SIZE); + SLSI_INFO_NODEV("HIP4_WLAN_RX_SIZE (%d)\n", HIP4_WLAN_RX_SIZE); + SLSI_INFO_NODEV("HIP4_WLAN_TOTAL_MEM (%d)\n", HIP4_WLAN_TOTAL_MEM); + SLSI_INFO_NODEV("HIP4_DAT_SLOTS (%d)\n", HIP4_DAT_SLOTS); + SLSI_INFO_NODEV("HIP4_CTL_SLOTS (%d)\n", HIP4_CTL_SLOTS); +#ifdef CONFIG_SCSC_WLAN_DEBUG + memset(&hip->hip_priv->stats, 0, sizeof(hip->hip_priv->stats)); + hip->hip_priv->stats.start = ktime_get(); + hip->hip_priv->stats.procfs_dir = proc_mkdir("driver/hip4", NULL); + if (hip->hip_priv->stats.procfs_dir) { + proc_create_data("info", S_IRUSR | S_IRGRP, + hip->hip_priv->stats.procfs_dir, &hip4_procfs_stats_fops, hip); + proc_create_data("history", S_IRUSR | S_IRGRP, + hip->hip_priv->stats.procfs_dir, &hip4_procfs_history_fops, hip); + proc_create_data("jitter", S_IRUSR | S_IRGRP, + hip->hip_priv->stats.procfs_dir, &hip4_procfs_jitter_fops, hip); + } + + hip->hip_priv->minor = hip4_sampler_register_hip(sdev->maxwell_core); + if (hip->hip_priv->minor < SCSC_HIP4_INTERFACES) { + SLSI_DBG1_NODEV(SLSI_HIP, "registered with minor %d\n", hip->hip_priv->minor); + sdev->minor_prof = hip->hip_priv->minor; + } else { + SLSI_DBG1_NODEV(SLSI_HIP, "hip4_sampler is not enabled\n"); + } +#endif + + /* Used in the workqueue */ + hip->hip_priv->hip = hip; + + service = sdev->service; + + hip->hip_priv->host_pool_id_dat = MBULK_POOL_ID_DATA; + hip->hip_priv->host_pool_id_ctl = MBULK_POOL_ID_CTRL; + + /* hip_ref contains the reference of the start of shared memory allocated for WLAN */ + /* hip_ptr is the kernel address of hip_ref*/ + hip_ptr = scsc_mx_service_mif_addr_to_ptr(service, hip->hip_ref); + +#ifdef CONFIG_SCSC_WLAN_DEBUG + /* Configure mbulk allocator - Data QUEUES */ + ret = mbulk_pool_add(MBULK_POOL_ID_DATA, hip_ptr + HIP4_WLAN_TX_DAT_OFFSET, + hip_ptr + HIP4_WLAN_TX_DAT_OFFSET + HIP4_WLAN_TX_DAT_SIZE, + (HIP4_WLAN_TX_DAT_SIZE / HIP4_DAT_SLOTS) - sizeof(struct mbulk), 5, + hip->hip_priv->minor); + if (ret) + return ret; + + /* Configure mbulk allocator - Control QUEUES */ + ret = mbulk_pool_add(MBULK_POOL_ID_CTRL, hip_ptr + HIP4_WLAN_TX_CTL_OFFSET, + hip_ptr + HIP4_WLAN_TX_CTL_OFFSET + HIP4_WLAN_TX_CTL_SIZE, + (HIP4_WLAN_TX_CTL_SIZE / HIP4_CTL_SLOTS) - sizeof(struct mbulk), 0, + hip->hip_priv->minor); + if (ret) + return ret; +#else + /* Configure mbulk allocator - Data QUEUES */ + ret = mbulk_pool_add(MBULK_POOL_ID_DATA, hip_ptr + HIP4_WLAN_TX_DAT_OFFSET, + hip_ptr + HIP4_WLAN_TX_DAT_OFFSET + HIP4_WLAN_TX_DAT_SIZE, + (HIP4_WLAN_TX_DAT_SIZE / HIP4_DAT_SLOTS) - sizeof(struct mbulk), 5); + if (ret) + return ret; + + /* Configure mbulk allocator - Control QUEUES */ + ret = mbulk_pool_add(MBULK_POOL_ID_CTRL, hip_ptr + HIP4_WLAN_TX_CTL_OFFSET, + hip_ptr + HIP4_WLAN_TX_CTL_OFFSET + HIP4_WLAN_TX_CTL_SIZE, + (HIP4_WLAN_TX_CTL_SIZE / HIP4_CTL_SLOTS) - sizeof(struct mbulk), 0); + if (ret) + return ret; +#endif + + /* Reset hip_control table */ + memset(hip_ptr, 0, sizeof(struct hip4_hip_control)); + + /* Reset Sample q values sending 0xff */ + SCSC_HIP4_SAMPLER_RESET(hip->hip_priv->minor); + + /* Set driver is not ready to receive interrupts */ + atomic_set(&hip->hip_priv->rx_ready, 0); + + /***** VERSION 4 *******/ + /* TOHOST Handler allocator */ +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + /* Q0 FH CTRL */ + hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_CTRL] = MIF_NO_IRQ; + /* Q1 FH DATA */ + hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_DAT] = MIF_NO_IRQ; + /* Q5 TH RFB */ + hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_RFB] = MIF_NO_IRQ; + /* Q2 FH FB */ + hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_RFB] = + scsc_service_mifintrbit_register_tohost(service, hip4_irq_handler_fb, hip); + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_RFB]); + /* Q3 TH CTRL */ + hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_CTRL] = + scsc_service_mifintrbit_register_tohost(service, hip4_irq_handler_ctrl, hip); + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_CTRL]); + /* Q4 TH DAT */ + hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT] = + scsc_service_mifintrbit_register_tohost(service, hip4_irq_handler_dat, hip); + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); +#endif + /* TOHOST Handler allocator */ + hip->hip_priv->intr_tohost = + scsc_service_mifintrbit_register_tohost(service, hip4_irq_handler, hip); + + /* Mask the interrupt to prevent intr been kicked during start */ + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost); + + /* FROMHOST Handler allocator */ + hip->hip_priv->intr_fromhost = + scsc_service_mifintrbit_alloc_fromhost(service, SCSC_MIFINTR_TARGET_R4); + + /* Get hip_control pointer on shared memory */ + hip_control = (struct hip4_hip_control *)(hip_ptr + + HIP4_WLAN_CONFIG_OFFSET); + + /* Initialize scoreboard */ + if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->scoreboard, &ref_scoreboard)) + return -EFAULT; + + /* Calculate total space used by wlan*.hcf files */ + for (i = 0, total_mib_len = 0; i < SLSI_WLAN_MAX_MIB_FILE; i++) + total_mib_len += sdev->mib[i].mib_len; + + /* Copy MIB content in shared memory if any */ + /* Clear the area to avoid picking up old values */ + memset(hip_ptr + HIP4_WLAN_MIB_OFFSET, 0, HIP4_WLAN_MIB_SIZE); + + if (total_mib_len > HIP4_WLAN_MIB_SIZE) { + SLSI_ERR_NODEV("MIB size (%d), is bigger than the MIB AREA (%d). Aborting memcpy\n", total_mib_len, HIP4_WLAN_MIB_SIZE); + hip_control->config_v4.mib_loc = 0; + hip_control->config_v4.mib_sz = 0; + hip_control->config_v5.mib_loc = 0; + hip_control->config_v5.mib_sz = 0; + total_mib_len = 0; + } else if (total_mib_len) { + SLSI_INFO_NODEV("Loading MIB into shared memory, size (%d)\n", total_mib_len); + /* Load each MIB file into shared DRAM region */ + for (i = 0, mib_file_offset = 0; + i < SLSI_WLAN_MAX_MIB_FILE; + i++) { + SLSI_INFO_NODEV("Loading MIB %d into shared memory, offset (%d), size (%d), total (%d)\n", i, mib_file_offset, sdev->mib[i].mib_len, total_mib_len); + if (sdev->mib[i].mib_len) { + memcpy((u8 *)hip_ptr + HIP4_WLAN_MIB_OFFSET + mib_file_offset, sdev->mib[i].mib_data, sdev->mib[i].mib_len); + mib_file_offset += sdev->mib[i].mib_len; + } + } + hip_control->config_v4.mib_loc = hip->hip_ref + HIP4_WLAN_MIB_OFFSET; + hip_control->config_v4.mib_sz = total_mib_len; + hip_control->config_v5.mib_loc = hip->hip_ref + HIP4_WLAN_MIB_OFFSET; + hip_control->config_v5.mib_sz = total_mib_len; + } else { + hip_control->config_v4.mib_loc = 0; + hip_control->config_v4.mib_sz = 0; + hip_control->config_v5.mib_loc = 0; + hip_control->config_v5.mib_sz = 0; + } + + /* Initialize hip_control table for version 4 */ + /***** VERSION 4 *******/ + hip_control->config_v4.magic_number = 0xcaba0401; + hip_control->config_v4.hip_config_ver = 4; + hip_control->config_v4.config_len = sizeof(struct hip4_hip_config_version_4); + hip_control->config_v4.host_cache_line = 64; + hip_control->config_v4.host_buf_loc = hip->hip_ref + HIP4_WLAN_TX_OFFSET; + hip_control->config_v4.host_buf_sz = HIP4_WLAN_TX_SIZE; + hip_control->config_v4.fw_buf_loc = hip->hip_ref + HIP4_WLAN_RX_OFFSET; + hip_control->config_v4.fw_buf_sz = HIP4_WLAN_RX_SIZE; + hip_control->config_v4.log_config_loc = 0; + + hip_control->config_v4.mif_fh_int_n = hip->hip_priv->intr_fromhost; + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) { +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + hip_control->config_v4.mif_th_int_n[i] = hip->hip_priv->intr_tohost_mul[i]; +#else + hip_control->config_v4.mif_th_int_n[i] = hip->hip_priv->intr_tohost; +#endif + } + + hip_control->config_v4.scbrd_loc = (u32)ref_scoreboard; + hip_control->config_v4.q_num = 6; + hip_control->config_v4.q_len = 256; + hip_control->config_v4.q_idx_sz = 1; + /* Initialize q relative positions */ + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) { + if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->q[i].array, &ref)) + return -EFAULT; + hip_control->config_v4.q_loc[i] = (u32)ref; + } + /***** END VERSION 4 *******/ + + /* Initialize hip_control table for version 5 */ + /***** VERSION 5 *******/ + hip_control->config_v5.magic_number = 0xcaba0401; + hip_control->config_v5.hip_config_ver = 5; + hip_control->config_v5.config_len = sizeof(struct hip4_hip_config_version_5); + hip_control->config_v5.host_cache_line = 64; + hip_control->config_v5.host_buf_loc = hip->hip_ref + HIP4_WLAN_TX_OFFSET; + hip_control->config_v5.host_buf_sz = HIP4_WLAN_TX_SIZE; + hip_control->config_v5.fw_buf_loc = hip->hip_ref + HIP4_WLAN_RX_OFFSET; + hip_control->config_v5.fw_buf_sz = HIP4_WLAN_RX_SIZE; + hip_control->config_v5.log_config_loc = 0; + hip_control->config_v5.mif_fh_int_n = hip->hip_priv->intr_fromhost; + hip_control->config_v5.mif_th_int_n = hip->hip_priv->intr_tohost; + hip_control->config_v5.q_num = 6; + hip_control->config_v5.q_len = 256; + hip_control->config_v5.q_idx_sz = 1; + hip_control->config_v5.scbrd_loc = (u32)ref_scoreboard; /* scoreborad location */ + + /* Initialize q relative positions */ + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) { + if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->q[i].array, &ref)) + return -EFAULT; + hip_control->config_v5.q_loc[i] = (u32)ref; + } + /***** END VERSION 5 *******/ + + /* Initialzie hip_init configuration */ + hip_control->init.magic_number = 0xcaaa0400; + if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->config_v4, &ref)) + return -EFAULT; + hip_control->init.version_a_ref = ref; + + if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->config_v5, &ref)) + return -EFAULT; + hip_control->init.version_b_ref = ref; + /* End hip_init configuration */ + + hip->hip_control = hip_control; + hip->hip_priv->scbrd_base = &hip_control->scoreboard; + + spin_lock_init(&hip->hip_priv->rx_lock); + atomic_set(&hip->hip_priv->in_rx, 0); + spin_lock_init(&hip->hip_priv->tx_lock); + atomic_set(&hip->hip_priv->in_tx, 0); + + /* Init work structs */ + hip->hip_priv->hip4_workq = create_singlethread_workqueue("hip4_work"); + if (!hip->hip_priv->hip4_workq) { + SLSI_ERR_NODEV("Error creating singlethread_workqueue\n"); + return -ENOMEM; + } +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + tasklet_init(&hip->hip_priv->intr_tasklet, hip4_irq_data_tasklet, (unsigned long)hip); + INIT_WORK(&hip->hip_priv->intr_wq_ctrl, hip4_wq_ctrl); + INIT_WORK(&hip->hip_priv->intr_wq_fb, hip4_wq_fb); +#endif + INIT_WORK(&hip->hip_priv->intr_wq, hip4_wq); + + rwlock_init(&hip->hip_priv->rw_scoreboard); + + /* Setup watchdog timer */ + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + spin_lock_init(&hip->hip_priv->watchdog_lock); +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + timer_setup(&hip->hip_priv->watchdog, hip4_watchdog, 0); +#else + setup_timer(&hip->hip_priv->watchdog, hip4_watchdog, (unsigned long)hip); +#endif + + atomic_set(&hip->hip_priv->gmod, HIP4_DAT_SLOTS); + atomic_set(&hip->hip_priv->gactive, 1); + spin_lock_init(&hip->hip_priv->gbot_lock); + hip->hip_priv->saturated = 0; + +#ifdef CONFIG_ANDROID +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + wake_lock_init(&hip->hip_priv->hip4_wake_lock_tx, WAKE_LOCK_SUSPEND, "hip4_wake_lock_tx"); + wake_lock_init(&hip->hip_priv->hip4_wake_lock_ctrl, WAKE_LOCK_SUSPEND, "hip4_wake_lock_ctrl"); + wake_lock_init(&hip->hip_priv->hip4_wake_lock_data, WAKE_LOCK_SUSPEND, "hip4_wake_lock_data"); +#endif + wake_lock_init(&hip->hip_priv->hip4_wake_lock, WAKE_LOCK_SUSPEND, "hip4_wake_lock"); +#endif +#ifdef CONFIG_SCSC_SMAPPER + /* Init SMAPPER */ + if (hip4_smapper_enable) { + if (hip4_smapper_init(sdev, hip)) { + SLSI_ERR_NODEV("Error on hip4_smapper init\n"); + hip4_smapper_is_enabled = false; + } else { + hip4_smapper_is_enabled = true; + } + } +#endif +#ifdef CONFIG_SCSC_QOS + /* setup for PM QoS */ + spin_lock_init(&hip->hip_priv->pm_qos_lock); + + if (hip4_qos_enable) { + if (!scsc_service_pm_qos_add_request(service, SCSC_QOS_DISABLED)) { + /* register to traffic monitor for throughput events */ + if (slsi_traffic_mon_client_register(sdev, hip, TRAFFIC_MON_CLIENT_MODE_EVENTS, (hip4_qos_med_tput_in_mbps * 1000 * 1000), (hip4_qos_max_tput_in_mbps * 1000 * 1000), hip4_traffic_monitor_cb)) + SLSI_WARN(sdev, "failed to add PM QoS client to traffic monitor\n"); + else + INIT_WORK(&hip->hip_priv->pm_qos_work, hip4_pm_qos_work); + } else { + SLSI_WARN(sdev, "failed to add PM QoS request\n"); + } + } +#endif +#ifdef CONFIG_SCSC_LOGRING + /* register to traffic monitor for dynamic logring logging */ + if (slsi_traffic_mon_client_register(sdev, hip->hip_priv, TRAFFIC_MON_CLIENT_MODE_EVENTS, 0, (hip4_dynamic_logging_tput_in_mbps * 1000 * 1000), hip4_traffic_monitor_logring_cb)) + SLSI_WARN(sdev, "failed to add Logring client to traffic monitor\n"); +#endif + return 0; +} + +/** + * This function returns the number of free slots available to + * transmit control packet. + */ +int hip4_free_ctrl_slots_count(struct slsi_hip4 *hip) +{ + return mbulk_pool_get_free_count(MBULK_POOL_ID_CTRL); +} + +/** + * This function is in charge to transmit a frame through the HIP. + * It does NOT take ownership of the SKB unless it successfully transmit it; + * as a consequence skb is NOT freed on error. + * We return ENOSPC on queue related troubles in order to trigger upper + * layers of kernel to requeue/retry. + * We free ONLY locally-allocated stuff. + */ +int scsc_wifi_transmit_frame(struct slsi_hip4 *hip, bool ctrl_packet, struct sk_buff *skb) +{ + struct scsc_service *service; + scsc_mifram_ref offset; + struct mbulk *m; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + struct fapi_signal_header *fapi_header; + int ret = 0; +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb); +#endif +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif + + if (!hip || !sdev || !sdev->service || !skb || !hip->hip_priv) + return -EINVAL; + + spin_lock_bh(&hip->hip_priv->tx_lock); + atomic_set(&hip->hip_priv->in_tx, 1); + +#ifdef CONFIG_ANDROID +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + if (conf_hip4_ver == 4) { + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock_tx)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock_tx, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock_tx", WL_REASON_TX); + } + } else { + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock", WL_REASON_TX); + } + } +#else + if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock)) { + wake_lock_timeout(&hip->hip_priv->hip4_wake_lock, msecs_to_jiffies(SLSI_HIP_WAKELOCK_TIME_OUT_IN_MS)); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_TAKEN, "hip4_wake_lock", WL_REASON_TX); + } +#endif +#endif + service = sdev->service; + + fapi_header = (struct fapi_signal_header *)skb->data; + + m = hip4_skb_to_mbulk(hip->hip_priv, skb, ctrl_packet); + if (!m) { + SCSC_HIP4_SAMPLER_MFULL(hip->hip_priv->minor); + ret = -ENOSPC; + SLSI_ERR_NODEV("mbulk is NULL\n"); + goto error; + } + + if (scsc_mx_service_mif_ptr_to_addr(service, m, &offset) < 0) { + mbulk_free_virt_host(m); + ret = -EFAULT; + SLSI_ERR_NODEV("Incorrect reference memory\n"); + goto error; + } + + if (hip4_q_add_signal(hip, ctrl_packet ? HIP4_MIF_Q_FH_CTRL : HIP4_MIF_Q_FH_DAT, offset, service)) { + SCSC_HIP4_SAMPLER_QFULL(hip->hip_priv->minor, ctrl_packet ? HIP4_MIF_Q_FH_CTRL : HIP4_MIF_Q_FH_DAT); + mbulk_free_virt_host(m); + ret = -ENOSPC; + SLSI_ERR_NODEV("No space\n"); + goto error; + } + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + /* colour is defined as: */ + /* u8 register bits: + * 0 - do not use + * [2:1] - vif + * [7:3] - peer_index + */ + if (ctrl_packet) { + /* Record control signal */ + SCSC_HIP4_SAMPLER_SIGNAL_CTRLTX(hip->hip_priv->minor, (fapi_header->id & 0xff00) >> 8, fapi_header->id & 0xff); + } else { + SCSC_HIP4_SAMPLER_PKT_TX_HIP4(hip->hip_priv->minor, fapi_get_u16(skb, u.ma_unitdata_req.host_tag)); + SCSC_HIP4_SAMPLER_VIF_PEER(hip->hip_priv->minor, 1, (cb->colour & 0x6) >> 1, (cb->colour & 0xf8) >> 3); + } +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG + hip4_history_record_add(FH, fapi_header->id); +#endif + + /* Here we push a copy of the bare skb TRANSMITTED data also to the logring + * as a binary record. Note that bypassing UDI subsystem as a whole + * means we are losing: + * UDI filtering / UDI Header INFO / UDI QueuesFrames Throttling / + * UDI Skb Asynchronous processing + * We keep separated DATA/CTRL paths. + */ + if (ctrl_packet) + SCSC_BIN_TAG_DEBUG(BIN_WIFI_CTRL_TX, skb->data, skb_headlen(skb)); + else + SCSC_BIN_TAG_DEBUG(BIN_WIFI_DATA_TX, skb->data, skb_headlen(skb)); + /* slsi_log_clients_log_signal_fast: skb is copied to all the log clients */ + slsi_log_clients_log_signal_fast(sdev, &sdev->log_clients, skb, SLSI_LOG_DIRECTION_FROM_HOST); + slsi_kfree_skb(skb); + atomic_set(&hip->hip_priv->in_tx, 0); + spin_unlock_bh(&hip->hip_priv->tx_lock); + return 0; + +error: +#ifdef CONFIG_ANDROID +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + if (conf_hip4_ver == 4) { + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock_tx)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock_tx); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock_tx", WL_REASON_TX); + } + } else { + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock", WL_REASON_TX); + } + } +#else + if (wake_lock_active(&hip->hip_priv->hip4_wake_lock)) { + wake_unlock(&hip->hip_priv->hip4_wake_lock); + SCSC_WLOG_WAKELOCK(WLOG_LAZY, WL_RELEASED, "hip4_wake_lock", WL_REASON_TX); + } +#endif +#endif + atomic_set(&hip->hip_priv->in_tx, 0); + spin_unlock_bh(&hip->hip_priv->tx_lock); + return ret; +} + +/* HIP4 has been initialize, setup with values + * provided by FW + */ +int hip4_setup(struct slsi_hip4 *hip) +{ + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + struct scsc_service *service; + u32 conf_hip4_ver = 0; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + struct net_device *dev; +#endif + + if (!sdev || !sdev->service) + return -EIO; + + if (atomic_read(&sdev->hip.hip_state) != SLSI_HIP_STATE_STARTED) + return -EIO; + + service = sdev->service; + + /* Get the Version reported by the FW */ + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + /* Check if the version is supported. And get the index */ + /* This is hardcoded and may change in future versions */ + if (conf_hip4_ver != 4 && conf_hip4_ver != 3) { + SLSI_ERR_NODEV("FW Version %d not supported\n", conf_hip4_ver); + return -EIO; + } + + if (conf_hip4_ver == 4) { + hip->hip_priv->unidat_req_headroom = + scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_headroom, 4); + hip->hip_priv->unidat_req_tailroom = + scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_tailroom, 4); + hip->hip_priv->version = 4; + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + rcu_read_lock(); + /* one NAPI instance is ok for multiple netdev devices */ + dev = slsi_get_netdev_rcu(sdev, SLSI_NET_INDEX_WLAN); + if (!dev) { + SLSI_ERR(sdev, "netdev No longer exists\n"); + rcu_read_unlock(); + return -EINVAL; + } + netif_napi_add(dev, &hip->hip_priv->napi, hip4_napi_poll, NAPI_POLL_WEIGHT); + napi_enable(&hip->hip_priv->napi); + rcu_read_unlock(); +#endif + } else { + /* version 5 */ + hip->hip_priv->unidat_req_headroom = + scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_headroom, 5); + hip->hip_priv->unidat_req_tailroom = + scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_tailroom, 5); + hip->hip_priv->version = 5; + } + /* Unmask interrupts - now host should handle them */ + atomic_set(&hip->hip_priv->stats.irqs, 0); + atomic_set(&hip->hip_priv->stats.spurious_irqs, 0); + atomic_set(&sdev->debug_inds, 0); + + atomic_set(&hip->hip_priv->closing, 0); + + /* Driver is ready to process IRQ */ + atomic_set(&hip->hip_priv->rx_ready, 1); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + if (conf_hip4_ver == 4) { + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_FH_RFB]); + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_CTRL]); + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost_mul[HIP4_MIF_Q_TH_DAT]); + } else { + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); + } +#else + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); +#endif + return 0; +} + +/* On suspend hip4 needs to ensure that TH interrupts *are* unmasked */ +void hip4_suspend(struct slsi_hip4 *hip) +{ + struct slsi_dev *sdev; + struct scsc_service *service; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif + + if (!hip || !hip->hip_priv) + return; + + sdev = container_of(hip, struct slsi_dev, hip4_inst); + if (!sdev || !sdev->service) + return; + + if (atomic_read(&sdev->hip.hip_state) != SLSI_HIP_STATE_STARTED) + return; + + service = sdev->service; + + slsi_log_client_msg(sdev, UDI_DRV_SUSPEND_IND, 0, NULL); + SCSC_HIP4_SAMPLER_SUSPEND(hip->hip_priv->minor); + + atomic_set(&hip->hip_priv->in_suspend, 1); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + + if (conf_hip4_ver == 4) { + for (u8 i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + if (hip->hip_priv->intr_tohost_mul[i] != MIF_NO_IRQ) + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost_mul[i]); + } else { + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); + } +#else + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); +#endif +} + +void hip4_resume(struct slsi_hip4 *hip) +{ + struct slsi_dev *sdev; + struct scsc_service *service; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif + + if (!hip || !hip->hip_priv) + return; + + sdev = container_of(hip, struct slsi_dev, hip4_inst); + if (!sdev || !sdev->service) + return; + + if (atomic_read(&sdev->hip.hip_state) != SLSI_HIP_STATE_STARTED) + return; + + service = sdev->service; + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + + if (conf_hip4_ver == 4) { + for (u8 i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + if (hip->hip_priv->intr_tohost_mul[i] != MIF_NO_IRQ) + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost_mul[i]); + } else { + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); + } +#else + scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->intr_tohost); +#endif + + slsi_log_client_msg(sdev, UDI_DRV_RESUME_IND, 0, NULL); + SCSC_HIP4_SAMPLER_RESUME(hip->hip_priv->minor); + atomic_set(&hip->hip_priv->in_suspend, 0); +} + +void hip4_freeze(struct slsi_hip4 *hip) +{ + struct slsi_dev *sdev; + struct scsc_service *service; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif + + if (!hip || !hip->hip_priv) + return; + + sdev = container_of(hip, struct slsi_dev, hip4_inst); + if (!sdev || !sdev->service) + return; + + if (atomic_read(&sdev->hip.hip_state) != SLSI_HIP_STATE_STARTED) + return; + + service = sdev->service; + + closing = ktime_get(); + atomic_set(&hip->hip_priv->closing, 1); + + hip4_dump_dbg(hip, NULL, NULL, service); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + + if (conf_hip4_ver == 4) { + for (u8 i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + if (hip->hip_priv->intr_tohost_mul[i] != MIF_NO_IRQ) + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost_mul[i]); + + tasklet_kill(&hip->hip_priv->intr_tasklet); + cancel_work_sync(&hip->hip_priv->intr_wq_ctrl); + cancel_work_sync(&hip->hip_priv->intr_wq_fb); + } else { + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost); + cancel_work_sync(&hip->hip_priv->intr_wq); + } +#else + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost); + cancel_work_sync(&hip->hip_priv->intr_wq); +#endif + flush_workqueue(hip->hip_priv->hip4_workq); + destroy_workqueue(hip->hip_priv->hip4_workq); + atomic_set(&hip->hip_priv->rx_ready, 0); + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + + /* Deactive the wd timer prior its expiration */ + del_timer_sync(&hip->hip_priv->watchdog); +} + +void hip4_deinit(struct slsi_hip4 *hip) +{ + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + struct scsc_service *service; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif + if (!sdev || !sdev->service) + return; + + service = sdev->service; + +#ifdef CONFIG_SCSC_LOGRING + slsi_traffic_mon_client_unregister(sdev, hip->hip_priv); + /* Reenable logring in case was disabled */ + scsc_logring_enable(true); +#endif +#ifdef CONFIG_SCSC_QOS + /* de-register with traffic monitor */ + slsi_traffic_mon_client_unregister(sdev, hip); + scsc_service_pm_qos_remove_request(service); +#endif + +#ifdef CONFIG_SCSC_SMAPPER + /* Init SMAPPER */ + if (hip4_smapper_is_enabled) { + hip4_smapper_is_enabled = false; + hip4_smapper_deinit(sdev, hip); + } +#endif +#ifdef CONFIG_ANDROID +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + wake_lock_destroy(&hip->hip_priv->hip4_wake_lock_tx); + wake_lock_destroy(&hip->hip_priv->hip4_wake_lock_ctrl); + wake_lock_destroy(&hip->hip_priv->hip4_wake_lock_data); +#endif + wake_lock_destroy(&hip->hip_priv->hip4_wake_lock); +#endif + closing = ktime_get(); + atomic_set(&hip->hip_priv->closing, 1); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + for (u8 i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + if (hip->hip_priv->intr_tohost_mul[i] != MIF_NO_IRQ) + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost_mul[i]); + + tasklet_kill(&hip->hip_priv->intr_tasklet); + cancel_work_sync(&hip->hip_priv->intr_wq_ctrl); + cancel_work_sync(&hip->hip_priv->intr_wq_fb); + + for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) + if (hip->hip_priv->intr_tohost_mul[i] != MIF_NO_IRQ) + scsc_service_mifintrbit_unregister_tohost(service, hip->hip_priv->intr_tohost_mul[i]); + + /* Get the Version reported by the FW */ + conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init); + + if (conf_hip4_ver == 4) { + netif_napi_del(&hip->hip_priv->napi); + } +#endif + scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->intr_tohost); + cancel_work_sync(&hip->hip_priv->intr_wq); + scsc_service_mifintrbit_unregister_tohost(service, hip->hip_priv->intr_tohost); + + flush_workqueue(hip->hip_priv->hip4_workq); + destroy_workqueue(hip->hip_priv->hip4_workq); + + scsc_service_mifintrbit_free_fromhost(service, hip->hip_priv->intr_fromhost, SCSC_MIFINTR_TARGET_R4); + + /* If we get to that point with rx_lock/tx_lock claimed, trigger BUG() */ + WARN_ON(atomic_read(&hip->hip_priv->in_tx)); + WARN_ON(atomic_read(&hip->hip_priv->in_rx)); + + atomic_set(&hip->hip_priv->rx_ready, 0); + atomic_set(&hip->hip_priv->watchdog_timer_active, 0); + /* Deactive the wd timer prior its expiration */ + del_timer_sync(&hip->hip_priv->watchdog); + +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (hip->hip_priv->stats.procfs_dir) { + remove_proc_entry("driver/hip4/jitter", NULL); + remove_proc_entry("driver/hip4/info", NULL); + remove_proc_entry("driver/hip4/history", NULL); + remove_proc_entry("driver/hip4", NULL); + } +#endif + kfree(hip->hip_priv); + + hip->hip_priv = NULL; +} diff --git a/drivers/net/wireless/scsc/hip4.h b/drivers/net/wireless/scsc/hip4.h new file mode 100755 index 000000000000..65f3bcf2f034 --- /dev/null +++ b/drivers/net/wireless/scsc/hip4.h @@ -0,0 +1,389 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __HIP4_H__ +#define __HIP4_H__ + +/** + * This header file is the public HIP4 interface, which will be accessible by + * Wi-Fi service driver components. + * + * All struct and internal HIP functions shall be moved to a private header + * file. + */ + +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_SCSC_WLAN_RX_NAPI +#include +#endif +#ifndef SLSI_TEST_DEV +#ifdef CONFIG_ANDROID +#include +#endif +#endif +#include "mbulk.h" +#ifdef CONFIG_SCSC_SMAPPER +#include "hip4_smapper.h" +#endif + +/* Shared memory Layout + * + * |-------------------------| CONFIG + * | CONFIG + Queues | + * | --------- | + * | MIB | + * |-------------------------| TX Pool + * | TX DAT | + * | --------- | + * | TX CTL | + * |-------------------------| RX Pool + * | RX | + * |-------------------------| + */ + +/**** OFFSET SHOULD BE 4096 BYTES ALIGNED ***/ +/*** CONFIG POOL ***/ +#define HIP4_WLAN_CONFIG_OFFSET 0x00000 +#define HIP4_WLAN_CONFIG_SIZE 0x02000 /* 8 kB*/ +/*** MIB POOL ***/ +#define HIP4_WLAN_MIB_OFFSET (HIP4_WLAN_CONFIG_OFFSET + HIP4_WLAN_CONFIG_SIZE) +#define HIP4_WLAN_MIB_SIZE 0x08000 /* 32 kB*/ +/*** TX POOL ***/ +#define HIP4_WLAN_TX_OFFSET (HIP4_WLAN_MIB_OFFSET + HIP4_WLAN_MIB_SIZE) +/*** TX POOL - DAT POOL ***/ +#define HIP4_WLAN_TX_DAT_OFFSET HIP4_WLAN_TX_OFFSET +#define HIP4_WLAN_TX_DAT_SIZE 0xe6000 /* 920 kB*/ +/*** TX POOL - CTL POOL ***/ +#define HIP4_WLAN_TX_CTL_OFFSET (HIP4_WLAN_TX_DAT_OFFSET + HIP4_WLAN_TX_DAT_SIZE) +#define HIP4_WLAN_TX_CTL_SIZE 0x10000 /* 64 kB*/ +#define HIP4_WLAN_TX_SIZE (HIP4_WLAN_TX_DAT_SIZE + HIP4_WLAN_TX_CTL_SIZE) +/*** RX POOL ***/ +#define HIP4_WLAN_RX_OFFSET (HIP4_WLAN_TX_CTL_OFFSET + HIP4_WLAN_TX_CTL_SIZE) +#ifdef CONFIG_SCSC_PCIE +#define HIP4_WLAN_RX_SIZE 0x80000 /* 512 kB */ +#else +#define HIP4_WLAN_RX_SIZE 0x100000 /* 1MB */ +#endif +/*** TOTAL : CONFIG POOL + TX POOL + RX POOL ***/ +#define HIP4_WLAN_TOTAL_MEM (HIP4_WLAN_CONFIG_SIZE + HIP4_WLAN_MIB_SIZE + \ + HIP4_WLAN_TX_SIZE + HIP4_WLAN_RX_SIZE) /* 2 MB */ + +#define HIP4_POLLING_MAX_PACKETS 512 + +#define HIP4_DAT_MBULK_SIZE (2 * 1024) +#define HIP4_DAT_SLOTS (HIP4_WLAN_TX_DAT_SIZE / HIP4_DAT_MBULK_SIZE) +#define HIP4_CTL_MBULK_SIZE (2 * 1024) +#define HIP4_CTL_SLOTS (HIP4_WLAN_TX_CTL_SIZE / HIP4_CTL_MBULK_SIZE) + +#define MIF_HIP_CFG_Q_NUM 6 + +#define MIF_NO_IRQ 0xff + +/* Current versions supported by this HIP */ +#define HIP4_SUPPORTED_V1 3 +#define HIP4_SUPPORTED_V2 4 + +enum hip4_hip_q_conf { + HIP4_MIF_Q_FH_CTRL, + HIP4_MIF_Q_FH_DAT, + HIP4_MIF_Q_FH_RFB, + HIP4_MIF_Q_TH_CTRL, + HIP4_MIF_Q_TH_DAT, + HIP4_MIF_Q_TH_RFB +}; + +struct hip4_hip_config_version_4 { + /* Host owned */ + u32 magic_number; /* 0xcaba0401 */ + u16 hip_config_ver; /* Version of this configuration structure = 2*/ + u16 config_len; /* Size of this configuration structure */ + + /* FW owned */ + u32 compat_flag; /* flag of the expected driver's behaviours */ + + u16 sap_mlme_ver; /* Fapi SAP_MLME version*/ + u16 sap_ma_ver; /* Fapi SAP_MA version */ + u16 sap_debug_ver; /* Fapi SAP_DEBUG version */ + u16 sap_test_ver; /* Fapi SAP_TEST version */ + + u32 fw_build_id; /* Firmware Build Id */ + u32 fw_patch_id; /* Firmware Patch Id */ + + u8 unidat_req_headroom; /* Headroom the host shall reserve in mbulk for MA-UNITDATA.REQ signal */ + u8 unidat_req_tailroom; /* Tailroom the host shall reserve in mbulk for MA-UNITDATA.REQ signal */ + u8 bulk_buffer_align; /* 4 */ + + /* Host owned */ + u8 host_cache_line; /* 64 */ + + u32 host_buf_loc; /* location of the host buffer in MIF_ADDR */ + u32 host_buf_sz; /* in byte, size of the host buffer */ + u32 fw_buf_loc; /* location of the firmware buffer in MIF_ADDR */ + u32 fw_buf_sz; /* in byte, size of the firmware buffer */ + u32 mib_loc; /* MIB location in MIF_ADDR */ + u32 mib_sz; /* MIB size */ + u32 log_config_loc; /* Logging Configuration Location in MIF_ADDR */ + u32 log_config_sz; /* Logging Configuration Size in MIF_ADDR */ + + u8 mif_fh_int_n; /* MIF from-host interrupt bit position for all HIP queue */ + u8 reserved1[3]; + + u8 mif_th_int_n[6]; /* MIF to-host interrupt bit positions for each HIP queue */ + u8 reserved2[2]; + + u32 scbrd_loc; /* Scoreboard locatin in MIF_ADDR */ + + u16 q_num; /* 6 */ + u16 q_len; /* 256 */ + u16 q_idx_sz; /* 1 */ + u8 reserved3[2]; + + u32 q_loc[MIF_HIP_CFG_Q_NUM]; + +#ifdef CONFIG_SCSC_SMAPPER + u8 smapper_th_req; /* TH smapper request interrupt bit position */ + u8 smapper_fh_ind; /* FH smapper ind interrupt bit position */ + u8 smapper_mbox_scb; /* SMAPPER MBOX scoreboard location */ + u8 smapper_entries_banks[16]; /* num entries banks */ + u8 smapper_pow_sz[16]; /* Power of size of entry i.e. 12 = 4096B */ + u32 smapper_bank_addr[16]; /* Bank start addr */ +#else + u8 reserved_nosmapper[99]; +#endif + u8 reserved4[16]; +} __packed; + +struct hip4_hip_config_version_5 { + /* Host owned */ + u32 magic_number; /* 0xcaba0401 */ + u16 hip_config_ver; /* Version of this configuration structure = 2*/ + u16 config_len; /* Size of this configuration structure */ + + /* FW owned */ + u32 compat_flag; /* flag of the expected driver's behaviours */ + + u16 sap_mlme_ver; /* Fapi SAP_MLME version*/ + u16 sap_ma_ver; /* Fapi SAP_MA version */ + u16 sap_debug_ver; /* Fapi SAP_DEBUG version */ + u16 sap_test_ver; /* Fapi SAP_TEST version */ + + u32 fw_build_id; /* Firmware Build Id */ + u32 fw_patch_id; /* Firmware Patch Id */ + + u8 unidat_req_headroom; /* Headroom the host shall reserve in mbulk for MA-UNITDATA.REQ signal */ + u8 unidat_req_tailroom; /* Tailroom the host shall reserve in mbulk for MA-UNITDATA.REQ signal */ + u8 bulk_buffer_align; /* 4 */ + + /* Host owned */ + u8 host_cache_line; /* 64 */ + + u32 host_buf_loc; /* location of the host buffer in MIF_ADDR */ + u32 host_buf_sz; /* in byte, size of the host buffer */ + u32 fw_buf_loc; /* location of the firmware buffer in MIF_ADDR */ + u32 fw_buf_sz; /* in byte, size of the firmware buffer */ + u32 mib_loc; /* MIB location in MIF_ADDR */ + u32 mib_sz; /* MIB size */ + u32 log_config_loc; /* Logging Configuration Location in MIF_ADDR */ + u32 log_config_sz; /* Logging Configuration Size in MIF_ADDR */ + + u8 mif_fh_int_n; /* MIF from-host interrupt bit position */ + u8 mif_th_int_n; /* MIF to-host interrpt bit position */ + u8 reserved[2]; + + u32 scbrd_loc; /* Scoreboard locatin in MIF_ADDR */ + + u16 q_num; /* 6 */ + u16 q_len; /* 256 */ + u16 q_idx_sz; /* 1 */ + u8 reserved2[2]; + + u32 q_loc[MIF_HIP_CFG_Q_NUM]; + + u8 reserved3[16]; +} __packed; + +struct hip4_hip_init { + /* Host owned */ + u32 magic_number; /* 0xcaaa0400 */ + /* FW owned */ + u32 conf_hip4_ver; + /* Host owned */ + u32 version_a_ref; /* Location of Config structure A (old) */ + u32 version_b_ref; /* Location of Config structure B (new) */ +} __packed; + +#define MAX_NUM 256 +struct hip4_hip_q { + u32 array[MAX_NUM]; + u8 idx_read; /* To keep track */ + u8 idx_write; /* To keep track */ + u8 total; +} __aligned(64); + +struct hip4_hip_control { + struct hip4_hip_init init; + struct hip4_hip_config_version_5 config_v5 __aligned(32); + struct hip4_hip_config_version_4 config_v4 __aligned(32); + u32 scoreboard[256] __aligned(64); + struct hip4_hip_q q[MIF_HIP_CFG_Q_NUM] __aligned(64); +} __aligned(4096); + +struct slsi_hip4; + +/* This struct is private to the HIP implementation */ +struct hip4_priv { +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + struct tasklet_struct intr_tasklet; + struct work_struct intr_wq_ctrl; + struct work_struct intr_wq_fb; + struct napi_struct napi; +#endif + struct work_struct intr_wq; + + /* Interrupts cache < v4 */ + /* TOHOST */ + u32 intr_tohost; + + /* Interrupts cache v4 */ + u32 intr_tohost_mul[MIF_HIP_CFG_Q_NUM]; + /* FROMHOST */ + u32 intr_fromhost; + + /* For workqueue */ + struct slsi_hip4 *hip; + + /* Pool for data frames*/ + u8 host_pool_id_dat; + /* Pool for ctl frames*/ + u8 host_pool_id_ctl; + + /* rx cycle lock */ + spinlock_t rx_lock; + /* tx cycle lock */ + spinlock_t tx_lock; + + /* Scoreboard update spinlock */ + rwlock_t rw_scoreboard; + + /* Watchdog timer */ + struct timer_list watchdog; + /* wd spinlock */ + spinlock_t watchdog_lock; + /* wd timer control */ + atomic_t watchdog_timer_active; +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + DECLARE_BITMAP(irq_bitmap, MIF_HIP_CFG_Q_NUM); +#endif + +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + struct wake_lock hip4_wake_lock_tx; + struct wake_lock hip4_wake_lock_ctrl; + struct wake_lock hip4_wake_lock_data; +#endif + /* Wakelock for modem_ctl */ + struct wake_lock hip4_wake_lock; +#endif + + /* Control the hip4 init */ + atomic_t rx_ready; + + /* Control the hip4 deinit */ + atomic_t closing; + atomic_t in_tx; + atomic_t in_rx; + atomic_t in_suspend; + u32 storm_count; + + struct { + atomic_t irqs; + atomic_t spurious_irqs; + u32 q_num_frames[MIF_HIP_CFG_Q_NUM]; + ktime_t start; + struct proc_dir_entry *procfs_dir; + } stats; + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + /*minor*/ + u32 minor; +#endif + u8 unidat_req_headroom; /* Headroom the host shall reserve in mbulk for MA-UNITDATA.REQ signal */ + u8 unidat_req_tailroom; /* Tailroom the host shall reserve in mbulk for MA-UNITDATA.REQ signal */ + u32 version; /* Version of the running FW */ + void *scbrd_base; /* Scbrd_base pointer */ + + /* Global domain Q control*/ + atomic_t gactive; + atomic_t gmod; + atomic_t gcod; + int saturated; + int guard; + /* Global domain Q spinlock */ + spinlock_t gbot_lock; + +#ifdef CONFIG_SCSC_SMAPPER + /* SMAPPER */ + /* Leman has 4 Banks of 160 entries each and 4 Banks of 64 entries each. Each Tx stream is + * expected to use 2 Bank . In RSDB, 5GHz streams require higher throughput + * so the bigger banks are allocated for 5GHz streams and the + * smaller banks are for 2.4GHz streams + */ + struct hip4_smapper_bank smapper_banks[HIP4_SMAPPER_TOTAL_BANKS]; + struct hip4_smapper_control smapper_control; +#endif +#ifdef CONFIG_SCSC_QOS + /* PM QoS control */ + struct work_struct pm_qos_work; + /* PM QoS control spinlock */ + spinlock_t pm_qos_lock; + u8 pm_qos_state; +#endif + /* Collection artificats */ + void *mib_collect; + u16 mib_sz; + /* Mutex to protect hcf file collection if a tear down is triggered */ + struct mutex in_collection; + + struct workqueue_struct *hip4_workq; +}; + +struct scsc_service; + +struct slsi_hip4 { + struct hip4_priv *hip_priv; + struct hip4_hip_control *hip_control; + scsc_mifram_ref hip_ref; +}; + +/* Public functions */ +int hip4_init(struct slsi_hip4 *hip); +int hip4_setup(struct slsi_hip4 *hip); +void hip4_suspend(struct slsi_hip4 *hip); +void hip4_resume(struct slsi_hip4 *hip); +void hip4_freeze(struct slsi_hip4 *hip); +void hip4_deinit(struct slsi_hip4 *hip); +int hip4_free_ctrl_slots_count(struct slsi_hip4 *hip); + +int scsc_wifi_transmit_frame(struct slsi_hip4 *hip, bool ctrl_packet, struct sk_buff *skb); + +/* Macros for accessing information stored in the hip_config struct */ +#define scsc_wifi_get_hip_config_version_4_u8(buff_ptr, member) le16_to_cpu((((struct hip4_hip_config_version_4 *)(buff_ptr))->member)) +#define scsc_wifi_get_hip_config_version_4_u16(buff_ptr, member) le16_to_cpu((((struct hip4_hip_config_version_4 *)(buff_ptr))->member)) +#define scsc_wifi_get_hip_config_version_4_u32(buff_ptr, member) le32_to_cpu((((struct hip4_hip_config_version_4 *)(buff_ptr))->member)) +#define scsc_wifi_get_hip_config_version_5_u8(buff_ptr, member) le16_to_cpu((((struct hip4_hip_config_version_5 *)(buff_ptr))->member)) +#define scsc_wifi_get_hip_config_version_5_u16(buff_ptr, member) le16_to_cpu((((struct hip4_hip_config_version_5 *)(buff_ptr))->member)) +#define scsc_wifi_get_hip_config_version_5_u32(buff_ptr, member) le32_to_cpu((((struct hip4_hip_config_version_5 *)(buff_ptr))->member)) +#define scsc_wifi_get_hip_config_u8(buff_ptr, member, ver) le16_to_cpu((((struct hip4_hip_config_version_##ver *)(buff_ptr->config_v##ver))->member)) +#define scsc_wifi_get_hip_config_u16(buff_ptr, member, ver) le16_to_cpu((((struct hip4_hip_config_version_##ver *)(buff_ptr->config_v##ver))->member)) +#define scsc_wifi_get_hip_config_u32(buff_ptr, member, ver) le32_to_cpu((((struct hip4_hip_config_version_##ver *)(buff_ptr->config_v##ver))->member)) +#define scsc_wifi_get_hip_config_version(buff_ptr) le32_to_cpu((((struct hip4_hip_init *)(buff_ptr))->conf_hip4_ver)) + +#endif diff --git a/drivers/net/wireless/scsc/hip4_sampler.c b/drivers/net/wireless/scsc/hip4_sampler.c new file mode 100755 index 000000000000..791950f2e6ec --- /dev/null +++ b/drivers/net/wireless/scsc/hip4_sampler.c @@ -0,0 +1,945 @@ +/***************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif +#include +#include +#include "hip4_sampler.h" + +#include "debug.h" + +struct hip4_record { + u32 record_num; + ktime_t ts; + u32 record; + u32 record2; +} __packed; + +static atomic_t in_read; + +/* Create a global spinlock for all the instances */ +/* It is less efficent, but we simplify the implementation */ +static spinlock_t g_spinlock; + +static bool hip4_sampler_enable = true; +module_param(hip4_sampler_enable, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_enable, "Enable hip4_sampler_enable. Run-time option - (default: Y)"); + +static bool hip4_sampler_dynamic = true; +module_param(hip4_sampler_dynamic, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_dynamic, "Enable hip4_sampler dynamic adaptation based on TPUT. Run-time option - (default: Y)"); + +static int hip4_sampler_kfifo_len = 128 * 1024; +module_param(hip4_sampler_kfifo_len, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_kfifo_len, "Streaming fifo buffer length in num of records- default: 262144 Max: 262144. Loaded at /dev open"); + +static int hip4_sampler_static_kfifo_len = 128 * 1024; +module_param(hip4_sampler_static_kfifo_len, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_static_kfifo_len, "Offline fifo buffer length in num of records- default: 262144 Max: 262144. Loaded at /dev open"); + +bool hip4_sampler_sample_q = true; +module_param(hip4_sampler_sample_q, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_q, "Sample Queues. Default: Y. Run time option"); + +bool hip4_sampler_sample_qref = true; +module_param(hip4_sampler_sample_qref, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_qref, "Sample Queue References. Default: Y. Run time option"); + +bool hip4_sampler_sample_int = true; +module_param(hip4_sampler_sample_int, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_int, "Sample WQ/Tasklet Intr BH in/out. Default: Y. Run time option"); + +bool hip4_sampler_sample_fapi = true; +module_param(hip4_sampler_sample_fapi, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_fapi, "Sample FAPI ctrl signals. Default: Y. Run time option"); + +bool hip4_sampler_sample_through = true; +module_param(hip4_sampler_sample_through, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_through, "Sample throughput. Default: Y. Run time option"); + +bool hip4_sampler_sample_tcp = true; +module_param(hip4_sampler_sample_tcp, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_tcp, "Sample TCP streams. Default: Y. Run time option"); + +bool hip4_sampler_sample_start_stop_q = true; +module_param(hip4_sampler_sample_start_stop_q, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_start_stop_q, "Sample Stop/Start queues. Default: Y. Run time option"); + +bool hip4_sampler_sample_mbulk = true; +module_param(hip4_sampler_sample_mbulk, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_mbulk, "Sample Mbulk counter. Default: Y. Run time option"); + +bool hip4_sampler_sample_qfull; +module_param(hip4_sampler_sample_qfull, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_qfull, "Sample Q full event. Default: N. Run time option"); + +bool hip4_sampler_sample_mfull = true; +module_param(hip4_sampler_sample_mfull, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_sample_mfull, "Sample Mbulk full event. Default: Y. Run time option"); + +bool hip4_sampler_vif = true; +module_param(hip4_sampler_vif, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_vif, "Sample VIF. Default: Y. Run time option"); + +bool hip4_sampler_bot = true; +module_param(hip4_sampler_bot, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_bot, "Sample BOT. Default: Y. Run time option"); + +bool hip4_sampler_pkt_tx = true; +module_param(hip4_sampler_pkt_tx, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_pkt_tx, "Track TX Data packet TX->HIP4->FB. Default: Y. Run time option"); + +bool hip4_sampler_suspend_resume = true; +module_param(hip4_sampler_suspend_resume, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hip4_sampler_suspend_resume, "Sample Suspend/Resume events. Default: Y. Run time option"); + +#define HIP4_TPUT_HLIMIT 400000000 /* 400Mbps */ +#define HIP4_TPUT_LLIMIT 350000000 /* 350Mbps */ +#define HIP4_TPUT_HSECONDS 1 +#define HIP4_TPUT_LSECONDS 5 + +static bool hip4_sampler_sample_q_hput; +static bool hip4_sampler_sample_qref_hput; +static bool hip4_sampler_sample_int_hput; +static bool hip4_sampler_sample_fapi_hput; +/* static bool hip4_sampler_sample_through_hput; */ +/* static bool hip4_sampler_sample_start_stop_q_hput; */ +/* static bool hip4_sampler_sample_mbulk_hput; */ +/* static bool hip4_sampler_sample_qfull_hput; */ +/* static bool hip4_sampler_sample_mfull_hput; */ +static bool hip4_sampler_vif_hput; +static bool hip4_sampler_bot_hput; +static bool hip4_sampler_pkt_tx_hput; +static bool hip4_sampler_suspend_resume_hput; + +static bool hip4_sampler_in_htput; +static u16 hip4_sampler_in_htput_seconds; + +#define DRV_NAME "hip4_sampler" +#define DEVICE_NAME "hip4_sampler" + +#ifndef VM_RESERVED +#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP) +#endif + +#define VER_MAJOR 0 +#define VER_MINOR 0 + +DECLARE_BITMAP(bitmap_hip4_sampler_minor, SCSC_HIP4_DEBUG_INTERFACES); + +enum hip4_dg_errors { + NO_ERROR = 0, + BUFFER_OVERFLOW, + KFIFO_ERROR, + KFIFO_FULL, +}; + +enum hip4_type { + STREAMING = 0, + OFFLINE, +}; + +struct hip4_sampler_dev { + /* file pointer */ + struct file *filp; + /* char device */ + struct cdev cdev; + /*device pointer*/ + struct device *dev; + /*mx pointer*/ + struct scsc_mx *mx; + /* Associated kfifo */ + DECLARE_KFIFO_PTR(fifo, struct hip4_record); + /* Associated read_wait queue.*/ + wait_queue_head_t read_wait; + /* Device in error */ + enum hip4_dg_errors error; + /* Device node mutex for fops */ + struct mutex mutex; + /* Record number */ + u32 record_num; + /* To profile kfifo num elements */ + u32 kfifo_max; + /* Sampler type streaming/offline */ + enum hip4_type type; + /* reference to minor number */ + u32 minor; +}; + +/** + * SCSC User Space debug sampler interface (singleton) + */ +static struct { + bool init; + dev_t device; + struct class *class_hip4_sampler; + struct hip4_sampler_dev devs[SCSC_HIP4_DEBUG_INTERFACES]; +} hip4_sampler; + +void __hip4_sampler_update_record(struct hip4_sampler_dev *hip4_dev, u32 minor, u8 param1, u8 param2, u8 param3, u8 param4, u32 param5) +{ + struct hip4_record ev; + u32 ret; + u32 cpu; + u32 freq = 0; + + /* If char device if open, use streaming buffer */ + if (hip4_dev->filp) { + /* put string into the Streaming fifo */ + if (kfifo_avail(&hip4_dev->fifo)) { + /* Push values in Fifo*/ + cpu = smp_processor_id(); + if (hip4_dev->record_num % 64 == 0) + freq = (cpufreq_quick_get(cpu) / 1000) & 0xfff; + ev.record_num = (0x0000ffff & hip4_dev->record_num++) | (cpu << 28) | (freq << 16); + ev.ts = ktime_get(); + ev.record = ((param1 & 0xff) << 24) | ((param2 & 0xff) << 16) | ((param3 & 0xff) << 8) | (param4 & 0xff); + ev.record2 = param5; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) + kfifo_put(&hip4_dev->fifo, ev); +#else + kfifo_put(&hip4_dev->fifo, &ev); +#endif + ret = kfifo_len(&hip4_dev->fifo); + if (ret > hip4_dev->kfifo_max) + hip4_dev->kfifo_max = ret; + } else { + hip4_dev->error = KFIFO_FULL; + return; + } + wake_up_interruptible(&hip4_dev->read_wait); + /* If streaming buffer is not in use, put samples in offline buffer */ + } else { + /* Get associated Offline buffer */ + hip4_dev = &hip4_sampler.devs[minor + 1]; + /* Record in offline fifo */ + /* if fifo is full, remove last item */ + if (kfifo_is_full(&hip4_dev->fifo)) + ret = kfifo_get(&hip4_dev->fifo, &ev); + cpu = smp_processor_id(); + if (hip4_dev->record_num % 64 == 0) + freq = (cpufreq_quick_get(cpu) / 1000) & 0xfff; + /* Push values in Static Fifo*/ + ev.record_num = (0x0000ffff & hip4_dev->record_num++) | (cpu << 28) | (freq << 16); + ev.ts = ktime_get(); + ev.record = ((param1 & 0xff) << 24) | ((param2 & 0xff) << 16) | ((param3 & 0xff) << 8) | (param4 & 0xff); + ev.record2 = param5; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) + kfifo_put(&hip4_dev->fifo, ev); +#else + kfifo_put(&hip4_dev->fifo, &ev); +#endif + } +} + +void hip4_sampler_update_record(u32 minor, u8 param1, u8 param2, u8 param3, u8 param4, u32 param5) +{ + struct hip4_sampler_dev *hip4_dev; + unsigned long flags; + + if (!hip4_sampler_enable || !hip4_sampler.init) + return; + + if (atomic_read(&in_read)) + return; + + if (minor >= SCSC_HIP4_INTERFACES) + return; + + spin_lock_irqsave(&g_spinlock, flags); + hip4_dev = &hip4_sampler.devs[minor]; + __hip4_sampler_update_record(hip4_dev, minor, param1, param2, param3, param4, param5); + spin_unlock_irqrestore(&g_spinlock, flags); +} + +static void hip4_sampler_store_param(void) +{ + hip4_sampler_sample_q_hput = hip4_sampler_sample_q; + hip4_sampler_sample_qref_hput = hip4_sampler_sample_qref; + hip4_sampler_sample_int_hput = hip4_sampler_sample_int; + hip4_sampler_sample_fapi_hput = hip4_sampler_sample_fapi; + /* hip4_sampler_sample_through_hput = hip4_sampler_sample_through; */ + /* hip4_sampler_sample_start_stop_q_hput = hip4_sampler_sample_start_stop_q; */ + /* hip4_sampler_sample_mbulk_hput = hip4_sampler_sample_mbulk; */ + /* hip4_sampler_sample_qfull_hput = hip4_sampler_sample_qfull; */ + /* hip4_sampler_sample_mfull_hput = hip4_sampler_sample_mfull; */ + hip4_sampler_vif_hput = hip4_sampler_vif; + hip4_sampler_bot_hput = hip4_sampler_bot; + hip4_sampler_pkt_tx_hput = hip4_sampler_pkt_tx; + hip4_sampler_suspend_resume_hput = hip4_sampler_suspend_resume; + + /* Reset values to avoid contention */ + hip4_sampler_sample_q = false; + hip4_sampler_sample_qref = false; + hip4_sampler_sample_int = false; + hip4_sampler_sample_fapi = false; + /* hip4_sampler_sample_through_hput = false; */ + /* hip4_sampler_sample_start_stop_q_hput = false; */ + /* hip4_sampler_sample_mbulk = false; */ + /* hip4_sampler_sample_qfull_hput = false; */ + /* hip4_sampler_sample_mfull_hput = false; */ + hip4_sampler_vif = false; + hip4_sampler_bot = false; + hip4_sampler_pkt_tx = false; + hip4_sampler_suspend_resume = false; +} + +static void hip4_sampler_restore_param(void) +{ + hip4_sampler_sample_q = hip4_sampler_sample_q_hput; + hip4_sampler_sample_qref = hip4_sampler_sample_qref_hput; + hip4_sampler_sample_int = hip4_sampler_sample_int_hput; + hip4_sampler_sample_fapi = hip4_sampler_sample_fapi_hput; + /* hip4_sampler_sample_through = hip4_sampler_sample_through_hput; */ + /* hip4_sampler_sample_start_stop_q = hip4_sampler_sample_start_stop_q_hput; */ + /* hip4_sampler_sample_mbulk = hip4_sampler_sample_mbulk_hput; */ + /* hip4_sampler_sample_qfull = hip4_sampler_sample_qfull_hput; */ + /* hip4_sampler_sample_mfull = hip4_sampler_sample_mfull_hput; */ + hip4_sampler_vif = hip4_sampler_vif_hput; + hip4_sampler_bot = hip4_sampler_bot_hput; + hip4_sampler_pkt_tx = hip4_sampler_pkt_tx_hput; + hip4_sampler_suspend_resume = hip4_sampler_suspend_resume_hput; +} + +static void hip4_sampler_dynamic_switcher(u32 bps) +{ + /* Running in htput, */ + if (hip4_sampler_in_htput) { + if (bps < HIP4_TPUT_LLIMIT) { + /* bps went down , count number of times to switch */ + hip4_sampler_in_htput_seconds++; + if (hip4_sampler_in_htput_seconds >= HIP4_TPUT_LSECONDS) { + /* HIP4_TPUT_LSECONDS have passed, switch to low tput samples */ + hip4_sampler_in_htput = false; + hip4_sampler_in_htput_seconds = 0; + hip4_sampler_restore_param(); + } + } else { + hip4_sampler_in_htput_seconds = 0; + } + } else { + if (bps > HIP4_TPUT_HLIMIT) { + /* bps went up, count number of times to switch */ + hip4_sampler_in_htput_seconds++; + if (hip4_sampler_in_htput_seconds >= HIP4_TPUT_HSECONDS) { + /* HIP4_TPUT_LSECONDS have passed, switch to high tput samples */ + hip4_sampler_in_htput = true; + hip4_sampler_in_htput_seconds = 0; + hip4_sampler_store_param(); + } + } else { + hip4_sampler_in_htput_seconds = 0; + } + } +} + +static u32 g_tput_rx; +static u32 g_tput_tx; + +void hip4_sampler_tput_monitor(void *client_ctx, u32 state, u32 tput_tx, u32 tput_rx) +{ + struct hip4_sampler_dev *hip4_sampler_dev = (struct hip4_sampler_dev *)client_ctx; + + if (!hip4_sampler_enable) + return; + + if ((g_tput_tx == tput_tx) && (g_tput_rx == tput_rx)) + return; + + g_tput_rx == tput_tx; + g_tput_rx == tput_rx; + + if (hip4_sampler_dynamic) { + /* Call the dynamic switcher with the computed bps + * The algorithm will decide to not change, decrease + * or increase the sampler verbosity + */ + if (tput_rx > tput_tx) + hip4_sampler_dynamic_switcher(tput_rx); + else + hip4_sampler_dynamic_switcher(tput_tx); + } + + /* Generate the TX sample, in bps, Kbps, or Mbps */ + if (tput_tx < 1000) { + SCSC_HIP4_SAMPLER_THROUG(hip4_sampler_dev->minor, 1, (tput_tx & 0xff00) >> 8, tput_tx & 0xff); + } else if ((tput_tx >= 1000) && (tput_tx < (1000 * 1000))) { + tput_tx = tput_tx / 1000; + SCSC_HIP4_SAMPLER_THROUG_K(hip4_sampler_dev->minor, 1, (tput_tx & 0xff00) >> 8, tput_tx & 0xff); + } else { + tput_tx = tput_tx / (1000 * 1000); + SCSC_HIP4_SAMPLER_THROUG_M(hip4_sampler_dev->minor, 1, (tput_tx & 0xff00) >> 8, tput_tx & 0xff); + } + + /* Generate the RX sample, in bps, Kbps, or Mbps */ + if (tput_rx < 1000) { + SCSC_HIP4_SAMPLER_THROUG(hip4_sampler_dev->minor, 0, (tput_rx & 0xff00) >> 8, tput_rx & 0xff); + } else if ((tput_rx >= 1000) && (tput_rx < (1000 * 1000))) { + tput_rx = tput_rx / 1000; + SCSC_HIP4_SAMPLER_THROUG_K(hip4_sampler_dev->minor, 0, (tput_rx & 0xff00) >> 8, tput_rx & 0xff); + } else { + tput_rx = tput_rx / (1000 * 1000); + SCSC_HIP4_SAMPLER_THROUG_M(hip4_sampler_dev->minor, 0, (tput_rx & 0xff00) >> 8, tput_rx & 0xff); + } +} + +void hip4_sampler_tcp_decode(struct slsi_dev *sdev, struct net_device *dev, u8 *frame, bool from_ba) +{ + struct tcphdr *tcp_hdr; + struct ethhdr *ehdr = (struct ethhdr *)(frame); + struct netdev_vif *ndev_vif = netdev_priv(dev); + u8 *ip_frame; + u16 ip_data_offset; + u8 hlen; + u16 len; + u8 proto; + u8 idx; + + if (be16_to_cpu(ehdr->h_proto) != ETH_P_IP) + return; + + ip_frame = frame + ETH_HLEN; + proto = ip_frame[9]; + + if (proto != IPPROTO_TCP) + return; + + ip_data_offset = 20; + hlen = ip_frame[0] & 0x0F; + len = ip_frame[2] << 8 | ip_frame[3]; + + if (hlen > 5) + ip_data_offset += (hlen - 5) * 4; + + tcp_hdr = (struct tcphdr *)(ip_frame + ip_data_offset); + + /* Search for an existing record on this connection. */ + for (idx = 0; idx < TCP_ACK_SUPPRESSION_RECORDS_MAX; idx++) { + struct slsi_tcp_ack_s *tcp_ack; + u32 rwnd = 0; + + tcp_ack = &ndev_vif->ack_suppression[idx]; + slsi_spinlock_lock(&tcp_ack->lock); + if ((tcp_ack->dport == tcp_hdr->source) && (tcp_ack->sport == tcp_hdr->dest)) { + if (from_ba && tcp_hdr->syn && tcp_hdr->ack) { + unsigned char *options; + u32 optlen = 0, len = 0; + + if (tcp_hdr->doff > 5) + optlen = (tcp_hdr->doff - 5) * 4; + + options = (u8 *)tcp_hdr + TCP_ACK_SUPPRESSION_OPTIONS_OFFSET; + + while (optlen > 0) { + switch (options[0]) { + case TCP_ACK_SUPPRESSION_OPTION_EOL: + len = 1; + break; + case TCP_ACK_SUPPRESSION_OPTION_NOP: + len = 1; + break; + case TCP_ACK_SUPPRESSION_OPTION_WINDOW: + tcp_ack->rx_window_scale = options[2]; + len = options[1]; + break; + default: + len = options[1]; + break; + } + /* if length field in TCP options is 0, or greater than + * total options length, then options are incorrect + */ + if ((len == 0) || (len >= optlen)) + break; + + if (optlen >= len) + optlen -= len; + else + optlen = 0; + options += len; + } + } + if (len > ((hlen * 4) + (tcp_hdr->doff * 4))) { + if (from_ba) + SCSC_HIP4_SAMPLER_TCP_DATA(sdev->minor_prof, tcp_ack->stream_id, tcp_hdr->seq); + else + SCSC_HIP4_SAMPLER_TCP_DATA_IN(sdev->minor_prof, tcp_ack->stream_id, tcp_hdr->seq); + } else { + if (tcp_ack->rx_window_scale) + rwnd = be16_to_cpu(tcp_hdr->window) * (2 << tcp_ack->rx_window_scale); + else + rwnd = be16_to_cpu(tcp_hdr->window); + if (from_ba) { + SCSC_HIP4_SAMPLER_TCP_ACK(sdev->minor_prof, tcp_ack->stream_id, be32_to_cpu(tcp_hdr->ack_seq)); + SCSC_HIP4_SAMPLER_TCP_RWND(sdev->minor_prof, tcp_ack->stream_id, rwnd); + } else { + SCSC_HIP4_SAMPLER_TCP_ACK_IN(sdev->minor_prof, tcp_ack->stream_id, be32_to_cpu(tcp_hdr->ack_seq)); + } + } + slsi_spinlock_unlock(&tcp_ack->lock); + break; + } + slsi_spinlock_unlock(&tcp_ack->lock); + } +} + +#ifdef CONFIG_SCSC_LOG_COLLECTION +int hip4_collect_init(struct scsc_log_collector_client *collect_client) +{ + /* Stop Sampling */ + atomic_set(&in_read, 1); + return 0; +} + +int hip4_collect(struct scsc_log_collector_client *collect_client, size_t size) +{ + int i = SCSC_HIP4_DEBUG_INTERFACES; + int ret = 0; + unsigned long flags; + u32 num_samples; + struct hip4_sampler_dev *hip4_dev; + void *buf; + + SLSI_INFO_NODEV("Triggered log collection in hip4_sampler\n"); + + if (!hip4_sampler_enable) + return 0; + + while (i--) + if (hip4_sampler.devs[i].mx == collect_client->prv && hip4_sampler.devs[i].type == OFFLINE) { + hip4_dev = &hip4_sampler.devs[i]; + num_samples = kfifo_len(&hip4_dev->fifo); + if (!num_samples) + continue; + buf = vmalloc(num_samples * sizeof(struct hip4_record)); + if (!buf) + continue; + spin_lock_irqsave(&g_spinlock, flags); + ret = kfifo_out(&hip4_dev->fifo, buf, num_samples); + spin_unlock_irqrestore(&g_spinlock, flags); + if (!ret) + goto error; + SLSI_DBG1_NODEV(SLSI_HIP, "num_samples %d ret %d size of hip4_record %zu\n", num_samples, ret, sizeof(struct hip4_record)); + ret = scsc_log_collector_write(buf, ret * sizeof(struct hip4_record), 1); + if (ret) + goto error; + vfree(buf); + } + return 0; +error: + vfree(buf); + return ret; +} + +int hip4_collect_end(struct scsc_log_collector_client *collect_client) +{ + /* Restart sampling */ + atomic_set(&in_read, 0); + return 0; +} + +/* Collect client registration */ +struct scsc_log_collector_client hip4_collect_client = { + .name = "HIP4 Sampler", + .type = SCSC_LOG_CHUNK_HIP4_SAMPLER, + .collect_init = hip4_collect_init, + .collect = hip4_collect, + .collect_end = hip4_collect_end, + .prv = NULL, +}; +#endif + +static int hip4_sampler_open(struct inode *inode, struct file *filp) +{ + struct hip4_sampler_dev *hip4_dev; + int ret = 0; + + hip4_dev = container_of(inode->i_cdev, struct hip4_sampler_dev, cdev); + + if (hip4_dev->type == OFFLINE) { + /* Offline buffer skip open */ + filp->private_data = hip4_dev; + return 0; + } + if (mutex_lock_interruptible(&hip4_dev->mutex)) + return -ERESTARTSYS; + + if (filp->private_data) { + SLSI_INFO_NODEV("Service already started\n"); + ret = 0; + goto end; + } + + if (hip4_sampler_kfifo_len > 256 * 1024) { + SLSI_DBG1_NODEV(SLSI_HIP, "hip4_sampler_kfifo_len %d > 2262144. Set to MAX", hip4_sampler_kfifo_len); + hip4_sampler_kfifo_len = 256 * 1024; + } + + ret = kfifo_alloc(&hip4_dev->fifo, hip4_sampler_kfifo_len, GFP_KERNEL); + if (ret) { + SLSI_ERR_NODEV("kfifo_alloc failed"); + ret = -ENOMEM; + goto end; + } + + filp->private_data = hip4_dev; + + /* Clear any remaining error */ + hip4_dev->error = NO_ERROR; + + hip4_dev->record_num = 0; + hip4_dev->kfifo_max = 0; + hip4_dev->filp = filp; + + SLSI_INFO_NODEV("%s: Sampling....\n", DRV_NAME); +end: + mutex_unlock(&hip4_dev->mutex); + return ret; +} + +static ssize_t hip4_sampler_read(struct file *filp, char __user *buf, size_t len, loff_t *offset) +{ + unsigned int copied; + int ret = 0; + struct hip4_sampler_dev *hip4_dev; + + hip4_dev = filp->private_data; + + if (hip4_dev->type == OFFLINE) { + /* Offline buffer skip open */ + if (mutex_lock_interruptible(&hip4_dev->mutex)) + return -EINTR; + atomic_set(&in_read, 1); + ret = kfifo_to_user(&hip4_dev->fifo, buf, len, &copied); + mutex_unlock(&hip4_dev->mutex); + return ret ? ret : copied; + } + + if (mutex_lock_interruptible(&hip4_dev->mutex)) + return -EINTR; + + /* Check whether the device is in error */ + if (hip4_dev->error != NO_ERROR) { + SLSI_ERR_NODEV("Device in error\n"); + ret = -EIO; + goto end; + } + + while (len) { + if (kfifo_len(&hip4_dev->fifo)) { + ret = kfifo_to_user(&hip4_dev->fifo, buf, len, &copied); + if (!ret) + ret = copied; + break; + } + + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + ret = wait_event_interruptible(hip4_dev->read_wait, + !kfifo_is_empty(&hip4_dev->fifo)); + if (ret < 0) + break; + } +end: + mutex_unlock(&hip4_dev->mutex); + return ret; +} + +static unsigned hip4_sampler_poll(struct file *filp, poll_table *wait) +{ + struct hip4_sampler_dev *hip4_dev; + int ret; + + hip4_dev = filp->private_data; + + if (hip4_dev->type == OFFLINE) + /* Offline buffer skip poll */ + return 0; + + if (mutex_lock_interruptible(&hip4_dev->mutex)) + return -EINTR; + + if (hip4_dev->error != NO_ERROR) { + ret = POLLERR; + goto end; + } + + poll_wait(filp, &hip4_dev->read_wait, wait); + + if (!kfifo_is_empty(&hip4_dev->fifo)) { + ret = POLLIN | POLLRDNORM; /* readeable */ + goto end; + } + + ret = POLLOUT | POLLWRNORM; /* writable */ + +end: + mutex_unlock(&hip4_dev->mutex); + return ret; +} + +static int hip4_sampler_release(struct inode *inode, struct file *filp) +{ + struct hip4_sampler_dev *hip4_dev; + + hip4_dev = container_of(inode->i_cdev, struct hip4_sampler_dev, cdev); + + if (hip4_dev->type == OFFLINE) { + atomic_set(&in_read, 0); + /* Offline buffer skip release */ + return 0; + } + + if (mutex_lock_interruptible(&hip4_dev->mutex)) + return -EINTR; + + if (!hip4_dev->filp) { + SLSI_ERR_NODEV("Device already closed\n"); + mutex_unlock(&hip4_dev->mutex); + return -EIO; + } + + if (hip4_dev != filp->private_data) { + SLSI_ERR_NODEV("Data mismatch\n"); + mutex_unlock(&hip4_dev->mutex); + return -EIO; + } + + filp->private_data = NULL; + hip4_dev->filp = NULL; + kfifo_free(&hip4_dev->fifo); + + mutex_unlock(&hip4_dev->mutex); + SLSI_INFO_NODEV("%s: Sampling... end. Kfifo_max = %d\n", DRV_NAME, hip4_dev->kfifo_max); + return 0; +} + +static const struct file_operations hip4_sampler_fops = { + .owner = THIS_MODULE, + .open = hip4_sampler_open, + .read = hip4_sampler_read, + .release = hip4_sampler_release, + .poll = hip4_sampler_poll, +}; + +/* Return minor (if exists) associated with this maxwell instance */ +int hip4_sampler_register_hip(struct scsc_mx *mx) +{ + int i = SCSC_HIP4_DEBUG_INTERFACES; + + while (i--) + if (hip4_sampler.devs[i].mx == mx && + hip4_sampler.devs[i].type == STREAMING) + return i; + return -ENODEV; +} + +void hip4_sampler_create(struct slsi_dev *sdev, struct scsc_mx *mx) +{ + dev_t devn; + int ret; + char dev_name[20]; + int minor; + int i; + + SLSI_INFO_NODEV("hip4_sampler version: %d.%d\n", VER_MAJOR, VER_MINOR); + + memset(&hip4_sampler, 0, sizeof(hip4_sampler)); + /* Check whether exists */ + if (!hip4_sampler.init) { + ret = alloc_chrdev_region(&hip4_sampler.device, 0, SCSC_HIP4_DEBUG_INTERFACES, "hip4_sampler_char"); + if (ret) + goto error; + + hip4_sampler.class_hip4_sampler = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(hip4_sampler.class_hip4_sampler)) { + SLSI_ERR_NODEV("hip4_sampler class creation failed\n"); + ret = PTR_ERR(hip4_sampler.class_hip4_sampler); + goto error_class; + } + } + + /* Search for free minors */ + minor = find_first_zero_bit(bitmap_hip4_sampler_minor, SCSC_HIP4_DEBUG_INTERFACES); + if (minor == SCSC_HIP4_DEBUG_INTERFACES) { + SLSI_INFO_NODEV("minor %d > SCSC_TTY_MINORS\n", minor); + return; + } + + /* Create Stream channels */ + /* Each Stream channel will have an associated Offline channel */ + for (i = 0; i < SCSC_HIP4_STREAM_CH; i++) { + minor += i; + devn = MKDEV(MAJOR(hip4_sampler.device), MINOR(minor)); + + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "hip4", i, "sam_str"); + + cdev_init(&hip4_sampler.devs[minor].cdev, &hip4_sampler_fops); + hip4_sampler.devs[minor].cdev.owner = THIS_MODULE; + hip4_sampler.devs[minor].cdev.ops = &hip4_sampler_fops; + + ret = cdev_add(&hip4_sampler.devs[minor].cdev, devn, 1); + if (ret) { + hip4_sampler.devs[minor].cdev.dev = 0; + return; + } + + hip4_sampler.devs[minor].dev = + device_create(hip4_sampler.class_hip4_sampler, NULL, hip4_sampler.devs[minor].cdev.dev, NULL, dev_name); + + if (!hip4_sampler.devs[minor].dev) { + SLSI_ERR_NODEV("dev is NULL\n"); + hip4_sampler.devs[minor].cdev.dev = 0; + cdev_del(&hip4_sampler.devs[minor].cdev); + return; + } + + hip4_sampler.devs[minor].mx = mx; + + mutex_init(&hip4_sampler.devs[minor].mutex); + hip4_sampler.devs[minor].kfifo_max = 0; + hip4_sampler.devs[minor].type = STREAMING; + hip4_sampler.devs[minor].minor = minor; + + init_waitqueue_head(&hip4_sampler.devs[minor].read_wait); + + slsi_traffic_mon_client_register( + sdev, + &hip4_sampler.devs[minor], + TRAFFIC_MON_CLIENT_MODE_PERIODIC, + 0, + 0, + hip4_sampler_tput_monitor); + + /* Update bit mask */ + set_bit(minor, bitmap_hip4_sampler_minor); + + minor++; + + /* Create associated offline channel */ + devn = MKDEV(MAJOR(hip4_sampler.device), MINOR(minor)); + + snprintf(dev_name, sizeof(dev_name), "%s_%d_%s", "hip4", i, "sam_off"); + + cdev_init(&hip4_sampler.devs[minor].cdev, &hip4_sampler_fops); + hip4_sampler.devs[minor].cdev.owner = THIS_MODULE; + hip4_sampler.devs[minor].cdev.ops = &hip4_sampler_fops; + + ret = cdev_add(&hip4_sampler.devs[minor].cdev, devn, 1); + if (ret) { + hip4_sampler.devs[minor].cdev.dev = 0; + return; + } + + hip4_sampler.devs[minor].dev = + device_create(hip4_sampler.class_hip4_sampler, NULL, hip4_sampler.devs[minor].cdev.dev, NULL, dev_name); + + if (!hip4_sampler.devs[minor].dev) { + hip4_sampler.devs[minor].cdev.dev = 0; + cdev_del(&hip4_sampler.devs[minor].cdev); + return; + } + + if (hip4_sampler_static_kfifo_len > 256 * 1024) { + SLSI_DBG1_NODEV(SLSI_HIP, "hip4_sampler_static_kfifo_len %d > 2262144. Set to MAX", hip4_sampler_static_kfifo_len); + hip4_sampler_static_kfifo_len = 256 * 1024; + } + ret = kfifo_alloc(&hip4_sampler.devs[minor].fifo, hip4_sampler_static_kfifo_len, GFP_KERNEL); + if (ret) { + SLSI_ERR_NODEV("kfifo_alloc failed"); + hip4_sampler.devs[minor].dev = NULL; + hip4_sampler.devs[minor].cdev.dev = 0; + cdev_del(&hip4_sampler.devs[minor].cdev); + return; + } + + hip4_sampler.devs[minor].mx = mx; + + mutex_init(&hip4_sampler.devs[minor].mutex); + hip4_sampler.devs[minor].kfifo_max = 0; + hip4_sampler.devs[minor].type = OFFLINE; + + /* Update bit mask */ + set_bit(minor, bitmap_hip4_sampler_minor); + } + +#ifdef CONFIG_SCSC_LOG_COLLECTION + hip4_collect_client.prv = mx; + scsc_log_collector_register_client(&hip4_collect_client); +#endif + spin_lock_init(&g_spinlock); + hip4_sampler.init = true; + + SLSI_INFO_NODEV("%s: Ready to start sampling....\n", DRV_NAME); + + return; + +error_class: + unregister_chrdev_region(hip4_sampler.device, SCSC_HIP4_DEBUG_INTERFACES); + hip4_sampler.init = false; +error: + return; +} + +void hip4_sampler_destroy(struct slsi_dev *sdev, struct scsc_mx *mx) +{ + int i = SCSC_HIP4_DEBUG_INTERFACES; + struct hip4_sampler_dev *hip4_dev; + + while (i--) + if (hip4_sampler.devs[i].cdev.dev && hip4_sampler.devs[i].mx) { + hip4_dev = &hip4_sampler.devs[i]; + /* This should be never be true - as knod should prevent unloading while + * the service (device node) is open + */ + if (hip4_sampler.devs[i].filp) { + hip4_sampler.devs[i].filp = NULL; + kfifo_free(&hip4_sampler.devs[i].fifo); + } + if (hip4_sampler.devs[i].type == OFFLINE) + kfifo_free(&hip4_sampler.devs[i].fifo); + + slsi_traffic_mon_client_unregister(sdev, hip4_dev); + device_destroy(hip4_sampler.class_hip4_sampler, hip4_sampler.devs[i].cdev.dev); + cdev_del(&hip4_sampler.devs[i].cdev); + memset(&hip4_sampler.devs[i].cdev, 0, sizeof(struct cdev)); + hip4_sampler.devs[i].mx = NULL; + clear_bit(i, bitmap_hip4_sampler_minor); + } +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_unregister_client(&hip4_collect_client); +#endif + class_destroy(hip4_sampler.class_hip4_sampler); + unregister_chrdev_region(hip4_sampler.device, SCSC_HIP4_DEBUG_INTERFACES); + hip4_sampler.init = false; +} diff --git a/drivers/net/wireless/scsc/hip4_sampler.h b/drivers/net/wireless/scsc/hip4_sampler.h new file mode 100644 index 000000000000..ddb798d0d3d0 --- /dev/null +++ b/drivers/net/wireless/scsc/hip4_sampler.h @@ -0,0 +1,412 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include + +#include "dev.h" + +/* TCP send buffer sizes */ +extern int sysctl_tcp_wmem[3]; + +#ifndef __HIP4_SAMPLER_H__ +#define __HIP4_SAMPLER_H__ + +#define HIP4_SAMPLER_SIGNAL_CTRLTX 0x20 +#define HIP4_SAMPLER_SIGNAL_CTRLRX 0x21 +#define HIP4_SAMPLER_THROUG 0x22 +#define HIP4_SAMPLER_THROUG_K 0x23 +#define HIP4_SAMPLER_THROUG_M 0x24 +#define HIP4_SAMPLER_STOP_Q 0x25 +#define HIP4_SAMPLER_START_Q 0x26 +#define HIP4_SAMPLER_QREF 0x27 +#define HIP4_SAMPLER_PEER 0x29 +#define HIP4_SAMPLER_BOT_RX 0x2a +#define HIP4_SAMPLER_BOT_TX 0x2b +#define HIP4_SAMPLER_BOT_ADD 0x2c +#define HIP4_SAMPLER_BOT_REMOVE 0x2d +#define HIP4_SAMPLER_BOT_STOP_Q 0x2e +#define HIP4_SAMPLER_BOT_START_Q 0x2f +#define HIP4_SAMPLER_BOT_QMOD_RX 0x30 +#define HIP4_SAMPLER_BOT_QMOD_TX 0x31 +#define HIP4_SAMPLER_BOT_QMOD_STOP 0x32 +#define HIP4_SAMPLER_BOT_QMOD_START 0x33 +#define HIP4_SAMPLER_PKT_TX 0x40 +#define HIP4_SAMPLER_PKT_TX_HIP4 0x41 +#define HIP4_SAMPLER_PKT_TX_FB 0x42 +#define HIP4_SAMPLER_SUSPEND 0x50 +#define HIP4_SAMPLER_RESUME 0x51 + +#define HIP4_SAMPLER_TCP_SYN 0x60 +#define HIP4_SAMPLER_TCP_FIN 0x61 +#define HIP4_SAMPLER_TCP_DATA 0x62 +#define HIP4_SAMPLER_TCP_ACK 0x63 +#define HIP4_SAMPLER_TCP_RWND 0x64 +#define HIP4_SAMPLER_TCP_CWND 0x65 +#define HIP4_SAMPLER_TCP_SEND_BUF 0x66 +#define HIP4_SAMPLER_TCP_DATA_IN 0x67 +#define HIP4_SAMPLER_TCP_ACK_IN 0x68 + +#define HIP4_SAMPLER_MBULK 0xaa +#define HIP4_SAMPLER_QFULL 0xbb +#define HIP4_SAMPLER_MFULL 0xcc +#define HIP4_SAMPLER_INT 0xdd +#define HIP4_SAMPLER_INT_OUT 0xee +#define HIP4_SAMPLER_INT_BH 0xde +#define HIP4_SAMPLER_INT_OUT_BH 0xef +#define HIP4_SAMPLER_RESET 0xff + +#define SCSC_HIP4_INTERFACES 1 + +#define SCSC_HIP4_STREAM_CH 1 +#define SCSC_HIP4_OFFLINE_CH SCSC_HIP4_STREAM_CH + +#if (SCSC_HIP4_OFFLINE_CH != SCSC_HIP4_STREAM_CH) +#error "SCSC_HIP4_STREAM_CH has to be equal to SCSC_HIP4_OFFLINE_CH" +#endif + +#define SCSC_HIP4_DEBUG_INTERFACES ((SCSC_HIP4_INTERFACES) * (SCSC_HIP4_STREAM_CH + SCSC_HIP4_OFFLINE_CH)) + +struct scsc_mx; + +void hip4_sampler_create(struct slsi_dev *sdev, struct scsc_mx *mx); +void hip4_sampler_destroy(struct slsi_dev *sdev, struct scsc_mx *mx); + +/* Register hip4 instance with the logger */ +/* return char device minor associated with the maxwell instance*/ +int hip4_sampler_register_hip(struct scsc_mx *mx); + +void hip4_sampler_update_record(u32 minor, u8 param1, u8 param2, u8 param3, u8 param4, u32 param5); +void hip4_sampler_tcp_decode(struct slsi_dev *sdev, struct net_device *dev, u8 *frame, bool from_ba); + +extern bool hip4_sampler_sample_q; +extern bool hip4_sampler_sample_qref; +extern bool hip4_sampler_sample_int; +extern bool hip4_sampler_sample_fapi; +extern bool hip4_sampler_sample_through; +extern bool hip4_sampler_sample_tcp; +extern bool hip4_sampler_sample_start_stop_q; +extern bool hip4_sampler_sample_mbulk; +extern bool hip4_sampler_sample_qfull; +extern bool hip4_sampler_sample_mfull; +extern bool hip4_sampler_vif; +extern bool hip4_sampler_bot; +extern bool hip4_sampler_pkt_tx; +extern bool hip4_sampler_suspend_resume; + +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING +#define SCSC_HIP4_SAMPLER_Q(minor, q, idx_rw, value, rw) \ + do { \ + if (hip4_sampler_sample_q) { \ + hip4_sampler_update_record(minor, q, idx_rw, value, rw, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_QREF(minor, ref, q) \ + do { \ + if (hip4_sampler_sample_qref) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_QREF, (ref & 0xff0000) >> 16, (ref & 0xff00) >> 8, (ref & 0xf0) | q, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_SIGNAL_CTRLTX(minor, bytes16_h, bytes16_l) \ + do { \ + if (hip4_sampler_sample_fapi) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_SIGNAL_CTRLTX, 0, bytes16_h, bytes16_l, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_SIGNAL_CTRLRX(minor, bytes16_h, bytes16_l) \ + do { \ + if (hip4_sampler_sample_fapi) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_SIGNAL_CTRLRX, 0, bytes16_h, bytes16_l, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_TCP_DECODE(sdev, dev, frame, from_ba) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_tcp_decode(sdev, dev, frame, from_ba); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_THROUG(minor, rx_tx, bytes16_h, bytes16_l) \ + do { \ + if (hip4_sampler_sample_through) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_THROUG, rx_tx, bytes16_h, bytes16_l, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_THROUG_K(minor, rx_tx, bytes16_h, bytes16_l) \ + do { \ + if (hip4_sampler_sample_through) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_THROUG_K, rx_tx, bytes16_h, bytes16_l, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_THROUG_M(minor, rx_tx, bytes16_h, bytes16_l) \ + do { \ + if (hip4_sampler_sample_through) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_THROUG_M, rx_tx, bytes16_h, bytes16_l, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_STOP_Q(minor, vif_id) \ + do { \ + if (hip4_sampler_sample_start_stop_q) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_STOP_Q, 0, 0, vif_id, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_START_Q(minor, vif_id) \ + do { \ + if (hip4_sampler_sample_start_stop_q) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_START_Q, 0, 0, vif_id, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_MBULK(minor, bytes16_h, bytes16_l, clas) \ + do { \ + if (hip4_sampler_sample_mbulk) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_MBULK, clas, bytes16_h, bytes16_l, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_QFULL(minor, q) \ + do { \ + if (hip4_sampler_sample_qfull) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_QFULL, 0, 0, q, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_MFULL(minor) \ + do { \ + if (hip4_sampler_sample_mfull) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_MFULL, 0, 0, 0, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_INT(minor, id) \ + do { \ + if (hip4_sampler_sample_int) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_INT, 0, 0, id, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_INT_OUT(minor, id) \ + do { \ + if (hip4_sampler_sample_int) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_INT_OUT, 0, 0, id, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_INT_BH(minor, id) \ + do { \ + if (hip4_sampler_sample_int) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_INT_BH, 0, 0, id, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_INT_OUT_BH(minor, id) \ + do { \ + if (hip4_sampler_sample_int) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_INT_OUT_BH, 0, 0, id, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_RESET(minor) \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_RESET, 0, 0, 0, 0) + +#define SCSC_HIP4_SAMPLER_VIF_PEER(minor, tx, vif, peer_index) \ + do { \ + if (hip4_sampler_vif) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_PEER, tx, vif, peer_index, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_RX(minor, scod, smod, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_RX, scod, smod, vif_peer, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_TX(minor, scod, smod, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_TX, scod, smod, vif_peer, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_BOT_ADD(minor, addr_1, addr_2, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_ADD, addr_1, addr_2, vif_peer, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_REMOVE(minor, addr_1, addr_2, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_REMOVE, addr_1, addr_2, vif_peer, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_START_Q(minor, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_START_Q, 0, 0, vif_peer, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_STOP_Q(minor, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_STOP_Q, 0, 0, vif_peer, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_BOT_QMOD_RX(minor, qcod, qmod, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_QMOD_RX, qcod, qmod, vif_peer, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_QMOD_TX(minor, qcod, qmod, vif_peer) \ + do { \ + if (hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_QMOD_TX, qcod, qmod, vif_peer, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_BOT_QMOD_START(minor, vif_peer) \ + do { \ + if (hip4_sampler_sample_start_stop_q || hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_QMOD_START, 0, 0, vif_peer, 0); \ + } \ + } while (0) + +#define SCSC_HIP4_SAMPLER_BOT_QMOD_STOP(minor, vif_peer) \ + do { \ + if (hip4_sampler_sample_start_stop_q || hip4_sampler_bot) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_BOT_QMOD_STOP, 0, 0, vif_peer, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_PKT_TX(minor, host_tag) \ + do { \ + if (hip4_sampler_pkt_tx) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_PKT_TX, 0, (host_tag >> 8) & 0xff, host_tag & 0xff, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_PKT_TX_HIP4(minor, host_tag) \ + do { \ + if (hip4_sampler_pkt_tx) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_PKT_TX_HIP4, 0, (host_tag >> 8) & 0xff, host_tag & 0xff, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_PKT_TX_FB(minor, host_tag) \ + do { \ + if (hip4_sampler_pkt_tx) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_PKT_TX_FB, 0, (host_tag >> 8) & 0xff, host_tag & 0xff, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_SUSPEND(minor) \ + do { \ + if (hip4_sampler_suspend_resume) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_SUSPEND, 0, 0, 0, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_RESUME(minor) \ + do { \ + if (hip4_sampler_suspend_resume) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_RESUME, 0, 0, 0, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_SYN(minor, id, mss) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_SYN, id, 0, 0, mss); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_FIN(minor, id) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_FIN, id, 0, 0, 0); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_DATA(minor, id, seq_num) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_DATA, id, 0, 0, seq_num); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_ACK(minor, id, ack_num) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_ACK, id, 0, 0, ack_num); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_DATA_IN(minor, id, seq_num) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_DATA_IN, id, 0, 0, seq_num); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_ACK_IN(minor, id, ack_num) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_ACK_IN, id, 0, 0, ack_num); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_RWND(minor, id, rwnd) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_RWND, id, 0, 0, rwnd); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_CWND(minor, id, cwnd) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_CWND, id, 0, 0, cwnd); \ + } \ + } while (0) +#define SCSC_HIP4_SAMPLER_TCP_SEND_BUF(minor, id, send_buff_size) \ + do { \ + if (hip4_sampler_sample_tcp) { \ + hip4_sampler_update_record(minor, HIP4_SAMPLER_TCP_SEND_BUF, id, 0, 0, send_buff_size); \ + } \ + } while (0) +#else +#define SCSC_HIP4_SAMPLER_Q(minor, q, idx_rw, value, rw) +#define SCSC_HIP4_SAMPLER_QREF(minor, ref, q) +#define SCSC_HIP4_SAMPLER_SIGNAL_CTRLTX(minor, bytes16_h, bytes16_l) +#define SCSC_HIP4_SAMPLER_SIGNAL_CTRLRX(minor, bytes16_h, bytes16_l) +#define SCSC_HIP4_SAMPLER_TPUT(minor, rx_tx, payload) +#define SCSC_HIP4_SAMPLER_THROUG(minor, bytes16_h, bytes16_l) +#define SCSC_HIP4_SAMPLER_THROUG_K(minor, bytes16_h, bytes16_l) +#define SCSC_HIP4_SAMPLER_THROUG_M(minor, bytes16_h, bytes16_l) +#define SCSC_HIP4_SAMPLER_MBULK(minor, bytes16_h, bytes16_l, clas) +#define SCSC_HIP4_SAMPLER_QFULL(minor, q) +#define SCSC_HIP4_SAMPLER_MFULL(minor) +#define SCSC_HIP4_SAMPLER_INT(minor, id) +#define SCSC_HIP4_SAMPLER_INT_BH(minor, id) +#define SCSC_HIP4_SAMPLER_INT_OUT(minor, id) +#define SCSC_HIP4_SAMPLER_INT_OUT_BH(minor, id) +#define SCSC_HIP4_SAMPLER_RESET(minor) +#define SCSC_HIP4_SAMPLER_VIF_PEER(minor, tx, vif, peer_index) +#define SCSC_HIP4_SAMPLER_BOT_RX(minor, scod, smod, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_TX(minor, scod, smod, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT(minor, init, addr_1, addr_2, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_ADD(minor, addr_1, addr_2, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_REMOVE(minor, addr_1, addr_2, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_START_Q(minor, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_STOP_Q(minor, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_QMOD_RX(minor, qcod, qmod, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_QMOD_TX(minor, qcod, qmod, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_QMOD_START(minor, vif_peer) +#define SCSC_HIP4_SAMPLER_BOT_QMOD_STOP(minor, vif_peer) +#define SCSC_HIP4_SAMPLER_PKT_TX(minor, host_tag) +#define SCSC_HIP4_SAMPLER_PKT_TX_HIP4(minor, host_tag) +#define SCSC_HIP4_SAMPLER_PKT_TX_FB(minor, host_tag) +#define SCSC_HIP4_SAMPLER_SUSPEND(minor) +#define SCSC_HIP4_SAMPLER_RESUME(minor) +#define SCSC_HIP4_SAMPLER_TCP_SYN(minor, id, mss) +#define SCSC_HIP4_SAMPLER_TCP_FIN(minor, id) +#define SCSC_HIP4_SAMPLER_TCP_DATA(minor, id, seq_num) +#define SCSC_HIP4_SAMPLER_TCP_ACK(minor, id, ack_num) +#define SCSC_HIP4_SAMPLER_TCP_DATA_IN(minor, id, seq_num) +#define SCSC_HIP4_SAMPLER_TCP_ACK_IN(minor, id, ack_num) +#define SCSC_HIP4_SAMPLER_TCP_RWND(minor, id, rwnd) +#define SCSC_HIP4_SAMPLER_TCP_CWND(minor, id, cwnd) +#define SCSC_HIP4_SAMPLER_TCP_SEND_BUF(minor, id, send_buff_size) +#define SCSC_HIP4_SAMPLER_TCP_DECODE(sdev, dev, frame, from_ba) +#endif /* CONFIG_SCSC_WLAN_HIP4_PROFILING */ + +#endif /* __HIP4_SAMPLER_H__ */ diff --git a/drivers/net/wireless/scsc/hip4_smapper.c b/drivers/net/wireless/scsc/hip4_smapper.c new file mode 100644 index 000000000000..2d3678670378 --- /dev/null +++ b/drivers/net/wireless/scsc/hip4_smapper.c @@ -0,0 +1,432 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include +#include +#include +#include + +#include "debug.h" +#include "dev.h" +#include "hip4.h" +#include "hip4_smapper.h" + +#define SMAPPER_GRANULARITY (4 * 1024) + +static void hip4_smapper_refill_isr(int irq, void *data); + +static int hip4_smapper_alloc_bank(struct slsi_dev *sdev, struct hip4_priv *priv, enum smapper_banks bank_name, u32 entry_size, bool is_large) +{ + u16 i; + struct hip4_smapper_bank *bank = &(priv)->smapper_banks[bank_name]; + struct hip4_smapper_control *control = &(priv)->smapper_control; + int err; + + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Init bank %d entry_size %d is_large %d\n", bank_name, entry_size, is_large); + bank->entry_size = entry_size; + + /* function returns negative number if an error occurs, otherwise returns the bank number */ + err = scsc_service_mifsmapper_alloc_bank(sdev->service, is_large, bank->entry_size, &bank->entries); + if (err < 0) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Error allocating bank %d\n", err); + return -ENOMEM; + } + + bank->bank = (u32)err; + if (bank->bank >= HIP4_SMAPPER_TOTAL_BANKS) { + scsc_service_mifsmapper_free_bank(sdev->service, bank->bank); + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Incorrect bank_num %d\n", bank->bank); + return -ENOMEM; + } + + bank->skbuff = kmalloc_array(bank->entries, sizeof(struct sk_buff *), + GFP_KERNEL); + bank->skbuff_dma = kmalloc_array(bank->entries, sizeof(dma_addr_t), + GFP_KERNEL); + if (!bank->skbuff || !bank->skbuff_dma) { + kfree(bank->skbuff_dma); + kfree(bank->skbuff); + return -ENOMEM; + } + + for (i = 0; i < bank->entries; i++) + bank->skbuff[i] = NULL; + + bank->align = scsc_service_get_alignment(sdev->service); + bank->in_use = true; + + /* update the mapping with BANK# in WLAN with PHY BANK#*/ + control->lookuptable[bank->bank] = bank_name; + + return 0; +} + +static int hip4_smapper_allocate_skb_buffer_entry(struct slsi_dev *sdev, struct hip4_smapper_bank *bank, int idx) +{ + struct sk_buff *skb; + int err; + + skb = slsi_alloc_skb(bank->entry_size, GFP_ATOMIC); + if (!skb) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Not enough memory\n"); + return -ENOMEM; + } + SLSI_DBG4_NODEV(SLSI_SMAPPER, "SKB allocated: 0x%p at bank %d entry %d\n", skb, bank->bank, idx); + bank->skbuff_dma[idx] = dma_map_single(sdev->dev, skb->data, + bank->entry_size, DMA_FROM_DEVICE); + err = dma_mapping_error(sdev->dev, bank->skbuff_dma[idx]); + if (err) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Error mapping SKB: 0x%p at bank %d entry %d\n", skb, bank->bank, idx); + slsi_kfree_skb(skb); + return err; + } + + /* Check alignment */ + if (!IS_ALIGNED(bank->skbuff_dma[idx], bank->align)) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Phys address: 0x%x not %d aligned. Unmap memory and return error\n", + bank->skbuff_dma[idx], bank->align); + dma_unmap_single(sdev->dev, bank->skbuff_dma[idx], bank->entry_size, DMA_FROM_DEVICE); + slsi_kfree_skb(skb); + bank->skbuff_dma[idx] = 0; + return -ENOMEM; + } + bank->skbuff[idx] = skb; + return 0; +} + +/* Pre-Allocate the skbs for the RX entries */ +static int hip4_smapper_allocate_skb_buffers(struct slsi_dev *sdev, struct hip4_smapper_bank *bank) +{ + unsigned int i; + unsigned int n; + int res; + + if (!bank) + return -EINVAL; + + n = bank->entries; + for (i = 0; i < n; i++) { + if (!bank->skbuff[i]) { + res = hip4_smapper_allocate_skb_buffer_entry(sdev, bank, i); + if (res != 0) + return res; + } + } + + return 0; +} + +static int hip4_smapper_free_skb_buffers(struct slsi_dev *sdev, struct hip4_smapper_bank *bank) +{ + unsigned int i; + unsigned int n; + + if (!bank) + return -EINVAL; + + n = bank->entries; + for (i = 0; i < n; i++) { + if (bank->skbuff[i]) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "SKB free: 0x%p at bank %d entry %d\n", bank->skbuff[i], bank->bank, i); + dma_unmap_single(sdev->dev, bank->skbuff_dma[i], bank->entry_size, DMA_FROM_DEVICE); + bank->skbuff_dma[i] = 0; + slsi_kfree_skb(bank->skbuff[i]); + bank->skbuff[i] = NULL; + } + } + + return 0; +} + +static int hip4_smapper_program(struct slsi_dev *sdev, struct hip4_smapper_bank *bank) +{ + unsigned int n; + + if (!bank) + return -EINVAL; + + n = bank->entries; + + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Programming Bank %d\n", bank->bank); + + return scsc_service_mifsmapper_write_sram(sdev->service, bank->bank, n, 0, bank->skbuff_dma); +} + +/* refill ISR. FW signals the Host whenever it wants to refill the smapper buffers */ +/* Only the Host Owned Buffers should be refilled */ +static void hip4_smapper_refill_isr(int irq, void *data) +{ + struct slsi_hip4 *hip = (struct slsi_hip4 *)data; + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + struct hip4_smapper_control *control; + struct hip4_smapper_bank *bank; + enum smapper_banks i; + unsigned long flags; + /* Temporary removed + * static DEFINE_RATELIMIT_STATE(ratelimit, 1 * HZ, 1); + */ + + control = &(hip->hip_priv->smapper_control); +#ifdef CONFIG_SCSC_QOS + /* Ignore request if TPUT is low or platform is in suspend */ + if (hip->hip_priv->pm_qos_state == SCSC_QOS_DISABLED || + atomic_read(&hip->hip_priv->in_suspend) || + *control->mbox_ptr == 0x0) { +#else + /* Ignore if platform is in suspend */ + if (atomic_read(&hip->hip_priv->in_suspend) || + *control->mbox_ptr == 0x0) { +#endif + /* + * Temporary removed + * if (__ratelimit(&ratelimit)) + * SLSI_DBG1_NODEV(SLSI_SMAPPER, "Ignore SMAPPER request. Invalid state.\n"); + */ + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(sdev->service, control->th_req); + return; + } + + spin_lock_irqsave(&control->smapper_lock, flags); + /* Check if FW has requested a BANK configuration */ + if (HIP4_SMAPPER_BANKS_CHECK_CONFIGURE(*control->mbox_ptr)) { + /* Temporary removed + * SLSI_DBG4_NODEV(SLSI_SMAPPER, "Trigger SMAPPER configuration\n"); + */ + scsc_service_mifsmapper_configure(sdev->service, SMAPPER_GRANULARITY); + HIP4_SMAPPER_BANKS_CONFIGURE_DONE(*control->mbox_ptr); + } + /* Read the first RX bank and check whether needs to be reprogrammed */ + for (i = RX_0; i < END_RX_BANKS; i++) { + bank = &hip->hip_priv->smapper_banks[i]; + + if (!bank->in_use) + continue; + + if (HIP4_SMAPPER_GET_BANK_OWNER(bank->bank, *control->mbox_ptr) == HIP_SMAPPER_OWNER_HOST) { + /* Temporary removed + * SLSI_DBG4_NODEV(SLSI_SMAPPER, "SKB allocation at bank %d\n", i); + */ + if (hip4_smapper_allocate_skb_buffers(sdev, bank)) { + /* Temporary removed + * SLSI_DBG4_NODEV(SLSI_SMAPPER, "Error Allocating skb buffers at bank %d. Setting owner to FW\n", i); + */ + HIP4_SMAPPER_SET_BANK_OWNER(bank->bank, *control->mbox_ptr, HIP_SMAPPER_OWNER_FW); + continue; + } + if (hip4_smapper_program(sdev, bank)) { + /* Temporary removed + * SLSI_DBG4_NODEV(SLSI_SMAPPER, "Error Programming bank %d. Setting owner to FW\n", i); + */ + HIP4_SMAPPER_SET_BANK_OWNER(bank->bank, *control->mbox_ptr, HIP_SMAPPER_OWNER_FW); + continue; + } + HIP4_SMAPPER_SET_BANK_STATE(bank->bank, *control->mbox_ptr, HIP_SMAPPER_STATUS_MAPPED); + HIP4_SMAPPER_SET_BANK_OWNER(bank->bank, *control->mbox_ptr, HIP_SMAPPER_OWNER_FW); + } + } + /* Inform FW that entries have been programmed */ + scsc_service_mifintrbit_bit_set(sdev->service, control->fh_ind, SCSC_MIFINTR_TARGET_R4); + + /* Clear interrupt */ + scsc_service_mifintrbit_bit_clear(sdev->service, control->th_req); + + spin_unlock_irqrestore(&control->smapper_lock, flags); +} + +int hip4_smapper_consume_entry(struct slsi_dev *sdev, struct slsi_hip4 *hip, struct sk_buff *skb_fapi) +{ + struct sk_buff *skb; + struct hip4_smapper_bank *bank; + u8 bank_num; + u8 entry; + u16 len; + u16 headroom; + struct hip4_smapper_descriptor *desc; + struct hip4_smapper_control *control; + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb_fapi); + + control = &(hip->hip_priv->smapper_control); + + desc = (struct hip4_smapper_descriptor *)skb_fapi->data; + + bank_num = desc->bank_num; + entry = desc->entry_num; + len = desc->entry_size; + headroom = desc->headroom; + + + if (bank_num >= HIP4_SMAPPER_TOTAL_BANKS) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Incorrect bank_num %d\n", bank_num); + goto error; + } + + /* Transform PHY BANK# with BANK# in Wlan service*/ + bank_num = control->lookuptable[bank_num]; + + bank = &hip->hip_priv->smapper_banks[bank_num]; + + if (entry > bank->entries) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Incorrect entry number %d\n", entry); + goto error; + } + + if (len > bank->entry_size) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Incorrect entry len %d\n", len); + goto error; + } + + skb = bank->skbuff[entry]; + if (!skb) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "SKB IS NULL at bank %d entry %d\n", bank_num, entry); + goto error; + } + + bank->skbuff[entry] = NULL; + dma_unmap_single(sdev->dev, bank->skbuff_dma[entry], bank->entry_size, DMA_FROM_DEVICE); + bank->skbuff_dma[entry] = 0; + + hip4_smapper_allocate_skb_buffer_entry(sdev, bank, entry); + + skb_reserve(skb, headroom); + skb_put(skb, len); + cb->skb_addr = skb; + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Consumed Bank %d Entry %d Len %d SKB smapper: 0x%p, SKB fapi %p\n", bank_num, entry, len, skb, skb_fapi); + + return 0; +error: + /* RX is broken.....*/ + return -EIO; +} + +void *hip4_smapper_get_skb_data(struct slsi_dev *sdev, struct slsi_hip4 *hip, struct sk_buff *skb_fapi) +{ + struct sk_buff *skb; + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb_fapi); + struct hip4_smapper_control *control; + + control = &(hip->hip_priv->smapper_control); + + skb = (struct sk_buff *)cb->skb_addr; + + if (!skb) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "NULL SKB smapper\n"); + return NULL; + } + + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Get SKB smapper: 0x%p, SKB fapi 0x%p\n", skb, skb_fapi); + return skb->data; +} + +struct sk_buff *hip4_smapper_get_skb(struct slsi_dev *sdev, struct slsi_hip4 *hip, struct sk_buff *skb_fapi) +{ + struct sk_buff *skb; + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb_fapi); + struct hip4_smapper_control *control; + + control = &(hip->hip_priv->smapper_control); + + skb = (struct sk_buff *)cb->skb_addr; + + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Get SKB smapper: 0x%p, SKB fapi 0x%p\n", skb, skb_fapi); + cb->free_ma_unitdat = true; + slsi_kfree_skb(skb_fapi); + + return skb; +} + +int hip4_smapper_init(struct slsi_dev *sdev, struct slsi_hip4 *hip) +{ + u8 i; + struct hip4_smapper_control *control; + + SLSI_DBG4_NODEV(SLSI_SMAPPER, "SMAPPER init\n"); + + control = &(hip->hip_priv->smapper_control); + + spin_lock_init(&control->smapper_lock); + + if (dma_set_mask_and_coherent(sdev->dev, DMA_BIT_MASK(64)) != 0) + return -EIO; + + if (!scsc_mx_service_alloc_mboxes(sdev->service, 1, &control->mbox_scb)) { + SLSI_DBG4_NODEV(SLSI_SMAPPER, "Unable to allocate mbox\n"); + return -ENODEV; + } + + /* Claim the RX buffers */ + hip4_smapper_alloc_bank(sdev, hip->hip_priv, RX_0, SMAPPER_GRANULARITY, HIP4_SMAPPER_BANK_LARGE); + hip4_smapper_alloc_bank(sdev, hip->hip_priv, RX_1, SMAPPER_GRANULARITY, HIP4_SMAPPER_BANK_LARGE); + hip4_smapper_alloc_bank(sdev, hip->hip_priv, RX_2, SMAPPER_GRANULARITY, HIP4_SMAPPER_BANK_LARGE); + hip4_smapper_alloc_bank(sdev, hip->hip_priv, RX_3, SMAPPER_GRANULARITY, HIP4_SMAPPER_BANK_LARGE); + /*Pre-allocate buffers */ + hip4_smapper_allocate_skb_buffers(sdev, &hip->hip_priv->smapper_banks[RX_0]); + hip4_smapper_allocate_skb_buffers(sdev, &hip->hip_priv->smapper_banks[RX_1]); + hip4_smapper_allocate_skb_buffers(sdev, &hip->hip_priv->smapper_banks[RX_2]); + hip4_smapper_allocate_skb_buffers(sdev, &hip->hip_priv->smapper_banks[RX_3]); + + /* Allocate Maxwell resources */ + control->th_req = + scsc_service_mifintrbit_register_tohost(sdev->service, hip4_smapper_refill_isr, hip); + control->fh_ind = + scsc_service_mifintrbit_alloc_fromhost(sdev->service, SCSC_MIFINTR_TARGET_R4); + + control->mbox_ptr = + scsc_mx_service_get_mbox_ptr(sdev->service, control->mbox_scb); + + /* All banks to REMAP and FW owner*/ + *control->mbox_ptr = 0x0; + + /* Update hip4 config table */ + hip->hip_control->config_v4.smapper_th_req = + control->th_req; + hip->hip_control->config_v4.smapper_fh_ind = + control->fh_ind; + hip->hip_control->config_v4.smapper_mbox_scb = + (u8)control->mbox_scb; + + for (i = RX_0; i < END_RX_BANKS; i++) { + u8 has_entries; + u8 bank; + + has_entries = hip->hip_priv->smapper_banks[i].entries; + if (has_entries) { + /* Get the bank index */ + bank = hip->hip_priv->smapper_banks[i].bank; + hip->hip_control->config_v4.smapper_bank_addr[bank] = scsc_service_mifsmapper_get_bank_base_address(sdev->service, bank); + hip->hip_control->config_v4.smapper_entries_banks[bank] = has_entries; + hip->hip_control->config_v4.smapper_pow_sz[bank] = 12; /* 4kB */ + } + } + return 0; +} + +void hip4_smapper_deinit(struct slsi_dev *sdev, struct slsi_hip4 *hip) +{ + struct hip4_smapper_bank *bank; + struct hip4_smapper_control *control; + unsigned long flags; + u8 i; + + SLSI_DBG4_NODEV(SLSI_SMAPPER, "SMAPPER deinit\n"); + control = &(hip->hip_priv->smapper_control); + + spin_lock_irqsave(&control->smapper_lock, flags); + for (i = RX_0; i < END_RX_BANKS; i++) { + bank = &hip->hip_priv->smapper_banks[i]; + bank->in_use = false; + hip4_smapper_free_skb_buffers(sdev, bank); + kfree(bank->skbuff_dma); + kfree(bank->skbuff); + scsc_service_mifsmapper_free_bank(sdev->service, bank->bank); + } + spin_unlock_irqrestore(&control->smapper_lock, flags); + + scsc_service_mifintrbit_unregister_tohost(sdev->service, control->th_req); + scsc_service_mifintrbit_free_fromhost(sdev->service, control->fh_ind, SCSC_MIFINTR_TARGET_R4); + scsc_service_free_mboxes(sdev->service, 1, control->mbox_scb); + +} diff --git a/drivers/net/wireless/scsc/hip4_smapper.h b/drivers/net/wireless/scsc/hip4_smapper.h new file mode 100644 index 000000000000..92270116da72 --- /dev/null +++ b/drivers/net/wireless/scsc/hip4_smapper.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __HIP4_SMAPPER_H__ +#define __HIP4_SMAPPER_H__ + +struct slsi_dev; +struct slsi_hip4; + +enum smapper_type { + TX_5G, + TX_2G, + RX +}; + +#define HIP4_SMAPPER_TOTAL_BANKS 10 + +#define HIP4_SMAPPER_BANK_SMALL false +#define HIP4_SMAPPER_BANK_LARGE true + +#define HIP_SMAPPER_OWNER_FW 0 +#define HIP_SMAPPER_OWNER_HOST 1 + +#define HIP_SMAPPER_STATUS_REFILL 0 +#define HIP_SMAPPER_STATUS_MAPPED 1 + +#define HIP4_SMAPPER_OTHER_CPU 0 +#define HIP4_SMAPPER_OWN_CPU 1 + +#define HIP4_SMAPPER_STATE_OUT 0 +#define HIP4_SMAPPER_STATE_WANT 1 +#define HIP4_SMAPPER_STATE_CLAIM 2 + +#define HIP4_SMAPPER_BANKS_CHECK_CONFIGURE(reg) (((reg) >> 30) == 0 ? 1 : 0) +#define HIP4_SMAPPER_BANKS_CONFIGURE_DONE(reg) ((reg) = (reg) | 0xc0000000) + +#define HIP4_SMAPPER_GET_BANK_STATE(b, reg) (((0x1 << ((b) * 2)) & (reg)) > 0 ? 1 : 0) +#define HIP4_SMAPPER_GET_BANK_OWNER(b, reg) (((0x2 << ((b) * 2)) & (reg)) > 0 ? 1 : 0) + +#define HIP4_SMAPPER_SET_BANK_STATE(b, reg, val) ((reg) = ((reg) & ~(0x1 << ((b) * 2))) | \ + ((val) << ((b) * 2))) +#define HIP4_SMAPPER_SET_BANK_OWNER(b, reg, val) ((reg) = (reg & ~(0x2 << ((b) * 2))) | \ + (((val) << 1) << ((b) * 2))) + + +struct hip4_smapper_descriptor { + u8 bank_num; + u8 entry_num; + u16 entry_size; + u16 headroom; +}; + +/* There should be an agreement between host and FW about bank mapping */ +/* TODO : think about this agreement */ +enum smapper_banks { + RX_0, + RX_1, + RX_2, + RX_3, + END_RX_BANKS +}; + +struct hip4_smapper_control { + u32 emul_loc; /* Smapper emulator location in MIF_ADDR */ + u32 emul_sz; /* Smapper emulator size */ + u8 th_req; /* TH smapper request interrupt bit position */ + u8 fh_ind; /* FH smapper ind interrupt bit position */ + u32 mbox_scb; /* SMAPPER MBOX scoreboard location */ + u32 *mbox_ptr; /* Mbox pointer */ + spinlock_t smapper_lock; + /* Lookup table to map the virtual bank mapping in wlan with the phy mapping in HW */ + /* Currently is safe to use this indexing as only WIFI is using smapper */ + u8 lookuptable[HIP4_SMAPPER_TOTAL_BANKS]; +}; + +struct hip4_smapper_bank { + enum smapper_type type; + u16 entries; + bool in_use; + u8 bank; + u8 cur; + u32 entry_size; + struct sk_buff **skbuff; + dma_addr_t *skbuff_dma; + struct hip4_smapper_control_entry *entry; + u16 align; +}; + +int hip4_smapper_init(struct slsi_dev *sdev, struct slsi_hip4 *hip); +void hip4_smapper_deinit(struct slsi_dev *sdev, struct slsi_hip4 *hip); + +struct mbulk *hip4_smapper_send(struct slsi_hip4 *hip, struct sk_buff *skb, int *val); +int hip4_smapper_consume_entry(struct slsi_dev *sdev, struct slsi_hip4 *hip, struct sk_buff *skb_fapi); +void *hip4_smapper_get_skb_data(struct slsi_dev *sdev, struct slsi_hip4 *hip, struct sk_buff *skb_fapi); +struct sk_buff *hip4_smapper_get_skb(struct slsi_dev *sdev, struct slsi_hip4 *hip, struct sk_buff *skb_fapi); +#endif diff --git a/drivers/net/wireless/scsc/hip_bh.h b/drivers/net/wireless/scsc/hip_bh.h new file mode 100644 index 000000000000..1919d51cc9db --- /dev/null +++ b/drivers/net/wireless/scsc/hip_bh.h @@ -0,0 +1,15 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_HIP_BH_H__ +#define __SLSI_HIP_BH_H__ + +#include "wl_result.h" + +CsrResult slsi_sdio_func_drv_register(void); +void slsi_sdio_func_drv_unregister(void); + +#endif /* __SLSI_HIP_BH_H__ */ diff --git a/drivers/net/wireless/scsc/ioctl.c b/drivers/net/wireless/scsc/ioctl.c new file mode 100755 index 000000000000..51bf1098b968 --- /dev/null +++ b/drivers/net/wireless/scsc/ioctl.c @@ -0,0 +1,2775 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "ioctl.h" +#include "debug.h" +#include "mlme.h" +#include "mgt.h" +#include "cac.h" +#include "hip.h" +#include "netif.h" +#include +#include +#include +#include "mib.h" +#include +#include +#include "dev.h" +#include "fapi.h" + +#define CMD_RXFILTERADD "RXFILTER-ADD" +#define CMD_RXFILTERREMOVE "RXFILTER-REMOVE" +#define CMD_RXFILTERSTART "RXFILTER-START" +#define CMD_RXFILTERSTOP "RXFILTER-STOP" +#define CMD_SETCOUNTRYREV "SETCOUNTRYREV" +#define CMD_GETCOUNTRYREV "GETCOUNTRYREV" +#define CMD_SETROAMTRIGGER "SETROAMTRIGGER" +#define CMD_GETROAMTRIGGER "GETROAMTRIGGER" +#define CMD_SETSUSPENDMODE "SETSUSPENDMODE" +#define CMD_SETROAMDELTA "SETROAMDELTA" +#define CMD_GETROAMDELTA "GETROAMDELTA" +#define CMD_SETROAMSCANPERIOD "SETROAMSCANPERIOD" +#define CMD_GETROAMSCANPERIOD "GETROAMSCANPERIOD" +#define CMD_SETFULLROAMSCANPERIOD "SETFULLROAMSCANPERIOD" +#define CMD_GETFULLROAMSCANPERIOD "GETFULLROAMSCANPERIOD" +#define CMD_SETSCANCHANNELTIME "SETSCANCHANNELTIME" +#define CMD_GETSCANCHANNELTIME "GETSCANCHANNELTIME" +#define CMD_SETSCANNPROBES "SETSCANNPROBES" +#define CMD_GETSCANNPROBES "GETSCANNPROBES" +#define CMD_SETROAMMODE "SETROAMMODE" +#define CMD_GETROAMMODE "GETROAMMODE" +#define CMD_SETROAMINTRABAND "SETROAMINTRABAND" +#define CMD_GETROAMINTRABAND "GETROAMINTRABAND" +#define CMD_SETROAMBAND "SETROAMBAND" +#define CMD_GETROAMBAND "GETROAMBAND" +#define CMD_SETROAMSCANCONTROL "SETROAMSCANCONTROL" +#define CMD_GETROAMSCANCONTROL "GETROAMSCANCONTROL" +#define CMD_SETSCANHOMETIME "SETSCANHOMETIME" +#define CMD_GETSCANHOMETIME "GETSCANHOMETIME" +#define CMD_SETSCANHOMEAWAYTIME "SETSCANHOMEAWAYTIME" +#define CMD_GETSCANHOMEAWAYTIME "GETSCANHOMEAWAYTIME" +#define CMD_SETOKCMODE "SETOKCMODE" +#define CMD_GETOKCMODE "GETOKCMODE" +#define CMD_SETWESMODE "SETWESMODE" +#define CMD_GETWESMODE "GETWESMODE" +#define CMD_SET_PMK "SET_PMK" +#define CMD_HAPD_GET_CHANNEL "HAPD_GET_CHANNEL" +#define CMD_SET_SAP_CHANNEL_LIST "SET_SAP_CHANNEL_LIST" +#define CMD_REASSOC "REASSOC" +#define CMD_SETROAMSCANCHANNELS "SETROAMSCANCHANNELS" +#define CMD_GETROAMSCANCHANNELS "GETROAMSCANCHANNELS" +#define CMD_SENDACTIONFRAME "SENDACTIONFRAME" +#define CMD_HAPD_MAX_NUM_STA "HAPD_MAX_NUM_STA" +#define CMD_COUNTRY "COUNTRY" +#define CMD_SEND_GK "SEND_GK" +#define CMD_SETAPP2PWPSIE "SET_AP_P2P_WPS_IE" +#define CMD_P2PSETPS "P2P_SET_PS" +#define CMD_P2PSETNOA "P2P_SET_NOA" +#define CMD_P2PECSA "P2P_ECSA" +#define CMD_P2PLOSTART "P2P_LO_START" +#define CMD_P2PLOSTOP "P2P_LO_STOP" +#define CMD_TDLSCHANNELSWITCH "TDLS_CHANNEL_SWITCH" +#define CMD_SETROAMOFFLOAD "SETROAMOFFLOAD" +#define CMD_SETROAMOFFLAPLIST "SETROAMOFFLAPLIST" +#ifdef CONFIG_SCSC_WLAN_LOW_LATENCY_MODE +#define CMD_SET_LATENCY_MODE "SET_LATENCY_MODE" +#define CMD_SET_POWER_MGMT "SET_POWER_MGMT" +#endif + +#define CMD_SETBAND "SETBAND" +#define CMD_GETBAND "GETBAND" +#define CMD_SET_FCC_CHANNEL "SET_FCC_CHANNEL" + +#define CMD_FAKEMAC "FAKEMAC" + +#define CMD_GETBSSRSSI "GET_BSS_RSSI" +#define CMD_GETBSSINFO "GETBSSINFO" +#define CMD_GETSTAINFO "GETSTAINFO" +#define CMD_GETASSOCREJECTINFO "GETASSOCREJECTINFO" + +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +#define CMD_BEACON_RECV "BEACON_RECV" +#endif +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +#define CMD_SET_ENHANCED_ARP_TARGET "SET_ENHANCED_ARP_TARGET" +#define CMD_GET_ENHANCED_ARP_COUNTS "GET_ENHANCED_ARP_COUNTS" +#endif + +/* Known commands from framework for which no handlers */ +#define CMD_AMPDU_MPDU "AMPDU_MPDU" +#define CMD_BTCOEXMODE "BTCOEXMODE" +#define CMD_BTCOEXSCAN_START "BTCOEXSCAN-START" +#define CMD_BTCOEXSCAN_STOP "BTCOEXSCAN-STOP" +#define CMD_CHANGE_RL "CHANGE_RL" +#define CMD_INTERFACE_CREATE "INTERFACE_CREATE" +#define CMD_INTERFACE_DELETE "INTERFACE_DELETE" +#define CMD_SET_INDOOR_CHANNELS "SET_INDOOR_CHANNELS" +#define CMD_GET_INDOOR_CHANNELS "GET_INDOOR_CHANNELS" +#define CMD_LTECOEX "LTECOEX" +#define CMD_MIRACAST "MIRACAST" +#define CMD_RESTORE_RL "RESTORE_RL" +#define CMD_RPSMODE "RPSMODE" +#define CMD_SETCCXMODE "SETCCXMODE" +#define CMD_SETDFSSCANMODE "SETDFSSCANMODE" +#define CMD_SETJOINPREFER "SETJOINPREFER" +#define CMD_SETSINGLEANT "SETSINGLEANT" +#define CMD_SET_TX_POWER_CALLING "SET_TX_POWER_CALLING" + +#define CMD_DRIVERDEBUGDUMP "DEBUG_DUMP" +#define CMD_DRIVERDEBUGCOMMAND "DEBUG_COMMAND" +#define CMD_TESTFORCEHANG "SLSI_TEST_FORCE_HANG" +#define CMD_GETREGULATORY "GETREGULATORY" + +#define CMD_SET_TX_POWER_SAR "SET_TX_POWER_SAR" +#define CMD_GET_TX_POWER_SAR "GET_TX_POWER_SAR" + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER +#define CMD_ENHANCED_PKT_FILTER "ENHANCED_PKT_FILTER" +#endif + +#ifdef CONFIG_SCSC_WLAN_SET_NUM_ANTENNAS +#define CMD_SET_NUM_ANTENNAS "SET_NUM_ANTENNAS" +#endif + +#define ROAMOFFLAPLIST_MIN 1 +#define ROAMOFFLAPLIST_MAX 100 + +static int slsi_parse_hex(unsigned char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; +} + +static void slsi_machexstring_to_macarray(char *mac_str, u8 *mac_arr) +{ + mac_arr[0] = slsi_parse_hex(mac_str[0]) << 4 | slsi_parse_hex(mac_str[1]); + mac_arr[1] = slsi_parse_hex(mac_str[3]) << 4 | slsi_parse_hex(mac_str[4]); + mac_arr[2] = slsi_parse_hex(mac_str[6]) << 4 | slsi_parse_hex(mac_str[7]); + mac_arr[3] = slsi_parse_hex(mac_str[9]) << 4 | slsi_parse_hex(mac_str[10]); + mac_arr[4] = slsi_parse_hex(mac_str[12]) << 4 | slsi_parse_hex(mac_str[13]); + mac_arr[5] = slsi_parse_hex(mac_str[15]) << 4 | slsi_parse_hex(mac_str[16]); +} + +static ssize_t slsi_set_suspend_mode(struct net_device *dev, char *command) +{ + int vif; + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct slsi_dev *sdev = netdev_vif->sdev; + int user_suspend_mode; + int previous_suspend_mode; + u8 host_state; + int ret = 0; + + user_suspend_mode = *(command + strlen(CMD_SETSUSPENDMODE) + 1) - '0'; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + previous_suspend_mode = sdev->device_config.user_suspend_mode; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + if (user_suspend_mode != previous_suspend_mode) { + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (vif = 1; vif <= CONFIG_SCSC_WLAN_MAX_INTERFACES; vif++) { + struct net_device *dev = slsi_get_netdev_locked(sdev, vif); + struct netdev_vif *ndev_vif; + + if (!dev) + continue; + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if ((ndev_vif->activated) && + (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + if (user_suspend_mode) + ret = slsi_update_packet_filters(sdev, dev); + else + ret = slsi_clear_packet_filters(sdev, dev); + if (ret != 0) + SLSI_NET_ERR(dev, "Error in updating /clearing the packet filters,ret=%d", ret); + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + } + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + sdev->device_config.user_suspend_mode = user_suspend_mode; + host_state = sdev->device_config.host_state; + + if (!sdev->device_config.user_suspend_mode) + host_state = host_state | FAPI_HOSTSTATE_LCD_ACTIVE; + else + host_state = host_state & ~FAPI_HOSTSTATE_LCD_ACTIVE; + sdev->device_config.host_state = host_state; + + ret = slsi_mlme_set_host_state(sdev, dev, host_state); + if (ret != 0) + SLSI_NET_ERR(dev, "Error in setting the Host State, ret=%d", ret); + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return ret; +} + +static ssize_t slsi_set_p2p_oppps(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif; + struct slsi_dev *sdev; + u8 *p2p_oppps_param = NULL; + int offset = 0; + unsigned int ct_param; + unsigned int legacy_ps; + unsigned int opp_ps; + int readbyte = 0; + int result = 0; + + p2p_oppps_param = command + strlen(CMD_P2PSETPS) + 1; + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* The NOA param shall be added only after P2P-VIF is active */ + if ((!ndev_vif->activated) || (ndev_vif->iftype != NL80211_IFTYPE_P2P_GO)) { + SLSI_ERR_NODEV("P2P GO vif not activated\n"); + result = -EINVAL; + goto exit; + } + + sdev = ndev_vif->sdev; + readbyte = slsi_str_to_int(&p2p_oppps_param[offset], &legacy_ps); + if (!readbyte) { + SLSI_ERR(sdev, "ct_param: failed to read legacy_ps\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + + readbyte = slsi_str_to_int(&p2p_oppps_param[offset], &opp_ps); + if (!readbyte) { + SLSI_ERR(sdev, "ct_param: failed to read ct_param\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + + readbyte = slsi_str_to_int(&p2p_oppps_param[offset], &ct_param); + if (!readbyte) { + SLSI_ERR(sdev, "ct_param: failed to read ct_param\n"); + result = -EINVAL; + goto exit; + } + + if (opp_ps == 0) + result = slsi_mlme_set_ctwindow(sdev, dev, opp_ps); + else if (ct_param < (unsigned int)ndev_vif->ap.beacon_interval) + result = slsi_mlme_set_ctwindow(sdev, dev, ct_param); + else + SLSI_DBG1(sdev, SLSI_CFG80211, "p2p ct window = %d is out of range for beacon interval(%d)\n", ct_param, ndev_vif->ap.beacon_interval); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return result; +} + +static ssize_t slsi_p2p_set_noa_params(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif; + struct slsi_dev *sdev; + int result = 0; + u8 *noa_params = NULL; + int offset = 0; + int readbyte = 0; + unsigned int noa_count; + unsigned int duration; + unsigned int interval; + + noa_params = command + strlen(CMD_P2PSETNOA) + 1; + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + /* The NOA param shall be added only after P2P-VIF is active */ + if ((!ndev_vif->activated) || (ndev_vif->iftype != NL80211_IFTYPE_P2P_GO)) { + SLSI_ERR_NODEV("P2P GO vif not activated\n"); + result = -EINVAL; + goto exit; + } + + sdev = ndev_vif->sdev; + readbyte = slsi_str_to_int(&noa_params[offset], &noa_count); + if (!readbyte) { + SLSI_ERR(sdev, "noa_count: failed to read a numeric value\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + + readbyte = slsi_str_to_int(&noa_params[offset], &interval); + if (!readbyte) { + SLSI_ERR(sdev, "interval: failed to read a numeric value\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + + readbyte = slsi_str_to_int(&noa_params[offset], &duration); + if (!readbyte) { + SLSI_ERR(sdev, "duration: failed to read a numeric value, at offset(%d)\n", offset); + result = -EINVAL; + goto exit; + } + + /* Skip start time */ + result = slsi_mlme_set_p2p_noa(sdev, dev, noa_count, interval, duration); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return result; +} + +static ssize_t slsi_p2p_ecsa(struct net_device *dev, char *command) +{ + struct netdev_vif *ndev_vif; + struct netdev_vif *group_dev_vif; + struct slsi_dev *sdev; + struct net_device *group_dev = NULL; + int result = 0; + u8 *ecsa_params = NULL; + int offset = 0; + int readbyte = 0; + unsigned int channel; + unsigned int bandwidth; + u16 center_freq = 0; + u16 chan_info = 0; + struct cfg80211_chan_def chandef; + enum nl80211_band band; + enum nl80211_channel_type chan_type = NL80211_CHAN_NO_HT; + + ecsa_params = command + strlen(CMD_P2PECSA) + 1; + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + sdev = ndev_vif->sdev; + group_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2PX_SWLAN); + if (!group_dev) { + SLSI_INFO(sdev, "No Group net_dev found\n"); + return -EINVAL; + } + readbyte = slsi_str_to_int(&ecsa_params[offset], &channel); + if (!readbyte) { + SLSI_ERR(sdev, "channel: failed to read a numeric value\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(&ecsa_params[offset], &bandwidth); + if (!readbyte) { + SLSI_ERR(sdev, "bandwidth: failed to read a numeric value\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + band = (channel <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + center_freq = ieee80211_channel_to_frequency(channel, band); + SLSI_DBG1(sdev, SLSI_CFG80211, "p2p ecsa_params (center_freq)= (%d)\n", center_freq); + chandef.chan = ieee80211_get_channel(sdev->wiphy, center_freq); + chandef.width = (band == NL80211_BAND_2GHZ) ? NL80211_CHAN_WIDTH_20_NOHT : NL80211_CHAN_WIDTH_80; + +#ifndef SSB_4963_FIXED + /* Default HT40 configuration */ + if (sdev->band_5g_supported) { + if (bandwidth == 80) { + chandef.width = NL80211_CHAN_WIDTH_40; + bandwidth = 40; + if (channel == 36 || channel == 44 || channel == 149 || channel == 157) + chan_type = NL80211_CHAN_HT40PLUS; + else + chan_type = NL80211_CHAN_HT40MINUS; + } + } +#endif + if (channel == 165 && bandwidth != 20) { + bandwidth = 20; + chan_type = NL80211_CHAN_HT20; + } + cfg80211_chandef_create(&chandef, chandef.chan, chan_type); + chan_info = slsi_get_chann_info(sdev, &chandef); + if (bandwidth != 20) + center_freq = slsi_get_center_freq1(sdev, chan_info, center_freq); + group_dev_vif = netdev_priv(group_dev); + SLSI_MUTEX_LOCK(group_dev_vif->vif_mutex); + result = slsi_mlme_channel_switch(sdev, group_dev, center_freq, chan_info); + SLSI_MUTEX_UNLOCK(group_dev_vif->vif_mutex); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return result; +} + +static ssize_t slsi_ap_vendor_ies_write(struct slsi_dev *sdev, struct net_device *dev, u8 *ie, + size_t ie_len, u16 purpose) +{ + u8 *vendor_ie = NULL; + int result = 0; + struct netdev_vif *ndev_vif; + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + /* During AP start before mlme_start_req, supplicant calls set_ap_wps_ie() to send the vendor IEs for each + * beacon, probe response and association response. As we get all of them in mlme_start_req, ignoring the + * same which comes before adding GO VIF + */ + if (!ndev_vif->activated) { + SLSI_DBG1(sdev, SLSI_CFG80211, "vif not activated\n"); + result = 0; + goto exit; + } + if (!(ndev_vif->iftype == NL80211_IFTYPE_P2P_GO || ndev_vif->iftype == NL80211_IFTYPE_AP)) { + SLSI_ERR(sdev, "Not AP or P2P interface. interfaceType:%d\n", ndev_vif->iftype); + result = -EINVAL; + goto exit; + } + + vendor_ie = kmalloc(ie_len, GFP_KERNEL); + if (!vendor_ie) { + SLSI_ERR(sdev, "kmalloc failed\n"); + result = -ENOMEM; + goto exit; + } + memcpy(vendor_ie, ie, ie_len); + + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + result = slsi_ap_prepare_add_info_ies(ndev_vif, vendor_ie, ie_len); + + if (result == 0) + result = slsi_mlme_add_info_elements(sdev, dev, purpose, ndev_vif->ap.add_info_ies, ndev_vif->ap.add_info_ies_len); + + slsi_clear_cached_ies(&ndev_vif->ap.add_info_ies, &ndev_vif->ap.add_info_ies_len); + kfree(vendor_ie); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return result; +} + +static ssize_t slsi_set_ap_p2p_wps_ie(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int readbyte = 0; + int offset = 0; + int result = 0; + enum if_type { + IF_TYPE_NONE, + IF_TYPE_P2P_DEVICE, + IF_TYPE_AP_P2P + } iftype = IF_TYPE_NONE; + enum frame_type { + FRAME_TYPE_NONE, + FRAME_TYPE_BEACON, + FRAME_TYPE_PROBE_RESPONSE, + FRAME_TYPE_ASSOC_RESPONSE + } frametype = FRAME_TYPE_NONE; + u8 *params = command + strlen(CMD_SETAPP2PWPSIE) + 1; + int params_len = buf_len - strlen(CMD_SETAPP2PWPSIE) - 1; + + readbyte = slsi_str_to_int(¶ms[offset], (int *)&frametype); + if (!readbyte) { + SLSI_ERR(sdev, "frametype: failed to read a numeric value\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(¶ms[offset], (int *)&iftype); + if (!readbyte) { + SLSI_ERR(sdev, "iftype: failed to read a numeric value\n"); + result = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + params_len = params_len - offset; + + SLSI_NET_DBG2(dev, SLSI_NETDEV, + "command=%s, frametype=%d, iftype=%d, total buf_len=%d, params_len=%d\n", + command, frametype, iftype, buf_len, params_len); + + /* check the net device interface type */ + if (iftype == IF_TYPE_P2P_DEVICE) { + u8 *probe_resp_ie = NULL; /* params+offset; */ + + if (frametype != FRAME_TYPE_PROBE_RESPONSE) { + SLSI_NET_ERR(dev, "Wrong frame type received\n"); + goto exit; + } + probe_resp_ie = kmalloc(params_len, GFP_KERNEL); + if (probe_resp_ie == NULL) { + SLSI_ERR(sdev, "Malloc for IEs failed\n"); + return -ENOMEM; + } + + memcpy(probe_resp_ie, params+offset, params_len); + + return slsi_p2p_dev_probe_rsp_ie(sdev, dev, probe_resp_ie, params_len); + } else if (iftype == IF_TYPE_AP_P2P) { + if (frametype == FRAME_TYPE_BEACON) + return slsi_ap_vendor_ies_write(sdev, dev, params + offset, params_len, FAPI_PURPOSE_BEACON); + else if (frametype == FRAME_TYPE_PROBE_RESPONSE) + return slsi_ap_vendor_ies_write(sdev, dev, params + offset, params_len, + FAPI_PURPOSE_PROBE_RESPONSE); + else if (frametype == FRAME_TYPE_ASSOC_RESPONSE) + return slsi_ap_vendor_ies_write(sdev, dev, params + offset, params_len, + FAPI_PURPOSE_ASSOCIATION_RESPONSE); + } +exit: + return result; +} + +/** + * P2P_LO_START handling. + * Add unsync vif, register for action frames and set the listen channel. + * The probe response IEs would be configured later. + */ +static int slsi_p2p_lo_start(struct net_device *dev, char *command) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct ieee80211_channel *chan = NULL; + char *lo_params = NULL; + unsigned int channel, duration, interval, count; + int ret = 0; + int freq; + int readbyte = 0; + enum nl80211_band band; + int offset = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* Reject LO if other operations are in progress. Back to back LO can be received. + * In such a case, if state is Listening then the listen offload flag should be true else + * reject the request as the Listening state would then be due to ROC. + */ + if ((sdev->p2p_state == P2P_SCANNING) || (sdev->p2p_state > P2P_LISTENING) || + ((sdev->p2p_state == P2P_LISTENING) && (!ndev_vif->unsync.listen_offload))) { + SLSI_NET_ERR(dev, "Reject LO due to ongoing P2P operation (state: %s)\n", slsi_p2p_state_text(sdev->p2p_state)); + ret = -EINVAL; + goto exit; + } + + lo_params = command + strlen(CMD_P2PLOSTART) + 1; + readbyte = slsi_str_to_int(&lo_params[offset], &channel); + if (!readbyte) { + SLSI_ERR(sdev, "channel: failed to read a numeric value\n"); + ret = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(&lo_params[offset], &duration); + if (!readbyte) { + SLSI_ERR(sdev, "duration: failed to read a numeric value\n"); + ret = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(&lo_params[offset], &interval); + if (!readbyte) { + SLSI_ERR(sdev, "interval: failed to read a numeric value\n"); + ret = -EINVAL; + goto exit; + } + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(&lo_params[offset], &count); + if (!readbyte) { + SLSI_ERR(sdev, "count: failed to read a numeric value\n"); + ret = -EINVAL; + goto exit; + } + + if (!ndev_vif->activated) { + ret = slsi_mlme_add_vif(sdev, dev, dev->dev_addr, dev->dev_addr); + if (ret != 0) { + SLSI_NET_ERR(dev, "Unsync vif addition failed\n"); + goto exit; + } + + ndev_vif->activated = true; + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + + ret = slsi_mlme_register_action_frame(sdev, dev, SLSI_ACTION_FRAME_PUBLIC, SLSI_ACTION_FRAME_PUBLIC); + if (ret != 0) { + SLSI_NET_ERR(dev, "Action frame registration for unsync vif failed\n"); + goto exit_with_vif_deactivate; + } + } + + /* Send set_channel irrespective of the values of LO parameters as they are not cached + * in driver to check whether they have changed. + */ + band = (channel <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(channel, band); + chan = ieee80211_get_channel(sdev->wiphy, freq); + if (!chan) { + SLSI_NET_ERR(dev, "Incorrect channel: %u - Listen Offload failed\n", channel); + ret = -EINVAL; + goto exit_with_vif_deactivate; + } + + ret = slsi_mlme_set_channel(sdev, dev, chan, duration, interval, count); + if (ret != 0) { + SLSI_NET_ERR(dev, "Set channel for unsync vif failed\n"); + goto exit_with_vif_deactivate; + } else { + ndev_vif->chan = chan; + ndev_vif->driver_channel = chan->hw_value; + } + /* If framework sends the values for listen offload as 1,500,5000 and 6, + * where 5000ms (5 seconds) is the listen interval which needs to be repeated + * 6 times(i.e. count). Hence listen_end_ind comes after 30 seconds + * (6 * 5000 = 30000ms) Hence host should wait 31 seconds to delete the + * unsync VIF for one such P2P listen offload request. + */ + slsi_p2p_queue_unsync_vif_del_work(ndev_vif, interval * count + 1000); + ndev_vif->unsync.listen_offload = true; + SLSI_P2P_STATE_CHANGE(ndev_vif->sdev, P2P_LISTENING); + goto exit; + +exit_with_vif_deactivate: + slsi_p2p_vif_deactivate(sdev, dev, true); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return ret; +} + +/** + * P2P_LO_STOP handling. + * Clear listen offload flag. + * Delete the P2P unsynchronized vif. + */ +static int slsi_p2p_lo_stop(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + WARN_ON((!ndev_vif->unsync.listen_offload) || (ndev_vif->sdev->p2p_state != P2P_LISTENING)); + + ndev_vif->unsync.listen_offload = false; + + /* Deactivating the p2p unsynchronized vif */ + if (ndev_vif->sdev->p2p_state == P2P_LISTENING) + slsi_p2p_vif_deactivate(ndev_vif->sdev, ndev_vif->wdev.netdev, true); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return 0; +} + +static ssize_t slsi_rx_filter_num_write(struct net_device *dev, int add_remove, int filter_num) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int ret = 0; + + if (add_remove) + sdev->device_config.rx_filter_num = filter_num; + else + sdev->device_config.rx_filter_num = 0; + return ret; +} + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if !defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION < 90000) +static ssize_t slsi_create_interface(struct net_device *dev, char *intf_name) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct net_device *ap_dev; + + ap_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2PX_SWLAN); + if (ap_dev && (strcmp(ap_dev->name, intf_name) == 0)) { + SLSI_NET_ERR(dev, "%s already created\n", intf_name); + return -EINVAL; + } + + ap_dev = slsi_dynamic_interface_create(sdev->wiphy, intf_name, NL80211_IFTYPE_AP, NULL); + if (ap_dev) { + sdev->netdev_ap = ap_dev; + return 0; + } + + SLSI_NET_ERR(dev, "Failed to create AP interface %s\n", intf_name); + return -EINVAL; +} + +static ssize_t slsi_delete_interface(struct net_device *dev, char *intf_name) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + + if (strcmp(intf_name, CONFIG_SCSC_AP_INTERFACE_NAME) == 0) + dev = sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]; + + if (!dev) { + SLSI_WARN(sdev, "AP dev is NULL"); + return -EINVAL; + } + ndev_vif = netdev_priv(dev); + + if (ndev_vif->activated) + slsi_stop_net_dev(sdev, dev); + slsi_netif_remove_rtlnl_locked(sdev, dev); + + sdev->netdev_ap = NULL; + SLSI_DBG1_NODEV(SLSI_MLME, "Successfully deleted AP interface %s ", intf_name); + + return 0; +} +#endif + +static ssize_t slsi_set_indoor_channels(struct net_device *dev, char *arg) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int readbyte = 0; + int offset = 0; + int res; + int ret; + + readbyte = slsi_str_to_int(&arg[offset], &res); + + ret = slsi_set_mib_wifi_sharing_5ghz_channel(sdev, SLSI_PSID_UNIFI_WI_FI_SHARING5_GHZ_CHANNEL, + res, offset, readbyte, arg); + + return ret; +} + +static ssize_t slsi_get_indoor_channels(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + char op[150] = ""; + char int_string[30] = ""; + int i; + int len = 0; + + SLSI_DBG1_NODEV(SLSI_MLME, "GET_INDOOR_CHANNELS : %d ", sdev->num_5g_restricted_channels); + + for (i = 0; i < sdev->num_5g_restricted_channels; i++) { + sprintf(int_string, "%d", sdev->wifi_sharing_5g_restricted_channels[i]); + strcat(op, int_string); + strcat(op, " "); + } + + len = snprintf(command, buf_len, "%d %s", sdev->num_5g_restricted_channels, op); + + return len; +} +#endif +static ssize_t slsi_set_country_rev(struct net_device *dev, char *country_code) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + char alpha2_rev[4]; + int status = 0; + + if (!country_code) + return -EINVAL; + + memcpy(alpha2_rev, country_code, 4); + + status = slsi_set_country_update_regd(sdev, alpha2_rev, 4); + + return status; +} + +static ssize_t slsi_get_country_rev(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + u8 buf[5]; + int len = 0; + + memset(buf, 0, sizeof(buf)); + + len = snprintf(command, buf_len, "%s %c%c %d", CMD_GETCOUNTRYREV, + sdev->device_config.domain_info.regdomain->alpha2[0], + sdev->device_config.domain_info.regdomain->alpha2[1], + sdev->device_config.domain_info.regdomain->dfs_region); + + return len; +} + +#ifdef CONFIG_SCSC_WLAN_WES_NCHO +static ssize_t slsi_roam_scan_trigger_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_RSSI_ROAM_SCAN_TRIGGER, mib_value); +} + +static ssize_t slsi_roam_scan_trigger_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_RSSI_ROAM_SCAN_TRIGGER, &mib_value); + if (res) + return res; + res = snprintf(command, buf_len, "%s %d", CMD_GETROAMTRIGGER, mib_value); + return res; +} + +static ssize_t slsi_roam_delta_trigger_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_DELTA_TRIGGER, mib_value); +} + +static ssize_t slsi_roam_delta_trigger_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_DELTA_TRIGGER, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETROAMDELTA, mib_value); + return res; +} + +static ssize_t slsi_cached_channel_scan_period_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_CACHED_CHANNEL_SCAN_PERIOD, mib_value * 1000000); +} + +static ssize_t slsi_cached_channel_scan_period_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_CACHED_CHANNEL_SCAN_PERIOD, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETROAMSCANPERIOD, mib_value / 1000000); + + return res; +} + +static ssize_t slsi_full_roam_scan_period_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_FULL_ROAM_SCAN_PERIOD, mib_value * 1000000); +} + +static ssize_t slsi_full_roam_scan_period_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_FULL_ROAM_SCAN_PERIOD, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETFULLROAMSCANPERIOD, mib_value / 1000000); + + return res; +} + +static ssize_t slsi_roam_scan_max_active_channel_time_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_MAX_ACTIVE_CHANNEL_TIME, mib_value); +} + +static ssize_t slsi_roam_scan_max_active_channel_time_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_SCAN_MAX_ACTIVE_CHANNEL_TIME, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETSCANCHANNELTIME, mib_value); + + return res; +} + +static ssize_t slsi_roam_scan_probe_interval_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_NPROBE, mib_value); +} + +static ssize_t slsi_roam_scan_probe_interval_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_SCAN_NPROBE, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETSCANNPROBES, mib_value); + + return res; +} + +static ssize_t slsi_roam_mode_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + if (slsi_is_rf_test_mode_enabled()) { + SLSI_DBG1_NODEV(SLSI_MLME, "SLSI_PSID_UNIFI_ROAM_MODE is not supported because of rf test mode.\n"); + return -ENOTSUPP; + } + + slsi_str_to_int(command, &mib_value); + + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_MODE, mib_value); +} + +static ssize_t slsi_roam_mode_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_MODE, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETROAMMODE, mib_value); + + return res; +} + +static int slsi_roam_offload_ap_list(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct cfg80211_acl_data *mac_acl; + int ap_count = 0; + int buf_pos = 0; + int i, r; + int malloc_len; + + /* command format: + * x,aa:bb:cc:dd:ee:ff,xx:yy:zz:qq:ww:ee... + * x = 1 to 100 + * each mac address id 17 bytes and every mac address is separated by ',' + */ + buf_pos = slsi_str_to_int(command, &ap_count); + if (ap_count < ROAMOFFLAPLIST_MIN || ap_count > ROAMOFFLAPLIST_MAX) { + SLSI_ERR(sdev, "ap_count: %d\n", ap_count); + return -EINVAL; + } + buf_pos++; + /* each mac address takes 18 bytes(17 for mac address and 1 for ',') except the last one. + * the last mac address is just 17 bytes(without a coma) + */ + if ((buf_len - buf_pos) < (ap_count*18 - 1)) { + SLSI_ERR(sdev, "Invalid buff len:%d for %d APs\n", (buf_len - buf_pos), ap_count); + return -EINVAL; + } + malloc_len = sizeof(struct cfg80211_acl_data) + sizeof(struct mac_address) * ap_count; + mac_acl = kmalloc(malloc_len, GFP_KERNEL); + if (!mac_acl) { + SLSI_ERR(sdev, "MEM fail for size:%ld\n", sizeof(struct cfg80211_acl_data) + sizeof(struct mac_address) * ap_count); + return -ENOMEM; + } + + for (i = 0; i < ap_count; i++) { + slsi_machexstring_to_macarray(&command[buf_pos], mac_acl->mac_addrs[i].addr); + buf_pos += 18; + SLSI_DBG3_NODEV(SLSI_MLME, "[%pM]", mac_acl->mac_addrs[i].addr); + } + mac_acl->acl_policy = NL80211_ACL_POLICY_DENY_UNLESS_LISTED; + mac_acl->n_acl_entries = ap_count; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + r = slsi_mlme_set_acl(sdev, dev, ndev_vif->ifnum, mac_acl); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + kfree(mac_acl); + return r; +} + +static ssize_t slsi_roam_scan_band_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_BAND, mib_value); +} + +static ssize_t slsi_roam_scan_band_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_SCAN_BAND, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETROAMINTRABAND, mib_value); + + return res; +} + +static ssize_t slsi_freq_band_write(struct net_device *dev, uint band) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + + slsi_band_update(sdev, band); + /* Convert to correct Mib value (intra_band:1, all_band:2) */ + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_BAND, (band == SLSI_FREQ_BAND_AUTO) ? 2 : 1); +} + +static ssize_t slsi_freq_band_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + memset(buf, '\0', 128); + pos += scnprintf(buf + pos, bufsz - pos, "Band %d", sdev->device_config.supported_band); + + buf[pos] = '\0'; + memcpy(command, buf, pos + 1); + + return pos; +} + +static ssize_t slsi_roam_scan_control_write(struct net_device *dev, int mode) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + + if (mode == 0 || mode == 1) { + sdev->device_config.roam_scan_mode = mode; + } else { + SLSI_ERR(sdev, "Invalid roam Mode: Must be 0 or, 1 Not '%c'\n", mode); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return -EINVAL; + } + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_CONTROL, sdev->device_config.roam_scan_mode); +} + +static ssize_t slsi_roam_scan_control_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_SCAN_CONTROL, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETROAMSCANCONTROL, mib_value); + + return res; +} + +static ssize_t slsi_roam_scan_home_time_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_HOME_TIME, mib_value); +} + +static ssize_t slsi_roam_scan_home_time_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_SCAN_HOME_TIME, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETSCANHOMETIME, mib_value); + + return res; +} + +static ssize_t slsi_roam_scan_home_away_time_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + + slsi_str_to_int(command, &mib_value); + return slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_SCAN_HOME_AWAY_TIME, mib_value); +} + +static ssize_t slsi_roam_scan_home_away_time_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mib_value = 0; + int res; + + res = slsi_get_mib_roam(sdev, SLSI_PSID_UNIFI_ROAM_SCAN_HOME_AWAY_TIME, &mib_value); + if (res) + return res; + + res = snprintf(command, buf_len, "%s %d", CMD_GETSCANHOMEAWAYTIME, mib_value); + + return res; +} + +static ssize_t slsi_roam_scan_channels_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int result = 0; + int i, channel_count; + int offset = 0; + int readbyte = 0; + int channels[MAX_CHANNEL_LIST]; + + readbyte = slsi_str_to_int(command, &channel_count); + + if (!readbyte) { + SLSI_ERR(sdev, "channel count: failed to read a numeric value"); + return -EINVAL; + } + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + + if (channel_count > MAX_CHANNEL_LIST) + channel_count = MAX_CHANNEL_LIST; + sdev->device_config.wes_roam_scan_list.n = channel_count; + + for (i = 0; i < channel_count; i++) { + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(&command[offset], &channels[i]); + if (!readbyte) { + SLSI_ERR(sdev, "failed to read a numeric value\n"); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return -EINVAL; + } + + sdev->device_config.wes_roam_scan_list.channels[i] = channels[i]; + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + result = slsi_mlme_set_cached_channels(sdev, dev, channel_count, sdev->device_config.wes_roam_scan_list.channels); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return result; +} + +static ssize_t slsi_roam_scan_channels_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + char channel_buf[128] = { 0 }; + int pos = 0; + int i; + int channel_count = 0; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + channel_count = sdev->device_config.wes_roam_scan_list.n; + pos = scnprintf(channel_buf, sizeof(channel_buf), "%s %d", CMD_GETROAMSCANCHANNELS, channel_count); + for (i = 0; i < channel_count; i++) + pos += scnprintf(channel_buf + pos, sizeof(channel_buf) - pos, " %d", sdev->device_config.wes_roam_scan_list.channels[i]); + channel_buf[pos] = '\0'; + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + memcpy(command, channel_buf, pos + 1); + + return pos; +} + +static ssize_t slsi_okc_mode_write(struct net_device *dev, int mode) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + + if (mode == 0 || mode == 1) { + sdev->device_config.okc_mode = mode; + } else { + SLSI_ERR(sdev, "Invalid OKC Mode: Must be 0 or, 1 Not '%c'\n", mode); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return -EINVAL; + } + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return 0; +} + +static ssize_t slsi_okc_mode_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int okc_mode; + int res; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + okc_mode = sdev->device_config.okc_mode; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + res = snprintf(command, buf_len, "%s %d", CMD_GETOKCMODE, okc_mode); + + return res; +} + +static ssize_t slsi_wes_mode_write(struct net_device *dev, int mode) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int result = 0; + u32 action_frame_bmap = SLSI_STA_ACTION_FRAME_BITMAP; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + + if (mode == 0 || mode == 1) { + sdev->device_config.wes_mode = mode; + } else { + SLSI_ERR(sdev, "Invalid WES Mode: Must be 0 or 1 Not '%c'\n", mode); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return -EINVAL; + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((ndev_vif->activated) && (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + if (sdev->device_config.wes_mode) + action_frame_bmap |= SLSI_ACTION_FRAME_VENDOR_SPEC; + + result = slsi_mlme_register_action_frame(sdev, dev, action_frame_bmap, action_frame_bmap); + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return result; +} + +static ssize_t slsi_wes_mode_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int wes_mode; + int res; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + wes_mode = sdev->device_config.wes_mode; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + res = snprintf(command, buf_len, "%s %d", CMD_GETWESMODE, wes_mode); + + return res; +} +#endif + +static ssize_t slsi_set_pmk(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + u8 pmk[33]; + int result = 0; + + memcpy((u8 *)pmk, command + strlen("SET_PMK "), 32); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + result = slsi_mlme_set_pmk(sdev, dev, pmk, 32); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return result; +} + +static ssize_t slsi_auto_chan_read(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int ap_auto_chan; + int result = 0; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + ap_auto_chan = sdev->device_config.ap_auto_chan; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + result = snprintf(command, buf_len, "%d\n", ap_auto_chan); + return result; +} + +static ssize_t slsi_auto_chan_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int n_channels; + struct ieee80211_channel *channels[SLSI_NO_OF_SCAN_CHANLS_FOR_AUTO_CHAN_MAX] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + int count_channels; + int offset; + int chan; +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + struct net_device *sta_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + struct netdev_vif *ndev_sta_vif = netdev_priv(sta_dev); + int sta_frequency; +#endif + + offset = slsi_str_to_int(&command[21], &n_channels); + if (!offset) { + SLSI_ERR(sdev, "channel count: failed to read a numeric value"); + return -EINVAL; + } + + if (n_channels > SLSI_NO_OF_SCAN_CHANLS_FOR_AUTO_CHAN_MAX) { + SLSI_ERR(sdev, "channel count:%d > SLSI_NO_OF_SCAN_CHANLS_FOR_AUTO_CHAN_MAX:%d\n", n_channels, SLSI_NO_OF_SCAN_CHANLS_FOR_AUTO_CHAN_MAX); + return -EINVAL; + } + + /* If "1 6 11" are passed, scan all "1 - 14" channels. If "1 6" are passed, scan "1 - 9" channels */ + if (n_channels == 3) + n_channels = 14; + else if (n_channels == 2) + n_channels = 9; + count_channels = 0; + for (chan = 1; chan <= n_channels; chan++) { + int center_freq; + + center_freq = ieee80211_channel_to_frequency(chan, NL80211_BAND_2GHZ); + channels[count_channels] = ieee80211_get_channel(sdev->wiphy, center_freq); + if (!channels[count_channels]) + SLSI_WARN(sdev, "channel number:%d invalid\n", chan); + else + count_channels++; + + } + + SLSI_DBG3(sdev, SLSI_INIT_DEINIT, "Number of channels for autochannel selection= %d", count_channels); + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + sdev->device_config.ap_auto_chan = 0; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +if ((ndev_sta_vif->activated) && (ndev_sta_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_sta_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTING || + ndev_sta_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + sta_frequency = ndev_sta_vif->chan->center_freq; + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + if ((sta_frequency / 1000) == 2) + sdev->device_config.ap_auto_chan = ieee80211_frequency_to_channel(sta_frequency); + else + sdev->device_config.ap_auto_chan = 1; + SLSI_INFO(sdev, "Channel selected = %d", sdev->device_config.ap_auto_chan); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return 0; +} +#endif /*wifi sharing*/ + return slsi_auto_chan_select_scan(sdev, count_channels, channels); +} + +static ssize_t slsi_reassoc_write(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + u8 bssid[6] = { 0 }; + int channel; + int freq; + enum nl80211_band band = NL80211_BAND_2GHZ; + int r = 0; + + if (command[17] != ' ') { + SLSI_ERR(sdev, "Invalid Format '%s' '%c'\n", command, command[17]); + return -EINVAL; + } + + command[17] = '\0'; + + slsi_machexstring_to_macarray(command, bssid); + + if (!slsi_str_to_int(&command[18], &channel)) { + SLSI_ERR(sdev, "Invalid channel string: '%s'\n", &command[18]); + return -EINVAL; + } + + if (channel > 14) + band = NL80211_BAND_5GHZ; + freq = (u16)ieee80211_channel_to_frequency(channel, band); + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + r = slsi_mlme_roam(sdev, dev, bssid, freq); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +static ssize_t slsi_send_action_frame(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + char *temp; + u8 bssid[6] = { 0 }; + int channel = 0; + int freq = 0; + enum nl80211_band band = NL80211_BAND_2GHZ; + int r = 0; + u16 host_tag = slsi_tx_mgmt_host_tag(sdev); + u32 dwell_time; + struct ieee80211_hdr *hdr; + u8 *buf = NULL; + u8 *final_buf = NULL; + u8 temp_byte; + int len = 0; + int final_length = 0; + int i = 0, j = 0; + char *pos; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_ERR(sdev, "Not a STA vif or status is not CONNECTED\n"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EINVAL; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + command[17] = '\0'; + slsi_machexstring_to_macarray(command, bssid); + + command[17] = ' '; + pos = strchr(command, ' '); + if (pos == NULL) + return -EINVAL; + *pos++ = '\0'; + + if (!slsi_str_to_int(pos, &channel)) { + SLSI_ERR(sdev, "Invalid channel string: '%s'\n", pos); + return -EINVAL; + } + pos++; + + if (channel > 14) + band = NL80211_BAND_5GHZ; + freq = (u16)ieee80211_channel_to_frequency(channel, band); + + pos = strchr(pos, ' '); + if (pos == NULL) + return -EINVAL; + *pos++ = '\0'; + + if (!slsi_str_to_int(pos, &dwell_time)) { + SLSI_ERR(sdev, "Invalid dwell time string: '%s'\n", pos); + return -EINVAL; + } + + pos = strchr(pos, ' '); + if (pos == NULL) + return -EINVAL; + pos++; + + /*Length of data*/ + temp = pos; + while (*temp != '\0') + temp++; + len = temp - pos; + + if (len <= 0) + return -EINVAL; + buf = kmalloc((len + 1) / 2, GFP_KERNEL); + + if (buf == NULL) { + SLSI_ERR(sdev, "Malloc failed\n"); + return -ENOMEM; + } + /*We receive a char buffer, convert to hex*/ + temp = pos; + for (i = 0, j = 0; j < len; j += 2) { + if (j + 1 == len) + temp_byte = slsi_parse_hex(temp[j]); + else + temp_byte = slsi_parse_hex(temp[j]) << 4 | slsi_parse_hex(temp[j + 1]); + buf[i++] = temp_byte; + } + len = i; + + final_length = len + IEEE80211_HEADER_SIZE; + final_buf = kmalloc(final_length, GFP_KERNEL); + if (final_buf == NULL) { + SLSI_ERR(sdev, "Malloc failed\n"); + kfree(buf); + return -ENOMEM; + } + + hdr = (struct ieee80211_hdr *)final_buf; + hdr->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, IEEE80211_STYPE_ACTION); + SLSI_ETHER_COPY(hdr->addr1, bssid); + SLSI_ETHER_COPY(hdr->addr2, sdev->hw_addr); + SLSI_ETHER_COPY(hdr->addr3, bssid); + memcpy(final_buf + IEEE80211_HEADER_SIZE, buf, len); + + kfree(buf); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + r = slsi_mlme_send_frame_mgmt(sdev, dev, final_buf, final_length, FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME, FAPI_MESSAGETYPE_IEEE80211_ACTION, host_tag, SLSI_FREQ_HOST_TO_FW(freq), dwell_time * 1000, 0); + + kfree(final_buf); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +static ssize_t slsi_setting_max_sta_write(struct net_device *dev, int sta_number) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct slsi_mib_data mib_data = { 0, NULL }; + int result = 0; + + if (sta_number > 10 || sta_number < 1) + return -EINVAL; + result = slsi_mib_encode_uint(&mib_data, SLSI_PSID_UNIFI_MAX_CLIENT, sta_number, 0); + if ((result != SLSI_MIB_STATUS_SUCCESS) || (mib_data.dataLength == 0)) + return -ENOMEM; + result = slsi_mlme_set(sdev, dev, mib_data.data, mib_data.dataLength); + if (result != 0) + SLSI_ERR(sdev, "max_sta: mlme_set_req failed: Result code: %d\n", result); + kfree(mib_data.data); + + return result; +} + +static ssize_t slsi_country_write(struct net_device *dev, char *country_code) +{ + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct slsi_dev *sdev = netdev_vif->sdev; + char alpha2_code[SLSI_COUNTRY_CODE_LEN]; + int status; + + if (strlen(country_code) < 2) + return -EINVAL; + + memcpy(alpha2_code, country_code, 2); + alpha2_code[2] = ' '; /* set 3rd byte of countrycode to ASCII space */ + + status = slsi_set_country_update_regd(sdev, alpha2_code, SLSI_COUNTRY_CODE_LEN); + + return status; +} + +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +static ssize_t slsi_forward_beacon(struct net_device *dev, char *action) +{ + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct slsi_dev *sdev = netdev_vif->sdev; + int intended_action = 0; + int ret = 0; + + if (strncasecmp(action, "stop", 4) == 0) { + intended_action = FAPI_ACTION_STOP; + } else if (strncasecmp(action, "start", 5) == 0) { + intended_action = FAPI_ACTION_START; + } else { + SLSI_NET_ERR(dev, "BEACON_RECV should be used with start or stop\n"); + return -EINVAL; + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "BEACON_RECV %s!!\n", intended_action ? "START" : "STOP"); + SLSI_MUTEX_LOCK(netdev_vif->vif_mutex); + + if ((!netdev_vif->activated) || (netdev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (netdev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_ERR(sdev, "Not a STA vif or status is not CONNECTED\n"); + ret = -EINVAL; + goto exit_vif_mutex; + } + + if (((intended_action == FAPI_ACTION_START) && netdev_vif->is_wips_running) || + ((intended_action == FAPI_ACTION_STOP) && !netdev_vif->is_wips_running)) { + SLSI_NET_INFO(dev, "Forwarding beacon is already %s!!\n", + netdev_vif->is_wips_running ? "running" : "stopped"); + ret = 0; + goto exit_vif_mutex; + } + + SLSI_MUTEX_LOCK(netdev_vif->scan_mutex); + if (intended_action == FAPI_ACTION_START && + (netdev_vif->scan[SLSI_SCAN_HW_ID].scan_req || netdev_vif->sta.roam_in_progress)) { + SLSI_NET_ERR(dev, "Rejecting BEACON_RECV start as scan/roam is running\n"); + ret = -EBUSY; + goto exit_scan_mutex; + } + + ret = slsi_mlme_set_forward_beacon(sdev, dev, intended_action); +exit_scan_mutex: + SLSI_MUTEX_UNLOCK(netdev_vif->scan_mutex); +exit_vif_mutex: + SLSI_MUTEX_UNLOCK(netdev_vif->vif_mutex); + return ret; +} +#endif + +static ssize_t slsi_update_rssi_boost(struct net_device *dev, char *rssi_boost_string) +{ + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct slsi_dev *sdev = netdev_vif->sdev; + int digit1, digit2, band, lendigit1, lendigit2; + int boost = 0, length = 0, i = 0; + + if (strlen(rssi_boost_string) < 8) + return -EINVAL; + for (i = 0; i < (strlen(rssi_boost_string) - 4);) { + if (rssi_boost_string[i] == '0' && + rssi_boost_string[i + 1] == '4') { + if (rssi_boost_string[i + 2] == '0' && + rssi_boost_string[i + 3] == '2' && + ((i + 7) < strlen(rssi_boost_string))) + i = i + 4; + else + return -EINVAL; + digit1 = slsi_parse_hex(rssi_boost_string[i]); + digit2 = slsi_parse_hex(rssi_boost_string[i + 1]); + boost = (digit1 * 16) + digit2; + band = rssi_boost_string[i + 3] - '0'; + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + if (band == 0) { + sdev->device_config.rssi_boost_2g = 0; + sdev->device_config.rssi_boost_5g = 0; + } else if (band == 1) { + sdev->device_config.rssi_boost_2g = 0; + sdev->device_config.rssi_boost_5g = boost; + } else { + sdev->device_config.rssi_boost_2g = boost; + sdev->device_config.rssi_boost_5g = 0; + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + if ((netdev_vif->activated) && + (netdev_vif->vif_type == FAPI_VIFTYPE_STATION)) { + return slsi_set_boost(sdev, dev); + } else { + return 0; + } + } else { + i = i + 2; + lendigit1 = slsi_parse_hex(rssi_boost_string[i]); + lendigit2 = slsi_parse_hex(rssi_boost_string[i + 1]); + length = (lendigit1 * 16) + lendigit2; + i = i + (length * 2) + 2; + } + } + return -EINVAL; +} + +static int slsi_tdls_channel_switch(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int r, len_processed; + u8 peer_mac[6]; + u32 center_freq = 0; + u32 chan_info = 0; + int is_ch_switch; + struct slsi_peer *peer; + +/* Command format: + * [0/1] [mac address] [center frequency] [channel_info] + * channel switch: "1 00:01:02:03:04:05 2412 20" + * cancel channel switch: "0 00:01:02:03:04:05" + */ +/* switch/cancel(1 byte) space(1byte) macaddress 17 char */ +#define SLSI_TDLS_IOCTL_CMD_DATA_MIN_LEN 19 + + if (buf_len < SLSI_TDLS_IOCTL_CMD_DATA_MIN_LEN) { + SLSI_NET_ERR(dev, "buf len should be atleast %d. but is:%d\n", SLSI_TDLS_IOCTL_CMD_DATA_MIN_LEN, buf_len); + return -EINVAL; + } + + if (ndev_vif->sta.sta_bss == NULL) { + SLSI_NET_ERR(dev, "sta_bss is not available\n"); + return -EINVAL; + } + + is_ch_switch = command[0] - '0'; + buf_len -= 2; + command += 2; + + slsi_machexstring_to_macarray(command, peer_mac); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (is_ch_switch) { + /* mac address(17 char) + space(1 char) = 18 */ + command += 18; + buf_len -= 18; + + len_processed = slsi_str_to_int(command, ¢er_freq); + /* +1 for space */ + buf_len -= len_processed + 1; + command += len_processed + 1; + if (buf_len <= 0) { + SLSI_NET_ERR(dev, "No buf for chan_info\n"); + r = -EINVAL; + goto exit; + } + buf_len -= slsi_str_to_int(command, &chan_info); + + if (((chan_info & 0xFF) != 20) && ((chan_info & 0xFF) != 40)) { + SLSI_NET_ERR(dev, "Invalid chan_info(%d)\n", chan_info); + r = -EINVAL; + goto exit; + } + /* In 2.4 Ghz channel 1(2412MHz) to channel 14(2484MHz) */ + /* for 40MHz channels are from 1 to 13, its 2422MHz to 2462MHz. */ + if ((((chan_info & 0xFF) == 20) && (center_freq < 2412 || center_freq > 2484)) || + (((chan_info & 0xFF) == 40) && (center_freq < 2422 || center_freq > 2462))) { + SLSI_NET_ERR(dev, "Invalid center_freq(%d) for chan_info(%d)\n", center_freq, chan_info); + r = -EINVAL; + goto exit; + } + } else { + /* Incase of cancel channel switch fallback to bss channel */ + center_freq = ndev_vif->sta.sta_bss->channel->center_freq; + chan_info = 20; /* Hardcoded to 20MHz as cert tests use BSS with 20MHz */ + } + + peer = slsi_get_peer_from_mac(sdev, dev, peer_mac); + + if (!peer || !slsi_is_tdls_peer(dev, peer)) { + SLSI_NET_ERR(dev, "%s peer aid:%d\n", peer ? "Invalid" : "No", peer ? peer->aid : 0); + r = -EINVAL; + goto exit; + } + + r = slsi_mlme_tdls_action(sdev, dev, peer_mac, FAPI_TDLSACTION_CHANNEL_SWITCH, center_freq, chan_info); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_set_tx_power_calling(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mode; + int error = 0; + u8 host_state; + + (void)slsi_str_to_int(command, &mode); + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + host_state = sdev->device_config.host_state; + + if (!mode) + host_state = host_state | FAPI_HOSTSTATE_SAR_ACTIVE; + else + host_state = host_state & ~FAPI_HOSTSTATE_SAR_ACTIVE; + + error = slsi_mlme_set_host_state(sdev, dev, host_state); + if (!error) + sdev->device_config.host_state = host_state; + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + return error; +} + +int slsi_set_tx_power_sar(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mode; + int error = 0; + u8 host_state; + + (void)slsi_str_to_int(command, &mode); + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + host_state = sdev->device_config.host_state; + host_state &= ~(FAPI_HOSTSTATE_SAR_ACTIVE | BIT(3) | BIT(4)); + + if (mode) + host_state |= ((mode - 1) << 3) | FAPI_HOSTSTATE_SAR_ACTIVE; + + error = slsi_mlme_set_host_state(sdev, dev, host_state); + if (!error) + sdev->device_config.host_state = host_state; + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + return error; +} + +int slsi_get_tx_power_sar(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int len = 0; + u8 host_state, index; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + host_state = sdev->device_config.host_state; + + if (host_state & FAPI_HOSTSTATE_SAR_ACTIVE) + index = ((host_state >> 3) & 3) + 1; + else + index = 0; + + len = snprintf(command, buf_len, "%u", index); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + return len; +} + +static int slsi_print_regulatory(struct slsi_802_11d_reg_domain *domain_info, char *buf, int buf_len, struct slsi_supported_channels *supported_channels, int supp_chan_length) +{ + int cur_pos = 0; + int i, j, k; + char *dfs_region_str[] = {"unknown", "ETSI", "FCC", "JAPAN", "GLOBAL", "CHINA"}; + u8 dfs_region_index; + struct ieee80211_reg_rule *reg_rule; + int channel_start_freq = 0; + int channel_end_freq = 0; + int channel_start_num = 0; + int channel_end_num = 0; + int channel_count = 0; + int channel_increment = 0; + int channel_band = 0; + bool display_pattern = false; + + cur_pos = snprintf(buf, buf_len, "country %c%c:", domain_info->regdomain->alpha2[0], + domain_info->regdomain->alpha2[1]); + dfs_region_index = domain_info->regdomain->dfs_region <= 5 ? domain_info->regdomain->dfs_region : 0; + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, "DFS-%s\n", dfs_region_str[dfs_region_index]); + for (i = 0; i < domain_info->regdomain->n_reg_rules; i++) { + reg_rule = &domain_info->regdomain->reg_rules[i]; + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, "\t(%d-%d @ %d), (N/A, %d)", + reg_rule->freq_range.start_freq_khz/1000, + reg_rule->freq_range.end_freq_khz/1000, + reg_rule->freq_range.max_bandwidth_khz/1000, + MBM_TO_DBM(reg_rule->power_rule.max_eirp)); + if (reg_rule->flags) { + if (reg_rule->flags & NL80211_RRF_DFS) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", DFS"); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (reg_rule->flags & NL80211_RRF_NO_OFDM) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", NO_OFDM"); +#endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)) + if (reg_rule->flags & (NL80211_RRF_PASSIVE_SCAN|NL80211_RRF_NO_IBSS)) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", NO_IR"); +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + if (reg_rule->flags & (NL80211_RRF_NO_IR)) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", NO_IR"); +#endif + if (reg_rule->flags & NL80211_RRF_NO_INDOOR) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", NO_INDOOR"); + if (reg_rule->flags & NL80211_RRF_NO_OUTDOOR) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", NO_OUTDOOR"); + } + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, "\n"); + } + + /* Display of Supported Channels for 2.4GHz and 5GHz */ + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, "Channels:"); + + for (i = 0; i < supp_chan_length; i++) { + channel_start_num = supported_channels[i].start_chan_num; + channel_count = supported_channels[i].channel_count; + channel_increment = supported_channels[i].increment; + channel_band = supported_channels[i].band; + channel_end_num = channel_start_num + ((channel_count - 1) * channel_increment); + for (j = channel_start_num; j <= channel_end_num; j += channel_increment) { + channel_start_freq = (ieee80211_channel_to_frequency(j, channel_band)*1000) - 10000; + channel_end_freq = (ieee80211_channel_to_frequency(j, channel_band)*1000) + 10000; + for (k = 0; k < domain_info->regdomain->n_reg_rules; k++) { + reg_rule = &domain_info->regdomain->reg_rules[k]; + if ((reg_rule->freq_range.start_freq_khz <= channel_start_freq) && + (reg_rule->freq_range.end_freq_khz >= channel_end_freq)) { + if (display_pattern) + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, ", %d", j); + else + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, " %d", j); + display_pattern = true; + break; + } + } + } + } + cur_pos += snprintf(buf + cur_pos, buf_len - cur_pos, "\n"); + return cur_pos; +} + +static int slsi_get_supported_channels(struct slsi_dev *sdev, struct net_device *dev, struct slsi_supported_channels *supported_channels) +{ + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_data supported_chan_mib = { 0, NULL }; + struct slsi_mib_value *values = NULL; + struct slsi_mib_get_entry get_values[] = {{SLSI_PSID_UNIFI_SUPPORTED_CHANNELS, { 0, 0 } } }; + int i, chan_count, chan_start; + int supp_chan_length = 0; + + /* Expect each mib length in response is <= 16. So assume 16 bytes for each MIB */ + mibrsp.dataLength = 16; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (mibrsp.data == NULL) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + return 0; + } + values = slsi_read_mibs(sdev, dev, get_values, 1, &mibrsp); + if (!values) + goto exit_with_mibrsp; + + if (values[0].type != SLSI_MIB_TYPE_OCTET) { + SLSI_ERR(sdev, "Supported_Chan invalid type."); + goto exit_with_values; + } + + supported_chan_mib = values[0].u.octetValue; + for (i = 0; i < supported_chan_mib.dataLength / 2; i++) { + chan_start = supported_chan_mib.data[i*2]; + chan_count = supported_chan_mib.data[i*2 + 1]; + if (chan_start == 1) { /* for 2.4GHz */ + supported_channels[supp_chan_length].start_chan_num = 1; + if (!(sdev->device_config.host_state & FAPI_HOSTSTATE_CELLULAR_ACTIVE) && + chan_count > 11 && sdev->device_config.disable_ch12_ch13) { + chan_count = 11; + SLSI_DBG1(sdev, SLSI_CFG80211, "Channels 12 and 13 have been disabled"); + } + supported_channels[supp_chan_length].channel_count = chan_count; + supported_channels[supp_chan_length].increment = 1; + supported_channels[supp_chan_length].band = NL80211_BAND_2GHZ; + supp_chan_length = supp_chan_length + 1; + } else { /* for 5GHz */ + supported_channels[supp_chan_length].start_chan_num = chan_start; + supported_channels[supp_chan_length].channel_count = chan_count; + supported_channels[supp_chan_length].increment = 4; + supported_channels[supp_chan_length].band = NL80211_BAND_5GHZ; + supp_chan_length = supp_chan_length + 1; + } + } +exit_with_values: + kfree(values); +exit_with_mibrsp: + kfree(mibrsp.data); + return supp_chan_length; +} + +static int slsi_get_regulatory(struct net_device *dev, char *buf, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int mode; + int cur_pos = 0; + int status; + u8 alpha2[3]; + struct slsi_supported_channels supported_channels[5]; + int supp_chan_length; + + mode = buf[strlen(CMD_GETREGULATORY) + 1] - '0'; + if (mode == 1) { + struct slsi_802_11d_reg_domain domain_info; + + memset(&domain_info, 0, sizeof(struct slsi_802_11d_reg_domain)); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated || ndev_vif->vif_type != FAPI_VIFTYPE_STATION || !ndev_vif->sta.sta_bss) { + cur_pos += snprintf(buf, buf_len - cur_pos, "Station not connected"); + SLSI_ERR(sdev, "station not connected. vif.activated:%d, vif.type:%d, vif.bss:%s\n", + ndev_vif->activated, ndev_vif->vif_type, ndev_vif->sta.sta_bss ? "yes" : "no"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EINVAL; + } + /* read vif specific country code, index = vifid+1 */ + status = slsi_read_default_country(sdev, alpha2, ndev_vif->ifnum + 1); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + if (status) + return status; + + /* max 20 rules */ + domain_info.regdomain = kmalloc(sizeof(*domain_info.regdomain) + sizeof(struct ieee80211_reg_rule) * 20, GFP_KERNEL); + if (!domain_info.regdomain) { + SLSI_ERR(sdev, "no memory size:%lu\n", + sizeof(struct ieee80211_regdomain) + sizeof(struct ieee80211_reg_rule) * 20); + return -ENOMEM; + } + + /* get regulatory rules based on country code */ + domain_info.countrylist = sdev->device_config.domain_info.countrylist; + domain_info.country_len = sdev->device_config.domain_info.country_len; + status = slsi_read_regulatory_rules(sdev, &domain_info, alpha2); + if (status) { + kfree(domain_info.regdomain); + return status; + } + /* get supported channels based on country code */ + supp_chan_length = slsi_get_supported_channels(sdev, dev, &supported_channels[0]); + cur_pos += slsi_print_regulatory(&domain_info, buf + cur_pos, buf_len - cur_pos, &supported_channels[0], supp_chan_length); + kfree(domain_info.regdomain); + } else if (mode == 0) { + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + supp_chan_length = slsi_get_supported_channels(sdev, dev, &supported_channels[0]); + cur_pos += slsi_print_regulatory(&sdev->device_config.domain_info, buf + cur_pos, buf_len - cur_pos, &supported_channels[0], supp_chan_length); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + } else { + cur_pos += snprintf(buf, buf_len - cur_pos, "invalid option %d", mode); + SLSI_ERR(sdev, "invalid option:%d\n", mode); + return -EINVAL; + } + /* Buf is somewhere close to 4Kbytes. so expect some spare space. If there is no spare + * space we might have missed printing some text in buf. + */ + if (buf_len - cur_pos) + return cur_pos; + else + return -ENOMEM; +} + +void slsi_disable_ch12_13(struct slsi_dev *sdev) +{ + struct wiphy *wiphy = sdev->wiphy; + struct ieee80211_channel *chan; + + if (wiphy->bands[0]) { + chan = &wiphy->bands[0]->channels[11]; + chan->flags |= IEEE80211_CHAN_DISABLED; + chan = &wiphy->bands[0]->channels[12]; + chan->flags |= IEEE80211_CHAN_DISABLED; + } + + SLSI_DBG1(sdev, SLSI_CFG80211, "Channels 12 and 13 have been disabled"); +} + +int slsi_set_fcc_channel(struct net_device *dev, char *cmd, int cmd_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int status; + bool flight_mode_ena; + u8 host_state; + int err; + char alpha2[3]; + + /* SET_FCC_CHANNEL 0 when device is in flightmode */ + flight_mode_ena = (cmd[0] == '0'); + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + host_state = sdev->device_config.host_state; + + if (flight_mode_ena) + host_state = host_state & ~FAPI_HOSTSTATE_CELLULAR_ACTIVE; + else + host_state = host_state | FAPI_HOSTSTATE_CELLULAR_ACTIVE; + sdev->device_config.host_state = host_state; + + status = slsi_mlme_set_host_state(sdev, dev, host_state); + if (status) { + SLSI_ERR(sdev, "Err setting MMaxPowerEna. error = %d\n", status); + } else { + err = slsi_read_default_country(sdev, alpha2, 1); + if (err) { + SLSI_WARN(sdev, "Err updating reg_rules = %d\n", err); + } else { + memcpy(sdev->device_config.domain_info.regdomain->alpha2, alpha2, 2); + /* Read the regulatory params for the country.*/ + if (slsi_read_regulatory_rules(sdev, &sdev->device_config.domain_info, alpha2) == 0) { + slsi_reset_channel_flags(sdev); + wiphy_apply_custom_regulatory(sdev->wiphy, sdev->device_config.domain_info.regdomain); + slsi_update_supported_channels_regd_flags(sdev); + if (flight_mode_ena && sdev->device_config.disable_ch12_ch13) + slsi_disable_ch12_13(sdev); + } + } + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + return status; +} + +int slsi_fake_mac_write(struct net_device *dev, char *cmd) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct slsi_mib_data mib_data = { 0, NULL }; + int status; + bool enable; + + if (strncmp(cmd, "ON", strlen("ON")) == 0) + enable = 1; + else + enable = 0; + + status = slsi_mib_encode_bool(&mib_data, SLSI_PSID_UNIFI_MAC_ADDRESS_RANDOMISATION, enable, 0); + if (status != SLSI_MIB_STATUS_SUCCESS) { + SLSI_ERR(sdev, "FAKE MAC FAIL: no mem for MIB\n"); + return -ENOMEM; + } + + status = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + + kfree(mib_data.data); + + if (status) + SLSI_ERR(sdev, "Err setting unifiMacAddrRandomistaion MIB. error = %d\n", status); + + return status; +} + +static char *slsi_get_assoc_status(u16 fw_result_code) +{ + char *assoc_status_label = "unspecified_failure"; + + switch (fw_result_code) { + case FAPI_RESULTCODE_SUCCESS: + assoc_status_label = "success"; + break; + case FAPI_RESULTCODE_TRANSMISSION_FAILURE: + assoc_status_label = "transmission_failure"; + break; + case FAPI_RESULTCODE_HOST_REQUEST_SUCCESS: + assoc_status_label = "host_request_success"; + break; + case FAPI_RESULTCODE_HOST_REQUEST_FAILED: + assoc_status_label = "host_request_failed"; + break; + case FAPI_RESULTCODE_PROBE_TIMEOUT: + assoc_status_label = "probe_timeout"; + break; + case FAPI_RESULTCODE_AUTH_TIMEOUT: + assoc_status_label = "auth_timeout"; + break; + case FAPI_RESULTCODE_ASSOC_TIMEOUT: + assoc_status_label = "assoc_timeout"; + break; + case FAPI_RESULTCODE_ASSOC_ABORT: + assoc_status_label = "assoc_abort"; + break; + } + return assoc_status_label; +} + +int slsi_get_sta_info(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int len; +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + struct net_device *ap_dev; + struct netdev_vif *ndev_ap_vif; + + ap_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2PX_SWLAN); + + if (ap_dev) { + ndev_ap_vif = netdev_priv(ap_dev); + SLSI_MUTEX_LOCK(ndev_ap_vif->vif_mutex); + if (SLSI_IS_VIF_INDEX_MHS(sdev, ndev_ap_vif)) + ndev_vif = ndev_ap_vif; + SLSI_MUTEX_UNLOCK(ndev_ap_vif->vif_mutex); + } +#endif + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_AP)) { + SLSI_ERR(sdev, "slsi_get_sta_info: AP is not up.Command not allowed vif.activated:%d, vif.type:%d\n", + ndev_vif->activated, ndev_vif->vif_type); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EINVAL; + } + +#if defined(ANDROID_VERSION) && (ANDROID_VERSION >= 90000) + len = snprintf(command, buf_len, "GETSTAINFO %pM Rx_Retry_Pkts=%d Rx_BcMc_Pkts=%d CAP=%04x %02x:%02x:%02x ", + ndev_vif->ap.last_disconnected_sta.address, + ndev_vif->ap.last_disconnected_sta.rx_retry_packets, + ndev_vif->ap.last_disconnected_sta.rx_bc_mc_packets, + ndev_vif->ap.last_disconnected_sta.capabilities, + ndev_vif->ap.last_disconnected_sta.address[0], + ndev_vif->ap.last_disconnected_sta.address[1], + ndev_vif->ap.last_disconnected_sta.address[2]); + + len += snprintf(&command[len], (buf_len - len), "%d %d %d %d %d %d %d %u %d", + ieee80211_frequency_to_channel(ndev_vif->ap.channel_freq), + ndev_vif->ap.last_disconnected_sta.bandwidth, ndev_vif->ap.last_disconnected_sta.rssi, + ndev_vif->ap.last_disconnected_sta.tx_data_rate, ndev_vif->ap.last_disconnected_sta.mode, + ndev_vif->ap.last_disconnected_sta.antenna_mode, + ndev_vif->ap.last_disconnected_sta.mimo_used, ndev_vif->ap.last_disconnected_sta.reason, + ndev_vif->ap.last_disconnected_sta.support_mode); +#else + len = snprintf(command, buf_len, "wl_get_sta_info : %02x%02x%02x %u %d %d %d %d %d %d %u ", + ndev_vif->ap.last_disconnected_sta.address[0], ndev_vif->ap.last_disconnected_sta.address[1], + ndev_vif->ap.last_disconnected_sta.address[2], ndev_vif->ap.channel_freq, + ndev_vif->ap.last_disconnected_sta.bandwidth, ndev_vif->ap.last_disconnected_sta.rssi, + ndev_vif->ap.last_disconnected_sta.tx_data_rate, ndev_vif->ap.last_disconnected_sta.mode, + ndev_vif->ap.last_disconnected_sta.antenna_mode, + ndev_vif->ap.last_disconnected_sta.mimo_used, ndev_vif->ap.last_disconnected_sta.reason); +#endif + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return len; +} + +static int slsi_get_bss_rssi(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int len = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + len = snprintf(command, buf_len, "%d", ndev_vif->sta.last_connected_bss.rssi); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return len; +} + +static int slsi_get_bss_info(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int len = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + len = snprintf(command, buf_len, "%02x:%02x:%02x %u %u %d %u %u %u %u %u %d %d %u %u %u %u", + ndev_vif->sta.last_connected_bss.address[0], ndev_vif->sta.last_connected_bss.address[1], + ndev_vif->sta.last_connected_bss.address[2], + ndev_vif->sta.last_connected_bss.channel_freq, ndev_vif->sta.last_connected_bss.bandwidth, + ndev_vif->sta.last_connected_bss.rssi, ndev_vif->sta.last_connected_bss.tx_data_rate, + ndev_vif->sta.last_connected_bss.mode, ndev_vif->sta.last_connected_bss.antenna_mode, + ndev_vif->sta.last_connected_bss.mimo_used, + ndev_vif->sta.last_connected_bss.passpoint_version, ndev_vif->sta.last_connected_bss.snr, + ndev_vif->sta.last_connected_bss.noise_level, ndev_vif->sta.last_connected_bss.roaming_akm, + ndev_vif->sta.last_connected_bss.roaming_count, ndev_vif->sta.last_connected_bss.kv, + ndev_vif->sta.last_connected_bss.kvie); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return len; +} + +static int slsi_get_assoc_reject_info(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int len = 0; + + len = snprintf(command, buf_len, "assoc_reject.status : %d %s", sdev->assoc_result_code, + slsi_get_assoc_status(sdev->assoc_result_code)); + + return len; +} + +#ifdef CONFIG_SCSC_WLAN_LOW_LATENCY_MODE +int slsi_set_latency_mode(struct net_device *dev, char *cmd, int cmd_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + bool enable_roaming; + + /* latency_mode =0 (Normal), latency_mode =1 (Low) */ + enable_roaming = (cmd[0] == '0') ? true : false; + SLSI_DBG1(sdev, SLSI_CFG80211, "Setting latency mode %d\n", cmd[0] - '0'); + + return slsi_set_mib_soft_roaming_enabled(sdev, dev, enable_roaming); +} + +static int slsi_set_power_mode(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int status; + u16 power_mode; + + power_mode = (command[0] == '0') ? FAPI_POWERMANAGEMENTMODE_ACTIVE_MODE : FAPI_POWERMANAGEMENTMODE_POWER_SAVE; + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if ((!ndev_vif->activated) || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + !(ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + SLSI_ERR(sdev, "Command not allowed vif.activated:%d, vif.type:%d, ndev_vif->sta.vif_status:%d\n", + ndev_vif->activated, ndev_vif->vif_type, ndev_vif->sta.vif_status); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EINVAL; + } + status = slsi_mlme_powermgt(sdev, dev, power_mode); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return status; +} +#endif + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +static int slsi_enhanced_arp_start_stop(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int result = 0; + int readbyte = 0; + int readvalue = 0; + int i = 0; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + if (!sdev->device_config.fw_enhanced_arp_detect_supported) { + SLSI_ERR(sdev, "Enhanced ARP Detect Feature is not supported.\n"); + return -ENOTSUPP; + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + SLSI_ERR(sdev, "Not in STA mode\n"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex) + return -EPERM; + } + + SLSI_DBG1(sdev, SLSI_CFG80211, "Enhanced ARP Start/Stop\n"); + + for (i = 0; i < 4 ; i++) { + readbyte = slsi_str_to_int(command, &readvalue); + ndev_vif->target_ip_addr[i] = readvalue; + command = command + readbyte + 1; + } + + if (ndev_vif->target_ip_addr[0] != 0) { /* start enhanced arp detect */ + /* clear all the counters */ + memset(&ndev_vif->enhanced_arp_stats, 0, sizeof(ndev_vif->enhanced_arp_stats)); + ndev_vif->enhanced_arp_detect_enabled = true; + result = slsi_mlme_arp_detect_request(sdev, dev, FAPI_ACTION_START, ndev_vif->target_ip_addr); + } else { /* stop enhanced arp detect */ + ndev_vif->enhanced_arp_detect_enabled = false; + result = slsi_mlme_arp_detect_request(sdev, dev, FAPI_ACTION_STOP, ndev_vif->target_ip_addr); + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return result; +} + +static int slsi_enhanced_arp_get_stats(struct net_device *dev, char *command, int buf_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int r = 0; + int len = 0; + + r = slsi_read_enhanced_arp_rx_count_by_lower_mac(sdev, dev, SLSI_PSID_UNIFI_ARP_DETECT_RESPONSE_COUNTER); + + if (r == 0) { + len = snprintf(command, buf_len, "%d %d %d %d %d %d %d %d %d %d", + ndev_vif->enhanced_arp_stats.arp_req_count_from_netdev, + ndev_vif->enhanced_arp_stats.arp_req_count_to_lower_mac, + ndev_vif->enhanced_arp_stats.arp_req_rx_count_by_lower_mac, + ndev_vif->enhanced_arp_stats.arp_req_count_tx_success, + ndev_vif->enhanced_arp_stats.arp_rsp_rx_count_by_lower_mac, + ndev_vif->enhanced_arp_stats.arp_rsp_rx_count_by_upper_mac, + ndev_vif->enhanced_arp_stats.arp_rsp_count_to_netdev, + ndev_vif->enhanced_arp_stats.arp_rsp_count_out_of_order_drop, + 0, /*ap_link_active not supported, set as 0*/ + ndev_vif->enhanced_arp_stats.is_duplicate_addr_detected); + } + + /*clear all the counters*/ + memset(&ndev_vif->enhanced_arp_stats, 0, sizeof(ndev_vif->enhanced_arp_stats)); + + return len; +} +#endif + +int slsi_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ +#define MAX_LEN_PRIV_COMMAND 4096 /*This value is the max reply size set in supplicant*/ + struct android_wifi_priv_cmd priv_cmd; + int ret = 0; + u8 *command = NULL; + + if (!dev) { + ret = -ENODEV; + goto exit; + } + + if (!rq->ifr_data) { + ret = -EINVAL; + goto exit; + } + if (copy_from_user((void *)&priv_cmd, (void *)rq->ifr_data, sizeof(struct android_wifi_priv_cmd))) { + ret = -EFAULT; + SLSI_NET_ERR(dev, "ifr data failed\n"); + goto exit; + } + + if ((priv_cmd.total_len > MAX_LEN_PRIV_COMMAND) || (priv_cmd.total_len < 0)) { + ret = -EINVAL; + SLSI_NET_ERR(dev, "Length mismatch total_len = %d\n", priv_cmd.total_len); + goto exit; + } + command = kmalloc((priv_cmd.total_len + 1), GFP_KERNEL); + if (!command) { + ret = -ENOMEM; + SLSI_NET_ERR(dev, "No memory\n"); + goto exit; + } + if (copy_from_user(command, priv_cmd.buf, priv_cmd.total_len)) { + ret = -EFAULT; + SLSI_NET_ERR(dev, "Buffer copy fail\n"); + goto exit; + } + command[priv_cmd.total_len] = '\0'; + + SLSI_INFO_NODEV("command: %.*s\n", priv_cmd.total_len, command); + + if (strncasecmp(command, CMD_SETSUSPENDMODE, strlen(CMD_SETSUSPENDMODE)) == 0) { + ret = slsi_set_suspend_mode(dev, command); + } else if (strncasecmp(command, CMD_SETJOINPREFER, strlen(CMD_SETJOINPREFER)) == 0) { + char *rssi_boost_string = command + strlen(CMD_SETJOINPREFER) + 1; + + ret = slsi_update_rssi_boost(dev, rssi_boost_string); + } else if (strncasecmp(command, CMD_RXFILTERADD, strlen(CMD_RXFILTERADD)) == 0) { + int filter_num = *(command + strlen(CMD_RXFILTERADD) + 1) - '0'; + + ret = slsi_rx_filter_num_write(dev, 1, filter_num); + } else if (strncasecmp(command, CMD_RXFILTERREMOVE, strlen(CMD_RXFILTERREMOVE)) == 0) { + int filter_num = *(command + strlen(CMD_RXFILTERREMOVE) + 1) - '0'; + + ret = slsi_rx_filter_num_write(dev, 0, filter_num); +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if !defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION < 90000) + } else if (strncasecmp(command, CMD_INTERFACE_CREATE, strlen(CMD_INTERFACE_CREATE)) == 0) { + char *intf_name = command + strlen(CMD_INTERFACE_CREATE) + 1; + + ret = slsi_create_interface(dev, intf_name); + } else if (strncasecmp(command, CMD_INTERFACE_DELETE, strlen(CMD_INTERFACE_DELETE)) == 0) { + char *intf_name = command + strlen(CMD_INTERFACE_DELETE) + 1; + + ret = slsi_delete_interface(dev, intf_name); +#endif + } else if (strncasecmp(command, CMD_SET_INDOOR_CHANNELS, strlen(CMD_SET_INDOOR_CHANNELS)) == 0) { + char *arg = command + strlen(CMD_SET_INDOOR_CHANNELS) + 1; + + ret = slsi_set_indoor_channels(dev, arg); + } else if (strncasecmp(command, CMD_GET_INDOOR_CHANNELS, strlen(CMD_GET_INDOOR_CHANNELS)) == 0) { + ret = slsi_get_indoor_channels(dev, command, priv_cmd.total_len); +#endif + } else if (strncasecmp(command, CMD_SETCOUNTRYREV, strlen(CMD_SETCOUNTRYREV)) == 0) { + char *country_code = command + strlen(CMD_SETCOUNTRYREV) + 1; + + ret = slsi_set_country_rev(dev, country_code); + } else if (strncasecmp(command, CMD_GETCOUNTRYREV, strlen(CMD_GETCOUNTRYREV)) == 0) { + ret = slsi_get_country_rev(dev, command, priv_cmd.total_len); +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + } else if (strncasecmp(command, CMD_SETROAMTRIGGER, strlen(CMD_SETROAMTRIGGER)) == 0) { + int skip = strlen(CMD_SETROAMTRIGGER) + 1; + + ret = slsi_roam_scan_trigger_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETROAMTRIGGER, strlen(CMD_GETROAMTRIGGER)) == 0) { + ret = slsi_roam_scan_trigger_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMDELTA, strlen(CMD_SETROAMDELTA)) == 0) { + int skip = strlen(CMD_SETROAMDELTA) + 1; + + ret = slsi_roam_delta_trigger_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETROAMDELTA, strlen(CMD_GETROAMDELTA)) == 0) { + ret = slsi_roam_delta_trigger_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMSCANPERIOD, strlen(CMD_SETROAMSCANPERIOD)) == 0) { + int skip = strlen(CMD_SETROAMSCANPERIOD) + 1; + + ret = slsi_cached_channel_scan_period_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETROAMSCANPERIOD, strlen(CMD_GETROAMSCANPERIOD)) == 0) { + ret = slsi_cached_channel_scan_period_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETFULLROAMSCANPERIOD, strlen(CMD_SETFULLROAMSCANPERIOD)) == 0) { + int skip = strlen(CMD_SETFULLROAMSCANPERIOD) + 1; + + ret = slsi_full_roam_scan_period_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETFULLROAMSCANPERIOD, strlen(CMD_GETFULLROAMSCANPERIOD)) == 0) { + ret = slsi_full_roam_scan_period_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETSCANCHANNELTIME, strlen(CMD_SETSCANCHANNELTIME)) == 0) { + int skip = strlen(CMD_SETSCANCHANNELTIME) + 1; + + ret = slsi_roam_scan_max_active_channel_time_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETSCANCHANNELTIME, strlen(CMD_GETSCANCHANNELTIME)) == 0) { + ret = slsi_roam_scan_max_active_channel_time_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETSCANNPROBES, strlen(CMD_SETSCANNPROBES)) == 0) { + int skip = strlen(CMD_SETSCANNPROBES) + 1; + + ret = slsi_roam_scan_probe_interval_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETSCANNPROBES, strlen(CMD_GETSCANNPROBES)) == 0) { + ret = slsi_roam_scan_probe_interval_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMMODE, strlen(CMD_SETROAMMODE)) == 0) { + int skip = strlen(CMD_SETROAMMODE) + 1; + + ret = slsi_roam_mode_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETROAMMODE, strlen(CMD_GETROAMMODE)) == 0) { + ret = slsi_roam_mode_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMINTRABAND, strlen(CMD_SETROAMINTRABAND)) == 0) { + int skip = strlen(CMD_SETROAMINTRABAND) + 1; + + ret = slsi_roam_scan_band_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETROAMINTRABAND, strlen(CMD_GETROAMINTRABAND)) == 0) { + ret = slsi_roam_scan_band_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMBAND, strlen(CMD_SETROAMBAND)) == 0) { + uint band = *(command + strlen(CMD_SETROAMBAND) + 1) - '0'; + + ret = slsi_freq_band_write(dev, band); + } else if (strncasecmp(command, CMD_SETBAND, strlen(CMD_SETBAND)) == 0) { + uint band = *(command + strlen(CMD_SETBAND) + 1) - '0'; + + ret = slsi_freq_band_write(dev, band); + } else if ((strncasecmp(command, CMD_GETROAMBAND, strlen(CMD_GETROAMBAND)) == 0) || (strncasecmp(command, CMD_GETBAND, strlen(CMD_GETBAND)) == 0)) { + ret = slsi_freq_band_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMSCANCONTROL, strlen(CMD_SETROAMSCANCONTROL)) == 0) { + int mode = *(command + strlen(CMD_SETROAMSCANCONTROL) + 1) - '0'; + + ret = slsi_roam_scan_control_write(dev, mode); + } else if (strncasecmp(command, CMD_GETROAMSCANCONTROL, strlen(CMD_GETROAMSCANCONTROL)) == 0) { + ret = slsi_roam_scan_control_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETSCANHOMETIME, strlen(CMD_SETSCANHOMETIME)) == 0) { + int skip = strlen(CMD_SETSCANHOMETIME) + 1; + + ret = slsi_roam_scan_home_time_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETSCANHOMETIME, strlen(CMD_GETSCANHOMETIME)) == 0) { + ret = slsi_roam_scan_home_time_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETSCANHOMEAWAYTIME, strlen(CMD_SETSCANHOMEAWAYTIME)) == 0) { + int skip = strlen(CMD_SETSCANHOMEAWAYTIME) + 1; + + ret = slsi_roam_scan_home_away_time_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETSCANHOMEAWAYTIME, strlen(CMD_GETSCANHOMEAWAYTIME)) == 0) { + ret = slsi_roam_scan_home_away_time_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETOKCMODE, strlen(CMD_SETOKCMODE)) == 0) { + int mode = *(command + strlen(CMD_SETOKCMODE) + 1) - '0'; + + ret = slsi_okc_mode_write(dev, mode); + } else if (strncasecmp(command, CMD_GETOKCMODE, strlen(CMD_GETOKCMODE)) == 0) { + ret = slsi_okc_mode_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETWESMODE, strlen(CMD_SETWESMODE)) == 0) { + int mode = *(command + strlen(CMD_SETWESMODE) + 1) - '0'; + + ret = slsi_wes_mode_write(dev, mode); + } else if (strncasecmp(command, CMD_GETWESMODE, strlen(CMD_GETWESMODE)) == 0) { + ret = slsi_wes_mode_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SETROAMSCANCHANNELS, strlen(CMD_SETROAMSCANCHANNELS)) == 0) { + int skip = strlen(CMD_SETROAMSCANCHANNELS) + 1; + + ret = slsi_roam_scan_channels_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GETROAMSCANCHANNELS, strlen(CMD_GETROAMSCANCHANNELS)) == 0) { + ret = slsi_roam_scan_channels_read(dev, command, priv_cmd.total_len); +#endif + } else if (strncasecmp(command, CMD_SET_PMK, strlen(CMD_SET_PMK)) == 0) { + ret = slsi_set_pmk(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_HAPD_GET_CHANNEL, strlen(CMD_HAPD_GET_CHANNEL)) == 0) { + ret = slsi_auto_chan_read(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_SET_SAP_CHANNEL_LIST, strlen(CMD_SET_SAP_CHANNEL_LIST)) == 0) { + ret = slsi_auto_chan_write(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_REASSOC, strlen(CMD_REASSOC)) == 0) { + int skip = strlen(CMD_REASSOC) + 1; + + ret = slsi_reassoc_write(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_SENDACTIONFRAME, strlen(CMD_SENDACTIONFRAME)) == 0) { + int skip = strlen(CMD_SENDACTIONFRAME) + 1; + + ret = slsi_send_action_frame(dev, command + skip, + priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_HAPD_MAX_NUM_STA, strlen(CMD_HAPD_MAX_NUM_STA)) == 0) { + int sta_num; + u8 *max_sta = command + strlen(CMD_HAPD_MAX_NUM_STA) + 1; + + slsi_str_to_int(max_sta, &sta_num); + ret = slsi_setting_max_sta_write(dev, sta_num); + } else if (strncasecmp(command, CMD_COUNTRY, strlen(CMD_COUNTRY)) == 0) { + char *country_code = command + strlen(CMD_COUNTRY) + 1; + + ret = slsi_country_write(dev, country_code); +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + } else if (strncasecmp(command, CMD_BEACON_RECV, strlen(CMD_BEACON_RECV)) == 0) { + char *action = command + strlen(CMD_BEACON_RECV) + 1; + + ret = slsi_forward_beacon(dev, action); +#endif + } else if (strncasecmp(command, CMD_SETAPP2PWPSIE, strlen(CMD_SETAPP2PWPSIE)) == 0) { + ret = slsi_set_ap_p2p_wps_ie(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_P2PSETPS, strlen(CMD_P2PSETPS)) == 0) { + ret = slsi_set_p2p_oppps(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_P2PSETNOA, strlen(CMD_P2PSETNOA)) == 0) { + ret = slsi_p2p_set_noa_params(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_P2PECSA, strlen(CMD_P2PECSA)) == 0) { + ret = slsi_p2p_ecsa(dev, command); + } else if (strncasecmp(command, CMD_P2PLOSTART, strlen(CMD_P2PLOSTART)) == 0) { + ret = slsi_p2p_lo_start(dev, command); + } else if (strncasecmp(command, CMD_P2PLOSTOP, strlen(CMD_P2PLOSTOP)) == 0) { + ret = slsi_p2p_lo_stop(dev); + } else if (strncasecmp(command, CMD_TDLSCHANNELSWITCH, strlen(CMD_TDLSCHANNELSWITCH)) == 0) { + ret = slsi_tdls_channel_switch(dev, command + strlen(CMD_TDLSCHANNELSWITCH) + 1, + priv_cmd.total_len - (strlen(CMD_TDLSCHANNELSWITCH) + 1)); + } else if (strncasecmp(command, CMD_SETROAMOFFLOAD, strlen(CMD_SETROAMOFFLOAD)) == 0) { + ret = slsi_roam_mode_write(dev, command + strlen(CMD_SETROAMOFFLOAD) + 1, + priv_cmd.total_len - (strlen(CMD_SETROAMOFFLOAD) + 1)); + } else if (strncasecmp(command, CMD_SETROAMOFFLAPLIST, strlen(CMD_SETROAMOFFLAPLIST)) == 0) { + ret = slsi_roam_offload_ap_list(dev, command + strlen(CMD_SETROAMOFFLAPLIST) + 1, + priv_cmd.total_len - (strlen(CMD_SETROAMOFFLAPLIST) + 1)); + } else if (strncasecmp(command, CMD_SET_TX_POWER_CALLING, strlen(CMD_SET_TX_POWER_CALLING)) == 0) { + ret = slsi_set_tx_power_calling(dev, command + strlen(CMD_SET_TX_POWER_CALLING) + 1, + priv_cmd.total_len - (strlen(CMD_SET_TX_POWER_CALLING) + 1)); + } else if (strncasecmp(command, CMD_SET_TX_POWER_SAR, strlen(CMD_SET_TX_POWER_SAR)) == 0) { + ret = slsi_set_tx_power_sar(dev, command + strlen(CMD_SET_TX_POWER_SAR) + 1, + priv_cmd.total_len - (strlen(CMD_SET_TX_POWER_SAR) + 1)); + } else if (strncasecmp(command, CMD_GET_TX_POWER_SAR, strlen(CMD_GET_TX_POWER_SAR)) == 0) { + ret = slsi_get_tx_power_sar(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_GETREGULATORY, strlen(CMD_GETREGULATORY)) == 0) { + ret = slsi_get_regulatory(dev, command, priv_cmd.total_len); +#ifdef CONFIG_SCSC_WLAN_HANG_TEST + } else if (strncasecmp(command, CMD_TESTFORCEHANG, strlen(CMD_TESTFORCEHANG)) == 0) { + ret = slsi_test_send_hanged_vendor_event(dev); +#endif + } else if (strncasecmp(command, CMD_SET_FCC_CHANNEL, strlen(CMD_SET_FCC_CHANNEL)) == 0) { + ret = slsi_set_fcc_channel(dev, command + strlen(CMD_SET_FCC_CHANNEL) + 1, + priv_cmd.total_len - (strlen(CMD_SET_FCC_CHANNEL) + 1)); + } else if (strncasecmp(command, CMD_FAKEMAC, strlen(CMD_FAKEMAC)) == 0) { + ret = slsi_fake_mac_write(dev, command + strlen(CMD_FAKEMAC) + 1); + } else if (strncasecmp(command, CMD_GETBSSRSSI, strlen(CMD_GETBSSRSSI)) == 0) { + ret = slsi_get_bss_rssi(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_GETBSSINFO, strlen(CMD_GETBSSINFO)) == 0) { + ret = slsi_get_bss_info(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_GETSTAINFO, strlen(CMD_GETSTAINFO)) == 0) { + ret = slsi_get_sta_info(dev, command, priv_cmd.total_len); + } else if (strncasecmp(command, CMD_GETASSOCREJECTINFO, strlen(CMD_GETASSOCREJECTINFO)) == 0) { + ret = slsi_get_assoc_reject_info(dev, command, priv_cmd.total_len); +#ifdef CONFIG_SCSC_WLAN_LOW_LATENCY_MODE + } else if (strncasecmp(command, CMD_SET_LATENCY_MODE, strlen(CMD_SET_LATENCY_MODE)) == 0) { + ret = slsi_set_latency_mode(dev, command + strlen(CMD_SET_LATENCY_MODE) + 1, + priv_cmd.total_len - (strlen(CMD_SET_LATENCY_MODE) + 1)); + } else if (strncasecmp(command, CMD_SET_POWER_MGMT, strlen(CMD_SET_POWER_MGMT)) == 0) { + ret = slsi_set_power_mode(dev, command + strlen(CMD_SET_POWER_MGMT) + 1, + priv_cmd.total_len - (strlen(CMD_SET_POWER_MGMT) + 1)); +#endif +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + } else if (strncasecmp(command, CMD_SET_ENHANCED_ARP_TARGET, strlen(CMD_SET_ENHANCED_ARP_TARGET)) == 0) { + int skip = strlen(CMD_SET_ENHANCED_ARP_TARGET) + 1; + + ret = slsi_enhanced_arp_start_stop(dev, command + skip, priv_cmd.total_len - skip); + } else if (strncasecmp(command, CMD_GET_ENHANCED_ARP_COUNTS, strlen(CMD_SET_ENHANCED_ARP_TARGET)) == 0) { + ret = slsi_enhanced_arp_get_stats(dev, command, priv_cmd.total_len); +#endif + } else if ((strncasecmp(command, CMD_RXFILTERSTART, strlen(CMD_RXFILTERSTART)) == 0) || + (strncasecmp(command, CMD_RXFILTERSTOP, strlen(CMD_RXFILTERSTOP)) == 0) || + (strncasecmp(command, CMD_BTCOEXMODE, strlen(CMD_BTCOEXMODE)) == 0) || + (strncasecmp(command, CMD_BTCOEXSCAN_START, strlen(CMD_BTCOEXSCAN_START)) == 0) || + (strncasecmp(command, CMD_BTCOEXSCAN_STOP, strlen(CMD_BTCOEXSCAN_STOP)) == 0) || + (strncasecmp(command, CMD_MIRACAST, strlen(CMD_MIRACAST)) == 0)) { + ret = 0; + } else if ((strncasecmp(command, CMD_AMPDU_MPDU, strlen(CMD_AMPDU_MPDU)) == 0) || + (strncasecmp(command, CMD_CHANGE_RL, strlen(CMD_CHANGE_RL)) == 0) || + (strncasecmp(command, CMD_INTERFACE_CREATE, strlen(CMD_INTERFACE_CREATE)) == 0) || + (strncasecmp(command, CMD_INTERFACE_DELETE, strlen(CMD_INTERFACE_DELETE)) == 0) || + (strncasecmp(command, CMD_LTECOEX, strlen(CMD_LTECOEX)) == 0) || + (strncasecmp(command, CMD_RESTORE_RL, strlen(CMD_RESTORE_RL)) == 0) || + (strncasecmp(command, CMD_RPSMODE, strlen(CMD_RPSMODE)) == 0) || + (strncasecmp(command, CMD_SETCCXMODE, strlen(CMD_SETCCXMODE)) == 0) || + (strncasecmp(command, CMD_SETDFSSCANMODE, strlen(CMD_SETDFSSCANMODE)) == 0) || + (strncasecmp(command, CMD_SETSINGLEANT, strlen(CMD_SETSINGLEANT)) == 0)) { + ret = -ENOTSUPP; +#ifndef SLSI_TEST_DEV + } else if ((strncasecmp(command, CMD_DRIVERDEBUGDUMP, strlen(CMD_DRIVERDEBUGDUMP)) == 0) || + (strncasecmp(command, CMD_DRIVERDEBUGCOMMAND, strlen(CMD_DRIVERDEBUGCOMMAND)) == 0)) { + slsi_dump_stats(dev); +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_schedule_collection(SCSC_LOG_DUMPSTATE, SCSC_LOG_DUMPSTATE_REASON_DRIVERDEBUGDUMP); +#else + ret = mx140_log_dump(); +#endif +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + } else if ((strncasecmp(command, CMD_ENHANCED_PKT_FILTER, strlen(CMD_ENHANCED_PKT_FILTER)) == 0)) { + const u8 enable = *(command + strlen(CMD_ENHANCED_PKT_FILTER) + 1) - '0'; + + ret = slsi_set_enhanced_pkt_filter(dev, enable); +#endif +#ifdef CONFIG_SCSC_WLAN_SET_NUM_ANTENNAS + } else if (strncasecmp(command, CMD_SET_NUM_ANTENNAS, strlen(CMD_SET_NUM_ANTENNAS)) == 0) { + struct netdev_vif *ndev_vif = netdev_priv(dev); + const u16 num_of_antennas = *(command + strlen(CMD_SET_NUM_ANTENNAS) + 1) - '0'; + + /* We cannot lock in slsi_set_num_antennas as + * this is also called in slsi_start_ap with netdev_vif lock. + */ + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + ret = slsi_set_num_antennas(dev, num_of_antennas); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +#endif + } else { + ret = -ENOTSUPP; + } + if (strncasecmp(command, CMD_SETROAMBAND, strlen(CMD_SETROAMBAND)) != 0 && strncasecmp(command, CMD_SETBAND, strlen(CMD_SETBAND)) != 0 && copy_to_user(priv_cmd.buf, command, priv_cmd.total_len)) { + ret = -EFAULT; + SLSI_NET_ERR(dev, "Buffer copy fail\n"); + } + +exit: + kfree(command); + return ret; +} diff --git a/drivers/net/wireless/scsc/ioctl.h b/drivers/net/wireless/scsc/ioctl.h new file mode 100755 index 000000000000..245c8f3b81cf --- /dev/null +++ b/drivers/net/wireless/scsc/ioctl.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef __MX_IOCTL_H__ +#define __MX_IOCTL_H__ + +#include +#include + +struct android_wifi_priv_cmd { + char *buf; + int used_len; + int total_len; +}; + +int slsi_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +int slsi_get_sta_info(struct net_device *dev, char *command, int buf_len); + +struct slsi_supported_channels { + int start_chan_num; + int channel_count; + int increment; + int band; +}; +#endif /* __MX_IOCTL_H__ */ diff --git a/drivers/net/wireless/scsc/kic.c b/drivers/net/wireless/scsc/kic.c new file mode 100755 index 000000000000..a139a93e7b05 --- /dev/null +++ b/drivers/net/wireless/scsc/kic.c @@ -0,0 +1,53 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include +#include "dev.h" +#include "debug.h" +#include "mxman.h" + +static int wifi_kic_trigger_recovery(void *priv, enum slsi_kic_test_recovery_type type) +{ + struct slsi_dev *sdev = (struct slsi_dev *)priv; + char reason[80]; + + if (!sdev) + return -EINVAL; + + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) + return -EAGAIN; + + switch (type) { + case slsi_kic_test_recovery_type_subsystem_panic: + SLSI_INFO(sdev, "Trigger Wi-Fi firmware subsystem panic\n"); + if (scsc_service_force_panic(sdev->service)) + return -EINVAL; + return 0; + case slsi_kic_test_recovery_type_emulate_firmware_no_response: + SLSI_INFO(sdev, "Trigger Wi-Fi host panic\n"); + snprintf(reason, sizeof(reason), "slsi_kic_test_recovery_type_emulate_firmware_no_response"); + slsi_sm_service_failed(sdev, reason); + return 0; + case slsi_kic_test_recovery_type_watch_dog: + case slsi_kic_test_recovery_type_chip_crash: + default: + return -EINVAL; + } +} + +static struct slsi_kic_wifi_ops kic_ops = { + .trigger_recovery = wifi_kic_trigger_recovery, +}; + +int wifi_kic_register(struct slsi_dev *sdev) +{ + return slsi_kic_wifi_ops_register((void *)sdev, &kic_ops); +} + +void wifi_kic_unregister(void) +{ + return slsi_kic_wifi_ops_unregister(&kic_ops); +} diff --git a/drivers/net/wireless/scsc/kic.h b/drivers/net/wireless/scsc/kic.h new file mode 100644 index 000000000000..ca714a2aa203 --- /dev/null +++ b/drivers/net/wireless/scsc/kic.h @@ -0,0 +1,13 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __KIC_H +#define __KIC_H + +int wifi_kic_register(struct slsi_dev *sdev); +void wifi_kic_unregister(void); + +#endif /* #ifndef __KIC_H */ diff --git a/drivers/net/wireless/scsc/log_clients.c b/drivers/net/wireless/scsc/log_clients.c new file mode 100755 index 000000000000..388c02d5d072 --- /dev/null +++ b/drivers/net/wireless/scsc/log_clients.c @@ -0,0 +1,118 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "dev.h" +#include "debug.h" +#include "udi.h" +#include "log_clients.h" +#include "unifiio.h" + +/* These functions should NOT be called from interrupt context */ +/* It is supposed to be called from process context, or + * NET_TX_SOFTIRQ - with BHs disabled and interrupts disabled + */ +/* Do not sleep */ + +void slsi_log_clients_log_signal_safe(struct slsi_dev *sdev, struct sk_buff *skb, u32 direction) +{ + struct list_head *pos, *n; + struct slsi_log_client *log_client; + int dir = (direction == SLSI_LOG_DIRECTION_FROM_HOST) ? UDI_FROM_HOST : UDI_TO_HOST; + + spin_lock_bh(&sdev->log_clients.log_client_spinlock); + list_for_each_safe(pos, n, &sdev->log_clients.log_client_list) { + log_client = list_entry(pos, struct slsi_log_client, q); + log_client->log_client_cb(log_client, skb, dir); + } + spin_unlock_bh(&sdev->log_clients.log_client_spinlock); +} + +void slsi_log_clients_init(struct slsi_dev *sdev) +{ + INIT_LIST_HEAD(&sdev->log_clients.log_client_list); + spin_lock_init(&sdev->log_clients.log_client_spinlock); +} + +/* The arg called "filter" will eventually be passed to kfree(). + * - so pass a NULL if you are not doing any filtering + */ +int slsi_log_client_register(struct slsi_dev *sdev, void *log_client_ctx, + int (*log_client_cb)(struct slsi_log_client *, struct sk_buff *, int), + char *filter, int min_signal_id, int max_signal_id) +{ + struct slsi_log_client *log_client; + int first_in_list = 0; + + first_in_list = list_empty(&sdev->log_clients.log_client_list); + log_client = kmalloc(sizeof(*log_client), GFP_KERNEL); + if (log_client == NULL) + return -ENOMEM; + + log_client->min_signal_id = min_signal_id; + log_client->max_signal_id = max_signal_id; + log_client->signal_filter = filter; + log_client->log_client_ctx = log_client_ctx; + log_client->log_client_cb = log_client_cb; + + /* Add to tail of log queue */ + spin_lock_bh(&sdev->log_clients.log_client_spinlock); + list_add_tail(&log_client->q, &sdev->log_clients.log_client_list); + spin_unlock_bh(&sdev->log_clients.log_client_spinlock); + + return 0; +} + +void slsi_log_clients_terminate(struct slsi_dev *sdev) +{ + /* If the driver is configured to try and terminate UDI user space + * applications, the following will try to do so. + */ + if (*sdev->term_udi_users) { + int num_polls_left = 50; + unsigned int timeout_ms = 4; + + slsi_log_client_msg(sdev, UDI_DRV_UNLOAD_IND, 0, NULL); + + /* Poll until all refs have gone away or timeout */ + while (slsi_check_cdev_refs() && num_polls_left) { + msleep(timeout_ms); + num_polls_left--; + } + } +} + +void slsi_log_client_msg(struct slsi_dev *sdev, u16 event, u32 event_data_length, const u8 *event_data) +{ + struct list_head *pos, *n; + struct slsi_log_client *log_client; + + spin_lock_bh(&sdev->log_clients.log_client_spinlock); + list_for_each_safe(pos, n, &sdev->log_clients.log_client_list) { + log_client = list_entry(pos, struct slsi_log_client, q); + spin_unlock_bh(&sdev->log_clients.log_client_spinlock); + if (slsi_kernel_to_user_space_event(log_client, event, event_data_length, event_data)) + SLSI_WARN(sdev, "Failed to send event(0x%.4X) to UDI client 0x%p\n", event, log_client); + spin_lock_bh(&sdev->log_clients.log_client_spinlock); + } + spin_unlock_bh(&sdev->log_clients.log_client_spinlock); +} + +void slsi_log_client_unregister(struct slsi_dev *sdev, void *log_client_ctx) +{ + struct list_head *pos, *n; + struct slsi_log_client *log_client; + + spin_lock_bh(&sdev->log_clients.log_client_spinlock); + list_for_each_safe(pos, n, &sdev->log_clients.log_client_list) { + log_client = list_entry(pos, struct slsi_log_client, q); + if (log_client->log_client_ctx == log_client_ctx) { + kfree(log_client->signal_filter); + list_del(pos); + kfree(log_client); + } + } + spin_unlock_bh(&sdev->log_clients.log_client_spinlock); +} diff --git a/drivers/net/wireless/scsc/log_clients.h b/drivers/net/wireless/scsc/log_clients.h new file mode 100644 index 000000000000..e5b809ff9ea7 --- /dev/null +++ b/drivers/net/wireless/scsc/log_clients.h @@ -0,0 +1,51 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_LOG_CLIENTS_H__ +#define __SLSI_LOG_CLIENTS_H__ + +#include + +struct slsi_dev; + +struct slsi_log_client { + struct list_head q; + void *log_client_ctx; + int (*log_client_cb)(struct slsi_log_client *, struct sk_buff *, int); + int min_signal_id; + int max_signal_id; + char *signal_filter; +}; + +struct slsi_log_clients { + /* a std spinlock */ + spinlock_t log_client_spinlock; + struct list_head log_client_list; +}; + +#define SLSI_LOG_DIRECTION_FROM_HOST 0 +#define SLSI_LOG_DIRECTION_TO_HOST 1 + +void slsi_log_clients_init(struct slsi_dev *sdev); +int slsi_log_client_register(struct slsi_dev *sdev, void *log_client_ctx, + int (*log_client_cb)(struct slsi_log_client *, struct sk_buff *, int), + char *filter, int min_signal_id, int max_signal_id); + +void slsi_log_client_unregister(struct slsi_dev *sdev, void *log_client_ctx); +void slsi_log_client_msg(struct slsi_dev *sdev, u16 event, u32 event_data_length, const u8 *event_data); +void slsi_log_clients_log_signal_safe(struct slsi_dev *sdev, struct sk_buff *skb, u32 direction); +static inline void slsi_log_clients_log_signal_fast(struct slsi_dev *sdev, struct slsi_log_clients *log_clients, struct sk_buff *skb, u32 direction) +{ + /* list_empty() is unsafe but fast + * slsi_log_clients_log_signal_safe() will make sure all is good + */ + if (!list_empty(&log_clients->log_client_list)) + slsi_log_clients_log_signal_safe(sdev, skb, direction); +} + +void slsi_log_clients_terminate(struct slsi_dev *sdev); + +#endif /*__SLSI_LOG_CLIENTS_H__*/ diff --git a/drivers/net/wireless/scsc/mbulk.c b/drivers/net/wireless/scsc/mbulk.c new file mode 100644 index 000000000000..037c3212afea --- /dev/null +++ b/drivers/net/wireless/scsc/mbulk.c @@ -0,0 +1,453 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ +#include +#include +#include "mbulk.h" +#include "hip4_sampler.h" + +#include "debug.h" + +#define MBULK_DEBUG + +/* mbulk descriptor is aligned to 64 bytes considering the host processor's + * cache line size + */ +#define MBULK_ALIGN (64) +#define MBULK_IS_ALIGNED(s) (((uintptr_t)(s) & (MBULK_ALIGN - 1)) == 0) +#define MBULK_SZ_ROUNDUP(s) round_up(s, MBULK_ALIGN) + +/* a magic number to allocate the remaining buffer to the bulk buffer + * in a segment. Used in chained mbulk allocation. + */ +#define MBULK_DAT_BUFSZ_REQ_BEST_MAGIC ((u32)(-2)) + +DEFINE_SPINLOCK(mbulk_pool_lock); + +static inline void mbulk_debug(struct mbulk *m) +{ + (void)m; /* may be unused */ + SLSI_DBG1_NODEV(SLSI_MBULK, "m->next_offset %p: %d\n", &m->next_offset, m->next_offset); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->flag %p: %d\n", &m->flag, m->flag); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->clas %p: %d\n", &m->clas, m->clas); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->pid %p: %d\n", &m->pid, m->pid); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->refcnt %p: %d\n", &m->refcnt, m->refcnt); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->dat_bufsz %p: %d\n", &m->dat_bufsz, m->dat_bufsz); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->sig_bufsz %p: %d\n", &m->sig_bufsz, m->sig_bufsz); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->len %p: %d\n", &m->len, m->len); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->head %p: %d\n", &m->head, m->head); + SLSI_DBG1_NODEV(SLSI_MBULK, "m->chain_next_offset %p: %d\n", &m->chain_next_offset, m->chain_next_offset); +} + +/* mbulk pool */ +struct mbulk_pool { + bool valid; /** is valid */ + u8 pid; /** pool id */ + struct mbulk *free_list; /** head of free segment list */ + int free_cnt; /** current number of free segments */ + int usage[MBULK_CLASS_MAX]; /** statistics of usage per mbulk clas*/ + char *base_addr; /** base address of the pool */ + char *end_addr; /** exclusive end address of the pool */ + mbulk_len_t seg_size; /** segment size in bytes "excluding" struct mbulk */ + u8 guard; /** pool guard **/ +#ifdef MBULK_DEBUG + int tot_seg_num; /** total number of segments in this pool */ +#endif +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + int minor; +#endif +}; + +/* get a segment from a pool */ +static inline struct mbulk *mbulk_pool_get(struct mbulk_pool *pool, enum mbulk_class clas) +{ + struct mbulk *m; + u8 guard = pool->guard; + + spin_lock_bh(&mbulk_pool_lock); + m = pool->free_list; + + if (m == NULL || pool->free_cnt <= guard) { /* guard */ + spin_unlock_bh(&mbulk_pool_lock); + return NULL; + } + + pool->free_cnt--; + pool->usage[clas]++; + + SCSC_HIP4_SAMPLER_MBULK(pool->minor, (pool->free_cnt & 0x100) >> 8, (pool->free_cnt & 0xff), clas); + + if (m->next_offset == 0) + pool->free_list = NULL; + else + pool->free_list = (struct mbulk *)((uintptr_t)pool->free_list + m->next_offset); + + memset(m, 0, sizeof(*m)); + m->pid = pool->pid; + m->clas = clas; + + spin_unlock_bh(&mbulk_pool_lock); + return m; +} + +/* put a segment to a pool */ +static inline void mbulk_pool_put(struct mbulk_pool *pool, struct mbulk *m) +{ + if (m->flag == MBULK_F_FREE) + return; + + spin_lock_bh(&mbulk_pool_lock); + pool->usage[m->clas]--; + pool->free_cnt++; + + SCSC_HIP4_SAMPLER_MBULK(pool->minor, (pool->free_cnt & 0x100) >> 8, (pool->free_cnt & 0xff), m->clas); + m->flag = MBULK_F_FREE; + if (pool->free_list != NULL) + m->next_offset = (uintptr_t)pool->free_list - (uintptr_t)m; + else + m->next_offset = 0; + pool->free_list = m; + spin_unlock_bh(&mbulk_pool_lock); +} + +/** mbulk pool configuration */ +struct mbulk_pool_config { + mbulk_len_t seg_sz; /** segment size "excluding" struct mbulk */ + int seg_num; /** number of segments. If -1, all remaining space is used */ +}; + +/** mbulk pools */ +static struct mbulk_pool mbulk_pools[MBULK_POOL_ID_MAX]; + +/** + * allocate a mbulk segment from the pool + * + * Note that the refcnt would be zero if \dat_bufsz is zero, as there is no + * allocated bulk data. + * If \dat_bufsz is \MBULK_DAT_BUFSZ_REQ_BEST_MAGIC, then this function + * allocates all remaining buffer space to the bulk buffer. + * + */ +static struct mbulk *mbulk_seg_generic_alloc(struct mbulk_pool *pool, + enum mbulk_class clas, size_t sig_bufsz, size_t dat_bufsz) +{ + struct mbulk *m; + + if (pool == NULL) + return NULL; + + /* get a segment from the pool */ + m = mbulk_pool_get(pool, clas); + if (m == NULL) + return NULL; + + /* signal buffer */ + m->sig_bufsz = (mbulk_len_t)sig_bufsz; + if (sig_bufsz) + m->flag = MBULK_F_SIG; + + /* data buffer. + * Note that data buffer size can be larger than the requested. + */ + m->head = m->sig_bufsz; + if (dat_bufsz == 0) { + m->dat_bufsz = 0; + m->refcnt = 0; + } else if (dat_bufsz == MBULK_DAT_BUFSZ_REQ_BEST_MAGIC) { + m->dat_bufsz = pool->seg_size - m->sig_bufsz; + m->refcnt = 1; + } else { + m->dat_bufsz = (mbulk_len_t)dat_bufsz; + m->refcnt = 1; + } + + mbulk_debug(m); + return m; +} + +int mbulk_pool_get_free_count(u8 pool_id) +{ + struct mbulk_pool *pool; + int num_free; + + if (pool_id >= MBULK_POOL_ID_MAX) { + WARN_ON(pool_id >= MBULK_POOL_ID_MAX); + return -EIO; + } + + spin_lock_bh(&mbulk_pool_lock); + pool = &mbulk_pools[pool_id]; + + if (!pool->valid) { + WARN_ON(!pool->valid); + spin_unlock_bh(&mbulk_pool_lock); + return -EIO; + } + + num_free = pool->free_cnt; + spin_unlock_bh(&mbulk_pool_lock); + + return num_free; +} + +/** + * Allocate a bulk buffer with an in-lined signal buffer + * + * A mbulk segment is allocated from the given the pool, if its size + * meeting the requested size. + * + */ +struct mbulk *mbulk_with_signal_alloc_by_pool(u8 pool_id, u16 colour, + enum mbulk_class clas, size_t sig_bufsz_req, size_t dat_bufsz) +{ + struct mbulk_pool *pool; + size_t sig_bufsz; + size_t tot_bufsz; + struct mbulk *m_ret; + + /* data buffer should be aligned */ + sig_bufsz = MBULK_SIG_BUFSZ_ROUNDUP(sizeof(struct mbulk) + sig_bufsz_req) - sizeof(struct mbulk); + + if (pool_id >= MBULK_POOL_ID_MAX) { + WARN_ON(pool_id >= MBULK_POOL_ID_MAX); + return NULL; + } + + pool = &mbulk_pools[pool_id]; + + if (!pool->valid) { + WARN_ON(!pool->valid); + return NULL; + } + + /* check if this pool meets the size */ + tot_bufsz = sig_bufsz + dat_bufsz; + if (dat_bufsz != MBULK_DAT_BUFSZ_REQ_BEST_MAGIC && + pool->seg_size < tot_bufsz) + return NULL; + + m_ret = mbulk_seg_generic_alloc(pool, clas, sig_bufsz, dat_bufsz); + /* Colour the mbulk */ + if (m_ret) { + /* Use pool id for coding vif and peer_id */ + m_ret->pid = m_ret->pid | (colour & 0xfe); + /* Code AC queue at the [7:6] bits */ + m_ret->clas = m_ret->clas | ((colour & 0x300) >> 2); + } + return m_ret; +} + +#ifdef MBULK_SUPPORT_SG_CHAIN +/** + * allocate a chained mbulk buffer from a specific mbulk pool + * + */ +struct mbulk *mbulk_chain_with_signal_alloc_by_pool(u8 pool_id, + enum mbulk_class clas, size_t sig_bufsz, size_t dat_bufsz) +{ + size_t tot_len; + struct mbulk *m, *head, *pre; + + head = mbulk_with_signal_alloc_by_pool(pool_id, clas, sig_bufsz, + MBULK_DAT_BUFSZ_REQ_BEST_MAGIC); + if (head == NULL || MBULK_SEG_TAILROOM(head) >= dat_bufsz) + return head; + + head->flag |= (MBULK_F_CHAIN_HEAD | MBULK_F_CHAIN); + tot_len = MBULK_SEG_TAILROOM(head); + pre = head; + + while (tot_len < dat_bufsz) { + m = mbulk_with_signal_alloc_by_pool(pool_id, clas, 0, + MBULK_DAT_BUFSZ_REQ_BEST_MAGIC); + if (m == NULL) + break; + /* all mbulk in this chain has an attribue, MBULK_F_CHAIN */ + m->flag |= MBULK_F_CHAIN; + tot_len += MBULK_SEG_TAILROOM(m); + pre->chain_next = m; + pre = m; + } + + if (tot_len < dat_bufsz) { + mbulk_chain_free(head); + return NULL; + } + + return head; +} + +/** + * free a chained mbulk + */ +void mbulk_chain_free(struct mbulk *sg) +{ + struct mbulk *chain_next, *m; + + /* allow null pointer */ + if (sg == NULL) + return; + + m = sg; + while (m != NULL) { + chain_next = m->chain_next; + + /* is not scatter-gather anymore */ + m->flag &= ~(MBULK_F_CHAIN | MBULK_F_CHAIN_HEAD); + mbulk_seg_free(m); + + m = chain_next; + } +} + +/** + * get a tail mbulk in the chain + * + */ +struct mbulk *mbulk_chain_tail(struct mbulk *m) +{ + while (m->chain_next != NULL) + m = m->chain_next; + return m; +} + +/** + * total buffer size in a chanied mbulk + * + */ +size_t mbulk_chain_bufsz(struct mbulk *m) +{ + size_t tbufsz = 0; + + while (m != NULL) { + tbufsz += m->dat_bufsz; + m = m->chain_next; + } + + return tbufsz; +} + +/** + * total data length in a chanied mbulk + * + */ +size_t mbulk_chain_tlen(struct mbulk *m) +{ + size_t tlen = 0; + + while (m != NULL) { + tlen += m->len; + m = m->chain_next; + } + + return tlen; +} +#endif /*MBULK_SUPPORT_SG_CHAIN*/ + +/** + * add a memory zone to a mbulk pool list + * + */ +#ifdef CONFIG_SCSC_WLAN_DEBUG +int mbulk_pool_add(u8 pool_id, char *base, char *end, size_t seg_size, u8 guard, int minor) +#else +int mbulk_pool_add(u8 pool_id, char *base, char *end, size_t seg_size, u8 guard) +#endif +{ + struct mbulk_pool *pool; + struct mbulk *next; + size_t byte_per_block; + + if (pool_id >= MBULK_POOL_ID_MAX) { + WARN_ON(pool_id >= MBULK_POOL_ID_MAX); + return -EIO; + } + + pool = &mbulk_pools[pool_id]; + + if (!MBULK_IS_ALIGNED(base)) { + WARN_ON(!MBULK_IS_ALIGNED(base)); + return -EIO; + } + + /* total required memory per block */ + byte_per_block = MBULK_SZ_ROUNDUP(sizeof(struct mbulk) + seg_size); + + /* init pool structure */ + memset(pool, 0, sizeof(*pool)); + pool->pid = pool_id; + pool->base_addr = base; + pool->end_addr = end; + pool->seg_size = (mbulk_len_t)(byte_per_block - sizeof(struct mbulk)); + pool->guard = guard; + + /* allocate segments */ + next = (struct mbulk *)base; + while (((uintptr_t)next + byte_per_block) <= (uintptr_t)end) { + memset(next, 0, sizeof(struct mbulk)); + next->pid = pool_id; + + /* add to the free list */ + if (pool->free_list == NULL) + next->next_offset = 0; + else + next->next_offset = (uintptr_t)pool->free_list - (uintptr_t)next; + next->flag = MBULK_F_FREE; + pool->free_list = next; +#ifdef MBULK_DEBUG + pool->tot_seg_num++; +#endif + pool->free_cnt++; +/* TOM.. BUG. */ + next = (struct mbulk *)((uintptr_t)next + byte_per_block); + } + + pool->valid = (pool->free_cnt) ? true : false; +#ifdef CONFIG_SCSC_WLAN_DEBUG + pool->minor = minor; +#endif + return 0; +} + +/** + * add mbulk pools in MIF address space + */ +void mbulk_pool_dump(u8 pool_id, int max_cnt) +{ + struct mbulk_pool *pool; + struct mbulk *m; + int cnt = max_cnt; + + pool = &mbulk_pools[pool_id]; + m = pool->free_list; + while (m != NULL && cnt--) + m = (m->next_offset == 0) ? NULL : + (struct mbulk *)(pool->base_addr + m->next_offset); +} + +/** + * free a mbulk in the virtual host + */ +void mbulk_free_virt_host(struct mbulk *m) +{ + u8 pool_id; + struct mbulk_pool *pool; + + if (m == NULL) + return; + + /* Remove colour */ + pool_id = m->pid & 0x1; + + pool = &mbulk_pools[pool_id]; + + if (!pool->valid) { + WARN_ON(!pool->valid); + return; + } + + /* put to the pool */ + mbulk_pool_put(pool, m); +} diff --git a/drivers/net/wireless/scsc/mbulk.h b/drivers/net/wireless/scsc/mbulk.h new file mode 100644 index 000000000000..60379da84318 --- /dev/null +++ b/drivers/net/wireless/scsc/mbulk.h @@ -0,0 +1,523 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef __MBULK_H__ +#define __MBULK_H__ + +/** + * mbulk(bulk memory) API + * + * This header file describes APIs of the bulk memory management. + * The diagram below is an example of a mbulk buffer with one + * segment (i.e. not a chained mbulk). + * + * sig_bufsz + * |<-------->| + * | |<--------- dat_bufsz ---------->| + * +--------------------------------------------------+ + * | mbulk| signal | bulk buffer | + * +-------------------------+---------------+--------+ + * | | | valid data | | + * | | |<--------+---->| | + * | | | mbulk_tlen(m) | | + * | |<----->| | |<------>| + * | mbulk_headroom(m)| | mbulk_tailroom(m) + * | | | + * | |-- off -->| + * v v | + * mbulk_get_sig(m) mbulk_dat(m) | + * v + * mbulk_dat_at(m,off) + * + * In general, all clients are supposed to use only mbulk_xxx() APIs (but not + * mbulk_seg_xxx() APIs), as they can handle S/G chained mbulk as well. + * But as of now, specially in Condor, S/G chained mbulk is not supported, + * which means the most of mbulk_xxx() would be wrappers of mbulk_seg_xxx(). + * + * An in-lined signal buffer can be allocated along with a mbulk buffer. + * There is no direct life-cycle relationship between the signal and the + * associated mbulk in this case, which means that the signal buffer should be + * de-allocated independently of the mbulk buffer. + * + */ + +/** + * bulk buffer descriptor + */ +struct mbulk; + +/** + * mbulk host pool ID + */ +#define MBULK_POOL_ID_DATA (0) +#define MBULK_POOL_ID_CTRL (1) +#define MBULK_POOL_ID_MAX (2) + +/** + * mbulk buffer classification + * + * Note that PACKED attribute is added to enum definition so that + * compiler assigns the smallest integral type (u8). + */ +enum mbulk_class { + MBULK_CLASS_CONTROL = 0, + MBULK_CLASS_HOSTIO = 1, + MBULK_CLASS_DEBUG = 2, + + MBULK_CLASS_FROM_HOST_DAT = 3, + MBULK_CLASS_FROM_HOST_CTL = 4, + MBULK_CLASS_FROM_RADIO = 5, + MBULK_CLASS_DPLP = 6, + MBULK_CLASS_OTHERS = 7, + MBULK_CLASS_FROM_RADIO_FORWARDED = 8, + MBULK_CLASS_MAX +} __packed; + +/** + * The private definition of mbulk structure is included here + * so that its members can be directly accessed, and the access + * codes can be in-lined by the compiler. + * But client codes are not supposed to directly refer to mbulk + * members, nor use mbulk_seg_xxx() functions. Only modules handling + * mbulk scatter/gather chain would directly use mulk_seg_xxx() APIs. + */ +#include "mbulk_def.h" + +/** + * Get the bulk data reference counter + * + * After a bulk buffer with non-zero data buffer size is created, + * the reference counter is set to one. Each time it is duplicated, + * its reference counter would be increased. + * + * Note that the reference counter is initialized to zero if a signal + * is created from mbulk pool but with zero data buffer size, as there + * is no data buffer. + */ +static inline int mbulk_refcnt(const struct mbulk *m) +{ + return MBULK_SEG_REFCNT(m); +} + +/** + * Get the bulk data buffer size + * + */ +static inline int mbulk_buffer_size(const struct mbulk *m) +{ + return MBULK_SEG_DAT_BUFSIZE(m); +} + +/** + * Check if mbulk has an in-lined signal buffer + * + */ +static inline bool mbulk_has_signal(const struct mbulk *m) +{ + return MBULK_SEG_HAS_SIGNAL(m); +} + +/** + * Set mbulk to be read-only + */ +static inline void mbulk_set_readonly(struct mbulk *m) +{ + MBULK_SEG_SET_READONLY(m); +} + +/** + * is mbulk read-only + */ +static inline bool mbulk_is_readonly(const struct mbulk *m) +{ + return MBULK_SEG_IS_READONLY(m); +} + +/** + * check if mbulk is a scatter/gather chained buffer + * + */ +static inline bool mbulk_is_sg(const struct mbulk *m) +{ + return MBULK_SEG_IS_CHAIN_HEAD(m); +} + +/** + * check if mbulk is a part of scatter/gather chained buffer + * + */ +static inline bool mbulk_is_chained(const struct mbulk *m) +{ + return MBULK_SEG_IS_CHAINED(m); +} + +/** + * Allocate a bulk buffer with an in-lined signal buffer + * + * Only one mbulk segment is used for allocation starting from the + * mbulk pool with the smallest segment size. If no segment fitting + * the requested size, then return NULL without trying to create + * a chained buffer. + * + */ +struct mbulk *mbulk_with_signal_alloc(enum mbulk_class clas, size_t sig_bufsz, + size_t dat_bufsz); +/** + * Allocate a bulk buffer with an in-lined signal buffer + * + * A mbulk segment is allocated from the given the pool, if its size + * meeting the requested size. + * + */ +struct mbulk *mbulk_with_signal_alloc_by_pool(u8 pool_id, u16 colour, + enum mbulk_class clas, size_t sig_bufsz, size_t dat_bufsz); + +/** + * Get the number of free mbulk slots in a pool + * + * Returns the number of mbulk slots available in a given pool. + */ +int mbulk_pool_get_free_count(u8 pool_id); + +/** + * Get a signal buffer address + * + * Given a mbulk buffer, returns a signal buffer address. + * + * @param m mbulk + * @return in-lined signal buffer + */ +static inline void *mbulk_get_seg(const struct mbulk *m) +{ + return (void *)MBULK_SEG_B(m); +} + +/** + * Get a signal buffer address + * + * Given a mbulk buffer, returns a signal buffer address if any in-lined + * signal buffer. + * + */ +static inline void *mbulk_get_signal(const struct mbulk *m) +{ + bool ret = false; + + ret = mbulk_has_signal(m); + + return ret ? mbulk_get_seg(m) : NULL; +} + +/** + * Allocate a bulk buffer + * + * Only one mbulk segment is used for allocation starting from the + * mbulk pool with the smallest segment size. If no segment fitting + * the requested size, then return NULL without trying to create + * a chained buffer. + * + */ +static inline struct mbulk *mbulk_alloc(enum mbulk_class clas, size_t dat_bufsz) +{ + return mbulk_with_signal_alloc(clas, 0, dat_bufsz); +} + +/** + * free mbulk buffer + * + * After checking the bulk reference counter, this function return the buffer + * to the mbulk pool if it is zero. Note that this doesn't free the in-lined + * signal buffer. + */ +static inline void mbulk_free(struct mbulk *m) +{ + mbulk_seg_free(m); +} + +/** + * get bulk buffer address for read or write access + * + * The address is the buffer address after the headroom in the mbulk segment. + * Note that this function can only be used to access the data in the same + * segment, including a segment in the mbulk chain (for example, to access + * the 802.11 header of A-MSDU). + * + */ +static inline void *mbulk_dat_rw(const struct mbulk *m) +{ + WARN_ON(MBULK_SEG_IS_READONLY(m)); + return MBULK_SEG_DAT(m); +} + +/** + * get bulk buffer address for read-only + * + * The address is the buffer address after the headroom in the mbulk segment. + * Note that this function can only be used to access the data in the same + * segment, including a segment in the mbulk chain (for example, to access + * the 802.11 header of A-MSDU). + * + */ +static inline const void *mbulk_dat_r(const struct mbulk *m) +{ + return (const void *)MBULK_SEG_DAT(m); +} + +/** + * get bulk buffer address at the offset for read or write access + * + */ +static inline void *mbulk_dat_at_rw(const struct mbulk *m, size_t off) +{ + WARN_ON(MBULK_SEG_IS_READONLY(m)); + return MBULK_SEG_DAT_AT(m, off); +} + +/** + * get bulk buffer address at the offset for read access + * + */ +static inline /*const*/ void *mbulk_dat_at_r(const struct mbulk *m, size_t off) +{ + return (/*const */ void *)MBULK_SEG_DAT_AT(m, off); +} + +/** + * get valid data length + * + */ +static inline size_t mbulk_tlen(const struct mbulk *m) +{ + return MBULK_SEG_LEN(m); +} + +/** + * get headroom + * + */ +static inline size_t mbulk_headroom(const struct mbulk *m) +{ + return MBULK_SEG_HEADROOM(m); +} + +static inline size_t mbulk_tailroom(const struct mbulk *m) +{ + return MBULK_SEG_TAILROOM(m); +} + +/** + * reserve headroom + * + * Note this API should be called right after mbulk is created or the valid + * data length is zero. + * + */ +static inline bool mbulk_reserve_head(struct mbulk *m, size_t headroom) +{ + return mbulk_seg_reserve_head(m, headroom); +} + +/** + * adjust the valid data range + * + * headroom would be placed after the signal buffer (or mbuf descriptor if + * no in-lined signal), and the valid data length is set to \len. + * + */ +static inline bool mbulk_adjust_range(struct mbulk *m, size_t headroom, size_t len) +{ + return mbulk_seg_adjust_range(m, headroom, len); +} + +/** + * extend the data range at the head + * + * The headroom would be reduced, and the data range is extended. + * To prepend data in the head, the headroom should have been reserved before. + * + */ +static inline bool mbulk_prepend_head(struct mbulk *m, size_t more) +{ + return mbulk_seg_prepend_head(m, more); +} + +/** + * extend the data at the tail + * + * Data range is expanded towards the tail. + * + */ +static inline bool mbulk_append_tail(struct mbulk *m, size_t more) +{ + return mbulk_seg_append_tail(m, more); +} + +/** + * trim data at the head + * + * The headroom would be increased, and the valid data range is reduced + * accordingly. + * + */ +static inline bool mbulk_trim_head(struct mbulk *m, size_t less) +{ + return mbulk_seg_trim_head(m, less); +} + +/** + * trim data at the tail + * + * The data length would be reduced. + * + */ +static inline bool mbulk_trim_tail(struct mbulk *m, size_t less) +{ + return mbulk_seg_trim_tail(m, less); +} + +/** + * duplicate a mbulk + * + * There is no data copy. but the referece counter of the orignal mbulk is + * increased by one. + * + */ +static inline struct mbulk *mbulk_duplicate(struct mbulk *m) +{ + return mbulk_seg_duplicate(m); +} + +/** + * clone a mbulk + * + * New mbulk buffer is created, and contents are copied. The signal is copied + * only when \copy_sig is TRUE. + * + */ +static inline struct mbulk *mbulk_clone(const struct mbulk *m, enum mbulk_class clas, + bool copy_sig) +{ + return mbulk_seg_clone(m, clas, copy_sig); +} + +/** + * allocate a signal buffer from mbulk pool + * + */ +void *msignal_alloc(size_t sig_sz); + +/** + * free a signal buffer created from mbulk pool + * + */ +void msignal_free(void *sig); + +/** + * get mbulk descriptor given a signal buffer address + * + */ +struct mbulk *msignal_to_mbulk(void *sig); + +/** + * get next chained mbulk in a scatter/gathered list + */ +static inline scsc_mifram_ref mbulk_chain_next(struct mbulk *m) +{ + return MBULK_SEG_CHAIN_NEXT(m); +} + +#ifdef MBULK_SUPPORT_SG_CHAIN +/** + * Scatter/Gather Chained Mbulk APIs + * ================================= + */ + +/** + * allocate a chained mbulk buffer from a specific mbulk pool + * + */ +struct mbulk *mbulk_chain_with_signal_alloc_by_pool(u8 pool_id, + enum mbulk_class clas, size_t sig_bufsz, size_t dat_bufsz); + +/** + * free a chained mbulk + */ +void mbulk_chain_free(struct mbulk *sg); + +/** + * get a tail mbulk in the chain + * + */ +struct mbulk *mbulk_chain_tail(struct mbulk *m); + +/** + * total buffer size in a chanied mbulk + * + */ +size_t mbulk_chain_bufsz(struct mbulk *m); + +/** + * total data length in a chanied mbulk + * + */ +size_t mbulk_chain_tlen(struct mbulk *m); + +/** + * get a number of mbulk segments in a chained mbulk + */ +static inline int mbulk_chain_num(const struct mbulk *m) +{ + if (mbulk_is_sg(m)) { + int n = 0; + + while (m != NULL) { + n++; + m = m->chain_next; + } + return n; + } + return 1; +} + +/* NOT IMPLEMENTED YET. */ +void *mbulk_chain_access(struct mbulk *m, size_t off, char *local_buf, size_t local_bufsz); +void *mbulk_chain_writeback(struct mbulk *m, size_t off, char *local_buf, size_t local_bufsz); +void *mbulk_chain_copy_from(struct mbulk *m, size_t off, char *buf, int len); +void *mbulk_chain_copy_to(struct mbulk *m, size_t off, char *buf, int len); +#endif /*MBULK_SUPPORT_SG_CHAIN*/ + +/** + * init mbulk library + */ +/*extern void init_mbulk(void);*/ +void init_mbulk(void *mem, size_t pool_size); + +/** + * add a memory zone to a mbulk pool list + * + */ +#ifdef CONFIG_SCSC_WLAN_DEBUG +int mbulk_pool_add(u8 pool_id, char *base, char *end, size_t seg_size, u8 guard, int minor); +#else +int mbulk_pool_add(u8 pool_id, char *base, char *end, size_t buf_size, u8 guard); +#endif +/** + * check sanity of a mbulk pool + */ +void mbulk_pool_check_sanity(u8 pool_id); + +/** + * configure the handler which returning the buffer to the host + */ +void mbulk_set_handler_return_host_mbulk(void (*free_host_buf)(struct mbulk *m)); + +/** + * free a mbulk in the virtual host + */ +void mbulk_free_virt_host(struct mbulk *m); +void mbulk_pool_dump(u8 pool_id, int max_cnt); + +#endif /*__MBULK_H__*/ diff --git a/drivers/net/wireless/scsc/mbulk_def.h b/drivers/net/wireless/scsc/mbulk_def.h new file mode 100644 index 000000000000..d3fff5e15284 --- /dev/null +++ b/drivers/net/wireless/scsc/mbulk_def.h @@ -0,0 +1,171 @@ +/****************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef __MBULK_DEF_H__ +#define __MBULK_DEF_H__ + +#include +#include + +/** + * mbulk + * + * The mbulk supports .... + */ + +/* MIF_HIP_CFG_BLUK_BUFFER_ALIGNE = 4 + * Hence, data buffer is always aligned to 4. + */ +#define MBULK_SIG_BUFSZ_ROUNDUP(sig_bufsz) round_up(sig_bufsz, 4) + +/** + * bulk memory descriptor, managing semgments allocated from mbulk pools + * + * mbulk is defined in public header, to avoid the function call dummy operations... + * Privae variables staring with __ should be not be referred by the client + * codes. + * + * mbulk structure is shared with the host. Hence the structure should be packed. + * data buffer size in byte + * + * This could be duplicated as the buffer size can be calculated from + * pool block size. There are two reasons why the data buffer size is + * set in the descritpor: + * - if mbulk comes from the host, the firmware is not aware of its pool + * configuration + * - if the data buffer is less than the block size in the pool, it's more + * cache efficient to flush only the data buffer area + * probably the firwmare has to invalidate the entire block ? + * + */ +typedef u16 mbulk_len_t; +struct mbulk { + scsc_mifram_ref next_offset; /** next mbulk offset */ + u8 flag; /** mbulk flags */ + enum mbulk_class clas; /** bulk buffer classification */ + u8 pid; /** mbulk pool id */ + u8 refcnt; /** reference counter of bulk buffer */ + mbulk_len_t dat_bufsz; /** data buffer size in byte */ + mbulk_len_t sig_bufsz; /** signal buffer size in byte */ + mbulk_len_t len; /** valid data length */ + mbulk_len_t head; /** start offset of data after mbulk structure */ + scsc_mifram_ref chain_next_offset; /** chain next mbulk offset */ +} __packed; + +/* mbulk flags */ +#define MBULK_F_SIG (1 << 0) /** has a signal after mbulk descriptor*/ +#define MBULK_F_READONLY (1 << 1) /** is read-only */ +#define MBULK_F_OBOUND (1 << 2) /** other CPU(host) owns the buffer */ +#define MBULK_F_WAKEUP (1 << 3) /** frame waking up the host */ +#define MBULK_F_FREE (1 << 5) /** mbulk alreay freed */ +#define MBULK_F_CHAIN_HEAD (1 << 6) /** is an head in scatter/gather chain */ +#define MBULK_F_CHAIN (1 << 7) /** is scatter/gather chain */ + +/* TODO: Define max number of chained mbulk */ +#define MBULK_MAX_CHAIN 16 +/* the buffer address just after mbulk descriptor */ +#define MBULK_SEG_B(m) ((uintptr_t)(m) + sizeof(struct mbulk)) + +/* raw buffer size of mbulk segment including struct mbulk */ +#define MBULK_SEG_RAW_BUFSZ(m) (sizeof(struct mbulk) + (m)->sig_bufsz + (m)->dat_bufsz) + +/* operations against "a" mbulk segment. */ +#define MBULK_SEG_NEXT(m) ((m)->next_offset) +#define MBULK_SEG_REFCNT(m) ((m)->refcnt) +#define MBULK_SEG_HAS_SIGNAL(m) ((m)->flag & MBULK_F_SIG) +#define MBULK_SEG_CHAIN_NEXT(m) ((m)->chain_next_offset) +#define MBULK_SEG_IS_CHAIN_HEAD(m) ((m)->flag & MBULK_F_CHAIN_HEAD) +#define MBULK_SEG_IS_CHAINED(m) ((m)->flag & MBULK_F_CHAIN) + +#define MBULK_SEG_IS_READONLY(m) ((m)->flag & MBULK_F_READONLY) +#define MBULK_SEG_SET_READONLY(m) ((m)->flag |= MBULK_F_READONLY) + +#define MBULK_SEG_DAT(m) ((char *)MBULK_SEG_B(m) + (m)->head) +#define MBULK_SEG_DAT_AT(m, off) ((char *)MBULK_SEG_B(m) + (m)->head + (mbulk_len_t)(off)) +#define MBULK_SEG_DAT_BUFSIZE(m) ((size_t)((m)->dat_bufsz)) +#define MBULK_SEG_SIG_BUFSIZE(m) ((size_t)((m)->sig_bufsz)) +#define MBULK_SEG_LEN(m) ((m)->len) +#define MBULK_SEG_HEADROOM(m) ((size_t)((m)->head - (m)->sig_bufsz)) +#define MBULK_SEG_TAILROOM(m) ((size_t)((m)->dat_bufsz - (MBULK_SEG_HEADROOM(m) + (m)->len))) + +static inline bool mbulk_seg_reserve_head(struct mbulk *m, size_t headroom) +{ + if (WARN_ON(!(m->dat_bufsz >= headroom))) + return false; + m->head += (mbulk_len_t)headroom; + return true; +} + +static inline bool mbulk_seg_adjust_range(struct mbulk *m, size_t headroom, + size_t len) +{ + if (WARN_ON(!(m->dat_bufsz >= (headroom + len)))) + return false; + m->head = m->sig_bufsz + (mbulk_len_t)headroom; + m->len = (mbulk_len_t)len; + return true; +} + +static inline bool mbulk_seg_prepend_head(struct mbulk *m, size_t more) +{ + if (WARN_ON(!(MBULK_SEG_HEADROOM(m) >= more))) + return false; + m->head -= (mbulk_len_t)more; + m->len += (mbulk_len_t)more; + return true; +} + +static inline bool mbulk_seg_append_tail(struct mbulk *m, size_t more) +{ + if (WARN_ON(!(MBULK_SEG_TAILROOM(m) >= more))) + return false; + m->len += (mbulk_len_t)more; + return true; +} + +static inline bool mbulk_seg_trim_head(struct mbulk *m, size_t less) +{ + m->head += (mbulk_len_t)less; + m->len = (m->len <= (mbulk_len_t)less) ? 0 : (m->len - (mbulk_len_t)less); + return true; +} + +static inline bool mbulk_seg_trim_tail(struct mbulk *m, size_t less) +{ + if (WARN_ON(!(m->len >= (mbulk_len_t)less))) + return false; + m->len -= (mbulk_len_t)less; + return true; +} + +/** + * free the bulk buffer of a segment + * + * Simply decrement the bulk reference counter and put to the pool if + * it is zero. Note that the signal buffer is not affected. + * + */ +void mbulk_seg_free(struct mbulk *m); + +/** + * duplicate the bulk buffer of a mbulk segment + * + * This is used to share the read-only bulk buffer. The reference counter is + * increased by one each time it is duplicated. + * + */ +struct mbulk *mbulk_seg_duplicate(struct mbulk *m); + +/** + * clone the bulk buffer of a mbulk segment + * + * A separate mbulk segment is created and the data is copied to it. + * If \copy_sig is true, then the signal is copied as well. + * + */ +struct mbulk *mbulk_seg_clone(const struct mbulk *m, enum mbulk_class clas, bool copy_sig); + +#endif /*__MBULK_DEF_H__*/ diff --git a/drivers/net/wireless/scsc/mgt.c b/drivers/net/wireless/scsc/mgt.c new file mode 100755 index 000000000000..8fdb70100d85 --- /dev/null +++ b/drivers/net/wireless/scsc/mgt.c @@ -0,0 +1,5996 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include +#include +#include + +#ifdef CONFIG_ARCH_EXYNOS +#include +#endif + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER +#include +#include +#endif + +#include +#include +#include "mgt.h" +#include "debug.h" +#include "mlme.h" +#include "netif.h" +#include "utils.h" +#include "udi.h" +#include "log_clients.h" +#ifdef SLSI_TEST_DEV +#include "unittest.h" +#endif +#include "hip.h" +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +#include "procfs.h" +#include "mib.h" +#include "unifiio.h" +#include "ba.h" +#include "scsc_wifi_fcq.h" +#include "cac.h" +#include "cfg80211_ops.h" +#include "nl80211_vendor.h" + +#ifdef CONFIG_SCSC_WLBTD +#include "scsc_wlbtd.h" +#endif +#define CSR_WIFI_SME_MIB2_HOST_PSID_MASK 0x8000 +#define SLSI_DEFAULT_HW_MAC_ADDR "\x00\x00\x0F\x11\x22\x33" +#define MX_WLAN_FILE_PATH_LEN_MAX (128) +#define SLSI_MIB_REG_RULES_MAX (50) +#define SLSI_MIB_MAX_CLIENT (10) +#define SLSI_REG_PARAM_START_INDEX (1) + +static char *mib_file_t = "wlan_t.hcf"; +module_param(mib_file_t, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mib_file_t, "mib data filename"); + +static char *mib_file2_t = "wlan_t_sw.hcf"; +module_param(mib_file2_t, charp, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mib_file2_t, "mib data filename"); + +/* MAC address override. If set to FF's, then + * the address is taken from config files or + * default derived from HW ID. + */ +static char mac_addr_override[] = "ff:ff:ff:ff:ff:ff"; +module_param_string(mac_addr, mac_addr_override, sizeof(mac_addr_override), S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mac_addr_override, "WLAN MAC address override"); + +static int slsi_mib_open_file(struct slsi_dev *sdev, struct slsi_dev_mib_info *mib_info, const struct firmware **fw); +static int slsi_mib_close_file(struct slsi_dev *sdev, const struct firmware *e); +static int slsi_mib_download_file(struct slsi_dev *sdev, struct slsi_dev_mib_info *mib_info); +static int slsi_country_to_index(struct slsi_802_11d_reg_domain *domain_info, const char *alpha2); +static int slsi_mib_initial_get(struct slsi_dev *sdev); +static int slsi_hanged_event_count; +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#define SLSI_MAX_CHAN_5G_BAND 25 +#define SLSI_2G_CHANNEL_ONE 2412 +static int slsi_5ghz_all_channels[] = {5180, 5200, 5220, 5240, 5260, 5280, 5300, 5320, 5500, 5520, + 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700, 5720, + 5745, 5765, 5785, 5805, 5825 }; +#endif + +/* MAC address override stored in /sys/wifi/mac_addr */ +static ssize_t sysfs_show_macaddr(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t sysfs_store_macaddr(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count); + +static struct kobject *wifi_kobj_ref; +static char sysfs_mac_override[] = "ff:ff:ff:ff:ff:ff"; +static struct kobj_attribute mac_attr = __ATTR(mac_addr, 0660, sysfs_show_macaddr, sysfs_store_macaddr); + +/* Retrieve mac address in sysfs global */ +static ssize_t sysfs_show_macaddr(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, sizeof(sysfs_mac_override), "%s", sysfs_mac_override); +} + +/* Update mac address in sysfs global */ +static ssize_t sysfs_store_macaddr(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + int r; + + SLSI_INFO_NODEV("Override WLAN MAC address %s\n", buf); + + /* size of macaddr string */ + r = sscanf(buf, "%18s", &sysfs_mac_override); + + return (r > 0) ? count : 0; +} + +/* Register sysfs mac address override */ +void slsi_create_sysfs_macaddr(void) +{ + int r; + + wifi_kobj_ref = mxman_wifi_kobject_ref_get(); + pr_info("wifi_kobj_ref: 0x%p\n", wifi_kobj_ref); + + if (wifi_kobj_ref) { + /* Create sysfs file /sys/wifi/mac_addr */ + r = sysfs_create_file(wifi_kobj_ref, &mac_attr.attr); + if (r) { + /* Failed, so clean up dir */ + pr_err("Can't create /sys/wifi/mac_addr\n"); + return; + } + } else { + pr_err("failed to create /sys/wifi/mac_addr\n"); + } +} + +/* Unregister sysfs mac address override */ +void slsi_destroy_sysfs_macaddr(void) +{ + if (!wifi_kobj_ref) + return; + + /* Destroy /sys/wifi/mac_addr file */ + sysfs_remove_file(wifi_kobj_ref, &mac_attr.attr); + + /* Destroy /sys/wifi virtual dir */ + mxman_wifi_kobject_ref_put(); +} + +void slsi_purge_scan_results_locked(struct netdev_vif *ndev_vif, u16 scan_id) +{ + struct slsi_scan_result *scan_result; + struct slsi_scan_result *prev = NULL; + + scan_result = ndev_vif->scan[scan_id].scan_results; + while (scan_result) { + slsi_kfree_skb(scan_result->beacon); + slsi_kfree_skb(scan_result->probe_resp); + prev = scan_result; + scan_result = scan_result->next; + kfree(prev); + } + ndev_vif->scan[scan_id].scan_results = NULL; +} + +void slsi_purge_scan_results(struct netdev_vif *ndev_vif, u16 scan_id) +{ + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + slsi_purge_scan_results_locked(ndev_vif, scan_id); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); +} + +struct sk_buff *slsi_dequeue_cached_scan_result(struct slsi_scan *scan, int *count) +{ + struct sk_buff *skb = NULL; + struct slsi_scan_result *scan_result = scan->scan_results; + + if (scan_result) { + if (scan_result->beacon) { + skb = scan_result->beacon; + scan_result->beacon = NULL; + } else if (scan_result->probe_resp) { + skb = scan_result->probe_resp; + scan_result->probe_resp = NULL; + } else { + SLSI_ERR_NODEV("Scan entry with no beacon /probe resp!!\n"); + } + + /*If beacon and probe response indicated above , remove the entry*/ + if (!scan_result->beacon && !scan_result->probe_resp) { + scan->scan_results = scan_result->next; + kfree(scan_result); + if (count) + (*count)++; + } + } + return skb; +} + +void slsi_get_hw_mac_address(struct slsi_dev *sdev, u8 *addr) +{ +#ifndef SLSI_TEST_DEV + const struct firmware *e = NULL; + int i; + u32 u[ETH_ALEN]; + char path_name[MX_WLAN_FILE_PATH_LEN_MAX]; + int r; + bool valid = false; + + /* Module parameter override */ + r = sscanf(mac_addr_override, "%02X:%02X:%02X:%02X:%02X:%02X", &u[0], &u[1], &u[2], &u[3], &u[4], &u[5]); + if (r != ETH_ALEN) { + SLSI_ERR(sdev, "mac_addr modparam set, but format is incorrect (should be e.g. xx:xx:xx:xx:xx:xx)\n"); + goto mac_sysfs; + } + for (i = 0; i < ETH_ALEN; i++) { + if (u[i] != 0xff) + valid = true; + addr[i] = u[i] & 0xff; + } + + /* If the override is valid, use it */ + if (valid) { + SLSI_INFO(sdev, "MAC address from modparam: %02X:%02X:%02X:%02X:%02X:%02X\n", + u[0], u[1], u[2], u[3], u[4], u[5]); + return; + } + + /* Sysfs parameter override */ +mac_sysfs: + r = sscanf(sysfs_mac_override, "%02X:%02X:%02X:%02X:%02X:%02X", &u[0], &u[1], &u[2], &u[3], &u[4], &u[5]); + if (r != ETH_ALEN) { + SLSI_ERR(sdev, "mac_addr in sysfs set, but format is incorrect (should be e.g. xx:xx:xx:xx:xx:xx)\n"); + goto mac_file; + } + for (i = 0; i < ETH_ALEN; i++) { + if (u[i] != 0xff) + valid = true; + addr[i] = u[i] & 0xff; + } + + /* If the override is valid, use it */ + if (valid) { + SLSI_INFO(sdev, "MAC address from sysfs: %02X:%02X:%02X:%02X:%02X:%02X\n", + u[0], u[1], u[2], u[3], u[4], u[5]); + return; + } + + /* read mac.txt */ +mac_file: + if (sdev->maddr_file_name) { + scnprintf(path_name, MX_WLAN_FILE_PATH_LEN_MAX, "wlan/%s", sdev->maddr_file_name); + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "MAC address file : %s\n", path_name); + + r = mx140_file_request_device_conf(sdev->maxwell_core, &e, path_name); + if (r != 0) + goto mac_efs; + + if (!e) { + SLSI_ERR(sdev, "mx140_file_request_device_conf() returned succes, but firmware was null\n"); + goto mac_efs; + } + r = sscanf(e->data, "%02X:%02X:%02X:%02X:%02X:%02X", &u[0], &u[1], &u[2], &u[3], &u[4], &u[5]); + mx140_file_release_conf(sdev->maxwell_core, e); + if (r != ETH_ALEN) { + SLSI_ERR(sdev, "%s exists, but format is incorrect (should be e.g. xx:xx:xx:xx:xx:xx)\n", path_name); + goto mac_efs; + } + for (i = 0; i < ETH_ALEN; i++) + addr[i] = u[i] & 0xff; + SLSI_INFO(sdev, "MAC address loaded from %s: %02X:%02X:%02X:%02X:%02X:%02X\n", path_name, u[0], u[1], u[2], u[3], u[4], u[5]); + return; + } +mac_efs: +#ifdef CONFIG_SCSC_WLAN_MAC_ADDRESS_FILENAME + r = mx140_request_file(sdev->maxwell_core, CONFIG_SCSC_WLAN_MAC_ADDRESS_FILENAME, &e); + if (r != 0) + goto mac_default; + if (!e) { + SLSI_ERR(sdev, "mx140_request_file() returned succes, but firmware was null\n"); + goto mac_default; + } + r = sscanf(e->data, "%02X:%02X:%02X:%02X:%02X:%02X", &u[0], &u[1], &u[2], &u[3], &u[4], &u[5]); + if (r != ETH_ALEN) { + SLSI_ERR(sdev, "%s exists, but format is incorrect (%d) [%20s] (should be e.g. xx:xx:xx:xx:xx:xx)\n", + CONFIG_SCSC_WLAN_MAC_ADDRESS_FILENAME, r, e->data); + goto mac_default; + } + for (i = 0; i < ETH_ALEN; i++) { + if (u[i] != 0xff) + valid = true; + addr[i] = u[i] & 0xff; + } +#endif + /* If MAC address seems valid, finished */ + if (valid) { + SLSI_INFO(sdev, "MAC address loaded from %s: %02X:%02X:%02X:%02X:%02X:%02X\n", + CONFIG_SCSC_WLAN_MAC_ADDRESS_FILENAME, u[0], u[1], u[2], u[3], u[4], u[5]); + + /* MAC address read could hold invalid values, try to fix it to normal address */ + if (addr[0] & 0x01) { + addr[0] = addr[0] & 0xfe; + SLSI_INFO(sdev, "MAC address invalid, fixed address: %pM\n", addr); + } + mx140_release_file(sdev->maxwell_core, e); + return; + } +mac_default: + /* This is safe to call, even if the struct firmware handle is NULL */ + mx140_file_release_conf(sdev->maxwell_core, e); + + SLSI_ETHER_COPY(addr, SLSI_DEFAULT_HW_MAC_ADDR); +#ifdef CONFIG_ARCH_EXYNOS + /* Randomise MAC address from the soc uid */ + addr[3] = (exynos_soc_info.unique_id & 0xFF0000000000) >> 40; + addr[4] = (exynos_soc_info.unique_id & 0x00FF00000000) >> 32; + addr[5] = (exynos_soc_info.unique_id & 0x0000FF000000) >> 24; +#endif + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, + "MAC addr file NOT found, using default MAC ADDR: %pM\n", addr); +#else + /* We use FIXED Mac addresses with the unittest driver */ + struct slsi_test_dev *uftestdev = (struct slsi_test_dev *)sdev->maxwell_core; + + SLSI_ETHER_COPY(addr, uftestdev->hw_addr); + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Test Device Address: %pM\n", addr); +#endif +} + +static void write_wifi_version_info_file(struct slsi_dev *sdev) +{ +#if defined(ANDROID_VERSION) && (ANDROID_VERSION >= 90000) + char *filepath = "/data/vendor/conn/.wifiver.info"; +#else + char *filepath = "/data/misc/conn/.wifiver.info"; +#endif + char buf[256]; + char build_id_fw[128]; + char build_id_drv[64]; + +#ifndef CONFIG_SCSC_WLBTD + struct file *fp = NULL; + + fp = filp_open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0644); + + if (IS_ERR(fp)) { + SLSI_WARN(sdev, "version file wasn't found\n"); + return; + } else if (!fp) { + SLSI_WARN(sdev, "%s doesn't exist.\n", filepath); + return; + } +#endif + mxman_get_fw_version(build_id_fw, 128); + mxman_get_driver_version(build_id_drv, 64); + + /* WARNING: + * Please do not change the format of the following string + * as it can have fatal consequences. + * The framework parser for the version may depend on this + * exact formatting. + * + * Also beware that ANDROID_VERSION will not be defined in AOSP. + */ +#if defined(ANDROID_VERSION) && (ANDROID_VERSION >= 90000) + /* P-OS */ + snprintf(buf, sizeof(buf), + "%s\n" /* drv_ver: already appended by mxman_get_driver_version() */ + "f/w_ver: %s\n" + "hcf_ver_hw: %s\n" + "hcf_ver_sw: %s\n" + "regDom_ver: %d.%d\n", + build_id_drv, + build_id_fw, + sdev->mib[0].platform, + sdev->mib[1].platform, + ((sdev->reg_dom_version >> 8) & 0xFF), (sdev->reg_dom_version & 0xFF)); +#else + /* O-OS, or unknown */ + snprintf(buf, sizeof(buf), + "%s (f/w_ver: %s)\nregDom_ver: %d.%d\n", + build_id_drv, + build_id_fw, + ((sdev->reg_dom_version >> 8) & 0xFF), (sdev->reg_dom_version & 0xFF)); +#endif + +/* If ANDROID_VERSION is not known, avoid writing the file, as it could go to the wrong + * location. + */ +#ifdef ANDROID_VERSION +#ifdef CONFIG_SCSC_WLBTD + wlbtd_write_file(filepath, buf); +#else + kernel_write(fp, buf, strlen(buf), 0); + if (fp) + filp_close(fp, NULL); +#endif + + SLSI_INFO(sdev, "Succeed to write firmware/host information to .wifiver.info\n"); +#else + SLSI_UNUSED_PARAMETER(filepath); +#endif +} + +static void write_m_test_chip_version_file(struct slsi_dev *sdev) +{ +#ifdef CONFIG_SCSC_WLBTD + char *filepath = "/data/vendor/conn/.cid.info"; + char buf[256]; + + snprintf(buf, sizeof(buf), "%s\n", SCSC_RELEASE_SOLUTION); + + wlbtd_write_file(filepath, buf); + + SLSI_WARN(sdev, "Wrote chip information to .cid.info\n"); +#endif +} + + +#ifdef CONFIG_SCSC_WLAN_DEBUG +int slsi_start_monitor_mode(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u8 device_address[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + ndev_vif->vif_type = FAPI_VIFTYPE_MONITOR; + + if (slsi_mlme_add_vif(sdev, dev, dev->dev_addr, device_address) != 0) { + SLSI_NET_ERR(dev, "add VIF for Monitor mode failed\n"); + ndev_vif->vif_type = SLSI_VIFTYPE_UNSPECIFIED; + return -EINVAL; + } + + /* set the link type for the device; it depends on the format of + * packet the firmware is going to Pass to Host. + * + * If the firmware passes MA data in 802.11 frame format, then + * dev->type = ARPHRD_IEEE80211; + * + * If the firmware adds Radio TAP header to MA data, + * dev->type = ARPHRD_IEEE80211_RADIOTAP; + */ + dev->type = ARPHRD_IEEE80211_RADIOTAP; + ndev_vif->activated = true; + return 0; +} + +void slsi_stop_monitor_mode(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "de-activate monitor VIF\n"); + slsi_mlme_del_vif(sdev, dev); + slsi_vif_deactivated(sdev, dev); +} +#endif + +#ifdef CONFIG_SCSC_LOG_COLLECTION +static int slsi_hcf_collect(struct scsc_log_collector_client *collect_client, size_t size) +{ + struct slsi_dev *sdev = (struct slsi_dev *)collect_client->prv; + int ret = 0; + u8 index = sdev->collect_mib.num_files; + u8 i; + u8 *data; + + SLSI_INFO_NODEV("Collecting WLAN HCF\n"); + + if (!sdev->collect_mib.enabled) + SLSI_INFO_NODEV("Collection not enabled\n"); + + spin_lock(&sdev->collect_mib.in_collection); + ret = scsc_log_collector_write(&index, sizeof(char), 1); + if (ret) { + spin_unlock(&sdev->collect_mib.in_collection); + return ret; + } + + for (i = 0; i < index; i++) { + SLSI_INFO_NODEV("Collecting WLAN HCF. File %s\n", sdev->collect_mib.file[i].file_name); + /* Write file name */ + ret = scsc_log_collector_write((char *)&sdev->collect_mib.file[i].file_name, 32, 1); + if (ret) { + spin_unlock(&sdev->collect_mib.in_collection); + return ret; + } + /* Write file len */ + ret = scsc_log_collector_write((char *)&sdev->collect_mib.file[i].len, sizeof(u16), 1); + if (ret) { + spin_unlock(&sdev->collect_mib.in_collection); + return ret; + } + /* Write data */ + data = sdev->collect_mib.file[i].data; + if (!data) + continue; + ret = scsc_log_collector_write((char *)data, sdev->collect_mib.file[i].len, 1); + if (ret) { + spin_unlock(&sdev->collect_mib.in_collection); + return ret; + } + } + spin_unlock(&sdev->collect_mib.in_collection); + + return ret; +} + +/* Collect client registration for HCF file*/ +struct scsc_log_collector_client slsi_hcf_client = { + .name = "wlan_hcf", + .type = SCSC_LOG_CHUNK_WLAN_HCF, + .collect_init = NULL, + .collect = slsi_hcf_collect, + .collect_end = NULL, + .prv = NULL, +}; +#endif + +int slsi_start(struct slsi_dev *sdev) +{ +#ifndef CONFIG_SCSC_DOWNLOAD_FILE + const struct firmware *fw = NULL; +#endif + int err = 0, r; + int i; + char alpha2[3]; +#ifdef CONFIG_SCSC_WLAN_AP_INFO_FILE + u32 offset = 0; + struct file *fp = NULL; +#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 + char *filepath = "/data/vendor/conn/.softap.info"; +#else + char *filepath = "/data/misc/conn/.softap.info"; +#endif + char buf[512]; +#endif +#ifdef CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA + struct file *file_ptr = NULL; + char *ant_file_path = "/data/vendor/conn/.ant.info"; + char ant_mode = '0'; + u16 antenna = 0; +#endif + + if (WARN_ON(!sdev)) + return -EINVAL; + + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + + slsi_wakelock(&sdev->wlan_wl); + + if (sdev->device_state != SLSI_DEVICE_STATE_STOPPED) { + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Device already started: device_state:%d\n", sdev->device_state); + goto done; + } + + if (sdev->recovery_status) { + r = wait_for_completion_timeout(&sdev->recovery_completed, + msecs_to_jiffies(sdev->recovery_timeout)); + if (r == 0) + SLSI_INFO(sdev, "recovery_completed timeout\n"); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) + reinit_completion(&sdev->recovery_completed); +#else + /*This is how the macro is used in the older version.*/ + INIT_COMPLETION(sdev->recovery_completed); +#endif + } + + sdev->device_state = SLSI_DEVICE_STATE_STARTING; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) + reinit_completion(&sdev->sig_wait.completion); +#else + INIT_COMPLETION(sdev->sig_wait.completion); +#endif + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Step [1/2]: Start WLAN service\n"); + SLSI_EC_GOTO(slsi_sm_wlan_service_open(sdev), err, err_done); + /** + * Download MIB data, if any. + */ + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Step [2/3]: Send MIB configuration\n"); + + sdev->local_mib.mib_hash = 0; /* Reset localmib hash value */ +#ifndef SLSI_TEST_DEV +#ifdef CONFIG_SCSC_LOG_COLLECTION + spin_lock_init(&sdev->collect_mib.in_collection); + sdev->collect_mib.num_files = 0; + sdev->collect_mib.enabled = false; +#endif +#ifndef CONFIG_SCSC_DOWNLOAD_FILE + /* The "_t" HCF is used in RF Test mode and wlanlite/production test mode */ + if (slsi_is_rf_test_mode_enabled() || slsi_is_test_mode_enabled()) { + sdev->mib[0].mib_file_name = mib_file_t; + sdev->mib[1].mib_file_name = mib_file2_t; + } else { + sdev->mib[0].mib_file_name = slsi_mib_file; + sdev->mib[1].mib_file_name = slsi_mib_file2; + } + + /* Place MIB files in shared memory */ + for (i = 0; i < SLSI_WLAN_MAX_MIB_FILE; i++) { + err = slsi_mib_open_file(sdev, &sdev->mib[i], &fw); + + /* Only the first file is mandatory */ + if (i == 0 && err) { + SLSI_ERR(sdev, "mib: Mandatory wlan hcf missing. WLAN will not start (err=%d)\n", err); + slsi_sm_wlan_service_close(sdev); + goto err_done; + } + } + + err = slsi_sm_wlan_service_start(sdev); + if (err) { + SLSI_ERR(sdev, "slsi_sm_wlan_service_start failed: err=%d\n", err); + slsi_mib_close_file(sdev, fw); + slsi_sm_wlan_service_close(sdev); + goto err_done; + } + slsi_mib_close_file(sdev, fw); +#else + /* Download main MIB file via mlme_set */ + err = slsi_sm_wlan_service_start(sdev); + if (err) { + SLSI_ERR(sdev, "slsi_sm_wlan_service_start failed: err=%d\n", err); + slsi_sm_wlan_service_close(sdev); + goto err_done; + } + SLSI_EC_GOTO(slsi_mib_download_file(sdev, &sdev->mib), err, err_hip_started); +#endif + /* Always try to download optional localmib file via mlme_set, ignore error */ + (void)slsi_mib_download_file(sdev, &sdev->local_mib); +#endif + /** + * Download MIB data, if any. + * Get f/w capabilities and default configuration + * configure firmware + */ + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + sdev->device_config.rssi_boost_2g = 0; + sdev->device_config.rssi_boost_5g = 0; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Step [3/3]: Get MIB configuration\n"); + SLSI_EC_GOTO(slsi_mib_initial_get(sdev), err, err_hip_started); + SLSI_INFO(sdev, "=== Version info from the [MIB] ===\n"); + SLSI_INFO(sdev, "HW Version : 0x%.4X (%u)\n", sdev->chip_info_mib.chip_version, sdev->chip_info_mib.chip_version); + SLSI_INFO(sdev, "Platform : 0x%.4X (%u)\n", sdev->plat_info_mib.plat_build, sdev->plat_info_mib.plat_build); + slsi_cfg80211_update_wiphy(sdev); + + /* Get UnifiCountryList */ + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + sdev->device_config.host_state = FAPI_HOSTSTATE_CELLULAR_ACTIVE; + err = slsi_read_unifi_countrylist(sdev, SLSI_PSID_UNIFI_COUNTRY_LIST); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + if (err) + goto err_hip_started; + + /* Get unifiDefaultCountry */ + err = slsi_read_default_country(sdev, alpha2, 1); + alpha2[2] = '\0'; + if (err < 0) + goto err_hip_started; + + /* unifiDefaultCountry != world_domain */ + if (!(alpha2[0] == '0' && alpha2[1] == '0')) + if (memcmp(sdev->device_config.domain_info.regdomain->alpha2, alpha2, 2) != 0) { + memcpy(sdev->device_config.domain_info.regdomain->alpha2, alpha2, 2); + + /* Read the regulatory params for the country*/ + if (slsi_read_regulatory_rules(sdev, &sdev->device_config.domain_info, alpha2) == 0) { + slsi_reset_channel_flags(sdev); + wiphy_apply_custom_regulatory(sdev->wiphy, sdev->device_config.domain_info.regdomain); + } + } + /* Do nothing for unifiDefaultCountry == world_domain */ + + /* write .wifiver.info */ + write_wifi_version_info_file(sdev); + + /* write .cid.info */ + write_m_test_chip_version_file(sdev); + +#ifdef CONFIG_SCSC_WLAN_AP_INFO_FILE + /* writing .softap.info in /data/vendor/conn */ + fp = filp_open(filepath, O_WRONLY | O_CREAT, 0644); + + if (!fp) { + WARN(1, "%s doesn't exist\n", filepath); + } else if (IS_ERR(fp)) { + WARN(1, "%s open returned error %d\n", filepath, IS_ERR(fp)); + } else { + offset = snprintf(buf + offset, sizeof(buf), "#softap.info\n"); + offset += snprintf(buf + offset, sizeof(buf), "DualBandConcurrency=%s\n", sdev->dualband_concurrency ? "yes" : "no"); + offset += snprintf(buf + offset, sizeof(buf), "DualInterface=%s\n", "yes"); + offset += snprintf(buf + offset, sizeof(buf), "5G=%s\n", sdev->band_5g_supported ? "yes" : "no"); + offset += snprintf(buf + offset, sizeof(buf), "maxClient=%d\n", !sdev->softap_max_client ? SLSI_MIB_MAX_CLIENT : sdev->softap_max_client); + + /* following are always supported */ + offset += snprintf(buf + offset, sizeof(buf), "HalFn_setCountryCodeHal=yes\n"); + offset += snprintf(buf + offset, sizeof(buf), "HalFn_getValidChannels=yes\n"); +#ifdef CONFIG_SCSC_WLBTD + wlbtd_write_file(filepath, buf); +#else + + kernel_write(fp, buf, strlen(buf), 0); +#endif + if (fp) + filp_close(fp, NULL); + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Succeed to write softap information to .softap.info\n"); + } +#endif + +#ifdef CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA + /* reading antenna mode from /data/vendor/conn/.ant.info */ + file_ptr = filp_open(ant_file_path, O_RDONLY, 0); + + if (!file_ptr) { + SLSI_DBG1(sdev, SLSI_CFG80211, "%s doesn't exist\n", ant_file_path); + } else if (IS_ERR(file_ptr)) { + SLSI_DBG1(sdev, SLSI_CFG80211, "%s open returned error %d\n", ant_file_path, IS_ERR(file_ptr)); + } else { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + kernel_read(file_ptr, &ant_mode, 1, &file_ptr->f_pos); +#else + kernel_read(file_ptr, file_ptr->f_pos, &ant_mode, 1); +#endif + antenna = ant_mode - '0'; + filp_close(file_ptr, NULL); + + slsi_set_mib_preferred_antenna(sdev, antenna); + } +#endif + +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Register with log collector to collect wlan hcf file */ + slsi_hcf_client.prv = sdev; + scsc_log_collector_register_client(&slsi_hcf_client); + sdev->collect_mib.enabled = true; +#endif + slsi_update_supported_channels_regd_flags(sdev); + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "---Driver started successfully---\n"); + sdev->device_state = SLSI_DEVICE_STATE_STARTED; + memset(sdev->rtt_vif, -1, sizeof(sdev->rtt_vif)); + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + + slsi_kic_system_event(slsi_kic_system_event_category_initialisation, + slsi_kic_system_events_wifi_service_driver_started, GFP_KERNEL); + + slsi_wakeunlock(&sdev->wlan_wl); + return err; + +err_hip_started: +#ifndef SLSI_TEST_DEV + slsi_sm_wlan_service_stop(sdev); + slsi_hip_stop(sdev); + slsi_sm_wlan_service_close(sdev); +#endif + +err_done: + sdev->device_state = SLSI_DEVICE_STATE_STOPPED; + +done: + slsi_wakeunlock(&sdev->wlan_wl); + + slsi_kic_system_event(slsi_kic_system_event_category_initialisation, + slsi_kic_system_events_wifi_on, GFP_KERNEL); + + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); + return err; +} + +struct net_device *slsi_dynamic_interface_create(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif = NULL; + int err = -EINVAL; + int iface; + + SLSI_DBG1(sdev, SLSI_CFG80211, "name:%s\n", name); + + iface = slsi_netif_dynamic_iface_add(sdev, name); + if (iface < 0) + return NULL; + + dev = slsi_get_netdev(sdev, iface); + if (!dev) + return NULL; + + ndev_vif = netdev_priv(dev); + + err = slsi_netif_register_rtlnl_locked(sdev, dev); + if (err) + return NULL; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + ndev_vif->iftype = type; + dev->ieee80211_ptr->iftype = type; + if (params) + dev->ieee80211_ptr->use_4addr = params->use_4addr; + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + return dev; +} + +static void slsi_stop_chip(struct slsi_dev *sdev) +{ +#ifdef CONFIG_SCSC_LOG_COLLECTION + u8 index = sdev->collect_mib.num_files; + u8 i; +#endif + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->start_stop_mutex)); + + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "netdev_up_count:%d device_state:%d\n", sdev->netdev_up_count, sdev->device_state); + + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) + return; + + /* Only shutdown on the last device going down. */ + if (sdev->netdev_up_count) + return; + + complete_all(&sdev->sig_wait.completion); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + sdev->collect_mib.enabled = false; + scsc_log_collector_unregister_client(&slsi_hcf_client); + for (i = 0; i < index; i++) + kfree(sdev->collect_mib.file[i].data); +#endif + + slsi_reset_channel_flags(sdev); + slsi_regd_init(sdev); + sdev->device_state = SLSI_DEVICE_STATE_STOPPING; + + slsi_sm_wlan_service_stop(sdev); + sdev->device_state = SLSI_DEVICE_STATE_STOPPED; + + slsi_hip_stop(sdev); +#ifndef SLSI_TEST_DEV + slsi_sm_wlan_service_close(sdev); +#endif + slsi_kic_system_event(slsi_kic_system_event_category_deinitialisation, + slsi_kic_system_events_wifi_service_driver_stopped, GFP_KERNEL); + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + sdev->mlme_blocked = false; + + slsi_kic_system_event(slsi_kic_system_event_category_deinitialisation, + slsi_kic_system_events_wifi_off, GFP_KERNEL); + + slsi_dbg_track_skb_report(); + slsi_dbg_track_skb_reset(); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); +} + +void slsi_vif_cleanup(struct slsi_dev *sdev, struct net_device *dev, bool hw_available) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int i; + + SLSI_NET_DBG3(dev, SLSI_INIT_DEINIT, "clean VIF\n"); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (ndev_vif->activated) { + netif_carrier_off(dev); + for (i = 0; i < SLSI_ADHOC_PEER_CONNECTIONS_MAX; i++) { + struct slsi_peer *peer = ndev_vif->peer_sta_record[i]; + + if (peer && peer->valid) + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + } + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + bool already_disconnected = false; + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Station active: hw_available=%d\n", hw_available); + if (hw_available) { + if (ndev_vif->sta.sta_bss) { + slsi_mlme_disconnect(sdev, dev, ndev_vif->sta.sta_bss->bssid, FAPI_REASONCODE_UNSPECIFIED_REASON, true); + slsi_handle_disconnect(sdev, dev, ndev_vif->sta.sta_bss->bssid, 0); + already_disconnected = true; + } else { + slsi_mlme_del_vif(sdev, dev); + } + } + if (!already_disconnected) { + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Calling slsi_vif_deactivated\n"); + slsi_vif_deactivated(sdev, dev); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) + cfg80211_disconnected(dev, FAPI_REASONCODE_UNSPECIFIED_REASON, NULL, 0, false, GFP_ATOMIC); +#else + cfg80211_disconnected(dev, FAPI_REASONCODE_UNSPECIFIED_REASON, NULL, 0, GFP_ATOMIC); +#endif + } + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "AP active\n"); + if (hw_available) { + struct slsi_peer *peer; + int j = 0; + + while (j < SLSI_PEER_INDEX_MAX) { + peer = ndev_vif->peer_sta_record[j]; + if (peer && peer->valid) + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + ++j; + } + slsi_mlme_del_vif(sdev, dev); + } + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Calling slsi_vif_deactivated\n"); + slsi_vif_deactivated(sdev, dev); + + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_NO_VIF); + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_UNSYNCHRONISED) { + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + slsi_wlan_unsync_vif_deactivate(sdev, dev, hw_available); + } else { + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "P2P active - Deactivate\n"); + slsi_p2p_vif_deactivate(sdev, dev, hw_available); + } + } +#ifdef CONFIG_SCSC_WLAN_DEBUG + else if (ndev_vif->vif_type == FAPI_VIFTYPE_MONITOR) + slsi_stop_monitor_mode(sdev, dev); +#endif + } +} + +void slsi_scan_cleanup(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int i; + struct cfg80211_scan_info info = {.aborted = false}; + + SLSI_NET_DBG3(dev, SLSI_INIT_DEINIT, "clean scan_data\n"); + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + for (i = 0; i < SLSI_SCAN_MAX; i++) { + if ((ndev_vif->scan[i].scan_req || ndev_vif->scan[i].acs_request) && + !sdev->mlme_blocked) + slsi_mlme_del_scan(sdev, dev, (ndev_vif->ifnum << 8 | i), false); + slsi_purge_scan_results(ndev_vif, i); + if (ndev_vif->scan[i].scan_req && i == SLSI_SCAN_HW_ID) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + cfg80211_scan_done(ndev_vif->scan[i].scan_req, &info); +#else + cfg80211_scan_done(ndev_vif->scan[i].scan_req, false); +#endif + + if (ndev_vif->scan[i].sched_req && i == SLSI_SCAN_SCHED_ID) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) + cfg80211_sched_scan_stopped(sdev->wiphy, ndev_vif->scan[i].sched_req->reqid); +#else + cfg80211_sched_scan_stopped(sdev->wiphy); +#endif + + ndev_vif->scan[i].scan_req = NULL; + kfree(ndev_vif->scan[i].acs_request); + ndev_vif->scan[i].acs_request = NULL; + ndev_vif->scan[i].sched_req = NULL; + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); +} + +static void slsi_stop_net_dev_locked(struct slsi_dev *sdev, struct net_device *dev, bool hw_available) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "Stopping netdev_up_count=%d, hw_available = %d\n", sdev->netdev_up_count, hw_available); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->start_stop_mutex)); + + if (!ndev_vif->is_available) { + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "Not Available\n"); + return; + } + + if (WARN_ON(!sdev->netdev_up_count)) { + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "sdev->netdev_up_count=%d\n", sdev->netdev_up_count); + return; + } + + complete_all(&ndev_vif->sig_wait.completion); + + slsi_scan_cleanup(sdev, dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + slsi_vif_cleanup(sdev, dev, hw_available); + ndev_vif->is_available = false; + sdev->netdev_up_count--; + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + slsi_stop_chip(sdev); +} + +/* Called when a net device wants to go DOWN */ +void slsi_stop_net_dev(struct slsi_dev *sdev, struct net_device *dev) +{ + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + slsi_stop_net_dev_locked(sdev, dev, sdev->recovery_status ? false : true); + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); +} + +/* Called when we get sdio_removed */ +void slsi_stop(struct slsi_dev *sdev) +{ + struct net_device *dev; + int i; + + SLSI_MUTEX_LOCK(sdev->start_stop_mutex); + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "netdev_up_count:%d\n", sdev->netdev_up_count); + + complete_all(&sdev->sig_wait.completion); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + dev = slsi_get_netdev_locked(sdev, i); + if (dev) + slsi_stop_net_dev_locked(sdev, sdev->netdev[i], false); + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + SLSI_MUTEX_UNLOCK(sdev->start_stop_mutex); +} + +/* MIB download handling */ +static u8 *slsi_mib_slice(struct slsi_dev *sdev, const u8 *data, u32 length, u32 *p_parsed_len, + u32 *p_mib_slice_len) +{ + const u8 *p = data; + u8 *mib_slice; + u32 mib_slice_len = 0; + + SLSI_UNUSED_PARAMETER(sdev); + + if (!length) + return NULL; + + mib_slice = kmalloc(length + 4, GFP_KERNEL); + if (!mib_slice) + return NULL; + + while (length >= 4) { + u16 psid = SLSI_BUFF_LE_TO_U16(p); + u16 pslen = (u16)(4 + SLSI_BUFF_LE_TO_U16(&p[2])); + + if (pslen & 0x1) + pslen++; + + if (psid & CSR_WIFI_SME_MIB2_HOST_PSID_MASK) { + /* do nothing */ + } else { + /* SLSI_ERR (sdev, "PSID=0x%04X : FW\n", psid); */ +#define CSR_WIFI_HOSTIO_MIB_SET_MAX (1800) + if ((mib_slice_len + pslen) > CSR_WIFI_HOSTIO_MIB_SET_MAX) + break; + if (pslen > length + 4) { + SLSI_ERR(sdev, "length %u read from MIB file > space %u - corrupt file?\n", pslen, length + 4); + mib_slice_len = 0; + break; + } + memcpy(&mib_slice[mib_slice_len], p, pslen); + mib_slice_len += pslen; + } + p += pslen; + length -= pslen; + } + + *p_mib_slice_len = mib_slice_len; + *p_parsed_len = (p - data); + + return mib_slice; +} + +/* Extract the platform name string from the HCF file */ +static int slsi_mib_get_platform(struct slsi_dev_mib_info *mib_info) +{ + size_t plat_name_len; + int pos = 0; + + /* The mib_data passed to this function should already + * have had its HCF header skipped. + * + * This is shoehorned into specific PSIDs to allow backward + * compatibility, so we must look into the HCF payload + * instead of the header :( + * + * The updated configcmd util guarantees that these keys + * will appear first: + * + * PSIDs: + * 0xfffe - 16 bit version ID, value 1. + * 0xffff - If version ID=1, holds platform name string. + */ + + mib_info->platform[0] = '\0'; + + /* Sanity - payload long enough for info? */ + if (mib_info->mib_len < 12) { + SLSI_INFO_NODEV("HCF file too short\n"); + return -EINVAL; /* file too short */ + } + + if (mib_info->mib_data[pos++] != 0xFE || /* Version ID FFFE */ + mib_info->mib_data[pos++] != 0xFF) { + SLSI_INFO_NODEV("No HCF version ID\n"); + return -EINVAL; /* No version ID */ + } + if (mib_info->mib_data[pos++] != 0x01 || /* Len 1, LE */ + mib_info->mib_data[pos++] != 0x00) { + SLSI_INFO_NODEV("Bad length\n"); + return -EINVAL; /* Unknown length */ + } + if (mib_info->mib_data[pos++] != 0x01 || /* Header ID 1, LE */ + mib_info->mib_data[pos++] != 0x00) { + SLSI_INFO_NODEV("Bad version ID\n"); + return -EINVAL; /* Unknown version ID */ + } + if (mib_info->mib_data[pos++] != 0xFF || /* Platform Name FFFF */ + mib_info->mib_data[pos++] != 0xFF) { + SLSI_INFO_NODEV("No HCF platform name\n"); + return -EINVAL; /* No platform name */ + } + + /* Length of platform name */ + plat_name_len = mib_info->mib_data[pos++]; + plat_name_len |= (mib_info->mib_data[pos++] << 16); + + /* Sanity check */ + if (plat_name_len + pos > mib_info->mib_len || plat_name_len < 2) { + SLSI_ERR_NODEV("Bad HCF FFFF key length %zu\n", + plat_name_len); + return -EINVAL; /* Implausible length */ + } + + /* Skip vldata header SC-506179-SP. This conveys the + * length of the platform string and is 2 or 3 octets long + * depending on the length of the string. + */ + { +#define SLSI_VLDATA_STRING 0xA0 +#define SLSI_VLDATA_LEN 0x17 + + u8 vlen_hdr = mib_info->mib_data[pos++]; + u8 vlen_len = vlen_hdr & SLSI_VLDATA_LEN; /* size of length field */ + + /* Skip vlen header octet */ + plat_name_len--; + + SLSI_DBG1_NODEV(SLSI_INIT_DEINIT, "vlhdr 0x%x, len %u\n", vlen_hdr, vlen_len); + + /* Is it an octet string type? */ + if (!(vlen_hdr & SLSI_VLDATA_STRING)) { + SLSI_ERR_NODEV("No string vlen header 0x%x\n", vlen_hdr); + return -EINVAL; + } + + /* Handle 1 or 2 octet length field only */ + if (vlen_len > 2) { + SLSI_ERR_NODEV("Too long octet string header %u\n", vlen_len); + return -EINVAL; + } + + /* Skip over the string length field. + * Note we just use datalength anyway. + */ + pos += vlen_len; + plat_name_len -= vlen_len; + } + + /* Limit the platform name to space in driver and read */ + { + size_t trunc_len = plat_name_len; + + if (trunc_len >= sizeof(mib_info->platform)) + trunc_len = sizeof(mib_info->platform) - 1; + + /* Extract platform name */ + memcpy(mib_info->platform, &mib_info->mib_data[pos], trunc_len); + mib_info->platform[trunc_len] = '\0'; + + /* Print non-truncated string in log now */ + SLSI_INFO_NODEV("MIB platform: %.*s\n", (int)plat_name_len, &mib_info->mib_data[pos]); + + SLSI_DBG1_NODEV(SLSI_INIT_DEINIT, "plat_name_len: %zu + %u\n", + plat_name_len, (plat_name_len & 1)); + } + + /* Pad string to 16-bit boundary */ + plat_name_len += (plat_name_len & 1); + pos += plat_name_len; + + /* Advance over the keys we read, FW doesn't need them */ + mib_info->mib_data += pos; + mib_info->mib_len -= pos; + + SLSI_DBG1_NODEV(SLSI_INIT_DEINIT, "Skip %d octets HCF payload\n", pos); + + return 0; +} + +#define MGT_HASH_SIZE_BYTES 2 /* Hash will be contained in a uint32 */ +#define MGT_HASH_OFFSET 4 +static int slsi_mib_open_file(struct slsi_dev *sdev, struct slsi_dev_mib_info *mib_info, const struct firmware **fw) +{ + int r = -1; + const struct firmware *e = NULL; + const char *mib_file_ext; + char path_name[MX_WLAN_FILE_PATH_LEN_MAX]; + char *mib_file_name = mib_info->mib_file_name; +#ifdef CONFIG_SCSC_LOG_COLLECTION + u8 index = sdev->collect_mib.num_files; + u8 *data; +#endif + + if (!mib_file_name || !fw) + return -EINVAL; + + mib_info->mib_data = NULL; + mib_info->mib_len = 0; + mib_info->mib_hash = 0; /* Reset mib hash value */ + + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "MIB file - Name : %s\n", mib_file_name); + + /* Use MIB file compatibility mode? */ + mib_file_ext = strrchr(mib_file_name, '.'); + if (!mib_file_ext) { + SLSI_ERR(sdev, "configuration file name '%s' invalid\n", mib_file_name); + return -EINVAL; + } + + /* Build MIB file path from override */ + scnprintf(path_name, MX_WLAN_FILE_PATH_LEN_MAX, "wlan/%s", mib_file_name); + SLSI_INFO(sdev, "Path to the MIB file : %s\n", path_name); + + r = mx140_file_request_conf(sdev->maxwell_core, &e, "wlan", mib_file_name); + if (r || (!e)) { + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "Skip MIB download as file %s is NOT found\n", mib_file_name); + *fw = e; + return r; + } + + mib_info->mib_data = (u8 *)e->data; + mib_info->mib_len = e->size; + +#ifdef CONFIG_SCSC_LOG_COLLECTION + spin_lock(&sdev->collect_mib.in_collection); + memset(&sdev->collect_mib.file[index].file_name, 0, 32); + memcpy(&sdev->collect_mib.file[index].file_name, mib_file_name, 32); + sdev->collect_mib.file[index].len = mib_info->mib_len; + data = kmalloc(mib_info->mib_len, GFP_ATOMIC); + if (!data) { + spin_unlock(&sdev->collect_mib.in_collection); + goto cont; + } + memcpy(data, mib_info->mib_data, mib_info->mib_len); + sdev->collect_mib.file[index].data = data; + sdev->collect_mib.num_files += 1; + spin_unlock(&sdev->collect_mib.in_collection); +cont: +#endif + /* Check MIB file header */ + if (mib_info->mib_len >= 8 && /* Room for header */ + /*(sdev->mib_data[6] & 0xF0) == 0x20 && */ /* WLAN subsystem */ + mib_info->mib_data[7] == 1) { /* First file format */ + int i; + + mib_info->mib_hash = 0; + + for (i = 0; i < MGT_HASH_SIZE_BYTES; i++) + mib_info->mib_hash = (mib_info->mib_hash << 8) | mib_info->mib_data[i + MGT_HASH_OFFSET]; + + SLSI_INFO(sdev, "MIB hash: 0x%.04x\n", mib_info->mib_hash); + /* All good - skip header and continue */ + mib_info->mib_data += 8; + mib_info->mib_len -= 8; + + /* Extract platform name if available */ + slsi_mib_get_platform(mib_info); + } else { + /* Bad header */ + SLSI_ERR(sdev, "configuration file '%s' has bad header\n", mib_info->mib_file_name); + mx140_file_release_conf(sdev->maxwell_core, e); + return -EINVAL; + } + + *fw = e; + return 0; +} + +static int slsi_mib_close_file(struct slsi_dev *sdev, const struct firmware *e) +{ + SLSI_DBG2(sdev, SLSI_INIT_DEINIT, "MIB close %p\n", e); + + if (!e || !sdev) + return -EIO; + + mx140_file_release_conf(sdev->maxwell_core, e); + + return 0; +} + +static int slsi_mib_download_file(struct slsi_dev *sdev, struct slsi_dev_mib_info *mib_info) +{ + int r = -1; + const struct firmware *e = NULL; + u8 *mib_slice; + u32 mib_slice_len, parsed_len; + + r = slsi_mib_open_file(sdev, mib_info, &e); + if (r) + return r; + /** + * MIB data should not be larger than CSR_WIFI_HOSTIO_MIB_SET_MAX. + * Slice it into smaller ones and download one by one + */ + while (mib_info->mib_len > 0) { + mib_slice = slsi_mib_slice(sdev, mib_info->mib_data, mib_info->mib_len, &parsed_len, &mib_slice_len); + if (!mib_slice) + break; + if (mib_slice_len == 0 || mib_slice_len > mib_info->mib_len) { + /* Sanity check MIB parsing */ + SLSI_ERR(sdev, "slsi_mib_slice returned implausible %d\n", mib_slice_len); + r = -EINVAL; + kfree(mib_slice); + break; + } + r = slsi_mlme_set(sdev, NULL, mib_slice, mib_slice_len); + kfree(mib_slice); + if (r != 0) /* some mib can fail to be set, but continue */ + SLSI_ERR(sdev, "mlme set failed r=0x%x during downloading:'%s'\n", + r, mib_info->mib_file_name); + + mib_info->mib_data += parsed_len; + mib_info->mib_len -= parsed_len; + } + + slsi_mib_close_file(sdev, e); + + return r; +} + +static int slsi_mib_initial_get(struct slsi_dev *sdev) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + int *band = sdev->supported_5g_channels; + int rx_len = 0; + int r; + int i = 0; + int j = 0; + int chan_start = 0; + int chan_count = 0; + int index = 0; + int mib_index = 0; + static const struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_CHIP_VERSION, { 0, 0 } }, + { SLSI_PSID_UNIFI_SUPPORTED_CHANNELS, { 0, 0 } }, + { SLSI_PSID_UNIFI_HT_ACTIVATED, {0, 0} }, + { SLSI_PSID_UNIFI_VHT_ACTIVATED, {0, 0} }, + { SLSI_PSID_UNIFI_HT_CAPABILITIES, {0, 0} }, + { SLSI_PSID_UNIFI_VHT_CAPABILITIES, {0, 0} }, + { SLSI_PSID_UNIFI_HARDWARE_PLATFORM, {0, 0} }, + { SLSI_PSID_UNIFI_REG_DOM_VERSION, {0, 0} }, + { SLSI_PSID_UNIFI_NAN_ENABLED, {0, 0} }, + { SLSI_PSID_UNIFI_DEFAULT_DWELL_TIME, {0, 0} }, +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + { SLSI_PSID_UNIFI_WI_FI_SHARING5_GHZ_CHANNEL, {0, 0} }, +#endif +#ifdef CONFIG_SCSC_WLAN_AP_INFO_FILE + { SLSI_PSID_UNIFI_DUAL_BAND_CONCURRENCY, {0, 0} }, + { SLSI_PSID_UNIFI_MAX_CLIENT, {0, 0} }, +#endif +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + { SLSI_PSID_UNIFI_MAC_ADDRESS_RANDOMISATION, {0, 0} }, +#endif + { SLSI_PSID_UNIFI_DEFAULT_COUNTRY_WITHOUT_CH12_CH13, {0, 0} }, +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + { SLSI_PSID_UNIFI_ARP_DETECT_ACTIVATED, {0, 0} }, +#endif + { SLSI_PSID_UNIFI_APF_ENABLED, {0, 0} } + };/*Check the mibrsp.dataLength when a new mib is added*/ + + /* 40 MHz bandwidth is not supported in 2.4 GHz in AP/GO Mode Currently. + * Reading the mib to check the support is removed and is initialized to 0. + */ + sdev->fw_2g_40mhz_enabled = 0; + r = slsi_mib_encode_get_list(&mibreq, sizeof(get_values) / sizeof(struct slsi_mib_get_entry), get_values); + if (r != SLSI_MIB_STATUS_SUCCESS) + return -ENOMEM; + + mibrsp.dataLength = 184; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, mibrsp.data, mibrsp.dataLength, &rx_len); + kfree(mibreq.data); + if (r == 0) { + struct slsi_mib_value *values; + + mibrsp.dataLength = (u32)rx_len; + + values = slsi_mib_decode_get_list(&mibrsp, sizeof(get_values) / sizeof(struct slsi_mib_get_entry), get_values); + + if (!values) { + kfree(mibrsp.data); + return -EINVAL; + } + + if (values[mib_index].type != SLSI_MIB_TYPE_NONE) { /* CHIP_VERSION */ + SLSI_CHECK_TYPE(sdev, values[mib_index].type, SLSI_MIB_TYPE_UINT); + sdev->chip_info_mib.chip_version = values[mib_index].u.uintValue; + } + + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) { /* SUPPORTED_CHANNELS */ + int k = 0; + int increment = 4; /* increment channel by 4 for 5G and by 1 for 2G */ + int buf_len = 150; /* 150 bytes for 14+25=39 channels and spaces between them */ + char *supported_channels_buffer = kmalloc(buf_len, GFP_KERNEL); + int buf_pos = 0; + SLSI_CHECK_TYPE(sdev, values[mib_index].type, SLSI_MIB_TYPE_OCTET); + if (values[mib_index].type == SLSI_MIB_TYPE_OCTET) { + sdev->band_5g_supported = 0; + memset(sdev->supported_2g_channels, 0, sizeof(sdev->supported_2g_channels)); + memset(sdev->supported_5g_channels, 0, sizeof(sdev->supported_5g_channels)); + for (i = 0; i < values[mib_index].u.octetValue.dataLength / 2; i++) { + /* If any 5GHz channel is supported, update band_5g_supported */ + if ((values[mib_index].u.octetValue.data[i * 2] > 14) && + (values[mib_index].u.octetValue.data[i * 2 + 1] > 0)) { + sdev->band_5g_supported = 1; + break; + } + } + for (i = 0; i < values[mib_index].u.octetValue.dataLength; i += 2) { + increment = 4; + k = 0; + chan_start = values[mib_index].u.octetValue.data[i]; + chan_count = values[mib_index].u.octetValue.data[i + 1]; + band = sdev->supported_5g_channels; + if (chan_start < 15) { + increment = 1; + index = chan_start - 1; + band = sdev->supported_2g_channels; + } else if (chan_start >= 36 && chan_start <= 48) { + index = (chan_start - 36) / 4; + } else if (chan_start >= 52 && chan_start <= 64) { + index = ((chan_start - 52) / 4) + 4; + } else if (chan_start >= 100 && chan_start <= 140) { + index = ((chan_start - 100) / 4) + 8; + } else if (chan_start >= 149 && chan_start <= 165) { + index = ((chan_start - 149) / 4) + 20; + } else { + continue; + } + + for (j = 0; j < chan_count; j++) { + band[index + j] = 1; + buf_pos += snprintf(supported_channels_buffer + buf_pos, + buf_len - buf_pos, "%d ", (chan_start + k)); + k = k + increment; + } + sdev->enabled_channel_count += chan_count; + } + } + SLSI_DBG1(sdev, SLSI_CFG80211, "Value for Supported Channels mib: %s\n", + supported_channels_buffer); + } + + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) /* HT enabled? */ + sdev->fw_ht_enabled = values[mib_index].u.boolValue; + else + SLSI_WARN(sdev, "Error reading HT enabled mib\n"); + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) /* VHT enabled? */ + sdev->fw_vht_enabled = values[mib_index].u.boolValue; + else + SLSI_WARN(sdev, "Error reading VHT enabled mib\n"); + if (values[++mib_index].type == SLSI_MIB_TYPE_OCTET) { /* HT capabilities */ + if (values[mib_index].u.octetValue.dataLength >= 4) + memcpy(&sdev->fw_ht_cap, values[mib_index].u.octetValue.data, 4); + else + SLSI_WARN(sdev, "Error reading HT capabilities\n"); + } else { + SLSI_WARN(sdev, "Error reading HT capabilities\n"); + } + if (values[++mib_index].type == SLSI_MIB_TYPE_OCTET) { /* VHT capabilities */ + if (values[mib_index].u.octetValue.dataLength >= 4) + memcpy(&sdev->fw_vht_cap, values[mib_index].u.octetValue.data, 4); + else + SLSI_WARN(sdev, "Error reading VHT capabilities\n"); + } else { + SLSI_WARN(sdev, "Error reading VHT capabilities\n"); + } + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) { /* HARDWARE_PLATFORM */ + SLSI_CHECK_TYPE(sdev, values[mib_index].type, SLSI_MIB_TYPE_UINT); + sdev->plat_info_mib.plat_build = values[mib_index].u.uintValue; + } else { + SLSI_WARN(sdev, "Error reading Hardware platform\n"); + } + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) { /* REG_DOM_VERSION */ + SLSI_CHECK_TYPE(sdev, values[mib_index].type, SLSI_MIB_TYPE_UINT); + sdev->reg_dom_version = values[mib_index].u.uintValue; + } else { + SLSI_WARN(sdev, "Error reading Reg domain version\n"); + } + + /* NAN enabled? */ + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) { + sdev->nan_enabled = values[mib_index].u.boolValue; + } else { + sdev->nan_enabled = false; + SLSI_WARN(sdev, "Error reading NAN enabled mib\n"); + } + SLSI_DBG1(sdev, SLSI_CFG80211, "Value for NAN enabled mib : %d\n", sdev->nan_enabled); + + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) { /* UnifiForcedScheduleDuration */ + SLSI_CHECK_TYPE(sdev, values[mib_index].type, SLSI_MIB_TYPE_UINT); + sdev->fw_dwell_time = values[mib_index].u.uintValue; + } else { + SLSI_WARN(sdev, "Error reading UnifiForcedScheduleDuration\n"); + } + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (values[++mib_index].type == SLSI_MIB_TYPE_OCTET) { /* 5Ghz Allowed Channels */ + if (values[mib_index].u.octetValue.dataLength >= 8) { + memcpy(&sdev->wifi_sharing_5ghz_channel, values[mib_index].u.octetValue.data, 8); + slsi_extract_valid_wifi_sharing_channels(sdev); + } else { + SLSI_WARN(sdev, "Error reading 5Ghz Allowed Channels\n"); + } + } else { + SLSI_WARN(sdev, "Error reading 5Ghz Allowed Channels\n"); + } +#endif + +#ifdef CONFIG_SCSC_WLAN_AP_INFO_FILE + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) /* Dual band concurrency */ + sdev->dualband_concurrency = values[mib_index].u.boolValue; + else + SLSI_WARN(sdev, "Error reading dual band concurrency\n"); + if (values[++mib_index].type == SLSI_MIB_TYPE_UINT) /* max client for soft AP */ + sdev->softap_max_client = values[mib_index].u.uintValue; + else + SLSI_WARN(sdev, "Error reading SoftAP max client\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) /* Mac Randomization enable? */ + sdev->fw_mac_randomization_enabled = values[mib_index].u.boolValue; + else + SLSI_WARN(sdev, "Error reading Mac Randomization Support\n"); +#endif + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) { /* Disable ch12/ch13 */ + sdev->device_config.disable_ch12_ch13 = values[mib_index].u.boolValue; + SLSI_DBG1(sdev, SLSI_CFG80211, "Value for default country without ch12/13 mib: %d\n", + sdev->device_config.disable_ch12_ch13); + } else { + SLSI_WARN(sdev, "Error reading default country without ch12/13 mib\n"); + } +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) /* Enhanced Arp Detect Support */ + sdev->device_config.fw_enhanced_arp_detect_supported = values[mib_index].u.boolValue; + else + SLSI_WARN(sdev, "Error reading Enhanced Arp Detect Support mib\n"); +#endif + if (values[++mib_index].type != SLSI_MIB_TYPE_NONE) /* APF Support */ + sdev->device_config.fw_apf_supported = values[mib_index].u.boolValue; + else + SLSI_WARN(sdev, "Error reading APF Support mib\n"); + + kfree(values); + } + kfree(mibrsp.data); + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + slsi_check_num_radios(sdev); +#endif + return r; +} + +int slsi_set_mib_roam(struct slsi_dev *dev, struct net_device *ndev, u16 psid, int value) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + int error = SLSI_MIB_STATUS_FAILURE; + + if (slsi_mib_encode_int(&mib_data, psid, value, 0) == SLSI_MIB_STATUS_SUCCESS) + if (mib_data.dataLength) { + error = slsi_mlme_set(dev, ndev, mib_data.data, mib_data.dataLength); + if (error) + SLSI_ERR(dev, "Err Setting MIB failed. error = %d\n", error); + kfree(mib_data.data); + } + + return error; +} + +#ifdef CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA +int slsi_set_mib_preferred_antenna(struct slsi_dev *dev, u16 value) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + int error = SLSI_MIB_STATUS_FAILURE; + + if (slsi_mib_encode_uint(&mib_data, SLSI_PSID_UNIFI_PREFERRED_ANTENNA_BITMAP, + value, 0) == SLSI_MIB_STATUS_SUCCESS) + if (mib_data.dataLength) { + error = slsi_mlme_set(dev, NULL, mib_data.data, mib_data.dataLength); + if (error) + SLSI_ERR(dev, "Err Setting MIB failed. error = %d\n", error); + kfree(mib_data.data); + } + + return error; +} +#endif + +void slsi_reset_throughput_stats(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct slsi_mib_data mib_data = { 0, NULL }; + int error = SLSI_MIB_STATUS_FAILURE; + + if (slsi_mib_encode_int(&mib_data, SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, 0, 0) == SLSI_MIB_STATUS_SUCCESS) + if (mib_data.dataLength) { + error = slsi_mlme_set(sdev, dev, mib_data.data, mib_data.dataLength); + if (error) + SLSI_ERR(sdev, "Err Setting MIB failed. error = %d\n", error); + kfree(mib_data.data); + } +} + +int slsi_get_mib_roam(struct slsi_dev *sdev, u16 psid, int *mib_value) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + int rx_len = 0; + int r; + struct slsi_mib_get_entry get_values[] = { { psid, { 0, 0 } } }; + + r = slsi_mib_encode_get_list(&mibreq, sizeof(get_values) / sizeof(struct slsi_mib_get_entry), get_values); + if (r != SLSI_MIB_STATUS_SUCCESS) + return -ENOMEM; + + mibrsp.dataLength = 64; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, mibrsp.data, mibrsp.dataLength, &rx_len); + kfree(mibreq.data); + + if (r == 0) { + struct slsi_mib_value *values; + + mibrsp.dataLength = (u32)rx_len; + + values = slsi_mib_decode_get_list(&mibrsp, sizeof(get_values) / sizeof(struct slsi_mib_get_entry), get_values); + + if (!values) { + kfree(mibrsp.data); + return -EINVAL; + } + + WARN_ON(values[0].type == SLSI_MIB_TYPE_OCTET || + values[0].type == SLSI_MIB_TYPE_NONE); + + if (values[0].type == SLSI_MIB_TYPE_INT) + *mib_value = (int)(values->u.intValue); + else if (values[0].type == SLSI_MIB_TYPE_UINT) + *mib_value = (int)(values->u.uintValue); + else if (values[0].type == SLSI_MIB_TYPE_BOOL) + *mib_value = (int)(values->u.boolValue); + + SLSI_DBG2(sdev, SLSI_MLME, "MIB value = %d\n", *mib_value); + kfree(values); + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + } + + kfree(mibrsp.data); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE +int slsi_mib_get_gscan_cap(struct slsi_dev *sdev, struct slsi_nl_gscan_capabilities *cap) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + int rx_len = 0; + int r; + static const struct slsi_mib_get_entry get_values[] = { { SLSI_PSID_UNIFI_GOOGLE_MAX_NUMBER_OF_PERIODIC_SCANS, { 0, 0 } }, + { SLSI_PSID_UNIFI_GOOGLE_MAX_RSSI_SAMPLE_SIZE, { 0, 0 } }, + { SLSI_PSID_UNIFI_GOOGLE_MAX_HOTLIST_APS, { 0, 0 } }, + { SLSI_PSID_UNIFI_GOOGLE_MAX_SIGNIFICANT_WIFI_CHANGE_APS, { 0, 0 } }, + { SLSI_PSID_UNIFI_GOOGLE_MAX_BSSID_HISTORY_ENTRIES, { 0, 0 } },}; + + r = slsi_mib_encode_get_list(&mibreq, sizeof(get_values) / sizeof(struct slsi_mib_get_entry), get_values); + if (r != SLSI_MIB_STATUS_SUCCESS) + return -ENOMEM; + + mibrsp.dataLength = 64; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, mibrsp.data, mibrsp.dataLength, &rx_len); + kfree(mibreq.data); + + if (r == 0) { + struct slsi_mib_value *values; + + mibrsp.dataLength = (u32)rx_len; + + values = slsi_mib_decode_get_list(&mibrsp, sizeof(get_values) / sizeof(struct slsi_mib_get_entry), get_values); + + if (!values) { + kfree(mibrsp.data); + return -EINVAL; + } + + if (values[0].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[0].type, SLSI_MIB_TYPE_UINT); + cap->max_scan_buckets = values[0].u.uintValue; + } + + if (values[1].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[1].type, SLSI_MIB_TYPE_UINT); + cap->max_rssi_sample_size = values[1].u.uintValue; + } + + if (values[2].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[2].type, SLSI_MIB_TYPE_UINT); + cap->max_hotlist_aps = values[2].u.uintValue; + } + + if (values[3].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[3].type, SLSI_MIB_TYPE_UINT); + cap->max_significant_wifi_change_aps = values[3].u.uintValue; + } + + if (values[4].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[4].type, SLSI_MIB_TYPE_UINT); + cap->max_bssid_history_entries = values[4].u.uintValue; + } + + kfree(values); + } + kfree(mibrsp.data); + return r; +} +#endif + +int slsi_mib_get_apf_cap(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + int data_length = 0; + int r = 0; + static const struct slsi_mib_get_entry get_values[] = { + { SLSI_PSID_UNIFI_APF_VERSION, { 0, 0 } }, /* to get the supported APF version*/ + { SLSI_PSID_UNIFI_APF_MAX_SIZE, { 0, 0 } } /* to get APF_MAX_SIZE*/ + }; + + r = slsi_mib_encode_get_list(&mibreq, (sizeof(get_values) / sizeof(struct slsi_mib_get_entry)), + get_values); + if (r != SLSI_MIB_STATUS_SUCCESS) + return -ENOMEM; + + /* 15*2 bytes for 2 Mib's */ + mibrsp.dataLength = 30; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_NET_DBG1(dev, SLSI_MLME, "failed to allocate memory\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, dev, mibreq.data, mibreq.dataLength, mibrsp.data, + mibrsp.dataLength, &data_length); + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = (u32)data_length; + values = slsi_mib_decode_get_list(&mibrsp, + (sizeof(get_values) / sizeof(struct slsi_mib_get_entry)), get_values); + if (!values) { + SLSI_NET_DBG1(dev, SLSI_MLME, "mib decode list failed\n"); + kfree(mibrsp.data); + return -ENOMEM; + } + + if (values[0].type == SLSI_MIB_TYPE_UINT) + sdev->device_config.apf_cap.version = values[0].u.uintValue; /* supported APF version */ + else + SLSI_ERR(sdev, "invalid type. index:%d\n", 0); + if (values[1].type == SLSI_MIB_TYPE_UINT) + sdev->device_config.apf_cap.max_length = values[1].u.uintValue; /* APF_MAX_LENGTH */ + else + SLSI_ERR(sdev, "invalid type. index:%d\n", 1); + } else { + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_get_req failed(result:0x%4x)\n", r); + } + + kfree(mibrsp.data); + kfree(values); + return r; +} + +int slsi_mib_get_rtt_cap(struct slsi_dev *sdev, struct net_device *dev, struct slsi_rtt_capabilities *cap) +{ + struct slsi_mib_data supported_rtt_capab = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + + struct slsi_mib_get_entry get_values[] = { { SLSI_PSID_UNIFI_RTT_CAPABILITIES, { 0, 0 } } }; + + mibrsp.dataLength = 64; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + kfree(mibrsp.data); + return -ENOMEM; + } + + values = slsi_read_mibs(sdev, dev, get_values, 1, &mibrsp); + if (!values) { + kfree(mibrsp.data); + return -EINVAL; + } + + if (values[0].type != SLSI_MIB_TYPE_OCTET) { + SLSI_ERR(sdev, "Invalid type (%d) for SLSI_PSID_UNIFI_RTT_CAPABILITIES", values[0].type); + kfree(mibrsp.data); + kfree(values); + return -EINVAL; + } + supported_rtt_capab = values[0].u.octetValue; + cap->rtt_one_sided_supported = supported_rtt_capab.data[0]; + cap->rtt_ftm_supported = supported_rtt_capab.data[1]; + cap->lci_support = supported_rtt_capab.data[2]; + cap->lcr_support = supported_rtt_capab.data[3]; + cap->responder_supported = supported_rtt_capab.data[4]; + cap->preamble_support = supported_rtt_capab.data[5]; + cap->bw_support = supported_rtt_capab.data[6]; + cap->mc_version = supported_rtt_capab.data[7]; + + kfree(values); + kfree(mibrsp.data); + return 0; +} + +struct slsi_peer *slsi_peer_add(struct slsi_dev *sdev, struct net_device *dev, u8 *peer_address, u16 aid) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + u16 queueset = 0; + + if (WARN_ON(!aid)) { + SLSI_NET_ERR(dev, "Invalid aid(0) received\n"); + return NULL; + } + queueset = MAP_AID_TO_QS(aid); + + /* MUST only be called from the control path that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + if (WARN_ON(!ndev_vif->activated)) + return NULL; + + if (!peer_address) { + SLSI_NET_WARN(dev, "Peer without address\n"); + return NULL; + } + + peer = slsi_get_peer_from_mac(sdev, dev, peer_address); + if (peer) { + if (ndev_vif->sta.tdls_enabled && (peer->queueset == 0)) { + SLSI_NET_DBG3(dev, SLSI_CFG80211, "TDLS enabled and its station queueset\n"); + } else { + SLSI_NET_WARN(dev, "Peer (MAC:%pM) already exists\n", peer_address); + return NULL; + } + } + + if (slsi_get_peer_from_qs(sdev, dev, queueset)) { + SLSI_NET_WARN(dev, "Peer (queueset:%d) already exists\n", queueset); + return NULL; + } + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "%pM, aid:%d\n", peer_address, aid); + + peer = ndev_vif->peer_sta_record[queueset]; + if (!peer) { + /* If it reaches here, something has gone wrong */ + SLSI_NET_ERR(dev, "Peer (queueset:%d) is NULL\n", queueset); + return NULL; + } + + peer->aid = aid; + peer->queueset = queueset; + SLSI_ETHER_COPY(peer->address, peer_address); + peer->assoc_ie = NULL; + peer->assoc_resp_ie = NULL; + peer->is_wps = false; + peer->connected_state = SLSI_STA_CONN_STATE_DISCONNECTED; + /* Initialise the Station info */ + slsi_peer_reset_stats(sdev, dev, peer); + ratelimit_state_init(&peer->sinfo_mib_get_rs, SLSI_SINFO_MIB_ACCESS_TIMEOUT, 0); + + if (scsc_wifi_fcq_ctrl_q_init(&peer->ctrl_q) < 0) { + SLSI_NET_ERR(dev, "scsc_wifi_fcq_ctrl_q_init failed\n"); + return NULL; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (scsc_wifi_fcq_unicast_qset_init(dev, &peer->data_qs, peer->queueset, sdev, ndev_vif->ifnum, peer) < 0) { +#else + if (scsc_wifi_fcq_unicast_qset_init(dev, &peer->data_qs, peer->queueset, sdev, ndev_vif->ifnum, peer) < 0) { +#endif + SLSI_NET_ERR(dev, "scsc_wifi_fcq_unicast_qset_init failed\n"); + scsc_wifi_fcq_ctrl_q_deinit(&peer->ctrl_q); + return NULL; + } + + /* A peer is only valid once all the data is initialised + * otherwise a process could check the flag and start to read + * uninitialised data. + */ + + if (ndev_vif->sta.tdls_enabled) + ndev_vif->sta.tdls_peer_sta_records++; + else + ndev_vif->peer_sta_records++; + + ndev_vif->cfg80211_sinfo_generation++; + skb_queue_head_init(&peer->buffered_frames); + + /* For TDLS this flag will be set while moving the packets from STAQ to TDLSQ */ + /* TODO: changes for moving packets is removed for now. Enable this when these data path changes go in*/ +/* if (!ndev_vif->sta.tdls_enabled) + * peer->valid = true; + */ + peer->valid = true; + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "created station peer %pM AID:%d\n", peer->address, aid); + return peer; +} + +void slsi_peer_reset_stats(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_UNUSED_PARAMETER(sdev); + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "Peer:%pM\n", peer->address); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + memset(&peer->sinfo, 0x00, sizeof(peer->sinfo)); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + peer->sinfo.filled = BIT(NL80211_STA_INFO_RX_BYTES) | + BIT(NL80211_STA_INFO_TX_BYTES) | + BIT(NL80211_STA_INFO_RX_PACKETS) | + BIT(NL80211_STA_INFO_TX_PACKETS) | + BIT(NL80211_STA_INFO_RX_DROP_MISC) | + BIT(NL80211_STA_INFO_TX_FAILED) | + BIT(NL80211_STA_INFO_SIGNAL) | + BIT(NL80211_STA_INFO_BSS_PARAM); +#else + peer->sinfo.filled = STATION_INFO_RX_BYTES | + STATION_INFO_TX_BYTES | + STATION_INFO_RX_PACKETS | + STATION_INFO_TX_PACKETS | + STATION_INFO_RX_DROP_MISC | + STATION_INFO_TX_FAILED | + STATION_INFO_SIGNAL | + STATION_INFO_BSS_PARAM; +#endif +} + +void slsi_dump_stats(struct net_device *dev) +{ + SLSI_UNUSED_PARAMETER(dev); + + SLSI_INFO_NODEV("slsi_hanged_event_count: %d\n", slsi_hanged_event_count); +} + +enum slsi_wlan_vendor_attr_hanged_event { + SLSI_WLAN_VENDOR_ATTR_HANGED_EVENT_PANIC_CODE = 1, + SLSI_WLAN_VENDOR_ATTR_HANGED_EVENT_MAX +}; + +int slsi_send_hanged_vendor_event(struct slsi_dev *sdev, u16 scsc_panic_code) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + struct sk_buff *skb; + int length = sizeof(scsc_panic_code); + + slsi_hanged_event_count++; + SLSI_INFO(sdev, "Sending SLSI_NL80211_VENDOR_HANGED_EVENT , count: %d, reason =0x%2x\n", slsi_hanged_event_count, scsc_panic_code); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, length, SLSI_NL80211_VENDOR_HANGED_EVENT, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, length, SLSI_NL80211_VENDOR_HANGED_EVENT, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate SKB for vendor hanged event"); + return -ENOMEM; + } + + if (nla_put(skb, SLSI_WLAN_VENDOR_ATTR_HANGED_EVENT_PANIC_CODE, length, &scsc_panic_code)) { + SLSI_ERR_NODEV("Failed nla_put for panic code\n"); + slsi_kfree_skb(skb); + return -EINVAL; + } + cfg80211_vendor_event(skb, GFP_KERNEL); + +#endif + return 0; +} + +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +int slsi_send_forward_beacon_vendor_event(struct slsi_dev *sdev, const u8 *ssid, const int ssid_len, const u8 *bssid, + u8 channel, const u16 beacon_int, const u64 timestamp, const u64 sys_time) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + struct sk_buff *skb; + u8 err = 0; + struct net_device *dev; + struct netdev_vif *ndev_vif; + + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(dev); + + SLSI_DBG2(sdev, SLSI_CFG80211, "Sending SLSI_NL80211_VENDOR_FORWARD_BEACON\n"); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, &ndev_vif->wdev, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_VENDOR_FORWARD_BEACON, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_VENDOR_FORWARD_BEACON, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate SKB for vendor forward_beacon event\n"); + return -ENOMEM; + } + + err |= nla_put(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_SSID, ssid_len, ssid); + err |= nla_put(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_BSSID, ETH_ALEN, bssid); + err |= nla_put_u8(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_CHANNEL, channel); + err |= nla_put_u16(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_BCN_INTERVAL, beacon_int); + err |= nla_put_u32(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_TIME_STAMP1, (timestamp & 0x00000000FFFFFFFF)); + err |= nla_put_u32(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_TIME_STAMP2, + ((timestamp >> 32) & 0x00000000FFFFFFFF)); + err |= nla_put_u64_64bit(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_SYS_TIME, sys_time, 0); + + if (err) { + SLSI_ERR_NODEV("Failed nla_put for forward_beacon\n"); + slsi_kfree_skb(skb); + return -EINVAL; + } + cfg80211_vendor_event(skb, GFP_KERNEL); + +#endif + return 0; +} + +int slsi_send_forward_beacon_abort_vendor_event(struct slsi_dev *sdev, u16 reason_code) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + struct sk_buff *skb; + u8 err = 0; + struct net_device *dev; + struct netdev_vif *ndev_vif; + + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(dev); + + SLSI_INFO(sdev, "Sending SLSI_NL80211_VENDOR_FORWARD_BEACON_ABORT\n"); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, &ndev_vif->wdev, sizeof(reason_code), + SLSI_NL80211_VENDOR_FORWARD_BEACON_ABORT, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, sizeof(reason_code), + SLSI_NL80211_VENDOR_FORWARD_BEACON_ABORT, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate SKB for vendor forward_beacon_abort event\n"); + return -ENOMEM; + } + + err = nla_put_u16(skb, SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_ABORT, reason_code); + + if (err) { + SLSI_ERR_NODEV("Failed nla_put for beacon_recv_abort\n"); + slsi_kfree_skb(skb); + return -EINVAL; + } + cfg80211_vendor_event(skb, GFP_KERNEL); + +#endif + return 0; +} +#endif + +#ifdef CONFIG_SCSC_WLAN_HANG_TEST +int slsi_test_send_hanged_vendor_event(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_INFO(ndev_vif->sdev, "Test FORCE HANG\n"); + return slsi_send_hanged_vendor_event(ndev_vif->sdev, SCSC_PANIC_CODE_HOST << 15); +} +#endif + +static bool slsi_search_ies_for_qos_indicators(struct slsi_dev *sdev, u8 *ies, int ies_len) +{ + SLSI_UNUSED_PARAMETER(sdev); + + if (cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, ies_len)) { + SLSI_DBG1(sdev, SLSI_CFG80211, "QOS enabled due to WLAN_EID_HT_CAPABILITY\n"); + return true; + } + if (cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, ies, ies_len)) { + SLSI_DBG1(sdev, SLSI_CFG80211, "QOS enabled due to WLAN_EID_VHT_CAPABILITY\n"); + return true; + } + if (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WMM, ies, ies_len)) { + SLSI_DBG1(sdev, SLSI_CFG80211, "QOS enabled due to WLAN_OUI_TYPE_MICROSOFT_WMM\n"); + return true; + } + return false; +} + +void slsi_peer_update_assoc_req(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 id = fapi_get_u16(skb, id); + + /* MUST only be called from the control path that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + switch (id) { + case MLME_CONNECTED_IND: + case MLME_PROCEDURE_STARTED_IND: + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_AP && + ndev_vif->vif_type != FAPI_VIFTYPE_STATION)) { + slsi_kfree_skb(skb); + return; + } + break; + default: + slsi_kfree_skb(skb); + WARN_ON(1); + return; + } + + slsi_kfree_skb(peer->assoc_ie); + peer->assoc_ie = NULL; + peer->capabilities = 0; + + if (fapi_get_datalen(skb)) { + int mgmt_hdr_len; + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + struct netdev_vif *ndev_vif = netdev_priv(dev); + + /* Update the skb to just point to the frame */ + skb_pull(skb, fapi_get_siglen(skb)); + + if (ieee80211_is_assoc_req(mgmt->frame_control)) { + mgmt_hdr_len = (mgmt->u.assoc_req.variable - (u8 *)mgmt); + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) + peer->capabilities = le16_to_cpu(mgmt->u.assoc_req.capab_info); + } else if (ieee80211_is_reassoc_req(mgmt->frame_control)) { + mgmt_hdr_len = (mgmt->u.reassoc_req.variable - (u8 *)mgmt); + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) + peer->capabilities = le16_to_cpu(mgmt->u.reassoc_req.capab_info); + } else { + WARN_ON(1); + slsi_kfree_skb(skb); + return; + } + + skb_pull(skb, mgmt_hdr_len); + + peer->assoc_ie = skb; + peer->sinfo.assoc_req_ies = skb->data; + peer->sinfo.assoc_req_ies_len = skb->len; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) + peer->sinfo.filled |= STATION_INFO_ASSOC_REQ_IES; +#endif + peer->qos_enabled = slsi_search_ies_for_qos_indicators(sdev, skb->data, skb->len); + } +} + +void slsi_peer_update_assoc_rsp(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 id = fapi_get_u16(skb, id); + + SLSI_UNUSED_PARAMETER(sdev); + + /* MUST only be called from the control path that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) + goto exit_with_warnon; + + if (id != MLME_CONNECT_IND && id != MLME_ROAMED_IND && id != MLME_REASSOCIATE_IND) { + SLSI_NET_ERR(dev, "Unexpected id =0x%4x\n", id); + goto exit_with_warnon; + } + + slsi_kfree_skb(peer->assoc_resp_ie); + peer->assoc_resp_ie = NULL; + peer->capabilities = 0; + if (fapi_get_datalen(skb)) { + int mgmt_hdr_len; + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + + /* Update the skb to just point to the frame */ + skb_pull(skb, fapi_get_siglen(skb)); + + if (ieee80211_is_assoc_resp(mgmt->frame_control)) { + mgmt_hdr_len = (mgmt->u.assoc_resp.variable - (u8 *)mgmt); + peer->capabilities = le16_to_cpu(mgmt->u.assoc_resp.capab_info); + } else if (ieee80211_is_reassoc_resp(mgmt->frame_control)) { + mgmt_hdr_len = (mgmt->u.reassoc_resp.variable - (u8 *)mgmt); + peer->capabilities = le16_to_cpu(mgmt->u.reassoc_resp.capab_info); + } else { + goto exit_with_warnon; + } + + skb_pull(skb, mgmt_hdr_len); + peer->assoc_resp_ie = skb; + } + return; + +exit_with_warnon: + WARN_ON(1); + slsi_kfree_skb(skb); +} + +int slsi_peer_remove(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *buff_frame; + + SLSI_UNUSED_PARAMETER(sdev); + + /* MUST only be called from the control path that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (!peer) { + SLSI_NET_WARN(dev, "peer=NULL"); + return -EINVAL; + } + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "%pM\n", peer->address); + + buff_frame = slsi_skb_dequeue(&peer->buffered_frames); + while (buff_frame) { + SLSI_NET_DBG3(dev, SLSI_MLME, "FLUSHING BUFFERED FRAMES\n"); + slsi_kfree_skb(buff_frame); + buff_frame = slsi_skb_dequeue(&peer->buffered_frames); + } + + slsi_rx_ba_stop_all(dev, peer); + + /* Take the peer lock to protect the transmit data path + * when accessing peer records. + */ + slsi_spinlock_lock(&ndev_vif->peer_lock); + + /* The information is no longer valid so first update the flag to ensure that + * another process doesn't try to use it any more. + */ + peer->valid = false; + peer->is_wps = false; + peer->connected_state = SLSI_STA_CONN_STATE_DISCONNECTED; + + if (slsi_is_tdls_peer(dev, peer)) + ndev_vif->sta.tdls_peer_sta_records--; + else + ndev_vif->peer_sta_records--; + + slsi_spinlock_unlock(&ndev_vif->peer_lock); + + ndev_vif->cfg80211_sinfo_generation++; + + scsc_wifi_fcq_qset_deinit(dev, &peer->data_qs, sdev, ndev_vif->ifnum, peer); + scsc_wifi_fcq_ctrl_q_deinit(&peer->ctrl_q); + + slsi_kfree_skb(peer->assoc_ie); + slsi_kfree_skb(peer->assoc_resp_ie); + memset(peer, 0x00, sizeof(*peer)); + + return 0; +} + +int slsi_vif_activated(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_UNUSED_PARAMETER(sdev); + + /* MUST only be called from the control path that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + /* MUST have cleared any peer records previously */ + WARN_ON(ndev_vif->peer_sta_records); + + if (WARN_ON(ndev_vif->activated)) + return -EALREADY; + + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) + /* Enable the Multicast queue set for AP mode */ + if (scsc_wifi_fcq_multicast_qset_init(dev, &ndev_vif->ap.group_data_qs, sdev, ndev_vif->ifnum) < 0) + return -EFAULT; + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + /* MUST have cleared any tdls peer records previously */ + WARN_ON(ndev_vif->sta.tdls_peer_sta_records); + + ndev_vif->sta.tdls_peer_sta_records = 0; + ndev_vif->sta.tdls_enabled = false; + ndev_vif->sta.roam_in_progress = false; + ndev_vif->sta.nd_offload_enabled = true; + + memset(ndev_vif->sta.keepalive_host_tag, 0, sizeof(ndev_vif->sta.keepalive_host_tag)); + } + + ndev_vif->cfg80211_sinfo_generation = 0; + ndev_vif->peer_sta_records = 0; + ndev_vif->activated = true; + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + return 0; +} + +void slsi_vif_deactivated(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int i; + + /* MUST only be called from the control path that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + /* The station type VIF is deactivated when the AP connection is lost */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + ndev_vif->sta.group_key_set = false; + ndev_vif->sta.vif_status = SLSI_VIF_STATUS_UNSPECIFIED; + memset(ndev_vif->sta.keepalive_host_tag, 0, sizeof(ndev_vif->sta.keepalive_host_tag)); + + /* delete the TSPEC entries (if any) if it is a STA vif */ + if (ndev_vif->iftype == NL80211_IFTYPE_STATION) + cac_delete_tspec_list(sdev); + + if (ndev_vif->sta.tdls_enabled) + WARN(ndev_vif->sta.tdls_peer_sta_records, "vif:%d, tdls_peer_sta_records:%d", ndev_vif->ifnum, ndev_vif->sta.tdls_peer_sta_records); + + if (ndev_vif->sta.sta_bss) { + slsi_cfg80211_put_bss(sdev->wiphy, ndev_vif->sta.sta_bss); + ndev_vif->sta.sta_bss = NULL; + } + ndev_vif->sta.tdls_enabled = false; +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + ndev_vif->is_wips_running = false; +#endif + } + + /* MUST be done first to ensure that other code doesn't treat the VIF as still active */ + ndev_vif->activated = false; + slsi_skb_queue_purge(&ndev_vif->rx_data.queue); + + for (i = 0; i < (SLSI_ADHOC_PEER_CONNECTIONS_MAX); i++) { + struct slsi_peer *peer = ndev_vif->peer_sta_record[i]; + + if (peer && peer->valid) { + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && peer->assoc_ie) + cfg80211_del_sta(dev, peer->address, GFP_KERNEL); + slsi_peer_remove(sdev, dev, peer); + } + } + + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + memset(&ndev_vif->ap.last_disconnected_sta, 0, sizeof(ndev_vif->ap.last_disconnected_sta)); + scsc_wifi_fcq_qset_deinit(dev, &ndev_vif->ap.group_data_qs, sdev, ndev_vif->ifnum, NULL); + } + + if ((ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) || (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO)) { + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_NO_VIF); + sdev->p2p_group_exp_frame = SLSI_P2P_PA_INVALID; + } + + /* MUST be done last as lots of code is dependent on checking the vif_type */ + ndev_vif->vif_type = SLSI_VIFTYPE_UNSPECIFIED; + ndev_vif->set_power_mode = FAPI_POWERMANAGEMENTMODE_POWER_SAVE; + if (slsi_is_rf_test_mode_enabled()) { + SLSI_NET_ERR(dev, "*#rf# rf test mode set is enabled.\n"); + ndev_vif->set_power_mode = FAPI_POWERMANAGEMENTMODE_ACTIVE_MODE; + } else { + ndev_vif->set_power_mode = FAPI_POWERMANAGEMENTMODE_POWER_SAVE; + } + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + + /* SHOULD have cleared any peer records */ + WARN(ndev_vif->peer_sta_records, "vif:%d, peer_sta_records:%d", ndev_vif->ifnum, ndev_vif->peer_sta_records); + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + if (ndev_vif->sta.tdls_enabled) + WARN(ndev_vif->sta.tdls_peer_sta_records, "vif:%d, tdls_peer_sta_records:%d", + ndev_vif->ifnum, ndev_vif->sta.tdls_peer_sta_records); + + if (ndev_vif->sta.sta_bss) { + slsi_cfg80211_put_bss(sdev->wiphy, ndev_vif->sta.sta_bss); + ndev_vif->sta.sta_bss = NULL; + } + ndev_vif->sta.tdls_enabled = false; + } + + sdev->device_config.qos_info = -1; +} + +static int slsi_sta_ieee80211_mode(struct net_device *dev, u16 current_bss_channel_frequency) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + const u8 *ie; + + ie = cfg80211_find_ie(WLAN_EID_VHT_OPERATION, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + if (ie) + return SLSI_80211_MODE_11AC; + + ie = cfg80211_find_ie(WLAN_EID_HT_OPERATION, ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len); + if (ie) + return SLSI_80211_MODE_11N; + + if (current_bss_channel_frequency > 5000) + return SLSI_80211_MODE_11A; + + ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len); + if (ie) + return slsi_get_supported_mode(ie); + return -EINVAL; +} + +static int slsi_get_sta_mode(struct net_device *dev, const u8 *last_peer_mac) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct slsi_peer *last_peer; + const u8 *peer_ie; + + last_peer = slsi_get_peer_from_mac(sdev, dev, last_peer_mac); + + if (!last_peer) { + SLSI_NET_ERR(dev, "Peer not found\n"); + return -EINVAL; + } + + ndev_vif->ap.last_disconnected_sta.support_mode = 0; + if (cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, last_peer->assoc_ie->data, + last_peer->assoc_ie->len)) + ndev_vif->ap.last_disconnected_sta.support_mode = 3; + else if (cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, last_peer->assoc_ie->data, + last_peer->assoc_ie->len)) + ndev_vif->ap.last_disconnected_sta.support_mode = 1; + + if (ndev_vif->ap.mode == SLSI_80211_MODE_11AC) { /*AP supports VHT*/ + peer_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, last_peer->assoc_ie->data, + last_peer->assoc_ie->len); + if (peer_ie) + return SLSI_80211_MODE_11AC; + + peer_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, last_peer->assoc_ie->data, + last_peer->assoc_ie->len); + if (peer_ie) + return SLSI_80211_MODE_11N; + return SLSI_80211_MODE_11A; + } + if (ndev_vif->ap.mode == SLSI_80211_MODE_11N) { /*AP supports HT*/ + peer_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, last_peer->assoc_ie->data, + last_peer->assoc_ie->len); + if (peer_ie) + return SLSI_80211_MODE_11N; + if (ndev_vif->ap.channel_freq > 5000) + return SLSI_80211_MODE_11A; + peer_ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, last_peer->assoc_ie->data, + last_peer->assoc_ie->len); + if (peer_ie) + return slsi_get_supported_mode(peer_ie); + } + + if (ndev_vif->ap.channel_freq > 5000) + return SLSI_80211_MODE_11A; + + if (ndev_vif->ap.mode == SLSI_80211_MODE_11G) { /*AP supports 11g mode */ + peer_ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, last_peer->assoc_ie->data, + last_peer->assoc_ie->len); + if (peer_ie) + return slsi_get_supported_mode(peer_ie); + } + + return SLSI_80211_MODE_11B; +} + +int slsi_populate_bss_record(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + const u8 *ie, *ext_capab, *rm_capab, *ext_data, *rm_data, *bss_load; + u8 ext_capab_ie_len, rm_capab_ie_len; + bool neighbor_report_bit = 0, btm = 0; + u16 fw_tx_rate; + struct slsi_mib_get_entry get_values[] = { { SLSI_PSID_UNIFI_CURRENT_BSS_CHANNEL_FREQUENCY, { 0, 0 } }, + { SLSI_PSID_UNIFI_CURRENT_BSS_BANDWIDTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_CURRENT_BSS_NSS, {0, 0} }, + { SLSI_PSID_UNIFI_AP_MIMO_USED, {0, 0} }, + { SLSI_PSID_UNIFI_LAST_BSS_SNR, {0, 0} }, + { SLSI_PSID_UNIFI_LAST_BSS_RSSI, { 0, 0 } }, + { SLSI_PSID_UNIFI_ROAMING_COUNT, {0, 0} }, + { SLSI_PSID_UNIFI_LAST_BSS_TX_DATA_RATE, { 0, 0 } }, + { SLSI_PSID_UNIFI_ROAMING_AKM, {0, 0} } }; + + mibrsp.dataLength = 10 * ARRAY_SIZE(get_values); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes for interface MIBs\n", mibrsp.dataLength); + return -ENOMEM; + } + + values = slsi_read_mibs(sdev, dev, get_values, ARRAY_SIZE(get_values), &mibrsp); + + memset(&ndev_vif->sta.last_connected_bss, 0, sizeof(ndev_vif->sta.last_connected_bss)); + + if (!values) { + SLSI_NET_DBG1(dev, SLSI_MLME, "mib decode list failed\n"); + kfree(values); + kfree(mibrsp.data); + return -EINVAL; + } + + /* The Below sequence of reading the BSS Info related Mibs is very important */ + if (values[0].type != SLSI_MIB_TYPE_NONE) { /* CURRENT_BSS_CHANNEL_FREQUENCY */ + SLSI_CHECK_TYPE(sdev, values[0].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.channel_freq = ((values[0].u.uintValue) / 2); + } + + if (values[1].type != SLSI_MIB_TYPE_NONE) { /* CURRENT_BSS_BANDWIDTH */ + SLSI_CHECK_TYPE(sdev, values[1].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.bandwidth = values[1].u.uintValue; + } + + if (values[2].type != SLSI_MIB_TYPE_NONE) { /* CURRENT_BSS_NSS */ + SLSI_CHECK_TYPE(sdev, values[2].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.antenna_mode = values[2].u.uintValue; + } + + if (values[3].type != SLSI_MIB_TYPE_NONE) { /* AP_MIMO_USED */ + SLSI_CHECK_TYPE(sdev, values[3].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.mimo_used = values[3].u.uintValue; + } + + if (values[4].type != SLSI_MIB_TYPE_NONE) { /* SNR */ + SLSI_CHECK_TYPE(sdev, values[4].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.snr = values[4].u.uintValue; + } + + if (values[5].type != SLSI_MIB_TYPE_NONE) { /* RSSI */ + SLSI_CHECK_TYPE(sdev, values[5].type, SLSI_MIB_TYPE_INT); + ndev_vif->sta.last_connected_bss.rssi = values[5].u.intValue; + } + + if (values[6].type != SLSI_MIB_TYPE_NONE) { /* ROAMING_COUNT */ + SLSI_CHECK_TYPE(sdev, values[6].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.roaming_count = values[6].u.uintValue; + } + + if (values[7].type != SLSI_MIB_TYPE_NONE) { /* TX_DATA_RATE */ + SLSI_CHECK_TYPE(sdev, values[7].type, SLSI_MIB_TYPE_UINT); + fw_tx_rate = values[7].u.uintValue; + slsi_fw_tx_rate_calc(fw_tx_rate, NULL, + (unsigned long *)(&ndev_vif->sta.last_connected_bss.tx_data_rate)); + } + + if (values[8].type != SLSI_MIB_TYPE_NONE) { /* ROAMING_AKM */ + SLSI_CHECK_TYPE(sdev, values[8].type, SLSI_MIB_TYPE_UINT); + ndev_vif->sta.last_connected_bss.roaming_akm = values[8].u.uintValue; + } + + kfree(values); + kfree(mibrsp.data); + + if (!ndev_vif->sta.sta_bss) { + SLSI_WARN(sdev, "Bss missing due to out of order msg from firmware!! Cannot collect Big Data\n"); + return -EINVAL; + } + + SLSI_ETHER_COPY(ndev_vif->sta.last_connected_bss.address, ndev_vif->sta.sta_bss->bssid); + + ndev_vif->sta.last_connected_bss.mode = slsi_sta_ieee80211_mode(dev, + ndev_vif->sta.last_connected_bss.channel_freq); + if (ndev_vif->sta.last_connected_bss.mode == -EINVAL) { + SLSI_ERR(sdev, "slsi_get_bss_info : Supported Rates IE is null"); + return -EINVAL; + } + + ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, SLSI_WLAN_OUI_TYPE_WFA_HS20_IND, + ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len); + if (ie) { + if ((ie[6] >> 4) == 0) + ndev_vif->sta.last_connected_bss.passpoint_version = 1; + else + ndev_vif->sta.last_connected_bss.passpoint_version = 2; + } + + ndev_vif->sta.last_connected_bss.noise_level = (ndev_vif->sta.last_connected_bss.rssi - + ndev_vif->sta.last_connected_bss.snr); + + ext_capab = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + rm_capab = cfg80211_find_ie(WLAN_EID_RRM_ENABLED_CAPABILITIES, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + bss_load = cfg80211_find_ie(WLAN_EID_QBSS_LOAD, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + + if (ext_capab) { + ext_capab_ie_len = ext_capab[1]; + ext_data = &ext_capab[2]; + if ((ext_capab_ie_len >= 2) && (ext_data[1] & + SLSI_WLAN_EXT_CAPA1_PROXY_ARP_ENABLED)) /*check bit12 is set or not */ + ndev_vif->sta.last_connected_bss.kvie |= 1 << 1; + if (ext_capab_ie_len >= 3) { + if (ext_data[2] & SLSI_WLAN_EXT_CAPA2_TFS_ENABLED) /*check bit16 is set or not */ + ndev_vif->sta.last_connected_bss.kvie |= 1 << 2; + if (ext_data[2] & SLSI_WLAN_EXT_CAPA2_WNM_SLEEP_ENABLED) /*check bit17 is set or not */ + ndev_vif->sta.last_connected_bss.kvie |= 1 << 3; + if (ext_data[2] & SLSI_WLAN_EXT_CAPA2_TIM_ENABLED) /*check bit18 is set or not */ + ndev_vif->sta.last_connected_bss.kvie |= 1 << 4; + /*check bit19 is set or not */ + if (ext_data[2] & SLSI_WLAN_EXT_CAPA2_BSS_TRANSISITION_ENABLED) { + ndev_vif->sta.last_connected_bss.kvie |= 1 << 5; + btm = 1; + } + if (ext_data[2] & SLSI_WLAN_EXT_CAPA2_DMS_ENABLED) /*check bit20 is set or not */ + ndev_vif->sta.last_connected_bss.kvie |= 1 << 6; + } + } + if (bss_load) + ndev_vif->sta.last_connected_bss.kvie |= 1; + if (rm_capab) { + rm_capab_ie_len = rm_capab[1]; + rm_data = &rm_capab[2]; + if (rm_capab_ie_len >= 1) { + neighbor_report_bit = SLSI_WLAN_RM_CAPA0_NEIGHBOR_REPORT_ENABLED & rm_data[0]; + if (SLSI_WLAN_RM_CAPA0_LINK_MEASUREMENT_ENABLED & rm_data[0]) + ndev_vif->sta.last_connected_bss.kvie |= 1 << 7; + if (neighbor_report_bit) + ndev_vif->sta.last_connected_bss.kvie |= 1 << 8; + if (SLSI_WLAN_RM_CAPA0_PASSIVE_MODE_ENABLED & rm_data[0]) + ndev_vif->sta.last_connected_bss.kvie |= 1 << 9; + if (SLSI_WLAN_RM_CAPA0_ACTIVE_MODE_ENABLED & rm_data[0]) + ndev_vif->sta.last_connected_bss.kvie |= 1 << 10; + if (SLSI_WLAN_RM_CAPA0_TABLE_MODE_ENABLED & rm_data[0]) + ndev_vif->sta.last_connected_bss.kvie |= 1 << 11; + } + } + if (!neighbor_report_bit && !btm && !bss_load) + ndev_vif->sta.last_connected_bss.kv = 0; + else if (neighbor_report_bit != 0 && (!btm && !bss_load)) + ndev_vif->sta.last_connected_bss.kv = 1; /*11k support */ + else if (!neighbor_report_bit && (btm || bss_load)) + ndev_vif->sta.last_connected_bss.kv = 2; /*11v support */ + else + ndev_vif->sta.last_connected_bss.kv = 3; /*11kv support */ + + return 0; +} + +static int slsi_fill_last_disconnected_sta_info(struct slsi_dev *sdev, struct net_device *dev, + const u8 *last_peer_mac, const u16 reason_code) +{ + int i; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *last_peer; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + u16 fw_tx_rate; + struct slsi_mib_get_entry get_values[] = { { SLSI_PSID_UNIFI_PEER_BANDWIDTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_CURRENT_PEER_NSS, {0, 0} }, + { SLSI_PSID_UNIFI_PEER_RSSI, { 0, 0 } }, + { SLSI_PSID_UNIFI_PEER_TX_DATA_RATE, { 0, 0 } } }; + + SLSI_ETHER_COPY(ndev_vif->ap.last_disconnected_sta.address, + last_peer_mac); + ndev_vif->ap.last_disconnected_sta.reason = reason_code; + ndev_vif->ap.last_disconnected_sta.mode = slsi_get_sta_mode(dev, last_peer_mac); + last_peer = slsi_get_peer_from_mac(sdev, dev, last_peer_mac); + if (!last_peer) { + SLSI_NET_ERR(dev, "Peer not found\n"); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(get_values); i++) + get_values[i].index[0] = last_peer->aid; + + ndev_vif->ap.last_disconnected_sta.rx_retry_packets = SLSI_DEFAULT_UNIFI_PEER_RX_RETRY_PACKETS; + ndev_vif->ap.last_disconnected_sta.rx_bc_mc_packets = SLSI_DEFAULT_UNIFI_PEER_RX_BC_MC_PACKETS; + ndev_vif->ap.last_disconnected_sta.capabilities = last_peer->capabilities; + ndev_vif->ap.last_disconnected_sta.bandwidth = SLSI_DEFAULT_UNIFI_PEER_BANDWIDTH; + ndev_vif->ap.last_disconnected_sta.antenna_mode = SLSI_DEFAULT_UNIFI_PEER_NSS; + ndev_vif->ap.last_disconnected_sta.rssi = SLSI_DEFAULT_UNIFI_PEER_RSSI; + ndev_vif->ap.last_disconnected_sta.tx_data_rate = SLSI_DEFAULT_UNIFI_PEER_TX_DATA_RATE; + + mibrsp.dataLength = 15 * ARRAY_SIZE(get_values); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes for interface MIBs\n", mibrsp.dataLength); + return -ENOMEM; + } + + values = slsi_read_mibs(sdev, dev, get_values, ARRAY_SIZE(get_values), &mibrsp); + + if (!values) { + SLSI_NET_DBG1(dev, SLSI_MLME, "mib decode list failed\n"); + kfree(values); + kfree(mibrsp.data); + return -EINVAL; + } + if (values[0].type != SLSI_MIB_TYPE_NONE) { /* LAST_PEER_BANDWIDTH */ + SLSI_CHECK_TYPE(sdev, values[0].type, SLSI_MIB_TYPE_INT); + ndev_vif->ap.last_disconnected_sta.bandwidth = values[0].u.intValue; + } + + if (values[1].type != SLSI_MIB_TYPE_NONE) { /*LAST_PEER_NSS*/ + SLSI_CHECK_TYPE(sdev, values[1].type, SLSI_MIB_TYPE_INT); + ndev_vif->ap.last_disconnected_sta.antenna_mode = values[1].u.intValue; + } + + if (values[2].type != SLSI_MIB_TYPE_NONE) { /* LAST_PEER_RSSI*/ + SLSI_CHECK_TYPE(sdev, values[2].type, SLSI_MIB_TYPE_INT); + ndev_vif->ap.last_disconnected_sta.rssi = values[2].u.intValue; + } + + if (values[3].type != SLSI_MIB_TYPE_NONE) { /* LAST_PEER_TX_DATA_RATE */ + SLSI_CHECK_TYPE(sdev, values[3].type, SLSI_MIB_TYPE_UINT); + fw_tx_rate = values[3].u.uintValue; + slsi_fw_tx_rate_calc(fw_tx_rate, NULL, + (unsigned long *)&ndev_vif->ap.last_disconnected_sta.tx_data_rate); + } + + kfree(values); + kfree(mibrsp.data); + + return 0; +} + +int slsi_handle_disconnect(struct slsi_dev *sdev, struct net_device *dev, u8 *peer_address, u16 reason) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + if (WARN_ON(!dev)) + goto exit; + + SLSI_NET_DBG3(dev, SLSI_MLME, "slsi_handle_disconnect(vif:%d)\n", ndev_vif->ifnum); + + /* MUST only be called from somewhere that has acquired the lock */ + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit; + } + + switch (ndev_vif->vif_type) { + case FAPI_VIFTYPE_STATION: + { + netif_carrier_off(dev); + + /* MLME-DISCONNECT-IND could indicate the completion of a MLME-DISCONNECT-REQ or + * the connection with the AP has been lost + */ + if (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTING) { + if (!peer_address) + SLSI_NET_WARN(dev, "Connection failure\n"); + } else if (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED) { + if (reason == FAPI_REASONCODE_SYNCHRONISATION_LOSS) + reason = 0; /*reason code to recognise beacon loss */ + else if (reason == FAPI_REASONCODE_KEEP_ALIVE_FAILURE) + reason = WLAN_REASON_DEAUTH_LEAVING;/* Change to a standard reason code */ + else if (reason >= 0x8200 && reason <= 0x82FF) + reason = reason & 0x00FF; + + if (ndev_vif->sta.is_wps) /* Ignore sending deauth or disassoc event to cfg80211 during WPS session */ + SLSI_NET_INFO(dev, "Ignoring Deauth notification to cfg80211 from the peer during WPS procedure\n"); + else { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) + cfg80211_disconnected(dev, reason, NULL, 0, false, GFP_KERNEL); +#else + cfg80211_disconnected(dev, reason, NULL, 0, GFP_KERNEL); +#endif + SLSI_NET_DBG3(dev, SLSI_MLME, "Received disconnect from AP, reason = %d\n", reason); + } + } else if (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_DISCONNECTING) { + /* Change keep alive and sync_loss reason code while sending to supplicant to a standard reason code */ + if (reason == FAPI_REASONCODE_KEEP_ALIVE_FAILURE || + reason == FAPI_REASONCODE_SYNCHRONISATION_LOSS) + reason = WLAN_REASON_DEAUTH_LEAVING; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) + cfg80211_disconnected(dev, reason, NULL, 0, true, GFP_KERNEL); +#else + cfg80211_disconnected(dev, reason, NULL, 0, GFP_KERNEL); +#endif + SLSI_NET_DBG3(dev, SLSI_MLME, "Completion of disconnect from AP\n"); + } else { + /* Vif status is in erronus state.*/ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) + cfg80211_disconnected(dev, reason, NULL, 0, false, GFP_KERNEL); +#else + cfg80211_disconnected(dev, reason, NULL, 0, GFP_KERNEL); +#endif + SLSI_NET_WARN(dev, "disconnect in wrong state vif_status(%d)\n", ndev_vif->sta.vif_status); + } + + ndev_vif->sta.is_wps = false; + + /* Populate bss records on incase of disconnection. + * For connection failure its not required. + */ + if (!(ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTING || + ndev_vif->sta.vif_status == SLSI_VIF_STATUS_UNSPECIFIED)) + slsi_populate_bss_record(dev); + + kfree(ndev_vif->sta.assoc_req_add_info_elem); + if (ndev_vif->sta.assoc_req_add_info_elem) { + ndev_vif->sta.assoc_req_add_info_elem = NULL; + ndev_vif->sta.assoc_req_add_info_elem_len = 0; + } +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + memset(&ndev_vif->enhanced_arp_stats, 0, sizeof(ndev_vif->enhanced_arp_stats)); +#endif + slsi_mlme_del_vif(sdev, dev); + slsi_vif_deactivated(sdev, dev); + break; + } + case FAPI_VIFTYPE_AP: + { + struct slsi_peer *peer = NULL; + + peer = slsi_get_peer_from_mac(sdev, dev, peer_address); + if (!peer) { + SLSI_NET_DBG1(dev, SLSI_MLME, "peer NOT found by MAC address\n"); + goto exit; + } + + SLSI_NET_DBG3(dev, SLSI_MLME, "MAC:%pM\n", peer_address); + slsi_fill_last_disconnected_sta_info(sdev, dev, peer_address, reason); + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + if ((peer->connected_state == SLSI_STA_CONN_STATE_CONNECTED) || (peer->connected_state == SLSI_STA_CONN_STATE_DOING_KEY_CONFIG)) + cfg80211_del_sta(dev, peer->address, GFP_KERNEL); + + slsi_peer_remove(sdev, dev, peer); + + /* If last client disconnects (after WPA2 handshake) then take wakelock till group is removed + * to avoid possibility of delay in group removal if platform suspends at this point. + */ + if (ndev_vif->ap.p2p_gc_keys_set && (ndev_vif->peer_sta_records == 0)) { + SLSI_NET_DBG2(dev, SLSI_MLME, "P2PGO - Acquire wakelock after last client disconnection\n"); + slsi_wakelock(&sdev->wlan_wl); + } + break; + } + default: + SLSI_NET_WARN(dev, "mlme_disconnect_ind(vif:%d, unexpected vif type:%d)\n", ndev_vif->ifnum, ndev_vif->vif_type); + break; + } +exit: + return 0; +} + +int slsi_ps_port_control(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, enum slsi_sta_conn_state s) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_UNUSED_PARAMETER(sdev); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + switch (s) { + case SLSI_STA_CONN_STATE_DISCONNECTED: + SLSI_NET_DBG1(dev, SLSI_TX, "STA disconnected, SET : FCQ - Disabled\n"); + peer->authorized = false; + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && !ndev_vif->peer_sta_records) + (void)scsc_wifi_fcq_8021x_port_state(dev, &ndev_vif->ap.group_data_qs, SCSC_WIFI_FCQ_8021x_STATE_BLOCKED); + return scsc_wifi_fcq_8021x_port_state(dev, &peer->data_qs, SCSC_WIFI_FCQ_8021x_STATE_BLOCKED); + + case SLSI_STA_CONN_STATE_DOING_KEY_CONFIG: + SLSI_NET_DBG1(dev, SLSI_TX, "STA doing KEY config, SET : FCQ - Disabled\n"); + peer->authorized = false; + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && !ndev_vif->peer_sta_records) + (void)scsc_wifi_fcq_8021x_port_state(dev, &ndev_vif->ap.group_data_qs, SCSC_WIFI_FCQ_8021x_STATE_BLOCKED); + return scsc_wifi_fcq_8021x_port_state(dev, &peer->data_qs, SCSC_WIFI_FCQ_8021x_STATE_BLOCKED); + + case SLSI_STA_CONN_STATE_CONNECTED: + SLSI_NET_DBG1(dev, SLSI_TX, "STA connected, SET : FCQ - Enabled\n"); + peer->authorized = true; + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) + (void)scsc_wifi_fcq_8021x_port_state(dev, &ndev_vif->ap.group_data_qs, SCSC_WIFI_FCQ_8021x_STATE_OPEN); + return scsc_wifi_fcq_8021x_port_state(dev, &peer->data_qs, SCSC_WIFI_FCQ_8021x_STATE_OPEN); + + default: + SLSI_NET_DBG1(dev, SLSI_TX, "SET : FCQ - Disabled\n"); + peer->authorized = false; + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && !ndev_vif->peer_sta_records) + (void)scsc_wifi_fcq_8021x_port_state(dev, &ndev_vif->ap.group_data_qs, SCSC_WIFI_FCQ_8021x_STATE_BLOCKED); + return scsc_wifi_fcq_8021x_port_state(dev, &peer->data_qs, SCSC_WIFI_FCQ_8021x_STATE_BLOCKED); + } + + return 0; +} + +int slsi_set_uint_mib(struct slsi_dev *sdev, struct net_device *dev, u16 psid, int value) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + int r = 0; + + SLSI_DBG2(sdev, SLSI_MLME, "UINT MIB Set Request (PSID = 0x%04X, Value = %d)\n", psid, value); + + r = slsi_mib_encode_uint(&mib_data, psid, value, 0); + if (r == SLSI_MIB_STATUS_SUCCESS) { + if (mib_data.dataLength) { + r = slsi_mlme_set(sdev, dev, mib_data.data, mib_data.dataLength); + if (r != 0) + SLSI_ERR(sdev, "MIB (PSID = 0x%04X) set error = %d\n", psid, r); + kfree(mib_data.data); + } + } + return r; +} + +int slsi_send_max_transmit_msdu_lifetime(struct slsi_dev *dev, struct net_device *ndev, u32 msdu_lifetime) +{ +#ifdef CCX_MSDU_LIFETIME_MIB_NA + struct slsi_mib_data mib_data = { 0, NULL }; + int error = 0; + + if (slsi_mib_encode_uint(&mib_data, SLSI_PSID_DOT11_MAX_TRANSMIT_MSDU_LIFETIME, msdu_lifetime, 0) == SLSI_MIB_STATUS_SUCCESS) + if (mib_data.dataLength) { + error = slsi_mlme_set(dev, ndev, mib_data.data, mib_data.dataLength); + if (error) + SLSI_ERR(dev, "Err Sending max msdu lifetime failed. error = %d\n", error); + kfree(mib_data.data); + } + return error; +#endif + /* TODO: current firmware do not have this MIB yet */ + return 0; +} + +int slsi_read_max_transmit_msdu_lifetime(struct slsi_dev *dev, struct net_device *ndev, u32 *msdu_lifetime) +{ +#ifdef CCX_MSDU_LIFETIME_MIB_NA + struct slsi_mib_data mib_data = { 0, NULL }; + struct slsi_mib_data mib_res = { 0, NULL }; + struct slsi_mib_entry mib_val; + int error = 0; + int mib_rx_len = 0; + size_t len; + + SLSI_UNUSED_PARAMETER(ndev); + + mib_res.dataLength = 10; /* PSID header(5) + dot11MaxReceiveLifetime 4 bytes + status(1) */ + mib_res.data = kmalloc(mib_res.dataLength, GFP_KERNEL); + + if (!mib_res.data) + return -ENOMEM; + + slsi_mib_encode_get(&mib_data, SLSI_PSID_DOT11_MAX_TRANSMIT_MSDU_LIFETIME, 0); + error = slsi_mlme_get(dev, NULL, mib_data.data, mib_data.dataLength, + mib_res.data, mib_res.dataLength, &mib_rx_len); + kfree(mib_data.data); + + if (error) { + SLSI_ERR(dev, "Err Reading max msdu lifetime failed. error = %d\n", error); + kfree(mib_res.data); + return error; + } + + len = slsi_mib_decode(&mib_res, &mib_val); + + if (len != 8) { + kfree(mib_res.data); + return -EINVAL; + } + *msdu_lifetime = mib_val.value.u.uintValue; + + kfree(mib_res.data); + + return error; +#endif + /* TODO: current firmware do not have this MIB yet */ + return 0; +} + +void slsi_band_cfg_update(struct slsi_dev *sdev, int band) +{ + /* TODO: lock scan_mutex*/ + switch (band) { + case SLSI_FREQ_BAND_AUTO: + sdev->wiphy->bands[0] = sdev->device_config.band_2G; + sdev->wiphy->bands[1] = sdev->device_config.band_5G; + break; + case SLSI_FREQ_BAND_5GHZ: + sdev->wiphy->bands[0] = NULL; + sdev->wiphy->bands[1] = sdev->device_config.band_5G; + break; + case SLSI_FREQ_BAND_2GHZ: + sdev->wiphy->bands[0] = sdev->device_config.band_2G; + sdev->wiphy->bands[1] = NULL; + break; + default: + break; + } + wiphy_apply_custom_regulatory(sdev->wiphy, sdev->device_config.domain_info.regdomain); + slsi_update_supported_channels_regd_flags(sdev); +} + +int slsi_band_update(struct slsi_dev *sdev, int band) +{ + int i; + struct net_device *dev; + struct netdev_vif *ndev_vif; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + + SLSI_DBG3(sdev, SLSI_CFG80211, "supported_band:%d\n", band); + + if (band == sdev->device_config.supported_band) { + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return 0; + } + + sdev->device_config.supported_band = band; + + slsi_band_cfg_update(sdev, band); + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + /* If new band is auto(2.4GHz + 5GHz, no need to check for station connection.*/ + if (band == 0) + return 0; + + /* If station is connected on any rejected band, disconnect the station. */ + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (i = 1; i < (CONFIG_SCSC_WLAN_MAX_INTERFACES + 1); i++) { + dev = slsi_get_netdev_locked(sdev, i); + if (!dev) + break; + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + /** + * 1. vif should be activated and vif type should be station. + * 2. Station should be either in connecting or connected state. + * 3. if (new band is 5G and connection is on 2.4) or (new band is 2.4 and connection is 5) + * when all the above conditions are true drop the connection + * Do not wait for disconnect ind. + */ + if ((ndev_vif->activated) && (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTING || ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED) && + (ndev_vif->chan->hw_value <= 14 ? band == SLSI_FREQ_BAND_5GHZ : band == SLSI_FREQ_BAND_2GHZ)) { + int r; + + if (!ndev_vif->sta.sta_bss) { + SLSI_ERR(sdev, "slsi_mlme_disconnect failed, sta_bss is not available\n"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -EINVAL; + } + + r = slsi_mlme_disconnect(sdev, dev, ndev_vif->sta.sta_bss->bssid, WLAN_REASON_DEAUTH_LEAVING, true); + LOG_CONDITIONALLY(r != 0, SLSI_ERR(sdev, "slsi_mlme_disconnect(%pM) failed with %d\n", ndev_vif->sta.sta_bss->bssid, r)); + + r = slsi_handle_disconnect(sdev, dev, ndev_vif->sta.sta_bss->bssid, 0); + LOG_CONDITIONALLY(r != 0, SLSI_ERR(sdev, "slsi_handle_disconnect(%pM) failed with %d\n", ndev_vif->sta.sta_bss->bssid, r)); + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + return 0; +} + +/* This takes care to free the SKB on failure */ +int slsi_send_gratuitous_arp(struct slsi_dev *sdev, struct net_device *dev) +{ + int ret = 0; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *arp; + struct ethhdr *ehdr; + static const u8 arp_hdr[] = { 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01 }; + int arp_size = sizeof(arp_hdr) + ETH_ALEN + sizeof(ndev_vif->ipaddress) + ETH_ALEN + sizeof(ndev_vif->ipaddress); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "\n"); + + if (!ndev_vif->ipaddress) + return 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION)) + return -EINVAL; + if (WARN_ON(ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) + return -EINVAL; + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "IP:%pI4\n", &ndev_vif->ipaddress); + + arp = slsi_alloc_skb_headroom(sizeof(struct ethhdr) + arp_size, GFP_KERNEL); + if (WARN_ON(!arp)) + return -ENOMEM; + + /* The Ethernet header is accessed in the stack. */ + skb_reset_mac_header(arp); + + /* Ethernet Header */ + ehdr = (struct ethhdr *)skb_put(arp, sizeof(struct ethhdr)); + memset(ehdr->h_dest, 0xFF, ETH_ALEN); + SLSI_ETHER_COPY(ehdr->h_source, dev->dev_addr); + ehdr->h_proto = cpu_to_be16(ETH_P_ARP); + + /* Arp Data */ + memcpy(skb_put(arp, sizeof(arp_hdr)), arp_hdr, sizeof(arp_hdr)); + SLSI_ETHER_COPY(skb_put(arp, ETH_ALEN), dev->dev_addr); + memcpy(skb_put(arp, sizeof(ndev_vif->ipaddress)), &ndev_vif->ipaddress, sizeof(ndev_vif->ipaddress)); + memset(skb_put(arp, ETH_ALEN), 0xFF, ETH_ALEN); + memcpy(skb_put(arp, sizeof(ndev_vif->ipaddress)), &ndev_vif->ipaddress, sizeof(ndev_vif->ipaddress)); + + arp->dev = dev; + arp->protocol = ETH_P_ARP; + arp->ip_summed = CHECKSUM_UNNECESSARY; + arp->queue_mapping = slsi_netif_get_peer_queue(0, 0); /* Queueset 0 AC 0 */ + + ret = slsi_tx_data(sdev, dev, arp); + if (ret) + slsi_kfree_skb(arp); + + return ret; +} + +const u8 addr_mask[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; +const u8 solicited_node_addr_mask[6] = { 0x33, 0x33, 0xff, 0x00, 0x00, 0x01 }; + +static void slsi_create_packet_filter_element(u8 filterid, + u8 pkt_filter_mode, + u8 num_pattern_desc, + struct slsi_mlme_pattern_desc *pattern_desc, + struct slsi_mlme_pkt_filter_elem *pkt_filter_elem, + u8 *pkt_filters_len) +{ + u8 pkt_filter_hdr[SLSI_PKT_FILTER_ELEM_HDR_LEN] = { 0xdd, /* vendor ie*/ + 0x00, /*Length to be filled*/ + 0x00, 0x16, 0x32, /*oui*/ + 0x02, + filterid, /*filter id to be filled*/ + pkt_filter_mode /* pkt filter mode to be filled */ + }; + u8 i, pattern_desc_len = 0; + + WARN_ON(num_pattern_desc > SLSI_MAX_PATTERN_DESC); + + memcpy(pkt_filter_elem->header, pkt_filter_hdr, SLSI_PKT_FILTER_ELEM_HDR_LEN); + pkt_filter_elem->num_pattern_desc = num_pattern_desc; + + for (i = 0; i < num_pattern_desc; i++) { + memcpy(&pkt_filter_elem->pattern_desc[i], &pattern_desc[i], sizeof(struct slsi_mlme_pattern_desc)); + pattern_desc_len += SLSI_PKT_DESC_FIXED_LEN + (2 * pattern_desc[i].mask_length); + } + + /*Update the length in the header*/ + pkt_filter_elem->header[1] = SLSI_PKT_FILTER_ELEM_FIXED_LEN + pattern_desc_len; + *pkt_filters_len += (SLSI_PKT_FILTER_ELEM_HDR_LEN + pattern_desc_len); + + SLSI_DBG3_NODEV(SLSI_MLME, "filterid=0x%x,pkt_filter_mode=0x%x,num_pattern_desc=0x%x\n", + filterid, pkt_filter_mode, num_pattern_desc); +} + +#define SLSI_SCREEN_OFF_FILTERS_COUNT 1 + +static int slsi_set_common_packet_filters(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc; + struct slsi_mlme_pkt_filter_elem pkt_filter_elem[1]; + u8 pkt_filters_len = 0, num_filters = 0; + + /*Opt out all broadcast and multicast packets (filter on I/G bit)*/ + pattern_desc.offset = 0; + pattern_desc.mask_length = 1; + pattern_desc.mask[0] = 0x01; + pattern_desc.pattern[0] = 0x01; + + slsi_create_packet_filter_element(SLSI_ALL_BC_MC_FILTER_ID, + FAPI_PACKETFILTERMODE_OPT_OUT_SLEEP | FAPI_PACKETFILTERMODE_OPT_OUT, + 1, &pattern_desc, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + return slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); +} + +int slsi_set_arp_packet_filter(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc[SLSI_MAX_PATTERN_DESC]; + int num_pattern_desc = 0; + u8 pkt_filters_len = 0, num_filters = 0; + struct slsi_mlme_pkt_filter_elem pkt_filter_elem[2]; + int ret; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION)) + return -EINVAL; + + if (WARN_ON(!peer)) + return -EINVAL; + + if (slsi_is_proxy_arp_supported_on_ap(peer->assoc_resp_ie)) + return 0; + + /*Set the IP address while suspending as this will be used by firmware for ARP/NDP offloading*/ + slsi_mlme_set_ip_address(sdev, dev); +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + slsi_mlme_set_ipv6_address(sdev, dev); +#endif + + SLSI_NET_DBG2(dev, SLSI_MLME, "Set ARP filter\n"); + + /*Opt in the broadcast ARP packets for Local IP address*/ + num_pattern_desc = 0; + pattern_desc[num_pattern_desc].offset = 0; /*filtering on MAC destination Address*/ + pattern_desc[num_pattern_desc].mask_length = ETH_ALEN; + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].mask, addr_mask); + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].pattern, addr_mask); + num_pattern_desc++; + + /*filter on ethertype ARP*/ + SET_ETHERTYPE_PATTERN_DESC(pattern_desc[num_pattern_desc], ETH_P_ARP); + num_pattern_desc++; + + pattern_desc[num_pattern_desc].offset = 0x26; /*filtering on Target IP Address*/ + pattern_desc[num_pattern_desc].mask_length = 4; + memcpy(pattern_desc[num_pattern_desc].mask, addr_mask, pattern_desc[num_pattern_desc].mask_length); + memcpy(pattern_desc[num_pattern_desc].pattern, &ndev_vif->ipaddress, pattern_desc[num_pattern_desc].mask_length); + num_pattern_desc++; + + slsi_create_packet_filter_element(SLSI_LOCAL_ARP_FILTER_ID, FAPI_PACKETFILTERMODE_OPT_IN, + num_pattern_desc, pattern_desc, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); + if (ret) + return ret; + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + pkt_filters_len = 0; + num_filters = 0; + + /*Opt in the multicast NS packets for Local IP address in active mode*/ + num_pattern_desc = 0; + pattern_desc[num_pattern_desc].offset = 0; /*filtering on MAC destination Address*/ + pattern_desc[num_pattern_desc].mask_length = ETH_ALEN; + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].mask, addr_mask); + memcpy(pattern_desc[num_pattern_desc].pattern, solicited_node_addr_mask, 3); + memcpy(&pattern_desc[num_pattern_desc].pattern[3], &ndev_vif->ipv6address.s6_addr[13], 3); /* last 3 bytes of IPv6 address*/ + num_pattern_desc++; + + /*filter on ethertype ARP*/ + SET_ETHERTYPE_PATTERN_DESC(pattern_desc[num_pattern_desc], 0x86DD); + num_pattern_desc++; + + pattern_desc[num_pattern_desc].offset = 0x14; /*filtering on next header*/ + pattern_desc[num_pattern_desc].mask_length = 1; + pattern_desc[num_pattern_desc].mask[0] = 0xff; + pattern_desc[num_pattern_desc].pattern[0] = 0x3a; + num_pattern_desc++; + + pattern_desc[num_pattern_desc].offset = 0x36; /*filtering on ICMP6 packet type*/ + pattern_desc[num_pattern_desc].mask_length = 1; + pattern_desc[num_pattern_desc].mask[0] = 0xff; + pattern_desc[num_pattern_desc].pattern[0] = 0x87; /* Neighbor Solicitation type in ICMPv6 */ + num_pattern_desc++; + + slsi_create_packet_filter_element(SLSI_LOCAL_NS_FILTER_ID, FAPI_PACKETFILTERMODE_OPT_IN, + num_pattern_desc, pattern_desc, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); + if (ret) + return ret; +#endif + + return ret; +} + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER +int slsi_set_enhanced_pkt_filter(struct net_device *dev, u8 pkt_filter_enable) +{ + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct slsi_dev *sdev = netdev_vif->sdev; + int ret = 0; + int is_suspend = 0; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + is_suspend = sdev->device_config.user_suspend_mode; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + if (is_suspend) { + SLSI_ERR(sdev, "Host is in early suspend state.\n"); + return -EPERM; /* set_enhanced_pkt_filter should not be called after suspend */ + } + + sdev->enhanced_pkt_filter_enabled = pkt_filter_enable; + SLSI_INFO(sdev, "Enhanced packet filter is %s", (pkt_filter_enable ? "enabled" : "disabled")); + return ret; +} + +static int slsi_set_opt_out_unicast_packet_filter(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc; + u8 pkt_filters_len = 0; + int ret = 0; + struct slsi_mlme_pkt_filter_elem pkt_filter_elem; + + /* IPv4 packet */ + pattern_desc.offset = 0; /* destination mac address*/ + pattern_desc.mask_length = ETH_ALEN; + memset(pattern_desc.mask, 0xff, ETH_ALEN); + memcpy(pattern_desc.pattern, sdev->hw_addr, ETH_ALEN); + + slsi_create_packet_filter_element(SLSI_OPT_OUT_ALL_FILTER_ID, + FAPI_PACKETFILTERMODE_OPT_OUT_SLEEP, + 1, &pattern_desc, + &pkt_filter_elem, &pkt_filters_len); + + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, 1, &pkt_filter_elem); + + return ret; +} + +static int slsi_set_opt_in_tcp4_packet_filter(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc[2]; + u8 pkt_filters_len = 0; + int ret = 0; + struct slsi_mlme_pkt_filter_elem pkt_filter_elem; + + /* IPv4 packet */ + pattern_desc[0].offset = ETH_ALEN + ETH_ALEN; /* ethhdr->h_proto */ + pattern_desc[0].mask_length = 2; + pattern_desc[0].mask[0] = 0xff; /* Big endian 0xffff */ + pattern_desc[0].mask[1] = 0xff; + pattern_desc[0].pattern[0] = 0x08; /* Big endian 0x0800 */ + pattern_desc[0].pattern[1] = 0x00; + + /* dest.addr(6) + src.addr(6) + Protocol(2) = sizeof(struct ethhdr) = 14 */ + /* VER(1) + Svc(1) + TotalLen(2) + ID(2) + Flag&Fragmentation(2) + TTL(1) = 9 */ + pattern_desc[1].offset = 23; /* iphdr->protocol */ + pattern_desc[1].mask_length = 1; + pattern_desc[1].mask[0] = 0xff; + pattern_desc[1].pattern[0] = IPPROTO_TCP; /* 0x11 */ + slsi_create_packet_filter_element(SLSI_OPT_IN_TCP4_FILTER_ID, + FAPI_PACKETFILTERMODE_OPT_IN_SLEEP, + 2, + pattern_desc, + &pkt_filter_elem, + &pkt_filters_len); + + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, 1, &pkt_filter_elem); + + return ret; +} + +static int slsi_set_opt_in_tcp6_packet_filter(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc[2]; + u8 pkt_filters_len = 0; + int ret = 0; + struct slsi_mlme_pkt_filter_elem pkt_filter_elem; + + /* IPv6 packet */ + pattern_desc[0].offset = ETH_ALEN + ETH_ALEN; /* ethhdr->h_proto */ + pattern_desc[0].mask_length = 2; + pattern_desc[0].mask[0] = 0xff; /* Big endian 0xffff */ + pattern_desc[0].mask[1] = 0xff; + pattern_desc[0].pattern[0] = 0x86; /* Big endian 0x86DD */ + pattern_desc[0].pattern[1] = 0xdd; + + pattern_desc[1].offset = sizeof(struct ethhdr) + 6; /*filtering on ipv6->next header*/ + pattern_desc[1].mask_length = 1; + pattern_desc[1].mask[0] = 0xff; + pattern_desc[1].pattern[0] = IPPROTO_TCP; + + slsi_create_packet_filter_element(SLSI_OPT_IN_TCP6_FILTER_ID, + FAPI_PACKETFILTERMODE_OPT_IN_SLEEP, + 2, + pattern_desc, + &pkt_filter_elem, + &pkt_filters_len); + + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, 1, &pkt_filter_elem); + + return ret; +} +#endif + +static int slsi_set_multicast_packet_filters(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc; + u8 pkt_filters_len = 0, i, num_filters = 0; + int ret = 0; + struct slsi_mlme_pkt_filter_elem *pkt_filter_elem = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + u8 mc_filter_id, mc_filter_count; + + /* Multicast packets for registered multicast addresses to be opted in on screen off*/ + SLSI_NET_DBG2(dev, SLSI_MLME, "Set mc filters ,count =%d\n", ndev_vif->sta.regd_mc_addr_count); + + mc_filter_count = ndev_vif->sta.regd_mc_addr_count; + if (!mc_filter_count) + return 0; + + pkt_filter_elem = kmalloc((mc_filter_count * sizeof(struct slsi_mlme_pkt_filter_elem)), GFP_KERNEL); + if (!pkt_filter_elem) { + SLSI_NET_ERR(dev, "ERROR Memory allocation failure\n"); + return -ENOMEM; + } + + pattern_desc.offset = 0; + pattern_desc.mask_length = ETH_ALEN; + SLSI_ETHER_COPY(pattern_desc.mask, addr_mask); + + for (i = 0; i < mc_filter_count; i++) { + SLSI_ETHER_COPY(pattern_desc.pattern, ndev_vif->sta.regd_mc_addr[i]); + mc_filter_id = SLSI_REGD_MC_FILTER_ID + i; +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + if (sdev->enhanced_pkt_filter_enabled) + slsi_create_packet_filter_element(mc_filter_id, + FAPI_PACKETFILTERMODE_OPT_IN, + 1, &pattern_desc, + &pkt_filter_elem[num_filters], &pkt_filters_len); + else +#endif + slsi_create_packet_filter_element(mc_filter_id, + FAPI_PACKETFILTERMODE_OPT_IN | + FAPI_PACKETFILTERMODE_OPT_IN_SLEEP, + 1, &pattern_desc, + &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + } + + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); + kfree(pkt_filter_elem); + + return ret; +} + +int slsi_clear_packet_filters(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + + u8 i, pkt_filters_len = 0; + int num_filters = 0; + int ret = 0; + struct slsi_mlme_pkt_filter_elem *pkt_filter_elem; + u8 mc_filter_id; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION)) + return -EINVAL; + + if (WARN_ON(!peer)) + return -EINVAL; + + SLSI_NET_DBG2(dev, SLSI_MLME, "Clear filters on Screen on"); + + /*calculate number of filters*/ + num_filters = ndev_vif->sta.regd_mc_addr_count + SLSI_SCREEN_OFF_FILTERS_COUNT; + if ((slsi_is_proxy_arp_supported_on_ap(peer->assoc_resp_ie)) == false) { + num_filters++; +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + num_filters++; +#endif + } + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + if (sdev->enhanced_pkt_filter_enabled) { + num_filters++; /*All OPT OUT*/ + num_filters++; /*TCP IPv4 OPT IN*/ + num_filters++; /*TCP IPv6 OPT IN*/ + } +#endif + + pkt_filter_elem = kmalloc((num_filters * sizeof(struct slsi_mlme_pkt_filter_elem)), GFP_KERNEL); + if (!pkt_filter_elem) { + SLSI_NET_ERR(dev, "ERROR Memory allocation failure"); + return -ENOMEM; + } + + num_filters = 0; + for (i = 0; i < ndev_vif->sta.regd_mc_addr_count; i++) { + mc_filter_id = SLSI_REGD_MC_FILTER_ID + i; + slsi_create_packet_filter_element(mc_filter_id, 0, 0, NULL, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + } + if ((slsi_is_proxy_arp_supported_on_ap(peer->assoc_resp_ie)) == false) { + slsi_create_packet_filter_element(SLSI_LOCAL_ARP_FILTER_ID, 0, 0, NULL, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + slsi_create_packet_filter_element(SLSI_LOCAL_NS_FILTER_ID, 0, 0, NULL, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; +#endif + } + + slsi_create_packet_filter_element(SLSI_ALL_BC_MC_FILTER_ID, 0, 0, NULL, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + if (sdev->enhanced_pkt_filter_enabled) { + slsi_create_packet_filter_element(SLSI_OPT_OUT_ALL_FILTER_ID, 0, 0, NULL, + &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + slsi_create_packet_filter_element(SLSI_OPT_IN_TCP4_FILTER_ID, 0, 0, NULL, + &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + slsi_create_packet_filter_element(SLSI_OPT_IN_TCP6_FILTER_ID, 0, 0, NULL, + &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + } +#endif + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); + kfree(pkt_filter_elem); + return ret; +} + +int slsi_update_packet_filters(struct slsi_dev *sdev, struct net_device *dev) +{ + int ret = 0; + + struct netdev_vif *ndev_vif = netdev_priv(dev); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION); + + ret = slsi_set_multicast_packet_filters(sdev, dev); + if (ret) + return ret; + + ret = slsi_set_arp_packet_filter(sdev, dev); + if (ret) + return ret; + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER + if (sdev->enhanced_pkt_filter_enabled) { + ret = slsi_set_opt_out_unicast_packet_filter(sdev, dev); + if (ret) + return ret; + ret = slsi_set_opt_in_tcp4_packet_filter(sdev, dev); + if (ret) + return ret; + ret = slsi_set_opt_in_tcp6_packet_filter(sdev, dev); + if (ret) + return ret; + } +#endif + return slsi_set_common_packet_filters(sdev, dev); +} + +#define IPV6_PF_PATTERN_MASK 0xf0 +#define IPV6_PF_PATTERN 0x60 + +#ifdef CONFIG_SCSC_WLAN_DISABLE_NAT_KA +#define SLSI_ON_CONNECT_FILTERS_COUNT 2 +#else +#define SLSI_ON_CONNECT_FILTERS_COUNT 3 +#endif + +void slsi_set_packet_filters(struct slsi_dev *sdev, struct net_device *dev) +{ + struct slsi_mlme_pattern_desc pattern_desc[SLSI_MAX_PATTERN_DESC]; + int num_pattern_desc = 0; + u8 pkt_filters_len = 0; + int num_filters = 0; + + struct slsi_mlme_pkt_filter_elem pkt_filter_elem[SLSI_ON_CONNECT_FILTERS_COUNT]; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + const u8 *ie; + + if (WARN_ON(!ndev_vif->activated)) + return; + + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION)) + return; + + if (WARN_ON(!peer)) + return; + + if (WARN_ON(!peer->assoc_resp_ie)) + return; + +#ifdef CONFIG_SCSC_WLAN_BLOCK_IPV6 + + /*Opt out all IPv6 packets in active and suspended mode (ipv6 filtering)*/ + num_pattern_desc = 0; + pattern_desc[num_pattern_desc].offset = 0x0E; /*filtering on IP Protocol version*/ + pattern_desc[num_pattern_desc].mask_length = 1; + pattern_desc[num_pattern_desc].mask[0] = IPV6_PF_PATTERN_MASK; + pattern_desc[num_pattern_desc].pattern[0] = IPV6_PF_PATTERN; + num_pattern_desc++; + + slsi_create_packet_filter_element(SLSI_ALL_IPV6_PKTS_FILTER_ID, + FAPI_PACKETFILTERMODE_OPT_OUT | FAPI_PACKETFILTERMODE_OPT_OUT_SLEEP, + num_pattern_desc, pattern_desc, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + +#endif + + ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, SLSI_WLAN_OUI_TYPE_WFA_HS20_IND, + ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len); + + if (ie) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Connected to HS2 AP "); + + if (slsi_is_proxy_arp_supported_on_ap(peer->assoc_resp_ie)) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Proxy ARP service supported on HS2 AP "); + + /* Opt out Gratuitous ARP packets (ARP Announcement) in active and suspended mode. + * For suspended mode, gratituous ARP is dropped by "opt out all broadcast" that will be + * set in slsi_set_common_packet_filters on screen off + */ + num_pattern_desc = 0; + pattern_desc[num_pattern_desc].offset = 0; /*filtering on MAC destination Address*/ + pattern_desc[num_pattern_desc].mask_length = ETH_ALEN; + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].mask, addr_mask); + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].pattern, addr_mask); + num_pattern_desc++; + + SET_ETHERTYPE_PATTERN_DESC(pattern_desc[num_pattern_desc], ETH_P_ARP); + num_pattern_desc++; + + slsi_create_packet_filter_element(SLSI_PROXY_ARP_FILTER_ID, FAPI_PACKETFILTERMODE_OPT_OUT, + num_pattern_desc, pattern_desc, &pkt_filter_elem[num_filters], + &pkt_filters_len); + num_filters++; + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + /* Opt out unsolicited Neighbor Advertisement packets .For suspended mode, NA is dropped by + * "opt out all IPv6 multicast" already set in slsi_create_common_packet_filters + */ + + num_pattern_desc = 0; + + pattern_desc[num_pattern_desc].offset = 0; /*filtering on MAC destination Address*/ + pattern_desc[num_pattern_desc].mask_length = ETH_ALEN; + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].mask, addr_mask); + SLSI_ETHER_COPY(pattern_desc[num_pattern_desc].pattern, solicited_node_addr_mask); + num_pattern_desc++; + + SET_ETHERTYPE_PATTERN_DESC(pattern_desc[num_pattern_desc], 0x86DD); + num_pattern_desc++; + + pattern_desc[num_pattern_desc].offset = 0x14; /*filtering on next header*/ + pattern_desc[num_pattern_desc].mask_length = 1; + pattern_desc[num_pattern_desc].mask[0] = 0xff; + pattern_desc[num_pattern_desc].pattern[0] = 0x3a; + num_pattern_desc++; + + pattern_desc[num_pattern_desc].offset = 0x36; /*filtering on ICMP6 packet type*/ + pattern_desc[num_pattern_desc].mask_length = 1; + pattern_desc[num_pattern_desc].mask[0] = 0xff; + pattern_desc[num_pattern_desc].pattern[0] = 0x88; /* Neighbor Advertisement type in ICMPv6 */ + num_pattern_desc++; + + slsi_create_packet_filter_element(SLSI_PROXY_ARP_NA_FILTER_ID, FAPI_PACKETFILTERMODE_OPT_OUT, + num_pattern_desc, pattern_desc, &pkt_filter_elem[num_filters], + &pkt_filters_len); + num_filters++; +#endif + } + } + +#ifndef CONFIG_SCSC_WLAN_DISABLE_NAT_KA + { + const u8 nat_ka_pattern[4] = { 0x11, 0x94, 0x00, 0x09 }; + /*Opt out the NAT T for IPsec*/ + num_pattern_desc = 0; + pattern_desc[num_pattern_desc].offset = 0x24; /*filtering on destination port number*/ + pattern_desc[num_pattern_desc].mask_length = 4; + memcpy(pattern_desc[num_pattern_desc].mask, addr_mask, 4); + memcpy(pattern_desc[num_pattern_desc].pattern, nat_ka_pattern, 4); + num_pattern_desc++; + + slsi_create_packet_filter_element(SLSI_NAT_IPSEC_FILTER_ID, FAPI_PACKETFILTERMODE_OPT_OUT_SLEEP, + num_pattern_desc, pattern_desc, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + } +#endif + + if (num_filters) + slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); +} + +int slsi_ip_address_changed(struct slsi_dev *sdev, struct net_device *dev, __be32 ipaddress) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int ret = 0; + + /* Store the IP address outside the check for vif being active + * as we get the same notification in case of static IP + */ + if (ndev_vif->ipaddress != ipaddress) + ndev_vif->ipaddress = ipaddress; + + if (ndev_vif->activated && (ndev_vif->vif_type == FAPI_VIFTYPE_AP)) { + struct slsi_mlme_pattern_desc pattern_desc[1]; + u8 num_patterns = 0; + struct slsi_mlme_pkt_filter_elem pkt_filter_elem[1]; + u8 pkt_filters_len = 0; + u8 num_filters = 0; + + ndev_vif->ipaddress = ipaddress; + ret = slsi_mlme_set_ip_address(sdev, dev); + if (ret != 0) + SLSI_NET_ERR(dev, "slsi_mlme_set_ip_address ERROR. ret=%d", ret); + + /* Opt out IPv6 packets in platform suspended mode */ + pattern_desc[num_patterns].offset = 0x0E; + pattern_desc[num_patterns].mask_length = 0x01; + pattern_desc[num_patterns].mask[0] = IPV6_PF_PATTERN_MASK; + pattern_desc[num_patterns++].pattern[0] = IPV6_PF_PATTERN; + + slsi_create_packet_filter_element(SLSI_AP_ALL_IPV6_PKTS_FILTER_ID, FAPI_PACKETFILTERMODE_OPT_OUT_SLEEP, + num_patterns, pattern_desc, &pkt_filter_elem[num_filters], &pkt_filters_len); + num_filters++; + ret = slsi_mlme_set_packet_filter(sdev, dev, pkt_filters_len, num_filters, pkt_filter_elem); + if (ret != 0) + SLSI_NET_ERR(dev, "slsi_mlme_set_packet_filter (return :%d) ERROR\n", ret); + } else if ((ndev_vif->activated) && + (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + + if (WARN_ON(!peer)) + return -EINVAL; + + if (!(peer->capabilities & WLAN_CAPABILITY_PRIVACY) || + (ndev_vif->sta.group_key_set && peer->pairwise_key_set)) + slsi_send_gratuitous_arp(sdev, dev); + else + ndev_vif->sta.gratuitous_arp_needed = true; + + slsi_mlme_powermgt(sdev, dev, ndev_vif->set_power_mode); + } + + return ret; +} + +#define SLSI_AP_AUTO_CHANLS_LIST_FROM_HOSTAPD_MAX 3 + +int slsi_auto_chan_select_scan(struct slsi_dev *sdev, int n_channels, struct ieee80211_channel *channels[]) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + struct sk_buff_head unique_scan_results; + int scan_result_count[SLSI_AP_AUTO_CHANLS_LIST_FROM_HOSTAPD_MAX] = { 0, 0, 0 }; + int i, j; + int r = 0; + int selected_index = 0; + int min_index = 0; + u32 freqdiff = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_WARN(sdev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + skb_queue_head_init(&unique_scan_results); + + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); /* use the main VIF */ + if (!dev) { + r = -EINVAL; + return r; + } + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) { + r = -EBUSY; + goto exit_with_vif; + } + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = true; + r = slsi_mlme_add_scan(sdev, + dev, + FAPI_SCANTYPE_AP_AUTO_CHANNEL_SELECTION, + FAPI_REPORTMODE_REAL_TIME, + 0, /* n_ssids */ + NULL, /* ssids */ + n_channels, + channels, + NULL, + NULL, /* ie */ + 0, /* ie_len */ + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan); + + if (r == 0) { + struct sk_buff *unique_scan; + struct sk_buff *scan; + + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + scan = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + while (scan) { + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(scan); + struct ieee80211_channel *channel; + + /* make sure this BSSID has not already been used */ + skb_queue_walk(&unique_scan_results, unique_scan) { + struct ieee80211_mgmt *unique_mgmt = fapi_get_mgmt(unique_scan); + + if (compare_ether_addr(mgmt->bssid, unique_mgmt->bssid) == 0) { + slsi_kfree_skb(scan); + goto next_scan; + } + } + + slsi_skb_queue_head(&unique_scan_results, scan); + + channel = slsi_find_scan_channel(sdev, mgmt, fapi_get_mgmtlen(scan), fapi_get_u16(scan, u.mlme_scan_ind.channel_frequency) / 2); + if (!channel) + goto next_scan; + + /* check for interfering channels for 1, 6 and 11 */ + for (i = 0, j = 0; i < SLSI_AP_AUTO_CHANLS_LIST_FROM_HOSTAPD_MAX && channels[j]; i++, j = j + 5) { + if (channel->center_freq == channels[j]->center_freq) { + SLSI_NET_DBG3(dev, SLSI_CFG80211, "exact match:%d\n", i); + scan_result_count[i] += 5; + goto next_scan; + } + freqdiff = abs((int)channel->center_freq - (channels[j]->center_freq)); + if (freqdiff <= 20) { + SLSI_NET_DBG3(dev, SLSI_CFG80211, "overlapping:%d, freqdiff:%d\n", i, freqdiff); + scan_result_count[i] += (5 - (freqdiff / 5)); + } + } + +next_scan: + scan = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + + /* Select the channel to use */ + for (i = 0, j = 0; i < SLSI_AP_AUTO_CHANLS_LIST_FROM_HOSTAPD_MAX; i++, j = j + 5) { + SLSI_NET_DBG3(dev, SLSI_CFG80211, "score[%d]:%d\n", i, scan_result_count[i]); + if (scan_result_count[i] <= scan_result_count[min_index]) { + min_index = i; + selected_index = j; + } + } + SLSI_NET_DBG3(dev, SLSI_CFG80211, "selected:%d with score:%d\n", selected_index, scan_result_count[min_index]); + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + sdev->device_config.ap_auto_chan = channels[selected_index]->hw_value & 0xFF; + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + SLSI_INFO(sdev, "Channel selected = %d", sdev->device_config.ap_auto_chan); + } + slsi_skb_queue_purge(&unique_scan_results); + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = false; + +exit_with_vif: + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; +} + +int slsi_set_boost(struct slsi_dev *sdev, struct net_device *dev) +{ + int error = 0; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + error = slsi_set_mib_rssi_boost(sdev, dev, SLSI_PSID_UNIFI_ROAM_RSSI_BOOST, 1, + sdev->device_config.rssi_boost_2g); + if (error) + SLSI_ERR(sdev, "Err setting boost value For 2g after adding vif. error = %d\n", error); + error = slsi_set_mib_rssi_boost(sdev, dev, SLSI_PSID_UNIFI_ROAM_RSSI_BOOST, 2, + sdev->device_config.rssi_boost_5g); + if (error) + SLSI_ERR(sdev, "Err setting boost value for 5g after adding vif . error = %d\n", error); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return error; +} + +/** + * Work to be done when ROC retention duration expires: + * Send ROC expired event to cfg80211 and queue work to delete unsync vif after retention timeout. + */ +static void slsi_p2p_roc_duration_expiry_work(struct work_struct *work) +{ + struct netdev_vif *ndev_vif = container_of((struct delayed_work *)work, struct netdev_vif, unsync.roc_expiry_work); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* There can be a race condition of this work function waiting for ndev_vif->vif_mutex and meanwhile the vif is deleted (due to net_stop). + * In such cases ndev_vif->chan would have been cleared. + */ + if (ndev_vif->sdev->p2p_state == P2P_IDLE_NO_VIF) { + SLSI_NET_DBG1(ndev_vif->wdev.netdev, SLSI_CFG80211, "P2P unsync vif is not present\n"); + goto exit; + } + + SLSI_NET_DBG3(ndev_vif->wdev.netdev, SLSI_CFG80211, "Send ROC expired event\n"); + + /* If action frame tx is in progress don't schedule work to delete vif */ + if (ndev_vif->sdev->p2p_state != P2P_ACTION_FRAME_TX_RX) { + /* After sucessful frame transmission, we will move to LISTENING or VIF ACTIVE state. + * Unset channel should not be sent down during p2p procedure. + */ + if (!ndev_vif->drv_in_p2p_procedure) { + if (delayed_work_pending(&ndev_vif->unsync.unset_channel_expiry_work)) + cancel_delayed_work(&ndev_vif->unsync.unset_channel_expiry_work); + queue_delayed_work(ndev_vif->sdev->device_wq, &ndev_vif->unsync.unset_channel_expiry_work, + msecs_to_jiffies(SLSI_P2P_UNSET_CHANNEL_EXTRA_MSEC)); + } + slsi_p2p_queue_unsync_vif_del_work(ndev_vif, SLSI_P2P_UNSYNC_VIF_EXTRA_MSEC); + SLSI_P2P_STATE_CHANGE(ndev_vif->sdev, P2P_IDLE_VIF_ACTIVE); + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_remain_on_channel_expired(&ndev_vif->wdev, ndev_vif->unsync.roc_cookie, ndev_vif->chan, GFP_KERNEL); +#else + cfg80211_remain_on_channel_expired(ndev_vif->wdev.netdev, ndev_vif->unsync.roc_cookie, + ndev_vif->chan, ndev_vif->channel_type, GFP_KERNEL); +#endif + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +/** + * Work to be done when unsync vif retention duration expires: + * Delete the unsync vif. + */ +static void slsi_p2p_unsync_vif_delete_work(struct work_struct *work) +{ + struct netdev_vif *ndev_vif = container_of((struct delayed_work *)work, struct netdev_vif, unsync.del_vif_work); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_NET_DBG1(ndev_vif->wdev.netdev, SLSI_CFG80211, "Delete vif duration expired - Deactivate unsync vif\n"); + slsi_p2p_vif_deactivate(ndev_vif->sdev, ndev_vif->wdev.netdev, true); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +/** + * Work to be done after roc expiry or cancel remain on channel: + * Unset channel to be sent to Fw. + */ +static void slsi_p2p_unset_channel_expiry_work(struct work_struct *work) +{ + struct netdev_vif *ndev_vif = container_of((struct delayed_work *)work, struct netdev_vif, + unsync.unset_channel_expiry_work); + struct slsi_dev *sdev = ndev_vif->sdev; + struct net_device *dev = ndev_vif->wdev.netdev; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (ndev_vif->activated) { + SLSI_NET_DBG1(ndev_vif->wdev.netdev, SLSI_CFG80211, "Unset channel expiry work-Send Unset Channel\n"); + if (!ndev_vif->drv_in_p2p_procedure) { + /* Supplicant has stopped FIND/LISTEN. Clear Probe Response IEs in firmware and driver */ + if (slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_PROBE_RESPONSE, NULL, 0) != 0) + SLSI_NET_ERR(dev, "Clearing Probe Response IEs failed for unsync vif\n"); + slsi_unsync_vif_set_probe_rsp_ie(ndev_vif, NULL, 0); + + /* Send Unset Channel */ + if (ndev_vif->driver_channel != 0) { + slsi_mlme_unset_channel_req(sdev, dev); + ndev_vif->driver_channel = 0; + } + } + } else { + SLSI_NET_ERR(dev, "P2P vif is not activated\n"); + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +/* Initializations for P2P - Change vif type to unsync, create workqueue and init work */ +int slsi_p2p_init(struct slsi_dev *sdev, struct netdev_vif *ndev_vif) +{ + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Initialize P2P - Init P2P state to P2P_IDLE_NO_VIF\n"); + sdev->p2p_state = P2P_IDLE_NO_VIF; + sdev->p2p_group_exp_frame = SLSI_P2P_PA_INVALID; + + ndev_vif->vif_type = FAPI_VIFTYPE_UNSYNCHRONISED; + ndev_vif->unsync.slsi_p2p_continuous_fullscan = false; + + + INIT_DELAYED_WORK(&ndev_vif->unsync.roc_expiry_work, slsi_p2p_roc_duration_expiry_work); + INIT_DELAYED_WORK(&ndev_vif->unsync.del_vif_work, slsi_p2p_unsync_vif_delete_work); + INIT_DELAYED_WORK(&ndev_vif->unsync.unset_channel_expiry_work, slsi_p2p_unset_channel_expiry_work); + return 0; +} + +/* De-initializations for P2P - Reset vif type, cancel work and destroy workqueue */ +void slsi_p2p_deinit(struct slsi_dev *sdev, struct netdev_vif *ndev_vif) +{ + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "De-initialize P2P\n"); + + ndev_vif->vif_type = SLSI_VIFTYPE_UNSPECIFIED; + + /* Work should have been cleaned up by now */ + if (WARN_ON(delayed_work_pending(&ndev_vif->unsync.del_vif_work))) + cancel_delayed_work(&ndev_vif->unsync.del_vif_work); + + if (WARN_ON(delayed_work_pending(&ndev_vif->unsync.roc_expiry_work))) + cancel_delayed_work(&ndev_vif->unsync.roc_expiry_work); +} + +/** + * P2P vif activation: + * Add unsync vif, register for action frames, configure Probe Rsp IEs if required and set channel + */ +int slsi_p2p_vif_activate(struct slsi_dev *sdev, struct net_device *dev, struct ieee80211_channel *chan, u16 duration, bool set_probe_rsp_ies) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u32 af_bmap_active = SLSI_ACTION_FRAME_PUBLIC; + u32 af_bmap_suspended = SLSI_ACTION_FRAME_PUBLIC; + int r = 0; + + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Activate P2P unsync vif\n"); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + /* Interface address and device address are same for P2P unsync vif */ + if (slsi_mlme_add_vif(sdev, dev, dev->dev_addr, dev->dev_addr) != 0) { + SLSI_NET_ERR(dev, "slsi_mlme_add_vif failed for unsync vif\n"); + goto exit_with_error; + } + + ndev_vif->activated = true; + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + + if (slsi_mlme_register_action_frame(sdev, dev, af_bmap_active, af_bmap_suspended) != 0) { + SLSI_NET_ERR(dev, "Action frame registration failed for unsync vif\n"); + goto exit_with_vif; + } + + if (set_probe_rsp_ies) { + u16 purpose = FAPI_PURPOSE_PROBE_RESPONSE; + + if (!ndev_vif->unsync.probe_rsp_ies) { + SLSI_NET_ERR(dev, "Probe Response IEs not available for ROC\n"); + goto exit_with_vif; + } + + if (slsi_mlme_add_info_elements(sdev, dev, purpose, ndev_vif->unsync.probe_rsp_ies, ndev_vif->unsync.probe_rsp_ies_len) != 0) { + SLSI_NET_ERR(dev, "Setting Probe Response IEs for unsync vif failed\n"); + goto exit_with_vif; + } + ndev_vif->unsync.ies_changed = false; + } + + if (slsi_mlme_set_channel(sdev, dev, chan, SLSI_FW_CHANNEL_DURATION_UNSPECIFIED, 0, 0) != 0) { + SLSI_NET_ERR(dev, "Set channel failed for unsync vif\n"); + goto exit_with_vif; + } else { + ndev_vif->chan = chan; + ndev_vif->driver_channel = chan->hw_value; + } + + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + goto exit; + +exit_with_vif: + slsi_p2p_vif_deactivate(sdev, dev, true); +exit_with_error: + r = -EINVAL; +exit: + return r; +} + +/* Delete unsync vif - DON'T update the vif type */ +void slsi_p2p_vif_deactivate(struct slsi_dev *sdev, struct net_device *dev, bool hw_available) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "De-activate P2P unsync vif\n"); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (sdev->p2p_state == P2P_IDLE_NO_VIF) { + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "P2P unsync vif already deactivated\n"); + return; + } + + /* Indicate failure using cfg80211_mgmt_tx_status() if frame TX is not completed during VIF delete */ + if (ndev_vif->mgmt_tx_data.exp_frame != SLSI_P2P_PA_INVALID) { + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) + cfg80211_mgmt_tx_status(&ndev_vif->wdev, ndev_vif->mgmt_tx_data.cookie, ndev_vif->mgmt_tx_data.buf, ndev_vif->mgmt_tx_data.buf_len, false, GFP_KERNEL); +#else + cfg80211_mgmt_tx_status(dev, ndev_vif->mgmt_tx_data.cookie, ndev_vif->mgmt_tx_data.buf, ndev_vif->mgmt_tx_data.buf_len, false, GFP_KERNEL); +#endif + } + + cancel_delayed_work(&ndev_vif->unsync.del_vif_work); + cancel_delayed_work(&ndev_vif->unsync.roc_expiry_work); + + if (hw_available) + slsi_mlme_del_vif(sdev, dev); + + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_NO_VIF); + + /* slsi_vif_deactivated is not used here after del_vif as it modifies vif type as well */ + + ndev_vif->activated = false; + ndev_vif->chan = NULL; + + if (WARN_ON(ndev_vif->unsync.listen_offload)) + ndev_vif->unsync.listen_offload = false; + + slsi_unsync_vif_set_probe_rsp_ie(ndev_vif, NULL, 0); + (void)slsi_set_mgmt_tx_data(ndev_vif, 0, 0, NULL, 0); + + SLSI_NET_DBG2(dev, SLSI_INIT_DEINIT, "P2P unsync vif deactivated\n"); +} + +/** + * Delete unsync vif when group role is being started. + * For such cases the net_device during the call would be of the group interface (called from ap_start/connect). + * Hence get the net_device using P2P Index. Take the mutex lock and call slsi_p2p_vif_deactivate. + */ +void slsi_p2p_group_start_remove_unsync_vif(struct slsi_dev *sdev) +{ + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif = NULL; + + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Starting P2P Group - Remove unsync vif\n"); + + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2P); + if (!dev) { + SLSI_ERR(sdev, "Failed to deactivate p2p vif as dev is not found\n"); + return; + } + + ndev_vif = netdev_priv(dev); + + if (WARN_ON(!(SLSI_IS_P2P_UNSYNC_VIF(ndev_vif)))) + return; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + slsi_p2p_vif_deactivate(sdev, dev, true); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +/** + * Called only for P2P Device mode (p2p0 interface) to store the Probe Response IEs + * which would be used in Listen (ROC) state. + * If the IEs are received in Listen Offload mode, then configure the IEs in firmware. + */ +int slsi_p2p_dev_probe_rsp_ie(struct slsi_dev *sdev, struct net_device *dev, u8 *probe_rsp_ie, size_t probe_rsp_ie_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int ret = 0; + + SLSI_UNUSED_PARAMETER(sdev); + + if (!SLSI_IS_P2P_UNSYNC_VIF(ndev_vif)) { + SLSI_NET_ERR(dev, "Incorrect vif type - Not unsync vif\n"); + kfree(probe_rsp_ie); + return -EINVAL; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Received Probe Rsp IE len = %zu, Current IE len = %zu\n", probe_rsp_ie_len, ndev_vif->unsync.probe_rsp_ies_len); + + if (!ndev_vif->unsync.listen_offload) { /* ROC */ + /* Store the IEs. Upon receiving it on subsequent occassions, store only if IEs have changed */ + if (ndev_vif->unsync.probe_rsp_ies_len != probe_rsp_ie_len) /* Check if IE length changed */ + ndev_vif->unsync.ies_changed = true; + else if (memcmp(ndev_vif->unsync.probe_rsp_ies, probe_rsp_ie, probe_rsp_ie_len) != 0) /* Check if IEs changed */ + ndev_vif->unsync.ies_changed = true; + else { /* No change in IEs */ + kfree(probe_rsp_ie); + goto exit; + } + + slsi_unsync_vif_set_probe_rsp_ie(ndev_vif, probe_rsp_ie, probe_rsp_ie_len); + } else { /* P2P Listen Offloading */ + if (sdev->p2p_state == P2P_LISTENING) { + ret = slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_PROBE_RESPONSE, probe_rsp_ie, probe_rsp_ie_len); + if (ret != 0) { + SLSI_NET_ERR(dev, "Listen Offloading: Setting Probe Response IEs for unsync vif failed\n"); + ndev_vif->unsync.listen_offload = false; + slsi_p2p_vif_deactivate(sdev, dev, true); + } + } + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return ret; +} + +/** + * This should be called only for P2P Device mode (p2p0 interface). NULL IEs to clear Probe Response IEs are not updated + * in driver to avoid configuring the Probe Response IEs to firmware on every ROC. + * Use this call as a cue to stop any ongoing P2P scan as there is no API from user space for cancelling scan. + * If ROC was in progress as part of P2P_FIND then Cancel ROC will be received. + */ +int slsi_p2p_dev_null_ies(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct cfg80211_scan_info info = {.aborted = true}; + + if (!SLSI_IS_P2P_UNSYNC_VIF(ndev_vif)) { + SLSI_NET_ERR(dev, "Incorrect vif type - Not unsync vif\n"); + return -EINVAL; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG3(dev, SLSI_CFG80211, "Probe Rsp NULL IEs\n"); + + if (sdev->p2p_state == P2P_SCANNING) { + struct sk_buff *scan_result; + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Stop Find - Abort ongoing P2P scan\n"); + + (void)slsi_mlme_del_scan(sdev, dev, ((ndev_vif->ifnum << 8) | SLSI_SCAN_HW_ID), false); + + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + scan_result = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + while (scan_result) { + slsi_rx_scan_pass_to_cfg80211(sdev, dev, scan_result); + scan_result = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + + WARN_ON(!ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + cfg80211_scan_done(ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req, &info); +#else + cfg80211_scan_done(ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req, true); +#endif + + ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req = NULL; + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + + if (ndev_vif->activated) { + /* Supplicant has stopped FIND. Also clear Probe Response IEs in firmware and driver + * as Cancel ROC will not be sent as driver was not in Listen + */ + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Stop Find - Clear Probe Response IEs in firmware\n"); + if (slsi_mlme_add_info_elements(sdev, dev, FAPI_PURPOSE_PROBE_RESPONSE, NULL, 0) != 0) + SLSI_NET_ERR(dev, "Clearing Probe Response IEs failed for unsync vif\n"); + slsi_unsync_vif_set_probe_rsp_ie(ndev_vif, NULL, 0); + + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + } else { + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_NO_VIF); + } + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return 0; +} + +/** + * Returns the P2P public action frame subtype. + * Returns SLSI_P2P_PA_INVALID if it is not a P2P public action frame. + */ +int slsi_p2p_get_public_action_subtype(const struct ieee80211_mgmt *mgmt) +{ + int subtype = SLSI_P2P_PA_INVALID; + /* Vendor specific Public Action (0x09), P2P OUI (0x50, 0x6f, 0x9a), P2P Subtype (0x09) */ + u8 p2p_pa_frame[5] = { 0x09, 0x50, 0x6f, 0x9a, 0x09 }; + u8 *action = (u8 *)&mgmt->u.action.u; + + if (memcmp(&action[0], p2p_pa_frame, 5) == 0) { + subtype = action[5]; + } else { + /* For service discovery action frames dummy subtype is used */ + switch (action[0]) { + case SLSI_PA_GAS_INITIAL_REQ: + case SLSI_PA_GAS_INITIAL_RSP: + case SLSI_PA_GAS_COMEBACK_REQ: + case SLSI_PA_GAS_COMEBACK_RSP: + subtype = (action[0] | SLSI_PA_GAS_DUMMY_SUBTYPE_MASK); + break; + } + } + + return subtype; +} + +/** + * Returns the P2P status code of Status attribute of the GO Neg Rsp frame. + * Returns -1 if status attribute is NOT found. + */ +int slsi_p2p_get_go_neg_rsp_status(struct net_device *dev, const struct ieee80211_mgmt *mgmt) +{ + int status = -1; + u8 p2p_oui_type[4] = { 0x50, 0x6f, 0x9a, 0x09 }; + u8 *action = (u8 *)&mgmt->u.action.u; + u8 *vendor_ie = &action[7]; /* 1 (0x09), 4 (0x50, 0x6f, 0x9a, 0x09), 1 (0x01), 1 (Dialog Token) */ + u8 ie_length, elem_idx; + u16 attr_length; + + while (vendor_ie && (*vendor_ie == SLSI_WLAN_EID_VENDOR_SPECIFIC)) { + ie_length = vendor_ie[1]; + + if (memcmp(&vendor_ie[2], p2p_oui_type, 4) == 0) { + elem_idx = 6; /* 1 (Id - 0xdd) + 1 (Length) + 4 (OUI and Type) */ + + while (ie_length > elem_idx) { + attr_length = ((vendor_ie[elem_idx + 1]) | (vendor_ie[elem_idx + 2] << 8)); + + if (vendor_ie[elem_idx] == SLSI_P2P_STATUS_ATTR_ID) { + SLSI_NET_DBG3(dev, SLSI_CFG80211, "Status Attribute Found, attr_length = %d, value (%u %u %u %u)\n", + attr_length, vendor_ie[elem_idx], vendor_ie[elem_idx + 1], vendor_ie[elem_idx + 2], vendor_ie[elem_idx + 3]); + status = vendor_ie[elem_idx + 3]; + break; + } + elem_idx += 3 + attr_length; + } + + break; + } + vendor_ie += 2 + ie_length; + } + + SLSI_UNUSED_PARAMETER(dev); + + return status; +} + +/** + * Returns the next expected P2P public action frame subtype for input subtype. + * Returns SLSI_P2P_PA_INVALID if no frame is expected. + */ +u8 slsi_p2p_get_exp_peer_frame_subtype(u8 subtype) +{ + switch (subtype) { + /* Peer response is expected for following frames */ + case SLSI_P2P_PA_GO_NEG_REQ: + case SLSI_P2P_PA_GO_NEG_RSP: + case SLSI_P2P_PA_INV_REQ: + case SLSI_P2P_PA_DEV_DISC_REQ: + case SLSI_P2P_PA_PROV_DISC_REQ: + case SLSI_PA_GAS_INITIAL_REQ_SUBTYPE: + case SLSI_PA_GAS_COMEBACK_REQ_SUBTYPE: + return subtype + 1; + default: + return SLSI_P2P_PA_INVALID; + } +} + +void slsi_wlan_dump_public_action_subtype(struct slsi_dev *sdev, struct ieee80211_mgmt *mgmt, bool tx) +{ + u8 action_code = ((u8 *)&mgmt->u.action.u)[0]; + u8 action_category = mgmt->u.action.category; + char *tx_rx_string = "Received"; + char wnm_action_fields[28][35] = { "Event Request", "Event Report", "Diagnostic Request", + "Diagnostic Report", "Location Configuration Request", + "Location Configuration Response", "BSS Transition Management Query", + "BSS Transition Management Request", + "BSS Transition Management Response", "FMS Request", "FMS Response", + "Collocated Interference Request", "Collocated Interference Report", + "TFS Request", "TFS Response", "TFS Notify", "WNM Sleep Mode Request", + "WNM Sleep Mode Response", "TIM Broadcast Request", + "TIM Broadcast Response", "QoS Traffic Capability Update", + "Channel Usage Request", "Channel Usage Response", "DMS Request", + "DMS Response", "Timing Measurement Request", + "WNM Notification Request", "WNM Notification Response" }; + + if (tx) + tx_rx_string = "Send"; + + switch (action_category) { + case WLAN_CATEGORY_RADIO_MEASUREMENT: + switch (action_code) { + case SLSI_RM_RADIO_MEASUREMENT_REQ: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Radio Measurement Req)\n", tx_rx_string); + break; + case SLSI_RM_RADIO_MEASUREMENT_REP: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Radio Measurement Rep)\n", tx_rx_string); + break; + case SLSI_RM_LINK_MEASUREMENT_REQ: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Link Measurement Req)\n", tx_rx_string); + break; + case SLSI_RM_LINK_MEASUREMENT_REP: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Link Measurement Rep)\n", tx_rx_string); + break; + case SLSI_RM_NEIGH_REP_REQ: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Neighbor Report Req)\n", tx_rx_string); + break; + case SLSI_RM_NEIGH_REP_RSP: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Neighbor Report Resp)\n", tx_rx_string); + break; + default: + SLSI_INFO(sdev, "%s Radio Measurement Frame (Reserved)\n", tx_rx_string); + } + break; + case WLAN_CATEGORY_PUBLIC: + switch (action_code) { + case SLSI_PA_GAS_INITIAL_REQ: + SLSI_DBG1_NODEV(SLSI_CFG80211, "%s: GAS Initial Request\n", tx ? "TX" : "RX"); + break; + case SLSI_PA_GAS_INITIAL_RSP: + SLSI_DBG1_NODEV(SLSI_CFG80211, "%s: GAS Initial Response\n", tx ? "TX" : "RX"); + break; + case SLSI_PA_GAS_COMEBACK_REQ: + SLSI_DBG1_NODEV(SLSI_CFG80211, "%s: GAS Comeback Request\n", tx ? "TX" : "RX"); + break; + case SLSI_PA_GAS_COMEBACK_RSP: + SLSI_DBG1_NODEV(SLSI_CFG80211, "%s: GAS Comeback Response\n", tx ? "TX" : "RX"); + break; + default: + SLSI_DBG1_NODEV(SLSI_CFG80211, "Unknown GAS Frame : %d\n", action_code); + } + break; + case WLAN_CATEGORY_WNM: + if (action_code >= SLSI_WNM_ACTION_FIELD_MIN && action_code <= SLSI_WNM_ACTION_FIELD_MAX) + SLSI_INFO(sdev, "%s WNM Frame (%s)\n", tx_rx_string, wnm_action_fields[action_code]); + else + SLSI_INFO(sdev, "%s WNM Frame (Reserved)\n", tx_rx_string); + break; + } +} + +void slsi_abort_sta_scan(struct slsi_dev *sdev) +{ + struct net_device *wlan_net_dev = NULL; + struct netdev_vif *ndev_vif; + struct cfg80211_scan_info info = {.aborted = true}; + + wlan_net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + + if (!wlan_net_dev) { + SLSI_ERR(sdev, "Dev not found\n"); + return; + } + + ndev_vif = netdev_priv(wlan_net_dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) { + struct sk_buff *scan_result; + + SLSI_DBG2(sdev, SLSI_CFG80211, "Abort ongoing WLAN scan\n"); + (void)slsi_mlme_del_scan(sdev, wlan_net_dev, ((ndev_vif->ifnum << 8) | SLSI_SCAN_HW_ID), false); + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + scan_result = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + while (scan_result) { + slsi_rx_scan_pass_to_cfg80211(sdev, wlan_net_dev, scan_result); + scan_result = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + cfg80211_scan_done(ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req, &info); +#else + cfg80211_scan_done(ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req, true); +#endif + + ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req = NULL; + ndev_vif->scan[SLSI_SCAN_HW_ID].requeue_timeout_work = false; + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +/** + * Returns a slsi_dhcp_tx enum value after verifying whether the 802.11 packet in skb + * is a DHCP packet (identified by UDP port numbers) + */ +int slsi_is_dhcp_packet(u8 *data) +{ + u8 *p; + int ret = SLSI_TX_IS_NOT_DHCP; + + p = data + SLSI_IP_TYPE_OFFSET; + + if (*p == SLSI_IP_TYPE_UDP) { + u16 source_port, dest_port; + + p = data + SLSI_IP_SOURCE_PORT_OFFSET; + source_port = p[0] << 8 | p[1]; + p = data + SLSI_IP_DEST_PORT_OFFSET; + dest_port = p[0] << 8 | p[1]; + if ((source_port == SLSI_DHCP_CLIENT_PORT) && (dest_port == SLSI_DHCP_SERVER_PORT)) + ret = SLSI_TX_IS_DHCP_CLIENT; + else if ((source_port == SLSI_DHCP_SERVER_PORT) && (dest_port == SLSI_DHCP_CLIENT_PORT)) + ret = SLSI_TX_IS_DHCP_SERVER; + } + + return ret; +} + +#ifdef CONFIG_SCSC_WLAN_PRIORITISE_IMP_FRAMES +int slsi_is_tcp_sync_packet(struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + /* for AP type (AP or P2P Go) check if the packet is local or intra BSS. If intra BSS then + * the IP header and TCP header are not set; so return 0 + */ + if ((ndev_vif->vif_type == FAPI_VIFTYPE_AP) && (compare_ether_addr(eth_hdr(skb)->h_source, dev->dev_addr) != 0)) + return 0; + if (be16_to_cpu(eth_hdr(skb)->h_proto) != ETH_P_IP) + return 0; + if (ip_hdr(skb)->protocol != IPPROTO_TCP) + return 0; + if (!skb_transport_header_was_set(skb)) + return 0; + if (tcp_hdr(skb)->syn) + return 1; + + return 0; +} + +int slsi_is_dns_packet(u8 *data) +{ + u8 *p; + + p = data + SLSI_IP_TYPE_OFFSET; + + if (*p == SLSI_IP_TYPE_UDP) { + u16 dest_port; + + p = data + SLSI_IP_DEST_PORT_OFFSET; + dest_port = p[0] << 8 | p[1]; + if (dest_port == SLSI_DNS_DEST_PORT) /* 0x0035 */ + return 1; + } + + return 0; +} + +int slsi_is_mdns_packet(u8 *data) +{ + u8 *p; + + p = data + SLSI_IP_TYPE_OFFSET; + + if (*p == SLSI_IP_TYPE_UDP) { + u16 dest_port; + + p = data + SLSI_IP_DEST_PORT_OFFSET; + dest_port = p[0] << 8 | p[1]; + if (dest_port == SLSI_MDNS_DEST_PORT) + return 1; + } + return 0; +} +#endif + +int slsi_ap_prepare_add_info_ies(struct netdev_vif *ndev_vif, const u8 *ies, size_t ies_len) +{ + const u8 *wps_p2p_ies = NULL; + size_t wps_p2p_ie_len = 0; + + /* The ies may contain Extended Capability followed by WPS IE. The Extended capability IE needs to be excluded. */ + wps_p2p_ies = cfg80211_find_ie(SLSI_WLAN_EID_VENDOR_SPECIFIC, ies, ies_len); + if (wps_p2p_ies) { + size_t temp_len = wps_p2p_ies - ies; + + wps_p2p_ie_len = ies_len - temp_len; + } + + SLSI_NET_DBG2(ndev_vif->wdev.netdev, SLSI_MLME, "WPA IE len = %zu, WMM IE len = %zu, IEs len = %zu, WPS_P2P IEs len = %zu\n", + ndev_vif->ap.wpa_ie_len, ndev_vif->ap.wmm_ie_len, ies_len, wps_p2p_ie_len); + + ndev_vif->ap.add_info_ies_len = ndev_vif->ap.wpa_ie_len + ndev_vif->ap.wmm_ie_len + wps_p2p_ie_len; + ndev_vif->ap.add_info_ies = kmalloc(ndev_vif->ap.add_info_ies_len, GFP_KERNEL); /* Caller needs to free this */ + + if (!ndev_vif->ap.add_info_ies) { + SLSI_NET_DBG1(ndev_vif->wdev.netdev, SLSI_MLME, "Failed to allocate memory for IEs\n"); + ndev_vif->ap.add_info_ies_len = 0; + return -ENOMEM; + } + + if (ndev_vif->ap.cache_wpa_ie) { + memcpy(ndev_vif->ap.add_info_ies, ndev_vif->ap.cache_wpa_ie, ndev_vif->ap.wpa_ie_len); + ndev_vif->ap.add_info_ies += ndev_vif->ap.wpa_ie_len; + } + + if (ndev_vif->ap.cache_wmm_ie) { + memcpy(ndev_vif->ap.add_info_ies, ndev_vif->ap.cache_wmm_ie, ndev_vif->ap.wmm_ie_len); + ndev_vif->ap.add_info_ies += ndev_vif->ap.wmm_ie_len; + } + + if (wps_p2p_ies) { + memcpy(ndev_vif->ap.add_info_ies, wps_p2p_ies, wps_p2p_ie_len); + ndev_vif->ap.add_info_ies += wps_p2p_ie_len; + } + + ndev_vif->ap.add_info_ies -= ndev_vif->ap.add_info_ies_len; + + return 0; +} + +/* Set the correct bit in the channel sets */ +static void slsi_roam_channel_cache_add_channel(struct slsi_roaming_network_map_entry *network_map, u8 channel) +{ + if (channel <= 14) + network_map->channels_24_ghz |= (1 << channel); + else if (channel >= 36 && channel <= 64) /* Uni1 */ + network_map->channels_5_ghz |= (1 << ((channel - 36) / 4)); + else if (channel >= 100 && channel <= 140) /* Uni2 */ + network_map->channels_5_ghz |= (1 << (8 + ((channel - 100) / 4))); + else if (channel >= 149 && channel <= 165) /* Uni3 */ + network_map->channels_5_ghz |= (1 << (24 + ((channel - 149) / 4))); +} + +void slsi_roam_channel_cache_add_entry(struct slsi_dev *sdev, struct net_device *dev, const u8 *ssid, const u8 *bssid, u8 channel) +{ + struct list_head *pos; + int found = 0; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + list_for_each(pos, &ndev_vif->sta.network_map) { + struct slsi_roaming_network_map_entry *network_map = list_entry(pos, struct slsi_roaming_network_map_entry, list); + + if (network_map->ssid.ssid_len == ssid[1] && + memcmp(network_map->ssid.ssid, &ssid[2], ssid[1]) == 0) { + found = 1; + network_map->last_seen_jiffies = jiffies; + if (network_map->only_one_ap_seen && memcmp(network_map->initial_bssid, bssid, ETH_ALEN) != 0) + network_map->only_one_ap_seen = false; + slsi_roam_channel_cache_add_channel(network_map, channel); + break; + } + } + if (!found) { + struct slsi_roaming_network_map_entry *network_map; + + SLSI_NET_DBG3(dev, SLSI_MLME, "New Entry : Channel: %d : %.*s\n", channel, ssid[1], &ssid[2]); + network_map = kmalloc(sizeof(*network_map), GFP_ATOMIC); + if (network_map) { + network_map->ssid.ssid_len = ssid[1]; + memcpy(network_map->ssid.ssid, &ssid[2], ssid[1]); + network_map->channels_24_ghz = 0; + network_map->channels_5_ghz = 0; + network_map->last_seen_jiffies = jiffies; + SLSI_ETHER_COPY(network_map->initial_bssid, bssid); + network_map->only_one_ap_seen = true; + slsi_roam_channel_cache_add_channel(network_map, channel); + list_add(&network_map->list, &ndev_vif->sta.network_map); + } else { + SLSI_ERR(sdev, "New Entry : %.*s kmalloc() failed\n", ssid[1], &ssid[2]); + } + } +} + +void slsi_roam_channel_cache_add(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + size_t mgmt_len = fapi_get_mgmtlen(skb); + int ielen = mgmt_len - (mgmt->u.beacon.variable - (u8 *)mgmt); + u32 freq = fapi_get_u16(skb, u.mlme_scan_ind.channel_frequency) / 2; + const u8 *scan_ds = cfg80211_find_ie(WLAN_EID_DS_PARAMS, mgmt->u.beacon.variable, ielen); + const u8 *scan_ht = cfg80211_find_ie(WLAN_EID_HT_OPERATION, mgmt->u.beacon.variable, ielen); + const u8 *scan_ssid = cfg80211_find_ie(WLAN_EID_SSID, mgmt->u.beacon.variable, ielen); + u8 chan = 0; + + /* Use the DS or HT channel as the Offchannel results mean the RX freq is not reliable */ + if (scan_ds) + chan = scan_ds[2]; + else if (scan_ht) + chan = scan_ht[2]; + else + chan = ieee80211_frequency_to_channel(freq); + + if (chan) { + enum nl80211_band band = NL80211_BAND_2GHZ; + + if (chan > 14) + band = NL80211_BAND_5GHZ; + +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (freq != (u32)ieee80211_channel_to_frequency(chan, band)) { + if (band == NL80211_BAND_5GHZ && freq < 3000) + SLSI_NET_DBG2(dev, SLSI_MLME, "Off Band Result : mlme_scan_ind(freq:%d) != DS(freq:%d)\n", freq, ieee80211_channel_to_frequency(chan, band)); + + if (band == NL80211_BAND_2GHZ && freq > 3000) + SLSI_NET_DBG2(dev, SLSI_MLME, "Off Band Result : mlme_scan_ind(freq:%d) != DS(freq:%d)\n", freq, ieee80211_channel_to_frequency(chan, band)); + } +#endif + } + + if (!scan_ssid || !scan_ssid[1] || scan_ssid[1] > 32) { + SLSI_NET_DBG3(dev, SLSI_MLME, "SSID not defined : Could not find SSID ie or Hidden\n"); + return; + } + + slsi_roam_channel_cache_add_entry(sdev, dev, scan_ssid, mgmt->bssid, chan); +} + +void slsi_roam_channel_cache_prune(struct net_device *dev, int seconds) +{ + struct slsi_roaming_network_map_entry *network_map; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct list_head *pos, *q; + unsigned long now = jiffies; + unsigned long age; + + list_for_each_safe(pos, q, &ndev_vif->sta.network_map) { + network_map = list_entry(pos, struct slsi_roaming_network_map_entry, list); + age = (now - network_map->last_seen_jiffies) / HZ; + + if (time_after_eq(now, network_map->last_seen_jiffies + (seconds * HZ))) { + list_del(pos); + kfree(network_map); + } + } +} + +int slsi_roam_channel_cache_get_channels_int(struct net_device *dev, struct slsi_roaming_network_map_entry *network_map, u8 *channels) +{ + int index = 0; + int i; + + SLSI_UNUSED_PARAMETER(dev); + + /* 2.4 Ghz Channels */ + for (i = 1; i <= 14; i++) + if (network_map->channels_24_ghz & (1 << i)) { + channels[index] = i; + index++; + } + + /* 5 Ghz Uni1 Channels */ + for (i = 36; i <= 64; i += 4) + if (network_map->channels_5_ghz & (1 << ((i - 36) / 4))) { + channels[index] = i; + index++; + } + + /* 5 Ghz Uni2 Channels */ + for (i = 100; i <= 140; i += 4) + if (network_map->channels_5_ghz & (1 << (8 + ((i - 100) / 4)))) { + channels[index] = i; + index++; + } + + /* 5 Ghz Uni3 Channels */ + for (i = 149; i <= 165; i += 4) + if (network_map->channels_5_ghz & (1 << (24 + ((i - 149) / 4)))) { + channels[index] = i; + index++; + } + return index; +} + +static struct slsi_roaming_network_map_entry *slsi_roam_channel_cache_get(struct net_device *dev, const u8 *ssid) +{ + struct slsi_roaming_network_map_entry *network_map = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct list_head *pos; + + if (WARN_ON(!ssid)) + return NULL; + + list_for_each(pos, &ndev_vif->sta.network_map) { + network_map = list_entry(pos, struct slsi_roaming_network_map_entry, list); + if (network_map->ssid.ssid_len == ssid[1] && + memcmp(network_map->ssid.ssid, &ssid[2], ssid[1]) == 0) + break; + } + return network_map; +} + +u32 slsi_roam_channel_cache_get_channels(struct net_device *dev, const u8 *ssid, u8 *channels) +{ + u32 channels_count = 0; + struct slsi_roaming_network_map_entry *network_map; + + network_map = slsi_roam_channel_cache_get(dev, ssid); + if (network_map) + channels_count = slsi_roam_channel_cache_get_channels_int(dev, network_map, channels); + + return channels_count; +} + +static bool slsi_roam_channel_cache_single_ap(struct net_device *dev, const u8 *ssid) +{ + bool only_one_ap_seen = true; + struct slsi_roaming_network_map_entry *network_map; + + network_map = slsi_roam_channel_cache_get(dev, ssid); + if (network_map) + only_one_ap_seen = network_map->only_one_ap_seen; + + return only_one_ap_seen; +} + +int slsi_roaming_scan_configure_channels(struct slsi_dev *sdev, struct net_device *dev, const u8 *ssid, u8 *channels) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u32 cached_channels_count; + + SLSI_UNUSED_PARAMETER(sdev); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + WARN_ON(!ndev_vif->activated); + WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION); + + cached_channels_count = slsi_roam_channel_cache_get_channels(dev, ssid, channels); + if (slsi_roam_channel_cache_single_ap(dev, ssid)) { + SLSI_NET_DBG3(dev, SLSI_MLME, "Skip Roaming Scan for Single AP %.*s\n", ssid[1], &ssid[2]); + return 0; + } + + SLSI_NET_DBG3(dev, SLSI_MLME, "Roaming Scan Channels. %d cached\n", cached_channels_count); + + return cached_channels_count; +} + +int slsi_send_acs_event(struct slsi_dev *sdev, struct slsi_acs_selected_channels acs_selected_channels) +{ + struct sk_buff *skb = NULL; + u8 err = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_VENDOR_ACS_EVENT, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_VENDOR_ACS_EVENT, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for VENDOR ACS event\n"); + return -ENOMEM; + } + err |= nla_put_u8(skb, SLSI_ACS_ATTR_PRIMARY_CHANNEL, acs_selected_channels.pri_channel); + err |= nla_put_u8(skb, SLSI_ACS_ATTR_SECONDARY_CHANNEL, acs_selected_channels.sec_channel); + err |= nla_put_u8(skb, SLSI_ACS_ATTR_VHT_SEG0_CENTER_CHANNEL, acs_selected_channels.vht_seg0_center_ch); + err |= nla_put_u8(skb, SLSI_ACS_ATTR_VHT_SEG1_CENTER_CHANNEL, acs_selected_channels.vht_seg1_center_ch); + err |= nla_put_u16(skb, SLSI_ACS_ATTR_CHWIDTH, acs_selected_channels.ch_width); + err |= nla_put_u8(skb, SLSI_ACS_ATTR_HW_MODE, acs_selected_channels.hw_mode); + SLSI_DBG3(sdev, SLSI_MLME, "pri_channel=%d,sec_channel=%d,vht_seg0_center_ch=%d," + "vht_seg1_center_ch=%d, ch_width=%d, hw_mode=%d\n", + acs_selected_channels.pri_channel, acs_selected_channels.sec_channel, + acs_selected_channels.vht_seg0_center_ch, acs_selected_channels.vht_seg1_center_ch, + acs_selected_channels.ch_width, acs_selected_channels.hw_mode); + if (err) { + SLSI_ERR_NODEV("Failed nla_put err=%d\n", err); + slsi_kfree_skb(skb); + return -EINVAL; + } + SLSI_INFO(sdev, "Event: SLSI_NL80211_VENDOR_ACS_EVENT(%d)\n", SLSI_NL80211_VENDOR_ACS_EVENT); + cfg80211_vendor_event(skb, GFP_KERNEL); + return 0; +} + +#ifdef CONFIG_SCSC_WLAN_WES_NCHO +int slsi_is_wes_action_frame(const struct ieee80211_mgmt *mgmt) +{ + int r = 0; + /* Vendor specific Action (0x7f), SAMSUNG OUI (0x00, 0x00, 0xf0) */ + u8 wes_vs_action_frame[4] = { 0x7f, 0x00, 0x00, 0xf0 }; + u8 *action = (u8 *)&mgmt->u.action; + + if (memcmp(action, wes_vs_action_frame, 4) == 0) + r = 1; + + return r; +} +#endif + +static u32 slsi_remap_reg_rule_flags(u8 flags) +{ + u32 remapped_flags = 0; + + if (flags & SLSI_REGULATORY_DFS) + remapped_flags |= NL80211_RRF_DFS; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (flags & SLSI_REGULATORY_NO_OFDM) + remapped_flags |= NL80211_RRF_NO_OFDM; +#endif +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 13, 0)) + if (flags & SLSI_REGULATORY_NO_IR) + remapped_flags |= NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS; +#endif + if (flags & SLSI_REGULATORY_NO_INDOOR) + remapped_flags |= NL80211_RRF_NO_INDOOR; + if (flags & SLSI_REGULATORY_NO_OUTDOOR) + remapped_flags |= NL80211_RRF_NO_OUTDOOR; + + return remapped_flags; +} + +static void slsi_reg_mib_to_regd(struct slsi_mib_data *mib, struct slsi_802_11d_reg_domain *domain_info) +{ + int i = 0; + int num_rules = 0; + u16 freq; + u8 byte_val; + struct ieee80211_reg_rule *reg_rule; + + domain_info->regdomain->alpha2[0] = *(u8 *)(&mib->data[i]); + i++; + + domain_info->regdomain->alpha2[1] = *(u8 *)(&mib->data[i]); + i++; + + domain_info->regdomain->dfs_region = *(u8 *)(&mib->data[i]); + i++; + + while (i < mib->dataLength) { + reg_rule = &domain_info->regdomain->reg_rules[num_rules]; + + /* start freq 2 bytes */ + freq = __le16_to_cpu(*(u16 *)(&mib->data[i])); + reg_rule->freq_range.start_freq_khz = MHZ_TO_KHZ(freq); + + /* end freq 2 bytes */ + freq = __le16_to_cpu(*(u16 *)(&mib->data[i + 2])); + reg_rule->freq_range.end_freq_khz = MHZ_TO_KHZ(freq); + + /* Max Bandwidth 1 byte */ + byte_val = *(u8 *)(&mib->data[i + 4]); + reg_rule->freq_range.max_bandwidth_khz = MHZ_TO_KHZ(byte_val); + + /* max_antenna_gain is obsolute now.*/ + reg_rule->power_rule.max_antenna_gain = 0; + + /* Max Power 1 byte */ + byte_val = *(u8 *)(&mib->data[i + 5]); + reg_rule->power_rule.max_eirp = DBM_TO_MBM(byte_val); + + /* Flags 1 byte */ + reg_rule->flags = slsi_remap_reg_rule_flags(*(u8 *)(&mib->data[i + 6])); + + i += 7; + + num_rules++; /* Num of reg rules */ + } + + domain_info->regdomain->n_reg_rules = num_rules; +} + +void slsi_reset_channel_flags(struct slsi_dev *sdev) +{ + enum nl80211_band band; + struct ieee80211_channel *chan; + int i; + struct wiphy *wiphy = sdev->wiphy; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + for (band = 0; band < NUM_NL80211_BANDS; band++) { +#else + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { +#endif + if (!wiphy->bands[band]) + continue; + for (i = 0; i < wiphy->bands[band]->n_channels; i++) { + chan = &wiphy->bands[band]->channels[i]; + chan->flags = 0; + } + } +} + +int slsi_read_regulatory_rules(struct slsi_dev *sdev, struct slsi_802_11d_reg_domain *domain_info, const char *alpha2) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_entry mib_val; + int r = 0; + int rx_len = 0; + int len = 0; + int index; + + index = slsi_country_to_index(domain_info, alpha2); + + if (index == -1) { + SLSI_ERR(sdev, "Unsupported index\n"); + return -EINVAL; + } + + slsi_mib_encode_get(&mibreq, SLSI_PSID_UNIFI_REGULATORY_PARAMETERS, index); + + /* Max of 6 regulatory constraints. + * each constraint start_freq(2 byte), end_freq(2 byte), Band width(1 byte), Max power(1 byte), + * rules flag (1 byte) + * firmware can have a max of 6 rules for a country. + */ + /* PSID header (5 bytes) + ((3 bytes) alpha2 code + dfs) + (max of 50 regulatory rules * 7 bytes each row) + MIB status(1) */ + mibrsp.dataLength = 5 + 3 + (SLSI_MIB_REG_RULES_MAX * 7) + 1; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Failed to alloc for Mib response\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, + mibrsp.data, mibrsp.dataLength, &rx_len); + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_len; + + len = slsi_mib_decode(&mibrsp, &mib_val); + + if (len == 0) { + kfree(mibrsp.data); + SLSI_ERR(sdev, "Mib decode error\n"); + return -EINVAL; + } + slsi_reg_mib_to_regd(&mib_val.value.u.octetValue, domain_info); + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + } + + kfree(mibrsp.data); + return r; +} + +static int slsi_country_to_index(struct slsi_802_11d_reg_domain *domain_info, const char *alpha2) +{ + int index = 0; + bool index_found = false; + + SLSI_DBG3_NODEV(SLSI_MLME, "\n"); + if (domain_info->countrylist) { + for (index = 0; index < domain_info->country_len; index += 2) { + if (memcmp(&domain_info->countrylist[index], alpha2, 2) == 0) { + index_found = true; + break; + } + } + + /* If the set country is not present in the country list, fall back to + * world domain i.e. regulatory rules index = 1 + */ + if (index_found) + return (index / 2) + 1; + else + return 1; + } + + return -1; +} + +/* Set the rssi boost value of a particular band as set in the SETJOINPREFER command*/ +int slsi_set_mib_rssi_boost(struct slsi_dev *sdev, struct net_device *dev, u16 psid, int index, int boost) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + int error = SLSI_MIB_STATUS_FAILURE; + + SLSI_DBG2(sdev, SLSI_MLME, "Set rssi boost: %d\n", boost); + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->device_config_mutex)); + if (slsi_mib_encode_int(&mib_data, psid, boost, index) == SLSI_MIB_STATUS_SUCCESS) + if (mib_data.dataLength) { + error = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + if (error) + SLSI_ERR(sdev, "Err Setting MIB failed. error = %d\n", error); + kfree(mib_data.data); + } + + return error; +} + +#ifdef CONFIG_SCSC_WLAN_LOW_LATENCY_MODE +int slsi_set_mib_soft_roaming_enabled(struct slsi_dev *sdev, struct net_device *dev, bool enable) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + int error = SLSI_MIB_STATUS_FAILURE; + + if (slsi_mib_encode_bool(&mib_data, SLSI_PSID_UNIFI_ROAM_SOFT_ROAMING_ENABLED, + enable, 0) == SLSI_MIB_STATUS_SUCCESS) + if (mib_data.dataLength) { + error = slsi_mlme_set(sdev, dev, mib_data.data, mib_data.dataLength); + if (error) + SLSI_ERR(sdev, "Err Setting MIB failed. error = %d\n", error); + kfree(mib_data.data); + } + + return error; +} +#endif + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +int slsi_read_enhanced_arp_rx_count_by_lower_mac(struct slsi_dev *sdev, struct net_device *dev, u16 psid) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_entry mib_val; + int r = 0; + int rx_len = 0; + int len = 0; + + SLSI_DBG3(sdev, SLSI_MLME, "\n"); + + slsi_mib_encode_get(&mibreq, psid, 0); + + mibrsp.dataLength = 10; /* PSID header(5) + uint 4 bytes + status(1) */ + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Failed to alloc for Mib response\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, dev, mibreq.data, mibreq.dataLength, mibrsp.data, + mibrsp.dataLength, &rx_len); + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_len; + len = slsi_mib_decode(&mibrsp, &mib_val); + + if (len == 0) { + kfree(mibrsp.data); + SLSI_ERR(sdev, "Mib decode error\n"); + return -EINVAL; + } + ndev_vif->enhanced_arp_stats.arp_rsp_rx_count_by_lower_mac = mib_val.value.u.uintValue; + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + } + + kfree(mibrsp.data); + return r; +} + +void slsi_fill_enhanced_arp_out_of_order_drop_counter(struct netdev_vif *ndev_vif, + struct sk_buff *skb) +{ + struct ethhdr *eth_hdr; + u8 *frame; + u16 arp_opcode; + +#ifdef CONFIG_SCSC_SMAPPER + /* Check if the payload is in the SMAPPER entry */ + if (fapi_get_u16(skb, u.ma_unitdata_ind.bulk_data_descriptor) == FAPI_BULKDATADESCRIPTOR_SMAPPER) { + frame = slsi_hip_get_skb_data_from_smapper(ndev_vif->sdev, skb); + eth_hdr = (struct ethhdr *)frame; + if (!(eth_hdr)) { + SLSI_DBG2(ndev_vif->sdev, SLSI_RX, "SKB from SMAPPER is NULL\n"); + return; + } + frame = frame + sizeof(struct ethhdr); + } else { + frame = fapi_get_data(skb) + sizeof(struct ethhdr); + eth_hdr = (struct ethhdr *)fapi_get_data(skb); + } +#else + frame = fapi_get_data(skb) + sizeof(struct ethhdr); + eth_hdr = (struct ethhdr *)fapi_get_data(skb); +#endif + + arp_opcode = frame[SLSI_ARP_OPCODE_OFFSET] << 8 | frame[SLSI_ARP_OPCODE_OFFSET + 1]; + /* check if sender ip = gateway ip and it is an ARP response*/ + if ((ntohs(eth_hdr->h_proto) == ETH_P_ARP) && + (arp_opcode == SLSI_ARP_REPLY_OPCODE) && + !SLSI_IS_GRATUITOUS_ARP(frame) && + !memcmp(&frame[SLSI_ARP_SRC_IP_ADDR_OFFSET], &ndev_vif->target_ip_addr, 4)) + ndev_vif->enhanced_arp_stats.arp_rsp_count_out_of_order_drop++; +} +#endif + +void slsi_modify_ies_on_channel_switch(struct net_device *dev, struct cfg80211_ap_settings *settings, + u8 *ds_params_ie, u8 *ht_operation_ie, struct ieee80211_mgmt *mgmt, + u16 beacon_ie_head_len) +{ + slsi_modify_ies(dev, WLAN_EID_DS_PARAMS, mgmt->u.beacon.variable, + beacon_ie_head_len, 2, ieee80211_frequency_to_channel(settings->chandef.chan->center_freq)); + + slsi_modify_ies(dev, WLAN_EID_HT_OPERATION, (u8 *)settings->beacon.tail, + settings->beacon.tail_len, 2, + ieee80211_frequency_to_channel(settings->chandef.chan->center_freq)); +} + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +void slsi_extract_valid_wifi_sharing_channels(struct slsi_dev *sdev) +{ + int i, j; + int p = 0; + int k = (SLSI_MAX_CHAN_5G_BAND - 1); + int flag = 0; + + for (i = 4; i >= 0 ; i--) { + for (j = 0; j <= 7 ; j++) { + if ((i == 4) && (j == 0)) + j = 1; + if (sdev->wifi_sharing_5ghz_channel[i] & (u8)(1 << (7 - j))) + sdev->valid_5g_freq[p] = slsi_5ghz_all_channels[k]; + else + sdev->valid_5g_freq[p] = 0; + p++; + k--; + if (p == SLSI_MAX_CHAN_5G_BAND) { + flag = 1; + break; + } + } + if (flag == 1) + break; + } +} + +bool slsi_if_valid_wifi_sharing_channel(struct slsi_dev *sdev, int freq) +{ + int i; + + for (i = 0; i <= (SLSI_MAX_CHAN_5G_BAND - 1) ; i++) { + if (sdev->valid_5g_freq[i] == freq) + return 1; + } + return 0; +} + +int slsi_check_if_non_indoor_non_dfs_channel(struct slsi_dev *sdev, int freq) +{ + struct ieee80211_channel *channel = NULL; + u32 chan_flags = 0; + + channel = ieee80211_get_channel(sdev->wiphy, freq); + if (!channel) { + SLSI_ERR(sdev, "Invalid frequency %d used to start AP. Channel not found\n", freq); + return 0; + } + + chan_flags = (IEEE80211_CHAN_INDOOR_ONLY | IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_DISABLED | +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 10, 13) + IEEE80211_CHAN_PASSIVE_SCAN +#else + IEEE80211_CHAN_NO_IR +#endif + ); + + if ((channel->flags) & chan_flags) + return 0; + + return 1; +} + +int slsi_select_wifi_sharing_ap_channel(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ap_settings *settings, + struct slsi_dev *sdev, int *wifi_sharing_channel_switched) +{ + struct net_device *sta_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + struct netdev_vif *ndev_sta_vif = netdev_priv(sta_dev); + int sta_frequency = ndev_sta_vif->chan->center_freq; + + SLSI_DBG1(sdev, SLSI_CFG80211, "Station connected on frequency: %d\n", sta_frequency); + + if (((sta_frequency) / 1000) == 2) { /*For 2.4GHz */ + /*if single antenna*/ +#ifdef CONFIG_SCSC_WLAN_SINGLE_ANTENNA + if ((settings->chandef.chan->center_freq) != (sta_frequency)) { + *wifi_sharing_channel_switched = 1; + settings->chandef.chan = ieee80211_get_channel(wiphy, sta_frequency); + settings->chandef.center_freq1 = sta_frequency; + } +#else + /* if dual antenna */ + if ((((settings->chandef.chan->center_freq) / 1000) == 5) && + !(slsi_check_if_channel_restricted_already(sdev, + ieee80211_frequency_to_channel(settings->chandef.chan->center_freq))) && + slsi_if_valid_wifi_sharing_channel(sdev, settings->chandef.chan->center_freq) && + slsi_check_if_non_indoor_non_dfs_channel(sdev, settings->chandef.chan->center_freq)) { + settings->chandef.chan = ieee80211_get_channel(wiphy, settings->chandef.chan->center_freq); + settings->chandef.center_freq1 = settings->chandef.chan->center_freq; + } else { + if ((settings->chandef.chan->center_freq) != (sta_frequency)) { + *wifi_sharing_channel_switched = 1; + settings->chandef.chan = ieee80211_get_channel(wiphy, sta_frequency); + settings->chandef.center_freq1 = sta_frequency; + } + } +#endif + } + + else { /* For 5GHz */ + /* For single antenna */ +#ifdef CONFIG_SCSC_WLAN_SINGLE_ANTENNA + if (!slsi_check_if_non_indoor_non_dfs_channel(sdev, sta_frequency)) + return 1; /*AP cannot start on indoor channel so we will reject request from the host*/ + if ((settings->chandef.chan->center_freq) != (sta_frequency)) { + *wifi_sharing_channel_switched = 1; + settings->chandef.chan = ieee80211_get_channel(wiphy, sta_frequency); + settings->chandef.center_freq1 = sta_frequency; + } + /* Single antenna end */ +#else + /* For Dual Antenna */ + if (((settings->chandef.chan->center_freq) / 1000) == 5) { + if (!(slsi_check_if_channel_restricted_already(sdev, + ieee80211_frequency_to_channel(sta_frequency))) && + slsi_if_valid_wifi_sharing_channel(sdev, sta_frequency) && + slsi_check_if_non_indoor_non_dfs_channel(sdev, sta_frequency)) { + if ((settings->chandef.chan->center_freq) != (sta_frequency)) { + *wifi_sharing_channel_switched = 1; + settings->chandef.chan = ieee80211_get_channel(wiphy, sta_frequency); + } + } else { + *wifi_sharing_channel_switched = 1; + settings->chandef.chan = ieee80211_get_channel(wiphy, SLSI_2G_CHANNEL_ONE); + settings->chandef.center_freq1 = SLSI_2G_CHANNEL_ONE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (!sdev->fw_2g_40mhz_enabled && (settings->chandef.width == NL80211_CHAN_WIDTH_40)) + settings->chandef.width = NL80211_CHAN_WIDTH_20; +#endif + } + } +#endif + } + + SLSI_DBG1(sdev, SLSI_CFG80211, "AP frequency chosen: %d\n", settings->chandef.chan->center_freq); + return 0; +} + +int slsi_get_byte_position(int bit) +{ + int byte_pos = 0; + + /* bit will find which bit, pos will tell which pos in the array */ + if (bit >= 8 && bit <= 15) + byte_pos = 1; + else if (bit >= 16 && bit <= 23) + byte_pos = 2; + else if (bit >= 24 && bit <= 31) + byte_pos = 3; + else if (bit >= 32 && bit <= 38) + byte_pos = 4; + + return byte_pos; +} + +int slsi_check_if_channel_restricted_already(struct slsi_dev *sdev, int channel) +{ + int i; + + for (i = 0; i < sdev->num_5g_restricted_channels; i++) + if (sdev->wifi_sharing_5g_restricted_channels[i] == channel) + return 1; + + return 0; +} + +int slsi_set_mib_wifi_sharing_5ghz_channel(struct slsi_dev *sdev, u16 psid, int res, + int offset, int readbyte, char *arg) +{ + struct slsi_mib_entry mib_entry; + struct slsi_mib_data buffer = { 0, NULL }; + int error = SLSI_MIB_STATUS_FAILURE; + int i; + int bit = 0; /* find which bit to set */ + int byte_pos = 0; /* which index to set bit among 8 larger set*/ + int freq; + int j; + int bit_mask; + int num_channels; + int p = 0; + int new_channels = 0; + int freq_to_be_checked = 0; + + mib_entry.value.type = SLSI_MIB_TYPE_OCTET; + mib_entry.value.u.octetValue.dataLength = 8; + mib_entry.value.u.octetValue.data = kmalloc(64, GFP_KERNEL); + + if (!mib_entry.value.u.octetValue.data) { + error = -ENOMEM; + goto exit; + } + + for (i = 0; i < 8; i++) + mib_entry.value.u.octetValue.data[i] = sdev->wifi_sharing_5ghz_channel[i]; + + if (res == 0) { + for (i = 0; i < 25 ; i++) + sdev->wifi_sharing_5g_restricted_channels[i] = 0; + sdev->num_5g_restricted_channels = 0; + new_channels = 1; + } else if (res == -1) { + for (i = 0; i < 8; i++) + mib_entry.value.u.octetValue.data[i] = 0x00; + + for (i = 0; i < 25 ; i++) + sdev->wifi_sharing_5g_restricted_channels[i] = 0; + + for (i = 24; i >= 0 ; i--) { + if (sdev->valid_5g_freq[i] != 0) + sdev->wifi_sharing_5g_restricted_channels[p++] = + ieee80211_frequency_to_channel(sdev->valid_5g_freq[i]); + } + sdev->num_5g_restricted_channels = p; + new_channels = 1; + } else { + num_channels = res; + + for (i = 0; i < num_channels; i++) { + offset = offset + readbyte + 1; + readbyte = slsi_str_to_int(&arg[offset], &res); + /*if channel is not already present , then only add it*/ + freq_to_be_checked = ieee80211_channel_to_frequency(res, NL80211_BAND_5GHZ); + if (slsi_if_valid_wifi_sharing_channel(sdev, freq_to_be_checked) && + (!slsi_check_if_channel_restricted_already(sdev, res))) { + if ((sdev->num_5g_restricted_channels) > 24) + break; + new_channels = 1; + sdev->wifi_sharing_5g_restricted_channels[(sdev->num_5g_restricted_channels)++] = res; + } + } + + if (new_channels) { + for (i = 0; i < (sdev->num_5g_restricted_channels); i++) { + freq = ieee80211_channel_to_frequency(sdev->wifi_sharing_5g_restricted_channels[i], + NL80211_BAND_5GHZ); + for (j = 0; j < 25; j++) { + if (slsi_5ghz_all_channels[j] == freq) { + bit = j + 14; + break; + } + } + byte_pos = slsi_get_byte_position(bit); + bit_mask = (bit % 8); + mib_entry.value.u.octetValue.data[byte_pos] &= (u8)(~(1 << (bit_mask))); + } + } + } + + if (new_channels) { + error = slsi_mib_encode_octet(&buffer, psid, mib_entry.value.u.octetValue.dataLength, + mib_entry.value.u.octetValue.data, 0); + if (error != SLSI_MIB_STATUS_SUCCESS) { + error = -ENOMEM; + goto exit; + } + + if (WARN_ON(buffer.dataLength == 0)) { + error = -EINVAL; + goto exit; + } + + error = slsi_mlme_set(sdev, NULL, buffer.data, buffer.dataLength); + kfree(buffer.data); + + if (!error) + return 0; + +exit: + SLSI_ERR(sdev, "Error in setting wifi sharing 5ghz channel. error = %d\n", error); + return error; + } + + return 0; +} +#endif +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION +int slsi_set_mac_randomisation_mask(struct slsi_dev *sdev, u8 *mac_address_mask) +{ + int r = 0; + struct slsi_mib_data mib_data = { 0, NULL }; + + SLSI_DBG1(sdev, SLSI_CFG80211, "Mask is :%pM\n", mac_address_mask); + r = slsi_mib_encode_octet(&mib_data, SLSI_PSID_UNIFI_MAC_ADDRESS_RANDOMISATION_MASK, ETH_ALEN, + mac_address_mask, 0); + if (r != SLSI_MIB_STATUS_SUCCESS) { + SLSI_ERR(sdev, "Err setting unifiMacAddrRandomistaionMask MIB. error = %d\n", r); + if (sdev->scan_addr_set) { + struct slsi_mib_data mib_data_randomization_activated = { 0, NULL }; + + r = slsi_mib_encode_bool(&mib_data_randomization_activated, + SLSI_PSID_UNIFI_MAC_ADDRESS_RANDOMISATION, 1, 0); + if (r != SLSI_MIB_STATUS_SUCCESS) { + SLSI_ERR(sdev, "UNIFI_MAC_ADDRESS_RANDOMISATION_ACTIVATED: no mem for MIB\n"); + return -ENOMEM; + } + + r = slsi_mlme_set(sdev, NULL, mib_data_randomization_activated.data, + mib_data_randomization_activated.dataLength); + + kfree(mib_data_randomization_activated.data); + + if (r) + SLSI_ERR(sdev, "Err setting unifiMacAddrRandomistaionActivated MIB. error = %d\n", r); + return r; + } + return -ENOMEM; + } + if (mib_data.dataLength == 0) { + SLSI_WARN(sdev, "Mib Data length is Zero\n"); + return -EINVAL; + } + r = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + if (r) + SLSI_ERR(sdev, "Err setting Randomized mac mask= %d\n", r); + kfree(mib_data.data); + return r; +} +#endif +/* Set the new country code and read the regulatory parameters of updated country. */ +int slsi_set_country_update_regd(struct slsi_dev *sdev, const char *alpha2_code, int size) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + char alpha2[4]; + int error = 0; + + SLSI_DBG2(sdev, SLSI_MLME, "Set country code: %c%c\n", alpha2_code[0], alpha2_code[1]); + + if (size == 4) { + memcpy(alpha2, alpha2_code, 4); + } else { + memcpy(alpha2, alpha2_code, 3); + alpha2[3] = '\0'; + } + + if (memcmp(alpha2, sdev->device_config.domain_info.regdomain->alpha2, 2) == 0) { + SLSI_DBG3(sdev, SLSI_MLME, "Country is already set to the requested country code\n"); + return 0; + } + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + + error = slsi_mib_encode_octet(&mib_data, SLSI_PSID_UNIFI_DEFAULT_COUNTRY, 3, alpha2, 0); + if (error != SLSI_MIB_STATUS_SUCCESS) { + error = -ENOMEM; + goto exit; + } + + if (WARN_ON(mib_data.dataLength == 0)) { + error = -EINVAL; + goto exit; + } + + error = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + + kfree(mib_data.data); + + if (error) { + SLSI_ERR(sdev, "Err setting country error = %d\n", error); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return -1; + } + + /* Read the regulatory params for the country */ + if (slsi_read_regulatory_rules(sdev, &sdev->device_config.domain_info, alpha2) == 0) { + slsi_reset_channel_flags(sdev); + wiphy_apply_custom_regulatory(sdev->wiphy, sdev->device_config.domain_info.regdomain); + slsi_update_supported_channels_regd_flags(sdev); + } + +exit: + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return error; +} + +/* Read unifiDisconnectTimeOut MIB */ +int slsi_read_disconnect_ind_timeout(struct slsi_dev *sdev, u16 psid) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_entry mib_val; + int r = 0; + int rx_len = 0; + int len = 0; + + SLSI_DBG3(sdev, SLSI_MLME, "\n"); + + slsi_mib_encode_get(&mibreq, psid, 0); + + mibrsp.dataLength = 10; /* PSID header(5) + uint 4 bytes + status(1) */ + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Failed to alloc for Mib response\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, mibrsp.data, + mibrsp.dataLength, &rx_len); + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_len; + len = slsi_mib_decode(&mibrsp, &mib_val); + + if (len == 0) { + kfree(mibrsp.data); + SLSI_ERR(sdev, "Mib decode error\n"); + return -EINVAL; + } + /* Add additional 1 sec delay */ + sdev->device_config.ap_disconnect_ind_timeout = ((mib_val.value.u.uintValue + 1) * 1000); + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + } + + kfree(mibrsp.data); + return r; +} + +/* Read unifiDefaultCountry MIB */ +int slsi_read_default_country(struct slsi_dev *sdev, u8 *alpha2, u16 index) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_entry mib_val; + int r = 0; + int rx_len = 0; + int len = 0; + + slsi_mib_encode_get(&mibreq, SLSI_PSID_UNIFI_DEFAULT_COUNTRY, index); + + mibrsp.dataLength = 11; /* PSID header(5) + index(1) + country code alpha2 3 bytes + status(1) */ + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Failed to alloc for Mib response\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, mibrsp.data, + mibrsp.dataLength, &rx_len); + + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_len; + len = slsi_mib_decode(&mibrsp, &mib_val); + + if (len == 0) { + kfree(mibrsp.data); + SLSI_ERR(sdev, "Mib decode error\n"); + return -EINVAL; + } + memcpy(alpha2, mib_val.value.u.octetValue.data, 2); + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + } + + kfree(mibrsp.data); + return r; +} + +int slsi_copy_country_table(struct slsi_dev *sdev, struct slsi_mib_data *mib, int len) +{ + SLSI_DBG3(sdev, SLSI_MLME, "\n"); + + kfree(sdev->device_config.domain_info.countrylist); + sdev->device_config.domain_info.countrylist = kmalloc(len, GFP_KERNEL); + + if (!sdev->device_config.domain_info.countrylist) { + SLSI_ERR(sdev, "kmalloc failed\n"); + return -EINVAL; + } + + if (!mib || !mib->data) { + SLSI_ERR(sdev, "Invalid MIB country table\n"); + return -EINVAL; + } + + memcpy(sdev->device_config.domain_info.countrylist, mib->data, len); + sdev->device_config.domain_info.country_len = len; + + return 0; +} + +/* Read unifi country list */ +int slsi_read_unifi_countrylist(struct slsi_dev *sdev, u16 psid) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_entry mib_val; + int r = 0; + int rx_len = 0; + int len = 0; + int ret; + + slsi_mib_encode_get(&mibreq, psid, 0); + + /* Fixed fields len (5) : 2 bytes(PSID) + 2 bytes (Len) + 1 byte (status) + * Data : 148 countries??? for SLSI_PSID_UNIFI_COUNTRY_LIST + */ + mibrsp.dataLength = 5 + (NUM_COUNTRY * 2); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Failed to alloc for Mib response\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, mibrsp.data, + mibrsp.dataLength, &rx_len); + + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_len; + len = slsi_mib_decode(&mibrsp, &mib_val); + + if (len == 0) { + kfree(mibrsp.data); + return -EINVAL; + } + ret = slsi_copy_country_table(sdev, &mib_val.value.u.octetValue, len); + if (ret < 0) { + kfree(mibrsp.data); + return ret; + } + } else { + SLSI_ERR(sdev, "Mib read failed (error: %d)\n", r); + } + + kfree(mibrsp.data); + return r; +} + +void slsi_regd_deinit(struct slsi_dev *sdev) +{ + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "slsi_regd_deinit\n"); + + kfree(sdev->device_config.domain_info.countrylist); +} + +void slsi_clear_offchannel_data(struct slsi_dev *sdev, bool acquire_lock) +{ + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif = NULL; + + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2PX_SWLAN); + if (WARN_ON(!dev)) { + SLSI_ERR(sdev, "No Group net dev found\n"); + return; + } + ndev_vif = netdev_priv(dev); + + if (acquire_lock) + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* Reset dwell time should be sent on group vif */ + (void)slsi_mlme_reset_dwell_time(sdev, dev); + + if (acquire_lock) + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + sdev->p2p_group_exp_frame = SLSI_P2P_PA_INVALID; +} + +static void slsi_hs2_unsync_vif_delete_work(struct work_struct *work) +{ + struct netdev_vif *ndev_vif = container_of((struct delayed_work *)work, struct netdev_vif, unsync.hs2_del_vif_work); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_NET_DBG1(ndev_vif->wdev.netdev, SLSI_CFG80211, "Delete HS vif duration expired - Deactivate unsync vif\n"); + slsi_wlan_unsync_vif_deactivate(ndev_vif->sdev, ndev_vif->wdev.netdev, true); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +int slsi_wlan_unsync_vif_activate(struct slsi_dev *sdev, struct net_device *dev, + struct ieee80211_channel *chan, u16 wait) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + u8 device_address[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + u32 action_frame_bmap; + + SLSI_DBG1(sdev, SLSI_INIT_DEINIT, "Activate wlan unsync vif\n"); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + ndev_vif->vif_type = FAPI_VIFTYPE_UNSYNCHRONISED; + + /* Avoid suspend when wlan unsync VIF is active */ + slsi_wakelock(&sdev->wlan_wl); + + /* Interface address and device address are same for unsync vif */ + if (slsi_mlme_add_vif(sdev, dev, dev->dev_addr, device_address) != 0) { + SLSI_NET_ERR(dev, "add vif failed for wlan unsync vif\n"); + goto exit_with_error; + } + + if (slsi_vif_activated(sdev, dev) != 0) { + SLSI_NET_ERR(dev, "vif activate failed for wlan unsync vif\n"); + slsi_mlme_del_vif(sdev, dev); + goto exit_with_error; + } + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_VIF_ACTIVE; + INIT_DELAYED_WORK(&ndev_vif->unsync.hs2_del_vif_work, slsi_hs2_unsync_vif_delete_work); + action_frame_bmap = SLSI_ACTION_FRAME_PUBLIC | SLSI_ACTION_FRAME_RADIO_MEASUREMENT; + + r = slsi_mlme_register_action_frame(sdev, dev, action_frame_bmap, action_frame_bmap); + if (r != 0) { + SLSI_NET_ERR(dev, "slsi_mlme_register_action_frame failed: resultcode = %d, action_frame_bmap:%d\n", + r, action_frame_bmap); + goto exit_with_vif; + } + + if (slsi_mlme_set_channel(sdev, dev, chan, SLSI_FW_CHANNEL_DURATION_UNSPECIFIED, 0, 0) != 0) { + SLSI_NET_ERR(dev, "Set channel failed for wlan unsync vif\n"); + goto exit_with_vif; + } + ndev_vif->chan = chan; + ndev_vif->driver_channel = chan->hw_value; + return r; + +exit_with_vif: + slsi_wlan_unsync_vif_deactivate(sdev, dev, true); +exit_with_error: + slsi_wakeunlock(&sdev->wlan_wl); + return -EINVAL; +} + +/* Delete unsync vif - DON'T update the vif type */ +void slsi_wlan_unsync_vif_deactivate(struct slsi_dev *sdev, struct net_device *dev, bool hw_available) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "De-activate wlan unsync vif\n"); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (sdev->wlan_unsync_vif_state == WLAN_UNSYNC_NO_VIF) { + SLSI_NET_DBG1(dev, SLSI_INIT_DEINIT, "wlan unsync vif already deactivated\n"); + return; + } + + cancel_delayed_work(&ndev_vif->unsync.hs2_del_vif_work); + + /* slsi_vif_deactivated is not used here after slsi_mlme_del_vif + * as it modifies vif type as well + */ + if (hw_available) + slsi_mlme_del_vif(sdev, dev); + + slsi_wakeunlock(&sdev->wlan_wl); + + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_NO_VIF; + ndev_vif->activated = false; + ndev_vif->chan = NULL; + + (void)slsi_set_mgmt_tx_data(ndev_vif, 0, 0, NULL, 0); +} + +void slsi_scan_ind_timeout_handle(struct work_struct *work) +{ + struct netdev_vif *ndev_vif = container_of((struct delayed_work *)work, struct netdev_vif, scan_timeout_work); + struct net_device *dev = slsi_get_netdev(ndev_vif->sdev, ndev_vif->ifnum); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) { + if (ndev_vif->scan[SLSI_SCAN_HW_ID].requeue_timeout_work) { + queue_delayed_work(ndev_vif->sdev->device_wq, &ndev_vif->scan_timeout_work, + msecs_to_jiffies(SLSI_FW_SCAN_DONE_TIMEOUT_MSEC)); + ndev_vif->scan[SLSI_SCAN_HW_ID].requeue_timeout_work = false; + } else { + SLSI_WARN(ndev_vif->sdev, "Mlme_scan_done_ind not received\n"); + (void)slsi_mlme_del_scan(ndev_vif->sdev, dev, ndev_vif->ifnum << 8 | SLSI_SCAN_HW_ID, true); + slsi_scan_complete(ndev_vif->sdev, dev, SLSI_SCAN_HW_ID, false); + } + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +void slsi_update_supported_channels_regd_flags(struct slsi_dev *sdev) +{ + int i = 0; + struct wiphy *wiphy = sdev->wiphy; + struct ieee80211_channel *chan; + + /* If all channels are supported by chip no need disable any channel + * So return + */ + if (sdev->enabled_channel_count == 39) + return; + if (wiphy->bands[0]) { + for (i = 0; i < ARRAY_SIZE(sdev->supported_2g_channels); i++) { + if (sdev->supported_2g_channels[i] == 0) { + chan = &wiphy->bands[0]->channels[i]; + chan->flags |= IEEE80211_CHAN_DISABLED; + } + } + } + if (sdev->band_5g_supported && wiphy->bands[1]) { + for (i = 0; i < ARRAY_SIZE(sdev->supported_5g_channels); i++) { + if (sdev->supported_5g_channels[i] == 0) { + chan = &wiphy->bands[1]->channels[i]; + chan->flags |= IEEE80211_CHAN_DISABLED; + } + } + } +} + +int slsi_find_chan_idx(u16 chan, u8 hw_mode) +{ + int idx = 0, i = 0; + u16 slsi_5ghz_channels_list[25] = {36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, + 136, 140, 144, 149, 153, 157, 161, 165}; + + if (hw_mode == SLSI_ACS_MODE_IEEE80211B || hw_mode == SLSI_ACS_MODE_IEEE80211G) { + idx = chan - 1; + return idx; + } + for (i = 0; i < 25; i++) { + if (chan == slsi_5ghz_channels_list[i]) { + idx = i; + break; + } + } + return idx; +} + +#ifdef CONFIG_SCSC_WLAN_SET_NUM_ANTENNAS +/* Note : netdev_vif lock should be taken care by caller. */ +int slsi_set_num_antennas(struct net_device *dev, const u16 num_of_antennas) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + struct sk_buff *req; + struct sk_buff *cfm; + int ret = 0; + const bool is_sta = (ndev_vif->iftype == NL80211_IFTYPE_STATION); + const bool is_softap = (ndev_vif->iftype == NL80211_IFTYPE_AP); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (num_of_antennas > 2 || num_of_antennas == 0) { + SLSI_NET_ERR(dev, "Invalid num_of_antennas %hu\n", num_of_antennas); + return -EINVAL; + } + if (!is_sta && !is_softap) { + SLSI_NET_ERR(dev, "Invalid interface type %s\n", dev->name); + return -EPERM; + } + if (is_sta && (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_NET_ERR(dev, "sta is not in connected state\n"); + return -EPERM; + } + SLSI_NET_INFO(dev, "mlme_set_num_antennas_req(vif:%u num_of_antennas:%u)\n", ndev_vif->ifnum, num_of_antennas); + /* TODO: Change signal name to MLME_SET_NUM_ANTENNAS_REQ and MLME_SET_NUM_ANTENNAS_CFM. */ + req = fapi_alloc(mlme_set_nss_req, MLME_SET_NSS_REQ, ndev_vif->ifnum, 0); + fapi_set_u16(req, u.mlme_set_nss_req.vif, ndev_vif->ifnum); + fapi_set_u16(req, u.mlme_set_nss_req.rx_nss, num_of_antennas); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_NSS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_nss_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_nss_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_nss_cfm.result_code)); + ret = -EINVAL; + } + slsi_kfree_skb(cfm); + return ret; +} +#endif diff --git a/drivers/net/wireless/scsc/mgt.h b/drivers/net/wireless/scsc/mgt.h new file mode 100755 index 000000000000..f44ee5abcc24 --- /dev/null +++ b/drivers/net/wireless/scsc/mgt.h @@ -0,0 +1,567 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef __SLSI_MGT_H__ +#define __SLSI_MGT_H__ + +#include + +#include "dev.h" +#include "debug.h" + +/* For 3.4.11 kernel support */ +#ifndef WLAN_OUI_MICROSOFT +#define WLAN_OUI_MICROSOFT 0x0050f2 +#define WLAN_OUI_TYPE_MICROSOFT_WPA 1 +#define WLAN_OUI_TYPE_MICROSOFT_WMM 2 +#define WLAN_OUI_TYPE_MICROSOFT_WPS 4 +#endif + +#define SLSI_COUNTRY_CODE_LEN 3 + +#define SLSI_EAPOL_TYPE_RSN_KEY (2) +#define SLSI_EAPOL_TYPE_WPA_KEY (254) + +#define SLSI_IEEE8021X_TYPE_EAPOL_KEY 3 +#define SLSI_IEEE8021X_TYPE_EAP_PACKET 0 + +#define SLSI_EAPOL_KEY_INFO_KEY_TYPE_BIT_IN_LOWER_BYTE BIT(3) /* Group = 0, Pairwise = 1 */ +#define SLSI_EAPOL_KEY_INFO_MIC_BIT_IN_HIGHER_BYTE BIT(0) +#define SLSI_EAPOL_KEY_INFO_SECURE_BIT_IN_HIGHER_BYTE BIT(1) +/* pkt_data would start from 802.1X Authentication field (pkt_data[0] = Version). + * For M4 packet, it will be something as below... member(size, position) + * Version (1, 0) + Type (1, 1) + Length (2, 2:3) + Descriptor Type (1, 4) + Key Information (2, 5:6) + + * key_length(2, 7:8) + replay_counter(8, 9:16) + key_nonce(32, 17:48) + key_iv(16, 49:64) + + * key_rsc (8, 65:72) + key_id(16, 73:80) + key_mic (16, 81:96) + key_data_length(2, 97:98) + + * keydata(key_data_length, 99:99+key_data_length) + */ +#define SLSI_EAPOL_IEEE8021X_TYPE_POS (1) +#define SLSI_EAPOL_TYPE_POS (4) +#define SLSI_EAPOL_KEY_INFO_HIGHER_BYTE_POS (5) +#define SLSI_EAPOL_KEY_INFO_LOWER_BYTE_POS (6) +#define SLSI_EAPOL_KEY_DATA_LENGTH_HIGHER_BYTE_POS (97) +#define SLSI_EAPOL_KEY_DATA_LENGTH_LOWER_BYTE_POS (98) + +#define SLSI_EAP_CODE_POS (4) +#define SLSI_EAP_PACKET_REQUEST (1) +#define SLSI_EAP_PACKET_RESPONSE (2) +#define SLSI_EAP_PACKET_SUCCESS (3) +#define SLSI_EAP_PACKET_FAILURE (4) +#define SLSI_EAP_TYPE_POS (8) +#define SLSI_EAP_TYPE_EXPANDED (254) +#define SLSI_EAP_OPCODE_POS (16) +#define SLSI_EAP_OPCODE_WSC_MSG (4) +#define SLSI_EAP_OPCODE_WSC_START (1) +#define SLSI_EAP_MSGTYPE_POS (27) +#define SLSI_EAP_MSGTYPE_M8 (12) +#define SLSI_EAP_WPS_DWELL_TIME (100000) /*100 ms */ +#define SLSI_EAP_TYPE_IDENTITY (1) + +#define SLSI_80211_AC_VO 0 +#define SLSI_80211_AC_VI 1 +#define SLSI_80211_AC_BE 2 +#define SLSI_80211_AC_BK 3 + +/* IF Number (Index) based checks */ +#define SLSI_IS_VIF_INDEX_WLAN(ndev_vif) (ndev_vif->ifnum == SLSI_NET_INDEX_WLAN) +#define SLSI_IS_VIF_INDEX_P2P(ndev_vif) (ndev_vif->ifnum == SLSI_NET_INDEX_P2P) +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#define SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif) ((ndev_vif->ifnum == SLSI_NET_INDEX_P2PX_SWLAN) &&\ + (sdev->netdev_ap != sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN])) +#define SLSI_IS_VIF_INDEX_MHS(sdev, ndev_vif) ((ndev_vif->ifnum == SLSI_NET_INDEX_P2PX_SWLAN) &&\ + (sdev->netdev_ap == sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN])) +#else +#define SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif) (ndev_vif->ifnum == SLSI_NET_INDEX_P2PX_SWLAN) +#endif +#define SLSI_IS_VIF_INDEX_NAN(ndev_vif) ((ndev_vif)->ifnum == SLSI_NET_INDEX_NAN) + +/* Check for P2P unsync vif type */ +#define SLSI_IS_P2P_UNSYNC_VIF(ndev_vif) ((ndev_vif->ifnum == SLSI_NET_INDEX_P2P) && (ndev_vif->vif_type == FAPI_VIFTYPE_UNSYNCHRONISED)) + +/* Check for HS unsync vif type */ +#define SLSI_IS_HS2_UNSYNC_VIF(ndev_vif) ((ndev_vif->ifnum == SLSI_NET_INDEX_WLAN) && (ndev_vif->vif_type == FAPI_VIFTYPE_UNSYNCHRONISED)) + +/* Check for P2P Group role */ +#define SLSI_IS_P2P_GROUP_STATE(sdev) ((sdev->p2p_state == P2P_GROUP_FORMED_GO) || (sdev->p2p_state == P2P_GROUP_FORMED_CLI)) + +/* Extra delay to wait after MLME-Roam.Response before obtaining roam reports */ +#define SLSI_STA_ROAM_REPORT_EXTRA_DELAY_MSEC 50 + +/* Extra duration in addition to ROC duration - For any workqueue scheduling delay */ +#define SLSI_P2P_ROC_EXTRA_MSEC 10 + +/* Extra duration to retain unsync vif even after ROC/mgmt_tx completes */ +#define SLSI_P2P_UNSYNC_VIF_EXTRA_MSEC 2000 +/* Extra duration to retain HS2 unsync vif even after mgmt_tx completes */ +#define SLSI_HS2_UNSYNC_VIF_EXTRA_MSEC 1000 + +/* Increased wait duration to retain unsync vif for GO-Negotiated to complete + * due to delayed response or, to allow peer to retry GO-Negotiation + */ +#define SLSI_P2P_NEG_PROC_UNSYNC_VIF_RETAIN_DURATION 3000 + +/* Increased wait duration to send unset channel to Fw. + * This would increase the listen time. + */ +#define SLSI_P2P_UNSET_CHANNEL_EXTRA_MSEC 600 +/* Extra duration in addition to mgmt tx wait */ +#define SLSI_P2P_MGMT_TX_EXTRA_MSEC 100 + +#define SLSI_FORCE_SCHD_ACT_FRAME_MSEC 100 +#define SLSI_P2PGO_KEEP_ALIVE_PERIOD_SEC 10 +#define SLSI_P2PGC_CONN_TIMEOUT_MSEC 10000 + +/* P2P Public Action Frames */ +#define SLSI_P2P_PA_GO_NEG_REQ 0 +#define SLSI_P2P_PA_GO_NEG_RSP 1 +#define SLSI_P2P_PA_GO_NEG_CFM 2 +#define SLSI_P2P_PA_INV_REQ 3 +#define SLSI_P2P_PA_INV_RSP 4 +#define SLSI_P2P_PA_DEV_DISC_REQ 5 +#define SLSI_P2P_PA_DEV_DISC_RSP 6 +#define SLSI_P2P_PA_PROV_DISC_REQ 7 +#define SLSI_P2P_PA_PROV_DISC_RSP 8 +#define SLSI_P2P_PA_INVALID 0xFF + +/* Service discovery public action frame types */ +#define SLSI_PA_GAS_INITIAL_REQ (10) +#define SLSI_PA_GAS_INITIAL_RSP (11) +#define SLSI_PA_GAS_COMEBACK_REQ (12) +#define SLSI_PA_GAS_COMEBACK_RSP (13) + +/*Radio Measurement action frames types */ +#define SLSI_RM_RADIO_MEASUREMENT_REQ (0) +#define SLSI_RM_RADIO_MEASUREMENT_REP (1) +#define SLSI_RM_LINK_MEASUREMENT_REQ (2) +#define SLSI_RM_LINK_MEASUREMENT_REP (3) +#define SLSI_RM_NEIGH_REP_REQ (4) +#define SLSI_RM_NEIGH_REP_RSP (5) + +#define SLSI_WNM_ACTION_FIELD_MIN (0) +#define SLSI_WNM_ACTION_FIELD_MAX (27) + +/* For service discovery action frames dummy subtype is used by setting the 7th bit */ +#define SLSI_PA_GAS_DUMMY_SUBTYPE_MASK 0x80 +#define SLSI_PA_GAS_INITIAL_REQ_SUBTYPE (SLSI_PA_GAS_INITIAL_REQ | SLSI_PA_GAS_DUMMY_SUBTYPE_MASK) +#define SLSI_PA_GAS_INITIAL_RSP_SUBTYPE (SLSI_PA_GAS_INITIAL_RSP | SLSI_PA_GAS_DUMMY_SUBTYPE_MASK) +#define SLSI_PA_GAS_COMEBACK_REQ_SUBTYPE (SLSI_PA_GAS_COMEBACK_REQ | SLSI_PA_GAS_DUMMY_SUBTYPE_MASK) +#define SLSI_PA_GAS_COMEBACK_RSP_SUBTYPE (SLSI_PA_GAS_COMEBACK_RSP | SLSI_PA_GAS_DUMMY_SUBTYPE_MASK) + +#define SLSI_P2P_STATUS_ATTR_ID 0 +#define SLSI_P2P_STATUS_CODE_SUCCESS 0 + +#define SLSI_ROAMING_CHANNEL_CACHE_TIMEOUT (5 * 60) + +#define SLSI_RX_SEQ_NUM_MASK 0xFFF +#define SLSI_RX_VIA_TDLS_LINK 0x8000 + +#define SET_ETHERTYPE_PATTERN_DESC(pd, ethertype) \ + pd.offset = 0x0C; \ + pd.mask_length = 2; \ + pd.mask[0] = 0xff; \ + pd.mask[1] = 0xff; \ + pd.pattern[0] = ethertype >> 8; \ + pd.pattern[1] = ethertype & 0xFF + +/* For checking DHCP frame */ +#define SLSI_IP_TYPE_UDP 0x11 +#define SLSI_IP_TYPE_OFFSET 23 +#define SLSI_IP_SOURCE_PORT_OFFSET 34 +#define SLSI_IP_DEST_PORT_OFFSET 36 +#define SLSI_DHCP_SERVER_PORT 67 +#define SLSI_DHCP_CLIENT_PORT 68 +#define SLSI_DNS_DEST_PORT 53 +#define SLSI_MDNS_DEST_PORT 5353 + +#define SLSI_DHCP_MSG_MAGIC_OFFSET 278 +#define SLSI_DHCP_OPTION 53 +#define SLSI_DHCP_MESSAGE_TYPE_DISCOVER 0x01 +#define SLSI_DHCP_MESSAGE_TYPE_OFFER 0x02 +#define SLSI_DHCP_MESSAGE_TYPE_REQUEST 0x03 +#define SLSI_DHCP_MESSAGE_TYPE_DECLINE 0x04 +#define SLSI_DHCP_MESSAGE_TYPE_ACK 0x05 +#define SLSI_DHCP_MESSAGE_TYPE_NAK 0x06 +#define SLSI_DHCP_MESSAGE_TYPE_RELEASE 0x07 +#define SLSI_DHCP_MESSAGE_TYPE_INFORM 0x08 +#define SLSI_DHCP_MESSAGE_TYPE_FORCERENEW 0x09 +#define SLSI_DHCP_MESSAGE_TYPE_INVALID 0x0A + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +#define SLSI_MAX_ARP_SEND_FRAME 8 +#endif +#define SLSI_ARP_SRC_IP_ADDR_OFFSET 14 +#define SLSI_ARP_DEST_IP_ADDR_OFFSET 24 +#define SLSI_IS_GRATUITOUS_ARP(frame) (!memcmp(&frame[SLSI_ARP_SRC_IP_ADDR_OFFSET],\ + &frame[SLSI_ARP_DEST_IP_ADDR_OFFSET], 4)) +#define SLSI_ARP_REPLY_OPCODE 2 +#define SLSI_ARP_REQUEST_OPCODE 1 +#define SLSI_ARP_OPCODE_OFFSET 6 + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 4, 0)) + #define WLAN_CATEGORY_WNM 10 +#endif + +enum slsi_dhcp_tx { + SLSI_TX_IS_NOT_DHCP, + SLSI_TX_IS_DHCP_SERVER, + SLSI_TX_IS_DHCP_CLIENT +}; + +enum slsi_fw_regulatory_rule_flags { + SLSI_REGULATORY_NO_IR = 1 << 0, + SLSI_REGULATORY_DFS = 1 << 1, + SLSI_REGULATORY_NO_OFDM = 1 << 2, + SLSI_REGULATORY_NO_INDOOR = 1 << 3, + SLSI_REGULATORY_NO_OUTDOOR = 1 << 4 +}; + +enum slsi_sta_conn_state { + SLSI_STA_CONN_STATE_DISCONNECTED = 0, + SLSI_STA_CONN_STATE_CONNECTING = 1, + SLSI_STA_CONN_STATE_DOING_KEY_CONFIG = 2, + SLSI_STA_CONN_STATE_CONNECTED = 3 +}; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0)) +static inline unsigned compare_ether_addr(const u8 *addr1, const u8 *addr2) +{ + return !ether_addr_equal(addr1, addr2); +} +#endif + +/** + * Peer record handling: + * Records are created/destroyed by the control path eg cfg80211 connect or + * when handling a MLME-CONNECT-IND when the VIA is an AP. + * + * However peer records are also currently accessed from the data path in both + * Tx and Rx directions: + * Tx - to determine the queueset + * Rx - for routing received packets back out to peers + * + * So the interactions required for the data path: + * 1. can NOT block + * 2. needs to be as quick as possible + */ +static inline struct slsi_peer *slsi_get_peer_from_mac(struct slsi_dev *sdev, struct net_device *dev, const u8 *mac) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + (void)sdev; /* unused */ + + /* Accesses the peer records but doesn't block as called from the data path. + * MUST check the valid flag on the record before accessing any other data in the record. + * Records are static, so having obtained a pointer the pointer will remain valid + * it just maybe the data that it points to gets set to ZERO. + */ + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + if (ndev_vif->sta.tdls_enabled) { + int i; + + for (i = 1; i < SLSI_TDLS_PEER_INDEX_MAX; i++) + if (ndev_vif->peer_sta_record[i] && ndev_vif->peer_sta_record[i]->valid && + compare_ether_addr(ndev_vif->peer_sta_record[i]->address, mac) == 0) + return ndev_vif->peer_sta_record[i]; + } + if (ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET] && ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET]->valid) + return ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET]; + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + int i = 0; + + for (i = 0; i < SLSI_PEER_INDEX_MAX; i++) + if (ndev_vif->peer_sta_record[i] && ndev_vif->peer_sta_record[i]->valid && + compare_ether_addr(ndev_vif->peer_sta_record[i]->address, mac) == 0) + return ndev_vif->peer_sta_record[i]; + } + return NULL; +} + +static inline struct slsi_peer *slsi_get_peer_from_qs(struct slsi_dev *sdev, struct net_device *dev, u16 queueset) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + (void)sdev; /* unused */ + + if (!ndev_vif->peer_sta_record[queueset] || !ndev_vif->peer_sta_record[queueset]->valid) + return NULL; + + return ndev_vif->peer_sta_record[queueset]; +} + +static inline bool slsi_is_tdls_peer(struct net_device *dev, struct slsi_peer *peer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + return (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && (peer->aid >= SLSI_TDLS_PEER_INDEX_MIN); +} + +static inline bool slsi_is_proxy_arp_supported_on_ap(struct sk_buff *assoc_resp_ie) +{ + const u8 *ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, assoc_resp_ie->data, assoc_resp_ie->len); + + if ((ie) && (ie[1] > 1)) + return ie[3] & 0x10; /*0: eid, 1: len; 3: proxy arp is 12th bit*/ + + return 0; +} + +static inline int slsi_cache_ies(const u8 *src_ie, size_t src_ie_len, u8 **dest_ie, size_t *dest_ie_len) +{ + *dest_ie = kmalloc(src_ie_len, GFP_KERNEL); + if (*dest_ie == NULL) + return -ENOMEM; + + memcpy(*dest_ie, src_ie, src_ie_len); + *dest_ie_len = src_ie_len; + + return 0; +} + +static inline void slsi_clear_cached_ies(u8 **ie, size_t *ie_len) +{ + if (*ie_len != 0) + kfree(*ie); + *ie = NULL; + *ie_len = 0; +} + +/* P2P Public Action frame subtype in text format for debug purposes */ +static inline char *slsi_p2p_pa_subtype_text(int subtype) +{ + switch (subtype) { + case SLSI_P2P_PA_GO_NEG_REQ: + return "GO_NEG_REQ"; + case SLSI_P2P_PA_GO_NEG_RSP: + return "GO_NEG_RSP"; + case SLSI_P2P_PA_GO_NEG_CFM: + return "GO_NEG_CFM"; + case SLSI_P2P_PA_INV_REQ: + return "INV_REQ"; + case SLSI_P2P_PA_INV_RSP: + return "INV_RSP"; + case SLSI_P2P_PA_DEV_DISC_REQ: + return "DEV_DISC_REQ"; + case SLSI_P2P_PA_DEV_DISC_RSP: + return "DEV_DISC_RSP"; + case SLSI_P2P_PA_PROV_DISC_REQ: + return "PROV_DISC_REQ"; + case SLSI_P2P_PA_PROV_DISC_RSP: + return "PROV_DISC_RSP"; + case SLSI_PA_GAS_INITIAL_REQ_SUBTYPE: + return "GAS_INITIAL_REQUEST"; + case SLSI_PA_GAS_INITIAL_RSP_SUBTYPE: + return "GAS_INITIAL_RESPONSE"; + case SLSI_PA_GAS_COMEBACK_REQ_SUBTYPE: + return "GAS_COMEBACK_REQUEST"; + case SLSI_PA_GAS_COMEBACK_RSP_SUBTYPE: + return "GAS_COMEBACK_RESPONSE"; + case SLSI_P2P_PA_INVALID: + return "PA_INVALID"; + default: + return "UNKNOWN"; + } +} + +/* Cookie generation and assignment for user space ROC and mgmt_tx request from supplicant */ +static inline void slsi_assign_cookie_id(u64 *cookie, u64 *counter) +{ + (*cookie) = ++(*counter); + if ((*cookie) == 0) + (*cookie) = ++(*counter); +} + +/* Update P2P Probe Response IEs in driver */ +static inline void slsi_unsync_vif_set_probe_rsp_ie(struct netdev_vif *ndev_vif, u8 *ies, size_t ies_len) +{ + if (ndev_vif->unsync.probe_rsp_ies_len) + kfree(ndev_vif->unsync.probe_rsp_ies); + ndev_vif->unsync.probe_rsp_ies = ies; + ndev_vif->unsync.probe_rsp_ies_len = ies_len; +} + +/* Set management frame tx data of vif */ +static inline int slsi_set_mgmt_tx_data(struct netdev_vif *ndev_vif, u64 cookie, u16 host_tag, const u8 *buf, size_t buf_len) +{ + u8 *tx_frame = NULL; + + if (buf_len != 0) { + tx_frame = kmalloc(buf_len, GFP_KERNEL); + if (!tx_frame) { + SLSI_NET_ERR(ndev_vif->wdev.netdev, "FAILED to allocate memory for Tx frame\n"); + return -ENOMEM; + } + SLSI_NET_DBG3(ndev_vif->wdev.netdev, SLSI_CFG80211, "Copy buffer for tx_status\n"); + memcpy(tx_frame, buf, buf_len); + } else if (ndev_vif->mgmt_tx_data.buf) { + SLSI_NET_DBG3(ndev_vif->wdev.netdev, SLSI_CFG80211, "Free buffer of tx_status\n"); + kfree(ndev_vif->mgmt_tx_data.buf); + } + + ndev_vif->mgmt_tx_data.cookie = cookie; + ndev_vif->mgmt_tx_data.host_tag = host_tag; + ndev_vif->mgmt_tx_data.buf = tx_frame; + ndev_vif->mgmt_tx_data.buf_len = buf_len; + + return 0; +} + +/** + * Handler to queue P2P unsync vif deletion work. + */ +static inline void slsi_p2p_queue_unsync_vif_del_work(struct netdev_vif *ndev_vif, unsigned int delay) +{ + cancel_delayed_work(&ndev_vif->unsync.del_vif_work); + queue_delayed_work(ndev_vif->sdev->device_wq, &ndev_vif->unsync.del_vif_work, msecs_to_jiffies(delay)); +} + +/* Update the new state for P2P. Also log the state change for debug purpose */ +#define SLSI_P2P_STATE_CHANGE(sdev, next_state) \ + do { \ + SLSI_DBG1(sdev, SLSI_CFG80211, "P2P state change: %s -> %s\n", slsi_p2p_state_text(sdev->p2p_state), slsi_p2p_state_text(next_state)); \ + sdev->p2p_state = next_state; \ + } while (0) + +void slsi_purge_scan_results(struct netdev_vif *ndev_vif, u16 scan_id); +void slsi_purge_scan_results_locked(struct netdev_vif *ndev_vif, u16 scan_id); +struct sk_buff *slsi_dequeue_cached_scan_result(struct slsi_scan *scan, int *count); +void slsi_get_hw_mac_address(struct slsi_dev *sdev, u8 *addr); +int slsi_start(struct slsi_dev *sdev); +int slsi_start_monitor_mode(struct slsi_dev *sdev, struct net_device *dev); +void slsi_stop_net_dev(struct slsi_dev *sdev, struct net_device *dev); +void slsi_stop(struct slsi_dev *sdev); +void slsi_stop_locked(struct slsi_dev *sdev); +struct slsi_peer *slsi_peer_add(struct slsi_dev *sdev, struct net_device *dev, u8 *peer_address, u16 aid); +void slsi_peer_update_assoc_req(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, struct sk_buff *skb); +void slsi_peer_update_assoc_rsp(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, struct sk_buff *skb); +void slsi_peer_reset_stats(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer); +int slsi_peer_remove(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer); +int slsi_ps_port_control(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, enum slsi_sta_conn_state s); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) +int slsi_del_station(struct wiphy *wiphy, struct net_device *dev, + struct station_del_parameters *del_params); +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) +int slsi_del_station(struct wiphy *wiphy, struct net_device *dev, const u8 *mac); +#else +int slsi_del_station(struct wiphy *wiphy, struct net_device *dev, u8 *mac); +#endif + +int slsi_vif_activated(struct slsi_dev *sdev, struct net_device *dev); +void slsi_vif_deactivated(struct slsi_dev *sdev, struct net_device *dev); +int slsi_handle_disconnect(struct slsi_dev *sdev, struct net_device *dev, u8 *peer_address, u16 reason); +int slsi_band_update(struct slsi_dev *sdev, int band); +int slsi_ip_address_changed(struct slsi_dev *sdev, struct net_device *dev, __be32 ipaddress); +int slsi_send_gratuitous_arp(struct slsi_dev *sdev, struct net_device *dev); +struct ieee80211_channel *slsi_find_scan_channel(struct slsi_dev *sdev, struct ieee80211_mgmt *mgmt, size_t mgmt_len, u16 freq); +int slsi_auto_chan_select_scan(struct slsi_dev *sdev, int chan_count, struct ieee80211_channel *channels[]); +int slsi_set_uint_mib(struct slsi_dev *dev, struct net_device *ndev, u16 psid, int value); +int slsi_update_regd_rules(struct slsi_dev *sdev, bool country_check); +int slsi_set_boost(struct slsi_dev *sdev, struct net_device *dev); +int slsi_p2p_init(struct slsi_dev *sdev, struct netdev_vif *ndev_vif); +void slsi_p2p_deinit(struct slsi_dev *sdev, struct netdev_vif *ndev_vif); +int slsi_p2p_vif_activate(struct slsi_dev *sdev, struct net_device *dev, struct ieee80211_channel *chan, u16 duration, bool set_probe_rsp_ies); +void slsi_p2p_vif_deactivate(struct slsi_dev *sdev, struct net_device *dev, bool hw_available); +void slsi_p2p_group_start_remove_unsync_vif(struct slsi_dev *sdev); +int slsi_p2p_dev_probe_rsp_ie(struct slsi_dev *sdev, struct net_device *dev, u8 *probe_rsp_ie, size_t probe_rsp_ie_len); +int slsi_p2p_dev_null_ies(struct slsi_dev *sdev, struct net_device *dev); +int slsi_p2p_get_public_action_subtype(const struct ieee80211_mgmt *mgmt); +int slsi_p2p_get_go_neg_rsp_status(struct net_device *dev, const struct ieee80211_mgmt *mgmt); +u8 slsi_p2p_get_exp_peer_frame_subtype(u8 subtype); +int slsi_send_txq_params(struct slsi_dev *sdev, struct net_device *ndev); +void slsi_abort_sta_scan(struct slsi_dev *sdev); +int slsi_is_dhcp_packet(u8 *data); + +#ifdef CONFIG_SCSC_WLAN_PRIORITISE_IMP_FRAMES +int slsi_is_dns_packet(u8 *data); +int slsi_is_mdns_packet(u8 *data); +int slsi_is_tcp_sync_packet(struct net_device *dev, struct sk_buff *skb); +#endif + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_PKT_FILTER +int slsi_set_enhanced_pkt_filter(struct net_device *dev, u8 pkt_filter_enable); +#endif +void slsi_set_packet_filters(struct slsi_dev *sdev, struct net_device *dev); +int slsi_update_packet_filters(struct slsi_dev *sdev, struct net_device *dev); +int slsi_clear_packet_filters(struct slsi_dev *sdev, struct net_device *dev); +int slsi_ap_prepare_add_info_ies(struct netdev_vif *ndev_vif, const u8 *ies, size_t ies_len); +int slsi_set_mib_roam(struct slsi_dev *dev, struct net_device *ndev, u16 psid, int value); +#ifdef CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA +int slsi_set_mib_preferred_antenna(struct slsi_dev *dev, u16 value); +#endif +void slsi_reset_throughput_stats(struct net_device *dev); +int slsi_set_mib_rssi_boost(struct slsi_dev *sdev, struct net_device *dev, u16 psid, int index, int boost); +#ifdef CONFIG_SCSC_WLAN_LOW_LATENCY_MODE +int slsi_set_mib_soft_roaming_enabled(struct slsi_dev *sdev, struct net_device *dev, bool enable); +#endif +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +int slsi_read_enhanced_arp_rx_count_by_lower_mac(struct slsi_dev *sdev, struct net_device *dev, u16 psid); +void slsi_fill_enhanced_arp_out_of_order_drop_counter(struct netdev_vif *ndev_vif, + struct sk_buff *skb); +#endif +void slsi_modify_ies_on_channel_switch(struct net_device *dev, struct cfg80211_ap_settings *settings, + u8 *ds_params_ie, u8 *ht_operation_ie, struct ieee80211_mgmt *mgmt, + u16 beacon_ie_head_len); +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +bool slsi_if_valid_wifi_sharing_channel(struct slsi_dev *sdev, int freq); +void slsi_extract_valid_wifi_sharing_channels(struct slsi_dev *sdev); +int slsi_check_if_non_indoor_non_dfs_channel(struct slsi_dev *sdev, int freq); +int slsi_select_wifi_sharing_ap_channel(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ap_settings *settings, struct slsi_dev *sdev, + int *wifi_sharing_channel_switched); +int slsi_set_mib_wifi_sharing_5ghz_channel(struct slsi_dev *sdev, u16 psid, int value, + int offset, int readbyte, char *arg); +int slsi_get_byte_position(int bit); +int slsi_check_if_channel_restricted_already(struct slsi_dev *sdev, int channel); +#endif +struct net_device *slsi_dynamic_interface_create(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + struct vif_params *params); +int slsi_get_mib_roam(struct slsi_dev *sdev, u16 psid, int *mib_value); +void slsi_roam_channel_cache_add(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_roam_channel_cache_prune(struct net_device *dev, int seconds); +int slsi_roaming_scan_configure_channels(struct slsi_dev *sdev, struct net_device *dev, const u8 *ssid, u8 *channels); +int slsi_send_max_transmit_msdu_lifetime(struct slsi_dev *dev, struct net_device *ndev, u32 msdu_lifetime); +int slsi_read_max_transmit_msdu_lifetime(struct slsi_dev *dev, struct net_device *ndev, u32 *msdu_lifetime); +int slsi_read_unifi_countrylist(struct slsi_dev *sdev, u16 psid); +int slsi_read_default_country(struct slsi_dev *sdev, u8 *alpha2, u16 index); +int slsi_read_disconnect_ind_timeout(struct slsi_dev *sdev, u16 psid); +int slsi_read_regulatory_rules(struct slsi_dev *sdev, struct slsi_802_11d_reg_domain *domain_info, const char *alpha2); +int slsi_send_acs_event(struct slsi_dev *sdev, struct slsi_acs_selected_channels acs_selected_channels); +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION +int slsi_set_mac_randomisation_mask(struct slsi_dev *sdev, u8 *mac_address_mask); +#endif +int slsi_set_country_update_regd(struct slsi_dev *sdev, const char *alpha2_code, int size); +void slsi_clear_offchannel_data(struct slsi_dev *sdev, bool acquire_lock); +int slsi_wlan_unsync_vif_activate(struct slsi_dev *sdev, struct net_device *dev, + struct ieee80211_channel *chan, u16 duration); +void slsi_wlan_unsync_vif_deactivate(struct slsi_dev *sdev, struct net_device *devbool, bool hw_available); +int slsi_is_wes_action_frame(const struct ieee80211_mgmt *mgmt); +void slsi_scan_ind_timeout_handle(struct work_struct *work); +void slsi_vif_cleanup(struct slsi_dev *sdev, struct net_device *dev, bool hw_available); +void slsi_scan_cleanup(struct slsi_dev *sdev, struct net_device *dev); +void slsi_dump_stats(struct net_device *dev); +int slsi_send_hanged_vendor_event(struct slsi_dev *sdev, u16 scsc_panic_code); +void slsi_update_supported_channels_regd_flags(struct slsi_dev *sdev); +#ifdef CONFIG_SCSC_WLAN_HANG_TEST +int slsi_test_send_hanged_vendor_event(struct net_device *dev); +#endif +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +int slsi_send_forward_beacon_vendor_event(struct slsi_dev *sdev, const u8 *ssid, const int ssid_len, const u8 *bssid, + u8 channel, const u16 beacon_int, const u64 timestamp, const u64 sys_time); +int slsi_send_forward_beacon_abort_vendor_event(struct slsi_dev *sdev, u16 reason_code); +#endif +void slsi_wlan_dump_public_action_subtype(struct slsi_dev *sdev, struct ieee80211_mgmt *mgmt, bool tx); +void slsi_reset_channel_flags(struct slsi_dev *sdev); + +/* Sysfs based mac address override */ +void slsi_create_sysfs_macaddr(void); +void slsi_destroy_sysfs_macaddr(void); +int slsi_find_chan_idx(u16 chan, u8 hw_mode); +#ifdef CONFIG_SCSC_WLAN_SET_NUM_ANTENNAS +int slsi_set_num_antennas(struct net_device *dev, const u16 num_of_antennas); +#endif +#endif /*__SLSI_MGT_H__*/ diff --git a/drivers/net/wireless/scsc/mib.c b/drivers/net/wireless/scsc/mib.c new file mode 100755 index 000000000000..10d16a4ac493 --- /dev/null +++ b/drivers/net/wireless/scsc/mib.c @@ -0,0 +1,598 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd and its Licensors. + * All rights reserved. + * + *****************************************************************************/ + +#include "const.h" +#include "mib.h" +#include "debug.h" + +#define SLSI_MIB_MORE_MASK 0x80 +#define SLSI_MIB_SIGN_MASK 0x40 +#define SLSI_MIB_TYPE_MASK 0x20 +#define SLSI_MIB_LENGTH_MASK 0x1FU +/** + * @brief + * Append a buffer to an existing buffer. + * This will kmalloc a new buffer and kfree the old one + */ +void slsi_mib_buf_append(struct slsi_mib_data *dst, size_t buffer_length, u8 *buffer) +{ + u8 *new_buffer = kmalloc(dst->dataLength + buffer_length, GFP_KERNEL); + + if (!new_buffer) { + SLSI_ERR_NODEV("kmalloc(%d) failed\n", (int)(dst->dataLength + buffer_length)); + return; + } + + memcpy(new_buffer, dst->data, dst->dataLength); + memcpy(&new_buffer[dst->dataLength], buffer, buffer_length); + + dst->dataLength += (u16)buffer_length; + kfree(dst->data); + dst->data = new_buffer; +} + +size_t slsi_mib_encode_uint32(u8 *buffer, u32 value) +{ + u8 i; + u8 write_count = 0; + + if (value < 64) { + buffer[0] = (u8)value; + return 1; + } + + /* Encode the Integer + * 0xABFF0055 = [0xAB, 0xFF, 0x00, 0x55] + * 0xAB0055 = [0xAB, 0x00, 0x55] + * 0xAB55 = [0xAB, 0x55] + * 0x55 = [0x55] + */ + for (i = 0; i < 4; i++) { + u8 byte_value = (value & 0xFF000000) >> 24; + + if (byte_value || write_count) { + buffer[1 + write_count] = byte_value; + write_count++; + } + value = value << 8; + } + + /* vldata Length | more bit */ + buffer[0] = write_count | SLSI_MIB_MORE_MASK; + + return 1 + write_count; +} + +size_t slsi_mib_encode_int32(u8 *buffer, s32 signed_value) +{ + u8 i; + u8 write_count = 0; + u32 value = (u32)signed_value; + + if (!(value & 0x10000000)) + /* just use the Unsigned Encoder */ + return slsi_mib_encode_uint32(buffer, value); + + if (signed_value >= -64) { + buffer[0] = (u8)value & 0x7F; /* vldata Length | more bit */ + return 1; + } + + /* Encode the Negative Integer */ + for (i = 0; i < 4; i++) { + u8 byte_value = (value & 0xFF000000) >> 24; + + if (!((byte_value == 0xFF) && (value & 0x800000)) || write_count) { + buffer[1 + write_count] = byte_value; + write_count++; + } + value = value << 8; + } + /* vldata Length | more bit | sign bit*/ + buffer[0] = write_count | SLSI_MIB_MORE_MASK | SLSI_MIB_SIGN_MASK; + + return 1 + write_count; +} + +size_t slsi_mib_encode_octet_str(u8 *buffer, struct slsi_mib_data *octet_value) +{ + u8 i; + u8 write_count = 0; + size_t length = octet_value->dataLength; + + /* Encode the Length (Up to 4 bytes 32 bits worth) + * 0xABFF0000 = [0xAB, 0xFF, 0x00, 0x00] + * 0xAB0000 = [0xAB, 0x00, 0x00] + * 0xAB00 = [0xAB, 0x00] + * 0x00 = [0x00] + */ + for (i = 0; i < 3; i++) { + u8 byte_value = (length & 0xFF000000) >> 24; + + if (byte_value || write_count) { + buffer[1 + write_count] = byte_value; + write_count++; + } + length = length << 8; + } + + buffer[0] = (1 + write_count) | SLSI_MIB_MORE_MASK | SLSI_MIB_TYPE_MASK; + buffer[1 + write_count] = octet_value->dataLength & 0xFF; + memcpy(&buffer[2 + write_count], octet_value->data, octet_value->dataLength); + + return 2U + write_count + octet_value->dataLength; +} + +size_t slsi_mib_decode_uint32(u8 *buffer, u32 *value) +{ + size_t i; + u32 v = 0; + size_t length = buffer[0] & SLSI_MIB_LENGTH_MASK; + + if (!(buffer[0] & SLSI_MIB_MORE_MASK)) { + *value = buffer[0] & 0x7F; + return 1; + } + + for (i = 0; i < length; i++) { + v = (v << 8); + v |= buffer[1 + i]; + } + + *value = v; + + return 1 + length; +} + +size_t slsi_mib_decodeUint64(u8 *buffer, u64 *value) +{ + size_t i; + u64 v = 0; + size_t length = buffer[0] & SLSI_MIB_LENGTH_MASK; + + if (!(buffer[0] & SLSI_MIB_MORE_MASK)) { + *value = buffer[0] & 0x7F; + return 1; + } + + for (i = 0; i < length; i++) { + v = (v << 8); + v |= buffer[1 + i]; + } + + *value = v; + + return 1 + length; +} + +size_t slsi_mib_decodeInt32(u8 *buffer, s32 *value) +{ + size_t i; + u32 v = 0xFFFFFFFF; + size_t length = buffer[0] & SLSI_MIB_LENGTH_MASK; + + if (!(buffer[0] & SLSI_MIB_SIGN_MASK)) + /* just use the Unsigned Decoder */ + return slsi_mib_decode_uint32(buffer, (u32 *)value); + + if (!(buffer[0] & SLSI_MIB_MORE_MASK)) { + *value = (s32)(0xFFFFFF80 | buffer[0]); + return 1; + } + + for (i = 0; i < length; i++) { + v = (v << 8); + v |= buffer[1 + i]; + } + + *value = (s32)v; + + return 1 + length; +} + +size_t slsi_mib_decodeInt64(u8 *buffer, s64 *value) +{ + size_t i; + u64 v = 0xFFFFFFFFFFFFFFFFULL; + size_t length = buffer[0] & SLSI_MIB_LENGTH_MASK; + + if (!(buffer[0] & SLSI_MIB_SIGN_MASK)) + /* just use the Unsigned Decoder */ + return slsi_mib_decodeUint64(buffer, (u64 *)value); + + if (!(buffer[0] & SLSI_MIB_MORE_MASK)) { + *value = (s64)(0xFFFFFFFFFFFFFF80ULL | buffer[0]); + return 1; + } + + for (i = 0; i < length; i++) { + v = (v << 8); + v |= buffer[1 + i]; + } + + *value = (s64)v; + + return 1 + length; +} + +/* Just references the oid in the existing buffer. No new memory is allcated */ +size_t slsi_mib_decode_octet_str(u8 *buffer, struct slsi_mib_data *octet_value) +{ + size_t i; + u32 oid_length_value = 0; + size_t length = buffer[0] & SLSI_MIB_LENGTH_MASK; + + for (i = 0; i < length; i++) { + oid_length_value = (oid_length_value << 8); + oid_length_value |= buffer[1 + i]; + } + + octet_value->dataLength = oid_length_value; + octet_value->data = NULL; + if (oid_length_value) + octet_value->data = &buffer[1 + length]; + + return 1 + length + oid_length_value; +} + +static u8 slsi_mib_decode_type_length(u8 *buffer, size_t *length) +{ + *length = 1; + if (buffer[0] & SLSI_MIB_MORE_MASK) + *length = buffer[0] & SLSI_MIB_LENGTH_MASK; + + if (buffer[0] & SLSI_MIB_SIGN_MASK) + return SLSI_MIB_TYPE_INT; + + if ((buffer[0] & SLSI_MIB_MORE_MASK) && + (buffer[0] & SLSI_MIB_TYPE_MASK)) { + size_t i; + size_t oid_length_value = 0; + + for (i = 0; i < *length; i++) { + oid_length_value = (oid_length_value << 8); + oid_length_value |= buffer[1 + i]; + } + *length += oid_length_value; + return SLSI_MIB_TYPE_OCTET; + } + return SLSI_MIB_TYPE_UINT; +} + +static size_t slsi_mib_encode_psid_indexs(u8 *buffer, const struct slsi_mib_get_entry *value) +{ + size_t i; + + SLSI_U16_TO_BUFF_LE(value->psid, &buffer[0]); + buffer[2] = 0; + buffer[3] = 0; + for (i = 0; i < SLSI_MIB_MAX_INDEXES && value->index[i] != 0; i++) + buffer[2] += (u8)slsi_mib_encode_uint32(&buffer[4 + buffer[2]], value->index[i]); + + if (buffer[2] % 2 == 1) { + /* Add a padding byte "0x00" to the encoded buffer. The Length + * value is NOT updated to account for this pad value. If the + * length is an Odd number the Pad values MUST be there if it + * is Even it will not be. + */ + buffer[4 + buffer[2]] = 0x00; + return 5 + buffer[2]; + } + + return 4 + buffer[2]; +} + +u16 slsi_mib_encode(struct slsi_mib_data *buffer, struct slsi_mib_entry *value) +{ + size_t i; + size_t required_size = 5U + (5U * SLSI_MIB_MAX_INDEXES) + + (value->value.type == SLSI_MIB_TYPE_OCTET ? value->value.u.octetValue.dataLength : 5U); + + size_t encoded_length = 4; + + u8 *tmp_buffer = kmalloc(required_size, GFP_KERNEL); + + if (!tmp_buffer) { + SLSI_ERR_NODEV("kmalloc(%d) failed\n", (int)required_size); + return SLSI_MIB_STATUS_FAILURE; + } + + SLSI_U16_TO_BUFF_LE(value->psid, &tmp_buffer[0]); + tmp_buffer[2] = 0; + tmp_buffer[3] = 0; + for (i = 0; i < SLSI_MIB_MAX_INDEXES && value->index[i] != 0; i++) + tmp_buffer[2] += (u8)slsi_mib_encode_uint32(&tmp_buffer[4 + tmp_buffer[2]], value->index[i]); + encoded_length += tmp_buffer[2]; + + switch (value->value.type) { + case SLSI_MIB_TYPE_UINT: + encoded_length += slsi_mib_encode_uint32(&tmp_buffer[encoded_length], value->value.u.uintValue); + break; + case SLSI_MIB_TYPE_INT: + encoded_length += slsi_mib_encode_int32(&tmp_buffer[encoded_length], value->value.u.intValue); + break; + case SLSI_MIB_TYPE_OCTET: + encoded_length += slsi_mib_encode_octet_str(&tmp_buffer[encoded_length], &value->value.u.octetValue); + break; + case SLSI_MIB_TYPE_BOOL: + encoded_length += slsi_mib_encode_uint32(&tmp_buffer[encoded_length], value->value.u.boolValue ? true : false); + break; + case SLSI_MIB_TYPE_NONE: + break; + default: + SLSI_WARN_NODEV("Invalid Type:%d requested\n", value->value.type); + kfree(tmp_buffer); + return SLSI_MIB_STATUS_FAILURE; + } + + SLSI_U16_TO_BUFF_LE(encoded_length - 4, &tmp_buffer[2]); /* length */ + + if (encoded_length % 2 == 1) { + /* Add a padding byte "0x00" to the encoded buffer. The Length + * value is NOT updated to account for this pad value. If the + * length is an Odd number the Pad values MUST be there if it + * is Even it will not be. + */ + tmp_buffer[encoded_length] = 0x00; + encoded_length++; + } + + slsi_mib_buf_append(buffer, encoded_length, tmp_buffer); + kfree(tmp_buffer); + + return SLSI_MIB_STATUS_SUCCESS; +} + +size_t slsi_mib_decode(struct slsi_mib_data *data, struct slsi_mib_entry *value) +{ + u8 *buffer = data->data; + u32 buffer_length = data->dataLength; + size_t index_count = 0; + size_t length; + size_t decoded_length = 4; + + memset(value, 0x00, sizeof(struct slsi_mib_entry)); + + if (buffer_length < 4) { + SLSI_WARN_NODEV("Mib Decode Length:%d Must be greater than 4\n", buffer_length); + return 0; + } + + if (!buffer) + return 0; + + length = SLSI_BUFF_LE_TO_U16(&buffer[2]); + + if (buffer_length < decoded_length + length) { + SLSI_ERR_NODEV("Mib Buffer Length:%d Must be >= than decoded length:%d\n", buffer_length, (int)(decoded_length + length)); + return 0; + } + + value->psid = SLSI_BUFF_LE_TO_U16(buffer); + value->value.type = SLSI_MIB_TYPE_NONE; + + while (decoded_length < 4 + length) { + size_t next_value_length; + u8 type = slsi_mib_decode_type_length(&buffer[decoded_length], &next_value_length); + + if (buffer_length < decoded_length + next_value_length) { + SLSI_ERR_NODEV("Mib Buffer Length:%d Must be >= than decoded length:%d\n", buffer_length, (int)(decoded_length + next_value_length)); + memset(value, 0x00, sizeof(struct slsi_mib_entry)); + return 0; + } + + switch (type) { + case SLSI_MIB_TYPE_UINT: + { + u32 v; + + decoded_length += slsi_mib_decode_uint32(&buffer[decoded_length], &v); + /* If this is that last value then it is the "unitValue" + * if other values follow it is an Index Value + */ + if ((decoded_length < 4 + length) && + (index_count != SLSI_MIB_MAX_INDEXES)) { + value->index[index_count] = (u16)v; + index_count++; + } else { + value->value.type = type; + value->value.u.uintValue = v; + if (decoded_length != 4 + length) + SLSI_WARN_NODEV("Uint Decode length:%d != expected:%d\n", (u32)decoded_length, (u32)(4 + length)); + } + break; + } + case SLSI_MIB_TYPE_INT: + value->value.type = type; + decoded_length += slsi_mib_decodeInt32(&buffer[decoded_length], &value->value.u.intValue); + if (decoded_length != 4 + length) + SLSI_WARN_NODEV("Int Decode length:%d != expected:%d\n", (u32)decoded_length, (u32)(4 + length)); + break; + case SLSI_MIB_TYPE_OCTET: + value->value.type = type; + decoded_length += slsi_mib_decode_octet_str(&buffer[decoded_length], &value->value.u.octetValue); + if (decoded_length != 4 + length) + SLSI_WARN_NODEV("Octet Decode length:%d != expected:%d\n", (u32)decoded_length, (u32)(4 + length)); + break; + default: + SLSI_ERR_NODEV("Invalid MIB data type(%d). Possible mbulk corruption\n", type); + memset(value, 0x00, sizeof(struct slsi_mib_entry)); + value->value.type = SLSI_MIB_TYPE_NONE; + return 0; + } + } + if (length % 2 == 1) { + /* Remove the padding byte "0x00" in the encoded buffer. + * The Length value does NOT account for this pad value + * If the length is an Odd number the Pad values MUST be + * there if it is Even it will not be. + */ + if (buffer[decoded_length] != 0x00) + SLSI_WARN_NODEV("psid:0x%.4X Padding Not Detected\n", value->psid); + length++; + } + return 4 + length; +} + +int slsi_mib_encode_get_list(struct slsi_mib_data *buffer, u16 psids_length, const struct slsi_mib_get_entry *psids) +{ + size_t i; + + buffer->dataLength = 0; + /* 13 Bytes per get will be loads of space for the max 3 indexes */ + buffer->data = kmalloc((u32)(psids_length * 13), GFP_KERNEL); + if (!buffer->data) { + SLSI_ERR_NODEV("kmalloc(%d) failed\n", psids_length * 13); + return SLSI_MIB_STATUS_OUT_OF_MEMORY; + } + for (i = 0; i < psids_length; i++) + buffer->dataLength += (u16)slsi_mib_encode_psid_indexs(&buffer->data[buffer->dataLength], &psids[i]); + + return SLSI_MIB_STATUS_SUCCESS; +} + +void slsi_mib_encode_get(struct slsi_mib_data *buffer, u16 psid, u16 idx) +{ + /* 13 Bytes per get will be loads of space for the max 3 indexes */ + size_t size; + u8 tmp_buffer[13]; + struct slsi_mib_get_entry entry; + + memset(&entry, 0x00, sizeof(struct slsi_mib_get_entry)); + entry.psid = psid; + entry.index[0] = idx; + size = slsi_mib_encode_psid_indexs(tmp_buffer, &entry); + slsi_mib_buf_append(buffer, size, tmp_buffer); +} + +u8 *slsi_mib_find(struct slsi_mib_data *buffer, const struct slsi_mib_get_entry *entry) +{ + size_t buffer_length = buffer->dataLength; + u8 *buff = buffer->data; + + if (buffer_length % 2 == 1) { + SLSI_WARN_NODEV("buffer_length(%d) %% 2 != 0 (Invalid Mib data Detected)\n", (int)buffer_length); + return NULL; + } + while (buffer_length >= 4) { + u16 psid = SLSI_BUFF_LE_TO_U16(buff); + size_t length = 4U + SLSI_BUFF_LE_TO_U16(&buff[2]); + + if (entry->psid == psid) { + size_t i; + u32 idx; + size_t bytes_read = 0; + + for (i = 0; i < SLSI_MIB_MAX_INDEXES; i++) { + if (!entry->index[i]) + return buff; + bytes_read = slsi_mib_decode_uint32(&buff[4 + bytes_read], &idx); + if (entry->index[i] != idx) + break; + } + if (i == SLSI_MIB_MAX_INDEXES) + return buff; + } + if (length % 2 == 1) + /* Remove the padding byte "0x00" in the encoded buffer. + * The Length value does NOT account for this pad value + * If the length is an Odd number the Pad values MUST be + * there if it is Even it will not be. + */ + length++; + + buff += length; + buffer_length -= length; + } + + return NULL; +} + +struct slsi_mib_value *slsi_mib_decode_get_list(struct slsi_mib_data *buffer, u16 psids_length, const struct slsi_mib_get_entry *psids) +{ + struct slsi_mib_value *results = kmalloc_array((size_t)psids_length, sizeof(struct slsi_mib_value), GFP_KERNEL); + size_t i; + int len = 0; + char psids_not_found[150] = ""; + + if (!results) { + SLSI_ERR_NODEV("kmalloc(%d) failed\n", (int)(sizeof(struct slsi_mib_value) * psids_length)); + return results; + } + + for (i = 0; i < psids_length; i++) { + struct slsi_mib_entry value; + struct slsi_mib_data data; + + data.data = slsi_mib_find(buffer, &psids[i]); + if (data.data) { + data.dataLength = buffer->dataLength - (data.data - buffer->data); + value.psid = psids[i].psid; + memcpy(value.index, psids[i].index, sizeof(value.index)); + (void)slsi_mib_decode(&data, &value); + + results[i] = value.value; + } else { + len += snprintf(&psids_not_found[0] + len, 150 - len, "%d ", psids[i].psid); + results[i].type = SLSI_MIB_TYPE_NONE; + } + } + + if (len) + SLSI_ERR_NODEV("Could not find psid's: %s\n", psids_not_found); + + return results; +} + +u16 slsi_mib_encode_bool(struct slsi_mib_data *buffer, u16 psid, bool value, u16 idx) +{ + struct slsi_mib_entry v; + + memset(&v, 0x00, sizeof(struct slsi_mib_entry)); + v.psid = psid; + v.index[0] = idx; + v.value.type = SLSI_MIB_TYPE_BOOL; + v.value.u.boolValue = value; + return slsi_mib_encode(buffer, &v); +} + +u16 slsi_mib_encode_int(struct slsi_mib_data *buffer, u16 psid, s32 value, u16 idx) +{ + struct slsi_mib_entry v; + + memset(&v, 0x00, sizeof(struct slsi_mib_entry)); + v.psid = psid; + v.index[0] = idx; + v.value.type = SLSI_MIB_TYPE_INT; + v.value.u.intValue = value; + return slsi_mib_encode(buffer, &v); +} + +u16 slsi_mib_encode_uint(struct slsi_mib_data *buffer, u16 psid, u32 value, u16 idx) +{ + struct slsi_mib_entry v; + + memset(&v, 0x00, sizeof(struct slsi_mib_entry)); + v.psid = psid; + v.index[0] = idx; + v.value.type = SLSI_MIB_TYPE_UINT; + v.value.u.uintValue = value; + return slsi_mib_encode(buffer, &v); +} + +u16 slsi_mib_encode_octet(struct slsi_mib_data *buffer, u16 psid, size_t dataLength, const u8 *data, u16 idx) +{ + struct slsi_mib_entry v; + + memset(&v, 0x00, sizeof(struct slsi_mib_entry)); + v.psid = psid; + v.index[0] = idx; + v.value.type = SLSI_MIB_TYPE_OCTET; + v.value.u.octetValue.dataLength = (u32)dataLength; + v.value.u.octetValue.data = (u8 *)data; + return slsi_mib_encode(buffer, &v); +} diff --git a/drivers/net/wireless/scsc/mib.h b/drivers/net/wireless/scsc/mib.h new file mode 100644 index 000000000000..b86113856775 --- /dev/null +++ b/drivers/net/wireless/scsc/mib.h @@ -0,0 +1,7785 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/* Note: this is an auto-generated file. */ + +#ifndef SLSI_MIB_H__ +#define SLSI_MIB_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct slsi_mib_data { + u32 dataLength; + u8 *data; +}; + +#define SLSI_MIB_MAX_INDEXES 2U + +#define SLSI_MIB_TYPE_BOOL 0 +#define SLSI_MIB_TYPE_UINT 1 +#define SLSI_MIB_TYPE_INT 2 +#define SLSI_MIB_TYPE_OCTET 3U +#define SLSI_MIB_TYPE_NONE 4 + +struct slsi_mib_value { + u8 type; + union { + bool boolValue; + s32 intValue; + u32 uintValue; + struct slsi_mib_data octetValue; + } u; +}; + +struct slsi_mib_entry { + u16 psid; + u16 index[SLSI_MIB_MAX_INDEXES]; /* 0 = no Index */ + struct slsi_mib_value value; +}; + +struct slsi_mib_get_entry { + u16 psid; + u16 index[SLSI_MIB_MAX_INDEXES]; /* 0 = no Index */ +}; + +#define SLSI_MIB_STATUS_SUCCESS 0x0000 +#define SLSI_MIB_STATUS_UNKNOWN_PSID 0x0001 +#define SLSI_MIB_STATUS_INVALID_INDEX 0x0002 +#define SLSI_MIB_STATUS_OUT_OF_RANGE 0x0003 +#define SLSI_MIB_STATUS_WRITE_ONLY 0x0004 +#define SLSI_MIB_STATUS_READ_ONLY 0x0005 +#define SLSI_MIB_STATUS_UNKNOWN_INTERFACE_TAG 0x0006 +#define SLSI_MIB_STATUS_INVALID_NUMBER_OF_INDICES 0x0007 +#define SLSI_MIB_STATUS_ERROR 0x0008 +#define SLSI_MIB_STATUS_UNSUPPORTED_ON_INTERFACE 0x0009 +#define SLSI_MIB_STATUS_UNAVAILABLE 0x000A +#define SLSI_MIB_STATUS_NOT_FOUND 0x000B +#define SLSI_MIB_STATUS_INCOMPATIBLE 0x000C +#define SLSI_MIB_STATUS_OUT_OF_MEMORY 0x000D +#define SLSI_MIB_STATUS_TO_MANY_REQUESTED_VARIABLES 0x000E +#define SLSI_MIB_STATUS_NOT_TRIED 0x000F +#define SLSI_MIB_STATUS_FAILURE 0xFFFF + +/******************************************************************************* + * + * NAME + * slsi_mib_encode_get Functions + * + * DESCRIPTION + * For use when getting data from the Wifi Stack. + * These functions append the encoded data to the "buffer". + * + * index == 0 where there is no index required + * + * EXAMPLE + * { + * static const struct slsi_mib_get_entry getValues[] = { + * { PSID1, { 0, 0 } }, + * { PSID2, { 3, 0 } }, + * }; + * struct slsi_mib_data buffer; + * slsi_mib_encode_get_list(&buffer, + * sizeof(getValues) / sizeof(struct slsi_mib_get_entry), + * getValues); + * } + * or + * { + * struct slsi_mib_data buffer = {0, NULL}; + * slsi_mib_encode_get(&buffer, PSID1, 0); + * slsi_mib_encode_get(&buffer, PSID2, 3); + * } + * RETURN + * SlsiResult: See SLSI_MIB_STATUS_* + * + *******************************************************************************/ +void slsi_mib_encode_get(struct slsi_mib_data *buffer, u16 psid, u16 index); +int slsi_mib_encode_get_list(struct slsi_mib_data *buffer, u16 psidsLength, const struct slsi_mib_get_entry *psids); + +/******************************************************************************* + * + * NAME + * SlsiWifiMibdEncode Functions + * + * DESCRIPTION + * For use when getting data from the Wifi Stack. + * + * index == 0 where there is no index required + * + * EXAMPLE + * { + * static const struct slsi_mib_get_entry getValues[] = { + * { PSID1, { 0, 0 } }, + * { PSID2, { 3, 0 } }, + * }; + * struct slsi_mib_data buffer = rxMibData; # Buffer with encoded Mib Data + * + * getValues = slsi_mib_decode_get_list(&buffer, + * sizeof(getValues) / sizeof(struct slsi_mib_get_entry), + * getValues); + * + * print("PSID1 = %d\n", getValues[0].u.uintValue); + * print("PSID2.3 = %s\n", getValues[1].u.boolValue?"TRUE":"FALSE"); + * + * kfree(getValues); + * + * } + * or + * { + * u8* buffer = rxMibData; # Buffer with encoded Mib Data + * size_t offset=0; + * struct slsi_mib_entry value; + * + * offset += slsi_mib_decode(&buffer[offset], &value); + * print("PSID1 = %d\n", value.u.uintValue); + * + * offset += slsi_mib_decode(&buffer[offset], &value); + * print("PSID2.3 = %s\n", value.u.boolValue?"TRUE":"FALSE"); + * + * } + * + *******************************************************************************/ +size_t slsi_mib_decode(struct slsi_mib_data *buffer, struct slsi_mib_entry *value); +struct slsi_mib_value *slsi_mib_decode_get_list(struct slsi_mib_data *buffer, u16 psidsLength, const struct slsi_mib_get_entry *psids); + +/******************************************************************************* + * + * NAME + * slsi_mib_encode Functions + * + * DESCRIPTION + * For use when setting data in the Wifi Stack. + * These functions append the encoded data to the "buffer". + * + * index == 0 where there is no index required + * + * EXAMPLE + * { + * u8 octets[2] = {0x00, 0x01}; + * struct slsi_mib_data buffer = {0, NULL}; + * slsi_mib_encode_bool(&buffer, PSID1, TRUE, 0); # Boolean set with no index + * slsi_mib_encode_int(&buffer, PSID2, -1234, 1); # Signed Integer set with on index 1 + * slsi_mib_encode_uint(&buffer, PSID2, 1234, 3); # Unsigned Integer set with on index 3 + * slsi_mib_encode_octet(&buffer, PSID3, sizeof(octets), octets, 0); # Octet set with no index + * } + * or + * { + # Unsigned Integer set with on index 3 + # struct slsi_mib_data buffer = {0, NULL}; + # struct slsi_mib_entry value; + # value.psid = psid; + # value.index[0] = 3; + # value.index[1] = 0; + # value.value.type = SLSI_MIB_TYPE_UINT; + # value.value.u.uintValue = 1234; + # slsi_mib_encode(buffer, &value); + # } + # RETURN + # See SLSI_MIB_STATUS_* + # + *******************************************************************************/ +u16 slsi_mib_encode(struct slsi_mib_data *buffer, struct slsi_mib_entry *value); +u16 slsi_mib_encode_bool(struct slsi_mib_data *buffer, u16 psid, bool value, u16 index); +u16 slsi_mib_encode_int(struct slsi_mib_data *buffer, u16 psid, s32 value, u16 index); +u16 slsi_mib_encode_uint(struct slsi_mib_data *buffer, u16 psid, u32 value, u16 index); +u16 slsi_mib_encode_octet(struct slsi_mib_data *buffer, u16 psid, size_t dataLength, const u8 *data, u16 index); + +/******************************************************************************* + * + * NAME + * SlsiWifiMib Low level Encode/Decode functions + * + *******************************************************************************/ +size_t slsi_mib_encode_uint32(u8 *buffer, u32 value); +size_t slsi_mib_encode_int32(u8 *buffer, s32 signedValue); +size_t slsi_mib_encode_octet_str(u8 *buffer, struct slsi_mib_data *octetValue); + +size_t slsi_mib_decodeUint32(u8 *buffer, u32 *value); +size_t slsi_mib_decodeInt32(u8 *buffer, s32 *value); +size_t slsi_mib_decodeUint64(u8 *buffer, u64 *value); +size_t slsi_mib_decodeInt64(u8 *buffer, s64 *value); +size_t slsi_mib_decode_octet_str(u8 *buffer, struct slsi_mib_data *octetValue); + +/******************************************************************************* + * + * NAME + * SlsiWifiMib Helper Functions + * + *******************************************************************************/ + +/* Find a the offset to psid data in an encoded buffer + * { + * struct slsi_mib_data buffer = rxMibData; # Buffer with encoded Mib Data + * struct slsi_mib_get_entry value = {PSID1, {0x01, 0x00}}; # Find value for PSID1.1 + * u8* mibdata = slsi_mib_find(&buffer, &value); + * if(mibdata) {print("Mib Data for PSID1.1 Found\n"); + * } + */ +u8 *slsi_mib_find(struct slsi_mib_data *buffer, const struct slsi_mib_get_entry *entry); + +/* Append data to a Buffer */ +void slsi_mib_buf_append(struct slsi_mib_data *dst, size_t bufferLength, u8 *buffer); + +/******************************************************************************* + * + * PSID Definitions + * + *******************************************************************************/ + +/******************************************************************************* + * NAME : Dot11TdlsPeerUapsdIndicationWindow + * PSID : 53 (0x0035) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : beacon intervals + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1 + * DESCRIPTION : + * The minimum time after the last TPU SP, before a RAME_TPU_SP indication + * can be issued. + *******************************************************************************/ +#define SLSI_PSID_DOT11_TDLS_PEER_UAPSD_INDICATION_WINDOW 0x0035 + +/******************************************************************************* + * NAME : Dot11AssociationSaQueryMaximumTimeout + * PSID : 100 (0x0064) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 1000 + * DESCRIPTION : + * Timeout (in TUs) before giving up on a Peer that has not responded to a + * SA Query frame. + *******************************************************************************/ +#define SLSI_PSID_DOT11_ASSOCIATION_SA_QUERY_MAXIMUM_TIMEOUT 0x0064 + +/******************************************************************************* + * NAME : Dot11AssociationSaQueryRetryTimeout + * PSID : 101 (0x0065) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 201 + * DESCRIPTION : + * Timeout (in TUs) before trying a Query Request frame. + *******************************************************************************/ +#define SLSI_PSID_DOT11_ASSOCIATION_SA_QUERY_RETRY_TIMEOUT 0x0065 + +/******************************************************************************* + * NAME : Dot11RtsThreshold + * PSID : 121 (0x0079) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : octet + * MIN : 0 + * MAX : 65536 + * DEFAULT : 65536 + * DESCRIPTION : + * Size of an MPDU, below which an RTS/CTS handshake shall not be performed, + * except as RTS/CTS is used as a cross modulation protection mechanism as + * defined in 9.10. An RTS/CTS handshake shall be performed at the beginning + * of any frame exchange sequence where the MPDU is of type Data or + * Management, the MPDU has an individual address in the Address1 field, and + * the length of the MPDU is greater than this threshold. (For additional + * details, refer to Table 21 in 9.7.) Setting larger than the maximum MSDU + * size shall have the effect of turning off the RTS/CTS handshake for + * frames of Data or Management type transmitted by this STA. Setting to + * zero shall have the effect of turning on the RTS/CTS handshake for all + * frames of Data or Management type transmitted by this STA. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RTS_THRESHOLD 0x0079 + +/******************************************************************************* + * NAME : Dot11ShortRetryLimit + * PSID : 122 (0x007A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 255 + * DEFAULT : 32 + * DESCRIPTION : + * Maximum number of transmission attempts of a frame, the length of which + * is less than or equal to dot11RTSThreshold, that shall be made before a + * failure condition is indicated. + *******************************************************************************/ +#define SLSI_PSID_DOT11_SHORT_RETRY_LIMIT 0x007A + +/******************************************************************************* + * NAME : Dot11LongRetryLimit + * PSID : 123 (0x007B) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 255 + * DEFAULT : 4 + * DESCRIPTION : + * Maximum number of transmission attempts of a frame, the length of which + * is greater than dot11RTSThreshold, that shall be made before a failure + * condition is indicated. + *******************************************************************************/ +#define SLSI_PSID_DOT11_LONG_RETRY_LIMIT 0x007B + +/******************************************************************************* + * NAME : Dot11FragmentationThreshold + * PSID : 124 (0x007C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 256 + * MAX : 11500 + * DEFAULT : 3000 + * DESCRIPTION : + * Current maximum size, in octets, of the MPDU that may be delivered to the + * security encapsulation. This maximum size does not apply when an MSDU is + * transmitted using an HT-immediate or HTdelayed Block Ack agreement, or + * when an MSDU or MMPDU is carried in an AMPDU that does not contain a VHT + * single MPDU. Fields added to the frame by security encapsulation are not + * counted against the limit specified. Except as described above, an MSDU + * or MMPDU is fragmented when the resulting frame has an individual address + * in the Address1 field, and the length of the frame is larger than this + * threshold, excluding security encapsulation fields. The default value is + * the lesser of 11500 or the aMPDUMaxLength or the aPSDUMaxLength of the + * attached PHY and the value never exceeds the lesser of 11500 or the + * aMPDUMaxLength or the aPSDUMaxLength of the attached PHY. + *******************************************************************************/ +#define SLSI_PSID_DOT11_FRAGMENTATION_THRESHOLD 0x007C + +/******************************************************************************* + * NAME : Dot11RtsSuccessCount + * PSID : 146 (0x0092) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * This counter shall increment when a CTS is received in response to an + * RTS. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RTS_SUCCESS_COUNT 0x0092 + +/******************************************************************************* + * NAME : Dot11AckFailureCount + * PSID : 148 (0x0094) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * This counter shall increment when an ACK is not received when expected. + *******************************************************************************/ +#define SLSI_PSID_DOT11_ACK_FAILURE_COUNT 0x0094 + +/******************************************************************************* + * NAME : Dot11MulticastReceivedFrameCount + * PSID : 150 (0x0096) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * This counter shall increment when a MSDU is received with the multicast + * bit set in the destination MAC address. + *******************************************************************************/ +#define SLSI_PSID_DOT11_MULTICAST_RECEIVED_FRAME_COUNT 0x0096 + +/******************************************************************************* + * NAME : Dot11FcsErrorCount + * PSID : 151 (0x0097) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : -9223372036854775808 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * This counter shall increment when an FCS error is detected in a received + * MPDU. + *******************************************************************************/ +#define SLSI_PSID_DOT11_FCS_ERROR_COUNT 0x0097 + +/******************************************************************************* + * NAME : Dot11WepUndecryptableCount + * PSID : 153 (0x0099) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * This counter shall increment when a frame is received with the WEP + * subfield of the Frame Control field set to one and the WEPOn value for + * the key mapped to the transmitter's MAC address indicates that the + * frame should not have been encrypted or that frame is discarded due to + * the receiving STA not implementing the privacy option. + *******************************************************************************/ +#define SLSI_PSID_DOT11_WEP_UNDECRYPTABLE_COUNT 0x0099 + +/******************************************************************************* + * NAME : Dot11ManufacturerProductVersion + * PSID : 183 (0x00B7) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 300 + * DEFAULT : + * DESCRIPTION : + * Printable string used to identify the manufacturer's product version + * of the resource. + *******************************************************************************/ +#define SLSI_PSID_DOT11_MANUFACTURER_PRODUCT_VERSION 0x00B7 + +/******************************************************************************* + * NAME : Dot11RsnaStatsStaAddress + * PSID : 430 (0x01AE) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The MAC address of the STA to which the statistics in this conceptual row + * belong. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_STA_ADDRESS 0x01AE + +/******************************************************************************* + * NAME : Dot11RsnaStatsTkipicvErrors + * PSID : 433 (0x01B1) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Counts the number of TKIP ICV errors encountered when decrypting packets + * for the STA. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_TKIPICV_ERRORS 0x01B1 + +/******************************************************************************* + * NAME : Dot11RsnaStatsTkipLocalMicFailures + * PSID : 434 (0x01B2) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Counts the number of MIC failures encountered when checking the integrity + * of packets received from the STA at this entity. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_TKIP_LOCAL_MIC_FAILURES 0x01B2 + +/******************************************************************************* + * NAME : Dot11RsnaStatsTkipRemoteMicFailures + * PSID : 435 (0x01B3) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Counts the number of MIC failures encountered by the STA identified by + * dot11RSNAStatsSTAAddress and reported back to this entity. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_TKIP_REMOTE_MIC_FAILURES 0x01B3 + +/******************************************************************************* + * NAME : Dot11RsnaStatsCcmpReplays + * PSID : 436 (0x01B4) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of received CCMP MPDUs discarded by the replay mechanism. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_CCMP_REPLAYS 0x01B4 + +/******************************************************************************* + * NAME : Dot11RsnaStatsCcmpDecryptErrors + * PSID : 437 (0x01B5) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of received MPDUs discarded by the CCMP decryption algorithm. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_CCMP_DECRYPT_ERRORS 0x01B5 + +/******************************************************************************* + * NAME : Dot11RsnaStatsTkipReplays + * PSID : 438 (0x01B6) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Counts the number of TKIP replay errors detected. + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_TKIP_REPLAYS 0x01B6 + +/******************************************************************************* + * NAME : Dot11RsnaStatsRobustMgmtCcmpReplays + * PSID : 441 (0x01B9) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of received Robust Management frame MPDUs discarded due to + * CCMP replay errors + *******************************************************************************/ +#define SLSI_PSID_DOT11_RSNA_STATS_ROBUST_MGMT_CCMP_REPLAYS 0x01B9 + +/******************************************************************************* + * NAME : UnifiMlmeConnectionTimeout + * PSID : 2000 (0x07D0) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_CONNECTION_TIMEOUT 0x07D0 + +/******************************************************************************* + * NAME : UnifiMlmeScanChannelMaxScanTime + * PSID : 2001 (0x07D1) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 14 + * MAX : 14 + * DEFAULT : { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * Test only: overrides max_scan_time. 0 indicates not used. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_CHANNEL_MAX_SCAN_TIME 0x07D1 + +/******************************************************************************* + * NAME : UnifiMlmeScanChannelProbeInterval + * PSID : 2002 (0x07D2) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 14 + * MAX : 14 + * DEFAULT : { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * Test only: overrides probe interval. 0 indicates not used. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_CHANNEL_PROBE_INTERVAL 0x07D2 + +/******************************************************************************* + * NAME : UnifiMlmeScanChannelRule + * PSID : 2003 (0x07D3) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 4 + * MAX : 4 + * DEFAULT : { 0X00, 0X01, 0X00, 0X01 } + * DESCRIPTION : + * Rules for channel scanners. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_CHANNEL_RULE 0x07D3 + +/******************************************************************************* + * NAME : UnifiMlmeDataReferenceTimeout + * PSID : 2005 (0x07D5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65534 + * DEFAULT : + * DESCRIPTION : + * Maximum time, in TU, allowed for the data in data references + * corresponding to MLME primitives to be made available to the firmware. + * The special value 0 specifies an infinite timeout. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_DATA_REFERENCE_TIMEOUT 0x07D5 + +/******************************************************************************* + * NAME : UnifiMlmeScanProbeInterval + * PSID : 2007 (0x07D7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_PROBE_INTERVAL 0x07D7 + +/******************************************************************************* + * NAME : UnifiMlmeScanHighRssiThreshold + * PSID : 2008 (0x07D8) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : -90 + * DESCRIPTION : + * Minimum RSSI, in dB, for a scan indication to be kept. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_HIGH_RSSI_THRESHOLD 0x07D8 + +/******************************************************************************* + * NAME : UnifiMlmeScanDeltaRssiThreshold + * PSID : 2010 (0x07DA) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 255 + * DEFAULT : 20 + * DESCRIPTION : + * Magnitude of the change in RSSI for which a scan result will be issued. + * In dB. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_DELTA_RSSI_THRESHOLD 0x07DA + +/******************************************************************************* + * NAME : UnifiMlmeScanMaximumAge + * PSID : 2014 (0x07DE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_MAXIMUM_AGE 0x07DE + +/******************************************************************************* + * NAME : UnifiMlmeScanMaximumResults + * PSID : 2015 (0x07DF) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 100 + * DESCRIPTION : + * Max number of scan results, per sps, which will be stored before the + * oldest result is discarded, irrespective of its age. The value 0 + * specifies no maximum. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_MAXIMUM_RESULTS 0x07DF + +/******************************************************************************* + * NAME : UnifiMlmeAutonomousScanNoisy + * PSID : 2016 (0x07E0) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_AUTONOMOUS_SCAN_NOISY 0x07E0 + +/******************************************************************************* + * NAME : UnifiChannelBusyThreshold + * PSID : 2018 (0x07E2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 100 + * DEFAULT : 25 + * DESCRIPTION : + * The threshold in percentage of CCA busy time when a channel would be + * considered busy + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CHANNEL_BUSY_THRESHOLD 0x07E2 + +/******************************************************************************* + * NAME : UnifiMacSequenceNumberRandomisationActivated + * PSID : 2020 (0x07E4) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enabling Sequence Number Randomisation to be applied for Probe Requests + * when scanning. Note: Randomisation only happens, if mac address gets + * randomised. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAC_SEQUENCE_NUMBER_RANDOMISATION_ACTIVATED 0x07E4 + +/******************************************************************************* + * NAME : UnifiFirmwareBuildId + * PSID : 2021 (0x07E5) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Numeric build identifier for this firmware build. This should normally be + * displayed in decimal. The textual build identifier is available via the + * standard dot11manufacturerProductVersion MIB attribute. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FIRMWARE_BUILD_ID 0x07E5 + +/******************************************************************************* + * NAME : UnifiChipVersion + * PSID : 2022 (0x07E6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Numeric identifier for the UniFi silicon revision (as returned by the + * GBL_CHIP_VERSION hardware register). Other than being different for each + * design variant (but not for alternative packaging options), the + * particular values returned do not have any significance. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CHIP_VERSION 0x07E6 + +/******************************************************************************* + * NAME : UnifiFirmwarePatchBuildId + * PSID : 2023 (0x07E7) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Numeric build identifier for the patch set that has been applied to this + * firmware image. This should normally be displayed in decimal. For a + * patched ROM build there will be two build identifiers, the first will + * correspond to the base ROM image, the second will correspond to the patch + * set that has been applied. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FIRMWARE_PATCH_BUILD_ID 0x07E7 + +/******************************************************************************* + * NAME : UnifiHtCapabilitiesSoftAp + * PSID : 2028 (0x07EC) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 21 + * MAX : 21 + * DEFAULT : { 0XEF, 0X0A, 0X17, 0XFF, 0XFF, 0X00, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * HT capabilities of the chip. See SC-503520-SP for further details. NOTE: + * Greenfield has been disabled due to interoperability issues wuth SGI. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HT_CAPABILITIES_SOFT_AP 0x07EC + +/******************************************************************************* + * NAME : UnifiSoftAp40MhzOn24g + * PSID : 2029 (0x07ED) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables 40MHz operation on 2.4GHz band for SoftAP. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SOFT_AP40_MHZ_ON24G 0x07ED + +/******************************************************************************* + * NAME : UnifiBasicCapabilities + * PSID : 2030 (0x07EE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X0730 + * DESCRIPTION : + * The 16-bit field follows the coding of IEEE 802.11 Capability + * Information. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BASIC_CAPABILITIES 0x07EE + +/******************************************************************************* + * NAME : UnifiExtendedCapabilities + * PSID : 2031 (0x07EF) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 9 + * MAX : 9 + * DEFAULT : { 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X40, 0X80 } + * DESCRIPTION : + * Extended capabilities. Bit field definition and coding follows IEEE + * 802.11 Extended Capability Information Element, with spare subfields for + * capabilities that are independent from chip/firmware implementation. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_EXTENDED_CAPABILITIES 0x07EF + +/******************************************************************************* + * NAME : UnifiHtCapabilities + * PSID : 2032 (0x07F0) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 21 + * MAX : 21 + * DEFAULT : { 0XEF, 0X0A, 0X17, 0XFF, 0XFF, 0X00, 0X00, 0X01, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * HT capabilities of the chip. See SC-503520-SP for further details. NOTE: + * Greenfield has been disabled due to interoperability issues wuth SGI. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HT_CAPABILITIES 0x07F0 + +/******************************************************************************* + * NAME : UnifiRsnCapabilities + * PSID : 2034 (0x07F2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSN_CAPABILITIES 0x07F2 + +/******************************************************************************* + * NAME : Unifi24G40MhzChannels + * PSID : 2035 (0x07F3) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables 40Mz wide channels in the 2.4G band for STA. + *******************************************************************************/ +#define SLSI_PSID_UNIFI24_G40_MHZ_CHANNELS 0x07F3 + +/******************************************************************************* + * NAME : UnifiExtendedCapabilitiesDisabled + * PSID : 2036 (0x07F4) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Suppress + * extended capabilities IE being sent in the association request. Please + * note that this may fix IOP issues with Aruba APs in WMMAC. Singed Decimal + *******************************************************************************/ +#define SLSI_PSID_UNIFI_EXTENDED_CAPABILITIES_DISABLED 0x07F4 + +/******************************************************************************* + * NAME : UnifiSupportedDataRates + * PSID : 2041 (0x07F9) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * UNITS : 500 kbps + * MIN : 2 + * MAX : 16 + * DEFAULT : { 0X02, 0X04, 0X0B, 0X0C, 0X12, 0X16, 0X18, 0X24, 0X30, 0X48, 0X60, 0X6C } + * DESCRIPTION : + * Defines the supported non-HT data rates. It is encoded as N+1 octets + * where the first octet is N and the subsequent octets each describe a + * single supported rate. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SUPPORTED_DATA_RATES 0x07F9 + +/******************************************************************************* + * NAME : UnifiRadioMeasurementActivated + * PSID : 2043 (0x07FB) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * When TRUE Radio Measurements are supported. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_MEASUREMENT_ACTIVATED 0x07FB + +/******************************************************************************* + * NAME : UnifiRadioMeasurementCapabilities + * PSID : 2044 (0x07FC) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 5 + * MAX : 5 + * DEFAULT : { 0X71, 0X00, 0X00, 0X00, 0X04 } + * DESCRIPTION : + * RM Enabled capabilities of the chip. See SC-503520-SP for further + * details. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_MEASUREMENT_CAPABILITIES 0x07FC + +/******************************************************************************* + * NAME : UnifiVhtActivated + * PSID : 2045 (0x07FD) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables VHT mode. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_VHT_ACTIVATED 0x07FD + +/******************************************************************************* + * NAME : UnifiHtActivated + * PSID : 2046 (0x07FE) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables HT mode. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HT_ACTIVATED 0x07FE + +/******************************************************************************* + * NAME : UnifiEnableTwoSimultaneousPassiveScansSameBand + * PSID : 2047 (0x07FF) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enable two passive scans to be simultaneously scheduled on two distinct + * channels at the same. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ENABLE_TWO_SIMULTANEOUS_PASSIVE_SCANS_SAME_BAND 0x07FF + +/******************************************************************************* + * NAME : UnifiRoamingEnabled + * PSID : 2049 (0x0801) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enable Roaming functionality + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAMING_ENABLED 0x0801 + +/******************************************************************************* + * NAME : UnifiRssiRoamScanTrigger + * PSID : 2050 (0x0802) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : -75 + * DESCRIPTION : + * The RSSI value, in dBm, below which roaming scan shall start. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI_ROAM_SCAN_TRIGGER 0x0802 + +/******************************************************************************* + * NAME : UnifiRoamDeltaTrigger + * PSID : 2051 (0x0803) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 255 + * DEFAULT : 10 + * DESCRIPTION : + * Hysteresis value, in dBm, for UnifiRssiRoamScanTrigger and + * unifiCURoamScanTrigger. i.e.: If the current AP RSSI is greater than + * UnifiRssiRoamScanTrigger+ UnifiRssiRoamDeltaTrigger, soft roaming scan + * can be terminated. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_DELTA_TRIGGER 0x0803 + +/******************************************************************************* + * NAME : UnifiRoamCachedChannelScanPeriod + * PSID : 2052 (0x0804) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 1 + * MAX : 4294967295 + * DEFAULT : 20000000 + * DESCRIPTION : + * The scan period for cached channels background roaming (microseconds) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_CACHED_CHANNEL_SCAN_PERIOD 0x0804 + +/******************************************************************************* + * NAME : UnifiFullRoamScanPeriod + * PSID : 2053 (0x0805) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 1 + * MAX : 4294967295 + * DEFAULT : 30000000 + * DESCRIPTION : + * NCHO: For certification and Host use only. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FULL_ROAM_SCAN_PERIOD 0x0805 + +/******************************************************************************* + * NAME : UnifiRoamSoftRoamingEnabled + * PSID : 2054 (0x0806) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables CASE_1 and CASE_2 scans. Host writes to the key, firmware reads + * it. See MFW-183 for details. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SOFT_ROAMING_ENABLED 0x0806 + +/******************************************************************************* + * NAME : UnifiRoamScanBand + * PSID : 2055 (0x0807) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 2 + * DEFAULT : 2 + * DESCRIPTION : + * Indicates whether only intra-band or all-band should be used for roaming + * scan. 2 - Roaming across band 1 - Roaming within band + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SCAN_BAND 0x0807 + +/******************************************************************************* + * NAME : UnifiRoamScanMaxActiveChannelTime + * PSID : 2057 (0x0809) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 65535 + * DEFAULT : 120 + * DESCRIPTION : + * NCHO: Name confusion for Host compatibility. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SCAN_MAX_ACTIVE_CHANNEL_TIME 0x0809 + +/******************************************************************************* + * NAME : UnifiRoamFullChannelScanFrequency + * PSID : 2058 (0x080A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 65535 + * DEFAULT : 9 + * DESCRIPTION : + * Every how many cached channel scans run a full channel scan. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_FULL_CHANNEL_SCAN_FREQUENCY 0x080A + +/******************************************************************************* + * NAME : UnifiRoamMode + * PSID : 2060 (0x080C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2 + * DEFAULT : 1 + * DESCRIPTION : + * Enable/Disable host resume when roaming. 0: Wake up the host all the + * time. 1: Only wakeup the host if the AP is not white-listed. 2: Don't + * wake up the host. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_MODE 0x080C + +/******************************************************************************* + * NAME : UnifiRssiRoamScanNoCandidateDeltaTrigger + * PSID : 2064 (0x0810) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 255 + * DEFAULT : 10 + * DESCRIPTION : + * The value, in dBm, by which unifiRssiRoamScanTrigger is lowered when no + * roaming candidates are found. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI_ROAM_SCAN_NO_CANDIDATE_DELTA_TRIGGER 0x0810 + +/******************************************************************************* + * NAME : UnifiRoamEapTimeout + * PSID : 2065 (0x0811) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 200 + * DESCRIPTION : + * Timeout, in ms, for receiving the first EAP/EAPOL frame from the AP + * during roaming + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_EAP_TIMEOUT 0x0811 + +/******************************************************************************* + * NAME : UnifiRoamScanControl + * PSID : 2067 (0x0813) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * NCHO: Enable MCD NCHO feature. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SCAN_CONTROL 0x0813 + +/******************************************************************************* + * NAME : UnifiRoamDfsScanMode + * PSID : 2068 (0x0814) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2 + * DEFAULT : 1 + * DESCRIPTION : + * NCHO: For certification and Host use only. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_DFS_SCAN_MODE 0x0814 + +/******************************************************************************* + * NAME : UnifiRoamScanHomeTime + * PSID : 2069 (0x0815) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 40 + * MAX : 65535 + * DEFAULT : 45 + * DESCRIPTION : + * NCHO: The time, in TU, to spend NOT scanning during a HomeAway scan. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SCAN_HOME_TIME 0x0815 + +/******************************************************************************* + * NAME : UnifiRoamScanHomeAwayTime + * PSID : 2070 (0x0816) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 40 + * MAX : 65535 + * DEFAULT : 100 + * DESCRIPTION : + * NCHO: The time, in TU, to spend scanning during a HomeAway scan. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SCAN_HOME_AWAY_TIME 0x0816 + +/******************************************************************************* + * NAME : UnifiRoamScanNProbe + * PSID : 2072 (0x0818) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * NCHO: The number of ProbeRequest frames per channel. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_SCAN_NPROBE 0x0818 + +/******************************************************************************* + * NAME : UnifiApOlbcDuration + * PSID : 2076 (0x081C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 300 + * DESCRIPTION : + * How long, in milliseconds, the AP enables reception of BEACON frames to + * perform Overlapping Legacy BSS Condition(OLBC). If set to 0 then OLBC is + * disabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_OLBC_DURATION 0x081C + +/******************************************************************************* + * NAME : UnifiApOlbcInterval + * PSID : 2077 (0x081D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2000 + * DESCRIPTION : + * How long, in milliseconds, between periods of receiving BEACON frames to + * perform Overlapping Legacy BSS Condition(OLBC). This value MUST exceed + * the OBLC duration MIB unifiApOlbcDuration. If set to 0 then OLBC is + * disabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_OLBC_INTERVAL 0x081D + +/******************************************************************************* + * NAME : UnifiMlmednsSupportEnabled + * PSID : 2078 (0x081E) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * This MIB enables support for transmitting DNS frame via MLME. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMEDNS_SUPPORT_ENABLED 0x081E + +/******************************************************************************* + * NAME : UnifiOffchannelScheduleTimeout + * PSID : 2079 (0x081F) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1000 + * DESCRIPTION : + * Maximum timeout in ms the Offchannel FSM will wait until the complete + * dwell time is scheduled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OFFCHANNEL_SCHEDULE_TIMEOUT 0x081F + +/******************************************************************************* + * NAME : UnifiFrameResponseTimeout + * PSID : 2080 (0x0820) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 500 + * DEFAULT : 200 + * DESCRIPTION : + * Timeout, in TU, to wait for a frame(Auth, Assoc, ReAssoc) after Rame + * replies to a send frame request + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FRAME_RESPONSE_TIMEOUT 0x0820 + +/******************************************************************************* + * NAME : UnifiConnectionFailureTimeout + * PSID : 2081 (0x0821) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 20000 + * DEFAULT : 10000 + * DESCRIPTION : + * Timeout, in TU, for a frame retry before giving up. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CONNECTION_FAILURE_TIMEOUT 0x0821 + +/******************************************************************************* + * NAME : UnifiConnectingProbeTimeout + * PSID : 2082 (0x0822) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 10 + * DESCRIPTION : + * How long, in TU, to wait for a ProbeRsp when syncronising before + * resending a ProbeReq + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CONNECTING_PROBE_TIMEOUT 0x0822 + +/******************************************************************************* + * NAME : UnifiDisconnectTimeout + * PSID : 2083 (0x0823) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 3000 + * DEFAULT : 1500 + * DESCRIPTION : + * Timeout, in milliseconds, to perform a disconnect or disconnect all STAs + * (triggered by MLME_DISCONNECT-REQ or MLME_DISCONNECT-REQ + * 00:00:00:00:00:00) before responding with MLME-DISCONNECT-IND and + * aborting the disconnection attempt. This is particulary important when a + * SoftAP is attempting to disconnect associated stations which might have + * "silently" left the ESS. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DISCONNECT_TIMEOUT 0x0823 + +/******************************************************************************* + * NAME : UnifiFrameResponseCfmTxLifetimeTimeout + * PSID : 2084 (0x0824) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Timeout, in TU, to wait to retry a frame (Auth, Assoc, ReAssoc) after TX + * Cfm trasnmission_status = TxLifetime. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FRAME_RESPONSE_CFM_TX_LIFETIME_TIMEOUT 0x0824 + +/******************************************************************************* + * NAME : UnifiFrameResponseCfmFailureTimeout + * PSID : 2085 (0x0825) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 40 + * DESCRIPTION : + * Timeout, in TU, to wait to retry a frame (Auth, Assoc, ReAssoc) after TX + * Cfm trasnmission_status != Successful | TxLifetime. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FRAME_RESPONSE_CFM_FAILURE_TIMEOUT 0x0825 + +/******************************************************************************* + * NAME : UnifiForceActiveDuration + * PSID : 2086 (0x0826) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 1000 + * DEFAULT : 200 + * DESCRIPTION : + * How long, in milliseconds, the firmware temporarily extends PowerSave for + * STA as a workaround for wonky APs such as D-link. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FORCE_ACTIVE_DURATION 0x0826 + +/******************************************************************************* + * NAME : UnifiMlmeScanMaxNumberOfProbeSets + * PSID : 2087 (0x0827) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_MAX_NUMBER_OF_PROBE_SETS 0x0827 + +/******************************************************************************* + * NAME : UnifiMlmeScanStopIfLessThanXFrames + * PSID : 2088 (0x0828) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 4 + * DESCRIPTION : + * Stop scanning on a channel if less than X Beacons or Probe Responses are + * received. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_STOP_IF_LESS_THAN_XFRAMES 0x0828 + +/******************************************************************************* + * NAME : UnifiApAssociationTimeout + * PSID : 2089 (0x0829) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2000 + * DESCRIPTION : + * SoftAP: Permitted time for a station to complete associatation with FW + * acting as AP in milliseconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_ASSOCIATION_TIMEOUT 0x0829 + +/******************************************************************************* + * NAME : UnifiHostNumAntennaControlActivated + * PSID : 2091 (0x082B) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Host has a control of number of antenna to use + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HOST_NUM_ANTENNA_CONTROL_ACTIVATED 0x082B + +/******************************************************************************* + * NAME : UnifiPeerBandwidth + * PSID : 2094 (0x082E) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The bandwidth used with peer station prior it disconnects + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PEER_BANDWIDTH 0x082E + +/******************************************************************************* + * NAME : UnifiCurrentPeerNss + * PSID : 2095 (0x082F) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The number of spatial streams used with peer station prior it disconnects + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CURRENT_PEER_NSS 0x082F + +/******************************************************************************* + * NAME : UnifiPeerTxDataRate + * PSID : 2096 (0x0830) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The tx rate that was used for transmissions prior disconnection + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PEER_TX_DATA_RATE 0x0830 + +/******************************************************************************* + * NAME : UnifiPeerRssi + * PSID : 2097 (0x0831) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * The recorded RSSI from peer station prior it disconnects + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PEER_RSSI 0x0831 + +/******************************************************************************* + * NAME : UnifiMlmeStationInactivityTimeout + * PSID : 2098 (0x0832) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 3 + * DESCRIPTION : + * Timeout, in seconds, for instigating ConnectonFailure procedures. Setting + * it to less than 3 seconds may result in frequent disconnection or roaming + * with the AP. Disable with Zero. Values lower than + * INACTIVITY_MINIMUM_TIMEOUT becomes INACTIVITY_MINIMUM_TIMEOUT. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_STATION_INACTIVITY_TIMEOUT 0x0832 + +/******************************************************************************* + * NAME : UnifiMlmeCliInactivityTimeout + * PSID : 2099 (0x0833) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Timeout, in seconds, for instigating ConnectonFailure procedures. Zero + * value disables the feature. Any value written lower than + * INACTIVITY_MINIMUM_TIMEOUT becomes INACTIVITY_MINIMUM_TIMEOUT. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_CLI_INACTIVITY_TIMEOUT 0x0833 + +/******************************************************************************* + * NAME : UnifiMlmeStationInitialKickTimeout + * PSID : 2100 (0x0834) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 50 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Timeout, in + * milliseconds, for sending the AP a NULL frame to kick off the EAPOL + * exchange. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_STATION_INITIAL_KICK_TIMEOUT 0x0834 + +/******************************************************************************* + * NAME : UnifiUartConfigure + * PSID : 2110 (0x083E) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * UART configuration using the values of the other unifiUart* attributes. + * The value supplied for this attribute is ignored. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_UART_CONFIGURE 0x083E + +/******************************************************************************* + * NAME : UnifiUartPios + * PSID : 2111 (0x083F) + * PER INTERFACE?: NO + * TYPE : unifiUartPios + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Specification of which PIOs should be connected to the UART. Currently + * defined values are: 1 - UART not used; all PIOs are available for other + * uses. 2 - Data transmit and receive connected to PIO[12] and PIO[14] + * respectively. No hardware handshaking lines. 3 - Data and handshaking + * lines connected to PIO[12:15]. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_UART_PIOS 0x083F + +/******************************************************************************* + * NAME : UnifiClockFrequency + * PSID : 2140 (0x085C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : kHz + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Query the nominal frequency of the external clock source or crystal + * oscillator used by UniFi. The clock frequency is a system parameter and + * can not be modified by key. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CLOCK_FREQUENCY 0x085C + +/******************************************************************************* + * NAME : UnifiCrystalFrequencyTrim + * PSID : 2141 (0x085D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 63 + * DEFAULT : 31 + * DESCRIPTION : + * The IEEE 802.11 standard requires a frequency accuracy of either +/- 20 + * ppm or +/- 25 ppm depending on the physical layer being used. If + * UniFi's frequency reference is a crystal then this attribute should + * be used to tweak the oscillating frequency to compensate for design- or + * device-specific variations. Each step change trims the frequency by + * approximately 2 ppm. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CRYSTAL_FREQUENCY_TRIM 0x085D + +/******************************************************************************* + * NAME : UnifiEnableDorm + * PSID : 2142 (0x085E) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Enable Dorm + * (deep sleep). When disabled, WLAN will not switch the radio power domain + * on/off *and* it will always veto deep sleep. Setting the value to TRUE + * means dorm functionality will behave normally. The intention is *not* for + * this value to be changed at runtime. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ENABLE_DORM 0x085E + +/******************************************************************************* + * NAME : UnifiExternalClockDetect + * PSID : 2146 (0x0862) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * If UniFi is running with an external fast clock source, i.e. + * unifiExternalFastClockRequest is set, it is common for this clock to be + * shared with other devices. Setting to true causes UniFi to detect when + * the clock is present (presumably in response to a request from another + * device), and to perform any pending activities at that time rather than + * requesting the clock again some time later. This is likely to reduce + * overall system power consumption by reducing the total time that the + * clock needs to be active. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_EXTERNAL_CLOCK_DETECT 0x0862 + +/******************************************************************************* + * NAME : UnifiExternalFastClockRequest + * PSID : 2149 (0x0865) + * PER INTERFACE?: NO + * TYPE : unifiExternalFastClockRequest + * MIN : 0 + * MAX : 255 + * DEFAULT : 1 + * DESCRIPTION : + * It is possible to supply UniFi with an external fast reference clock, as + * an alternative to using a crystal. If such a clock is used then it is + * only required when UniFi is active. A signal can be output on PIO[2] or + * if the version of UniFi in use is the UF602x or later, any PIO may be + * used (see unifiExternalFastClockRequestPIO) to indicate when UniFi + * requires a fast clock. Setting makes this signal become active and + * determines the type of signal output. 0 - No clock request. 1 - Non + * inverted, totem pole. 2 - Inverted, totem pole. 3 - Open drain. 4 - Open + * source. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_EXTERNAL_FAST_CLOCK_REQUEST 0x0865 + +/******************************************************************************* + * NAME : UnifiWatchdogTimeout + * PSID : 2152 (0x0868) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : ms + * MIN : 1 + * MAX : 65535 + * DEFAULT : 1500 + * DESCRIPTION : + * Maximum time the background may be busy or locked out for. If this time + * is exceeded, UniFi will reset. If this key is set to 65535 then the + * watchdog will be disabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WATCHDOG_TIMEOUT 0x0868 + +/******************************************************************************* + * NAME : UnifiScanParameters + * PSID : 2154 (0x086A) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 18 + * MAX : 18 + * DEFAULT : + * DESCRIPTION : + * Scan parameters. Each row of the table contains 2 entries for a scan: + * first entry when there is 0 registered VIFs, second - when there is 1 or + * more registered VIFs. Entry has the following structure: octet 0 - Scan + * priority (uint8) octet 1 - Scan Flags (uint8) (see unifiScanFlags) bit 0 + * - Enable Early Channel Exit (bool) bit 1 - Disable Scan (bool) bit 2 - + * Enable NCHO (bool) bit 3 - Enable MAC Randomization (bool) octet 2 ~ 3 - + * Probe Interval in Time Units (uint16) octet 4 ~ 5 - Max Active Channel + * Time in Time Units (uint16) octet 6 ~ 7 - Max Passive Channel Time in + * Time Units (uint16) octet 8 - Scan Policy (uint8) Size of each entry is 9 + * octets, row size is 18 octets. A Time Units value specifies a time + * interval as a multiple of TU (1024 us). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SCAN_PARAMETERS 0x086A + +/******************************************************************************* + * NAME : UnifiOverrideEdcaParamActivated + * PSID : 2155 (0x086B) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * To enable / disable, override STA edca config parameters with + * unifiOverrideEDCAParam. default: True - for volcano, and False - for + * others + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_EDCA_PARAM_ACTIVATED 0x086B + +/******************************************************************************* + * NAME : UnifiOverrideEdcaParam + * PSID : 2156 (0x086C) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * EDCA Parameters to be used if unifiOverrideEDCAParamEnable is true, + * indexed by unifiAccessClassIndex octet 0 - AIFSN octet 1 - [7:4] ECW MAX + * [3:0] ECW MIN octet 2 ~ 3 - TXOP[7:0] TXOP[15:8] in 32 usec units for + * both non-HT and HT connections. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_EDCA_PARAM 0x086C + +/******************************************************************************* + * NAME : UnifiExternalFastClockRequestPio + * PSID : 2158 (0x086E) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 15 + * DEFAULT : 9 + * DESCRIPTION : + * If an external fast reference clock is being supplied to UniFi as an + * alternative to a crystal (see unifiExternalFastClockRequest) and the + * version of UniFi in use is the UF602x or later, any PIO may be used as + * the external fast clock request output from UniFi. key determines the PIO + * to use. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_EXTERNAL_FAST_CLOCK_REQUEST_PIO 0x086E + +/******************************************************************************* + * NAME : UnifiRxDataRate + * PSID : 2196 (0x0894) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The bit rate of the last received frame on this VIF. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_DATA_RATE 0x0894 + +/******************************************************************************* + * NAME : UnifiPeerRxRetryCount + * PSID : 2198 (0x0896) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The number of retry packets from peer station + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PEER_RX_RETRY_COUNT 0x0896 + +/******************************************************************************* + * NAME : UnifiPeerRxMulticastCount + * PSID : 2199 (0x0897) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The number of multicast and broadcast packets received from peer station + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PEER_RX_MULTICAST_COUNT 0x0897 + +/******************************************************************************* + * NAME : UnifiRssi + * PSID : 2200 (0x0898) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Running average of the Received Signal Strength Indication (RSSI) for + * packets received by UniFi's radio. The value should only be treated + * as an indication of the signal strength; it is not an accurate + * measurement. The result is only meaningful if the unifiRxExternalGain + * attribute is set to the correct calibration value. If UniFi is part of a + * BSS, only frames originating from devices in the BSS are reported (so far + * as this can be determined). The average is reset when UniFi joins or + * starts a BSS or is reset. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI 0x0898 + +/******************************************************************************* + * NAME : UnifiLastBssRssi + * PSID : 2201 (0x0899) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Last BSS RSSI. See unifiRSSI description. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAST_BSS_RSSI 0x0899 + +/******************************************************************************* + * NAME : UnifiSnr + * PSID : 2202 (0x089A) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Provides a running average of the Signal to Noise Ratio (dB) for packets + * received by UniFi's radio. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SNR 0x089A + +/******************************************************************************* + * NAME : UnifiLastBssSnr + * PSID : 2203 (0x089B) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Last BSS SNR. See unifiSNR description. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAST_BSS_SNR 0x089B + +/******************************************************************************* + * NAME : UnifiSwTxTimeout + * PSID : 2204 (0x089C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : second + * MIN : 0 + * MAX : 65535 + * DEFAULT : 5 + * DESCRIPTION : + * Maximum time in seconds for a frame to be queued in firmware, ready to be + * sent, but not yet actually pumped to hardware. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SW_TX_TIMEOUT 0x089C + +/******************************************************************************* + * NAME : UnifiHwTxTimeout + * PSID : 2205 (0x089D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : milliseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 512 + * DESCRIPTION : + * Maximum time in milliseconds for a frame to be queued in the + * hardware/DPIF. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HW_TX_TIMEOUT 0x089D + +/******************************************************************************* + * NAME : UnifiRateStatsRxSuccessCount + * PSID : 2206 (0x089E) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of successful receptions of complete management and data + * frames at the rate indexed by unifiRateStatsIndex.This number will wrap + * to zero after the range is exceeded. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RATE_STATS_RX_SUCCESS_COUNT 0x089E + +/******************************************************************************* + * NAME : UnifiRateStatsTxSuccessCount + * PSID : 2207 (0x089F) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of successful (acknowledged) unicast transmissions of complete + * data or management frames the rate indexed by unifiRateStatsIndex. This + * number will wrap to zero after the range is exceeded. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RATE_STATS_TX_SUCCESS_COUNT 0x089F + +/******************************************************************************* + * NAME : UnifiTxDataRate + * PSID : 2208 (0x08A0) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The bit rate currently in use for transmissions of unicast data frames; + * On an infrastructure BSS, this is the data rate used in communicating + * with the associated access point, if there is none, an error is returned + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_DATA_RATE 0x08A0 + +/******************************************************************************* + * NAME : UnifiSnrExtraOffsetCck + * PSID : 2209 (0x08A1) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dB + * MIN : -32768 + * MAX : 32767 + * DEFAULT : 8 + * DESCRIPTION : + * This offset is added to SNR values received at 802.11b data rates. This + * accounts for differences in the RF pathway between 802.11b and 802.11g + * demodulators. The offset applies to values of unifiSNR as well as SNR + * values in scan indications. Not used in 5GHz mode. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SNR_EXTRA_OFFSET_CCK 0x08A1 + +/******************************************************************************* + * NAME : UnifiRssiMaxAveragingPeriod + * PSID : 2210 (0x08A2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 1 + * MAX : 65535 + * DEFAULT : 3000 + * DESCRIPTION : + * Limits the period over which the value of unifiRSSI is averaged. If no + * more than unifiRSSIMinReceivedFrames frames have been received in the + * period, then the value of unifiRSSI is reset to the value of the next + * measurement and the rolling average is restarted. This ensures that the + * value is timely (although possibly poorly averaged) when little data is + * being received. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI_MAX_AVERAGING_PERIOD 0x08A2 + +/******************************************************************************* + * NAME : UnifiRssiMinReceivedFrames + * PSID : 2211 (0x08A3) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * See the description of unifiRSSIMaxAveragingPeriod for how the + * combination of attributes is used. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI_MIN_RECEIVED_FRAMES 0x08A3 + +/******************************************************************************* + * NAME : UnifiRateStatsRate + * PSID : 2212 (0x08A4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : 500 kbps + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The rate corresponding to the current table entry. The value is rounded + * to the nearest number of units where necessary. Most rates do not require + * rounding, but when short guard interval is in effect the rates are no + * longer multiples of the base unit. Note that there may be two occurrences + * of the value 130: the first corresponds to MCS index 7, and the second, + * if present, to MCS index 6 with short guard interval. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RATE_STATS_RATE 0x08A4 + +/******************************************************************************* + * NAME : UnifiLastBssTxDataRate + * PSID : 2213 (0x08A5) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Last BSS Tx DataRate. See unifiTxDataRate description. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAST_BSS_TX_DATA_RATE 0x08A5 + +/******************************************************************************* + * NAME : UnifiDiscardedFrameCount + * PSID : 2214 (0x08A6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * This is a counter that indicates the number of data and management frames + * that have been processed by the UniFi hardware but were discarded before + * being processed by the firmware. It does not include frames not processed + * by the hardware because they were not addressed to the local device, nor + * does it include frames discarded by the firmware in the course of normal + * MAC processing (which include, for example, frames in an appropriate + * encryption state and multicast frames not requested by the host). + * Typically this counter indicates lost data frames for which there was no + * buffer space; however, other cases may cause the counter to increment, + * such as receiving a retransmitted frame that was already successfully + * processed. Hence this counter should not be treated as a reliable guide + * to lost frames. The counter wraps to 0 after 65535. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DISCARDED_FRAME_COUNT 0x08A6 + +/******************************************************************************* + * NAME : UnifiMacrameDebugStats + * PSID : 2215 (0x08A7) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * MACRAME debug stats readout key. Use set to write a debug readout, then + * read the same key to get the actual readout. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MACRAME_DEBUG_STATS 0x08A7 + +/******************************************************************************* + * NAME : UnifiCurrentTsfTime + * PSID : 2218 (0x08AA) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : -9223372036854775808 + * MAX : 9223372036854775807 + * DEFAULT : + * DESCRIPTION : + * Get TSF time (last 32 bits) for the specified VIF. VIF index can't be 0 + * as that is treated as global VIF For station VIF - Correct BSS TSF wil + * only be reported after MLME-CONNECT.indication(success) indication to + * host. Note that if MAC Hardware is switched off then TSF returned is + * estimated value + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CURRENT_TSF_TIME 0x08AA + +/******************************************************************************* + * NAME : UnifiBaRxEnableTid + * PSID : 2219 (0x08AB) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X1555 + * DESCRIPTION : + * Configure Block Ack RX on a per-TID basis. Bit mask is two bits per TID + * (B1 = Not Used, B0 = enable). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BA_RX_ENABLE_TID 0x08AB + +/******************************************************************************* + * NAME : UnifiBaTxEnableTid + * PSID : 2221 (0x08AD) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X0557 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Configure + * Block Ack TX on a per-TID basis. Bit mask is two bits per TID (B1 = + * autosetup, B0 = enable). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BA_TX_ENABLE_TID 0x08AD + +/******************************************************************************* + * NAME : UnifiTrafficThresholdToSetupBa + * PSID : 2222 (0x08AE) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 100 + * DESCRIPTION : + * Sets the default Threshold (as packet count) to setup BA agreement per + * TID. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TRAFFIC_THRESHOLD_TO_SETUP_BA 0x08AE + +/******************************************************************************* + * NAME : UnifiDplaneTxAmsduHwCapability + * PSID : 2223 (0x08AF) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Returns 0 if A-MSDU size limited to 4K. Returns 1 is A-MSDU size is + * limited to 8K. This value is chip specific and limited by HW. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPLANE_TX_AMSDU_HW_CAPABILITY 0x08AF + +/******************************************************************************* + * NAME : UnifiDplaneTxAmsduSubframeCountMax + * PSID : 2224 (0x08B0) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 4 + * DEFAULT : 3 + * DESCRIPTION : + * Defines the maximum number of A-MSDU sub-frames per A-MSDU. A value of 1 + * indicates A-MSDU aggregation has been disabled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPLANE_TX_AMSDU_SUBFRAME_COUNT_MAX 0x08B0 + +/******************************************************************************* + * NAME : UnifiBaConfig + * PSID : 2225 (0x08B1) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X3FFF01 + * DESCRIPTION : + * Block Ack Configuration. It is composed of A-MSDU supported, TX MPDU per + * A-MPDU, RX Buffer size, TX Buffer size and Block Ack Timeout. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BA_CONFIG 0x08B1 + +/******************************************************************************* + * NAME : UnifiBaTxMaxNumber + * PSID : 2226 (0x08B2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X10 + * DESCRIPTION : + * Block Ack Configuration. Maximum number of BAs. Limited by HW. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BA_TX_MAX_NUMBER 0x08B2 + +/******************************************************************************* + * NAME : UnifiMoveBKtoBe + * PSID : 2227 (0x08B3) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : + * DESCRIPTION : + * Deprecated. Golden Certification MIB don't delete, change PSID or name + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MOVE_BKTO_BE 0x08B3 + +/******************************************************************************* + * NAME : UnifiBeaconReceived + * PSID : 2228 (0x08B4) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Access point beacon received count from connected AP + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BEACON_RECEIVED 0x08B4 + +/******************************************************************************* + * NAME : UnifiAcRetries + * PSID : 2229 (0x08B5) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * It represents the number of retransmitted frames under each ac priority + * (indexed by unifiAccessClassIndex). This number will wrap to zero after + * the range is exceeded. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AC_RETRIES 0x08B5 + +/******************************************************************************* + * NAME : UnifiRadioOnTime + * PSID : 2230 (0x08B6) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * msecs the radio is awake (32 bits number accruing over time). On + * multi-radio platforms an index to the radio instance is required + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_ON_TIME 0x08B6 + +/******************************************************************************* + * NAME : UnifiRadioTxTime + * PSID : 2231 (0x08B7) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * msecs the radio is transmitting (32 bits number accruing over time). On + * multi-radio platforms an index to the radio instance is required + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_TX_TIME 0x08B7 + +/******************************************************************************* + * NAME : UnifiRadioRxTime + * PSID : 2232 (0x08B8) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * msecs the radio is in active receive (32 bits number accruing over time). + * On multi-radio platforms an index to the radio instance is required + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_RX_TIME 0x08B8 + +/******************************************************************************* + * NAME : UnifiRadioScanTime + * PSID : 2233 (0x08B9) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * msecs the radio is awake due to all scan (32 bits number accruing over + * time). On multi-radio platforms an index to the radio instance is + * required + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_SCAN_TIME 0x08B9 + +/******************************************************************************* + * NAME : UnifiPsLeakyAp + * PSID : 2234 (0x08BA) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * indicate that this AP typically leaks packets beyond the guard time + * (5msecs). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PS_LEAKY_AP 0x08BA + +/******************************************************************************* + * NAME : UnifiTqamActivated + * PSID : 2235 (0x08BB) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables Vendor VHT IE for 256-QAM mode on 2.4GHz. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TQAM_ACTIVATED 0x08BB + +/******************************************************************************* + * NAME : UnifiRadioOnTimeNan + * PSID : 2236 (0x08BC) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * msecs the radio is awake due to NAN operations (32 bits number accruing + * over time). On multi-radio platforms an index to the radio instance is + * required + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_ON_TIME_NAN 0x08BC + +/******************************************************************************* + * NAME : UnifiOutputRadioInfoToKernelLog + * PSID : 2239 (0x08BF) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Print messages about the radio status to the Android Kernel Log. See + * document SC-508266-TC. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OUTPUT_RADIO_INFO_TO_KERNEL_LOG 0x08BF + +/******************************************************************************* + * NAME : UnifiNoAckActivationCount + * PSID : 2240 (0x08C0) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of frames that are discarded due to HW No-ack activated during + * test. This number will wrap to zero after the range is exceeded. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NO_ACK_ACTIVATION_COUNT 0x08C0 + +/******************************************************************************* + * NAME : UnifiRxFcsErrorCount + * PSID : 2241 (0x08C1) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of received frames that are discarded due to bad FCS (CRC). + * This number will wrap to zero after the range is exceeded. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_FCS_ERROR_COUNT 0x08C1 + +/******************************************************************************* + * NAME : UnifiBeaconsReceivedPercentage + * PSID : 2245 (0x08C5) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Percentage of beacons received, calculated as received / expected. The + * percentage is scaled to an integer value between 0 (0%) and 1000 (100%). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BEACONS_RECEIVED_PERCENTAGE 0x08C5 + +/******************************************************************************* + * NAME : UnifiArpDetectActivated + * PSID : 2246 (0x08C6) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enable feature support for Enhanced ARP Detect. This is required by + * Volcano. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ARP_DETECT_ACTIVATED 0x08C6 + +/******************************************************************************* + * NAME : UnifiArpDetectResponseCounter + * PSID : 2247 (0x08C7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Counter used to track ARP Response frame for Enhanced ARP Detect. This is + * required by Volcano. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ARP_DETECT_RESPONSE_COUNTER 0x08C7 + +/******************************************************************************* + * NAME : UnifiEnableMgmtTxPacketStats + * PSID : 2249 (0x08C9) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Consider management packets for TX stats counters + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ENABLE_MGMT_TX_PACKET_STATS 0x08C9 + +/******************************************************************************* + * NAME : UnifiSwToHwQueueStats + * PSID : 2250 (0x08CA) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The timing statistics of packets being queued between SW-HW + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SW_TO_HW_QUEUE_STATS 0x08CA + +/******************************************************************************* + * NAME : UnifiHostToSwQueueStats + * PSID : 2251 (0x08CB) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The timing statistics of packets being queued between HOST-SW + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HOST_TO_SW_QUEUE_STATS 0x08CB + +/******************************************************************************* + * NAME : UnifiQueueStatsEnable + * PSID : 2252 (0x08CC) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables recording timing statistics of packets being queued between + * HOST-SW-HW + *******************************************************************************/ +#define SLSI_PSID_UNIFI_QUEUE_STATS_ENABLE 0x08CC + +/******************************************************************************* + * NAME : UnifiTxDataConfirm + * PSID : 2253 (0x08CD) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Allows to request on a per access class basis that an MA_UNITDATA.confirm + * be generated after each packet transfer. The default value is applied for + * all ACs. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_DATA_CONFIRM 0x08CD + +/******************************************************************************* + * NAME : UnifiThroughputDebug + * PSID : 2254 (0x08CE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * is used to access throughput related counters that can help diagnose + * throughput problems. The index of the MIB will access different counters, + * as described in SC-506328-DD. Setting any index for a VIF to any value, + * clears all DPLP debug stats for the MAC instance used by the VIF. This is + * useful mainly for debugging LAA or small scale throughput issues that + * require short term collection of the statistics. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_THROUGHPUT_DEBUG 0x08CE + +/******************************************************************************* + * NAME : UnifiLoadDpdLut + * PSID : 2255 (0x08CF) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 147 + * MAX : 147 + * DEFAULT : + * DESCRIPTION : + * Write a static DPD LUT to the FW, read DPD LUT from hardware + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LOAD_DPD_LUT 0x08CF + +/******************************************************************************* + * NAME : UnifiDpdMasterSwitch + * PSID : 2256 (0x08D0) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Enables Digital Pre-Distortion + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPD_MASTER_SWITCH 0x08D0 + +/******************************************************************************* + * NAME : UnifiDpdPredistortGains + * PSID : 2257 (0x08D1) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 14 + * MAX : 14 + * DEFAULT : + * DESCRIPTION : + * DPD pre-distort gains. Takes a range of frequencies, where f_min <= + * f_channel < f_max. The format is [freq_min_msb, freq_min_lsb, + * freq_max_msb, freq_max_lsb, DPD policy bitmap, bandwidth_bitmap, + * power_trim_enable, OFDM0_gain, OFDM1_gain, CCK_gain, TR_gain, CCK PSAT + * gain, OFDM PSAT gain]. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPD_PREDISTORT_GAINS 0x08D1 + +/******************************************************************************* + * NAME : UnifiOverrideDpdLut + * PSID : 2258 (0x08D2) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 147 + * MAX : 147 + * DEFAULT : + * DESCRIPTION : + * Write a DPD LUT directly to the HW + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_DPD_LUT 0x08D2 + +/******************************************************************************* + * NAME : UnifiGoogleMaxNumberOfPeriodicScans + * PSID : 2260 (0x08D4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GOOGLE_MAX_NUMBER_OF_PERIODIC_SCANS 0x08D4 + +/******************************************************************************* + * NAME : UnifiGoogleMaxRssiSampleSize + * PSID : 2261 (0x08D5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GOOGLE_MAX_RSSI_SAMPLE_SIZE 0x08D5 + +/******************************************************************************* + * NAME : UnifiGoogleMaxHotlistAPs + * PSID : 2262 (0x08D6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GOOGLE_MAX_HOTLIST_APS 0x08D6 + +/******************************************************************************* + * NAME : UnifiGoogleMaxSignificantWifiChangeAPs + * PSID : 2263 (0x08D7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GOOGLE_MAX_SIGNIFICANT_WIFI_CHANGE_APS 0x08D7 + +/******************************************************************************* + * NAME : UnifiGoogleMaxBssidHistoryEntries + * PSID : 2264 (0x08D8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GOOGLE_MAX_BSSID_HISTORY_ENTRIES 0x08D8 + +/******************************************************************************* + * NAME : UnifiMacBeaconTimeout + * PSID : 2270 (0x08DE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 128 + * DESCRIPTION : + * The maximum time in microseconds we want to stall TX data when expecting + * a beacon at EBRT time as a station. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAC_BEACON_TIMEOUT 0x08DE + +/******************************************************************************* + * NAME : UnifiStaUsesOneAntennaWhenIdle + * PSID : 2274 (0x08E2) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Allow the platform to downgrade antenna usage for STA VIFs to 1 if the + * VIF is idle. Only valid for multi-radio platforms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_USES_ONE_ANTENNA_WHEN_IDLE 0x08E2 + +/******************************************************************************* + * NAME : UnifiStaUsesMultiAntennasDuringConnect + * PSID : 2275 (0x08E3) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Allow the platform to use multiple antennas for STA VIFs during the + * connect phase. Only valid for multi-radio platforms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_USES_MULTI_ANTENNAS_DURING_CONNECT 0x08E3 + +/******************************************************************************* + * NAME : UnifiApUsesOneAntennaWhenPeersIdle + * PSID : 2276 (0x08E4) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Allow the platform to downgrade antenna usage for AP VIFs when all + * connected peers are idle. Only valid for multi-radio platforms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_USES_ONE_ANTENNA_WHEN_PEERS_IDLE 0x08E4 + +/******************************************************************************* + * NAME : deprecated_unifiUpdateAntennaCapabilitiesWhenScanning + * PSID : 2277 (0x08E5) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Specify whether antenna scan activities will be allowed to cause an + * update of VIF capability. Only valid for multi-radio platforms. WARNING: + * Changing this value after system start-up will have no effect. + *******************************************************************************/ +#define SLSI_PSID_DEPRECATED_UNIFI_UPDATE_ANTENNA_CAPABILITIES_WHEN_SCANNING 0x08E5 + +/******************************************************************************* + * NAME : UnifiPreferredAntennaBitmap + * PSID : 2278 (0x08E6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Specify the preferred antenna(s) to use. A value of 0 means that the FW + * will decide on the antenna(s) to use. Only valid for multi-radio + * platforms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PREFERRED_ANTENNA_BITMAP 0x08E6 + +/******************************************************************************* + * NAME : UnifiMaxConcurrentMaCs + * PSID : 2279 (0x08E7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * Specify the maximum number of MACs that may be used for the platform. For + * multi-MAC platforms that value *could* be greater than 1. WARNING: + * Changing this value after system start-up will have no effect. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAX_CONCURRENT_MA_CS 0x08E7 + +/******************************************************************************* + * NAME : UnifiLoadDpdLutPerRadio + * PSID : 2280 (0x08E8) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 147 + * MAX : 147 + * DEFAULT : + * DESCRIPTION : + * Write a static DPD LUT to the FW, read DPD LUT from hardware (for devices + * that support multiple radios) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LOAD_DPD_LUT_PER_RADIO 0x08E8 + +/******************************************************************************* + * NAME : UnifiOverrideDpdLutPerRadio + * PSID : 2281 (0x08E9) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 147 + * MAX : 147 + * DEFAULT : + * DESCRIPTION : + * Write a DPD LUT directly to the HW (for devices that support multiple + * radios) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_DPD_LUT_PER_RADIO 0x08E9 + +/******************************************************************************* + * NAME : UnifiRoamDeauthReason + * PSID : 2294 (0x08F6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 3 + * DESCRIPTION : + * A deauthentication reason for which the STA will trigger a roaming scan + * rather than disconnect directly. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_DEAUTH_REASON 0x08F6 + +/******************************************************************************* + * NAME : UnifiCuRoamfactor + * PSID : 2295 (0x08F7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Table allocating CUfactor to Channel Utilisation values range. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CU_ROAMFACTOR 0x08F7 + +/******************************************************************************* + * NAME : UnifiRoamCuHighLowPoints + * PSID : 2296 (0x08F8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Table allocating the high and low points for computing the linear + * CUfactor. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_CU_HIGH_LOW_POINTS 0x08F8 + +/******************************************************************************* + * NAME : UnifiRoamRssiHighLowPoints + * PSID : 2297 (0x08F9) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Table allocating the high and low points for computing the linear RSSI + * factor. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_RSSI_HIGH_LOW_POINTS 0x08F9 + +/******************************************************************************* + * NAME : UnifiRoamRssiBoost + * PSID : 2298 (0x08FA) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * The value in dBm of the RSSI boost for each band + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_RSSI_BOOST 0x08FA + +/******************************************************************************* + * NAME : UnifiRoamTrackingScanPeriod + * PSID : 2299 (0x08FB) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 1 + * MAX : 4294967295 + * DEFAULT : 5000000 + * DESCRIPTION : + * The scan period for tracking not yet suitable candidate(s)(microseconds) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_TRACKING_SCAN_PERIOD 0x08FB + +/******************************************************************************* + * NAME : UnifiRoamCuLocal + * PSID : 2300 (0x08FC) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Channel utilisation for the STA VIF, value 255=100% channel utilisation. + * - used for roaming + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_CU_LOCAL 0x08FC + +/******************************************************************************* + * NAME : UnifiCuRoamScanNoCandidateDeltaTrigger + * PSID : 2301 (0x08FD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 15 + * DESCRIPTION : + * The delta, in percentage points, to apply to unifiCuRoamScanTrigger when + * no candidate found during first cycle of cached channel soft scan, + * triggered by channel utilization. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CU_ROAM_SCAN_NO_CANDIDATE_DELTA_TRIGGER 0x08FD + +/******************************************************************************* + * NAME : UnifiRoamApSelectDeltaFactor + * PSID : 2302 (0x08FE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 20 + * DESCRIPTION : + * How much higher, in percentage points, does a candidate's score needs to + * be in order be considered an eligible candidate? A "0" value renders all + * candidates eligible. Please note this applies only to soft roams. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_AP_SELECT_DELTA_FACTOR 0x08FE + +/******************************************************************************* + * NAME : UnifiCuRoamweight + * PSID : 2303 (0x08FF) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 30 + * DESCRIPTION : + * Weight of CUfactor, in percentage points, in AP selection algorithm. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CU_ROAMWEIGHT 0x08FF + +/******************************************************************************* + * NAME : UnifiRssiRoamweight + * PSID : 2305 (0x0901) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 70 + * DESCRIPTION : + * Weight of RSSI factor, in percentage points, in AP selection algorithm. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI_ROAMWEIGHT 0x0901 + +/******************************************************************************* + * NAME : UnifiRssiRoamfactor + * PSID : 2306 (0x0902) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Table allocating RSSIfactor to RSSI values range. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSI_ROAMFACTOR 0x0902 + +/******************************************************************************* + * NAME : UnifiRssicuRoamScanTrigger + * PSID : 2307 (0x0903) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * The current channel Averaged RSSI value below which a soft roaming scan + * shall initially start, providing high channel utilisation (see + * unifiCURoamScanTrigger). This is a table indexed by frequency band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RSSICU_ROAM_SCAN_TRIGGER 0x0903 + +/******************************************************************************* + * NAME : UnifiCuRoamScanTrigger + * PSID : 2308 (0x0904) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * BSS Load / Channel Utilisation doesn't need to be monitored more than + * every 10th Beacons. This is a table indexed by frequency band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CU_ROAM_SCAN_TRIGGER 0x0904 + +/******************************************************************************* + * NAME : UnifiRoamBssLoadMonitoringFrequency + * PSID : 2309 (0x0905) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 10 + * DESCRIPTION : + * How often, in reveived beacons, should the BSS load be monitored? - used + * for roaming + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_BSS_LOAD_MONITORING_FREQUENCY 0x0905 + +/******************************************************************************* + * NAME : UnifiRoamBlacklistSize + * PSID : 2310 (0x0906) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : entries + * MIN : 0 + * MAX : 100 + * DEFAULT : 5 + * DESCRIPTION : + * Do not remove! Read by the host! And then passed up to the framework. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_BLACKLIST_SIZE 0x0906 + +/******************************************************************************* + * NAME : UnifiCuMeasurementInterval + * PSID : 2311 (0x0907) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 1 + * MAX : 1000 + * DEFAULT : 500 + * DESCRIPTION : + * The interval in ms to perform the channel usage update + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CU_MEASUREMENT_INTERVAL 0x0907 + +/******************************************************************************* + * NAME : UnifiCurrentBssNss + * PSID : 2312 (0x0908) + * PER INTERFACE?: NO + * TYPE : unifiAntennaMode + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * specifies current AP antenna mode: 0 = SISO, 1 = MIMO (2x2), 2 = MIMO + * (3x3), 3 = MIMO (4x4) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CURRENT_BSS_NSS 0x0908 + +/******************************************************************************* + * NAME : UnifiApMimoUsed + * PSID : 2313 (0x0909) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * AP uses MU-MIMO + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_MIMO_USED 0x0909 + +/******************************************************************************* + * NAME : UnifiRoamEapolTimeout + * PSID : 2314 (0x090A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 10 + * DESCRIPTION : + * Maximum time, in seconds, allowed for an offloaded Eapol (4 way + * handshake). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_EAPOL_TIMEOUT 0x090A + +/******************************************************************************* + * NAME : UnifiRoamingCount + * PSID : 2315 (0x090B) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Number of roams + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAMING_COUNT 0x090B + +/******************************************************************************* + * NAME : UnifiRoamingAkm + * PSID : 2316 (0x090C) + * PER INTERFACE?: NO + * TYPE : unifiRoamingAKM + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * specifies current AKM 0 = None 1 = OKC 2 = FT (FT_1X) 3 = PSK 4 = FT_PSK + * 5 = PMKSA Caching + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAMING_AKM 0x090C + +/******************************************************************************* + * NAME : UnifiCurrentBssBandwidth + * PSID : 2317 (0x090D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Current bandwidth the STA is operating on channel_bw_20_mhz = 20, + * channel_bw_40_mhz = 40, channel_bw_80_mhz = 80, channel_bw_160_mhz = 160 + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CURRENT_BSS_BANDWIDTH 0x090D + +/******************************************************************************* + * NAME : UnifiCurrentBssChannelFrequency + * PSID : 2318 (0x090E) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Centre frequency for the connected channel + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CURRENT_BSS_CHANNEL_FREQUENCY 0x090E + +/******************************************************************************* + * NAME : UnifiLoggerEnabled + * PSID : 2320 (0x0910) + * PER INTERFACE?: NO + * TYPE : unifiWifiLogger + * MIN : 0 + * MAX : 255 + * DEFAULT : 1 + * DESCRIPTION : + * Enable reporting of the following events for Android logging: - firmware + * connectivity events - fate of management frames sent by the host through + * the MLME SAP It can take the following values: - 0: reporting for non + * mandetory triggers disabled. EAPOL, security, btm frames and roam + * triggers are reported. - 1: partial reporting is enabled. Beacons frames + * will not be reported. - 2: full reporting is enabled. Beacons frames are + * included. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LOGGER_ENABLED 0x0910 + +/******************************************************************************* + * NAME : UnifiMaPacketFateEnabled + * PSID : 2321 (0x0911) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enable reporting of the fate of the TX packets sent by the host.This mib + * value will be updated if "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MA_PACKET_FATE_ENABLED 0x0911 + +/******************************************************************************* + * NAME : UnifiFrameRxCounters + * PSID : 2326 (0x0916) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Frame RX Counters used by the host. These are required by MCD. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FRAME_RX_COUNTERS 0x0916 + +/******************************************************************************* + * NAME : UnifiFrameTxCounters + * PSID : 2327 (0x0917) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Frame TX Counters used by the host. These are required by MCD. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FRAME_TX_COUNTERS 0x0917 + +/******************************************************************************* + * NAME : UnifiLaaNssSpeculationIntervalSlotTime + * PSID : 2330 (0x091A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 300 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the repeatable amount of time, + * in ms, that firmware will start to send speculation frames for spatial + * streams. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_NSS_SPECULATION_INTERVAL_SLOT_TIME 0x091A + +/******************************************************************************* + * NAME : UnifiLaaNssSpeculationIntervalSlotMaxNum + * PSID : 2331 (0x091B) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 5 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the maximum number of + * speculation time slot for spatial stream. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_NSS_SPECULATION_INTERVAL_SLOT_MAX_NUM 0x091B + +/******************************************************************************* + * NAME : UnifiLaaBwSpeculationIntervalSlotTime + * PSID : 2332 (0x091C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 300 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the repeatable amount of time, + * in ms, that firmware will start to send speculation frames for bandwidth. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_BW_SPECULATION_INTERVAL_SLOT_TIME 0x091C + +/******************************************************************************* + * NAME : UnifiLaaBwSpeculationIntervalSlotMaxNum + * PSID : 2333 (0x091D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 8 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the maximum number of + * speculation time slot for bandwidth. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_BW_SPECULATION_INTERVAL_SLOT_MAX_NUM 0x091D + +/******************************************************************************* + * NAME : UnifiLaaMcsSpeculationIntervalSlotTime + * PSID : 2334 (0x091E) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 100 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the repeatable amount of time, + * in ms, that firmware will start to send speculation frames for MCS or + * rate index. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_MCS_SPECULATION_INTERVAL_SLOT_TIME 0x091E + +/******************************************************************************* + * NAME : UnifiLaaMcsSpeculationIntervalSlotMaxNum + * PSID : 2335 (0x091F) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the maximum number of + * speculation time slot for MCS or rate index. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_MCS_SPECULATION_INTERVAL_SLOT_MAX_NUM 0x091F + +/******************************************************************************* + * NAME : UnifiLaaGiSpeculationIntervalSlotTime + * PSID : 2336 (0x0920) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 100 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the repeatable amount of time, + * in ms, that firmware will start to send speculation frames for guard + * interval. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_GI_SPECULATION_INTERVAL_SLOT_TIME 0x0920 + +/******************************************************************************* + * NAME : UnifiLaaGiSpeculationIntervalSlotMaxNum + * PSID : 2337 (0x0921) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 50 + * DESCRIPTION : + * For Link Adaptation Algorithm. It defines the maximum number of + * speculation time slot for guard interval. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_GI_SPECULATION_INTERVAL_SLOT_MAX_NUM 0x0921 + +/******************************************************************************* + * NAME : UnifiLaaTxDiversityBeamformEnabled + * PSID : 2350 (0x092E) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * For Link Adaptation Algorithm. It is used to enable or disable TX + * beamformer functionality. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_TX_DIVERSITY_BEAMFORM_ENABLED 0x092E + +/******************************************************************************* + * NAME : UnifiLaaTxDiversityBeamformMinMcs + * PSID : 2351 (0x092F) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * For Link Adaptation Algorithm. TX Beamform is applied when MCS is same or + * larger than this threshold value. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_TX_DIVERSITY_BEAMFORM_MIN_MCS 0x092F + +/******************************************************************************* + * NAME : UnifiLaaTxDiversityFixMode + * PSID : 2352 (0x0930) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * For Link Adaptation Algorithm. It is used to fix TX diversity mode. With + * two antennas available and only one spatial stream used, then one of the + * following modes can be selected: - 0 : Not fixed. Tx diversity mode is + * automatically selected by LAA. - 1 : CDD fixed mode - 2 : Beamforming + * fixed mode - 3 : STBC fixed mode + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_TX_DIVERSITY_FIX_MODE 0x0930 + +/******************************************************************************* + * NAME : UnifiLaaProtectionConfigOverride + * PSID : 2356 (0x0934) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 6 + * DESCRIPTION : + * Overrides the default Protection configuration. Only valid flags are + * DPIF_PEER_INFO_PROTECTION_TXOP_AMPDU and + * DPIF_PEER_INFO_PROTECTION_ALLOWED. Default allows protection code to work + * out the rules based on VIF configuration. If + * DPIF_PEER_INFO_PROTECTION_ALLOWED is unset, all protection, for this vif, + * is disabled. If DPIF_PEER_INFO_PROTECTION_TXOP_AMPDU is unset then, for + * the specified vif, the first A-MPDU in the TxOp is no longer protected. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LAA_PROTECTION_CONFIG_OVERRIDE 0x0934 + +/******************************************************************************* + * NAME : UnifiRateStatsRtsErrorCount + * PSID : 2358 (0x0936) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of successive RTS failures. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RATE_STATS_RTS_ERROR_COUNT 0x0936 + +/******************************************************************************* + * NAME : UnifiCsrOnlyEifsDuration + * PSID : 2362 (0x093A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 12 + * DESCRIPTION : + * Specifies time that is used for EIFS. A value of 0 causes the build in + * value to be used. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CSR_ONLY_EIFS_DURATION 0x093A + +/******************************************************************************* + * NAME : UnifiOverrideDefaultBetxopForHt + * PSID : 2364 (0x093C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 171 + * DESCRIPTION : + * When set to non-zero value then this will override the BE TXOP for 11n + * and higher modulations (in 32 usec units) to the value specified here. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_DEFAULT_BETXOP_FOR_HT 0x093C + +/******************************************************************************* + * NAME : UnifiOverrideDefaultBetxop + * PSID : 2365 (0x093D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 78 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: When set to + * non-zero value then this will override the BE TXOP for 11g (in 32 usec + * units) to the value specified here. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_DEFAULT_BETXOP 0x093D + +/******************************************************************************* + * NAME : UnifiRxabbTrimSettings + * PSID : 2366 (0x093E) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Various settings to change RX ABB filter trim behavior. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RXABB_TRIM_SETTINGS 0x093E + +/******************************************************************************* + * NAME : UnifiRadioTrimsEnable + * PSID : 2367 (0x093F) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X0FF5 + * DESCRIPTION : + * A bitmap for enabling/disabling trims at runtime. Check unifiEnabledTrims + * enum for description of the possible values. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_TRIMS_ENABLE 0x093F + +/******************************************************************************* + * NAME : UnifiRadioCcaThresholds + * PSID : 2368 (0x0940) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * The wideband CCA ED thresholds so that the CCA-ED triggers at the + * regulatory value of -62 dBm. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_CCA_THRESHOLDS 0x0940 + +/******************************************************************************* + * NAME : UnifiHardwarePlatform + * PSID : 2369 (0x0941) + * PER INTERFACE?: NO + * TYPE : unifiHardwarePlatform + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware platform. This is necessary so we can apply tweaks to specific + * revisions, even though they might be running the same baseband and RF + * chip combination. Check unifiHardwarePlatform enum for description of the + * possible values. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_HARDWARE_PLATFORM 0x0941 + +/******************************************************************************* + * NAME : UnifiForceChannelBw + * PSID : 2370 (0x0942) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Test only: Force channel bandwidth to specified value. This can also be + * used to allow emulator/silicon back to back connection to communicate at + * bandwidth other than default (20 MHz) Setting it to 0 uses the default + * bandwidth as selected by firmware channel_bw_20_mhz = 20, + * channel_bw_40_mhz = 40, channel_bw_80_mhz = 80 This mib value will be + * updated if "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FORCE_CHANNEL_BW 0x0942 + +/******************************************************************************* + * NAME : UnifiDpdTrainingDuration + * PSID : 2371 (0x0943) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Duration of DPD training (in ms). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPD_TRAINING_DURATION 0x0943 + +/******************************************************************************* + * NAME : UnifiTxFtrimSettings + * PSID : 2372 (0x0944) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter frequency compensation settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_FTRIM_SETTINGS 0x0944 + +/******************************************************************************* + * NAME : UnifiDpdTrainPacketConfig + * PSID : 2373 (0x0945) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 8 + * MAX : 8 + * DEFAULT : + * DESCRIPTION : + * This MIB allows the dummy packets training bandwidth and rates to be + * overriden. Tipically the bandwidth would be the same as the channel + * bandwidth (for example 80 MHz packets for an 80 Mhz channel) and rates + * MCS1 and MCS5. With this MIB you can set, for example, an 80 MHz channel + * to be trained using 20 MHz bandwidth (centered or not) with MCS2 and MCS7 + * packets. The MIB index dictates what channel bandwidth the configuration + * is for (1 for 20 MHz, 2 for 40 MHz and so on). The format is: - octet 0: + * train bandwidth (this basically follows the rice_channel_bw_t enum). - + * octet 1: train primary channel position - octet 2-3: OFDM 0 rate - octet + * 4-5: OFDM 1 rate - octet 6-7: CCK rate (unused) The rates are encoded in + * host(FAPI) format, see SC-506179, section 4.41. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPD_TRAIN_PACKET_CONFIG 0x0945 + +/******************************************************************************* + * NAME : UnifiTxPowerTrimCommonConfig + * PSID : 2374 (0x0946) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 3 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Common transmitter power trim settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_POWER_TRIM_COMMON_CONFIG 0x0946 + +/******************************************************************************* + * NAME : UnifiIqDebugEnabled + * PSID : 2375 (0x0947) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Send IQ capture data to host for IQ debug + *******************************************************************************/ +#define SLSI_PSID_UNIFI_IQ_DEBUG_ENABLED 0x0947 + +/******************************************************************************* + * NAME : UnifiCoexDebugOverrideBt + * PSID : 2425 (0x0979) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables overriding of all BT activities by WLAN. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_COEX_DEBUG_OVERRIDE_BT 0x0979 + +/******************************************************************************* + * NAME : UnifiLteMailbox + * PSID : 2430 (0x097E) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 36 + * MAX : 40 + * DEFAULT : + * DESCRIPTION : + * Set modem status to simulate lte status updates. See SC-505775-SP for API + * description. Defined as array of uint32 represented by the octet string + * FOR TEST PURPOSES ONLY + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_MAILBOX 0x097E + +/******************************************************************************* + * NAME : UnifiLteMwsSignal + * PSID : 2431 (0x097F) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Set modem status to simulate lte status updates. See SC-505775-SP for API + * description. See unifiLteSignalsBitField for enum bitmap. FOR TEST + * PURPOSES ONLY + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_MWS_SIGNAL 0x097F + +/******************************************************************************* + * NAME : UnifiLteEnableChannelAvoidance + * PSID : 2432 (0x0980) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables channel avoidance scheme for LTE Coex + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_ENABLE_CHANNEL_AVOIDANCE 0x0980 + +/******************************************************************************* + * NAME : UnifiLteEnablePowerBackoff + * PSID : 2433 (0x0981) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables power backoff scheme for LTE Coex + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_ENABLE_POWER_BACKOFF 0x0981 + +/******************************************************************************* + * NAME : UnifiLteEnableTimeDomain + * PSID : 2434 (0x0982) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables TDD scheme for LTE Coex + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_ENABLE_TIME_DOMAIN 0x0982 + +/******************************************************************************* + * NAME : UnifiLteEnableLteCoex + * PSID : 2435 (0x0983) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables LTE Coex support + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_ENABLE_LTE_COEX 0x0983 + +/******************************************************************************* + * NAME : UnifiLteBand40PowerBackoffChannelMask + * PSID : 2436 (0x0984) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 3 + * DESCRIPTION : + * Channel Mask defining channels on which to apply power backoff when LTE + * operating on Band40. Defined as a 16 bit bitmask, as only 2G4 channels + * are impacted by this feature. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_BAND40_POWER_BACKOFF_CHANNEL_MASK 0x0984 + +/******************************************************************************* + * NAME : UnifiLteBand40PowerBackoffRsrpLow + * PSID : 2437 (0x0985) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -140 + * MAX : -77 + * DEFAULT : -100 + * DESCRIPTION : + * WLAN Power Reduction shall be applied when RSRP of LTE operating on band + * 40 falls below this level + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_BAND40_POWER_BACKOFF_RSRP_LOW 0x0985 + +/******************************************************************************* + * NAME : UnifiLteBand40PowerBackoffRsrpHigh + * PSID : 2438 (0x0986) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -140 + * MAX : -77 + * DEFAULT : -95 + * DESCRIPTION : + * WLAN Power Reduction shall be restored when RSRP of LTE operating on band + * 40 climbs above this level + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_BAND40_POWER_BACKOFF_RSRP_HIGH 0x0986 + +/******************************************************************************* + * NAME : UnifiLteBand40PowerBackoffRsrpAveragingAlpha + * PSID : 2439 (0x0987) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : percentage + * MIN : 0 + * MAX : 65535 + * DEFAULT : 50 + * DESCRIPTION : + * Weighting applied when calculaing the average RSRP when considering Power + * Back Off Specifies the percentage weighting (alpha) to give to the most + * recent value when calculating the moving average. ma_new = alpha * + * new_sample + (1-alpha) * ma_old. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_BAND40_POWER_BACKOFF_RSRP_AVERAGING_ALPHA 0x0987 + +/******************************************************************************* + * NAME : UnifiLteSetChannel + * PSID : 2440 (0x0988) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Enables LTE Coex support + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_SET_CHANNEL 0x0988 + +/******************************************************************************* + * NAME : UnifiLteSetPowerBackoff + * PSID : 2441 (0x0989) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * MIB to force WLAN Power Backoff for LTE COEX testing purposes + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_SET_POWER_BACKOFF 0x0989 + +/******************************************************************************* + * NAME : UnifiLteSetTddDebugMode + * PSID : 2442 (0x098A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * MIB to enable LTE TDD COEX simulation for testing purposes + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_SET_TDD_DEBUG_MODE 0x098A + +/******************************************************************************* + * NAME : UnifiApScanAbsenceDuration + * PSID : 2480 (0x09B0) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : beacon intervals + * MIN : 0 + * MAX : 65535 + * DEFAULT : 7 + * DESCRIPTION : + * Duration of the Absence time to use when protecting AP VIFs from scan + * operations. A value of 0 disables the feature. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_SCAN_ABSENCE_DURATION 0x09B0 + +/******************************************************************************* + * NAME : UnifiApScanAbsencePeriod + * PSID : 2481 (0x09B1) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : beacon intervals + * MIN : 0 + * MAX : 65535 + * DEFAULT : 14 + * DESCRIPTION : + * Period of the Absence/Presence times cycles to use when protecting AP + * VIFs from scan operations. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_SCAN_ABSENCE_PERIOD 0x09B1 + +/******************************************************************************* + * NAME : UnifiMlmestaKeepAliveTimeoutCheck + * PSID : 2485 (0x09B5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 5 + * DESCRIPTION : + * DO NOT SET TO A VALUE HIGHER THAN THE TIMEOUT. How long before keepalive + * timeout to start polling, in seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMESTA_KEEP_ALIVE_TIMEOUT_CHECK 0x09B5 + +/******************************************************************************* + * NAME : UnifiMlmeapKeepAliveTimeoutCheck + * PSID : 2486 (0x09B6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 5 + * DESCRIPTION : + * DO NOT SET TO A VALUE HIGHER THAN THE TIMEOUT. How long before keepalive + * timeout to start polling, in seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMEAP_KEEP_ALIVE_TIMEOUT_CHECK 0x09B6 + +/******************************************************************************* + * NAME : UnifiMlmegoKeepAliveTimeoutCheck + * PSID : 2487 (0x09B7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 5 + * DESCRIPTION : + * DO NOT SET TO A VALUE HIGHER THAN THE TIMEOUT. How long before keepalive + * timeout to start polling, in seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMEGO_KEEP_ALIVE_TIMEOUT_CHECK 0x09B7 + +/******************************************************************************* + * NAME : UnifiBssMaxIdlePeriod + * PSID : 2488 (0x09B8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : second + * MIN : 0 + * MAX : 300 + * DEFAULT : 300 + * DESCRIPTION : + * BSS Idle MAX Period. Used to cap the value coming from BSS Max Idle + * Period IE, in seconds + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BSS_MAX_IDLE_PERIOD 0x09B8 + +/******************************************************************************* + * NAME : UnifiIdlemodeListenIntervalSkippingDtim + * PSID : 2495 (0x09BF) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : DTIM intervals + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X00054645 + * DESCRIPTION : + * Listen interval of beacons when in single-vif power saving mode, + * receiving DTIMs is enabled and idle mode enabled. No DTIMs are skipped + * during MVIF operation. A maximum of the listen interval beacons are + * skipped, which may be less than the number of DTIMs that can be skipped. + * The value is a lookup table for DTIM counts. Each 4bits, in LSB order, + * represent DTIM1, DTIM2, DTIM3, DTIM4, DTIM5, (unused). This key is only + * used for STA VIF, connected to an AP. For P2P group client intervals, + * refer to unifiIdlemodeP2PListenIntervalSkippingDTIM, PSID=2496. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_IDLEMODE_LISTEN_INTERVAL_SKIPPING_DTIM 0x09BF + +/******************************************************************************* + * NAME : UnifiIdlemodeP2PListenIntervalSkippingDtim + * PSID : 2496 (0x09C0) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : DTIM intervals + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X00000002 + * DESCRIPTION : + * Listen interval of beacons when in single-vif, P2P client power saving + * mode,receiving DTIMs and idle mode enabled. No DTIMs are skipped during + * MVIF operation. A maximum of (listen interval - 1) beacons are skipped, + * which may be less than the number of DTIMs that can be skipped. The value + * is a lookup table for DTIM counts. Each 4bits, in LSB order, represent + * DTIM1, DTIM2, DTIM3, DTIM4, DTIM5, (unused). This key is only used for + * P2P group client. For STA connected to an AP, refer to + * unifiIdlemodeListenIntervalSkippingDTIM, PSID=2495. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_IDLEMODE_P2_PLISTEN_INTERVAL_SKIPPING_DTIM 0x09C0 + +/******************************************************************************* + * NAME : UnifiApIdleModeEnabled + * PSID : 2497 (0x09C1) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables AP Idle mode which can transmit beacons in MIFLess mode, if + * softAP is active, and there has been no activity for a time. This mib has + * priority over unifiIdleModeLiteEnabled. If unifiAPIdleEnabled is enabled, + * Idle Mode Lite won't be activated. This mib value will be runtime + * (post-wlan enable) applied only if "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_IDLE_MODE_ENABLED 0x09C1 + +/******************************************************************************* + * NAME : UnifiFastPowerSaveTimeout + * PSID : 2500 (0x09C4) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 400000 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: UniFi + * implements a proprietary power management mode called Fast Power Save + * that balances network performance against power consumption. In this mode + * UniFi delays entering power save mode until it detects that there has + * been no exchange of data for the duration of time specified. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FAST_POWER_SAVE_TIMEOUT 0x09C4 + +/******************************************************************************* + * NAME : UnifiFastPowerSaveTimeoutSmall + * PSID : 2501 (0x09C5) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 200000 + * DESCRIPTION : + * UniFi implements a proprietary power management mode called Fast Power + * Save that balances network performance against power consumption. In this + * mode UniFi delays entering power save mode until it detects that there + * has been no exchange of data for the duration of time specified. The + * unifiFastPowerSaveTimeOutSmall aims to improve the power consumption by + * setting a lower bound for the Fast Power Save Timeout. If set with a + * value above unifiFastPowerSaveTimeOut it will default to + * unifiFastPowerSaveTimeOut. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FAST_POWER_SAVE_TIMEOUT_SMALL 0x09C5 + +/******************************************************************************* + * NAME : UnifiMlmestaKeepAliveTimeout + * PSID : 2502 (0x09C6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2147 + * DEFAULT : 30 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Timeout + * before disconnecting in seconds. 0 = Disabled. Capped to greater than 6 + * seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMESTA_KEEP_ALIVE_TIMEOUT 0x09C6 + +/******************************************************************************* + * NAME : UnifiMlmeapKeepAliveTimeout + * PSID : 2503 (0x09C7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2147 + * DEFAULT : 10 + * DESCRIPTION : + * Timeout before disconnecting in seconds. 0 = Disabled. Capped to greater + * than 6 seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMEAP_KEEP_ALIVE_TIMEOUT 0x09C7 + +/******************************************************************************* + * NAME : UnifiMlmegoKeepAliveTimeout + * PSID : 2504 (0x09C8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2147 + * DEFAULT : 10 + * DESCRIPTION : + * Timeout before disconnecting in seconds. 0 = Disabled. Capped to greater + * than 6 seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLMEGO_KEEP_ALIVE_TIMEOUT 0x09C8 + +/******************************************************************************* + * NAME : UnifiStaRouterAdvertisementMinimumIntervalToForward + * PSID : 2505 (0x09C9) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 60 + * MAX : 4294967285 + * DEFAULT : 60 + * DESCRIPTION : + * STA Mode: Minimum interval to forward Router Advertisement frames to + * Host. Minimum value = 60 secs. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_ROUTER_ADVERTISEMENT_MINIMUM_INTERVAL_TO_FORWARD 0x09C9 + +/******************************************************************************* + * NAME : UnifiRoamConnectionQualityCheckWaitAfterConnect + * PSID : 2506 (0x09CA) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : ms + * MIN : 0 + * MAX : 65535 + * DEFAULT : 200 + * DESCRIPTION : + * The amount of time a STA will wait after connection before starting to + * check the MLME-installed connection quality trigger thresholds + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ROAM_CONNECTION_QUALITY_CHECK_WAIT_AFTER_CONNECT 0x09CA + +/******************************************************************************* + * NAME : UnifiApBeaconMaxDrift + * PSID : 2507 (0x09CB) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0XFFFF + * DESCRIPTION : + * The maximum drift in microseconds we will allow for each beacon sent when + * we're trying to move it to get a 50% duty cycle between GO and STA in + * multiple VIF scenario. We'll delay our TX beacon by a maximum of this + * value until we reach our target TBTT. We have 3 possible cases for this + * value: a) ap_beacon_max_drift = 0x0000 - Feature disabled b) + * ap_beacon_max_drift between 0x0001 and 0xFFFE - Each time we transmit the + * beacon we'll move it a little bit forward but never more than this. (Not + * implemented yet) c) ap_beacon_max_drift = 0xFFFF - Move the beacon to the + * desired position in one shot. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AP_BEACON_MAX_DRIFT 0x09CB + +/******************************************************************************* + * NAME : UnifiBssMaxIdlePeriodEnabled + * PSID : 2508 (0x09CC) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * If set STA will configure keep-alive with options specified in a received + * BSS max idle period IE + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BSS_MAX_IDLE_PERIOD_ENABLED 0x09CC + +/******************************************************************************* + * NAME : UnifiVifIdleMonitorTime + * PSID : 2509 (0x09CD) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : second + * MIN : 0 + * MAX : 1800 + * DEFAULT : 1 + * DESCRIPTION : + * In Fast Power Save mode, the STA will decide whether it is idle based on + * monitoring its traffic class. If the traffic class is continuously + * "occasional" for equal or longer than the specified value (in seconds), + * then the VIF is marked as idle. Traffic class monitoring is based on the + * interval specified in the "unifiExitPowerSavePeriod" MIB + *******************************************************************************/ +#define SLSI_PSID_UNIFI_VIF_IDLE_MONITOR_TIME 0x09CD + +/******************************************************************************* + * NAME : UnifiDisableLegacyPowerSave + * PSID : 2510 (0x09CE) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: This affects + * Station VIF power save behaviour. Setting it to true will disable legacy + * power save (i.e. we wil use fast power save to retrieve data) Note that + * actually disables full power save mode (i.e sending trigger to retrieve + * frames which will be PS-POLL for legacy and QOS-NULL for UAPSD) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DISABLE_LEGACY_POWER_SAVE 0x09CE + +/******************************************************************************* + * NAME : UnifiDebugForceActive + * PSID : 2511 (0x09CF) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Force station + * power save mode to be active (when scheduled). VIF scheduling, coex and + * other non-VIF specific reasons could still force power save on the VIF. + * Applies to all VIFs of type station (includes P2P client). Changes to the + * mib will only get applied after next host/mlme power management request. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_FORCE_ACTIVE 0x09CF + +/******************************************************************************* + * NAME : UnifiStationActivityIdleTime + * PSID : 2512 (0x09D0) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : milliseconds + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 500 + * DESCRIPTION : + * Time since last station activity when it can be considered to be idle. + * Only used in SoftAP mode when determining if all connected stations are + * idle (not active). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STATION_ACTIVITY_IDLE_TIME 0x09D0 + +/******************************************************************************* + * NAME : UnifiDmsEnabled + * PSID : 2513 (0x09D1) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables Directed Multicast Service (DMS) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DMS_ENABLED 0x09D1 + +/******************************************************************************* + * NAME : UnifiPowerManagementDelayTimeout + * PSID : 2514 (0x09D2) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 30000 + * DESCRIPTION : + * When UniFi enters power save mode it signals the new state by setting the + * power management bit in the frame control field of a NULL frame. It then + * remains active for the period since the previous unicast reception, or + * since the transmission of the NULL frame, whichever is later. This entry + * controls the maximum time during which UniFi will continue to listen for + * data. This allows any buffered data on a remote device to be cleared. + * Specifies an upper limit on the timeout. UniFi internally implements a + * proprietary algorithm to adapt the timeout depending upon the + * situation.This is used by firmware when current station VIF is only + * station VIF which can be scheduled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_POWER_MANAGEMENT_DELAY_TIMEOUT 0x09D2 + +/******************************************************************************* + * NAME : UnifiApsdServicePeriodTimeout + * PSID : 2515 (0x09D3) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 20000 + * DESCRIPTION : + * During Unscheduled Automated Power Save Delivery (U-APSD), UniFi may + * trigger a service period in order to fetch data from the access point. + * The service period is normally terminated by a frame from the access + * point with the EOSP (End Of Service Period) flag set, at which point + * UniFi returns to sleep. However, if the access point is temporarily + * inaccessible, UniFi would stay awake indefinitely. Specifies a timeout + * starting from the point where the trigger frame has been sent. If the + * timeout expires and no data has been received from the access point, + * UniFi will behave as if the service period had been ended normally and + * return to sleep. This timeout takes precedence over + * unifiPowerSaveExtraListenTime if both would otherwise be applicable. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_APSD_SERVICE_PERIOD_TIMEOUT 0x09D3 + +/******************************************************************************* + * NAME : UnifiConcurrentPowerManagementDelayTimeout + * PSID : 2516 (0x09D4) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 10000 + * DESCRIPTION : + * When UniFi enters power save mode it signals the new state by setting the + * power management bit in the frame control field of a NULL frame. It then + * remains active for the period since the previous unicast reception, or + * since the transmission of the NULL frame, whichever is later. This entry + * controls the maximum time during which UniFi will continue to listen for + * data. This allows any buffered data on a remote device to be cleared. + * This is same as unifiPowerManagementDelayTimeout but this value is + * considered only when we are doing multivif operations and other VIFs are + * waiting to be scheduled.Note that firmware automatically chooses one of + * unifiPowerManagementDelayTimeout and + * unifiConcurrentPowerManagementDelayTimeout depending upon the current + * situation.It is sensible to set unifiPowerManagementDelayTimeout to be + * always more thanunifiConcurrentPowerManagementDelayTimeout. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CONCURRENT_POWER_MANAGEMENT_DELAY_TIMEOUT 0x09D4 + +/******************************************************************************* + * NAME : UnifiStationQosInfo + * PSID : 2517 (0x09D5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * QoS capability for a non-AP Station, and is encoded as per IEEE 802.11 + * QoS Capability. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STATION_QOS_INFO 0x09D5 + +/******************************************************************************* + * NAME : UnifiListenIntervalSkippingDtim + * PSID : 2518 (0x09D6) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : DTIM intervals + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X000A89AA + * DESCRIPTION : + * Listen interval of beacons when in single-vif power saving mode,receiving + * DTIMs is enabled and idle mode disabled. No DTIMs are skipped during MVIF + * operation. A maximum of the listen interval beacons are skipped, which + * may be less than the number of DTIMs that can be skipped. The value is a + * lookup table for DTIM counts. Each 4bits, in LSB order, represent DTIM1, + * DTIM2, DTIM3, DTIM4, DTIM5, (unused). This key is only used for STA VIF, + * connected to an AP. For P2P group client intervals, refer to + * unifiP2PListenIntervalSkippingDTIM, PSID=2523. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LISTEN_INTERVAL_SKIPPING_DTIM 0x09D6 + +/******************************************************************************* + * NAME : UnifiListenInterval + * PSID : 2519 (0x09D7) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 10 + * DESCRIPTION : + * Association request listen interval parameter in beacon intervals. Not + * used for any other purpose. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LISTEN_INTERVAL 0x09D7 + +/******************************************************************************* + * NAME : UnifiLegacyPsPollTimeout + * PSID : 2520 (0x09D8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 15000 + * DESCRIPTION : + * Time we try to stay awake after sending a PS-POLL to receive data. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LEGACY_PS_POLL_TIMEOUT 0x09D8 + +/******************************************************************************* + * NAME : UnifiBeaconSkippingControl + * PSID : 2521 (0x09D9) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X00010103 + * DESCRIPTION : + * Control beacon skipping behaviour within firmware with bit flags. 1 + * defines enabled, with 0 showing the case disabled. If beacon skipping is + * enabled, further determine if DTIM beacons can be skipped, or only + * non-DTIM beacons. The following applies: bit 0: station skipping on host + * suspend bit 1: station skipping on host awake bit 2: station skipping on + * LCD on bit 3: station skipping with multivif bit 4: station skipping with + * BT active. bit 8: station skip dtim on host suspend bit 9: station skip + * dtim on host awake bit 10: station skip dtim on LCD on bit 11: station + * skip dtim on multivif bit 12: station skip dtim with BT active bit 16: + * p2p-gc skipping on host suspend bit 17: p2p-gc skipping on host awake bit + * 18: p2p-gc skipping on LCD on bit 19: p2p-gc skipping with multivif bit + * 20: p2p-gc skipping with BT active bit 24: p2p-gc skip dtim on host + * suspend bit 25: p2p-gc skip dtim on host awake bit 26: p2p-gc skip dtim + * on LCD on bit 27: p2p-gc skip dtim on multivif bit 28: p2p-gc skip dtim + * with BT active + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BEACON_SKIPPING_CONTROL 0x09D9 + +/******************************************************************************* + * NAME : UnifiTogglePowerDomain + * PSID : 2522 (0x09DA) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Toggle WLAN power domain when entering dorm mode (deep sleep). When + * entering deep sleep and this value it true, then the WLAN power domain is + * disabled for the deep sleep duration. When false, the power domain is + * left turned on. This is to work around issues with WLAN rx, and is + * considered temporary until the root cause is found and fixed. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TOGGLE_POWER_DOMAIN 0x09DA + +/******************************************************************************* + * NAME : UnifiP2PListenIntervalSkippingDtim + * PSID : 2523 (0x09DB) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : DTIM intervals + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X00000002 + * DESCRIPTION : + * Listen interval of beacons when in single-vif, P2P client power saving + * mode,receiving DTIMs and idle mode disabled. No DTIMs are skipped during + * MVIF operation. A maximum of (listen interval - 1) beacons are skipped, + * which may be less than the number of DTIMs that can be skipped. The value + * is a lookup table for DTIM counts. Each 4bits, in LSB order, represent + * DTIM1, DTIM2, DTIM3, DTIM4, DTIM5, (unused). This key is only used for + * P2P group client. For STA connected to an AP, refer to + * unifiListenIntervalSkippingDTIM, PSID=2518. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_P2_PLISTEN_INTERVAL_SKIPPING_DTIM 0x09DB + +/******************************************************************************* + * NAME : UnifiFragmentationDuration + * PSID : 2524 (0x09DC) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * A limit on transmission time for a data frame. If the data payload would + * take longer than unifiFragmentationDuration to transmit, UniFi will + * attempt to fragment the frame to ensure that the data portion of each + * fragment is within the limit. The limit imposed by the fragmentation + * threshold is also respected, and no more than 16 fragments may be + * generated. If the value is zero no limit is imposed. The value may be + * changed dynamically during connections. Note that the limit is a + * guideline and may not always be respected. In particular, the data rate + * is finalised after fragmentation in order to ensure responsiveness to + * conditions, the calculation is not performed to high accuracy, and octets + * added during encryption are not included in the duration calculation. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FRAGMENTATION_DURATION 0x09DC + +/******************************************************************************* + * NAME : UnifiIdleModeLiteEnabled + * PSID : 2526 (0x09DE) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables Idle Mode Lite, if softAP is active, and there has been no + * activity for a time. Idle mode lite should not be active if host has sent + * a command to change key. This mib value will be runtime (post-wlan + * enable) applied only if "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_IDLE_MODE_LITE_ENABLED 0x09DE + +/******************************************************************************* + * NAME : UnifiIdleModeEnabled + * PSID : 2527 (0x09DF) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enables Idle Mode, if single vif station is active or there is no vif, + * and there has been no activity for a time. This mib value will be runtime + * (post-wlan enable) applied if only "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_IDLE_MODE_ENABLED 0x09DF + +/******************************************************************************* + * NAME : UnifiDtimWaitTimeout + * PSID : 2529 (0x09E1) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : 50000 + * DESCRIPTION : + * If UniFi is in power save and receives a Traffic Indication Map from its + * associated access point with a DTIM indication, it will wait a maximum + * time given by this attribute for succeeding broadcast or multicast + * traffic, or until it receives such traffic with the 'more data' + * flag clear. Any reception of broadcast or multicast traffic with the + * 'more data' flag set, or any reception of unicast data, resets + * the timeout. The timeout can be turned off by setting the value to zero; + * in that case UniFi will remain awake indefinitely waiting for broadcast + * or multicast data. Otherwise, the value should be larger than that of + * unifiPowerSaveExtraListenTime. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DTIM_WAIT_TIMEOUT 0x09E1 + +/******************************************************************************* + * NAME : UnifiListenIntervalMaxTime + * PSID : 2530 (0x09E2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1000 + * DESCRIPTION : + * Maximum number length of time, in Time Units (1TU = 1024us), that can be + * used as a beacon listen interval. This will limit how many beacons maybe + * skipped, and affects the DTIM beacon skipping count; DTIM skipping (if + * enabled) will be such that skipped count = (unifiListenIntervalMaxTime / + * DTIM_period). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LISTEN_INTERVAL_MAX_TIME 0x09E2 + +/******************************************************************************* + * NAME : UnifiScanMaxProbeTransmitLifetime + * PSID : 2531 (0x09E3) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 1 + * MAX : 4294967295 + * DEFAULT : 64 + * DESCRIPTION : + * In TU. If non-zero, used during active scans as the maximum lifetime for + * probe requests. It is the elapsed time after the initial transmission at + * which further attempts to transmit the probe are terminated. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SCAN_MAX_PROBE_TRANSMIT_LIFETIME 0x09E3 + +/******************************************************************************* + * NAME : UnifiPowerSaveTransitionPacketThreshold + * PSID : 2532 (0x09E4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name:If VIF has + * this many packets queued/transmitted/received in last + * unifiFastPowerSaveTransitionPeriod then firmware may decide to come out + * of aggressive power save mode. This is applicable to STA/CLI and AP/GO + * VIFs. Note that this is only a guideline. Firmware internal factors may + * override this MIB. Also see unifiTrafficAnalysisPeriod and + * unifiAggressivePowerSaveTransitionPeriod. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_POWER_SAVE_TRANSITION_PACKET_THRESHOLD 0x09E4 + +/******************************************************************************* + * NAME : UnifiProbeResponseLifetime + * PSID : 2533 (0x09E5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 100 + * DESCRIPTION : + * Lifetime of proberesponse frame in unit of ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PROBE_RESPONSE_LIFETIME 0x09E5 + +/******************************************************************************* + * NAME : UnifiProbeResponseMaxRetry + * PSID : 2534 (0x09E6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 255 + * DEFAULT : 5 + * DESCRIPTION : + * Number of retries of probe response frame. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PROBE_RESPONSE_MAX_RETRY 0x09E6 + +/******************************************************************************* + * NAME : UnifiTrafficAnalysisPeriod + * PSID : 2535 (0x09E7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 0 + * MAX : 65535 + * DEFAULT : 200 + * DESCRIPTION : + * Period in TUs over which firmware counts number of packet + * transmitted/queued/received to make decisions like coming out of + * aggressive power save mode or setting up BlockAck. This is applicable to + * STA/CLI and AP/GO VIFs. Note that this is only a guideline. Firmware + * internal factors may override this MIB. Also see + * unifiPowerSaveTransitionPacketThreshold, + * unifiAggressivePowerSaveTransitionPeriod and + * unifiTrafficThresholdToSetupBA. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TRAFFIC_ANALYSIS_PERIOD 0x09E7 + +/******************************************************************************* + * NAME : UnifiAggressivePowerSaveTransitionPeriod + * PSID : 2536 (0x09E8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 0 + * MAX : 65535 + * DEFAULT : 5 + * DESCRIPTION : + * Defines how many unifiExitPowerSavePeriod firmware should wait in which + * VIF had received/transmitted/queued less than + * unifiPowerSaveTransitionPacketThreshold packets - before entering + * aggressive power save mode (when not in aggressive power save mode) This + * is applicable to STA/CLI and AP/GO VIFs. Note that this is only a + * guideline. Firmware internal factors may override this MIB. Also see + * unifiPowerSaveTransitionPacketThreshold and unifiTrafficAnalysisPeriod. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AGGRESSIVE_POWER_SAVE_TRANSITION_PERIOD 0x09E8 + +/******************************************************************************* + * NAME : UnifiActiveTimeAfterMoreBit + * PSID : 2537 (0x09E9) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : TU + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 30 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: After seeing + * the "more" bit set in a message from the AP, the STA will goto active + * mode for this duration of time. After this time, traffic information is + * evaluated to determine whether the STA should stay active or go to + * powersave. Setting this value to 0 means that the described functionality + * is disabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_ACTIVE_TIME_AFTER_MORE_BIT 0x09E9 + +/******************************************************************************* + * NAME : UnifiDefaultDwellTime + * PSID : 2538 (0x09EA) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 50 + * DESCRIPTION : + * Dwell time, in TU, for frames that need a response but have no dwell time + * associated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEFAULT_DWELL_TIME 0x09EA + +/******************************************************************************* + * NAME : UnifiVhtCapabilities + * PSID : 2540 (0x09EC) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 12 + * MAX : 12 + * DEFAULT : { 0XB1, 0X7A, 0X11, 0X03, 0XFA, 0XFF, 0X00, 0X00, 0XFA, 0XFF, 0X00, 0X00 } + * DESCRIPTION : + * VHT capabilities of the chip. see SC-503520-SP. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_VHT_CAPABILITIES 0x09EC + +/******************************************************************************* + * NAME : UnifiMaxVifScheduleDuration + * PSID : 2541 (0x09ED) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 0 + * MAX : 65535 + * DEFAULT : 50 + * DESCRIPTION : + * Default time for which a non-scan VIF can be scheduled. Applies to + * multiVIF scenario. Internal firmware logic or BSS state (e.g. NOA) may + * cut short the schedule. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAX_VIF_SCHEDULE_DURATION 0x09ED + +/******************************************************************************* + * NAME : UnifiVifLongIntervalTime + * PSID : 2542 (0x09EE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 0 + * MAX : 65535 + * DEFAULT : 60 + * DESCRIPTION : + * When the scheduler expects a VIF to schedule for time longer than this + * parameter (specified in TUs), then the VIF may come out of powersave. + * Only valid for STA VIFs. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_VIF_LONG_INTERVAL_TIME 0x09EE + +/******************************************************************************* + * NAME : UnifiDisallowSchedRelinquish + * PSID : 2543 (0x09EF) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * When enabled the VIFs will not relinquish their assigned schedules when + * they have nothing left to do. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DISALLOW_SCHED_RELINQUISH 0x09EF + +/******************************************************************************* + * NAME : UnifiRameDplaneOperationTimeout + * PSID : 2544 (0x09F0) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : milliseconds + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 1000 + * DESCRIPTION : + * Timeout for requests sent from MACRAME to Data Plane. Any value below + * 1000ms will be capped at 1000ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RAME_DPLANE_OPERATION_TIMEOUT 0x09F0 + +/******************************************************************************* + * NAME : UnifiDebugKeepRadioOn + * PSID : 2545 (0x09F1) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Keep the radio on. For debug purposes only. Setting the value to FALSE + * means radio on/off functionality will behave normally. Note that setting + * this value to TRUE will automatically disable dorm. The intention is + * not* for this value to be changed at runtime. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_KEEP_RADIO_ON 0x09F1 + +/******************************************************************************* + * NAME : UnifiForceFixedDurationSchedule + * PSID : 2546 (0x09F2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : TU + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * For schedules with fixed duration e.g. scan, unsync VIF, the schedule + * will be forced after this time to avoid VIF starving + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FORCE_FIXED_DURATION_SCHEDULE 0x09F2 + +/******************************************************************************* + * NAME : UnifiRameUpdateMibs + * PSID : 2547 (0x09F3) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * When this mib is called/toggled MACRAME mibs will be read and compared + * with mib values in ramedata.mibs and updated if the value changes + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RAME_UPDATE_MIBS 0x09F3 + +/******************************************************************************* + * NAME : UnifiGoScanAbsenceDuration + * PSID : 2548 (0x09F4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : beacon intervals + * MIN : 0 + * MAX : 65535 + * DEFAULT : 7 + * DESCRIPTION : + * Duration of the Absence time to use when protecting P2PGO VIFs from scan + * operations. A value of 0 disables the feature. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GO_SCAN_ABSENCE_DURATION 0x09F4 + +/******************************************************************************* + * NAME : UnifiGoScanAbsencePeriod + * PSID : 2549 (0x09F5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : beacon intervals + * MIN : 0 + * MAX : 65535 + * DEFAULT : 14 + * DESCRIPTION : + * Period of the Absence/Presence times cycles to use when protecting P2PGO + * VIFs from scan operations. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_GO_SCAN_ABSENCE_PERIOD 0x09F5 + +/******************************************************************************* + * NAME : UnifiMaxClient + * PSID : 2550 (0x09F6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 10 + * DEFAULT : 10 + * DESCRIPTION : + * Restricts the maximum number of associated STAs for SoftAP. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAX_CLIENT 0x09F6 + +/******************************************************************************* + * NAME : UnifiTdlsInP2pActivated + * PSID : 2556 (0x09FC) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enable TDLS in P2P. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_IN_P2P_ACTIVATED 0x09FC + +/******************************************************************************* + * NAME : UnifiTdlsActivated + * PSID : 2558 (0x09FE) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Enable TDLS. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_ACTIVATED 0x09FE + +/******************************************************************************* + * NAME : UnifiTdlsTpThresholdPktSecs + * PSID : 2559 (0x09FF) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 100 + * DESCRIPTION : + * Used for "throughput_threshold_pktsecs" of + * RAME-MLME-ENABLE-PEER-TRAFFIC-REPORTING.request. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_TP_THRESHOLD_PKT_SECS 0x09FF + +/******************************************************************************* + * NAME : UnifiTdlsRssiThreshold + * PSID : 2560 (0x0A00) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : -75 + * DESCRIPTION : + * FW initiated TDLS Discovery/Setup procedure. If the RSSI of a received + * TDLS Discovery Response frame is greater than this value, initiate the + * TDLS Setup procedure. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_RSSI_THRESHOLD 0x0A00 + +/******************************************************************************* + * NAME : UnifiTdlsMaximumRetry + * PSID : 2561 (0x0A01) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_MAXIMUM_RETRY 0x0A01 + +/******************************************************************************* + * NAME : UnifiTdlsTpMonitorSecs + * PSID : 2562 (0x0A02) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Measurement period for recording the number of packets sent to a peer + * over a TDLS link. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_TP_MONITOR_SECS 0x0A02 + +/******************************************************************************* + * NAME : UnifiTdlsBasicHtMcsSet + * PSID : 2563 (0x0A03) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_BASIC_HT_MCS_SET 0x0A03 + +/******************************************************************************* + * NAME : UnifiTdlsBasicVhtMcsSet + * PSID : 2564 (0x0A04) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_BASIC_VHT_MCS_SET 0x0A04 + +/******************************************************************************* + * NAME : Dot11TdlsDiscoveryRequestWindow + * PSID : 2565 (0x0A05) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 10 + * DESCRIPTION : + * Time to gate Discovery Request frame (in DTIM intervals) after + * transmitting a Discovery Request frame. + *******************************************************************************/ +#define SLSI_PSID_DOT11_TDLS_DISCOVERY_REQUEST_WINDOW 0x0A05 + +/******************************************************************************* + * NAME : Dot11TdlsResponseTimeout + * PSID : 2566 (0x0A06) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 5 + * DESCRIPTION : + * If a valid Setup Response frame is not received within (seconds), the + * initiator STA shall terminate the setup procedure and discard any Setup + * Response frames. + *******************************************************************************/ +#define SLSI_PSID_DOT11_TDLS_RESPONSE_TIMEOUT 0x0A06 + +/******************************************************************************* + * NAME : Dot11TdlsChannelSwitchActivated + * PSID : 2567 (0x0A07) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_DOT11_TDLS_CHANNEL_SWITCH_ACTIVATED 0x0A07 + +/******************************************************************************* + * NAME : UnifiTdlsDesignForTestMode + * PSID : 2568 (0x0A08) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_DESIGN_FOR_TEST_MODE 0x0A08 + +/******************************************************************************* + * NAME : UnifiTdlsWiderBandwidthProhibited + * PSID : 2569 (0x0A09) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Wider bandwidth prohibited flag. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_WIDER_BANDWIDTH_PROHIBITED 0x0A09 + +/******************************************************************************* + * NAME : UnifiTdlsKeyLifeTimeInterval + * PSID : 2577 (0x0A11) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X000FFFFF + * DESCRIPTION : + * Build the Key Lifetime Interval in the TDLS Setup Request frame. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_KEY_LIFE_TIME_INTERVAL 0x0A11 + +/******************************************************************************* + * NAME : UnifiTdlsTeardownFrameTxTimeout + * PSID : 2578 (0x0A12) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 500 + * DESCRIPTION : + * Allowed time in milliseconds for a Teardown frame to be transmitted. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TDLS_TEARDOWN_FRAME_TX_TIMEOUT 0x0A12 + +/******************************************************************************* + * NAME : UnifiWifiSharingEnabled + * PSID : 2580 (0x0A14) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables WiFi Sharing feature + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WIFI_SHARING_ENABLED 0x0A14 + +/******************************************************************************* + * NAME : UnifiWiFiSharing5GHzChannel + * PSID : 2582 (0x0A16) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 8 + * MAX : 8 + * DEFAULT : { 0X00, 0XC0, 0XFF, 0XFF, 0X7F, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * Applicable 5GHz Primary Channels mask. Defined in a uint64 represented by + * the octet string. First byte of the octet string maps to LSB. Bits 0-13 + * representing 2.4G channels are always set to 0. Mapping defined in + * ChannelisationRules; i.e. Bit 14 maps to channel 36. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WI_FI_SHARING5_GHZ_CHANNEL 0x0A16 + +/******************************************************************************* + * NAME : UnifiWifiSharingChannelSwitchCount + * PSID : 2583 (0x0A17) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 3 + * MAX : 10 + * DEFAULT : 10 + * DESCRIPTION : + * Channel switch announcement count which will be used in the Channel + * announcement IE when using wifi sharing + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WIFI_SHARING_CHANNEL_SWITCH_COUNT 0x0A17 + +/******************************************************************************* + * NAME : UnifiChannelAnnouncementCount + * PSID : 2584 (0x0A18) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Channel switch announcement count which will be used in the Channel + * announcement IE + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CHANNEL_ANNOUNCEMENT_COUNT 0x0A18 + +/******************************************************************************* + * NAME : UnifiRaTestStoredSa + * PSID : 2585 (0x0A19) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X00000000 + * DESCRIPTION : + * Test only: Source address of router contained in virtural router + * advertisement packet, specified in chapter '6.2 Forward Received RA frame + * to Host' in SC-506393-TE + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RA_TEST_STORED_SA 0x0A19 + +/******************************************************************************* + * NAME : UnifiRaTestStoreFrame + * PSID : 2586 (0x0A1A) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X00000000 + * DESCRIPTION : + * Test only: Virtual router advertisement packet. Specified in chapter '6.2 + * Forward Received RA frame to Host' in SC-506393-TE + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RA_TEST_STORE_FRAME 0x0A1A + +/******************************************************************************* + * NAME : Dot11TdlsPeerUapsdBufferStaActivated + * PSID : 2587 (0x0A1B) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enable TDLS peer U-APSD. + *******************************************************************************/ +#define SLSI_PSID_DOT11_TDLS_PEER_UAPSD_BUFFER_STA_ACTIVATED 0x0A1B + +/******************************************************************************* + * NAME : UnifiProbeResponseLifetimeP2p + * PSID : 2600 (0x0A28) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 500 + * DESCRIPTION : + * Lifetime of proberesponse frame in unit of ms for P2P. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PROBE_RESPONSE_LIFETIME_P2P 0x0A28 + +/******************************************************************************* + * NAME : UnifiStaChannelSwitchSlowApActivated + * PSID : 2601 (0x0A29) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: ChanelSwitch: + * Enable waiting for a slow AP. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_CHANNEL_SWITCH_SLOW_AP_ACTIVATED 0x0A29 + +/******************************************************************************* + * NAME : UnifiStaChannelSwitchSlowApMaxTime + * PSID : 2604 (0x0A2C) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 70 + * DESCRIPTION : + * ChannelSwitch delay for Slow APs. In Seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_CHANNEL_SWITCH_SLOW_AP_MAX_TIME 0x0A2C + +/******************************************************************************* + * NAME : UnifiStaChannelSwitchSlowApPollInterval + * PSID : 2605 (0x0A2D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1 + * DESCRIPTION : + * ChannelSwitch polling interval for Slow APs. In Seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_CHANNEL_SWITCH_SLOW_AP_POLL_INTERVAL 0x0A2D + +/******************************************************************************* + * NAME : UnifiStaChannelSwitchSlowApProcedureTimeoutIncrement + * PSID : 2606 (0x0A2E) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * ChannelSwitch procedure timeout increment for Slow APs. In Seconds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STA_CHANNEL_SWITCH_SLOW_AP_PROCEDURE_TIMEOUT_INCREMENT 0x0A2E + +/******************************************************************************* + * NAME : UnifiMlmeScanMaxAerials + * PSID : 2607 (0x0A2F) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 65535 + * DEFAULT : 1 + * DESCRIPTION : + * Limit the number of Aerials that Scan will use. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_MAX_AERIALS 0x0A2F + +/******************************************************************************* + * NAME : UnifiApfEnabled + * PSID : 2650 (0x0A5A) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * It is used to enable or disable Android Packet Filter(APF). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_APF_ENABLED 0x0A5A + +/******************************************************************************* + * NAME : UnifiApfVersion + * PSID : 2651 (0x0A5B) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 4 + * DESCRIPTION : + * APF version currently supported by the FW. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_APF_VERSION 0x0A5B + +/******************************************************************************* + * NAME : UnifiApfMaxSize + * PSID : 2652 (0x0A5C) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2048 + * DEFAULT : 1024 + * DESCRIPTION : + * Max size in bytes supported by FW per VIF. Includes both program len and + * data len. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_APF_MAX_SIZE 0x0A5C + +/******************************************************************************* + * NAME : UnifiApfActiveMode + * PSID : 2653 (0x0A5D) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1 + * DESCRIPTION : + * Indicates if APF is supported in host active mode. Applicable to only + * group addressed frames. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_APF_ACTIVE_MODE 0x0A5D + +/******************************************************************************* + * NAME : UnifiCsrOnlyMibShield + * PSID : 4001 (0x0FA1) + * PER INTERFACE?: NO + * TYPE : unifiCSROnlyMIBShield + * MIN : 0 + * MAX : 255 + * DEFAULT : 2 + * DESCRIPTION : + * Each element of the MIB has a set of read/write access constraints that + * may be applied when the element is accessed by the host. For most + * elements the constants are derived from their MAX-ACCESS clauses. + * unifiCSROnlyMIBShield controls the access mechanism. If this entry is set + * to 'warn', when the host makes an inappropriate access to a MIB + * variable (e.g., writing to a 'read-only' entry) then the + * firmware attempts to send a warning message to the host, but access is + * allowed to the MIB variable. If this entry is set to 'guard' + * then inappropriate accesses from the host are prevented. If this entry is + * set to 'alarm' then inappropriate accesses from the host are + * prevented and the firmware attempts to send warning messages to the host. + * If this entry is set to 'open' then no access constraints are + * applied and now warnings issued. Note that certain MIB entries have + * further protection schemes. In particular, the MIB prevents the host from + * reading some security keys (WEP keys, etc.). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CSR_ONLY_MIB_SHIELD 0x0FA1 + +/******************************************************************************* + * NAME : UnifiPrivateBbbTxFilterConfig + * PSID : 4071 (0x0FE7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X17 + * DESCRIPTION : + * entry is written directly to the BBB_TX_FILTER_CONFIG register. Only the + * lower eight bits of this register are implemented . Bits 0-3 are the + * 'Tx Gain', bits 6-8 are the 'Tx Delay'. This register + * should only be changed by an expert. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PRIVATE_BBB_TX_FILTER_CONFIG 0x0FE7 + +/******************************************************************************* + * NAME : UnifiPrivateSwagcFrontEndGain + * PSID : 4075 (0x0FEB) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : + * DESCRIPTION : + * Gain of the path between chip and antenna when LNA is on. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PRIVATE_SWAGC_FRONT_END_GAIN 0x0FEB + +/******************************************************************************* + * NAME : UnifiPrivateSwagcFrontEndLoss + * PSID : 4076 (0x0FEC) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : + * DESCRIPTION : + * Loss of the path between chip and antenna when LNA is off. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PRIVATE_SWAGC_FRONT_END_LOSS 0x0FEC + +/******************************************************************************* + * NAME : UnifiPrivateSwagcExtThresh + * PSID : 4077 (0x0FED) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : -25 + * DESCRIPTION : + * Signal level at which external LNA will be used for AGC purposes. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PRIVATE_SWAGC_EXT_THRESH 0x0FED + +/******************************************************************************* + * NAME : UnifiCsrOnlyPowerCalDelay + * PSID : 4078 (0x0FEE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : microseconds + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Delay applied at each step of the power calibration routine used with an + * external PA. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CSR_ONLY_POWER_CAL_DELAY 0x0FEE + +/******************************************************************************* + * NAME : UnifiRxAgcControl + * PSID : 4079 (0x0FEF) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 9 + * MAX : 11 + * DEFAULT : + * DESCRIPTION : + * Override the AGC by adjusting the Rx minimum and maximum gains of each + * stage. Set requests write the values to a static structure in + * mac/hal/halradio/halradio_agc.c. The saved values are written to the Jar + * register WLRF_RADIO_AGC_CONFIG2 and to the Night registers + * WL_RADIO_AGC_CONFIG2 and WL_RADIO_AGC_CONFIG3. The saved values are also + * used to configure the AGC whenever halradio_agc_setup() is called. Get + * requests read the values from the static structure in + * mac/hal/halradio/halradio_agc.c. AGC enables are not altered. Fixed gain + * may be tested by setting the minimums and maximums to the same value. + * Version. octet 0 - Version number for this mib. Gain values. Default in + * brackets. octet 1 - 5G LNA minimum gain (0). octet 2 - 5G LNA maximum + * gain (4). octet 3 - 2G LNA minimum gain (0). octet 4 - 2G LNA maximum + * gain (5). octet 5 - Mixer minimum gain (0). octet 6 - Mixer maximum gain + * (2). octet 7 - ABB minimum gain (0). octet 8 - ABB maximum gain (27). + * octet 9 - Digital minimum gain (0). octet 10 - Digital maximum gain (7). + * For Rock / Hopper the saved values are written to the Hopper register + * WLRF_RADIO_AGC_CONFIG2_I0, WLRF_RADIO_AGC_CONFIG2_I1 and Rock registers + * WL_RADIO_AGC_CONFIG3_I0, WL_RADIO_AGC_CONFIG3_I1 Version. octet 0 - + * Version number for this mib. Gain values. Default in brackets. octet 1 - + * 5G FE minimum gain (1). octet 2 - 5G FE maximum gain (8). octet 3 - 2G FE + * minimum gain (0). octet 4 - 2G FE maximum gain (8). octet 5 - ABB minimum + * gain (0). octet 6 - ABB maximum gain (8). octet 7 - Digital minimum gain + * (0). octet 8 - Digital maximum gain (17). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_AGC_CONTROL 0x0FEF + +/******************************************************************************* + * NAME : UnifiWapiQosMask + * PSID : 4130 (0x1022) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 15 + * DESCRIPTION : + * Forces the WAPI encryption hardware use the QoS mask specified.This mib + * value will be updated if "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WAPI_QOS_MASK 0x1022 + +/******************************************************************************* + * NAME : UnifiWmmStallEnable + * PSID : 4139 (0x102B) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1 + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Enable + * workaround stall WMM traffic if the admitted time has been used up, used + * for certtification. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WMM_STALL_ENABLE 0x102B + +/******************************************************************************* + * NAME : UnifiRaaTxHostRate + * PSID : 4148 (0x1034) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 16385 + * DESCRIPTION : + * Fixed TX rate set by Host. Ideally this should be done by the driver. 0 + * means "host did not specified any rate". + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RAA_TX_HOST_RATE 0x1034 + +/******************************************************************************* + * NAME : UnifiFallbackShortFrameRetryDistribution + * PSID : 4149 (0x1035) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 6 + * MAX : 5 + * DEFAULT : {0X3, 0X2, 0X2, 0X2, 0X1, 0X0} + * DESCRIPTION : + * Configure the retry distribution for fallback for short frames octet 0 - + * Number of retries for starting rate. octet 1 - Number of retries for next + * rate. octet 2 - Number of retries for next rate. octet 3 - Number of + * retries for next rate. octet 4 - Number of retries for next rate. octet 5 + * - Number of retries for last rate. If 0 is written to an entry then the + * retries for that rate will be the short retry limit minus the sum of the + * retries for each rate above that entry (e.g. 15 - 5). Therefore, this + * should always be the value for octet 4. Also, when the starting rate has + * short guard enabled, the number of retries in octet 1 will be used and + * for the next rate in the fallback table (same MCS value, but with sgi + * disabled) octet 0 number of retries will be used. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FALLBACK_SHORT_FRAME_RETRY_DISTRIBUTION 0x1035 + +/******************************************************************************* + * NAME : UnifiRxthroughputlow + * PSID : 4150 (0x1036) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 37500000 + * DESCRIPTION : + * Lower threshold for number of bytes received in a second - default value + * based on 300Mbps + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RXTHROUGHPUTLOW 0x1036 + +/******************************************************************************* + * NAME : UnifiRxthroughputhigh + * PSID : 4151 (0x1037) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 50000000 + * DESCRIPTION : + * Upper threshold for number of bytes received in a second - default value + * based on 400Mbps + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RXTHROUGHPUTHIGH 0x1037 + +/******************************************************************************* + * NAME : UnifiSetFixedAmpduAggregationSize + * PSID : 4152 (0x1038) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * A non 0 value defines the max number of mpdus that a ampdu can have. A 0 + * value tells FW to manage the aggregation size. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SET_FIXED_AMPDU_AGGREGATION_SIZE 0x1038 + +/******************************************************************************* + * NAME : UnifiThroughputDebugReportInterval + * PSID : 4153 (0x1039) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1000 + * DESCRIPTION : + * dataplane reports throughput diag report every this interval in msec. 0 + * means to disable this report. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_THROUGHPUT_DEBUG_REPORT_INTERVAL 0x1039 + +/******************************************************************************* + * NAME : UnifiPreEbrtWindow + * PSID : 4171 (0x104B) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 100 + * DESCRIPTION : + * Latest time before the expected beacon reception time that UniFi will + * turn on its radio in order to receive the beacon. Reducing this value can + * reduce UniFi power consumption when using low power modes, however a + * value which is too small may cause beacons to be missed, requiring the + * radio to remain on for longer periods to ensure reception of the + * subsequent beacon. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PRE_EBRT_WINDOW 0x104B + +/******************************************************************************* + * NAME : UnifiPostEbrtWindow + * PSID : 4173 (0x104D) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 2000 + * DESCRIPTION : + * Minimum time after the expected beacon reception time that UniFi will + * continue to listen for the beacon in an infrastructure BSS before timing + * out. Reducing this value can reduce UniFi power consumption when using + * low power modes, however a value which is too small may cause beacons to + * be missed, requiring the radio to remain on for longer periods to ensure + * reception of the subsequent beacon. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_POST_EBRT_WINDOW 0x104D + +/******************************************************************************* + * NAME : UnifiPsPollThreshold + * PSID : 4179 (0x1053) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 30 + * DESCRIPTION : + * PS Poll threshold. When Unifi chip is configured for normal power save + * mode and when access point does not respond to PS-Poll requests, then a + * fault will be generated on non-zero PS Poll threshold indicating mode has + * been switched from power save to fast power save. Ignored PS Poll count + * is given as the fault argument. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PS_POLL_THRESHOLD 0x1053 + +/******************************************************************************* + * NAME : UnifiSableContainerSizeConfiguration + * PSID : 5000 (0x1388) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 3 + * MAX : 3 + * DEFAULT : {0X64} + * DESCRIPTION : + * Sable Container Size Configuration Sable WLAN reserved memory size is + * determined by the host. Sable TLV containers are allocated from this WLAN + * reserved area. Each container has different requirement on its size. For + * example, frame logging or IQ capture would be very greedy, requesting + * most of available memroy. But some just need fixed size, but not large. + * To cope with such requirements, each container size is configured with + * the following rules: 1. To allocate a certain percentage of the whole + * wlan reserved area, put the percentage in hex format. For example, + * 0x28(=40) means 40% of reserved area will be assigned. The number + * 0x64(=100) is specially treated that all remaining space will be assigned + * after all the other containers are first served. 2. To request (n * 2048) + * bytes, put (100 + n) value in hex format. For example, 0x96 (= 150) means + * 50 * 2048 = 102400 bytes. Here are the list of containers: - octet 0 - + * WTLV_CONTAINER_ID_DPLANE_FRAME_LOG + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SABLE_CONTAINER_SIZE_CONFIGURATION 0x1388 + +/******************************************************************************* + * NAME : UnifiSableFrameLogMode + * PSID : 5001 (0x1389) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 2 + * DEFAULT : 2 + * DESCRIPTION : + * Sable Frame Logging mode - 0: disable frame logging - 1: enable frame + * logging always, regardless of CPU resource state - 2: dynamically enable + * frame logging base on CPU resource. If CPU too busy, frame logging is + * disabled. Logging is enabled when CPU resource gets recovered. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SABLE_FRAME_LOG_MODE 0x1389 + +/******************************************************************************* + * NAME : UnifiSableFrameLogCpuThresPercent + * PSID : 5002 (0x138A) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 95 + * DESCRIPTION : + * CPU target in percent. When CPU usage is higher than this target, frame + * logging will be disabled by firmware. Firmware will check if CPU resource + * is recovered every 1 second. If CPU resource recovered, then frame + * logging is re-enabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SABLE_FRAME_LOG_CPU_THRES_PERCENT 0x138A + +/******************************************************************************* + * NAME : UnifiSableFrameLogCpuOverheadPercent + * PSID : 5003 (0x138B) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 3 + * DESCRIPTION : + * Expected CPU overhead introduced by frame logging. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SABLE_FRAME_LOG_CPU_OVERHEAD_PERCENT 0x138B + +/******************************************************************************* + * NAME : UnifiDebugSvcModeStackHighWaterMark + * PSID : 5010 (0x1392) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Read the SVC mode stack high water mark in bytes + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_SVC_MODE_STACK_HIGH_WATER_MARK 0x1392 + +/******************************************************************************* + * NAME : UnifiOverrideEdcaParamBe + * PSID : 5023 (0x139F) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 4 + * MAX : 4 + * DEFAULT : { 0X03, 0XA4, 0X00, 0X00 } + * DESCRIPTION : + * Override the BE EDCA parameters. octet 0 - AIFS octet 1 - [7:4] ECW MAX + * [3:0] ECW MIN octet 2 ~ 3 - TXOP[7:0] TXOP[15:8] in 32 usec units for + * both non-HT and HT connections. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_EDCA_PARAM_BE 0x139F + +/******************************************************************************* + * NAME : UnifiOverrideEdcaParamBeEnable + * PSID : 5024 (0x13A0) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * override STA - BE EDCA using the values in unifiOverrideEDCAParamBE for + * certification. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OVERRIDE_EDCA_PARAM_BE_ENABLE 0x13A0 + +/******************************************************************************* + * NAME : UnifiPanicSubSystemControl + * PSID : 5026 (0x13A2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * PANIC levels for WLAN SubSystems. Panic level is used to filter Panic + * sent to the host. 4 levels of Panic per subsystem are available + * (FAILURE_LEVEL_T): a. 0 FATAL - Always reported to host b. 1 ERROR c. 2 + * WARNING d. 3 DEBUG NOTE: If Panic level of a subsystem is configured to + * FATAL, all the Panics within that subsystem configured to FATAL will be + * effective, panics with ERROR, WARNING and Debug level will be converted + * to faults. If Panic level of a subsystem is configured to WARNING, all + * the panics within that subsystem configured to FATAL, ERROR and WARNING + * will be issued to host, panics with Debug level will be converted to + * faults. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PANIC_SUB_SYSTEM_CONTROL 0x13A2 + +/******************************************************************************* + * NAME : UnifiFaultEnable + * PSID : 5027 (0x13A3) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Send Fault to host state. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FAULT_ENABLE 0x13A3 + +/******************************************************************************* + * NAME : UnifiFaultSubSystemControl + * PSID : 5028 (0x13A4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Fault levels for WLAN SubSystems. Fault level is used to filter faults + * sent to the host. 4 levels of faults per subsystem are available + * (FAILURE_LEVEL_T): a. 0 ERROR b. 1 WARNING c. 2 INFO_1 d. 3 INFO_2 + * Modifying Fault Levels at run time: 1. Set the fault level for the + * subsystems in unifiFaultConfigTable 2. Set unifiFaultEnable NOTE: If + * fault level of a subsystem is configured to ERROR, all the faults within + * that subsystem configured to ERROR will only be issued to host, faults + * with WARNING, INFO_1 and INFO_2 level will be converted to debug message + * If fault level of a subsystem is configured to WARNING, all the faults + * within that subsystem configured to ERROR and WARNING will be issued to + * host, faults with INFO_1 and INFO_2 level will be converted to debug + * message + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FAULT_SUB_SYSTEM_CONTROL 0x13A4 + +/******************************************************************************* + * NAME : UnifiDebugModuleControl + * PSID : 5029 (0x13A5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Debug Module levels for all modules. Module debug level is used to filter + * debug messages sent to the host. Only 6 levels of debug per module are + * available: a. -1 No debug created. b. 0 Debug if compiled in. Should not + * cause Buffer Full in normal testing. c. 1 - 3 Levels to allow sensible + * setting of the .hcf file while running specific tests or debugging d. 4 + * Debug will harm normal execution due to excessive levels or processing + * time required. Only used in emergency debugging. Additional control for + * FSM transition and FSM signals logging is provided. Debug module level + * and 2 boolean flags are encoded within a uint16: Function | Is sending + * FSM signals | Is sending FSM transitions | Is sending FSM Timers | + * Reserved | Module level (signed int) + * -_-_-_-_-_+-_-_-_-_-_-_-_-_-_-_-_-_-_+-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_+-_-_-_-_-_-_-_-_-_-_-_-_-_+-_-_-_-_-_-_+-_-_-_-_-_-_-_-_-_-_-_-_-_- Bits | 15 | 14 | 13 | 12 - 8 | 7 - 0 Note: 0x00FF disables any debug for a module 0xE004 enables all debug for a module + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_MODULE_CONTROL 0x13A5 + +/******************************************************************************* + * NAME : UnifiTxUsingLdpcEnabled + * PSID : 5030 (0x13A6) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * LDPC will be used to code packets, for transmit only. If disabled, chip + * will not send LDPC coded packets even if peer supports it. To advertise + * reception of LDPC coded packets,enable bit 0 of unifiHtCapabilities, and + * bit 4 of unifiVhtCapabilities. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_USING_LDPC_ENABLED 0x13A6 + +/******************************************************************************* + * NAME : UnifiTxSettings + * PSID : 5031 (0x13A7) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_SETTINGS 0x13A7 + +/******************************************************************************* + * NAME : UnifiTxGainSettings + * PSID : 5032 (0x13A8) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter gain settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_GAIN_SETTINGS 0x13A8 + +/******************************************************************************* + * NAME : UnifiTxAntennaConnectionLossFrequency + * PSID : 5033 (0x13A9) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 3940 + * MAX : 12000 + * DEFAULT : + * DESCRIPTION : + * The corresponding set of frequency values for + * TxAntennaConnectionLossTable + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_ANTENNA_CONNECTION_LOSS_FREQUENCY 0x13A9 + +/******************************************************************************* + * NAME : UnifiTxAntennaConnectionLoss + * PSID : 5034 (0x13AA) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : + * DESCRIPTION : + * The set of Antenna Connection Loss value (qdB), which is used for + * TPO/EIRP conversion + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_ANTENNA_CONNECTION_LOSS 0x13AA + +/******************************************************************************* + * NAME : UnifiTxAntennaMaxGainFrequency + * PSID : 5035 (0x13AB) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 3940 + * MAX : 12000 + * DEFAULT : + * DESCRIPTION : + * The corresponding set of frequency values for TxAntennaMaxGain + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_ANTENNA_MAX_GAIN_FREQUENCY 0x13AB + +/******************************************************************************* + * NAME : UnifiTxAntennaMaxGain + * PSID : 5036 (0x13AC) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : + * DESCRIPTION : + * The set of Antenna Max Gain value (qdB), which is used for TPO/EIRP + * conversion + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_ANTENNA_MAX_GAIN 0x13AC + +/******************************************************************************* + * NAME : UnifiRxExternalGainFrequency + * PSID : 5037 (0x13AD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 3940 + * MAX : 12000 + * DEFAULT : + * DESCRIPTION : + * The set of RSSI offset value + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_EXTERNAL_GAIN_FREQUENCY 0x13AD + +/******************************************************************************* + * NAME : UnifiRxExternalGain + * PSID : 5038 (0x13AE) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : + * DESCRIPTION : + * The table giving frequency-dependent RSSI offset value + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_EXTERNAL_GAIN 0x13AE + +/******************************************************************************* + * NAME : UnifiTxSgI20Enabled + * PSID : 5040 (0x13B0) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * SGI 20MHz will be used to code packets for transmit only. If disabled, + * chip will not send SGI 20MHz packets even if peer supports it. To + * advertise reception of SGI 20MHz packets, enable bit 5 of + * unifiHtCapabilities. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_SG_I20_ENABLED 0x13B0 + +/******************************************************************************* + * NAME : UnifiTxSgI40Enabled + * PSID : 5041 (0x13B1) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * SGI 40MHz will be used to code packets, for transmit only. If disabled, + * chip will not send SGI 40MHz packets even if peer supports it. To + * advertise reception of SGI 40MHz packets, enable bit 6 of + * unifiHtCapabilities. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_SG_I40_ENABLED 0x13B1 + +/******************************************************************************* + * NAME : UnifiTxSgI80Enabled + * PSID : 5042 (0x13B2) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * SGI 80MHz will be used to code packets, for transmit only. If disabled, + * chip will not send SGI 80MHz packets even if peer supports it. To + * advertise reception of SGI 80MHz packets, enable bit 5 of + * unifiVhtCapabilities. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_SG_I80_ENABLED 0x13B2 + +/******************************************************************************* + * NAME : UnifiTxSgI160Enabled + * PSID : 5043 (0x13B3) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * SGI 160/80+80MHz will be used to code packets, for transmit only. If + * disabled, chip will not send SGI 160/80+80MHz packets even if peer + * supports it. To advertise reception of SGI 160/80+80MHz packets, enable + * bit 6 of unifiVhtCapabilities. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_SG_I160_ENABLED 0x13B3 + +/******************************************************************************* + * NAME : UnifiMacAddressRandomisation + * PSID : 5044 (0x13B4) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Golden Certification MIB don't delete, change PSID or name: Enabling Mac + * Address Randomisation to be applied for Probe Requests when scanning. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAC_ADDRESS_RANDOMISATION 0x13B4 + +/******************************************************************************* + * NAME : UnifiMacAddressRandomisationMask + * PSID : 5047 (0x13B7) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 6 + * MAX : 6 + * DEFAULT : { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * FW randomises MAC Address bits that have a corresponding bit set to 0 in + * the MAC Mask for Probe Requests. This excludes U/L and I/G bits which + * will be set to Local and Individual respectively. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAC_ADDRESS_RANDOMISATION_MASK 0x13B7 + +/******************************************************************************* + * NAME : UnifiWipsActivated + * PSID : 5050 (0x13BA) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables Wips. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_WIPS_ACTIVATED 0x13BA + +/******************************************************************************* + * NAME : UnifiRfTestModeEnabled + * PSID : 5054 (0x13BE) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Test only: Set to true when running in RF Test mode. Setting this MIB key + * to true prevents setting mandatory HT MCS Rates. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RF_TEST_MODE_ENABLED 0x13BE + +/******************************************************************************* + * NAME : UnifiTxPowerDetectorResponse + * PSID : 5055 (0x13BF) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter detector response settings. 2G settings + * before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_POWER_DETECTOR_RESPONSE 0x13BF + +/******************************************************************************* + * NAME : UnifiTxDetectorTemperatureCompensation + * PSID : 5056 (0x13C0) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter detector temperature compensation settings. + * 2G settings before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_DETECTOR_TEMPERATURE_COMPENSATION 0x13C0 + +/******************************************************************************* + * NAME : UnifiTxDetectorFrequencyCompensation + * PSID : 5057 (0x13C1) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter detector frequency compensation settings. + * 2G settings before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_DETECTOR_FREQUENCY_COMPENSATION 0x13C1 + +/******************************************************************************* + * NAME : UnifiTxOpenLoopTemperatureCompensation + * PSID : 5058 (0x13C2) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter open-loop temperature compensation + * settings. 2G settings before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_OPEN_LOOP_TEMPERATURE_COMPENSATION 0x13C2 + +/******************************************************************************* + * NAME : UnifiTxOpenLoopFrequencyCompensation + * PSID : 5059 (0x13C3) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter open-loop frequency compensation settings. + * 2G settings before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_OPEN_LOOP_FREQUENCY_COMPENSATION 0x13C3 + +/******************************************************************************* + * NAME : UnifiTxOfdmSelect + * PSID : 5060 (0x13C4) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 4 + * MAX : 8 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter OFDM selection settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_OFDM_SELECT 0x13C4 + +/******************************************************************************* + * NAME : UnifiTxDigGain + * PSID : 5061 (0x13C5) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 16 + * MAX : 48 + * DEFAULT : + * DESCRIPTION : + * Specify gain specific modulation power optimisation. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_DIG_GAIN 0x13C5 + +/******************************************************************************* + * NAME : UnifiChipTemperature + * PSID : 5062 (0x13C6) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : celcius + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Read the chip temperature as seen by WLAN radio firmware. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CHIP_TEMPERATURE 0x13C6 + +/******************************************************************************* + * NAME : UnifiBatteryVoltage + * PSID : 5063 (0x13C7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * UNITS : millivolt + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Battery voltage + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BATTERY_VOLTAGE 0x13C7 + +/******************************************************************************* + * NAME : UnifiTxOobConstraints + * PSID : 5064 (0x13C8) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * OOB constraints table. | octects | description | + * |-_-_-_-_-+-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-| | 0 | DPD applicability bitmask: 0 = no DPD, 1 = dynamic DPD, 2 = static DPD, 3 = applies to both static and dynamic DPD | | 1-2 | Bitmask indicating which regulatory domains this rule applies to FCC=bit0, ETSI=bit1, JAPAN=bit2 | | 3-4 | Bitmask indicating which band edges this rule applies to RICE_BAND_EDGE_ISM_24G_LOWER = bit 0, RICE_BAND_EDGE_ISM_24G_UPPER = bit 1, RICE_BAND_EDGE_U_NII_1_LOWER = bit 2, RICE_BAND_EDGE_U_NII_1_UPPER = bit 3, RICE_BAND_EDGE_U_NII_2_LOWER = bit 4, RICE_BAND_EDGE_U_NII_2_UPPER = bit 5, RICE_BAND_EDGE_U_NII_2E_LOWER = bit 6, RICE_BAND_EDGE_U_NII_2E_UPPER = bit 7, RICE_BAND_EDGE_U_NII_3_LOWER = bit 8, RICE_BAND_EDGE_U_NII_3_UPPER = bit 9 | | 5 | Bitmask indicating which modulation types this rule applies to (LSB/b0=DSSS/CCK, b1= OFDM0 modulation group, b2= OFDM1 modulation group) | | 6 | Bitmask indicating which channel bandwidths this rule applies to (LSB/b0=20MHz, b1=40MHz, b2=80MHz) | | 7 | Minimum distance to nearest band edge in 500 kHz units for which this constraint becomes is applicable. | | 8 | Maximum power (EIRP) for this particular constraint - specified in units of quarter dBm. | | 9-32 | Spectral shaping configuration to be used for this particular constraint. The value is specific to the radio hardware and should only be altered under advice from the IC supplier. | | 33-56| Tx DPD Spectral shaping configuration to be used for this particular constraint. The value is specific to the radio hardware and should only be altered under advice from the IC supplier. | | + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_OOB_CONSTRAINTS 0x13C8 + +/******************************************************************************* + * NAME : UnifiTxPaGainDpdTemperatureCompensation + * PSID : 5066 (0x13CA) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter PA gain for DPD temperature compensation + * settings. 2G settings before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_PA_GAIN_DPD_TEMPERATURE_COMPENSATION 0x13CA + +/******************************************************************************* + * NAME : UnifiTxPaGainDpdFrequencyCompensation + * PSID : 5067 (0x13CB) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter PA gain for DPD frequency compensation + * settings. 2G settings before 5G. Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_PA_GAIN_DPD_FREQUENCY_COMPENSATION 0x13CB + +/******************************************************************************* + * NAME : UnifiTxPowerTrimConfig + * PSID : 5072 (0x13D0) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 25 + * MAX : 25 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter power trim settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_POWER_TRIM_CONFIG 0x13D0 + +/******************************************************************************* + * NAME : UnifiForceShortSlotTime + * PSID : 5080 (0x13D8) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * If set to true, forces FW to use short slot times for all VIFs. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FORCE_SHORT_SLOT_TIME 0x13D8 + +/******************************************************************************* + * NAME : UnifiTxGainStepSettings + * PSID : 5081 (0x13D9) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * Hardware specific transmitter gain step settings. 2G settings before 5G. + * Increasing order within band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TX_GAIN_STEP_SETTINGS 0x13D9 + +/******************************************************************************* + * NAME : UnifiDebugDisableRadioNannyActions + * PSID : 5082 (0x13DA) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Bitmap to disable the radio nanny actions. B0==radio 0, B1==radio 1 + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_DISABLE_RADIO_NANNY_ACTIONS 0x13DA + +/******************************************************************************* + * NAME : UnifiRxCckModemSensitivity + * PSID : 5083 (0x13DB) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 6 + * MAX : 6 + * DEFAULT : + * DESCRIPTION : + * Specify values of CCK modem sensitivity for scan, normal and low + * sensitivity. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_CCK_MODEM_SENSITIVITY 0x13DB + +/******************************************************************************* + * NAME : UnifiDpdPerBandwidth + * PSID : 5084 (0x13DC) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 63 + * DESCRIPTION : + * Bitmask to enable Digital Pre-Distortion per bandwidth + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPD_PER_BANDWIDTH 0x13DC + +/******************************************************************************* + * NAME : UnifiBbVersion + * PSID : 5085 (0x13DD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Baseband chip version number determined by reading BBIC version + *******************************************************************************/ +#define SLSI_PSID_UNIFI_BB_VERSION 0x13DD + +/******************************************************************************* + * NAME : UnifiRfVersion + * PSID : 5086 (0x13DE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * RF chip version number determined by reading RFIC version + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RF_VERSION 0x13DE + +/******************************************************************************* + * NAME : UnifiReadHardwareCounter + * PSID : 5087 (0x13DF) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Read a value from a hardware packet counter for a specific radio_id and + * return it. The firmware will convert the radio_id to the associated + * mac_instance. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_READ_HARDWARE_COUNTER 0x13DF + +/******************************************************************************* + * NAME : UnifiClearRadioTrimCache + * PSID : 5088 (0x13E0) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Clears the radio trim cache. The parameter is ignored. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CLEAR_RADIO_TRIM_CACHE 0x13E0 + +/******************************************************************************* + * NAME : UnifiRadioTxSettingsRead + * PSID : 5089 (0x13E1) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Read value from Tx settings. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_TX_SETTINGS_READ 0x13E1 + +/******************************************************************************* + * NAME : UnifiModemSgiOffset + * PSID : 5090 (0x13E2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Overwrite SGI sampling offset. Indexed by Band and Bandwidth. Defaults + * currently defined in fw. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MODEM_SGI_OFFSET 0x13E2 + +/******************************************************************************* + * NAME : UnifiRadioTxPowerOverride + * PSID : 5091 (0x13E3) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -128 + * MAX : 127 + * DEFAULT : + * DESCRIPTION : + * Option in radio code to override the power requested by the upper layer + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_TX_POWER_OVERRIDE 0x13E3 + +/******************************************************************************* + * NAME : UnifiRxRadioCsMode + * PSID : 5092 (0x13E4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * OBSOLETE. Configures RX Radio CS detection for 80MHz bandwidth. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_RADIO_CS_MODE 0x13E4 + +/******************************************************************************* + * NAME : UnifiRxPriEnergyDetThreshold + * PSID : 5093 (0x13E5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * OBSOLETE. Energy detection threshold for primary 20MHz channel. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_PRI_ENERGY_DET_THRESHOLD 0x13E5 + +/******************************************************************************* + * NAME : UnifiRxSecEnergyDetThreshold + * PSID : 5094 (0x13E6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * OBSOLETE. Energy detection threshold for secondary 20MHz channel. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_SEC_ENERGY_DET_THRESHOLD 0x13E6 + +/******************************************************************************* + * NAME : UnifiAgcThresholds + * PSID : 5095 (0x13E7) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * AGC Thresholds settings + *******************************************************************************/ +#define SLSI_PSID_UNIFI_AGC_THRESHOLDS 0x13E7 + +/******************************************************************************* + * NAME : UnifiRadioRxSettingsRead + * PSID : 5096 (0x13E8) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Read value from Rx settings. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_RX_SETTINGS_READ 0x13E8 + +/******************************************************************************* + * NAME : UnifiStaticDpdGain + * PSID : 5097 (0x13E9) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 11 + * MAX : 27 + * DEFAULT : + * DESCRIPTION : + * Specify modulation specifc gains for static dpd optimisation. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_STATIC_DPD_GAIN 0x13E9 + +/******************************************************************************* + * NAME : UnifiIqBufferSize + * PSID : 5098 (0x13EA) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Buffer Size for IQ capture to allow CATs to read it. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_IQ_BUFFER_SIZE 0x13EA + +/******************************************************************************* + * NAME : UnifiNarrowbandCcaThresholds + * PSID : 5099 (0x13EB) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 255 + * DEFAULT : + * DESCRIPTION : + * The narrowband CCA ED thresholds so that the CCA-ED triggers at the + * regulatory value of -62 dBm. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NARROWBAND_CCA_THRESHOLDS 0x13EB + +/******************************************************************************* + * NAME : UnifiRadioCcaDebug + * PSID : 5100 (0x13EC) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Read values from Radio CCA settings. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_CCA_DEBUG 0x13EC + +/******************************************************************************* + * NAME : UnifiCcacsThresh + * PSID : 5101 (0x13ED) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Configures CCA CS thresholds. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CCACS_THRESH 0x13ED + +/******************************************************************************* + * NAME : UnifiCcaMasterSwitch + * PSID : 5102 (0x13EE) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X00540050 + * DESCRIPTION : + * Enables CCA + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CCA_MASTER_SWITCH 0x13EE + +/******************************************************************************* + * NAME : UnifiRxSyncCcaCfg + * PSID : 5103 (0x13EF) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Configures CCA per 20 MHz sub-band. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RX_SYNC_CCA_CFG 0x13EF + +/******************************************************************************* + * NAME : UnifiMacCcaBusyTime + * PSID : 5104 (0x13F0) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Counts the time CCA indicates busy + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAC_CCA_BUSY_TIME 0x13F0 + +/******************************************************************************* + * NAME : UnifiMacSecChanClearTime + * PSID : 5105 (0x13F1) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Configures PIFS + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MAC_SEC_CHAN_CLEAR_TIME 0x13F1 + +/******************************************************************************* + * NAME : UnifiDpdDebug + * PSID : 5106 (0x13F2) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Debug MIBs for DPD + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPD_DEBUG 0x13F2 + +/******************************************************************************* + * NAME : UnifiNarrowbandCcaDebug + * PSID : 5107 (0x13F3) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Read values from Radio CCA settings. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NARROWBAND_CCA_DEBUG 0x13F3 + +/******************************************************************************* + * NAME : UnifiNannyTemperatureReportDelta + * PSID : 5109 (0x13F5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 4 + * DESCRIPTION : + * A temperature difference, in degrees Celsius, above which the nanny + * process will generate a temperature update debug word + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NANNY_TEMPERATURE_REPORT_DELTA 0x13F5 + +/******************************************************************************* + * NAME : UnifiNannyTemperatureReportInterval + * PSID : 5110 (0x13F6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 200 + * DESCRIPTION : + * A report interval in milliseconds where temperature is checked + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NANNY_TEMPERATURE_REPORT_INTERVAL 0x13F6 + +/******************************************************************************* + * NAME : UnifiRadioRxDcocDebugIqValue + * PSID : 5111 (0x13F7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * RX DCOC debug testing. Allows user to override LUT index IQ values in + * combination with unifiRadioRxDcocDebug. This MIB sets IQ value that all + * LUT index Is and Qs get set to. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_RX_DCOC_DEBUG_IQ_VALUE 0x13F7 + +/******************************************************************************* + * NAME : UnifiRadioRxDcocDebug + * PSID : 5112 (0x13F8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * RX DCOC debug testing. Allows user to override LUT index IQ values in + * combination with unifiRadioRxDcocDebugIqValue. This MIB enables the + * feature. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_RX_DCOC_DEBUG 0x13F8 + +/******************************************************************************* + * NAME : UnifiNannyRetrimDpdMod + * PSID : 5113 (0x13F9) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * Bitmap to selectively enable nanny retrim of DPD per modulation. + * B0==OFDM0, B1==OFDM1, B2==CCK + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NANNY_RETRIM_DPD_MOD 0x13F9 + +/******************************************************************************* + * NAME : UnifiDisableDpdSubIteration + * PSID : 5114 (0x13FA) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * For Engineering debug use only. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DISABLE_DPD_SUB_ITERATION 0x13FA + +/******************************************************************************* + * NAME : UnifiRttCapabilities + * PSID : 5300 (0x14B4) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 8 + * MAX : 8 + * DEFAULT : { 0X01, 0X01, 0X01, 0X01, 0X00, 0X07, 0X1C, 0X32 } + * DESCRIPTION : + * RTT capabilities of the chip. see SC-506960-SW. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RTT_CAPABILITIES 0x14B4 + +/******************************************************************************* + * NAME : UnifiFtmMinDeltaFrames + * PSID : 5301 (0x14B5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 255 + * DEFAULT : 5 + * DESCRIPTION : + * Default minimum time between consecutive FTM frames in units of 100 us. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_MIN_DELTA_FRAMES 0x14B5 + +/******************************************************************************* + * NAME : UnifiFtmPerBurst + * PSID : 5302 (0x14B6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 1 + * MAX : 31 + * DEFAULT : 4 + * DESCRIPTION : + * Requested FTM frames per burst. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_PER_BURST 0x14B6 + +/******************************************************************************* + * NAME : UnifiFtmBurstDuration + * PSID : 5303 (0x14B7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 2 + * MAX : 11 + * DEFAULT : 6 + * DESCRIPTION : + * indicates the duration of a burst instance, values 0, 1, 12-14 are + * reserved, [2..11], the burst duration is defined as (250 x 2)^(N-2), and + * 15 means "no preference". + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_BURST_DURATION 0x14B7 + +/******************************************************************************* + * NAME : UnifiFtmNumOfBurstsExponent + * PSID : 5304 (0x14B8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 14 + * DEFAULT : + * DESCRIPTION : + * The number of burst instances is 2^(Number of Bursts Exponent), value 15 + * means "no preference". + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_NUM_OF_BURSTS_EXPONENT 0x14B8 + +/******************************************************************************* + * NAME : UnifiFtmAsapModeEnabled + * PSID : 5305 (0x14B9) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enable support for ASAP mode in FTM + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_ASAP_MODE_ENABLED 0x14B9 + +/******************************************************************************* + * NAME : UnifiFtmResponderEnabled + * PSID : 5306 (0x14BA) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enable support for FTM Responder + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_RESPONDER_ENABLED 0x14BA + +/******************************************************************************* + * NAME : UnifiFtmDefaultSessionEstablishmentTimeout + * PSID : 5307 (0x14BB) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 10 + * MAX : 100 + * DEFAULT : 50 + * DESCRIPTION : + * Default timeout for session estabishmant in units of ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_DEFAULT_SESSION_ESTABLISHMENT_TIMEOUT 0x14BB + +/******************************************************************************* + * NAME : UnifiFtmDefaultGapBeforeFirstBurstPerResponder + * PSID : 5308 (0x14BC) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Deprecated + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_DEFAULT_GAP_BEFORE_FIRST_BURST_PER_RESPONDER 0x14BC + +/******************************************************************************* + * NAME : UnifiFtmDefaultGapBetweenBursts + * PSID : 5309 (0x14BD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 5 + * MAX : 50 + * DEFAULT : 10 + * DESCRIPTION : + * Interval between consecutive Bursts. In units of ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_DEFAULT_GAP_BETWEEN_BURSTS 0x14BD + +/******************************************************************************* + * NAME : UnifiFtmDefaultTriggerDelay + * PSID : 5310 (0x14BE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 1 + * DESCRIPTION : + * Delay to account for differences in time between Initiator and Responder + * at start of the Burst. In units of ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_DEFAULT_TRIGGER_DELAY 0x14BE + +/******************************************************************************* + * NAME : UnifiFtmDefaultEndBurstDelay + * PSID : 5311 (0x14BF) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 100 + * DEFAULT : 10 + * DESCRIPTION : + * Delay to account for differences in time between Initiator and Responder + * at the end of the Burst. In units of ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_DEFAULT_END_BURST_DELAY 0x14BF + +/******************************************************************************* + * NAME : UnifiFtmRequestValidationEnabled + * PSID : 5312 (0x14C0) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enable Validation for FTM Add Range request RTT_Configs + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_REQUEST_VALIDATION_ENABLED 0x14C0 + +/******************************************************************************* + * NAME : UnifiFtmResponseValidationEnabled + * PSID : 5313 (0x14C1) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Enable Validation for FTM Response + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_RESPONSE_VALIDATION_ENABLED 0x14C1 + +/******************************************************************************* + * NAME : UnifiFtmUseResponseParameters + * PSID : 5314 (0x14C2) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Use Response burst parameters for burst + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_USE_RESPONSE_PARAMETERS 0x14C2 + +/******************************************************************************* + * NAME : UnifiFtmInitialResponseTimeout + * PSID : 5315 (0x14C3) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 10 + * MAX : 100 + * DEFAULT : 50 + * DESCRIPTION : + * Default timeout for FtmInitialResponse in units of ms. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_FTM_INITIAL_RESPONSE_TIMEOUT 0x14C3 + +/******************************************************************************* + * NAME : UnifiMlmeScanContinueIfMoreThanXAps + * PSID : 5410 (0x1522) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Part of Scan Algorithm: Keep scanning on a channel with lots of APs. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_CONTINUE_IF_MORE_THAN_XAPS 0x1522 + +/******************************************************************************* + * NAME : UnifiMlmeScanStopIfLessThanXNewAps + * PSID : 5411 (0x1523) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 4 + * DESCRIPTION : + * Part of Scan Algorithm: Stop scanning on a channel if less than X NEW APs + * are seen. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_STOP_IF_LESS_THAN_XNEW_APS 0x1523 + +/******************************************************************************* + * NAME : UnifiMlmeScanMultiVifEnabled + * PSID : 5412 (0x1524) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : 1 + * DESCRIPTION : + * Part of Scan Algorithm: Enable support for Multi Vif channel times. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_MULTI_VIF_ENABLED 0x1524 + +/******************************************************************************* + * NAME : UnifiMlmeScanNewAlgorithmEnabled + * PSID : 5413 (0x1525) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : 1 + * DESCRIPTION : + * Part of Scan Algorithm: Enable support for the new algorithm. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_MLME_SCAN_NEW_ALGORITHM_ENABLED 0x1525 + +/******************************************************************************* + * NAME : UnifiTpcMinPower2Gmimo + * PSID : 6011 (0x177B) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : 52 + * DESCRIPTION : + * Minimum power for 2.4GHz MIMO interface when RSSI is above + * unifiTPCMinPowerRSSIThreshold (quarter dbm). Should be greater than + * dot11PowerCapabilityMinImplemented. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_MIN_POWER2_GMIMO 0x177B + +/******************************************************************************* + * NAME : UnifiTpcMinPower5Gmimo + * PSID : 6012 (0x177C) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : 52 + * DESCRIPTION : + * Minimum power for 5 GHz MIMO interface when RSSI is above + * unifiTPCMinPowerRSSIThreshold (quarter dbm). Should be greater than + * dot11PowerCapabilityMinImplemented. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_MIN_POWER5_GMIMO 0x177C + +/******************************************************************************* + * NAME : UnifiLnaControlEnabled + * PSID : 6013 (0x177D) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enable dynamic switching of the LNA based on RSSI. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LNA_CONTROL_ENABLED 0x177D + +/******************************************************************************* + * NAME : UnifiLnaControlRssiThresholdLower + * PSID : 6014 (0x177E) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -128 + * MAX : 127 + * DEFAULT : -40 + * DESCRIPTION : + * The lower RSSI threshold for dynamic switching of the LNA. If the RSSI + * avg of received frames is lower than this value for all scheduled VIFs, + * then the external LNA will be enabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LNA_CONTROL_RSSI_THRESHOLD_LOWER 0x177E + +/******************************************************************************* + * NAME : UnifiLnaControlRssiThresholdUpper + * PSID : 6015 (0x177F) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -128 + * MAX : 127 + * DEFAULT : -30 + * DESCRIPTION : + * The upper RSSI threshold for dynamic switching of the LNA. If the RSSI + * avg of received frames is higher than this value for all scheduled VIFs, + * then the external LNA will be disabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LNA_CONTROL_RSSI_THRESHOLD_UPPER 0x177F + +/******************************************************************************* + * NAME : UnifiPowerIsGrip + * PSID : 6016 (0x1780) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Is using Grip power cap instead of SAR cap. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_POWER_IS_GRIP 0x1780 + +/******************************************************************************* + * NAME : UnifiTpcEnabled + * PSID : 6019 (0x1783) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : + * DESCRIPTION : + * Deprecated. Golden Certification MIB don't delete, change PSID or name + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_ENABLED 0x1783 + +/******************************************************************************* + * NAME : UnifiCurrentTxpowerLevel + * PSID : 6020 (0x1784) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : qdBm + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Maximum air power for the VIF. Values are expressed in 0.25 dBm units. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_CURRENT_TXPOWER_LEVEL 0x1784 + +/******************************************************************************* + * NAME : UnifiUserSetTxpowerLevel + * PSID : 6021 (0x1785) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : 127 + * DESCRIPTION : + * Test only: Maximum User Set Tx Power (quarter dBm). Enable it in + * unifiTestTxPowerEnable. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_USER_SET_TXPOWER_LEVEL 0x1785 + +/******************************************************************************* + * NAME : UnifiTpcMaxPowerRssiThreshold + * PSID : 6022 (0x1786) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : -55 + * DESCRIPTION : + * Below the (dBm) threshold, switch to the max power allowed by regulatory, + * if it has been previously reduced due to unifiTPCMinPowerRSSIThreshold. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_MAX_POWER_RSSI_THRESHOLD 0x1786 + +/******************************************************************************* + * NAME : UnifiTpcMinPowerRssiThreshold + * PSID : 6023 (0x1787) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : -45 + * DESCRIPTION : + * Above the (dBm) threshold, switch to the minimum hardware supported - + * capped by unifiTPCMinPower2G/unifiTPCMinPower5G. A Zero value reverts the + * power to a default state. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_MIN_POWER_RSSI_THRESHOLD 0x1787 + +/******************************************************************************* + * NAME : UnifiTpcMinPower2g + * PSID : 6024 (0x1788) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : 52 + * DESCRIPTION : + * Minimum power for 2.4GHz SISO interface when RSSI is above + * unifiTPCMinPowerRSSIThreshold (quarter dbm). Should be greater than + * dot11PowerCapabilityMinImplemented. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_MIN_POWER2G 0x1788 + +/******************************************************************************* + * NAME : UnifiTpcMinPower5g + * PSID : 6025 (0x1789) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : 40 + * DESCRIPTION : + * Minimum power for 5 GHz SISO interface when RSSI is above + * unifiTPCMinPowerRSSIThreshold (quarter dbm). Should be greater than + * dot11PowerCapabilityMinImplemented. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_MIN_POWER5G 0x1789 + +/******************************************************************************* + * NAME : UnifiSarBackoff + * PSID : 6026 (0x178A) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Max power values per band per index(quarter dBm). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SAR_BACKOFF 0x178A + +/******************************************************************************* + * NAME : UnifiTpcUseAfterConnectRsp + * PSID : 6027 (0x178B) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Use TPC only after MlmeConnect_Rsp has been received from the Host i.e. + * not during initial connection exchanges (EAPOL/DHCP operation) as RSSI + * readings might be inaccurate. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TPC_USE_AFTER_CONNECT_RSP 0x178B + +/******************************************************************************* + * NAME : UnifiRadioLpRxRssiThresholdLower + * PSID : 6028 (0x178C) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -128 + * MAX : 127 + * DEFAULT : -75 + * DESCRIPTION : + * The lower RSSI threshold for switching between low power rx and normal + * rx. If the RSSI avg of received frames is lower than this value for a + * VIF, then that VIF will vote against using low-power radio RX. Low power + * rx could negatively influence the receiver sensitivity. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_LP_RX_RSSI_THRESHOLD_LOWER 0x178C + +/******************************************************************************* + * NAME : UnifiRadioLpRxRssiThresholdUpper + * PSID : 6029 (0x178D) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * UNITS : dBm + * MIN : -128 + * MAX : 127 + * DEFAULT : -65 + * DESCRIPTION : + * The upper RSSI threshold for switching between low power rx and normal + * rx. If the RSSI avg of received frames is higher than this value for a + * VIF, then that VIF will vote in favour of using low-power radio RX. Low + * power RX could negatively influence the receiver sensitivity. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_RADIO_LP_RX_RSSI_THRESHOLD_UPPER 0x178D + +/******************************************************************************* + * NAME : UnifiTestTxPowerEnable + * PSID : 6032 (0x1790) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X03DD + * DESCRIPTION : + * Test only: Bitfield to enable Control Plane Tx Power processing. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TEST_TX_POWER_ENABLE 0x1790 + +/******************************************************************************* + * NAME : UnifiLteCoexMaxPowerRssiThreshold + * PSID : 6033 (0x1791) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : -55 + * DESCRIPTION : + * Below this (dBm) threshold, switch to max power allowed by regulatory, if + * it has been previously reduced due to unifiTPCMinPowerRSSIThreshold. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_COEX_MAX_POWER_RSSI_THRESHOLD 0x1791 + +/******************************************************************************* + * NAME : UnifiLteCoexMinPowerRssiThreshold + * PSID : 6034 (0x1792) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : -45 + * DESCRIPTION : + * Above this(dBm) threshold, switch to minimum hardware supported - capped + * by unifiTPCMinPower2G/unifiTPCMinPower5G. Zero reverts the power to its + * default state. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_COEX_MIN_POWER_RSSI_THRESHOLD 0x1792 + +/******************************************************************************* + * NAME : UnifiLteCoexPowerReduction + * PSID : 6035 (0x1793) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 127 + * DEFAULT : 24 + * DESCRIPTION : + * When LTE Coex Power Reduction provisions are met, impose a power cap of + * the regulatory domain less the amount specified by this MIB (quarter dB) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LTE_COEX_POWER_REDUCTION 0x1793 + +/******************************************************************************* + * NAME : UnifiPmfAssociationComebackTimeDelta + * PSID : 6050 (0x17A2) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 1100 + * DESCRIPTION : + * Timeout interval, in TU, for the TimeOut IE in the SA Query request + * frame. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_PMF_ASSOCIATION_COMEBACK_TIME_DELTA 0x17A2 + +/******************************************************************************* + * NAME : UnifiTestTspecHack + * PSID : 6060 (0x17AC) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Test only: Hack to allow in-house tspec testing + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TEST_TSPEC_HACK 0x17AC + +/******************************************************************************* + * NAME : UnifiTestTspecHackValue + * PSID : 6061 (0x17AD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Test only: Saved dialog number of tspec request action frame from the + * Host + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TEST_TSPEC_HACK_VALUE 0x17AD + +/******************************************************************************* + * NAME : UnifiDebugInstantDelivery + * PSID : 6069 (0x17B5) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Instant delivery control of the debug messages when set to true. Note: + * will not allow the host to suspend when set to True. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_INSTANT_DELIVERY 0x17B5 + +/******************************************************************************* + * NAME : UnifiDebugEnable + * PSID : 6071 (0x17B7) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Debug to host state. Debug is either is sent to the host or it isn't. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEBUG_ENABLE 0x17B7 + +/******************************************************************************* + * NAME : UnifiDPlaneDebug + * PSID : 6073 (0x17B9) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X03 + * DESCRIPTION : + * Bit mask for turning on individual debug entities in the data_plane that + * if enabled effect throughput. See DPLP_DEBUG_ENTITIES_T in + * dplane_dplp_debug.h for bits. Default of 0x3 means dplp and ampdu logs + * are enabled. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DPLANE_DEBUG 0x17B9 + +/******************************************************************************* + * NAME : UnifiNanEnabled + * PSID : 6080 (0x17C0) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Enables Neighbour Aware Networking (NAN) + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_ENABLED 0x17C0 + +/******************************************************************************* + * NAME : UnifiNanBeaconCapabilities + * PSID : 6081 (0x17C1) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X0720 + * DESCRIPTION : + * The 16-bit field follows the coding of IEEE 802.11 Capability + * Information. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_BEACON_CAPABILITIES 0x17C1 + +/******************************************************************************* + * NAME : UnifiNanMaxConcurrentClusters + * PSID : 6082 (0x17C2) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 1 + * DESCRIPTION : + * Maximum number of concurrent NAN clusters supported. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_CONCURRENT_CLUSTERS 0x17C2 + +/******************************************************************************* + * NAME : UnifiNanMaxConcurrentPublishes + * PSID : 6083 (0x17C3) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * Maximum number of concurrent NAN Publish instances supported. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_CONCURRENT_PUBLISHES 0x17C3 + +/******************************************************************************* + * NAME : UnifiNanMaxConcurrentSubscribes + * PSID : 6084 (0x17C4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 2 + * DESCRIPTION : + * Maximum number of concurrent NAN Subscribe instances supported. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_CONCURRENT_SUBSCRIBES 0x17C4 + +/******************************************************************************* + * NAME : UnifiNanMaxServiceNameLength + * PSID : 6085 (0x17C5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 255 + * DESCRIPTION : + * Maximum Service Name Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_SERVICE_NAME_LENGTH 0x17C5 + +/******************************************************************************* + * NAME : UnifiNanMaxMatchFilterLength + * PSID : 6086 (0x17C6) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 255 + * DESCRIPTION : + * Maximum Match Filter Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_MATCH_FILTER_LENGTH 0x17C6 + +/******************************************************************************* + * NAME : UnifiNanMaxTotalMatchFilterLength + * PSID : 6087 (0x17C7) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 255 + * DESCRIPTION : + * Maximum Total Match Filter Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_TOTAL_MATCH_FILTER_LENGTH 0x17C7 + +/******************************************************************************* + * NAME : UnifiNanMaxServiceSpecificInfoLength + * PSID : 6088 (0x17C8) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 255 + * DESCRIPTION : + * Maximum Service Specific Info Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_SERVICE_SPECIFIC_INFO_LENGTH 0x17C8 + +/******************************************************************************* + * NAME : UnifiNanMaxVsaDataLength + * PSID : 6089 (0x17C9) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Maximum Vendor Specific Attribute Data Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_VSA_DATA_LENGTH 0x17C9 + +/******************************************************************************* + * NAME : UnifiNanMaxMeshDataLength + * PSID : 6090 (0x17CA) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Maximum Mesh Data Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_MESH_DATA_LENGTH 0x17CA + +/******************************************************************************* + * NAME : UnifiNanMaxNdiInterfaces + * PSID : 6091 (0x17CB) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Maximum NDI Interfaces. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_NDI_INTERFACES 0x17CB + +/******************************************************************************* + * NAME : UnifiNanMaxNdpSessions + * PSID : 6092 (0x17CC) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Maximum NDP Sessions. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_NDP_SESSIONS 0x17CC + +/******************************************************************************* + * NAME : UnifiNanMaxAppInfoLength + * PSID : 6093 (0x17CD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Maximum App Info Length. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NAN_MAX_APP_INFO_LENGTH 0x17CD + +/******************************************************************************* + * NAME : ReservedForNan + * PSID : 6094 (0x17CE) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 3 + * DESCRIPTION : + * Enables low power radio RX for idle STA and AP VIFs respectively. + * Setting/clearing bit 0 enables/disabled LP RX for (all) STA/Cli VIFs. + * Setting/clearing bit 1 enables/disabled LP RX for AP/GO VIFs.This mib + * value will be updated if "unifiRameUpdateMibs" mib is toggled + *******************************************************************************/ +#define SLSI_PSID_RESERVED_FOR_NAN 0x17CE + +/******************************************************************************* + * NAME : hutsReadWriteDataElementInt32 + * PSID : 6100 (0x17D4) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 1000 + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of int32 type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_DATA_ELEMENT_INT32 0x17D4 + +/******************************************************************************* + * NAME : hutsReadWriteDataElementBoolean + * PSID : 6101 (0x17D5) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of boolean type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_DATA_ELEMENT_BOOLEAN 0x17D5 + +/******************************************************************************* + * NAME : hutsReadWriteDataElementOctetString + * PSID : 6102 (0x17D6) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 9 + * MAX : 9 + * DEFAULT : { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of octet string + * type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_DATA_ELEMENT_OCTET_STRING 0x17D6 + +/******************************************************************************* + * NAME : hutsReadWriteTableInt16Row + * PSID : 6103 (0x17D7) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry table of int16 + * type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_TABLE_INT16_ROW 0x17D7 + +/******************************************************************************* + * NAME : hutsReadWriteTableOctetStringRow + * PSID : 6104 (0x17D8) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 6 + * MAX : 73 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry table of octet + * string type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_TABLE_OCTET_STRING_ROW 0x17D8 + +/******************************************************************************* + * NAME : hutsReadWriteRemoteProcedureCallInt32 + * PSID : 6105 (0x17D9) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : 0X000A0001 + * DESCRIPTION : + * Reserved for HUTS tests - Remote Procedure call read/write entry of int32 + * type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_REMOTE_PROCEDURE_CALL_INT32 0x17D9 + +/******************************************************************************* + * NAME : hutsReadWriteRemoteProcedureCallOctetString + * PSID : 6107 (0x17DB) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 144 + * MAX : 144 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Remote Procedure call read/write entry of octet + * string type. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_REMOTE_PROCEDURE_CALL_OCTET_STRING 0x17DB + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiInt16 + * PSID : 6108 (0x17DC) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : -55 + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of int16 type via + * internal API. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_INT16 0x17DC + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiUint16 + * PSID : 6109 (0x17DD) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 0X0730 + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of unsigned int16 + * type via internal API. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_UINT16 0x17DD + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiUint32 + * PSID : 6110 (0x17DE) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * UNITS : microseconds + * MIN : 0 + * MAX : 2147483647 + * DEFAULT : 30000 + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of unsigned int32 + * type via internal API. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_UINT32 0x17DE + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiInt64 + * PSID : 6111 (0x17DF) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : -9223372036854775808 + * MAX : 9223372036854775807 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of int64 type via + * internal API. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_INT64 0x17DF + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiBoolean + * PSID : 6112 (0x17E0) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : TRUE + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of boolean type + * via internal API. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_BOOLEAN 0x17E0 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiOctetString + * PSID : 6113 (0x17E1) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 8 + * MAX : 8 + * DEFAULT : { 0X00, 0X18, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * Reserved for HUTS tests - Data element read/write entry of octet string + * type via internal API. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_OCTET_STRING 0x17E1 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiFixedSizeTableRow + * PSID : 6114 (0x17E2) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : 0 + * MAX : 100 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Fixed size table rows of int16 type via + * internal API + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_FIXED_SIZE_TABLE_ROW 0x17E2 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiVarSizeTableRow + * PSID : 6115 (0x17E3) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 6 + * MAX : 73 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Variable size table rows of octet string type + * via internal API + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_VAR_SIZE_TABLE_ROW 0x17E3 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiFixSizeTableKey1Row + * PSID : 6116 (0x17E4) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Fixed size table rows of int16 type via + * internal API + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_FIX_SIZE_TABLE_KEY1_ROW 0x17E4 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiFixSizeTableKey2Row + * PSID : 6117 (0x17E5) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Reserved for HUTS tests - Fixed size table rows of int16 type via + * internal API + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_FIX_SIZE_TABLE_KEY2_ROW 0x17E5 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiFixVarSizeTableKey1Row + * PSID : 6118 (0x17E6) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The values stored in hutsReadWriteInternalAPIFixVarSizeTableKeys + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_FIX_VAR_SIZE_TABLE_KEY1_ROW 0x17E6 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiFixVarSizeTableKey2Row + * PSID : 6119 (0x17E7) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * The values stored in hutsReadWriteInternalAPIFixVarSizeTableKeys + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_FIX_VAR_SIZE_TABLE_KEY2_ROW 0x17E7 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiFixSizeTableKeyRow + * PSID : 6120 (0x17E8) + * PER INTERFACE?: NO + * TYPE : INT64 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * The number of received MPDUs discarded by the CCMP decryption algorithm. + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_FIX_SIZE_TABLE_KEY_ROW 0x17E8 + +/******************************************************************************* + * NAME : hutsReadWriteInternalApiVarSizeTableKeyRow + * PSID : 6121 (0x17E9) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 144 + * MAX : 144 + * DEFAULT : + * DESCRIPTION : + * Write a DPD LUT entry + *******************************************************************************/ +#define SLSI_PSID_HUTS_READ_WRITE_INTERNAL_API_VAR_SIZE_TABLE_KEY_ROW 0x17E9 + +/******************************************************************************* + * NAME : UnifiTestScanNoMedium + * PSID : 6122 (0x17EA) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Test only: Stop Scan from using the Medium to allow thruput testing. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_TEST_SCAN_NO_MEDIUM 0x17EA + +/******************************************************************************* + * NAME : UnifiDualBandConcurrency + * PSID : 6123 (0x17EB) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Identify whether the chip supports dualband concurrency or not (RSDB vs. + * VSDB). Set in the respective platform htf file. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DUAL_BAND_CONCURRENCY 0x17EB + +/******************************************************************************* + * NAME : UnifiLoggerMaxDelayedEvents + * PSID : 6124 (0x17EC) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : 10 + * DESCRIPTION : + * Maximum number of events to keep when host is suspended. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_LOGGER_MAX_DELAYED_EVENTS 0x17EC + +/******************************************************************************* + * NAME : UnifiRegulatoryParameters + * PSID : 8011 (0x1F4B) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 3 + * MAX : 73 + * DEFAULT : + * DESCRIPTION : + * Regulatory parameters. Each row of the table contains the regulatory + * rules for one country: octet 0 - first character of alpha2 code for + * country octet 1 - second character of alpha2 code for country octet 2 - + * regulatory domain for the country Followed by the rules for the country, + * numbered 0..n in this description octet 7n+3 - LSB start frequency octet + * 7n+4 - MSB start frequency octet 7n+5 - LSB end frequency octet 7n+6 - + * MSB end frequency octet 7n+7 - maximum bandwidth octet 7n+8 - maximum + * power octet 7n+9 - rule flags + *******************************************************************************/ +#define SLSI_PSID_UNIFI_REGULATORY_PARAMETERS 0x1F4B + +/******************************************************************************* + * NAME : UnifiSupportedChannels + * PSID : 8012 (0x1F4C) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 20 + * DEFAULT : {0X01,0X0D,0X24,0X04,0X34,0X04,0X64,0X0C,0X95,0X05} + * DESCRIPTION : + * Supported 20MHz channel centre frequency grouped in sub-bands. For each + * sub-band: starting channel number, followed by number of channels + *******************************************************************************/ +#define SLSI_PSID_UNIFI_SUPPORTED_CHANNELS 0x1F4C + +/******************************************************************************* + * NAME : UnifiDefaultCountry + * PSID : 8013 (0x1F4D) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 3 + * MAX : 3 + * DEFAULT : {0X30, 0X30, 0X20} + * DESCRIPTION : + * Hosts sets the Default Code. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEFAULT_COUNTRY 0x1F4D + +/******************************************************************************* + * NAME : UnifiCountryList + * PSID : 8014 (0x1F4E) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 2 + * MAX : 270 + * DEFAULT : (Too Large to display) + * DESCRIPTION : + * Defines the ordered list of countries present in unifiRegulatoryTable. + * Each country is coded as 2 ASCII characters. If unifiRegulatoryTable is + * modified, such as a country is either added, deleted or its relative + * location is modified, has to be updated as well. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_COUNTRY_LIST 0x1F4E + +/******************************************************************************* + * NAME : UnifiOperatingClassParamters + * PSID : 8015 (0x1F4F) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 1 + * MAX : 73 + * DEFAULT : + * DESCRIPTION : + * Supported Operating Class parameters. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_OPERATING_CLASS_PARAMTERS 0x1F4F + +/******************************************************************************* + * NAME : UnifiVifCountry + * PSID : 8016 (0x1F50) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Per VIf: Each VIF updates its Country Code for the Host to read + *******************************************************************************/ +#define SLSI_PSID_UNIFI_VIF_COUNTRY 0x1F50 + +/******************************************************************************* + * NAME : UnifiNoCellMaxPower + * PSID : 8017 (0x1F51) + * PER INTERFACE?: NO + * TYPE : SlsiInt16 + * MIN : -32768 + * MAX : 32767 + * DEFAULT : + * DESCRIPTION : + * Max power values for included channels (quarter dBm). + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NO_CELL_MAX_POWER 0x1F51 + +/******************************************************************************* + * NAME : UnifiNoCellIncludedChannels + * PSID : 8018 (0x1F52) + * PER INTERFACE?: NO + * TYPE : SlsiUint8 + * MIN : 8 + * MAX : 8 + * DEFAULT : { 0X00, 0X18, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 } + * DESCRIPTION : + * Channels applicable. Defined in a uint64 represented by the octet string. + * First byte of the octet string maps to LSB. Bit 0 maps to channel 1. + * Mapping defined in ChannelisationRules. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_NO_CELL_INCLUDED_CHANNELS 0x1F52 + +/******************************************************************************* + * NAME : UnifiRegDomVersion + * PSID : 8019 (0x1F53) + * PER INTERFACE?: NO + * TYPE : SlsiUint16 + * MIN : 0 + * MAX : 65535 + * DEFAULT : + * DESCRIPTION : + * Regulatory domain version encoded into 2 bytes, major version as MSB and + * minor version as LSB + *******************************************************************************/ +#define SLSI_PSID_UNIFI_REG_DOM_VERSION 0x1F53 + +/******************************************************************************* + * NAME : UnifiDefaultCountryWithoutCH12CH13 + * PSID : 8020 (0x1F54) + * PER INTERFACE?: NO + * TYPE : SlsiBool + * MIN : 0 + * MAX : 1 + * DEFAULT : FALSE + * DESCRIPTION : + * Update the default country code to ensure CH12 and CH13 are not used. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_DEFAULT_COUNTRY_WITHOUT_CH12_CH13 0x1F54 + +/******************************************************************************* + * NAME : UnifiReadReg + * PSID : 8051 (0x1F73) + * PER INTERFACE?: NO + * TYPE : SlsiUint32 + * MIN : 0 + * MAX : 4294967295 + * DEFAULT : + * DESCRIPTION : + * Read value from a register and return it. + *******************************************************************************/ +#define SLSI_PSID_UNIFI_READ_REG 0x1F73 + +#ifdef __cplusplus +} +#endif +#endif /* SLSI_MIB_H__ */ diff --git a/drivers/net/wireless/scsc/mib_text_convert.c b/drivers/net/wireless/scsc/mib_text_convert.c new file mode 100644 index 000000000000..dc6e63eb81ae --- /dev/null +++ b/drivers/net/wireless/scsc/mib_text_convert.c @@ -0,0 +1,327 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd and its Licensors. + * All rights reserved. + * + *****************************************************************************/ + +#include "debug.h" +#include "mib_text_convert.h" + +#define CSR_TOUPPER(character) (((character) >= 'a') && ((character) <= 'z') ? ((character) - 0x20) : (character)) + +static inline bool CsrIsSpace(u8 c) +{ + switch (c) { + case '\t': + case '\n': + case '\f': + case '\r': + case ' ': + return true; + default: + return false; + } +} + +static inline char *CsrStrDup(const char *string) +{ + if (string != NULL) { + u32 len = strlen(string) + 1; + + return memcpy(kmalloc(len, GFP_KERNEL), string, len); + } + return NULL; +} + +static int CsrStrNICmp(const char *string1, + const char *string2, + size_t count) +{ + u32 index; + int returnValue = 0; + + for (index = 0; index < count; index++) { + if (CSR_TOUPPER(string1[index]) != CSR_TOUPPER(string2[index])) { + if (CSR_TOUPPER(string1[index]) > CSR_TOUPPER(string2[index])) + returnValue = 1; + else + returnValue = -1; + break; + } + if (string1[index] == '\0') + break; + } + return returnValue; +} + +static bool CsrHexStrToUint8(const char *string, u8 *returnValue) +{ + u16 currentIndex = 0; + + *returnValue = 0; + if ((string[currentIndex] == '0') && (CSR_TOUPPER(string[currentIndex + 1]) == 'X')) + string += 2; + if (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) || ((CSR_TOUPPER(string[currentIndex]) >= 'A') && (CSR_TOUPPER(string[currentIndex]) <= 'F'))) { + while (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) || ((CSR_TOUPPER(string[currentIndex]) >= 'A') && (CSR_TOUPPER(string[currentIndex]) <= 'F'))) { + *returnValue = (u8)(*returnValue * 16 + (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) ? string[currentIndex] - '0' : CSR_TOUPPER(string[currentIndex]) - 'A' + 10)); + currentIndex++; + if (currentIndex >= 2) + break; + } + return true; + } + return false; +} + +static bool CsrHexStrToUint16(const char *string, u16 *returnValue) +{ + u16 currentIndex = 0; + + *returnValue = 0; + if ((string[currentIndex] == '0') && (CSR_TOUPPER(string[currentIndex + 1]) == 'X')) + string += 2; + if (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) || ((CSR_TOUPPER(string[currentIndex]) >= 'A') && (CSR_TOUPPER(string[currentIndex]) <= 'F'))) { + while (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) || ((CSR_TOUPPER(string[currentIndex]) >= 'A') && (CSR_TOUPPER(string[currentIndex]) <= 'F'))) { + *returnValue = (u16)(*returnValue * 16 + (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) ? string[currentIndex] - '0' : CSR_TOUPPER(string[currentIndex]) - 'A' + 10)); + currentIndex++; + if (currentIndex >= 4) + break; + } + return true; + } + return false; +} + +static bool CsrHexStrToUint32(const char *string, u32 *returnValue) +{ + u16 currentIndex = 0; + + *returnValue = 0; + if ((string[currentIndex] == '0') && (CSR_TOUPPER(string[currentIndex + 1]) == 'X')) + string += 2; + if (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) || ((CSR_TOUPPER(string[currentIndex]) >= 'A') && (CSR_TOUPPER(string[currentIndex]) <= 'F'))) { + while (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) || ((CSR_TOUPPER(string[currentIndex]) >= 'A') && (CSR_TOUPPER(string[currentIndex]) <= 'F'))) { + *returnValue = *returnValue * 16 + (((string[currentIndex] >= '0') && (string[currentIndex] <= '9')) ? string[currentIndex] - '0' : CSR_TOUPPER(string[currentIndex]) - 'A' + 10); + currentIndex++; + if (currentIndex >= 8) + break; + } + return true; + } + return false; +} + +static bool CsrWifiMibConvertStrToUint16(const char *str, u16 *returnValue) +{ + u16 currentIndex = 0; + + if (str[1] == 'x') + return CsrHexStrToUint16(str, returnValue); + + *returnValue = 0; + if ((str[currentIndex] >= '0') && (str[currentIndex] <= '9')) { + while (str[currentIndex] >= '0' && str[currentIndex] <= '9') { + *returnValue *= 10; + *returnValue += (u8)str[currentIndex++] - '0'; + } + return true; + } + return false; +} + +static bool CsrWifiMibConvertStrToUint32(const char *str, u32 *returnValue) +{ + u16 currentIndex = 0; + + if (str[1] == 'x') + return CsrHexStrToUint32(str, returnValue); + + *returnValue = 0; + if ((str[currentIndex] >= '0') && (str[currentIndex] <= '9')) { + while (str[currentIndex] >= '0' && str[currentIndex] <= '9') { + *returnValue *= 10; + *returnValue += (u8)str[currentIndex++] - '0'; + } + return true; + } + return false; +} + +static bool CsrWifiMibConvertTextParseLine(const char *linestr, struct slsi_mib_data *mibDataSet, struct slsi_mib_data *mibDataGet) +{ + struct slsi_mib_entry entry; + bool result = true; + size_t equals = 0; + size_t dot1 = 0; + size_t dot2 = 0; + size_t trimmedIndex = 0; + char *trimmed = kmalloc(strlen(linestr) + 1, GFP_KERNEL); + const char *current_char = linestr; + bool processingStr = false; + + memset(&entry, 0x00, sizeof(entry)); + while (current_char[0] != '\0') { + if (current_char[0] == '"') + processingStr = !processingStr; + if (!processingStr) { + if (current_char[0] == '#') + break; + if (CsrIsSpace((u8)current_char[0])) { + current_char++; + continue; + } + if (!equals && (current_char[0] == '.')) { + if (!dot1) { + dot1 = trimmedIndex; + } else if (!dot2) { + dot2 = trimmedIndex; + } else { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') only 2 indexes supported", trimmed); + result = false; + } + } + if (!equals && (current_char[0] == '=')) + equals = trimmedIndex; + } + trimmed[trimmedIndex++] = current_char[0]; + + current_char++; + } + trimmed[trimmedIndex] = '\0'; + + if (strlen(trimmed) == 0) { + kfree(trimmed); + return result; + } + + if (result) { + char sep = trimmed[dot1 ? dot1 : equals]; + + if (dot1 || equals) + trimmed[dot1 ? dot1 : equals] = '\0'; + trimmed[dot1 ? dot1 : equals] = sep; + + if (!CsrWifiMibConvertStrToUint16(trimmed, &entry.psid)) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') Convert failed", trimmed); + result = false; + } + if (dot1 && !CsrWifiMibConvertStrToUint16(&trimmed[dot1 + 1], &entry.index[0])) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') Convert failed", trimmed); + result = false; + } + if (dot2 && !CsrWifiMibConvertStrToUint16(&trimmed[dot2 + 1], &entry.index[1])) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') Convert failed", trimmed); + result = false; + } + + if (result && !equals && mibDataGet) { + entry.value.type = SLSI_MIB_TYPE_NONE; + (void)slsi_mib_encode(mibDataGet, &entry); + } + + if (result && equals && mibDataSet) { + char *data = &trimmed[equals + 1]; + /*SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') psid:%d, index1:%d, index2:%d, data '%s'", trimmed, entry.psid, entry.index[0], entry.index[1], data); */ + if (CsrStrNICmp(data, "true", 4) == 0) { + entry.value.type = SLSI_MIB_TYPE_BOOL; + entry.value.u.boolValue = 1; + } else if (CsrStrNICmp(data, "false", 5) == 0) { + entry.value.type = SLSI_MIB_TYPE_BOOL; + entry.value.u.boolValue = 0; + } else if (data[0] == '"') { + /* Null Terminated String */ + entry.value.type = SLSI_MIB_TYPE_OCTET; + entry.value.u.octetValue.dataLength = (u32)strlen(&data[1]); + entry.value.u.octetValue.data = (u8 *)CsrStrDup(&data[1]); + entry.value.u.octetValue.data[entry.value.u.octetValue.dataLength - 1] = '\0'; + } else if (data[0] == '[') { + /* Octet String */ + size_t i; + u16 octetLen = ((u16)strlen(&data[1]) - 1) / 2; + + entry.value.type = SLSI_MIB_TYPE_OCTET; + entry.value.u.octetValue.dataLength = octetLen; + entry.value.u.octetValue.data = kmalloc(entry.value.u.octetValue.dataLength + 1, GFP_KERNEL); + for (i = 0; i < octetLen; i++) + if (!CsrHexStrToUint8(&data[1 + (i * 2)], &entry.value.u.octetValue.data[i])) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') Convert Hex Bytes failed", trimmed); + result = false; + break; + } + entry.value.u.octetValue.data[octetLen] = '\0'; /* Make sure the Octet Stream is NULL terminated in case it is interpreted as a String */ + } else if (data[0] == '-') { + /* Negative Integer Value */ + entry.value.type = SLSI_MIB_TYPE_INT; + if (!CsrWifiMibConvertStrToUint32(&data[1], &entry.value.u.uintValue)) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') Convert Integer failed", trimmed); + result = false; + } else { + entry.value.u.intValue = (s32)(0 - (u32)entry.value.u.uintValue); + } + } else if (!CsrWifiMibConvertStrToUint32(data, &entry.value.u.uintValue)) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine('%s') Convert Unsigned Integer failed", trimmed); + result = false; + } else { + entry.value.type = SLSI_MIB_TYPE_UINT; + } + if (result) + (void)slsi_mib_encode(mibDataSet, &entry); + if (entry.value.type == SLSI_MIB_TYPE_OCTET) + kfree(entry.value.u.octetValue.data); + } + } + + kfree(trimmed); + + return result; +} + +static bool CsrWifiMibConvertTextAppend(const char *mibText, struct slsi_mib_data *mibDataSet, struct slsi_mib_data *mibDataGet) +{ + bool result = true; + const char *lineStart = mibText; + const char *lineEnd = mibText; + + if (mibText == NULL) + return false; + + while (lineEnd[0] != '\0') { + if ((lineEnd[0] == '\n') || (lineEnd[0] == '\r') || (lineEnd[1] == '\0')) { + size_t strSize = (size_t)(lineEnd - lineStart); + + if ((lineEnd[1] == '\0')) + strSize++; + if (strSize > 2) { + char *line = kmalloc(strSize + 1, GFP_KERNEL); + (void)strncpy(line, lineStart, strSize); + line[strSize] = '\0'; + if (!CsrWifiMibConvertTextParseLine(line, mibDataSet, mibDataGet)) { + SLSI_ERR_NODEV("CsrWifiMibConvertTextParseLine() Failed for line '%s'", line); + result = false; + } + kfree(line); + } + lineEnd++; + lineStart = lineEnd; + continue; + } + lineEnd++; + } + + return result; +} + +bool CsrWifiMibConvertText(const char *mibText, struct slsi_mib_data *mibDataSet, struct slsi_mib_data *mibDataGet) +{ + if (mibDataSet) { + mibDataSet->data = NULL; + mibDataSet->dataLength = 0; + } + if (mibDataGet) { + mibDataGet->data = NULL; + mibDataGet->dataLength = 0; + } + + return CsrWifiMibConvertTextAppend(mibText, mibDataSet, mibDataGet); +} diff --git a/drivers/net/wireless/scsc/mib_text_convert.h b/drivers/net/wireless/scsc/mib_text_convert.h new file mode 100644 index 000000000000..0864f7488d6d --- /dev/null +++ b/drivers/net/wireless/scsc/mib_text_convert.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd and its Licensors. + * All rights reserved. + * + *****************************************************************************/ + +#ifndef MIB_TEXT_CONVERT_H__ +#define MIB_TEXT_CONVERT_H__ + +#include "mib.h" + +/******************************************************************************* + * + * NAME + * CsrWifiMibConvertText + * + * DESCRIPTION + * Converts a simple text mib file format into an encoded mib data buffer + * + * TEXT FILE FORMAT + # Comment + # = + # + # 123 = 0x01 # Unsigned Integer + # 123 = 1 # Unsigned Integer + # 123 = -1 # Integer + # 123 = true # Boolean (Case Sensitive) + # 123 = false # Boolean (Case Sensitive) + # 123 = [12183984] # Octet String (Hex Format) + # 123 = "ABCDEFGH" # Octet String (Null Terminated Ascii) + # + # Indexes (Max 2 indexes supported) + # 123.1 = 1 # Single Index + # 123.1.2 = 1 # Multi Index + # + # Hex Format (All Unsigned Integer values can use the alternative 0x[0-F] format) + # 0xF12E.0x1.0x2 = 0x01 # Unsigned Integer + # + # RETURN + # bool: true = No errors in conversion + # + *******************************************************************************/ +bool CsrWifiMibConvertText(const char *mibText, struct slsi_mib_data *mibDataSet, struct slsi_mib_data *mibDataGet); + +#endif /* MIB_TEXT_CONVERT_H__ */ diff --git a/drivers/net/wireless/scsc/mlme.c b/drivers/net/wireless/scsc/mlme.c new file mode 100755 index 000000000000..7bba88987145 --- /dev/null +++ b/drivers/net/wireless/scsc/mlme.c @@ -0,0 +1,4365 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "dev.h" +#include "debug.h" +#include "mlme.h" +#include "mib.h" +#include "mgt.h" +#include "cac.h" + +#define SLSI_SCAN_PRIVATE_IE_CHANNEL_LIST_HEADER_LEN 7 +#define SLSI_SCAN_PRIVATE_IE_SSID_FILTER_HEADER_LEN 7 +#define SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE 3 +#define SLSI_CHANN_INFO_HT_SCB 0x0100 + +#define SLSI_NOA_CONFIG_REQUEST_ID (1) +#define SLSI_MLME_ARP_DROP_FREE_SLOTS_COUNT 16 + +static bool missing_cfm_ind_panic = true; +module_param(missing_cfm_ind_panic, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(missing_cfm_ind_panic, "Panic on missing confirm or indication from the chip"); + +struct slsi_mlme_rsse { + u8 group_cs_count; + const u8 *group_cs; + u8 pairwise_cs_count; + const u8 *pairwise_cs; + u8 akm_suite_count; + const u8 *akm_suite; + u8 pmkid_count; + const u8 *pmkid; + const u8 *group_mgmt_cs; /* used for PMF*/ +}; + +static struct sk_buff *slsi_mlme_wait_for_cfm(struct slsi_dev *sdev, struct slsi_sig_send *sig_wait) +{ + struct sk_buff *cfm = NULL; + int tm; + + tm = wait_for_completion_timeout(&sig_wait->completion, msecs_to_jiffies(*sdev->sig_wait_cfm_timeout)); + spin_lock_bh(&sig_wait->send_signal_lock); + + /* Confirm timed out? */ + if (!sig_wait->cfm) { + SLSI_ERR(sdev, "No cfm(0x%.4X) for req(0x%04X) senderid=0x%x\n", sig_wait->cfm_id, sig_wait->req_id, sig_wait->process_id); + if (tm == 0) { + char reason[80]; + + WARN(1, "Timeout - confirm 0x%04x not received from chip\n", sig_wait->cfm_id); + if (missing_cfm_ind_panic) { + snprintf(reason, sizeof(reason), "Timed out while waiting for the cfm(0x%.4x) for req(0x%04x)", + sig_wait->cfm_id, sig_wait->req_id); + + spin_unlock_bh(&sig_wait->send_signal_lock); + slsi_sm_service_failed(sdev, reason); + spin_lock_bh(&sig_wait->send_signal_lock); + } + } else { + WARN(1, "Confirm 0x%04x lost\n", sig_wait->cfm_id); + } + } else { + WARN_ON(fapi_get_u16(sig_wait->cfm, receiver_pid) != sig_wait->process_id); + WARN_ON(fapi_get_u16(sig_wait->cfm, id) != sig_wait->cfm_id); + } + + sig_wait->cfm_id = 0; + cfm = sig_wait->cfm; + sig_wait->cfm = NULL; + if (!cfm) + sig_wait->ind_id = 0; + + spin_unlock_bh(&sig_wait->send_signal_lock); + + return cfm; +} + +static int panic_on_lost_ind(u16 ind_id) +{ + if (ind_id == MLME_SCAN_DONE_IND) + return 0; + return 1; +} + +static struct sk_buff *slsi_mlme_wait_for_ind(struct slsi_dev *sdev, struct net_device *dev, struct slsi_sig_send *sig_wait, u16 ind_id) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *ind = NULL; + int tm = 0; + + /* The indication and confirm may have been received in the same HIP read. + * The HIP receive buffer processes all received signals in one thread whilst the + * waiting process may not be scheduled even if the "complete" call is made. + * In this scenario, the complete() call has already been made for this object + * and the wait will return immediately. + */ + if (ind_id == MLME_SCAN_DONE_IND) + /* To handle the coex scenario where BTscan has high priority increasing the wait time to 40 secs */ + tm = wait_for_completion_timeout(&sig_wait->completion, msecs_to_jiffies(SLSI_SCAN_DONE_IND_WAIT_TIMEOUT)); + else if ((ind_id == MLME_DISCONNECT_IND) && (ndev_vif->vif_type == FAPI_VIFTYPE_AP)) + tm = wait_for_completion_timeout(&sig_wait->completion, msecs_to_jiffies(sdev->device_config.ap_disconnect_ind_timeout)); + else + tm = wait_for_completion_timeout(&sig_wait->completion, msecs_to_jiffies(*sdev->sig_wait_cfm_timeout)); + + spin_lock_bh(&sig_wait->send_signal_lock); + + /* Indication timed out? */ + if (!sig_wait->ind) { + SLSI_ERR(sdev, "No ind(0x%.4X) for req(0x%04X) senderid=0x%x\n", sig_wait->ind_id, sig_wait->req_id, sig_wait->process_id); + if (tm == 0) { + char reason[80]; + + WARN(1, "Timeout - indication 0x%04x not received from chip\n", sig_wait->ind_id); + if (missing_cfm_ind_panic && panic_on_lost_ind(ind_id)) { + snprintf(reason, sizeof(reason), "Timed out while waiting for the ind(0x%.4x) for req(0x%04x)", + sig_wait->ind_id, sig_wait->req_id); + + spin_unlock_bh(&sig_wait->send_signal_lock); + slsi_sm_service_failed(sdev, reason); + spin_lock_bh(&sig_wait->send_signal_lock); + } + } else { + WARN(1, "Indication 0x%04x lost\n", sig_wait->ind_id); + } + } else { + WARN_ON(fapi_get_u16(sig_wait->ind, receiver_pid) != sig_wait->process_id); + WARN_ON(fapi_get_u16(sig_wait->ind, id) != sig_wait->ind_id); + } + + sig_wait->ind_id = 0; + ind = sig_wait->ind; + sig_wait->ind = NULL; + + spin_unlock_bh(&sig_wait->send_signal_lock); + + return ind; +} + +/* mib_error: NULL when not required + * ind: 0 when not required, if used validate_cfm_wait_ind MUST be supplied + * validate_cfm_wait_ind: NULL when not required, if used ind MUS not be 0 + * NOTE: dev can be NULL! + */ +static struct sk_buff *slsi_mlme_tx_rx(struct slsi_dev *sdev, + struct net_device *dev, + struct sk_buff *skb, + u16 cfm_id, + struct sk_buff **mib_error, + u16 ind_id, + bool (*validate_cfm_wait_ind)(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *cfm)) +{ + struct sk_buff *rx = NULL; + int err; + u16 req_id = fapi_get_u16(skb, id); + struct slsi_sig_send *sig_wait = &sdev->sig_wait; + + if (dev) { + struct netdev_vif *ndev_vif = netdev_priv(dev); + + sig_wait = &ndev_vif->sig_wait; + } + + if (sdev->mlme_blocked) { + SLSI_DBG3(sdev, SLSI_TX, "Rejected. mlme_blocked=%d", sdev->mlme_blocked); + slsi_kfree_skb(skb); + return NULL; + } + + slsi_wakelock(&sdev->wlan_wl); + SLSI_MUTEX_LOCK(sig_wait->mutex); + + spin_lock_bh(&sig_wait->send_signal_lock); + if (++sig_wait->process_id > SLSI_TX_PROCESS_ID_MAX) + sig_wait->process_id = SLSI_TX_PROCESS_ID_MIN; + + WARN_ON(sig_wait->cfm); + WARN_ON(sig_wait->ind); + slsi_kfree_skb(sig_wait->cfm); + slsi_kfree_skb(sig_wait->ind); + slsi_kfree_skb(sig_wait->mib_error); + sig_wait->cfm = NULL; + sig_wait->ind = NULL; + sig_wait->mib_error = NULL; + sig_wait->req_id = req_id; + sig_wait->cfm_id = cfm_id; + sig_wait->ind_id = ind_id; + + fapi_set_u16(skb, sender_pid, sig_wait->process_id); + spin_unlock_bh(&sig_wait->send_signal_lock); + + err = slsi_tx_control(sdev, dev, skb); + if (err != 0) { + SLSI_ERR(sdev, "Failed to send mlme signal:0x%.4X, err=%d\n", req_id, err); + slsi_kfree_skb(skb); + goto clean_exit; + } + + if (cfm_id) { + rx = slsi_mlme_wait_for_cfm(sdev, sig_wait); + if (rx && ind_id) { + /* The cfm skb is owned by the validate_cfm_wait_ind() function and MUST be freed or saved there */ + if (validate_cfm_wait_ind(sdev, dev, rx)) { + rx = slsi_mlme_wait_for_ind(sdev, dev, sig_wait, ind_id); + } else { + sig_wait->ind_id = 0; /* Reset as there is no wait for indication */ + rx = NULL; + } + } + } else if (ind_id) { + rx = slsi_mlme_wait_for_ind(sdev, dev, sig_wait, ind_id); + } + + /* The cfm_id and ind_id should ALWAYS be 0 at this point */ + WARN_ON(sig_wait->cfm_id); + WARN_ON(sig_wait->ind_id); + WARN_ON(sig_wait->cfm); + WARN_ON(sig_wait->ind); +clean_exit: + + spin_lock_bh(&sig_wait->send_signal_lock); + + sig_wait->req_id = 0; + sig_wait->cfm_id = 0; + sig_wait->ind_id = 0; + slsi_kfree_skb(sig_wait->cfm); + slsi_kfree_skb(sig_wait->ind); + sig_wait->cfm = NULL; + sig_wait->ind = NULL; + + if (mib_error) + *mib_error = sig_wait->mib_error; + else + slsi_kfree_skb(sig_wait->mib_error); + sig_wait->mib_error = NULL; + spin_unlock_bh(&sig_wait->send_signal_lock); + + SLSI_MUTEX_UNLOCK(sig_wait->mutex); + + slsi_wakeunlock(&sdev->wlan_wl); + return rx; +} + +/** + * NOTE: dev can be NULL! + */ +int slsi_mlme_req(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + int ret = 0; + struct slsi_sig_send *sig_wait = &sdev->sig_wait; + + if (dev) { + struct netdev_vif *ndev_vif = netdev_priv(dev); + + sig_wait = &ndev_vif->sig_wait; + } + spin_lock_bh(&sig_wait->send_signal_lock); + if (++sig_wait->process_id > SLSI_TX_PROCESS_ID_MAX) + sig_wait->process_id = SLSI_TX_PROCESS_ID_MIN; + fapi_set_u16(skb, sender_pid, sig_wait->process_id); + spin_unlock_bh(&sig_wait->send_signal_lock); + + ret = slsi_tx_control(sdev, dev, skb); + if (ret) + slsi_kfree_skb(skb); + return ret; +} + +struct sk_buff *slsi_mlme_req_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 ind_id) +{ + if (WARN_ON(!ind_id)) + goto err; + return slsi_mlme_tx_rx(sdev, dev, skb, 0, NULL, ind_id, NULL); +err: + slsi_kfree_skb(skb); + return NULL; +} + +struct sk_buff *slsi_mlme_req_no_cfm(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + return slsi_mlme_tx_rx(sdev, dev, skb, 0, NULL, 0, NULL); +} + +struct sk_buff *slsi_mlme_req_cfm(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 cfm_id) +{ + if (WARN_ON(!cfm_id)) + goto err; + return slsi_mlme_tx_rx(sdev, dev, skb, cfm_id, NULL, 0, NULL); +err: + slsi_kfree_skb(skb); + return NULL; +} + +/* NOTE: dev can be NULL! */ +static inline struct sk_buff *slsi_mlme_req_cfm_mib(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 cfm_id, struct sk_buff **mib_error) +{ + if (WARN_ON(!cfm_id)) + goto err; + if (WARN_ON(!mib_error)) + goto err; + return slsi_mlme_tx_rx(sdev, dev, skb, cfm_id, mib_error, 0, NULL); +err: + slsi_kfree_skb(skb); + return NULL; +} + +/* NOTE: dev can be NULL! */ +static inline struct sk_buff *slsi_mlme_req_cfm_ind(struct slsi_dev *sdev, + struct net_device *dev, + struct sk_buff *skb, + u16 cfm_id, + u16 ind_id, + bool (*validate_cfm_wait_ind)(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *cfm)) +{ + if (WARN_ON(!cfm_id)) + goto err; + if (WARN_ON(!ind_id)) + goto err; + if (WARN_ON(!validate_cfm_wait_ind)) + goto err; + + return slsi_mlme_tx_rx(sdev, dev, skb, cfm_id, NULL, ind_id, validate_cfm_wait_ind); + +err: + slsi_kfree_skb(skb); + return NULL; +} + +static struct ieee80211_reg_rule *slsi_get_reg_rule(u32 center_freq, struct slsi_802_11d_reg_domain *domain_info) +{ + struct ieee80211_reg_rule *rule; + int i; + + for (i = 0; i < domain_info->regdomain->n_reg_rules; i++) { + rule = &domain_info->regdomain->reg_rules[i]; + + /* Consider 10Mhz on both side from the center frequency */ + if (((center_freq - MHZ_TO_KHZ(10)) >= rule->freq_range.start_freq_khz) && + ((center_freq + MHZ_TO_KHZ(10)) <= rule->freq_range.end_freq_khz)) + return rule; + } + + return NULL; +} + +u16 slsi_compute_chann_info(struct slsi_dev *sdev, u16 width, u16 center_freq0, u16 channel_freq) +{ + u16 chann_info; + u16 prim_chan_pos = 0; + + SLSI_DBG3(sdev, SLSI_MLME, "compute channel info\n"); + switch (width) { + case NL80211_CHAN_WIDTH_20: + chann_info = 20; + break; + case NL80211_CHAN_WIDTH_40: + chann_info = 40; + /* Check HT Minus */ + if (center_freq0 < channel_freq) + chann_info |= SLSI_CHANN_INFO_HT_SCB; + break; + case NL80211_CHAN_WIDTH_80: + /* F = { F1-30, ... F1+30 } => { 0x0000, ... 0x0300} */ + prim_chan_pos = ((30 + channel_freq - center_freq0) / 20); + if (prim_chan_pos > 3) { + SLSI_ERR(sdev, "Invalid center_freq0 in chandef : %u, primary channel = %u," + "primary chan pos calculated = %d\n", center_freq0, channel_freq, prim_chan_pos); + prim_chan_pos = 0; + } + prim_chan_pos = 0xFFFF & (prim_chan_pos << 8); + chann_info = 80 | prim_chan_pos; + break; + default: + SLSI_WARN(sdev, "Invalid chandef.width(0x%x)\n", width); + chann_info = 0; + break; + } + + SLSI_DBG3(sdev, SLSI_MLME, "channel_width:%u, chann_info:0x%x\n", width, chann_info); + return chann_info; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +u16 slsi_get_chann_info(struct slsi_dev *sdev, struct cfg80211_chan_def *chandef) +{ + u16 chann_info = 0; + + SLSI_UNUSED_PARAMETER(sdev); + + if (chandef->width == NL80211_CHAN_WIDTH_20 || chandef->width == NL80211_CHAN_WIDTH_20_NOHT) { + chann_info = 20; + SLSI_DBG3(sdev, SLSI_MLME, "channel_width:%u, chann_info:0x%x\n", chandef->width, chann_info); + } else if (chandef->chan) { + chann_info = slsi_compute_chann_info(sdev, chandef->width, chandef->center_freq1, + chandef->chan->center_freq); + } + return chann_info; +} + +int slsi_check_channelization(struct slsi_dev *sdev, struct cfg80211_chan_def *chandef, + int wifi_sharing_channel_switched) +{ + u8 width; + struct ieee80211_reg_rule *rule = NULL; + struct ieee80211_channel *channel = NULL; + u32 ref_flags; + + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20: + case NL80211_CHAN_WIDTH_20_NOHT: + width = 20; + break; + case NL80211_CHAN_WIDTH_40: + width = 40; + break; + case NL80211_CHAN_WIDTH_80: + width = 80; + break; + default: + SLSI_ERR(sdev, "Invalid chandef.width(0x%x)\n", chandef->width); + return -EINVAL; + } + + channel = ieee80211_get_channel(sdev->wiphy, chandef->chan->center_freq); + if (!channel) { + SLSI_ERR(sdev, "Invalid channel %d used to start AP. Channel not found\n", chandef->chan->center_freq); + return -EINVAL; + } + + if (wifi_sharing_channel_switched == 1) { + ref_flags = IEEE80211_CHAN_DISABLED +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 10, 13) + | IEEE80211_CHAN_PASSIVE_SCAN +#endif + ; + } else { + ref_flags = IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_RADAR +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 10, 13) + | IEEE80211_CHAN_PASSIVE_SCAN +#endif + ; + } + + if (channel->flags & ref_flags) { + SLSI_ERR(sdev, "Invalid channel %d used to start AP\n", chandef->chan->center_freq); + return -EINVAL; + } + rule = slsi_get_reg_rule(MHZ_TO_KHZ(chandef->center_freq1), &sdev->device_config.domain_info); + if (!rule) { + SLSI_ERR(sdev, "Invalid channel %d used to start AP. No reg rule found for this channel\n", chandef->chan->center_freq); + return -EINVAL; + } + + if (MHZ_TO_KHZ(width) <= rule->freq_range.max_bandwidth_khz) { + u32 width_boundary1, width_boundary2; + + width_boundary1 = MHZ_TO_KHZ(chandef->center_freq1 - width / 2); + width_boundary2 = MHZ_TO_KHZ(chandef->center_freq1 + width / 2); + if ((width_boundary1 >= rule->freq_range.start_freq_khz) && (width_boundary2 <= rule->freq_range.end_freq_khz)) + return 0; + SLSI_ERR(sdev, "Invalid channel %d used to start AP. Channel not within frequency range of the reg rule\n", chandef->chan->center_freq); + return -EINVAL; + } + return -EINVAL; +} + +#else +u16 slsi_get_chann_info(struct slsi_dev *sdev, enum nl80211_channel_type channel_type) +{ + u16 chann_info; + + SLSI_UNUSED_PARAMETER(sdev); + + /* Channel Info + * bits 0 ~ 7 : Channel Width (5, 10, 20, 40) + * bit 8 : Set to 1 if primary channel is greater than secondary channel (HT Minus) + */ + switch (channel_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + chann_info = 20; + break; + case NL80211_CHAN_HT40MINUS: + chann_info = 40 | SLSI_CHANN_INFO_HT_SCB; + break; + case NL80211_CHAN_HT40PLUS: + chann_info = 40; + break; + default: + SLSI_WARN(sdev, "Unknown channel_type: %d\n", channel_type); + chann_info = 0; + break; + } + + SLSI_DBG3(sdev, SLSI_MLME, "channel_type:%d, chann_info:0x%x\n", channel_type, chann_info); + return chann_info; +} + +int slsi_check_channelization(struct slsi_dev *sdev, enum nl80211_channel_type channel_type) +{ + return 0; +} + +#endif + +/* Called in the case of MIB SET errors. + * Decode and print a MIB buffer to the log for debug purposes. + */ +static void mib_buffer_dump_to_log(struct slsi_dev *sdev, u8 *mib_buffer, unsigned int mib_buffer_len) +{ + size_t mib_decode_result; + size_t offset = 0; + struct slsi_mib_entry decoded_mib_value; + struct slsi_mib_data mibdata; + int error_out_len = mib_buffer_len * 3; + int error_out_pos = 0; + char *error_out; + + SLSI_UNUSED_PARAMETER(sdev); + + FUNC_ENTER(sdev); + SLSI_ERR(sdev, "MIB buffer length: %u. MIB Error (decoded):", mib_buffer_len); + + if (!mib_buffer) { + SLSI_ERR(sdev, "MIB buffer pointer is NULL - can not decode MIB keys\n"); + return; + } + error_out = kmalloc(error_out_len, GFP_KERNEL); + + while (offset < mib_buffer_len) { + error_out_pos = 0; + mibdata.data = &mib_buffer[offset]; + mibdata.dataLength = mib_buffer_len - offset; + + mib_decode_result = slsi_mib_decode(&mibdata, &decoded_mib_value); + if (!mib_decode_result) { + SLSI_ERR_HEX(sdev, mibdata.data, mibdata.dataLength, "slsi_mib_decode() Failed to Decode:\n"); + break; + } + + offset += mib_decode_result; + /* Time for some eye candy - output the decoded MIB key at error level in the log */ + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "%d", (int)(decoded_mib_value.psid)); + if (decoded_mib_value.index[0]) { + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, ".%d", (int)(decoded_mib_value.index[0])); + if (decoded_mib_value.index[1]) + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, ".%d", (int)(decoded_mib_value.index[1])); + } + + switch (decoded_mib_value.value.type) { + case SLSI_MIB_TYPE_BOOL: + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "=%s\n", decoded_mib_value.value.u.boolValue ? "TRUE" : "FALSE"); + break; + case SLSI_MIB_TYPE_UINT: + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "=%d\n", (int)decoded_mib_value.value.u.uintValue); + break; + case SLSI_MIB_TYPE_INT: + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "=%d\n", (int)decoded_mib_value.value.u.intValue); + break; + case SLSI_MIB_TYPE_OCTET: + { + u32 i; + + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "=["); + for (i = 0; i < decoded_mib_value.value.u.octetValue.dataLength; i++) + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "%.2X", (int)decoded_mib_value.value.u.octetValue.data[i]); + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "]\n"); + break; + } + default: + error_out_pos += snprintf(error_out + error_out_pos, error_out_len - error_out_pos, "=Can not decode MIB key type\n"); + break; + } + + SLSI_INFO_NODEV("%s", error_out); + } + kfree(error_out); + FUNC_EXIT(sdev); +} + +int slsi_mlme_set_ip_address(struct slsi_dev *sdev, struct net_device *dev) +{ + struct sk_buff *req; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *cfm; + int r = 0; + u32 ipaddr; + u8 multicast_add[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_SET_IP_ADDRESS.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + req = fapi_alloc(mlme_set_ip_address_req, MLME_SET_IP_ADDRESS_REQ, ndev_vif->ifnum, sizeof(ndev_vif->ipaddress)); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_set_ip_address_req.ip_version, 4); + fapi_set_memcpy(req, u.mlme_set_ip_address_req.multicast_address, multicast_add); + + ipaddr = htonl(be32_to_cpu(ndev_vif->ipaddress)); + fapi_append_data(req, (const u8 *)(&ipaddr), sizeof(ipaddr)); + + SLSI_DBG2(sdev, SLSI_MLME, "slsi_mlme_set_ip_address(vif: %d, IP: %pI4)\n", ndev_vif->ifnum, &ndev_vif->ipaddress); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_IP_ADDRESS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_ip_address_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_ip_address_cfm(result:0x%04x) ERROR\n", fapi_get_u16(cfm, u.mlme_set_ip_address_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + +int slsi_mlme_set_ipv6_address(struct slsi_dev *sdev, struct net_device *dev) +{ + struct sk_buff *req; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *cfm; + int r = 0; + u8 solicited_node_addr[ETH_ALEN] = { 0x33, 0x33, 0xff, 0x00, 0x00, 0x00 }; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_SET_IP_ADDRESS.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + req = fapi_alloc(mlme_set_ip_address_req, MLME_SET_IP_ADDRESS_REQ, ndev_vif->ifnum, 16); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_set_ip_address_req.ip_version, 6); + + if (ndev_vif->sta.nd_offload_enabled == 1) { + slsi_spinlock_lock(&ndev_vif->ipv6addr_lock); + memcpy(&solicited_node_addr[3], &ndev_vif->ipv6address.s6_addr[13], 3); + slsi_spinlock_unlock(&ndev_vif->ipv6addr_lock); + + fapi_set_memcpy(req, u.mlme_set_ip_address_req.multicast_address, solicited_node_addr); + fapi_append_data(req, ndev_vif->ipv6address.s6_addr, 16); + SLSI_DBG2(sdev, SLSI_MLME, "mlme_set_ip_address_req(vif: %d, IP: %pI6)\n", ndev_vif->ifnum, + &ndev_vif->ipv6address); + } else { + u8 node_addr_nd_disable[16]; + + memset(&node_addr_nd_disable, 0, sizeof(node_addr_nd_disable)); + fapi_append_data(req, node_addr_nd_disable, 16); + SLSI_DBG2(sdev, SLSI_MLME, "mlme_set_ip_address_req(vif: %d, IP-setting ip address to all zeros)\n", + ndev_vif->ifnum); + } + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_IP_ADDRESS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_ip_address_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_ip_address_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_ip_address_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} +#endif + +int slsi_mlme_set(struct slsi_dev *sdev, struct net_device *dev, u8 *mib, int mib_len) +{ + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u16 ifnum = 0; + + if (dev) { + struct netdev_vif *ndev_vif = netdev_priv(dev); + + ifnum = ndev_vif->ifnum; + } + + req = fapi_alloc(mlme_set_req, MLME_SET_REQ, ifnum, mib_len); + if (!req) + return -ENOMEM; + + fapi_append_data(req, mib, mib_len); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_datalen(cfm)) { + mib_buffer_dump_to_log(sdev, fapi_get_data(cfm), fapi_get_datalen(cfm)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + + return r; +} + +int slsi_mlme_get(struct slsi_dev *sdev, struct net_device *dev, u8 *mib, int mib_len, u8 *resp, + int resp_buf_len, int *resp_len) +{ + struct sk_buff *req; + struct sk_buff *err = NULL; + struct sk_buff *cfm; + int r = 0; + u16 ifnum = 0; + + *resp_len = 0; + + if (dev) { + struct netdev_vif *ndev_vif = netdev_priv(dev); + + ifnum = ndev_vif->ifnum; + } + req = fapi_alloc(mlme_get_req, MLME_GET_REQ, ifnum, mib_len); + if (!req) + return -ENOMEM; + fapi_append_data(req, mib, mib_len); + + cfm = slsi_mlme_req_cfm_mib(sdev, dev, req, MLME_GET_CFM, &err); + if (!cfm) + return -EIO; + + if (err) { + SLSI_DBG1(sdev, SLSI_MLME, "ERROR: mlme_get_cfm with mib error\n"); + mib_buffer_dump_to_log(sdev, fapi_get_data(err), fapi_get_datalen(err)); + LOG_CONDITIONALLY(fapi_get_datalen(cfm) > resp_buf_len, + SLSI_ERR(sdev, "Insufficient resp_buf_len(%d). mlme_get_cfm(%d)\n", + resp_buf_len, fapi_get_datalen(cfm))); + r = -EINVAL; + } + + /* if host has requested for multiple PSIDs in same request, we can get a + * combination of error and success + */ + if (fapi_get_datalen(cfm) <= resp_buf_len) { + *resp_len = fapi_get_datalen(cfm); + memcpy(resp, fapi_get_data(cfm), fapi_get_datalen(cfm)); + r = 0; + } else { + SLSI_WARN(sdev, "Insufficient length (%d) to read MIB values, expected =%d\n", resp_buf_len, fapi_get_datalen(cfm)); + r = -EINVAL; + } + + slsi_kfree_skb(err); + slsi_kfree_skb(cfm); + + return r; +} + +int slsi_mlme_add_vif(struct slsi_dev *sdev, struct net_device *dev, u8 *interface_address, u8 *device_address) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0, i; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_ADD_VIF.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + /* reset host stats */ + for (i = 0; i < SLSI_LLS_AC_MAX; i++) { + ndev_vif->tx_no_ack[i] = 0; + ndev_vif->tx_packets[i] = 0; + ndev_vif->rx_packets[i] = 0; + } + req = fapi_alloc(mlme_add_vif_req, MLME_ADD_VIF_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + fapi_set_u16(req, u.mlme_add_vif_req.virtual_interface_type, ndev_vif->vif_type); + fapi_set_memcpy(req, u.mlme_add_vif_req.interface_address, interface_address); + fapi_set_memcpy(req, u.mlme_add_vif_req.device_address, device_address); + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_add_vif_req(vif:%d)\n", ndev_vif->ifnum); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_ADD_VIF_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_add_vif_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_add_vif_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_add_vif_cfm.result_code)); + r = -EINVAL; + } + + /* By default firmware vif will be in active mode */ + ndev_vif->power_mode = FAPI_POWERMANAGEMENTMODE_ACTIVE_MODE; + + slsi_kfree_skb(cfm); + return r; +} + +void slsi_mlme_del_vif(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_DEL_VIF.request\n"); + return; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_del_vif_req(vif:%d)\n", ndev_vif->ifnum); + req = fapi_alloc(mlme_del_vif_req, MLME_DEL_VIF_REQ, ndev_vif->ifnum, 0); + if (!req) + return; + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_DEL_VIF_CFM); + if (!cfm) + return; + + if (fapi_get_u16(cfm, u.mlme_del_vif_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) + SLSI_NET_ERR(dev, "mlme_del_vif_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_del_vif_cfm.result_code)); + + if (((ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) || (ndev_vif->iftype == NL80211_IFTYPE_STATION)) && + (ndev_vif->delete_probe_req_ies)) { + kfree(ndev_vif->probe_req_ies); + ndev_vif->probe_req_ies = NULL; + ndev_vif->probe_req_ie_len = 0; + ndev_vif->delete_probe_req_ies = false; + } + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif)) + ndev_vif->drv_in_p2p_procedure = false; + + slsi_kfree_skb(cfm); +} + +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +int slsi_mlme_set_forward_beacon(struct slsi_dev *sdev, struct net_device *dev, int action) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "wlanlite does not support mlme_forward_bacon_req\n"); + return -EOPNOTSUPP; + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_forward_beacon_req(action = %s(%d))\n", action ? "start" : "stop", action); + + req = fapi_alloc(mlme_forward_beacon_req, MLME_FORWARD_BEACON_REQ, ndev_vif->ifnum, 0); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc for mlme_forward_beacon_req is failed\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_forward_beacon_req.wips_action, action); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_FORWARD_BEACON_CFM); + if (!cfm) { + SLSI_NET_ERR(dev, "receiving mlme_forward_beacon_cfm is failed\n"); + return -EIO; + } + + if (fapi_get_u16(cfm, u.mlme_forward_beacon_cfm.result_code) != FAPI_RESULTCODE_HOST_REQUEST_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_forward_beacon_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_forward_beacon_cfm.result_code)); + return -EINVAL; + } + + ndev_vif->is_wips_running = (action ? true : false); + + slsi_kfree_skb(cfm); + return 0; +} +#endif + +int slsi_mlme_set_channel(struct slsi_dev *sdev, struct net_device *dev, struct ieee80211_channel *chan, u16 duration, u16 interval, u16 count) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "wlanlite does not support MLME_SET_CHANNEL.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_set_channel_req(freq:%u, duration:%u, interval:%u, count:%u)\n", chan->center_freq, duration, interval, count); + + req = fapi_alloc(mlme_set_channel_req, MLME_SET_CHANNEL_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_set_channel_req.availability_duration, duration); + fapi_set_u16(req, u.mlme_set_channel_req.availability_interval, interval); + fapi_set_u16(req, u.mlme_set_channel_req.count, count); + fapi_set_u16(req, u.mlme_set_channel_req.channel_frequency, SLSI_FREQ_HOST_TO_FW(chan->center_freq)); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_CHANNEL_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_channel_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_channel_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_channel_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_unset_channel_req(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + SLSI_NET_DBG3(dev, SLSI_MLME, "slsi_mlme_unset_channel_req\n"); + + req = fapi_alloc(mlme_unset_channel_req, MLME_UNSET_CHANNEL_REQ, ndev_vif->ifnum, 0); + + if (!req) + return -ENOMEM; + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_UNSET_CHANNEL_CFM); + + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_unset_channel_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_unset_channel_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_unset_channel_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +void slsi_ap_obss_scan_done_ind(struct net_device *dev, struct netdev_vif *ndev_vif) +{ + struct sk_buff *scan_res; + u16 scan_id = SLSI_SCAN_HW_ID; + + SLSI_UNUSED_PARAMETER(dev); + + SLSI_NET_DBG1(dev, SLSI_MLME, "Scan before AP start completed\n"); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)); + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + + scan_res = slsi_dequeue_cached_scan_result(&ndev_vif->scan[scan_id], NULL); + while (scan_res) { + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(scan_res); + size_t mgmt_len = fapi_get_mgmtlen(scan_res); + size_t ie_len = mgmt_len - offsetof(struct ieee80211_mgmt, u.beacon.variable); /* ieee80211_mgmt structure is similar for Probe Response and Beacons */ + + SLSI_NET_DBG4(dev, SLSI_MLME, "OBSS scan result (scan_id:%d, %pM, freq:%d, rssi:%d, ie_len = %zu)\n", + fapi_get_u16(scan_res, u.mlme_scan_ind.scan_id), + fapi_get_mgmt(scan_res)->bssid, + fapi_get_u16(scan_res, u.mlme_scan_ind.channel_frequency) / 2, + fapi_get_s16(scan_res, u.mlme_scan_ind.rssi), + ie_len); + + if (!cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, mgmt->u.beacon.variable, ie_len)) { + SLSI_NET_DBG1(dev, SLSI_MLME, "Non HT BSS detected on primary channel\n"); + ndev_vif->ap.non_ht_bss_present = true; + } + + slsi_kfree_skb(scan_res); + scan_res = slsi_dequeue_cached_scan_result(&ndev_vif->scan[scan_id], NULL); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); +} + +/* Null check for cfm done in caller function */ +static bool slsi_scan_cfm_validate(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *cfm) +{ + bool r = true; + + if (fapi_get_u16(cfm, u.mlme_add_scan_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_ERR_NODEV("mlme_add_scan_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_add_scan_cfm.result_code)); + r = false; + } + + slsi_kfree_skb(cfm); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE +int slsi_mlme_append_gscan_channel_list(struct slsi_dev *sdev, + struct net_device *dev, + struct sk_buff *req, + struct slsi_nl_bucket_param *nl_bucket) +{ + u16 channel_freq; + u8 i; + u8 *p; + const u8 channels_list_ie_header[] = { + 0xDD, /* Element ID: Vendor Specific */ + 0x05, /* Length: actual length will be updated later */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x01, /* OUI Type: Scan parameters */ + 0x02 /* OUI Subtype: channel list */ + }; + u8 *channels_list_ie = fapi_append_data(req, channels_list_ie_header, sizeof(channels_list_ie_header)); + + if (!channels_list_ie) { + SLSI_WARN(sdev, "channel list IE append failed\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + + if (nl_bucket->band == WIFI_BAND_UNSPECIFIED) + /* channel list is added only if band is UNSPECIFIED */ + for (i = 0; i < nl_bucket->num_channels; i++) { + p = fapi_append_data(req, NULL, SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + if (!p) { + SLSI_ERR(sdev, "chan desc[%d] append failed\n", i); + slsi_kfree_skb(req); + return -EINVAL; + } + channel_freq = SLSI_FREQ_HOST_TO_FW(nl_bucket->channels[i].channel); + channel_freq = cpu_to_le16(channel_freq); + memcpy(p, &channel_freq, sizeof(channel_freq)); + p[2] = FAPI_SCANPOLICY_ANY_RA; + channels_list_ie[1] += SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE; /* Length */ + } + else { + p = fapi_append_data(req, NULL, SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + if (!p) { + SLSI_ERR(sdev, "chan desc(band specific)append failed\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + /* Channel frequency set to 0 for all channels allowed by the corresponding regulatory domain and scan policy */ + channel_freq = 0; + memcpy(p, &channel_freq, sizeof(channel_freq)); + p[2] = slsi_gscan_get_scan_policy(nl_bucket->band); + channels_list_ie[1] += SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE; + } + + return 0; +} +#endif + +static int slsi_mlme_append_channel_list(struct slsi_dev *sdev, + struct net_device *dev, + struct sk_buff *req, + u32 num_channels, + struct ieee80211_channel *channels[], + u16 scan_type, + bool passive_scan) +{ + int chann; + u16 freq_fw_unit; + u8 i; + int n_valid_channels = 0; + u8 *p; + + const u8 channels_list_ie_header[] = { + 0xDD, /* Element ID: vendor specific */ + 0x05, /* Length: actual length will be updated later */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x01, /* OUI Type: scan parameters */ + 0x02 /* OUI Subtype: channel list */ + }; + + u8 *channels_list_ie = fapi_append_data(req, channels_list_ie_header, sizeof(channels_list_ie_header)); + + if (!channels_list_ie) { + SLSI_WARN(sdev, "channel list IE append failed\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + + /* For P2P Full Scan, Setting Channel Frequency = 0x0000, Scan Policy = 2.4GHz, 5GHz and Non-Dfs. */ + if (scan_type == FAPI_SCANTYPE_P2P_SCAN_FULL) { + p = fapi_append_data(req, NULL, SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + if (!p) { + SLSI_WARN(sdev, "scan channel descriptor append failed\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + p[0] = 0; + p[1] = 0; + p[2] = FAPI_SCANPOLICY_2_4GHZ | FAPI_SCANPOLICY_5GHZ | FAPI_SCANPOLICY_NON_DFS; + channels_list_ie[1] += SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE; + return 0; + } + + for (i = 0; i < num_channels; i++) { + chann = channels[i]->hw_value & 0xFF; + + if (sdev->device_config.supported_band) { + if (channels[i]->band == NL80211_BAND_2GHZ && sdev->device_config.supported_band != SLSI_FREQ_BAND_2GHZ) + continue; + if (channels[i]->band == NL80211_BAND_5GHZ && sdev->device_config.supported_band != SLSI_FREQ_BAND_5GHZ) + continue; + } + + n_valid_channels++; + p = fapi_append_data(req, NULL, SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + if (!p) { + SLSI_WARN(sdev, "scan channel descriptor append failed\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + freq_fw_unit = 2 * ieee80211_channel_to_frequency(chann, (chann <= 14) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); + freq_fw_unit = cpu_to_le16(freq_fw_unit); + memcpy(p, &freq_fw_unit, sizeof(freq_fw_unit)); + + if (passive_scan && (scan_type != FAPI_SCANTYPE_AP_AUTO_CHANNEL_SELECTION)) + p[2] = FAPI_SCANPOLICY_PASSIVE; + else + p[2] = 0; + + channels_list_ie[1] += SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE; + } + if (n_valid_channels == 0) { + SLSI_NET_ERR(dev, "no valid channels to Scan\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + return 0; +} + +static inline int slsi_set_scan_params( + struct net_device *dev, + u16 scan_id, + u16 scan_type, + u16 report_mode, + int num_ssids, + struct cfg80211_ssid *ssids, + struct sk_buff *req) +{ + u8 *p = NULL; + u8 i; + struct cfg80211_ssid *pssid = ssids; +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct slsi_dev *sdev = netdev_vif->sdev; +#endif + + fapi_set_u16(req, u.mlme_add_scan_req.scan_id, scan_id); + fapi_set_u16(req, u.mlme_add_scan_req.scan_type, scan_type); + fapi_set_u16(req, u.mlme_add_scan_req.report_mode_bitmap, report_mode); + + +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + if (sdev->scan_addr_set) + fapi_set_memcpy(req, u.mlme_add_scan_req.device_address, sdev->scan_mac_addr); + else +#endif + fapi_set_memcpy(req, u.mlme_add_scan_req.device_address, dev->dev_addr); + + for (i = 0; i < num_ssids; i++, pssid++) { + p = fapi_append_data(req, NULL, 2 + pssid->ssid_len); + if (!p) { + slsi_kfree_skb(req); + SLSI_NET_WARN(dev, "fail to append SSID element to scan request\n"); + return -EINVAL; + } + + *p++ = WLAN_EID_SSID; + *p++ = pssid->ssid_len; + + if (pssid->ssid_len) + memcpy(p, pssid->ssid, pssid->ssid_len); + } + return 0; +} + +#define SLSI_MAX_SSID_DESC_IN_SSID_FILTER_ELEM 7 +int slsi_mlme_add_sched_scan(struct slsi_dev *sdev, + struct net_device *dev, + struct cfg80211_sched_scan_request *request, + const u8 *ies, + u16 ies_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + size_t alloc_data_size = 0; + u32 i, j; + u32 num_ssid_filter_elements = 0; + + /* Scan Timing IE: default values */ + u8 scan_timing_ie[] = { + 0xdd, /* Element ID: Vendor Specific */ + 0x11, /* Length */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x01, /* OUI Type: Scan parameters */ + 0x01, /* OUI Subtype: Scan timing */ + 0x00, 0x00, 0x00, 0x00, /* Min_Period: filled later in the function */ + 0x00, 0x00, 0x00, 0x00, /* Max_Period: filled later in the function */ + 0x01, /* Exponent */ + 0x01, /* Step count */ + 0x00, 0x01 /* Skip first period: true for scheduled scans*/ + }; + + u8 ssid_filter_ie_hdr[] = { + 0xdd, /* Element ID: Vendor Specific */ + 0x05, /* Length */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x01, /* OUI Type: Scan parameters */ + 0x04 /* OUI Subtype: SSID Filter */ + }; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_WARN(dev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + if (WARN_ON(!(dev->dev_addr))) + return -EINVAL; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)); + + alloc_data_size += sizeof(scan_timing_ie) + ies_len + SLSI_SCAN_PRIVATE_IE_CHANNEL_LIST_HEADER_LEN + + (request->n_channels * SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + + for (i = 0; i < request->n_ssids; i++) { + /* 2 bytes for SSID EID and length field + variable length SSID */ + alloc_data_size += (2 + request->ssids[i].ssid_len); + } + + if (request->n_match_sets) { + num_ssid_filter_elements = (request->n_match_sets / SLSI_MAX_SSID_DESC_IN_SSID_FILTER_ELEM) + 1; + /* EID(1) + len(1) + oui(3) + type/subtype(2) + 7 ssid descriptors(7 * 33) */ + alloc_data_size += 238 * num_ssid_filter_elements; + } + + req = fapi_alloc(mlme_add_scan_req, MLME_ADD_SCAN_REQ, 0, alloc_data_size); + if (!req) + return -ENOMEM; + + r = slsi_set_scan_params(dev, (ndev_vif->ifnum << 8 | SLSI_SCAN_SCHED_ID), + FAPI_SCANTYPE_SCHEDULED_SCAN, + FAPI_REPORTMODE_REAL_TIME, + request->n_ssids, + request->ssids, + req); + if (r) + return r; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)) + SLSI_U32_TO_BUFF_LE(request->scan_plans->interval * 1000 * 1000, &scan_timing_ie[7]); + SLSI_U32_TO_BUFF_LE(request->scan_plans->interval * 1000 * 1000, &scan_timing_ie[11]); +#else + SLSI_U32_TO_BUFF_LE(request->interval * 1000, &scan_timing_ie[7]); + SLSI_U32_TO_BUFF_LE(request->interval * 1000, &scan_timing_ie[11]); +#endif + + fapi_append_data(req, scan_timing_ie, sizeof(scan_timing_ie)); + fapi_append_data(req, ies, ies_len); + + if (request->n_match_sets) { + struct cfg80211_match_set *match_sets = request->match_sets; + u8 *ssid_filter_ie; + + for (j = 0; j < num_ssid_filter_elements; j++) { + ssid_filter_ie = fapi_append_data(req, ssid_filter_ie_hdr, sizeof(ssid_filter_ie_hdr)); + if (!ssid_filter_ie) { + slsi_kfree_skb(req); + SLSI_ERR(sdev, "ssid_filter_ie append failed\n"); + return -EIO; + } + for (i = 0; i < SLSI_MAX_SSID_DESC_IN_SSID_FILTER_ELEM; i++, match_sets++) { + if ((j * SLSI_MAX_SSID_DESC_IN_SSID_FILTER_ELEM) + i >= request->n_match_sets) + break; + SLSI_NET_DBG2(dev, SLSI_MLME, "SSID: %.*s", + match_sets->ssid.ssid_len, match_sets->ssid.ssid); + ssid_filter_ie[1] += (1 + match_sets->ssid.ssid_len); + fapi_append_data(req, &match_sets->ssid.ssid_len, 1); + fapi_append_data(req, match_sets->ssid.ssid, match_sets->ssid.ssid_len); + } + } + } + + if (request->n_channels) { + r = slsi_mlme_append_channel_list(sdev, dev, req, request->n_channels, request->channels, + FAPI_SCANTYPE_SCHEDULED_SCAN, request->n_ssids == 0); + if (r) + return r; + } + + rx = slsi_mlme_req_cfm(sdev, NULL, req, MLME_ADD_SCAN_CFM); + if (!rx) + return -EIO; + + if (fapi_get_u16(rx, u.mlme_add_scan_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_add_scan_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_add_scan_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(rx); + return r; +} + +int slsi_mlme_add_scan( + struct slsi_dev *sdev, + struct net_device *dev, + u16 scan_type, + u16 report_mode, + u32 n_ssids, + struct cfg80211_ssid *ssids, + u32 n_channels, + struct ieee80211_channel *channels[], + void *gscan, + const u8 *ies, + u16 ies_len, + bool wait_for_ind) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + size_t alloc_data_size = 0; + u32 i; + + /* Scan Timing IE: default values */ + u8 scan_timing_ie[] = { + 0xdd, /* Element ID: Vendor Specific */ + 0x11, /* Length */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x01, /* OUI Type: Scan parameters */ + 0x01, /* OUI Subtype: Scan timing */ + 0x00, 0x00, 0x00, 0x00, /* Min_Period: filled later in the function */ + 0x00, 0x00, 0x00, 0x00, /* Max_Period: filled later in the function */ + 0x00, /* Exponent */ + 0x00, /* Step count */ + 0x00, 0x00 /* Skip first period: false */ + }; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_WARN(dev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + if (WARN_ON(!(dev->dev_addr))) + return -EINVAL; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)); + SLSI_INFO(sdev, "scan started for id:0x%x, n_channels:%d, n_ssids:%d, scan_type:%d\n", + (ndev_vif->ifnum << 8 | SLSI_SCAN_HW_ID), n_channels, n_ssids, scan_type); + + alloc_data_size += sizeof(scan_timing_ie) + + ies_len + + (SLSI_SCAN_PRIVATE_IE_CHANNEL_LIST_HEADER_LEN + (n_channels * SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE)); + + for (i = 0; i < n_ssids; i++) + alloc_data_size += 2 + ssids[i].ssid_len; /* 2: SSID EID + len */ + + req = fapi_alloc(mlme_add_scan_req, MLME_ADD_SCAN_REQ, 0, alloc_data_size); + if (!req) + return -ENOMEM; + + if (!gscan) { + r = slsi_set_scan_params( + dev, + (ndev_vif->ifnum << 8 | SLSI_SCAN_HW_ID), + scan_type, + report_mode, + n_ssids, + ssids, + req); + if (r) + return r; + + fapi_append_data(req, scan_timing_ie, sizeof(scan_timing_ie)); + fapi_append_data(req, ies, ies_len); + + if (n_channels) { + r = slsi_mlme_append_channel_list(sdev, dev, req, n_channels, channels, scan_type, + n_ssids == 0); + if (r) + return r; + } + } +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + else { + struct slsi_gscan_param *gscan_param = (struct slsi_gscan_param *)gscan; + + r = slsi_set_scan_params( + dev, + gscan_param->bucket->scan_id, + scan_type, + report_mode, + n_ssids, + ssids, + req); + if (r) + return r; + + SLSI_U32_TO_BUFF_LE((gscan_param->nl_bucket->period * 1000), &scan_timing_ie[7]); + if (gscan_param->nl_bucket->exponent) { + SLSI_U32_TO_BUFF_LE((gscan_param->nl_bucket->max_period * 1000), &scan_timing_ie[11]); + scan_timing_ie[15] = (u8)gscan_param->nl_bucket->exponent; + scan_timing_ie[16] = (u8)gscan_param->nl_bucket->step_count; + } + fapi_append_data(req, scan_timing_ie, sizeof(scan_timing_ie)); + + r = slsi_mlme_append_gscan_channel_list(sdev, dev, req, gscan_param->nl_bucket); + if (r) + return r; + } +#endif + if (wait_for_ind) { + /* Use the Global sig_wait not the Interface specific for Scan Req */ + rx = slsi_mlme_req_cfm_ind(sdev, NULL, req, MLME_ADD_SCAN_CFM, MLME_SCAN_DONE_IND, slsi_scan_cfm_validate); + if (!rx) + return -EIO; + SLSI_NET_DBG3(dev, SLSI_MLME, "mlme_scan_done_ind()\n"); + + /* slsi_mlme_add_scan is a generic definition for multiple handlers + * Any added functionality, if not generic, should not be defined here. + * It should be a part of calling function. + */ + } else { + /* Use the Global sig_wait not the Interface specific for Scan Req */ + rx = slsi_mlme_req_cfm(sdev, NULL, req, MLME_ADD_SCAN_CFM); + if (!rx) + return -EIO; + if (fapi_get_u16(rx, u.mlme_add_scan_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_add_scan_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_add_scan_cfm.result_code)); + r = -EINVAL; + } + } + slsi_kfree_skb(rx); + return r; +} + +int slsi_mlme_del_scan(struct slsi_dev *sdev, struct net_device *dev, u16 scan_id, bool scan_timed_out) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_WARN(dev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_del_scan_req(scan_id:%d)\n", scan_id); + + if ((scan_id & 0xFF) == SLSI_SCAN_HW_ID && ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req && !scan_timed_out) + cancel_delayed_work(&ndev_vif->scan_timeout_work); + + req = fapi_alloc(mlme_del_scan_req, MLME_DEL_SCAN_REQ, 0, 0); + if (!req) + return -ENOMEM; + fapi_set_u16(req, u.mlme_del_scan_req.scan_id, scan_id); + + /* Use the Global sig_wait not the Interface specific for Scan Req */ + cfm = slsi_mlme_req_cfm(sdev, NULL, req, MLME_DEL_SCAN_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_del_scan_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_del_scan_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_del_scan_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +static void slsi_ap_add_ext_capab_ie(struct sk_buff *req, struct netdev_vif *ndev_vif, const u8 *prev_ext) +{ + u8 ext_capa_ie[SLSI_AP_EXT_CAPAB_IE_LEN_MAX]; + int i; + int prev_len = 0; + + ext_capa_ie[0] = WLAN_EID_EXT_CAPABILITY; + ext_capa_ie[1] = SLSI_AP_EXT_CAPAB_IE_LEN_MAX - 1 - 1; + if (prev_ext) { + prev_len = prev_ext[1]; + for (i = 2; i < prev_len + 2; i++) + ext_capa_ie[i] = prev_ext[i]; + } + for (i = prev_len + 2; i < SLSI_AP_EXT_CAPAB_IE_LEN_MAX; i++) + ext_capa_ie[i] = 0x00; + SLSI_DBG3(ndev_vif->sdev, SLSI_MLME, "New Ext capab Added\n"); + /* For VHT, set the Operating Mode Notification field - Bit 62 (8th Octet) */ + ext_capa_ie[9] |= 0x40; + + fapi_append_data(req, &ext_capa_ie[0], SLSI_AP_EXT_CAPAB_IE_LEN_MAX); +} +#endif + +static int slsi_prepare_country_ie(struct slsi_dev *sdev, u16 center_freq, u8 *country_ie, u8 **new_country_ie) +{ + struct ieee80211_supported_band band; + struct ieee80211_reg_rule *rule; + struct ieee80211_channel *channels; + u8 *ie; + u8 offset = 0; + int i; + + /* Select frequency band */ + if (center_freq < 5180) + band = slsi_band_2ghz; + else + band = slsi_band_5ghz; + + /* Allocate memory for the new country IE - EID(1) + Len(1) + CountryString(3) + ChannelInfo (n * 3) */ + ie = kmalloc(5 + (band.n_channels * 3), GFP_KERNEL); + if (!ie) { + SLSI_ERR(sdev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + /* Preapre the new country IE */ + ie[offset++] = country_ie[0]; /* Element IE */ + ie[offset++] = 0; /* IE Length - initialized at the end of this function */ + ie[offset++] = sdev->device_config.domain_info.regdomain->alpha2[0]; /* Country code */ + ie[offset++] = sdev->device_config.domain_info.regdomain->alpha2[1]; /* Country code */ + ie[offset++] = country_ie[4]; /* CountryString: 3rd octet */ + + channels = band.channels; + for (i = 0; i < band.n_channels; i++, channels++) { + /* Get the regulatory rule for the channel */ + rule = slsi_get_reg_rule(MHZ_TO_KHZ(channels->center_freq), &sdev->device_config.domain_info); + if (rule) { + ie[offset++] = channels->hw_value; /* Channel number */ + ie[offset++] = 1; /* Number of channels */ + ie[offset++] = MBM_TO_DBM(rule->power_rule.max_eirp); /* Max TX power */ + } + } + + ie[1] = offset - 2; /* Length of IE */ + *new_country_ie = ie; + + return 0; +} + +int slsi_modify_ies(struct net_device *dev, u8 eid, u8 *ies, int ies_len, u8 ie_index, u8 ie_value) +{ + u8 *ie; + + SLSI_NET_DBG1(dev, SLSI_MLME, "eid: %d, ie_value = 0x%x\n", eid, ie_value); + + ie = (u8 *)cfg80211_find_ie(eid, ies, ies_len); + if (ie) { + switch (eid) { + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_VHT_CAPABILITY: + ie[ie_index] |= ie_value; + break; + case WLAN_EID_DS_PARAMS: + case WLAN_EID_HT_OPERATION: + if (ie_index == 2) + ie[ie_index] = ie_value; + else + ie[ie_index] |= ie_value; + break; + default: + SLSI_NET_WARN(dev, "slsi_modify_ies: IE type mismatch : %d\n", eid); + return false; + } + return true; + } + SLSI_NET_WARN(dev, "slsi_modify_ies: IE not found : %d\n", eid); + return false; +} + +static void slsi_mlme_start_prepare_ies(struct sk_buff *req, struct netdev_vif *ndev_vif, struct cfg80211_ap_settings *settings, const u8 *wpa_ie_pos, const u8 *wmm_ie_pos) +{ + const u8 *wps_ie, *vht_capab_ie, *tail_pos = NULL, *ext_capab_ie; + size_t beacon_ie_len = 0, tail_length = 0; + u8 *country_ie; + const u8 *beacon_tail = settings->beacon.tail; + size_t beacon_tail_len = settings->beacon.tail_len; + + /** + * Channel list of Country IE prepared by hostapd is wrong, so driver needs remove the existing country IE and prepare correct one. + * Hostapd adds country IE at the beginning of the tail, beacon_tail is moved to the next IE to avoid the default county IE. + */ + country_ie = (u8 *)cfg80211_find_ie(WLAN_EID_COUNTRY, beacon_tail, beacon_tail_len); + if (country_ie) { + u8 *new_country_ie = NULL; + + SLSI_DBG3(ndev_vif->sdev, SLSI_MLME, "Country IE found, length = %d", country_ie[1]); + + /* Prepare the new country IE */ + if (slsi_prepare_country_ie(ndev_vif->sdev, ndev_vif->chan->center_freq, country_ie, &new_country_ie) != 0) + SLSI_ERR(ndev_vif->sdev, "Failed to prepare country IE"); + + /* Add the new country IE */ + if (new_country_ie) { + /* new_country_ie[1] ontains the length of IE */ + fapi_append_data(req, new_country_ie, (new_country_ie[1] + 2)); + + /* Free the memory allocated for the new country IE */ + kfree(new_country_ie); + + /* Remove the default country IE from the beacon_tail */ + beacon_tail += (country_ie[1] + 2); + beacon_tail_len -= (country_ie[1] + 2); + } + } + + /* Modify HT IE based on OBSS scan data */ + if (ndev_vif->ap.non_ht_bss_present) { + u8 op_mode = 1; + + SLSI_NET_DBG1(ndev_vif->wdev.netdev, SLSI_MLME, "Modify Operating mode of BSS in HT IE\n"); + slsi_modify_ies(ndev_vif->wdev.netdev, WLAN_EID_HT_OPERATION, (u8 *)settings->beacon.tail, settings->beacon.tail_len, 4, op_mode); + ndev_vif->ap.non_ht_bss_present = false; + } + + /* Vendor IEs are excluded from start_req. Currently WPA IE, WMM IE, WPS IE and P2P IE need to be excluded. + * From hostapd, order of IEs are - WPA, WMM, WPS and P2P + * Of these the WMM, WPS and P2P IE are usually at the end. + * Note: There can be "eid_p2p_manage" and "eid_hs20" after WPS and P2P IE. Both of these are currently not supported. + */ + + /* Exclude WMM or WPS IE */ + if (wmm_ie_pos) /* WMM IE is present. Remove from this position onwards, i.e. copy only till this data. WPS and P2P IE will also get removed. */ + beacon_ie_len = wmm_ie_pos - beacon_tail; + else { + /* WMM IE is not present. Check for WPS IE (and thereby P2P IE) and exclude it */ + wps_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPS, beacon_tail, beacon_tail_len); + if (wps_ie) + beacon_ie_len = wps_ie - beacon_tail; + else + beacon_ie_len = beacon_tail_len; + } + + /* Exclude WPA IE if present */ + if (wpa_ie_pos) { + size_t len_before, len; + + len_before = wpa_ie_pos - beacon_tail; + fapi_append_data(req, beacon_tail, len_before); + + len = len_before + ndev_vif->ap.wpa_ie_len; + + if (beacon_ie_len > len) { /* More IEs to go */ + tail_length = beacon_ie_len - len; + tail_pos = (beacon_tail + len); + } else /* No more IEs, don't add Ext Capab IE as no HT/VHT */ + return; + } else { + tail_length = beacon_ie_len; + tail_pos = beacon_tail; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + /* Add Ext Capab IE only for VHT mode for now */ + if (ndev_vif->chandef->width == NL80211_CHAN_WIDTH_80) { + /* Ext Capab should be before VHT IEs */ + vht_capab_ie = (cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, tail_pos, tail_length)); + ext_capab_ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, tail_pos, tail_length); + while (tail_length > 2) { + if (tail_pos[0] == WLAN_EID_VHT_CAPABILITY) + slsi_ap_add_ext_capab_ie(req, ndev_vif, ext_capab_ie); + else if (tail_pos[0] != WLAN_EID_EXT_CAPABILITY && tail_pos[0] != WLAN_EID_VHT_OPERATION) + fapi_append_data(req, tail_pos, tail_pos[1] + 2); + + tail_length -= tail_pos[1] + 2; + tail_pos += tail_pos[1] + 2; + } + if (!vht_capab_ie) + slsi_ap_add_ext_capab_ie(req, ndev_vif, ext_capab_ie); + } else { + fapi_append_data(req, tail_pos, tail_length); + } +#else + fapi_append_data(req, tail_pos, tail_length); +#endif +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +/*EID + LEN + CAPABILITIES + MCS */ +/* 1+1+4+8 */ +#define SLSI_VHT_CAPABILITIES_IE_LEN 14 + +/* EID + LEN + WIDTH + SEG0 + SEG1 + MCS */ +/* 1+1+1+1+1+2 */ +#define SLSI_VHT_OPERATION_IE_LEN 7 + +static int slsi_prepare_vht_ies(struct net_device *dev, u8 **vht_ie_capab, u8 **vht_ie_operation) +{ + u32 capabs; + u16 mcs; + u8 *p_cap, *p_oper; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + *vht_ie_capab = kmalloc(SLSI_VHT_CAPABILITIES_IE_LEN, GFP_KERNEL); + if (!(*vht_ie_capab)) + return -EINVAL; + *vht_ie_operation = kmalloc(SLSI_VHT_OPERATION_IE_LEN, GFP_KERNEL); + if (!(*vht_ie_operation)) { + kfree(*vht_ie_capab); + return -EINVAL; + } + + p_cap = *vht_ie_capab; + p_oper = *vht_ie_operation; + + *p_cap++ = WLAN_EID_VHT_CAPABILITY; + *p_cap++ = SLSI_VHT_CAPABILITIES_IE_LEN - 1 - 1; + capabs = cpu_to_le32(slsi_vht_cap.cap); + memcpy(p_cap, &capabs, sizeof(capabs)); + p_cap += sizeof(capabs); + memcpy(p_cap, &slsi_vht_cap.vht_mcs, sizeof(slsi_vht_cap.vht_mcs)); + + *p_oper++ = WLAN_EID_VHT_OPERATION; + *p_oper++ = SLSI_VHT_OPERATION_IE_LEN - 1 - 1; + *p_oper++ = IEEE80211_VHT_CHANWIDTH_80MHZ; + *p_oper++ = ieee80211_frequency_to_channel(ndev_vif->chandef->center_freq1); + *p_oper++ = 0; + mcs = cpu_to_le16(0xfffc); + memcpy(p_oper, &mcs, sizeof(mcs)); + + return 0; +} +#endif + +int slsi_mlme_start(struct slsi_dev *sdev, struct net_device *dev, u8 *bssid, struct cfg80211_ap_settings *settings, const u8 *wpa_ie_pos, const u8 *wmm_ie_pos, bool append_vht_ies) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + struct ieee80211_mgmt *mgmt; + int r = 0; + u8 *p; + enum nl80211_auth_type auth_type = settings->auth_type; + u16 beacon_ie_head_len; + u16 chan_info; + u16 fw_freq; + u16 vht_ies_len = 0; + u8 ext_capab_len = 0; + const u8 *recv_vht_capab_ie, *recv_vht_operation_ie; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + u8 *vht_ie_capab, *vht_ie_operation; +#endif + SLSI_UNUSED_PARAMETER(bssid); + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_START.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + mgmt = (struct ieee80211_mgmt *)settings->beacon.head; + beacon_ie_head_len = settings->beacon.head_len - ((u8 *)mgmt->u.beacon.variable - (u8 *)mgmt); + + /* For port enabling, save the privacy bit used in assoc response or beacon */ + ndev_vif->ap.privacy = (mgmt->u.beacon.capab_info & WLAN_CAPABILITY_PRIVACY); + ndev_vif->ap.qos_enabled = (mgmt->u.beacon.capab_info & WLAN_CAPABILITY_QOS); + + switch (auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + case NL80211_AUTHTYPE_SHARED_KEY: + break; + case NL80211_AUTHTYPE_AUTOMATIC: + auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM; + if (settings->privacy && settings->crypto.cipher_group == 0) + auth_type = NL80211_AUTHTYPE_SHARED_KEY; + break; + default: + SLSI_NET_ERR(dev, "Unsupported auth_type: %d\n", auth_type); + return -EOPNOTSUPP; + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_start_req(vif:%u, bssid:%pM, ssid:%.*s, hidden:%d)\n", ndev_vif->ifnum, bssid, (int)settings->ssid_len, settings->ssid, settings->hidden_ssid); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if (append_vht_ies) + vht_ies_len = SLSI_VHT_CAPABILITIES_IE_LEN + SLSI_VHT_OPERATION_IE_LEN; + + recv_vht_capab_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, settings->beacon.tail, + settings->beacon.tail_len); + if (recv_vht_capab_ie) + vht_ies_len -= (recv_vht_capab_ie[1] + 2); + + recv_vht_operation_ie = cfg80211_find_ie(WLAN_EID_VHT_OPERATION, settings->beacon.tail, + settings->beacon.tail_len); + if (recv_vht_operation_ie) + vht_ies_len -= (recv_vht_operation_ie[1] + 2); + + if (ndev_vif->chandef->width == NL80211_CHAN_WIDTH_80) { + /* Ext Capab are not advertised by driver and so the IE would not be sent by hostapd. + * Frame the IE in driver and set the required bit(s). + */ + SLSI_NET_DBG1(dev, SLSI_MLME, "VHT - Ext Capab IE to be included\n"); + ext_capab_len = SLSI_AP_EXT_CAPAB_IE_LEN_MAX; + } +#endif + + if (settings->hidden_ssid == 1) + req = fapi_alloc(mlme_start_req, MLME_START_REQ, ndev_vif->ifnum, settings->ssid_len + beacon_ie_head_len + settings->beacon.tail_len + vht_ies_len + ext_capab_len); + else + req = fapi_alloc(mlme_start_req, MLME_START_REQ, ndev_vif->ifnum, beacon_ie_head_len + settings->beacon.tail_len + vht_ies_len + ext_capab_len); + + if (!req) + return -ENOMEM; + fapi_set_memcpy(req, u.mlme_start_req.bssid, dev->dev_addr); + fapi_set_u16(req, u.mlme_start_req.beacon_period, settings->beacon_interval); + fapi_set_u16(req, u.mlme_start_req.dtim_period, settings->dtim_period); + fapi_set_u16(req, u.mlme_start_req.capability_information, le16_to_cpu(mgmt->u.beacon.capab_info)); + fapi_set_u16(req, u.mlme_start_req.authentication_type, auth_type); + fapi_set_u16(req, u.mlme_start_req.hidden_ssid, settings->hidden_ssid < 3 ? settings->hidden_ssid : NL80211_HIDDEN_SSID_ZERO_LEN); + + fw_freq = ndev_vif->chan->center_freq; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + chan_info = slsi_get_chann_info(sdev, ndev_vif->chandef); +#else + chan_info = slsi_get_chann_info(sdev, ndev_vif->channel_type); +#endif + if ((chan_info & 20) != 20) + fw_freq = slsi_get_center_freq1(sdev, chan_info, fw_freq); + + fapi_set_u16(req, u.mlme_start_req.channel_frequency, (2 * fw_freq)); + fapi_set_u16(req, u.mlme_start_req.channel_information, chan_info); + ndev_vif->ap.channel_freq = fw_freq; + + /* Addition of SSID IE in mlme_start_req for hiddenSSID case */ + if (settings->hidden_ssid != 0) { + p = fapi_append_data(req, NULL, 2 + settings->ssid_len); + if (!p) { + slsi_kfree_skb(req); + return -EINVAL; + } + *p++ = WLAN_EID_SSID; + *p++ = settings->ssid_len; + memcpy(p, settings->ssid, settings->ssid_len); + } + + if (beacon_ie_head_len && settings->hidden_ssid == 0) + fapi_append_data(req, mgmt->u.beacon.variable, beacon_ie_head_len); + else if (beacon_ie_head_len && settings->hidden_ssid == 1) + fapi_append_data(req, mgmt->u.beacon.variable + 2, beacon_ie_head_len - 2); + else if (beacon_ie_head_len && settings->hidden_ssid == 2) + fapi_append_data(req, mgmt->u.beacon.variable + 2 + settings->ssid_len, beacon_ie_head_len - (2 + settings->ssid_len)); + + if (settings->beacon.tail_len) + slsi_mlme_start_prepare_ies(req, ndev_vif, settings, wpa_ie_pos, wmm_ie_pos); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + if ((append_vht_ies) && !slsi_prepare_vht_ies(dev, &vht_ie_capab, &vht_ie_operation)) { + fapi_append_data(req, vht_ie_capab, SLSI_VHT_CAPABILITIES_IE_LEN); + fapi_append_data(req, vht_ie_operation, SLSI_VHT_OPERATION_IE_LEN); + kfree(vht_ie_capab); + kfree(vht_ie_operation); + } +#endif + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_START_CFM); + if (!cfm) + return -EIO; + if (fapi_get_u16(cfm, u.mlme_start_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_start_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_start_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +static const u8 *slsi_mlme_connect_get_sec_ie(struct cfg80211_connect_params *sme, int *sec_ie_len) +{ + u16 version; + const u8 *ptr = NULL; + + if (sme->crypto.wpa_versions == 0) { + /* WAPI */ + ptr = cfg80211_find_ie(SLSI_WLAN_EID_WAPI, sme->ie, sme->ie_len); + if (ptr) { + version = ptr[3] << 8 | ptr[2]; + if (version != 1) { + SLSI_ERR_NODEV("Unexpected version (%d) in WAPI ie\n", version); + return NULL; + } + } + } else if (sme->crypto.wpa_versions == 2) { + /* RSN */ + ptr = cfg80211_find_ie(WLAN_EID_RSN, sme->ie, sme->ie_len); + + if (ptr) { + /* version index is 2 for RSN */ + version = ptr[2 + 1] << 8 | ptr[2]; + if (version != 1) { + SLSI_ERR_NODEV("Unexpected version (%d) in rsn ie\n", version); + return NULL; + } + } + } + *sec_ie_len = ptr ? ptr[1] + 2 : 0; + return ptr; +} + +/* If is_copy is true copy the required IEs from connect_ie to ie_dest. else + * calculate the required ie length + */ +static int slsi_mlme_connect_info_elems_ie_prep(struct slsi_dev *sdev, const u8 *connect_ie, + const size_t connect_ie_len, bool is_copy, u8 *ie_dest, int ie_dest_len) +{ + const u8 *ie_pos = NULL; + int info_elem_length = 0; + u16 curr_ie_len; + + if (is_copy && (!ie_dest || ie_dest_len == 0)) + return -EINVAL; + + /* find interworking ie id:107 */ + ie_pos = cfg80211_find_ie(SLSI_WLAN_EID_INTERWORKING, connect_ie, connect_ie_len); + if (ie_pos) { + curr_ie_len = *(ie_pos + 1) + 2; + if (is_copy) { + if (ie_dest_len >= curr_ie_len) { + memcpy(ie_dest, ie_pos, curr_ie_len); + ie_dest += curr_ie_len; + /* free space avail in ie_dest for next ie*/ + ie_dest_len -= curr_ie_len; + } else { + SLSI_ERR_NODEV("interwork ie extract error (ie_copy_l:%d, c_ie_l:%d):\n", ie_dest_len, curr_ie_len); + return -EINVAL; + } + } else { + info_elem_length = curr_ie_len; + } + } + + /* vendor specific IEs will be the last elements. */ + ie_pos = cfg80211_find_ie(WLAN_EID_VENDOR_SPECIFIC, connect_ie, connect_ie_len); + if (ie_pos) { + /* length of all the vendor specific IEs */ + curr_ie_len = connect_ie_len - (ie_pos - connect_ie); + if (is_copy) { + if (ie_dest_len >= curr_ie_len) { + memcpy(ie_dest, ie_pos, curr_ie_len); + ie_dest += curr_ie_len; + ie_dest_len -= curr_ie_len; + } else { + SLSI_ERR_NODEV("vendor ie extract error (ie_copy_l:%d, c_ie_l:%d):\n", ie_dest_len, curr_ie_len); + return -EINVAL; + } + } else { + info_elem_length += curr_ie_len; + } + } + + if (sdev->device_config.qos_info != -1) { + if (is_copy) { + if (ie_dest_len >= 9) { + int pos = 0; + + ie_dest[pos++] = SLSI_WLAN_EID_VENDOR_SPECIFIC; + ie_dest[pos++] = 0x07; + ie_dest[pos++] = 0x00; + ie_dest[pos++] = 0x50; + ie_dest[pos++] = 0xf2; + ie_dest[pos++] = WLAN_OUI_TYPE_MICROSOFT_WMM; + ie_dest[pos++] = WMM_OUI_SUBTYPE_INFORMATION_ELEMENT; + ie_dest[pos++] = WMM_VERSION; + ie_dest[pos++] = sdev->device_config.qos_info & 0x0F; + ie_dest += pos; + ie_dest_len -= pos; + } else { + SLSI_ERR_NODEV("Required 9bytes but left:%d\n", ie_dest_len); + return -EINVAL; + } + } else { + info_elem_length += 9; + } + } + return info_elem_length; +} + +static int slsi_mlme_connect_info_elements(struct slsi_dev *sdev, struct net_device *dev, struct cfg80211_connect_params *sme) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int info_elem_length = 0; + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u8 *p; + + info_elem_length = slsi_mlme_connect_info_elems_ie_prep(sdev, sme->ie, sme->ie_len, false, NULL, 0); + + /* NO IE required in MLME-ADD-INFO-ELEMENTS */ + if (info_elem_length <= 0) + return info_elem_length; + + req = fapi_alloc(mlme_add_info_elements_req, MLME_ADD_INFO_ELEMENTS_REQ, + ndev_vif->ifnum, info_elem_length); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_add_info_elements_req.purpose, FAPI_PURPOSE_ASSOCIATION_REQUEST); + + p = fapi_append_data(req, NULL, info_elem_length); + if (!p) { + slsi_kfree_skb(req); + return -EINVAL; + } + + (void)slsi_mlme_connect_info_elems_ie_prep(sdev, sme->ie, sme->ie_len, true, p, info_elem_length); + + /* backup ies */ + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + if (ndev_vif->sta.assoc_req_add_info_elem_len) + kfree(ndev_vif->sta.assoc_req_add_info_elem); + ndev_vif->sta.assoc_req_add_info_elem_len = 0; + + ndev_vif->sta.assoc_req_add_info_elem = kmalloc(info_elem_length, GFP_KERNEL); + if (ndev_vif->sta.assoc_req_add_info_elem) { + memcpy(ndev_vif->sta.assoc_req_add_info_elem, p, info_elem_length); + ndev_vif->sta.assoc_req_add_info_elem_len = info_elem_length; + } else { + SLSI_WARN(sdev, "No mem for ndev_vif->sta.assoc_req_add_info_elem size %d\n", info_elem_length); + } + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_add_info_elements_req(vif:%u)\n", ndev_vif->ifnum); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_ADD_INFO_ELEMENTS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_add_info_elements_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_add_info_elements_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_connect_cfm.result_code)); + r = -EINVAL; + } + + if (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPS, sme->ie, sme->ie_len)) + ndev_vif->sta.is_wps = true; + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_connect(struct slsi_dev *sdev, struct net_device *dev, struct cfg80211_connect_params *sme, struct ieee80211_channel *channel, const u8 *bssid) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u8 *p; + enum nl80211_auth_type auth_type = sme->auth_type; + u8 mac_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + struct key_params slsi_key; + const u8 *sec_ie = NULL; + int sec_ie_len = 0; + + memset(&slsi_key, 0, sizeof(slsi_key)); + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_CONNECT.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (WARN(!bssid, "BSSID is Null")) + return -EINVAL; + + if (WARN(!sme->ssid_len, "SSID is Null")) + return -EINVAL; + + switch (auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + case NL80211_AUTHTYPE_SHARED_KEY: + break; + case NL80211_AUTHTYPE_SAE: + auth_type = NL80211_AUTHTYPE_NETWORK_EAP; + break; + case NL80211_AUTHTYPE_AUTOMATIC: + /* In case of WEP, need to try both open and shared. + * FW does this if auth is shared_key. So set it to shared. + */ + if (sme->privacy && + (sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP40 || + sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP104)) + auth_type = NL80211_AUTHTYPE_SHARED_KEY; + else + auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM; + break; + default: + SLSI_NET_ERR(dev, "Unsupported auth_type: %d\n", auth_type); + return -EOPNOTSUPP; + } + + /* We save the WEP key for shared authentication. */ + if ((auth_type == NL80211_AUTHTYPE_SHARED_KEY) && + ((sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP40) || + (sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP104)) && + (ndev_vif->vif_type == FAPI_VIFTYPE_STATION)) { + SLSI_NET_DBG3(dev, SLSI_MLME, "key len (%d)\n", sme->key_len); + slsi_key.key = (u8 *)sme->key; + if (!slsi_key.key) + return -EINVAL; + slsi_key.key_len = sme->key_len; + slsi_key.seq_len = 0; + if (sme->crypto.n_ciphers_pairwise) + slsi_key.cipher = sme->crypto.ciphers_pairwise[0]; + + r = slsi_mlme_set_key(sdev, dev, sme->key_idx, FAPI_KEYTYPE_WEP, mac_addr, &slsi_key); + if (r != 0) { + SLSI_NET_ERR(dev, "Error Setting Shared key (%d)", r); + return r; + } + } + + /*Do not check sme->ie as wpa_supplicant sends some invalid value in it even if ie_len is zero .*/ + if (sme->ie_len) { + r = slsi_mlme_connect_info_elements(sdev, dev, sme); + if (r) + return r; + + sec_ie = slsi_mlme_connect_get_sec_ie(sme, &sec_ie_len); + if (sec_ie_len < 0) { + SLSI_NET_ERR(dev, "ERROR preparing Security IEs\n"); + return sec_ie_len; + } + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_connect_req(vif:%u, bssid:%pM, ssid:%.*s)\n", ndev_vif->ifnum, bssid, (int)sme->ssid_len, sme->ssid); + req = fapi_alloc(mlme_connect_req, MLME_CONNECT_REQ, ndev_vif->ifnum, + 2 + sme->ssid_len + /*SSID IE*/ + sec_ie_len); /*WPA/WPA2/WAPI/OSEN*/ + if (!req) + return -ENOMEM; + + fapi_set_memcpy(req, u.mlme_connect_req.bssid, bssid); + fapi_set_u16(req, u.mlme_connect_req.authentication_type, auth_type); + /* Need to double the freq for the firmware */ + fapi_set_u16(req, u.mlme_connect_req.channel_frequency, (2 * channel->center_freq)); + + p = fapi_append_data(req, NULL, 2 + sme->ssid_len + sec_ie_len); + if (!p) { + slsi_kfree_skb(req); + return -EINVAL; + } + *p++ = WLAN_EID_SSID; + *p++ = sme->ssid_len; + memcpy(p, sme->ssid, sme->ssid_len); + p += sme->ssid_len; + + if (sec_ie_len) + memcpy(p, sec_ie, sec_ie_len); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_CONNECT_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_connect_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_connect_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_connect_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +void slsi_mlme_connect_resp(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_CONNECT_RESP\n"); + return; + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_connect_resp(vif:%u)\n", ndev_vif->ifnum); + req = fapi_alloc(mlme_connect_res, MLME_CONNECT_RES, ndev_vif->ifnum, 0); + if (!req) + return; + + cfm = slsi_mlme_req_no_cfm(sdev, dev, req); + WARN_ON(cfm); +} + +void slsi_mlme_connected_resp(struct slsi_dev *sdev, struct net_device *dev, u16 peer_index) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_CONNECT_RESP\n"); + return; + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_connected_resp(vif:%u, peer_index:%d)\n", ndev_vif->ifnum, peer_index); + req = fapi_alloc(mlme_connected_res, MLME_CONNECTED_RES, ndev_vif->ifnum, 0); + if (!req) { + SLSI_NET_ERR(dev, "mlme-connected-response :: memory allocation failed\n"); + return; + } + + fapi_set_u16(req, u.mlme_connected_res.peer_index, peer_index); + slsi_mlme_req_no_cfm(sdev, dev, req); +} + +void slsi_mlme_roamed_resp(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_ROAMED_RESP\n"); + return; + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_roamed_resp\n"); + req = fapi_alloc(mlme_roamed_res, MLME_ROAMED_RES, ndev_vif->ifnum, 0); + if (!req) + return; + + cfm = slsi_mlme_req_no_cfm(sdev, dev, req); + WARN_ON(cfm); +} + +/* Null check for cfm done in caller function */ +bool slsi_disconnect_cfm_validate(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *cfm) +{ + int result = fapi_get_u16(cfm, u.mlme_disconnect_cfm.result_code); + bool r = false; + + SLSI_UNUSED_PARAMETER(sdev); + + if (WARN_ON(!dev)) + goto exit; + + if (result == FAPI_RESULTCODE_SUCCESS) + r = true; + /* Not present code would mean peer is already disconnected and hence no ind (could be race scenario), don't log as error */ + else if (result != FAPI_RESULTCODE_NOT_PRESENT) + SLSI_NET_ERR(dev, "mlme_disconnect_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_disconnect_cfm.result_code)); + +exit: + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_disconnect(struct slsi_dev *sdev, struct net_device *dev, u8 *mac, u16 reason_code, bool wait_ind) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_DISCONNECT.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_disconnect_req(vif:%u, bssid:%pM, reason:%d)\n", ndev_vif->ifnum, mac, reason_code); + + /* No data reference required */ + req = fapi_alloc(mlme_disconnect_req, MLME_DISCONNECT_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + SLSI_INFO(sdev, "Send DEAUTH, reason = %d\n", reason_code); + fapi_set_u16(req, u.mlme_disconnect_req.reason_code, reason_code); + if (mac) + fapi_set_memcpy(req, u.mlme_disconnect_req.peer_sta_address, mac); + else + fapi_set_memset(req, u.mlme_disconnect_req.peer_sta_address, 0); + if (wait_ind) { + rx = slsi_mlme_req_cfm_ind(sdev, dev, req, MLME_DISCONNECT_CFM, MLME_DISCONNECT_IND, slsi_disconnect_cfm_validate); + if (!rx) { + SLSI_NET_ERR(dev, "mlme_disconnect_cfm() ERROR\n"); + r = -EINVAL; + } + } else { + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_DISCONNECT_CFM); + if (rx) { + if (fapi_get_u16(rx, u.mlme_disconnect_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_disconnect_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_disconnect_cfm.result_code)); + r = -EINVAL; + } + } else { + r = -EIO; + } + } + + slsi_kfree_skb(rx); + return r; +} + +int slsi_mlme_set_key(struct slsi_dev *sdev, struct net_device *dev, u16 key_id, u16 key_type, const u8 *address, struct key_params *key) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_SETKEYS.request\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_setkeys_req(key_id:%d, key_type:%d, address:%pM, length:%d, cipher:0x%.8X)\n", key_id, key_type, address, key->key_len, key->cipher); + req = fapi_alloc(mlme_setkeys_req, MLME_SETKEYS_REQ, ndev_vif->ifnum, key->key_len + 1); /* + 1 for the wep key index */ + if (!req) + return -ENOMEM; + fapi_set_u16(req, u.mlme_setkeys_req.length, key->key_len * 8); + fapi_set_u16(req, u.mlme_setkeys_req.key_id, key_id); + fapi_set_u16(req, u.mlme_setkeys_req.key_type, key_type); + fapi_set_memcpy(req, u.mlme_setkeys_req.address, address); + fapi_set_memset(req, u.mlme_setkeys_req.sequence_number, 0x00); + + if (key->seq_len && key->seq) { + int i; + u16 temp_seq; + + SLSI_NET_DBG3(dev, SLSI_MLME, "mlme_setkeys_req(key->seq_len:%d)\n", key->seq_len); + + /* Sequence would be in little endian format + * If sequence is say key->seq is + * 04 03 02 01 00 00 00 00, it would be encoded as : + * 0x0304 0x0102 0x0000 0x0000 for firmware + */ + for (i = 0; i < key->seq_len; i += 2) { + temp_seq = (u16)(key->seq[i + 1] << 8) | (u16)(key->seq[i]); + fapi_set_u16(req, u.mlme_setkeys_req.sequence_number[i / 2], temp_seq); + } + } + + fapi_set_u32(req, u.mlme_setkeys_req.cipher_suite_selector, key->cipher); + + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || key->cipher == WLAN_CIPHER_SUITE_WEP104) { + u8 wep_key_id = (u8)key_id; + + if (key_id > 3) + SLSI_NET_WARN(dev, "Key ID is greater than 3"); + /* Incase of WEP key index is appended before key. + * So increment length by one + */ + fapi_set_u16(req, u.mlme_setkeys_req.length, (key->key_len + 1) * 8); + fapi_append_data(req, &wep_key_id, 1); + } + fapi_append_data(req, key->key, key->key_len); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SETKEYS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_setkeys_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_setkeys_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_setkeys_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_get_key(struct slsi_dev *sdev, struct net_device *dev, u16 key_id, u16 key_type, u8 *seq, int *seq_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_get_key_sequence_req(key_id:%d, key_type:%d)\n", key_id, key_type); + req = fapi_alloc(mlme_get_key_sequence_req, MLME_GET_KEY_SEQUENCE_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + fapi_set_u16(req, u.mlme_get_key_sequence_req.key_id, key_id); + fapi_set_u16(req, u.mlme_get_key_sequence_req.key_type, key_type); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_GET_KEY_SEQUENCE_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_get_key_sequence_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_get_key_sequence_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_get_key_sequence_cfm.result_code)); + r = -ENOENT; + } else { + int i; + u16 temp_seq; + + /* For WPA2 Key RSC - 8 octets. For WPAI, it would be 16 octets (code would need to be updated) + * Length is not available in cfm but even if max length 8 is assigned, it should be ok as other octets + * would be padded with 0s + */ + *seq_len = 8; + + /* Sequence from firmware is of a[8] type u16 (16 octets) and only 8 octets are required for WPA/WPA2. + * If sequence is say 0x01 0x02 0x03 0x04 with 0x01 as MSB and 0x04 as LSB then + * it would be encoded as: 0x0304 0x0102 by firmware. + * Sequence is expected to be returned in little endian + */ + + for (i = 0; i < *seq_len / 2; i++) { + temp_seq = fapi_get_u16(cfm, u.mlme_get_key_sequence_cfm.sequence_number[i]); + *seq = (u8)(temp_seq & 0xFF); + *(seq + 1) = (u8)((temp_seq >> 8) & 0xFF); + + seq += 2; + } + } + + slsi_kfree_skb(cfm); + return r; +} + +void slsi_fw_tx_rate_calc(u16 fw_rate, struct rate_info *tx_rate, unsigned long *data_rate_mbps) +{ + const int fw_rate_idx_to_80211_rate[] = { 0, 10, 20, 55, 60, 90, 110, 120, 180, 240, 360, 480, 540 }; + + if (tx_rate) { + tx_rate->flags = 0; + tx_rate->legacy = 0; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + tx_rate->bw = 0; +#endif + } + if ((fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) == SLSI_FW_API_RATE_NON_HT_SELECTED) { + u16 fw_rate_idx = fw_rate & SLSI_FW_API_RATE_INDEX_FIELD; + + if (fw_rate > 0 && fw_rate_idx < ARRAY_SIZE(fw_rate_idx_to_80211_rate)) { + if (tx_rate) + tx_rate->legacy = fw_rate_idx_to_80211_rate[fw_rate_idx]; + if (data_rate_mbps) + *data_rate_mbps = fw_rate_idx_to_80211_rate[fw_rate_idx] / 10; + } + + } else if ((fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) == SLSI_FW_API_RATE_HT_SELECTED) { + u8 mcs = SLSI_FW_API_RATE_HT_MCS_FIELD & fw_rate; + u8 nss = ((SLSI_FW_API_RATE_HT_NSS_FIELD & fw_rate) >> 6) + 1; + + if (tx_rate) { + tx_rate->flags |= RATE_INFO_FLAGS_MCS; + tx_rate->mcs = mcs; + + if ((fw_rate & SLSI_FW_API_RATE_BW_FIELD) == SLSI_FW_API_RATE_BW_40MHZ) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + tx_rate->bw |= RATE_INFO_BW_40; +#else + tx_rate->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH; +#endif + if (fw_rate & SLSI_FW_API_RATE_SGI) + tx_rate->flags |= RATE_INFO_FLAGS_SHORT_GI; + } + + if (data_rate_mbps) { + int chan_bw_idx; + int gi_idx; + int mcs_idx; + + chan_bw_idx = (fw_rate & SLSI_FW_API_RATE_BW_FIELD) >> 9; + gi_idx = ((fw_rate & SLSI_FW_API_RATE_SGI) == SLSI_FW_API_RATE_SGI) ? 1 : 0; + mcs_idx = SLSI_FW_API_RATE_HT_MCS_FIELD & fw_rate; + + if ((chan_bw_idx < 2) && (mcs_idx <= 7)) { + *data_rate_mbps = (unsigned long)(nss * slsi_rates_table[chan_bw_idx][gi_idx][mcs_idx]) / 10; + } else if (mcs == 32 && chan_bw_idx == 1) { + if (gi_idx == 1) + *data_rate_mbps = (unsigned long)(nss * 67)/10; + else + *data_rate_mbps = nss * 6; + } else { + SLSI_WARN_NODEV("FW DATA RATE decode error fw_rate:%x, bw:%x, mcs_idx:%x, nss : %d\n", + fw_rate, chan_bw_idx, mcs_idx, nss); + } + } + + } else if ((fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) == SLSI_FW_API_RATE_VHT_SELECTED) { + int chan_bw_idx; + int gi_idx; + int mcs_idx; + u8 nss; + + /* report vht rate in legacy units and not as mcs index. reason: upper layers may still be not + * updated with vht msc table. + */ + chan_bw_idx = (fw_rate & SLSI_FW_API_RATE_BW_FIELD) >> 9; + gi_idx = ((fw_rate & SLSI_FW_API_RATE_SGI) == SLSI_FW_API_RATE_SGI) ? 1 : 0; + /* Calculate NSS --> bits 6 to 4*/ + nss = ((SLSI_FW_API_RATE_VHT_NSS_FIELD & fw_rate) >> 4) + 1; + mcs_idx = SLSI_FW_API_RATE_VHT_MCS_FIELD & fw_rate; + /* Bandwidth (BW): 0x0= 20 MHz, 0x1= 40 MHz, 0x2= 80 MHz, 0x3= 160/ 80+80 MHz. 0x3 is not supported */ + if ((chan_bw_idx <= 2) && (mcs_idx <= 9)) { + if (tx_rate) + tx_rate->legacy = nss * slsi_rates_table[chan_bw_idx][gi_idx][mcs_idx]; + if (data_rate_mbps) + *data_rate_mbps = (unsigned long)(nss * slsi_rates_table[chan_bw_idx][gi_idx][mcs_idx]) / 10; + } else { + SLSI_WARN_NODEV("FW DATA RATE decode error fw_rate:%x, bw:%x, mcs_idx:%x,nss : %d\n", fw_rate, chan_bw_idx, mcs_idx, nss); + } + } +} + +int slsi_mlme_get_sinfo_mib(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_peer *peer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + int data_length = 0; + int r = 0; + static const struct slsi_mib_get_entry get_values[] = { + { SLSI_PSID_UNIFI_TX_DATA_RATE, { 0, 0 } }, /* to get STATION_INFO_TX_BITRATE*/ + { SLSI_PSID_UNIFI_RSSI, { 0, 0 } }, /* to get STATION_INFO_SIGNAL_AVG*/ + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 3, 0 } }, /* bad_fcs_count*/ + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 25, 0 } }, /* mac_bad_sig_count*/ + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 30, 0 } }, /* rx_error_count*/ + { SLSI_PSID_UNIFI_FRAME_TX_COUNTERS, { 1, 0 } }, /*tx good count*/ + { SLSI_PSID_UNIFI_FRAME_TX_COUNTERS, { 2, 0 } }, /*tx bad count*/ + { SLSI_PSID_UNIFI_FRAME_RX_COUNTERS, { 1, 0 } }, /*rx good count*/ +#ifdef CONFIG_SCSC_ENHANCED_PACKET_STATS + { SLSI_PSID_UNIFI_FRAME_TX_COUNTERS, { 3, 0 } }, /*tx retry count*/ +#endif + }; + int rx_counter = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (!peer) { + SLSI_WARN(sdev, "Peer Not available\n"); + return -EINVAL; + } + + /*check if function is called within given period*/ + if (__ratelimit(&peer->sinfo_mib_get_rs)) + return 0; + + r = slsi_mib_encode_get_list(&mibreq, (sizeof(get_values) / sizeof(struct slsi_mib_get_entry)), + get_values); + if (r != SLSI_MIB_STATUS_SUCCESS) + return -ENOMEM; + + /* Fixed fields len (5) : 2 bytes(PSID) + 2 bytes (Len) + 1 byte (VLDATA header ) [10 for 2 PSIDs] + * Data : 3 bytes for SLSI_PSID_UNIFI_TX_DATA_RATE , 1 byte for SLSI_PSID_UNIFI_RSSI + * 10*7 bytes for 3 Throughput Mib's and 4 counter Mib's + */ + mibrsp.dataLength = 84; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_NET_DBG1(dev, SLSI_MLME, "failed to allocate memory\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, dev, mibreq.data, mibreq.dataLength, mibrsp.data, + mibrsp.dataLength, &data_length); + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = (u32)data_length; + values = slsi_mib_decode_get_list(&mibrsp, + (sizeof(get_values) / sizeof(struct slsi_mib_get_entry)), get_values); + if (!values) { + SLSI_NET_DBG1(dev, SLSI_MLME, "mib decode list failed\n"); + kfree(mibrsp.data); + return -ENOMEM; + } + + if (values[0].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[0].type, SLSI_MIB_TYPE_UINT); + slsi_fw_tx_rate_calc((u16)values[0].u.uintValue, &peer->sinfo.txrate, NULL); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + peer->sinfo.filled |= BIT(NL80211_STA_INFO_TX_BITRATE); +#else + peer->sinfo.filled |= STATION_INFO_TX_BITRATE; +#endif + SLSI_DBG3(sdev, SLSI_MLME, "SLSI_PSID_UNIFI_TX_DATA_RATE = %d\n", + values[0].u.uintValue); + } + + if (values[1].type != SLSI_MIB_TYPE_NONE) { + SLSI_CHECK_TYPE(sdev, values[1].type, SLSI_MIB_TYPE_INT); + if (values[1].u.intValue >= 0) + peer->sinfo.signal = -1; + else + peer->sinfo.signal = (s8)values[1].u.intValue; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + peer->sinfo.filled |= BIT(NL80211_STA_INFO_SIGNAL); +#else + peer->sinfo.filled |= STATION_INFO_SIGNAL; +#endif + SLSI_DBG3(sdev, SLSI_MLME, "SLSI_PSID_UNIFI_RSSI = %d\n", + values[1].u.intValue); + } + + if (values[2].type == SLSI_MIB_TYPE_UINT) + rx_counter += values[2].u.uintValue; /*bad_fcs_count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 2); + if (values[3].type == SLSI_MIB_TYPE_UINT) + rx_counter += values[3].u.uintValue; /*mac_bad_sig_count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 3); + if (values[4].type == SLSI_MIB_TYPE_UINT) + rx_counter += values[4].u.uintValue; /*rx_error_count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 4); + if (values[5].type == SLSI_MIB_TYPE_UINT) + peer->sinfo.tx_packets = values[5].u.uintValue; /*tx good count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 5); + if (values[6].type == SLSI_MIB_TYPE_UINT) + peer->sinfo.tx_failed = values[6].u.uintValue; /*tx bad count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 6); + if (values[7].type == SLSI_MIB_TYPE_UINT) + peer->sinfo.rx_packets = values[7].u.uintValue; /*rx good count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 7); +#ifdef CONFIG_SCSC_ENHANCED_PACKET_STATS + if (values[8].type == SLSI_MIB_TYPE_UINT) + peer->sinfo.tx_retries = values[8].u.uintValue; /*tx retry count*/ + else + SLSI_ERR(sdev, "invalid type. iter:%d", 8); +#endif + + peer->sinfo.rx_dropped_misc = rx_counter; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + peer->sinfo.filled |= BIT(NL80211_STA_INFO_TX_FAILED) | BIT(NL80211_STA_INFO_RX_DROP_MISC) | + BIT(NL80211_STA_INFO_TX_PACKETS) | BIT(NL80211_STA_INFO_RX_PACKETS); +#ifdef CONFIG_SCSC_ENHANCED_PACKET_STATS + peer->sinfo.filled |= BIT(NL80211_STA_INFO_TX_RETRIES); +#endif +#endif + } else { + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_get_req failed(result:0x%4x)\n", r); + } + + kfree(mibrsp.data); + kfree(values); + return r; +} + +int slsi_mlme_connect_scan(struct slsi_dev *sdev, struct net_device *dev, + u32 n_ssids, struct cfg80211_ssid *ssids, struct ieee80211_channel *channel) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int r = 0; + struct ieee80211_channel **scan_channels = NULL; + struct ieee80211_channel **add_scan_channels; + int n_channels = 0; + struct sk_buff *scan; + struct cfg80211_scan_info info = {.aborted = true}; + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + if (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req) { + SLSI_NET_DBG3(dev, SLSI_MLME, "stop on-going Scan\n"); + (void)slsi_mlme_del_scan(sdev, dev, ndev_vif->ifnum << 8 | SLSI_SCAN_HW_ID, false); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + cfg80211_scan_done(ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req, &info); +#else + cfg80211_scan_done(ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req, true); +#endif + + ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req = NULL; + } + + if (!channel) { + enum nl80211_band band; + struct wiphy *wiphy = sdev->wiphy; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + for (band = 0; band < NUM_NL80211_BANDS; band++) { +#else + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { +#endif + if (!wiphy->bands[band]) + continue; + n_channels += wiphy->bands[band]->n_channels; + } + + WARN_ON(n_channels == 0); + scan_channels = kmalloc_array((size_t)n_channels, sizeof(*scan_channels), GFP_KERNEL); + if (!scan_channels) { + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return -ENOMEM; + } + n_channels = 0; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + for (band = 0; band < NUM_NL80211_BANDS; band++) { +#else + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { +#endif + int j; + + if (!wiphy->bands[band]) + continue; + for (j = 0; j < wiphy->bands[band]->n_channels; j++) + if (!(wiphy->bands[band]->channels[j].flags & IEEE80211_CHAN_DISABLED)) { + scan_channels[n_channels] = &wiphy->bands[band]->channels[j]; + n_channels++; + } + } + add_scan_channels = scan_channels; + } else { + n_channels = 1; + add_scan_channels = &channel; + } + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = true; + r = slsi_mlme_add_scan(sdev, + dev, + FAPI_SCANTYPE_FULL_SCAN, + FAPI_REPORTMODE_REAL_TIME, + n_ssids, + ssids, + n_channels, + add_scan_channels, + NULL, + ndev_vif->probe_req_ies, + ndev_vif->probe_req_ie_len, + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan); + + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + scan = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + while (scan) { + slsi_rx_scan_pass_to_cfg80211(sdev, dev, scan); + scan = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + + kfree(scan_channels); + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = false; + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; +} + +/** + * The powermgt_lock mutex is to ensure atomic update of the power management state. + */ +DEFINE_MUTEX(powermgt_lock); +/** + * The slsi_mlme_powermgt_unlocked() must be called from a context that is synchronised + * with ndev_vif. if called without the ndev_vif mutex already taken, other mechanisms + * must ensure that ndev_vif will exist for the duration of the function. + */ +int slsi_mlme_powermgt_unlocked(struct slsi_dev *sdev, struct net_device *dev, u16 power_mode) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + + mutex_lock(&powermgt_lock); + + if (WARN_ON(!ndev_vif->activated)) { + mutex_unlock(&powermgt_lock); + return -EINVAL; + } + + if (ndev_vif->power_mode == power_mode) { + mutex_unlock(&powermgt_lock); + SLSI_NET_DBG3(dev, SLSI_MLME, "power management mode is same as requested. No changes done\n"); + return 0; + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_powermgt_req(vif:%d, power_management_mode:%d)\n", ndev_vif->ifnum, power_mode); + req = fapi_alloc(mlme_powermgt_req, MLME_POWERMGT_REQ, ndev_vif->ifnum, 0); + if (!req) { + mutex_unlock(&powermgt_lock); + return -ENOMEM; + } + fapi_set_u16(req, u.mlme_powermgt_req.power_management_mode, power_mode); + + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_POWERMGT_CFM); + if (!rx) { + mutex_unlock(&powermgt_lock); + return -EIO; + } + + if (fapi_get_u16(rx, u.mlme_powermgt_cfm.result_code) == FAPI_RESULTCODE_SUCCESS) { + ndev_vif->power_mode = power_mode; + } else { + SLSI_NET_ERR(dev, "mlme_powermgt_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_powermgt_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(rx); + mutex_unlock(&powermgt_lock); + return r; +} + +int slsi_mlme_powermgt(struct slsi_dev *sdev, struct net_device *dev, u16 power_mode) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + return slsi_mlme_powermgt_unlocked(sdev, dev, power_mode); +} + +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG +int slsi_mlme_synchronised_response(struct slsi_dev *sdev, struct net_device *dev, + struct cfg80211_external_auth_params *params) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + if (ndev_vif->activated) { + SLSI_NET_DBG3(dev, SLSI_MLME, "MLME_SYNCHRONISED_RES\n"); + + req = fapi_alloc(mlme_synchronised_res, MLME_SYNCHRONISED_RES, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_synchronised_res.result_code, params->status); + fapi_set_memcpy(req, u.mlme_synchronised_res.bssid, params->bssid); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_synchronised_response(vif:%d) status:%d\n", + ndev_vif->ifnum, params->status); + cfm = slsi_mlme_req_no_cfm(sdev, dev, req); + if (cfm) + SLSI_NET_ERR(dev, "Received cfm for MLME_SYNCHRONISED_RES\n"); + } else + SLSI_NET_DBG1(dev, SLSI_MLME, "vif is not active"); + + return 0; +} +#endif + +int slsi_mlme_register_action_frame(struct slsi_dev *sdev, struct net_device *dev, u32 af_bitmap_active, u32 af_bitmap_suspended) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + req = fapi_alloc(mlme_register_action_frame_req, MLME_REGISTER_ACTION_FRAME_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u32(req, u.mlme_register_action_frame_req.action_frame_category_bitmap_active, af_bitmap_active); + fapi_set_u32(req, u.mlme_register_action_frame_req.action_frame_category_bitmap_suspended, af_bitmap_suspended); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_register_action_frame(vif:%d, active:%d, suspended:%d)\n", ndev_vif->ifnum, af_bitmap_active, af_bitmap_suspended); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_REGISTER_ACTION_FRAME_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_register_action_frame_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_register_action_frame_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_register_action_frame_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_channel_switch(struct slsi_dev *sdev, struct net_device *dev, u16 center_freq, u16 chan_info) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_channel_switch_req(vif:%d, freq: %d, channel info: 0x%x)\n", ndev_vif->ifnum, center_freq, chan_info); + req = fapi_alloc(mlme_channel_switch_req, MLME_CHANNEL_SWITCH_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_channel_switch_req.channel_frequency, SLSI_FREQ_HOST_TO_FW(center_freq)); + fapi_set_u16(req, u.mlme_channel_switch_req.channel_information, chan_info); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_CHANNEL_SWITCH_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_channel_switch_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_channel_switch_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_channel_switch_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_add_info_elements(struct slsi_dev *sdev, struct net_device *dev, u16 purpose, const u8 *ies, const u16 ies_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u8 *p; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + req = fapi_alloc(mlme_add_info_elements_req, MLME_ADD_INFO_ELEMENTS_REQ, ndev_vif->ifnum, ies_len); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_add_info_elements_req.purpose, purpose); + + if (ies_len != 0) { + p = fapi_append_data(req, ies, ies_len); + if (!p) { + slsi_kfree_skb(req); + return -EINVAL; + } + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_add_info_elements_req(vif:%d, ies_len:%d)\n", ndev_vif->ifnum, ies_len); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_ADD_INFO_ELEMENTS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_add_info_elements_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_add_info_elements_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_add_info_elements_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_send_frame_data(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 msg_type, + u16 host_tag, u32 dwell_time, u32 period) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 len = skb->len; + struct sk_buff *original_skb = 0; + int ret; +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + int is_enhanced_arp_request_frame = 0; +#endif + + /* don't let ARP frames exhaust all the control slots */ + if (msg_type == FAPI_MESSAGETYPE_ARP) { + int free_slots = 0; + + free_slots = hip4_free_ctrl_slots_count(&sdev->hip4_inst); + + if (free_slots < 0) { + SLSI_DBG1(sdev, SLSI_MLME, "drop ARP (error in getting free slot count)\n"); + return free_slots; + } + + if (free_slots < SLSI_MLME_ARP_DROP_FREE_SLOTS_COUNT) { + SLSI_DBG1(sdev, SLSI_MLME, "drop ARP (running out of Control slots:%d)\n", free_slots); + slsi_kfree_skb(skb); + return NETDEV_TX_OK; + } + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (ndev_vif->enhanced_arp_detect_enabled && (msg_type == FAPI_MESSAGETYPE_ARP)) { + u8 *frame = skb->data + sizeof(struct ethhdr); + u16 arp_opcode = frame[SLSI_ARP_OPCODE_OFFSET] << 8 | frame[SLSI_ARP_OPCODE_OFFSET + 1]; + + if ((arp_opcode == SLSI_ARP_REQUEST_OPCODE) && + !SLSI_IS_GRATUITOUS_ARP(frame) && + !memcmp(&frame[SLSI_ARP_DEST_IP_ADDR_OFFSET], &ndev_vif->target_ip_addr, 4)) + is_enhanced_arp_request_frame = 1; + } +#endif + } + + /* check for headroom to push signal header; if not available, re-alloc headroom */ + if (skb_headroom(skb) < (fapi_sig_size(mlme_send_frame_req))) { + struct sk_buff *skb2 = NULL; + + skb2 = slsi_skb_realloc_headroom(skb, fapi_sig_size(mlme_send_frame_req)); + if (!skb2) + return -EINVAL; + original_skb = skb; + skb = skb2; + } + len = skb->len; + (void)skb_push(skb, (fapi_sig_size(mlme_send_frame_req))); + + /* fill the signal header */ + fapi_set_u16(skb, id, MLME_SEND_FRAME_REQ); + fapi_set_u16(skb, receiver_pid, 0); + fapi_set_u16(skb, sender_pid, SLSI_TX_PROCESS_ID_MIN); + fapi_set_u16(skb, fw_reference, 0); + + /* fill in signal parameters */ + fapi_set_u16(skb, u.mlme_send_frame_req.vif, ndev_vif->ifnum); + + if (host_tag == 0) + host_tag = slsi_tx_mgmt_host_tag(sdev); + + fapi_set_u16(skb, u.mlme_send_frame_req.host_tag, host_tag); + fapi_set_u16(skb, u.mlme_send_frame_req.data_unit_descriptor, FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME); + fapi_set_u16(skb, u.mlme_send_frame_req.message_type, msg_type); + fapi_set_u16(skb, u.mlme_send_frame_req.channel_frequency, 0); + fapi_set_u32(skb, u.mlme_send_frame_req.dwell_time, dwell_time); + fapi_set_u32(skb, u.mlme_send_frame_req.period, period); + + SLSI_DBG2(sdev, SLSI_MLME, "mlme_send_frame_req(vif:%d, message_type:%d, host_tag:%d)\n", ndev_vif->ifnum, msg_type, host_tag); + /* slsi_tx_control frees the skb. Do not use it after this call. */ + ret = slsi_tx_control(sdev, dev, skb); + if (ret != 0) { + SLSI_WARN(sdev, "failed to send MLME signal(err=%d)\n", ret); + return ret; + } + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (is_enhanced_arp_request_frame) { + int i; + + ndev_vif->enhanced_arp_stats.arp_req_count_to_lower_mac++; + for (i = 0; i < SLSI_MAX_ARP_SEND_FRAME; i++) { + if (!ndev_vif->enhanced_arp_host_tag[i]) { + ndev_vif->enhanced_arp_host_tag[i] = host_tag; + break; + } + } + } +#endif + + if (original_skb) + slsi_kfree_skb(original_skb); + + /* as the frame is queued to HIP for transmission, store the host tag of the frames + * to validate the transmission status in MLME-Frame-Transmission.indication. + * Take necessary action based on the type of frame and status of it's transmission + */ + if (msg_type == FAPI_MESSAGETYPE_EAPOL_KEY_M4) { + ndev_vif->sta.m4_host_tag = host_tag; + SLSI_NET_DBG1(dev, SLSI_MLME, "EAPOL-Key M4 frame (host_tag:%d)\n", ndev_vif->sta.m4_host_tag); + } else if (msg_type == FAPI_MESSAGETYPE_EAP_MESSAGE) { + if (!ndev_vif->sta.is_wps && (ndev_vif->iftype == NL80211_IFTYPE_STATION)) { + /* In case of non-P2P station and Enterprise security store the host_tag. + * If transmission of such frame fails, inform supplicant to disconnect. + */ + ndev_vif->sta.eap_hosttag = host_tag; + SLSI_NET_DBG1(dev, SLSI_MLME, "EAP frame (host_tag:%d)\n", ndev_vif->sta.eap_hosttag); + } + } + return ret; +} + +int slsi_mlme_send_frame_mgmt(struct slsi_dev *sdev, struct net_device *dev, const u8 *frame, int frame_len, + u16 data_desc, u16 msg_type, u16 host_tag, u16 freq, u32 dwell_time, u32 period) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u8 *p; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + req = fapi_alloc(mlme_send_frame_req, MLME_SEND_FRAME_REQ, ndev_vif->ifnum, frame_len); + if (!req) { + SLSI_WARN(sdev, "failed to alloc memory\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_send_frame_req.host_tag, host_tag); + fapi_set_u16(req, u.mlme_send_frame_req.data_unit_descriptor, data_desc); + fapi_set_u16(req, u.mlme_send_frame_req.message_type, msg_type); + fapi_set_u16(req, u.mlme_send_frame_req.channel_frequency, freq); + fapi_set_u32(req, u.mlme_send_frame_req.dwell_time, dwell_time); + fapi_set_u32(req, u.mlme_send_frame_req.period, period); + + p = fapi_append_data(req, frame, frame_len); + if (!p) { + slsi_kfree_skb(req); + SLSI_WARN(sdev, "failed to append data\n"); + return -EINVAL; + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_send_frame_req(vif:%d, message_type:%d,host_tag:%d)\n", ndev_vif->ifnum, msg_type, host_tag); + slsi_debug_frame(sdev, dev, req, "TX"); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SEND_FRAME_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_send_frame_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_send_frame_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_send_frame_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_reset_dwell_time(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_reset_dwell_time_req (vif:%d)\n", ndev_vif->ifnum); + + req = fapi_alloc(mlme_reset_dwell_time_req, MLME_RESET_DWELL_TIME_REQ, ndev_vif->ifnum, 0); + + if (!req) + return -ENOMEM; + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_RESET_DWELL_TIME_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_reset_dwell_time_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_reset_dwell_time_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_reset_dwell_time_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_set_packet_filter(struct slsi_dev *sdev, struct net_device *dev, + int pkt_filter_len, + u8 num_filters, + struct slsi_mlme_pkt_filter_elem *pkt_filter_elems) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0, i = 0, j = 0; + u8 *p; + u8 index = 0; + + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + + if (WARN_ON(!num_filters)) + return -EINVAL; + + req = fapi_alloc(mlme_set_packet_filter_req, MLME_SET_PACKET_FILTER_REQ, ndev_vif->ifnum, pkt_filter_len); + if (!req) + return -ENOMEM; + + p = fapi_append_data(req, NULL, pkt_filter_len); + if (!p) { + slsi_kfree_skb(req); + return -EINVAL; + } + + for (i = 0; i < num_filters; i++) { + struct slsi_mlme_pkt_filter_elem pkt_filter_elem = pkt_filter_elems[i]; + + memcpy(&p[index], pkt_filter_elem.header, SLSI_PKT_FILTER_ELEM_HDR_LEN); + index += SLSI_PKT_FILTER_ELEM_HDR_LEN; + + for (j = 0; j < pkt_filter_elem.num_pattern_desc; j++) { + p[index++] = pkt_filter_elem.pattern_desc[j].offset; + p[index++] = pkt_filter_elem.pattern_desc[j].mask_length; + memcpy(&p[index], pkt_filter_elem.pattern_desc[j].mask, pkt_filter_elem.pattern_desc[j].mask_length); + index += pkt_filter_elem.pattern_desc[j].mask_length; + memcpy(&p[index], pkt_filter_elem.pattern_desc[j].pattern, pkt_filter_elem.pattern_desc[j].mask_length); + index += pkt_filter_elem.pattern_desc[j].mask_length; + } + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_set_packet_filter_req(vif:%d, num_filters:%d)\n", ndev_vif->ifnum, num_filters); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_PACKET_FILTER_CFM); + if (!cfm) + return -EIO; + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_set_pmk(struct slsi_dev *sdev, struct net_device *dev, const u8 *pmk, u16 pmklen) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + if (pmk) + req = fapi_alloc(mlme_set_pmk_req, MLME_SET_PMK_REQ, ndev_vif->ifnum, pmklen); + else + req = fapi_alloc(mlme_set_pmk_req, MLME_SET_PMK_REQ, ndev_vif->ifnum, 0); + + if (!req) + return -ENOMEM; + if (pmk) + fapi_append_data(req, pmk, pmklen); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_set_pmk_req(vif:%u, pmklen:%d)\n", ndev_vif->ifnum, pmklen); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_PMK_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_pmk_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_pmk_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_pmk_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_roam(struct slsi_dev *sdev, struct net_device *dev, const u8 *bssid, u16 freq) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_roam_req(vif:%u, bssid:%pM, freq:%d)\n", ndev_vif->ifnum, bssid, freq); + req = fapi_alloc(mlme_roam_req, MLME_ROAM_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + fapi_set_memcpy(req, u.mlme_roam_req.bssid, bssid); + fapi_set_u16(req, u.mlme_roam_req.channel_frequency, SLSI_FREQ_HOST_TO_FW(freq)); + atomic_set(&ndev_vif->sta.drop_roamed_ind, 1); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_ROAM_CFM); + atomic_set(&ndev_vif->sta.drop_roamed_ind, 0); + if (!cfm) + return -EIO; + if (fapi_get_u16(cfm, u.mlme_roam_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_roam_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_roam_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + ndev_vif->sta.roam_in_progress = true; + return r; +} + +int slsi_mlme_set_cached_channels(struct slsi_dev *sdev, struct net_device *dev, u32 channels_count, u8 *channels) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + u8 *p; + int r = 0; + size_t channel_ie = 0; + int i; + const u8 channels_list_ie_header[] = { + 0xDD, /* Element ID: Vendor Specific */ + 0x05, /* Length: actual length will be updated later */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x01, /* OUI Type: Scan parameters */ + 0x02 /* OUI Subtype: channel list */ + }; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + + if (channels_count) { + channel_ie += 6 + (channels_count * SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + req = fapi_alloc(mlme_set_cached_channels_req, MLME_SET_CACHED_CHANNELS_REQ, ndev_vif->ifnum, channel_ie); + } else { + req = fapi_alloc(mlme_set_cached_channels_req, MLME_SET_CACHED_CHANNELS_REQ, ndev_vif->ifnum, 0); + } + if (!req) + return -ENOMEM; + + if (channels_count) { + u16 freq_fw_unit; + u8 *channels_list_ie = fapi_append_data(req, channels_list_ie_header, sizeof(channels_list_ie_header)); + + if (!channels_list_ie) { + SLSI_WARN(sdev, "channel list IE append failed\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + + for (i = 0; i < channels_count; i++) { + SLSI_NET_DBG3(dev, SLSI_MLME, "request for channels %d\n", channels[i]); + p = fapi_append_data(req, NULL, SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE); + if (!p) { + slsi_kfree_skb(req); + return -EINVAL; + } + freq_fw_unit = 2 * ieee80211_channel_to_frequency(channels[i], (channels[i] <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); + freq_fw_unit = cpu_to_le16(freq_fw_unit); + memcpy(p, &freq_fw_unit, sizeof(freq_fw_unit)); + + p[2] = FAPI_SCANPOLICY_2_4GHZ | FAPI_SCANPOLICY_5GHZ; + + channels_list_ie[1] += SLSI_SCAN_CHANNEL_DESCRIPTOR_SIZE; + } + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_set_cached_channels_req(vif:%d)\n", ndev_vif->ifnum); + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_CACHED_CHANNELS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_cached_channels_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_cached_channels_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_cached_channels_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_mlme_set_acl(struct slsi_dev *sdev, struct net_device *dev, u16 ifnum, const struct cfg80211_acl_data *params) +{ + struct sk_buff *req; + struct sk_buff *cfm; + size_t mac_acl_size = 0; + int i, r = 0; + + mac_acl_size = sizeof((params->mac_addrs[0])) * (params->n_acl_entries); + req = fapi_alloc(mlme_set_acl_req, MLME_SET_ACL_REQ, ifnum, mac_acl_size); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + fapi_set_u16(req, u.mlme_set_acl_req.entries, params->n_acl_entries); + fapi_set_u16(req, u.mlme_set_acl_req.acl_policy, params->acl_policy); + + for (i = 0; i < params->n_acl_entries; i++) + fapi_append_data(req, params->mac_addrs[i].addr, sizeof((params->mac_addrs[i]))); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_set_acl_req(vif:%u, n_acl_entries:%d)\n", ifnum, params->n_acl_entries); + + if (ifnum) + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_ACL_CFM); + else + cfm = slsi_mlme_req_cfm(sdev, NULL, req, MLME_SET_ACL_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_acl_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_acl_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_acl_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} +#endif + +int slsi_mlme_set_traffic_parameters(struct slsi_dev *sdev, struct net_device *dev, u16 user_priority, u16 medium_time, u16 minimun_data_rate, u8 *mac) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION && ndev_vif->iftype == NL80211_IFTYPE_STATION)) + return -EINVAL; + + req = fapi_alloc(mlme_set_traffic_parameters_req, MLME_SET_TRAFFIC_PARAMETERS_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_set_traffic_parameters_req.user_priority, user_priority); + fapi_set_u16(req, u.mlme_set_traffic_parameters_req.medium_time, medium_time); + fapi_set_u16(req, u.mlme_set_traffic_parameters_req.minimum_data_rate, minimun_data_rate); + + if (mac) + fapi_set_memcpy(req, u.mlme_set_traffic_parameters_req.peer_address, mac); + else + fapi_set_memset(req, u.mlme_set_traffic_parameters_req.peer_address, 0); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_set_traffic_parameters_req(vif:%u, user_priority:%d, medium_time:%d)\n", ndev_vif->ifnum, user_priority, medium_time); + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_TRAFFIC_PARAMETERS_CFM); + if (!rx) + return -EIO; + + if (fapi_get_u16(rx, u.mlme_set_traffic_parameters_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_traffic_parameters_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_set_traffic_parameters_cfm.result_code)); + r = -EINVAL; + } + + return r; +} + +int slsi_mlme_del_traffic_parameters(struct slsi_dev *sdev, struct net_device *dev, u16 user_priority) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION && ndev_vif->iftype == NL80211_IFTYPE_STATION)) + return -EINVAL; + + req = fapi_alloc(mlme_del_traffic_parameters_req, MLME_DEL_TRAFFIC_PARAMETERS_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_del_traffic_parameters_req.user_priority, user_priority); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_del_traffic_parameters_req(vif:%u, user_priority:%d)\n", ndev_vif->ifnum, user_priority); + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_DEL_TRAFFIC_PARAMETERS_CFM); + if (!rx) + return -EIO; + + if (fapi_get_u16(rx, u.mlme_del_traffic_parameters_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_del_traffic_parameters_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_del_traffic_parameters_cfm.result_code)); + r = -EINVAL; + } + + return r; +} + +int slsi_mlme_set_ext_capab(struct slsi_dev *sdev, struct net_device *dev, struct slsi_mib_value *mib_val) +{ + struct slsi_mib_data mib_data = { 0, NULL }; + int error = 0; + + error = slsi_mib_encode_octet(&mib_data, SLSI_PSID_UNIFI_EXTENDED_CAPABILITIES, mib_val->u.octetValue.dataLength, mib_val->u.octetValue.data, 0); + if (error != SLSI_MIB_STATUS_SUCCESS) { + error = -ENOMEM; + goto exit; + } + + if (WARN_ON(mib_data.dataLength == 0)) { + error = -EINVAL; + goto exit; + } + + error = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + kfree(mib_data.data); + + if (!error) + return 0; + +exit: + SLSI_ERR(sdev, "Error in setting ext capab. error = %d\n", error); + return error; +} + +int slsi_mlme_set_hs2_ext_cap(struct slsi_dev *sdev, struct net_device *dev, const u8 *ies, int ie_len) +{ + struct slsi_mib_entry mib_entry; + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_data mibrsp = { 0, NULL }; + const u8 *ext_capab_ie; + int r = 0; + int rx_length = 0; + int len = 0; + + slsi_mib_encode_get(&mibreq, SLSI_PSID_UNIFI_EXTENDED_CAPABILITIES, 0); + + /* 5 (header) + 9 (data) + 2 (mlme expects 16 (??))*/ + mibrsp.dataLength = 16; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + + if (!mibrsp.data) { + SLSI_ERR(sdev, "Failed to alloc for Mib response\n"); + kfree(mibreq.data); + return -ENOMEM; + } + + r = slsi_mlme_get(sdev, NULL, mibreq.data, mibreq.dataLength, + mibrsp.data, mibrsp.dataLength, &rx_length); + kfree(mibreq.data); + + if (r == 0) { + mibrsp.dataLength = rx_length; + len = slsi_mib_decode(&mibrsp, &mib_entry); + if (len == 0) { + SLSI_ERR(sdev, "Mib decode error\n"); + r = -EINVAL; + goto exit; + } + } else { + SLSI_NET_DBG1(dev, SLSI_MLME, "Mib read failed (error: %d)\n", r); + goto exit; + } + + ext_capab_ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ies, ie_len); + + if (ext_capab_ie) { + u8 ext_capab_ie_len = ext_capab_ie[1]; + + ext_capab_ie += 2; /* skip the EID and length*/ + + /*BSS Transition bit is bit 19 ,ie length must be >= 3 */ + if ((ext_capab_ie_len >= 3) && (ext_capab_ie[2] & SLSI_WLAN_EXT_CAPA2_BSS_TRANSISITION_ENABLED)) + mib_entry.value.u.octetValue.data[2] |= SLSI_WLAN_EXT_CAPA2_BSS_TRANSISITION_ENABLED; + else + mib_entry.value.u.octetValue.data[2] &= ~SLSI_WLAN_EXT_CAPA2_BSS_TRANSISITION_ENABLED; + + /*interworking bit is bit 31 ,ie length must be >= 4 */ + if ((ext_capab_ie_len >= 4) && (ext_capab_ie[3] & SLSI_WLAN_EXT_CAPA3_INTERWORKING_ENABLED)) + mib_entry.value.u.octetValue.data[3] |= SLSI_WLAN_EXT_CAPA3_INTERWORKING_ENABLED; + else + mib_entry.value.u.octetValue.data[3] &= ~SLSI_WLAN_EXT_CAPA3_INTERWORKING_ENABLED; + + /*QoS MAP is bit 32 ,ie length must be >= 5 */ + if ((ext_capab_ie_len >= 5) && (ext_capab_ie[4] & SLSI_WLAN_EXT_CAPA4_QOS_MAP_ENABLED)) + mib_entry.value.u.octetValue.data[4] |= SLSI_WLAN_EXT_CAPA4_QOS_MAP_ENABLED; + else + mib_entry.value.u.octetValue.data[4] &= ~SLSI_WLAN_EXT_CAPA4_QOS_MAP_ENABLED; + + /*WNM- Notification bit is bit 46 ,ie length must be >= 6 */ + if ((ext_capab_ie_len >= 6) && (ext_capab_ie[5] & SLSI_WLAN_EXT_CAPA5_WNM_NOTIF_ENABLED)) + mib_entry.value.u.octetValue.data[5] |= SLSI_WLAN_EXT_CAPA5_WNM_NOTIF_ENABLED; + else + mib_entry.value.u.octetValue.data[5] &= ~SLSI_WLAN_EXT_CAPA5_WNM_NOTIF_ENABLED; + } else { + mib_entry.value.u.octetValue.data[2] &= ~SLSI_WLAN_EXT_CAPA2_BSS_TRANSISITION_ENABLED; + mib_entry.value.u.octetValue.data[3] &= ~SLSI_WLAN_EXT_CAPA3_INTERWORKING_ENABLED; + mib_entry.value.u.octetValue.data[4] &= ~SLSI_WLAN_EXT_CAPA4_QOS_MAP_ENABLED; + mib_entry.value.u.octetValue.data[5] &= ~SLSI_WLAN_EXT_CAPA5_WNM_NOTIF_ENABLED; + } + + r = slsi_mlme_set_ext_capab(sdev, dev, &mib_entry.value); +exit: + kfree(mibrsp.data); + return r; +} + +int slsi_mlme_tdls_peer_resp(struct slsi_dev *sdev, struct net_device *dev, u16 pid, u16 tdls_event) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + + req = fapi_alloc(mlme_tdls_peer_res, MLME_TDLS_PEER_RES, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_tdls_peer_res.peer_index, pid); + fapi_set_u16(req, u.mlme_tdls_peer_res.tdls_event, tdls_event); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_tdls_peer_res(vif:%d)\n", ndev_vif->ifnum); + cfm = slsi_mlme_req_no_cfm(sdev, dev, req); + WARN_ON(cfm); + + return 0; +} + +int slsi_mlme_tdls_action(struct slsi_dev *sdev, struct net_device *dev, const u8 *peer, int action, u16 center_freq, u16 chan_info) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_tdls_action_req(action:%u)\n", action); + req = fapi_alloc(mlme_tdls_action_req, MLME_TDLS_ACTION_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + center_freq = SLSI_FREQ_HOST_TO_FW(center_freq); + + fapi_set_memcpy(req, u.mlme_tdls_action_req.peer_sta_address, peer); + fapi_set_u16(req, u.mlme_tdls_action_req.tdls_action, action); + fapi_set_u16(req, u.mlme_tdls_action_req.channel_frequency, center_freq); + fapi_set_u16(req, u.mlme_tdls_action_req.channel_information, chan_info); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_TDLS_ACTION_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_tdls_action_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_tdls_action_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_tdls_action_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + + return r; +} + +int slsi_mlme_reassociate(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + if (WARN_ON(!ndev_vif->activated)) + return -EINVAL; + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_reassoc_req(vif:%u)\n", ndev_vif->ifnum); + req = fapi_alloc(mlme_reassociate_req, MLME_REASSOCIATE_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_REASSOCIATE_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_reassociate_cfm.result_code) == FAPI_RESULTCODE_HOST_REQUEST_SUCCESS) { + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_reassoc_cfm(result:0x%04x)\n", + fapi_get_u16(cfm, u.mlme_reassociate_cfm.result_code)); + } else { + SLSI_NET_ERR(dev, "mlme_reassoc_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_reassociate_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} + +void slsi_mlme_reassociate_resp(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip sending signal, WlanLite FW does not support MLME_REASSOCIATE_RESP\n"); + return; + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_reassociate_resp(vif:%d)\n", ndev_vif->ifnum); + req = fapi_alloc(mlme_reassociate_res, MLME_REASSOCIATE_RES, ndev_vif->ifnum, 0); + if (!req) + return; + + cfm = slsi_mlme_req_no_cfm(sdev, dev, req); + WARN_ON(cfm); +} + +int slsi_mlme_add_range_req(struct slsi_dev *sdev, u8 count, + struct slsi_rtt_config *nl_rtt_params, u16 rtt_id, u16 vif_idx, u8 *source_addr) +{ + struct sk_buff *req; + struct sk_buff *rx; + int r = 0, i; + size_t alloc_data_size = 0; + u8 fapi_ie_generic[] = { 0xdd, 0x1c, 0x00, 0x16, 0x32, 0x0a, 0x01 }; + /* calculate data size */ + alloc_data_size += count * (fapi_ie_generic[1] + 2); + + req = fapi_alloc(mlme_add_range_req, MLME_ADD_RANGE_REQ, 0, alloc_data_size); + if (!req) { + SLSI_ERR(sdev, "failed to alloc %zd\n", alloc_data_size); + return -ENOMEM; + } + SLSI_DBG2(sdev, SLSI_MLME, "count:%d allocated data size: %d, source_addr:%pM\n", + count, alloc_data_size, source_addr); + /*fill the data */ + fapi_set_u16(req, u.mlme_add_range_req.vif, vif_idx); + fapi_set_u16(req, u.mlme_add_range_req.rtt_id, rtt_id); + fapi_set_memcpy(req, u.mlme_add_range_req.device_address, source_addr); + for (i = 0; i < count; i++) { + fapi_append_data(req, fapi_ie_generic, sizeof(fapi_ie_generic)); + fapi_append_data(req, nl_rtt_params[i].peer_addr, ETH_ALEN); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].type, 2); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].channel_freq, 2); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].burst_period, 1); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].num_burst, 1); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].num_frames_per_burst, 1); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].num_retries_per_ftmr, 1); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].burst_duration, 1); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].preamble, 2); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].bw, 2); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].LCI_request, 2); + fapi_append_data(req, (u8 *)&nl_rtt_params[i].LCR_request, 2); + } + rx = slsi_mlme_req_cfm(sdev, NULL, req, MLME_ADD_RANGE_CFM); + SLSI_DBG2(sdev, SLSI_MLME, "(After mlme req cfm for rtt config)\n"); + if (!rx) + return -EIO; + if (fapi_get_u16(rx, u.mlme_add_range_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_ERR(sdev, "mlme_add_range_cfm(ERROR:0x%04x)", + fapi_get_u16(rx, u.mlme_add_range_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(rx); + return r; +} + +bool slsi_del_range_cfm_validate(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *cfm) +{ + int result = fapi_get_u16(cfm, u.mlme_del_range_cfm.result_code); + bool r = false; + + SLSI_UNUSED_PARAMETER(sdev); + + if (WARN_ON(!dev)) + goto exit; + + if (result == FAPI_RESULTCODE_SUCCESS) + r = true; + else + SLSI_NET_ERR(dev, "mlme_del_range_cfm(result:0x%04x) ERROR\n", result); + +exit: + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_del_range_req(struct slsi_dev *sdev, struct net_device *dev, u16 count, + u8 *addr, u16 rtt_id) +{ + struct sk_buff *req; + struct sk_buff *rx; + u16 *rtt_vif_idx = sdev->rtt_vif; + int r = 0, i; + size_t alloc_data_size = 0; + + /* calculate data size-->2 bytes for vif */ + alloc_data_size += count * sizeof(ETH_ALEN); + /* Alloc data size */ + req = fapi_alloc(mlme_del_range_req, MLME_DEL_RANGE_REQ, 0, alloc_data_size); + if (!req) { + SLSI_ERR(sdev, "failed to alloc %zd\n", alloc_data_size); + return -ENOMEM; + } + /*fill the data */ + fapi_set_u16(req, u.mlme_del_range_req.vif, rtt_vif_idx[rtt_id]); + fapi_set_u16(req, u.mlme_del_range_req.rtt_id, rtt_id); + fapi_set_u16(req, u.mlme_del_range_req.entries, count); + for (i = 0; i < count; i++) + fapi_append_data(req, &addr[i * ETH_ALEN], ETH_ALEN); + + rx = slsi_mlme_req_cfm_ind(sdev, dev, req, MLME_DEL_RANGE_CFM, MLME_RANGE_IND, slsi_del_range_cfm_validate); + sdev->rtt_vif[rtt_id] = -1; + if (!rx) { + SLSI_NET_ERR(dev, "mlme_del_range_cfm() ERROR\n"); + slsi_kfree_skb(rx); + return -EINVAL; + } + slsi_kfree_skb(rx); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE +#define SLSI_FAPI_EPNO_NETWORK_MIN_SIZE (3) +int slsi_mlme_set_pno_list(struct slsi_dev *sdev, int count, + struct slsi_epno_param *epno_param, struct slsi_epno_hs2_param *epno_hs2_param) +{ + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + size_t alloc_data_size = 0; + u32 i, j; + u8 fapi_ie_generic[] = { 0xdd, 0, 0x00, 0x16, 0x32, 0x01, 0x00 }; + u8 *buff_ptr, *ie_start_pos; + + if (count) { + /* calculate data size */ + if (epno_param) { + alloc_data_size += sizeof(fapi_ie_generic) + SLSI_FAPI_EPNO_NETWORK_MIN_SIZE * count + 11; + for (i = 0; i < count; i++) + alloc_data_size += epno_param->epno_ssid[i].ssid_len; + } else if (epno_hs2_param) { + for (i = 0; i < count; i++) { + /* fapi_ie_generic + Network_block_ID(1) + Realm_length(1) + realm_data(x) + * + Roaming_Consortium_Count(1) + Roaming Consortium data(16 * 8) + + * PLMN length(1) + PLMN data(6) + */ + if (strlen(epno_hs2_param->realm)) + alloc_data_size += sizeof(fapi_ie_generic) + 1 + 1 + (strlen(epno_hs2_param->realm) + 1) + + 1 + 16 * 8 + 1 + 6; + else + alloc_data_size += sizeof(fapi_ie_generic) + 1 + 1 + 0 + + 1 + 16 * 8 + 1 + 6; + } + } + } + + /* Alloc data size */ + req = fapi_alloc(mlme_set_pno_list_req, MLME_SET_PNO_LIST_REQ, 0, alloc_data_size); + if (!req) { + SLSI_ERR(sdev, "failed to alloc %zd\n", alloc_data_size); + return -ENOMEM; + } + if (count) { + /* Fill data */ + if (epno_param) { + fapi_ie_generic[1] = alloc_data_size - 2; + fapi_ie_generic[6] = 9; /* OUI */ + fapi_append_data(req, fapi_ie_generic, sizeof(fapi_ie_generic)); + fapi_append_data(req, (u8 *)epno_param, (sizeof(*epno_param) - 1)); + for (i = 0; i < count; i++) { + fapi_append_data(req, (u8 *)&epno_param->epno_ssid[i].flags, 2); + fapi_append_data(req, (u8 *)&epno_param->epno_ssid[i].ssid_len, 1); + fapi_append_data(req, (u8 *)epno_param->epno_ssid[i].ssid, + epno_param->epno_ssid[i].ssid_len); + } + } else if (epno_hs2_param) { + u8 realm_length; + u8 roaming_consortium_count = 16; + u8 plmn_length = 6; + u8 plmn_digit[6]; + + fapi_ie_generic[6] = 0x10; /* OUI subtype = Passpoint Network */ + for (i = 0; i < count; i++) { + buff_ptr = fapi_append_data(req, fapi_ie_generic, sizeof(fapi_ie_generic)); + if (!buff_ptr) { + SLSI_ERR(sdev, "failed append data\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + ie_start_pos = buff_ptr; + + fapi_append_data(req, (u8 *)&epno_hs2_param[i].id, 1); + + realm_length = strlen(epno_hs2_param[i].realm); + if (realm_length) { + realm_length++; + fapi_append_data(req, &realm_length, 1); + fapi_append_data(req, epno_hs2_param[i].realm, realm_length); + } else { + fapi_append_data(req, &realm_length, 1); + } + + fapi_append_data(req, &roaming_consortium_count, 1); + fapi_append_data(req, (u8 *)&epno_hs2_param[i].roaming_consortium_ids, 16 * 8); + + fapi_append_data(req, &plmn_length, 1); + for (j = 0; j < 3; j++) { + plmn_digit[j * 2] = epno_hs2_param[i].plmn[i] & 0x0F; + plmn_digit[(j * 2) + 1] = epno_hs2_param[i].plmn[i] & 0xF0 >> 4; + } + buff_ptr = fapi_append_data(req, plmn_digit, sizeof(plmn_digit)); + if (!buff_ptr) { + SLSI_ERR(sdev, "failed append data\n"); + slsi_kfree_skb(req); + return -EINVAL; + } + + buff_ptr += sizeof(plmn_digit); + ie_start_pos[1] = buff_ptr - ie_start_pos - 2; /* fill ie length field */ + } + } + } + + /* Send signal */ + /* Use the Global sig_wait not the Interface specific for mlme-set-pno.list */ + rx = slsi_mlme_req_cfm(sdev, NULL, req, MLME_SET_PNO_LIST_CFM); + if (!rx) + return -EIO; + if (fapi_get_u16(rx, u.mlme_set_pno_list_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_ERR(sdev, "mlme_set_pno_list_cfm(ERROR:0x%04x)", + fapi_get_u16(rx, u.mlme_set_pno_list_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(rx); + return r; +} + +int slsi_mlme_start_link_stats_req(struct slsi_dev *sdev, u16 mpdu_size_threshold, bool aggressive_stats_enabled) +{ + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + req = fapi_alloc(mlme_start_link_statistics_req, MLME_START_LINK_STATISTICS_REQ, 0, 0); + if (!req) { + SLSI_ERR(sdev, "memory allocation failed for signal\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_start_link_statistics_req.mpdu_size_threshold, mpdu_size_threshold); + fapi_set_u16(req, u.mlme_start_link_statistics_req.aggressive_statistics_gathering_enabled, + aggressive_stats_enabled); + + SLSI_DBG2(sdev, SLSI_MLME, "(mpdu_size_threshold:%d, aggressive_stats_enabled:%d)\n", + mpdu_size_threshold, aggressive_stats_enabled); + cfm = slsi_mlme_req_cfm(sdev, NULL, req, MLME_START_LINK_STATISTICS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_start_link_statistics_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_ERR(sdev, "mlme_start_link_statistics_cfm (result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_start_link_statistics_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_stop_link_stats_req(struct slsi_dev *sdev, u16 stats_stop_mask) +{ + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + req = fapi_alloc(mlme_stop_link_statistics_req, MLME_STOP_LINK_STATISTICS_REQ, 0, 0); + if (!req) { + SLSI_ERR(sdev, "memory allocation failed for signal\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_stop_link_statistics_req.statistics_stop_bitmap, stats_stop_mask); + + SLSI_DBG2(sdev, SLSI_MLME, "statistics_stop_bitmap:%d\n", stats_stop_mask); + cfm = slsi_mlme_req_cfm(sdev, NULL, req, MLME_STOP_LINK_STATISTICS_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_stop_link_statistics_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_ERR(sdev, "mlme_stop_link_statistics_cfm (result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_stop_link_statistics_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} +#endif + +int slsi_mlme_set_rssi_monitor(struct slsi_dev *sdev, struct net_device *dev, u8 enable, s8 low_rssi_threshold, s8 high_rssi_threshold) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_set_rssi_monitor(vif:%u), enable =%d, low_rssi_threshold = %d,high_rssi_threshold =%d\n", + ndev_vif->ifnum, enable, low_rssi_threshold, high_rssi_threshold); + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + req = fapi_alloc(mlme_monitor_rssi_req, MLME_MONITOR_RSSI_REQ, ndev_vif->ifnum, 0); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_monitor_rssi_req.rssi_monitoring_enabled, enable); + fapi_set_u16(req, u.mlme_monitor_rssi_req.low_rssi_threshold, low_rssi_threshold); + fapi_set_u16(req, u.mlme_monitor_rssi_req.high_rssi_threshold, high_rssi_threshold); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_MONITOR_RSSI_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_monitor_rssi_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_monitor_rssi_cfm(result:0x%04x) ERROR\n", fapi_get_u16(cfm, u.mlme_monitor_rssi_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +struct slsi_mib_value *slsi_read_mibs(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_mib_get_entry *mib_entries, int mib_count, struct slsi_mib_data *mibrsp) +{ + struct slsi_mib_data mibreq = { 0, NULL }; + struct slsi_mib_value *values; + int rx_length, r; + + r = slsi_mib_encode_get_list(&mibreq, mib_count, mib_entries); + if (r != SLSI_MIB_STATUS_SUCCESS) { + SLSI_WARN(sdev, "slsi_mib_encode_get_list fail %d\n", r); + return NULL; + } + + r = slsi_mlme_get(sdev, dev, mibreq.data, mibreq.dataLength, mibrsp->data, mibrsp->dataLength, &rx_length); + kfree(mibreq.data); + + if (r != 0) { + SLSI_ERR(sdev, "Mib (err:%d)\n", r); + return NULL; + } + + mibrsp->dataLength = (u32)rx_length; + values = slsi_mib_decode_get_list(mibrsp, mib_count, mib_entries); + if (!values) + SLSI_WARN(sdev, "decode error\n"); + return values; +} + +int slsi_mlme_set_ctwindow(struct slsi_dev *sdev, struct net_device *dev, unsigned int ct_param) +{ + struct netdev_vif *ndev_vif; + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_set_ctwindow(ct_param = %d)\n", ct_param); + + ndev_vif = netdev_priv(dev); + + req = fapi_alloc(mlme_set_ctwindow_req, MLME_SET_CTWINDOW_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_set_ctwindow_req.ctwindow, ct_param); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_CTWINDOW_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_ctwindow_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_ctwindow_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_ctwindow_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_set_p2p_noa(struct slsi_dev *sdev, struct net_device *dev, unsigned int noa_count, + unsigned int interval, unsigned int duration) +{ + struct netdev_vif *ndev_vif; + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_set_noa_req(noa_count = %d, interval = %d, duration = %d)\n", + noa_count, interval, duration); + + ndev_vif = netdev_priv(dev); + + req = fapi_alloc(mlme_set_noa_req, MLME_SET_NOA_REQ, ndev_vif->ifnum, 0); + if (!req) + return -ENOMEM; + + fapi_set_u16(req, u.mlme_set_noa_req.request_id, SLSI_NOA_CONFIG_REQUEST_ID); + fapi_set_u16(req, u.mlme_set_noa_req.noa_count, noa_count); + if (!interval) + fapi_set_u32(req, u.mlme_set_noa_req.interval, (1 * 1024 * ndev_vif->ap.beacon_interval)); + else + fapi_set_u32(req, u.mlme_set_noa_req.interval, interval * 1000); + fapi_set_u32(req, u.mlme_set_noa_req.duration, duration * 1000); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_SET_NOA_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_set_noa_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_set_noa_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_set_noa_cfm.result_code)); + r = -EINVAL; + } + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_set_host_state(struct slsi_dev *sdev, struct net_device *dev, u8 host_state) +{ + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_INFO(dev, "Skip MLME_HOST_STATE_REQ in wlanlite mode\n"); + return -EOPNOTSUPP; + } + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_set_host_state(state =%d)\n", host_state); + + req = fapi_alloc(mlme_host_state_req, MLME_HOST_STATE_REQ, 0, 0); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_host_state_req.host_state, host_state); + + cfm = slsi_mlme_req_cfm(sdev, NULL, req, MLME_HOST_STATE_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_host_state_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +int slsi_mlme_read_apf_request(struct slsi_dev *sdev, struct net_device *dev, u8 **host_dst, int *datalen) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_ERR(sdev, "ndev_vif is not activated\n"); + r = -EINVAL; + goto exit; + } + + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + SLSI_ERR(sdev, "vif_type is not FAPI_VIFTYPE_STATION\n"); + r = -EINVAL; + goto exit; + } + + req = fapi_alloc(mlme_read_apf_req, MLME_READ_APF_REQ, ndev_vif->ifnum, 0); + if (!req) { + r = -ENOMEM; + goto exit; + } + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_read_apf_req(vif:%u)\n", ndev_vif->ifnum); + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_READ_APF_CFM); + if (!rx) { + r = -EIO; + goto exit; + } + + if (fapi_get_u16(rx, u.mlme_read_apf_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_read_apf_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_read_apf_cfm.result_code)); + r = -EINVAL; + } + + *datalen = fapi_get_datalen(rx); + *host_dst = fapi_get_data(rx); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +int slsi_mlme_install_apf_request(struct slsi_dev *sdev, struct net_device *dev, + u8 *program, u32 program_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_ERR(sdev, "ndev_vif is not activated\n"); + r = -EINVAL; + goto exit; + } + + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + SLSI_ERR(sdev, "vif_type is not FAPI_VIFTYPE_STATION\n"); + r = -EINVAL; + goto exit; + } + + req = fapi_alloc(mlme_install_apf_req, MLME_INSTALL_APF_REQ, ndev_vif->ifnum, program_len); + if (!req) { + r = -ENOMEM; + goto exit; + } + + /* filter_mode will be "don't care" for FW */ + fapi_set_u16(req, u.mlme_install_apf_req.filter_mode, FAPI_APFFILTERMODE_SUSPEND); + fapi_append_data(req, program, program_len); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_install_apf_req(vif:%u, filter_mode:%d)\n", + ndev_vif->ifnum, FAPI_APFFILTERMODE_SUSPEND); + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_INSTALL_APF_CFM); + if (!rx) { + r = -EIO; + goto exit; + } + + if (fapi_get_u16(rx, u.mlme_install_apf_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_install_apf_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_install_apf_cfm.result_code)); + r = -EINVAL; + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +int slsi_mlme_arp_detect_request(struct slsi_dev *sdev, struct net_device *dev, u16 action, u8 *target_ipaddr) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *rx; + int r = 0; + u32 ipaddress = 0x0; + int i = 0; + + if (!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)) { + SLSI_ERR(sdev, "ndev_vif mutex is not locked\n"); + r = -EINVAL; + goto exit; + } + + if (!ndev_vif->activated) { + SLSI_ERR(sdev, "ndev_vif is not activated\n"); + r = -EINVAL; + goto exit; + } + + if ((ndev_vif->vif_type != FAPI_VIFTYPE_STATION) && (ndev_vif->iftype == NL80211_IFTYPE_STATION)) { + SLSI_ERR(sdev, "vif_type is not FAPI_VIFTYPE_STATION\n"); + r = -EINVAL; + goto exit; + } + + req = fapi_alloc(mlme_arp_detect_req, MLME_ARP_DETECT_REQ, ndev_vif->ifnum, 0); + if (!req) { + r = -ENOMEM; + goto exit; + } + + for (i = 0; i < 4; i++) + ipaddress = (ipaddress << 8) | ((unsigned char)target_ipaddr[i]); + ipaddress = htonl(ipaddress); + + fapi_set_u16(req, u.mlme_arp_detect_req.arp_detect_action, action); + fapi_append_data(req, (const u8 *)&ipaddress, 4); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_arp_detect_req(vif:%u, action:%d IP Address:%d.%d.%d.%d)\n", + ndev_vif->ifnum, action, ndev_vif->target_ip_addr[0], ndev_vif->target_ip_addr[1], + ndev_vif->target_ip_addr[2], ndev_vif->target_ip_addr[3]); + rx = slsi_mlme_req_cfm(sdev, dev, req, MLME_ARP_DETECT_CFM); + if (!rx) { + r = -EIO; + goto exit; + } + + if (fapi_get_u16(rx, u.mlme_arp_detect_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "mlme_arp_detect_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(rx, u.mlme_arp_detect_cfm.result_code)); + r = -EINVAL; + } + +exit: + return r; +} +#endif + +#ifdef CONFIG_SCSC_WLAN_DEBUG +#define SLSI_TEST_CONFIG_MONITOR_MODE_DESCRIPTOR_SIZE (12) +int slsi_test_sap_configure_monitor_mode(struct slsi_dev *sdev, struct net_device *dev, struct cfg80211_chan_def *chandef) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + u8 *p = NULL; + size_t alloc_data_size = 0; + u16 center_freq1; + u16 center_freq2; + u16 chan_info; + int r = 0; + + const u8 monitor_config_ie_header[] = { + 0xDD, /* Element ID: Vendor Specific */ + 0x11, /* Length */ + 0x00, 0x16, 0x32, /* OUI: Samsung Electronics Co. */ + 0x10, /* OUI Type: Monitor mode parameters */ + 0x01 /* OUI Subtype: configuration */ + }; + + if (slsi_is_test_mode_enabled()) { + SLSI_NET_WARN(dev, "WlanLite: NOT supported\n"); + return -EOPNOTSUPP; + } + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + + chan_info = slsi_get_chann_info(sdev, chandef); + SLSI_NET_DBG2(dev, SLSI_MLME, "test_configure_monitor_mode_req(center_freq1:%u, chan_info:%u, center_freq2:%u)\n", + chandef->center_freq1, + chan_info, + chandef->center_freq2); + + center_freq1 = SLSI_FREQ_HOST_TO_FW(chandef->center_freq1); + center_freq1 = cpu_to_le16(center_freq1); + center_freq2 = SLSI_FREQ_HOST_TO_FW(chandef->center_freq2); + center_freq2 = cpu_to_le16(center_freq2); + + alloc_data_size = sizeof(monitor_config_ie_header) + SLSI_TEST_CONFIG_MONITOR_MODE_DESCRIPTOR_SIZE; + + req = fapi_alloc(test_configure_monitor_mode_req, TEST_CONFIGURE_MONITOR_MODE_REQ, ndev_vif->ifnum, alloc_data_size); + if (!req) { + SLSI_NET_ERR(dev, "failed to alloc Monitor mode request (len:%d)\n", alloc_data_size); + return -ENOMEM; + } + + fapi_append_data(req, monitor_config_ie_header, sizeof(monitor_config_ie_header)); + fapi_append_data(req, (const u8 *)¢er_freq1, 2); + fapi_append_data(req, (const u8 *)&chan_info, 2); + p = fapi_append_data(req, (const u8 *)¢er_freq2, 2); + + /* MAC address filtering is not supported yet; so fill in zeros */ + memset(p, 0, 6); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, TEST_CONFIGURE_MONITOR_MODE_CFM); + if (!cfm) { + SLSI_NET_ERR(dev, "failed to receive Confirm\n"); + return -EIO; + } + + if (fapi_get_u16(cfm, u.mlme_set_channel_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "test_configure_monitor_mode_cfm(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.test_configure_monitor_mode_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +#endif diff --git a/drivers/net/wireless/scsc/mlme.h b/drivers/net/wireless/scsc/mlme.h new file mode 100755 index 000000000000..ad59afc8f3ad --- /dev/null +++ b/drivers/net/wireless/scsc/mlme.h @@ -0,0 +1,280 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_MLME_H__ +#define __SLSI_MLME_H__ + +#include "dev.h" +#include "mib.h" + +enum slsi_ac_index_wmm_pe { + AC_BE, + AC_BK, + AC_VI, + AC_VO +}; + +#define SLSI_FREQ_FW_TO_HOST(f) ((f) / 2) +#define SLSI_FREQ_HOST_TO_FW(f) ((f) * 2) + +#define SLSI_SINFO_MIB_ACCESS_TIMEOUT (1000) /* 1 sec timeout */ + +#define SLSI_WLAN_EID_VENDOR_SPECIFIC 0xdd +#define SLSI_WLAN_EID_INTERWORKING 107 + +#define SLSI_WLAN_OUI_TYPE_WFA_HS20_IND 0x10 +#define SLSI_WLAN_OUI_TYPE_WFA_OSEN 0x12 + +/*Extended capabilities bytes*/ +#define SLSI_WLAN_EXT_CAPA2_BSS_TRANSISITION_ENABLED (1 << 3) +#define SLSI_WLAN_EXT_CAPA3_INTERWORKING_ENABLED (1 << 7) +#define SLSI_WLAN_EXT_CAPA4_QOS_MAP_ENABLED (1 << 0) +#define SLSI_WLAN_EXT_CAPA5_WNM_NOTIF_ENABLED (1 << 6) +#define SLSI_WLAN_EXT_CAPA2_QBSS_LOAD_ENABLED BIT(7) +#define SLSI_WLAN_EXT_CAPA1_PROXY_ARP_ENABLED BIT(4) +#define SLSI_WLAN_EXT_CAPA2_TFS_ENABLED BIT(0) +#define SLSI_WLAN_EXT_CAPA2_WNM_SLEEP_ENABLED BIT(1) +#define SLSI_WLAN_EXT_CAPA2_TIM_ENABLED BIT(2) +#define SLSI_WLAN_EXT_CAPA2_DMS_ENABLED BIT(4) + +/*RM Enabled Capabilities Bytes*/ +#define SLSI_WLAN_RM_CAPA0_LINK_MEASUREMENT_ENABLED BIT(0) +#define SLSI_WLAN_RM_CAPA0_NEIGHBOR_REPORT_ENABLED BIT(1) +#define SLSI_WLAN_RM_CAPA0_PASSIVE_MODE_ENABLED BIT(4) +#define SLSI_WLAN_RM_CAPA0_ACTIVE_MODE_ENABLED BIT(5) +#define SLSI_WLAN_RM_CAPA0_TABLE_MODE_ENABLED BIT(6) + + +#define SLSI_AP_EXT_CAPAB_IE_LEN_MAX 12 /* EID (1) + Len (1) + Ext Capab (8) */ + +#define SLSI_SCAN_DONE_IND_WAIT_TIMEOUT 40000 /* 40 seconds */ + +/* WLAN_EID_COUNTRY available from kernel version 3.7 */ +#ifndef WLAN_EID_COUNTRY +#define WLAN_EID_COUNTRY 7 +#endif + +/* P2P (Wi-Fi Direct) */ +#define SLSI_P2P_WILDCARD_SSID "DIRECT-" +#define SLSI_P2P_WILDCARD_SSID_LENGTH 7 +#define SLSI_P2P_SOCIAL_CHAN_COUNT 3 + +/* A join scan with P2P GO SSID can come and hence the SSID length comparision should include >= */ +#define SLSI_IS_P2P_SSID(ssid, ssid_len) ((ssid_len >= SLSI_P2P_WILDCARD_SSID_LENGTH) && \ + (memcmp(ssid, SLSI_P2P_WILDCARD_SSID, SLSI_P2P_WILDCARD_SSID_LENGTH) == 0)) + +/* Action frame categories for registering with firmware */ +#define SLSI_ACTION_FRAME_PUBLIC (1 << 4) +#define SLSI_ACTION_FRAME_VENDOR_SPEC_PROTECTED (1 << 30) +#define SLSI_ACTION_FRAME_VENDOR_SPEC (1 << 31) +#define SLSI_ACTION_FRAME_WMM (1 << 17) +#define SLSI_ACTION_FRAME_WNM (1 << 10) +#define SLSI_ACTION_FRAME_QOS (1 << 1) +#define SLSI_ACTION_FRAME_PROTECTED_DUAL BIT(9) +#define SLSI_ACTION_FRAME_RADIO_MEASUREMENT BIT(5) + +/* Firmware transmit rates */ +#define SLSI_TX_RATE_NON_HT_1MBPS 0x4001 +#define SLSI_TX_RATE_NON_HT_6MBPS 0x4004 +#define SLSI_ROAMING_CHANNELS_MAX 38 + +#define SLSI_WLAN_EID_WAPI 68 + +/** + * If availability_duration is set to SLSI_FW_CHANNEL_DURATION_UNSPECIFIED + * then the firmware autonomously decides how long to remain listening on + * the configured channel. + */ +#define SLSI_FW_CHANNEL_DURATION_UNSPECIFIED (0x0000) +extern struct ieee80211_supported_band slsi_band_2ghz; +extern struct ieee80211_supported_band slsi_band_5ghz; +extern struct ieee80211_sta_vht_cap slsi_vht_cap; + +/* Packet Filtering */ +#define SLSI_MAX_PATTERN_DESC 4 /* We are not using more than 4 pattern descriptors in a pkt filter*/ +#define SLSI_PKT_DESC_FIXED_LEN 2 /* offset (1) + mask length (1)*/ +#define SLSI_PKT_FILTER_ELEM_FIXED_LEN 6 /* oui(3) + oui type(1) + filter id (1) + pkt filter mode(1)*/ +#define SLSI_PKT_FILTER_ELEM_HDR_LEN (2 + SLSI_PKT_FILTER_ELEM_FIXED_LEN) /* element id + len + SLSI_PKT_FILTER_ELEM_FIXED_LEN*/ +#define SLSI_MAX_PATTERN_LENGTH 6 + +/*Default values of MIBS params for GET_STA_INFO driver private command */ +#define SLSI_DEFAULT_UNIFI_PEER_RX_RETRY_PACKETS 0 +#define SLSI_DEFAULT_UNIFI_PEER_RX_BC_MC_PACKETS 0 +#define SLSI_DEFAULT_UNIFI_PEER_BANDWIDTH -1 +#define SLSI_DEFAULT_UNIFI_PEER_NSS 0 +#define SLSI_DEFAULT_UNIFI_PEER_RSSI 1 +#define SLSI_DEFAULT_UNIFI_PEER_TX_DATA_RATE 0 + +#define SLSI_CHECK_TYPE(sdev, recv_type, exp_type) \ + do { \ + int var1 = recv_type; \ + int var2 = exp_type; \ + if (var1 != var2) { \ + SLSI_WARN(sdev, "Type mismatched, expected type: %d and received type %d ", var2, var1); \ + } \ + } while (0) + + +struct slsi_mlme_pattern_desc { + u8 offset; + u8 mask_length; + u8 mask[SLSI_MAX_PATTERN_LENGTH]; + u8 pattern[SLSI_MAX_PATTERN_LENGTH]; +}; + +struct slsi_mlme_pkt_filter_elem { + u8 header[SLSI_PKT_FILTER_ELEM_HDR_LEN]; + u8 num_pattern_desc; + struct slsi_mlme_pattern_desc pattern_desc[SLSI_MAX_PATTERN_DESC]; +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +u16 slsi_get_chann_info(struct slsi_dev *sdev, struct cfg80211_chan_def *chandef); +int slsi_check_channelization(struct slsi_dev *sdev, struct cfg80211_chan_def *chandef, + int wifi_sharing_channel_switched); +#else +u16 slsi_get_chann_info(struct slsi_dev *sdev, enum nl80211_channel_type channel_type); +int slsi_check_channelization(struct slsi_dev *sdev, enum nl80211_channel_type channel_type); +#endif + +int slsi_mlme_set_ip_address(struct slsi_dev *sdev, struct net_device *dev); +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 +int slsi_mlme_set_ipv6_address(struct slsi_dev *sdev, struct net_device *dev); +#endif +int slsi_mlme_set(struct slsi_dev *sdev, struct net_device *dev, u8 *req, int req_len); +int slsi_mlme_get(struct slsi_dev *sdev, struct net_device *dev, u8 *req, int req_len, + u8 *resp, int resp_buf_len, int *resp_len); + +int slsi_mlme_add_vif(struct slsi_dev *sdev, struct net_device *dev, u8 *interface_address, u8 *device_address); +void slsi_mlme_del_vif(struct slsi_dev *sdev, struct net_device *dev); +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +int slsi_mlme_set_forward_beacon(struct slsi_dev *sdev, struct net_device *dev, int action); +#endif +int slsi_mlme_set_channel(struct slsi_dev *sdev, struct net_device *dev, struct ieee80211_channel *chan, u16 duration, u16 interval, u16 count); +void slsi_ap_obss_scan_done_ind(struct net_device *dev, struct netdev_vif *ndev_vif); + +int slsi_mlme_unset_channel_req(struct slsi_dev *sdev, struct net_device *dev); + +u16 slsi_compute_chann_info(struct slsi_dev *sdev, u16 width, u16 center_freq0, u16 channel_freq); +/** + * slsi_mlme_add_autonomous_scan() Returns: + * 0 : Scan installed + * >0 : Scan NOT installed. Not an Error + * <0 : Scan NOT installed. Error + */ +int slsi_mlme_add_scan(struct slsi_dev *sdev, + struct net_device *dev, + u16 scan_type, + u16 report_mode, + u32 n_ssids, + struct cfg80211_ssid *ssids, + u32 n_channels, + struct ieee80211_channel *channels[], + void *gscan_param, + const u8 *ies, + u16 ies_len, + bool wait_for_ind); + +int slsi_mlme_add_sched_scan(struct slsi_dev *sdev, + struct net_device *dev, + struct cfg80211_sched_scan_request *request, + const u8 *ies, + u16 ies_len); + +int slsi_mlme_del_scan(struct slsi_dev *sdev, struct net_device *dev, u16 scan_id, bool scan_timed_out); +int slsi_mlme_start(struct slsi_dev *sdev, struct net_device *dev, u8 *bssid, struct cfg80211_ap_settings *settings, const u8 *wpa_ie_pos, const u8 *wmm_ie_pos, bool append_vht_ies); +int slsi_mlme_connect(struct slsi_dev *sdev, struct net_device *dev, struct cfg80211_connect_params *sme, struct ieee80211_channel *channel, const u8 *bssid); +int slsi_mlme_set_key(struct slsi_dev *sdev, struct net_device *dev, u16 key_id, u16 key_type, const u8 *address, struct key_params *key); +int slsi_mlme_get_key(struct slsi_dev *sdev, struct net_device *dev, u16 key_id, u16 key_type, u8 *seq, int *seq_len); + +/** + * Sends MLME-DISCONNECT-REQ and waits for the MLME-DISCONNECT-CFM + * MLME-DISCONNECT-CFM only indicates if the firmware has accepted the request (or not) + * the actual end of the disconnection is indicated by the firmware sending + * MLME-DISCONNECT-IND (following a successful MLME-DISCONNECT-CFM). + * The host has to wait for the full exchange to complete with the firmware before + * returning to cfg80211 if it made the disconnect request. Due to this, this function + * waits for both the MLME-DISCONNECT-CFM and the MLME-DISCONNECT-IND (if the + * MLME-DISCONNECT-CFM was successful) + */ +int slsi_mlme_disconnect(struct slsi_dev *sdev, struct net_device *dev, u8 *bssid, u16 reason_code, bool wait_ind); + +int slsi_mlme_req(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +struct sk_buff *slsi_mlme_req_no_cfm(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); + +struct sk_buff *slsi_mlme_req_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 ind_id); +/* Reads multiple MIB data related to station info. returns 0 if success else errno*/ +int slsi_mlme_get_sinfo_mib(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_peer *peer); + +int slsi_mlme_connect_scan(struct slsi_dev *sdev, struct net_device *dev, + u32 n_ssids, struct cfg80211_ssid *ssids, struct ieee80211_channel *channel); +int slsi_mlme_powermgt(struct slsi_dev *sdev, struct net_device *dev, u16 ps_mode); +int slsi_mlme_powermgt_unlocked(struct slsi_dev *sdev, struct net_device *dev, u16 ps_mode); +int slsi_mlme_register_action_frame(struct slsi_dev *sdev, struct net_device *dev, u32 af_bitmap_active, u32 af_bitmap_suspended); +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG +int slsi_mlme_synchronised_response(struct slsi_dev *sdev, struct net_device *dev, + struct cfg80211_external_auth_params *params); +#endif +int slsi_mlme_channel_switch(struct slsi_dev *sdev, struct net_device *dev, u16 center_freq, u16 chan_info); +int slsi_mlme_add_info_elements(struct slsi_dev *sdev, struct net_device *dev, u16 purpose, const u8 *ies, const u16 ies_len); +int slsi_mlme_send_frame_mgmt(struct slsi_dev *sdev, struct net_device *dev, const u8 *frame, int frame_len, u16 data_desc, u16 msg_type, u16 host_tag, u16 freq, u32 dwell_time, u32 period); +int slsi_mlme_send_frame_data(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 msg_type, + u16 host_tag, u32 dwell_time, u32 period); +int slsi_mlme_reset_dwell_time(struct slsi_dev *sdev, struct net_device *dev); +int slsi_mlme_set_packet_filter(struct slsi_dev *sdev, struct net_device *dev, int pkt_filter_len, u8 num_filters, struct slsi_mlme_pkt_filter_elem *pkt_filter_elems); +void slsi_mlme_connect_resp(struct slsi_dev *sdev, struct net_device *dev); +void slsi_mlme_connected_resp(struct slsi_dev *sdev, struct net_device *dev, u16 peer_index); +void slsi_mlme_roamed_resp(struct slsi_dev *sdev, struct net_device *dev); +int slsi_mlme_set_pmk(struct slsi_dev *sdev, struct net_device *dev, const u8 *pmk, u16 pmklen); +int slsi_mlme_roam(struct slsi_dev *sdev, struct net_device *dev, const u8 *bssid, u16 freq); +int slsi_mlme_set_cached_channels(struct slsi_dev *sdev, struct net_device *dev, u32 channels_count, u8 *channels); +int slsi_mlme_tdls_peer_resp(struct slsi_dev *sdev, struct net_device *dev, u16 pid, u16 tdls_event); +int slsi_mlme_tdls_action(struct slsi_dev *sdev, struct net_device *dev, const u8 *peer, int action, u16 center_freq, u16 chan_info); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +int slsi_mlme_set_acl(struct slsi_dev *sdev, struct net_device *dev, u16 ifnum, const struct cfg80211_acl_data *params); +#endif +int slsi_mlme_set_traffic_parameters(struct slsi_dev *sdev, struct net_device *dev, u16 user_priority, u16 medium_time, u16 minimun_data_rate, u8 *mac); +int slsi_mlme_del_traffic_parameters(struct slsi_dev *sdev, struct net_device *dev, u16 user_priority); + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE +int slsi_mlme_set_pno_list(struct slsi_dev *sdev, int count, + struct slsi_epno_param *epno_param, struct slsi_epno_hs2_param *epno_hs2_param); +int slsi_mlme_start_link_stats_req(struct slsi_dev *sdev, u16 mpdu_size_threshold, bool aggressive_statis_enabled); +int slsi_mlme_stop_link_stats_req(struct slsi_dev *sdev, u16 stats_stop_mask); +int slsi_mlme_nan_enable(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_enable_req *hal_req); +int slsi_mlme_nan_publish(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_publish_req *hal_req, + u16 publish_id); +int slsi_mlme_nan_subscribe(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_subscribe_req *hal_req, + u16 subscribe_id); +int slsi_mlme_nan_tx_followup(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_hal_nan_transmit_followup_req *hal_req); +int slsi_mlme_nan_set_config(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_config_req *hal_req); +#endif + +int slsi_mlme_set_ext_capab(struct slsi_dev *sdev, struct net_device *dev, struct slsi_mib_value *mib_val); +int slsi_mlme_set_hs2_ext_cap(struct slsi_dev *sdev, struct net_device *dev, const u8 *ies, int ie_len); +int slsi_mlme_reassociate(struct slsi_dev *sdev, struct net_device *dev); +void slsi_mlme_reassociate_resp(struct slsi_dev *sdev, struct net_device *dev); +int slsi_modify_ies(struct net_device *dev, u8 eid, u8 *ies, int ies_len, u8 ie_index, u8 ie_value); +int slsi_mlme_set_rssi_monitor(struct slsi_dev *sdev, struct net_device *dev, u8 enable, s8 low_rssi_threshold, s8 high_rssi_threshold); +struct slsi_mib_value *slsi_read_mibs(struct slsi_dev *sdev, struct net_device *dev, struct slsi_mib_get_entry *mib_entries, int mib_count, struct slsi_mib_data *mibrsp); +int slsi_mlme_set_host_state(struct slsi_dev *sdev, struct net_device *dev, u8 host_state); +int slsi_mlme_read_apf_request(struct slsi_dev *sdev, struct net_device *dev, u8 **host_dst, int *datalen); +int slsi_mlme_install_apf_request(struct slsi_dev *sdev, struct net_device *dev, + u8 *program, u32 program_len); +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT +int slsi_mlme_arp_detect_request(struct slsi_dev *sdev, struct net_device *dev, u16 action, u8 *ipaddr); +#endif +int slsi_mlme_set_ctwindow(struct slsi_dev *sdev, struct net_device *dev, unsigned int ct_param); +int slsi_mlme_set_p2p_noa(struct slsi_dev *sdev, struct net_device *dev, unsigned int noa_count, + unsigned int interval, unsigned int duration); +void slsi_fw_tx_rate_calc(u16 fw_rate, struct rate_info *tx_rate, unsigned long *data_rate_mbps); +int slsi_test_sap_configure_monitor_mode(struct slsi_dev *sdev, struct net_device *dev, struct cfg80211_chan_def *chandef); + +struct sk_buff *slsi_mlme_req_cfm(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 cfm_id); + +#endif /*__SLSI_MLME_H__*/ diff --git a/drivers/net/wireless/scsc/mlme_nan.c b/drivers/net/wireless/scsc/mlme_nan.c new file mode 100755 index 000000000000..2a14f1e6552a --- /dev/null +++ b/drivers/net/wireless/scsc/mlme_nan.c @@ -0,0 +1,640 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "debug.h" +#include "mlme.h" + +#define SLSI_FAPI_NAN_ATTRIBUTE_PUT_U8(req, attribute, val) \ + { \ + u16 attribute_len = 1; \ + struct sk_buff *req_p = req; \ + fapi_append_data((req_p), (u8 *)&(attribute), 2); \ + fapi_append_data((req_p), (u8 *)&attribute_len, 2); \ + fapi_append_data((req_p), (u8 *)&(val), 1); \ + } + +#define SLSI_FAPI_NAN_ATTRIBUTE_PUT_U16(req, attribute, val) \ + { \ + u16 attribute_len = 2; \ + __le16 le16val = cpu_to_le16(val); \ + struct sk_buff *req_p = req; \ + fapi_append_data((req_p), (u8 *)&(attribute), 2); \ + fapi_append_data((req_p), (u8 *)&attribute_len, 2); \ + fapi_append_data((req_p), (u8 *)&le16val, 2); \ + } + +#define SLSI_FAPI_NAN_ATTRIBUTE_PUT_U32(req, attribute, val) \ + { \ + u16 attribute_len = 4; \ + __le32 le32val = cpu_to_le32(val);\ + struct sk_buff *req_p = req; \ + fapi_append_data((req_p), (u8 *)&(attribute), 2); \ + fapi_append_data((req_p), (u8 *)&attribute_len, 2); \ + fapi_append_data((req_p), (u8 *)&le32val, 4); \ + } + +#define SLSI_FAPI_NAN_ATTRIBUTE_PUT_DATA(req, attribute, val, val_len) \ + { \ + u16 attribute_len = (val_len); \ + struct sk_buff *req_p = req; \ + fapi_append_data((req_p), (u8 *)&(attribute), 2); \ + fapi_append_data((req_p), (u8 *)&attribute_len, 2); \ + fapi_append_data((req_p), (val), (attribute_len)); \ + } + +static void slsi_mlme_nan_enable_fapi_data(struct sk_buff *req, struct slsi_hal_nan_enable_req *hal_req) +{ + u8 nan_config_fields_header[] = {0xdd, 0x24, 0x00, 0x16, 0x32, 0x0b, 0x01}; + u16 fapi_bool; + u8 fapi_u8 = 0; + u16 rssi_window = hal_req->config_rssi_window_size ? hal_req->rssi_window_size_val : 8; + + fapi_append_data(req, nan_config_fields_header, sizeof(nan_config_fields_header)); + + fapi_append_data(req, &hal_req->master_pref, 1); + + /* publish service ID inclusion in beacon */ + fapi_bool = hal_req->config_sid_beacon && (hal_req->sid_beacon_val & 0x01); + fapi_append_data(req, (u8 *)&fapi_bool, 2); + if (fapi_bool) + fapi_u8 = hal_req->sid_beacon_val >> 1; + fapi_append_data(req, &fapi_u8, 1); + + /* subscribe service ID inclusion in beacon */ + fapi_bool = hal_req->config_subscribe_sid_beacon && (hal_req->subscribe_sid_beacon_val & 0x01); + fapi_append_data(req, (u8 *)&fapi_bool, 2); + if (fapi_bool) + fapi_u8 = hal_req->subscribe_sid_beacon_val >> 1; + fapi_append_data(req, &fapi_u8, 1); + + fapi_append_data(req, (u8 *)&rssi_window, 2); + fapi_append_data(req, (u8 *)&hal_req->disc_mac_addr_rand_interval_sec, 4); + + /* 2.4G NAN band specific config*/ + fapi_u8 = hal_req->config_2dot4g_rssi_close ? hal_req->rssi_close_2dot4g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_u8 = hal_req->config_2dot4g_rssi_middle ? hal_req->rssi_middle_2dot4g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_u8 = hal_req->config_2dot4g_rssi_proximity ? hal_req->rssi_proximity_2dot4g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_append_data(req, &hal_req->scan_params_val.dwell_time[0], 1); + fapi_append_data(req, (u8 *)&hal_req->scan_params_val.scan_period[0], 2); + fapi_bool = hal_req->config_2dot4g_dw_band; + fapi_append_data(req, (u8 *)&fapi_bool, 2); + fapi_append_data(req, (u8 *)&hal_req->dw_2dot4g_interval_val, 1); + + /* 5G NAN band specific config*/ + fapi_u8 = hal_req->config_5g_rssi_close ? hal_req->rssi_close_5g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_u8 = hal_req->config_5g_rssi_middle ? hal_req->rssi_middle_5g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_u8 = hal_req->config_5g_rssi_close_proximity ? hal_req->rssi_close_proximity_5g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_append_data(req, &hal_req->scan_params_val.dwell_time[1], 1); + fapi_append_data(req, (u8 *)&hal_req->scan_params_val.scan_period[1], 2); + fapi_bool = hal_req->config_5g_dw_band; + fapi_append_data(req, (u8 *)&fapi_bool, 2); + fapi_append_data(req, (u8 *)&hal_req->dw_5g_interval_val, 1); +} + +int slsi_mlme_nan_enable(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_enable_req *hal_req) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u16 nan_oper_ctrl = 0; + u16 operatein5gband = hal_req->config_support_5g && hal_req->support_5g_val; + u16 hopcountmax = hal_req->config_hop_count_limit ? hal_req->hop_count_limit_val : 0; + + SLSI_NET_DBG3(dev, SLSI_MLME, "\n"); + + /* max mbulk data IE info length is 0x24. So need 0x26 bytes */ + req = fapi_alloc(mlme_nan_start_req, MLME_NAN_START_REQ, ndev_vif->ifnum, 0x26); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + + nan_oper_ctrl |= FAPI_NANOPERATIONCONTROL_MAC_ADDRESS_EVENT | FAPI_NANOPERATIONCONTROL_START_CLUSTER_EVENT | + FAPI_NANOPERATIONCONTROL_JOINED_CLUSTER_EVENT; + + fapi_set_u16(req, u.mlme_nan_start_req.operatein5gband, operatein5gband); + fapi_set_u16(req, u.mlme_nan_start_req.hopcountmax, hopcountmax); + fapi_set_u16(req, u.mlme_nan_start_req.nan_operation_control_flags, nan_oper_ctrl); + + slsi_mlme_nan_enable_fapi_data(req, hal_req); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_NAN_START_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_nan_start_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "MLME_NAN_START_CFM(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +static void slsi_mlme_nan_append_tlv(struct sk_buff *req, __le16 tlv_t, __le16 tlv_l, u8 *tlv_v, u8 **header_ptr, + u8 header_ie_generic_len, u8 **end_ptr) +{ + u8 tmp_buf[255 + 4]; + u8 *tmp_buf_pos; + int tmp_buf_len, len1, ip_ie_len; + + memcpy(tmp_buf, &tlv_t, 2); + memcpy(tmp_buf + 2, &tlv_l, 2); + memcpy(tmp_buf + 4, tlv_v, tlv_l); + tmp_buf_len = 4 + tlv_l; + ip_ie_len = *end_ptr - *header_ptr - 2; + tmp_buf_pos = tmp_buf; + + while (tmp_buf_len + ip_ie_len > 255) { + len1 = 255 - ip_ie_len; + fapi_append_data(req, tmp_buf_pos, len1); + (*header_ptr)[1] = 255; + tmp_buf_len -= len1; + tmp_buf_pos += len1; + ip_ie_len = 0; + if (tmp_buf_len) { + *header_ptr = fapi_append_data(req, *header_ptr, header_ie_generic_len); + *end_ptr = *header_ptr + header_ie_generic_len; + } else { + *end_ptr = *header_ptr + header_ie_generic_len + 255; + } + } + if (tmp_buf_len) { + fapi_append_data(req, tmp_buf, tmp_buf_len); + *end_ptr += tmp_buf_len; + } +} + +static void slsi_mlme_nan_fapi_put_data_path_security_ie(struct sk_buff *req, struct slsi_nan_security_info *sec_info) +{ + u8 ie_header[] = {0xdd, 0x00, 0x00, 0x16, 0x32, 0x0b, 0x07}; + u8 *header_ptr; + u8 u8val, i, key_len = 0; + + header_ptr = fapi_append_data(req, ie_header, sizeof(ie_header)); + u8val = sec_info->key_info.key_type == 1 || sec_info->key_info.key_type == 2 ? sec_info->key_info.key_type : 0; + fapi_append_data(req, &u8val, 1); + fapi_append_data(req, (u8 *)&sec_info->cipher_type, 1); + if (sec_info->key_info.key_type == 1) { + fapi_append_data(req, sec_info->key_info.body.pmk_info.pmk, 32); + u8val = 0; + fapi_append_data(req, &u8val, 1); + } else { + u8val = 0; + for (i = 0; i < 32; i++) + fapi_append_data(req, &u8val, 1); + if (sec_info->key_info.key_type == 2) { + key_len = sec_info->key_info.body.passphrase_info.passphrase_len; + fapi_append_data(req, &key_len, 1); + fapi_append_data(req, sec_info->key_info.body.passphrase_info.passphrase, key_len); + } else { + fapi_append_data(req, &u8val, 1); + } + } + header_ptr[1] = 40 + key_len; +} + +static void slsi_mlme_nan_fapi_put_nan_ranging_ie(struct sk_buff *req, struct slsi_nan_ranging_cfg *cfg) +{ + u8 ie_header[] = {0xdd, 0x0b, 0x00, 0x16, 0x32, 0x0b, 0x09}; + + fapi_append_data(req, ie_header, sizeof(ie_header)); + fapi_append_data(req, (u8 *)&cfg->ranging_interval_msec, 2); + fapi_append_data(req, (u8 *)&cfg->config_ranging_indications, 1); + fapi_append_data(req, (u8 *)&cfg->distance_ingress_mm, 2); + fapi_append_data(req, (u8 *)&cfg->distance_egress_mm, 2); +} + +static void slsi_mlme_nan_publish_fapi_data(struct sk_buff *req, struct slsi_hal_nan_publish_req *hal_req) +{ + u8 nan_publish_fields_header[] = {0xdd, 0x00, 0x00, 0x16, 0x32, 0x0b, 0x02}; + u8 *header_ptr, *end_ptr; + __le16 le16val; + u8 u8val; + + header_ptr = fapi_append_data(req, nan_publish_fields_header, sizeof(nan_publish_fields_header)); + fapi_append_data(req, &hal_req->publish_type, 1); + fapi_append_data(req, &hal_req->tx_type, 1); + + le16val = cpu_to_le16(hal_req->ttl); + fapi_append_data(req, (u8 *)&le16val, 2); + le16val = cpu_to_le16(hal_req->period); + fapi_append_data(req, (u8 *)&le16val, 2); + fapi_append_data(req, &hal_req->publish_count, 1); + fapi_append_data(req, &hal_req->publish_match_indicator, 1); + le16val = cpu_to_le16(hal_req->rssi_threshold_flag); + fapi_append_data(req, (u8 *)&le16val, 2); + u8val = 0; + fapi_append_data(req, (u8 *)&u8val, 1); /* Ranging required */ + fapi_append_data(req, (u8 *)&u8val, 1); /* Data path required */ + + end_ptr = header_ptr + sizeof(nan_publish_fields_header) + 12; + + if (hal_req->service_name_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SERVICE_NAME), + cpu_to_le16 (hal_req->service_name_len), hal_req->service_name, &header_ptr, + sizeof(nan_publish_fields_header), &end_ptr); + + if (hal_req->service_specific_info_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SERVICE_SPECIFIC_INFO), + cpu_to_le16 (hal_req->service_specific_info_len), + hal_req->service_specific_info, &header_ptr, + sizeof(nan_publish_fields_header), &end_ptr); + + if (hal_req->sdea_service_specific_info_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SDEA), + cpu_to_le16 (hal_req->sdea_service_specific_info_len), + hal_req->sdea_service_specific_info, + &header_ptr, sizeof(nan_publish_fields_header), &end_ptr); + + if (hal_req->rx_match_filter_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_RX_MATCH_FILTER), + cpu_to_le16 (hal_req->rx_match_filter_len), hal_req->rx_match_filter, + &header_ptr, sizeof(nan_publish_fields_header), &end_ptr); + + if (hal_req->tx_match_filter_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_TX_MATCH_FILTER), + cpu_to_le16 (hal_req->tx_match_filter_len), hal_req->tx_match_filter, + &header_ptr, sizeof(nan_publish_fields_header), &end_ptr); + + /* update len */ + header_ptr[1] = end_ptr - header_ptr - 2; + slsi_mlme_nan_fapi_put_data_path_security_ie(req, &hal_req->sec_info); + slsi_mlme_nan_fapi_put_nan_ranging_ie(req, &hal_req->ranging_cfg); + +} + +int slsi_mlme_nan_publish(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_publish_req *hal_req, + u16 publish_id) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u16 nan_sdf_flags = 0; + + SLSI_NET_DBG3(dev, SLSI_MLME, "\n"); + if (hal_req) { + u16 max_mbulk_data_len; + u16 length = 17; /* non tlv info in fapi publish IE */ + + length += hal_req->service_name_len ? hal_req->service_name_len + 4 : 0; + length += hal_req->service_specific_info_len ? hal_req->service_specific_info_len + 4 : 0; + length += hal_req->rx_match_filter_len ? hal_req->rx_match_filter_len + 4 : 0; + length += hal_req->tx_match_filter_len ? hal_req->tx_match_filter_len + 4 : 0; + length += hal_req->sdea_service_specific_info_len ? hal_req->sdea_service_specific_info_len + 4 : 0; + if (length > 255) + /* 2 = ie_id _ie_len, 5 = oui+type+sub_type*/ + max_mbulk_data_len = (255 + 2) * (length / (255 - (2 + 5)) + 1); + else + max_mbulk_data_len = length + 2; + max_mbulk_data_len += 42 + 64; /* max length for NAN Data Path Security IE */ + max_mbulk_data_len += 14; /* NAN Ranging IE*/ + + req = fapi_alloc(mlme_nan_publish_req, MLME_NAN_PUBLISH_REQ, ndev_vif->ifnum, max_mbulk_data_len); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + + /* Set/Enable corresponding bits to disable any indications + * that follow a publish. + * BIT0 - Disable publish termination indication. + * BIT1 - Disable match expired indication. + * BIT2 - Disable followUp indication received (OTA). + */ + if (hal_req->recv_indication_cfg & BIT(0)) + nan_sdf_flags |= FAPI_NANSDFCONTROL_PUBLISH_END_EVENT; + if (hal_req->recv_indication_cfg & BIT(1)) + nan_sdf_flags |= FAPI_NANSDFCONTROL_MATCH_EXPIRED_EVENT; + if (hal_req->recv_indication_cfg & BIT(2)) + nan_sdf_flags |= FAPI_NANSDFCONTROL_RECEIVED_FOLLOWUP_EVENT; + } else { + req = fapi_alloc(mlme_nan_publish_req, MLME_NAN_PUBLISH_REQ, ndev_vif->ifnum, 0); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + } + + fapi_set_u16(req, u.mlme_nan_publish_req.publish_id, publish_id); + fapi_set_u16(req, u.mlme_nan_publish_req.nan_sdf_flags, nan_sdf_flags); + + if (hal_req) + slsi_mlme_nan_publish_fapi_data(req, hal_req); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_NAN_PUBLISH_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_nan_publish_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "MLME_NAN_PUBLISH_CFM(result:0x%04x) ERROR\n", + fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code)); + r = -EINVAL; + } + + if (hal_req && !r) + ndev_vif->nan.publish_id_map |= BIT(publish_id); + else + ndev_vif->nan.publish_id_map &= ~BIT(publish_id); + slsi_kfree_skb(cfm); + return r; +} + +static void slsi_mlme_nan_subscribe_fapi_data(struct sk_buff *req, struct slsi_hal_nan_subscribe_req *hal_req) +{ + u8 nan_subscribe_fields_header[] = {0xdd, 0x00, 0x00, 0x16, 0x32, 0x0b, 0x03}; + u8 *header_ptr, *end_ptr; + __le16 le16val; + u8 u8val = 0; + + header_ptr = fapi_append_data(req, nan_subscribe_fields_header, sizeof(nan_subscribe_fields_header)); + fapi_append_data(req, &hal_req->subscribe_type, 1); + fapi_append_data(req, &hal_req->service_response_filter, 1); + fapi_append_data(req, &hal_req->service_response_include, 1); + fapi_append_data(req, &hal_req->use_service_response_filter, 1); + fapi_append_data(req, &hal_req->ssi_required_for_match_indication, 1); + + le16val = cpu_to_le16(hal_req->ttl); + fapi_append_data(req, (u8 *)&le16val, 2); + le16val = cpu_to_le16(hal_req->period); + fapi_append_data(req, (u8 *)&le16val, 2); + fapi_append_data(req, &hal_req->subscribe_count, 1); + fapi_append_data(req, &hal_req->subscribe_match_indicator, 1); + le16val = cpu_to_le16(hal_req->rssi_threshold_flag); + fapi_append_data(req, (u8 *)&le16val, 2); + fapi_append_data(req, &u8val, 1); /* ranging required */ + end_ptr = fapi_append_data(req, &u8val, 1); /* datapath required */ + end_ptr += 1; + + if (hal_req->service_name_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SERVICE_NAME), + cpu_to_le16 (hal_req->service_name_len), hal_req->service_name, &header_ptr, + sizeof(nan_subscribe_fields_header), &end_ptr); + + if (hal_req->service_specific_info_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SERVICE_SPECIFIC_INFO), + cpu_to_le16 (hal_req->service_specific_info_len), + hal_req->service_specific_info, &header_ptr, + sizeof(nan_subscribe_fields_header), &end_ptr); + + if (hal_req->rx_match_filter_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_RX_MATCH_FILTER), + cpu_to_le16 (hal_req->rx_match_filter_len), hal_req->rx_match_filter, + &header_ptr, sizeof(nan_subscribe_fields_header), &end_ptr); + + if (hal_req->tx_match_filter_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_TX_MATCH_FILTER), + cpu_to_le16 (hal_req->tx_match_filter_len), hal_req->tx_match_filter, + &header_ptr, sizeof(nan_subscribe_fields_header), &end_ptr); + + if (hal_req->sdea_service_specific_info_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SDEA), + cpu_to_le16 (hal_req->sdea_service_specific_info_len), + hal_req->sdea_service_specific_info, + &header_ptr, sizeof(nan_subscribe_fields_header), &end_ptr); + + /* update len */ + header_ptr[1] = end_ptr - header_ptr - 2; + slsi_mlme_nan_fapi_put_data_path_security_ie(req, &hal_req->sec_info); + slsi_mlme_nan_fapi_put_nan_ranging_ie(req, &hal_req->ranging_cfg); +} + +int slsi_mlme_nan_subscribe(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_subscribe_req *hal_req, + u16 subscribe_id) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u16 nan_sdf_flags = 0; + + SLSI_NET_DBG3(dev, SLSI_MLME, "\n"); + if (hal_req) { + /*max possible length for publish attributes: 8*255 */ + u16 max_mbulk_data_len; + u16 length = 17; /* non tlv info in fapi publish IE */ + + length += hal_req->service_name_len ? hal_req->service_name_len + 4 : 0; + length += hal_req->service_specific_info_len ? hal_req->service_specific_info_len + 4 : 0; + length += hal_req->rx_match_filter_len ? hal_req->rx_match_filter_len + 4 : 0; + length += hal_req->tx_match_filter_len ? hal_req->tx_match_filter_len + 4 : 0; + length += hal_req->sdea_service_specific_info_len ? hal_req->sdea_service_specific_info_len + 4 : 0; + if (length > 255) + /* 2 = ie_id _ie_len, 5 = oui+type+sub_type*/ + max_mbulk_data_len = (255 + 2) * (length / (255 - (2 + 5)) + 1); + else + max_mbulk_data_len = length + 2; + + req = fapi_alloc(mlme_nan_subscribe_req, MLME_NAN_SUBSCRIBE_REQ, ndev_vif->ifnum, max_mbulk_data_len); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + /* Set/Enable corresponding bits to disable + * indications that follow a subscribe. + * BIT0 - Disable subscribe termination indication. + * BIT1 - Disable match expired indication. + * BIT2 - Disable followUp indication received (OTA). + */ + if (hal_req->recv_indication_cfg & BIT(0)) + nan_sdf_flags |= FAPI_NANSDFCONTROL_SUBSCRIBE_END_EVENT; + if (hal_req->recv_indication_cfg & BIT(1)) + nan_sdf_flags |= FAPI_NANSDFCONTROL_MATCH_EXPIRED_EVENT; + if (hal_req->recv_indication_cfg & BIT(2)) + nan_sdf_flags |= FAPI_NANSDFCONTROL_RECEIVED_FOLLOWUP_EVENT; + } else { + req = fapi_alloc(mlme_nan_subscribe_req, MLME_NAN_SUBSCRIBE_REQ, ndev_vif->ifnum, 0); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + } + + fapi_set_u16(req, u.mlme_nan_subscribe_req.subscribe_id, subscribe_id); + fapi_set_u16(req, u.mlme_nan_subscribe_req.nan_sdf_flags, nan_sdf_flags); + + if (hal_req) + slsi_mlme_nan_subscribe_fapi_data(req, hal_req); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_NAN_SUBSCRIBE_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_nan_subscribe_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "MLME_NAN_SUBSCRIBE_CFM(res:0x%04x)\n", + fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code)); + r = -EINVAL; + } + + if (hal_req && !r) + ndev_vif->nan.subscribe_id_map |= BIT(subscribe_id); + else + ndev_vif->nan.subscribe_id_map &= ~BIT(subscribe_id); + slsi_kfree_skb(cfm); + return r; +} + +static void slsi_mlme_nan_followup_fapi_data(struct sk_buff *req, struct slsi_hal_nan_transmit_followup_req *hal_req) +{ + u8 nan_followup_fields_header[] = {0xdd, 0x00, 0x00, 0x16, 0x32, 0x0b, 0x05}; + u8 *header_ptr, *end_ptr; + + header_ptr = fapi_append_data(req, nan_followup_fields_header, sizeof(nan_followup_fields_header)); + fapi_append_data(req, hal_req->addr, ETH_ALEN); + fapi_append_data(req, &hal_req->priority, 1); + end_ptr = fapi_append_data(req, &hal_req->dw_or_faw, 1); + end_ptr += 1; + + if (hal_req->service_specific_info_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SERVICE_SPECIFIC_INFO), + cpu_to_le16 (hal_req->service_specific_info_len), + hal_req->service_specific_info, &header_ptr, + sizeof(nan_followup_fields_header), &end_ptr); + if (hal_req->sdea_service_specific_info_len) + slsi_mlme_nan_append_tlv(req, cpu_to_le16 (SLSI_FAPI_NAN_SDEA), + cpu_to_le16 (hal_req->sdea_service_specific_info_len), + hal_req->sdea_service_specific_info, &header_ptr, + sizeof(nan_followup_fields_header), &end_ptr); + + /* update len */ + header_ptr[1] = end_ptr - header_ptr - 2; +} + +int slsi_mlme_nan_tx_followup(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_hal_nan_transmit_followup_req *hal_req) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u16 nan_sdf_flags = 0; + + SLSI_NET_DBG3(dev, SLSI_MLME, "\n"); + + /* max possible length for publish attributes: 5*255 */ + req = fapi_alloc(mlme_nan_followup_req, MLME_NAN_FOLLOWUP_REQ, ndev_vif->ifnum, 5 * 255); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + + fapi_set_u16(req, u.mlme_nan_followup_req.publish_subscribe_id, hal_req->publish_subscribe_id); + fapi_set_u16(req, u.mlme_nan_followup_req.peer_id, hal_req->requestor_instance_id); + fapi_set_u16(req, u.mlme_nan_subscribe_req.nan_sdf_flags, nan_sdf_flags); + + slsi_mlme_nan_followup_fapi_data(req, hal_req); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_NAN_FOLLOWUP_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_nan_followup_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "MLME_NAN_FOLLOWUP_CFM(res:0x%04x)\n", + fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} + +static void slsi_mlme_nan_config_fapi_data(struct sk_buff *req, struct slsi_hal_nan_config_req *hal_req) +{ + u8 nan_config_fields_header[] = {0xdd, 0x24, 0x00, 0x16, 0x32, 0x0b, 0x01}; + u16 fapi_bool; + u8 fapi_u8 = 0; + u16 rssi_window = hal_req->config_rssi_window_size ? hal_req->rssi_window_size_val : 8; + + fapi_append_data(req, nan_config_fields_header, sizeof(nan_config_fields_header)); + + fapi_append_data(req, &hal_req->master_pref, 1); + + /* publish service ID inclusion in beacon */ + fapi_bool = hal_req->config_sid_beacon && (hal_req->sid_beacon & 0x01); + fapi_append_data(req, (u8 *)&fapi_bool, 2); + + fapi_u8 = fapi_bool ? hal_req->sid_beacon >> 1 : 0; + fapi_append_data(req, &fapi_u8, 1); + + /* subscribe service ID inclusion in beacon */ + fapi_bool = hal_req->config_subscribe_sid_beacon && (hal_req->subscribe_sid_beacon_val & 0x01); + fapi_append_data(req, (u8 *)&fapi_bool, 2); + + fapi_u8 = fapi_bool ? hal_req->subscribe_sid_beacon_val >> 1 : 0; + fapi_append_data(req, &fapi_u8, 1); + + fapi_append_data(req, (u8 *)&rssi_window, 2); + fapi_append_data(req, (u8 *)&hal_req->disc_mac_addr_rand_interval_sec, 4); + + /* 2.4G NAN band specific config*/ + fapi_u8 = 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_append_data(req, &fapi_u8, 1); + fapi_u8 = hal_req->config_rssi_proximity ? hal_req->rssi_proximity : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_append_data(req, &hal_req->scan_params_val.dwell_time[0], 1); + fapi_append_data(req, (u8 *)&hal_req->scan_params_val.scan_period[0], 2); + fapi_bool = hal_req->config_2dot4g_dw_band; + fapi_append_data(req, (u8 *)&fapi_bool, 2); + fapi_append_data(req, (u8 *)&hal_req->dw_2dot4g_interval_val, 1); + + /* 5G NAN band specific config*/ + fapi_u8 = 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_append_data(req, &fapi_u8, 1); + fapi_u8 = hal_req->config_5g_rssi_close_proximity ? hal_req->rssi_close_proximity_5g_val : 0; + fapi_append_data(req, &fapi_u8, 1); + fapi_append_data(req, &hal_req->scan_params_val.dwell_time[1], 1); + fapi_append_data(req, (u8 *)&hal_req->scan_params_val.scan_period[1], 2); + fapi_bool = hal_req->config_5g_dw_band; + fapi_append_data(req, (u8 *)&fapi_bool, 2); + fapi_append_data(req, (u8 *)&hal_req->dw_5g_interval_val, 1); +} + +int slsi_mlme_nan_set_config(struct slsi_dev *sdev, struct net_device *dev, struct slsi_hal_nan_config_req *hal_req) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *req; + struct sk_buff *cfm; + int r = 0; + u16 nan_oper_ctrl = 0; + + SLSI_NET_DBG3(dev, SLSI_MLME, "\n"); + /* max possible length for publish attributes 5*255 */ + req = fapi_alloc(mlme_nan_config_req, MLME_NAN_CONFIG_REQ, ndev_vif->ifnum, 5 * 255); + if (!req) { + SLSI_NET_ERR(dev, "fapi alloc failure\n"); + return -ENOMEM; + } + + nan_oper_ctrl |= FAPI_NANOPERATIONCONTROL_MAC_ADDRESS_EVENT | FAPI_NANOPERATIONCONTROL_START_CLUSTER_EVENT | + FAPI_NANOPERATIONCONTROL_JOINED_CLUSTER_EVENT; + fapi_set_u16(req, u.mlme_nan_config_req.nan_operation_control_flags, nan_oper_ctrl); + + slsi_mlme_nan_config_fapi_data(req, hal_req); + + cfm = slsi_mlme_req_cfm(sdev, dev, req, MLME_NAN_FOLLOWUP_CFM); + if (!cfm) + return -EIO; + + if (fapi_get_u16(cfm, u.mlme_nan_followup_cfm.result_code) != FAPI_RESULTCODE_SUCCESS) { + SLSI_NET_ERR(dev, "MLME_NAN_FOLLOWUP_CFM(res:0x%04x)\n", + fapi_get_u16(cfm, u.mlme_host_state_cfm.result_code)); + r = -EINVAL; + } + + slsi_kfree_skb(cfm); + return r; +} diff --git a/drivers/net/wireless/scsc/netif.c b/drivers/net/wireless/scsc/netif.c new file mode 100755 index 000000000000..263810d67951 --- /dev/null +++ b/drivers/net/wireless/scsc/netif.c @@ -0,0 +1,2109 @@ +/* + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "netif.h" +#include "dev.h" +#include "mgt.h" +#include "scsc_wifi_fcq.h" +#include "ioctl.h" +#include "mib.h" +#include "hip4_sampler.h" + +#define IP4_OFFSET_TO_TOS_FIELD 1 +#define IP6_OFFSET_TO_TC_FIELD_0 0 +#define IP6_OFFSET_TO_TC_FIELD_1 1 +#define FIELD_TO_DSCP 2 + +/* DSCP */ +/* (RFC5865) */ +#define DSCP_VA 0x2C +/* (RFC3246) */ +#define DSCP_EF 0x2E +/* (RFC2597) */ +#define DSCP_AF43 0x26 +#define DSCP_AF42 0x24 +#define DSCP_AF41 0x22 +#define DSCP_AF33 0x1E +#define DSCP_AF32 0x1C +#define DSCP_AF31 0x1A +#define DSCP_AF23 0x16 +#define DSCP_AF22 0x14 +#define DSCP_AF21 0x12 +#define DSCP_AF13 0x0E +#define DSCP_AF12 0x0C +#define DSCP_AF11 0x0A +/* (RFC2474) */ +#define CS7 0x38 +#define CS6 0x30 +#define CS5 0x28 +#define CS4 0x20 +#define CS3 0x18 +#define CS2 0x10 +#define CS0 0x00 +/* (RFC3662) */ +#define CS1 0x08 + +#ifndef CONFIG_ARM +static bool tcp_ack_suppression_disable; +module_param(tcp_ack_suppression_disable, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_disable, "Disable TCP ack suppression feature"); + +static bool tcp_ack_suppression_disable_2g; +module_param(tcp_ack_suppression_disable_2g, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_disable_2g, "Disable TCP ack suppression for only 2.4GHz band"); + +static bool tcp_ack_suppression_monitor = true; +module_param(tcp_ack_suppression_monitor, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_monitor, "TCP ack suppression throughput monitor: Y: enable (default), N: disable"); + +static uint tcp_ack_suppression_monitor_interval = 500; +module_param(tcp_ack_suppression_monitor_interval, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_monitor_interval, "Sampling interval (in ms) for throughput monitor"); + +static uint tcp_ack_suppression_timeout = 16; +module_param(tcp_ack_suppression_timeout, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_timeout, "Timeout (in ms) before cached TCP ack is flushed to tx"); + +static uint tcp_ack_suppression_max = 16; +module_param(tcp_ack_suppression_max, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_max, "Maximum number of TCP acks suppressed before latest flushed to tx"); + +static uint tcp_ack_suppression_rate_very_high = 100; +module_param(tcp_ack_suppression_rate_very_high, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_very_high, "Rate (in Mbps) to apply very high degree of suppression"); + +static uint tcp_ack_suppression_rate_very_high_timeout = 4; +module_param(tcp_ack_suppression_rate_very_high_timeout, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_very_high_timeout, "Timeout (in ms) before cached TCP ack is flushed in very high rate"); + +static uint tcp_ack_suppression_rate_very_high_acks = 20; +module_param(tcp_ack_suppression_rate_very_high_acks, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_very_high_acks, "Maximum number of TCP acks suppressed before latest flushed in very high rate"); + +static uint tcp_ack_suppression_rate_high = 20; +module_param(tcp_ack_suppression_rate_high, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_high, "Rate (in Mbps) to apply high degree of suppression"); + +static uint tcp_ack_suppression_rate_high_timeout = 4; +module_param(tcp_ack_suppression_rate_high_timeout, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_high_timeout, "Timeout (in ms) before cached TCP ack is flushed in high rate"); + +static uint tcp_ack_suppression_rate_high_acks = 16; +module_param(tcp_ack_suppression_rate_high_acks, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_high_acks, "Maximum number of TCP acks suppressed before latest flushed in high rate"); + +static uint tcp_ack_suppression_rate_low = 1; +module_param(tcp_ack_suppression_rate_low, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_low, "Rate (in Mbps) to apply low degree of suppression"); + +static uint tcp_ack_suppression_rate_low_timeout = 4; +module_param(tcp_ack_suppression_rate_low_timeout, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_low_timeout, "Timeout (in ms) before cached TCP ack is flushed in low rate"); + +static uint tcp_ack_suppression_rate_low_acks = 10; +module_param(tcp_ack_suppression_rate_low_acks, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rate_low_acks, "Maximum number of TCP acks suppressed before latest flushed in low rate"); + +static uint tcp_ack_suppression_slow_start_acks = 512; +module_param(tcp_ack_suppression_slow_start_acks, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_slow_start_acks, "Maximum number of Acks sent in slow start"); + +static uint tcp_ack_suppression_rcv_window = 128; +module_param(tcp_ack_suppression_rcv_window, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tcp_ack_suppression_rcv_window, "Receive window size (in unit of Kbytes) that triggers Ack suppression"); + +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE +static void slsi_netif_tcp_ack_suppression_timeout(struct timer_list *t); +#else +static void slsi_netif_tcp_ack_suppression_timeout(unsigned long data); +#endif +static int slsi_netif_tcp_ack_suppression_start(struct net_device *dev); +static int slsi_netif_tcp_ack_suppression_stop(struct net_device *dev); +static struct sk_buff *slsi_netif_tcp_ack_suppression_pkt(struct net_device *dev, struct sk_buff *skb); +#endif + +/* Net Device callback operations */ +static int slsi_net_open(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int err; + unsigned char dev_addr_zero_check[ETH_ALEN]; + + if (WARN_ON(ndev_vif->is_available)) + return -EINVAL; + + if (sdev->mlme_blocked) { + SLSI_NET_WARN(dev, "Fail: called when MLME in blocked state\n"); + return -EIO; + } + + slsi_wakelock(&sdev->wlan_wl); + + /* check if request to rf test mode. */ + slsi_check_rf_test_mode(); + + err = slsi_start(sdev); + if (WARN_ON(err)) { + slsi_wakeunlock(&sdev->wlan_wl); + return err; + } + + if (!sdev->netdev_up_count) { + slsi_get_hw_mac_address(sdev, sdev->hw_addr); + /* Assign Addresses */ + 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 */ +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + SLSI_ETHER_COPY(sdev->netdev_addresses[SLSI_NET_INDEX_NAN], sdev->hw_addr); + sdev->netdev_addresses[SLSI_NET_INDEX_NAN][0] |= 0x02; /* Set the local bit */ + sdev->netdev_addresses[SLSI_NET_INDEX_NAN][3] ^= 0x80; /* EXOR 4th byte with 0x80 */ +#endif + sdev->initial_scan = true; + } + + memset(dev_addr_zero_check, 0, ETH_ALEN); + if (!memcmp(dev->dev_addr, dev_addr_zero_check, ETH_ALEN)) { +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (SLSI_IS_VIF_INDEX_MHS(sdev, ndev_vif)) + SLSI_ETHER_COPY(dev->dev_addr, sdev->netdev_addresses[SLSI_NET_INDEX_P2P]); + else + SLSI_ETHER_COPY(dev->dev_addr, sdev->netdev_addresses[ndev_vif->ifnum]); +#else + SLSI_ETHER_COPY(dev->dev_addr, sdev->netdev_addresses[ndev_vif->ifnum]); +#endif + } + SLSI_ETHER_COPY(dev->perm_addr, sdev->netdev_addresses[ndev_vif->ifnum]); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (ndev_vif->iftype == NL80211_IFTYPE_MONITOR) { + err = slsi_start_monitor_mode(sdev, dev); + if (WARN_ON(err)) { + slsi_wakeunlock(&sdev->wlan_wl); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return err; + } + } +#endif + SLSI_NET_INFO(dev, "ifnum:%d r:%d MAC:%pM\n", ndev_vif->ifnum, sdev->recovery_status, dev->dev_addr); + ndev_vif->is_available = true; + sdev->netdev_up_count++; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + reinit_completion(&ndev_vif->sig_wait.completion); +#else + INIT_COMPLETION(ndev_vif->sig_wait.completion); +#endif +#ifndef CONFIG_ARM + slsi_netif_tcp_ack_suppression_start(dev); +#endif + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + netif_tx_start_all_queues(dev); + slsi_wakeunlock(&sdev->wlan_wl); + + /* The default power mode in host*/ + /* 2511 measn unifiForceActive and 1 means active */ + if (slsi_is_rf_test_mode_enabled()) { + SLSI_NET_INFO(dev, "*#rf# rf test mode set is enabled.\n"); + slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAMING_ENABLED, 0); + slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAM_MODE, 0); + slsi_set_mib_roam(sdev, NULL, 2511, 1); + slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_TPC_MAX_POWER_RSSI_THRESHOLD, 0); + } + + return 0; +} + +static int slsi_net_stop(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + + SLSI_NET_INFO(dev, "ifnum:%d r:%d\n", ndev_vif->ifnum, sdev->recovery_status); + slsi_wakelock(&sdev->wlan_wl); + netif_tx_stop_all_queues(dev); + sdev->initial_scan = false; + + if (!ndev_vif->is_available) { + /* May have been taken out by the Chip going down */ + SLSI_NET_DBG1(dev, SLSI_NETDEV, "Not available\n"); + slsi_wakeunlock(&sdev->wlan_wl); + return 0; + } +#ifndef SLSI_TEST_DEV + if (!slsi_is_rf_test_mode_enabled() && !sdev->recovery_status) { + SLSI_NET_DBG1(dev, SLSI_NETDEV, "To user mode\n"); + slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_TPC_MAX_POWER_RSSI_THRESHOLD, -55); + } +#endif +#ifndef CONFIG_ARM + slsi_netif_tcp_ack_suppression_stop(dev); +#endif + slsi_stop_net_dev(sdev, dev); + +#ifndef SLSI_TEST_DEV + memset(dev->dev_addr, 0, ETH_ALEN); +#endif + + sdev->allow_switch_40_mhz = true; + sdev->allow_switch_80_mhz = true; + sdev->acs_channel_switched = false; + slsi_wakeunlock(&sdev->wlan_wl); + return 0; +} + +/* This is called after the WE handlers */ +static int slsi_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + SLSI_NET_DBG4(dev, SLSI_NETDEV, "IOCTL cmd:0x%.4x\n", cmd); + + if (cmd == SIOCDEVPRIVATE + 2) { /* 0x89f0 + 2 from wpa_supplicant */ + return slsi_ioctl(dev, rq, cmd); + } + + return -EOPNOTSUPP; +} + +static struct net_device_stats *slsi_net_get_stats(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_NET_DBG4(dev, SLSI_NETDEV, "\n"); + return &ndev_vif->stats; +} + +#ifdef CONFIG_SCSC_USE_WMM_TOS +static u16 slsi_get_priority_from_tos(u8 *frame, u16 proto) +{ + if (WARN_ON(!frame)) + return FAPI_PRIORITY_QOS_UP0; + + switch (proto) { + case ETH_P_IP: /* IPv4 */ + return (u16)(((frame[IP4_OFFSET_TO_TOS_FIELD]) & 0xE0) >> 5); + + case ETH_P_IPV6: /* IPv6 */ + return (u16)((*frame & 0x0E) >> 1); + + default: + return FAPI_PRIORITY_QOS_UP0; + } +} + +#else +static u16 slsi_get_priority_from_tos_dscp(u8 *frame, u16 proto) +{ + u8 dscp; + + if (WARN_ON(!frame)) + return FAPI_PRIORITY_QOS_UP0; + + switch (proto) { + case ETH_P_IP: /* IPv4 */ + dscp = frame[IP4_OFFSET_TO_TOS_FIELD] >> FIELD_TO_DSCP; + break; + + case ETH_P_IPV6: /* IPv6 */ + /* Get traffic class */ + dscp = (((frame[IP6_OFFSET_TO_TC_FIELD_0] & 0x0F) << 4) | + ((frame[IP6_OFFSET_TO_TC_FIELD_1] & 0xF0) >> 4)) >> FIELD_TO_DSCP; + break; + + default: + return FAPI_PRIORITY_QOS_UP0; + } +/* DSCP table based in RFC8325 from Android 10 */ +#if (defined(ANDROID_VERSION) && ANDROID_VERSION >= 100000) + switch (dscp) { + case CS7: + return FAPI_PRIORITY_QOS_UP7; + case CS6: + case DSCP_EF: + case DSCP_VA: + return FAPI_PRIORITY_QOS_UP6; + case CS5: + return FAPI_PRIORITY_QOS_UP5; + case DSCP_AF41: + case DSCP_AF42: + case DSCP_AF43: + case CS4: + case DSCP_AF31: + case DSCP_AF32: + case DSCP_AF33: + case CS3: + return FAPI_PRIORITY_QOS_UP4; + case DSCP_AF21: + case DSCP_AF22: + case DSCP_AF23: + return FAPI_PRIORITY_QOS_UP3; + case CS2: + case DSCP_AF11: + case DSCP_AF12: + case DSCP_AF13: + case CS0: + return FAPI_PRIORITY_QOS_UP0; + case CS1: + return FAPI_PRIORITY_QOS_UP1; + default: + return FAPI_PRIORITY_QOS_UP0; + } +#else + switch (dscp) { + case DSCP_EF: + case DSCP_VA: + return FAPI_PRIORITY_QOS_UP6; + case DSCP_AF43: + case DSCP_AF42: + case DSCP_AF41: + return FAPI_PRIORITY_QOS_UP5; + case DSCP_AF33: + case DSCP_AF32: + case DSCP_AF31: + case DSCP_AF23: + case DSCP_AF22: + case DSCP_AF21: + case DSCP_AF13: + case DSCP_AF12: + case DSCP_AF11: + return FAPI_PRIORITY_QOS_UP0; + case CS7: + return FAPI_PRIORITY_QOS_UP7; + case CS6: + return FAPI_PRIORITY_QOS_UP6; + case CS5: + return FAPI_PRIORITY_QOS_UP5; + case CS4: + return FAPI_PRIORITY_QOS_UP4; + case CS3: + return FAPI_PRIORITY_QOS_UP3; + case CS2: + return FAPI_PRIORITY_QOS_UP2; + case CS1: + return FAPI_PRIORITY_QOS_UP1; + case CS0: + return FAPI_PRIORITY_QOS_UP0; + default: + return FAPI_PRIORITY_QOS_UP0; + } +#endif +} + +#endif + +static bool slsi_net_downgrade_ac(struct net_device *dev, struct sk_buff *skb) +{ + SLSI_UNUSED_PARAMETER(dev); + + switch (skb->priority) { + case 6: + case 7: + skb->priority = FAPI_PRIORITY_QOS_UP5; /* VO -> VI */ + return true; + case 4: + case 5: + skb->priority = FAPI_PRIORITY_QOS_UP3; /* VI -> BE */ + return true; + case 0: + case 3: + skb->priority = FAPI_PRIORITY_QOS_UP2; /* BE -> BK */ + return true; + default: + return false; + } +} + +static u8 slsi_net_up_to_ac_mapping(u8 priority) +{ + switch (priority) { + case FAPI_PRIORITY_QOS_UP6: + case FAPI_PRIORITY_QOS_UP7: + return BIT(FAPI_PRIORITY_QOS_UP6) | BIT(FAPI_PRIORITY_QOS_UP7); + case FAPI_PRIORITY_QOS_UP4: + case FAPI_PRIORITY_QOS_UP5: + return BIT(FAPI_PRIORITY_QOS_UP4) | BIT(FAPI_PRIORITY_QOS_UP5); + case FAPI_PRIORITY_QOS_UP0: + case FAPI_PRIORITY_QOS_UP3: + return BIT(FAPI_PRIORITY_QOS_UP0) | BIT(FAPI_PRIORITY_QOS_UP3); + default: + return BIT(FAPI_PRIORITY_QOS_UP1) | BIT(FAPI_PRIORITY_QOS_UP2); + } +} + +enum slsi_traffic_q slsi_frame_priority_to_ac_queue(u16 priority) +{ + switch (priority) { + case FAPI_PRIORITY_QOS_UP0: + case FAPI_PRIORITY_QOS_UP3: + return SLSI_TRAFFIC_Q_BE; + case FAPI_PRIORITY_QOS_UP1: + case FAPI_PRIORITY_QOS_UP2: + return SLSI_TRAFFIC_Q_BK; + case FAPI_PRIORITY_QOS_UP4: + case FAPI_PRIORITY_QOS_UP5: + return SLSI_TRAFFIC_Q_VI; + case FAPI_PRIORITY_QOS_UP6: + case FAPI_PRIORITY_QOS_UP7: + return SLSI_TRAFFIC_Q_VO; + default: + return SLSI_TRAFFIC_Q_BE; + } +} + +int slsi_ac_to_tids(enum slsi_traffic_q ac, int *tids) +{ + switch (ac) { + case SLSI_TRAFFIC_Q_BE: + tids[0] = FAPI_PRIORITY_QOS_UP0; + tids[1] = FAPI_PRIORITY_QOS_UP3; + break; + + case SLSI_TRAFFIC_Q_BK: + tids[0] = FAPI_PRIORITY_QOS_UP1; + tids[1] = FAPI_PRIORITY_QOS_UP2; + break; + + case SLSI_TRAFFIC_Q_VI: + tids[0] = FAPI_PRIORITY_QOS_UP4; + tids[1] = FAPI_PRIORITY_QOS_UP5; + break; + + case SLSI_TRAFFIC_Q_VO: + tids[0] = FAPI_PRIORITY_QOS_UP6; + tids[1] = FAPI_PRIORITY_QOS_UP7; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void slsi_net_downgrade_pri(struct net_device *dev, struct slsi_peer *peer, + struct sk_buff *skb) +{ + /* in case we are a client downgrade the ac if acm is + * set and tspec is not established + */ + while (unlikely(peer->wmm_acm & BIT(skb->priority)) && + !(peer->tspec_established & slsi_net_up_to_ac_mapping(skb->priority))) { + SLSI_NET_DBG3(dev, SLSI_NETDEV, "Downgrading from UP:%d\n", skb->priority); + if (!slsi_net_downgrade_ac(dev, skb)) + break; + } + SLSI_NET_DBG4(dev, SLSI_NETDEV, "To UP:%d\n", skb->priority); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)) +static u16 slsi_net_select_queue(struct net_device *dev, struct sk_buff *skb, void *accel_priv, select_queue_fallback_t fallback) +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) +static u16 slsi_net_select_queue(struct net_device *dev, struct sk_buff *skb, void *accel_priv) +#else +static u16 slsi_net_select_queue(struct net_device *dev, struct sk_buff *skb) +#endif +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + u16 netif_q = 0; + struct ethhdr *ehdr = (struct ethhdr *)skb->data; + int proto = 0; + struct slsi_peer *peer; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + (void)accel_priv; +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)) + (void)fallback; +#endif + SLSI_NET_DBG4(dev, SLSI_NETDEV, "\n"); + + /* Defensive check for uninitialized mac header */ + if (!skb_mac_header_was_set(skb)) + skb_reset_mac_header(skb); + + if (is_zero_ether_addr(ehdr->h_dest) || is_zero_ether_addr(ehdr->h_source)) { + SLSI_NET_WARN(dev, "invalid Ethernet addresses (dest:%pM,src:%pM)\n", ehdr->h_dest, ehdr->h_source); + SCSC_BIN_TAG_INFO(BINARY, skb->data, skb->len > 128 ? 128 : skb->len); + return SLSI_NETIF_Q_DISCARD; + } + + proto = be16_to_cpu(eth_hdr(skb)->h_proto); + + switch (proto) { + default: + /* SLSI_NETIF_Q_PRIORITY is used only for EAP, ARP and IP frames with DHCP */ + break; + case ETH_P_PAE: + case ETH_P_WAI: + SLSI_NET_DBG3(dev, SLSI_TX, "EAP packet. Priority Queue Selected\n"); + return SLSI_NETIF_Q_PRIORITY; + case ETH_P_ARP: + SLSI_NET_DBG3(dev, SLSI_TX, "ARP frame. Priority Queue Selected\n"); + return SLSI_NETIF_Q_PRIORITY; + case ETH_P_IP: + if (slsi_is_dhcp_packet(skb->data) == SLSI_TX_IS_NOT_DHCP) + break; + SLSI_NET_DBG3(dev, SLSI_TX, "DHCP packet. Priority Queue Selected\n"); + return SLSI_NETIF_Q_PRIORITY; + } + + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) + /* MULTICAST/BROADCAST Queue is only used for AP */ + if (is_multicast_ether_addr(ehdr->h_dest)) { + SLSI_NET_DBG3(dev, SLSI_TX, "Multicast AC queue will be selected\n"); +#ifdef CONFIG_SCSC_USE_WMM_TOS + skb->priority = slsi_get_priority_from_tos(skb->data + ETH_HLEN, proto); +#else + skb->priority = slsi_get_priority_from_tos_dscp(skb->data + ETH_HLEN, proto); +#endif + return slsi_netif_get_multicast_queue(slsi_frame_priority_to_ac_queue(skb->priority)); + } + + slsi_spinlock_lock(&ndev_vif->peer_lock); + peer = slsi_get_peer_from_mac(sdev, dev, ehdr->h_dest); + if (!peer) { + SLSI_NET_DBG1(dev, SLSI_TX, "Discard: Peer %pM NOT found\n", ehdr->h_dest); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return SLSI_NETIF_Q_DISCARD; + } + + if (peer->qos_enabled) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + if (peer->qos_map_set) { /*802.11 QoS for interworking*/ + skb->priority = cfg80211_classify8021d(skb, &peer->qos_map); + } else +#endif + { +#ifdef CONFIG_SCSC_WLAN_PRIORITISE_IMP_FRAMES + if ((proto == ETH_P_IP && slsi_is_dns_packet(skb->data)) || + (proto == ETH_P_IP && slsi_is_mdns_packet(skb->data)) || + (proto == ETH_P_IP && slsi_is_tcp_sync_packet(dev, skb))) { + skb->priority = FAPI_PRIORITY_QOS_UP7; + } else +#endif + { +#ifdef CONFIG_SCSC_USE_WMM_TOS + skb->priority = slsi_get_priority_from_tos(skb->data + ETH_HLEN, proto); +#else + skb->priority = slsi_get_priority_from_tos_dscp(skb->data + ETH_HLEN, proto); +#endif + } + } + } else{ + skb->priority = FAPI_PRIORITY_QOS_UP0; + } + + /* Downgrade the priority if acm bit is set and tspec is not established */ + slsi_net_downgrade_pri(dev, peer, skb); + + netif_q = slsi_netif_get_peer_queue(peer->queueset, slsi_frame_priority_to_ac_queue(skb->priority)); + SLSI_NET_DBG3(dev, SLSI_TX, "prio:%d queue:%u\n", skb->priority, netif_q); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return netif_q; +} + +void slsi_tdls_move_packets(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_peer *sta_peer, struct slsi_peer *tdls_peer, bool connection) +{ + struct netdev_vif *netdev_vif = netdev_priv(dev); + struct sk_buff *skb = NULL; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + struct sk_buff *skb_to_free = NULL; +#endif + struct ethhdr *ehdr; + struct Qdisc *qd; + u32 num_pkts; + u16 staq; + u16 tdlsq; + u16 netq; + u16 i; + u16 j; + int index; + struct slsi_tcp_ack_s *tcp_ack; + + /* Get the netdev queue number from queueset */ + staq = slsi_netif_get_peer_queue(sta_peer->queueset, 0); + tdlsq = slsi_netif_get_peer_queue(tdls_peer->queueset, 0); + + SLSI_NET_DBG1(dev, SLSI_TDLS, "Connection: %d, sta_qset: %d, tdls_qset: %d, sta_netq: %d, tdls_netq: %d\n", + connection, sta_peer->queueset, tdls_peer->queueset, staq, tdlsq); + + /* Pause the TDLS queues and STA netdev queues */ + slsi_tx_pause_queues(sdev); + + /* walk through frames in TCP Ack suppression queue and change mapping to TDLS queue */ + for (index = 0; index < TCP_ACK_SUPPRESSION_RECORDS_MAX; index++) { + tcp_ack = &netdev_vif->ack_suppression[index]; + if (!tcp_ack && !tcp_ack->state) + continue; + slsi_spinlock_lock(&tcp_ack->lock); + skb_queue_walk(&tcp_ack->list, skb) { + SLSI_NET_DBG2(dev, SLSI_TDLS, "frame in TCP Ack list (peer:%pM)\n", eth_hdr(skb)->h_dest); + /* is it destined to TDLS peer? */ + if (compare_ether_addr(tdls_peer->address, eth_hdr(skb)->h_dest) == 0) { + if (connection) { + /* TDLS setup: change the queue mapping to TDLS queue */ + skb->queue_mapping += (tdls_peer->queueset * SLSI_NETIF_Q_PER_PEER); + } else { + /* TDLS teardown: change the queue to STA queue */ + skb->queue_mapping -= (tdls_peer->queueset * SLSI_NETIF_Q_PER_PEER); + } + } + } + slsi_spinlock_unlock(&tcp_ack->lock); + } + + slsi_spinlock_lock(&netdev_vif->peer_lock); + /** + * For TDLS connection set PEER valid to true. After this ndo_select_queue() will select TDLSQ instead of STAQ + * For TDLS teardown set PEER valid to false. After this ndo_select_queue() will select STAQ instead of TDLSQ + */ + if (connection) + tdls_peer->valid = true; + else + tdls_peer->valid = false; + + /* Move packets from netdev queues */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + SLSI_NET_DBG2(dev, SLSI_TDLS, "NETQ%d: Before: tdlsq_len = %d, staq_len = %d\n", + i, dev->_tx[tdlsq + i].qdisc->q.qlen, dev->_tx[staq + i].qdisc->q.qlen); + + if (connection) { + /* Check if any packet is already avilable in TDLS queue (most likely from last session) */ + if (dev->_tx[tdlsq + i].qdisc->q.qlen) + SLSI_NET_ERR(dev, "tdls_connection: Packet present in queue %d\n", tdlsq + i); + + qd = dev->_tx[staq + i].qdisc; + /* Get the total number of packets in STAQ */ + num_pkts = qd->q.qlen; + + /* Check all the pkt in STAQ and move the TDLS pkts to TDSLQ */ + for (j = 0; j < num_pkts; j++) { + qd = dev->_tx[staq + i].qdisc; + /* Dequeue the pkt form STAQ. This logic is similar to kernel API dequeue_skb() */ + skb = qd->gso_skb; + if (skb) { + qd->gso_skb = NULL; + qd->q.qlen--; + } else { + skb = qd->dequeue(qd); + } + + if (!skb) { + SLSI_NET_ERR(dev, "tdls_connection: STA NETQ skb is NULL\n"); + break; + } + + /* Change the queue mapping for the TDLS packets */ + netq = skb->queue_mapping; + ehdr = (struct ethhdr *)skb->data; + if (compare_ether_addr(tdls_peer->address, ehdr->h_dest) == 0) { + netq += (tdls_peer->queueset * SLSI_NETIF_Q_PER_PEER); + SLSI_NET_DBG3(dev, SLSI_TDLS, "NETQ%d: Queue mapping changed from %d to %d\n", + i, skb->queue_mapping, netq); + skb_set_queue_mapping(skb, netq); + } + + qd = dev->_tx[netq].qdisc; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + qd->enqueue(skb, qd, &skb_to_free); +#else + /* If the netdev queue is already full then enqueue() will drop the skb */ + qd->enqueue(skb, qd); +#endif + } + } else { + num_pkts = dev->_tx[tdlsq + i].qdisc->q.qlen; + /* Move the packets from TDLS to STA queue */ + for (j = 0; j < num_pkts; j++) { + /* Dequeue the pkt form TDLS_Q. This logic is similar to kernel API dequeue_skb() */ + qd = dev->_tx[tdlsq + i].qdisc; + skb = qd->gso_skb; + if (skb) { + qd->gso_skb = NULL; + qd->q.qlen--; + } else { + skb = qd->dequeue(qd); + } + + if (!skb) { + SLSI_NET_ERR(dev, "tdls_teardown: TDLS NETQ skb is NULL\n"); + break; + } + + /* Update the queue mapping */ + skb_set_queue_mapping(skb, staq + i); + + /* Enqueue the packet in STA queue */ + qd = dev->_tx[staq + i].qdisc; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + qd->enqueue(skb, qd, &skb_to_free); +#else + /* If the netdev queue is already full then enqueue() will drop the skb */ + qd->enqueue(skb, qd); +#endif + } + } + SLSI_NET_DBG2(dev, SLSI_TDLS, "NETQ%d: After : tdlsq_len = %d, staq_len = %d\n", + i, dev->_tx[tdlsq + i].qdisc->q.qlen, dev->_tx[staq + i].qdisc->q.qlen); + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (unlikely(skb_to_free)) + kfree_skb_list(skb_to_free); +#endif + slsi_spinlock_unlock(&netdev_vif->peer_lock); + + /* Teardown - after teardown there should not be any packet in TDLS queues */ + if (!connection) + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + if (dev->_tx[tdlsq + i].qdisc->q.qlen) + SLSI_NET_ERR(dev, "tdls_teardown: Packet present in NET queue %d\n", tdlsq + i); + } + + /* Resume the STA and TDLS netdev queues */ + slsi_tx_unpause_queues(sdev); +} + +/** + * This is the main TX entry point for the driver. + * + * Ownership of the skb is transferred to another function ONLY IF such + * function was able to deal with that skb and ended with a SUCCESS ret code. + * Owner HAS the RESPONSIBILITY to handle the life cycle of the skb. + * + * In the context of this function: + * - ownership is passed DOWN to the LOWER layers HIP-functions when skbs were + * SUCCESSFULLY transmitted, and there they will be FREED. As a consequence + * kernel netstack will receive back NETDEV_TX_OK too. + * - ownership is KEPT HERE by this function when lower layers fails somehow + * to deal with the transmission of the skb. In this case the skb WOULD HAVE + * NOT BEEN FREED by lower layers that instead returns a proper ERRCODE. + * - intermediate lower layer functions (NOT directly involved in failure or + * success) will relay any retcode up to this layer for evaluation. + * + * WHAT HAPPENS THEN, is ERRCODE-dependent, and at the moment: + * - ENOSPC: something related to queueing happened...this should be + * retried....NETDEV_TX_BUSY is returned to NetStack ...packet will be + * requeued by the Kernel NetStack itself, using the proper queue. + * As a consequence SKB is NOT FREED HERE !. + * - ANY OTHER ERR: all other errors are considered at the moment NOT + * recoverable and SO skbs are droppped(FREED) HERE...Kernel will receive + * the proper ERRCODE and stops dealing with the packet considering it + * consumed by lower layer. (same behavior as NETDEV_TX_OK) + * + * BIG NOTE: + * As detailed in Documentation/networking/drivers.txt the above behavior + * of returning NETDEV_TX_BUSY to trigger requeueinng by the Kernel is + * discouraged and should be used ONLY in case of a real HARD error(?); + * the advised solution is to actively STOP the queues before finishing + * the available space and WAKING them up again when more free buffers + * would have arrived. + */ +static netdev_tx_t slsi_net_hw_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_dev *sdev = ndev_vif->sdev; + int r = NETDEV_TX_OK; + struct sk_buff *original_skb = NULL; +#ifdef CONFIG_SCSC_WLAN_DEBUG + int known_users = 0; +#endif + /* Keep the packet length. The packet length will be used to increment + * stats for the netdev if the packet was successfully transmitted. + * The ownership of the SKB is passed to lower layers, so we should + * not refer the SKB after this point + */ + unsigned int packet_len = skb->len; + enum slsi_traffic_q traffic_q = slsi_frame_priority_to_ac_queue(skb->priority); + + slsi_wakelock(&sdev->wlan_wl); + slsi_skb_cb_init(skb); + + /* Check for misaligned (oddly aligned) data. + * The f/w requires 16 bit aligned. + * This is a corner case - for example, the kernel can generate BPDU + * that are oddly aligned. Therefore it is acceptable to copy these + * frames to a 16 bit alignment. + */ + if ((uintptr_t)skb->data & 0x1) { + struct sk_buff *skb2 = NULL; + /* Received a socket buffer aligned on an odd address. + * Re-align by asking for headroom. + */ + skb2 = skb_copy_expand(skb, SLSI_NETIF_SKB_HEADROOM, skb_tailroom(skb), GFP_ATOMIC); + if (skb2 && (!(((uintptr_t)skb2->data) & 0x1))) { + /* We should account for this duplication */ + original_skb = skb; + skb = skb2; + SLSI_NET_DBG3(dev, SLSI_TX, "Oddly aligned skb realigned\n"); + } else { + /* Drop the packet if we can't re-align. */ + SLSI_NET_WARN(dev, "Oddly aligned skb failed realignment, dropping\n"); + if (skb2) { + SLSI_NET_DBG3(dev, SLSI_TX, "skb_copy_expand didn't align for us\n"); + slsi_kfree_skb(skb2); + } else { + SLSI_NET_DBG3(dev, SLSI_TX, "skb_copy_expand failed when trying to align\n"); + } + r = -EFAULT; + goto evaluate; + } + } + slsi_dbg_track_skb(skb, GFP_ATOMIC); + + /* Be defensive about the mac_header - some kernels have a bug where a + * frame can be delivered to the driver with mac_header initialised + * to ~0U and this causes a crash when the pointer is dereferenced to + * access part of the Ethernet header. + */ + if (!skb_mac_header_was_set(skb)) + skb_reset_mac_header(skb); + + SLSI_NET_DBG3(dev, SLSI_TX, "Proto 0x%.4X\n", be16_to_cpu(eth_hdr(skb)->h_proto)); + + if (!ndev_vif->is_available) { + SLSI_NET_WARN(dev, "vif NOT available\n"); + r = -EFAULT; + goto evaluate; + } + if (skb->queue_mapping == SLSI_NETIF_Q_DISCARD) { + SLSI_NET_WARN(dev, "Discard Queue :: Packet Dropped\n"); + r = -EIO; + goto evaluate; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) + known_users = refcount_read(&skb->users); +#else + known_users = atomic_read(&skb->users); +#endif +#endif + +#ifndef CONFIG_ARM + skb = slsi_netif_tcp_ack_suppression_pkt(dev, skb); + if (!skb) { + slsi_wakeunlock(&sdev->wlan_wl); + if (original_skb) + slsi_kfree_skb(original_skb); + return NETDEV_TX_OK; + } +#endif + + /* SKB is owned by slsi_tx_data() ONLY IF ret value is success (0) */ + r = slsi_tx_data(sdev, dev, skb); +evaluate: + if (r == 0) { + /** + * A copy has been passed down and successfully transmitted + * and freed....here we free the original coming from the + * upper network layers....if a copy was passed down. + */ + if (original_skb) + slsi_kfree_skb(original_skb); + /* skb freed by lower layers on success...enjoy */ + + ndev_vif->tx_packets[traffic_q]++; + ndev_vif->stats.tx_packets++; + ndev_vif->stats.tx_bytes += packet_len; + r = NETDEV_TX_OK; + } else { + /** + * Failed to send: + * - if QueueFull/OutOfMBulk (-ENOSPC returned) the skb was + * NOT discarded by lower layers and NETDEV_TX_BUSY should + * be returned to upper layers: this will cause the skb + * (THAT MUST NOT HAVE BEEN FREED BY LOWER LAYERS !) + * to be requeued ... + * NOTE THAT it's the original skb that will be retried + * by upper netstack. + * THIS CONDITION SHOULD NOT BE REACHED...NEVER...see in + * the following. + * + * - with any other -ERR instead return the error: this + * anyway let the kernel think that the SKB has + * been consumed, and we drop the frame and free it. + * + * - a WARN_ON() takes care to ensure the SKB has NOT been + * freed by someone despite this was NOT supposed to happen, + * just before the actual freeing. + * + */ + if (r == -ENOSPC) { + /* SLSI_NET_DBG1(dev, SLSI_TEST, "Packet Requeued...should NOT get here !\n"); */ + ndev_vif->stats.tx_fifo_errors++; + /* Free the local copy if any ... */ + if (original_skb) + slsi_kfree_skb(skb); + r = NETDEV_TX_BUSY; + } else { +#ifdef CONFIG_SCSC_WLAN_DEBUG +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) + WARN_ON(known_users && refcount_read(&skb->users) != known_users); +#else + WARN_ON(known_users && atomic_read(&skb->users) != known_users); +#endif +#endif + if (original_skb) + slsi_kfree_skb(original_skb); + slsi_kfree_skb(skb); + ndev_vif->stats.tx_dropped++; + /* We return the ORIGINAL Error 'r' anyway + * BUT Kernel treats them as TX complete anyway + * and assumes the SKB has been consumed. + */ + /* SLSI_NET_DBG1(dev, SLSI_TEST, "Packet Dropped\n"); */ + } + } + /* SKBs are always considered consumed if the driver + * returns NETDEV_TX_OK. + */ + slsi_wakeunlock(&sdev->wlan_wl); + return r; +} + +static netdev_features_t slsi_net_fix_features(struct net_device *dev, netdev_features_t features) +{ + SLSI_UNUSED_PARAMETER(dev); + +#ifdef CONFIG_SCSC_WLAN_SG + SLSI_NET_DBG1(dev, SLSI_RX, "Scatter-gather and GSO enabled\n"); + features |= NETIF_F_SG; + features |= NETIF_F_GSO; +#endif + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI_GRO + SLSI_NET_DBG1(dev, SLSI_RX, "NAPI Rx GRO enabled\n"); + features |= NETIF_F_GRO; +#else + SLSI_NET_DBG1(dev, SLSI_RX, "NAPI Rx GRO disabled\n"); + features &= ~NETIF_F_GRO; +#endif + return features; +} + +static void slsi_set_multicast_list(struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u8 count, i = 0; + u8 mdns_addr[ETH_ALEN] = { 0x01, 0x00, 0x5E, 0x00, 0x00, 0xFB }; + +#ifdef CONFIG_SCSC_WLAN_BLOCK_IPV6 + u8 mc_addr_prefix[3] = { 0x01, 0x00, 0x5e }; +#else + u8 mdns6_addr[ETH_ALEN] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0xFB }; + const u8 solicited_node_addr[ETH_ALEN] = { 0x33, 0x33, 0xff, 0x00, 0x00, 0x01 }; + u8 ipv6addr_suffix[3]; +#endif + struct netdev_hw_addr *ha; + + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) + return; + + if (!ndev_vif->is_available) { + SLSI_NET_DBG1(dev, SLSI_NETDEV, "vif NOT available\n"); + return; + } + + count = netdev_mc_count(dev); + if (!count) + goto exit; + +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + slsi_spinlock_lock(&ndev_vif->ipv6addr_lock); + memcpy(ipv6addr_suffix, &ndev_vif->ipv6address.s6_addr[13], 3); + slsi_spinlock_unlock(&ndev_vif->ipv6addr_lock); +#endif + + netdev_for_each_mc_addr(ha, dev) { +#ifdef CONFIG_SCSC_WLAN_BLOCK_IPV6 + if ((!memcmp(ha->addr, mdns_addr, ETH_ALEN)) || /*mDns is handled separately*/ + (memcmp(ha->addr, mc_addr_prefix, 3))) { /*only consider IPv4 multicast addresses*/ +#else + if ((!memcmp(ha->addr, mdns_addr, ETH_ALEN)) || + (!memcmp(ha->addr, mdns6_addr, ETH_ALEN)) || /*mDns is handled separately*/ + (!memcmp(ha->addr, solicited_node_addr, 3) && + !memcmp(&ha->addr[3], ipv6addr_suffix, 3))) { /* local multicast addr handled separately*/ +#endif + + SLSI_NET_DBG3(dev, SLSI_NETDEV, "Drop MAC %pM\n", ha->addr); + continue; + } + if (i == SLSI_MC_ADDR_ENTRY_MAX) { + SLSI_NET_WARN(dev, "MAC list has reached max limit (%d), actual count %d\n", SLSI_MC_ADDR_ENTRY_MAX, count); + break; + } + + SLSI_NET_DBG3(dev, SLSI_NETDEV, "idx %d MAC %pM\n", i, ha->addr); + SLSI_ETHER_COPY(ndev_vif->sta.regd_mc_addr[i++], ha->addr); + } + +exit: + ndev_vif->sta.regd_mc_addr_count = i; +} + +static int slsi_set_mac_address(struct net_device *dev, void *addr) +{ + struct sockaddr *sa = (struct sockaddr *)addr; + + SLSI_NET_DBG1(dev, SLSI_NETDEV, "slsi_set_mac_address %pM\n", sa->sa_data); + SLSI_ETHER_COPY(dev->dev_addr, sa->sa_data); + return 0; +} + +static const struct net_device_ops slsi_netdev_ops = { + .ndo_open = slsi_net_open, + .ndo_stop = slsi_net_stop, + .ndo_start_xmit = slsi_net_hw_xmit, + .ndo_do_ioctl = slsi_net_ioctl, + .ndo_get_stats = slsi_net_get_stats, + .ndo_select_queue = slsi_net_select_queue, + .ndo_fix_features = slsi_net_fix_features, + .ndo_set_rx_mode = slsi_set_multicast_list, + .ndo_set_mac_address = slsi_set_mac_address, +}; + +static void slsi_if_setup(struct net_device *dev) +{ + ether_setup(dev); + dev->netdev_ops = &slsi_netdev_ops; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 9)) + dev->needs_free_netdev = true; +#else + dev->destructor = free_netdev; +#endif +} + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + +#ifdef CONFIG_SOC_EXYNOS9610 +#define SCSC_NETIF_RPS_CPUS_MASK "fe" +#else +#define SCSC_NETIF_RPS_CPUS_MASK "0" +#endif + +static void slsi_netif_rps_map_clear(struct net_device *dev) +{ + struct rps_map *map; + + map = rcu_dereference_protected(dev->_rx->rps_map, 1); + if (map) { + RCU_INIT_POINTER(dev->_rx->rps_map, NULL); + kfree_rcu(map, rcu); + SLSI_NET_INFO(dev, "clear rps_cpus map\n"); + } +} + +static int slsi_netif_rps_map_set(struct net_device *dev, char *buf, size_t len) +{ + struct rps_map *old_map, *map; + cpumask_var_t mask; + int err, cpu, i; + static DEFINE_SPINLOCK(rps_map_lock); + + if (!alloc_cpumask_var(&mask, GFP_KERNEL)) + return -ENOMEM; + + err = bitmap_parse(buf, len, cpumask_bits(mask), nr_cpumask_bits); + if (err) { + free_cpumask_var(mask); + SLSI_NET_WARN(dev, "CPU bitmap parse failed\n"); + return err; + } + + map = kzalloc(max_t(unsigned int, RPS_MAP_SIZE(cpumask_weight(mask)), L1_CACHE_BYTES), GFP_KERNEL); + if (!map) { + free_cpumask_var(mask); + SLSI_NET_WARN(dev, "CPU mask alloc failed\n"); + return -ENOMEM; + } + + i = 0; + for_each_cpu_and(cpu, mask, cpu_online_mask) + map->cpus[i++] = cpu; + + if (i) { + map->len = i; + } else { + kfree(map); + map = NULL; + } + + spin_lock(&rps_map_lock); + old_map = rcu_dereference_protected(dev->_rx->rps_map, lockdep_is_held(&rps_map_lock)); + rcu_assign_pointer(dev->_rx->rps_map, map); + spin_unlock(&rps_map_lock); + + if (map) + static_key_slow_inc(&rps_needed); + if (old_map) + static_key_slow_dec(&rps_needed); + + if (old_map) + kfree_rcu(old_map, rcu); + + free_cpumask_var(mask); + SLSI_NET_INFO(dev, "rps_cpus map set(%s)\n", buf); + return len; +} +#endif + +int slsi_netif_add_locked(struct slsi_dev *sdev, const char *name, int ifnum) +{ + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif; + struct wireless_dev *wdev; + int alloc_size, txq_count = 0, ret; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->netdev_add_remove_mutex)); + + if (WARN_ON(!sdev || ifnum > CONFIG_SCSC_WLAN_MAX_INTERFACES || sdev->netdev[ifnum])) + return -EINVAL; + + alloc_size = sizeof(struct netdev_vif); + + txq_count = SLSI_NETIF_Q_PEER_START + (SLSI_NETIF_Q_PER_PEER * (SLSI_ADHOC_PEER_CONNECTIONS_MAX)); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 16, 0)) + dev = alloc_netdev_mqs(alloc_size, name, NET_NAME_PREDICTABLE, slsi_if_setup, txq_count, 1); +#else + dev = alloc_netdev_mqs(alloc_size, name, slsi_if_setup, txq_count, 1); +#endif + if (!dev) { + SLSI_ERR(sdev, "Failed to allocate private data for netdev\n"); + return -ENOMEM; + } + + /* Reserve space in skb for later use */ + dev->needed_headroom = SLSI_NETIF_SKB_HEADROOM; + dev->needed_tailroom = SLSI_NETIF_SKB_TAILROOM; + + ret = dev_alloc_name(dev, dev->name); + if (ret < 0) + goto exit_with_error; + + ndev_vif = netdev_priv(dev); + memset(ndev_vif, 0x00, sizeof(*ndev_vif)); + SLSI_MUTEX_INIT(ndev_vif->vif_mutex); + SLSI_MUTEX_INIT(ndev_vif->scan_mutex); + SLSI_MUTEX_INIT(ndev_vif->scan_result_mutex); + skb_queue_head_init(&ndev_vif->ba_complete); + slsi_sig_send_init(&ndev_vif->sig_wait); + ndev_vif->sdev = sdev; + ndev_vif->ifnum = ifnum; + ndev_vif->vif_type = SLSI_VIFTYPE_UNSPECIFIED; +#ifndef CONFIG_SCSC_WLAN_BLOCK_IPV6 + slsi_spinlock_create(&ndev_vif->ipv6addr_lock); +#endif + slsi_spinlock_create(&ndev_vif->peer_lock); + atomic_set(&ndev_vif->ba_flush, 0); + + /* Reserve memory for the peer database - Not required for p2p0/nan interface */ + if (!(SLSI_IS_VIF_INDEX_P2P(ndev_vif) || SLSI_IS_VIF_INDEX_NAN(ndev_vif))) { + int queueset; + + for (queueset = 0; queueset < SLSI_ADHOC_PEER_CONNECTIONS_MAX; queueset++) { + ndev_vif->peer_sta_record[queueset] = kzalloc(sizeof(*ndev_vif->peer_sta_record[queueset]), GFP_KERNEL); + + if (!ndev_vif->peer_sta_record[queueset]) { + int j; + + SLSI_NET_ERR(dev, "Could not allocate memory for peer entry (queueset:%d)\n", queueset); + + /* Free previously allocated peer database memory till current queueset */ + for (j = 0; j < queueset; j++) { + kfree(ndev_vif->peer_sta_record[j]); + ndev_vif->peer_sta_record[j] = NULL; + } + + ret = -ENOMEM; + goto exit_with_error; + } + } + } + + /* The default power mode in host*/ + if (slsi_is_rf_test_mode_enabled()) { + SLSI_NET_ERR(dev, "*#rf# rf test mode set is enabled.\n"); + ndev_vif->set_power_mode = FAPI_POWERMANAGEMENTMODE_ACTIVE_MODE; + } else { + ndev_vif->set_power_mode = FAPI_POWERMANAGEMENTMODE_POWER_SAVE; + } + + INIT_LIST_HEAD(&ndev_vif->sta.network_map); + SLSI_DBG1(sdev, SLSI_NETDEV, "ifnum=%d\n", ndev_vif->ifnum); + + /* For HS2 interface */ + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_NO_VIF; + + /* For p2p0 interface */ + else if (SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + ret = slsi_p2p_init(sdev, ndev_vif); + if (ret) + goto exit_with_error; + } + + INIT_DELAYED_WORK(&ndev_vif->scan_timeout_work, slsi_scan_ind_timeout_handle); + + ret = slsi_skb_work_init(sdev, dev, &ndev_vif->rx_data, "slsi_wlan_rx_data", slsi_rx_netdev_data_work); + if (ret) + goto exit_with_error; + + ret = slsi_skb_work_init(sdev, dev, &ndev_vif->rx_mlme, "slsi_wlan_rx_mlme", slsi_rx_netdev_mlme_work); + if (ret) { + slsi_skb_work_deinit(&ndev_vif->rx_data); + goto exit_with_error; + } + + wdev = &ndev_vif->wdev; + + dev->ieee80211_ptr = wdev; + wdev->wiphy = sdev->wiphy; + wdev->netdev = dev; + wdev->iftype = NL80211_IFTYPE_STATION; + SET_NETDEV_DEV(dev, sdev->dev); + + /* We are not ready to send data yet. */ + netif_carrier_off(dev); + +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + if (strcmp(name, CONFIG_SCSC_AP_INTERFACE_NAME) == 0) + SLSI_ETHER_COPY(dev->dev_addr, sdev->netdev_addresses[SLSI_NET_INDEX_P2P]); + else + SLSI_ETHER_COPY(dev->dev_addr, sdev->netdev_addresses[ifnum]); +#else + SLSI_ETHER_COPY(dev->dev_addr, sdev->netdev_addresses[ifnum]); +#endif + SLSI_DBG1(sdev, SLSI_NETDEV, "Add:%pM\n", dev->dev_addr); + rcu_assign_pointer(sdev->netdev[ifnum], dev); + ndev_vif->delete_probe_req_ies = false; + ndev_vif->probe_req_ies = NULL; + ndev_vif->probe_req_ie_len = 0; + ndev_vif->drv_in_p2p_procedure = false; + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + slsi_netif_rps_map_set(dev, SCSC_NETIF_RPS_CPUS_MASK, strlen(SCSC_NETIF_RPS_CPUS_MASK)); +#endif + return 0; + +exit_with_error: + mutex_lock(&sdev->netdev_remove_mutex); + free_netdev(dev); + mutex_unlock(&sdev->netdev_remove_mutex); + return ret; +} + +int slsi_netif_dynamic_iface_add(struct slsi_dev *sdev, const char *name) +{ + int index = -EINVAL; + int err; + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) + if (sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN] == sdev->netdev_ap) { + rcu_assign_pointer(sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN], NULL); + err = slsi_netif_add_locked(sdev, name, SLSI_NET_INDEX_P2PX_SWLAN); + index = err ? err : SLSI_NET_INDEX_P2PX_SWLAN; + } +#else + err = slsi_netif_add_locked(sdev, name, SLSI_NET_INDEX_P2PX_SWLAN); + index = err ? err : SLSI_NET_INDEX_P2PX_SWLAN; +#endif + + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return index; +} + +int slsi_netif_init(struct slsi_dev *sdev) +{ + int i; + + SLSI_DBG3(sdev, SLSI_NETDEV, "\n"); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + + /* Initialize all other netdev interfaces to NULL */ + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) + RCU_INIT_POINTER(sdev->netdev[i], NULL); + + if (slsi_netif_add_locked(sdev, "wlan%d", SLSI_NET_INDEX_WLAN) != 0) { + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -EINVAL; + } + + if (slsi_netif_add_locked(sdev, "p2p%d", SLSI_NET_INDEX_P2P) != 0) { + rtnl_lock(); + slsi_netif_remove_locked(sdev, sdev->netdev[SLSI_NET_INDEX_WLAN]); + rtnl_unlock(); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -EINVAL; + } +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) + if (slsi_netif_add_locked(sdev, CONFIG_SCSC_AP_INTERFACE_NAME, SLSI_NET_INDEX_P2PX_SWLAN) != 0) { + rtnl_lock(); + slsi_netif_remove_locked(sdev, sdev->netdev[SLSI_NET_INDEX_WLAN]); + slsi_netif_remove_locked(sdev, sdev->netdev[SLSI_NET_INDEX_P2P]); + rtnl_unlock(); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -EINVAL; + } +#endif +#endif +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + if (slsi_netif_add_locked(sdev, "nan%d", SLSI_NET_INDEX_NAN) != 0) { + rtnl_lock(); + slsi_netif_remove_locked(sdev, sdev->netdev[SLSI_NET_INDEX_WLAN]); + slsi_netif_remove_locked(sdev, sdev->netdev[SLSI_NET_INDEX_P2P]); +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING +#if defined(CONFIG_SCSC_WLAN_MHS_STATIC_INTERFACE) || (defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000) + slsi_netif_remove_locked(sdev, sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]); +#endif +#endif + rtnl_unlock(); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return -EINVAL; + } +#endif + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return 0; +} + +static int slsi_netif_register_locked(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int err; + + WARN_ON(!rtnl_is_locked()); + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->netdev_add_remove_mutex)); + if (atomic_read(&ndev_vif->is_registered)) { + SLSI_NET_ERR(dev, "Register:%pM Failed: Already registered\n", dev->dev_addr); + return 0; + } + + err = register_netdevice(dev); + if (err) + SLSI_NET_ERR(dev, "Register:%pM Failed\n", dev->dev_addr); + else + atomic_set(&ndev_vif->is_registered, 1); + return err; +} + +int slsi_netif_register_rtlnl_locked(struct slsi_dev *sdev, struct net_device *dev) +{ + int err; + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + err = slsi_netif_register_locked(sdev, dev); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return err; +} + +int slsi_netif_register(struct slsi_dev *sdev, struct net_device *dev) +{ + int err; + + rtnl_lock(); + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + err = slsi_netif_register_locked(sdev, dev); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + rtnl_unlock(); + return err; +} + +void slsi_netif_remove_locked(struct slsi_dev *sdev, struct net_device *dev) +{ + int i; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_NET_DBG1(dev, SLSI_NETDEV, "Unregister:%pM\n", dev->dev_addr); + + WARN_ON(!rtnl_is_locked()); + WARN_ON(!SLSI_MUTEX_IS_LOCKED(sdev->netdev_add_remove_mutex)); + + if (atomic_read(&ndev_vif->is_registered)) { + netif_tx_disable(dev); + netif_carrier_off(dev); + + slsi_stop_net_dev(sdev, dev); + } + + rcu_assign_pointer(sdev->netdev[ndev_vif->ifnum], NULL); + synchronize_rcu(); + + /* Free memory of the peer database - Not required for p2p0 interface */ + if (!SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + int queueset; + + for (queueset = 0; queueset < SLSI_ADHOC_PEER_CONNECTIONS_MAX; queueset++) { + kfree(ndev_vif->peer_sta_record[queueset]); + ndev_vif->peer_sta_record[queueset] = NULL; + } + } + + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif)) { + slsi_p2p_deinit(sdev, ndev_vif); + } else if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_NO_VIF; + ndev_vif->vif_type = SLSI_VIFTYPE_UNSPECIFIED; + } + + cancel_delayed_work(&ndev_vif->scan_timeout_work); + ndev_vif->scan[SLSI_SCAN_HW_ID].requeue_timeout_work = false; + + slsi_skb_work_deinit(&ndev_vif->rx_data); + slsi_skb_work_deinit(&ndev_vif->rx_mlme); + + for (i = 0; i < SLSI_SCAN_MAX; i++) + slsi_purge_scan_results(ndev_vif, i); + + slsi_kfree_skb(ndev_vif->sta.mlme_scan_ind_skb); + slsi_roam_channel_cache_prune(dev, 0); + kfree(ndev_vif->probe_req_ies); + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + slsi_netif_rps_map_clear(dev); +#endif + if (atomic_read(&ndev_vif->is_registered)) { + atomic_set(&ndev_vif->is_registered, 0); + unregister_netdevice(dev); + } else { + mutex_lock(&sdev->netdev_remove_mutex); + free_netdev(dev); + mutex_unlock(&sdev->netdev_remove_mutex); + } +} + +void slsi_netif_remove_rtlnl_locked(struct slsi_dev *sdev, struct net_device *dev) +{ + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + slsi_netif_remove_locked(sdev, dev); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); +} + +void slsi_netif_remove(struct slsi_dev *sdev, struct net_device *dev) +{ + rtnl_lock(); + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + slsi_netif_remove_locked(sdev, dev); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + rtnl_unlock(); +} + +void slsi_netif_remove_all(struct slsi_dev *sdev) +{ + int i; + + SLSI_DBG1(sdev, SLSI_NETDEV, "\n"); + rtnl_lock(); + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) + if (sdev->netdev[i]) + slsi_netif_remove_locked(sdev, sdev->netdev[i]); + rcu_assign_pointer(sdev->netdev_ap, NULL); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + rtnl_unlock(); +} + +void slsi_netif_deinit(struct slsi_dev *sdev) +{ + SLSI_DBG1(sdev, SLSI_NETDEV, "\n"); + slsi_netif_remove_all(sdev); +} + +#ifndef CONFIG_ARM +static int slsi_netif_tcp_ack_suppression_start(struct net_device *dev) +{ + int index; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_tcp_ack_s *tcp_ack; + + ndev_vif->last_tcp_ack = NULL; + for (index = 0; index < TCP_ACK_SUPPRESSION_RECORDS_MAX; index++) { + tcp_ack = &ndev_vif->ack_suppression[index]; + tcp_ack->dport = 0; + tcp_ack->daddr = 0; + tcp_ack->sport = 0; + tcp_ack->saddr = 0; + tcp_ack->ack_seq = 0; + tcp_ack->count = 0; + tcp_ack->max = 0; + tcp_ack->age = 0; + skb_queue_head_init(&tcp_ack->list); +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + timer_setup(&tcp_ack->timer, slsi_netif_tcp_ack_suppression_timeout, 0); +#else + tcp_ack->timer.function = slsi_netif_tcp_ack_suppression_timeout; + tcp_ack->timer.data = (unsigned long)tcp_ack; + init_timer(&tcp_ack->timer); +#endif + tcp_ack->state = 1; + slsi_spinlock_create(&tcp_ack->lock); + } + + memset(&ndev_vif->tcp_ack_stats, 0, sizeof(struct slsi_tcp_ack_stats)); + return 0; +} + +static int slsi_netif_tcp_ack_suppression_stop(struct net_device *dev) +{ + int index; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_tcp_ack_s *tcp_ack; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + for (index = 0; index < TCP_ACK_SUPPRESSION_RECORDS_MAX; index++) { + tcp_ack = &ndev_vif->ack_suppression[index]; + del_timer_sync(&tcp_ack->timer); + slsi_spinlock_lock(&tcp_ack->lock); + tcp_ack->state = 0; + skb_queue_purge(&tcp_ack->list); + slsi_spinlock_unlock(&tcp_ack->lock); + } + ndev_vif->last_tcp_ack = NULL; + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return 0; +} + +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE +static void slsi_netif_tcp_ack_suppression_timeout(struct timer_list *t) +#else +static void slsi_netif_tcp_ack_suppression_timeout(unsigned long data) +#endif +{ +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + struct slsi_tcp_ack_s *tcp_ack = from_timer(tcp_ack, t, timer); +#else + struct slsi_tcp_ack_s *tcp_ack = (struct slsi_tcp_ack_s *)data; +#endif + struct sk_buff *skb; + struct netdev_vif *ndev_vif; + struct slsi_dev *sdev; + int r; + + if (!tcp_ack) + return; + + if (!tcp_ack->state) + return; + + slsi_spinlock_lock(&tcp_ack->lock); + while ((skb = skb_dequeue(&tcp_ack->list)) != 0) { + tcp_ack->count = 0; + + if (!skb->dev) { + kfree_skb(skb); + slsi_spinlock_unlock(&tcp_ack->lock); + return; + } + ndev_vif = netdev_priv(skb->dev); + sdev = ndev_vif->sdev; + ndev_vif->tcp_ack_stats.tack_timeout++; + + r = slsi_tx_data(sdev, skb->dev, skb); + if (r == 0) { + ndev_vif->tcp_ack_stats.tack_sent++; + tcp_ack->last_sent = ktime_get(); + } else if (r == -ENOSPC) { + ndev_vif->tcp_ack_stats.tack_dropped++; + slsi_kfree_skb(skb); + } else { + ndev_vif->tcp_ack_stats.tack_dropped++; + } + } + slsi_spinlock_unlock(&tcp_ack->lock); +} + +static int slsi_netif_tcp_ack_suppression_option(struct sk_buff *skb, u32 option) +{ + unsigned char *options; + u32 optlen = 0, len = 0; + + if (tcp_hdr(skb)->doff > 5) + optlen = (tcp_hdr(skb)->doff - 5) * 4; + + options = ((u8 *)tcp_hdr(skb)) + TCP_ACK_SUPPRESSION_OPTIONS_OFFSET; + + while (optlen > 0) { + switch (options[0]) { + case TCP_ACK_SUPPRESSION_OPTION_EOL: + return 0; + case TCP_ACK_SUPPRESSION_OPTION_NOP: + len = 1; + break; + case TCP_ACK_SUPPRESSION_OPTION_MSS: + if (option == TCP_ACK_SUPPRESSION_OPTION_MSS) + return ((options[2] << 8) | options[3]); + len = options[1]; + break; + case TCP_ACK_SUPPRESSION_OPTION_WINDOW: + if (option == TCP_ACK_SUPPRESSION_OPTION_WINDOW) + return options[2]; + len = 1; + break; + case TCP_ACK_SUPPRESSION_OPTION_SACK: + if (option == TCP_ACK_SUPPRESSION_OPTION_SACK) + return 1; + len = options[1]; + break; + default: + len = options[1]; + break; + } + /* if length field in TCP options is 0, or greater than + * total options length, then options are incorrect; return here + */ + if ((len == 0) || (len > optlen)) { + SLSI_DBG_HEX_NODEV(SLSI_TX, skb->data, skb->len < 128 ? skb->len : 128, "SKB:\n"); + return 0; + } + optlen -= len; + options += len; + } + return 0; +} + +static void slsi_netif_tcp_ack_suppression_syn(struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_tcp_ack_s *tcp_ack; + int index; + + SLSI_NET_DBG2(dev, SLSI_TX, "\n"); + for (index = 0; index < TCP_ACK_SUPPRESSION_RECORDS_MAX; index++) { + tcp_ack = &ndev_vif->ack_suppression[index]; + slsi_spinlock_lock(&tcp_ack->lock); + + if (!tcp_ack->state) { + slsi_spinlock_unlock(&tcp_ack->lock); + return; + } + /* Recover old/hung/unused record. */ + if (tcp_ack->daddr) { + if (ktime_to_ms(ktime_sub(ktime_get(), tcp_ack->last_sent)) >= TCP_ACK_SUPPRESSION_RECORD_UNUSED_TIMEOUT * 1000) { + SLSI_NET_DBG2(dev, SLSI_TX, "delete at %d (%pI4.%d > %pI4.%d)\n", index, &tcp_ack->saddr, ntohs(tcp_ack->sport), &tcp_ack->daddr, ntohs(tcp_ack->dport)); + skb_queue_purge(&tcp_ack->list); + tcp_ack->dport = 0; + tcp_ack->sport = 0; + tcp_ack->daddr = 0; + tcp_ack->saddr = 0; + tcp_ack->count = 0; + tcp_ack->ack_seq = 0; + del_timer(&tcp_ack->timer); + } + } + + if (tcp_ack->daddr == 0) { + SLSI_NET_DBG2(dev, SLSI_TX, "add at %d (%pI4.%d > %pI4.%d)\n", index, &ip_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->source), &ip_hdr(skb)->daddr, ntohs(tcp_hdr(skb)->dest)); + tcp_ack->daddr = ip_hdr(skb)->daddr; + tcp_ack->saddr = ip_hdr(skb)->saddr; + tcp_ack->dport = tcp_hdr(skb)->dest; + tcp_ack->sport = tcp_hdr(skb)->source; + tcp_ack->count = 0; + tcp_ack->ack_seq = 0; + tcp_ack->slow_start_count = 0; + tcp_ack->tcp_slow_start = true; + if (tcp_ack_suppression_monitor) { + tcp_ack->max = 0; + tcp_ack->age = 0; + } else { + tcp_ack->max = tcp_ack_suppression_max; + tcp_ack->age = tcp_ack_suppression_timeout; + } + tcp_ack->last_sent = ktime_get(); + + if (tcp_ack_suppression_monitor) { + tcp_ack->last_sample_time = ktime_get(); + tcp_ack->last_ack_seq = 0; + tcp_ack->last_tcp_rate = 0; + tcp_ack->num_bytes = 0; + tcp_ack->hysteresis = 0; + } +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + tcp_ack->stream_id = index; +#endif + /* read and validate the window scaling multiplier */ + tcp_ack->window_multiplier = slsi_netif_tcp_ack_suppression_option(skb, TCP_ACK_SUPPRESSION_OPTION_WINDOW); + if (tcp_ack->window_multiplier > 14) + tcp_ack->window_multiplier = 0; + tcp_ack->mss = slsi_netif_tcp_ack_suppression_option(skb, TCP_ACK_SUPPRESSION_OPTION_MSS); + SLSI_NET_DBG2(dev, SLSI_TX, "options: mss:%u, window:%u\n", tcp_ack->mss, tcp_ack->window_multiplier); + SCSC_HIP4_SAMPLER_TCP_SYN(ndev_vif->sdev->minor_prof, index, tcp_ack->mss); + SCSC_HIP4_SAMPLER_TCP_DATA(ndev_vif->sdev->minor_prof, index, be32_to_cpu(tcp_hdr(skb)->seq)); + slsi_spinlock_unlock(&tcp_ack->lock); + return; + } + slsi_spinlock_unlock(&tcp_ack->lock); + } +} + +static void slsi_netif_tcp_ack_suppression_fin(struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_tcp_ack_s *tcp_ack; + int index; + + SLSI_NET_DBG2(dev, SLSI_TX, "\n"); + for (index = 0; index < TCP_ACK_SUPPRESSION_RECORDS_MAX; index++) { + tcp_ack = &ndev_vif->ack_suppression[index]; + slsi_spinlock_lock(&tcp_ack->lock); + + if ((tcp_ack->dport == tcp_hdr(skb)->dest) && + (tcp_ack->daddr == ip_hdr(skb)->daddr)) { + SLSI_NET_DBG2(dev, SLSI_TX, "delete at %d (%pI4.%d > %pI4.%d)\n", index, &tcp_ack->saddr, ntohs(tcp_ack->sport), &tcp_ack->daddr, ntohs(tcp_ack->dport)); + skb_queue_purge(&tcp_ack->list); + tcp_ack->dport = 0; + tcp_ack->sport = 0; + tcp_ack->daddr = 0; + tcp_ack->saddr = 0; + tcp_ack->count = 0; + tcp_ack->ack_seq = 0; + + if (tcp_ack_suppression_monitor) { + tcp_ack->last_ack_seq = 0; + tcp_ack->last_tcp_rate = 0; + tcp_ack->num_bytes = 0; + tcp_ack->hysteresis = 0; + } + + del_timer(&tcp_ack->timer); +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + tcp_ack->stream_id = 0; +#endif + SCSC_HIP4_SAMPLER_TCP_FIN(ndev_vif->sdev->minor_prof, index); + slsi_spinlock_unlock(&tcp_ack->lock); + return; + } + slsi_spinlock_unlock(&tcp_ack->lock); + } +} + +static struct sk_buff *slsi_netif_tcp_ack_suppression_pkt(struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int index, found; + struct slsi_tcp_ack_s *tcp_ack; + int forward_now = 0, flush = 0; + struct sk_buff *cskb = 0; + u32 tcp_recv_window_size = 0; + + if (tcp_ack_suppression_disable) + return skb; + + if (tcp_ack_suppression_disable_2g && !SLSI_IS_VIF_CHANNEL_5G(ndev_vif)) + return skb; + + /* for AP type (AP or P2P Go) check if the packet is local or intra BSS. If intra BSS then + * the IP header and TCP header are not set; so return the SKB + */ + if ((ndev_vif->vif_type == FAPI_VIFTYPE_AP) && (compare_ether_addr(eth_hdr(skb)->h_source, dev->dev_addr) != 0)) + return skb; + + /* Return SKB that doesn't match. */ + if (be16_to_cpu(eth_hdr(skb)->h_proto) != ETH_P_IP) + return skb; + if (ip_hdr(skb)->protocol != IPPROTO_TCP) + return skb; + if (!skb_transport_header_was_set(skb)) + return skb; + if (tcp_hdr(skb)->syn) { + slsi_netif_tcp_ack_suppression_syn(dev, skb); + return skb; + } + if (tcp_hdr(skb)->fin) { + slsi_netif_tcp_ack_suppression_fin(dev, skb); + return skb; + } + if (!tcp_hdr(skb)->ack) + return skb; + if (tcp_hdr(skb)->rst) + return skb; + if (tcp_hdr(skb)->urg) + return skb; + + ndev_vif->tcp_ack_stats.tack_acks++; + /* If we find a record, leave the spinlock taken until the end of the function. */ + found = 0; + if (ndev_vif->last_tcp_ack) { + tcp_ack = ndev_vif->last_tcp_ack; + slsi_spinlock_lock(&tcp_ack->lock); + if (!tcp_ack->state) { + slsi_spinlock_unlock(&tcp_ack->lock); + ndev_vif->tcp_ack_stats.tack_sent++; + SLSI_ERR_NODEV("last_tcp_ack record not enabled\n"); + return skb; + } + if ((tcp_ack->dport == tcp_hdr(skb)->dest) && + (tcp_ack->sport == tcp_hdr(skb)->source) && + (tcp_ack->daddr == ip_hdr(skb)->daddr)) { + found = 1; + ndev_vif->tcp_ack_stats.tack_lastrecord++; + } else { + slsi_spinlock_unlock(&tcp_ack->lock); + } + } + if (found == 0) { + /* Search for an existing record on this connection. */ + for (index = 0; index < TCP_ACK_SUPPRESSION_RECORDS_MAX; index++) { + tcp_ack = &ndev_vif->ack_suppression[index]; + + slsi_spinlock_lock(&tcp_ack->lock); + + if (!tcp_ack->state) { + slsi_spinlock_unlock(&tcp_ack->lock); + ndev_vif->tcp_ack_stats.tack_sent++; + SLSI_ERR_NODEV("tcp_ack record %d not enabled\n", index); + return skb; + } + if ((tcp_ack->dport == tcp_hdr(skb)->dest) && + (tcp_ack->sport == tcp_hdr(skb)->source) && + (tcp_ack->daddr == ip_hdr(skb)->daddr)) { + found = 1; + ndev_vif->tcp_ack_stats.tack_searchrecord++; + break; + } + slsi_spinlock_unlock(&tcp_ack->lock); + } + if (found == 0) { + /* No record found, so We cannot suppress the ack, return. */ + ndev_vif->tcp_ack_stats.tack_norecord++; + ndev_vif->tcp_ack_stats.tack_sent++; + return skb; + } + ndev_vif->last_tcp_ack = tcp_ack; + } + + /* If it is a DUP Ack, send straight away without flushing the cache. */ + if (be32_to_cpu(tcp_hdr(skb)->ack_seq) < tcp_ack->ack_seq) { + /* check for wrap-around */ + if (((s32)((u32)be32_to_cpu(tcp_hdr(skb)->ack_seq) - (u32)tcp_ack->ack_seq)) < 0) { + ndev_vif->tcp_ack_stats.tack_dacks++; + ndev_vif->tcp_ack_stats.tack_sent++; + slsi_spinlock_unlock(&tcp_ack->lock); + return skb; + } + } + + /* Has data, forward straight away. */ + if (be16_to_cpu(ip_hdr(skb)->tot_len) > ((ip_hdr(skb)->ihl * 4) + (tcp_hdr(skb)->doff * 4))) { + SCSC_HIP4_SAMPLER_TCP_DATA(ndev_vif->sdev->minor_prof, tcp_ack->stream_id, be32_to_cpu(tcp_hdr(skb)->seq)); + SCSC_HIP4_SAMPLER_TCP_CWND(ndev_vif->sdev->minor_prof, tcp_ack->stream_id, (skb->sk) ? tcp_sk(skb->sk)->snd_cwnd : 0); + SCSC_HIP4_SAMPLER_TCP_SEND_BUF(ndev_vif->sdev->minor_prof, tcp_ack->stream_id, sysctl_tcp_wmem[2]); + ndev_vif->tcp_ack_stats.tack_hasdata++; + forward_now = 1; + goto _forward_now; + } + + /* PSH flag set, forward straight away. */ + if (tcp_hdr(skb)->psh) { + ndev_vif->tcp_ack_stats.tack_psh++; + forward_now = 1; + goto _forward_now; + } + + /* The ECE flag is set for Explicit Congestion Notification supporting connections when the ECT flag + * is set in the segment packet. We must forward ECE marked acks immediately for ECN to work. + */ + if (tcp_hdr(skb)->ece) { + ndev_vif->tcp_ack_stats.tack_ece++; + forward_now = 1; + goto _forward_now; + } + + if (tcp_ack_suppression_monitor) { + /* Measure the throughput of TCP stream by monitoring the bytes Acked by each Ack over a + * sampling period. Based on throughput apply different degree of Ack suppression + */ + if (tcp_ack->last_ack_seq) + tcp_ack->num_bytes += ((u32)be32_to_cpu(tcp_hdr(skb)->ack_seq) - tcp_ack->last_ack_seq); + + tcp_ack->last_ack_seq = be32_to_cpu(tcp_hdr(skb)->ack_seq); + if (ktime_to_ms(ktime_sub(ktime_get(), tcp_ack->last_sample_time)) > tcp_ack_suppression_monitor_interval) { + u16 acks_max; + u32 tcp_rate = ((tcp_ack->num_bytes * 8) / (tcp_ack_suppression_monitor_interval * 1000)); + + SLSI_NET_DBG2(dev, SLSI_TX, "hysteresis:%u total_bytes:%llu rate:%u Mbps\n", + tcp_ack->hysteresis, tcp_ack->num_bytes, tcp_rate); + + /* hysterisis - change only if the variation from last value is more than threshold */ + if ((abs(tcp_rate - tcp_ack->last_tcp_rate)) > tcp_ack->hysteresis) { + if (tcp_rate >= tcp_ack_suppression_rate_very_high) { + tcp_ack->max = tcp_ack_suppression_rate_very_high_acks; + tcp_ack->age = tcp_ack_suppression_rate_very_high_timeout; + } else if (tcp_rate >= tcp_ack_suppression_rate_high) { + tcp_ack->max = tcp_ack_suppression_rate_high_acks; + tcp_ack->age = tcp_ack_suppression_rate_high_timeout; + } else if (tcp_rate >= tcp_ack_suppression_rate_low) { + tcp_ack->max = tcp_ack_suppression_rate_low_acks; + tcp_ack->age = tcp_ack_suppression_rate_low_timeout; + } else { + tcp_ack->max = 0; + tcp_ack->age = 0; + } + + /* Should not be suppressing Acks more than 20% of receiver window size + * doing so can lead to increased RTT and low transmission rate at the + * TCP sender + */ + if (tcp_ack->window_multiplier) + tcp_recv_window_size = be16_to_cpu(tcp_hdr(skb)->window) * (2 << tcp_ack->window_multiplier); + else + tcp_recv_window_size = be16_to_cpu(tcp_hdr(skb)->window); + SCSC_HIP4_SAMPLER_TCP_RWND(ndev_vif->sdev->minor_prof, tcp_ack->stream_id, tcp_recv_window_size); + + acks_max = (tcp_recv_window_size / 5) / (2 * tcp_ack->mss); + if (tcp_ack->max > acks_max) + tcp_ack->max = acks_max; + } + tcp_ack->hysteresis = tcp_rate / 5; /* 20% hysteresis */ + tcp_ack->last_tcp_rate = tcp_rate; + tcp_ack->num_bytes = 0; + tcp_ack->last_sample_time = ktime_get(); + } + } + + /* Do not suppress Selective Acks. */ + if (slsi_netif_tcp_ack_suppression_option(skb, TCP_ACK_SUPPRESSION_OPTION_SACK)) { + ndev_vif->tcp_ack_stats.tack_sacks++; + + /* A TCP selective Ack suggests TCP segment loss. The TCP sender + * may reduce congestion window and limit the number of segments + * it sends before waiting for Ack. + * It is ideal to switch off TCP ack suppression for certain time + * (being replicated here by tcp_ack_suppression_slow_start_acks + * count) and send as many Acks as possible to allow the cwnd to + * grow at the TCP sender + */ + tcp_ack->slow_start_count = 0; + tcp_ack->tcp_slow_start = true; + forward_now = 1; + goto _forward_now; + } + + if (be32_to_cpu(tcp_hdr(skb)->ack_seq) == tcp_ack->ack_seq) { + ndev_vif->tcp_ack_stats.tack_dacks++; + forward_now = 1; + goto _forward_now; + } + + /* When the TCP connection is made, wait until a number of Acks + * are sent before applying the suppression rules. It is to + * allow the cwnd to grow at a normal rate at the TCP sender + */ + if (tcp_ack->tcp_slow_start) { + tcp_ack->slow_start_count++; + if (tcp_ack->slow_start_count >= tcp_ack_suppression_slow_start_acks) { + tcp_ack->slow_start_count = 0; + tcp_ack->tcp_slow_start = false; + } + forward_now = 1; + goto _forward_now; + } + + /* do not suppress if so decided by the TCP monitor */ + if (tcp_ack_suppression_monitor && (!tcp_ack->max || !tcp_ack->age)) { + forward_now = 1; + goto _forward_now; + } + + /* do not suppress delayed Acks that acknowledges for more than 2 TCP + * maximum size segments + */ + if (((u32)be32_to_cpu(tcp_hdr(skb)->ack_seq)) - (tcp_ack->ack_seq) > (2 * tcp_ack->mss)) { + ndev_vif->tcp_ack_stats.tack_delay_acks++; + forward_now = 1; + goto _forward_now; + } + + /* Do not suppress unless the receive window is large + * enough. + * With low receive window size the cwnd can't grow much. + * So suppressing Acks has a negative impact on sender + * rate as it increases the Round trip time measured at + * sender + */ + if (!tcp_ack_suppression_monitor) { + if (tcp_ack->window_multiplier) + tcp_recv_window_size = be16_to_cpu(tcp_hdr(skb)->window) * (2 << tcp_ack->window_multiplier); + else + tcp_recv_window_size = be16_to_cpu(tcp_hdr(skb)->window); + if (tcp_recv_window_size < tcp_ack_suppression_rcv_window * 1024) { + ndev_vif->tcp_ack_stats.tack_low_window++; + forward_now = 1; + goto _forward_now; + } + } + + if (!tcp_ack_suppression_monitor && ktime_to_ms(ktime_sub(ktime_get(), tcp_ack->last_sent)) >= tcp_ack->age) { + ndev_vif->tcp_ack_stats.tack_ktime++; + forward_now = 1; + goto _forward_now; + } + + /* Test for a new cache */ + if (!skb_queue_len(&tcp_ack->list)) { + skb_queue_tail(&tcp_ack->list, skb); + tcp_ack->count = 1; + tcp_ack->ack_seq = be32_to_cpu(tcp_hdr(skb)->ack_seq); + if (tcp_ack->age) + mod_timer(&tcp_ack->timer, jiffies + msecs_to_jiffies(tcp_ack->age)); + slsi_spinlock_unlock(&tcp_ack->lock); + return 0; + } +_forward_now: + cskb = skb_dequeue(&tcp_ack->list); + if (cskb) { + if (tcp_ack_suppression_monitor && tcp_ack->age) + mod_timer(&tcp_ack->timer, jiffies + msecs_to_jiffies(tcp_ack->age)); + ndev_vif->tcp_ack_stats.tack_suppressed++; + slsi_kfree_skb(cskb); + } + skb_queue_tail(&tcp_ack->list, skb); + tcp_ack->ack_seq = be32_to_cpu(tcp_hdr(skb)->ack_seq); + tcp_ack->count++; + if (forward_now) { + flush = 1; + } else { + if (tcp_ack->count >= tcp_ack->max) { + flush = 1; + ndev_vif->tcp_ack_stats.tack_max++; + } + } + if (!flush) { + slsi_spinlock_unlock(&tcp_ack->lock); + return 0; + } + /* Flush the cache. */ + cskb = skb_dequeue(&tcp_ack->list); + tcp_ack->count = 0; + + if (tcp_ack->age) + del_timer(&tcp_ack->timer); + + tcp_ack->last_sent = ktime_get(); + + slsi_spinlock_unlock(&tcp_ack->lock); + ndev_vif->tcp_ack_stats.tack_sent++; + return cskb; +} +#endif diff --git a/drivers/net/wireless/scsc/netif.h b/drivers/net/wireless/scsc/netif.h new file mode 100755 index 000000000000..3a59c9458173 --- /dev/null +++ b/drivers/net/wireless/scsc/netif.h @@ -0,0 +1,105 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "dev.h" + +#ifndef __SLSI_NETIF_H__ +#define __SLSI_NETIF_H__ + +/* net_device queues + * --------------------------------------------- + * 1 Queue for Security frames (EAPOL, WAPI etc) + * 1 Queue for Broadcast/Multicast when in AP mode. + * 4 Queues per Peer + * + * STA/ADHOC + * Queues + * ------------------------------------------------------- + * | 0 | 1 | 2 - 5 | 6 | 7 | 8 | 9 | + * ------------------------------------------------------- + * | Eapol | Discard | Not | AC | AC | AC | AC | + * | Frames | Queue | Used | 0 | 1 | 2 | 3 | + * ------------------------------------------------------- + * + * AP + * Queues + * -------------------------------------------------------- + * | Peer 1 ACs (0 - 4) | Peer 2 ACs (0 - 4) | ...... + * -------------------------------------------------------------------------------------------------------------------- + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ...... + * -------------------------------------------------------------------------------------------------------------------- + * | Eapol | Discard |B/M Cast |B/M Cast |B/M Cast |B/M Cast | AC | AC | AC | AC | AC | AC | AC | AC | ...... + * | Frames | Queue | AC 0 | AC 1 | AC 2 | AC 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | ...... + * -------------------------------------------------------------------------------------------------------------------- + */ + +#ifndef ETH_P_PAE +#define ETH_P_PAE 0x888e +#endif +#ifndef ETH_P_WAI +#define ETH_P_WAI 0x88b4 +#endif + +#define SLSI_NETIF_Q_PRIORITY 0 +#define SLSI_NETIF_Q_DISCARD 1 +#define SLSI_NETIF_Q_MULTICAST_START 2 +#define SLSI_NETIF_Q_PEER_START 6 + +#define SLSI_NETIF_Q_PER_PEER 4 + +#define SLSI_NETIF_SKB_HEADROOM (68 + 160) /* sizeof ma_unitdata_req [36] + pad [30] + pad_words [2] */ +#define SLSI_NETIF_SKB_TAILROOM 0 + +#define SLSI_IS_MULTICAST_QUEUE_MAPPING(queue) (queue >= SLSI_NETIF_Q_MULTICAST_START && queue < (SLSI_NETIF_Q_MULTICAST_START + SLSI_NETIF_Q_PER_PEER) ? 1 : 0) + +static inline u16 slsi_netif_get_peer_queue(s16 queueset, s16 ac) +{ + WARN_ON(ac > SLSI_NETIF_Q_PER_PEER); + return SLSI_NETIF_Q_PEER_START + (queueset * SLSI_NETIF_Q_PER_PEER) + ac; +} + +/* queueset is one less than the assigned aid. */ +static inline unsigned short slsi_netif_get_qs_from_queue(short queue, short ac) +{ + return ((queue - ac - SLSI_NETIF_Q_PEER_START) / SLSI_NETIF_Q_PER_PEER); +} + +static inline u16 slsi_netif_get_multicast_queue(s16 ac) +{ + WARN_ON(ac > SLSI_NETIF_Q_PER_PEER); + return SLSI_NETIF_Q_MULTICAST_START + ac; +} + +#define MAP_QS_TO_AID(qs) (qs + 1) +#define MAP_AID_TO_QS(aid) (aid - 1) + +enum slsi_traffic_q { + SLSI_TRAFFIC_Q_BE = 0, + SLSI_TRAFFIC_Q_BK, + SLSI_TRAFFIC_Q_VI, + SLSI_TRAFFIC_Q_VO, +}; + +enum slsi_traffic_q slsi_frame_priority_to_ac_queue(u16 priority); +int slsi_ac_to_tids(enum slsi_traffic_q ac, int *tids); + +struct slsi_dev; +struct slsi_peer; + +int slsi_netif_init(struct slsi_dev *sdev); +/* returns the index or -E code */ +int slsi_netif_dynamic_iface_add(struct slsi_dev *sdev, const char *name); +int slsi_netif_register(struct slsi_dev *sdev, struct net_device *dev); +int slsi_netif_register_rtlnl_locked(struct slsi_dev *sdev, struct net_device *dev); +void slsi_netif_remove(struct slsi_dev *sdev, struct net_device *dev); +void slsi_netif_remove_rtlnl_locked(struct slsi_dev *sdev, struct net_device *dev); +void slsi_netif_remove_all(struct slsi_dev *sdev); +void slsi_netif_deinit(struct slsi_dev *sdev); +void slsi_tdls_move_packets(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_peer *sta_peer, struct slsi_peer *tdls_peer, bool connection); +void slsi_netif_remove_locked(struct slsi_dev *sdev, struct net_device *dev); + +#endif /*__SLSI_NETIF_H__*/ diff --git a/drivers/net/wireless/scsc/nl80211_vendor.c b/drivers/net/wireless/scsc/nl80211_vendor.c new file mode 100755 index 000000000000..628ebf421d29 --- /dev/null +++ b/drivers/net/wireless/scsc/nl80211_vendor.c @@ -0,0 +1,4920 @@ +/***************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include +#include +#include +#include "dev.h" +#include "cfg80211_ops.h" +#include "debug.h" +#include "mgt.h" +#include "mlme.h" +#include "netif.h" +#include "unifiio.h" +#include "mib.h" +#include "nl80211_vendor.h" + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING +#include "scsc_wifilogger.h" +#include "scsc_wifilogger_rings.h" +#include "scsc_wifilogger_types.h" +#endif +#define SLSI_WIFI_TAG_RSSI 21 +#define SLSI_WIFI_TAG_REASON_CODE 14 +#define SLSI_WIFI_TAG_VENDOR_SPECIFIC 0 +#define SLSI_WIFI_TAG_EAPOL_MESSAGE_TYPE 29 +#define SLSI_WIFI_TAG_STATUS 4 + +#define SLSI_GSCAN_INVALID_RSSI 0x7FFF +#define SLSI_EPNO_AUTH_FIELD_WEP_OPEN 1 +#define SLSI_EPNO_AUTH_FIELD_WPA_PSK 2 +#define SLSI_EPNO_AUTH_FIELD_WPA_EAP 4 +#define WIFI_EVENT_FW_BTM_FRAME_REQUEST 56 // Request for a BTM frame is received +#define WIFI_EVENT_FW_BTM_FRAME_RESPONSE 57 // A BTM frame is transmitted. +#define WIFI_EVENT_FW_NR_FRAME_REQUEST 58 +#define WIFI_EVENT_FW_RM_FRAME_RESPONSE 59 + +#define SLSI_WIFI_TAG_VD_CHANNEL_UTILISATION 0xf01a +#define SLSI_WIFI_TAG_VD_ROAMING_REASON 0xf019 +#define SLSI_WIFI_TAG_VD_BTM_REQUEST_MODE 0xf01b +#define SLSI_WIFI_TAG_VD_BTM_RESPONSE_STATUS 0xf01c +#define SLSI_WIFI_TAG_VD_RETRY_COUNT 0xf00f +#define SLSI_WIFI_TAG_VD_EAPOL_KEY_TYPE 0xF008 +#define SLSI_WIFI_EAPOL_KEY_TYPE_GTK 0x0000 +#define SLSI_WIFI_EAPOL_KEY_TYPE_PTK 0x0001 +#define SLSI_WIFI_ROAMING_SEARCH_REASON_RESERVED 0 +#define SLSI_WIFI_ROAMING_SEARCH_REASON_LOW_RSSI 1 +#define SLSI_WIFI_ROAMING_SEARCH_REASON_LINK_LOSS 2 +#define SLSI_WIFI_ROAMING_SEARCH_REASON_BTM_REQ 3 +#define SLSI_WIFI_ROAMING_SEARCH_REASON_CU_TRIGGER 4 + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING +static int mem_dump_buffer_size; +static char *mem_dump_buffer; +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG +char *slsi_print_event_name(int event_id) +{ + switch (event_id) { + case SLSI_NL80211_SCAN_RESULTS_AVAILABLE_EVENT: + return "SCAN_RESULTS_AVAILABLE_EVENT"; + case SLSI_NL80211_FULL_SCAN_RESULT_EVENT: + return "FULL_SCAN_RESULT_EVENT"; + case SLSI_NL80211_SCAN_EVENT: + return "BUCKET_SCAN_DONE_EVENT"; +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD + case SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH: + return "KEY_MGMT_ROAM_AUTH"; +#endif + case SLSI_NL80211_VENDOR_HANGED_EVENT: + return "SLSI_NL80211_VENDOR_HANGED_EVENT"; + case SLSI_NL80211_EPNO_EVENT: + return "SLSI_NL80211_EPNO_EVENT"; + case SLSI_NL80211_HOTSPOT_MATCH: + return "SLSI_NL80211_HOTSPOT_MATCH"; + case SLSI_NL80211_RSSI_REPORT_EVENT: + return "SLSI_NL80211_RSSI_REPORT_EVENT"; +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + case SLSI_NL80211_LOGGER_RING_EVENT: + return "SLSI_NL80211_LOGGER_RING_EVENT"; + case SLSI_NL80211_LOGGER_FW_DUMP_EVENT: + return "SLSI_NL80211_LOGGER_FW_DUMP_EVENT"; +#endif + case SLSI_NL80211_NAN_RESPONSE_EVENT: + return "SLSI_NL80211_NAN_RESPONSE_EVENT"; + case SLSI_NL80211_NAN_PUBLISH_TERMINATED_EVENT: + return "SLSI_NL80211_NAN_PUBLISH_TERMINATED_EVENT"; + case SLSI_NL80211_NAN_MATCH_EVENT: + return "SLSI_NL80211_NAN_MATCH_EVENT"; + case SLSI_NL80211_NAN_MATCH_EXPIRED_EVENT: + return "SLSI_NL80211_NAN_MATCH_EXPIRED_EVENT"; + case SLSI_NL80211_NAN_SUBSCRIBE_TERMINATED_EVENT: + return "SLSI_NL80211_NAN_SUBSCRIBE_TERMINATED_EVENT"; + case SLSI_NL80211_NAN_FOLLOWUP_EVENT: + return "SLSI_NL80211_NAN_FOLLOWUP_EVENT"; + case SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT: + return "SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT"; + case SLSI_NL80211_RTT_RESULT_EVENT: + return "SLSI_NL80211_RTT_RESULT_EVENT"; + case SLSI_NL80211_RTT_COMPLETE_EVENT: + return "SLSI_NL80211_RTT_COMPLETE_EVENT"; + case SLSI_NL80211_VENDOR_ACS_EVENT: + return "SLSI_NL80211_VENDOR_ACS_EVENT"; + default: + return "UNKNOWN_EVENT"; + } +} +#endif + +int slsi_vendor_event(struct slsi_dev *sdev, int event_id, const void *data, int len) +{ + struct sk_buff *skb; + +#ifdef CONFIG_SCSC_WLAN_DEBUG + SLSI_DBG1_NODEV(SLSI_MLME, "Event: %s(%d), data = %p, len = %d\n", + slsi_print_event_name(event_id), event_id, data, len); +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, len, event_id, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, len, event_id, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for vendor event: %d\n", event_id); + return -ENOMEM; + } + + nla_put_nohdr(skb, len, data); + + cfg80211_vendor_event(skb, GFP_KERNEL); + + return 0; +} + +static int slsi_vendor_cmd_reply(struct wiphy *wiphy, const void *data, int len) +{ + struct sk_buff *skb; + + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, len); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb\n"); + return -ENOMEM; + } + + nla_put_nohdr(skb, len, data); + + return cfg80211_vendor_cmd_reply(skb); +} + +static struct net_device *slsi_gscan_get_netdev(struct slsi_dev *sdev) +{ + return slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); +} + +static struct netdev_vif *slsi_gscan_get_vif(struct slsi_dev *sdev) +{ + struct net_device *dev; + + dev = slsi_gscan_get_netdev(sdev); + if (!dev) { + SLSI_WARN_NODEV("Dev is NULL\n"); + return NULL; + } + + return netdev_priv(dev); +} + +#ifdef CONFIG_SCSC_WLAN_DEBUG +static void slsi_gscan_add_dump_params(struct slsi_nl_gscan_param *nl_gscan_param) +{ + int i; + int j; + + SLSI_DBG2_NODEV(SLSI_GSCAN, "Parameters for SLSI_NL80211_VENDOR_SUBCMD_ADD_GSCAN sub-command:\n"); + SLSI_DBG2_NODEV(SLSI_GSCAN, "base_period: %d max_ap_per_scan: %d report_threshold_percent: %d report_threshold_num_scans = %d num_buckets: %d\n", + nl_gscan_param->base_period, nl_gscan_param->max_ap_per_scan, + nl_gscan_param->report_threshold_percent, nl_gscan_param->report_threshold_num_scans, + nl_gscan_param->num_buckets); + + for (i = 0; i < nl_gscan_param->num_buckets; i++) { + SLSI_DBG2_NODEV(SLSI_GSCAN, "Bucket: %d\n", i); + SLSI_DBG2_NODEV(SLSI_GSCAN, "\tbucket_index: %d band: %d period: %d report_events: %d num_channels: %d\n", + nl_gscan_param->nl_bucket[i].bucket_index, nl_gscan_param->nl_bucket[i].band, + nl_gscan_param->nl_bucket[i].period, nl_gscan_param->nl_bucket[i].report_events, + nl_gscan_param->nl_bucket[i].num_channels); + + for (j = 0; j < nl_gscan_param->nl_bucket[i].num_channels; j++) + SLSI_DBG2_NODEV(SLSI_GSCAN, "\tchannel_list[%d]: %d\n", + j, nl_gscan_param->nl_bucket[i].channels[j].channel); + } +} + +void slsi_gscan_scan_res_dump(struct slsi_gscan_result *scan_data) +{ + struct slsi_nl_scan_result_param *nl_scan_res = &scan_data->nl_scan_res; + + SLSI_DBG3_NODEV(SLSI_GSCAN, "TS:%llu SSID:%s BSSID:%pM Chan:%d RSSI:%d Bcn_Int:%d Capab:%#x IE_Len:%d\n", + nl_scan_res->ts, nl_scan_res->ssid, nl_scan_res->bssid, nl_scan_res->channel, + nl_scan_res->rssi, nl_scan_res->beacon_period, nl_scan_res->capability, nl_scan_res->ie_length); + + SLSI_DBG_HEX_NODEV(SLSI_GSCAN, &nl_scan_res->ie_data[0], nl_scan_res->ie_length, "IE_Data:\n"); + if (scan_data->anqp_length) { + SLSI_DBG3_NODEV(SLSI_GSCAN, "ANQP_LENGTH:%d\n", scan_data->anqp_length); + SLSI_DBG_HEX_NODEV(SLSI_GSCAN, nl_scan_res->ie_data + nl_scan_res->ie_length, scan_data->anqp_length, "ANQP_info:\n"); + } +} +#endif + +static int slsi_gscan_get_capabilities(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_nl_gscan_capabilities nl_cap; + int ret = 0; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_GET_GSCAN_CAPABILITIES\n"); + + memset(&nl_cap, 0, sizeof(struct slsi_nl_gscan_capabilities)); + + ret = slsi_mib_get_gscan_cap(sdev, &nl_cap); + if (ret != 0) { + SLSI_ERR(sdev, "Failed to read mib\n"); + return ret; + } + + nl_cap.max_scan_cache_size = SLSI_GSCAN_MAX_SCAN_CACHE_SIZE; + nl_cap.max_ap_cache_per_scan = SLSI_GSCAN_MAX_AP_CACHE_PER_SCAN; + nl_cap.max_scan_reporting_threshold = SLSI_GSCAN_MAX_SCAN_REPORTING_THRESHOLD; + + ret = slsi_vendor_cmd_reply(wiphy, &nl_cap, sizeof(struct slsi_nl_gscan_capabilities)); + if (ret) + SLSI_ERR_NODEV("gscan_get_capabilities vendor cmd reply failed (err = %d)\n", ret); + + return ret; +} + +static u32 slsi_gscan_put_channels(struct ieee80211_supported_band *chan_data, bool no_dfs, bool only_dfs, u32 *buf) +{ + u32 chan_count = 0; + u32 chan_flags; + int i; + + if (chan_data == NULL) { + SLSI_DBG3_NODEV(SLSI_GSCAN, "Band not supported\n"); + return 0; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + chan_flags = (IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_OFDM | IEEE80211_CHAN_RADAR); +#else + chan_flags = (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_OFDM | IEEE80211_CHAN_RADAR); +#endif + + for (i = 0; i < chan_data->n_channels; i++) { + if (chan_data->channels[i].flags & IEEE80211_CHAN_DISABLED) + continue; + if (only_dfs) { + if (chan_data->channels[i].flags & chan_flags) + buf[chan_count++] = chan_data->channels[i].center_freq; + continue; + } + if (no_dfs && (chan_data->channels[i].flags & chan_flags)) + continue; + buf[chan_count++] = chan_data->channels[i].center_freq; + } + return chan_count; +} + +static int slsi_gscan_get_valid_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0, type, band; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + u32 *chan_list; + u32 chan_count = 0, mem_len = 0; + struct sk_buff *reply; + + type = nla_type(data); + + if (type == GSCAN_ATTRIBUTE_BAND) + band = nla_get_u32(data); + else + return -EINVAL; + + if (band == 0) { + SLSI_WARN(sdev, "NO Bands. return 0 channel\n"); + return ret; + } + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + SLSI_DBG3(sdev, SLSI_GSCAN, "band %d\n", band); + if (wiphy->bands[NL80211_BAND_2GHZ]) + mem_len += wiphy->bands[NL80211_BAND_2GHZ]->n_channels * sizeof(u32); + + if (wiphy->bands[NL80211_BAND_5GHZ]) + mem_len += wiphy->bands[NL80211_BAND_5GHZ]->n_channels * sizeof(u32); + + if (mem_len == 0) { + ret = -ENOTSUPP; + goto exit; + } + + chan_list = kmalloc(mem_len, GFP_KERNEL); + if (chan_list == NULL) { + ret = -ENOMEM; + goto exit; + } + mem_len += SLSI_NL_VENDOR_REPLY_OVERHEAD + (SLSI_NL_ATTRIBUTE_U32_LEN * 2); + reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, mem_len); + if (reply == NULL) { + ret = -ENOMEM; + goto exit_with_chan_list; + } + switch (band) { + case WIFI_BAND_BG: + chan_count = slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_2GHZ], false, false, chan_list); + break; + case WIFI_BAND_A: + chan_count = slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_5GHZ], true, false, chan_list); + break; + case WIFI_BAND_A_DFS: + chan_count = slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_5GHZ], false, true, chan_list); + break; + case WIFI_BAND_A_WITH_DFS: + chan_count = slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_5GHZ], false, false, chan_list); + break; + case WIFI_BAND_ABG: + chan_count = slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_2GHZ], true, false, chan_list); + chan_count += slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_5GHZ], true, false, chan_list + chan_count); + break; + case WIFI_BAND_ABG_WITH_DFS: + chan_count = slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_2GHZ], false, false, chan_list); + chan_count += slsi_gscan_put_channels(wiphy->bands[NL80211_BAND_5GHZ], false, false, chan_list + chan_count); + break; + default: + chan_count = 0; + SLSI_WARN(sdev, "Invalid Band %d\n", band); + } + nla_put_u32(reply, GSCAN_ATTRIBUTE_NUM_CHANNELS, chan_count); + nla_put(reply, GSCAN_ATTRIBUTE_CHANNEL_LIST, chan_count * sizeof(u32), chan_list); + + ret = cfg80211_vendor_cmd_reply(reply); + + if (ret) + SLSI_ERR(sdev, "FAILED to reply GET_VALID_CHANNELS\n"); + +exit_with_chan_list: + kfree(chan_list); +exit: + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return ret; +} + +struct slsi_gscan_result *slsi_prepare_scan_result(struct sk_buff *skb, u16 anqp_length, int hs2_id) +{ + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + struct slsi_gscan_result *scan_res; + struct timespec ts; + const u8 *ssid_ie; + int mem_reqd; + int ie_len; + u8 *ie; + + ie = &mgmt->u.beacon.variable[0]; + ie_len = fapi_get_datalen(skb) - (ie - (u8 *)mgmt) - anqp_length; + + /* Exclude 1 byte for ie_data[1]. sizeof(u16) to include anqp_length, sizeof(int) for hs_id */ + mem_reqd = (sizeof(struct slsi_gscan_result) - 1) + ie_len + anqp_length + sizeof(int) + sizeof(u16); + + /* Allocate memory for scan result */ + scan_res = kmalloc(mem_reqd, GFP_KERNEL); + if (scan_res == NULL) { + SLSI_ERR_NODEV("Failed to allocate memory for scan result\n"); + return NULL; + } + + /* Exclude 1 byte for ie_data[1] */ + scan_res->scan_res_len = (sizeof(struct slsi_nl_scan_result_param) - 1) + ie_len; + scan_res->anqp_length = 0; + + get_monotonic_boottime(&ts); + scan_res->nl_scan_res.ts = (u64)TIMESPEC_TO_US(ts); + + ssid_ie = cfg80211_find_ie(WLAN_EID_SSID, &mgmt->u.beacon.variable[0], ie_len); + if ((ssid_ie != NULL) && (ssid_ie[1] > 0) && (ssid_ie[1] < IEEE80211_MAX_SSID_LEN)) { + memcpy(scan_res->nl_scan_res.ssid, &ssid_ie[2], ssid_ie[1]); + scan_res->nl_scan_res.ssid[ssid_ie[1]] = '\0'; + } else { + scan_res->nl_scan_res.ssid[0] = '\0'; + } + + SLSI_ETHER_COPY(scan_res->nl_scan_res.bssid, mgmt->bssid); + scan_res->nl_scan_res.channel = fapi_get_u16(skb, u.mlme_scan_ind.channel_frequency) / 2; + scan_res->nl_scan_res.rssi = fapi_get_s16(skb, u.mlme_scan_ind.rssi); + scan_res->nl_scan_res.rtt = SLSI_GSCAN_RTT_UNSPECIFIED; + scan_res->nl_scan_res.rtt_sd = SLSI_GSCAN_RTT_UNSPECIFIED; + scan_res->nl_scan_res.beacon_period = mgmt->u.beacon.beacon_int; + scan_res->nl_scan_res.capability = mgmt->u.beacon.capab_info; + scan_res->nl_scan_res.ie_length = ie_len; + memcpy(scan_res->nl_scan_res.ie_data, ie, ie_len); + memcpy(scan_res->nl_scan_res.ie_data + ie_len, &hs2_id, sizeof(int)); + memcpy(scan_res->nl_scan_res.ie_data + ie_len + sizeof(int), &anqp_length, sizeof(u16)); + if (anqp_length) { + memcpy(scan_res->nl_scan_res.ie_data + ie_len + sizeof(u16) + sizeof(int), ie + ie_len, anqp_length); + scan_res->anqp_length = anqp_length + sizeof(u16) + sizeof(int); + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + slsi_gscan_scan_res_dump(scan_res); +#endif + + return scan_res; +} + +static void slsi_gscan_hash_add(struct slsi_dev *sdev, struct slsi_gscan_result *scan_res) +{ + u8 key = SLSI_GSCAN_GET_HASH_KEY(scan_res->nl_scan_res.bssid[5]); + struct netdev_vif *ndev_vif; + + ndev_vif = slsi_gscan_get_vif(sdev); + if (!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)) + SLSI_WARN_NODEV("ndev_vif->scan_mutex is not locked\n"); + + scan_res->hnext = sdev->gscan_hash_table[key]; + sdev->gscan_hash_table[key] = scan_res; + + /* Update the total buffer consumed and number of scan results */ + sdev->buffer_consumed += scan_res->scan_res_len; + sdev->num_gscan_results++; +} + +static struct slsi_gscan_result *slsi_gscan_hash_get(struct slsi_dev *sdev, u8 *mac) +{ + struct slsi_gscan_result *temp; + struct netdev_vif *ndev_vif; + u8 key = SLSI_GSCAN_GET_HASH_KEY(mac[5]); + + ndev_vif = slsi_gscan_get_vif(sdev); + + if (!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)) + SLSI_WARN_NODEV("ndev_vif->scan_mutex is not locked\n"); + + temp = sdev->gscan_hash_table[key]; + while (temp != NULL) { + if (memcmp(temp->nl_scan_res.bssid, mac, ETH_ALEN) == 0) + return temp; + temp = temp->hnext; + } + + return NULL; +} + +void slsi_gscan_hash_remove(struct slsi_dev *sdev, u8 *mac) +{ + u8 key = SLSI_GSCAN_GET_HASH_KEY(mac[5]); + struct slsi_gscan_result *curr; + struct slsi_gscan_result *prev; + struct netdev_vif *ndev_vif; + struct slsi_gscan_result *scan_res = NULL; + + ndev_vif = slsi_gscan_get_vif(sdev); + if (!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)) + SLSI_WARN_NODEV("ndev_vif->scan_mutex is not locked\n"); + + if (sdev->gscan_hash_table[key] == NULL) + return; + + if (memcmp(sdev->gscan_hash_table[key]->nl_scan_res.bssid, mac, ETH_ALEN) == 0) { + scan_res = sdev->gscan_hash_table[key]; + sdev->gscan_hash_table[key] = sdev->gscan_hash_table[key]->hnext; + } else { + prev = sdev->gscan_hash_table[key]; + curr = prev->hnext; + + while (curr != NULL) { + if (memcmp(curr->nl_scan_res.bssid, mac, ETH_ALEN) == 0) { + scan_res = curr; + prev->hnext = curr->hnext; + break; + } + prev = curr; + curr = curr->hnext; + } + } + + if (scan_res) { + /* Update the total buffer consumed and number of scan results */ + sdev->buffer_consumed -= scan_res->scan_res_len; + sdev->num_gscan_results--; + kfree(scan_res); + } + + if (sdev->num_gscan_results < 0) + SLSI_ERR(sdev, "Wrong num_gscan_results: %d\n", sdev->num_gscan_results); +} + +int slsi_check_scan_result(struct slsi_dev *sdev, struct slsi_bucket *bucket, struct slsi_gscan_result *new_scan_res) +{ + struct slsi_gscan_result *scan_res; + + /* Check if the scan result for the same BSS already exists in driver buffer */ + scan_res = slsi_gscan_hash_get(sdev, new_scan_res->nl_scan_res.bssid); + if (scan_res == NULL) { /* New scan result */ + if ((sdev->buffer_consumed + new_scan_res->scan_res_len) >= SLSI_GSCAN_MAX_SCAN_CACHE_SIZE) { + SLSI_DBG2(sdev, SLSI_GSCAN, + "Scan buffer full, discarding scan result, buffer_consumed = %d, buffer_threshold = %d\n", + sdev->buffer_consumed, sdev->buffer_threshold); + + /* Scan buffer is full can't store anymore new results */ + return SLSI_DISCARD_SCAN_RESULT; + } + + return SLSI_KEEP_SCAN_RESULT; + } + + /* Even if scan buffer is full existing results can be replaced with the latest one */ + if (scan_res->scan_cycle == bucket->scan_cycle) + /* For the same scan cycle the result will be replaced only if the RSSI is better */ + if (new_scan_res->nl_scan_res.rssi < scan_res->nl_scan_res.rssi) + return SLSI_DISCARD_SCAN_RESULT; + + /* Remove the existing scan result */ + slsi_gscan_hash_remove(sdev, scan_res->nl_scan_res.bssid); + + return SLSI_KEEP_SCAN_RESULT; +} + +void slsi_gscan_handle_scan_result(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 scan_id, bool scan_done) +{ + struct slsi_gscan_result *scan_res = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_bucket *bucket; + u16 bucket_index; + int event_type = WIFI_SCAN_FAILED; + u16 anqp_length; + int hs2_network_id; + + if (!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_mutex)) + SLSI_WARN_NODEV("ndev_vif->scan_mutex is not locked\n"); + + SLSI_NET_DBG_HEX(dev, SLSI_GSCAN, skb->data, skb->len, "mlme_scan_ind skb->len: %d\n", skb->len); + + bucket_index = scan_id - SLSI_GSCAN_SCAN_ID_START; + if (bucket_index >= SLSI_GSCAN_MAX_BUCKETS) { + SLSI_NET_ERR(dev, "Invalid bucket index: %d (scan_id = %#x)\n", bucket_index, scan_id); + goto out; + } + + bucket = &sdev->bucket[bucket_index]; + if (!bucket->used) { + SLSI_NET_DBG1(dev, SLSI_GSCAN, "Bucket is not active, index: %d (scan_id = %#x)\n", bucket_index, scan_id); + goto out; + } + + /* For scan_done indication - no need to store the results */ + if (scan_done) { + bucket->scan_cycle++; + bucket->gscan->num_scans++; + + SLSI_NET_DBG3(dev, SLSI_GSCAN, "scan done, scan_cycle = %d, num_scans = %d\n", + bucket->scan_cycle, bucket->gscan->num_scans); + + if (bucket->report_events & SLSI_REPORT_EVENTS_EACH_SCAN) + event_type = WIFI_SCAN_RESULTS_AVAILABLE; + if (bucket->gscan->num_scans % bucket->gscan->report_threshold_num_scans == 0) + event_type = WIFI_SCAN_THRESHOLD_NUM_SCANS; + if (sdev->buffer_consumed >= sdev->buffer_threshold) + event_type = WIFI_SCAN_THRESHOLD_PERCENT; + + if (event_type != WIFI_SCAN_FAILED) + slsi_vendor_event(sdev, SLSI_NL80211_SCAN_EVENT, &event_type, sizeof(event_type)); + + goto out; + } + + anqp_length = fapi_get_u16(skb, u.mlme_scan_ind.anqp_elements_length); + /* TODO new FAPI 3.c has mlme_scan_ind.network_block_id, use that when fapi is updated. */ + hs2_network_id = 1; + + scan_res = slsi_prepare_scan_result(skb, anqp_length, hs2_network_id); + if (scan_res == NULL) { + SLSI_NET_ERR(dev, "Failed to prepare scan result\n"); + goto out; + } + + /* Check for ePNO networks */ + if (fapi_get_u16(skb, u.mlme_scan_ind.preferrednetwork_ap)) { + if (anqp_length == 0) + slsi_vendor_event(sdev, SLSI_NL80211_EPNO_EVENT, + &scan_res->nl_scan_res, scan_res->scan_res_len); + else + slsi_vendor_event(sdev, SLSI_NL80211_HOTSPOT_MATCH, + &scan_res->nl_scan_res, scan_res->scan_res_len + scan_res->anqp_length); + } + + if (bucket->report_events & SLSI_REPORT_EVENTS_FULL_RESULTS) { + struct sk_buff *nlevent; + + SLSI_NET_DBG3(dev, SLSI_GSCAN, "report_events: SLSI_REPORT_EVENTS_FULL_RESULTS\n"); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + nlevent = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, scan_res->scan_res_len + 4, SLSI_NL80211_FULL_SCAN_RESULT_EVENT, GFP_KERNEL); +#else + nlevent = cfg80211_vendor_event_alloc(sdev->wiphy, scan_res->scan_res_len + 4, SLSI_NL80211_FULL_SCAN_RESULT_EVENT, GFP_KERNEL); +#endif + if (!nlevent) { + SLSI_ERR(sdev, "failed to allocate sbk of size:%d\n", scan_res->scan_res_len + 4); + kfree(scan_res); + goto out; + } + if (nla_put_u32(nlevent, GSCAN_ATTRIBUTE_SCAN_BUCKET_BIT, (1 << bucket_index)) || + nla_put(nlevent, GSCAN_ATTRIBUTE_SCAN_RESULTS, scan_res->scan_res_len, &scan_res->nl_scan_res)) { + SLSI_ERR(sdev, "failed to put data\n"); + kfree_skb(nlevent); + kfree(scan_res); + goto out; + } + cfg80211_vendor_event(nlevent, GFP_KERNEL); + } + + if (slsi_check_scan_result(sdev, bucket, scan_res) == SLSI_DISCARD_SCAN_RESULT) { + kfree(scan_res); + goto out; + } + slsi_gscan_hash_add(sdev, scan_res); + +out: + slsi_kfree_skb(skb); +} + +u8 slsi_gscan_get_scan_policy(enum wifi_band band) +{ + u8 scan_policy; + + switch (band) { + case WIFI_BAND_UNSPECIFIED: + scan_policy = FAPI_SCANPOLICY_ANY_RA; + break; + case WIFI_BAND_BG: + scan_policy = FAPI_SCANPOLICY_2_4GHZ; + break; + case WIFI_BAND_A: + scan_policy = (FAPI_SCANPOLICY_5GHZ | + FAPI_SCANPOLICY_NON_DFS); + break; + case WIFI_BAND_A_DFS: + scan_policy = (FAPI_SCANPOLICY_5GHZ | + FAPI_SCANPOLICY_DFS); + break; + case WIFI_BAND_A_WITH_DFS: + scan_policy = (FAPI_SCANPOLICY_5GHZ | + FAPI_SCANPOLICY_NON_DFS | + FAPI_SCANPOLICY_DFS); + break; + case WIFI_BAND_ABG: + scan_policy = (FAPI_SCANPOLICY_5GHZ | + FAPI_SCANPOLICY_NON_DFS | + FAPI_SCANPOLICY_2_4GHZ); + break; + case WIFI_BAND_ABG_WITH_DFS: + scan_policy = (FAPI_SCANPOLICY_5GHZ | + FAPI_SCANPOLICY_NON_DFS | + FAPI_SCANPOLICY_DFS | + FAPI_SCANPOLICY_2_4GHZ); + break; + default: + scan_policy = FAPI_SCANPOLICY_ANY_RA; + break; + } + + SLSI_DBG2_NODEV(SLSI_GSCAN, "Scan Policy: %#x\n", scan_policy); + + return scan_policy; +} + +static int slsi_gscan_add_read_params(struct slsi_nl_gscan_param *nl_gscan_param, const void *data, int len) +{ + int j = 0; + int type, tmp, tmp1, tmp2, k = 0; + const struct nlattr *iter, *iter1, *iter2; + struct slsi_nl_bucket_param *nl_bucket; + + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + + if (j >= SLSI_GSCAN_MAX_BUCKETS) + break; + + switch (type) { + case GSCAN_ATTRIBUTE_BASE_PERIOD: + nl_gscan_param->base_period = nla_get_u32(iter); + break; + case GSCAN_ATTRIBUTE_NUM_AP_PER_SCAN: + nl_gscan_param->max_ap_per_scan = nla_get_u32(iter); + break; + case GSCAN_ATTRIBUTE_REPORT_THRESHOLD: + nl_gscan_param->report_threshold_percent = nla_get_u32(iter); + break; + case GSCAN_ATTRIBUTE_REPORT_THRESHOLD_NUM_SCANS: + nl_gscan_param->report_threshold_num_scans = nla_get_u32(iter); + break; + case GSCAN_ATTRIBUTE_NUM_BUCKETS: + nl_gscan_param->num_buckets = nla_get_u32(iter); + break; + case GSCAN_ATTRIBUTE_CH_BUCKET_1: + case GSCAN_ATTRIBUTE_CH_BUCKET_2: + case GSCAN_ATTRIBUTE_CH_BUCKET_3: + case GSCAN_ATTRIBUTE_CH_BUCKET_4: + case GSCAN_ATTRIBUTE_CH_BUCKET_5: + case GSCAN_ATTRIBUTE_CH_BUCKET_6: + case GSCAN_ATTRIBUTE_CH_BUCKET_7: + case GSCAN_ATTRIBUTE_CH_BUCKET_8: + nla_for_each_nested(iter1, iter, tmp1) { + type = nla_type(iter1); + nl_bucket = nl_gscan_param->nl_bucket; + + switch (type) { + case GSCAN_ATTRIBUTE_BUCKET_ID: + nl_bucket[j].bucket_index = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_BUCKET_PERIOD: + nl_bucket[j].period = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS: + nl_bucket[j].num_channels = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_BUCKET_CHANNELS: + nla_for_each_nested(iter2, iter1, tmp2) { + nl_bucket[j].channels[k].channel = nla_get_u32(iter2); + k++; + } + k = 0; + break; + case GSCAN_ATTRIBUTE_BUCKETS_BAND: + nl_bucket[j].band = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_REPORT_EVENTS: + nl_bucket[j].report_events = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_BUCKET_EXPONENT: + nl_bucket[j].exponent = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_BUCKET_STEP_COUNT: + nl_bucket[j].step_count = nla_get_u32(iter1); + break; + case GSCAN_ATTRIBUTE_BUCKET_MAX_PERIOD: + nl_bucket[j].max_period = nla_get_u32(iter1); + break; + default: + SLSI_ERR_NODEV("No ATTR_BUKTS_type - %x\n", type); + break; + } + } + j++; + break; + default: + SLSI_ERR_NODEV("No GSCAN_ATTR_CH_BUKT_type - %x\n", type); + break; + } + } + + return 0; +} + +int slsi_gscan_add_verify_params(struct slsi_nl_gscan_param *nl_gscan_param) +{ + int i; + + if ((nl_gscan_param->max_ap_per_scan < 0) || (nl_gscan_param->max_ap_per_scan > SLSI_GSCAN_MAX_AP_CACHE_PER_SCAN)) { + SLSI_ERR_NODEV("Invalid max_ap_per_scan: %d\n", nl_gscan_param->max_ap_per_scan); + return -EINVAL; + } + + if ((nl_gscan_param->report_threshold_percent < 0) || (nl_gscan_param->report_threshold_percent > SLSI_GSCAN_MAX_SCAN_REPORTING_THRESHOLD)) { + SLSI_ERR_NODEV("Invalid report_threshold_percent: %d\n", nl_gscan_param->report_threshold_percent); + return -EINVAL; + } + + if ((nl_gscan_param->num_buckets <= 0) || (nl_gscan_param->num_buckets > SLSI_GSCAN_MAX_BUCKETS)) { + SLSI_ERR_NODEV("Invalid num_buckets: %d\n", nl_gscan_param->num_buckets); + return -EINVAL; + } + + for (i = 0; i < nl_gscan_param->num_buckets; i++) { + if ((nl_gscan_param->nl_bucket[i].band == WIFI_BAND_UNSPECIFIED) && (nl_gscan_param->nl_bucket[i].num_channels == 0)) { + SLSI_ERR_NODEV("No band/channels provided for gscan: band = %d, num_channel = %d\n", + nl_gscan_param->nl_bucket[i].band, nl_gscan_param->nl_bucket[i].num_channels); + return -EINVAL; + } + + if (nl_gscan_param->nl_bucket[i].report_events > 4) { + SLSI_ERR_NODEV("Unsupported report event: report_event = %d\n", nl_gscan_param->nl_bucket[i].report_events); + return -EINVAL; + } + } + + return 0; +} + +void slsi_gscan_add_to_list(struct slsi_gscan **sdev_gscan, struct slsi_gscan *gscan) +{ + gscan->next = *sdev_gscan; + *sdev_gscan = gscan; +} + +int slsi_gscan_alloc_buckets(struct slsi_dev *sdev, struct slsi_gscan *gscan, int num_buckets) +{ + int i; + int bucket_index = 0; + int free_buckets = 0; + + for (i = 0; i < SLSI_GSCAN_MAX_BUCKETS; i++) + if (!sdev->bucket[i].used) + free_buckets++; + + if (num_buckets > free_buckets) { + SLSI_ERR_NODEV("Not enough free buckets, num_buckets = %d, free_buckets = %d\n", + num_buckets, free_buckets); + return -EINVAL; + } + + /* Allocate free buckets for the current gscan */ + for (i = 0; i < SLSI_GSCAN_MAX_BUCKETS; i++) + if (!sdev->bucket[i].used) { + sdev->bucket[i].used = true; + sdev->bucket[i].gscan = gscan; + gscan->bucket[bucket_index] = &sdev->bucket[i]; + bucket_index++; + if (bucket_index == num_buckets) + break; + } + + return 0; +} + +static void slsi_gscan_free_buckets(struct slsi_gscan *gscan) +{ + struct slsi_bucket *bucket; + int i; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "gscan = %p, num_buckets = %d\n", gscan, gscan->num_buckets); + + for (i = 0; i < gscan->num_buckets; i++) { + bucket = gscan->bucket[i]; + + SLSI_DBG2_NODEV(SLSI_GSCAN, "bucket = %p, used = %d, report_events = %d, scan_id = %#x, gscan = %p\n", + bucket, bucket->used, bucket->report_events, bucket->scan_id, bucket->gscan); + if (bucket->used) { + bucket->used = false; + bucket->report_events = 0; + bucket->gscan = NULL; + } + } +} + +void slsi_gscan_flush_scan_results(struct slsi_dev *sdev) +{ + struct netdev_vif *ndev_vif; + struct slsi_gscan_result *temp; + int i; + + ndev_vif = slsi_gscan_get_vif(sdev); + if (!ndev_vif) { + SLSI_WARN_NODEV("ndev_vif is NULL"); + return; + } + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + for (i = 0; i < SLSI_GSCAN_HASH_TABLE_SIZE; i++) + while (sdev->gscan_hash_table[i]) { + temp = sdev->gscan_hash_table[i]; + sdev->gscan_hash_table[i] = sdev->gscan_hash_table[i]->hnext; + sdev->num_gscan_results--; + sdev->buffer_consumed -= temp->scan_res_len; + kfree(temp); + } + + SLSI_DBG2(sdev, SLSI_GSCAN, "num_gscan_results: %d, buffer_consumed = %d\n", + sdev->num_gscan_results, sdev->buffer_consumed); + + if (sdev->num_gscan_results != 0) + SLSI_WARN_NODEV("sdev->num_gscan_results is not zero\n"); + + if (sdev->buffer_consumed != 0) + SLSI_WARN_NODEV("sdev->buffer_consumedis not zero\n"); + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); +} + +static int slsi_gscan_add_mlme(struct slsi_dev *sdev, struct slsi_nl_gscan_param *nl_gscan_param, struct slsi_gscan *gscan) +{ + struct slsi_gscan_param gscan_param; + struct net_device *dev; + int ret = 0; + int i; +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + u8 mac_addr_mask[ETH_ALEN] = {0xFF}; +#endif + + dev = slsi_gscan_get_netdev(sdev); + + if (!dev) { + SLSI_WARN_NODEV("dev is NULL\n"); + return -EINVAL; + } + + for (i = 0; i < nl_gscan_param->num_buckets; i++) { + u16 report_mode = 0; + + gscan_param.nl_bucket = &nl_gscan_param->nl_bucket[i]; /* current bucket */ + gscan_param.bucket = gscan->bucket[i]; + + if (gscan_param.bucket->report_events) { + if (gscan_param.bucket->report_events & SLSI_REPORT_EVENTS_EACH_SCAN) + report_mode |= FAPI_REPORTMODE_END_OF_SCAN_CYCLE; + if (gscan_param.bucket->report_events & SLSI_REPORT_EVENTS_FULL_RESULTS) + report_mode |= FAPI_REPORTMODE_REAL_TIME; + if (gscan_param.bucket->report_events & SLSI_REPORT_EVENTS_NO_BATCH) + report_mode |= FAPI_REPORTMODE_NO_BATCH; + } else { + report_mode = FAPI_REPORTMODE_RESERVED; + } + + if (report_mode == 0) { + SLSI_NET_ERR(dev, "Invalid report event value: %d\n", gscan_param.bucket->report_events); + return -EINVAL; + } + + /* In case of epno no_batch mode should be set. */ + if (sdev->epno_active) + report_mode |= FAPI_REPORTMODE_NO_BATCH; + +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + if (sdev->scan_addr_set == 1) { + memset(mac_addr_mask, 0xFF, ETH_ALEN); + for (i = 3; i < ETH_ALEN; i++) + mac_addr_mask[i] = 0x00; + ret = slsi_set_mac_randomisation_mask(sdev, mac_addr_mask); + if (ret) + sdev->scan_addr_set = 0; + } else { + memset(mac_addr_mask, 0xFF, ETH_ALEN); + slsi_set_mac_randomisation_mask(sdev, mac_addr_mask); + } +#endif + ret = slsi_mlme_add_scan(sdev, + dev, + FAPI_SCANTYPE_GSCAN, + report_mode, + 0, /* n_ssids */ + NULL, /* ssids */ + nl_gscan_param->nl_bucket[i].num_channels, + NULL, /* ieee80211_channel */ + &gscan_param, + NULL, /* ies */ + 0, /* ies_len */ + false /* wait_for_ind */); + + if (ret != 0) { + SLSI_NET_ERR(dev, "Failed to add bucket: %d\n", i); + + /* Delete the scan those are already added */ + for (i = (i - 1); i >= 0; i--) + slsi_mlme_del_scan(sdev, dev, gscan->bucket[i]->scan_id, false); + break; + } + } + + return ret; +} + +static int slsi_gscan_add(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct slsi_nl_gscan_param *nl_gscan_param = NULL; + struct slsi_gscan *gscan; + struct netdev_vif *ndev_vif; + int buffer_threshold; + int i; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_ADD_GSCAN\n"); + + if (!sdev) { + SLSI_WARN_NODEV("sdev is NULL\n"); + return -EINVAL; + } + + if (!slsi_dev_gscan_supported()) + return -ENOTSUPP; + + ndev_vif = slsi_gscan_get_vif(sdev); + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + /* Allocate memory for the received scan params */ + nl_gscan_param = kzalloc(sizeof(*nl_gscan_param), GFP_KERNEL); + if (nl_gscan_param == NULL) { + SLSI_ERR_NODEV("Failed for allocate memory for gscan params\n"); + ret = -ENOMEM; + goto exit; + } + + slsi_gscan_add_read_params(nl_gscan_param, data, len); + +#ifdef CONFIG_SCSC_WLAN_DEBUG + slsi_gscan_add_dump_params(nl_gscan_param); +#endif + + ret = slsi_gscan_add_verify_params(nl_gscan_param); + if (ret) { + /* After adding a hotlist a new gscan is added with 0 buckets - return success */ + if (nl_gscan_param->num_buckets == 0) { + kfree(nl_gscan_param); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return 0; + } + + goto exit; + } + + /* Allocate Memory for the new gscan */ + gscan = kzalloc(sizeof(*gscan), GFP_KERNEL); + if (gscan == NULL) { + SLSI_ERR_NODEV("Failed to allocate memory for gscan\n"); + ret = -ENOMEM; + goto exit; + } + + gscan->num_buckets = nl_gscan_param->num_buckets; + gscan->report_threshold_percent = nl_gscan_param->report_threshold_percent; + gscan->report_threshold_num_scans = nl_gscan_param->report_threshold_num_scans; + gscan->nl_bucket = nl_gscan_param->nl_bucket[0]; + + /* If multiple gscan is added; consider the lowest report_threshold_percent */ + buffer_threshold = (SLSI_GSCAN_MAX_SCAN_CACHE_SIZE * nl_gscan_param->report_threshold_percent) / 100; + if ((sdev->buffer_threshold == 0) || (buffer_threshold < sdev->buffer_threshold)) + sdev->buffer_threshold = buffer_threshold; + + ret = slsi_gscan_alloc_buckets(sdev, gscan, nl_gscan_param->num_buckets); + if (ret) + goto exit_with_gscan_free; + + for (i = 0; i < nl_gscan_param->num_buckets; i++) + gscan->bucket[i]->report_events = nl_gscan_param->nl_bucket[i].report_events; + + ret = slsi_gscan_add_mlme(sdev, nl_gscan_param, gscan); + if (ret) { + /* Free the buckets */ + slsi_gscan_free_buckets(gscan); + + goto exit_with_gscan_free; + } + + slsi_gscan_add_to_list(&sdev->gscan, gscan); + + goto exit; + +exit_with_gscan_free: + kfree(gscan); +exit: + kfree(nl_gscan_param); + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return ret; +} + +static int slsi_gscan_del(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev; + struct netdev_vif *ndev_vif; + struct slsi_gscan *gscan; + int ret = 0; + int i; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_DEL_GSCAN\n"); + + dev = slsi_gscan_get_netdev(sdev); + if (!dev) { + SLSI_WARN_NODEV("dev is NULL\n"); + return -EINVAL; + } + + ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + while (sdev->gscan != NULL) { + gscan = sdev->gscan; + + SLSI_DBG3(sdev, SLSI_GSCAN, "gscan = %p, num_buckets = %d\n", gscan, gscan->num_buckets); + + for (i = 0; i < gscan->num_buckets; i++) + if (gscan->bucket[i]->used) + slsi_mlme_del_scan(sdev, dev, gscan->bucket[i]->scan_id, false); + slsi_gscan_free_buckets(gscan); + sdev->gscan = gscan->next; + kfree(gscan); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + + slsi_gscan_flush_scan_results(sdev); + + sdev->buffer_threshold = 0; + + return ret; +} + +static int slsi_gscan_get_scan_results(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct sk_buff *skb; + struct slsi_gscan_result *scan_res; + struct nlattr *scan_hdr; + struct netdev_vif *ndev_vif; + int num_results = 0; + int mem_needed; + const struct nlattr *attr; + int nl_num_results = 0; + int ret = 0; + int temp; + int type; + int i; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_GET_SCAN_RESULTS\n"); + + /* Read the number of scan results need to be given */ + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + + switch (type) { + case GSCAN_ATTRIBUTE_NUM_OF_RESULTS: + nl_num_results = nla_get_u32(attr); + break; + default: + SLSI_ERR_NODEV("Unknown attribute: %d\n", type); + break; + } + } + + ndev_vif = slsi_gscan_get_vif(sdev); + if (!ndev_vif) { + SLSI_WARN_NODEV("ndev_vif is NULL\n"); + return -EINVAL; + } + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + num_results = sdev->num_gscan_results; + + SLSI_DBG3(sdev, SLSI_GSCAN, "nl_num_results: %d, num_results = %d\n", nl_num_results, sdev->num_gscan_results); + + if (num_results == 0) { + SLSI_DBG1(sdev, SLSI_GSCAN, "No scan results available\n"); + /* Return value should be 0 for this scenario */ + goto exit; + } + + /* Find the number of results to return */ + if (num_results > nl_num_results) + num_results = nl_num_results; + + /* 12 bytes additional for scan_id, flags and num_resuls */ + mem_needed = num_results * sizeof(struct slsi_nl_scan_result_param) + 12; + + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, mem_needed); + if (skb == NULL) { + SLSI_ERR_NODEV("skb alloc failed"); + ret = -ENOMEM; + goto exit; + } + + scan_hdr = nla_nest_start(skb, GSCAN_ATTRIBUTE_SCAN_RESULTS); + if (scan_hdr == NULL) { + kfree_skb(skb); + SLSI_ERR_NODEV("scan_hdr is NULL.\n"); + ret = -ENOMEM; + goto exit; + } + + nla_put_u32(skb, GSCAN_ATTRIBUTE_SCAN_ID, 0); + nla_put_u8(skb, GSCAN_ATTRIBUTE_SCAN_FLAGS, 0); + nla_put_u32(skb, GSCAN_ATTRIBUTE_NUM_OF_RESULTS, num_results); + + for (i = 0; i < SLSI_GSCAN_HASH_TABLE_SIZE; i++) + while (sdev->gscan_hash_table[i]) { + scan_res = sdev->gscan_hash_table[i]; + sdev->gscan_hash_table[i] = sdev->gscan_hash_table[i]->hnext; + sdev->num_gscan_results--; + sdev->buffer_consumed -= scan_res->scan_res_len; + /* TODO: If IE is included then HAL is not able to parse the results */ + nla_put(skb, GSCAN_ATTRIBUTE_SCAN_RESULTS, sizeof(struct slsi_nl_scan_result_param), &scan_res->nl_scan_res); + kfree(scan_res); + num_results--; + if (num_results == 0) + goto out; + } +out: + nla_nest_end(skb, scan_hdr); + + ret = cfg80211_vendor_cmd_reply(skb); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return ret; +} + +void slsi_rx_rssi_report_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_rssi_monitor_evt event_data; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_ETHER_COPY(event_data.bssid, fapi_get_buff(skb, u.mlme_rssi_report_ind.bssid)); + event_data.rssi = fapi_get_s16(skb, u.mlme_rssi_report_ind.rssi); + SLSI_DBG3(sdev, SLSI_GSCAN, "RSSI threshold breached, Current RSSI for %pM= %d\n", event_data.bssid, event_data.rssi); + slsi_vendor_event(sdev, SLSI_NL80211_RSSI_REPORT_EVENT, &event_data, sizeof(event_data)); + slsi_kfree_skb(skb); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD +static int slsi_key_mgmt_set_pmk(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *pmk, int pmklen) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int r = 0; + + if (wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) { + SLSI_DBG3(sdev, SLSI_GSCAN, "Not required to set PMK for P2P client\n"); + return r; + } + SLSI_DBG3(sdev, SLSI_GSCAN, "SUBCMD_SET_PMK Received\n"); + + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(net_dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + r = slsi_mlme_set_pmk(sdev, net_dev, pmk, (u16)pmklen); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} +#endif + +static int slsi_set_bssid_blacklist(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int temp1; + int type; + const struct nlattr *attr; + u32 num_bssids = 0; + u8 i = 0; + int ret; + u8 *bssid = NULL; + struct cfg80211_acl_data *acl_data = NULL; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_SET_BSSID_BLACK_LIST\n"); + + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + if (!net_dev) { + SLSI_WARN_NODEV("net_dev is NULL\n"); + return -EINVAL; + } + + ndev_vif = netdev_priv(net_dev); + /*This subcmd can be issued in either connected or disconnected state. + * Hence using scan_mutex and not vif_mutex + */ + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + nla_for_each_attr(attr, data, len, temp1) { + type = nla_type(attr); + + switch (type) { + case GSCAN_ATTRIBUTE_NUM_BSSID: + if (acl_data) + break; + + num_bssids = nla_get_u32(attr); + acl_data = kmalloc(sizeof(*acl_data) + (sizeof(struct mac_address) * num_bssids), GFP_KERNEL); + if (!acl_data) { + ret = -ENOMEM; + goto exit; + } + acl_data->n_acl_entries = num_bssids; + break; + + case GSCAN_ATTRIBUTE_BLACKLIST_BSSID: + if (!acl_data) { + ret = -EINVAL; + goto exit; + } + bssid = (u8 *)nla_data(attr); + SLSI_ETHER_COPY(acl_data->mac_addrs[i].addr, bssid); + SLSI_DBG3_NODEV(SLSI_GSCAN, "mac_addrs[%d]:%pM)\n", i, acl_data->mac_addrs[i].addr); + i++; + break; + default: + SLSI_ERR_NODEV("Unknown attribute: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + if (acl_data) { + acl_data->acl_policy = FAPI_ACLPOLICY_BLACKLIST; + ret = slsi_mlme_set_acl(sdev, net_dev, 0, acl_data); + if (ret) + SLSI_ERR_NODEV("Failed to set bssid blacklist\n"); + } else { + ret = -EINVAL; + } +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + kfree(acl_data); + return ret; +} + +static int slsi_start_keepalive_offload(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ +#ifndef CONFIG_SCSC_WLAN_NAT_KEEPALIVE_DISABLE + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + + int temp; + int type; + const struct nlattr *attr; + u16 ip_pkt_len = 0; + u8 *ip_pkt = NULL, *src_mac_addr = NULL, *dst_mac_addr = NULL; + u32 period = 0; + struct slsi_peer *peer; + struct sk_buff *skb; + struct ethhdr *ehdr; + int r = 0; + u16 host_tag = 0; + u8 index = 0; + + SLSI_DBG3(sdev, SLSI_MLME, "SUBCMD_START_KEEPALIVE_OFFLOAD received\n"); + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(net_dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + SLSI_WARN_NODEV("ndev_vif is not activated\n"); + r = -EINVAL; + goto exit; + } + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + SLSI_WARN_NODEV("ndev_vif->vif_type is not FAPI_VIFTYPE_STATION\n"); + r = -EINVAL; + goto exit; + } + if (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED) { + SLSI_WARN_NODEV("ndev_vif->sta.vif_status is not SLSI_VIF_STATUS_CONNECTED\n"); + r = -EINVAL; + goto exit; + } + + peer = slsi_get_peer_from_qs(sdev, net_dev, SLSI_STA_PEER_QUEUESET); + if (!peer) { + SLSI_WARN_NODEV("peer is NULL\n"); + r = -EINVAL; + goto exit; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + + switch (type) { + case MKEEP_ALIVE_ATTRIBUTE_IP_PKT_LEN: + ip_pkt_len = nla_get_u16(attr); + break; + + case MKEEP_ALIVE_ATTRIBUTE_IP_PKT: + ip_pkt = (u8 *)nla_data(attr); + break; + + case MKEEP_ALIVE_ATTRIBUTE_PERIOD_MSEC: + period = nla_get_u32(attr); + break; + + case MKEEP_ALIVE_ATTRIBUTE_DST_MAC_ADDR: + dst_mac_addr = (u8 *)nla_data(attr); + break; + + case MKEEP_ALIVE_ATTRIBUTE_SRC_MAC_ADDR: + src_mac_addr = (u8 *)nla_data(attr); + break; + + case MKEEP_ALIVE_ATTRIBUTE_ID: + index = nla_get_u8(attr); + break; + + default: + SLSI_ERR_NODEV("Unknown attribute: %d\n", type); + r = -EINVAL; + goto exit; + } + } + + /* Stop any existing request. This may fail if no request exists + * so ignore the return value + */ + slsi_mlme_send_frame_mgmt(sdev, net_dev, NULL, 0, + FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME, + FAPI_MESSAGETYPE_ANY_OTHER, + ndev_vif->sta.keepalive_host_tag[index - 1], 0, 0, 0); + + skb = slsi_alloc_skb_headroom(sizeof(struct ethhdr) + ip_pkt_len, GFP_KERNEL); + if (!skb) { + SLSI_WARN_NODEV("Memory allocation failed for skb\n"); + r = -ENOMEM; + goto exit; + } + + skb_reset_mac_header(skb); + skb_set_network_header(skb, sizeof(struct ethhdr)); + + /* Ethernet Header */ + ehdr = (struct ethhdr *)skb_put(skb, sizeof(struct ethhdr)); + + if (dst_mac_addr) + SLSI_ETHER_COPY(ehdr->h_dest, dst_mac_addr); + if (src_mac_addr) + SLSI_ETHER_COPY(ehdr->h_source, src_mac_addr); + ehdr->h_proto = cpu_to_be16(ETH_P_IP); + if (ip_pkt) + memcpy(skb_put(skb, ip_pkt_len), ip_pkt, ip_pkt_len); + + skb->dev = net_dev; + skb->protocol = ETH_P_IP; + skb->ip_summed = CHECKSUM_UNNECESSARY; + + /* Queueset 0 AC 0 */ + skb->queue_mapping = slsi_netif_get_peer_queue(0, 0); + + /* Enabling the "Don't Fragment" Flag in the IP Header */ + ip_hdr(skb)->frag_off |= htons(IP_DF); + + /* Calculation of IP header checksum */ + ip_hdr(skb)->check = 0; + ip_send_check(ip_hdr(skb)); + + host_tag = slsi_tx_mgmt_host_tag(sdev); + r = slsi_mlme_send_frame_data(sdev, net_dev, skb, FAPI_MESSAGETYPE_ANY_OTHER, host_tag, + 0, (period * 1000)); + if (r == 0) + ndev_vif->sta.keepalive_host_tag[index - 1] = host_tag; + else + slsi_kfree_skb(skb); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +#else + SLSI_DBG3_NODEV(SLSI_MLME, "SUBCMD_START_KEEPALIVE_OFFLOAD received\n"); + SLSI_DBG3_NODEV(SLSI_MLME, "NAT Keep Alive Feature is disabled\n"); + return -EOPNOTSUPP; + +#endif +} + +static int slsi_stop_keepalive_offload(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ +#ifndef CONFIG_SCSC_WLAN_NAT_KEEPALIVE_DISABLE + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int r = 0; + int temp; + int type; + const struct nlattr *attr; + u8 index = 0; + + SLSI_DBG3(sdev, SLSI_MLME, "SUBCMD_STOP_KEEPALIVE_OFFLOAD received\n"); + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(net_dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + SLSI_WARN(sdev, "VIF is not activated\n"); + r = -EINVAL; + goto exit; + } + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + SLSI_WARN(sdev, "Not a STA VIF\n"); + r = -EINVAL; + goto exit; + } + if (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED) { + SLSI_WARN(sdev, "VIF is not connected\n"); + r = -EINVAL; + goto exit; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + + switch (type) { + case MKEEP_ALIVE_ATTRIBUTE_ID: + index = nla_get_u8(attr); + break; + + default: + SLSI_ERR_NODEV("Unknown attribute: %d\n", type); + r = -EINVAL; + goto exit; + } + } + + r = slsi_mlme_send_frame_mgmt(sdev, net_dev, NULL, 0, FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME, + FAPI_MESSAGETYPE_ANY_OTHER, ndev_vif->sta.keepalive_host_tag[index - 1], 0, 0, 0); + ndev_vif->sta.keepalive_host_tag[index - 1] = 0; + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +#else + SLSI_DBG3_NODEV(SLSI_MLME, "SUBCMD_STOP_KEEPALIVE_OFFLOAD received\n"); + SLSI_DBG3_NODEV(SLSI_MLME, "NAT Keep Alive Feature is disabled\n"); + return -EOPNOTSUPP; + +#endif +} + +static inline int slsi_epno_ssid_list_get(struct slsi_dev *sdev, + struct slsi_epno_ssid_param *epno_ssid_params, const struct nlattr *outer) +{ + int type, tmp; + u8 epno_auth; + u8 len = 0; + const struct nlattr *inner; + + nla_for_each_nested(inner, outer, tmp) { + type = nla_type(inner); + switch (type) { + case SLSI_ATTRIBUTE_EPNO_FLAGS: + epno_ssid_params->flags |= nla_get_u16(inner); + break; + case SLSI_ATTRIBUTE_EPNO_AUTH: + epno_auth = nla_get_u8(inner); + if (epno_auth & SLSI_EPNO_AUTH_FIELD_WEP_OPEN) + epno_ssid_params->flags |= FAPI_EPNOPOLICY_AUTH_OPEN; + else if (epno_auth & SLSI_EPNO_AUTH_FIELD_WPA_PSK) + epno_ssid_params->flags |= FAPI_EPNOPOLICY_AUTH_PSK; + else if (epno_auth & SLSI_EPNO_AUTH_FIELD_WPA_EAP) + epno_ssid_params->flags |= FAPI_EPNOPOLICY_AUTH_EAPOL; + break; + case SLSI_ATTRIBUTE_EPNO_SSID_LEN: + len = nla_get_u8(inner); + if (len <= 32) { + epno_ssid_params->ssid_len = len; + } else { + SLSI_ERR(sdev, "SSID too long %d\n", len); + return -EINVAL; + } + break; + case SLSI_ATTRIBUTE_EPNO_SSID: + memcpy(epno_ssid_params->ssid, nla_data(inner), len); + break; + default: + SLSI_WARN(sdev, "Ignoring unknown type:%d\n", type); + } + } + return 0; +} + +static int slsi_set_epno_ssid(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int r = 0; + int tmp, tmp1, type, num = 0; + const struct nlattr *outer, *iter; + u8 i = 0; + struct slsi_epno_ssid_param *epno_ssid_params; + struct slsi_epno_param *epno_params; + + SLSI_DBG3(sdev, SLSI_GSCAN, "SUBCMD_SET_EPNO_LIST Received\n"); + + if (!slsi_dev_epno_supported()) + return -EOPNOTSUPP; + + epno_params = kmalloc((sizeof(*epno_params) + (sizeof(*epno_ssid_params) * SLSI_GSCAN_MAX_EPNO_SSIDS)), + GFP_KERNEL); + if (!epno_params) { + SLSI_ERR(sdev, "Mem alloc fail\n"); + return -ENOMEM; + } + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(net_dev); + nla_for_each_attr(iter, data, len, tmp1) { + type = nla_type(iter); + switch (type) { + case SLSI_ATTRIBUTE_EPNO_MINIMUM_5G_RSSI: + epno_params->min_5g_rssi = nla_get_u16(iter); + break; + case SLSI_ATTRIBUTE_EPNO_MINIMUM_2G_RSSI: + epno_params->min_2g_rssi = nla_get_u16(iter); + break; + case SLSI_ATTRIBUTE_EPNO_INITIAL_SCORE_MAX: + epno_params->initial_score_max = nla_get_u16(iter); + break; + case SLSI_ATTRIBUTE_EPNO_CUR_CONN_BONUS: + epno_params->current_connection_bonus = nla_get_u8(iter); + break; + case SLSI_ATTRIBUTE_EPNO_SAME_NETWORK_BONUS: + epno_params->same_network_bonus = nla_get_u8(iter); + break; + case SLSI_ATTRIBUTE_EPNO_SECURE_BONUS: + epno_params->secure_bonus = nla_get_u8(iter); + break; + case SLSI_ATTRIBUTE_EPNO_5G_BONUS: + epno_params->band_5g_bonus = nla_get_u8(iter); + break; + case SLSI_ATTRIBUTE_EPNO_SSID_LIST: + nla_for_each_nested(outer, iter, tmp) { + epno_ssid_params = &epno_params->epno_ssid[i]; + epno_ssid_params->flags = 0; + r = slsi_epno_ssid_list_get(sdev, epno_ssid_params, outer); + if (r) + goto exit; + i++; + } + break; + case SLSI_ATTRIBUTE_EPNO_SSID_NUM: + num = nla_get_u8(iter); + if (num > SLSI_GSCAN_MAX_EPNO_SSIDS) { + SLSI_ERR(sdev, "Cannot support %d SSIDs. max %d\n", num, SLSI_GSCAN_MAX_EPNO_SSIDS); + r = -EINVAL; + goto exit; + } + epno_params->num_networks = num; + break; + default: + SLSI_ERR(sdev, "Invalid attribute %d\n", type); + r = -EINVAL; + goto exit; + } + } + + if (i != num) { + SLSI_ERR(sdev, "num_ssid %d does not match ssids sent %d\n", num, i); + r = -EINVAL; + goto exit; + } + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + r = slsi_mlme_set_pno_list(sdev, num, epno_params, NULL); + if (r == 0) + sdev->epno_active = (num != 0); + else + sdev->epno_active = false; + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + kfree(epno_params); + return r; +} + +static int slsi_set_hs_params(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int r = 0; + int tmp, tmp1, tmp2, type, num = 0; + const struct nlattr *outer, *inner, *iter; + u8 i = 0; + struct slsi_epno_hs2_param *epno_hs2_params_array; + struct slsi_epno_hs2_param *epno_hs2_params; + + SLSI_DBG3(sdev, SLSI_GSCAN, "SUBCMD_SET_HS_LIST Received\n"); + + if (!slsi_dev_epno_supported()) + return -EOPNOTSUPP; + + epno_hs2_params_array = kmalloc(sizeof(*epno_hs2_params_array) * SLSI_GSCAN_MAX_EPNO_HS2_PARAM, GFP_KERNEL); + if (!epno_hs2_params_array) { + SLSI_ERR(sdev, "Mem alloc fail\n"); + return -ENOMEM; + } + + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(net_dev); + + nla_for_each_attr(iter, data, len, tmp2) { + type = nla_type(iter); + switch (type) { + case SLSI_ATTRIBUTE_EPNO_HS_PARAM_LIST: + nla_for_each_nested(outer, iter, tmp) { + epno_hs2_params = &epno_hs2_params_array[i]; + i++; + nla_for_each_nested(inner, outer, tmp1) { + type = nla_type(inner); + + switch (type) { + case SLSI_ATTRIBUTE_EPNO_HS_ID: + epno_hs2_params->id = (u32)nla_get_u32(inner); + break; + case SLSI_ATTRIBUTE_EPNO_HS_REALM: + memcpy(epno_hs2_params->realm, nla_data(inner), 256); + break; + case SLSI_ATTRIBUTE_EPNO_HS_CONSORTIUM_IDS: + memcpy(epno_hs2_params->roaming_consortium_ids, nla_data(inner), 16 * 8); + break; + case SLSI_ATTRIBUTE_EPNO_HS_PLMN: + memcpy(epno_hs2_params->plmn, nla_data(inner), 3); + break; + default: + SLSI_WARN(sdev, "Ignoring unknown type:%d\n", type); + } + } + } + break; + case SLSI_ATTRIBUTE_EPNO_HS_NUM: + num = nla_get_u8(iter); + if (num > SLSI_GSCAN_MAX_EPNO_HS2_PARAM) { + SLSI_ERR(sdev, "Cannot support %d SSIDs. max %d\n", num, SLSI_GSCAN_MAX_EPNO_SSIDS); + r = -EINVAL; + goto exit; + } + break; + default: + SLSI_ERR(sdev, "Invalid attribute %d\n", type); + r = -EINVAL; + goto exit; + } + } + if (i != num) { + SLSI_ERR(sdev, "num_ssid %d does not match ssids sent %d\n", num, i); + r = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + r = slsi_mlme_set_pno_list(sdev, num, NULL, epno_hs2_params_array); + if (r == 0) + sdev->epno_active = true; + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + kfree(epno_hs2_params_array); + return r; +} + +static int slsi_reset_hs_params(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int r; + + SLSI_DBG3(sdev, SLSI_GSCAN, "SUBCMD_RESET_HS_LIST Received\n"); + + if (!slsi_dev_epno_supported()) + return -EOPNOTSUPP; + + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(net_dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + r = slsi_mlme_set_pno_list(sdev, 0, NULL, NULL); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + sdev->epno_active = false; + return r; +} + +static int slsi_set_rssi_monitor(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev; + struct netdev_vif *ndev_vif; + int r = 0; + int temp; + int type; + const struct nlattr *attr; + s8 min_rssi = 0, max_rssi = 0; + u16 enable = 0; + + SLSI_DBG3(sdev, SLSI_GSCAN, "Recd RSSI monitor command\n"); + + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + if (net_dev == NULL) { + SLSI_ERR(sdev, "netdev is NULL!!\n"); + return -ENODEV; + } + + ndev_vif = netdev_priv(net_dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_ERR(sdev, "Vif not activated\n"); + r = -EINVAL; + goto exit; + } + if (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) { + SLSI_ERR(sdev, "Not a STA vif\n"); + r = -EINVAL; + goto exit; + } + if (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED) { + SLSI_ERR(sdev, "STA vif not connected\n"); + r = -EINVAL; + goto exit; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_RSSI_MONITOR_ATTRIBUTE_START: + enable = (u16)nla_get_u8(attr); + break; + case SLSI_RSSI_MONITOR_ATTRIBUTE_MIN_RSSI: + min_rssi = nla_get_s8(attr); + break; + case SLSI_RSSI_MONITOR_ATTRIBUTE_MAX_RSSI: + max_rssi = nla_get_s8(attr); + break; + default: + r = -EINVAL; + goto exit; + } + } + if (min_rssi > max_rssi) { + SLSI_ERR(sdev, "Invalid params, min_rssi= %d ,max_rssi = %d\n", min_rssi, max_rssi); + r = -EINVAL; + goto exit; + } + r = slsi_mlme_set_rssi_monitor(sdev, net_dev, enable, min_rssi, max_rssi); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return r; +} + +#ifdef CONFIG_SCSC_WLAN_DEBUG +void slsi_lls_debug_dump_stats(struct slsi_dev *sdev, struct slsi_lls_radio_stat *radio_stat, + struct slsi_lls_iface_stat *iface_stat, u8 *buf, int buf_len, int num_of_radios) +{ + int i, j; + + for (j = 0; j < num_of_radios; j++) { + SLSI_DBG3(sdev, SLSI_GSCAN, "radio_stat====\n"); + SLSI_DBG3(sdev, SLSI_GSCAN, "\tradio_id : %d, on_time : %d, tx_time : %d, rx_time : %d," + "on_time_scan : %d, num_channels : %d\n", radio_stat->radio, radio_stat->on_time, + radio_stat->tx_time, radio_stat->rx_time, radio_stat->on_time_scan, + radio_stat->num_channels); + + radio_stat = (struct slsi_lls_radio_stat *)((u8 *)radio_stat + sizeof(struct slsi_lls_radio_stat) + + (sizeof(struct slsi_lls_channel_stat) * radio_stat->num_channels)); + } + SLSI_DBG3(sdev, SLSI_GSCAN, "iface_stat====\n"); + SLSI_DBG3(sdev, SLSI_GSCAN, "\tiface %p info : (mode : %d, mac_addr : %pM, state : %d, roaming : %d," + " capabilities : %d, ssid : %s, bssid : %pM, ap_country_str : [%d%d%d])\trssi_data : %d\n", + iface_stat->iface, iface_stat->info.mode, iface_stat->info.mac_addr, iface_stat->info.state, + iface_stat->info.roaming, iface_stat->info.capabilities, iface_stat->info.ssid, + iface_stat->info.bssid, iface_stat->info.ap_country_str[0], iface_stat->info.ap_country_str[1], + iface_stat->info.ap_country_str[2], iface_stat->rssi_data); + + SLSI_DBG3(sdev, SLSI_GSCAN, "\tnum_peers %d\n", iface_stat->num_peers); + for (i = 0; i < iface_stat->num_peers; i++) { + SLSI_DBG3(sdev, SLSI_GSCAN, "\t\tpeer_mac_address %pM\n", iface_stat->peer_info[i].peer_mac_address); + } + + SLSI_DBG_HEX(sdev, SLSI_GSCAN, buf, buf_len, "return buffer\n"); +} +#endif + +static int slsi_lls_set_stats(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *net_dev = NULL; + struct netdev_vif *ndev_vif = NULL; + int temp; + int type; + const struct nlattr *attr; + u32 mpdu_size_threshold = 0; + u32 aggr_stat_gathering = 0; + int r = 0, i; + + if (!slsi_dev_lls_supported()) + return -EOPNOTSUPP; + + if (slsi_is_test_mode_enabled()) { + SLSI_WARN(sdev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + + switch (type) { + case LLS_ATTRIBUTE_SET_MPDU_SIZE_THRESHOLD: + mpdu_size_threshold = nla_get_u32(attr); + break; + + case LLS_ATTRIBUTE_SET_AGGR_STATISTICS_GATHERING: + aggr_stat_gathering = nla_get_u32(attr); + break; + + default: + SLSI_ERR_NODEV("Unknown attribute: %d\n", type); + r = -EINVAL; + } + } + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + /* start Statistics measurements in Firmware */ + (void)slsi_mlme_start_link_stats_req(sdev, mpdu_size_threshold, aggr_stat_gathering); + + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + if (net_dev) { + ndev_vif = netdev_priv(net_dev); + for (i = 0; i < SLSI_LLS_AC_MAX; i++) { + ndev_vif->rx_packets[i] = 0; + ndev_vif->tx_packets[i] = 0; + ndev_vif->tx_no_ack[i] = 0; + } + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return 0; +} + +static int slsi_lls_clear_stats(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int temp; + int type; + const struct nlattr *attr; + u32 stats_clear_req_mask = 0; + u32 stop_req = 0; + int r = 0, i; + struct net_device *net_dev = NULL; + struct netdev_vif *ndev_vif = NULL; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + + switch (type) { + case LLS_ATTRIBUTE_CLEAR_STOP_REQUEST_MASK: + stats_clear_req_mask = nla_get_u32(attr); + SLSI_DBG3(sdev, SLSI_GSCAN, "stats_clear_req_mask:%u\n", stats_clear_req_mask); + break; + + case LLS_ATTRIBUTE_CLEAR_STOP_REQUEST: + stop_req = nla_get_u32(attr); + SLSI_DBG3(sdev, SLSI_GSCAN, "stop_req:%u\n", stop_req); + break; + + default: + SLSI_ERR(sdev, "Unknown attribute:%d\n", type); + r = -EINVAL; + } + } + + /* stop_req = 0 : clear the stats which are flaged 0 + * stop_req = 1 : clear the stats which are flaged 1 + */ + if (!stop_req) + stats_clear_req_mask = ~stats_clear_req_mask; + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + (void)slsi_mlme_stop_link_stats_req(sdev, stats_clear_req_mask); + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + if (net_dev) { + ndev_vif = netdev_priv(net_dev); + for (i = 0; i < SLSI_LLS_AC_MAX; i++) { + ndev_vif->rx_packets[i] = 0; + ndev_vif->tx_packets[i] = 0; + ndev_vif->tx_no_ack[i] = 0; + } + } + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return 0; +} + +static u32 slsi_lls_ie_to_cap(const u8 *ies, int ies_len) +{ + u32 capabilities = 0; + const u8 *ie_data; + const u8 *ie; + int ie_len; + + if (!ies || ies_len == 0) { + SLSI_ERR_NODEV("no ie[&%p %d]\n", ies, ies_len); + return 0; + } + ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ies, ies_len); + if (ie) { + ie_len = ie[1]; + ie_data = &ie[2]; + if ((ie_len >= 4) && (ie_data[3] & SLSI_WLAN_EXT_CAPA3_INTERWORKING_ENABLED)) + capabilities |= SLSI_LLS_CAPABILITY_INTERWORKING; + if ((ie_len >= 7) && (ie_data[6] & 0x01)) /* Bit48: UTF-8 ssid */ + capabilities |= SLSI_LLS_CAPABILITY_SSID_UTF8; + } + + ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, SLSI_WLAN_OUI_TYPE_WFA_HS20_IND, ies, ies_len); + if (ie) + capabilities |= SLSI_LLS_CAPABILITY_HS20; + return capabilities; +} + +static void slsi_lls_iface_sta_stats(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, + struct slsi_lls_iface_stat *iface_stat) +{ + int i; + struct slsi_lls_interface_link_layer_info *lls_info = &iface_stat->info; + enum slsi_lls_peer_type peer_type; + struct slsi_peer *peer; + const u8 *ie_data, *ie; + u8 ie_len; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + + if (ndev_vif->ifnum == SLSI_NET_INDEX_WLAN) { + lls_info->mode = SLSI_LLS_INTERFACE_STA; + peer_type = SLSI_LLS_PEER_AP; + } else { + lls_info->mode = SLSI_LLS_INTERFACE_P2P_CLIENT; + peer_type = SLSI_LLS_PEER_P2P_GO; + } + + switch (ndev_vif->sta.vif_status) { + case SLSI_VIF_STATUS_CONNECTING: + lls_info->state = SLSI_LLS_AUTHENTICATING; + break; + case SLSI_VIF_STATUS_CONNECTED: + lls_info->state = SLSI_LLS_ASSOCIATED; + break; + default: + lls_info->state = SLSI_LLS_DISCONNECTED; + } + lls_info->roaming = ndev_vif->sta.roam_in_progress ? + SLSI_LLS_ROAMING_ACTIVE : SLSI_LLS_ROAMING_IDLE; + + iface_stat->info.capabilities = 0; + lls_info->ssid[0] = 0; + if (ndev_vif->sta.sta_bss) { + ie = cfg80211_find_ie(WLAN_EID_SSID, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + if (ie) { + ie_len = ie[1]; + ie_data = &ie[2]; + memcpy(lls_info->ssid, ie_data, ie_len); + lls_info->ssid[ie_len] = 0; + } + SLSI_ETHER_COPY(lls_info->bssid, ndev_vif->sta.sta_bss->bssid); + ie = cfg80211_find_ie(WLAN_EID_COUNTRY, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + if (ie) { + ie_data = &ie[2]; + memcpy(lls_info->ap_country_str, ie_data, 3); + iface_stat->peer_info[0].capabilities |= SLSI_LLS_CAPABILITY_COUNTRY; + } + } + + peer = ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET]; /* connected AP */ + if (peer && peer->valid && peer->assoc_ie && peer->assoc_resp_ie) { + iface_stat->info.capabilities |= slsi_lls_ie_to_cap(peer->assoc_ie->data, peer->assoc_ie->len); + if (peer->capabilities & WLAN_CAPABILITY_PRIVACY) { + iface_stat->peer_info[0].capabilities |= SLSI_LLS_CAPABILITY_PROTECTED; + iface_stat->info.capabilities |= SLSI_LLS_CAPABILITY_PROTECTED; + } + if (peer->qos_enabled) { + iface_stat->peer_info[0].capabilities |= SLSI_LLS_CAPABILITY_QOS; + iface_stat->info.capabilities |= SLSI_LLS_CAPABILITY_QOS; + } + iface_stat->peer_info[0].capabilities |= slsi_lls_ie_to_cap(peer->assoc_resp_ie->data, peer->assoc_resp_ie->len); + + SLSI_ETHER_COPY(iface_stat->peer_info[0].peer_mac_address, peer->address); + iface_stat->peer_info[0].type = peer_type; + iface_stat->num_peers = 1; + } + + for (i = MAP_AID_TO_QS(SLSI_TDLS_PEER_INDEX_MIN); i <= MAP_AID_TO_QS(SLSI_TDLS_PEER_INDEX_MAX); i++) { + peer = ndev_vif->peer_sta_record[i]; + if (peer && peer->valid) { + SLSI_ETHER_COPY(iface_stat->peer_info[iface_stat->num_peers].peer_mac_address, peer->address); + iface_stat->peer_info[iface_stat->num_peers].type = SLSI_LLS_PEER_TDLS; + if (peer->qos_enabled) + iface_stat->peer_info[iface_stat->num_peers].capabilities |= SLSI_LLS_CAPABILITY_QOS; + iface_stat->peer_info[iface_stat->num_peers].num_rate = 0; + iface_stat->num_peers++; + } + } +} + +static void slsi_lls_iface_ap_stats(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, struct slsi_lls_iface_stat *iface_stat) +{ + enum slsi_lls_peer_type peer_type = SLSI_LLS_PEER_INVALID; + struct slsi_peer *peer; + int i; + struct net_device *dev; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + + /* We are AP/GO, so we advertize our own country. */ + memcpy(iface_stat->info.ap_country_str, iface_stat->info.country_str, 3); + + if (ndev_vif->ifnum == SLSI_NET_INDEX_WLAN) { + iface_stat->info.mode = SLSI_LLS_INTERFACE_SOFTAP; + peer_type = SLSI_LLS_PEER_STA; + } else if (ndev_vif->ifnum == SLSI_NET_INDEX_P2PX_SWLAN) { + dev = sdev->netdev[SLSI_NET_INDEX_P2PX_SWLAN]; + if (SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif)) { + iface_stat->info.mode = SLSI_LLS_INTERFACE_P2P_GO; + peer_type = SLSI_LLS_PEER_P2P_CLIENT; + } + } + + for (i = MAP_AID_TO_QS(SLSI_PEER_INDEX_MIN); i <= MAP_AID_TO_QS(SLSI_PEER_INDEX_MAX); i++) { + peer = ndev_vif->peer_sta_record[i]; + if (peer && peer->valid) { + SLSI_ETHER_COPY(iface_stat->peer_info[iface_stat->num_peers].peer_mac_address, peer->address); + iface_stat->peer_info[iface_stat->num_peers].type = peer_type; + iface_stat->peer_info[iface_stat->num_peers].num_rate = 0; + if (peer->qos_enabled) + iface_stat->peer_info[iface_stat->num_peers].capabilities = SLSI_LLS_CAPABILITY_QOS; + iface_stat->num_peers++; + } + } + + memcpy(iface_stat->info.ssid, ndev_vif->ap.ssid, ndev_vif->ap.ssid_len); + iface_stat->info.ssid[ndev_vif->ap.ssid_len] = 0; + if (ndev_vif->ap.privacy) + iface_stat->info.capabilities |= SLSI_LLS_CAPABILITY_PROTECTED; + if (ndev_vif->ap.qos_enabled) + iface_stat->info.capabilities |= SLSI_LLS_CAPABILITY_QOS; +} + +static void slsi_lls_iface_stat_fill(struct slsi_dev *sdev, + struct net_device *net_dev, + struct slsi_lls_iface_stat *iface_stat) +{ + int i; + struct netdev_vif *ndev_vif; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_AC_RETRIES, { SLSI_TRAFFIC_Q_BE + 1, 0 } }, + { SLSI_PSID_UNIFI_AC_RETRIES, { SLSI_TRAFFIC_Q_BK + 1, 0 } }, + { SLSI_PSID_UNIFI_AC_RETRIES, { SLSI_TRAFFIC_Q_VI + 1, 0 } }, + { SLSI_PSID_UNIFI_AC_RETRIES, { SLSI_TRAFFIC_Q_VO + 1, 0 } }, + { SLSI_PSID_UNIFI_BEACON_RECEIVED, {0, 0} }, + { SLSI_PSID_UNIFI_PS_LEAKY_AP, {0, 0} }, + { SLSI_PSID_UNIFI_RSSI, {0, 0} } }; + + iface_stat->iface = NULL; + iface_stat->info.mode = SLSI_LLS_INTERFACE_UNKNOWN; + iface_stat->info.country_str[0] = sdev->device_config.domain_info.regdomain->alpha2[0]; + iface_stat->info.country_str[1] = sdev->device_config.domain_info.regdomain->alpha2[1]; + iface_stat->info.country_str[2] = ' '; /* 3rd char of our country code is ASCII */ + + for (i = 0; i < SLSI_LLS_AC_MAX; i++) + iface_stat->ac[i].ac = SLSI_LLS_AC_MAX; + + if (!net_dev) + return; + + ndev_vif = netdev_priv(net_dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) + goto exit; + + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + slsi_lls_iface_sta_stats(sdev, ndev_vif, iface_stat); + } else if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + slsi_lls_iface_ap_stats(sdev, ndev_vif, iface_stat); + SLSI_ETHER_COPY(iface_stat->info.bssid, net_dev->dev_addr); + } + SLSI_ETHER_COPY(iface_stat->info.mac_addr, net_dev->dev_addr); + + mibrsp.dataLength = 10 * sizeof(get_values) / sizeof(get_values[0]); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes for interface MIBs\n", mibrsp.dataLength); + goto exit; + } + + values = slsi_read_mibs(sdev, net_dev, get_values, sizeof(get_values) / sizeof(get_values[0]), &mibrsp); + if (!values) + goto exit; + + for (i = 0; i < SLSI_LLS_AC_MAX; i++) { + if (values[i].type == SLSI_MIB_TYPE_UINT) { + iface_stat->ac[i].ac = slsi_fapi_to_android_traffic_q(i); + iface_stat->ac[i].retries = values[i].u.uintValue; + iface_stat->ac[i].rx_mpdu = ndev_vif->rx_packets[i]; + iface_stat->ac[i].tx_mpdu = ndev_vif->tx_packets[i]; + iface_stat->ac[i].mpdu_lost = ndev_vif->tx_no_ack[i]; + } else { + SLSI_WARN(sdev, "LLS: expected datatype 1 but received %d\n", values[i].type); + } + } + + if (values[4].type == SLSI_MIB_TYPE_UINT) + iface_stat->beacon_rx = values[4].u.uintValue; + + if (values[5].type == SLSI_MIB_TYPE_UINT) { + iface_stat->leaky_ap_detected = values[5].u.uintValue; + iface_stat->leaky_ap_guard_time = 5; /* 5 milli sec. As mentioned in lls document */ + } + + if (values[6].type == SLSI_MIB_TYPE_INT) + iface_stat->rssi_data = values[6].u.intValue; + +exit: + kfree(values); + kfree(mibrsp.data); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +void slsi_check_num_radios(struct slsi_dev *sdev) +{ + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_RADIO_SCAN_TIME, { 1, 0 } } }; + + if (slsi_is_test_mode_enabled()) { + SLSI_WARN(sdev, "not supported in WlanLite mode\n"); + return; + } + + /* Expect each mib length in response is <= 15 So assume 15 bytes for each MIB */ + mibrsp.dataLength = 15 * ARRAY_SIZE(get_values); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + sdev->lls_num_radio = 0; + return; + } + + values = slsi_read_mibs(sdev, NULL, get_values, ARRAY_SIZE(get_values), &mibrsp); + if (!values) { + sdev->lls_num_radio = 0; + } else { + sdev->lls_num_radio = values[0].type == SLSI_MIB_TYPE_NONE ? 1 : 2; + kfree(values); + } + + kfree(mibrsp.data); +} + +static void slsi_lls_radio_stat_fill(struct slsi_dev *sdev, struct net_device *dev, + struct slsi_lls_radio_stat *radio_stat, + int max_chan_count, int radio_index, int twoorfive) +{ + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_data supported_chan_mib = { 0, NULL }; + struct slsi_mib_value *values = NULL; + struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_RADIO_SCAN_TIME, { radio_index, 0 } }, + { SLSI_PSID_UNIFI_RADIO_RX_TIME, { radio_index, 0 } }, + { SLSI_PSID_UNIFI_RADIO_TX_TIME, { radio_index, 0 } }, + { SLSI_PSID_UNIFI_RADIO_ON_TIME, { radio_index, 0 } }, + { SLSI_PSID_UNIFI_SUPPORTED_CHANNELS, { 0, 0 } } }; + u32 *radio_data[] = {&radio_stat->on_time_scan, &radio_stat->rx_time, + &radio_stat->tx_time, &radio_stat->on_time}; + int i, j, chan_count, chan_start, k; + + radio_stat->radio = radio_index; + + /* Expect each mib length in response is <= 15 So assume 15 bytes for each MIB */ + mibrsp.dataLength = 15 * sizeof(get_values) / sizeof(get_values[0]); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (mibrsp.data == NULL) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + return; + } + values = slsi_read_mibs(sdev, NULL, get_values, sizeof(get_values) / sizeof(get_values[0]), &mibrsp); + if (!values) + goto exit_with_mibrsp; + + for (i = 0; i < (sizeof(get_values) / sizeof(get_values[0])) - 1; i++) { + if (values[i].type == SLSI_MIB_TYPE_UINT) { + *radio_data[i] = values[i].u.uintValue; + } else { + SLSI_ERR(sdev, "invalid type. iter:%d", i); + } + } + if (values[4].type != SLSI_MIB_TYPE_OCTET) { + SLSI_ERR(sdev, "Supported_Chan invalid type."); + goto exit_with_values; + } + + supported_chan_mib = values[4].u.octetValue; + for (j = 0; j < supported_chan_mib.dataLength / 2; j++) { + struct slsi_lls_channel_info *radio_chan; + + chan_start = supported_chan_mib.data[j * 2]; + chan_count = supported_chan_mib.data[j * 2 + 1]; + if (radio_stat->num_channels + chan_count > max_chan_count) + chan_count = max_chan_count - radio_stat->num_channels; + if (chan_start == 1 && (twoorfive & BIT(0))) { /* for 2.4GHz */ + for (k = 0; k < chan_count; k++) { + radio_chan = &radio_stat->channels[radio_stat->num_channels + k].channel; + if (k + chan_start == 14) + radio_chan->center_freq = 2484; + else + radio_chan->center_freq = 2407 + (chan_start + k) * 5; + radio_chan->width = SLSI_LLS_CHAN_WIDTH_20; + } + radio_stat->num_channels += chan_count; + } else if (chan_start != 1 && (twoorfive & BIT(1))) { + /* for 5GHz */ + for (k = 0; k < chan_count; k++) { + radio_chan = &radio_stat->channels[radio_stat->num_channels + k].channel; + radio_chan->center_freq = 5000 + (chan_start + (k * 4)) * 5; + radio_chan->width = SLSI_LLS_CHAN_WIDTH_20; + } + radio_stat->num_channels += chan_count; + } + } +exit_with_values: + kfree(values); +exit_with_mibrsp: + kfree(mibrsp.data); +} + +static int slsi_lls_fill(struct slsi_dev *sdev, u8 **src_buf) +{ + struct net_device *net_dev = NULL; + struct slsi_lls_radio_stat *radio_stat; + struct slsi_lls_radio_stat *radio_stat_temp; + struct slsi_lls_iface_stat *iface_stat; + int buf_len = 0; + int max_chan_count = 0; + u8 *buf; + int num_of_radios_supported; + int i = 0; + int radio_type[2] = {BIT(0), BIT(1)}; + + if (sdev->lls_num_radio == 0) { + slsi_check_num_radios(sdev); + if (sdev->lls_num_radio == 0) + return -EIO; + } + + num_of_radios_supported = sdev->lls_num_radio; + net_dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + + if (sdev->wiphy->bands[NL80211_BAND_2GHZ]) + max_chan_count = sdev->wiphy->bands[NL80211_BAND_2GHZ]->n_channels; + if (sdev->wiphy->bands[NL80211_BAND_5GHZ]) + max_chan_count += sdev->wiphy->bands[NL80211_BAND_5GHZ]->n_channels; + buf_len = (int)((num_of_radios_supported * sizeof(struct slsi_lls_radio_stat)) + + sizeof(struct slsi_lls_iface_stat) + + sizeof(u8) + + (sizeof(struct slsi_lls_peer_info) * SLSI_ADHOC_PEER_CONNECTIONS_MAX) + + (sizeof(struct slsi_lls_channel_stat) * max_chan_count)); + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) { + SLSI_ERR(sdev, "No mem. Size:%d\n", buf_len); + return -ENOMEM; + } + buf[0] = num_of_radios_supported; + *src_buf = buf; + iface_stat = (struct slsi_lls_iface_stat *)(buf + sizeof(u8)); + slsi_lls_iface_stat_fill(sdev, net_dev, iface_stat); + + radio_stat = (struct slsi_lls_radio_stat *)(buf + sizeof(u8) + sizeof(struct slsi_lls_iface_stat) + + (sizeof(struct slsi_lls_peer_info) * iface_stat->num_peers)); + radio_stat_temp = radio_stat; + if (num_of_radios_supported == 1) { + radio_type[0] = BIT(0) | BIT(1); + slsi_lls_radio_stat_fill(sdev, net_dev, radio_stat, max_chan_count, 0, radio_type[0]); + radio_stat = (struct slsi_lls_radio_stat *)((u8 *)radio_stat + sizeof(struct slsi_lls_radio_stat) + + (sizeof(struct slsi_lls_channel_stat) * radio_stat->num_channels)); + } else { + for (i = 1; i <= num_of_radios_supported ; i++) { + slsi_lls_radio_stat_fill(sdev, net_dev, radio_stat, max_chan_count, i, radio_type[i - 1]); + radio_stat = (struct slsi_lls_radio_stat *)((u8 *)radio_stat + + sizeof(struct slsi_lls_radio_stat) + (sizeof(struct slsi_lls_channel_stat) + * radio_stat->num_channels)); + } + } +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (slsi_dev_llslogs_supported()) + slsi_lls_debug_dump_stats(sdev, radio_stat_temp, iface_stat, buf, buf_len, num_of_radios_supported); +#endif + return buf_len; +} + +static int slsi_lls_get_stats(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret; + u8 *buf = NULL; + int buf_len; + + if (!slsi_dev_lls_supported()) + return -EOPNOTSUPP; + + if (slsi_is_test_mode_enabled()) { + SLSI_WARN(sdev, "not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + + if (!sdev) { + SLSI_ERR(sdev, "sdev is Null\n"); + return -EINVAL; + } + + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + /* In case of lower layer failure do not read LLS MIBs */ + if (sdev->mlme_blocked) + buf_len = -EIO; + else + buf_len = slsi_lls_fill(sdev, &buf); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + + if (buf_len > 0) { + ret = slsi_vendor_cmd_reply(wiphy, buf, buf_len); + if (ret) + SLSI_ERR_NODEV("vendor cmd reply failed (err:%d)\n", ret); + } else { + ret = buf_len; + } + kfree(buf); + return ret; +} + +static int slsi_gscan_set_oui(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; + +#ifdef CONFIG_SCSC_WLAN_ENABLE_MAC_RANDOMISATION + + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + struct netdev_vif *ndev_vif; + int temp; + int type; + const struct nlattr *attr; + u8 scan_oui[6]; + + memset(&scan_oui, 0, 6); + + if (!dev) { + SLSI_ERR(sdev, "dev is NULL!!\n"); + return -EINVAL; + } + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + sdev->scan_addr_set = 0; + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_NL_ATTRIBUTE_PNO_RANDOM_MAC_OUI: + { + memcpy(&scan_oui, nla_data(attr), 3); + memcpy(sdev->scan_mac_addr, scan_oui, 6); + sdev->scan_addr_set = 1; + break; + } + default: + ret = -EINVAL; + SLSI_ERR(sdev, "Invalid type : %d\n", type); + break; + } + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); +#endif + return ret; +} + +static int slsi_get_feature_set(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + u32 feature_set = 0; + int ret = 0; + + SLSI_DBG3_NODEV(SLSI_GSCAN, "\n"); + + feature_set |= SLSI_WIFI_HAL_FEATURE_RSSI_MONITOR; + feature_set |= SLSI_WIFI_HAL_FEATURE_CONTROL_ROAMING; + feature_set |= SLSI_WIFI_HAL_FEATURE_TDLS | SLSI_WIFI_HAL_FEATURE_TDLS_OFFCHANNEL; +#ifndef CONFIG_SCSC_WLAN_NAT_KEEPALIVE_DISABLE + feature_set |= SLSI_WIFI_HAL_FEATURE_MKEEP_ALIVE; +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + feature_set |= SLSI_WIFI_HAL_FEATURE_LOGGER; +#endif + if (slsi_dev_gscan_supported()) + feature_set |= SLSI_WIFI_HAL_FEATURE_GSCAN; + if (slsi_dev_lls_supported()) + feature_set |= SLSI_WIFI_HAL_FEATURE_LINK_LAYER_STATS; + if (slsi_dev_epno_supported()) + feature_set |= SLSI_WIFI_HAL_FEATURE_HAL_EPNO; + if (slsi_dev_nan_supported(SDEV_FROM_WIPHY(wiphy))) + feature_set |= SLSI_WIFI_HAL_FEATURE_NAN; + if (slsi_dev_rtt_supported()) { + feature_set |= SLSI_WIFI_HAL_FEATURE_D2D_RTT; + feature_set |= SLSI_WIFI_HAL_FEATURE_D2AP_RTT; + } + + ret = slsi_vendor_cmd_reply(wiphy, &feature_set, sizeof(feature_set)); + + return ret; +} + +static int slsi_set_country_code(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp; + int type; + const struct nlattr *attr; + char country_code[SLSI_COUNTRY_CODE_LEN]; + + SLSI_DBG3(sdev, SLSI_GSCAN, "Received country code command\n"); + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_NL_ATTRIBUTE_COUNTRY_CODE: + { + if (nla_len(attr) < (SLSI_COUNTRY_CODE_LEN - 1)) { + ret = -EINVAL; + SLSI_ERR(sdev, "Insufficient Country Code Length : %d\n", nla_len(attr)); + return ret; + } + memcpy(country_code, nla_data(attr), (SLSI_COUNTRY_CODE_LEN - 1)); + break; + } + default: + ret = -EINVAL; + SLSI_ERR(sdev, "Invalid type : %d\n", type); + return ret; + } + } + ret = slsi_set_country_update_regd(sdev, country_code, SLSI_COUNTRY_CODE_LEN); + if (ret < 0) + SLSI_ERR(sdev, "Set country failed ret:%d\n", ret); + return ret; +} + +static int slsi_apf_read_filter(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + u8 *host_dst; + int datalen; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_APF_READ_FILTER\n"); + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + if (!sdev->device_config.fw_apf_supported) { + SLSI_WARN(sdev, "APF not supported by the firmware.\n"); + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return -ENOTSUPP; + } + + ret = slsi_mlme_read_apf_request(sdev, dev, &host_dst, &datalen); + if (!ret) + ret = slsi_vendor_cmd_reply(wiphy, host_dst, datalen); + + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return ret; +} + +static int slsi_apf_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + struct sk_buff *nl_skb; + struct nlattr *nlattr_start; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_APF_GET_CAPABILITIES\n"); + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + if (!sdev->device_config.fw_apf_supported) { + SLSI_WARN(sdev, "APF not supported by the firmware.\n"); + ret = -ENOTSUPP; + goto exit; + } + memset(&sdev->device_config.apf_cap, 0, sizeof(struct slsi_apf_capabilities)); + + ret = slsi_mib_get_apf_cap(sdev, dev); + if (ret != 0) { + SLSI_ERR(sdev, "Failed to read mib\n"); + goto exit; + } + SLSI_DBG3(sdev, SLSI_GSCAN, "APF version: %d Max_Length:%d\n", sdev->device_config.apf_cap.version, + sdev->device_config.apf_cap.max_length); + nl_skb = cfg80211_vendor_cmd_alloc_reply_skb(sdev->wiphy, NLMSG_DEFAULT_SIZE); + if (!nl_skb) { + SLSI_ERR(sdev, "NO MEM for nl_skb!!!\n"); + ret = -ENOMEM; + goto exit; + } + + nlattr_start = nla_nest_start(nl_skb, NL80211_ATTR_VENDOR_DATA); + if (!nlattr_start) { + SLSI_ERR(sdev, "failed to put NL80211_ATTR_VENDOR_DATA\n"); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + ret = -EINVAL; + goto exit; + } + + ret = nla_put_u16(nl_skb, SLSI_APF_ATTR_VERSION, sdev->device_config.apf_cap.version); + ret |= nla_put_u16(nl_skb, SLSI_APF_ATTR_MAX_LEN, sdev->device_config.apf_cap.max_length); + if (ret) { + SLSI_ERR(sdev, "Error in nla_put*:%x\n", ret); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(nl_skb); + if (ret) + SLSI_ERR(sdev, "apf_get_capabilities cfg80211_vendor_cmd_reply failed :%d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return ret; +} + +static int slsi_apf_set_filter(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + int ret = 0; + int temp; + int type; + const struct nlattr *attr; + u32 program_len = 0; + u8 *program; + + SLSI_DBG3(sdev, SLSI_GSCAN, "Received apf_set_filter command\n"); + SLSI_MUTEX_LOCK(sdev->device_config_mutex); + if (!sdev->device_config.fw_apf_supported) { + SLSI_WARN(sdev, "APF not supported by the firmware.\n"); + ret = -ENOTSUPP; + goto exit; + } + + if (!dev) { + SLSI_ERR(sdev, "dev is NULL!!\n"); + ret = -EINVAL; + goto exit; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_APF_ATTR_PROGRAM_LEN: + { + program_len = nla_get_u32(attr); + program = kmalloc(program_len, GFP_KERNEL); + break; + } + case SLSI_APF_ATTR_PROGRAM: + { + memcpy(program, (u8 *)nla_data(attr), program_len); + break; + } + default: + SLSI_ERR(sdev, "Invalid type : %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + ret = slsi_mlme_install_apf_request(sdev, dev, program, program_len); + if (ret < 0) { + SLSI_ERR(sdev, "apf_set_filter failed ret:%d\n", ret); + ret = -EINVAL; + goto exit; + } +exit: + SLSI_MUTEX_UNLOCK(sdev->device_config_mutex); + return ret; +} + +static int slsi_rtt_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_rtt_capabilities rtt_cap; + int ret = 0; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_GET_RTT_CAPABILITIES\n"); + if (!slsi_dev_rtt_supported()) { + SLSI_WARN(sdev, "RTT not supported.\n"); + return -ENOTSUPP; + } + memset(&rtt_cap, 0, sizeof(struct slsi_rtt_capabilities)); + + ret = slsi_mib_get_rtt_cap(sdev, dev, &rtt_cap); + if (ret != 0) { + SLSI_ERR(sdev, "Failed to read mib\n"); + return ret; + } + ret = slsi_vendor_cmd_reply(wiphy, &rtt_cap, sizeof(struct slsi_rtt_capabilities)); + if (ret) + SLSI_ERR_NODEV("rtt_get_capabilities vendor cmd reply failed (err = %d)\n", ret); + return ret; +} + +static int slsi_rtt_set_config(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + int r, type, j = 0; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); +#ifdef CONFIG_SCSC_WIFI_NAN_ENABLE + struct netdev_vif *ndev_vif; +#endif + struct net_device *dev = wdev->netdev; + struct slsi_rtt_config *nl_rtt_params; + const struct nlattr *iter, *outer, *inner; + u8 source_addr[ETH_ALEN]; + int tmp, tmp1, tmp2; + u16 rtt_id = 0; + u8 num_devices = 0; + u16 rtt_peer = SLSI_RTT_PEER_AP; + u16 vif_idx = 0; + u16 channel_freq = 0; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_RTT_RANGE_START\n"); + if (!slsi_dev_rtt_supported()) { + SLSI_ERR(sdev, "RTT not supported.\n"); + return WIFI_HAL_ERROR_NOT_SUPPORTED; + } + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case SLSI_RTT_ATTRIBUTE_TARGET_CNT: + num_devices = nla_get_u8(iter); + SLSI_DBG1_NODEV(SLSI_GSCAN, "Target cnt %d\n", num_devices); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_ID: + rtt_id = nla_get_u16(iter); + SLSI_DBG1_NODEV(SLSI_GSCAN, "Target id %d\n", rtt_id); + break; + default: + SLSI_ERR_NODEV("Unexpected RTT attribute:type - %d\n", type); + break; + } + } + if (!num_devices) { + SLSI_ERR_NODEV("No device found for rtt configuration!\n"); + return -EINVAL; + } + /* Allocate memory for the received config params */ + nl_rtt_params = kcalloc(num_devices, sizeof(*nl_rtt_params), GFP_KERNEL); + if (!nl_rtt_params) { + SLSI_ERR_NODEV("Failed for allocate memory for config rtt_param\n"); + return -ENOMEM; + } + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case SLSI_RTT_ATTRIBUTE_TARGET_INFO: + nla_for_each_nested(outer, iter, tmp1) { + nla_for_each_nested(inner, outer, tmp2) { + switch (nla_type(inner)) { + case SLSI_RTT_ATTRIBUTE_TARGET_MAC: + memcpy(nl_rtt_params[j].peer_addr, nla_data(inner), ETH_ALEN); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_TYPE: + nl_rtt_params[j].type = nla_get_u16(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_PEER: + rtt_peer = nla_get_u16(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_CHAN_FREQ: + channel_freq = nla_get_u16(inner); + nl_rtt_params[j].channel_freq = channel_freq * 2; + break; + case SLSI_RTT_ATTRIBUTE_TARGET_PERIOD: + nl_rtt_params[j].burst_period = nla_get_u8(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_NUM_BURST: + nl_rtt_params[j].num_burst = nla_get_u8(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_NUM_FTM_BURST: + nl_rtt_params[j].num_frames_per_burst = nla_get_u8(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_NUM_RETRY_FTMR: + nl_rtt_params[j].num_retries_per_ftmr = nla_get_u8(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_BURST_DURATION: + nl_rtt_params[j].burst_duration = nla_get_u8(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_PREAMBLE: + nl_rtt_params[j].preamble = nla_get_u16(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_BW: + nl_rtt_params[j].bw = nla_get_u16(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_LCI: + nl_rtt_params[j].LCI_request = nla_get_u16(inner); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_LCR: + nl_rtt_params[j].LCR_request = nla_get_u16(inner); + break; + default: + SLSI_ERR_NODEV("Unknown RTT INFO ATTRIBUTE type: %d\n", type); + break; + } + } + j++; + } + break; + default: + SLSI_ERR_NODEV("No ATTRIBUTE_Target cnt - %d\n", type); + break; + } + } + + SLSI_ETHER_COPY(source_addr, dev->dev_addr); + + if (rtt_peer == SLSI_RTT_PEER_NAN) { +#ifdef CONFIG_SCSC_WIFI_NAN_ENABLE + if (!slsi_dev_nan_supported(sdev)) { + SLSI_ERR(sdev, "NAN not supported(mib:%d)\n", sdev->nan_enabled); + kfree(nl_rtt_params); + return WIFI_HAL_ERROR_NOT_SUPPORTED; + } + ndev_vif = netdev_priv(dev); + if (ndev_vif->activated) { + vif_idx = ndev_vif->vif_type; + } else { + SLSI_ERR(sdev, "NAN vif not activated\n"); + kfree(nl_rtt_params); + return -EINVAL; + } +#else + SLSI_ERR(sdev, "NAN not enabled\n"); + return -ENOTSUPP; +#endif + } + r = slsi_mlme_add_range_req(sdev, num_devices, nl_rtt_params, rtt_id, vif_idx, source_addr); + if (r) { + r = -EINVAL; + SLSI_ERR_NODEV("Failed to set rtt config\n"); + } else { + sdev->rtt_vif[rtt_id] = vif_idx; + SLSI_DBG1_NODEV(SLSI_GSCAN, "Successfully set rtt config\n"); + } + kfree(nl_rtt_params); + return r; +} + +int slsi_tx_rate_calc(struct sk_buff *nl_skb, u16 fw_rate, int res, bool tx_rate) +{ + u8 preamble; + const u32 fw_rate_idx_to_80211_rate[] = { 0, 10, 20, 55, 60, 90, 110, 120, 180, 240, 360, 480, 540 }; + u32 data_rate = 0; + u32 mcs = 0, nss = 0; + u32 chan_bw_idx = 0; + int gi_idx; + + preamble = (fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) >> 14; + if ((fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) == SLSI_FW_API_RATE_NON_HT_SELECTED) { + u16 fw_rate_idx = fw_rate & SLSI_FW_API_RATE_INDEX_FIELD; + + if (fw_rate > 0 && fw_rate_idx < ARRAY_SIZE(fw_rate_idx_to_80211_rate)) + data_rate = fw_rate_idx_to_80211_rate[fw_rate_idx]; + } else if ((fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) == SLSI_FW_API_RATE_HT_SELECTED) { + nss = (SLSI_FW_API_RATE_HT_NSS_FIELD & fw_rate) >> 6; + chan_bw_idx = (fw_rate & SLSI_FW_API_RATE_BW_FIELD) >> 9; + gi_idx = ((fw_rate & SLSI_FW_API_RATE_SGI) == SLSI_FW_API_RATE_SGI) ? 1 : 0; + mcs = SLSI_FW_API_RATE_HT_MCS_FIELD & fw_rate; + if ((chan_bw_idx < 2) && (mcs <= 7)) { + data_rate = (nss + 1) * slsi_rates_table[chan_bw_idx][gi_idx][mcs]; + } else if (mcs == 32 && chan_bw_idx == 1) { + if (gi_idx == 1) + data_rate = (nss + 1) * 67; + else + data_rate = (nss + 1) * 60; + } else { + SLSI_WARN_NODEV("FW DATA RATE decode error fw_rate:%x, bw:%x, mcs_idx:%x, nss : %d\n", + fw_rate, chan_bw_idx, mcs, nss); + } + } else if ((fw_rate & SLSI_FW_API_RATE_HT_SELECTOR_FIELD) == SLSI_FW_API_RATE_VHT_SELECTED) { + /* report vht rate in legacy units and not as mcs index. reason: upper layers may still be not + * updated with vht msc table. + */ + chan_bw_idx = (fw_rate & SLSI_FW_API_RATE_BW_FIELD) >> 9; + gi_idx = ((fw_rate & SLSI_FW_API_RATE_SGI) == SLSI_FW_API_RATE_SGI) ? 1 : 0; + /* Calculate NSS --> bits 6 to 4*/ + nss = (SLSI_FW_API_RATE_VHT_NSS_FIELD & fw_rate) >> 4; + mcs = SLSI_FW_API_RATE_VHT_MCS_FIELD & fw_rate; + /* Bandwidth (BW): 0x0= 20 MHz, 0x1= 40 MHz, 0x2= 80 MHz, 0x3= 160/ 80+80 MHz. 0x3 is not supported */ + if ((chan_bw_idx <= 2) && (mcs <= 9)) + data_rate = (nss + 1) * slsi_rates_table[chan_bw_idx][gi_idx][mcs]; + else + SLSI_WARN_NODEV("FW DATA RATE decode error fw_rate:%x, bw:%x, mcs_idx:%x,nss : %d\n", + fw_rate, chan_bw_idx, mcs, nss); + if (nss > 1) + nss += 1; + } + + if (tx_rate) { + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_TX_PREAMBLE, preamble); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_TX_NSS, nss); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_TX_BW, chan_bw_idx); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_TX_MCS, mcs); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_TX_RATE, data_rate); + } else { + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_RX_PREAMBLE, preamble); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_RX_NSS, nss); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_RX_BW, chan_bw_idx); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_RX_MCS, mcs); + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_RX_RATE, data_rate); + } + return res; +} + +void slsi_rx_range_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u32 i, tm; + u16 rtt_entry_count = fapi_get_u16(skb, u.mlme_range_ind.entries); + u16 rtt_id = fapi_get_u16(skb, u.mlme_range_ind.rtt_id); + u32 tmac = fapi_get_u32(skb, u.mlme_range_ind.spare_3); + int data_len = fapi_get_datalen(skb); + u8 *ip_ptr, *start_ptr; + u16 tx_data, rx_data; + struct sk_buff *nl_skb; + int res = 0; + struct nlattr *nlattr_nested; + struct timespec ts; + u64 tkernel; + u8 rep_cnt = 0; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_RTT_RESULT_EVENT, GFP_KERNEL); +#else + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, SLSI_NL80211_RTT_RESULT_EVENT, + GFP_KERNEL); +#endif +#ifdef CONFIG_SCSC_WLAN_DEBUG + SLSI_DBG1_NODEV(SLSI_GSCAN, "Event: %s(%d)\n", + slsi_print_event_name(SLSI_NL80211_RTT_RESULT_EVENT), SLSI_NL80211_RTT_RESULT_EVENT); +#endif + + if (!nl_skb) { + SLSI_ERR(sdev, "NO MEM for nl_skb!!!\n"); + goto exit; + } + + ip_ptr = fapi_get_data(skb); + start_ptr = fapi_get_data(skb); + res |= nla_put_u16(nl_skb, SLSI_RTT_ATTRIBUTE_RESULT_CNT, rtt_entry_count); + res |= nla_put_u16(nl_skb, SLSI_RTT_ATTRIBUTE_TARGET_ID, rtt_id); + res |= nla_put_u8(nl_skb, SLSI_RTT_ATTRIBUTE_RESULTS_PER_TARGET, 1); + for (i = 0; i < rtt_entry_count; i++) { + nlattr_nested = nla_nest_start(nl_skb, SLSI_RTT_ATTRIBUTE_RESULT); + if (!nlattr_nested) { + SLSI_ERR(sdev, "Error in nla_nest_start\n"); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + goto exit; + } + ip_ptr += 7; /*skip first 7 bytes for fapi_ie_generic */ + res |= nla_put(nl_skb, SLSI_RTT_EVENT_ATTR_ADDR, ETH_ALEN, ip_ptr); + ip_ptr += 6; + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_BURST_NUM, *ip_ptr++); + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_MEASUREMENT_NUM, *ip_ptr++); + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_SUCCESS_NUM, *ip_ptr++); + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_NUM_PER_BURST_PEER, *ip_ptr++); + res |= nla_put_u16(nl_skb, SLSI_RTT_EVENT_ATTR_STATUS, *ip_ptr); + ip_ptr += 2; + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_RETRY_AFTER_DURATION, *ip_ptr++); + res |= nla_put_u16(nl_skb, SLSI_RTT_EVENT_ATTR_TYPE, *ip_ptr); + ip_ptr += 2; + res |= nla_put_u16(nl_skb, SLSI_RTT_EVENT_ATTR_RSSI, *ip_ptr); + ip_ptr += 2; + res |= nla_put_u16(nl_skb, SLSI_RTT_EVENT_ATTR_RSSI_SPREAD, *ip_ptr); + ip_ptr += 2; + memcpy(&tx_data, ip_ptr, 2); + res = slsi_tx_rate_calc(nl_skb, tx_data, res, 1); + ip_ptr += 2; + memcpy(&rx_data, ip_ptr, 2); + res = slsi_tx_rate_calc(nl_skb, rx_data, res, 0); + ip_ptr += 2; + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_RTT, *ip_ptr); + ip_ptr += 4; + res |= nla_put_u16(nl_skb, SLSI_RTT_EVENT_ATTR_RTT_SD, *ip_ptr); + ip_ptr += 2; + res |= nla_put_u16(nl_skb, SLSI_RTT_EVENT_ATTR_RTT_SPREAD, *ip_ptr); + ip_ptr += 2; + get_monotonic_boottime(&ts); + tkernel = (u64)TIMESPEC_TO_US(ts); + tm = *ip_ptr; + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_TIMESTAMP_US, tkernel - (tmac - tm)); + ip_ptr += 4; + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_DISTANCE_MM, *ip_ptr); + ip_ptr += 4; + res |= nla_put_u32(nl_skb, SLSI_RTT_EVENT_ATTR_DISTANCE_SD_MM, *ip_ptr); + ip_ptr += 4; + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_BURST_DURATION_MSN, *ip_ptr++); + res |= nla_put_u8(nl_skb, SLSI_RTT_EVENT_ATTR_NEGOTIATED_BURST_NUM, *ip_ptr++); + for (rep_cnt = 0; rep_cnt < 2; rep_cnt++) { + if (ip_ptr - start_ptr < data_len && ip_ptr[0] == WLAN_EID_MEASURE_REPORT) { + if (ip_ptr[4] == 8) /*LCI Element*/ + res |= nla_put(nl_skb, SLSI_RTT_EVENT_ATTR_LCI, + ip_ptr[1] + 2, ip_ptr); + else if (ip_ptr[4] == 11) /*LCR element */ + res |= nla_put(nl_skb, SLSI_RTT_EVENT_ATTR_LCR, + ip_ptr[1] + 2, ip_ptr); + ip_ptr += ip_ptr[1] + 2; + } + } + nla_nest_end(nl_skb, nlattr_nested); + } + SLSI_DBG_HEX(sdev, SLSI_GSCAN, fapi_get_data(skb), fapi_get_datalen(skb), "range indication skb buffer:\n"); + if (res) { + SLSI_ERR(sdev, "Error in nla_put*:%x\n", res); + kfree_skb(nl_skb); + goto exit; + } + cfg80211_vendor_event(nl_skb, GFP_KERNEL); +exit: + slsi_kfree_skb(skb); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +void slsi_rx_range_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 rtt_id = fapi_get_u16(skb, u.mlme_range_ind.rtt_id); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); +#ifdef CONFIG_SCSC_WLAN_DEBUG + SLSI_DBG1_NODEV(SLSI_GSCAN, "Event: %s(%d)\n", + slsi_print_event_name(SLSI_NL80211_RTT_COMPLETE_EVENT), SLSI_NL80211_RTT_COMPLETE_EVENT); +#endif + slsi_vendor_event(sdev, SLSI_NL80211_RTT_COMPLETE_EVENT, &rtt_id, sizeof(rtt_id)); + slsi_kfree_skb(skb); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +static int slsi_rtt_cancel_config(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + int temp, ret, r = 1, j = 0, type; + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + u8 *addr; + const struct nlattr *iter; + u16 num_devices = 0, rtt_id = 0; + + SLSI_DBG1_NODEV(SLSI_GSCAN, "RTT_SUBCMD_CANCEL_CONFIG\n"); + if (!slsi_dev_rtt_supported()) { + SLSI_WARN(sdev, "RTT not supported.\n"); + return -ENOTSUPP; + } + nla_for_each_attr(iter, data, len, temp) { + type = nla_type(iter); + switch (type) { + case SLSI_RTT_ATTRIBUTE_TARGET_CNT: + num_devices = nla_get_u16(iter); + SLSI_DBG1_NODEV(SLSI_GSCAN, "Target cnt %d\n", num_devices); + break; + case SLSI_RTT_ATTRIBUTE_TARGET_ID: + rtt_id = nla_get_u16(iter); + SLSI_DBG1_NODEV(SLSI_GSCAN, "Target id %d\n", rtt_id); + break; + default: + SLSI_ERR_NODEV("No ATTRIBUTE_Target cnt - %d\n", type); + break; + } + } + /* Allocate memory for the received mac addresses */ + if (num_devices) { + addr = kzalloc(ETH_ALEN * num_devices, GFP_KERNEL); + if (!addr) { + SLSI_ERR_NODEV("Failed for allocate memory for mac addresses\n"); + ret = -ENOMEM; + return ret; + } + nla_for_each_attr(iter, data, len, temp) { + type = nla_type(iter); + if (type == SLSI_RTT_ATTRIBUTE_TARGET_MAC) { + memcpy(&addr[j], nla_data(iter), ETH_ALEN); + j++; + } else { + SLSI_ERR_NODEV("No ATTRIBUTE_MAC - %d\n", type); + } + } + + r = slsi_mlme_del_range_req(sdev, dev, num_devices, addr, rtt_id); + kfree(addr); + } + if (r) + SLSI_ERR_NODEV("Failed to cancel rtt config\n"); + return r; +} + +static int slsi_configure_nd_offload(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + struct netdev_vif *ndev_vif; + int ret = 0; + int temp; + int type; + const struct nlattr *attr; + u8 nd_offload_enabled = 0; + + SLSI_DBG3(sdev, SLSI_GSCAN, "Received nd_offload command\n"); + + if (!dev) { + SLSI_ERR(sdev, "dev is NULL!!\n"); + return -EINVAL; + } + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated || (ndev_vif->vif_type != FAPI_VIFTYPE_STATION) || + (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTED)) { + SLSI_DBG3(sdev, SLSI_GSCAN, "vif error\n"); + ret = -EPERM; + goto exit; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_NL_ATTRIBUTE_ND_OFFLOAD_VALUE: + { + nd_offload_enabled = nla_get_u8(attr); + break; + } + default: + SLSI_ERR(sdev, "Invalid type : %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + ndev_vif->sta.nd_offload_enabled = nd_offload_enabled; + ret = slsi_mlme_set_ipv6_address(sdev, dev); + if (ret < 0) { + SLSI_ERR(sdev, "Configure nd_offload failed ret:%d nd_offload_enabled: %d\n", ret, nd_offload_enabled); + ret = -EINVAL; + goto exit; + } +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return ret; +} + +static int slsi_get_roaming_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + struct netdev_vif *ndev_vif; + int ret = 0; + struct slsi_mib_value *values = NULL; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_ROAM_BLACKLIST_SIZE, { 0, 0 } } }; + u32 max_blacklist_size = 0; + u32 max_whitelist_size = 0; + struct sk_buff *nl_skb; + struct nlattr *nlattr_start; + + if (!dev) { + SLSI_ERR(sdev, "dev is NULL!!\n"); + return -EINVAL; + } + + ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + mibrsp.dataLength = 10; + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + ret = -ENOMEM; + goto exit; + } + values = slsi_read_mibs(sdev, NULL, get_values, ARRAY_SIZE(get_values), &mibrsp); + if (values && (values[0].type == SLSI_MIB_TYPE_UINT || values[0].type == SLSI_MIB_TYPE_INT)) + max_blacklist_size = values[0].u.uintValue; + nl_skb = cfg80211_vendor_cmd_alloc_reply_skb(sdev->wiphy, NLMSG_DEFAULT_SIZE); + if (!nl_skb) { + SLSI_ERR(sdev, "NO MEM for nl_skb!!!\n"); + ret = -ENOMEM; + goto exit_with_mib_resp; + } + + nlattr_start = nla_nest_start(nl_skb, NL80211_ATTR_VENDOR_DATA); + if (!nlattr_start) { + SLSI_ERR(sdev, "failed to put NL80211_ATTR_VENDOR_DATA\n"); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + ret = -EINVAL; + goto exit_with_mib_resp; + } + + ret = nla_put_u32(nl_skb, SLSI_NL_ATTR_MAX_BLACKLIST_SIZE, max_blacklist_size); + ret |= nla_put_u32(nl_skb, SLSI_NL_ATTR_MAX_WHITELIST_SIZE, max_whitelist_size); + if (ret) { + SLSI_ERR(sdev, "Error in nla_put*:%x\n", ret); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + goto exit_with_mib_resp; + } + + ret = cfg80211_vendor_cmd_reply(nl_skb); + if (ret) + SLSI_ERR(sdev, "cfg80211_vendor_cmd_reply failed :%d\n", ret); +exit_with_mib_resp: + kfree(mibrsp.data); + kfree(values); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return ret; +} + +static int slsi_set_roaming_state(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + int temp = 0; + int type = 0; + const struct nlattr *attr; + int ret = 0; + int roam_state = 0; + + if (!dev) { + SLSI_WARN_NODEV("net_dev is NULL\n"); + return -EINVAL; + } + + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_NL_ATTR_ROAM_STATE: + roam_state = nla_get_u8(attr); + break; + default: + SLSI_ERR_NODEV("Unknown attribute: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + SLSI_DBG1_NODEV(SLSI_GSCAN, "SUBCMD_SET_ROAMING_STATE roam_state = %d\n", roam_state); + ret = slsi_set_mib_roam(sdev, NULL, SLSI_PSID_UNIFI_ROAMING_ENABLED, roam_state); + if (ret < 0) + SLSI_ERR_NODEV("Failed to set roaming state\n"); + +exit: + return ret; +} + +char *slsi_get_roam_reason_str(int roam_reason) +{ + switch (roam_reason) { + case SLSI_WIFI_ROAMING_SEARCH_REASON_RESERVED: + return "WIFI_ROAMING_SEARCH_REASON_RESERVED"; + case SLSI_WIFI_ROAMING_SEARCH_REASON_LOW_RSSI: + return "WIFI_ROAMING_SEARCH_REASON_LOW_RSSI"; + case SLSI_WIFI_ROAMING_SEARCH_REASON_LINK_LOSS: + return "WIFI_ROAMING_SEARCH_REASON_LINK_LOSS"; + case SLSI_WIFI_ROAMING_SEARCH_REASON_BTM_REQ: + return "WIFI_ROAMING_SEARCH_REASON_BTM_REQ"; + case SLSI_WIFI_ROAMING_SEARCH_REASON_CU_TRIGGER: + return "WIFI_ROAMING_SEARCH_REASON_CU_TRIGGER"; + default: + return "UNKNOWN_REASON"; + } +} + +void slsi_rx_event_log_indication(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 event_id = 0; + u64 timestamp = 0; + u8 *tlv_data; + u32 roam_reason = 0, chan_utilisation = 0, btm_request_mode = 0, btm_response = 0, eapol_msg_type = 0; + u32 deauth_reason = 0, eapol_retry_count = 0, roam_rssi, status_code = 0; + u16 vendor_len, tag_id, tag_len, vtag_id, eapol_key_type = 0; + u32 tag_value, vtag_value, rssi_bits = 0; + int roam_rssi_val = 0; + __le16 *le16_ptr = NULL; + int tlv_buffer__len = fapi_get_datalen(skb), i = 0; + + SLSI_MUTEX_LOCK(sdev->logger_mutex); + event_id = fapi_get_s16(skb, u.mlme_event_log_ind.event); + timestamp = fapi_get_u64(skb, u.mlme_event_log_ind.timestamp); + tlv_data = fapi_get_data(skb); + + SLSI_DBG3(sdev, SLSI_GSCAN, + "slsi_rx_event_log_indication, event id = %d, len = %d\n", event_id, tlv_buffer__len); + +#ifdef CONFIG_SCSC_WIFILOGGER + SCSC_WLOG_FW_EVENT(WLOG_NORMAL, event_id, timestamp, fapi_get_data(skb), fapi_get_datalen(skb)); +#endif + while (i + 4 < tlv_buffer__len) { + le16_ptr = (__le16 *)&tlv_data[i]; + tag_id = le16_to_cpu(*le16_ptr); + le16_ptr = (__le16 *)&tlv_data[i + 2]; + tag_len = le16_to_cpu(*le16_ptr); + i += 4; + if (i + tag_len > tlv_buffer__len) { + SLSI_INFO(sdev, "Incorrect fapi bulk data\n"); + slsi_kfree_skb(skb); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return; + } + tag_value = slsi_convert_tlv_data_to_value(&tlv_data[i], tag_len); + switch (tag_id) { + case SLSI_WIFI_TAG_RSSI: + roam_rssi = tag_value; + while (roam_rssi) { + rssi_bits++; + roam_rssi >>= 1; + } + roam_rssi_val = ((1 << rssi_bits) - 1) ^ tag_value; + roam_rssi_val = -(roam_rssi_val + 1); + break; + case SLSI_WIFI_TAG_REASON_CODE: + deauth_reason = tag_value; + break; + case SLSI_WIFI_TAG_VENDOR_SPECIFIC: + vendor_len = tag_len - 2; + le16_ptr = (__le16 *)&tlv_data[i]; + vtag_id = le16_to_cpu(*le16_ptr); + vtag_value = slsi_convert_tlv_data_to_value(&tlv_data[i + 2], vendor_len); + switch (vtag_id) { + case SLSI_WIFI_TAG_VD_CHANNEL_UTILISATION: + chan_utilisation = vtag_value; + break; + case SLSI_WIFI_TAG_VD_ROAMING_REASON: + roam_reason = vtag_value; + break; + case SLSI_WIFI_TAG_VD_BTM_REQUEST_MODE: + btm_request_mode = vtag_value; + break; + case SLSI_WIFI_TAG_VD_BTM_RESPONSE_STATUS: + btm_response = vtag_value; + break; + case SLSI_WIFI_TAG_VD_RETRY_COUNT: + eapol_retry_count = vtag_value; + break; + case SLSI_WIFI_TAG_VD_EAPOL_KEY_TYPE: + eapol_key_type = vtag_value; + break; + } + break; + case SLSI_WIFI_TAG_EAPOL_MESSAGE_TYPE: + eapol_msg_type = tag_value; + break; + case SLSI_WIFI_TAG_STATUS: + status_code = tag_value; + break; + } + i += tag_len; + } + switch (event_id) { + case FAPI_EVENT_WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_START: + if (eapol_key_type == SLSI_WIFI_EAPOL_KEY_TYPE_GTK) + SLSI_INFO(sdev, "WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_START, Send GTK, G%d\n", eapol_msg_type); + else if (eapol_key_type == SLSI_WIFI_EAPOL_KEY_TYPE_PTK) + SLSI_INFO(sdev, "WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_START, Send 4way-H/S, M%d\n", + eapol_msg_type); + break; + case FAPI_EVENT_WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_STOP: + SLSI_INFO(sdev, "WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_STOP,Result Code:%d, Retry Count:%d\n", + status_code, eapol_retry_count); + break; + case FAPI_EVENT_WIFI_EVENT_FW_EAPOL_FRAME_RECEIVED: + if (eapol_key_type == SLSI_WIFI_EAPOL_KEY_TYPE_GTK) + SLSI_INFO(sdev, "WIFI_EVENT_FW_EAPOL_FRAME_RECEIVED, Received GTK, G%d\n", eapol_msg_type); + else if (eapol_key_type == SLSI_WIFI_EAPOL_KEY_TYPE_PTK) + SLSI_INFO(sdev, "WIFI_EVENT_FW_EAPOL_FRAME_RECEIVED, Received 4way-H/S, M%d\n", eapol_msg_type); + break; + case WIFI_EVENT_FW_BTM_FRAME_REQUEST: + SLSI_INFO(sdev, "WIFI_EVENT_FW_BTM_FRAME_REQUEST,Request Mode:%d\n", btm_request_mode); + break; + case WIFI_EVENT_FW_BTM_FRAME_RESPONSE: + SLSI_INFO(sdev, "WIFI_EVENT_FW_BTM_FRAME_RESPONSE,Status code:%d\n", btm_response); + break; + case FAPI_EVENT_WIFI_EVENT_ROAM_SEARCH_STARTED: + SLSI_INFO(sdev, "WIFI_EVENT_ROAM_SEARCH_STARTED, RSSI:%d, Deauth Reason:0x%04x, Channel Utilisation:%d," + "Roam Reason: %s\n", roam_rssi_val, deauth_reason, chan_utilisation, + slsi_get_roam_reason_str(roam_reason)); + break; + case FAPI_EVENT_WIFI_EVENT_FW_AUTH_STARTED: + SLSI_INFO(sdev, "WIFI_EVENT_FW_AUTH_STARTED\n"); + break; + case FAPI_EVENT_WIFI_EVENT_AUTH_COMPLETE: + SLSI_INFO(sdev, "WIFI_EVENT_AUTH_COMPLETE,Status code:%d\n", status_code); + break; + case FAPI_EVENT_WIFI_EVENT_ROAM_ASSOC_COMPLETE: + SLSI_INFO(sdev, "Received Association Response\n"); + break; + case WIFI_EVENT_FW_NR_FRAME_REQUEST: + SLSI_INFO(sdev, "Send Radio Measurement Frame (Neighbor Report Req)\n"); + break; + case WIFI_EVENT_FW_RM_FRAME_RESPONSE: + SLSI_INFO(sdev, "Received Radio Measurement Frame (Radio Measurement Rep)\n"); + break; + } + + slsi_kfree_skb(skb); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); +} + +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING +static void slsi_on_ring_buffer_data(char *ring_name, char *buffer, int buffer_size, + struct scsc_wifi_ring_buffer_status *buffer_status, void *ctx) +{ + struct sk_buff *skb; + int event_id = SLSI_NL80211_LOGGER_RING_EVENT; + struct slsi_dev *sdev = ctx; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, buffer_size, event_id, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, buffer_size, event_id, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for vendor event: %d\n", event_id); + return; + } + + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_STATUS, sizeof(*buffer_status), buffer_status) || + nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_DATA, buffer_size, buffer)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + return; + } + cfg80211_vendor_event(skb, GFP_KERNEL); +} + +static void slsi_on_alert(char *buffer, int buffer_size, int err_code, void *ctx) +{ + struct sk_buff *skb; + int event_id = SLSI_NL80211_LOGGER_FW_DUMP_EVENT; + struct slsi_dev *sdev = ctx; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, buffer_size, event_id, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, buffer_size, event_id, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for vendor event: %d\n", event_id); + goto exit; + } + + if (nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_LEN, buffer_size) || + nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_DATA, buffer_size, buffer)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + goto exit; + } + cfg80211_vendor_event(skb, GFP_KERNEL); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); +} + +static void slsi_on_firmware_memory_dump(char *buffer, int buffer_size, void *ctx) +{ + SLSI_ERR_NODEV("slsi_on_firmware_memory_dump\n"); + kfree(mem_dump_buffer); + mem_dump_buffer = NULL; + mem_dump_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!mem_dump_buffer) { + SLSI_ERR_NODEV("Failed to allocate memory for mem_dump_buffer\n"); + return; + } + mem_dump_buffer_size = buffer_size; + memcpy(mem_dump_buffer, buffer, mem_dump_buffer_size); +} + +static void slsi_on_driver_memory_dump(char *buffer, int buffer_size, void *ctx) +{ + SLSI_ERR_NODEV("slsi_on_driver_memory_dump\n"); + kfree(mem_dump_buffer); + mem_dump_buffer = NULL; + mem_dump_buffer_size = buffer_size; + mem_dump_buffer = kmalloc(mem_dump_buffer_size, GFP_KERNEL); + if (!mem_dump_buffer) { + SLSI_ERR_NODEV("Failed to allocate memory for mem_dump_buffer\n"); + return; + } + memcpy(mem_dump_buffer, buffer, mem_dump_buffer_size); +} + +static int slsi_enable_logging(struct slsi_dev *sdev, bool enable) +{ + int status = 0; +#ifdef ENABLE_WIFI_LOGGER_MIB_WRITE + struct slsi_mib_data mib_data = { 0, NULL }; + + SLSI_DBG3(sdev, SLSI_GSCAN, "Value of enable is : %d\n", enable); + status = slsi_mib_encode_bool(&mib_data, SLSI_PSID_UNIFI_LOGGER_ENABLED, enable, 0); + if (status != SLSI_MIB_STATUS_SUCCESS) { + SLSI_ERR(sdev, "slsi_enable_logging failed: no mem for MIB\n"); + status = -ENOMEM; + goto exit; + } + status = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + kfree(mib_data.data); + if (status) + SLSI_ERR(sdev, "Err setting unifiLoggerEnabled MIB. error = %d\n", status); + +exit: + return status; +#else + SLSI_DBG3(sdev, SLSI_GSCAN, "UnifiLoggerEnabled MIB write disabled\n"); + return status; +#endif +} + +static int slsi_start_logging(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + char ring_name[32] = {0}; + int verbose_level = 0; + int ring_flags = 0; + int max_interval_sec = 0; + int min_data_size = 0; + const struct nlattr *attr; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_NAME: + strncpy(ring_name, nla_data(attr), MIN(sizeof(ring_name) - 1, nla_len(attr))); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_VERBOSE_LEVEL: + verbose_level = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_FLAGS: + ring_flags = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_LOG_MAX_INTERVAL: + max_interval_sec = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_LOG_MIN_DATA_SIZE: + min_data_size = nla_get_u32(attr); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + ret = scsc_wifi_set_log_handler(slsi_on_ring_buffer_data, sdev); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_set_log_handler failed ret: %d\n", ret); + goto exit; + } + ret = scsc_wifi_set_alert_handler(slsi_on_alert, sdev); + if (ret < 0) { + SLSI_ERR(sdev, "Warning : scsc_wifi_set_alert_handler failed ret: %d\n", ret); + } + ret = slsi_enable_logging(sdev, 1); + if (ret < 0) { + SLSI_ERR(sdev, "slsi_enable_logging for enable = 1, failed ret: %d\n", ret); + goto exit_with_reset_alert_handler; + } + ret = scsc_wifi_start_logging(verbose_level, ring_flags, max_interval_sec, min_data_size, ring_name); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_start_logging failed ret: %d\n", ret); + goto exit_with_disable_logging; + } else { + goto exit; + } +exit_with_disable_logging: + ret = slsi_enable_logging(sdev, 0); + if (ret < 0) + SLSI_ERR(sdev, "slsi_enable_logging for enable = 0, failed ret: %d\n", ret); +exit_with_reset_alert_handler: + ret = scsc_wifi_reset_alert_handler(); + if (ret < 0) + SLSI_ERR(sdev, "Warning : scsc_wifi_reset_alert_handler failed ret: %d\n", ret); + ret = scsc_wifi_reset_log_handler(); + if (ret < 0) + SLSI_ERR(sdev, "scsc_wifi_reset_log_handler failed ret: %d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_reset_logging(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + ret = slsi_enable_logging(sdev, 0); + if (ret < 0) + SLSI_ERR(sdev, "slsi_enable_logging for enable = 0, failed ret: %d\n", ret); + ret = scsc_wifi_reset_log_handler(); + if (ret < 0) + SLSI_ERR(sdev, "scsc_wifi_reset_log_handler failed ret: %d\n", ret); + ret = scsc_wifi_reset_alert_handler(); + if (ret < 0) + SLSI_ERR(sdev, "Warning : scsc_wifi_reset_alert_handler failed ret: %d\n", ret); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_trigger_fw_mem_dump(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + struct sk_buff *skb = NULL; + int length = 100; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + + ret = scsc_wifi_get_firmware_memory_dump(slsi_on_firmware_memory_dump, sdev); + if (ret) { + SLSI_ERR(sdev, "scsc_wifi_get_firmware_memory_dump failed : %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, length); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + if (nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_LEN, mem_dump_buffer_size)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); + +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_fw_mem_dump(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + int buf_len = 0; + void __user *user_buf = NULL; + const struct nlattr *attr; + struct sk_buff *skb; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_LEN: + buf_len = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_DATA: + user_buf = (void __user *)(unsigned long)nla_get_u64(attr); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return -EINVAL; + } + } + if (buf_len > 0 && user_buf) { + ret = copy_to_user(user_buf, mem_dump_buffer, buf_len); + if (ret) { + SLSI_ERR(sdev, "failed to copy memdump into user buffer : %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + /* Indicate the memdump is successfully copied */ + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_DATA, sizeof(ret), &ret)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); + } + +exit: + kfree(mem_dump_buffer); + mem_dump_buffer = NULL; + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_trigger_driver_mem_dump(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + struct sk_buff *skb = NULL; + int length = 100; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + + ret = scsc_wifi_get_driver_memory_dump(slsi_on_driver_memory_dump, sdev); + if (ret) { + SLSI_ERR(sdev, "scsc_wifi_get_driver_memory_dump failed : %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, length); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + if (nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_DUMP_LEN, mem_dump_buffer_size)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); + +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_driver_mem_dump(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + int buf_len = 0; + void __user *user_buf = NULL; + const struct nlattr *attr; + struct sk_buff *skb; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_DUMP_LEN: + buf_len = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_DUMP_DATA: + user_buf = (void __user *)(unsigned long)nla_get_u64(attr); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return -EINVAL; + } + } + if (buf_len > 0 && user_buf) { + ret = copy_to_user(user_buf, mem_dump_buffer, buf_len); + if (ret) { + SLSI_ERR(sdev, "failed to copy memdump into user buffer : %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 100); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + /* Indicate the memdump is successfully copied */ + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_DUMP_DATA, sizeof(ret), &ret)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); + } + +exit: + kfree(mem_dump_buffer); + mem_dump_buffer = NULL; + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_version(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + int buffer_size = 1024; + bool log_version = false; + char *buffer; + const struct nlattr *attr; + + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + SLSI_ERR(sdev, "No mem. Size:%d\n", buffer_size); + return -ENOMEM; + } + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_VERSION: + log_version = true; + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_VERSION: + log_version = false; + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + if (log_version) + ret = scsc_wifi_get_driver_version(buffer, buffer_size); + else + ret = scsc_wifi_get_firmware_version(buffer, buffer_size); + + if (ret < 0) { + SLSI_ERR(sdev, "failed to get the version %d\n", ret); + goto exit; + } + + ret = slsi_vendor_cmd_reply(wiphy, buffer, strlen(buffer)); +exit: + kfree(buffer); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_ring_buffers_status(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int num_rings = 10; + struct sk_buff *skb; + struct scsc_wifi_ring_buffer_status status[num_rings]; + + SLSI_DBG1(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + memset(status, 0, sizeof(struct scsc_wifi_ring_buffer_status) * num_rings); + ret = scsc_wifi_get_ring_buffers_status(&num_rings, status); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_get_ring_buffers_status failed ret:%d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 700); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + /* Indicate that the ring count and ring buffers status is successfully copied */ + if (nla_put_u8(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_NUM, num_rings) || + nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_STATUS, sizeof(status[0]) * num_rings, status)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_ring_data(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + char ring_name[32] = {0}; + const struct nlattr *attr; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_NAME: + strncpy(ring_name, nla_data(attr), MIN(sizeof(ring_name) - 1, nla_len(attr))); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + goto exit; + } + } + + ret = scsc_wifi_get_ring_data(ring_name); + if (ret < 0) + SLSI_ERR(sdev, "trigger_get_data failed ret:%d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_logger_supported_feature_set(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + u32 supported_features = 0; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + ret = scsc_wifi_get_logger_supported_feature_set(&supported_features); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_get_logger_supported_feature_set failed ret:%d\n", ret); + goto exit; + } + ret = slsi_vendor_cmd_reply(wiphy, &supported_features, sizeof(supported_features)); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_start_pkt_fate_monitoring(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; +#ifdef ENABLE_WIFI_LOGGER_MIB_WRITE + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct slsi_mib_data mib_data = { 0, NULL }; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + ret = slsi_mib_encode_bool(&mib_data, SLSI_PSID_UNIFI_TX_DATA_CONFIRM, 1, 0); + if (ret != SLSI_MIB_STATUS_SUCCESS) { + SLSI_ERR(sdev, "Failed to set UnifiTxDataConfirm MIB : no mem for MIB\n"); + ret = -ENOMEM; + goto exit; + } + + ret = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + + if (ret) { + SLSI_ERR(sdev, "Err setting UnifiTxDataConfirm MIB. error = %d\n", ret); + goto exit; + } + + ret = scsc_wifi_start_pkt_fate_monitoring(); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_start_pkt_fate_monitoring failed, ret=%d\n", ret); + + // Resetting the SLSI_PSID_UNIFI_TX_DATA_CONFIRM mib back to 0. + mib_data.dataLength = 0; + mib_data.data = NULL; + ret = slsi_mib_encode_bool(&mib_data, SLSI_PSID_UNIFI_TX_DATA_CONFIRM, 1, 0); + if (ret != SLSI_MIB_STATUS_SUCCESS) { + SLSI_ERR(sdev, "Failed to set UnifiTxDataConfirm MIB : no mem for MIB\n"); + ret = -ENOMEM; + goto exit; + } + + ret = slsi_mlme_set(sdev, NULL, mib_data.data, mib_data.dataLength); + + if (ret) { + SLSI_ERR(sdev, "Err setting UnifiTxDataConfirm MIB. error = %d\n", ret); + goto exit; + } + } +exit: + kfree(mib_data.data); + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +#else + SLSI_ERR_NODEV("slsi_start_pkt_fate_monitoring : UnifiTxDataConfirm MIB write disabled\n"); + return ret; +#endif +} + +static int slsi_get_tx_pkt_fates(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + void __user *user_buf = NULL; + u32 req_count = 0; + size_t provided_count = 0; + struct sk_buff *skb; + const struct nlattr *attr; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_NUM: + req_count = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_DATA: + user_buf = (void __user *)(unsigned long)nla_get_u64(attr); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + ret = scsc_wifi_get_tx_pkt_fates(user_buf, req_count, &provided_count); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_get_tx_pkt_fates failed ret: %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 200); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_NUM, sizeof(provided_count), &provided_count)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_rx_pkt_fates(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + int ret = 0; + int temp = 0; + int type = 0; + void __user *user_buf = NULL; + u32 req_count = 0; + size_t provided_count = 0; + struct sk_buff *skb; + const struct nlattr *attr; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_NUM: + req_count = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_DATA: + user_buf = (void __user *)(unsigned long)nla_get_u64(attr); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + ret = scsc_wifi_get_rx_pkt_fates(user_buf, req_count, &provided_count); + if (ret < 0) { + SLSI_ERR(sdev, "scsc_wifi_get_rx_pkt_fates failed ret: %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 200); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_NUM, sizeof(provided_count), &provided_count)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +static int slsi_get_wake_reason_stats(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct slsi_wlan_driver_wake_reason_cnt wake_reason_count; + int ret = 0; + int temp = 0; + int type = 0; + const struct nlattr *attr; + struct sk_buff *skb; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + // Initialising the wake_reason_count structure values to 0. + memset(&wake_reason_count, 0, sizeof(struct slsi_wlan_driver_wake_reason_cnt)); + + SLSI_MUTEX_LOCK(sdev->logger_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_CMD_EVENT_WAKE_CNT_SZ: + wake_reason_count.cmd_event_wake_cnt_sz = nla_get_u32(attr); + break; + case SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_DRIVER_FW_LOCAL_WAKE_CNT_SZ: + wake_reason_count.driver_fw_local_wake_cnt_sz = nla_get_u32(attr); + break; + default: + SLSI_ERR(sdev, "Unknown type: %d\n", type); + ret = -EINVAL; + goto exit; + } + } + + if (ret < 0) { + SLSI_ERR(sdev, "Failed to get wake reason stats : %d\n", ret); + goto exit; + } + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 700); + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for Vendor event\n"); + ret = -ENOMEM; + goto exit; + } + + if (nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_TOTAL_CMD_EVENT_WAKE, + wake_reason_count.total_cmd_event_wake)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_CMD_EVENT_WAKE_CNT_PTR, 0, + wake_reason_count.cmd_event_wake_cnt)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + if (nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_TOTAL_DRIVER_FW_LOCAL_WAKE, + wake_reason_count.total_driver_fw_local_wake)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + if (nla_put(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_DRIVER_FW_LOCAL_WAKE_CNT_PTR, 0, + wake_reason_count.driver_fw_local_wake_cnt)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + if (nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_TOTAL_RX_DATA_WAKE, + wake_reason_count.total_rx_data_wake) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_RX_UNICAST_CNT, + wake_reason_count.rx_wake_details.rx_unicast_cnt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_RX_MULTICAST_CNT, + wake_reason_count.rx_wake_details.rx_multicast_cnt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_RX_BROADCAST_CNT, + wake_reason_count.rx_wake_details.rx_broadcast_cnt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP_PKT, + wake_reason_count.rx_wake_pkt_classification_info.icmp_pkt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_PKT, + wake_reason_count.rx_wake_pkt_classification_info.icmp6_pkt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_RA, + wake_reason_count.rx_wake_pkt_classification_info.icmp6_ra) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_NA, + wake_reason_count.rx_wake_pkt_classification_info.icmp6_na) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_NS, + wake_reason_count.rx_wake_pkt_classification_info.icmp6_ns) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP4_RX_MULTICAST_CNT, + wake_reason_count.rx_multicast_wake_pkt_info.ipv4_rx_multicast_addr_cnt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_RX_MULTICAST_CNT, + wake_reason_count.rx_multicast_wake_pkt_info.ipv6_rx_multicast_addr_cnt) || + nla_put_u32(skb, SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_OTHER_RX_MULTICAST_CNT, + wake_reason_count.rx_multicast_wake_pkt_info.other_rx_multicast_addr_cnt)) { + SLSI_ERR_NODEV("Failed nla_put\n"); + slsi_kfree_skb(skb); + ret = -EINVAL; + goto exit; + } + ret = cfg80211_vendor_cmd_reply(skb); + + if (ret) + SLSI_ERR(sdev, "Vendor Command reply failed ret:%d\n", ret); +exit: + SLSI_MUTEX_UNLOCK(sdev->logger_mutex); + return ret; +} + +#endif /* CONFIG_SCSC_WLAN_ENHANCED_LOGGING */ + +static int slsi_acs_validate_width_hw_mode(struct slsi_acs_request *request) +{ + if (request->hw_mode != SLSI_ACS_MODE_IEEE80211A && request->hw_mode != SLSI_ACS_MODE_IEEE80211B && + request->hw_mode != SLSI_ACS_MODE_IEEE80211G) + return -EINVAL; + if (request->ch_width != 20 && request->ch_width != 40 && request->ch_width != 80) + return -EINVAL; + return 0; +} + +static int slsi_acs_init(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = wdev->netdev; + struct netdev_vif *ndev_vif; + struct slsi_acs_request *request; + int temp; + int type; + const struct nlattr *attr; + int r = 0; + u32 *freq_list = NULL; + int freq_list_len = 0; + + SLSI_INFO(sdev, "SUBCMD_ACS_INIT Received\n"); + if (slsi_is_test_mode_enabled()) { + SLSI_ERR(sdev, "Not supported in WlanLite mode\n"); + return -EOPNOTSUPP; + } + if (wdev->iftype != NL80211_IFTYPE_AP) { + SLSI_ERR(sdev, "Invalid iftype: %d\n", wdev->iftype); + return -EINVAL; + } + if (!dev) { + SLSI_ERR(sdev, "Dev not found!\n"); + return -ENODEV; + } + request = kcalloc(1, sizeof(*request), GFP_KERNEL); + if (!request) { + SLSI_ERR(sdev, "No memory for request!"); + return -ENOMEM; + } + ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + nla_for_each_attr(attr, data, len, temp) { + type = nla_type(attr); + switch (type) { + case SLSI_ACS_ATTR_HW_MODE: + { + request->hw_mode = nla_get_u8(attr); + SLSI_INFO(sdev, "ACS hw mode: %d\n", request->hw_mode); + break; + } + case SLSI_ACS_ATTR_CHWIDTH: + { + request->ch_width = nla_get_u16(attr); + SLSI_INFO(sdev, "ACS ch_width: %d\n", request->ch_width); + break; + } + case SLSI_ACS_ATTR_FREQ_LIST: + { + if (freq_list) /* This check is to avoid Prevent Issue */ + break; + + freq_list = kmalloc(nla_len(attr), GFP_KERNEL); + if (!freq_list) { + SLSI_ERR(sdev, "No memory for frequency list!"); + kfree(request); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return -ENOMEM; + } + memcpy(freq_list, nla_data(attr), nla_len(attr)); + freq_list_len = nla_len(attr) / sizeof(u32); + SLSI_INFO(sdev, "ACS freq_list_len: %d\n", freq_list_len); + break; + } + default: + if (type > SLSI_ACS_ATTR_MAX) + SLSI_ERR(sdev, "Invalid type : %d\n", type); + break; + } + } + + r = slsi_acs_validate_width_hw_mode(request); + if (r == 0 && freq_list_len) { + struct ieee80211_channel *channels[freq_list_len]; + struct slsi_acs_chan_info ch_info[MAX_CHAN_VALUE_ACS]; + struct slsi_acs_selected_channels acs_selected_channels; + int i = 0, num_channels = 0; + int idx; + u32 chan_flags = (IEEE80211_CHAN_INDOOR_ONLY | IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_DISABLED | +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 10, 13) + IEEE80211_CHAN_PASSIVE_SCAN +#else + IEEE80211_CHAN_NO_IR +#endif + ); + + memset(channels, 0, sizeof(channels)); + memset(&ch_info, 0, sizeof(ch_info)); + for (i = 0; i < freq_list_len; i++) { + channels[num_channels] = ieee80211_get_channel(wiphy, freq_list[i]); + if (!channels[num_channels]) { + SLSI_INFO(sdev, "Ignore invalid freq:%d in freq list\n", freq_list[i]); + } else if (channels[num_channels]->flags & chan_flags) { + SLSI_INFO(sdev, "Skip invalid channel:%d for ACS\n", channels[num_channels]->hw_value); + } else { + idx = slsi_find_chan_idx(channels[num_channels]->hw_value, request->hw_mode); + ch_info[idx].chan = channels[num_channels]->hw_value; + num_channels++; + } + } + + if (num_channels == 1) { + memset(&acs_selected_channels, 0, sizeof(acs_selected_channels)); + acs_selected_channels.ch_width = 20; + acs_selected_channels.hw_mode = request->hw_mode; + acs_selected_channels.pri_channel = channels[0]->hw_value; + r = slsi_send_acs_event(sdev, acs_selected_channels); + sdev->acs_channel_switched = true; + kfree(freq_list); + kfree(request); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; + } + + if (request->hw_mode == SLSI_ACS_MODE_IEEE80211A) + request->ch_list_len = 25; + else + request->ch_list_len = 14; + memcpy(&request->acs_chan_info[0], &ch_info[0], sizeof(ch_info)); + ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request = request; + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = false; + r = slsi_mlme_add_scan(sdev, + dev, + FAPI_SCANTYPE_AP_AUTO_CHANNEL_SELECTION, + FAPI_REPORTMODE_REAL_TIME, + 0, /* n_ssids */ + NULL, /* ssids */ + num_channels, + channels, + NULL, + NULL, /* ie */ + 0, /* ie_len */ + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan); + } else { + SLSI_ERR(sdev, "Invalid freq_list len:%d or ch_width:%d or hw_mode:%d\n", freq_list_len, + request->ch_width, request->hw_mode); + r = -EINVAL; + kfree(request); + } + kfree(freq_list); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return r; +} + +static const struct nl80211_vendor_cmd_info slsi_vendor_events[] = { + /**********Deprecated now due to fapi updates.Do not remove*/ + { OUI_GOOGLE, SLSI_NL80211_SIGNIFICANT_CHANGE_EVENT }, + { OUI_GOOGLE, SLSI_NL80211_HOTLIST_AP_FOUND_EVENT }, + /******************************************/ + { OUI_GOOGLE, SLSI_NL80211_SCAN_RESULTS_AVAILABLE_EVENT }, + { OUI_GOOGLE, SLSI_NL80211_FULL_SCAN_RESULT_EVENT }, + { OUI_GOOGLE, SLSI_NL80211_SCAN_EVENT }, + /**********Deprecated now due to fapi updates.Do not remove*/ + { OUI_GOOGLE, SLSI_NL80211_HOTLIST_AP_LOST_EVENT }, + /******************************************/ +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD + { OUI_SAMSUNG, SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH }, +#endif + { OUI_SAMSUNG, SLSI_NL80211_VENDOR_HANGED_EVENT }, + { OUI_GOOGLE, SLSI_NL80211_EPNO_EVENT }, + { OUI_GOOGLE, SLSI_NL80211_HOTSPOT_MATCH }, + { OUI_GOOGLE, SLSI_NL80211_RSSI_REPORT_EVENT}, +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + { OUI_GOOGLE, SLSI_NL80211_LOGGER_RING_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_LOGGER_FW_DUMP_EVENT}, +#endif + { OUI_GOOGLE, SLSI_NL80211_NAN_RESPONSE_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_PUBLISH_TERMINATED_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_MATCH_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_MATCH_EXPIRED_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_SUBSCRIBE_TERMINATED_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_FOLLOWUP_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_NAN_DISABLED_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_RTT_RESULT_EVENT}, + { OUI_GOOGLE, SLSI_NL80211_RTT_COMPLETE_EVENT}, + { OUI_SAMSUNG, SLSI_NL80211_VENDOR_ACS_EVENT}, + { OUI_SAMSUNG, SLSI_NL80211_VENDOR_FORWARD_BEACON}, + { OUI_SAMSUNG, SLSI_NL80211_VENDOR_FORWARD_BEACON_ABORT} +}; + +static const struct wiphy_vendor_command slsi_vendor_cmd[] = { + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_CAPABILITIES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_gscan_get_capabilities + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_VALID_CHANNELS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_gscan_get_valid_channel + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_ADD_GSCAN + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_gscan_add + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_DEL_GSCAN + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_gscan_del + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_SCAN_RESULTS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_gscan_get_scan_results + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_GSCAN_OUI + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_gscan_set_oui + }, +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD + { + { + .vendor_id = OUI_SAMSUNG, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_key_mgmt_set_pmk + }, +#endif + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_BSSID_BLACKLIST + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_set_bssid_blacklist + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_START_KEEP_ALIVE_OFFLOAD + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_start_keepalive_offload + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_STOP_KEEP_ALIVE_OFFLOAD + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_stop_keepalive_offload + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_EPNO_LIST + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_set_epno_ssid + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_HS_LIST + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_set_hs_params + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_RESET_HS_LIST + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_reset_hs_params + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_RSSI_MONITOR + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_set_rssi_monitor + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_LSTATS_SUBCMD_SET_STATS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_lls_set_stats + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_LSTATS_SUBCMD_GET_STATS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_lls_get_stats + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_LSTATS_SUBCMD_CLEAR_STATS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_lls_clear_stats + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_FEATURE_SET + }, + .flags = 0, + .doit = slsi_get_feature_set + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_COUNTRY_CODE + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_set_country_code + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_CONFIGURE_ND_OFFLOAD + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_configure_nd_offload + }, +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_START_LOGGING + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_start_logging + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_RESET_LOGGING + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_reset_logging + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_TRIGGER_FW_MEM_DUMP + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_trigger_fw_mem_dump + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_FW_MEM_DUMP + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_fw_mem_dump + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_TRIGGER_DRIVER_MEM_DUMP + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_trigger_driver_mem_dump + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_DRIVER_MEM_DUMP + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_driver_mem_dump + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_VERSION + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_version + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_RING_STATUS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_ring_buffers_status + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_RING_DATA + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_ring_data + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_FEATURE + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_logger_supported_feature_set + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_START_PKT_FATE_MONITORING + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_start_pkt_fate_monitoring + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_TX_PKT_FATES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_tx_pkt_fates + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_RX_PKT_FATES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_rx_pkt_fates + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_WAKE_REASON_STATS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_wake_reason_stats + }, +#endif /* CONFIG_SCSC_WLAN_ENHANCED_LOGGING */ +#ifdef CONFIG_SCSC_WIFI_NAN_ENABLE + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_ENABLE + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_enable + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_DISABLE + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_disable + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_PUBLISH + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_publish + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_PUBLISHCANCEL + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_publish_cancel + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_SUBSCRIBE + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_subscribe + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_SUBSCRIBECANCEL + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_subscribe_cancel + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_TXFOLLOWUP + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_transmit_followup + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_CONFIG + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_set_config + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_NAN_CAPABILITIES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_nan_get_capabilities + }, +#endif + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_GET_ROAMING_CAPABILITIES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_get_roaming_capabilities + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_SET_ROAMING_STATE + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_set_roaming_state + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_RTT_GET_CAPABILITIES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_rtt_get_capabilities + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_RTT_RANGE_START + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_rtt_set_config + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_RTT_RANGE_CANCEL + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_rtt_cancel_config + }, + { + { + .vendor_id = OUI_SAMSUNG, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_ACS_INIT + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_acs_init + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_APF_GET_CAPABILITIES + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_apf_get_capabilities + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_APF_SET_FILTER + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_apf_set_filter + }, + { + { + .vendor_id = OUI_GOOGLE, + .subcmd = SLSI_NL80211_VENDOR_SUBCMD_APF_READ_FILTER + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = slsi_apf_read_filter + } +}; + +void slsi_nl80211_vendor_deinit(struct slsi_dev *sdev) +{ + SLSI_DBG2(sdev, SLSI_GSCAN, "De-initialise vendor command and events\n"); + sdev->wiphy->vendor_commands = NULL; + sdev->wiphy->n_vendor_commands = 0; + sdev->wiphy->vendor_events = NULL; + sdev->wiphy->n_vendor_events = 0; + + SLSI_DBG2(sdev, SLSI_GSCAN, "Gscan cleanup\n"); + slsi_gscan_flush_scan_results(sdev); + +} + +void slsi_nl80211_vendor_init(struct slsi_dev *sdev) +{ + int i; + + SLSI_DBG2(sdev, SLSI_GSCAN, "Init vendor command and events\n"); + + sdev->wiphy->vendor_commands = slsi_vendor_cmd; + sdev->wiphy->n_vendor_commands = ARRAY_SIZE(slsi_vendor_cmd); + sdev->wiphy->vendor_events = slsi_vendor_events; + sdev->wiphy->n_vendor_events = ARRAY_SIZE(slsi_vendor_events); + + for (i = 0; i < SLSI_GSCAN_MAX_BUCKETS; i++) + sdev->bucket[i].scan_id = (SLSI_GSCAN_SCAN_ID_START + i); + + for (i = 0; i < SLSI_GSCAN_HASH_TABLE_SIZE; i++) + sdev->gscan_hash_table[i] = NULL; + + INIT_LIST_HEAD(&sdev->hotlist_results); +} diff --git a/drivers/net/wireless/scsc/nl80211_vendor.h b/drivers/net/wireless/scsc/nl80211_vendor.h new file mode 100755 index 000000000000..125bb7cfa5c1 --- /dev/null +++ b/drivers/net/wireless/scsc/nl80211_vendor.h @@ -0,0 +1,1030 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "nl80211_vendor_nan.h" + +#ifndef __SLSI_NL80211_VENDOR_H_ +#define __SLSI_NL80211_VENDOR_H_ + +#define OUI_GOOGLE 0x001A11 +#define OUI_SAMSUNG 0x0000f0 +#define SLSI_NL80211_GSCAN_SUBCMD_RANGE_START 0x1000 +#define SLSI_NL80211_GSCAN_EVENT_RANGE_START 0x01 +#define SLSI_NL80211_LOGGING_SUBCMD_RANGE_START 0x1400 +#define SLSI_NL80211_NAN_SUBCMD_RANGE_START 0x1500 +#define SLSI_NL80211_RTT_SUBCMD_RANGE_START 0x1100 +#define SLSI_NL80211_APF_SUBCMD_RANGE_START 0x1600 +#define SLSI_GSCAN_SCAN_ID_START 0x410 +#define SLSI_GSCAN_SCAN_ID_END 0x500 + +#define SLSI_GSCAN_MAX_BUCKETS (8) +#define SLSI_GSCAN_MAX_CHANNELS (16) /* As per gscan.h */ +#define SLSI_GSCAN_MAX_HOTLIST_APS (64) +#define SLSI_GSCAN_MAX_BUCKETS_PER_GSCAN (SLSI_GSCAN_MAX_BUCKETS) +#define SLSI_GSCAN_MAX_SCAN_CACHE_SIZE (12000) +#define SLSI_GSCAN_MAX_AP_CACHE_PER_SCAN (16) +#define SLSI_GSCAN_MAX_SCAN_REPORTING_THRESHOLD (100) +#define SLSI_GSCAN_MAX_SIGNIFICANT_CHANGE_APS (64) +#define SLSI_GSCAN_MAX_EPNO_SSIDS (32) +#define SLSI_GSCAN_MAX_EPNO_HS2_PARAM (8) /* Framework is not using this. Tune when needed */ + +#define SLSI_REPORT_EVENTS_NONE (0) +#define SLSI_REPORT_EVENTS_EACH_SCAN (1) +#define SLSI_REPORT_EVENTS_FULL_RESULTS (2) +#define SLSI_REPORT_EVENTS_NO_BATCH (4) + +#define SLSI_NL_ATTRIBUTE_U32_LEN (NLA_HDRLEN + 4) +#define SLSI_NL_ATTRIBUTE_COUNTRY_CODE (4) +#define SLSI_NL_VENDOR_ID_OVERHEAD SLSI_NL_ATTRIBUTE_U32_LEN +#define SLSI_NL_VENDOR_SUBCMD_OVERHEAD SLSI_NL_ATTRIBUTE_U32_LEN +#define SLSI_NL_VENDOR_DATA_OVERHEAD (NLA_HDRLEN) + +#define SLSI_NL_VENDOR_REPLY_OVERHEAD (SLSI_NL_VENDOR_ID_OVERHEAD + \ + SLSI_NL_VENDOR_SUBCMD_OVERHEAD + \ + SLSI_NL_VENDOR_DATA_OVERHEAD) + +#define SLSI_GSCAN_RTT_UNSPECIFIED (-1) +#define SLSI_GSCAN_HASH_TABLE_SIZE (32) +#define SLSI_GSCAN_HASH_KEY_MASK (0x1F) +#define SLSI_GSCAN_GET_HASH_KEY(_key) (_key & SLSI_GSCAN_HASH_KEY_MASK) + +#define SLSI_KEEP_SCAN_RESULT (0) +#define SLSI_DISCARD_SCAN_RESULT (1) + +#define SLSI_GSCAN_MAX_BSSID_PER_IE (20) + +#define SLSI_LLS_CAPABILITY_QOS 0x00000001 /* set for QOS association */ +#define SLSI_LLS_CAPABILITY_PROTECTED 0x00000002 /* set for protected association (802.11 beacon frame control protected bit set)*/ +#define SLSI_LLS_CAPABILITY_INTERWORKING 0x00000004 /* set if 802.11 Extended Capabilities element interworking bit is set*/ +#define SLSI_LLS_CAPABILITY_HS20 0x00000008 /* set for HS20 association*/ +#define SLSI_LLS_CAPABILITY_SSID_UTF8 0x00000010 /* set is 802.11 Extended Capabilities element UTF-8 SSID bit is set*/ +#define SLSI_LLS_CAPABILITY_COUNTRY 0x00000020 /* set is 802.11 Country Element is present*/ + +#define TIMESPEC_TO_US(ts) (((u64)(ts).tv_sec * USEC_PER_SEC) + (ts).tv_nsec / NSEC_PER_USEC) + +/* Feature enums */ +#define SLSI_WIFI_HAL_FEATURE_INFRA 0x000001 /* Basic infrastructure mode */ +#define SLSI_WIFI_HAL_FEATURE_INFRA_5G 0x000002 /* Support for 5 GHz Band */ +#define SLSI_WIFI_HAL_FEATURE_HOTSPOT 0x000004 /* Support for GAS/ANQP */ +#define SLSI_WIFI_HAL_FEATURE_P2P 0x000008 /* Wifi-Direct */ +#define SLSI_WIFI_HAL_FEATURE_SOFT_AP 0x000010 /* Soft AP */ +#define SLSI_WIFI_HAL_FEATURE_GSCAN 0x000020 /* Google-Scan APIs */ +#define SLSI_WIFI_HAL_FEATURE_NAN 0x000040 /* Neighbor Awareness Networking */ +#define SLSI_WIFI_HAL_FEATURE_D2D_RTT 0x000080 /* Device-to-device RTT */ +#define SLSI_WIFI_HAL_FEATURE_D2AP_RTT 0x000100 /* Device-to-AP RTT */ +#define SLSI_WIFI_HAL_FEATURE_BATCH_SCAN 0x000200 /* Batched Scan (legacy) */ +#define SLSI_WIFI_HAL_FEATURE_PNO 0x000400 /* Preferred network offload */ +#define SLSI_WIFI_HAL_FEATURE_ADDITIONAL_STA 0x000800 /* Support for two STAs */ +#define SLSI_WIFI_HAL_FEATURE_TDLS 0x001000 /* Tunnel directed link setup */ +#define SLSI_WIFI_HAL_FEATURE_TDLS_OFFCHANNEL 0x002000 /* Support for TDLS off channel */ +#define SLSI_WIFI_HAL_FEATURE_EPR 0x004000 /* Enhanced power reporting */ +#define SLSI_WIFI_HAL_FEATURE_AP_STA 0x008000 /* Support for AP STA Concurrency */ +#define SLSI_WIFI_HAL_FEATURE_LINK_LAYER_STATS 0x010000 /* Link layer stats collection */ +#define SLSI_WIFI_HAL_FEATURE_LOGGER 0x020000 /* WiFi Logger */ +#define SLSI_WIFI_HAL_FEATURE_HAL_EPNO 0x040000 /* WiFi PNO enhanced */ +#define SLSI_WIFI_HAL_FEATURE_RSSI_MONITOR 0x080000 /* RSSI Monitor */ +#define SLSI_WIFI_HAL_FEATURE_MKEEP_ALIVE 0x100000 /* WiFi mkeep_alive */ +#define SLSI_WIFI_HAL_FEATURE_CONTROL_ROAMING 0x800000 /* Enable/Disable firmware roaming macro */ + +enum slsi_wifi_attr { + SLSI_NL_ATTRIBUTE_ND_OFFLOAD_VALUE = 0, + SLSI_NL_ATTRIBUTE_PNO_RANDOM_MAC_OUI +}; + +enum SLSI_APF_ATTRIBUTES { + SLSI_APF_ATTR_VERSION = 0, + SLSI_APF_ATTR_MAX_LEN, + SLSI_APF_ATTR_PROGRAM, + SLSI_APF_ATTR_PROGRAM_LEN +}; + +enum SLSI_ROAM_ATTRIBUTES { + SLSI_NL_ATTR_MAX_BLACKLIST_SIZE, + SLSI_NL_ATTR_MAX_WHITELIST_SIZE, + SLSI_NL_ATTR_ROAM_STATE +}; + +enum slsi_acs_attr_offload { + SLSI_ACS_ATTR_CHANNEL_INVALID = 0, + SLSI_ACS_ATTR_PRIMARY_CHANNEL, + SLSI_ACS_ATTR_SECONDARY_CHANNEL, + SLSI_ACS_ATTR_HW_MODE, + SLSI_ACS_ATTR_HT_ENABLED, + SLSI_ACS_ATTR_HT40_ENABLED, + SLSI_ACS_ATTR_VHT_ENABLED, + SLSI_ACS_ATTR_CHWIDTH, + SLSI_ACS_ATTR_CH_LIST, + SLSI_ACS_ATTR_VHT_SEG0_CENTER_CHANNEL, + SLSI_ACS_ATTR_VHT_SEG1_CENTER_CHANNEL, + SLSI_ACS_ATTR_FREQ_LIST, + /* keep last */ + SLSI_ACS_ATTR_AFTER_LAST, + SLSI_ACS_ATTR_MAX = + SLSI_ACS_ATTR_AFTER_LAST - 1 +}; + +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +enum slsi_wips_attr { + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_SSID = 0, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_BSSID, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_CHANNEL, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_BCN_INTERVAL, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_TIME_STAMP1, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_TIME_STAMP2, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_SYS_TIME, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_MAX +}; + +enum slsi_wips_abort_attr { + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_ABORT = 0, + SLSI_WLAN_VENDOR_ATTR_FORWARD_BEACON_ABORT_MAX, +}; + +enum slsi_forward_beacon_abort_reason { + SLSI_FORWARD_BEACON_ABORT_REASON_UNSPECIFIED = 0, + SLSI_FORWARD_BEACON_ABORT_REASON_SCANNING, + SLSI_FORWARD_BEACON_ABORT_REASON_ROAMING, + SLSI_FORWARD_BEACON_ABORT_REASON_SUSPENDED, + SLSI_FORWARD_BEACON_ABORT_REASON_OFFSET = 0x8007, +}; +#endif + +enum slsi_acs_hw_mode { + SLSI_ACS_MODE_IEEE80211B, + SLSI_ACS_MODE_IEEE80211G, + SLSI_ACS_MODE_IEEE80211A, + SLSI_ACS_MODE_IEEE80211AD, + SLSI_ACS_MODE_IEEE80211ANY, +}; + +enum GSCAN_ATTRIBUTE { + GSCAN_ATTRIBUTE_NUM_BUCKETS = 10, + GSCAN_ATTRIBUTE_BASE_PERIOD, + GSCAN_ATTRIBUTE_BUCKETS_BAND, + GSCAN_ATTRIBUTE_BUCKET_ID, + GSCAN_ATTRIBUTE_BUCKET_PERIOD, + GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS, + GSCAN_ATTRIBUTE_BUCKET_CHANNELS, + GSCAN_ATTRIBUTE_NUM_AP_PER_SCAN, + GSCAN_ATTRIBUTE_REPORT_THRESHOLD, + GSCAN_ATTRIBUTE_NUM_SCANS_TO_CACHE, + GSCAN_ATTRIBUTE_REPORT_THRESHOLD_NUM_SCANS, + GSCAN_ATTRIBUTE_BAND = GSCAN_ATTRIBUTE_BUCKETS_BAND, + + GSCAN_ATTRIBUTE_ENABLE_FEATURE = 20, + GSCAN_ATTRIBUTE_SCAN_RESULTS_COMPLETE, /* indicates no more results */ + GSCAN_ATTRIBUTE_REPORT_EVENTS, + + /* remaining reserved for additional attributes */ + GSCAN_ATTRIBUTE_NUM_OF_RESULTS = 30, + GSCAN_ATTRIBUTE_SCAN_RESULTS, /* flat array of wifi_scan_result */ + GSCAN_ATTRIBUTE_NUM_CHANNELS, + GSCAN_ATTRIBUTE_CHANNEL_LIST, + GSCAN_ATTRIBUTE_SCAN_ID, + GSCAN_ATTRIBUTE_SCAN_FLAGS, + GSCAN_ATTRIBUTE_SCAN_BUCKET_BIT, + + /* remaining reserved for additional attributes */ + GSCAN_ATTRIBUTE_RSSI_SAMPLE_SIZE = 60, + GSCAN_ATTRIBUTE_LOST_AP_SAMPLE_SIZE, + GSCAN_ATTRIBUTE_MIN_BREACHING, + GSCAN_ATTRIBUTE_SIGNIFICANT_CHANGE_BSSIDS, + + GSCAN_ATTRIBUTE_BUCKET_STEP_COUNT = 70, + GSCAN_ATTRIBUTE_BUCKET_EXPONENT, + GSCAN_ATTRIBUTE_BUCKET_MAX_PERIOD, + + GSCAN_ATTRIBUTE_NUM_BSSID, + GSCAN_ATTRIBUTE_BLACKLIST_BSSID, + + GSCAN_ATTRIBUTE_MAX +}; + +enum epno_ssid_attribute { + SLSI_ATTRIBUTE_EPNO_MINIMUM_5G_RSSI, + SLSI_ATTRIBUTE_EPNO_MINIMUM_2G_RSSI, + SLSI_ATTRIBUTE_EPNO_INITIAL_SCORE_MAX, + SLSI_ATTRIBUTE_EPNO_CUR_CONN_BONUS, + SLSI_ATTRIBUTE_EPNO_SAME_NETWORK_BONUS, + SLSI_ATTRIBUTE_EPNO_SECURE_BONUS, + SLSI_ATTRIBUTE_EPNO_5G_BONUS, + SLSI_ATTRIBUTE_EPNO_SSID_NUM, + SLSI_ATTRIBUTE_EPNO_SSID_LIST, + SLSI_ATTRIBUTE_EPNO_SSID, + SLSI_ATTRIBUTE_EPNO_SSID_LEN, + SLSI_ATTRIBUTE_EPNO_FLAGS, + SLSI_ATTRIBUTE_EPNO_AUTH, + SLSI_ATTRIBUTE_EPNO_MAX +}; + +enum epno_hs_attribute { + SLSI_ATTRIBUTE_EPNO_HS_PARAM_LIST, + SLSI_ATTRIBUTE_EPNO_HS_NUM, + SLSI_ATTRIBUTE_EPNO_HS_ID, + SLSI_ATTRIBUTE_EPNO_HS_REALM, + SLSI_ATTRIBUTE_EPNO_HS_CONSORTIUM_IDS, + SLSI_ATTRIBUTE_EPNO_HS_PLMN, + SLSI_ATTRIBUTE_EPNO_HS_MAX +}; + +enum gscan_bucket_attributes { + GSCAN_ATTRIBUTE_CH_BUCKET_1, + GSCAN_ATTRIBUTE_CH_BUCKET_2, + GSCAN_ATTRIBUTE_CH_BUCKET_3, + GSCAN_ATTRIBUTE_CH_BUCKET_4, + GSCAN_ATTRIBUTE_CH_BUCKET_5, + GSCAN_ATTRIBUTE_CH_BUCKET_6, + GSCAN_ATTRIBUTE_CH_BUCKET_7, + GSCAN_ATTRIBUTE_CH_BUCKET_8 +}; + +enum wifi_band { + WIFI_BAND_UNSPECIFIED, + WIFI_BAND_BG = 1, /* 2.4 GHz */ + WIFI_BAND_A = 2, /* 5 GHz without DFS */ + WIFI_BAND_A_DFS = 4, /* 5 GHz DFS only */ + WIFI_BAND_A_WITH_DFS = 6, /* 5 GHz with DFS */ + WIFI_BAND_ABG = 3, /* 2.4 GHz + 5 GHz; no DFS */ + WIFI_BAND_ABG_WITH_DFS = 7, /* 2.4 GHz + 5 GHz with DFS */ +}; + +enum wifi_scan_event { + WIFI_SCAN_RESULTS_AVAILABLE, + WIFI_SCAN_THRESHOLD_NUM_SCANS, + WIFI_SCAN_THRESHOLD_PERCENT, + WIFI_SCAN_FAILED, +}; + +enum wifi_mkeep_alive_attribute { + MKEEP_ALIVE_ATTRIBUTE_ID, + MKEEP_ALIVE_ATTRIBUTE_IP_PKT, + MKEEP_ALIVE_ATTRIBUTE_IP_PKT_LEN, + MKEEP_ALIVE_ATTRIBUTE_SRC_MAC_ADDR, + MKEEP_ALIVE_ATTRIBUTE_DST_MAC_ADDR, + MKEEP_ALIVE_ATTRIBUTE_PERIOD_MSEC +}; + +enum wifi_rssi_monitor_attr { + SLSI_RSSI_MONITOR_ATTRIBUTE_MAX_RSSI, + SLSI_RSSI_MONITOR_ATTRIBUTE_MIN_RSSI, + SLSI_RSSI_MONITOR_ATTRIBUTE_START +}; + +enum lls_attribute { + LLS_ATTRIBUTE_SET_MPDU_SIZE_THRESHOLD = 1, + LLS_ATTRIBUTE_SET_AGGR_STATISTICS_GATHERING, + LLS_ATTRIBUTE_CLEAR_STOP_REQUEST_MASK, + LLS_ATTRIBUTE_CLEAR_STOP_REQUEST, + LLS_ATTRIBUTE_MAX +}; + +enum slsi_hal_vendor_subcmds { + SLSI_NL80211_VENDOR_SUBCMD_GET_CAPABILITIES = SLSI_NL80211_GSCAN_SUBCMD_RANGE_START, + SLSI_NL80211_VENDOR_SUBCMD_GET_VALID_CHANNELS, + SLSI_NL80211_VENDOR_SUBCMD_ADD_GSCAN, + SLSI_NL80211_VENDOR_SUBCMD_DEL_GSCAN, + SLSI_NL80211_VENDOR_SUBCMD_GET_SCAN_RESULTS, + /*****Deprecated due to fapi updates.Do not remove.************/ + SLSI_NL80211_VENDOR_SUBCMD_SET_BSSID_HOTLIST, + SLSI_NL80211_VENDOR_SUBCMD_RESET_BSSID_HOTLIST, + SLSI_NL80211_VENDOR_SUBCMD_GET_HOTLIST_RESULTS, + SLSI_NL80211_VENDOR_SUBCMD_SET_SIGNIFICANT_CHANGE, + SLSI_NL80211_VENDOR_SUBCMD_RESET_SIGNIFICANT_CHANGE, + /********************************************************/ + SLSI_NL80211_VENDOR_SUBCMD_SET_GSCAN_OUI, + SLSI_NL80211_VENDOR_SUBCMD_SET_NODFS, + SLSI_NL80211_VENDOR_SUBCMD_START_KEEP_ALIVE_OFFLOAD, + SLSI_NL80211_VENDOR_SUBCMD_STOP_KEEP_ALIVE_OFFLOAD, + SLSI_NL80211_VENDOR_SUBCMD_SET_BSSID_BLACKLIST, + SLSI_NL80211_VENDOR_SUBCMD_SET_EPNO_LIST, + SLSI_NL80211_VENDOR_SUBCMD_SET_HS_LIST, + SLSI_NL80211_VENDOR_SUBCMD_RESET_HS_LIST, + SLSI_NL80211_VENDOR_SUBCMD_SET_RSSI_MONITOR, + SLSI_NL80211_VENDOR_SUBCMD_LSTATS_SUBCMD_SET_STATS, + SLSI_NL80211_VENDOR_SUBCMD_LSTATS_SUBCMD_GET_STATS, + SLSI_NL80211_VENDOR_SUBCMD_LSTATS_SUBCMD_CLEAR_STATS, + SLSI_NL80211_VENDOR_SUBCMD_GET_FEATURE_SET, + SLSI_NL80211_VENDOR_SUBCMD_SET_COUNTRY_CODE, + SLSI_NL80211_VENDOR_SUBCMD_CONFIGURE_ND_OFFLOAD, + SLSI_NL80211_VENDOR_SUBCMD_GET_ROAMING_CAPABILITIES, + SLSI_NL80211_VENDOR_SUBCMD_SET_ROAMING_STATE, + SLSI_NL80211_VENDOR_SUBCMD_START_LOGGING = SLSI_NL80211_LOGGING_SUBCMD_RANGE_START, + SLSI_NL80211_VENDOR_SUBCMD_TRIGGER_FW_MEM_DUMP, + SLSI_NL80211_VENDOR_SUBCMD_GET_FW_MEM_DUMP, + SLSI_NL80211_VENDOR_SUBCMD_GET_VERSION, + SLSI_NL80211_VENDOR_SUBCMD_GET_RING_STATUS, + SLSI_NL80211_VENDOR_SUBCMD_GET_RING_DATA, + SLSI_NL80211_VENDOR_SUBCMD_GET_FEATURE, + SLSI_NL80211_VENDOR_SUBCMD_RESET_LOGGING, + SLSI_NL80211_VENDOR_SUBCMD_TRIGGER_DRIVER_MEM_DUMP, + SLSI_NL80211_VENDOR_SUBCMD_GET_DRIVER_MEM_DUMP, + SLSI_NL80211_VENDOR_SUBCMD_START_PKT_FATE_MONITORING, + SLSI_NL80211_VENDOR_SUBCMD_GET_TX_PKT_FATES, + SLSI_NL80211_VENDOR_SUBCMD_GET_RX_PKT_FATES, + SLSI_NL80211_VENDOR_SUBCMD_GET_WAKE_REASON_STATS, + SLSI_NL80211_VENDOR_SUBCMD_NAN_ENABLE = SLSI_NL80211_NAN_SUBCMD_RANGE_START, + SLSI_NL80211_VENDOR_SUBCMD_NAN_DISABLE, + SLSI_NL80211_VENDOR_SUBCMD_NAN_PUBLISH, + SLSI_NL80211_VENDOR_SUBCMD_NAN_PUBLISHCANCEL, + SLSI_NL80211_VENDOR_SUBCMD_NAN_SUBSCRIBE, + SLSI_NL80211_VENDOR_SUBCMD_NAN_SUBSCRIBECANCEL, + SLSI_NL80211_VENDOR_SUBCMD_NAN_TXFOLLOWUP, + SLSI_NL80211_VENDOR_SUBCMD_NAN_CONFIG, + SLSI_NL80211_VENDOR_SUBCMD_NAN_CAPABILITIES, + SLSI_NL80211_VENDOR_SUBCMD_RTT_GET_CAPABILITIES = SLSI_NL80211_RTT_SUBCMD_RANGE_START, + SLSI_NL80211_VENDOR_SUBCMD_RTT_RANGE_START, + SLSI_NL80211_VENDOR_SUBCMD_RTT_RANGE_CANCEL, + SLSI_NL80211_VENDOR_SUBCMD_APF_SET_FILTER = SLSI_NL80211_APF_SUBCMD_RANGE_START, + SLSI_NL80211_VENDOR_SUBCMD_APF_GET_CAPABILITIES, + SLSI_NL80211_VENDOR_SUBCMD_APF_READ_FILTER +}; + +enum slsi_supp_vendor_subcmds { + SLSI_NL80211_VENDOR_SUBCMD_UNSPEC = 0, + SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY, + SLSI_NL80211_VENDOR_SUBCMD_ACS_INIT, +}; + +enum slsi_vendor_event_values { + /**********Deprecated now due to fapi updates.Do not remove*/ + SLSI_NL80211_SIGNIFICANT_CHANGE_EVENT, + SLSI_NL80211_HOTLIST_AP_FOUND_EVENT, + /******************************************/ + SLSI_NL80211_SCAN_RESULTS_AVAILABLE_EVENT, + SLSI_NL80211_FULL_SCAN_RESULT_EVENT, + SLSI_NL80211_SCAN_EVENT, + /**********Deprecated now due to fapi updates.Do not remove*/ + SLSI_NL80211_HOTLIST_AP_LOST_EVENT, + /******************************************/ + SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH, + SLSI_NL80211_VENDOR_HANGED_EVENT, + SLSI_NL80211_EPNO_EVENT, + SLSI_NL80211_HOTSPOT_MATCH, + SLSI_NL80211_RSSI_REPORT_EVENT, + SLSI_NL80211_LOGGER_RING_EVENT, + SLSI_NL80211_LOGGER_FW_DUMP_EVENT, + SLSI_NL80211_NAN_RESPONSE_EVENT, + SLSI_NL80211_NAN_PUBLISH_TERMINATED_EVENT, + SLSI_NL80211_NAN_MATCH_EVENT, + SLSI_NL80211_NAN_MATCH_EXPIRED_EVENT, + SLSI_NL80211_NAN_SUBSCRIBE_TERMINATED_EVENT, + SLSI_NL80211_NAN_FOLLOWUP_EVENT, + SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT, + SLSI_NL80211_NAN_DISABLED_EVENT, + SLSI_NL80211_RTT_RESULT_EVENT, + SLSI_NL80211_RTT_COMPLETE_EVENT, + SLSI_NL80211_VENDOR_ACS_EVENT, + SLSI_NL80211_VENDOR_FORWARD_BEACON, + SLSI_NL80211_VENDOR_FORWARD_BEACON_ABORT +}; + +enum slsi_lls_interface_mode { + SLSI_LLS_INTERFACE_STA = 0, + SLSI_LLS_INTERFACE_SOFTAP = 1, + SLSI_LLS_INTERFACE_IBSS = 2, + SLSI_LLS_INTERFACE_P2P_CLIENT = 3, + SLSI_LLS_INTERFACE_P2P_GO = 4, + SLSI_LLS_INTERFACE_NAN = 5, + SLSI_LLS_INTERFACE_MESH = 6, + SLSI_LLS_INTERFACE_UNKNOWN = -1 +}; + +enum slsi_lls_connection_state { + SLSI_LLS_DISCONNECTED = 0, + SLSI_LLS_AUTHENTICATING = 1, + SLSI_LLS_ASSOCIATING = 2, + SLSI_LLS_ASSOCIATED = 3, + SLSI_LLS_EAPOL_STARTED = 4, /* if done by firmware/driver*/ + SLSI_LLS_EAPOL_COMPLETED = 5, /* if done by firmware/driver*/ +}; + +enum slsi_lls_roam_state { + SLSI_LLS_ROAMING_IDLE = 0, + SLSI_LLS_ROAMING_ACTIVE = 1, +}; + +/* access categories */ +enum slsi_lls_traffic_ac { + SLSI_LLS_AC_VO = 0, + SLSI_LLS_AC_VI = 1, + SLSI_LLS_AC_BE = 2, + SLSI_LLS_AC_BK = 3, + SLSI_LLS_AC_MAX = 4, +}; + +/* channel operating width */ +enum slsi_lls_channel_width { + SLSI_LLS_CHAN_WIDTH_20 = 0, + SLSI_LLS_CHAN_WIDTH_40 = 1, + SLSI_LLS_CHAN_WIDTH_80 = 2, + SLSI_LLS_CHAN_WIDTH_160 = 3, + SLSI_LLS_CHAN_WIDTH_80P80 = 4, + SLSI_LLS_CHAN_WIDTH_5 = 5, + SLSI_LLS_CHAN_WIDTH_10 = 6, + SLSI_LLS_CHAN_WIDTH_INVALID = -1 +}; + +/* wifi peer type */ +enum slsi_lls_peer_type { + SLSI_LLS_PEER_STA, + SLSI_LLS_PEER_AP, + SLSI_LLS_PEER_P2P_GO, + SLSI_LLS_PEER_P2P_CLIENT, + SLSI_LLS_PEER_NAN, + SLSI_LLS_PEER_TDLS, + SLSI_LLS_PEER_INVALID, +}; + +/* slsi_enhanced_logging_attributes */ +enum slsi_enhanced_logging_attributes { + SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_VERSION, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_VERSION, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_ID, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_NAME, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_FLAGS, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_VERBOSE_LEVEL, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_LOG_MAX_INTERVAL, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_LOG_MIN_DATA_SIZE, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_LEN, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_FW_DUMP_DATA, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_DATA, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_STATUS, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_RING_NUM, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_DUMP_LEN, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_DRIVER_DUMP_DATA, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_NUM, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_PKT_FATE_DATA, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_INVALID = 0, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_TOTAL_CMD_EVENT_WAKE, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_CMD_EVENT_WAKE_CNT_PTR, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_CMD_EVENT_WAKE_CNT_SZ, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_TOTAL_DRIVER_FW_LOCAL_WAKE, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_DRIVER_FW_LOCAL_WAKE_CNT_PTR, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_DRIVER_FW_LOCAL_WAKE_CNT_SZ, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_TOTAL_RX_DATA_WAKE, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_RX_UNICAST_CNT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_RX_MULTICAST_CNT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_RX_BROADCAST_CNT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP_PKT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_PKT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_RA, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_NA, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_NS, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP4_RX_MULTICAST_CNT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_ICMP6_RX_MULTICAST_CNT, + SLSI_ENHANCED_LOGGING_ATTRIBUTE_WAKE_STATS_OTHER_RX_MULTICAST_CNT, +}; + +enum slsi_rtt_event_attributes { + SLSI_RTT_EVENT_ATTR_ADDR = 0, + SLSI_RTT_EVENT_ATTR_BURST_NUM, + SLSI_RTT_EVENT_ATTR_MEASUREMENT_NUM, + SLSI_RTT_EVENT_ATTR_SUCCESS_NUM, + SLSI_RTT_EVENT_ATTR_NUM_PER_BURST_PEER, + SLSI_RTT_EVENT_ATTR_STATUS, + SLSI_RTT_EVENT_ATTR_RETRY_AFTER_DURATION, + SLSI_RTT_EVENT_ATTR_TYPE, + SLSI_RTT_EVENT_ATTR_RSSI, + SLSI_RTT_EVENT_ATTR_RSSI_SPREAD, + SLSI_RTT_EVENT_ATTR_TX_PREAMBLE, + SLSI_RTT_EVENT_ATTR_TX_NSS, + SLSI_RTT_EVENT_ATTR_TX_BW, + SLSI_RTT_EVENT_ATTR_TX_MCS, + SLSI_RTT_EVENT_ATTR_TX_RATE, + SLSI_RTT_EVENT_ATTR_RX_PREAMBLE, + SLSI_RTT_EVENT_ATTR_RX_NSS, + SLSI_RTT_EVENT_ATTR_RX_BW, + SLSI_RTT_EVENT_ATTR_RX_MCS, + SLSI_RTT_EVENT_ATTR_RX_RATE, + SLSI_RTT_EVENT_ATTR_RTT, + SLSI_RTT_EVENT_ATTR_RTT_SD, + SLSI_RTT_EVENT_ATTR_RTT_SPREAD, + SLSI_RTT_EVENT_ATTR_DISTANCE_MM, + SLSI_RTT_EVENT_ATTR_DISTANCE_SD_MM, + SLSI_RTT_EVENT_ATTR_DISTANCE_SPREAD_MM, + SLSI_RTT_EVENT_ATTR_TIMESTAMP_US, + SLSI_RTT_EVENT_ATTR_BURST_DURATION_MSN, + SLSI_RTT_EVENT_ATTR_NEGOTIATED_BURST_NUM, + SLSI_RTT_EVENT_ATTR_LCI, + SLSI_RTT_EVENT_ATTR_LCR, + +}; + +/* RTT peer type */ +enum slsi_rtt_peer_type { + SLSI_RTT_PEER_AP = 0x1, + SLSI_RTT_PEER_STA, + SLSI_RTT_PEER_P2P_GO, + SLSI_RTT_PEER_P2P_CLIENT, + SLSI_RTT_PEER_NAN, +}; + +/* RTT Measurement Bandwidth */ +enum slsi_wifi_rtt_bw { + SLSI_WIFI_RTT_BW_5 = 0x01, + SLSI_WIFI_RTT_BW_10 = 0x02, + SLSI_WIFI_RTT_BW_20 = 0x04, + SLSI_WIFI_RTT_BW_40 = 0x08, + SLSI_WIFI_RTT_BW_80 = 0x10, + SLSI_WIFI_RTT_BW_160 = 0x20 +}; + +/* RTT Measurement Preamble */ +enum slsi_wifi_rtt_preamble { + SLSI_WIFI_RTT_PREAMBLE_LEGACY = 0x1, + SLSI_WIFI_RTT_PREAMBLE_HT = 0x2, + SLSI_WIFI_RTT_PREAMBLE_VHT = 0x4 +}; + +/* RTT Type */ +enum slsi_wifi_rtt_type { + SLSI_RTT_TYPE_1_SIDED = 0x1, + SLSI_RTT_TYPE_2_SIDED, +}; + +enum slsi_rtt_attribute { + SLSI_RTT_ATTRIBUTE_TARGET_CNT = 0, + SLSI_RTT_ATTRIBUTE_TARGET_INFO, + SLSI_RTT_ATTRIBUTE_TARGET_MAC, + SLSI_RTT_ATTRIBUTE_TARGET_TYPE, + SLSI_RTT_ATTRIBUTE_TARGET_PEER, + SLSI_RTT_ATTRIBUTE_TARGET_CHAN_FREQ, + SLSI_RTT_ATTRIBUTE_TARGET_PERIOD, + SLSI_RTT_ATTRIBUTE_TARGET_NUM_BURST, + SLSI_RTT_ATTRIBUTE_TARGET_NUM_FTM_BURST, + SLSI_RTT_ATTRIBUTE_TARGET_NUM_RETRY_FTM, + SLSI_RTT_ATTRIBUTE_TARGET_NUM_RETRY_FTMR, + SLSI_RTT_ATTRIBUTE_TARGET_LCI, + SLSI_RTT_ATTRIBUTE_TARGET_LCR, + SLSI_RTT_ATTRIBUTE_TARGET_BURST_DURATION, + SLSI_RTT_ATTRIBUTE_TARGET_PREAMBLE, + SLSI_RTT_ATTRIBUTE_TARGET_BW, + SLSI_RTT_ATTRIBUTE_RESULTS_COMPLETE = 30, + SLSI_RTT_ATTRIBUTE_RESULTS_PER_TARGET, + SLSI_RTT_ATTRIBUTE_RESULT_CNT, + SLSI_RTT_ATTRIBUTE_RESULT, + SLSI_RTT_ATTRIBUTE_TARGET_ID +}; + +/* Ranging status */ +enum slsi_wifi_rtt_status { + SLSI_RTT_STATUS_SUCCESS = 0, + SLSI_RTT_STATUS_FAILURE, /* general failure status */ + SLSI_RTT_STATUS_FAIL_NO_RSP, /* target STA does not respond to request */ + SLSI_RTT_STATUS_FAIL_REJECTED, /* request rejected. Applies to 2-sided RTT only*/ + SLSI_RTT_STATUS_FAIL_NOT_SCHEDULED_YET, + SLSI_RTT_STATUS_FAIL_TM_TIMEOUT, /* timing measurement times out */ + SLSI_RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL, /* Target on different channel, cannot range */ + SLSI_RTT_STATUS_FAIL_NO_CAPABILITY, /* ranging not supported */ + SLSI_RTT_STATUS_ABORTED, /* request aborted for unknown reason */ + SLSI_RTT_STATUS_FAIL_INVALID_TS, /* Invalid T1-T4 timestamp */ + SLSI_RTT_STATUS_FAIL_PROTOCOL, /* 11mc protocol failed */ + SLSI_RTT_STATUS_FAIL_SCHEDULE, /* request could not be scheduled */ + SLSI_RTT_STATUS_FAIL_BUSY_TRY_LATER, /* responder cannot collaborate at time of request */ + SLSI_RTT_STATUS_INVALID_REQ, /* bad request args */ + SLSI_RTT_STATUS_NO_WIFI, /* WiFi not enabled */ + SLSI_RTT_STATUS_FAIL_FTM_PARAM_OVERRIDE /* Responder overrides param info, cannot range with new params */ +}; + +/* Format of information elements found in the beacon */ +struct slsi_wifi_information_element { + u8 id; /* element identifier */ + u8 len; /* number of bytes to follow */ + u8 data[]; +}; + +struct slsi_nl_gscan_capabilities { + int max_scan_cache_size; + int max_scan_buckets; + int max_ap_cache_per_scan; + int max_rssi_sample_size; + int max_scan_reporting_threshold; + int max_hotlist_aps; + int max_hotlist_ssids; + int max_significant_wifi_change_aps; + int max_bssid_history_entries; + int max_number_epno_networks; + int max_number_epno_networks_by_ssid; + int max_number_of_white_listed_ssid; +}; + +struct slsi_nl_channel_param { + int channel; + int dwell_time_ms; + int passive; /* 0 => active, 1 => passive scan; ignored for DFS */ +}; + +struct slsi_nl_bucket_param { + int bucket_index; + enum wifi_band band; + int period; /* desired period in millisecond */ + u8 report_events; + int max_period; /* If non-zero: scan period will grow exponentially to a maximum period of max_period */ + int exponent; /* multiplier: new_period = old_period ^ exponent */ + int step_count; /* number of scans performed at a given period and until the exponent is applied */ + int num_channels; + struct slsi_nl_channel_param channels[SLSI_GSCAN_MAX_CHANNELS]; +}; + +struct slsi_nl_gscan_param { + int base_period; /* base timer period in ms */ + int max_ap_per_scan; /* number of APs to store in each scan in the BSSID/RSSI history buffer */ + int report_threshold_percent; /* when scan_buffer is this much full, wake up application processor */ + int report_threshold_num_scans; /* wake up application processor after these many scans */ + int num_buckets; + struct slsi_nl_bucket_param nl_bucket[SLSI_GSCAN_MAX_BUCKETS]; +}; + +struct slsi_nl_scan_result_param { + u64 ts; /* time since boot (in microsecond) when the result was retrieved */ + u8 ssid[IEEE80211_MAX_SSID_LEN + 1]; /* NULL terminated */ + u8 bssid[6]; + int channel; /* channel frequency in MHz */ + int rssi; /* in db */ + s64 rtt; /* in nanoseconds */ + s64 rtt_sd; /* standard deviation in rtt */ + u16 beacon_period; /* period advertised in the beacon */ + u16 capability; /* capabilities advertised in the beacon */ + u32 ie_length; /* size of the ie_data blob */ + u8 ie_data[1]; /* beacon IE */ +}; + +struct slsi_bucket { + bool used; /* to identify if this entry is free */ + bool for_change_tracking; /* Indicates if this scan_id is used for change_tracking */ + u8 report_events; /* this is received from HAL/Framework */ + u16 scan_id; /* SLSI_GSCAN_SCAN_ID_START + */ + int scan_cycle; /* To find the current scan cycle */ + struct slsi_gscan *gscan; /* gscan ref in which this bucket belongs */ +}; + +struct slsi_gscan { + int max_ap_per_scan; /* received from HAL/Framework */ + int report_threshold_percent; /* received from HAL/Framework */ + int report_threshold_num_scans; /* received from HAL/Framework */ + int num_scans; + int num_buckets; /* received from HAL/Framework */ + struct slsi_nl_bucket_param nl_bucket; /* store the first bucket params. used in tracking*/ + struct slsi_bucket *bucket[SLSI_GSCAN_MAX_BUCKETS_PER_GSCAN]; + struct slsi_gscan *next; +}; + +struct slsi_gscan_param { + struct slsi_nl_bucket_param *nl_bucket; + struct slsi_bucket *bucket; +}; + +struct slsi_gscan_result { + struct slsi_gscan_result *hnext; + int scan_cycle; + int scan_res_len; + int anqp_length; + struct slsi_nl_scan_result_param nl_scan_res; +}; + +struct slsi_epno_ssid_param { + u16 flags; + u8 ssid_len; + u8 ssid[32]; +}; + +struct slsi_epno_param { + u16 min_5g_rssi; /* minimum 5GHz RSSI for a BSSID to be considered */ + u16 min_2g_rssi; /* minimum 2.4GHz RSSI for a BSSID to be considered */ + u16 initial_score_max; /* maximum score that a network can have before bonuses */ + u8 current_connection_bonus; /* report current connection bonus only, when there is a + * network's score this much higher than the current connection + */ + u8 same_network_bonus; /* score bonus for all networks with the same network flag */ + u8 secure_bonus; /* score bonus for networks that are not open */ + u8 band_5g_bonus; /* 5GHz RSSI score bonus (applied to all 5GHz networks) */ + u8 num_networks; /* number of wifi_epno_network objects */ + struct slsi_epno_ssid_param epno_ssid[]; /* PNO networks */ +}; + +struct slsi_epno_hs2_param { + u32 id; /* identifier of this network block, report this in event */ + u8 realm[256]; /* null terminated UTF8 encoded realm, 0 if unspecified */ + s64 roaming_consortium_ids[16]; /* roaming consortium ids to match, 0s if unspecified */ + u8 plmn[3]; /* mcc/mnc combination as per rules, 0s if unspecified */ +}; + +struct slsi_rssi_monitor_evt { + s16 rssi; + u8 bssid[ETH_ALEN]; +}; + +/* channel information */ +struct slsi_lls_channel_info { + enum slsi_lls_channel_width width; /* channel width (20, 40, 80, 80+80, 160)*/ + int center_freq; /* primary 20 MHz channel */ + int center_freq0; /* center frequency (MHz) first segment */ + int center_freq1; /* center frequency (MHz) second segment */ +}; + +/* channel statistics */ +struct slsi_lls_channel_stat { + struct slsi_lls_channel_info channel; + u32 on_time; /* msecs the radio is awake (32 bits number accruing over time) */ + u32 cca_busy_time; /* msecs the CCA register is busy (32 bits number accruing over time) */ +}; + +/* wifi rate */ +struct slsi_lls_rate { + u32 preamble :3; /* 0: OFDM, 1:CCK, 2:HT 3:VHT 4..7 reserved*/ + u32 nss :2; /* 0:1x1, 1:2x2, 3:3x3, 4:4x4*/ + u32 bw :3; /* 0:20MHz, 1:40Mhz, 2:80Mhz, 3:160Mhz*/ + u32 rate_mcs_idx :8; /* OFDM/CCK rate code mcs index*/ + u32 reserved :16; /* reserved*/ + u32 bitrate; /* units of 100 Kbps*/ +}; + +/* per rate statistics */ +struct slsi_lls_rate_stat { + struct slsi_lls_rate rate; /* rate information*/ + u32 tx_mpdu; /* number of successfully transmitted data pkts (ACK rcvd)*/ + u32 rx_mpdu; /* number of received data pkts*/ + u32 mpdu_lost; /* number of data packet losses (no ACK)*/ + u32 retries; /* total number of data pkt retries*/ + u32 retries_short; /* number of short data pkt retries*/ + u32 retries_long; /* number of long data pkt retries*/ +}; + +/* radio statistics */ +struct slsi_lls_radio_stat { + int radio; /* wifi radio (if multiple radio supported)*/ + u32 on_time; /* msecs the radio is awake (32 bits number accruing over time)*/ + u32 tx_time; /* msecs the radio is transmitting (32 bits number accruing over time)*/ + u32 rx_time; /* msecs the radio is in active receive (32 bits number accruing over time)*/ + u32 on_time_scan; /* msecs the radio is awake due to all scan (32 bits number accruing over time)*/ + u32 on_time_nbd; /* msecs the radio is awake due to NAN (32 bits number accruing over time)*/ + u32 on_time_gscan; /* msecs the radio is awake due to G?scan (32 bits number accruing over time)*/ + u32 on_time_roam_scan; /* msecs the radio is awake due to roam?scan (32 bits number accruing over time)*/ + u32 on_time_pno_scan; /* msecs the radio is awake due to PNO scan (32 bits number accruing over time)*/ + u32 on_time_hs20; /* msecs the radio is awake due to HS2.0 scans and GAS exchange (32 bits number accruing over time)*/ + u32 num_channels; /* number of channels*/ + struct slsi_lls_channel_stat channels[]; /* channel statistics*/ +}; + +struct slsi_lls_interface_link_layer_info { + enum slsi_lls_interface_mode mode; /* interface mode*/ + u8 mac_addr[6]; /* interface mac address (self)*/ + enum slsi_lls_connection_state state; /* connection state (valid for STA, CLI only)*/ + enum slsi_lls_roam_state roaming; /* roaming state*/ + u32 capabilities; /* WIFI_CAPABILITY_XXX (self)*/ + u8 ssid[33]; /* null terminated SSID*/ + u8 bssid[6]; /* bssid*/ + u8 ap_country_str[3]; /* country string advertised by AP*/ + u8 country_str[3]; /* country string for this association*/ +}; + +/* per peer statistics */ +struct slsi_lls_peer_info { + enum slsi_lls_peer_type type; /* peer type (AP, TDLS, GO etc.)*/ + u8 peer_mac_address[6]; /* mac address*/ + u32 capabilities; /* peer WIFI_CAPABILITY_XXX*/ + u32 num_rate; /* number of rates*/ + struct slsi_lls_rate_stat rate_stats[]; /* per rate statistics, number of entries = num_rate*/ +}; + +/* Per access category statistics */ +struct slsi_lls_wmm_ac_stat { + enum slsi_lls_traffic_ac ac; /* access category (VI, VO, BE, BK)*/ + u32 tx_mpdu; /* number of successfully transmitted unicast data pkts (ACK rcvd)*/ + u32 rx_mpdu; /* number of received unicast data packets*/ + u32 tx_mcast; /* number of successfully transmitted multicast data packets*/ + u32 rx_mcast; /* number of received multicast data packets*/ + u32 rx_ampdu; /* number of received unicast a-mpdus; support of this counter is optional*/ + u32 tx_ampdu; /* number of transmitted unicast a-mpdus; support of this counter is optional*/ + u32 mpdu_lost; /* number of data pkt losses (no ACK)*/ + u32 retries; /* total number of data pkt retries*/ + u32 retries_short; /* number of short data pkt retries*/ + u32 retries_long; /* number of long data pkt retries*/ + u32 contention_time_min; /* data pkt min contention time (usecs)*/ + u32 contention_time_max; /* data pkt max contention time (usecs)*/ + u32 contention_time_avg; /* data pkt avg contention time (usecs)*/ + u32 contention_num_samples; /* num of data pkts used for contention statistics*/ +}; + +struct slsi_rx_data_cnt_details { + int rx_unicast_cnt; /*Total rx unicast packet which woke up host */ + int rx_multicast_cnt; /*Total rx multicast packet which woke up host */ + int rx_broadcast_cnt; /*Total rx broadcast packet which woke up host */ +}; + +struct slsi_rx_wake_pkt_type_classification { + int icmp_pkt; /*wake icmp packet count */ + int icmp6_pkt; /*wake icmp6 packet count */ + int icmp6_ra; /*wake icmp6 RA packet count */ + int icmp6_na; /*wake icmp6 NA packet count */ + int icmp6_ns; /*wake icmp6 NS packet count */ +}; + +struct slsi_rx_multicast_cnt { + int ipv4_rx_multicast_addr_cnt; /*Rx wake packet was ipv4 multicast */ + int ipv6_rx_multicast_addr_cnt; /*Rx wake packet was ipv6 multicast */ + int other_rx_multicast_addr_cnt;/*Rx wake packet was non-ipv4 and non-ipv6*/ +}; + +/* + * Structure holding all the driver/firmware wake count reasons. + * + * Buffers for the array fields (cmd_event_wake_cnt/driver_fw_local_wake_cnt) + * are allocated and freed by the framework. The size of each allocated + * array is indicated by the corresponding |_cnt| field. HAL needs to fill in + * the corresponding |_used| field to indicate the number of elements used in + * the array. + */ +struct slsi_wlan_driver_wake_reason_cnt { + int total_cmd_event_wake; /* Total count of cmd event wakes */ + int *cmd_event_wake_cnt; /* Individual wake count array, each index a reason */ + int cmd_event_wake_cnt_sz; /* Max number of cmd event wake reasons */ + int cmd_event_wake_cnt_used; /* Number of cmd event wake reasons specific to the driver */ + + int total_driver_fw_local_wake; /* Total count of drive/fw wakes, for local reasons */ + int *driver_fw_local_wake_cnt; /* Individual wake count array, each index a reason */ + int driver_fw_local_wake_cnt_sz; /* Max number of local driver/fw wake reasons */ + int driver_fw_local_wake_cnt_used; /* Number of local driver/fw wake reasons specific to the driver */ + + int total_rx_data_wake; /* total data rx packets, that woke up host */ + struct slsi_rx_data_cnt_details rx_wake_details; + struct slsi_rx_wake_pkt_type_classification rx_wake_pkt_classification_info; + struct slsi_rx_multicast_cnt rx_multicast_wake_pkt_info; +}; + +/* interface statistics */ +struct slsi_lls_iface_stat { + void *iface; /* wifi interface*/ + struct slsi_lls_interface_link_layer_info info; /* current state of the interface*/ + u32 beacon_rx; /* access point beacon received count from connected AP*/ + u64 average_tsf_offset; /* average beacon offset encountered (beacon_TSF - TBTT)*/ + u32 leaky_ap_detected; /* indicate that this AP typically leaks packets beyond the driver guard time.*/ + u32 leaky_ap_avg_num_frames_leaked; /* average number of frame leaked by AP after frame with PM bit set was ACK'ed by AP*/ + u32 leaky_ap_guard_time; + u32 mgmt_rx; /* access point mgmt frames received count from connected AP (including Beacon)*/ + u32 mgmt_action_rx; /* action frames received count*/ + u32 mgmt_action_tx; /* action frames transmit count*/ + int rssi_mgmt; /* access Point Beacon and Management frames RSSI (averaged)*/ + int rssi_data; /* access Point Data Frames RSSI (averaged) from connected AP*/ + int rssi_ack; /* access Point ACK RSSI (averaged) from connected AP*/ + struct slsi_lls_wmm_ac_stat ac[SLSI_LLS_AC_MAX]; /* per ac data packet statistics*/ + u32 num_peers; /* number of peers*/ + struct slsi_lls_peer_info peer_info[]; /* per peer statistics*/ +}; + +enum slsi_wifi_hal_api_return_types { + WIFI_HAL_SUCCESS = 0, + WIFI_HAL_ERROR_NONE = 0, + WIFI_HAL_ERROR_UNKNOWN = -1, + WIFI_HAL_ERROR_UNINITIALIZED = -2, + WIFI_HAL_ERROR_NOT_SUPPORTED = -3, + WIFI_HAL_ERROR_NOT_AVAILABLE = -4, + WIFI_HAL_ERROR_INVALID_ARGS = -5, + WIFI_HAL_ERROR_INVALID_REQUEST_ID = -6, + WIFI_HAL_ERROR_TIMED_OUT = -7, + WIFI_HAL_ERROR_TOO_MANY_REQUESTS = -8, + WIFI_HAL_ERROR_OUT_OF_MEMORY = -9 +}; + +struct slsi_rtt_capabilities { + u8 rtt_one_sided_supported; /* if 1-sided rtt data collection is supported */ + u8 rtt_ftm_supported; /* if ftm rtt data collection is supported */ + u8 lci_support; /* if initiator supports LCI request. Applies to 2-sided RTT */ + u8 lcr_support; /* if initiator supports LCR request. Applies to 2-sided RTT */ + u8 preamble_support; /* bit mask indicates what preamble is supported by initiator */ + u8 bw_support; /* bit mask indicates what BW is supported by initiator */ + u8 responder_supported; /* if 11mc responder mode is supported */ + u8 mc_version; /* draft 11mc spec version supported by chip. For instance, + *version 4.0 should be 40 and version 4.3 should be 43 etc. + */ +}; + +/* RTT configuration */ +struct slsi_rtt_config { + u8 peer_addr[ETH_ALEN]; /* peer device mac address */ + u16 type; /* 1-sided or 2-sided RTT */ + u16 channel_freq; /* Required for STA-AP mode, optional for P2P, NBD etc. */ + u16 channel_info; + u8 burst_period; /* Time interval between bursts (units: 100 ms). */ + /* Applies to 1-sided and 2-sided RTT multi-burst requests. + *Range: 0-31, 0: no preference by initiator (2-sided RTT) + */ + u8 num_burst; /* Total number of RTT bursts to be executed. It will be + *specified in the same way as the parameter "Number of + *Burst Exponent" found in the FTM frame format. It + *applies to both: 1-sided RTT and 2-sided RTT. Valid + *values are 0 to 15 as defined in 802.11mc std + *0 means single shot + *The implication of this parameter on the maximum + *number of RTT results is the following: + *for 1-sided RTT: max num of RTT results = (2^num_burst)*(num_frames_per_burst) + *for 2-sided RTT: max num of RTT results = (2^num_burst)*(num_frames_per_burst - 1) + */ + u8 num_frames_per_burst; /* num of frames per burst. + *Minimum value = 1, Maximum value = 31 + *For 2-sided this equals the number of FTM frames + *to be attempted in a single burst. This also + *equals the number of FTM frames that the + *initiator will request that the responder send + *in a single frame. + */ + u8 num_retries_per_ftmr; /* Maximum number of retries that the initiator can + *retry an FTMR frame. + *Minimum value = 0, Maximum value = 3 + */ + u8 burst_duration; /* Applies to 1-sided and 2-sided RTT. Valid values will + *be 2-11 and 15 as specified by the 802.11mc std for + *the FTM parameter burst duration. In a multi-burst + *request, if responder overrides with larger value, + *the initiator will return failure. In a single-burst + *request if responder overrides with larger value, + *the initiator will sent TMR_STOP to terminate RTT + *at the end of the burst_duration it requested. + */ + u16 preamble; /* RTT preamble to be used in the RTT frames */ + u16 bw; /* RTT BW to be used in the RTT frames */ + u16 LCI_request; /* 1: request LCI, 0: do not request LCI */ + u16 LCR_request; /* 1: request LCR, 0: do not request LCR */ +}; + +#define MAX_CHAN_VALUE_ACS 25 /*Max number of supported channel is 25*/ + +struct slsi_acs_chan_info { + u16 chan; + u8 num_ap; + u8 num_bss_load_ap; + u8 total_chan_utilization; + u8 avg_chan_utilization; + int rssi_factor; + int adj_rssi_factor; +}; + +struct slsi_acs_selected_channels { + u8 pri_channel; + u8 sec_channel; + u8 vht_seg0_center_ch; + u8 vht_seg1_center_ch; + u16 ch_width; + enum slsi_acs_hw_mode hw_mode; +}; + +struct slsi_acs_request { + struct slsi_acs_chan_info acs_chan_info[MAX_CHAN_VALUE_ACS]; + u8 hw_mode; + u16 ch_width; + u8 ch_list_len; +}; + +void slsi_nl80211_vendor_init(struct slsi_dev *sdev); +void slsi_nl80211_vendor_deinit(struct slsi_dev *sdev); +u8 slsi_gscan_get_scan_policy(enum wifi_band band); +void slsi_gscan_handle_scan_result(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, u16 scan_id, bool scan_done); +void slsi_gscan_hash_remove(struct slsi_dev *sdev, u8 *mac); +void slsi_rx_significant_change_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +int slsi_gscan_alloc_buckets(struct slsi_dev *sdev, struct slsi_gscan *gscan, int num_buckets); +int slsi_vendor_event(struct slsi_dev *sdev, int event_id, const void *data, int len); +int slsi_mib_get_gscan_cap(struct slsi_dev *sdev, struct slsi_nl_gscan_capabilities *cap); +void slsi_rx_rssi_report_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +int slsi_mib_get_apf_cap(struct slsi_dev *sdev, struct net_device *dev); +int slsi_mib_get_rtt_cap(struct slsi_dev *sdev, struct net_device *dev, struct slsi_rtt_capabilities *cap); +int slsi_mlme_add_range_req(struct slsi_dev *sdev, u8 count, struct slsi_rtt_config *nl_rtt_params, + u16 rtt_id, u16 vif_idx, u8 *source_addr); +int slsi_mlme_del_range_req(struct slsi_dev *sdev, struct net_device *dev, u16 count, u8 *addr, u16 rtt_id); +void slsi_rx_range_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_range_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +int slsi_tx_rate_calc(struct sk_buff *nl_skb, u16 fw_rate, int res, bool tx_rate); +void slsi_check_num_radios(struct slsi_dev *sdev); +void slsi_rx_event_log_indication(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +#ifdef CONFIG_SCSC_WLAN_DEBUG +char *slsi_print_event_name(int event_id); +#endif + + +static inline bool slsi_is_gscan_id(u16 scan_id) +{ + if ((scan_id >= SLSI_GSCAN_SCAN_ID_START) && (scan_id <= SLSI_GSCAN_SCAN_ID_END)) + return true; + + return false; +} + +static inline enum slsi_lls_traffic_ac slsi_fapi_to_android_traffic_q(enum slsi_traffic_q fapi_q) +{ + switch (fapi_q) { + case SLSI_TRAFFIC_Q_BE: + return SLSI_LLS_AC_BE; + case SLSI_TRAFFIC_Q_BK: + return SLSI_LLS_AC_BK; + case SLSI_TRAFFIC_Q_VI: + return SLSI_LLS_AC_VI; + case SLSI_TRAFFIC_Q_VO: + return SLSI_LLS_AC_VO; + default: + return SLSI_LLS_AC_MAX; + } +} + +#endif diff --git a/drivers/net/wireless/scsc/nl80211_vendor_nan.c b/drivers/net/wireless/scsc/nl80211_vendor_nan.c new file mode 100755 index 000000000000..e53c006d45df --- /dev/null +++ b/drivers/net/wireless/scsc/nl80211_vendor_nan.c @@ -0,0 +1,1842 @@ +/***************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "cfg80211_ops.h" +#include "debug.h" +#include "mgt.h" +#include "mlme.h" + +struct net_device *slsi_nan_get_netdev(struct slsi_dev *sdev) +{ +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + return slsi_get_netdev(sdev, SLSI_NET_INDEX_NAN); +#else + return NULL; +#endif +} + +static int slsi_nan_get_new_id(u32 id_map, int max_ids) +{ + int i; + + for (i = 1; i <= max_ids; i++) { + if (!(id_map & BIT(i))) + return i; + } + return 0; +} + +static int slsi_nan_get_new_publish_id(struct netdev_vif *ndev_vif) +{ + return slsi_nan_get_new_id(ndev_vif->nan.publish_id_map, SLSI_NAN_MAX_PUBLISH_ID); +} + +static int slsi_nan_get_new_subscribe_id(struct netdev_vif *ndev_vif) +{ + return slsi_nan_get_new_id(ndev_vif->nan.subscribe_id_map, SLSI_NAN_MAX_SUBSCRIBE_ID); +} + +static bool slsi_nan_is_publish_id_active(struct netdev_vif *ndev_vif, u32 id) +{ + return ndev_vif->nan.publish_id_map & BIT(id); +} + +static bool slsi_nan_is_subscribe_id_active(struct netdev_vif *ndev_vif, u32 id) +{ + return ndev_vif->nan.subscribe_id_map & BIT(id); +} + +/*Note: vendor IE length of the stitched info may not be correct. Caller + * has to depend on return value for full length of IE. + */ +static u32 slsi_nan_stitch_ie(u8 *buff, u32 buff_len, u16 oui_type_subtype, u8 *dest_buff) +{ + u8 samsung_oui[] = {0x00, 0x16, 0x32}; + u8 *pos = buff; + u32 dest_buff_len = 0; + + while (buff_len - (pos - buff) > 2 + sizeof(samsung_oui) + 2) { + /* check if buffer is alteast al long as IE length */ + if (pos[1] + 2 + pos - buff > buff_len) { + pos = buff + buff_len; + continue; + } + if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && + memcmp(samsung_oui, &pos[2], sizeof(samsung_oui)) == 0 && + memcmp((u8 *)&oui_type_subtype, &pos[5], sizeof(oui_type_subtype)) == 0) { + if (!dest_buff_len) { + memcpy(dest_buff, pos, pos[1] + 2); + dest_buff_len = pos[1] + 2; + } else { + memcpy(&dest_buff[dest_buff_len], &pos[7], pos[1] - 5); + dest_buff_len += pos[1] - 5; + } + } + pos += pos[1] + 2; + } + + return dest_buff_len; +} + +void slsi_nan_get_mac(struct slsi_dev *sdev, char *nan_mac_addr) +{ + memset(nan_mac_addr, 0, ETH_ALEN); +#if CONFIG_SCSC_WLAN_MAX_INTERFACES >= 4 + if (slsi_dev_nan_supported(sdev)) + ether_addr_copy(nan_mac_addr, sdev->netdev_addresses[SLSI_NET_INDEX_NAN]); +#endif +} + +static void slsi_vendor_nan_command_reply(struct wiphy *wiphy, u32 status, u32 error, u32 response_type, + u16 publish_subscribe_id, struct slsi_hal_nan_capabilities *capabilities) +{ + int reply_len; + struct sk_buff *reply; + + reply_len = SLSI_NL_VENDOR_REPLY_OVERHEAD + SLSI_NL_ATTRIBUTE_U32_LEN * + (3 + sizeof(struct slsi_hal_nan_capabilities)); + reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, reply_len); + if (!reply) { + SLSI_WARN_NODEV("SKB alloc failed for vendor_cmd reply\n"); + return; + } + + nla_put_u32(reply, NAN_REPLY_ATTR_STATUS_TYPE, status); + nla_put_u32(reply, NAN_REPLY_ATTR_VALUE, error); + nla_put_u32(reply, NAN_REPLY_ATTR_RESPONSE_TYPE, response_type); + + if (capabilities) { + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_CONCURRENT_CLUSTER, + capabilities->max_concurrent_nan_clusters); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_PUBLISHES, capabilities->max_publishes); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_SUBSCRIBES, capabilities->max_subscribes); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_SERVICE_NAME_LEN, capabilities->max_service_name_len); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_MATCH_FILTER_LEN, capabilities->max_match_filter_len); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_TOTAL_MATCH_FILTER_LEN, + capabilities->max_total_match_filter_len); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_SERVICE_SPECIFIC_INFO_LEN, + capabilities->max_service_specific_info_len); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_VSA_DATA_LEN, capabilities->max_vsa_data_len); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_MESH_DATA_LEN, capabilities->max_mesh_data_len); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_NDI_INTERFACES, capabilities->max_ndi_interfaces); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_NDP_SESSIONS, capabilities->max_ndp_sessions); + nla_put_u32(reply, NAN_REPLY_ATTR_CAP_MAX_APP_INFO_LEN, capabilities->max_app_info_len); + } else if (publish_subscribe_id) { + nla_put_u16(reply, NAN_REPLY_ATTR_PUBLISH_SUBSCRIBE_TYPE, publish_subscribe_id); + } + + if (cfg80211_vendor_cmd_reply(reply)) + SLSI_ERR_NODEV("FAILED to reply nan coammnd. response_type:%d\n", response_type); +} + +static int slsi_nan_get_sdea_params_nl(struct slsi_dev *sdev, struct slsi_nan_sdea_ctrl_params *sdea_params, + const struct nlattr *iter, int nl_attr_id) +{ + switch (nl_attr_id) { + case NAN_REQ_ATTR_SDEA_PARAM_NDP_TYPE: + sdea_params->ndp_type = nla_get_u8(iter); + sdea_params->config_nan_data_path = 1; + break; + case NAN_REQ_ATTR_SDEA_PARAM_SECURITY_CFG: + sdea_params->security_cfg = nla_get_u8(iter); + sdea_params->config_nan_data_path = 1; + break; + case NAN_REQ_ATTR_SDEA_PARAM_RANGING_STATE: + sdea_params->ranging_state = nla_get_u8(iter); + sdea_params->config_nan_data_path = 1; + break; + case NAN_REQ_ATTR_SDEA_PARAM_RANGE_REPORT: + sdea_params->range_report = nla_get_u8(iter); + sdea_params->config_nan_data_path = 1; + break; + case NAN_REQ_ATTR_SDEA_PARAM_QOS_CFG: + sdea_params->qos_cfg = nla_get_u8(iter); + sdea_params->config_nan_data_path = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int slsi_nan_get_ranging_cfg_nl(struct slsi_dev *sdev, struct slsi_nan_ranging_cfg *ranging_cfg, + const struct nlattr *iter, int nl_attr_id) +{ + switch (nl_attr_id) { + case NAN_REQ_ATTR_RANGING_CFG_INTERVAL: + ranging_cfg->ranging_interval_msec = nla_get_u32(iter); + break; + case NAN_REQ_ATTR_RANGING_CFG_INDICATION: + ranging_cfg->config_ranging_indications = nla_get_u32(iter); + break; + case NAN_REQ_ATTR_RANGING_CFG_INGRESS_MM: + ranging_cfg->distance_ingress_mm = nla_get_u32(iter); + break; + case NAN_REQ_ATTR_RANGING_CFG_EGRESS_MM: + ranging_cfg->distance_egress_mm = nla_get_u32(iter); + break; + default: + return -EINVAL; + } + return 0; +} + +static int slsi_nan_get_security_info_nl(struct slsi_dev *sdev, struct slsi_nan_security_info *sec_info, + const struct nlattr *iter, int nl_attr_id) +{ + u32 len; + + switch (nl_attr_id) { + case NAN_REQ_ATTR_CIPHER_TYPE: + sec_info->cipher_type = nla_get_u32(iter); + break; + case NAN_REQ_ATTR_SECURITY_KEY_TYPE: + sec_info->key_info.key_type = nla_get_u8(iter); + break; + case NAN_REQ_ATTR_SECURITY_PMK_LEN: + len = nla_get_u32(iter); + sec_info->key_info.body.pmk_info.pmk_len = len; + break; + case NAN_REQ_ATTR_SECURITY_PMK: + memcpy(sec_info->key_info.body.pmk_info.pmk, nla_data(iter), len); + break; + case NAN_REQ_ATTR_SECURITY_PASSPHRASE_LEN: + len = nla_get_u32(iter); + sec_info->key_info.body.passphrase_info.passphrase_len = len; + break; + case NAN_REQ_ATTR_SECURITY_PASSPHRASE: + memcpy(sec_info->key_info.body.passphrase_info.passphrase, nla_data(iter), len); + break; + case NAN_REQ_ATTR_SCID_LEN: + sec_info->scid_len = nla_get_u32(iter); + break; + case NAN_REQ_ATTR_SCID: + memcpy(sec_info->scid, nla_data(iter), sec_info->scid_len); + break; + default: + return -EINVAL; + } + return 0; +} + +static int slsi_nan_get_range_resp_cfg_nl(struct slsi_dev *sdev, struct slsi_nan_range_response_cfg *cfg, + const struct nlattr *iter, int nl_attr_id) +{ + switch (nl_attr_id) { + case NAN_REQ_ATTR_RANGE_RESPONSE_CFG_PUBLISH_ID: + cfg->publish_id = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_RANGE_RESPONSE_CFG_REQUESTOR_ID: + cfg->requestor_instance_id = nla_get_u32(iter); + break; + + case NAN_REQ_ATTR_RANGE_RESPONSE_CFG_PEER_ADDR: + memcpy(cfg->peer_addr, nla_data(iter), ETH_ALEN); + break; + + case NAN_REQ_ATTR_RANGE_RESPONSE_CFG_RANGING_RESPONSE: + cfg->ranging_response = nla_get_u8(iter); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int slsi_nan_enable_get_nl_params(struct slsi_dev *sdev, struct slsi_hal_nan_enable_req *hal_req, + const void *data, int len) +{ + int type, tmp; + const struct nlattr *iter; + + memset(hal_req, 0, sizeof(*hal_req)); + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_MASTER_PREF: + hal_req->master_pref = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_CLUSTER_LOW: + hal_req->cluster_low = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_CLUSTER_HIGH: + hal_req->cluster_high = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUPPORT_5G_VAL: + hal_req->support_5g_val = nla_get_u8(iter); + hal_req->config_support_5g = 1; + break; + + case NAN_REQ_ATTR_SID_BEACON_VAL: + hal_req->sid_beacon_val = nla_get_u8(iter); + hal_req->config_sid_beacon = 1; + break; + + case NAN_REQ_ATTR_RSSI_CLOSE_2G4_VAL: + hal_req->rssi_close_2dot4g_val = nla_get_u8(iter); + hal_req->config_2dot4g_rssi_close = 1; + break; + + case NAN_REQ_ATTR_RSSI_MIDDLE_2G4_VAL: + hal_req->rssi_middle_2dot4g_val = nla_get_u8(iter); + hal_req->config_2dot4g_rssi_middle = 1; + break; + + case NAN_REQ_ATTR_RSSI_PROXIMITY_2G4_VAL: + hal_req->rssi_proximity_2dot4g_val = nla_get_u8(iter); + hal_req->rssi_proximity_2dot4g_val = 1; + break; + + case NAN_REQ_ATTR_HOP_COUNT_LIMIT_VAL: + hal_req->hop_count_limit_val = nla_get_u8(iter); + hal_req->config_hop_count_limit = 1; + break; + + case NAN_REQ_ATTR_SUPPORT_2G4_VAL: + hal_req->support_2dot4g_val = nla_get_u8(iter); + hal_req->config_2dot4g_support = 1; + break; + + case NAN_REQ_ATTR_BEACONS_2G4_VAL: + hal_req->beacon_2dot4g_val = nla_get_u8(iter); + hal_req->config_2dot4g_beacons = 1; + break; + + case NAN_REQ_ATTR_SDF_2G4_VAL: + hal_req->sdf_2dot4g_val = nla_get_u8(iter); + hal_req->config_2dot4g_sdf = 1; + break; + + case NAN_REQ_ATTR_BEACON_5G_VAL: + hal_req->beacon_5g_val = nla_get_u8(iter); + hal_req->config_5g_beacons = 1; + break; + + case NAN_REQ_ATTR_SDF_5G_VAL: + hal_req->sdf_5g_val = nla_get_u8(iter); + hal_req->config_5g_sdf = 1; + break; + + case NAN_REQ_ATTR_RSSI_CLOSE_5G_VAL: + hal_req->rssi_close_5g_val = nla_get_u8(iter); + hal_req->config_5g_rssi_close = 1; + break; + + case NAN_REQ_ATTR_RSSI_MIDDLE_5G_VAL: + hal_req->rssi_middle_5g_val = nla_get_u8(iter); + hal_req->config_5g_rssi_middle = 1; + break; + + case NAN_REQ_ATTR_RSSI_CLOSE_PROXIMITY_5G_VAL: + hal_req->rssi_close_proximity_5g_val = nla_get_u8(iter); + hal_req->config_5g_rssi_close_proximity = 1; + break; + + case NAN_REQ_ATTR_RSSI_WINDOW_SIZE_VAL: + hal_req->rssi_window_size_val = nla_get_u8(iter); + hal_req->config_rssi_window_size = 1; + break; + + case NAN_REQ_ATTR_OUI_VAL: + hal_req->oui_val = nla_get_u32(iter); + hal_req->config_oui = 1; + break; + + case NAN_REQ_ATTR_MAC_ADDR_VAL: + memcpy(hal_req->intf_addr_val, nla_data(iter), ETH_ALEN); + hal_req->config_intf_addr = 1; + break; + + case NAN_REQ_ATTR_CLUSTER_VAL: + hal_req->config_cluster_attribute_val = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SOCIAL_CH_SCAN_DWELL_TIME: + memcpy(hal_req->scan_params_val.dwell_time, nla_data(iter), + sizeof(hal_req->scan_params_val.dwell_time)); + hal_req->config_scan_params = 1; + break; + + case NAN_REQ_ATTR_SOCIAL_CH_SCAN_PERIOD: + memcpy(hal_req->scan_params_val.scan_period, nla_data(iter), + sizeof(hal_req->scan_params_val.scan_period)); + hal_req->config_scan_params = 1; + break; + + case NAN_REQ_ATTR_RANDOM_FACTOR_FORCE_VAL: + hal_req->random_factor_force_val = nla_get_u8(iter); + hal_req->config_random_factor_force = 1; + break; + + case NAN_REQ_ATTR_HOP_COUNT_FORCE_VAL: + hal_req->hop_count_force_val = nla_get_u8(iter); + hal_req->config_hop_count_force = 1; + break; + + case NAN_REQ_ATTR_CHANNEL_2G4_MHZ_VAL: + hal_req->channel_24g_val = nla_get_u32(iter); + hal_req->config_24g_channel = 1; + break; + + case NAN_REQ_ATTR_CHANNEL_5G_MHZ_VAL: + hal_req->channel_5g_val = nla_get_u8(iter); + hal_req->config_5g_channel = 1; + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SID_BEACON_VAL: + hal_req->subscribe_sid_beacon_val = nla_get_u8(iter); + hal_req->config_subscribe_sid_beacon = 1; + break; + + case NAN_REQ_ATTR_DW_2G4_INTERVAL: + hal_req->dw_2dot4g_interval_val = nla_get_u8(iter); + /* valid range for 2.4G is 1-5 */ + if (hal_req->dw_2dot4g_interval_val > 0 && hal_req->dw_2dot4g_interval_val < 5) + hal_req->config_2dot4g_dw_band = 1; + break; + + case NAN_REQ_ATTR_DW_5G_INTERVAL: + hal_req->dw_5g_interval_val = nla_get_u8(iter); + /* valid range for 5g is 0-5 */ + if (hal_req->dw_5g_interval_val < 5) + hal_req->config_5g_dw_band = 1; + break; + + case NAN_REQ_ATTR_DISC_MAC_ADDR_RANDOM_INTERVAL: + hal_req->disc_mac_addr_rand_interval_sec = nla_get_u8(iter); + break; + + default: + SLSI_ERR(sdev, "Unexpected NAN enable attribute TYPE:%d\n", type); + return SLSI_HAL_NAN_STATUS_INVALID_PARAM; + } + } + return SLSI_HAL_NAN_STATUS_SUCCESS; +} + +int slsi_nan_enable(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct slsi_hal_nan_enable_req hal_req; + int ret; + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + u8 nan_vif_mac_address[ETH_ALEN]; + u8 broadcast_mac[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + u32 reply_status = SLSI_HAL_NAN_STATUS_SUCCESS; + + if (!dev) { + SLSI_ERR(sdev, "No NAN interface\n"); + ret = -ENOTSUPP; + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + goto exit; + } + + if (!slsi_dev_nan_supported(sdev)) { + SLSI_ERR(sdev, "NAN not allowed(mib:%d)\n", sdev->nan_enabled); + ret = WIFI_HAL_ERROR_NOT_SUPPORTED; + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + goto exit; + } + + ndev_vif = netdev_priv(dev); + + reply_status = slsi_nan_enable_get_nl_params(sdev, &hal_req, data, len); + if (reply_status != SLSI_HAL_NAN_STATUS_SUCCESS) { + ret = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (ndev_vif->activated) { + ret = -EINVAL; + SLSI_DBG1(sdev, SLSI_GSCAN, "Already Enabled. Req Rejected\n"); + goto exit_with_mutex; + } + ndev_vif->vif_type = FAPI_VIFTYPE_NAN; + + if (hal_req.config_intf_addr) + ether_addr_copy(nan_vif_mac_address, hal_req.intf_addr_val); + else + slsi_nan_get_mac(sdev, nan_vif_mac_address); + + ret = slsi_mlme_add_vif(sdev, dev, nan_vif_mac_address, broadcast_mac); + if (ret) { + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + SLSI_ERR(sdev, "failed to set unsync vif. Cannot start NAN\n"); + } else { + ret = slsi_mlme_nan_enable(sdev, dev, &hal_req); + if (ret) { + SLSI_ERR(sdev, "failed to enable NAN.\n"); + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + slsi_mlme_del_vif(sdev, dev); + ndev_vif->activated = false; + ndev_vif->nan.subscribe_id_map = 0; + ndev_vif->nan.publish_id_map = 0; + } else { + slsi_vif_activated(sdev, dev); + } + } + +exit_with_mutex: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_ENABLED, 0, NULL); + return ret; +} + +int slsi_nan_disable(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + + if (dev) { + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (ndev_vif->activated) { + slsi_mlme_del_vif(sdev, dev); + ndev_vif->activated = false; + ndev_vif->nan.subscribe_id_map = 0; + ndev_vif->nan.publish_id_map = 0; + } else { + SLSI_WARN(sdev, "NAN FWif not active!!"); + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } else { + SLSI_WARN(sdev, "No NAN interface!!"); + } + + slsi_vendor_nan_command_reply(wiphy, SLSI_HAL_NAN_STATUS_SUCCESS, 0, NAN_RESPONSE_DISABLED, 0, NULL); + + return 0; +} + +static int slsi_nan_publish_get_nl_params(struct slsi_dev *sdev, struct slsi_hal_nan_publish_req *hal_req, + const void *data, int len) +{ + int type, tmp, r; + const struct nlattr *iter; + + memset(hal_req, 0, sizeof(*hal_req)); + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_PUBLISH_ID: + hal_req->publish_id = nla_get_u16(iter); + break; + case NAN_REQ_ATTR_PUBLISH_TTL: + hal_req->ttl = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_PERIOD: + hal_req->period = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_TYPE: + hal_req->publish_type = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_TX_TYPE: + hal_req->tx_type = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_COUNT: + hal_req->publish_count = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SERVICE_NAME_LEN: + hal_req->service_name_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SERVICE_NAME: + memcpy(hal_req->service_name, nla_data(iter), hal_req->service_name_len); + break; + + case NAN_REQ_ATTR_PUBLISH_MATCH_ALGO: + hal_req->publish_match_indicator = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SERVICE_INFO_LEN: + hal_req->service_specific_info_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SERVICE_INFO: + memcpy(hal_req->service_specific_info, nla_data(iter), hal_req->service_specific_info_len); + break; + + case NAN_REQ_ATTR_PUBLISH_RX_MATCH_FILTER_LEN: + hal_req->rx_match_filter_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_RX_MATCH_FILTER: + memcpy(hal_req->rx_match_filter, nla_data(iter), hal_req->rx_match_filter_len); + break; + + case NAN_REQ_ATTR_PUBLISH_TX_MATCH_FILTER_LEN: + hal_req->tx_match_filter_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_TX_MATCH_FILTER: + memcpy(hal_req->tx_match_filter, nla_data(iter), hal_req->tx_match_filter_len); + break; + + case NAN_REQ_ATTR_PUBLISH_RSSI_THRESHOLD_FLAG: + hal_req->rssi_threshold_flag = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_CONN_MAP: + hal_req->connmap = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_RECV_IND_CFG: + hal_req->recv_indication_cfg = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SDEA_LEN: + hal_req->sdea_service_specific_info_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SDEA: + memcpy(hal_req->sdea_service_specific_info, nla_data(iter), + hal_req->sdea_service_specific_info_len); + break; + + case NAN_REQ_ATTR_RANGING_AUTO_RESPONSE: + hal_req->ranging_auto_response = nla_get_u8(iter); + break; + + default: + r = slsi_nan_get_sdea_params_nl(sdev, &hal_req->sdea_params, iter, type); + if (r) + r = slsi_nan_get_ranging_cfg_nl(sdev, &hal_req->ranging_cfg, iter, type); + if (r) + r = slsi_nan_get_security_info_nl(sdev, &hal_req->sec_info, iter, type); + if (r) + r = slsi_nan_get_range_resp_cfg_nl(sdev, &hal_req->range_response_cfg, iter, type); + if (r) { + SLSI_ERR(sdev, "Unexpected NAN publish attribute TYPE:%d\n", type); + return SLSI_HAL_NAN_STATUS_INVALID_PARAM; + } + } + } + return SLSI_HAL_NAN_STATUS_SUCCESS; +} + +int slsi_nan_publish(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct slsi_hal_nan_publish_req *hal_req; + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + int ret; + u32 reply_status; + u32 publish_id = 0; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!\n"); + ret = -EINVAL; + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + goto exit; + } + + hal_req = kmalloc(sizeof(*hal_req), GFP_KERNEL); + if (!hal_req) { + SLSI_ERR(sdev, "failed to alloc hal_req\n"); + reply_status = SLSI_HAL_NAN_STATUS_NO_RESOURCE_AVAILABLE; + ret = -ENOMEM; + goto exit; + } + + ndev_vif = netdev_priv(dev); + reply_status = slsi_nan_publish_get_nl_params(sdev, hal_req, data, len); + if (reply_status != SLSI_HAL_NAN_STATUS_SUCCESS) { + kfree(hal_req); + ret = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_WARN(sdev, "NAN vif not activated\n"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + goto exit_with_lock; + } + + if (!hal_req->publish_id) { + hal_req->publish_id = slsi_nan_get_new_publish_id(ndev_vif); + } else if (!slsi_nan_is_publish_id_active(ndev_vif, hal_req->publish_id)) { + SLSI_WARN(sdev, "Publish id %d not found. map:%x\n", hal_req->publish_id, + ndev_vif->nan.publish_id_map); + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; + ret = -EINVAL; + goto exit_with_lock; + } + + if (hal_req->publish_id) { + ret = slsi_mlme_nan_publish(sdev, dev, hal_req, hal_req->publish_id); + if (ret) + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + else + publish_id = hal_req->publish_id; + } else { + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; + SLSI_WARN(sdev, "Too Many concurrent PUBLISH REQ(map:%x)\n", + ndev_vif->nan.publish_id_map); + ret = -ENOTSUPP; + } +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + kfree(hal_req); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_PUBLISH, publish_id, NULL); + return ret; +} + +int slsi_nan_publish_cancel(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + int type, tmp, ret = 0; + u16 publish_id = 0; + const struct nlattr *iter; + u32 reply_status = SLSI_HAL_NAN_STATUS_SUCCESS; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = -EINVAL; + goto exit; + } + + ndev_vif = netdev_priv(dev); + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_PUBLISH_ID: + publish_id = nla_get_u16(iter); + break; + default: + SLSI_ERR(sdev, "Unexpected NAN publishcancel attribute TYPE:%d\n", type); + } + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + goto exit_with_lock; + } + if (!publish_id || !slsi_nan_is_publish_id_active(ndev_vif, publish_id)) { + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; + SLSI_WARN(sdev, "Publish_id(%d) not active. map:%x\n", + publish_id, ndev_vif->nan.publish_id_map); + } else { + ret = slsi_mlme_nan_publish(sdev, dev, NULL, publish_id); + if (ret) + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + } +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_PUBLISH_CANCEL, publish_id, NULL); + return ret; +} + +static int slsi_nan_subscribe_get_nl_params(struct slsi_dev *sdev, struct slsi_hal_nan_subscribe_req *hal_req, + const void *data, int len) +{ + int type, tmp, r; + const struct nlattr *iter; + + memset(hal_req, 0, sizeof(*hal_req)); + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_SUBSCRIBE_ID: + hal_req->subscribe_id = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_TTL: + hal_req->ttl = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_PERIOD: + hal_req->period = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_TYPE: + hal_req->subscribe_type = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_RESP_FILTER_TYPE: + hal_req->service_response_filter = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_RESP_INCLUDE: + hal_req->service_response_include = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_USE_RESP_FILTER: + hal_req->use_service_response_filter = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SSI_REQUIRED: + hal_req->ssi_required_for_match_indication = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_MATCH_INDICATOR: + hal_req->subscribe_match_indicator = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_COUNT: + hal_req->subscribe_count = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SERVICE_NAME_LEN: + hal_req->service_name_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SERVICE_NAME: + memcpy(hal_req->service_name, nla_data(iter), hal_req->service_name_len); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SERVICE_INFO_LEN: + hal_req->service_specific_info_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SERVICE_INFO: + memcpy(hal_req->service_specific_info, nla_data(iter), hal_req->service_specific_info_len); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_RX_MATCH_FILTER_LEN: + hal_req->rx_match_filter_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_RX_MATCH_FILTER: + memcpy(hal_req->rx_match_filter, nla_data(iter), hal_req->rx_match_filter_len); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_TX_MATCH_FILTER_LEN: + hal_req->tx_match_filter_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_TX_MATCH_FILTER: + memcpy(hal_req->tx_match_filter, nla_data(iter), hal_req->tx_match_filter_len); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_RSSI_THRESHOLD_FLAG: + hal_req->rssi_threshold_flag = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_CONN_MAP: + hal_req->connmap = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_NUM_INTF_ADDR_PRESENT: + hal_req->num_intf_addr_present = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_INTF_ADDR: + memcpy(hal_req->intf_addr, nla_data(iter), hal_req->num_intf_addr_present * ETH_ALEN); + break; + + case NAN_REQ_ATTR_SUBSCRIBE_RECV_IND_CFG: + hal_req->recv_indication_cfg = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SDEA_LEN: + hal_req->sdea_service_specific_info_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SDEA: + memcpy(hal_req->sdea_service_specific_info, nla_data(iter), + hal_req->sdea_service_specific_info_len); + break; + + case NAN_REQ_ATTR_RANGING_AUTO_RESPONSE: + hal_req->ranging_auto_response = nla_get_u8(iter); + break; + + default: + r = slsi_nan_get_sdea_params_nl(sdev, &hal_req->sdea_params, iter, type); + if (r) + r = slsi_nan_get_ranging_cfg_nl(sdev, &hal_req->ranging_cfg, iter, type); + if (r) + r = slsi_nan_get_security_info_nl(sdev, &hal_req->sec_info, iter, type); + if (r) + r = slsi_nan_get_range_resp_cfg_nl(sdev, &hal_req->range_response_cfg, iter, type); + if (r) { + SLSI_ERR(sdev, "Unexpected NAN subscribe attribute TYPE:%d\n", type); + return SLSI_HAL_NAN_STATUS_INVALID_PARAM; + } + } + } + return SLSI_HAL_NAN_STATUS_SUCCESS; +} + +int slsi_nan_subscribe(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + struct slsi_hal_nan_subscribe_req *hal_req; + int ret; + u32 reply_status; + u32 subscribe_id = 0; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!\n"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = -EINVAL; + goto exit; + } + + hal_req = kmalloc(sizeof(*hal_req), GFP_KERNEL); + if (!hal_req) { + SLSI_ERR(sdev, "Failed to alloc hal_req structure!!!\n"); + reply_status = SLSI_HAL_NAN_STATUS_NO_RESOURCE_AVAILABLE; + ret = -ENOMEM; + goto exit; + } + + ndev_vif = netdev_priv(dev); + reply_status = slsi_nan_subscribe_get_nl_params(sdev, hal_req, data, len); + if (reply_status != SLSI_HAL_NAN_STATUS_SUCCESS) { + kfree(hal_req); + ret = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + SLSI_WARN(sdev, "NAN vif not activated\n"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + goto exit_with_lock; + } + + if (!hal_req->subscribe_id) { + hal_req->subscribe_id = slsi_nan_get_new_subscribe_id(ndev_vif); + } else if (!slsi_nan_is_subscribe_id_active(ndev_vif, hal_req->subscribe_id)) { + SLSI_WARN(sdev, "Subscribe id %d not found. map:%x\n", hal_req->subscribe_id, + ndev_vif->nan.subscribe_id_map); + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; + ret = -EINVAL; + goto exit_with_lock; + } + + ret = slsi_mlme_nan_subscribe(sdev, dev, hal_req, hal_req->subscribe_id); + if (ret) + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + else + subscribe_id = hal_req->subscribe_id; + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + kfree(hal_req); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_SUBSCRIBE, subscribe_id, NULL); + return ret; +} + +int slsi_nan_subscribe_cancel(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + int type, tmp, ret = WIFI_HAL_ERROR_UNKNOWN; + u16 subscribe_id = 0; + const struct nlattr *iter; + u32 reply_status = SLSI_HAL_NAN_STATUS_SUCCESS; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + goto exit; + } + + ndev_vif = netdev_priv(dev); + + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_SUBSCRIBE_ID: + subscribe_id = nla_get_u16(iter); + break; + default: + SLSI_ERR(sdev, "Unexpected NAN subscribecancel attribute TYPE:%d\n", type); + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PARAM; + goto exit; + } + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (ndev_vif->activated) { + if (!subscribe_id || !slsi_nan_is_subscribe_id_active(ndev_vif, subscribe_id)) { + SLSI_WARN(sdev, "subscribe_id(%d) not active. map:%x\n", + subscribe_id, ndev_vif->nan.subscribe_id_map); + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; + } else { + ret = slsi_mlme_nan_subscribe(sdev, dev, NULL, subscribe_id); + if (ret) + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + } + } else { + SLSI_ERR(sdev, "vif not activated\n"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_SUBSCRIBE_CANCEL, subscribe_id, NULL); + return ret; +} + +static int slsi_nan_followup_get_nl_params(struct slsi_dev *sdev, struct slsi_hal_nan_transmit_followup_req *hal_req, + const void *data, int len) +{ + int type, tmp; + const struct nlattr *iter; + + memset(hal_req, 0, sizeof(*hal_req)); + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_FOLLOWUP_ID: + hal_req->publish_subscribe_id = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_FOLLOWUP_REQUESTOR_ID: + hal_req->requestor_instance_id = nla_get_u32(iter); + break; + + case NAN_REQ_ATTR_FOLLOWUP_ADDR: + memcpy(hal_req->addr, nla_data(iter), ETH_ALEN); + break; + + case NAN_REQ_ATTR_FOLLOWUP_PRIORITY: + hal_req->priority = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_FOLLOWUP_TX_WINDOW: + hal_req->dw_or_faw = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_FOLLOWUP_SERVICE_NAME_LEN: + hal_req->service_specific_info_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_FOLLOWUP_SERVICE_NAME: + memcpy(hal_req->service_specific_info, nla_data(iter), hal_req->service_specific_info_len); + break; + + case NAN_REQ_ATTR_FOLLOWUP_RECV_IND_CFG: + hal_req->recv_indication_cfg = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SDEA_LEN: + hal_req->sdea_service_specific_info_len = nla_get_u16(iter); + break; + + case NAN_REQ_ATTR_PUBLISH_SDEA: + memcpy(hal_req->sdea_service_specific_info, nla_data(iter), + hal_req->sdea_service_specific_info_len); + break; + + default: + SLSI_ERR(sdev, "Unexpected NAN followup attribute TYPE:%d\n", type); + return SLSI_HAL_NAN_STATUS_INVALID_PARAM; + } + } + return SLSI_HAL_NAN_STATUS_SUCCESS; +} + +int slsi_nan_transmit_followup(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + struct slsi_hal_nan_transmit_followup_req hal_req; + int ret; + u32 reply_status = SLSI_HAL_NAN_STATUS_SUCCESS; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!"); + ret = -EINVAL; + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + goto exit; + } + + ndev_vif = netdev_priv(dev); + reply_status = slsi_nan_followup_get_nl_params(sdev, &hal_req, data, len); + if (reply_status) { + ret = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + SLSI_WARN(sdev, "NAN vif not activated\n"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + goto exit_with_lock; + } + + if (!hal_req.publish_subscribe_id || + !(slsi_nan_is_subscribe_id_active(ndev_vif, hal_req.publish_subscribe_id) || + slsi_nan_is_publish_id_active(ndev_vif, hal_req.publish_subscribe_id))) { + SLSI_WARN(sdev, "publish/Subscribe id %d not found. map:%x\n", hal_req.publish_subscribe_id, + ndev_vif->nan.subscribe_id_map); + reply_status = SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID; + ret = -EINVAL; + goto exit_with_lock; + } + + ret = slsi_mlme_nan_tx_followup(sdev, dev, &hal_req); + if (ret) + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_TRANSMIT_FOLLOWUP, 0, NULL); + return ret; +} + +static int slsi_nan_config_get_nl_params(struct slsi_dev *sdev, struct slsi_hal_nan_config_req *hal_req, + const void *data, int len) +{ + int type, type1, tmp, tmp1, disc_attr_idx = 0, famchan_idx = 0; + const struct nlattr *iter, *iter1; + struct slsi_hal_nan_post_discovery_param *disc_attr; + struct slsi_hal_nan_further_availability_channel *famchan; + + memset(hal_req, 0, sizeof(*hal_req)); + nla_for_each_attr(iter, data, len, tmp) { + type = nla_type(iter); + switch (type) { + case NAN_REQ_ATTR_SID_BEACON_VAL: + hal_req->sid_beacon = nla_get_u8(iter); + hal_req->config_sid_beacon = 1; + break; + + case NAN_REQ_ATTR_RSSI_PROXIMITY_2G4_VAL: + hal_req->rssi_proximity = nla_get_u8(iter); + hal_req->config_rssi_proximity = 1; + break; + + case NAN_REQ_ATTR_MASTER_PREF: + hal_req->master_pref = nla_get_u8(iter); + hal_req->config_master_pref = 1; + break; + + case NAN_REQ_ATTR_RSSI_CLOSE_PROXIMITY_5G_VAL: + hal_req->rssi_close_proximity_5g_val = nla_get_u8(iter); + hal_req->config_5g_rssi_close_proximity = 1; + break; + + case NAN_REQ_ATTR_RSSI_WINDOW_SIZE_VAL: + hal_req->rssi_window_size_val = nla_get_u8(iter); + hal_req->config_rssi_window_size = 1; + break; + + case NAN_REQ_ATTR_CLUSTER_VAL: + hal_req->config_cluster_attribute_val = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_SOCIAL_CH_SCAN_DWELL_TIME: + memcpy(hal_req->scan_params_val.dwell_time, nla_data(iter), + sizeof(hal_req->scan_params_val.dwell_time)); + hal_req->config_scan_params = 1; + break; + + case NAN_REQ_ATTR_SOCIAL_CH_SCAN_PERIOD: + memcpy(hal_req->scan_params_val.scan_period, nla_data(iter), + sizeof(hal_req->scan_params_val.scan_period)); + hal_req->config_scan_params = 1; + break; + + case NAN_REQ_ATTR_RANDOM_FACTOR_FORCE_VAL: + hal_req->random_factor_force_val = nla_get_u8(iter); + hal_req->config_random_factor_force = 1; + break; + + case NAN_REQ_ATTR_HOP_COUNT_FORCE_VAL: + hal_req->hop_count_force_val = nla_get_u8(iter); + hal_req->config_hop_count_force = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_PAYLOAD_TX: + hal_req->conn_capability_val.payload_transmit_flag = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_WFD: + hal_req->conn_capability_val.is_wfd_supported = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_WFDS: + hal_req->conn_capability_val.is_wfds_supported = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_TDLS: + hal_req->conn_capability_val.is_tdls_supported = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_MESH: + hal_req->conn_capability_val.is_mesh_supported = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_IBSS: + hal_req->conn_capability_val.is_ibss_supported = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_CONN_CAPABILITY_WLAN_INFRA: + hal_req->conn_capability_val.wlan_infra_field = nla_get_u8(iter); + hal_req->config_conn_capability = 1; + break; + + case NAN_REQ_ATTR_DISCOVERY_ATTR_NUM_ENTRIES: + hal_req->num_config_discovery_attr = nla_get_u8(iter); + break; + + case NAN_REQ_ATTR_DISCOVERY_ATTR_VAL: + if (disc_attr_idx >= hal_req->num_config_discovery_attr) { + SLSI_ERR(sdev, + "disc attr(%d) > num disc attr(%d)\n", + disc_attr_idx + 1, hal_req->num_config_discovery_attr); + return -EINVAL; + } + disc_attr = &hal_req->discovery_attr_val[disc_attr_idx]; + disc_attr_idx++; + nla_for_each_nested(iter1, iter, tmp1) { + type1 = nla_type(iter1); + switch (type1) { + case NAN_REQ_ATTR_CONN_TYPE: + disc_attr->type = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_NAN_ROLE: + disc_attr->role = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_TRANSMIT_FREQ: + disc_attr->transmit_freq = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_AVAILABILITY_DURATION: + disc_attr->duration = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_AVAILABILITY_INTERVAL: + disc_attr->avail_interval_bitmap = nla_get_u32(iter1); + break; + + case NAN_REQ_ATTR_MAC_ADDR_VAL: + memcpy(disc_attr->addr, nla_data(iter1), ETH_ALEN); + break; + + case NAN_REQ_ATTR_MESH_ID_LEN: + disc_attr->mesh_id_len = nla_get_u16(iter1); + break; + + case NAN_REQ_ATTR_MESH_ID: + memcpy(disc_attr->mesh_id, nla_data(iter1), disc_attr->mesh_id_len); + break; + + case NAN_REQ_ATTR_INFRASTRUCTURE_SSID_LEN: + disc_attr->infrastructure_ssid_len = nla_get_u16(iter1); + break; + + case NAN_REQ_ATTR_INFRASTRUCTURE_SSID: + memcpy(disc_attr->infrastructure_ssid_val, nla_data(iter1), + disc_attr->infrastructure_ssid_len); + break; + } + } + break; + + case NAN_REQ_ATTR_FURTHER_AVAIL_NUM_ENTRIES: + hal_req->fam_val.numchans = nla_get_u8(iter); + hal_req->config_fam = 1; + break; + + case NAN_REQ_ATTR_FURTHER_AVAIL_VAL: + hal_req->config_fam = 1; + if (famchan_idx >= hal_req->fam_val.numchans) { + SLSI_ERR(sdev, + "famchan attr(%d) > numchans(%d)\n", + famchan_idx + 1, hal_req->fam_val.numchans); + return -EINVAL; + } + famchan = &hal_req->fam_val.famchan[famchan_idx]; + famchan_idx++; + nla_for_each_nested(iter1, iter, tmp1) { + type1 = nla_type(iter1); + switch (type1) { + case NAN_REQ_ATTR_FURTHER_AVAIL_ENTRY_CTRL: + famchan->entry_control = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_FURTHER_AVAIL_CHAN_CLASS: + famchan->class_val = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_FURTHER_AVAIL_CHAN: + famchan->channel = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_FURTHER_AVAIL_CHAN_MAPID: + famchan->mapid = nla_get_u8(iter1); + break; + + case NAN_REQ_ATTR_FURTHER_AVAIL_INTERVAL_BITMAP: + famchan->avail_interval_bitmap = nla_get_u32(iter1); + break; + } + } + break; + + case NAN_REQ_ATTR_SUBSCRIBE_SID_BEACON_VAL: + hal_req->subscribe_sid_beacon_val = nla_get_u8(iter); + hal_req->config_subscribe_sid_beacon = 1; + break; + + case NAN_REQ_ATTR_DW_2G4_INTERVAL: + hal_req->dw_2dot4g_interval_val = nla_get_u8(iter); + /* valid range for 2.4G is 1-5 */ + if (hal_req->dw_2dot4g_interval_val > 0 && hal_req->dw_2dot4g_interval_val < 6) + hal_req->config_2dot4g_dw_band = 1; + break; + + case NAN_REQ_ATTR_DW_5G_INTERVAL: + hal_req->dw_5g_interval_val = nla_get_u8(iter); + /* valid range for 5g is 0-5 */ + if (hal_req->dw_5g_interval_val < 6) + hal_req->config_5g_dw_band = 1; + break; + + case NAN_REQ_ATTR_DISC_MAC_ADDR_RANDOM_INTERVAL: + hal_req->disc_mac_addr_rand_interval_sec = nla_get_u8(iter); + break; + + default: + SLSI_ERR(sdev, "Unexpected NAN config attribute TYPE:%d\n", type); + return SLSI_HAL_NAN_STATUS_INVALID_PARAM; + } + } + return SLSI_HAL_NAN_STATUS_SUCCESS; +} + +int slsi_nan_set_config(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + struct slsi_hal_nan_config_req hal_req; + int ret; + u32 reply_status = SLSI_HAL_NAN_STATUS_SUCCESS; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!"); + ret = -EINVAL; + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + goto exit; + } + + ndev_vif = netdev_priv(dev); + reply_status = slsi_nan_config_get_nl_params(sdev, &hal_req, data, len); + if (reply_status) { + ret = -EINVAL; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + SLSI_WARN(sdev, "NAN vif not activated\n"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = WIFI_HAL_ERROR_NOT_AVAILABLE; + } else { + ret = slsi_mlme_nan_set_config(sdev, dev, &hal_req); + if (ret) + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_CONFIG, 0, NULL); + return ret; +} + +int slsi_nan_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) +{ + struct slsi_dev *sdev = SDEV_FROM_WIPHY(wiphy); + struct net_device *dev = slsi_nan_get_netdev(sdev); + struct netdev_vif *ndev_vif; + u32 reply_status = SLSI_HAL_NAN_STATUS_SUCCESS; + struct slsi_hal_nan_capabilities nan_capabilities; + int ret = 0, i; + struct slsi_mib_value *values = NULL; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_NAN_MAX_CONCURRENT_CLUSTERS, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_CONCURRENT_PUBLISHES, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_CONCURRENT_SUBSCRIBES, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_SERVICE_NAME_LENGTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_MATCH_FILTER_LENGTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_TOTAL_MATCH_FILTER_LENGTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_SERVICE_SPECIFIC_INFO_LENGTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_VSA_DATA_LENGTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_MESH_DATA_LENGTH, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_NDI_INTERFACES, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_NDP_SESSIONS, { 0, 0 } }, + { SLSI_PSID_UNIFI_NAN_MAX_APP_INFO_LENGTH, { 0, 0 } } }; + u32 *capabilities_mib_val[] = { &nan_capabilities.max_concurrent_nan_clusters, + &nan_capabilities.max_publishes, + &nan_capabilities.max_subscribes, + &nan_capabilities.max_service_name_len, + &nan_capabilities.max_match_filter_len, + &nan_capabilities.max_total_match_filter_len, + &nan_capabilities.max_service_specific_info_len, + &nan_capabilities.max_vsa_data_len, + &nan_capabilities.max_mesh_data_len, + &nan_capabilities.max_ndi_interfaces, + &nan_capabilities.max_ndp_sessions, + &nan_capabilities.max_app_info_len }; + + if (!dev) { + SLSI_ERR(sdev, "NAN netif not active!!"); + reply_status = SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED; + ret = -EINVAL; + goto exit; + } + + ndev_vif = netdev_priv(dev); + + /* Expect each mib length in response is 11 */ + mibrsp.dataLength = 11 * ARRAY_SIZE(get_values); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) { + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + reply_status = SLSI_HAL_NAN_STATUS_NO_RESOURCE_AVAILABLE; + ret = -ENOMEM; + goto exit; + } + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + values = slsi_read_mibs(sdev, NULL, get_values, ARRAY_SIZE(get_values), &mibrsp); + if (!values) { + ret = 0xFFFFFFFF; + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + goto exit_with_mibrsp; + } + + for (i = 0; i < ARRAY_SIZE(get_values); i++) { + if (values[i].type == SLSI_MIB_TYPE_UINT) { + *capabilities_mib_val[i] = values[i].u.uintValue; + SLSI_DBG2(sdev, SLSI_GSCAN, "MIB value = %ud\n", *capabilities_mib_val[i]); + } else { + SLSI_ERR(sdev, "invalid type(%d). iter:%d\n", values[i].type, i); + ret = 0xFFFFFFFF; + reply_status = SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE; + *capabilities_mib_val[i] = 0; + } + } + + kfree(values); +exit_with_mibrsp: + kfree(mibrsp.data); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +exit: + slsi_vendor_nan_command_reply(wiphy, reply_status, ret, NAN_RESPONSE_GET_CAPABILITIES, 0, &nan_capabilities); + return ret; +} + +void slsi_nan_event(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct sk_buff *nl_skb = NULL; + int res = 0; + u16 event, identifier, evt_reason; + u8 *mac_addr; + u16 hal_event; + struct netdev_vif *ndev_vif; + enum slsi_nan_disc_event_type disc_event_type = 0; + + ndev_vif = netdev_priv(dev); + event = fapi_get_u16(skb, u.mlme_nan_event_ind.event); + identifier = fapi_get_u16(skb, u.mlme_nan_event_ind.identifier); + mac_addr = fapi_get_buff(skb, u.mlme_nan_event_ind.address_or_identifier); + evt_reason = fapi_get_u16(skb, u.mlme_nan_event_ind.reason_code); + + switch (event) { + case FAPI_EVENT_WIFI_EVENT_NAN_PUBLISH_TERMINATED: + hal_event = SLSI_NL80211_NAN_PUBLISH_TERMINATED_EVENT; + break; + case FAPI_EVENT_WIFI_EVENT_NAN_MATCH_EXPIRED: + hal_event = SLSI_NL80211_NAN_MATCH_EXPIRED_EVENT; + break; + case FAPI_EVENT_WIFI_EVENT_NAN_SUBSCRIBE_TERMINATED: + hal_event = SLSI_NL80211_NAN_SUBSCRIBE_TERMINATED_EVENT; + break; + case FAPI_EVENT_WIFI_EVENT_NAN_ADDRESS_CHANGED: + disc_event_type = NAN_EVENT_ID_DISC_MAC_ADDR; + hal_event = SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT; + break; + case FAPI_EVENT_WIFI_EVENT_NAN_CLUSTER_STARTED: + disc_event_type = NAN_EVENT_ID_STARTED_CLUSTER; + hal_event = SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT; + break; + case FAPI_EVENT_WIFI_EVENT_NAN_CLUSTER_JOINED: + disc_event_type = NAN_EVENT_ID_JOINED_CLUSTER; + hal_event = SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT; + break; + default: + return; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + SLSI_DBG1_NODEV(SLSI_GSCAN, "Event: %s(%d)\n", + slsi_print_event_name(hal_event), hal_event); +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE, hal_event, GFP_KERNEL); +#else + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, hal_event, GFP_KERNEL); +#endif + if (!nl_skb) { + SLSI_ERR(sdev, "NO MEM for nl_skb!!!\n"); + return; + } + + switch (hal_event) { + case SLSI_NL80211_NAN_PUBLISH_TERMINATED_EVENT: + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_PUBLISH_ID, identifier); + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_PUBLISH_ID, evt_reason); + ndev_vif->nan.publish_id_map &= ~BIT(identifier); + break; + case SLSI_NL80211_NAN_MATCH_EXPIRED_EVENT: + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_MATCH_PUBLISH_SUBSCRIBE_ID, identifier); + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_MATCH_REQUESTOR_INSTANCE_ID, evt_reason); + break; + case SLSI_NL80211_NAN_SUBSCRIBE_TERMINATED_EVENT: + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_SUBSCRIBE_ID, identifier); + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_SUBSCRIBE_REASON, evt_reason); + ndev_vif->nan.subscribe_id_map &= ~BIT(identifier); + break; + case SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT: + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_DISCOVERY_ENGINE_EVT_TYPE, disc_event_type); + res |= nla_put(nl_skb, NAN_EVT_ATTR_DISCOVERY_ENGINE_MAC_ADDR, ETH_ALEN, mac_addr); + break; + } + + if (res) { + SLSI_ERR(sdev, "Error in nla_put*:%x\n", res); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + return; + } + + cfg80211_vendor_event(nl_skb, GFP_KERNEL); +} + +void slsi_nan_followup_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 tag_id, tag_len; + u8 *stitched_ie_p, *ptr; + int stitched_ie_len; + struct slsi_hal_nan_followup_ind *hal_evt; + struct sk_buff *nl_skb; + int res; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + stitched_ie_len = fapi_get_datalen(skb); /* max length of stitched_ie */ + if (!stitched_ie_len) { + SLSI_ERR(sdev, "mlme_nan_followup_ind no mbulk data\n"); + return; + } + stitched_ie_p = kmalloc(stitched_ie_len, GFP_KERNEL); + if (!stitched_ie_p) { + SLSI_ERR(sdev, "No memory for followup_ind fapi_data\n"); + return; + } + + hal_evt = kmalloc(sizeof(*hal_evt), GFP_KERNEL); + if (!hal_evt) { + SLSI_ERR(sdev, "No memory for followup_ind\n"); + kfree(stitched_ie_p); + return; + } + memset(hal_evt, 0, sizeof(*hal_evt)); + stitched_ie_len = slsi_nan_stitch_ie(fapi_get_data(skb), stitched_ie_len, 0x050b, stitched_ie_p); + if (!stitched_ie_len) { + SLSI_ERR(sdev, "No followup ind IE\n"); + kfree(hal_evt); + kfree(stitched_ie_p); + return; + } + hal_evt->publish_subscribe_id = fapi_get_u16(skb, u.mlme_nan_followup_ind.publish_subscribe_id); + hal_evt->requestor_instance_id = fapi_get_u16(skb, u.mlme_nan_followup_ind.peer_id); + ptr = stitched_ie_p + 7; /* 7 = ie_id(1), ie_len(1), oui(3) type/subtype(2)*/ + + ether_addr_copy(hal_evt->addr, ptr); + ptr += ETH_ALEN; + ptr += 1; /* skip priority */ + hal_evt->dw_or_faw = *ptr; + ptr += 1; + while (stitched_ie_len > (ptr - stitched_ie_p) + 4) { + tag_id = *(u16 *)ptr; + ptr += 2; + tag_len = *(u16 *)ptr; + ptr += 2; + if (stitched_ie_p[1] + 2 < (ptr - stitched_ie_p) + tag_len) { + SLSI_ERR(sdev, "TLV error\n"); + kfree(hal_evt); + return; + } + if (tag_id == SLSI_FAPI_NAN_SERVICE_SPECIFIC_INFO) { + hal_evt->service_specific_info_len = tag_len; + memcpy(hal_evt->service_specific_info, ptr, tag_len); + } else if (tag_id == SLSI_FAPI_NAN_SDEA) { + hal_evt->sdea_service_specific_info_len = tag_len; + memcpy(hal_evt->sdea_service_specific_info, ptr, tag_len); + } + ptr += tag_len; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + SLSI_DBG1_NODEV(SLSI_GSCAN, "Event: %s(%d)\n", + slsi_print_event_name(SLSI_NL80211_NAN_FOLLOWUP_EVENT), SLSI_NL80211_NAN_FOLLOWUP_EVENT); +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE, SLSI_NL80211_NAN_FOLLOWUP_EVENT, + GFP_KERNEL); +#else + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, SLSI_NL80211_NAN_FOLLOWUP_EVENT, + GFP_KERNEL); +#endif + + if (!nl_skb) { + SLSI_ERR(sdev, "NO MEM for nl_skb!!!\n"); + kfree(hal_evt); + kfree(stitched_ie_p); + return; + } + + res = nla_put_be16(nl_skb, NAN_EVT_ATTR_FOLLOWUP_PUBLISH_SUBSCRIBE_ID, + cpu_to_le16(hal_evt->publish_subscribe_id)); + res |= nla_put_be16(nl_skb, NAN_EVT_ATTR_FOLLOWUP_REQUESTOR_INSTANCE_ID, + cpu_to_le16(hal_evt->requestor_instance_id)); + res |= nla_put(nl_skb, NAN_EVT_ATTR_FOLLOWUP_ADDR, ETH_ALEN, hal_evt->addr); + res |= nla_put_u8(nl_skb, NAN_EVT_ATTR_FOLLOWUP_DW_OR_FAW, hal_evt->dw_or_faw); + res |= nla_put_u16(nl_skb, NAN_EVT_ATTR_FOLLOWUP_SERVICE_SPECIFIC_INFO_LEN, hal_evt->service_specific_info_len); + if (hal_evt->service_specific_info_len) + res |= nla_put(nl_skb, NAN_EVT_ATTR_FOLLOWUP_SERVICE_SPECIFIC_INFO, hal_evt->service_specific_info_len, + hal_evt->service_specific_info); + res |= nla_put_u16(nl_skb, NAN_EVT_ATTR_SDEA_LEN, hal_evt->sdea_service_specific_info_len); + if (hal_evt->sdea_service_specific_info_len) + res |= nla_put(nl_skb, NAN_EVT_ATTR_SDEA, hal_evt->sdea_service_specific_info_len, + hal_evt->sdea_service_specific_info); + + if (res) { + SLSI_ERR(sdev, "Error in nla_put*:%x\n", res); + kfree(hal_evt); + kfree(stitched_ie_p); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + return; + } + + cfg80211_vendor_event(nl_skb, GFP_KERNEL); + kfree(hal_evt); + kfree(stitched_ie_p); +} + +void slsi_nan_service_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 tag_id, tag_len; + u8 *stitched_ie_p, *ptr; + const u8 *ie_ptr; + int stitched_ie_len, ie_len; + struct slsi_hal_nan_match_ind *hal_evt; + struct sk_buff *nl_skb; + int res; + + SLSI_DBG3(sdev, SLSI_GSCAN, "\n"); + + stitched_ie_len = fapi_get_datalen(skb); /* max length of stitched_ie */ + if (!stitched_ie_len) { + SLSI_ERR(sdev, "mlme_nan_service_ind no mbulk data\n"); + return; + } + + stitched_ie_p = kmalloc(stitched_ie_len, GFP_KERNEL); + if (!stitched_ie_p) { + SLSI_ERR(sdev, "No memory for service_ind fapi_data\n"); + return; + } + + hal_evt = kmalloc(sizeof(*hal_evt), GFP_KERNEL); + if (!hal_evt) { + SLSI_ERR(sdev, "No memory for service_ind\n"); + kfree(stitched_ie_p); + return; + } + + memset(hal_evt, 0, sizeof(*hal_evt)); + stitched_ie_len = slsi_nan_stitch_ie(fapi_get_data(skb), stitched_ie_len, 0x040b, stitched_ie_p); + if (!stitched_ie_len) { + SLSI_ERR(sdev, "No match ind IE\n"); + kfree(hal_evt); + kfree(stitched_ie_p); + return; + } + hal_evt->publish_subscribe_id = fapi_get_u16(skb, u.mlme_nan_service_ind.publish_subscribe_id); + hal_evt->requestor_instance_id = fapi_get_u16(skb, u.mlme_nan_service_ind.peer_id); + + /* 7 = ie_id(1), ie_len(1), oui(3) type/subtype(2)*/ + ptr = stitched_ie_p + 7; + ether_addr_copy(hal_evt->addr, ptr); + ptr += ETH_ALEN; + hal_evt->match_occurred_flag = *ptr; + ptr += 1; + hal_evt->out_of_resource_flag = *ptr; + ptr += 1; + hal_evt->rssi_value = *ptr; + ptr += 1 + 8; /* skip 8 bytes related to datapath and ranging*/ + while (stitched_ie_len > (ptr - stitched_ie_p) + 4) { + tag_id = *(u16 *)ptr; + ptr += 2; + tag_len = *(u16 *)ptr; + ptr += 2; + if (stitched_ie_p[1] + 2 < (ptr - stitched_ie_p) + tag_len) { + SLSI_ERR(sdev, "TLV error\n"); + kfree(hal_evt); + kfree(stitched_ie_p); + return; + } + switch (tag_id) { + case SLSI_FAPI_NAN_SERVICE_SPECIFIC_INFO: + hal_evt->service_specific_info_len = tag_len; + memcpy(hal_evt->service_specific_info, ptr, tag_len); + break; + + case SLSI_FAPI_NAN_SDEA: + hal_evt->sdea_service_specific_info_len = tag_len; + memcpy(hal_evt->sdea_service_specific_info, ptr, tag_len); + break; + + case SLSI_FAPI_NAN_SDF_MATCH_FILTER: + hal_evt->sdf_match_filter_len = tag_len; + memcpy(hal_evt->sdf_match_filter, ptr, tag_len); + break; + } + ptr += tag_len; + } + + ie_ptr = fapi_get_data(skb); + ie_len = fapi_get_datalen(skb); +#define SLSI_OUI 0x001632 +#define SLSI_OUI_TYPE_NAN_PARAMS 0x0b +#define SLSI_OUI_TYPE_RTT_PARAMS 0x0a + while (ie_ptr && (fapi_get_datalen(skb) > ie_ptr - fapi_get_data(skb))) { + ie_ptr = cfg80211_find_vendor_ie(SLSI_OUI, SLSI_OUI_TYPE_NAN_PARAMS, ie_ptr, ie_len); + if (!ie_ptr) + break; + if (ie_len > 9 && ie_ptr[1] > 5 && ie_ptr[6] == 9) + hal_evt->sec_info.cipher_type = ie_ptr[8]; + break; + ie_len -= ie_ptr[1] + 2; + if (ie_len < 3) + ie_ptr = NULL; + else + ie_ptr += ie_ptr[1] + 2; + } + + ie_ptr = fapi_get_data(skb); + ie_len = fapi_get_datalen(skb); + while (ie_ptr && (fapi_get_datalen(skb) > ie_ptr - fapi_get_data(skb))) { + ie_ptr = cfg80211_find_vendor_ie(SLSI_OUI, SLSI_OUI_TYPE_RTT_PARAMS, ie_ptr, ie_len); + if (!ie_ptr) + break; + if (ie_len >= 0x35 && ie_ptr[1] == 0x33 && ie_ptr[6] == 2) + hal_evt->range_measurement_mm = ie_ptr[43]; + hal_evt->ranging_event_type = 0; + break; + ie_len -= ie_ptr[1] + 2; + if (ie_len < 3) + ie_ptr = NULL; + else + ie_ptr += ie_ptr[1] + 2; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + SLSI_DBG1_NODEV(SLSI_GSCAN, "Event: %s(%d)\n", + slsi_print_event_name(SLSI_NL80211_NAN_MATCH_EVENT), SLSI_NL80211_NAN_MATCH_EVENT); +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE, SLSI_NL80211_NAN_MATCH_EVENT, + GFP_KERNEL); +#else + nl_skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, SLSI_NL80211_NAN_MATCH_EVENT, GFP_KERNEL); +#endif + if (!nl_skb) { + SLSI_ERR(sdev, "NO MEM for nl_skb!!!\n"); + kfree(hal_evt); + kfree(stitched_ie_p); + return; + } + res = nla_put_u16(nl_skb, NAN_EVT_ATTR_MATCH_PUBLISH_SUBSCRIBE_ID, hal_evt->publish_subscribe_id); + res |= nla_put_u32(nl_skb, NAN_EVT_ATTR_MATCH_REQUESTOR_INSTANCE_ID, hal_evt->requestor_instance_id); + res |= nla_put(nl_skb, NAN_EVT_ATTR_MATCH_ADDR, ETH_ALEN, hal_evt->addr); + res |= nla_put_u16(nl_skb, NAN_EVT_ATTR_MATCH_SERVICE_SPECIFIC_INFO_LEN, hal_evt->service_specific_info_len); + if (hal_evt->service_specific_info_len) + res |= nla_put(nl_skb, NAN_EVT_ATTR_MATCH_SERVICE_SPECIFIC_INFO, hal_evt->service_specific_info_len, + hal_evt->service_specific_info); + res |= nla_put_u16(nl_skb, NAN_EVT_ATTR_MATCH_SDF_MATCH_FILTER_LEN, hal_evt->sdf_match_filter_len); + if (hal_evt->sdf_match_filter_len) + res |= nla_put(nl_skb, NAN_EVT_ATTR_MATCH_SDF_MATCH_FILTER, hal_evt->sdf_match_filter_len, + hal_evt->sdf_match_filter); + res |= nla_put_u16(nl_skb, NAN_EVT_ATTR_SDEA_LEN, hal_evt->sdea_service_specific_info_len); + if (hal_evt->sdea_service_specific_info_len) + res |= nla_put(nl_skb, NAN_EVT_ATTR_SDEA, hal_evt->sdea_service_specific_info_len, + hal_evt->sdea_service_specific_info); + + res |= nla_put_u8(nl_skb, NAN_EVT_ATTR_MATCH_MATCH_OCCURRED_FLAG, hal_evt->match_occurred_flag); + res |= nla_put_u8(nl_skb, NAN_EVT_ATTR_MATCH_OUT_OF_RESOURCE_FLAG, hal_evt->out_of_resource_flag); + res |= nla_put_u8(nl_skb, NAN_EVT_ATTR_MATCH_RSSI_VALUE, hal_evt->rssi_value); + res |= nla_put_u32(nl_skb, NAN_EVT_ATTR_RANGE_MEASUREMENT_MM, hal_evt->range_measurement_mm); + res |= nla_put_u32(nl_skb, NAN_EVT_ATTR_RANGEING_EVENT_TYPE, hal_evt->ranging_event_type); + res |= nla_put_u32(nl_skb, NAN_EVT_ATTR_SECURITY_CIPHER_TYPE, hal_evt->sec_info.cipher_type); + + if (res) { + SLSI_ERR(sdev, "Error in nla_put*:%x\n", res); + /* Dont use slsi skb wrapper for this free */ + kfree_skb(nl_skb); + kfree(hal_evt); + kfree(stitched_ie_p); + return; + } + + cfg80211_vendor_event(nl_skb, GFP_KERNEL); + kfree(hal_evt); + kfree(stitched_ie_p); +} diff --git a/drivers/net/wireless/scsc/nl80211_vendor_nan.h b/drivers/net/wireless/scsc/nl80211_vendor_nan.h new file mode 100755 index 000000000000..797d182404ce --- /dev/null +++ b/drivers/net/wireless/scsc/nl80211_vendor_nan.h @@ -0,0 +1,888 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#ifndef __SLSI_NL80211_VENDOR_NAN_H_ +#define __SLSI_NL80211_VENDOR_NAN_H_ + +enum SLSI_NAN_REPLY_ATTRIBUTES { + NAN_REPLY_ATTR_STATUS_TYPE, + NAN_REPLY_ATTR_VALUE, + NAN_REPLY_ATTR_RESPONSE_TYPE, + NAN_REPLY_ATTR_PUBLISH_SUBSCRIBE_TYPE, + NAN_REPLY_ATTR_CAP_MAX_CONCURRENT_CLUSTER, + NAN_REPLY_ATTR_CAP_MAX_PUBLISHES, + NAN_REPLY_ATTR_CAP_MAX_SUBSCRIBES, + NAN_REPLY_ATTR_CAP_MAX_SERVICE_NAME_LEN, + NAN_REPLY_ATTR_CAP_MAX_MATCH_FILTER_LEN, + NAN_REPLY_ATTR_CAP_MAX_TOTAL_MATCH_FILTER_LEN, + NAN_REPLY_ATTR_CAP_MAX_SERVICE_SPECIFIC_INFO_LEN, + NAN_REPLY_ATTR_CAP_MAX_VSA_DATA_LEN, + NAN_REPLY_ATTR_CAP_MAX_MESH_DATA_LEN, + NAN_REPLY_ATTR_CAP_MAX_NDI_INTERFACES, + NAN_REPLY_ATTR_CAP_MAX_NDP_SESSIONS, + NAN_REPLY_ATTR_CAP_MAX_APP_INFO_LEN +}; + +enum SLSI_NAN_REQ_ATTRIBUTES { + NAN_REQ_ATTR_MASTER_PREF, + NAN_REQ_ATTR_CLUSTER_LOW, + NAN_REQ_ATTR_CLUSTER_HIGH, + NAN_REQ_ATTR_HOP_COUNT_LIMIT_VAL, + NAN_REQ_ATTR_SID_BEACON_VAL, + NAN_REQ_ATTR_SUPPORT_2G4_VAL, + NAN_REQ_ATTR_SUPPORT_5G_VAL, + NAN_REQ_ATTR_RSSI_CLOSE_2G4_VAL, + NAN_REQ_ATTR_RSSI_MIDDLE_2G4_VAL, + NAN_REQ_ATTR_RSSI_PROXIMITY_2G4_VAL, + NAN_REQ_ATTR_BEACONS_2G4_VAL = 10, + NAN_REQ_ATTR_SDF_2G4_VAL, + NAN_REQ_ATTR_CHANNEL_2G4_MHZ_VAL, + NAN_REQ_ATTR_RSSI_PROXIMITY_VAL, + NAN_REQ_ATTR_RSSI_CLOSE_5G_VAL, + NAN_REQ_ATTR_RSSI_CLOSE_PROXIMITY_5G_VAL, + NAN_REQ_ATTR_RSSI_MIDDLE_5G_VAL, + NAN_REQ_ATTR_RSSI_PROXIMITY_5G_VAL, + NAN_REQ_ATTR_BEACON_5G_VAL, + NAN_REQ_ATTR_SDF_5G_VAL, + NAN_REQ_ATTR_CHANNEL_5G_MHZ_VAL = 20, + NAN_REQ_ATTR_RSSI_WINDOW_SIZE_VAL, + NAN_REQ_ATTR_OUI_VAL, + NAN_REQ_ATTR_MAC_ADDR_VAL, + NAN_REQ_ATTR_CLUSTER_VAL, + NAN_REQ_ATTR_SOCIAL_CH_SCAN_DWELL_TIME, + NAN_REQ_ATTR_SOCIAL_CH_SCAN_PERIOD, + NAN_REQ_ATTR_RANDOM_FACTOR_FORCE_VAL, + NAN_REQ_ATTR_HOP_COUNT_FORCE_VAL, + NAN_REQ_ATTR_CONN_CAPABILITY_PAYLOAD_TX, + NAN_REQ_ATTR_CONN_CAPABILITY_IBSS = 30, + NAN_REQ_ATTR_CONN_CAPABILITY_WFD, + NAN_REQ_ATTR_CONN_CAPABILITY_WFDS, + NAN_REQ_ATTR_CONN_CAPABILITY_TDLS, + NAN_REQ_ATTR_CONN_CAPABILITY_MESH, + NAN_REQ_ATTR_CONN_CAPABILITY_WLAN_INFRA, + NAN_REQ_ATTR_DISCOVERY_ATTR_NUM_ENTRIES, + NAN_REQ_ATTR_DISCOVERY_ATTR_VAL, + NAN_REQ_ATTR_CONN_TYPE, + NAN_REQ_ATTR_NAN_ROLE, + NAN_REQ_ATTR_TRANSMIT_FREQ = 40, + NAN_REQ_ATTR_AVAILABILITY_DURATION, + NAN_REQ_ATTR_AVAILABILITY_INTERVAL, + NAN_REQ_ATTR_MESH_ID_LEN, + NAN_REQ_ATTR_MESH_ID, + NAN_REQ_ATTR_INFRASTRUCTURE_SSID_LEN, + NAN_REQ_ATTR_INFRASTRUCTURE_SSID, + NAN_REQ_ATTR_FURTHER_AVAIL_NUM_ENTRIES, + NAN_REQ_ATTR_FURTHER_AVAIL_VAL, + NAN_REQ_ATTR_FURTHER_AVAIL_ENTRY_CTRL, + NAN_REQ_ATTR_FURTHER_AVAIL_CHAN_CLASS = 50, + NAN_REQ_ATTR_FURTHER_AVAIL_CHAN, + NAN_REQ_ATTR_FURTHER_AVAIL_CHAN_MAPID, + NAN_REQ_ATTR_FURTHER_AVAIL_INTERVAL_BITMAP, + NAN_REQ_ATTR_PUBLISH_ID, + NAN_REQ_ATTR_PUBLISH_TTL, + NAN_REQ_ATTR_PUBLISH_PERIOD, + NAN_REQ_ATTR_PUBLISH_TYPE, + NAN_REQ_ATTR_PUBLISH_TX_TYPE, + NAN_REQ_ATTR_PUBLISH_COUNT, + NAN_REQ_ATTR_PUBLISH_SERVICE_NAME_LEN = 60, + NAN_REQ_ATTR_PUBLISH_SERVICE_NAME, + NAN_REQ_ATTR_PUBLISH_MATCH_ALGO, + NAN_REQ_ATTR_PUBLISH_SERVICE_INFO_LEN, + NAN_REQ_ATTR_PUBLISH_SERVICE_INFO, + NAN_REQ_ATTR_PUBLISH_RX_MATCH_FILTER_LEN, + NAN_REQ_ATTR_PUBLISH_RX_MATCH_FILTER, + NAN_REQ_ATTR_PUBLISH_TX_MATCH_FILTER_LEN, + NAN_REQ_ATTR_PUBLISH_TX_MATCH_FILTER, + NAN_REQ_ATTR_PUBLISH_RSSI_THRESHOLD_FLAG, + NAN_REQ_ATTR_PUBLISH_CONN_MAP = 70, + NAN_REQ_ATTR_PUBLISH_RECV_IND_CFG, + NAN_REQ_ATTR_SUBSCRIBE_ID, + NAN_REQ_ATTR_SUBSCRIBE_TTL, + NAN_REQ_ATTR_SUBSCRIBE_PERIOD, + NAN_REQ_ATTR_SUBSCRIBE_TYPE, + NAN_REQ_ATTR_SUBSCRIBE_RESP_FILTER_TYPE, + NAN_REQ_ATTR_SUBSCRIBE_RESP_INCLUDE, + NAN_REQ_ATTR_SUBSCRIBE_USE_RESP_FILTER, + NAN_REQ_ATTR_SUBSCRIBE_SSI_REQUIRED, + NAN_REQ_ATTR_SUBSCRIBE_MATCH_INDICATOR = 80, + NAN_REQ_ATTR_SUBSCRIBE_COUNT, + NAN_REQ_ATTR_SUBSCRIBE_SERVICE_NAME_LEN, + NAN_REQ_ATTR_SUBSCRIBE_SERVICE_NAME, + NAN_REQ_ATTR_SUBSCRIBE_SERVICE_INFO_LEN, + NAN_REQ_ATTR_SUBSCRIBE_SERVICE_INFO, + NAN_REQ_ATTR_SUBSCRIBE_RX_MATCH_FILTER_LEN, + NAN_REQ_ATTR_SUBSCRIBE_RX_MATCH_FILTER, + NAN_REQ_ATTR_SUBSCRIBE_TX_MATCH_FILTER_LEN, + NAN_REQ_ATTR_SUBSCRIBE_TX_MATCH_FILTER, + NAN_REQ_ATTR_SUBSCRIBE_RSSI_THRESHOLD_FLAG = 90, + NAN_REQ_ATTR_SUBSCRIBE_CONN_MAP, + NAN_REQ_ATTR_SUBSCRIBE_NUM_INTF_ADDR_PRESENT, + NAN_REQ_ATTR_SUBSCRIBE_INTF_ADDR, + NAN_REQ_ATTR_SUBSCRIBE_RECV_IND_CFG, + NAN_REQ_ATTR_FOLLOWUP_ID, + NAN_REQ_ATTR_FOLLOWUP_REQUESTOR_ID, + NAN_REQ_ATTR_FOLLOWUP_ADDR, + NAN_REQ_ATTR_FOLLOWUP_PRIORITY, + NAN_REQ_ATTR_FOLLOWUP_SERVICE_NAME_LEN, + NAN_REQ_ATTR_FOLLOWUP_SERVICE_NAME = 100, + NAN_REQ_ATTR_FOLLOWUP_TX_WINDOW, + NAN_REQ_ATTR_FOLLOWUP_RECV_IND_CFG, + NAN_REQ_ATTR_SUBSCRIBE_SID_BEACON_VAL, + NAN_REQ_ATTR_DW_2G4_INTERVAL, + NAN_REQ_ATTR_DW_5G_INTERVAL, + NAN_REQ_ATTR_DISC_MAC_ADDR_RANDOM_INTERVAL, + NAN_REQ_ATTR_PUBLISH_SDEA_LEN, + NAN_REQ_ATTR_PUBLISH_SDEA, + NAN_REQ_ATTR_RANGING_AUTO_RESPONSE, + NAN_REQ_ATTR_SDEA_PARAM_NDP_TYPE = 110, + NAN_REQ_ATTR_SDEA_PARAM_SECURITY_CFG, + NAN_REQ_ATTR_SDEA_PARAM_RANGING_STATE, + NAN_REQ_ATTR_SDEA_PARAM_RANGE_REPORT, + NAN_REQ_ATTR_SDEA_PARAM_QOS_CFG, + NAN_REQ_ATTR_RANGING_CFG_INTERVAL, + NAN_REQ_ATTR_RANGING_CFG_INDICATION, + NAN_REQ_ATTR_RANGING_CFG_INGRESS_MM, + NAN_REQ_ATTR_RANGING_CFG_EGRESS_MM, + NAN_REQ_ATTR_CIPHER_TYPE, + NAN_REQ_ATTR_SCID_LEN = 120, + NAN_REQ_ATTR_SCID, + NAN_REQ_ATTR_SECURITY_KEY_TYPE, + NAN_REQ_ATTR_SECURITY_PMK_LEN, + NAN_REQ_ATTR_SECURITY_PMK, + NAN_REQ_ATTR_SECURITY_PASSPHRASE_LEN, + NAN_REQ_ATTR_SECURITY_PASSPHRASE, + NAN_REQ_ATTR_RANGE_RESPONSE_CFG_PUBLISH_ID, + NAN_REQ_ATTR_RANGE_RESPONSE_CFG_REQUESTOR_ID, + NAN_REQ_ATTR_RANGE_RESPONSE_CFG_PEER_ADDR, + NAN_REQ_ATTR_RANGE_RESPONSE_CFG_RANGING_RESPONSE +}; + +enum SLSI_NAN_RESP_ATTRIBUTES { + NAN_RESP_ATTR_MAX_CONCURRENT_NAN_CLUSTERS, + NAN_RESP_ATTR_MAX_PUBLISHES, + NAN_RESP_ATTR_MAX_SUBSCRIBES, + NAN_RESP_ATTR_MAX_SERVICE_NAME_LEN, + NAN_RESP_ATTR_MAX_MATCH_FILTER_LEN, + NAN_RESP_ATTR_MAX_TOTAL_MATCH_FILTER_LEN, + NAN_RESP_ATTR_MAX_SERVICE_SPECIFIC_INFO_LEN, + NAN_RESP_ATTR_MAX_VSA_DATA_LEN, + NAN_RESP_ATTR_MAX_MESH_DATA_LEN, + NAN_RESP_ATTR_MAX_NDI_INTERFACES, + NAN_RESP_ATTR_MAX_NDP_SESSIONS, + NAN_RESP_ATTR_MAX_APP_INFO_LEN, + NAN_RESP_ATTR_SUBSCRIBE_ID, + NAN_RESP_ATTR_PUBLISH_ID +}; + +enum SLSI_NAN_EVT_ATTRIBUTES { + NAN_EVT_ATTR_MATCH_PUBLISH_SUBSCRIBE_ID, + NAN_EVT_ATTR_MATCH_REQUESTOR_INSTANCE_ID, + NAN_EVT_ATTR_MATCH_ADDR, + NAN_EVT_ATTR_MATCH_SERVICE_SPECIFIC_INFO_LEN, + NAN_EVT_ATTR_MATCH_SERVICE_SPECIFIC_INFO, + NAN_EVT_ATTR_MATCH_SDF_MATCH_FILTER_LEN, + NAN_EVT_ATTR_MATCH_SDF_MATCH_FILTER, + NAN_EVT_ATTR_MATCH_MATCH_OCCURRED_FLAG, + NAN_EVT_ATTR_MATCH_OUT_OF_RESOURCE_FLAG, + NAN_EVT_ATTR_MATCH_RSSI_VALUE, + NAN_EVT_ATTR_MATCH_CONN_CAPABILITY_IS_WFD_SUPPORTED = 10, + NAN_EVT_ATTR_MATCH_CONN_CAPABILITY_IS_WFDS_SUPPORTED, + NAN_EVT_ATTR_MATCH_CONN_CAPABILITY_IS_TDLS_SUPPORTED, + NAN_EVT_ATTR_MATCH_CONN_CAPABILITY_IS_IBSS_SUPPORTED, + NAN_EVT_ATTR_MATCH_CONN_CAPABILITY_IS_MESH_SUPPORTED, + NAN_EVT_ATTR_MATCH_CONN_CAPABILITY_WLAN_INFRA_FIELD, + NAN_EVT_ATTR_MATCH_NUM_RX_DISCOVERY_ATTR, + NAN_EVT_ATTR_MATCH_RX_DISCOVERY_ATTR, + NAN_EVT_ATTR_MATCH_DISC_ATTR_TYPE, + NAN_EVT_ATTR_MATCH_DISC_ATTR_ROLE, + NAN_EVT_ATTR_MATCH_DISC_ATTR_DURATION = 20, + NAN_EVT_ATTR_MATCH_DISC_ATTR_AVAIL_INTERVAL_BITMAP, + NAN_EVT_ATTR_MATCH_DISC_ATTR_MAPID, + NAN_EVT_ATTR_MATCH_DISC_ATTR_ADDR, + NAN_EVT_ATTR_MATCH_DISC_ATTR_MESH_ID_LEN, + NAN_EVT_ATTR_MATCH_DISC_ATTR_MESH_ID, + NAN_EVT_ATTR_MATCH_DISC_ATTR_INFRASTRUCTURE_SSID_LEN, + NAN_EVT_ATTR_MATCH_DISC_ATTR_INFRASTRUCTURE_SSID_VAL, + NAN_EVT_ATTR_MATCH_NUM_CHANS, + NAN_EVT_ATTR_MATCH_FAMCHAN, + NAN_EVT_ATTR_MATCH_FAM_ENTRY_CONTROL = 30, + NAN_EVT_ATTR_MATCH_FAM_CLASS_VAL, + NAN_EVT_ATTR_MATCH_FAM_CHANNEL, + NAN_EVT_ATTR_MATCH_FAM_MAPID, + NAN_EVT_ATTR_MATCH_FAM_AVAIL_INTERVAL_BITMAP, + NAN_EVT_ATTR_MATCH_CLUSTER_ATTRIBUTE_LEN, + NAN_EVT_ATTR_MATCH_CLUSTER_ATTRIBUTE, + NAN_EVT_ATTR_PUBLISH_ID, + NAN_EVT_ATTR_PUBLISH_REASON, + NAN_EVT_ATTR_SUBSCRIBE_ID, + NAN_EVT_ATTR_SUBSCRIBE_REASON = 40, + NAN_EVT_ATTR_DISABLED_REASON, + NAN_EVT_ATTR_FOLLOWUP_PUBLISH_SUBSCRIBE_ID, + NAN_EVT_ATTR_FOLLOWUP_REQUESTOR_INSTANCE_ID, + NAN_EVT_ATTR_FOLLOWUP_ADDR, + NAN_EVT_ATTR_FOLLOWUP_DW_OR_FAW, + NAN_EVT_ATTR_FOLLOWUP_SERVICE_SPECIFIC_INFO_LEN, + NAN_EVT_ATTR_FOLLOWUP_SERVICE_SPECIFIC_INFO, + NAN_EVT_ATTR_DISCOVERY_ENGINE_EVT_TYPE, + NAN_EVT_ATTR_DISCOVERY_ENGINE_MAC_ADDR, + NAN_EVT_ATTR_DISCOVERY_ENGINE_CLUSTER = 50, + NAN_EVT_ATTR_SDEA, + NAN_EVT_ATTR_SDEA_LEN, + NAN_EVT_ATTR_SCID, + NAN_EVT_ATTR_SCID_LEN, + NAN_EVT_ATTR_SDEA_PARAM_CONFIG_NAN_DATA_PATH, + NAN_EVT_ATTR_SDEA_PARAM_NDP_TYPE, + NAN_EVT_ATTR_SDEA_PARAM_SECURITY_CONFIG, + NAN_EVT_ATTR_SDEA_PARAM_RANGE_STATE, + NAN_EVT_ATTR_SDEA_PARAM_RANGE_REPORT, + NAN_EVT_ATTR_SDEA_PARAM_QOS_CFG = 60, + NAN_EVT_ATTR_RANGE_MEASUREMENT_MM, + NAN_EVT_ATTR_RANGEING_EVENT_TYPE, + NAN_EVT_ATTR_SECURITY_CIPHER_TYPE +}; + +#define SLSI_FAPI_NAN_CONFIG_PARAM_SID_BEACON 0X0003 +#define SLSI_FAPI_NAN_CONFIG_PARAM_2_4_RSSI_CLOSE 0X0004 +#define SLSI_FAPI_NAN_CONFIG_PARAM_2_4_RSSI_MIDDLE 0X0005 +#define SLSI_FAPI_NAN_CONFIG_PARAM_2_4_RSSI_PROXIMITY 0X0006 +#define SLSI_FAPI_NAN_CONFIG_PARAM_BAND_USAGE 0X0007 +#define SLSI_FAPI_NAN_CONFIG_PARAM_5_RSSI_CLOSE 0X0008 +#define SLSI_FAPI_NAN_CONFIG_PARAM_5_RSSI_MIDDLE 0X0009 +#define SLSI_FAPI_NAN_CONFIG_PARAM_5_RSSI_PROXIMITY 0X000A +#define SLSI_FAPI_NAN_CONFIG_PARAM_HOP_COUNT_LIMIT 0X000B +#define SLSI_FAPI_NAN_CONFIG_PARAM_RSSI_WINDOW_SIZE 0X000C +#define SLSI_FAPI_NAN_CONFIG_PARAM_SCAN_PARAMETER_2_4 0X000D +#define SLSI_FAPI_NAN_CONFIG_PARAM_SCAN_PARAMETER_5 0X000E +#define SLSI_FAPI_NAN_CONFIG_PARAM_MASTER_PREFERENCE 0X000F +#define SLSI_FAPI_NAN_CONFIG_PARAM_CONNECTION_CAPAB 0X0010 +#define SLSI_FAPI_NAN_CONFIG_PARAM_POST_DISCOVER_PARAM 0X0011 +#define SLSI_FAPI_NAN_CONFIG_PARAM_FURTHER_AVAIL_CHANNEL_MAP 0X0012 +#define SLSI_FAPI_NAN_CONFIG_PARAM_ADDR_RANDOM_INTERVAL 0X0013 +#define SLSI_FAPI_NAN_SERVICE_NAME 0X0020 +#define SLSI_FAPI_NAN_SERVICE_SPECIFIC_INFO 0X0021 +#define SLSI_FAPI_NAN_RX_MATCH_FILTER 0X0022 +#define SLSI_FAPI_NAN_TX_MATCH_FILTER 0X0023 +#define SLSI_FAPI_NAN_SDF_MATCH_FILTER 0X0024 +#define SLSI_FAPI_NAN_CLUSTER_ATTRIBUTE 0X0025 +#define SLSI_FAPI_NAN_INTERFACE_ADDRESS_SET 0X0026 +#define SLSI_FAPI_NAN_SDEA 0X0027 + +#define SLSI_HAL_NAN_MAX_SOCIAL_CHANNELS 3 +#define SLSI_HAL_NAN_MAX_SERVICE_NAME_LEN 255 +#define SLSI_HAL_NAN_MAX_SERVICE_SPECIFIC_INFO_LEN 1024 +#define SLSI_HAL_NAN_MAX_MATCH_FILTER_LEN 255 +#define SLSI_HAL_NAN_MAX_SUBSCRIBE_MAX_ADDRESS 42 +#define SLSI_HAL_NAN_MAX_POSTDISCOVERY_LEN 5 +#define SLSI_HAL_NAN_MAX_SDEA_SERVICE_SPECIFIC_INFO_LEN 1024 + +enum slsi_wifi_hal_nan_status_type { + /* NAN Protocol Response Codes */ + SLSI_HAL_NAN_STATUS_SUCCESS = 0, + /* NAN Discovery Engine/Host driver failures */ + SLSI_HAL_NAN_STATUS_INTERNAL_FAILURE = 1, + /* NAN OTA failures */ + SLSI_HAL_NAN_STATUS_PROTOCOL_FAILURE = 2, + /* if the publish/subscribe id is invalid */ + SLSI_HAL_NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID = 3, + /* If we run out of resources allocated */ + SLSI_HAL_NAN_STATUS_NO_RESOURCE_AVAILABLE = 4, + /* if invalid params are passed */ + SLSI_HAL_NAN_STATUS_INVALID_PARAM = 5, + /* if the requestor instance id is invalid */ + SLSI_HAL_NAN_STATUS_INVALID_REQUESTOR_INSTANCE_ID = 6, + /* if the ndp id is invalid */ + SLSI_HAL_NAN_STATUS_INVALID_NDP_ID = 7, + /* if NAN is enabled when wifi is turned off */ + SLSI_HAL_NAN_STATUS_NAN_NOT_ALLOWED = 8, + /* if over the air ack is not received */ + SLSI_HAL_NAN_STATUS_NO_OTA_ACK = 9, + /* If NAN is already enabled and we are try to re-enable the same */ + SLSI_HAL_NAN_STATUS_ALREADY_ENABLED = 10, + /* If followup message internal queue is full */ + SLSI_HAL_NAN_STATUS_FOLLOWUP_QUEUE_FULL = 11, + /* Unsupported concurrency session enabled, NAN disabled notified */ + SLSI_HAL_NAN_STATUS_UNSUPPORTED_CONCURRENCY_NAN_DISABLED = 12 +}; + +enum slsi_nan_status_type { + /* NAN Protocol Response Codes */ + NAN_STATUS_SUCCESS = 0, + NAN_STATUS_TIMEOUT = 1, + NAN_STATUS_DE_FAILURE = 2, + NAN_STATUS_INVALID_MSG_VERSION = 3, + NAN_STATUS_INVALID_MSG_LEN = 4, + NAN_STATUS_INVALID_MSG_ID = 5, + NAN_STATUS_INVALID_HANDLE = 6, + NAN_STATUS_NO_SPACE_AVAILABLE = 7, + NAN_STATUS_INVALID_PUBLISH_TYPE = 8, + NAN_STATUS_INVALID_TX_TYPE = 9, + NAN_STATUS_INVALID_MATCH_ALGORITHM = 10, + NAN_STATUS_DISABLE_IN_PROGRESS = 11, + NAN_STATUS_INVALID_TLV_LEN = 12, + NAN_STATUS_INVALID_TLV_TYPE = 13, + NAN_STATUS_MISSING_TLV_TYPE = 14, + NAN_STATUS_INVALID_TOTAL_TLVS_LEN = 15, + NAN_STATUS_INVALID_MATCH_HANDLE = 16, + NAN_STATUS_INVALID_TLV_VALUE = 17, + NAN_STATUS_INVALID_TX_PRIORITY = 18, + NAN_STATUS_INVALID_CONNECTION_MAP = 19, + NAN_STATUS_INVALID_TCA_ID = 20, + NAN_STATUS_INVALID_STATS_ID = 21, + NAN_STATUS_NAN_NOT_ALLOWED = 22, + NAN_STATUS_NO_OTA_ACK = 23, + NAN_STATUS_TX_FAIL = 24, + /* 25-4095 Reserved */ + /* NAN Configuration Response codes */ + NAN_STATUS_INVALID_RSSI_CLOSE_VALUE = 4096, + NAN_STATUS_INVALID_RSSI_MIDDLE_VALUE = 4097, + NAN_STATUS_INVALID_HOP_COUNT_LIMIT = 4098, + NAN_STATUS_INVALID_MASTER_PREFERENCE_VALUE = 4099, + NAN_STATUS_INVALID_LOW_CLUSTER_ID_VALUE = 4100, + NAN_STATUS_INVALID_HIGH_CLUSTER_ID_VALUE = 4101, + NAN_STATUS_INVALID_BACKGROUND_SCAN_PERIOD = 4102, + NAN_STATUS_INVALID_RSSI_PROXIMITY_VALUE = 4103, + NAN_STATUS_INVALID_SCAN_CHANNEL = 4104, + NAN_STATUS_INVALID_POST_NAN_CONNECTIVITY_CAPABILITIES_BITMAP = 4105, + NAN_STATUS_INVALID_FURTHER_AVAILABILITY_MAP_NUMCHAN_VALUE = 4106, + NAN_STATUS_INVALID_FURTHER_AVAILABILITY_MAP_DURATION_VALUE = 4107, + NAN_STATUS_INVALID_FURTHER_AVAILABILITY_MAP_CLASS_VALUE = 4108, + NAN_STATUS_INVALID_FURTHER_AVAILABILITY_MAP_CHANNEL_VALUE = 4109, + NAN_STATUS_INVALID_FURTHER_AVAILABILITY_MAP_AVAILABILITY_INTERVAL_BITMAP_VALUE = 4110, + NAN_STATUS_INVALID_FURTHER_AVAILABILITY_MAP_MAP_ID = 4111, + NAN_STATUS_INVALID_POST_NAN_DISCOVERY_CONN_TYPE_VALUE = 4112, + NAN_STATUS_INVALID_POST_NAN_DISCOVERY_DEVICE_ROLE_VALUE = 4113, + NAN_STATUS_INVALID_POST_NAN_DISCOVERY_DURATION_VALUE = 4114, + NAN_STATUS_INVALID_POST_NAN_DISCOVERY_BITMAP_VALUE = 4115, + NAN_STATUS_MISSING_FUTHER_AVAILABILITY_MAP = 4116, + NAN_STATUS_INVALID_BAND_CONFIG_FLAGS = 4117, + NAN_STATUS_INVALID_RANDOM_FACTOR_UPDATE_TIME_VALUE = 4118, + NAN_STATUS_INVALID_ONGOING_SCAN_PERIOD = 4119, + NAN_STATUS_INVALID_DW_INTERVAL_VALUE = 4120, + NAN_STATUS_INVALID_DB_INTERVAL_VALUE = 4121, + /* 4122-8191 RESERVED */ + NAN_TERMINATED_REASON_INVALID = 8192, + NAN_TERMINATED_REASON_TIMEOUT = 8193, + NAN_TERMINATED_REASON_USER_REQUEST = 8194, + NAN_TERMINATED_REASON_FAILURE = 8195, + NAN_TERMINATED_REASON_COUNT_REACHED = 8196, + NAN_TERMINATED_REASON_DE_SHUTDOWN = 8197, + NAN_TERMINATED_REASON_DISABLE_IN_PROGRESS = 8198, + NAN_TERMINATED_REASON_POST_DISC_ATTR_EXPIRED = 8199, + NAN_TERMINATED_REASON_POST_DISC_LEN_EXCEEDED = 8200, + NAN_TERMINATED_REASON_FURTHER_AVAIL_MAP_EMPTY = 8201 +}; + +enum slsi_nan_response_type { + NAN_RESPONSE_ENABLED = 0, + NAN_RESPONSE_DISABLED = 1, + NAN_RESPONSE_PUBLISH = 2, + NAN_RESPONSE_PUBLISH_CANCEL = 3, + NAN_RESPONSE_TRANSMIT_FOLLOWUP = 4, + NAN_RESPONSE_SUBSCRIBE = 5, + NAN_RESPONSE_SUBSCRIBE_CANCEL = 6, + NAN_RESPONSE_STATS = 7, + NAN_RESPONSE_CONFIG = 8, + NAN_RESPONSE_TCA = 9, + NAN_RESPONSE_ERROR = 10, + NAN_RESPONSE_BEACON_SDF_PAYLOAD = 11, + NAN_RESPONSE_GET_CAPABILITIES = 12 +}; + +enum slsi_nan_disc_event_type { + NAN_EVENT_ID_DISC_MAC_ADDR = 0, + NAN_EVENT_ID_STARTED_CLUSTER, + NAN_EVENT_ID_JOINED_CLUSTER +}; + +struct slsi_hal_nan_social_channel_scan_params { + u8 dwell_time[SLSI_HAL_NAN_MAX_SOCIAL_CHANNELS]; + u16 scan_period[SLSI_HAL_NAN_MAX_SOCIAL_CHANNELS]; +}; + +struct slsi_hal_nan_connectivity_capability { + u8 payload_transmit_flag; + u8 is_wfd_supported; + u8 is_wfds_supported; + u8 is_tdls_supported; + u8 is_ibss_supported; + u8 is_mesh_supported; + u8 wlan_infra_field; +}; + +struct slsi_hal_nan_post_discovery_param { + u8 type; /* NanConnectionType */ + u8 role; /* NanDeviceRole */ + u8 transmit_freq; + u8 duration; /* NanAvailDuration */ + u32 avail_interval_bitmap; + u8 addr[ETH_ALEN]; + u16 mesh_id_len; + u8 mesh_id[32]; + u16 infrastructure_ssid_len; + u8 infrastructure_ssid_val[32]; +}; + +struct slsi_hal_nan_further_availability_channel { + /* struct slsi_hal_nan_further_availability_channel*/ + u8 entry_control; + u8 class_val; + u8 channel; + u8 mapid; + u32 avail_interval_bitmap; +}; + +struct slsi_hal_nan_further_availability_map { + u8 numchans; + struct slsi_hal_nan_further_availability_channel famchan[32]; +}; + +struct slsi_hal_nan_receive_post_discovery { + u8 type; + u8 role; + u8 duration; + u32 avail_interval_bitmap; + u8 mapid; + u8 addr[ETH_ALEN]; + u16 mesh_id_len; + u8 mesh_id[32]; + u16 infrastructure_ssid_len; + u8 infrastructure_ssid_val[32]; +}; + +struct slsi_nan_sdea_ctrl_params { + u8 config_nan_data_path; + u8 ndp_type; + u8 security_cfg; + u8 ranging_state; + u8 range_report; + u8 qos_cfg; +}; + +struct slsi_nan_ranging_cfg { + u32 ranging_interval_msec; + u32 config_ranging_indications; + u32 distance_ingress_mm; + u32 distance_egress_mm; +}; + +struct slsi_nan_range_response_cfg { + u16 publish_id; + u32 requestor_instance_id; + u8 peer_addr[ETH_ALEN]; + u8 ranging_response; +}; + +#define SLSI_NAN_PMK_INFO_LEN 32 +#define SLSI_NAN_SECURITY_MAX_PASSPHRASE_LEN 63 +#define SLSI_NAN_MAX_SCID_BUF_LEN 1024 +struct slsi_nan_security_pmk { + u32 pmk_len; + u8 pmk[SLSI_NAN_PMK_INFO_LEN]; +}; + +struct slsi_nan_security_passphrase { + u32 passphrase_len; + u8 passphrase[SLSI_NAN_SECURITY_MAX_PASSPHRASE_LEN]; +}; + +struct slsi_nan_security_key_info { + u8 key_type; + union { + struct slsi_nan_security_pmk pmk_info; + struct slsi_nan_security_passphrase passphrase_info; + } body; +}; + +struct slsi_nan_security_info { + u32 cipher_type; + u32 scid_len; + u8 scid[SLSI_NAN_MAX_SCID_BUF_LEN]; + struct slsi_nan_security_key_info key_info; +}; + +struct slsi_hal_nan_enable_req { + /* Mandatory parameters below */ + u8 master_pref; + u16 cluster_low; + u16 cluster_high; + + u8 config_support_5g; + u8 support_5g_val; + u8 config_sid_beacon; + u8 sid_beacon_val; + u8 config_2dot4g_rssi_close; + u8 rssi_close_2dot4g_val; + + u8 config_2dot4g_rssi_middle; + u8 rssi_middle_2dot4g_val; + + u8 config_2dot4g_rssi_proximity; + u8 rssi_proximity_2dot4g_val; + + u8 config_hop_count_limit; + u8 hop_count_limit_val; + + u8 config_2dot4g_support; + u8 support_2dot4g_val; + + u8 config_2dot4g_beacons; + u8 beacon_2dot4g_val; + u8 config_2dot4g_sdf; + u8 sdf_2dot4g_val; + u8 config_5g_beacons; + u8 beacon_5g_val; + u8 config_5g_sdf; + u8 sdf_5g_val; + u8 config_5g_rssi_close; + u8 rssi_close_5g_val; + u8 config_5g_rssi_middle; + u8 rssi_middle_5g_val; + u8 config_5g_rssi_close_proximity; + u8 rssi_close_proximity_5g_val; + u8 config_rssi_window_size; + u8 rssi_window_size_val; + /* The 24 bit Organizationally Unique ID + the 8 bit Network Id. */ + u8 config_oui; + u32 oui_val; + u8 config_intf_addr; + u8 intf_addr_val[ETH_ALEN]; + + u8 config_cluster_attribute_val; + u8 config_scan_params; + struct slsi_hal_nan_social_channel_scan_params scan_params_val; + u8 config_random_factor_force; + u8 random_factor_force_val; + u8 config_hop_count_force; + u8 hop_count_force_val; + + /* channel frequency in MHz to enable Nan on */ + u8 config_24g_channel; + u32 channel_24g_val; + + u8 config_5g_channel; + int channel_5g_val; + u8 config_subscribe_sid_beacon; + u32 subscribe_sid_beacon_val; + + /*NanConfigDW config_dw*/ + u8 config_2dot4g_dw_band; + u32 dw_2dot4g_interval_val; + u8 config_5g_dw_band; + u32 dw_5g_interval_val; + u32 disc_mac_addr_rand_interval_sec; +}; + +struct slsi_hal_nan_publish_req { + /* id 0 means new publish, any other id is existing publish */ + u16 publish_id; + /* how many seconds to run for. 0 means forever until canceled */ + u16 ttl; + /* periodicity of OTA unsolicited publish. + * Specified in increments of 500 ms + */ + u16 period; + u8 publish_type;/* 0= unsolicited, solicited = 1, 2= both */ + u8 tx_type; /* 0 = broadcast, 1= unicast if solicited publish */ + /* number of OTA Publish, 0 means forever until canceled */ + u8 publish_count; + u16 service_name_len; + u8 service_name[SLSI_HAL_NAN_MAX_SERVICE_NAME_LEN]; + u8 publish_match_indicator; + + u16 service_specific_info_len; + u8 service_specific_info[SLSI_HAL_NAN_MAX_SERVICE_SPECIFIC_INFO_LEN]; + + u16 rx_match_filter_len; + u8 rx_match_filter[SLSI_HAL_NAN_MAX_MATCH_FILTER_LEN]; + + u16 tx_match_filter_len; + u8 tx_match_filter[SLSI_HAL_NAN_MAX_MATCH_FILTER_LEN]; + + u8 rssi_threshold_flag; + + /* 8-bit bitmap which allows the Host to associate this publish + * with a particular Post-NAN Connectivity attribute + * which has been sent down in a NanConfigureRequest/NanEnableRequest + * message. If the DE fails to find a configured Post-NAN + * connectivity attributes referenced by the bitmap, + * the DE will return an error code to the Host. + * If the Publish is configured to use a Post-NAN Connectivity + * attribute and the Host does not refresh the Post-NAN Connectivity + * attribute the Publish will be canceled and the Host will be sent + * a PublishTerminatedIndication message. + */ + u8 connmap; + /* Set/Enable corresponding bits to disable any + * indications that follow a publish. + * BIT0 - Disable publish termination indication. + * BIT1 - Disable match expired indication. + * BIT2 - Disable followUp indication received (OTA). + */ + u8 recv_indication_cfg; + + u8 service_responder_policy; + struct slsi_nan_security_info sec_info; + struct slsi_nan_sdea_ctrl_params sdea_params; + struct slsi_nan_ranging_cfg ranging_cfg; + u8 ranging_auto_response; + struct slsi_nan_range_response_cfg range_response_cfg; + + u16 sdea_service_specific_info_len; + u8 sdea_service_specific_info[SLSI_HAL_NAN_MAX_SDEA_SERVICE_SPECIFIC_INFO_LEN]; +}; + +struct slsi_hal_nan_subscribe_req { + /* id 0 means new subscribe, non zero is existing subscribe */ + u16 subscribe_id; + /* how many seconds to run for. 0 means forever until canceled */ + u16 ttl; + /* periodicity of OTA Active Subscribe. Units in increments + * of 500 ms , 0 = attempt every DW + */ + u16 period; + + /* Flag which specifies how the Subscribe request shall be processed. */ + u8 subscribe_type; /* 0 - PASSIVE , 1- ACTIVE */ + + /* Flag which specifies on Active Subscribes how the Service Response + * Filter attribute is populated. + */ + u8 service_response_filter; /* 0 - Bloom Filter, 1 - MAC Addr */ + + /* Flag which specifies how the Service Response Filter Include + * bit is populated. + * 0=Do not respond if in the Address Set, 1= Respond + */ + u8 service_response_include; + + /* Flag which specifies if the Service Response Filter + * should be used when creating Subscribes. + * 0=Do not send the Service Response Filter,1= send + */ + u8 use_service_response_filter; + + /* Flag which specifies if the Service Specific Info is needed in + * the Publish message before creating the MatchIndication + */ + u8 ssi_required_for_match_indication; /* 0=Not needed, 1= Required */ + + /* Field which specifies how matching indication to host is controlled. + * 0 - Match and Indicate Once + * 1 - Match and Indicate continuous + * 2 - Match and Indicate never. This means don't + * indicate match to host. + * 3 - Reserved + */ + u8 subscribe_match_indicator; + + /* The number of Subscribe Matches which should occur + * before the Subscribe request is automatically terminated. + */ + /* If this value is 0 this field is not used by DE.*/ + u8 subscribe_count; + + /* length of service name */ + /* UTF-8 encoded string identifying the service */ + u16 service_name_len; + u8 service_name[SLSI_HAL_NAN_MAX_SERVICE_NAME_LEN]; + + /* Sequence of values which further specify the published service + * beyond the service name + */ + u16 service_specific_info_len; + u8 service_specific_info[SLSI_HAL_NAN_MAX_SERVICE_SPECIFIC_INFO_LEN]; + + /* Ordered sequence of pairs used to filter out + * received publish discovery messages. + * This can be sent both for a Passive or an Active Subscribe + */ + u16 rx_match_filter_len; + u8 rx_match_filter[SLSI_HAL_NAN_MAX_MATCH_FILTER_LEN]; + + /* Ordered sequence of pairs included in the + * Discovery Frame when an Active Subscribe is used. + */ + u16 tx_match_filter_len; + u8 tx_match_filter[SLSI_HAL_NAN_MAX_MATCH_FILTER_LEN]; + u8 rssi_threshold_flag; + + u8 connmap; + /* NAN Interface Address, conforming to the format as described in + * 8.2.4.3.2 of IEEE Std. 802.11-2012. + */ + u8 num_intf_addr_present; + u8 intf_addr[SLSI_HAL_NAN_MAX_SUBSCRIBE_MAX_ADDRESS][ETH_ALEN]; + /* Set/Enable corresponding bits to disable + * indications that follow a subscribe. + * BIT0 - Disable subscribe termination indication. + * BIT1 - Disable match expired indication. + * BIT2 - Disable followUp indication received (OTA). + */ + u8 recv_indication_cfg; + + struct slsi_nan_security_info sec_info; + struct slsi_nan_sdea_ctrl_params sdea_params; + struct slsi_nan_ranging_cfg ranging_cfg; + u8 ranging_auto_response; + struct slsi_nan_range_response_cfg range_response_cfg; + + u16 sdea_service_specific_info_len; + u8 sdea_service_specific_info[SLSI_HAL_NAN_MAX_SDEA_SERVICE_SPECIFIC_INFO_LEN]; +}; + +struct slsi_hal_nan_transmit_followup_req { + /* Publish or Subscribe Id of an earlier Publish/Subscribe */ + u16 publish_subscribe_id; + + /* This Id is the Requestor Instance that is passed as + * part of earlier MatchInd/FollowupInd message. + */ + u32 requestor_instance_id; + u8 addr[ETH_ALEN]; /* Unicast address */ + u8 priority; /* priority of the request 2=high */ + u8 dw_or_faw; /* 0= send in a DW, 1=send in FAW */ + + /* Sequence of values which further specify the published service beyond + * the service name. + */ + u16 service_specific_info_len; + u8 service_specific_info[SLSI_HAL_NAN_MAX_SERVICE_SPECIFIC_INFO_LEN]; + /* Set/Enable corresponding bits to disable + * responses after followUp. + * BIT0 - Disable followUp response from FW. + */ + u8 recv_indication_cfg; + + u16 sdea_service_specific_info_len; + u8 sdea_service_specific_info[SLSI_HAL_NAN_MAX_SDEA_SERVICE_SPECIFIC_INFO_LEN]; +}; + +struct slsi_hal_nan_config_req { + u8 config_sid_beacon; + u8 sid_beacon; + u8 config_rssi_proximity; + u8 rssi_proximity; + u8 config_master_pref; + u8 master_pref; + /* 1 byte value which defines the RSSI filter threshold. + * Any Service Descriptors received above this value + * that are configured for RSSI filtering will be dropped. + * The rssi values should be specified without sign. + * For eg: -70dBm should be specified as 70. + */ + u8 config_5g_rssi_close_proximity; + u8 rssi_close_proximity_5g_val; + u8 config_rssi_window_size; + u16 rssi_window_size_val; + /* If set to 1, the Discovery Engine will enclose the Cluster + * Attribute only sent in Beacons in a Vendor Specific Attribute + * and transmit in a Service Descriptor Frame. + */ + u8 config_cluster_attribute_val; + u8 config_scan_params; + struct slsi_hal_nan_social_channel_scan_params scan_params_val; + /* 1 byte quantity which forces the Random Factor to a particular + * value for all transmitted Sync/Discovery beacons + */ + u8 config_random_factor_force; + u8 random_factor_force_val; + /* 1 byte quantity which forces the HC for all transmitted Sync and + * Discovery Beacon NO matter the real HC being received over the + * air. + */ + u8 config_hop_count_force; + u8 hop_count_force_val; + /* NAN Post Connectivity Capability */ + u8 config_conn_capability; + struct slsi_hal_nan_connectivity_capability conn_capability_val; + /* NAN Post Discover Capability */ + u8 num_config_discovery_attr; + struct slsi_hal_nan_post_discovery_param discovery_attr_val[SLSI_HAL_NAN_MAX_POSTDISCOVERY_LEN]; + /* NAN Further availability Map */ + u8 config_fam; + struct slsi_hal_nan_further_availability_map fam_val; + + int channel_5g_val; + u8 config_subscribe_sid_beacon; + u32 subscribe_sid_beacon_val; + + /*NanConfigDW config_dw*/ + u8 config_2dot4g_dw_band; + u32 dw_2dot4g_interval_val; + u8 config_5g_dw_band; + u32 dw_5g_interval_val; + u32 disc_mac_addr_rand_interval_sec; + +}; + +struct slsi_hal_nan_capabilities { + u32 max_concurrent_nan_clusters; + u32 max_publishes; + u32 max_subscribes; + u32 max_service_name_len; + u32 max_match_filter_len; + u32 max_total_match_filter_len; + u32 max_service_specific_info_len; + u32 max_vsa_data_len; + u32 max_mesh_data_len; + u32 max_ndi_interfaces; + u32 max_ndp_sessions; + u32 max_app_info_len; +}; + +struct slsi_hal_nan_followup_ind { + u16 publish_subscribe_id; + u32 requestor_instance_id; + u8 addr[ETH_ALEN]; + u8 dw_or_faw; + u16 service_specific_info_len; + u8 service_specific_info[SLSI_HAL_NAN_MAX_SERVICE_SPECIFIC_INFO_LEN]; + u16 sdea_service_specific_info_len; + u8 sdea_service_specific_info[SLSI_HAL_NAN_MAX_SDEA_SERVICE_SPECIFIC_INFO_LEN]; +}; + +struct slsi_hal_nan_match_ind { + u16 publish_subscribe_id; + u32 requestor_instance_id; + u8 addr[ETH_ALEN]; + u16 service_specific_info_len; + u8 service_specific_info[SLSI_HAL_NAN_MAX_SERVICE_SPECIFIC_INFO_LEN]; + u16 sdf_match_filter_len; + u8 sdf_match_filter[SLSI_HAL_NAN_MAX_MATCH_FILTER_LEN]; + u8 match_occurred_flag; + u8 out_of_resource_flag; + u8 rssi_value; + u8 is_conn_capability_valid; + struct slsi_hal_nan_connectivity_capability conn_capability; + u8 num_rx_discovery_attr; + struct slsi_hal_nan_receive_post_discovery discovery_attr[SLSI_HAL_NAN_MAX_POSTDISCOVERY_LEN]; + u8 num_chans; + struct slsi_hal_nan_further_availability_channel famchan[32]; + u8 cluster_attribute_len; + u8 cluster_attribute[32]; + struct slsi_nan_security_info sec_info; + struct slsi_nan_sdea_ctrl_params peer_sdea_params; + u32 range_measurement_mm; + u32 ranging_event_type; + u16 sdea_service_specific_info_len; + u8 sdea_service_specific_info[SLSI_HAL_NAN_MAX_SDEA_SERVICE_SPECIFIC_INFO_LEN]; +}; + +void slsi_nan_event(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_nan_followup_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_nan_service_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_nan_get_mac(struct slsi_dev *sdev, char *nan_mac_addr); +struct net_device *slsi_nan_get_netdev(struct slsi_dev *sdev); +int slsi_nan_enable(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_disable(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_publish(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_publish_cancel(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int len); +int slsi_nan_subscribe(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_subscribe_cancel(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_transmit_followup(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_set_config(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +int slsi_nan_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len); +#endif diff --git a/drivers/net/wireless/scsc/porting_imx.h b/drivers/net/wireless/scsc/porting_imx.h new file mode 100644 index 000000000000..ac9765a65eeb --- /dev/null +++ b/drivers/net/wireless/scsc/porting_imx.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/**/ + +#ifndef _PORTING_IMX__H__ +#define _PORTING_IMX__H__ +#endif +/** + * ether_addr_copy - Copy an Ethernet address + * @dst: Pointer to a six-byte array Ethernet address destination + * @src: Pointer to a six-byte array Ethernet address source + * + * Please note: dst & src must both be aligned to u16. + */ +static inline void ether_addr_copy(u8 *dst, const u8 *src) +{ +#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) + *(u32 *)dst = *(const u32 *)src; + *(u16 *)(dst + 4) = *(const u16 *)(src + 4); +#else + u16 *a = (u16 *)dst; + const u16 *b = (const u16 *)src; + + a[0] = b[0]; + a[1] = b[1]; + a[2] = b[2]; +#endif +} + +static inline ktime_t ktime_add_ms(const ktime_t kt, const u64 msec) +{ + return ktime_add_ns(kt, msec * NSEC_PER_MSEC); +} + + diff --git a/drivers/net/wireless/scsc/procfs.c b/drivers/net/wireless/scsc/procfs.c new file mode 100755 index 000000000000..738141f31e3a --- /dev/null +++ b/drivers/net/wireless/scsc/procfs.c @@ -0,0 +1,1204 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "dev.h" +#include "procfs.h" +#include "debug.h" +#include "mlme.h" +#include "mgt.h" +#include "mib.h" +#include "cac.h" +#include "hip.h" +#include "netif.h" +#include "ioctl.h" +#include "nl80211_vendor.h" + +#include "mib.h" + +int slsi_procfs_open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = SLSI_PDE_DATA(inode); + return 0; +} + +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG +static int slsi_printf_mutex_stats(char *buf, const size_t bufsz, const char *printf_padding, struct slsi_mutex *mutex_p) +{ + int pos = 0; + const char *filename; + bool is_locked; + + if (mutex_p->valid) { + is_locked = SLSI_MUTEX_IS_LOCKED(*mutex_p); + pos += scnprintf(buf, bufsz, "INFO: lock:%d\n", is_locked); + if (is_locked) { + filename = strrchr(mutex_p->file_name_before, '/'); + if (filename) + filename++; + else + filename = mutex_p->file_name_before; + pos += scnprintf(buf + pos, bufsz - pos, "\t%sTryingToAcquire:%s:%d\n", printf_padding, + filename, mutex_p->line_no_before); + filename = strrchr(mutex_p->file_name_after, '/'); + if (filename) + filename++; + else + filename = mutex_p->file_name_after; + pos += scnprintf(buf + pos, bufsz - pos, "\t%sAcquired:%s:%d:%s\n", printf_padding, + filename, mutex_p->line_no_after, mutex_p->function); + pos += scnprintf(buf + pos, bufsz - pos, "\t%sProcessName:%s\n", printf_padding, mutex_p->owner->comm); + } + } else { + pos += scnprintf(buf, bufsz, "NoInit\n"); + } + return pos; +} + +static ssize_t slsi_procfs_mutex_stats_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[76 + (200 * CONFIG_SCSC_WLAN_MAX_INTERFACES)]; + int pos = 0; + int i; + const size_t bufsz = sizeof(buf); + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + struct net_device *dev; + struct netdev_vif *ndev_vif; + + SLSI_UNUSED_PARAMETER(file); + + pos += scnprintf(buf, bufsz, "sdev\n"); + pos += scnprintf(buf + pos, bufsz - pos, "\tnetdev_add_remove_mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t", &sdev->netdev_add_remove_mutex); + pos += scnprintf(buf + pos, bufsz - pos, "\tstart_stop_mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t", &sdev->start_stop_mutex); + pos += scnprintf(buf + pos, bufsz - pos, "\tdevice_config_mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t", &sdev->device_config_mutex); + pos += scnprintf(buf + pos, bufsz - pos, "\tsig_wait.mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t", &sdev->sig_wait.mutex); +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + pos += scnprintf(buf + pos, bufsz - pos, "\tlogger_mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t", &sdev->logger_mutex); +#endif + + for (i = 1; i < CONFIG_SCSC_WLAN_MAX_INTERFACES + 1; i++) { + pos += scnprintf(buf + pos, bufsz - pos, "netdevvif %d\n", i); + dev = slsi_get_netdev_locked(sdev, i); + if (!dev) + continue; + ndev_vif = netdev_priv(dev); + if (ndev_vif->is_available) { + pos += scnprintf(buf + pos, bufsz - pos, "\tvif_mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t\t", &ndev_vif->vif_mutex); + pos += scnprintf(buf + pos, bufsz - pos, "\tsig_wait.mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t\t", &ndev_vif->sig_wait.mutex); + pos += scnprintf(buf + pos, bufsz - pos, "\tscan_mutex "); + pos += slsi_printf_mutex_stats(buf + pos, bufsz - pos, "\t\t", &ndev_vif->scan_mutex); + } else { + pos += scnprintf(buf + pos, bufsz - pos, "\tvif UNAVAILABLE\n"); + } + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} +#endif + +static ssize_t slsi_procfs_throughput_stats_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[5 * 25]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + struct net_device *dev; + struct netdev_vif *ndev_vif; + struct slsi_mib_data mibrsp = { 0, NULL }; + struct slsi_mib_value *values = NULL; + struct slsi_mib_get_entry get_values[] = {{ SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 3, 0 } }, + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 4, 0 } }, + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 5, 0 } }, + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 25, 0 } }, + { SLSI_PSID_UNIFI_THROUGHPUT_DEBUG, { 30, 0 } } }; + + SLSI_UNUSED_PARAMETER(file); + + dev = slsi_get_netdev(sdev, 1); + ndev_vif = netdev_priv(dev); + + if (ndev_vif->activated) { + mibrsp.dataLength = 15 * ARRAY_SIZE(get_values); + mibrsp.data = kmalloc(mibrsp.dataLength, GFP_KERNEL); + if (!mibrsp.data) + SLSI_ERR(sdev, "Cannot kmalloc %d bytes\n", mibrsp.dataLength); + values = slsi_read_mibs(sdev, dev, get_values, ARRAY_SIZE(get_values), &mibrsp); + if (!values) { + kfree(mibrsp.data); + return -EINVAL; + } + if (values[0].type != SLSI_MIB_TYPE_UINT) + SLSI_ERR(sdev, "invalid type. iter:%d", 0); /*bad_fcs_count*/ + if (values[1].type != SLSI_MIB_TYPE_UINT) + SLSI_ERR(sdev, "invalid type. iter:%d", 1); /*missed_ba_count*/ + if (values[2].type != SLSI_MIB_TYPE_UINT) + SLSI_ERR(sdev, "invalid type. iter:%d", 2); /*missed_ack_count*/ + if (values[3].type != SLSI_MIB_TYPE_UINT) + SLSI_ERR(sdev, "invalid type. iter:%d", 3); /*mac_bad_sig_count*/ + if (values[4].type != SLSI_MIB_TYPE_UINT) + SLSI_ERR(sdev, "invalid type. iter:%d", 4); /*rx_error_count*/ + + pos += scnprintf(buf, bufsz, "RX FCS: %d\n", values[0].u.uintValue); + pos += scnprintf(buf + pos, bufsz - pos, "RX bad SIG: %d\n", values[3].u.uintValue); + pos += scnprintf(buf + pos, bufsz - pos, "RX dot11 error: %d\n", values[4].u.uintValue); + pos += scnprintf(buf + pos, bufsz - pos, "TX MPDU no ACK: %d\n", values[2].u.uintValue); + pos += scnprintf(buf + pos, bufsz - pos, "TX A-MPDU no ACK: %d\n", values[1].u.uintValue); + + kfree(values); + kfree(mibrsp.data); + } else { + pos += scnprintf(buf, bufsz, "RX FCS: %d\n", 0); + pos += scnprintf(buf + pos, bufsz - pos, "RX bad SIG: %d\n", 0); + pos += scnprintf(buf + pos, bufsz - pos, "RX dot11 error: %d\n", 0); + pos += scnprintf(buf + pos, bufsz - pos, "TX MPDU no ACK: %d\n", 0); + pos += scnprintf(buf + pos, bufsz - pos, "TX A-MPDU no ACK: %d\n", 0); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t slsi_procfs_sta_bss_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[100]; + int pos; + const size_t bufsz = sizeof(buf); + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + struct net_device *dev; + struct netdev_vif *ndev_vif; + struct cfg80211_bss *sta_bss; + int channel = 0, center_freq = 0; + u8 no_mac[] = {0, 0, 0, 0, 0, 0}; + u8 *mac_ptr; + u8 ssid[33]; + s32 signal = 0; + + SLSI_UNUSED_PARAMETER(file); + + mac_ptr = no_mac; + ssid[0] = 0; + + dev = slsi_get_netdev(sdev, 1); + if (!dev) + goto exit; + + ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + sta_bss = ndev_vif->sta.sta_bss; + if (sta_bss && ndev_vif->vif_type == FAPI_VIFTYPE_STATION && + ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED) { + const u8 *ssid_ie = cfg80211_find_ie(WLAN_EID_SSID, sta_bss->ies->data, sta_bss->ies->len); + + if (ssid_ie) { + memcpy(ssid, &ssid_ie[2], ssid_ie[1]); + ssid[ssid_ie[1]] = 0; + } + + if (sta_bss->channel) { + channel = sta_bss->channel->hw_value; + center_freq = sta_bss->channel->center_freq; + } + mac_ptr = sta_bss->bssid; + signal = sta_bss->signal; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + +exit: + pos = scnprintf(buf, bufsz, "%pM,%s,%d,%d,%d", mac_ptr, ssid, channel, center_freq, signal); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t slsi_procfs_big_data_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[100]; + int pos; + const size_t bufsz = sizeof(buf); + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + struct net_device *dev; + struct netdev_vif *ndev_vif; + + SLSI_UNUSED_PARAMETER(file); + dev = slsi_get_netdev(sdev, 1); + if (!dev) + goto exit; + + ndev_vif = netdev_priv(dev); + +exit: + pos = slsi_get_sta_info(dev, buf, bufsz); + if (pos >= 0) + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); + return 0; +} + +static int slsi_procfs_status_show(struct seq_file *m, void *v) +{ + struct slsi_dev *sdev = (struct slsi_dev *)m->private; + const char *state; + u32 conf_hip4_ver = 0; + int i; + + SLSI_UNUSED_PARAMETER(v); + + switch (sdev->device_state) { + case SLSI_DEVICE_STATE_ATTACHING: + state = "Attaching"; + break; + case SLSI_DEVICE_STATE_STOPPED: + state = "Stopped"; + break; + case SLSI_DEVICE_STATE_STARTING: + state = "Starting"; + break; + case SLSI_DEVICE_STATE_STARTED: + state = "Started"; + break; + case SLSI_DEVICE_STATE_STOPPING: + state = "Stopping"; + break; + default: + state = "UNKNOWN"; + break; + } + + seq_printf(m, "Driver FAPI Version: MA SAP : %d.%d.%d\n", FAPI_MAJOR_VERSION(FAPI_DATA_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_DATA_SAP_VERSION), FAPI_DATA_SAP_ENG_VERSION); + seq_printf(m, "Driver FAPI Version: MLME SAP : %d.%d.%d\n", FAPI_MAJOR_VERSION(FAPI_CONTROL_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_CONTROL_SAP_VERSION), FAPI_CONTROL_SAP_ENG_VERSION); + seq_printf(m, "Driver FAPI Version: DEBUG SAP : %d.%d.%d\n", FAPI_MAJOR_VERSION(FAPI_DEBUG_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_DEBUG_SAP_VERSION), FAPI_DEBUG_SAP_ENG_VERSION); + seq_printf(m, "Driver FAPI Version: TEST SAP : %d.%d.%d\n", FAPI_MAJOR_VERSION(FAPI_TEST_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_TEST_SAP_VERSION), FAPI_TEST_SAP_ENG_VERSION); + + if (atomic_read(&sdev->hip.hip_state) == SLSI_HIP_STATE_STARTED) { + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + seq_printf(m, "HIP4 Version : %d\n", conf_hip4_ver); + if (conf_hip4_ver == 4) { + seq_printf(m, "Chip FAPI Version (v4): MA SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_ma_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_ma_ver))); + seq_printf(m, "Chip FAPI Version (v4): MLME SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_mlme_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_mlme_ver))); + seq_printf(m, "Chip FAPI Version (v4): DEBUG SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_debug_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_debug_ver))); + seq_printf(m, "Chip FAPI Version (v4): TEST SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_test_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_test_ver))); + } else if (conf_hip4_ver == 5) { + seq_printf(m, "Chip FAPI Version (v5): MA SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_ma_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_ma_ver))); + seq_printf(m, "Chip FAPI Version (v5): MLME SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_mlme_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_mlme_ver))); + seq_printf(m, "Chip FAPI Version (v5): DEBUG SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_debug_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_debug_ver))); + seq_printf(m, "Chip FAPI Version (v5): TEST SAP : %d.%d\n", + FAPI_MAJOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_test_ver)), + FAPI_MINOR_VERSION(scsc_wifi_get_hip_config_version_5_u16(&sdev->hip4_inst.hip_control->config_v5, sap_test_ver))); + } + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + seq_puts(m, "Driver Debug : Enabled\n"); +#else + seq_puts(m, "Driver Debug : Disabled\n"); +#endif + seq_printf(m, "Driver State : %s\n", state); + + seq_printf(m, "HW Version [MIB] : 0x%.4X (%u)\n", sdev->chip_info_mib.chip_version, sdev->chip_info_mib.chip_version); + seq_printf(m, "Platform Build [MIB] : 0x%.4X (%u)\n", sdev->plat_info_mib.plat_build, sdev->plat_info_mib.plat_build); + for (i = 0; i < SLSI_WLAN_MAX_MIB_FILE; i++) { + seq_printf(m, "Hash [MIB%2d] : 0x%.4X (%u)\n", i, sdev->mib[i].mib_hash, sdev->mib[i].mib_hash); + seq_printf(m, "Platform: [MIB%2d] : %s\n", i, sdev->mib[i].platform); + } + seq_printf(m, "Hash [local_MIB] : 0x%.4X (%u)\n", sdev->local_mib.mib_hash, sdev->local_mib.mib_hash); + seq_printf(m, "Platform: [local_MIB] : %s\n", sdev->local_mib.platform); + + return 0; +} + +static int slsi_procfs_build_show(struct seq_file *m, void *v) +{ + SLSI_UNUSED_PARAMETER(v); + seq_printf(m, "FAPI_DATA_SAP_VERSION : %d.%d.%d\n", + FAPI_MAJOR_VERSION(FAPI_DATA_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_DATA_SAP_VERSION), + FAPI_DATA_SAP_ENG_VERSION); + seq_printf(m, "FAPI_CONTROL_SAP_VERSION : %d.%d.%d\n", + FAPI_MAJOR_VERSION(FAPI_CONTROL_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_CONTROL_SAP_VERSION), + FAPI_CONTROL_SAP_ENG_VERSION); + seq_printf(m, "FAPI_DEBUG_SAP_VERSION : %d.%d.%d\n", + FAPI_MAJOR_VERSION(FAPI_DEBUG_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_DEBUG_SAP_VERSION), + FAPI_DEBUG_SAP_ENG_VERSION); + seq_printf(m, "FAPI_TEST_SAP_VERSION : %d.%d.%d\n", + FAPI_MAJOR_VERSION(FAPI_TEST_SAP_VERSION), + FAPI_MINOR_VERSION(FAPI_TEST_SAP_VERSION), + FAPI_TEST_SAP_ENG_VERSION); + seq_printf(m, "CONFIG_SCSC_WLAN_MAX_INTERFACES : %d\n", CONFIG_SCSC_WLAN_MAX_INTERFACES); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + seq_puts(m, "CONFIG_SCSC_WLAN_RX_NAPI : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_RX_NAPI : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_RX_NAPI_GRO + seq_puts(m, "CONFIG_SCSC_WLAN_RX_NAPI_GRO : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_RX_NAPI_GRO : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_ANDROID + seq_puts(m, "CONFIG_SCSC_WLAN_ANDROID : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_ANDROID : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + seq_puts(m, "CONFIG_SCSC_WLAN_GSCAN_ENABLE : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_GSCAN_ENABLE : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD + seq_puts(m, "CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_PRIORITISE_IMP_FRAMES + seq_puts(m, "CONFIG_SCSC_WLAN_PRIORITISE_IMP_FRAMES : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_PRIORITISE_IMP_FRAMES : n\n"); +#endif + seq_puts(m, "-------------------------------------------------\n"); +#ifdef CONFIG_SCSC_WLAN_DEBUG + seq_puts(m, "CONFIG_SCSC_WLAN_DEBUG : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_DEBUG : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_SKB_TRACKING + seq_puts(m, "CONFIG_SCSC_WLAN_SKB_TRACKING : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_SKB_TRACKING : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + seq_puts(m, "CONFIG_SCSC_WLAN_MUTEX_DEBUG : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_MUTEX_DEBUG : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + seq_puts(m, "CONFIG_SCSC_WLAN_ENHANCED_LOGGING : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_ENHANCED_LOGGING : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_WIFI_SHARING + seq_puts(m, "CONFIG_SCSC_WLAN_WIFI_SHARING : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_WIFI_SHARING : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_SINGLE_ANTENNA + seq_puts(m, "CONFIG_SCSC_WLAN_SINGLE_ANTENNA : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_SINGLE_ANTENNA : n\n"); +#endif +#ifdef CONFIG_SCSC_AP_INTERFACE_NAME + seq_printf(m, "CONFIG_SCSC_AP_INTERFACE_NAME : %s\n", CONFIG_SCSC_AP_INTERFACE_NAME); +#endif +#ifdef CONFIG_SCSC_WIFI_NAN_ENABLE + seq_puts(m, "CONFIG_SCSC_WIFI_NAN_ENABLE : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WIFI_NAN_ENABLE : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA + seq_puts(m, "CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_SET_PREFERRED_ANTENNA : n\n"); +#endif +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + seq_puts(m, "CONFIG_SLSI_WLAN_STA_FWD_BEACON : y\n"); +#else + seq_puts(m, "CONFIG_SLSI_WLAN_STA_FWD_BEACON : n\n"); +#endif +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + seq_puts(m, "CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT : y\n"); +#else + seq_puts(m, "CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT : n\n"); +#endif + + return 0; +} + +static const char *slsi_procfs_vif_type_to_str(u16 type) +{ + switch (type) { + case FAPI_VIFTYPE_STATION: + return "STATION"; + case FAPI_VIFTYPE_AP: + return "AP"; + case FAPI_VIFTYPE_UNSYNCHRONISED: + return "UNSYNCH"; + default: + return "?"; + } +} + +static int slsi_procfs_vifs_show(struct seq_file *m, void *v) +{ + struct slsi_dev *sdev = (struct slsi_dev *)m->private; + u16 vif; + u16 peer_index; + + SLSI_UNUSED_PARAMETER(v); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (vif = 1; vif <= CONFIG_SCSC_WLAN_MAX_INTERFACES; vif++) { + struct net_device *dev = slsi_get_netdev_locked(sdev, vif); + struct netdev_vif *ndev_vif; + + if (!dev) + continue; + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + continue; + } + seq_printf(m, "vif:%d %pM %s\n", vif, dev->dev_addr, slsi_procfs_vif_type_to_str(ndev_vif->vif_type)); + for (peer_index = 0; peer_index < SLSI_ADHOC_PEER_CONNECTIONS_MAX; peer_index++) { + struct slsi_peer *peer = ndev_vif->peer_sta_record[peer_index]; + + if (peer && peer->valid) + seq_printf(m, "vif:%d %pM peer[%d] %pM\n", vif, dev->dev_addr, peer_index, peer->address); + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + return 0; +} + +static ssize_t slsi_procfs_read_int(struct file *file, char __user *user_buf, size_t count, loff_t *ppos, int value, const char *extra) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + SLSI_UNUSED_PARAMETER(file); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", value); + if (extra) + pos += scnprintf(buf + pos, bufsz - pos, "%s", extra); + SLSI_INFO((struct slsi_dev *)file->private_data, "%s", buf); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t slsi_procfs_uapsd_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + struct net_device *dev = NULL; + struct netdev_vif *ndev_vif = NULL; + int qos_info = 0; + int offset = 0; + char *read_string; + + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + + if (!dev) { + SLSI_ERR(sdev, "Dev not found\n"); + return -EINVAL; + } + + ndev_vif = netdev_priv(dev); + + if (!count) + return -EINVAL; + + read_string = kmalloc(count + 1, GFP_KERNEL); + memset(read_string, 0, (count + 1)); + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + offset = slsi_str_to_int(read_string, &qos_info); + if (!offset) { + SLSI_ERR(sdev, "qos info : failed to read a numeric value"); + kfree(read_string); + return -EINVAL; + } + + /*Store the qos info and use it to set MIB during connection*/ + sdev->device_config.qos_info = qos_info; + SLSI_DBG1(sdev, SLSI_MLME, "set qos_info:%d\n", sdev->device_config.qos_info); + + + kfree(read_string); + return count; +} + +static ssize_t slsi_procfs_ap_cert_disable_ht_vht_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + int offset = 0; + int width = 0; + char *read_string; + + if (!count) + return -EINVAL; + + read_string = kmalloc(count + 1, GFP_KERNEL); + memset(read_string, 0, (count + 1)); + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + offset = slsi_str_to_int(read_string, &width); + if (!offset) { + SLSI_ERR(sdev, "Failed to read a numeric value"); + kfree(read_string); + return -EINVAL; + } + + /* Disable default upgrade of corresponding width during AP start */ + if (width == 80) + sdev->allow_switch_80_mhz = false; + else if (width == 40) + sdev->allow_switch_40_mhz = false; + + kfree(read_string); + return count; +} + +static ssize_t slsi_procfs_p2p_certif_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + char *read_string; + int cert_info = 0; + int offset = 0; + + read_string = kmalloc(count + 1, GFP_KERNEL); + memset(read_string, 0, (count + 1)); + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + offset = slsi_str_to_int(read_string, &cert_info); + if (!offset) { + SLSI_ERR(sdev, "qos info : failed to read a numeric value"); + kfree(read_string); + return -EINVAL; + } + sdev->p2p_certif = cert_info; + kfree(read_string); + return count; +} + +static ssize_t slsi_procfs_p2p_certif_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + SLSI_UNUSED_PARAMETER(file); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", sdev->p2p_certif); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static int slsi_procfs_mac_addr_show(struct seq_file *m, void *v) +{ + struct slsi_dev *sdev = (struct slsi_dev *)m->private; + + SLSI_UNUSED_PARAMETER(v); + + seq_printf(m, "%pM", sdev->hw_addr); + return 0; +} + +static ssize_t slsi_procfs_create_tspec_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + static const char *extra_info = ""; + + return slsi_procfs_read_int(file, user_buf, count, ppos, sdev->current_tspec_id, extra_info); +} + +static ssize_t slsi_procfs_create_tspec_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sfdev = (struct slsi_dev *)file->private_data; + char *read_string = kmalloc(count + 1, GFP_KERNEL); + + if (!read_string) { + SLSI_ERR(sfdev, "Malloc for read_string failed\n"); + return -ENOMEM; + } + + if (!count) { + kfree(read_string); + return 0; + } + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + sfdev->current_tspec_id = cac_ctrl_create_tspec(sfdev, read_string); + if (sfdev->current_tspec_id < 0) { + SLSI_ERR(sfdev, "create tspec: No parameters or not valid parameters\n"); + kfree(read_string); + return -EINVAL; + } + kfree(read_string); + + return count; +} + +static ssize_t slsi_procfs_confg_tspec_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + static const char *extra_info = "Not implemented yet"; + int value = 10; + + return slsi_procfs_read_int(file, user_buf, count, ppos, value, extra_info); +} + +static ssize_t slsi_procfs_confg_tspec_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sfdev = (struct slsi_dev *)file->private_data; + char *read_string = kmalloc(count + 1, GFP_KERNEL); + + if (!read_string) { + SLSI_ERR(sfdev, "Malloc for read_string failed\n"); + return -ENOMEM; + } + + if (!count) { + kfree(read_string); + return 0; + } + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + /* to do: call to config_tspec() to configure a tspec field */ + if (cac_ctrl_config_tspec(sfdev, read_string) < 0) { + SLSI_ERR(sfdev, "config tspec error\n"); + kfree(read_string); + return -EINVAL; + } + + kfree(read_string); + + return count; +} + +static ssize_t slsi_procfs_send_addts_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + static const char *extra_info = ""; + + return slsi_procfs_read_int(file, user_buf, count, ppos, sdev->tspec_error_code, extra_info); +} + +static ssize_t slsi_procfs_send_addts_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sfdev = (struct slsi_dev *)file->private_data; + char *read_string = kmalloc(count + 1, GFP_KERNEL); + + if (!read_string) { + SLSI_ERR(sfdev, "Malloc for read_string failed\n"); + return -ENOMEM; + } + + sfdev->tspec_error_code = -1; + if (!count) { + kfree(read_string); + return 0; + } + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + /* to do: call to config_tspec() to configure a tspec field */ + if (cac_ctrl_send_addts(sfdev, read_string) < 0) { + SLSI_ERR(sfdev, "send addts error\n"); + kfree(read_string); + return -EINVAL; + } + kfree(read_string); + return count; +} + +static ssize_t slsi_procfs_send_delts_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = file->private_data; + static const char *extra_info = ""; + + return slsi_procfs_read_int(file, user_buf, count, ppos, sdev->tspec_error_code, extra_info); +} + +static ssize_t slsi_procfs_send_delts_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sfdev = (struct slsi_dev *)file->private_data; + char *read_string = kmalloc(count + 1, GFP_KERNEL); + + if (!read_string) { + SLSI_ERR(sfdev, "Malloc for read_string failed\n"); + return -ENOMEM; + } + + sfdev->tspec_error_code = -1; + + if (!count) { + kfree(read_string); + return 0; + } + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + /* to do: call to config_tspec() to configure a tspec field */ + if (cac_ctrl_send_delts(sfdev, read_string) < 0) { + SLSI_ERR(sfdev, "send delts error\n"); + kfree(read_string); + return -EINVAL; + } + kfree(read_string); + return count; +} + +static ssize_t slsi_procfs_del_tspec_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + static const char *extra_info = "Not implemented yet"; + int value = 10; + + return slsi_procfs_read_int(file, user_buf, count, ppos, value, extra_info); +} + +static ssize_t slsi_procfs_del_tspec_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sfdev = (struct slsi_dev *)file->private_data; + char *read_string = kmalloc(count + 1, GFP_KERNEL); + + if (!read_string) { + SLSI_ERR(sfdev, "Malloc for read_string failed\n"); + return -ENOMEM; + } + + if (!count) { + kfree(read_string); + return 0; + } + + simple_write_to_buffer(read_string, count, ppos, user_buf, count); + read_string[count] = '\0'; + + /* to do: call to config_tspec() to configure a tspec field */ + if (cac_ctrl_delete_tspec(sfdev, read_string) < 0) { + SLSI_ERR(sfdev, "config tspec error\n"); + kfree(read_string); + return -EINVAL; + } + kfree(read_string); + return count; +} + +static ssize_t slsi_procfs_tput_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + struct net_device *dev; + struct netdev_vif *ndev_vif; + int i; + char buf[256]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + dev = sdev->netdev[i]; + if (dev) { + ndev_vif = netdev_priv(dev); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t", dev->name); + if (ndev_vif->throughput_tx_bps < 1000) + pos += scnprintf(buf + pos, bufsz - pos, "TX:%u bps\t", ndev_vif->throughput_tx_bps); + else if ((ndev_vif->throughput_tx_bps >= 1000) && (ndev_vif->throughput_tx_bps < (1000 * 1000))) + pos += scnprintf(buf + pos, bufsz - pos, "TX:%u Kbps\t", (ndev_vif->throughput_tx_bps / 1000)); + else + pos += scnprintf(buf + pos, bufsz - pos, "TX:%u Mbps\t", (ndev_vif->throughput_tx_bps / (1000 * 1000))); + + if (ndev_vif->throughput_rx_bps < 1000) + pos += scnprintf(buf + pos, bufsz - pos, "RX:%u bps\n", ndev_vif->throughput_rx_bps); + else if ((ndev_vif->throughput_rx_bps >= 1000) && (ndev_vif->throughput_rx_bps < (1000 * 1000))) + pos += scnprintf(buf + pos, bufsz - pos, "RX:%u Kbps\n", (ndev_vif->throughput_rx_bps / 1000)); + else + pos += scnprintf(buf + pos, bufsz - pos, "RX:%u Mbps\n", (ndev_vif->throughput_rx_bps / (1000 * 1000))); + } + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t slsi_procfs_tput_write(struct file *file, const char __user *user_buf, size_t len, loff_t *ppos) +{ + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + char read_buf[2]; + + if (!sdev) { + SLSI_ERR(sdev, "invalid sdev\n"); + return -ENOMEM; + } + + if (!len || (len > sizeof(read_buf))) { + SLSI_ERR(sdev, "invalid len\n"); + return -EINVAL; + } + + simple_write_to_buffer(read_buf, len, ppos, user_buf, len); + + switch (read_buf[0]) { + case '1': + if (!slsi_traffic_mon_is_running(sdev)) { + SLSI_DBG1(sdev, SLSI_HIP, "start Traffic monitor\n"); + slsi_traffic_mon_client_register(sdev, sdev, 0, 0, 0, NULL); + } + break; + case '0': + SLSI_DBG1(sdev, SLSI_HIP, "stop Traffic monitor\n"); + slsi_traffic_mon_client_unregister(sdev, sdev); + break; + default: + SLSI_DBG1(sdev, SLSI_HIP, "invalid value %c\n", read_buf[0]); + return -EINVAL; + } + return len; +} + +static atomic_t fd_opened_count; + +void slsi_procfs_inc_node(void) +{ + atomic_inc(&fd_opened_count); +} + +void slsi_procfs_dec_node(void) +{ + if (0 == atomic_read(&fd_opened_count)) { + WARN_ON(1); + return; + } + atomic_dec(&fd_opened_count); +} + +static ssize_t slsi_procfs_fd_opened_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[128]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + SLSI_UNUSED_PARAMETER(file); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", atomic_read(&fd_opened_count)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static int slsi_procfs_fcq_show(struct seq_file *m, void *v) +{ + struct slsi_dev *sdev = (struct slsi_dev *)m->private; + int ac; + s32 vif, i; + + SLSI_UNUSED_PARAMETER(v); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (vif = 1; vif <= CONFIG_SCSC_WLAN_MAX_INTERFACES; vif++) { + struct net_device *dev = slsi_get_netdev_locked(sdev, vif); + struct netdev_vif *ndev_vif; + + if (!dev) + continue; + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* Unicast */ + for (i = 0; i < SLSI_ADHOC_PEER_CONNECTIONS_MAX; i++) { + struct slsi_peer *peer = ndev_vif->peer_sta_record[i]; + int smod = 0, scod = 0, qmod = 0, qcod = 0; + struct scsc_wifi_fcq_q_stat queue_stat; + u32 peer_ps_state_transitions = 0; + enum scsc_wifi_fcq_8021x_state cp_state; + + if (!peer || !peer->valid) + continue; + + if (scsc_wifi_fcq_stat_queueset(&peer->data_qs, &queue_stat, &smod, &scod, &cp_state, &peer_ps_state_transitions) != 0) + continue; + + seq_printf(m, "|%-12s|%-6d|%-6s|\n%d). peer:%pM, qs:%2d, smod:%u, scod:%u, netq stops :%u, netq resumes :%u, PS transitions :%u Controlled port :%s\n", + netdev_name(dev), + vif, + "UCAST", + i + 1, + peer->address, + peer->queueset, + smod, + scod, + queue_stat.netq_stops, + queue_stat.netq_resumes, + peer_ps_state_transitions, + cp_state == SCSC_WIFI_FCQ_8021x_STATE_BLOCKED ? "Blocked" : "Opened"); + + seq_printf(m, " |%-12s|%-17s|%4s|%8s|%8s|%8s|%8s|%10s|%8s|\n", + "netdev", + "peer", + "AC index", "qcod", "qmod", + "nq_state", "nq_stop", "nq_resume", + "tq_state"); + + for (ac = 0; ac < SLSI_NETIF_Q_PER_PEER; ac++) { + if (scsc_wifi_fcq_stat_queue(&peer->data_qs.ac_q[ac].head, + &queue_stat, + &qmod, &qcod) == 0) + seq_printf(m, " |%-12s|%pM|%4d|%8u|%8u|%8u|%8u\n", + netdev_name(dev), + peer->address, + ac, + qcod, + qmod, + queue_stat.netq_stops, + queue_stat.netq_resumes); + else + break; + } + } + + /* Groupcast */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + int smod = 0, scod = 0, qmod = 0, qcod = 0; + struct scsc_wifi_fcq_q_stat queue_stat; + u32 peer_ps_state_transitions = 0; + enum scsc_wifi_fcq_8021x_state cp_state; + + if (scsc_wifi_fcq_stat_queueset(&ndev_vif->ap.group_data_qs, &queue_stat, &smod, &scod, &cp_state, &peer_ps_state_transitions) != 0) + continue; + + seq_printf(m, "|%-12s|%-6d|%-6s|\n%d). smod:%u, scod:%u, netq stops :%u, netq resumes :%u, PS transitions :%u Controlled port :%s\n", + netdev_name(dev), + vif, + "MCAST", + i + 1, + smod, + scod, + queue_stat.netq_stops, + queue_stat.netq_resumes, + peer_ps_state_transitions, + cp_state == SCSC_WIFI_FCQ_8021x_STATE_BLOCKED ? "Blocked" : "Opened"); + + seq_printf(m, " |%-12s|%4s|%8s|%8s|%8s|%8s|%10s|%8s|\n", + "netdev", + "AC index", "qcod", "qmod", + "nq_state", "nq_stop", "nq_resume", + "tq_state"); + + for (ac = 0; ac < SLSI_NETIF_Q_PER_PEER; ac++) { + if (scsc_wifi_fcq_stat_queue(&ndev_vif->ap.group_data_qs.ac_q[ac].head, + &queue_stat, + &qmod, &qcod) == 0) + seq_printf(m, " |%-12s|%4d|%8u|%8u|%8u|%8u\n", + netdev_name(dev), + ac, + qcod, + qmod, + queue_stat.netq_stops, + queue_stat.netq_resumes); + else + break; + } + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } + + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + + return 0; +} + +static int slsi_procfs_tcp_ack_suppression_show(struct seq_file *m, void *v) +{ + struct slsi_dev *sdev = (struct slsi_dev *)m->private; + struct net_device *dev; + struct netdev_vif *ndev_vif; + int i; + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + dev = sdev->netdev[i]; + if (dev) { + ndev_vif = netdev_priv(dev); + + seq_printf(m, "%s: tack_acks=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_acks); + seq_printf(m, "%s: tack_suppressed=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_suppressed); + seq_printf(m, "%s: tack_sent=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_sent); + seq_printf(m, "%s: tack_max=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_max); + seq_printf(m, "%s: tack_timeout=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_timeout); + seq_printf(m, "%s: tack_aged=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_ktime); + seq_printf(m, "%s: tack_dacks=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_dacks); + seq_printf(m, "%s: tack_sacks=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_sacks); + seq_printf(m, "%s: tack_delay_acks=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_delay_acks); + seq_printf(m, "%s: tack_low_window=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_low_window); + seq_printf(m, "%s: tack_ece=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_ece); + seq_printf(m, "%s: tack_nocache=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_nocache); + seq_printf(m, "%s: tack_norecord=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_norecord); + seq_printf(m, "%s: tack_lastrecord=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_lastrecord); + seq_printf(m, "%s: tack_searchrecord=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_searchrecord); + seq_printf(m, "%s: tack_hasdata=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_hasdata); + seq_printf(m, "%s: tack_psh=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_psh); + seq_printf(m, "%s: tack_dropped=%u\n", dev->name, ndev_vif->tcp_ack_stats.tack_dropped); + + /* reset stats after it is read */ + memset(&ndev_vif->tcp_ack_stats, 0, sizeof(struct slsi_tcp_ack_stats)); + } + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + return 0; +} + +static ssize_t slsi_procfs_nan_mac_addr_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[20]; + char nan_mac[ETH_ALEN]; + int pos = 0; +#ifdef CONFIG_SCSC_WIFI_NAN_ENABLE + struct slsi_dev *sdev = (struct slsi_dev *)file->private_data; + + slsi_nan_get_mac(sdev, nan_mac); +#else + + SLSI_UNUSED_PARAMETER(file); + memset(nan_mac, 0, ETH_ALEN); +#endif + pos = scnprintf(buf, sizeof(buf), "%pM", nan_mac); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +SLSI_PROCFS_SEQ_FILE_OPS(vifs); +SLSI_PROCFS_SEQ_FILE_OPS(mac_addr); +SLSI_PROCFS_WRITE_FILE_OPS(uapsd); +SLSI_PROCFS_WRITE_FILE_OPS(ap_cert_disable_ht_vht); +SLSI_PROCFS_RW_FILE_OPS(p2p_certif); +SLSI_PROCFS_RW_FILE_OPS(create_tspec); +SLSI_PROCFS_RW_FILE_OPS(confg_tspec); +SLSI_PROCFS_RW_FILE_OPS(send_addts); +SLSI_PROCFS_RW_FILE_OPS(send_delts); +SLSI_PROCFS_RW_FILE_OPS(del_tspec); +SLSI_PROCFS_RW_FILE_OPS(tput); +SLSI_PROCFS_READ_FILE_OPS(fd_opened); +SLSI_PROCFS_SEQ_FILE_OPS(build); +SLSI_PROCFS_SEQ_FILE_OPS(status); +SLSI_PROCFS_SEQ_FILE_OPS(fcq); +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG +SLSI_PROCFS_READ_FILE_OPS(mutex_stats); +#endif +SLSI_PROCFS_READ_FILE_OPS(sta_bss); +SLSI_PROCFS_READ_FILE_OPS(big_data); +SLSI_PROCFS_READ_FILE_OPS(throughput_stats); +SLSI_PROCFS_SEQ_FILE_OPS(tcp_ack_suppression); +SLSI_PROCFS_READ_FILE_OPS(nan_mac_addr); + + +int slsi_create_proc_dir(struct slsi_dev *sdev) +{ + char dir[16]; + struct proc_dir_entry *parent; + + (void)snprintf(dir, sizeof(dir), "driver/unifi%d", sdev->procfs_instance); + parent = proc_mkdir(dir, NULL); + if (parent) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) + parent->data = sdev; +#endif + sdev->procfs_dir = parent; + + SLSI_PROCFS_SEQ_ADD_FILE(sdev, build, parent, S_IRUSR | S_IRGRP | S_IROTH); + SLSI_PROCFS_SEQ_ADD_FILE(sdev, status, parent, S_IRUSR | S_IRGRP | S_IROTH); + SLSI_PROCFS_SEQ_ADD_FILE(sdev, fcq, parent, S_IRUSR | S_IRGRP | S_IROTH); + SLSI_PROCFS_SEQ_ADD_FILE(sdev, vifs, parent, S_IRUSR | S_IRGRP); + SLSI_PROCFS_SEQ_ADD_FILE(sdev, mac_addr, parent, S_IRUSR | S_IRGRP | S_IROTH); /*Add S_IROTH permission so that android settings can access it*/ + SLSI_PROCFS_ADD_FILE(sdev, uapsd, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, ap_cert_disable_ht_vht, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, p2p_certif, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, create_tspec, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, confg_tspec, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, send_addts, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, send_delts, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, del_tspec, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, tput, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, fd_opened, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + SLSI_PROCFS_ADD_FILE(sdev, mutex_stats, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); +#endif + SLSI_PROCFS_ADD_FILE(sdev, sta_bss, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, big_data, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_ADD_FILE(sdev, throughput_stats, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + SLSI_PROCFS_SEQ_ADD_FILE(sdev, tcp_ack_suppression, sdev->procfs_dir, S_IRUSR | S_IRGRP); + SLSI_PROCFS_ADD_FILE(sdev, nan_mac_addr, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + return 0; + } + +err: + SLSI_DBG1(sdev, SLSI_HIP, "Failure in creation of proc directories\n"); + return -EINVAL; +} + +void slsi_remove_proc_dir(struct slsi_dev *sdev) +{ + if (sdev->procfs_dir) { + char dir[32]; + + SLSI_PROCFS_REMOVE_FILE(build, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(release, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(version, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(status, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(vifs, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(mac_addr, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(fcq, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(uapsd, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(ap_cert_disable_ht_vht, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(p2p_certif, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(create_tspec, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(confg_tspec, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(send_addts, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(send_delts, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(del_tspec, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(tput, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(fd_opened, sdev->procfs_dir); +#ifdef CONFIG_SCSC_WLAN_MUTEX_DEBUG + SLSI_PROCFS_REMOVE_FILE(mutex_stats, sdev->procfs_dir); +#endif + SLSI_PROCFS_REMOVE_FILE(sta_bss, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(big_data, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(throughput_stats, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(tcp_ack_suppression, sdev->procfs_dir); + SLSI_PROCFS_REMOVE_FILE(nan_mac_addr, sdev->procfs_dir); + + (void)snprintf(dir, sizeof(dir), "driver/unifi%d", sdev->procfs_instance); + remove_proc_entry(dir, NULL); + sdev->procfs_dir = NULL; + } +} diff --git a/drivers/net/wireless/scsc/procfs.h b/drivers/net/wireless/scsc/procfs.h new file mode 100644 index 000000000000..9a67bd6719ec --- /dev/null +++ b/drivers/net/wireless/scsc/procfs.h @@ -0,0 +1,127 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef __SLSI_PROCFS_H__ +#define __SLSI_PROCFS_H__ + +#include +#include +#include +#include + +struct slsi_dev; +struct slsi_vif; + +#ifdef CONFIG_SCSC_WLAN_ANDROID +# ifndef AID_WIFI +# define AID_WIFI 1010 +# endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define SLSI_PROCFS_SET_UID_GID(_entry) \ + do { \ + kuid_t proc_kuid = KUIDT_INIT(AID_WIFI); \ + kgid_t proc_kgid = KGIDT_INIT(AID_WIFI); \ + proc_set_user(_entry, proc_kuid, proc_kgid); \ + } while (0) +#else +#define SLSI_PROCFS_SET_UID_GID(entry) \ + do { \ + (entry)->uid = AID_WIFI; \ + (entry)->gid = AID_WIFI; \ + } while (0) +#endif +#else +#define SLSI_PROCFS_SET_UID_GID(entry) +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define SLSI_PDE_DATA(inode) PDE_DATA(inode) +#else +#define SLSI_PDE_DATA(inode) (PDE(inode)->data) +#endif + +/* procfs operations */ +int slsi_create_proc_dir(struct slsi_dev *sdev); +void slsi_remove_proc_dir(struct slsi_dev *sdev); + +int slsi_procfs_open_file_generic(struct inode *inode, struct file *file); + +#define SLSI_PROCFS_SEQ_FILE_OPS(name) \ + static int slsi_procfs_ ## name ## _show(struct seq_file *m, void *v); \ + static int slsi_procfs_ ## name ## _open(struct inode *inode, struct file *file) \ + { \ + return single_open(file, slsi_procfs_ ## name ## _show, SLSI_PDE_DATA(inode)); \ + } \ + static const struct file_operations slsi_procfs_ ## name ## _fops = { \ + .open = slsi_procfs_ ## name ## _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + } + +#define SLSI_PROCFS_SEQ_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = proc_create_data(# name, mode, parent, &slsi_procfs_ ## name ## _fops, _sdev); \ + if (!entry) { \ + goto err; \ + } \ + SLSI_PROCFS_SET_UID_GID(entry); \ + } while (0) + +#define SLSI_PROCFS_READ_FILE_OPS(name) \ + static ssize_t slsi_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations slsi_procfs_ ## name ## _fops = { \ + .read = slsi_procfs_ ## name ## _read, \ + .open = slsi_procfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + +#define SLSI_PROCFS_WRITE_FILE_OPS(name) \ + static ssize_t slsi_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations slsi_procfs_ ## name ## _fops = { \ + .write = slsi_procfs_ ## name ## _write, \ + .open = slsi_procfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + +#define SLSI_PROCFS_RW_FILE_OPS(name) \ + static ssize_t slsi_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \ + static ssize_t slsi_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \ + static const struct file_operations slsi_procfs_ ## name ## _fops = { \ + .read = slsi_procfs_ ## name ## _read, \ + .write = slsi_procfs_ ## name ## _write, \ + .open = slsi_procfs_open_file_generic, \ + .llseek = generic_file_llseek \ + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) +#define SLSI_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &slsi_procfs_ ## name ## _fops, _sdev); \ + SLSI_PROCFS_SET_UID_GID(entry); \ + } while (0) +#else +#define SLSI_PROCFS_ADD_FILE(_sdev, name, parent, mode) \ + do { \ + struct proc_dir_entry *entry; \ + entry = create_proc_entry(# name, mode, parent); \ + if (entry) { \ + entry->proc_fops = &slsi_procfs_ ## name ## _fops; \ + entry->data = _sdev; \ + SLSI_PROCFS_SET_UID_GID(entry); \ + } else { \ + goto err; \ + } \ + } while (0) +#endif +#define SLSI_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent) + +void slsi_procfs_inc_node(void); +void slsi_procfs_dec_node(void); + +#endif diff --git a/drivers/net/wireless/scsc/rx.c b/drivers/net/wireless/scsc/rx.c new file mode 100755 index 000000000000..575fba63ad57 --- /dev/null +++ b/drivers/net/wireless/scsc/rx.c @@ -0,0 +1,2786 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include + +#include "debug.h" +#include "dev.h" +#include "mgt.h" +#include "mlme.h" +#include "src_sink.h" +#include "const.h" +#include "ba.h" +#include "mib.h" +#include "cac.h" +#include "nl80211_vendor.h" + +#ifdef CONFIG_ANDROID +#include "scsc_wifilogger_rings.h" +#endif + +#ifdef CONFIG_SCSC_LOG_COLLECTION +#include +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)) +#include "porting_imx.h" +#endif +struct ieee80211_channel *slsi_find_scan_channel(struct slsi_dev *sdev, struct ieee80211_mgmt *mgmt, size_t mgmt_len, u16 freq) +{ + int ielen = mgmt_len - (mgmt->u.beacon.variable - (u8 *)mgmt); + const u8 *scan_ds = cfg80211_find_ie(WLAN_EID_DS_PARAMS, mgmt->u.beacon.variable, ielen); + const u8 *scan_ht = cfg80211_find_ie(WLAN_EID_HT_OPERATION, mgmt->u.beacon.variable, ielen); + u8 chan = 0; + + /* Use the DS or HT channel where possible as the Offchannel results mean the RX freq is not reliable */ + if (scan_ds) + chan = scan_ds[2]; + else if (scan_ht) + chan = scan_ht[2]; + + if (chan) { + enum nl80211_band band = NL80211_BAND_2GHZ; + + if (chan > 14) + band = NL80211_BAND_5GHZ; + freq = (u16)ieee80211_channel_to_frequency(chan, band); + } + if (!freq) + return NULL; + + return ieee80211_get_channel(sdev->wiphy, freq); +} + +static struct ieee80211_mgmt *slsi_rx_scan_update_ssid(struct slsi_dev *sdev, struct net_device *dev, + struct ieee80211_mgmt *mgmt, size_t mgmt_len, size_t *new_len, + u16 freq) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u8 *new_mgmt; + int offset; + const u8 *mgmt_pos; + const u8 *ssid; + int i; + int band; + + if (!SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) + return NULL; + + /* update beacon, not probe response as probe response will always have actual ssid.*/ + if (!ieee80211_is_beacon(mgmt->frame_control)) + return NULL; + + ssid = cfg80211_find_ie(WLAN_EID_SSID, mgmt->u.beacon.variable, + mgmt_len - (mgmt->u.beacon.variable - (u8 *)mgmt)); + if (!ssid) { + SLSI_WARN(sdev, "beacon with NO SSID IE\n"); + return NULL; + } + /* update beacon only if hidden ssid. So, Skip if not hidden ssid*/ + if ((ssid[1] > 0) && (ssid[2] != '\0')) + return NULL; + + band = (freq / 1000) == 2 ? SLSI_FREQ_BAND_2GHZ : SLSI_FREQ_BAND_5GHZ; + + /* check we have a known ssid for a bss */ + for (i = 0; i < SLSI_SCAN_SSID_MAP_MAX; i++) { + if (SLSI_ETHER_EQUAL(sdev->ssid_map[i].bssid, mgmt->bssid) && (sdev->ssid_map[i].band == band)) { + new_mgmt = kmalloc(mgmt_len + 34, GFP_KERNEL); + if (!new_mgmt) { + SLSI_ERR_NODEV("malloc failed(len:%ld)\n", mgmt_len + 34); + return NULL; + } + + /* copy frame till ssid element */ + memcpy(new_mgmt, mgmt, ssid - (u8 *)mgmt); + offset = ssid - (u8 *)mgmt; + /* copy bss ssid into new frame */ + new_mgmt[offset++] = WLAN_EID_SSID; + new_mgmt[offset++] = sdev->ssid_map[i].ssid_len; + memcpy(new_mgmt + offset, sdev->ssid_map[i].ssid, sdev->ssid_map[i].ssid_len); + offset += sdev->ssid_map[i].ssid_len; + /* copy rest of the frame following ssid */ + mgmt_pos = ssid + ssid[1] + 2; + memcpy(new_mgmt + offset, mgmt_pos, mgmt_len - (mgmt_pos - (u8 *)mgmt)); + offset += mgmt_len - (mgmt_pos - (u8 *)mgmt); + *new_len = offset; + + return (struct ieee80211_mgmt *)new_mgmt; + } + } + return NULL; +} + +void slsi_rx_scan_pass_to_cfg80211(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + size_t mgmt_len = fapi_get_mgmtlen(skb); + s32 signal = fapi_get_s16(skb, u.mlme_scan_ind.rssi) * 100; + u16 freq = SLSI_FREQ_FW_TO_HOST(fapi_get_u16(skb, u.mlme_scan_ind.channel_frequency)); + struct ieee80211_channel *channel = slsi_find_scan_channel(sdev, mgmt, mgmt_len, freq); + struct timespec uptime; + + get_monotonic_boottime(&uptime); + SLSI_UNUSED_PARAMETER(dev); + + /* update timestamp with device uptime in micro sec */ + mgmt->u.beacon.timestamp = (uptime.tv_sec * 1000000) + (uptime.tv_nsec / 1000); + + if (channel) { + struct cfg80211_bss *bss; + struct ieee80211_mgmt *mgmt_new; + size_t mgmt_new_len = 0; + + mgmt_new = slsi_rx_scan_update_ssid(sdev, dev, mgmt, mgmt_len, &mgmt_new_len, freq); + if (mgmt_new) + bss = cfg80211_inform_bss_frame(sdev->wiphy, channel, mgmt_new, mgmt_new_len, signal, GFP_KERNEL); + else + bss = cfg80211_inform_bss_frame(sdev->wiphy, channel, mgmt, mgmt_len, signal, GFP_KERNEL); + + slsi_cfg80211_put_bss(sdev->wiphy, bss); + kfree(mgmt_new); + } else { + SLSI_NET_DBG1(dev, SLSI_MLME, "No Channel info found for freq:%d\n", freq); + } + + slsi_kfree_skb(skb); +} + +static int slsi_add_to_scan_list(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, + struct sk_buff *skb, const u8 *scan_ssid, u16 scan_id) +{ + struct slsi_scan_result *head; + struct slsi_scan_result *scan_result, *current_result, *prev = NULL; + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + bool found = 0, skb_stored = 0; + int current_rssi, current_band; + + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + head = ndev_vif->scan[scan_id].scan_results; + scan_result = head; + current_rssi = fapi_get_s16(skb, u.mlme_scan_ind.rssi); + current_band = (fapi_get_s16(skb, + u.mlme_scan_ind.channel_frequency) / + 2000) == 2 ? SLSI_FREQ_BAND_2GHZ : SLSI_FREQ_BAND_5GHZ; + + while (scan_result) { + if (SLSI_ETHER_EQUAL(scan_result->bssid, mgmt->bssid) && (scan_result->band == current_band)) { + /*entry exists for bssid*/ + if (!scan_result->probe_resp && ieee80211_is_probe_resp(mgmt->frame_control)) { + scan_result->probe_resp = skb; + skb_stored = 1; + } else if (!scan_result->beacon && ieee80211_is_beacon(mgmt->frame_control)) { + scan_result->beacon = skb; + skb_stored = 1; + if (!scan_ssid || !scan_ssid[1] || scan_ssid[2] == '\0') + scan_result->hidden = 1; + } + + /* Use the best RSSI value from all beacons/probe resp for a bssid. If no improvment + * in RSSI and beacon and probe response exist, ignore this result + */ + if (current_rssi < scan_result->rssi) { + if (!skb_stored) + slsi_kfree_skb(skb); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + return 0; + } + + scan_result->rssi = current_rssi; + if (!skb_stored) { + if (ieee80211_is_beacon(mgmt->frame_control)) { + slsi_kfree_skb(scan_result->beacon); + scan_result->beacon = skb; + } else { + slsi_kfree_skb(scan_result->probe_resp); + scan_result->probe_resp = skb; + } + } + + /*No change in position if rssi is still less than prev node*/ + if (!prev || (prev->rssi > current_rssi)) { + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + return 0; + } + + /*remove and re-insert*/ + found = 1; + prev->next = scan_result->next; + scan_result->next = NULL; + current_result = scan_result; + + break; + } + + prev = scan_result; + scan_result = scan_result->next; + } + + if (!found) { + /*add_new node*/ + current_result = kzalloc(sizeof(*current_result), GFP_KERNEL); + if (!current_result) { + SLSI_ERR(sdev, "Failed to allocate node for scan result\n"); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + return -1; + } + SLSI_ETHER_COPY(current_result->bssid, mgmt->bssid); + + current_result->rssi = current_rssi; + current_result->band = current_band; + if (ieee80211_is_beacon(mgmt->frame_control)) { + current_result->beacon = skb; + if (!scan_ssid || !scan_ssid[1] || scan_ssid[2] == '\0') + current_result->hidden = 1; + } else { + current_result->probe_resp = skb; + } + current_result->next = NULL; + + if (!head) { /*first node*/ + ndev_vif->scan[scan_id].scan_results = current_result; + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + return 0; + } + } + + scan_result = head; + prev = NULL; + /* insert based on rssi in descending order*/ + while (scan_result) { + if (current_result->rssi > scan_result->rssi) { + current_result->next = scan_result; + if (prev) + prev->next = current_result; + else + ndev_vif->scan[scan_id].scan_results = current_result; + break; + } + prev = scan_result; + scan_result = scan_result->next; + } + if (!scan_result) { + /*insert at the end*/ + prev->next = current_result; + current_result->next = NULL; + } + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + return 0; +} + +static int slsi_add_to_p2p_scan_list(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, + struct sk_buff *skb, u16 scan_id) +{ + struct slsi_scan_result *current_result; + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + struct slsi_scan *scan; + + /*add_new node*/ + current_result = kzalloc(sizeof(*current_result), GFP_KERNEL); + if (!current_result) { + SLSI_ERR(sdev, "Failed to allocate node for scan result\n"); + return -1; + } + SLSI_ETHER_COPY(current_result->bssid, mgmt->bssid); + + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + scan = &ndev_vif->scan[scan_id]; + if (ieee80211_is_beacon(mgmt->frame_control)) + current_result->beacon = skb; + else + current_result->probe_resp = skb; + + if (!scan->scan_results) { + scan->scan_results = current_result; + current_result->next = NULL; + } else { + current_result->next = scan->scan_results; + scan->scan_results = current_result; + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + + return 0; +} + +void slsi_rx_scan_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 scan_id = fapi_get_u16(skb, u.mlme_scan_ind.scan_id); + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + size_t mgmt_len = fapi_get_mgmtlen(skb); + size_t ie_len = mgmt_len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + const u8 *scan_ssid = NULL; + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + if (slsi_is_gscan_id(scan_id)) { + SLSI_NET_DBG3(dev, SLSI_GSCAN, "scan_id:%#x bssid:%pM\n", scan_id, fapi_get_mgmt(skb)->bssid); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + slsi_gscan_handle_scan_result(sdev, dev, skb, scan_id, false); + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + return; + } +#endif + + scan_ssid = cfg80211_find_ie(WLAN_EID_SSID, mgmt->u.probe_resp.variable, ie_len); + + if (sdev->p2p_certif && (ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) && (scan_id == (ndev_vif->ifnum << 8 | SLSI_SCAN_HW_ID))) { + /* When supplicant receives a peer GO probe response with selected registrar set and group capability as 0, + * which is invalid, it is unable to store persistent network block. Hence such probe response is getting ignored here. + * This is mainly for an inter-op with Realtek P2P GO in P2P certification + */ + if (scan_ssid && scan_ssid[1] > 7) { + const u8 *p2p_ie = NULL; + + p2p_ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P, mgmt->u.probe_resp.variable, ie_len); +#define P2P_GROUP_CAPAB_PERSISTENT_GROUP BIT(1) + if (p2p_ie && !(p2p_ie[10] & P2P_GROUP_CAPAB_PERSISTENT_GROUP)) { + SLSI_NET_INFO(dev, "Ignoring a peer GO probe response with group_capab as 0\n"); + slsi_kfree_skb(skb); + return; + } + } + } + + scan_id = (scan_id & 0xFF); + + if (WARN_ON(scan_id >= SLSI_SCAN_MAX)) { + slsi_kfree_skb(skb); + return; + } + + /* Blocking scans already taken scan mutex. + * So scan mutex only incase of non blocking scans. + */ + if (!ndev_vif->scan[scan_id].is_blocking_scan) + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + + if (fapi_get_vif(skb) != 0 && fapi_get_u16(skb, u.mlme_scan_ind.scan_id) == 0) { + /* Connect/Roaming scan data : Save for processing later */ + SLSI_NET_DBG1(dev, SLSI_MLME, "Connect/Roaming scan indication received, bssid:%pM\n", fapi_get_mgmt(skb)->bssid); + slsi_kfree_skb(ndev_vif->sta.mlme_scan_ind_skb); + ndev_vif->sta.mlme_scan_ind_skb = skb; + } else if (ndev_vif->scan[scan_id].scan_req || ndev_vif->scan[scan_id].sched_req || + ndev_vif->scan[scan_id].acs_request || + ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan) { + slsi_roam_channel_cache_add(sdev, dev, skb); + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) + slsi_add_to_scan_list(sdev, ndev_vif, skb, scan_ssid, scan_id); + else + slsi_add_to_p2p_scan_list(sdev, ndev_vif, skb, scan_id); + } + + if (!ndev_vif->scan[scan_id].is_blocking_scan) + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); +} + +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +void slsi_rx_beacon_reporting_event_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 reason_code = fapi_get_u16(skb, u.mlme_beacon_reporting_event_ind.abort_reason) - + SLSI_FORWARD_BEACON_ABORT_REASON_OFFSET; + int ret = 0; + + if (!ndev_vif->is_wips_running) { + SLSI_ERR(sdev, "WIPS is not running. Ignore beacon_reporting_event_ind(%u)\n", reason_code); + return; + } + + ndev_vif->is_wips_running = false; + + if (reason_code >= SLSI_FORWARD_BEACON_ABORT_REASON_UNSPECIFIED && + reason_code <= SLSI_FORWARD_BEACON_ABORT_REASON_SUSPENDED) { + SLSI_INFO(sdev, "received abort_event from FW with reason(%u)\n", reason_code); + } else { + SLSI_ERR(sdev, "received abort_event unsupporting reason(%u)\n", reason_code); + } + + ret = slsi_send_forward_beacon_abort_vendor_event(sdev, reason_code); + if (ret) + SLSI_ERR(sdev, "Failed to send forward_beacon_abort_event(err=%d)\n", ret); +} + +void slsi_handle_wips_beacon(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, + struct ieee80211_mgmt *mgmt, int mgmt_len) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + size_t ie_len = mgmt_len - offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *ssid_ie = NULL; + const u8 *scan_ssid = NULL; + const u8 *scan_bssid = NULL; + u16 beacon_int = 0; + u64 timestamp = 0; + int ssid_len = 0; + struct timespec sys_time; + int ret = 0; + + u8 channel = (u8)(ndev_vif->chan->hw_value); + + get_monotonic_boottime(&sys_time); + scan_bssid = fapi_get_mgmt(skb)->bssid; + + ssid_ie = cfg80211_find_ie(WLAN_EID_SSID, mgmt->u.beacon.variable, ie_len); + ssid_len = ssid_ie[1]; + scan_ssid = &ssid_ie[2]; + beacon_int = mgmt->u.beacon.beacon_int; + timestamp = mgmt->u.beacon.timestamp; + + SLSI_NET_DBG2(dev, SLSI_RX, + "forward_beacon from bssid:%pM beacon_int:%u timestamp:%llu system_time:%llu\n", + fapi_get_mgmt(skb)->bssid, beacon_int, timestamp, + (u64)TIMESPEC_TO_US(sys_time)); + + ret = slsi_send_forward_beacon_vendor_event(sdev, scan_ssid, ssid_len, scan_bssid, + channel, beacon_int, timestamp, + (u64)TIMESPEC_TO_US(sys_time)); + if (ret) + SLSI_ERR(sdev, "Failed to forward beacon_event\n"); +} +#endif + +static void slsi_scan_update_ssid_map(struct slsi_dev *sdev, struct net_device *dev, u16 scan_id) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct ieee80211_mgmt *mgmt; + const u8 *ssid_ie = NULL, *connected_ssid = NULL; + int i, found = 0, is_connected = 0; + struct slsi_scan_result *scan_result = NULL; + int band; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->scan_result_mutex)); + + if (ndev_vif->activated && ndev_vif->vif_type == FAPI_VIFTYPE_STATION && ndev_vif->sta.sta_bss) { + band = (ndev_vif->sta.sta_bss->channel->center_freq / + 1000) == 2 ? SLSI_FREQ_BAND_2GHZ : SLSI_FREQ_BAND_5GHZ; + is_connected = 1; + connected_ssid = cfg80211_find_ie(WLAN_EID_SSID, ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len); + } + + /* sanitize map: [remove any old entries] */ + for (i = 0; i < SLSI_SCAN_SSID_MAP_MAX; i++) { + found = 0; + if (!sdev->ssid_map[i].ssid_len) + continue; + + /* We are connected to this hidden AP. So no need to check if this AP is present in scan results */ + if (is_connected && SLSI_ETHER_EQUAL(ndev_vif->sta.sta_bss->bssid, sdev->ssid_map[i].bssid) && + (sdev->ssid_map[i].band == band)) + continue; + + /* If this entry AP is found to be non-hidden, remove entry. */ + scan_result = ndev_vif->scan[scan_id].scan_results; + while (scan_result) { + if (SLSI_ETHER_EQUAL(sdev->ssid_map[i].bssid, scan_result->bssid) && + (sdev->ssid_map[i].band == scan_result->band)) { + /* AP is no more hidden. OR AP is hidden but did not + * receive probe resp. Go for expiry. + */ + if (!scan_result->hidden || (scan_result->hidden && !scan_result->probe_resp)) + sdev->ssid_map[i].age = SLSI_SCAN_SSID_MAP_EXPIRY_AGE; + else + found = 1; + break; + } + scan_result = scan_result->next; + } + + if (!found) { + sdev->ssid_map[i].age++; + if (sdev->ssid_map[i].age > SLSI_SCAN_SSID_MAP_EXPIRY_AGE) { + sdev->ssid_map[i].ssid_len = 0; + sdev->ssid_map[i].age = 0; + } + } + } + + scan_result = ndev_vif->scan[scan_id].scan_results; + /* update/add hidden bss with known ssid */ + while (scan_result) { + ssid_ie = NULL; + + if (scan_result->hidden) { + if (is_connected && SLSI_ETHER_EQUAL(ndev_vif->sta.sta_bss->bssid, scan_result->bssid) && + (scan_result->band == band)) { + ssid_ie = connected_ssid; + } else if (scan_result->probe_resp) { + mgmt = fapi_get_mgmt(scan_result->probe_resp); + ssid_ie = cfg80211_find_ie(WLAN_EID_SSID, mgmt->u.beacon.variable, fapi_get_mgmtlen(scan_result->probe_resp) - (mgmt->u.beacon.variable - (u8 *)mgmt)); + } + } + + if (!ssid_ie) { + scan_result = scan_result->next; + continue; + } + + found = 0; + /* if this bss is in map, update map */ + for (i = 0; i < SLSI_SCAN_SSID_MAP_MAX; i++) { + if (!sdev->ssid_map[i].ssid_len) + continue; + if (SLSI_ETHER_EQUAL(scan_result->bssid, sdev->ssid_map[i].bssid) && + (scan_result->band == sdev->ssid_map[i].band)) { + sdev->ssid_map[i].ssid_len = ssid_ie[1]; + memcpy(sdev->ssid_map[i].ssid, &ssid_ie[2], ssid_ie[1]); + found = 1; + break; + } + } + if (!found) { + /* add a new entry in map */ + for (i = 0; i < SLSI_SCAN_SSID_MAP_MAX; i++) { + if (sdev->ssid_map[i].ssid_len) + continue; + SLSI_ETHER_COPY(sdev->ssid_map[i].bssid, scan_result->bssid); + sdev->ssid_map[i].age = 0; + sdev->ssid_map[i].ssid_len = ssid_ie[1]; + sdev->ssid_map[i].band = scan_result->band; + memcpy(sdev->ssid_map[i].ssid, &ssid_ie[2], ssid_ie[1]); + break; + } + } + scan_result = scan_result->next; + } +} + +void slsi_scan_complete(struct slsi_dev *sdev, struct net_device *dev, u16 scan_id, bool aborted) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *scan; + int count = 0; + int *result_count = NULL, max_count = 0; + struct cfg80211_scan_info info = {.aborted = aborted}; + int scan_results_count = 0; + int more_than_max_count = 0; + + if (WARN_ON(scan_id >= SLSI_SCAN_MAX)) + return; + + if (scan_id == SLSI_SCAN_HW_ID && !ndev_vif->scan[scan_id].scan_req) + return; + + if (WARN_ON(scan_id == SLSI_SCAN_SCHED_ID && !ndev_vif->scan[scan_id].sched_req)) + return; + + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + slsi_scan_update_ssid_map(sdev, dev, scan_id); + result_count = &count; + max_count = slsi_dev_get_scan_result_count(); + } + scan = slsi_dequeue_cached_scan_result(&ndev_vif->scan[scan_id], result_count); + while (scan) { + scan_results_count++; + /* skb freed inside slsi_rx_scan_pass_to_cfg80211 */ + slsi_rx_scan_pass_to_cfg80211(sdev, dev, scan); + + if ((SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) && (*result_count >= max_count)) { + more_than_max_count = 1; + slsi_purge_scan_results_locked(ndev_vif, scan_id); + break; + } + scan = slsi_dequeue_cached_scan_result(&ndev_vif->scan[scan_id], result_count); + } + SLSI_INFO(sdev, "Scan count:%d APs\n", scan_results_count); + SLSI_NET_DBG3(dev, SLSI_MLME, "interface:%d, scan_id:%d,%s\n", ndev_vif->ifnum, scan_id, + more_than_max_count ? "Scan results overflow" : ""); + slsi_roam_channel_cache_prune(dev, SLSI_ROAMING_CHANNEL_CACHE_TIMEOUT); + + if (scan_id == SLSI_SCAN_HW_ID) { + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif) && (!SLSI_IS_P2P_GROUP_STATE(sdev))) { + /* Check for unsync vif as it could be present during the cycle of social channel scan and listen */ + if (ndev_vif->activated) + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + else + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_NO_VIF); + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) + cfg80211_scan_done(ndev_vif->scan[scan_id].scan_req, &info); +#else + cfg80211_scan_done(ndev_vif->scan[scan_id].scan_req, aborted); +#endif + + ndev_vif->scan[scan_id].scan_req = NULL; + ndev_vif->scan[scan_id].requeue_timeout_work = false; + } + + if (scan_id == SLSI_SCAN_SCHED_ID && scan_results_count > 0) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) + cfg80211_sched_scan_results(sdev->wiphy, ndev_vif->scan[scan_id].sched_req->reqid); +#else + cfg80211_sched_scan_results(sdev->wiphy); +#endif + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); +} + +int slsi_set_2g_auto_channel(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, + struct slsi_acs_selected_channels *acs_selected_channels, + struct slsi_acs_chan_info *ch_info) +{ + int i = 0, j = 0, avg_load, total_num_ap, total_rssi, adjacent_rssi; + bool all_bss_load = true, none_bss_load = true; + int min_avg_chan_utilization = INT_MAX, min_adjacent_rssi = INT_MAX; + int ch_idx_min_load = 0, ch_idx_min_rssi = 0; + int min_avg_chan_utilization_20 = INT_MAX, min_adjacent_rssi_20 = INT_MAX; + int ch_idx_min_load_20 = 0, ch_idx_min_rssi_20 = 0; + int ret = 0; + int ch_list_len = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_list_len; + + acs_selected_channels->ch_width = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_width; + acs_selected_channels->hw_mode = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode; + + SLSI_DBG3(sdev, SLSI_MLME, "ch_lis_len:%d\n", ch_list_len); + for (i = 0; i < ch_list_len; i++) { + if (!ch_info[i].chan) + continue; + adjacent_rssi = 0; /* Assuming ch_list is in sorted order. */ + for (j = -2; j <= 2; j++) + if (i + j >= 0 && i + j < ch_list_len) + adjacent_rssi += ch_info[i + j].rssi_factor; + ch_info[i].adj_rssi_factor = adjacent_rssi; + if (ch_info[i].num_bss_load_ap != 0) { + ch_info[i].avg_chan_utilization = ch_info[i].total_chan_utilization / + ch_info[i].num_bss_load_ap; + if (ch_info[i].avg_chan_utilization < min_avg_chan_utilization_20) { + min_avg_chan_utilization_20 = ch_info[i].avg_chan_utilization; + ch_idx_min_load_20 = i; + } else if (ch_info[i].avg_chan_utilization == min_avg_chan_utilization_20 && + ch_info[i].num_ap < ch_info[ch_idx_min_load_20].num_ap) { + ch_idx_min_load_20 = i; + } + none_bss_load = false; + } else { + SLSI_DBG3(sdev, SLSI_MLME, "BSS load IE not found\n"); + all_bss_load = false; + } + if (adjacent_rssi < min_adjacent_rssi_20) { + min_adjacent_rssi_20 = adjacent_rssi; + ch_idx_min_rssi_20 = i; + } else if (adjacent_rssi == min_adjacent_rssi_20 && + ch_info[i].num_ap < ch_info[ch_idx_min_rssi_20].num_ap) { + ch_idx_min_rssi_20 = i; + } + SLSI_DBG3(sdev, SLSI_MLME, "min rssi:%d min_rssi_idx:%d\n", min_adjacent_rssi_20, ch_idx_min_rssi_20); + SLSI_DBG3(sdev, SLSI_MLME, "num_ap:%d,chan:%d,total_util:%d,avg_util:%d,rssi_fac:%d,adj_rssi_fac:%d," + "bss_ap:%d\n", ch_info[i].num_ap, ch_info[i].chan, ch_info[i].total_chan_utilization, + ch_info[i].avg_chan_utilization, ch_info[i].rssi_factor, ch_info[i].adj_rssi_factor, + ch_info[i].num_bss_load_ap); + } + + if (acs_selected_channels->ch_width == 40) { + for (i = 0; i < ch_list_len; i++) { + if (i + 4 >= ch_list_len || !ch_info[i + 4].chan || !ch_info[i].chan) + continue; + avg_load = ch_info[i].avg_chan_utilization + ch_info[i + 4].avg_chan_utilization; + total_num_ap = ch_info[i].num_ap + ch_info[i + 4].num_ap; + total_rssi = ch_info[i].adj_rssi_factor + ch_info[i + 4].adj_rssi_factor; + + if (avg_load < min_avg_chan_utilization) { + min_avg_chan_utilization = avg_load; + ch_idx_min_load = i; + } else if (avg_load == min_avg_chan_utilization && + total_num_ap < ch_info[ch_idx_min_load].num_ap + + ch_info[ch_idx_min_load + 4].num_ap) { + ch_idx_min_load = i; + } + if (total_rssi < min_adjacent_rssi) { + min_adjacent_rssi = total_rssi; + ch_idx_min_rssi = i; + } else if (total_rssi == min_adjacent_rssi && + total_num_ap < ch_info[ch_idx_min_rssi].num_ap + + ch_info[ch_idx_min_rssi + 4].num_ap) { + ch_idx_min_rssi = i; + } + } + if (all_bss_load) { + acs_selected_channels->pri_channel = ch_info[ch_idx_min_load].chan; + acs_selected_channels->sec_channel = ch_info[ch_idx_min_load].chan + 4; + } else { + acs_selected_channels->pri_channel = ch_info[ch_idx_min_rssi].chan; + acs_selected_channels->sec_channel = ch_info[ch_idx_min_rssi].chan + 4; + } + + if (!acs_selected_channels->pri_channel) + acs_selected_channels->ch_width = 20; + } + + if (acs_selected_channels->ch_width == 20) { + if (all_bss_load) + acs_selected_channels->pri_channel = ch_info[ch_idx_min_load_20].chan; + else + acs_selected_channels->pri_channel = ch_info[ch_idx_min_rssi_20].chan; + } + return ret; +} + +int slsi_is_40mhz_5gchan(u8 pri_channel, u8 sec_channel) +{ + int slsi_40mhz_chan[12] = {38, 46, 54, 62, 102, 110, 118, 126, 134, 142, 151, 159}; + int i; + + for (i = 0; i < 12; i++) { + if (pri_channel == slsi_40mhz_chan[i] - 2 && sec_channel == slsi_40mhz_chan[i] + 2) + return 1; + else if (pri_channel < slsi_40mhz_chan[i]) + return 0; + } + return 0; +} + +int slsi_is_80mhz_5gchan(u8 pri_channel, u8 last_channel) +{ + int slsi_80mhz_chan[6] = {42, 58, 106, 122, 138, 155}; + int i; + + for (i = 0; i < 6; i++) { + if (pri_channel == slsi_80mhz_chan[i] - 6 && last_channel == slsi_80mhz_chan[i] + 6) + return 1; + else if (pri_channel < slsi_80mhz_chan[i]) + return 0; + } + return 0; +} + +int slsi_set_5g_auto_channel(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, + struct slsi_acs_selected_channels *acs_selected_channels, + struct slsi_acs_chan_info *ch_info) +{ + int i = 0, avg_load, total_num_ap; + bool all_bss_load = true, none_bss_load = true; + int min_num_ap = INT_MAX, min_avg_chan_utilization = INT_MAX; + int ch_idx_min_load = 0, ch_idx_min_ap = 0; + int min_avg_chan_utilization_20 = INT_MAX, min_num_ap_20 = INT_MAX; + int ch_idx_min_load_20 = 0, ch_idx_min_ap_20 = 0; + int ret = 0; + int ch_list_len = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_list_len; + + acs_selected_channels->ch_width = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_width; + acs_selected_channels->hw_mode = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode; + + SLSI_DBG3(sdev, SLSI_MLME, "ch_lis_len:%d\n", ch_list_len); + for (i = 0; i < ch_list_len; i++) { + if (!ch_info[i].chan) + continue; + if (ch_info[i].num_bss_load_ap != 0) { + ch_info[i].avg_chan_utilization = ch_info[i].total_chan_utilization / + ch_info[i].num_bss_load_ap; + if (ch_info[i].avg_chan_utilization < min_avg_chan_utilization_20) { + min_avg_chan_utilization_20 = ch_info[i].avg_chan_utilization; + ch_idx_min_load_20 = i; + } else if (ch_info[i].avg_chan_utilization == min_avg_chan_utilization_20 && + ch_info[i].num_ap < ch_info[ch_idx_min_load_20].num_ap) { + ch_idx_min_load_20 = i; + } + none_bss_load = false; + } else { + if (ch_info[i].num_ap < min_num_ap_20) { + min_num_ap_20 = ch_info[i].num_ap; + ch_idx_min_ap_20 = i; + } + SLSI_DBG3(sdev, SLSI_MLME, "BSS load IE not found\n"); + ch_info[i].avg_chan_utilization = 128; + all_bss_load = false; + } + SLSI_DBG3(sdev, SLSI_MLME, "num_ap:%d chan:%d, total_chan_util:%d, avg_chan_util:%d, bss_load_ap:%d\n", + ch_info[i].num_ap, ch_info[i].chan, ch_info[i].total_chan_utilization, + ch_info[i].avg_chan_utilization, ch_info[i].num_bss_load_ap); + } + + if (acs_selected_channels->ch_width == 80) { + for (i = 0; i < ch_list_len; i++) { + if (i + 3 >= ch_list_len) + continue; + if (!ch_info[i].chan || !ch_info[i + 1].chan || !ch_info[i + 2].chan || !ch_info[i + 3].chan) + continue; + if (slsi_is_80mhz_5gchan(ch_info[i].chan, ch_info[i + 3].chan)) { + avg_load = ch_info[i].avg_chan_utilization + ch_info[i + 1].avg_chan_utilization + + ch_info[i + 2].avg_chan_utilization + ch_info[i + 3].avg_chan_utilization; + total_num_ap = ch_info[i].num_ap + ch_info[i + 1].num_ap + ch_info[i + 2].num_ap + + ch_info[i + 3].num_ap; + if (avg_load < min_avg_chan_utilization) { + min_avg_chan_utilization = avg_load; + ch_idx_min_load = i; + } else if (avg_load == min_avg_chan_utilization && total_num_ap < + (ch_info[ch_idx_min_load].num_ap + ch_info[ch_idx_min_load + 1].num_ap + + ch_info[ch_idx_min_load + 2].num_ap + + ch_info[ch_idx_min_load + 3].num_ap)) { + ch_idx_min_load = i; + } + if (total_num_ap < min_num_ap) { + min_num_ap = total_num_ap; + ch_idx_min_ap = i; + } + } + } + if (all_bss_load || min_avg_chan_utilization <= 512) { + acs_selected_channels->pri_channel = ch_info[ch_idx_min_load].chan; + acs_selected_channels->vht_seg0_center_ch = ch_info[ch_idx_min_load].chan + 6; + } else if (none_bss_load || min_avg_chan_utilization > 512) { + acs_selected_channels->pri_channel = ch_info[ch_idx_min_ap].chan; + acs_selected_channels->vht_seg0_center_ch = ch_info[ch_idx_min_ap].chan + 6; + } + + if (!acs_selected_channels->pri_channel) + acs_selected_channels->ch_width = 40; + } + + if (acs_selected_channels->ch_width == 40) { + for (i = 0; i < ch_list_len; i++) { + if (!ch_info[i].chan || i + 1 >= ch_list_len || !ch_info[i + 1].chan) + continue; + if (slsi_is_40mhz_5gchan(ch_info[i].chan, ch_info[i + 1].chan)) { + avg_load = ch_info[i].avg_chan_utilization + ch_info[i + 1].avg_chan_utilization; + total_num_ap = ch_info[i].num_ap + ch_info[i + 1].num_ap; + if (avg_load < min_avg_chan_utilization) { + min_avg_chan_utilization = avg_load; + ch_idx_min_load = i; + } else if (avg_load == min_avg_chan_utilization && total_num_ap < + ch_info[ch_idx_min_load].num_ap + ch_info[ch_idx_min_load + 1].num_ap) { + ch_idx_min_load = i; + } + if (total_num_ap < min_num_ap) { + min_num_ap = total_num_ap; + ch_idx_min_ap = i; + } + } + } + if (all_bss_load || min_avg_chan_utilization <= 256) { + acs_selected_channels->pri_channel = ch_info[ch_idx_min_load].chan; + acs_selected_channels->sec_channel = ch_info[ch_idx_min_load + 1].chan; + } else if (none_bss_load || min_avg_chan_utilization > 256) { + acs_selected_channels->pri_channel = ch_info[ch_idx_min_ap].chan; + acs_selected_channels->sec_channel = ch_info[ch_idx_min_ap + 1].chan; + } + + if (!acs_selected_channels->pri_channel) + acs_selected_channels->ch_width = 20; + } + + if (acs_selected_channels->ch_width == 20) { + if (all_bss_load || min_avg_chan_utilization_20 < 128) + acs_selected_channels->pri_channel = ch_info[ch_idx_min_load_20].chan; + else if (none_bss_load || min_avg_chan_utilization_20 >= 128) + acs_selected_channels->pri_channel = ch_info[ch_idx_min_ap_20].chan; + } + return ret; +} + +int slsi_acs_get_rssi_factor(struct slsi_dev *sdev, int rssi, int ch_util) +{ + int frac_pow_val[10] = {10, 12, 15, 19, 25, 31, 39, 50, 63, 79}; + int res = 1; + int i; + + if (rssi < 0) + rssi = 0 - rssi; + else + return INT_MAX; + for (i = 0; i < rssi / 10; i++) + res *= 10; + res = (10000000 * ch_util / res) / frac_pow_val[rssi % 10]; + + SLSI_DBG3(sdev, SLSI_MLME, "ch_util:%d\n", ch_util); + return res; +} + +struct slsi_acs_chan_info *slsi_acs_scan_results(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, u16 scan_id) +{ + struct sk_buff *scan_res; + struct sk_buff *unique_scan; + struct sk_buff_head unique_scan_results; + struct slsi_acs_chan_info *ch_info = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->acs_chan_info; + + SLSI_DBG3(sdev, SLSI_MLME, "Received acs_results\n"); + skb_queue_head_init(&unique_scan_results); + SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex); + scan_res = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL); + + while (scan_res) { + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(scan_res); + size_t mgmt_len = fapi_get_mgmtlen(scan_res); + struct ieee80211_channel *scan_channel; + int idx = 0; + const u8 *ie_data; + const u8 *ie; + int ie_len; + int ch_util = 128; + /* ieee80211_mgmt structure is similar for Probe Response and Beacons */ + size_t ies_len = mgmt_len - offsetof(struct ieee80211_mgmt, u.beacon.variable); + /* make sure this BSSID has not already been used */ + skb_queue_walk(&unique_scan_results, unique_scan) { + struct ieee80211_mgmt *unique_mgmt = fapi_get_mgmt(unique_scan); + + if (compare_ether_addr(mgmt->bssid, unique_mgmt->bssid) == 0) + goto next_scan; + } + slsi_skb_queue_head(&unique_scan_results, scan_res); + scan_channel = slsi_find_scan_channel(sdev, mgmt, mgmt_len, + fapi_get_u16(scan_res, u.mlme_scan_ind.channel_frequency) / 2); + if (!scan_channel) + goto next_scan; + SLSI_DBG3(sdev, SLSI_MLME, "scan result (scan_id:%d, %pM, channel:%d, rssi:%d, ie_len = %zu)\n", + fapi_get_u16(scan_res, u.mlme_scan_ind.scan_id), + fapi_get_mgmt(scan_res)->bssid, scan_channel->hw_value, + fapi_get_s16(scan_res, u.mlme_scan_ind.rssi), + ies_len); + + idx = slsi_find_chan_idx(scan_channel->hw_value, ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode); + SLSI_DBG3(sdev, SLSI_MLME, "chan_idx:%d chan_value: %d\n", idx, ch_info[idx].chan); + + if ((idx < 0) || (idx > 24)) { + SLSI_DBG3(sdev, SLSI_MLME, "idx is not in range idx=%d\n", idx); + goto next_scan; + } + if (ch_info[idx].chan) { + ch_info[idx].num_ap += 1; + ie = cfg80211_find_ie(WLAN_EID_QBSS_LOAD, mgmt->u.beacon.variable, ies_len); + if (ie) { + ie_len = ie[1]; + ie_data = &ie[2]; + if (ie_len >= 3) { + ch_util = ie_data[2]; + ch_info[idx].num_bss_load_ap += 1; + ch_info[idx].total_chan_utilization += ch_util; + } + } + if (idx == scan_channel->hw_value - 1) { /*if 2.4GHZ channel */ + int res = 0; + + res = slsi_acs_get_rssi_factor(sdev, fapi_get_s16(scan_res, u.mlme_scan_ind.rssi), + ch_util); + ch_info[idx].rssi_factor += res; + SLSI_DBG3(sdev, SLSI_MLME, "ch_info[idx].rssi_factor:%d\n", ch_info[idx].rssi_factor); + } + } else { + goto next_scan; + } +next_scan: + scan_res = slsi_dequeue_cached_scan_result(&ndev_vif->scan[scan_id], NULL); + } + SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex); + slsi_skb_queue_purge(&unique_scan_results); + return ch_info; +} + +void slsi_acs_scan_complete(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, u16 scan_id) +{ + struct slsi_acs_selected_channels acs_selected_channels; + struct slsi_acs_chan_info *ch_info; + int r = 0; + + memset(&acs_selected_channels, 0, sizeof(acs_selected_channels)); + ch_info = slsi_acs_scan_results(sdev, ndev_vif, scan_id); + if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode == SLSI_ACS_MODE_IEEE80211A) + r = slsi_set_5g_auto_channel(sdev, ndev_vif, &acs_selected_channels, ch_info); + else if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode == SLSI_ACS_MODE_IEEE80211B || + ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode == SLSI_ACS_MODE_IEEE80211G) + r = slsi_set_2g_auto_channel(sdev, ndev_vif, &acs_selected_channels, ch_info); + else + r = -EINVAL; + if (!r) { + r = slsi_send_acs_event(sdev, acs_selected_channels); + if (r != 0) + SLSI_ERR(sdev, "Could not send ACS vendor event up\n"); + } else { + SLSI_ERR(sdev, "set_auto_channel failed: %d\n", r); + } + sdev->acs_channel_switched = true; + kfree(ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request); + if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request) + ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request = NULL; +} + +void slsi_rx_scan_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 scan_id = fapi_get_u16(skb, u.mlme_scan_done_ind.scan_id); + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_MUTEX_LOCK(ndev_vif->scan_mutex); + SLSI_NET_DBG3(dev, SLSI_GSCAN, "slsi_rx_scan_done_ind Received scan_id:%#x\n", scan_id); + +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + if (slsi_is_gscan_id(scan_id)) { + SLSI_NET_DBG3(dev, SLSI_GSCAN, "scan_id:%#x\n", scan_id); + + slsi_gscan_handle_scan_result(sdev, dev, skb, scan_id, true); + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return; + } +#endif + scan_id = (scan_id & 0xFF); + + if (scan_id == SLSI_SCAN_HW_ID && (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req || + ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request)) + cancel_delayed_work(&ndev_vif->scan_timeout_work); + if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request) + slsi_acs_scan_complete(sdev, ndev_vif, scan_id); + else + slsi_scan_complete(sdev, dev, scan_id, false); + + SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_channel_switched_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 freq = 0; + int width; + int primary_chan_pos; + u16 temp_chan_info; + struct cfg80211_chan_def chandef; + u16 cf1 = 0; + struct netdev_vif *ndev_vif = netdev_priv(dev); + + temp_chan_info = fapi_get_u16(skb, u.mlme_channel_switched_ind.channel_information); + cf1 = fapi_get_u16(skb, u.mlme_channel_switched_ind.channel_frequency); + cf1 = cf1 / 2; + + primary_chan_pos = (temp_chan_info >> 8); + width = (temp_chan_info & 0x00FF); + + /* If width is 80MHz/40MHz then do frequency calculation, else store as it is */ + if (width == 40) + freq = cf1 + (primary_chan_pos * 20) - 10; + else if (width == 80) + freq = cf1 + (primary_chan_pos * 20) - 30; + else + freq = cf1; + + if (width == 20) + width = NL80211_CHAN_WIDTH_20; + else if (width == 40) + width = NL80211_CHAN_WIDTH_40; + else if (width == 80) + width = NL80211_CHAN_WIDTH_80; + else if (width == 160) + width = NL80211_CHAN_WIDTH_160; + + chandef.chan = ieee80211_get_channel(sdev->wiphy, freq); + chandef.width = width; + chandef.center_freq1 = cf1; + chandef.center_freq2 = 0; + + ndev_vif->ap.channel_freq = freq; /* updated for GETSTAINFO */ + ndev_vif->chan = chandef.chan; + + cfg80211_ch_switch_notify(dev, &chandef); + slsi_kfree_skb(skb); +} + +void __slsi_rx_blockack_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + + SLSI_NET_DBG1(dev, SLSI_MLME, "ma_blockack_ind(vif:%d, peer_qsta_address:%pM, parameter_set:%d, sequence_number:%d, reason_code:%d, direction:%d)\n", + fapi_get_vif(skb), + fapi_get_buff(skb, u.ma_blockack_ind.peer_qsta_address), + fapi_get_u16(skb, u.ma_blockack_ind.blockack_parameter_set), + fapi_get_u16(skb, u.ma_blockack_ind.sequence_number), + fapi_get_u16(skb, u.ma_blockack_ind.reason_code), + fapi_get_u16(skb, u.ma_blockack_ind.direction)); + + peer = slsi_get_peer_from_mac(sdev, dev, fapi_get_buff(skb, u.ma_blockack_ind.peer_qsta_address)); + WARN_ON(!peer); + + if (peer) { + /* Buffering of frames before the mlme_connected_ind */ + if ((ndev_vif->vif_type == FAPI_VIFTYPE_AP) && (peer->connected_state == SLSI_STA_CONN_STATE_CONNECTING)) { + SLSI_DBG3(sdev, SLSI_MLME, "Buffering MA-BlockAck.Indication\n"); + slsi_skb_queue_tail(&peer->buffered_frames, skb); + return; + } + slsi_handle_blockack( + dev, + peer, + fapi_get_vif(skb), + fapi_get_buff(skb, u.ma_blockack_ind.peer_qsta_address), + fapi_get_u16(skb, u.ma_blockack_ind.blockack_parameter_set), + fapi_get_u16(skb, u.ma_blockack_ind.sequence_number), + fapi_get_u16(skb, u.ma_blockack_ind.reason_code), + fapi_get_u16(skb, u.ma_blockack_ind.direction) + ); + } + + slsi_kfree_skb(skb); +} + +void slsi_rx_blockack_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + __slsi_rx_blockack_ind(sdev, dev, skb); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +static bool get_wmm_ie_from_resp_ie(struct slsi_dev *sdev, struct net_device *dev, u8 *resp_ie, size_t resp_ie_len, const u8 **wmm_elem, size_t *wmm_elem_len) +{ + struct ieee80211_vendor_ie *ie; + + SLSI_UNUSED_PARAMETER(sdev); + + if (!resp_ie) { + SLSI_NET_ERR(dev, "Received invalid pointer to the ie's of the association response\n"); + return false; + } + + *wmm_elem = resp_ie; + while (*wmm_elem && (*wmm_elem - resp_ie < resp_ie_len)) { + /* parse response ie elements and return the wmm ie */ + *wmm_elem = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WMM, *wmm_elem, + resp_ie_len - (*wmm_elem - resp_ie)); + /* re-assoc-res can contain wmm parameter IE and wmm TSPEC IE. + * we want wmm parameter Element) + */ + if (*wmm_elem && (*wmm_elem)[1] > 6 && (*wmm_elem)[6] == WMM_OUI_SUBTYPE_PARAMETER_ELEMENT) + break; + if (*wmm_elem) + *wmm_elem += (*wmm_elem)[1]; + } + + if (!(*wmm_elem)) { + SLSI_NET_DBG2(dev, SLSI_MLME, "No WMM IE\n"); + return false; + } + ie = (struct ieee80211_vendor_ie *)*wmm_elem; + *wmm_elem_len = ie->len + 2; + + SLSI_NET_DBG3(dev, SLSI_MLME, "WMM IE received and parsed successfully\n"); + return true; +} + +static bool sta_wmm_update_uapsd(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, u8 *assoc_req_ie, size_t assoc_req_ie_len) +{ + const u8 *wmm_information_ie; + + if (!assoc_req_ie) { + SLSI_NET_ERR(dev, "null reference to IE\n"); + return false; + } + + wmm_information_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WMM, assoc_req_ie, assoc_req_ie_len); + if (!wmm_information_ie) { + SLSI_NET_DBG1(dev, SLSI_MLME, "no WMM IE\n"); + return false; + } + + peer->uapsd = wmm_information_ie[8]; + SLSI_NET_DBG1(dev, SLSI_MLME, "peer->uapsd = 0x%x\n", peer->uapsd); + return true; +} + +static bool sta_wmm_update_wmm_ac_ies(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer, + u8 *assoc_rsp_ie, size_t assoc_rsp_ie_len) +{ + size_t left; + const u8 *pos; + const u8 *wmm_elem = NULL; + size_t wmm_elem_len = 0; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_wmm_ac *wmm_ac = &ndev_vif->sta.wmm_ac[0]; + + if (!get_wmm_ie_from_resp_ie(sdev, dev, assoc_rsp_ie, assoc_rsp_ie_len, &wmm_elem, &wmm_elem_len)) { + SLSI_NET_DBG1(dev, SLSI_MLME, "No WMM IE received\n"); + return false; + } + + if (wmm_elem_len < 10 || wmm_elem[7] /* version */ != 1) { + SLSI_NET_WARN(dev, "Invalid WMM IE: wmm_elem_len=%lu, wmm_elem[7]=%d\n", (unsigned long int)wmm_elem_len, (int)wmm_elem[7]); + return false; + } + + pos = wmm_elem + 10; + left = wmm_elem_len - 10; + + for (; left >= 4; left -= 4, pos += 4) { + int aci = (pos[0] >> 5) & 0x03; + int acm = (pos[0] >> 4) & 0x01; + + memcpy(wmm_ac, pos, sizeof(struct slsi_wmm_ac)); + + switch (aci) { + case 1: /* AC_BK */ + if (acm) + peer->wmm_acm |= BIT(1) | BIT(2); /* BK/- */ + break; + case 2: /* AC_VI */ + if (acm) + peer->wmm_acm |= BIT(4) | BIT(5); /* CL/VI */ + break; + case 3: /* AC_VO */ + if (acm) + peer->wmm_acm |= BIT(6) | BIT(7); /* VO/NC */ + break; + case 0: /* AC_BE */ + default: + if (acm) + peer->wmm_acm |= BIT(0) | BIT(3); /* BE/EE */ + break; + } + wmm_ac++; + } + + SLSI_NET_DBG3(dev, SLSI_MLME, "WMM ies have been updated successfully\n"); + return true; +} + +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD +enum slsi_wlan_vendor_attr_roam_auth { + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_INVALID = 0, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_BSSID, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_REQ_IE, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_RESP_IE, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_AUTHORIZED, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_KEY_REPLAY_CTR, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_PTK_KCK, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_PTK_KEK, + SLSI_WLAN_VENDOR_ATTR_ROAM_BEACON_IE, + /* keep last */ + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_AFTER_LAST, + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_MAX = + SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_AFTER_LAST - 1 +}; + +int slsi_send_roam_vendor_event(struct slsi_dev *sdev, const u8 *bssid, + const u8 *req_ie, u32 req_ie_len, const u8 *resp_ie, u32 resp_ie_len, + const u8 *beacon_ie, u32 beacon_ie_len, bool authorized) +{ + bool is_secured_bss; + struct sk_buff *skb = NULL; + u8 err = 0; + + is_secured_bss = cfg80211_find_ie(WLAN_EID_RSN, req_ie, req_ie_len) || + cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPA, req_ie, req_ie_len); + + SLSI_DBG2(sdev, SLSI_MLME, "authorized:%d, is_secured_bss:%d\n", authorized, is_secured_bss); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH, GFP_KERNEL); +#else + skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE, + SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH, GFP_KERNEL); +#endif + if (!skb) { + SLSI_ERR_NODEV("Failed to allocate skb for VENDOR Roam event\n"); + return -ENOMEM; + } + + err |= nla_put(skb, SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_BSSID, ETH_ALEN, bssid) ? BIT(1) : 0; + err |= nla_put(skb, SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_AUTHORIZED, 1, &authorized) ? BIT(2) : 0; + err |= (req_ie && nla_put(skb, SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_REQ_IE, req_ie_len, req_ie)) ? BIT(3) : 0; + err |= (resp_ie && nla_put(skb, SLSI_WLAN_VENDOR_ATTR_ROAM_AUTH_RESP_IE, resp_ie_len, resp_ie)) ? BIT(4) : 0; + err |= (beacon_ie && nla_put(skb, SLSI_WLAN_VENDOR_ATTR_ROAM_BEACON_IE, beacon_ie_len, beacon_ie)) ? BIT(5) : 0; + if (err) { + SLSI_ERR_NODEV("Failed nla_put ,req_ie_len=%d,resp_ie_len=%d,beacon_ie_len=%d,condition_failed=%d\n", + req_ie_len, resp_ie_len, beacon_ie_len, err); + slsi_kfree_skb(skb); + return -EINVAL; + } + SLSI_DBG3_NODEV(SLSI_MLME, "Event: KEY_MGMT_ROAM_AUTH(%d)\n", SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH); + cfg80211_vendor_event(skb, GFP_KERNEL); + return 0; +} +#endif /* offload */ + +void slsi_rx_roamed_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + struct slsi_peer *peer; + u16 temporal_keys_required = fapi_get_u16(skb, u.mlme_roamed_ind.temporal_keys_required); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + enum ieee80211_privacy bss_privacy; +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) + struct cfg80211_roam_info roam_info = {}; +#endif + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_roamed_ind(vif:%d) Roaming to %pM\n", + fapi_get_vif(skb), + mgmt->bssid); + + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!peer)) + goto exit; + + if (WARN_ON(!ndev_vif->sta.sta_bss)) + goto exit; + + slsi_rx_ba_stop_all(dev, peer); + + SLSI_ETHER_COPY(peer->address, mgmt->bssid); + + if (ndev_vif->sta.mlme_scan_ind_skb) { + /* saved skb [mlme_scan_ind] freed inside slsi_rx_scan_pass_to_cfg80211 */ + slsi_rx_scan_pass_to_cfg80211(sdev, dev, ndev_vif->sta.mlme_scan_ind_skb); + ndev_vif->sta.mlme_scan_ind_skb = NULL; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + if (ndev_vif->sta.sta_bss->capability & WLAN_CAPABILITY_PRIVACY) + bss_privacy = IEEE80211_PRIVACY_ON; + else + bss_privacy = IEEE80211_PRIVACY_OFF; +#endif + + slsi_cfg80211_put_bss(sdev->wiphy, ndev_vif->sta.sta_bss); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + ndev_vif->sta.sta_bss = cfg80211_get_bss(sdev->wiphy, NULL, peer->address, NULL, 0, + IEEE80211_BSS_TYPE_ANY, bss_privacy); +#else + ndev_vif->sta.sta_bss = cfg80211_get_bss(sdev->wiphy, NULL, peer->address, NULL, 0, 0, 0); +#endif + + if (!ndev_vif->sta.sta_bss || !ndev_vif->sta.roam_mlme_procedure_started_ind) { + if (!ndev_vif->sta.sta_bss) + SLSI_INFO(sdev, "BSS not updated in cfg80211\n"); + if (!ndev_vif->sta.roam_mlme_procedure_started_ind) + SLSI_INFO(sdev, "procedure-started-ind not received before roamed-ind\n"); + netif_carrier_off(dev); + slsi_mlme_disconnect(sdev, dev, peer->address, 0, true); + slsi_handle_disconnect(sdev, dev, peer->address, 0); + } else { + u8 *assoc_ie = NULL; + int assoc_ie_len = 0; + u8 *assoc_rsp_ie = NULL; + int assoc_rsp_ie_len = 0; + + slsi_peer_reset_stats(sdev, dev, peer); + slsi_peer_update_assoc_req(sdev, dev, peer, ndev_vif->sta.roam_mlme_procedure_started_ind); + slsi_peer_update_assoc_rsp(sdev, dev, peer, skb); + + /* skb is consumed by slsi_peer_update_assoc_rsp. So do not access this anymore. */ + skb = NULL; + + if (peer->assoc_ie) { + assoc_ie = peer->assoc_ie->data; + assoc_ie_len = peer->assoc_ie->len; + } + + if (peer->assoc_resp_ie) { + assoc_rsp_ie = peer->assoc_resp_ie->data; + assoc_rsp_ie_len = peer->assoc_resp_ie->len; + } + + /* this is the right place to initialize the bitmasks for + * acm bit and tspec establishment + */ + peer->wmm_acm = 0; + peer->tspec_established = 0; + peer->uapsd = 0; + + /* update the uapsd bitmask according to the bit values + * in wmm information element of association request + */ + if (!sta_wmm_update_uapsd(sdev, dev, peer, assoc_ie, assoc_ie_len)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Fail to update WMM uapsd\n"); + + /* update the acm bitmask according to the acm bit values that + * are included in wmm ie element of association response + */ + if (!sta_wmm_update_wmm_ac_ies(sdev, dev, peer, assoc_rsp_ie, assoc_rsp_ie_len)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Fail to update WMM AC ies\n"); + + ndev_vif->sta.roam_mlme_procedure_started_ind = NULL; + + if (temporal_keys_required) { + peer->pairwise_key_set = 0; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DOING_KEY_CONFIG); + } + + WARN_ON(assoc_ie_len && !assoc_ie); + WARN_ON(assoc_rsp_ie_len && !assoc_rsp_ie); + + SLSI_NET_DBG3(dev, SLSI_MLME, "cfg80211_roamed()\n"); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) + /* cfg80211 does not require bss pointer in roam_info. + * If bss pointer is given in roam_info, cfg80211 bss + * data base goes bad and results in random panic. + */ + roam_info.channel = ndev_vif->sta.sta_bss->channel; + roam_info.bssid = peer->address; + roam_info.req_ie = assoc_ie; + roam_info.req_ie_len = assoc_ie_len; + roam_info.resp_ie = assoc_rsp_ie; + roam_info.resp_ie_len = assoc_rsp_ie_len; + cfg80211_roamed(dev, &roam_info, GFP_KERNEL); +#else + cfg80211_roamed(dev, + ndev_vif->sta.sta_bss->channel, + peer->address, + assoc_ie, + assoc_ie_len, + assoc_rsp_ie, + assoc_rsp_ie_len, + GFP_KERNEL); +#endif +#ifdef CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD + if (slsi_send_roam_vendor_event(sdev, peer->address, assoc_ie, assoc_ie_len, + assoc_rsp_ie, assoc_rsp_ie_len, + ndev_vif->sta.sta_bss->ies->data, ndev_vif->sta.sta_bss->ies->len, + !temporal_keys_required) != 0) { + SLSI_NET_ERR(dev, "Could not send Roam vendor event up"); + } +#endif + SLSI_NET_DBG3(dev, SLSI_MLME, "cfg80211_roamed() Done\n"); + + ndev_vif->sta.roam_in_progress = false; + ndev_vif->chan = ndev_vif->sta.sta_bss->channel; +#if !defined SLSI_TEST_DEV && defined CONFIG_ANDROID + SLSI_NET_DBG1(dev, SLSI_MLME, "Taking a wakelock for DHCP to finish after roaming\n"); + wake_lock_timeout(&sdev->wlan_wl_roam, msecs_to_jiffies(10 * 1000)); +#ifdef CONFIG_SCSC_WIFILOGGER + SCSC_WLOG_WAKELOCK(WLOG_NORMAL, WL_TAKEN, "wlan_wl_roam", WL_REASON_ROAM); +#endif +#endif + + if (!temporal_keys_required) { + slsi_mlme_roamed_resp(sdev, dev); + cac_update_roam_traffic_params(sdev, dev); + } else { + ndev_vif->sta.resp_id = MLME_ROAMED_RES; + } + } + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_roam_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + enum ieee80211_statuscode status = WLAN_STATUS_SUCCESS; + + SLSI_UNUSED_PARAMETER(sdev); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_roam_ind(vif:%d, aid:0, result:0x%04x )\n", + fapi_get_vif(skb), + fapi_get_u16(skb, u.mlme_roam_ind.result_code)); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit_with_lock; + } + + if (WARN(ndev_vif->vif_type != FAPI_VIFTYPE_STATION, "Not a Station VIF\n")) + goto exit_with_lock; + + if (fapi_get_u16(skb, u.mlme_roam_ind.result_code) != FAPI_RESULTCODE_HOST_REQUEST_SUCCESS) + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +static void slsi_tdls_event_discovered(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 tdls_event = fapi_get_u16(skb, u.mlme_tdls_peer_ind.tdls_event); + u16 peer_index = fapi_get_u16(skb, u.mlme_tdls_peer_ind.peer_index); + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + int len = fapi_get_mgmtlen(skb); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_INFO(sdev, "\n"); + + if (len != 0) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_rx_mgmt(&ndev_vif->wdev, ndev_vif->chan->center_freq, 0, (const u8 *)mgmt, len, GFP_ATOMIC); +#else + cfg80211_rx_mgmt(dev, ndev_vif->chan->center_freq, 0, (const u8 *)mgmt, len, GFP_ATOMIC); +#endif + /* Handling MLME-TDLS-PEER.response */ + slsi_mlme_tdls_peer_resp(sdev, dev, peer_index, tdls_event); + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + slsi_kfree_skb(skb); +} + +static void slsi_tdls_event_connected(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct slsi_peer *peer = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 peer_index = fapi_get_u16(skb, u.mlme_tdls_peer_ind.peer_index); + u16 tdls_event = fapi_get_u16(skb, u.mlme_tdls_peer_ind.tdls_event); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + ndev_vif->sta.tdls_enabled = true; + + SLSI_INFO(sdev, "(vif:%d, peer_index:%d mac[%pM])\n", + fapi_get_vif(skb), peer_index, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address)); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit_with_lock; + } + + if (WARN(ndev_vif->vif_type != FAPI_VIFTYPE_STATION, "STA VIF")) + goto exit_with_lock; + + /* Check for MAX client */ + if ((ndev_vif->sta.tdls_peer_sta_records) + 1 > SLSI_TDLS_PEER_CONNECTIONS_MAX) { + SLSI_NET_ERR(dev, "MAX TDLS peer limit reached. Ignore ind for peer_index:%d\n", peer_index); + goto exit_with_lock; + } + + if (peer_index < SLSI_TDLS_PEER_INDEX_MIN || peer_index > SLSI_TDLS_PEER_INDEX_MAX) { + SLSI_NET_ERR(dev, "Received incorrect peer_index: %d\n", peer_index); + goto exit_with_lock; + } + + peer = slsi_peer_add(sdev, dev, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address), peer_index); + + if (!peer) { + SLSI_NET_ERR(dev, "Peer NOT Created\n"); + goto exit_with_lock; + } + + /* QoS is mandatory for TDLS - enable QoS for TDLS peer by default */ + peer->qos_enabled = true; + + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + + /* Move TDLS packets from STA_Q to TDLS_Q */ + slsi_tdls_move_packets(sdev, dev, ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET], peer, true); + + /* Handling MLME-TDLS-PEER.response */ + slsi_mlme_tdls_peer_resp(sdev, dev, peer_index, tdls_event); + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +static void slsi_tdls_event_disconnected(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct slsi_peer *peer = NULL; + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 pid = fapi_get_u16(skb, u.mlme_tdls_peer_ind.peer_index); + u16 tdls_event = fapi_get_u16(skb, u.mlme_tdls_peer_ind.tdls_event); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (WARN_ON(!dev)) + goto exit; + + SLSI_INFO(sdev, "(vif:%d, MAC:%pM)\n", ndev_vif->ifnum, + fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address)); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit; + } + + peer = slsi_get_peer_from_mac(sdev, dev, fapi_get_buff(skb, u.mlme_tdls_peer_ind.peer_sta_address)); + + if (!peer || (peer->aid == 0)) { + WARN_ON(!peer || (peer->aid == 0)); + SLSI_NET_DBG1(dev, SLSI_MLME, "peer NOT found by MAC address\n"); + goto exit; + } + + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DISCONNECTED); + + /* Move TDLS packets from TDLS_Q to STA_Q */ + slsi_tdls_move_packets(sdev, dev, ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET], peer, false); + + slsi_peer_remove(sdev, dev, peer); + + slsi_mlme_tdls_peer_resp(sdev, dev, pid, tdls_event); +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + + slsi_kfree_skb(skb); +} + +/* Handling for MLME-TDLS-PEER.indication + */ +void slsi_tdls_peer_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 tdls_event = fapi_get_u16(skb, u.mlme_tdls_peer_ind.tdls_event); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_tdls_peer_ind tdls_event: %d\n", tdls_event); + + switch (tdls_event) { + case FAPI_TDLSEVENT_CONNECTED: + slsi_tdls_event_connected(sdev, dev, skb); + break; + case FAPI_TDLSEVENT_DISCONNECTED: + slsi_tdls_event_disconnected(sdev, dev, skb); + break; + case FAPI_TDLSEVENT_DISCOVERED: + slsi_tdls_event_discovered(sdev, dev, skb); + break; + default: + WARN_ON((tdls_event == 0) || (tdls_event > 4)); + slsi_kfree_skb(skb); + break; + } +} + +/* Retrieve any buffered frame before connected_ind and pass them up. */ +void slsi_rx_buffered_frames(struct slsi_dev *sdev, struct net_device *dev, struct slsi_peer *peer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *buff_frame = NULL; + + WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); + if (WARN(!peer, "Peer is NULL")) + return; + WARN(peer->connected_state == SLSI_STA_CONN_STATE_CONNECTING, "Wrong state"); + + SLSI_NET_DBG2(dev, SLSI_MLME, "Processing buffered RX frames received before mlme_connected_ind for (vif:%d, aid:%d)\n", + ndev_vif->ifnum, peer->aid); + buff_frame = slsi_skb_dequeue(&peer->buffered_frames); + while (buff_frame) { + slsi_debug_frame(sdev, dev, buff_frame, "RX_BUFFERED"); + switch (fapi_get_sigid(buff_frame)) { + case MA_BLOCKACK_IND: + SLSI_NET_DBG2(dev, SLSI_MLME, "Transferring buffered MA_BLOCKACK_IND frame"); + __slsi_rx_blockack_ind(sdev, dev, buff_frame); + break; + default: + SLSI_NET_WARN(dev, "Unexpected Data: 0x%.4x\n", fapi_get_sigid(buff_frame)); + slsi_kfree_skb(buff_frame); + break; + } + buff_frame = slsi_skb_dequeue(&peer->buffered_frames); + } +} + +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG +void slsi_rx_synchronised_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + const u8 *connecting_ssid = NULL; + struct cfg80211_external_auth_params auth_request; + int r; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + SLSI_NET_DBG1(dev, SLSI_MLME, "Received slsi_rx_synchronised_ind\n"); + if (ndev_vif->sta.sta_bss->ies->len) + connecting_ssid = cfg80211_find_ie(WLAN_EID_SSID, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + + auth_request.action = NL80211_EXTERNAL_AUTH_START; + memcpy(auth_request.bssid, ndev_vif->sta.sta_bss->bssid, ETH_ALEN); + if (connecting_ssid && (connecting_ssid[1] > 0)) { + memcpy(auth_request.ssid.ssid, &connecting_ssid[2], connecting_ssid[1]); + auth_request.ssid.ssid_len = connecting_ssid[1]; + } + auth_request.key_mgmt_suite = ndev_vif->sta.crypto.akm_suites[0]; + r = cfg80211_external_auth_request(dev, &auth_request, GFP_KERNEL); + if (r) + SLSI_NET_DBG1(dev, SLSI_MLME, "cfg80211_external_auth_request failed"); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} +#endif + +void slsi_rx_connected_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + u16 aid = fapi_get_u16(skb, u.mlme_connected_ind.peer_index); + + /* For AP mode, peer_index value is equivalent to aid(association_index) value */ + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_connected_ind(vif:%d, peer_index:%d)\n", + fapi_get_vif(skb), + aid); + SLSI_INFO(sdev, "Received Association Response\n"); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit_with_lock; + } + + if (WARN(ndev_vif->vif_type == FAPI_VIFTYPE_STATION, "STA VIF and Not Roaming")) + goto exit_with_lock; + + switch (ndev_vif->vif_type) { + case FAPI_VIFTYPE_AP: + { + if (aid < SLSI_PEER_INDEX_MIN || aid > SLSI_PEER_INDEX_MAX) { + SLSI_NET_ERR(dev, "Received incorrect peer_index: %d\n", aid); + goto exit_with_lock; + } + + peer = slsi_get_peer_from_qs(sdev, dev, aid - 1); + if (!peer) { + SLSI_NET_ERR(dev, "Peer (aid:%d) Not Found - Disconnect peer\n", aid); + goto exit_with_lock; + } + + cfg80211_new_sta(dev, peer->address, &peer->sinfo, GFP_KERNEL); + + if (ndev_vif->ap.privacy) { + peer->connected_state = SLSI_STA_CONN_STATE_DOING_KEY_CONFIG; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DOING_KEY_CONFIG); + } else { + peer->connected_state = SLSI_STA_CONN_STATE_CONNECTED; + slsi_mlme_connected_resp(sdev, dev, aid); + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + } + slsi_rx_buffered_frames(sdev, dev, peer); + break; + } + + default: + SLSI_NET_WARN(dev, "mlme_connected_ind(vif:%d, unexpected vif type:%d)\n", fapi_get_vif(skb), ndev_vif->vif_type); + break; + } +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_reassoc_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + enum ieee80211_statuscode status = WLAN_STATUS_SUCCESS; + struct slsi_peer *peer = NULL; + u8 *assoc_ie = NULL; + int assoc_ie_len = 0; + u8 *reassoc_rsp_ie = NULL; + int reassoc_rsp_ie_len = 0; + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_reassoc_ind(vif:%d, result:0x%04x)\n", + fapi_get_vif(skb), + fapi_get_u16(skb, u.mlme_reassociate_ind.result_code)); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit_with_lock; + } + + if (WARN(ndev_vif->vif_type != FAPI_VIFTYPE_STATION, "Not a Station VIF\n")) + goto exit_with_lock; + + peer = slsi_get_peer_from_qs(sdev, dev, 0); + if (WARN_ON(!peer)) { + SLSI_NET_ERR(dev, "PEER Not found\n"); + goto exit_with_lock; + } + + if (fapi_get_u16(skb, u.mlme_reassociate_ind.result_code) != FAPI_RESULTCODE_SUCCESS) { + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + slsi_rx_ba_stop_all(dev, peer); + } else { + peer->pairwise_key_set = 0; + + if (peer->assoc_ie) { + assoc_ie = peer->assoc_ie->data; + assoc_ie_len = peer->assoc_ie->len; + WARN_ON(assoc_ie_len && !assoc_ie); + } + + slsi_peer_reset_stats(sdev, dev, peer); + + peer->sinfo.assoc_req_ies = assoc_ie; + peer->sinfo.assoc_req_ies_len = assoc_ie_len; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) + peer->sinfo.filled |= STATION_INFO_ASSOC_REQ_IES; +#endif + slsi_peer_update_assoc_rsp(sdev, dev, peer, skb); + /* skb is consumed by slsi_peer_update_assoc_rsp. So do not access this anymore. */ + skb = NULL; + if (peer->assoc_resp_ie) { + reassoc_rsp_ie = peer->assoc_resp_ie->data; + reassoc_rsp_ie_len = peer->assoc_resp_ie->len; + WARN_ON(reassoc_rsp_ie_len && !reassoc_rsp_ie); + } + + /* update the uapsd bitmask according to the bit values + * in wmm information element of association request + */ + if (!sta_wmm_update_uapsd(sdev, dev, peer, assoc_ie, assoc_ie_len)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Fail to update WMM uapsd\n"); + + /* update the acm bitmask according to the acm bit values that + * are included in wmm ie elements of re-association response + */ + if (!sta_wmm_update_wmm_ac_ies(sdev, dev, peer, reassoc_rsp_ie, reassoc_rsp_ie_len)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Fail to update WMM AC ies\n"); + } + + /* cfg80211_connect_result will take a copy of any ASSOC or (RE)ASSOC RSP IEs passed to it */ + cfg80211_connect_result(dev, + peer->address, + assoc_ie, assoc_ie_len, + reassoc_rsp_ie, reassoc_rsp_ie_len, + status, + GFP_KERNEL); + + if (status == WLAN_STATUS_SUCCESS) { + ndev_vif->sta.vif_status = SLSI_VIF_STATUS_CONNECTED; + + /* For Open & WEP AP,send reassoc response. + * For secured AP, all this would be done after handshake + */ + if ((peer->capabilities & WLAN_CAPABILITY_PRIVACY) && + (cfg80211_find_ie(WLAN_EID_RSN, assoc_ie, assoc_ie_len) || + cfg80211_find_ie(SLSI_WLAN_EID_WAPI, assoc_ie, assoc_ie_len) || + cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPA, assoc_ie, assoc_ie_len))) { + /*secured AP*/ + ndev_vif->sta.resp_id = MLME_REASSOCIATE_RES; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DOING_KEY_CONFIG); + peer->connected_state = SLSI_STA_CONN_STATE_DOING_KEY_CONFIG; + } else { + /*Open/WEP AP*/ + slsi_mlme_reassociate_resp(sdev, dev); + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + peer->connected_state = SLSI_STA_CONN_STATE_CONNECTED; + } + } else { + netif_carrier_off(dev); + slsi_mlme_del_vif(sdev, dev); + slsi_vif_deactivated(sdev, dev); + } + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_connect_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + enum ieee80211_statuscode status = WLAN_STATUS_SUCCESS; + struct slsi_peer *peer = NULL; + u8 *assoc_ie = NULL; + int assoc_ie_len = 0; + u8 *assoc_rsp_ie = NULL; + int assoc_rsp_ie_len = 0; + u8 bssid[ETH_ALEN]; + u16 fw_result_code; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + fw_result_code = fapi_get_u16(skb, u.mlme_connect_ind.result_code); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_connect_ind(vif:%d, result:0x%04x)\n", + fapi_get_vif(skb), fw_result_code); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit_with_lock; + } + + if (WARN(ndev_vif->vif_type != FAPI_VIFTYPE_STATION, "Not a Station VIF\n")) + goto exit_with_lock; + + if (ndev_vif->sta.vif_status != SLSI_VIF_STATUS_CONNECTING) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not connecting\n"); + goto exit_with_lock; + } + + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (peer) { + SLSI_ETHER_COPY(bssid, peer->address); + } else { + SLSI_NET_ERR(dev, "!!NO peer record for AP\n"); + eth_zero_addr(bssid); + } + sdev->assoc_result_code = fw_result_code; + if (fw_result_code != FAPI_RESULTCODE_SUCCESS) { + if (fw_result_code == FAPI_RESULTCODE_AUTH_NO_ACK) { + SLSI_INFO(sdev, "Connect failed,Result code:AUTH_NO_ACK\n"); + } else if (fw_result_code == FAPI_RESULTCODE_ASSOC_NO_ACK) { + SLSI_INFO(sdev, "Connect failed,Result code:ASSOC_NO_ACK\n"); + } else if (fw_result_code >= 0x8100 && fw_result_code <= 0x81FF) { + fw_result_code = fw_result_code & 0x00FF; + SLSI_INFO(sdev, "Connect failed(Auth failure), Result code:0x%04x\n", fw_result_code); + } else if (fw_result_code >= 0x8200 && fw_result_code <= 0x82FF) { + fw_result_code = fw_result_code & 0x00FF; + SLSI_INFO(sdev, "Connect failed(Assoc Failure), Result code:0x%04x\n", fw_result_code); + } else { + SLSI_INFO(sdev, "Connect failed,Result code:0x%04x\n", fw_result_code); + } +#ifdef CONFIG_SCSC_LOG_COLLECTION + /* Trigger log collection if fw result code is not success */ + scsc_log_collector_schedule_collection(SCSC_LOG_HOST_WLAN, SCSC_LOG_HOST_WLAN_REASON_CONNECT_ERR); +#endif + status = fw_result_code; +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG + if (ndev_vif->sta.crypto.wpa_versions == 3) { + const u8 *connecting_ssid = NULL; + int r; + struct cfg80211_external_auth_params auth_request; + + if (ndev_vif->sta.sta_bss->ies->len) + connecting_ssid = cfg80211_find_ie(WLAN_EID_SSID, ndev_vif->sta.sta_bss->ies->data, + ndev_vif->sta.sta_bss->ies->len); + + auth_request.action = NL80211_EXTERNAL_AUTH_ABORT; + memcpy(auth_request.bssid, ndev_vif->sta.sta_bss->bssid, ETH_ALEN); + if (connecting_ssid && (connecting_ssid[1] > 0)) { + memcpy(auth_request.ssid.ssid, &connecting_ssid[2], connecting_ssid[1]); + auth_request.ssid.ssid_len = connecting_ssid[1]; + } + auth_request.key_mgmt_suite = ndev_vif->sta.crypto.akm_suites[0]; + r = cfg80211_external_auth_request(dev, &auth_request, GFP_KERNEL); + if (r) + SLSI_NET_DBG1(dev, SLSI_MLME, "cfg80211_external_auth_request Abort failed"); + } +#endif + } else { + SLSI_INFO(sdev, "Received Association Response\n"); + if (!peer || !peer->assoc_ie) { + if (peer) + WARN(!peer->assoc_ie, "proc-started-ind not received before connect-ind"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + } else { + if (peer->assoc_ie) { + assoc_ie = peer->assoc_ie->data; + assoc_ie_len = peer->assoc_ie->len; + } + + slsi_peer_update_assoc_rsp(sdev, dev, peer, skb); + /* skb is consumed by slsi_peer_update_assoc_rsp. So do not access this anymore. */ + skb = NULL; + + if (peer->assoc_resp_ie) { + assoc_rsp_ie = peer->assoc_resp_ie->data; + assoc_rsp_ie_len = peer->assoc_resp_ie->len; + } + + /* this is the right place to initialize the bitmasks for + * acm bit and tspec establishment + */ + peer->wmm_acm = 0; + peer->tspec_established = 0; + peer->uapsd = 0; + + /* update the uapsd bitmask according to the bit values + * in wmm information element of association request + */ + if (!sta_wmm_update_uapsd(sdev, dev, peer, assoc_ie, assoc_ie_len)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Fail to update WMM uapsd\n"); + + /* update the wmm ac bitmasks according to the bit values that + * are included in wmm ie elements of association response + */ + if (!sta_wmm_update_wmm_ac_ies(sdev, dev, peer, assoc_rsp_ie, assoc_rsp_ie_len)) + SLSI_NET_DBG1(dev, SLSI_MLME, "Fail to update WMM AC ies\n"); + + WARN_ON(!assoc_rsp_ie_len && !assoc_rsp_ie); + } + + WARN(!ndev_vif->sta.mlme_scan_ind_skb, "mlme_scan.ind not received before connect-ind"); + + if (ndev_vif->sta.mlme_scan_ind_skb) { + SLSI_NET_DBG1(dev, SLSI_MLME, "Sending scan indication to cfg80211, bssid: %pM\n", fapi_get_mgmt(ndev_vif->sta.mlme_scan_ind_skb)->bssid); + + /* saved skb [mlme_scan_ind] freed inside slsi_rx_scan_pass_to_cfg80211 */ + slsi_rx_scan_pass_to_cfg80211(sdev, dev, ndev_vif->sta.mlme_scan_ind_skb); + ndev_vif->sta.mlme_scan_ind_skb = NULL; + } + + if (!ndev_vif->sta.sta_bss) { + if (peer) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + ndev_vif->sta.sta_bss = cfg80211_get_bss(sdev->wiphy, NULL, peer->address, NULL, 0, IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY); +#else + ndev_vif->sta.sta_bss = cfg80211_get_bss(sdev->wiphy, NULL, peer->address, NULL, 0, 0, 0); +#endif + if (!ndev_vif->sta.sta_bss) { + SLSI_NET_ERR(dev, "sta_bss is not available, terminating the connection (peer: %p)\n", peer); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + } + } + } + + /* cfg80211_connect_result will take a copy of any ASSOC or ASSOC RSP IEs passed to it */ + cfg80211_connect_result(dev, + bssid, + assoc_ie, assoc_ie_len, + assoc_rsp_ie, assoc_rsp_ie_len, + status, + GFP_KERNEL); + + if (status == WLAN_STATUS_SUCCESS) { + ndev_vif->sta.vif_status = SLSI_VIF_STATUS_CONNECTED; + + /* For Open & WEP AP,set the power mode (static IP scenario) ,send connect response and install the packet filters . + * For secured AP, all this would be done after handshake + */ + if ((peer->capabilities & WLAN_CAPABILITY_PRIVACY) && + (cfg80211_find_ie(WLAN_EID_RSN, assoc_ie, assoc_ie_len) || + cfg80211_find_ie(SLSI_WLAN_EID_WAPI, assoc_ie, assoc_ie_len) || + cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPA, assoc_ie, assoc_ie_len))) { + /*secured AP*/ + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_DOING_KEY_CONFIG); + ndev_vif->sta.resp_id = MLME_CONNECT_RES; + } else { + /*Open/WEP AP*/ + slsi_mlme_connect_resp(sdev, dev); + slsi_set_packet_filters(sdev, dev); + + if (ndev_vif->ipaddress) + slsi_mlme_powermgt(sdev, dev, ndev_vif->set_power_mode); + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + } + + /* For P2PCLI, set the Connection Timeout (beacon miss) mib to 10 seconds + * This MIB set failure does not cause any fatal isuue. It just varies the + * detection time of GO's absence from 10 sec to FW default. So Do not disconnect + */ + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) + SLSI_P2P_STATE_CHANGE(sdev, P2P_GROUP_FORMED_CLI); + + /*Update the firmware with cached channels*/ +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + if (!sdev->device_config.roam_scan_mode && ndev_vif->vif_type == FAPI_VIFTYPE_STATION && ndev_vif->activated && ndev_vif->iftype != NL80211_IFTYPE_P2P_CLIENT) { +#else + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION && ndev_vif->activated && ndev_vif->iftype != NL80211_IFTYPE_P2P_CLIENT) { +#endif + const u8 *ssid = cfg80211_find_ie(WLAN_EID_SSID, assoc_ie, assoc_ie_len); + u8 channels[SLSI_ROAMING_CHANNELS_MAX]; + u32 channels_count = slsi_roaming_scan_configure_channels(sdev, dev, ssid, channels); + + if (channels_count) + if (slsi_mlme_set_cached_channels(sdev, dev, channels_count, channels) != 0) + SLSI_NET_ERR(dev, "MLME-SET-CACHED-CHANNELS.req failed\n"); + } + } else { + /* Firmware reported connection success, but driver reported failure to cfg80211: + * send mlme-disconnect.req to firmware + */ + if ((fw_result_code == FAPI_RESULTCODE_SUCCESS) && peer) { + slsi_mlme_disconnect(sdev, dev, peer->address, FAPI_REASONCODE_UNSPECIFIED_REASON, true); + slsi_handle_disconnect(sdev, dev, peer->address, FAPI_REASONCODE_UNSPECIFIED_REASON); + } else { + slsi_handle_disconnect(sdev, dev, NULL, FAPI_REASONCODE_UNSPECIFIED_REASON); + } + } + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_disconnect_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_disconnect_ind(vif:%d, MAC:%pM)\n", + fapi_get_vif(skb), + fapi_get_buff(skb, u.mlme_disconnect_ind.peer_sta_address)); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_schedule_collection(SCSC_LOG_HOST_WLAN, SCSC_LOG_HOST_WLAN_REASON_DISCONNECT_IND); +#else + mx140_log_dump(); +#endif + + SLSI_INFO(sdev, "Received DEAUTH, reason = 0\n"); + slsi_handle_disconnect(sdev, + dev, + fapi_get_buff(skb, u.mlme_disconnect_ind.peer_sta_address), + 0); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_disconnected_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 reason; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + reason = fapi_get_u16(skb, u.mlme_disconnected_ind.reason_code); + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_disconnected_ind(vif:%d, reason:%d, MAC:%pM)\n", + fapi_get_vif(skb), + fapi_get_u16(skb, u.mlme_disconnected_ind.reason_code), + fapi_get_buff(skb, u.mlme_disconnected_ind.peer_sta_address)); + +#ifdef CONFIG_SCSC_LOG_COLLECTION + scsc_log_collector_schedule_collection(SCSC_LOG_HOST_WLAN, SCSC_LOG_HOST_WLAN_REASON_DISCONNECTED_IND); +#else + mx140_log_dump(); +#endif + if (reason >= 0 && reason <= 0xFF) { + SLSI_INFO(sdev, "Received DEAUTH, reason = %d\n", reason); + } else if (reason >= 0x8100 && reason <= 0x81FF) { + reason = reason & 0x00FF; + SLSI_INFO(sdev, "Received DEAUTH, reason = %d\n", reason); + } else if (reason >= 0x8200 && reason <= 0x82FF) { + reason = reason & 0x00FF; + SLSI_INFO(sdev, "Received DISASSOC, reason = %d\n", reason); + } else { + SLSI_INFO(sdev, "Received DEAUTH, reason = Local Disconnect <%d>\n", reason); + } + + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + if (fapi_get_u16(skb, u.mlme_disconnected_ind.reason_code) == + FAPI_REASONCODE_HOTSPOT_MAX_CLIENT_REACHED) { + SLSI_NET_DBG1(dev, SLSI_MLME, + "Sending max hotspot client reached notification to user space\n"); + cfg80211_conn_failed(dev, fapi_get_buff(skb, u.mlme_disconnected_ind.peer_sta_address), + NL80211_CONN_FAIL_MAX_CLIENTS, GFP_KERNEL); + goto exit; + } + } + + slsi_handle_disconnect(sdev, + dev, + fapi_get_buff(skb, u.mlme_disconnected_ind.peer_sta_address), + fapi_get_u16(skb, u.mlme_disconnected_ind.reason_code)); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); + return; +} + +/* Handle Procedure Started (Type = Device Discovered) indication for P2P */ +static void slsi_rx_p2p_device_discovered_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + int mgmt_len; + + SLSI_UNUSED_PARAMETER(sdev); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Freq = %d\n", ndev_vif->chan->center_freq); + + /* Only Probe Request is expected as of now */ + mgmt_len = fapi_get_mgmtlen(skb); + if (mgmt_len) { + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + + if (ieee80211_is_mgmt(mgmt->frame_control)) { + if (ieee80211_is_probe_req(mgmt->frame_control)) { + SLSI_NET_DBG3(dev, SLSI_CFG80211, "Received Probe Request\n"); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_rx_mgmt(&ndev_vif->wdev, ndev_vif->chan->center_freq, 0, (const u8 *)mgmt, mgmt_len, GFP_ATOMIC); +#else + cfg80211_rx_mgmt(dev, ndev_vif->chan->center_freq, 0, (const u8 *)mgmt, mgmt_len, GFP_ATOMIC); +#endif + } else + SLSI_NET_ERR(dev, "Ignore Indication - Not Probe Request frame\n"); + } else { + SLSI_NET_ERR(dev, "Ignore Indication - Not Management frame\n"); + } + } +} + +void slsi_rx_procedure_started_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_procedure_started_ind(vif:%d, type:%d, peer_index:%d)\n", + fapi_get_vif(skb), + fapi_get_u16(skb, u.mlme_procedure_started_ind.procedure_type), + fapi_get_u16(skb, u.mlme_procedure_started_ind.peer_index)); + SLSI_INFO(sdev, "Send Association Request\n"); + + if (!ndev_vif->activated) { + SLSI_NET_DBG1(dev, SLSI_MLME, "VIF not activated\n"); + goto exit_with_lock; + } + + switch (fapi_get_u16(skb, u.mlme_procedure_started_ind.procedure_type)) { + case FAPI_PROCEDURETYPE_CONNECTION_STARTED: + switch (ndev_vif->vif_type) { + case FAPI_VIFTYPE_AP: + { + u16 peer_index = fapi_get_u16(skb, u.mlme_procedure_started_ind.peer_index); + + /* Check for MAX client */ + if ((ndev_vif->peer_sta_records + 1) > SLSI_AP_PEER_CONNECTIONS_MAX) { + SLSI_NET_ERR(dev, "MAX Station limit reached. Ignore ind for peer_index:%d\n", peer_index); + goto exit_with_lock; + } + + if (peer_index < SLSI_PEER_INDEX_MIN || peer_index > SLSI_PEER_INDEX_MAX) { + SLSI_NET_ERR(dev, "Received incorrect peer_index: %d\n", peer_index); + goto exit_with_lock; + } + + peer = slsi_peer_add(sdev, dev, (fapi_get_mgmt(skb))->sa, peer_index); + if (!peer) { + SLSI_NET_ERR(dev, "Peer NOT Created\n"); + goto exit_with_lock; + } + slsi_peer_update_assoc_req(sdev, dev, peer, skb); + /* skb is consumed by slsi_peer_update_assoc_req. So do not access this anymore. */ + skb = NULL; + peer->connected_state = SLSI_STA_CONN_STATE_CONNECTING; + + if ((ndev_vif->iftype == NL80211_IFTYPE_P2P_GO) && + (peer->assoc_ie) && + (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, WLAN_OUI_TYPE_MICROSOFT_WPS, peer->assoc_ie->data, peer->assoc_ie->len))) { + SLSI_NET_DBG2(dev, SLSI_MLME, "WPS IE is present. Setting peer->is_wps to TRUE\n"); + peer->is_wps = true; + } + + /* Take a wakelock to avoid platform suspend before EAPOL exchanges (to avoid connection delay) */ + slsi_wakelock_timeout(&sdev->wlan_wl_mlme, SLSI_WAKELOCK_TIME_MSEC_EAPOL); + break; + } + case FAPI_VIFTYPE_STATION: + { + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!peer)) { + SLSI_NET_ERR(dev, "Peer NOT FOUND\n"); + goto exit_with_lock; + } + slsi_peer_update_assoc_req(sdev, dev, peer, skb); + /* skb is consumed by slsi_peer_update_assoc_rsp. So do not access this anymore. */ + skb = NULL; + break; + } + default: + SLSI_NET_ERR(dev, "Incorrect vif type for proceduretype_connection_started\n"); + break; + } + break; + case FAPI_PROCEDURETYPE_DEVICE_DISCOVERED: + /* Expected only in P2P Device and P2P GO role */ + if (WARN_ON(!SLSI_IS_VIF_INDEX_P2P(ndev_vif) && (ndev_vif->iftype != NL80211_IFTYPE_P2P_GO))) + goto exit_with_lock; + + /* Send probe request to supplicant only if in listening state. Issues were seen earlier if + * Probe request was sent to supplicant while waiting for GO Neg Req from peer. + * Send Probe request to supplicant if received in GO mode + */ + if ((sdev->p2p_state == P2P_LISTENING) || (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO)) + slsi_rx_p2p_device_discovered_ind(sdev, dev, skb); + break; + case FAPI_PROCEDURETYPE_ROAMING_STARTED: + { + SLSI_NET_DBG1(dev, SLSI_MLME, "Roaming Procedure Starting with %pM\n", (fapi_get_mgmt(skb))->bssid); + if (WARN_ON(ndev_vif->vif_type != FAPI_VIFTYPE_STATION)) + goto exit_with_lock; + if (WARN_ON(!ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET] || !ndev_vif->peer_sta_record[SLSI_STA_PEER_QUEUESET]->valid)) + goto exit_with_lock; + slsi_kfree_skb(ndev_vif->sta.roam_mlme_procedure_started_ind); + ndev_vif->sta.roam_mlme_procedure_started_ind = skb; + /* skb is consumed here. So remove reference to this.*/ + skb = NULL; + break; + } + default: + SLSI_NET_DBG1(dev, SLSI_MLME, "Unknown Procedure: %d\n", fapi_get_u16(skb, u.mlme_procedure_started_ind.procedure_type)); + goto exit_with_lock; + } + +exit_with_lock: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_frame_transmission_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + u16 host_tag = fapi_get_u16(skb, u.mlme_frame_transmission_ind.host_tag); + u16 tx_status = fapi_get_u16(skb, u.mlme_frame_transmission_ind.transmission_status); + bool ack = true; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_frame_transmission_ind(vif:%d, host_tag:%d, transmission_status:%d)\n", fapi_get_vif(skb), + host_tag, + tx_status); + + if (ndev_vif->mgmt_tx_data.host_tag == host_tag) { + struct netdev_vif *ndev_vif_to_cfg = ndev_vif; + + /* If frame tx failed allow del_vif work to take care of vif deletion. + * This work would be queued as part of frame_tx with the wait duration + */ + if (tx_status != FAPI_TRANSMISSIONSTATUS_SUCCESSFUL) { + ack = false; + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + if (sdev->wlan_unsync_vif_state == WLAN_UNSYNC_VIF_TX) + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_VIF_ACTIVE; /*We wouldn't delete VIF*/ + } else { + if (sdev->p2p_group_exp_frame != SLSI_P2P_PA_INVALID) + slsi_clear_offchannel_data(sdev, false); + else if (ndev_vif->mgmt_tx_data.exp_frame != SLSI_P2P_PA_INVALID) + (void)slsi_mlme_reset_dwell_time(sdev, dev); + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + } + } + + /* Change state if frame tx was in Listen as peer response is not expected */ + if (SLSI_IS_VIF_INDEX_P2P(ndev_vif) && (ndev_vif->mgmt_tx_data.exp_frame == SLSI_P2P_PA_INVALID)) { + if (delayed_work_pending(&ndev_vif->unsync.roc_expiry_work)) + SLSI_P2P_STATE_CHANGE(sdev, P2P_LISTENING); + else + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + } else if (SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif)) { + const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *)ndev_vif->mgmt_tx_data.buf; + + /* If frame transmission was initiated on P2P device vif by supplicant, then use the net_dev of that vif (i.e. p2p0) */ + if ((mgmt) && (memcmp(mgmt->sa, dev->dev_addr, ETH_ALEN) != 0)) { + struct net_device *ndev = slsi_get_netdev(sdev, SLSI_NET_INDEX_P2P); + + SLSI_NET_DBG2(dev, SLSI_MLME, "Frame Tx was requested with device address - Change ndev_vif for tx_status\n"); + + ndev_vif_to_cfg = netdev_priv(ndev); + if (!ndev_vif_to_cfg) { + SLSI_NET_ERR(dev, "Getting P2P Index netdev failed\n"); + ndev_vif_to_cfg = ndev_vif; + } + } + } +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + if (!sdev->device_config.wes_mode) { +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) + cfg80211_mgmt_tx_status(&ndev_vif_to_cfg->wdev, ndev_vif->mgmt_tx_data.cookie, ndev_vif->mgmt_tx_data.buf, ndev_vif->mgmt_tx_data.buf_len, ack, GFP_KERNEL); +#else + cfg80211_mgmt_tx_status(ndev_vif_to_cfg->wdev.netdev, ndev_vif->mgmt_tx_data.cookie, ndev_vif->mgmt_tx_data.buf, ndev_vif->mgmt_tx_data.buf_len, ack, GFP_KERNEL); +#endif + +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + } +#endif + (void)slsi_set_mgmt_tx_data(ndev_vif, 0, 0, NULL, 0); + } + + if ((tx_status == FAPI_TRANSMISSIONSTATUS_SUCCESSFUL) || (tx_status == FAPI_TRANSMISSIONSTATUS_RETRY_LIMIT)) { +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (ndev_vif->enhanced_arp_detect_enabled && (ndev_vif->vif_type == FAPI_VIFTYPE_STATION)) { + int i = 0; + + for (i = 0; i < SLSI_MAX_ARP_SEND_FRAME; i++) { + if (ndev_vif->enhanced_arp_host_tag[i] == host_tag) { + ndev_vif->enhanced_arp_host_tag[i] = 0; + ndev_vif->enhanced_arp_stats.arp_req_rx_count_by_lower_mac++; + if (tx_status == FAPI_TRANSMISSIONSTATUS_SUCCESSFUL) + ndev_vif->enhanced_arp_stats.arp_req_count_tx_success++; + break; + } + } + } +#endif + if ((ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.m4_host_tag == host_tag)) { + switch (ndev_vif->sta.resp_id) { + case MLME_ROAMED_RES: + slsi_mlme_roamed_resp(sdev, dev); + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!peer)) + break; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + cac_update_roam_traffic_params(sdev, dev); + break; + case MLME_CONNECT_RES: + slsi_mlme_connect_resp(sdev, dev); + slsi_set_packet_filters(sdev, dev); + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (WARN_ON(!peer)) + break; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + break; + case MLME_REASSOCIATE_RES: + slsi_mlme_reassociate_resp(sdev, dev); + break; + default: + break; + } + ndev_vif->sta.m4_host_tag = 0; + ndev_vif->sta.resp_id = 0; + } + if (tx_status == FAPI_TRANSMISSIONSTATUS_RETRY_LIMIT) { + if ((ndev_vif->iftype == NL80211_IFTYPE_STATION) && + (ndev_vif->sta.eap_hosttag == host_tag)) { + if (ndev_vif->sta.sta_bss) { + SLSI_NET_WARN(dev, "Disconnect as EAP frame transmission failed\n"); + slsi_mlme_disconnect(sdev, dev, ndev_vif->sta.sta_bss->bssid, FAPI_REASONCODE_UNSPECIFIED_REASON, false); + } else { + SLSI_NET_WARN(dev, "EAP frame transmission failed, sta_bss not available\n"); + } + } + ndev_vif->stats.tx_errors++; + } + } else { + ndev_vif->stats.tx_errors++; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_received_frame_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 data_unit_descriptor = fapi_get_u16(skb, u.mlme_received_frame_ind.data_unit_descriptor); + u16 frequency = SLSI_FREQ_FW_TO_HOST(fapi_get_u16(skb, u.mlme_received_frame_ind.channel_frequency)); + u8 *eapol = NULL; + u8 *eap = NULL; + u16 protocol = 0; + u32 dhcp_message_type = SLSI_DHCP_MESSAGE_TYPE_INVALID; + u16 eap_length = 0; + + SLSI_NET_DBG2(dev, SLSI_MLME, "mlme_received_frame_ind(vif:%d, data descriptor:%d, freq:%d)\n", + fapi_get_vif(skb), + data_unit_descriptor, + frequency); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (data_unit_descriptor == FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME) { + struct ieee80211_mgmt *mgmt; + int mgmt_len; + + mgmt_len = fapi_get_mgmtlen(skb); + if (!mgmt_len) + goto exit; + mgmt = fapi_get_mgmt(skb); + if (ieee80211_is_auth(mgmt->frame_control)) { + cfg80211_rx_mgmt(&ndev_vif->wdev, frequency, 0, (const u8 *)mgmt, mgmt_len, GFP_ATOMIC); + goto exit; + } +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + if (ndev_vif->is_wips_running && ieee80211_is_beacon(mgmt->frame_control) && + SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { + slsi_handle_wips_beacon(sdev, dev, skb, mgmt, mgmt_len); + goto exit; + } +#endif + if (WARN_ON(!(ieee80211_is_action(mgmt->frame_control)))) + goto exit; + if (SLSI_IS_VIF_INDEX_WLAN(ndev_vif)) { +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + if (slsi_is_wes_action_frame(mgmt)) { + SLSI_NET_DBG1(dev, SLSI_CFG80211, "Received NCHO WES VS action frame\n"); + if (!sdev->device_config.wes_mode) + goto exit; + } else { +#endif + if (mgmt->u.action.category == WLAN_CATEGORY_WMM) { + cac_rx_wmm_action(sdev, dev, mgmt, mgmt_len); + } else { + slsi_wlan_dump_public_action_subtype(sdev, mgmt, false); + if (sdev->wlan_unsync_vif_state == WLAN_UNSYNC_VIF_TX) + sdev->wlan_unsync_vif_state = WLAN_UNSYNC_VIF_ACTIVE; + } +#ifdef CONFIG_SCSC_WLAN_WES_NCHO + } +#endif + } else { + int subtype = slsi_p2p_get_public_action_subtype(mgmt); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Received action frame (%s)\n", slsi_p2p_pa_subtype_text(subtype)); + + if (SLSI_IS_P2P_UNSYNC_VIF(ndev_vif) && (ndev_vif->mgmt_tx_data.exp_frame != SLSI_P2P_PA_INVALID) && (subtype == ndev_vif->mgmt_tx_data.exp_frame)) { + if (sdev->p2p_state == P2P_LISTENING) + SLSI_NET_WARN(dev, "Driver in incorrect P2P state (P2P_LISTENING)"); + + cancel_delayed_work(&ndev_vif->unsync.del_vif_work); + /* Sending down the Unset channel is delayed when listen work expires in + * middle of P2P procedure. For example,When Listen Work expires after + * sending provision discovery req,unset channel is not sent to FW. + * After Receiving the PROV_DISC_RESP, if listen work is not present + * Unset channel to be sent down.Similarly During P2P Negotiation procedure, + * Unset channel is not sent to FW.Once Negotiation is complete, + * if listen work is not present Unset channel to be sent down. + */ + if ((subtype == SLSI_P2P_PA_GO_NEG_CFM) || (subtype == SLSI_P2P_PA_PROV_DISC_RSP)) { + ndev_vif->drv_in_p2p_procedure = false; + if (!delayed_work_pending(&ndev_vif->unsync.roc_expiry_work)) { + slsi_mlme_unset_channel_req(ndev_vif->sdev, ndev_vif->wdev.netdev); + ndev_vif->driver_channel = 0; + } + } + + ndev_vif->mgmt_tx_data.exp_frame = SLSI_P2P_PA_INVALID; + (void)slsi_mlme_reset_dwell_time(sdev, dev); + if (delayed_work_pending(&ndev_vif->unsync.roc_expiry_work)) { + SLSI_P2P_STATE_CHANGE(sdev, P2P_LISTENING); + } else { + queue_delayed_work(sdev->device_wq, &ndev_vif->unsync.del_vif_work, + msecs_to_jiffies(SLSI_P2P_UNSYNC_VIF_EXTRA_MSEC)); + SLSI_P2P_STATE_CHANGE(sdev, P2P_IDLE_VIF_ACTIVE); + } + } else if ((sdev->p2p_group_exp_frame != SLSI_P2P_PA_INVALID) && (sdev->p2p_group_exp_frame == subtype)) { + SLSI_NET_DBG2(dev, SLSI_MLME, "Expected action frame (%s) received on Group VIF\n", slsi_p2p_pa_subtype_text(subtype)); + slsi_clear_offchannel_data(sdev, + (!SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, + ndev_vif)) ? true : false); + } + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_rx_mgmt(&ndev_vif->wdev, frequency, 0, (const u8 *)mgmt, mgmt_len, GFP_ATOMIC); +#else + cfg80211_rx_mgmt(dev, frequency, 0, (const u8 *)mgmt, mgmt_len, GFP_ATOMIC); +#endif + } else if (data_unit_descriptor == FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME) { + struct slsi_peer *peer = NULL; + struct ethhdr *ehdr = (struct ethhdr *)fapi_get_data(skb); + + peer = slsi_get_peer_from_mac(sdev, dev, ehdr->h_source); + if (!peer) { + SLSI_DBG1(sdev, SLSI_RX, "drop packet as No peer found\n"); + goto exit; + } + + /* strip signal and any signal/bulk roundings/offsets */ + skb_pull(skb, fapi_get_siglen(skb)); + + skb->dev = dev; + skb->ip_summed = CHECKSUM_NONE; + + ndev_vif->stats.rx_packets++; + ndev_vif->stats.rx_bytes += skb->len; +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 10, 0)) + dev->last_rx = jiffies; +#endif + /* Storing Data for Logging Information */ + if ((skb->len - sizeof(struct ethhdr)) >= 99) + eapol = skb->data + sizeof(struct ethhdr); + + if ((skb->len - sizeof(struct ethhdr)) >= 9) { + eap_length = (skb->len - sizeof(struct ethhdr)) - 4; + eap = skb->data + sizeof(struct ethhdr); + } + if (skb->len >= 285 && slsi_is_dhcp_packet(skb->data) != SLSI_TX_IS_NOT_DHCP) + dhcp_message_type = skb->data[284]; + + skb->protocol = eth_type_trans(skb, dev); + protocol = ntohs(skb->protocol); + if (protocol == ETH_P_PAE) { + if (eapol && eapol[SLSI_EAPOL_IEEE8021X_TYPE_POS] == SLSI_IEEE8021X_TYPE_EAPOL_KEY) { + if ((eapol[SLSI_EAPOL_TYPE_POS] == SLSI_EAPOL_TYPE_RSN_KEY || + eapol[SLSI_EAPOL_TYPE_POS] == SLSI_EAPOL_TYPE_WPA_KEY) && + (eapol[SLSI_EAPOL_KEY_INFO_LOWER_BYTE_POS] & + SLSI_EAPOL_KEY_INFO_KEY_TYPE_BIT_IN_LOWER_BYTE) && + (eapol[SLSI_EAPOL_KEY_INFO_HIGHER_BYTE_POS] & + SLSI_EAPOL_KEY_INFO_MIC_BIT_IN_HIGHER_BYTE) && + (eapol[SLSI_EAPOL_KEY_DATA_LENGTH_HIGHER_BYTE_POS] == 0) && + (eapol[SLSI_EAPOL_KEY_DATA_LENGTH_LOWER_BYTE_POS] == 0)) { + SLSI_INFO(sdev, "Received 4way-H/S, M4\n"); + } else if (!(eapol[SLSI_EAPOL_KEY_INFO_HIGHER_BYTE_POS] & + SLSI_EAPOL_KEY_INFO_MIC_BIT_IN_HIGHER_BYTE)) { + SLSI_INFO(sdev, "Received 4way-H/S, M1\n"); + } else if (eapol[SLSI_EAPOL_KEY_INFO_HIGHER_BYTE_POS] & + SLSI_EAPOL_KEY_INFO_SECURE_BIT_IN_HIGHER_BYTE) { + SLSI_INFO(sdev, "Received 4way-H/S, M3\n"); + } else { + SLSI_INFO(sdev, "Received 4way-H/S, M2\n"); + } + } else if (eap && eap[SLSI_EAPOL_IEEE8021X_TYPE_POS] == SLSI_IEEE8021X_TYPE_EAP_PACKET) { + if (eap[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_REQUEST) + SLSI_INFO(sdev, "Received EAP-Request (%d)\n", eap_length); + else if (eap[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_RESPONSE) + SLSI_INFO(sdev, "Received EAP-Response (%d)\n", eap_length); + else if (eap[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_SUCCESS) + SLSI_INFO(sdev, "Received EAP-Success (%d)\n", eap_length); + else if (eap[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_FAILURE) + SLSI_INFO(sdev, "Received EAP-Failure (%d)\n", eap_length); + } + } else if (protocol == ETH_P_IP) { + if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_DISCOVER) + SLSI_INFO(sdev, "Received DHCP [DISCOVER]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_OFFER) + SLSI_INFO(sdev, "Received DHCP [OFFER]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_REQUEST) + SLSI_INFO(sdev, "Received DHCP [REQUEST]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_DECLINE) + SLSI_INFO(sdev, "Received DHCP [DECLINE]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_ACK) + SLSI_INFO(sdev, "Received DHCP [ACK]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_NAK) + SLSI_INFO(sdev, "Received DHCP [NAK]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_RELEASE) + SLSI_INFO(sdev, "Received DHCP [RELEASE]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_INFORM) + SLSI_INFO(sdev, "Received DHCP [INFORM]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_FORCERENEW) + SLSI_INFO(sdev, "Received DHCP [FORCERENEW]\n"); + else + SLSI_INFO(sdev, "Received DHCP [INVALID]\n"); + } + slsi_dbg_untrack_skb(skb); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + SLSI_DBG2(sdev, SLSI_MLME, "pass %u bytes up (proto:%d)\n", skb->len, ntohs(skb->protocol)); + netif_rx_ni(skb); + slsi_wakelock_timeout(&sdev->wlan_wl_mlme, SLSI_WAKELOCK_TIME_MSEC_EAPOL); + return; + } +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_mic_failure_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u8 *mac_addr; + u16 key_type, key_id; + enum nl80211_key_type nl_key_type; + + SLSI_UNUSED_PARAMETER(sdev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + mac_addr = fapi_get_buff(skb, u.mlme_disconnected_ind.peer_sta_address); + key_type = fapi_get_u16(skb, u.mlme_mic_failure_ind.key_type); + key_id = fapi_get_u16(skb, u.mlme_mic_failure_ind.key_id); + + SLSI_NET_DBG1(dev, SLSI_MLME, "mlme_mic_failure_ind(vif:%d, MAC:%pM, key_type:%d, key_id:%d)\n", + fapi_get_vif(skb), mac_addr, key_type, key_id); + + if (WARN_ON((key_type != FAPI_KEYTYPE_GROUP) && (key_type != FAPI_KEYTYPE_PAIRWISE))) + goto exit; + + nl_key_type = (key_type == FAPI_KEYTYPE_GROUP) ? NL80211_KEYTYPE_GROUP : NL80211_KEYTYPE_PAIRWISE; + + cfg80211_michael_mic_failure(dev, mac_addr, nl_key_type, key_id, NULL, GFP_KERNEL); + +exit: + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +/** + * Handler for mlme_listen_end_ind. + * The listen_end_ind would be received when the total Listen Offloading time is over. + * Indicate completion of Listen Offloading to supplicant by sending Cancel-ROC event + * with cookie 0xffff. Queue delayed work for unsync vif deletion. + */ +void slsi_rx_listen_end_ind(struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG2(dev, SLSI_CFG80211, "Inform completion of P2P Listen Offloading\n"); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + cfg80211_remain_on_channel_expired(&ndev_vif->wdev, 0xffff, ndev_vif->chan, GFP_KERNEL); +#else + cfg80211_remain_on_channel_expired(ndev_vif->wdev.netdev, 0xffff, ndev_vif->chan, ndev_vif->channel_type, GFP_KERNEL); +#endif + + ndev_vif->unsync.listen_offload = false; + + slsi_p2p_queue_unsync_vif_del_work(ndev_vif, SLSI_P2P_UNSYNC_VIF_EXTRA_MSEC); + + SLSI_P2P_STATE_CHANGE(ndev_vif->sdev, P2P_IDLE_VIF_ACTIVE); + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + slsi_kfree_skb(skb); +} + +static int slsi_rx_wait_ind_match(u16 recv_id, u16 wait_id) +{ + if (recv_id == wait_id) + return 1; + if (wait_id == MLME_DISCONNECT_IND && recv_id == MLME_DISCONNECTED_IND) + return 1; + return 0; +} + +int slsi_rx_blocking_signals(struct slsi_dev *sdev, struct sk_buff *skb) +{ + u16 pid, id; + struct slsi_sig_send *sig_wait; + u16 vif = fapi_get_vif(skb); + + sig_wait = &sdev->sig_wait; + id = fapi_get_sigid(skb); + pid = fapi_get_u16(skb, receiver_pid); + + /* ALL mlme cfm signals MUST have blocking call waiting for it (Per Vif or Global) */ + if (fapi_is_cfm(skb)) { + struct net_device *dev; + struct netdev_vif *ndev_vif; + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (dev) { + ndev_vif = netdev_priv(dev); + sig_wait = &ndev_vif->sig_wait; + } + spin_lock_bh(&sig_wait->send_signal_lock); + if (id == sig_wait->cfm_id && pid == sig_wait->process_id) { + if (WARN_ON(sig_wait->cfm)) + slsi_kfree_skb(sig_wait->cfm); + sig_wait->cfm = skb; + spin_unlock_bh(&sig_wait->send_signal_lock); + complete(&sig_wait->completion); + rcu_read_unlock(); + return 0; + } + /** + * Important data frames such as EAPOL, ARP, DHCP are send + * over MLME. For these frames driver does not block on confirms. + * So there can be unexpected confirms here for such data frames. + * These confirms are treated as normal and is silently dropped + * here + */ + if (id == MLME_SEND_FRAME_CFM) { + spin_unlock_bh(&sig_wait->send_signal_lock); + rcu_read_unlock(); + slsi_kfree_skb(skb); + return 0; + } + + SLSI_DBG1(sdev, SLSI_MLME, "Unexpected cfm(0x%.4x, pid:0x%.4x, vif:%d)\n", id, pid, vif); + spin_unlock_bh(&sig_wait->send_signal_lock); + rcu_read_unlock(); + return -EINVAL; + } + /* Some mlme ind signals have a blocking call waiting (Per Vif or Global) */ + if (fapi_is_ind(skb)) { + struct net_device *dev; + struct netdev_vif *ndev_vif; + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (dev) { + ndev_vif = netdev_priv(dev); + sig_wait = &ndev_vif->sig_wait; + } + spin_lock_bh(&sig_wait->send_signal_lock); + if (slsi_rx_wait_ind_match(id, sig_wait->ind_id) && pid == sig_wait->process_id) { + if (WARN_ON(sig_wait->ind)) + slsi_kfree_skb(sig_wait->ind); + sig_wait->ind = skb; + spin_unlock_bh(&sig_wait->send_signal_lock); + complete(&sig_wait->completion); + rcu_read_unlock(); + return 0; + } + spin_unlock_bh(&sig_wait->send_signal_lock); + rcu_read_unlock(); + } + return -EINVAL; +} diff --git a/drivers/net/wireless/scsc/sap.h b/drivers/net/wireless/scsc/sap.h new file mode 100644 index 000000000000..3c7c1e36ff52 --- /dev/null +++ b/drivers/net/wireless/scsc/sap.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SAP_H__ +#define __SAP_H__ + +/* Number of SAPs */ +#define SAP_TOTAL 4 + +#define SAP_MLME 0 +#define SAP_MA 1 +#define SAP_DBG 2 +#define SAP_TST 3 + +/* Max number of versions supported */ +#define SAP_MAX_VER 2 + +#define SAP_MAJOR(version) ((version & 0xff00) >> 8) +#define SAP_MINOR(version) (version & 0xff) + +struct slsi_dev; +struct sk_buff; + +struct sap_api { + u8 sap_class; + u16 sap_versions[SAP_MAX_VER]; + int (*sap_version_supported)(u16 version); + int (*sap_handler)(struct slsi_dev *sdev, struct sk_buff *skb); + int (*sap_txdone)(struct slsi_dev *sdev, u16 colour); + int (*sap_notifier)(struct slsi_dev *sdev, unsigned long event); +}; +#endif diff --git a/drivers/net/wireless/scsc/sap_dbg.c b/drivers/net/wireless/scsc/sap_dbg.c new file mode 100644 index 000000000000..6ca436b33cf7 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_dbg.c @@ -0,0 +1,173 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include "debug.h" +#include "dev.h" +#include "sap.h" +#include "sap_dbg.h" +#include "hip.h" + +#define SUPPORTED_OLD_VERSION 0 + +static int sap_dbg_version_supported(u16 version); +static int sap_dbg_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb); + +static struct sap_api sap_dbg = { + .sap_class = SAP_DBG, + .sap_version_supported = sap_dbg_version_supported, + .sap_handler = sap_dbg_rx_handler, + .sap_versions = { FAPI_DEBUG_SAP_VERSION, SUPPORTED_OLD_VERSION }, +}; + +static int sap_dbg_version_supported(u16 version) +{ + unsigned int major = SAP_MAJOR(version); + unsigned int minor = SAP_MINOR(version); + u8 i = 0; + + SLSI_INFO_NODEV("Reported version: %d.%d\n", major, minor); + + for (i = 0; i < SAP_MAX_VER; i++) + if (SAP_MAJOR(sap_dbg.sap_versions[i]) == major) + return 0; + + SLSI_ERR_NODEV("Version %d.%d Not supported\n", major, minor); + + return -EINVAL; +} + +static void slsi_rx_debug(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 id = fapi_get_u16(skb, id); + + SLSI_UNUSED_PARAMETER(dev); + + switch (id) { + case DEBUG_FAULT_IND: + SLSI_WARN(sdev, "WF_FW_INFO: |cpu %s|id 0x%04X|arg 0x%08X|count %d|timestamp %10u|\n", + ((fapi_get_u16(skb, u.debug_fault_ind.cpu) == 0x8000) ? "MAC" : + (fapi_get_u16(skb, u.debug_fault_ind.cpu) == 0x4000) ? "PHY" : "???"), + fapi_get_u16(skb, u.debug_fault_ind.faultid), + fapi_get_u32(skb, u.debug_fault_ind.arg), + fapi_get_u16(skb, u.debug_fault_ind.count), + fapi_get_u32(skb, u.debug_fault_ind.timestamp)); + break; + case DEBUG_WORD12IND: + atomic_inc(&sdev->debug_inds); + SLSI_DBG4(sdev, SLSI_FW_TEST, "FW DEBUG(id:%d, subid:%d, vif:%d, time:%u) %04X %04X %04X %04X %04X %04X %04X %04X %04X %04X %04X %04X\n", + fapi_get_u16(skb, u.debug_word12_ind.module_id), + fapi_get_u16(skb, u.debug_word12_ind.module_sub_id), + fapi_get_vif(skb), + fapi_get_u32(skb, u.debug_word12_ind.timestamp), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[0]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[1]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[2]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[3]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[4]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[5]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[6]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[7]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[8]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[9]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[10]), + fapi_get_u16(skb, u.debug_word12_ind.debug_words[11])); + break; + default: + SLSI_DBG1(sdev, SLSI_MLME, "Unhandled Debug Ind: 0x%.4x\n", id); + break; + } + slsi_kfree_skb(skb); +} + +static int slsi_rx_dbg_sap(struct slsi_dev *sdev, struct sk_buff *skb) +{ + u16 id = fapi_get_u16(skb, id); + u16 vif = fapi_get_vif(skb); + struct net_device *dev; + + switch (id) { + case DEBUG_FAULT_IND: + case DEBUG_WORD12IND: + case DEBUG_GENERIC_IND: + slsi_rx_debug(sdev, NULL, skb); + break; + case DEBUG_PKT_SINK_REPORT_IND: + { + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (!dev) { + rcu_read_unlock(); + slsi_kfree_skb(skb); + break; + } + slsi_rx_sink_report(sdev, dev, skb); + rcu_read_unlock(); + break; + } + case DEBUG_PKT_GEN_REPORT_IND: + { + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (!dev) { + rcu_read_unlock(); + slsi_kfree_skb(skb); + break; + } + slsi_rx_gen_report(sdev, dev, skb); + rcu_read_unlock(); + break; + } + default: + slsi_kfree_skb(skb); + SLSI_ERR(sdev, "Unhandled Ind: 0x%.4x\n", id); + break; + } + + return 0; +} + +void slsi_rx_dbg_sap_work(struct work_struct *work) +{ + struct slsi_skb_work *w = container_of(work, struct slsi_skb_work, work); + struct slsi_dev *sdev = w->sdev; + struct sk_buff *skb = slsi_skb_work_dequeue(w); + + slsi_wakelock(&sdev->wlan_wl); + while (skb) { + slsi_debug_frame(sdev, NULL, skb, "RX"); + slsi_rx_dbg_sap(sdev, skb); + skb = slsi_skb_work_dequeue(w); + } + slsi_wakeunlock(&sdev->wlan_wl); +} + +static int sap_dbg_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb) +{ + /* DEBUG SAP has a generic confirm. Theoretically, that + * can mean upper layer code can block on the confirm. + */ + if (slsi_rx_blocking_signals(sdev, skb) == 0) + return 0; + + slsi_skb_work_enqueue(&sdev->rx_dbg_sap, skb); + return 0; +} + +int sap_dbg_init(void) +{ + SLSI_INFO_NODEV("Registering SAP\n"); + + slsi_hip_sap_register(&sap_dbg); + + return 0; +} + +int sap_dbg_deinit(void) +{ + SLSI_INFO_NODEV("Unregistering SAP\n"); + slsi_hip_sap_unregister(&sap_dbg); + return 0; +} diff --git a/drivers/net/wireless/scsc/sap_dbg.h b/drivers/net/wireless/scsc/sap_dbg.h new file mode 100644 index 000000000000..fb52f79dbd28 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_dbg.h @@ -0,0 +1,14 @@ + +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SAP_DBG_H__ +#define __SAP_DBG_H__ + +int sap_dbg_init(void); +int sap_dbg_deinit(void); + +#endif diff --git a/drivers/net/wireless/scsc/sap_ma.c b/drivers/net/wireless/scsc/sap_ma.c new file mode 100755 index 000000000000..c3fc8a3ae6e2 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_ma.c @@ -0,0 +1,752 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include "debug.h" +#include "dev.h" +#include "sap.h" +#include "sap_ma.h" +#include "hip.h" +#include "ba.h" +#include "mgt.h" +#include "nl80211_vendor.h" +#include "hip4_sampler.h" +#include "traffic_monitor.h" + +#ifdef CONFIG_ANDROID +#include "scsc_wifilogger_rings.h" +#endif + +#define SUPPORTED_OLD_VERSION 0 + +static int sap_ma_version_supported(u16 version); +static int sap_ma_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb); +static int sap_ma_txdone(struct slsi_dev *sdev, u16 colour); +static int sap_ma_notifier(struct slsi_dev *sdev, unsigned long event); + +static struct sap_api sap_ma = { + .sap_class = SAP_MA, + .sap_version_supported = sap_ma_version_supported, + .sap_handler = sap_ma_rx_handler, + .sap_versions = { FAPI_DATA_SAP_VERSION, SUPPORTED_OLD_VERSION }, + .sap_txdone = sap_ma_txdone, + .sap_notifier = sap_ma_notifier, +}; + +static int sap_ma_notifier(struct slsi_dev *sdev, unsigned long event) +{ + uint vif; + + SLSI_INFO_NODEV("Notifier event received: %lu\n", event); + if (event >= SCSC_MAX_NOTIFIER) + return -EIO; + + switch (event) { + case SCSC_WIFI_STOP: + SLSI_INFO_NODEV("Stop netdev queues\n"); + rcu_read_lock(); + for (vif = SLSI_NET_INDEX_WLAN; + vif <= SLSI_NET_INDEX_P2PX_SWLAN; vif++) { + struct net_device *ndev = + slsi_get_netdev_rcu(sdev, vif); + if (ndev && !netif_queue_stopped(ndev)) + netif_tx_stop_all_queues(ndev); + } + rcu_read_unlock(); + break; + + case SCSC_WIFI_FAILURE_RESET: + SLSI_DBG1_NODEV(SLSI_NETDEV, "Netdevs queues will not be restarted - recovery will take care of it\n"); + break; + + case SCSC_WIFI_SUSPEND: + break; + + case SCSC_WIFI_RESUME: + break; + default: + SLSI_INFO_NODEV("Unknown event code %lu\n", event); + break; + } + + return 0; +} + +static int sap_ma_version_supported(u16 version) +{ + unsigned int major = SAP_MAJOR(version); + unsigned int minor = SAP_MINOR(version); + u8 i = 0; + + SLSI_INFO_NODEV("Reported version: %d.%d\n", major, minor); + + for (i = 0; i < SAP_MAX_VER; i++) + if (SAP_MAJOR(sap_ma.sap_versions[i]) == major) + return 0; + + SLSI_ERR_NODEV("Version %d.%d Not supported\n", major, minor); + + return -EINVAL; +} + +static int slsi_rx_amsdu_deaggregate(struct net_device *dev, struct sk_buff *skb, struct sk_buff_head *msdu_list) +{ + unsigned int msdu_len; + unsigned int subframe_len; + int padding; + struct sk_buff *subframe = NULL; + bool last_sub_frame = false; + const unsigned char mac_0[ETH_ALEN] = { 0 }; + bool skip_frame = false; + struct ethhdr *mh; + + SLSI_NET_DBG4(dev, SLSI_RX, "A-MSDU received (len:%d)\n", skb->len); + + while (!last_sub_frame) { + msdu_len = (skb->data[ETH_ALEN * 2] << 8) | skb->data[(ETH_ALEN * 2) + 1]; + + /* check if the length of sub-frame is valid */ + if (msdu_len > skb->len) { + SLSI_NET_ERR(dev, "invalid MSDU length %d, SKB length = %d\n", msdu_len, skb->len); + slsi_kfree_skb(skb); + return -EINVAL; + } + + subframe_len = msdu_len + (2 * ETH_ALEN) + 2; + + /* For the last subframe skb length and subframe length will be same */ + if (skb->len == subframe_len) { + subframe = slsi_skb_copy(skb, GFP_ATOMIC); + + if (!subframe) { + SLSI_NET_ERR(dev, "failed to alloc the SKB for A-MSDU subframe\n"); + __skb_queue_purge(msdu_list); + slsi_kfree_skb(skb); + return -ENOMEM; + } + + /* There is no padding for last subframe */ + padding = 0; + last_sub_frame = true; + } else { + /* Copy the skb for the subframe */ + subframe = slsi_skb_copy(skb, GFP_ATOMIC); + + if (!subframe) { + SLSI_NET_ERR(dev, "failed to alloc the SKB for A-MSDU subframe\n"); + __skb_queue_purge(msdu_list); + slsi_kfree_skb(skb); + return -ENOMEM; + } + + padding = (4 - (subframe_len % 4)) & 0x3; + } + + /* Remove the other subframes by adjusting the tail pointer of the copied skb */ + skb_trim(subframe, subframe_len); + + /* Overwrite LLC+SNAP header with src and dest addr */ + SLSI_ETHER_COPY(&subframe->data[14], &subframe->data[6]); + SLSI_ETHER_COPY(&subframe->data[8], &subframe->data[0]); + + /* Remove 8 bytes of LLC+SNAP header */ + skb_pull(subframe, LLC_SNAP_HDR_LEN); + + SLSI_NET_DBG_HEX(dev, SLSI_RX, subframe->data, + subframe->len < 64 ? subframe->len : 64, "Subframe before giving to OS:\n"); + + /* Before preparing the skb, filter out if the Destination Address of the Ethernet frame + * or A-MSDU subframe is set to an invalid value, i.e. all zeros + */ + skb_set_mac_header(subframe, 0); + mh = eth_hdr(subframe); + if (SLSI_ETHER_EQUAL(mh->h_dest, mac_0)) { + SLSI_NET_DBG3(dev, SLSI_RX, "msdu subframe filtered out: MAC destination address %pM\n", mh->h_dest); + skip_frame = true; + } + + /* If this is not the last subframe then move to the next subframe */ + if (!last_sub_frame) + skb_pull(skb, (subframe_len + padding)); + + /* If this frame has been filtered out, free the clone and continue */ + if (skip_frame) { + skip_frame = false; + /* Free the the skbuff structure itself but not the data */ + /* skb will be freed if it is the last subframe (i.e. subframe == skb) */ + slsi_kfree_skb(subframe); + continue; + } + __skb_queue_tail(msdu_list, subframe); + } + slsi_kfree_skb(skb); + return 0; +} + +static inline bool slsi_rx_is_amsdu(struct sk_buff *skb) +{ + return (fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_AMSDU); +} + +void slsi_rx_data_deliver_skb(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, bool from_ba_timer) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff_head msdu_list; + struct slsi_peer *peer = NULL; + struct ethhdr *eth_hdr; + bool is_amsdu = slsi_rx_is_amsdu(skb); + u8 trafic_q = slsi_frame_priority_to_ac_queue(fapi_get_u16(skb, u.ma_unitdata_ind.priority)); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif + + __skb_queue_head_init(&msdu_list); + +#ifdef CONFIG_SCSC_SMAPPER + /* Check if the payload is in the SMAPPER entry */ + if (fapi_get_u16(skb, u.ma_unitdata_ind.bulk_data_descriptor) == FAPI_BULKDATADESCRIPTOR_SMAPPER) { + /* Retrieve the associated smapper skb */ + skb = slsi_hip_get_skb_from_smapper(sdev, skb); + if (!skb) { + SLSI_NET_DBG2(dev, SLSI_RX, "SKB from SMAPPER is NULL\n"); + return; + } + } else { + /* strip signal and any signal/bulk roundings/offsets */ + skb_pull(skb, fapi_get_siglen(skb)); + } +#else + skb_pull(skb, fapi_get_siglen(skb)); +#endif + + eth_hdr = (struct ethhdr *)skb->data; + peer = slsi_get_peer_from_mac(sdev, dev, eth_hdr->h_source); + if (!peer) { + SLSI_NET_WARN(dev, "Packet dropped (no peer records)\n"); + slsi_kfree_skb(skb); + return; + } + + /* A-MSDU deaggregation */ + if (is_amsdu) { + if (slsi_rx_amsdu_deaggregate(dev, skb, &msdu_list)) { + ndev_vif->stats.rx_dropped++; + if (peer) + peer->sinfo.rx_dropped_misc++; + return; + } + } else { + __skb_queue_tail(&msdu_list, skb); + } + /* WARNING: skb may be NULL here and should not be used after this */ + while (!skb_queue_empty(&msdu_list)) { + struct sk_buff *rx_skb; + + rx_skb = __skb_dequeue(&msdu_list); + + /* In STA mode, the AP relays back our multicast traffic. + * Receiving these frames and passing it up confuses some + * protocols and applications, notably IPv6 Duplicate + * Address Detection. + * + * So these frames are dropped instead of passing it further. + * No need to update the drop statistics as these frames are + * locally generated and should not be accounted in reception. + */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { + struct ethhdr *ehdr = (struct ethhdr *)(rx_skb->data); + + if (is_multicast_ether_addr(ehdr->h_dest) && + !compare_ether_addr(ehdr->h_source, dev->dev_addr)) { + SLSI_NET_DBG2(dev, SLSI_RX, "drop locally generated multicast frame relayed back by AP\n"); + slsi_kfree_skb(rx_skb); + continue; + } + } + + /* Intra BSS */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && ndev_vif->peer_sta_records) { + struct slsi_peer *peer = NULL; + struct ethhdr *ehdr = (struct ethhdr *)(rx_skb->data); + + if (is_multicast_ether_addr(ehdr->h_dest)) { +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + struct sk_buff *rebroadcast_skb = slsi_skb_copy(rx_skb, GFP_ATOMIC); +#else + struct sk_buff *rebroadcast_skb = slsi_skb_copy(rx_skb, GFP_KERNEL); +#endif + if (!rebroadcast_skb) { + SLSI_WARN(sdev, "Intra BSS: failed to alloc new SKB for broadcast\n"); + } else { + SLSI_DBG3(sdev, SLSI_RX, "Intra BSS: multicast %pM\n", ehdr->h_dest); + rebroadcast_skb->dev = dev; + rebroadcast_skb->protocol = cpu_to_be16(ETH_P_802_3); + slsi_dbg_untrack_skb(rebroadcast_skb); + skb_reset_network_header(rebroadcast_skb); + skb_reset_mac_header(rebroadcast_skb); + dev_queue_xmit(rebroadcast_skb); + } + } else { + peer = slsi_get_peer_from_mac(sdev, dev, ehdr->h_dest); + if (peer && peer->authorized) { + SLSI_DBG3(sdev, SLSI_RX, "Intra BSS: unicast %pM\n", ehdr->h_dest); + rx_skb->dev = dev; + rx_skb->protocol = cpu_to_be16(ETH_P_802_3); + slsi_dbg_untrack_skb(rx_skb); + skb_reset_network_header(rx_skb); + skb_reset_mac_header(rx_skb); + dev_queue_xmit(rx_skb); + continue; + } + } + } + + if (peer) { + peer->sinfo.rx_bytes += rx_skb->len; + } + ndev_vif->stats.rx_packets++; + ndev_vif->stats.rx_bytes += rx_skb->len; + ndev_vif->rx_packets[trafic_q]++; + +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (!ndev_vif->enhanced_arp_stats.is_duplicate_addr_detected) { + u8 *frame = rx_skb->data + 12; /* frame points to packet type */ + u16 packet_type = frame[0] << 8 | frame[1]; + + if (packet_type == ETH_P_ARP) { + frame = frame + 2; /* ARP packet */ + /*match source IP address in ARP with the DUT Ip address*/ + if ((frame[SLSI_ARP_SRC_IP_ADDR_OFFSET] == (ndev_vif->ipaddress & 255)) && + (frame[SLSI_ARP_SRC_IP_ADDR_OFFSET + 1] == ((ndev_vif->ipaddress >> 8U) & 255)) && + (frame[SLSI_ARP_SRC_IP_ADDR_OFFSET + 2] == ((ndev_vif->ipaddress >> 16U) & 255)) && + (frame[SLSI_ARP_SRC_IP_ADDR_OFFSET + 3] == ((ndev_vif->ipaddress >> 24U) & 255)) && + !SLSI_IS_GRATUITOUS_ARP(frame) && + !SLSI_ETHER_EQUAL(sdev->hw_addr, frame + 8)) /*if src MAC = DUT MAC */ + ndev_vif->enhanced_arp_stats.is_duplicate_addr_detected = 1; + } + } + + if (ndev_vif->enhanced_arp_detect_enabled && (ndev_vif->vif_type == FAPI_VIFTYPE_STATION)) { + u8 *frame = rx_skb->data + 12; /* frame points to packet type */ + u16 packet_type = frame[0] << 8 | frame[1]; + u16 arp_opcode; + + if (packet_type == ETH_P_ARP) { + frame = frame + 2; /* ARP packet */ + arp_opcode = frame[SLSI_ARP_OPCODE_OFFSET] << 8 | frame[SLSI_ARP_OPCODE_OFFSET + 1]; + /* check if sender ip = gateway ip and it is an ARP response */ + if ((arp_opcode == SLSI_ARP_REPLY_OPCODE) && + !SLSI_IS_GRATUITOUS_ARP(frame) && + !memcmp(&frame[SLSI_ARP_SRC_IP_ADDR_OFFSET], &ndev_vif->target_ip_addr, 4)) { + ndev_vif->enhanced_arp_stats.arp_rsp_count_to_netdev++; + ndev_vif->enhanced_arp_stats.arp_rsp_rx_count_by_upper_mac++; + } + } + } +#endif + + rx_skb->dev = dev; + rx_skb->ip_summed = CHECKSUM_NONE; + rx_skb->protocol = eth_type_trans(rx_skb, dev); + + SCSC_HIP4_SAMPLER_TCP_DECODE(sdev, dev, rx_skb->data, true); + slsi_traffic_mon_event_rx(sdev, dev, rx_skb); + slsi_dbg_untrack_skb(rx_skb); + + SLSI_DBG4(sdev, SLSI_RX, "pass %u bytes to local stack\n", rx_skb->len); +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + if (conf_hip4_ver == 4) { +#ifdef CONFIG_SCSC_WLAN_RX_NAPI_GRO + if (!from_ba_timer) + napi_gro_receive(&sdev->hip4_inst.hip_priv->napi, rx_skb); + else + netif_receive_skb(rx_skb); +#else + netif_receive_skb(rx_skb); +#endif + } else { + netif_rx_ni(rx_skb); + } +#else + netif_rx_ni(rx_skb); +#endif + slsi_wakelock_timeout(&sdev->wlan_wl_ma, SLSI_RX_WAKELOCK_TIME); + } +} + +static void slsi_rx_data_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = NULL; + struct ethhdr *eth_hdr; + u16 seq_num; + + if (!((fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME) || + (fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME) || + (fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_AMSDU))) { + WARN_ON(1); + slsi_kfree_skb(skb); + return; + } + +#ifdef CONFIG_SCSC_WLAN_DEBUG + /* pass the data up "As is" if the VIF type is Monitor */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_MONITOR) { +#ifdef CONFIG_SCSC_SMAPPER + /* Check if the payload is in the SMAPPER entry */ + if (fapi_get_u16(skb, u.ma_unitdata_ind.bulk_data_descriptor) == FAPI_BULKDATADESCRIPTOR_SMAPPER) { + /* Retrieve the associated smapper skb */ + skb = slsi_hip_get_skb_from_smapper(sdev, skb); + if (!skb) { + SLSI_NET_DBG2(dev, SLSI_RX, "SKB from SMAPPER is NULL\n"); + return; + } + } else { + /* strip signal and any signal/bulk roundings/offsets */ + skb_pull(skb, fapi_get_siglen(skb)); + } +#else + skb_pull(skb, fapi_get_siglen(skb)); +#endif + skb_reset_mac_header(skb); + skb->dev = dev; + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->pkt_type = PACKET_OTHERHOST; + netif_rx_ni(skb); + return; + } +#endif + +#ifdef CONFIG_SCSC_SMAPPER + /* Check if the payload is in the SMAPPER entry */ + if (fapi_get_u16(skb, u.ma_unitdata_ind.bulk_data_descriptor) == FAPI_BULKDATADESCRIPTOR_SMAPPER) { + eth_hdr = (struct ethhdr *)slsi_hip_get_skb_data_from_smapper(sdev, skb); + if (!(eth_hdr)) { + SLSI_NET_DBG2(dev, SLSI_RX, "SKB from SMAPPER is NULL\n"); + slsi_kfree_skb(skb); + return; + } + } else { + eth_hdr = (struct ethhdr *)fapi_get_data(skb); + } +#else + eth_hdr = (struct ethhdr *)fapi_get_data(skb); +#endif + seq_num = fapi_get_u16(skb, u.ma_unitdata_ind.sequence_number); + SLSI_NET_DBG4(dev, SLSI_RX, "ma_unitdata_ind(vif:%d, dest:%pM, src:%pM, datatype:%d, priority:%d, s:%d)\n", + fapi_get_vif(skb), + eth_hdr->h_dest, + eth_hdr->h_source, + fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor), + fapi_get_u16(skb, u.ma_unitdata_ind.priority), + (seq_num & SLSI_RX_SEQ_NUM_MASK)); + + peer = slsi_get_peer_from_mac(sdev, dev, eth_hdr->h_source); + if (!peer) { + SLSI_NET_WARN(dev, "Packet dropped (no peer records)\n"); + /* Race in Data plane (Shows up in fw test mode) */ + slsi_kfree_skb(skb); + return; + } + + /* discard data frames if received before key negotiations are completed */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && peer->connected_state != SLSI_STA_CONN_STATE_CONNECTED) { + SLSI_NET_WARN(dev, "Packet dropped (peer connection not complete (state:%u))\n", peer->connected_state); + slsi_kfree_skb(skb); + return; + } + + /* When TDLS connection has just been closed a few last frame may still arrive from the closed connection. + * This frames must not be injected in to the block session with the AP as the sequence numbers are different + * that will confuse the BA process. Therefore we have to skip BA for those frames. + */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION && peer->aid < SLSI_TDLS_PEER_INDEX_MIN && (seq_num & SLSI_RX_VIA_TDLS_LINK)) { + if (printk_ratelimit()) + SLSI_NET_WARN(dev, "Packet received from TDLS but no TDLS exists (seq: %x) Skip BA\n", seq_num); + + /* Skip BA reorder and pass the frames Up */ + slsi_rx_data_deliver_skb(sdev, dev, skb, false); + return; + } + + /* TDLS is enabled for the PEER but still packet is received through the AP. Process this packet with the AP PEER */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION && peer->aid >= SLSI_TDLS_PEER_INDEX_MIN && (!(seq_num & SLSI_RX_VIA_TDLS_LINK))) { + SLSI_NET_DBG2(dev, SLSI_TDLS, "Packet received from TDLS peer through the AP(seq: %x)\n", seq_num); + peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + if (!peer) { + SLSI_NET_WARN(dev, "Packet dropped (AP peer not found)\n"); + slsi_kfree_skb(skb); + return; + } + } + + /* If frame belongs to a negotiated BA, BA will consume the frame */ + if (slsi_ba_check(peer, fapi_get_u16(skb, u.ma_unitdata_ind.priority))) + if (!slsi_ba_process_frame(dev, peer, skb, (seq_num & SLSI_RX_SEQ_NUM_MASK), + fapi_get_u16(skb, u.ma_unitdata_ind.priority))) + return; + + /* Pass to next receive process */ + slsi_rx_data_deliver_skb(sdev, dev, skb, false); +} + +static int slsi_rx_data_cfm(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + u16 host_tag = fapi_get_u16(skb, u.ma_unitdata_cfm.host_tag); + + SLSI_NET_DBG1(dev, SLSI_TX, "ma_unitdata_cfm(vif:%d, host_tag:0x%x, status:%d)\n", + fapi_get_vif(skb), + host_tag, + fapi_get_u16(skb, u.ma_unitdata_cfm.transmission_status)); +#ifdef CONFIG_SCSC_WLAN_DEBUG + if (fapi_get_u16(skb, u.ma_unitdata_cfm.transmission_status) == FAPI_TRANSMISSIONSTATUS_TX_LIFETIME) { + if (printk_ratelimit()) + SLSI_NET_WARN(dev, "ma_unitdata_cfm: tx_lifetime(vif:%d, host_tag:0x%x)\n", fapi_get_vif(skb), host_tag); + } +#endif + if (fapi_get_u16(skb, u.ma_unitdata_cfm.transmission_status) == FAPI_TRANSMISSIONSTATUS_RETRY_LIMIT) + ndev_vif->tx_no_ack[SLSI_HOST_TAG_TRAFFIC_QUEUE(host_tag)]++; + + slsi_kfree_skb(skb); + return 0; +} + +#ifdef CONFIG_SCSC_WLAN_RX_NAPI +static int slsi_rx_napi_process(struct slsi_dev *sdev, struct sk_buff *skb) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + u16 vif; + + vif = fapi_get_vif(skb); + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (!dev) { + SLSI_ERR(sdev, "netdev(%d) No longer exists\n", vif); + rcu_read_unlock(); + return -EINVAL; + } + rcu_read_unlock(); + + ndev_vif = netdev_priv(dev); + + switch (fapi_get_u16(skb, id)) { + case MA_UNITDATA_IND: + slsi_rx_data_ind(sdev, dev, skb); + + /* SKBs in a BA session are not passed yet */ + if (atomic_read(&ndev_vif->ba_flush)) { + atomic_set(&ndev_vif->ba_flush, 0); + slsi_ba_process_complete(dev, false); + } + break; + case MA_UNITDATA_CFM: + (void)slsi_rx_data_cfm(sdev, dev, skb); + break; + default: + SLSI_DBG1(sdev, SLSI_RX, "Unexpected Data: 0x%.4x\n", fapi_get_sigid(skb)); + slsi_kfree_skb(skb); + break; + } + return 0; +} +#endif +void slsi_rx_netdev_data_work(struct work_struct *work) +{ + struct slsi_skb_work *w = container_of(work, struct slsi_skb_work, work); + struct slsi_dev *sdev = w->sdev; + struct net_device *dev = w->dev; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct sk_buff *skb; + + if (WARN_ON(!dev)) + return; + + slsi_wakelock(&sdev->wlan_wl); + + while (1) { + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + if (!ndev_vif->activated) { + slsi_skb_queue_purge(&w->queue); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + break; + } + + if (atomic_read(&ndev_vif->ba_flush)) { + atomic_set(&ndev_vif->ba_flush, 0); + slsi_ba_process_complete(dev, false); + } + + skb = slsi_skb_work_dequeue(w); + if (!skb) { + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + break; + } + + switch (fapi_get_u16(skb, id)) { + case MA_UNITDATA_IND: +#ifdef CONFIG_SCSC_SMAPPER + if (fapi_get_u16(skb, u.ma_unitdata_ind.bulk_data_descriptor) == FAPI_BULKDATADESCRIPTOR_SMAPPER) { + u8 *frame = (u8 *)slsi_hip_get_skb_data_from_smapper(sdev, skb); + + if (frame) + SCSC_HIP4_SAMPLER_TCP_DECODE(sdev, dev, frame, false); + } else { + SCSC_HIP4_SAMPLER_TCP_DECODE(sdev, dev, skb->data + fapi_get_siglen(skb), false); + } +#else + SCSC_HIP4_SAMPLER_TCP_DECODE(sdev, dev, skb->data + fapi_get_siglen(skb), false); +#endif + slsi_rx_data_ind(sdev, dev, skb); + break; + case MA_UNITDATA_CFM: + (void)slsi_rx_data_cfm(sdev, dev, skb); + break; + default: + SLSI_DBG1(sdev, SLSI_RX, "Unexpected Data: 0x%.4x\n", fapi_get_sigid(skb)); + slsi_kfree_skb(skb); + break; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } + slsi_wakeunlock(&sdev->wlan_wl); +} + +static int slsi_rx_queue_data(struct slsi_dev *sdev, struct sk_buff *skb) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + int vif; + + vif = fapi_get_vif(skb); + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (!dev) { + SLSI_ERR(sdev, "netdev(%d) No longer exists\n", vif); + rcu_read_unlock(); + goto err; + } + ndev_vif = netdev_priv(dev); + + slsi_skb_work_enqueue(&ndev_vif->rx_data, skb); + rcu_read_unlock(); + return 0; +err: + return -EINVAL; +} + +static int sap_ma_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb) +{ +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + u32 conf_hip4_ver = 0; +#endif +#ifdef CONFIG_SCSC_SMAPPER + u16 sig_len; + u32 err; +#endif + + switch (fapi_get_sigid(skb)) { + case MA_UNITDATA_IND: +#ifdef CONFIG_SCSC_SMAPPER + /* Check SMAPPER to nullify entry*/ + if (fapi_get_u16(skb, u.ma_unitdata_ind.bulk_data_descriptor) == FAPI_BULKDATADESCRIPTOR_SMAPPER) { + sig_len = fapi_get_siglen(skb); + skb_pull(skb, sig_len); + err = slsi_hip_consume_smapper_entry(sdev, skb); + skb_push(skb, sig_len); + if (err) + return err; + } +#endif + case MA_UNITDATA_CFM: +#ifdef CONFIG_SCSC_WLAN_RX_NAPI + conf_hip4_ver = scsc_wifi_get_hip_config_version(&sdev->hip4_inst.hip_control->init); + if (conf_hip4_ver == 4) + return slsi_rx_napi_process(sdev, skb); + else + return slsi_rx_queue_data(sdev, skb); +#else + return slsi_rx_queue_data(sdev, skb); +#endif + case MA_BLOCKACK_IND: + /* It is anomolous to handle the MA_BLOCKACK_IND in the + * mlme wq. + */ + return slsi_rx_enqueue_netdev_mlme(sdev, skb, fapi_get_vif(skb)); + default: + break; + } + + SLSI_ERR_NODEV("Shouldn't be getting here!\n"); + return -EINVAL; +} + +/* Adjust the scod value and flow control appropriately. */ +static int sap_ma_txdone(struct slsi_dev *sdev, u16 colour) +{ + struct net_device *dev; + struct slsi_peer *peer; + u16 vif, peer_index, ac; + + /* Extract information from the coloured mbulk */ + /* colour is defined as: */ + /* u16 register bits: + * 0 - do not use + * [2:1] - vif + * [7:3] - peer_index + * [10:8] - ac queue + */ + vif = (colour & 0x6) >> 1; + peer_index = (colour & 0xf8) >> 3; + ac = (colour & 0x300) >> 8; + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + + if (!dev) { + SLSI_ERR(sdev, "netdev(%d) No longer exists\n", vif); + rcu_read_unlock(); + return -EINVAL; + } + rcu_read_unlock(); + + if (peer_index <= SLSI_PEER_INDEX_MAX) { + /* peer_index = 0 for Multicast queues */ + if (peer_index == 0) { + struct netdev_vif *ndev_vif = netdev_priv(dev); + + return scsc_wifi_fcq_receive_data(dev, &ndev_vif->ap.group_data_qs, ac, sdev, vif, peer_index); + } + peer = slsi_get_peer_from_qs(sdev, dev, MAP_AID_TO_QS(peer_index)); + if (peer) + return scsc_wifi_fcq_receive_data(dev, &peer->data_qs, ac, sdev, vif, peer_index); + + SLSI_DBG3(sdev, SLSI_RX, "peer record NOT found for peer_index=%d\n", peer_index); + /* We need to handle this case as special. Peer disappeared bug hip4 + * is sending back the colours to free. + */ + return scsc_wifi_fcq_receive_data_no_peer(dev, ac, sdev, vif, peer_index); + } + SLSI_ERR(sdev, "illegal peer_index vif=%d peer_index=%d\n", vif, peer_index); + return -EINVAL; +} + +int sap_ma_init(void) +{ + SLSI_INFO_NODEV("Registering SAP\n"); + slsi_hip_sap_register(&sap_ma); + return 0; +} + +int sap_ma_deinit(void) +{ + SLSI_INFO_NODEV("Unregistering SAP\n"); + slsi_hip_sap_unregister(&sap_ma); + return 0; +} diff --git a/drivers/net/wireless/scsc/sap_ma.h b/drivers/net/wireless/scsc/sap_ma.h new file mode 100644 index 000000000000..2cb5bf97ada1 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_ma.h @@ -0,0 +1,14 @@ + +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SAP_MA_H__ +#define __SAP_MA_H__ + +int sap_ma_init(void); +int sap_ma_deinit(void); + +#endif diff --git a/drivers/net/wireless/scsc/sap_mlme.c b/drivers/net/wireless/scsc/sap_mlme.c new file mode 100755 index 000000000000..942c6a7ac940 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_mlme.c @@ -0,0 +1,434 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include "debug.h" +#include "dev.h" +#include "sap.h" +#include "sap_mlme.h" +#include "hip.h" +#include "mgt.h" + +#ifdef CONFIG_ANDROID +#include "scsc_wifilogger_rings.h" +#endif +#include "nl80211_vendor.h" +#include "mlme.h" + +#define SUPPORTED_OLD_VERSION 0 + +static int sap_mlme_version_supported(u16 version); +static int sap_mlme_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb); + +static int sap_mlme_notifier(struct slsi_dev *sdev, unsigned long event); + +static struct sap_api sap_mlme = { + .sap_class = SAP_MLME, + .sap_version_supported = sap_mlme_version_supported, + .sap_handler = sap_mlme_rx_handler, + .sap_versions = { FAPI_CONTROL_SAP_VERSION, SUPPORTED_OLD_VERSION }, + .sap_notifier = sap_mlme_notifier, +}; + +static int sap_mlme_notifier(struct slsi_dev *sdev, unsigned long event) +{ + int i; +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + struct net_device *dev; +#endif + struct netdev_vif *ndev_vif; + + SLSI_INFO_NODEV("Notifier event received: %lu\n", event); + if (event >= SCSC_MAX_NOTIFIER) + return -EIO; + + switch (event) { + case SCSC_WIFI_STOP: + /* Stop sending signals down*/ + sdev->mlme_blocked = true; + SLSI_INFO_NODEV("MLME BLOCKED\n"); + + /* cleanup all the VIFs and scan data */ + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + complete_all(&sdev->sig_wait.completion); + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) + if (sdev->netdev[i]) { + ndev_vif = netdev_priv(sdev->netdev[i]); + slsi_scan_cleanup(sdev, sdev->netdev[i]); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + slsi_vif_cleanup(sdev, sdev->netdev[i], 0); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + SLSI_INFO_NODEV("Force cleaned all VIFs\n"); + break; + + case SCSC_WIFI_FAILURE_RESET: + break; + + case SCSC_WIFI_SUSPEND: + break; + + case SCSC_WIFI_RESUME: +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + dev = slsi_get_netdev(sdev, SLSI_NET_INDEX_WLAN); + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + if ((ndev_vif->is_wips_running) && (ndev_vif->activated) && + (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && + (ndev_vif->sta.vif_status == SLSI_VIF_STATUS_CONNECTED)) { + ndev_vif->is_wips_running = false; + + slsi_send_forward_beacon_abort_vendor_event(sdev, SLSI_FORWARD_BEACON_ABORT_REASON_SUSPENDED); + SLSI_INFO_NODEV("FORWARD_BEACON: SUSPEND_RESUMED!! send abort event\n"); + } + + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +#endif + break; + + default: + SLSI_INFO_NODEV("Unknown event code %lu\n", event); + break; + } + + return 0; +} + +static int sap_mlme_version_supported(u16 version) +{ + unsigned int major = SAP_MAJOR(version); + unsigned int minor = SAP_MINOR(version); + u8 i = 0; + + SLSI_INFO_NODEV("Reported version: %d.%d\n", major, minor); + + for (i = 0; i < SAP_MAX_VER; i++) + if (SAP_MAJOR(sap_mlme.sap_versions[i]) == major) + return 0; + + SLSI_ERR_NODEV("Version %d.%d Not supported\n", major, minor); + + return -EINVAL; +} + +static int slsi_rx_netdev_mlme(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + u16 id = fapi_get_u16(skb, id); + + /* The skb is consumed by the functions called. + */ + switch (id) { + case MLME_SCAN_IND: + slsi_rx_scan_ind(sdev, dev, skb); + break; + case MLME_SCAN_DONE_IND: + slsi_rx_scan_done_ind(sdev, dev, skb); + break; + case MLME_CONNECT_IND: + slsi_rx_connect_ind(sdev, dev, skb); + break; + case MLME_CONNECTED_IND: + slsi_rx_connected_ind(sdev, dev, skb); + break; + case MLME_RECEIVED_FRAME_IND: + slsi_rx_received_frame_ind(sdev, dev, skb); + break; + case MLME_DISCONNECT_IND: + slsi_rx_disconnect_ind(sdev, dev, skb); + break; + case MLME_DISCONNECTED_IND: + slsi_rx_disconnected_ind(sdev, dev, skb); + break; + case MLME_PROCEDURE_STARTED_IND: + slsi_rx_procedure_started_ind(sdev, dev, skb); + break; + case MLME_FRAME_TRANSMISSION_IND: + slsi_rx_frame_transmission_ind(sdev, dev, skb); + break; + case MA_BLOCKACK_IND: + slsi_rx_blockack_ind(sdev, dev, skb); + break; + case MLME_ROAMED_IND: + slsi_rx_roamed_ind(sdev, dev, skb); + break; + case MLME_ROAM_IND: + slsi_rx_roam_ind(sdev, dev, skb); + break; + case MLME_MIC_FAILURE_IND: + slsi_rx_mic_failure_ind(sdev, dev, skb); + break; + case MLME_REASSOCIATE_IND: + slsi_rx_reassoc_ind(sdev, dev, skb); + break; + case MLME_TDLS_PEER_IND: + slsi_tdls_peer_ind(sdev, dev, skb); + break; + case MLME_LISTEN_END_IND: + slsi_rx_listen_end_ind(dev, skb); + break; + case MLME_CHANNEL_SWITCHED_IND: + slsi_rx_channel_switched_ind(sdev, dev, skb); + break; + case MLME_AC_PRIORITY_UPDATE_IND: + SLSI_DBG1(sdev, SLSI_MLME, "Unexpected MLME_AC_PRIORITY_UPDATE_IND\n"); + slsi_kfree_skb(skb); + break; +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + case MLME_RSSI_REPORT_IND: + slsi_rx_rssi_report_ind(sdev, dev, skb); + break; + case MLME_RANGE_IND: + slsi_rx_range_ind(sdev, dev, skb); + break; + case MLME_RANGE_DONE_IND: + slsi_rx_range_done_ind(sdev, dev, skb); + break; + case MLME_EVENT_LOG_IND: + slsi_rx_event_log_indication(sdev, dev, skb); + break; +#endif +#ifdef CONFIG_SCSC_WIFI_NAN_ENABLE + case MLME_NAN_EVENT_IND: + slsi_nan_event(sdev, dev, skb); + slsi_kfree_skb(skb); + break; + case MLME_NAN_FOLLOWUP_IND: + slsi_nan_followup_ind(sdev, dev, skb); + slsi_kfree_skb(skb); + break; + case MLME_NAN_SERVICE_IND: + slsi_nan_service_ind(sdev, dev, skb); + slsi_kfree_skb(skb); + break; +#endif +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG + case MLME_SYNCHRONISED_IND: + slsi_rx_synchronised_ind(sdev, dev, skb); + slsi_kfree_skb(skb); + break; +#endif +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON + case MLME_BEACON_REPORTING_EVENT_IND: + slsi_rx_beacon_reporting_event_ind(sdev, dev, skb); + break; +#endif + default: + slsi_kfree_skb(skb); + SLSI_NET_ERR(dev, "Unhandled Ind: 0x%.4x\n", id); + break; + } + return 0; +} + +void slsi_rx_netdev_mlme_work(struct work_struct *work) +{ + struct slsi_skb_work *w = container_of(work, struct slsi_skb_work, work); + struct slsi_dev *sdev = w->sdev; + struct net_device *dev = w->dev; + struct sk_buff *skb = slsi_skb_work_dequeue(w); + + if (WARN_ON(!dev)) + return; + + slsi_wakelock(&sdev->wlan_wl); + while (skb) { + slsi_debug_frame(sdev, dev, skb, "RX"); + slsi_rx_netdev_mlme(sdev, dev, skb); + skb = slsi_skb_work_dequeue(w); + } + slsi_wakeunlock(&sdev->wlan_wl); +} + +int slsi_rx_enqueue_netdev_mlme(struct slsi_dev *sdev, struct sk_buff *skb, u16 vif) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (WARN_ON(!dev)) { + rcu_read_unlock(); + /* Calling function should free the skb */ + return -ENODEV; + } + + ndev_vif = netdev_priv(dev); + + if (unlikely(ndev_vif->is_fw_test)) { + slsi_kfree_skb(skb); + rcu_read_unlock(); + return 0; + } + + slsi_skb_work_enqueue(&ndev_vif->rx_mlme, skb); + rcu_read_unlock(); + return 0; +} + +static int slsi_rx_action_enqueue_netdev_mlme(struct slsi_dev *sdev, struct sk_buff *skb, u16 vif) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (WARN_ON(!dev)) { + rcu_read_unlock(); + return -ENODEV; + } + + ndev_vif = netdev_priv(dev); + + if (unlikely(ndev_vif->is_fw_test)) { + rcu_read_unlock(); + return 0; + } + + if (ndev_vif->iftype == NL80211_IFTYPE_P2P_GO || ndev_vif->iftype == NL80211_IFTYPE_P2P_CLIENT) { + struct ieee80211_mgmt *mgmt = fapi_get_mgmt(skb); + /* Check the DA of received action frame with the GO interface address */ + if (memcmp(mgmt->da, dev->dev_addr, ETH_ALEN) != 0) { + /* If not equal, compare DA of received action frame with the P2P DEV address*/ + struct net_device *p2pdev = slsi_get_netdev_rcu(sdev, SLSI_NET_INDEX_P2P); + + if (WARN_ON(!p2pdev)) { + rcu_read_unlock(); + return -ENODEV; + } + if (memcmp(mgmt->da, p2pdev->dev_addr, ETH_ALEN) == 0) { + /* If destination address is equal to P2P DEV ADDR, then action frame is received on + * GO interface. Hence indicate action frames on P2P DEV + */ + ndev_vif = netdev_priv(p2pdev); + + if (unlikely(ndev_vif->is_fw_test)) { + slsi_kfree_skb(skb); + rcu_read_unlock(); + return 0; + } + } + } + } + + slsi_skb_work_enqueue(&ndev_vif->rx_mlme, skb); + + rcu_read_unlock(); + return 0; +} + +static int sap_mlme_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb) +{ + u16 scan_id; + u16 vif = fapi_get_vif(skb); + + if (slsi_rx_blocking_signals(sdev, skb) == 0) + return 0; + + if (fapi_is_ind(skb)) { +#ifdef CONFIG_SCSC_WIFILOGGER + SCSC_WLOG_PKTFATE_LOG_RX_CTRL_FRAME(fapi_get_data(skb), fapi_get_datalen(skb)); +#endif + + switch (fapi_get_sigid(skb)) { + case MLME_SCAN_DONE_IND: + scan_id = fapi_get_u16(skb, u.mlme_scan_done_ind.scan_id); +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + if (slsi_is_gscan_id(scan_id)) + return slsi_rx_enqueue_netdev_mlme(sdev, skb, SLSI_NET_INDEX_WLAN); +#endif + return slsi_rx_enqueue_netdev_mlme(sdev, skb, (scan_id >> 8)); + case MLME_SCAN_IND: + if (vif) + return slsi_rx_enqueue_netdev_mlme(sdev, skb, vif); + scan_id = fapi_get_u16(skb, u.mlme_scan_ind.scan_id); +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + if (slsi_is_gscan_id(scan_id)) + return slsi_rx_enqueue_netdev_mlme(sdev, skb, SLSI_NET_INDEX_WLAN); +#endif + return slsi_rx_enqueue_netdev_mlme(sdev, skb, (scan_id >> 8)); + case MLME_RECEIVED_FRAME_IND: + if (vif == 0) { + SLSI_WARN(sdev, "Received MLME_RECEIVED_FRAME_IND on VIF 0\n"); + goto err; + } + return slsi_rx_action_enqueue_netdev_mlme(sdev, skb, vif); +#ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE + case MLME_NAN_EVENT_IND: + case MLME_NAN_FOLLOWUP_IND: + case MLME_NAN_SERVICE_IND: + return slsi_rx_enqueue_netdev_mlme(sdev, skb, vif); + case MLME_RANGE_IND: + case MLME_RANGE_DONE_IND: + if (vif == 0) + return slsi_rx_enqueue_netdev_mlme(sdev, skb, SLSI_NET_INDEX_WLAN); + else + return slsi_rx_enqueue_netdev_mlme(sdev, skb, vif); +#endif +#ifdef CONFIG_SCSC_WLAN_ENHANCED_LOGGING + case MLME_EVENT_LOG_IND: + return slsi_rx_enqueue_netdev_mlme(sdev, skb, SLSI_NET_INDEX_WLAN); +#endif + case MLME_ROAMED_IND: + if (vif == 0) { + SLSI_WARN(sdev, "Received MLME_ROAMED_IND on VIF 0, return error\n"); + goto err; + } else { + struct net_device *dev; + struct netdev_vif *ndev_vif; + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (WARN_ON(!dev)) { + rcu_read_unlock(); + return -ENODEV; + } + ndev_vif = netdev_priv(dev); + if (atomic_read(&ndev_vif->sta.drop_roamed_ind)) { + /* If roam cfm is not received for the req, ignore this roamed indication. */ + slsi_kfree_skb(skb); + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + return slsi_rx_enqueue_netdev_mlme(sdev, skb, vif); + } + default: + if (vif == 0) { + SLSI_WARN(sdev, "Received signal 0x%04x on VIF 0, return error\n", fapi_get_sigid(skb)); + goto err; + } else { + return slsi_rx_enqueue_netdev_mlme(sdev, skb, vif); + } + } + } + if (WARN_ON(fapi_is_req(skb))) + goto err; + + if (slsi_is_test_mode_enabled()) { + slsi_kfree_skb(skb); + return 0; + } + + WARN_ON(1); + +err: + return -EINVAL; +} + +int sap_mlme_init(void) +{ + SLSI_INFO_NODEV("Registering SAP\n"); + slsi_hip_sap_register(&sap_mlme); + return 0; +} + +int sap_mlme_deinit(void) +{ + SLSI_INFO_NODEV("Unregistering SAP\n"); + slsi_hip_sap_unregister(&sap_mlme); + return 0; +} diff --git a/drivers/net/wireless/scsc/sap_mlme.h b/drivers/net/wireless/scsc/sap_mlme.h new file mode 100755 index 000000000000..e2c95fb0e0c2 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_mlme.h @@ -0,0 +1,39 @@ + +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SAP_MLME_H__ +#define __SAP_MLME_H__ + +int sap_mlme_init(void); +int sap_mlme_deinit(void); + +/* MLME signal handlers in rx.c */ +void slsi_rx_scan_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +#ifdef CONFIG_SLSI_WLAN_STA_FWD_BEACON +void slsi_rx_beacon_reporting_event_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +#endif +void slsi_rx_scan_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_channel_switched_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +#ifdef CONFIG_SCSC_WLAN_SAE_CONFIG +void slsi_rx_synchronised_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +#endif +void slsi_rx_connect_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_connected_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_received_frame_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_disconnect_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_disconnected_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_procedure_started_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_frame_transmission_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_roamed_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_roam_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_mic_failure_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_reassoc_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_tdls_peer_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_listen_end_ind(struct net_device *dev, struct sk_buff *skb); +void slsi_rx_blockack_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); + +#endif diff --git a/drivers/net/wireless/scsc/sap_test.c b/drivers/net/wireless/scsc/sap_test.c new file mode 100644 index 000000000000..403368337c90 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_test.c @@ -0,0 +1,67 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include "dev.h" +#include "sap.h" +#include "sap_test.h" +#include "hip.h" + +#include "debug.h" + +#define SUPPORTED_OLD_VERSION 0 + +static int sap_test_version_supported(u16 version); +static int sap_test_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb); + +static struct sap_api sap_test = { + .sap_class = SAP_TST, + .sap_version_supported = sap_test_version_supported, + .sap_handler = sap_test_rx_handler, + .sap_versions = { FAPI_TEST_SAP_VERSION, SUPPORTED_OLD_VERSION }, +}; + +static int sap_test_version_supported(u16 version) +{ + unsigned int major = SAP_MAJOR(version); + unsigned int minor = SAP_MINOR(version); + u8 i = 0; + + SLSI_INFO_NODEV("Reported version: %d.%d\n", major, minor); + + for (i = 0; i < SAP_MAX_VER; i++) + if (SAP_MAJOR(sap_test.sap_versions[i]) == major) + return 0; + + SLSI_ERR_NODEV("Version %d.%d Not supported\n", major, minor); + + return -EINVAL; +} + +static int sap_test_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb) +{ + if (slsi_rx_blocking_signals(sdev, skb) == 0) + return 0; + + SLSI_INFO_NODEV("TEST SAP not implemented\n"); + /* Silently consume the skb */ + slsi_kfree_skb(skb); + /* return success */ + return 0; +} + +int sap_test_init(void) +{ + SLSI_INFO_NODEV("Registering SAP\n"); + slsi_hip_sap_register(&sap_test); + return 0; +} + +int sap_test_deinit(void) +{ + SLSI_INFO_NODEV("Unregistering SAP\n"); + slsi_hip_sap_unregister(&sap_test); + return 0; +} diff --git a/drivers/net/wireless/scsc/sap_test.h b/drivers/net/wireless/scsc/sap_test.h new file mode 100644 index 000000000000..0b4c5dae8544 --- /dev/null +++ b/drivers/net/wireless/scsc/sap_test.h @@ -0,0 +1,14 @@ + +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SAP_TEST_H__ +#define __SAP_TEST_H__ + +int sap_test_init(void); +int sap_test_deinit(void); + +#endif diff --git a/drivers/net/wireless/scsc/scsc_wifi_cm_if.h b/drivers/net/wireless/scsc/scsc_wifi_cm_if.h new file mode 100755 index 000000000000..a1ee9a146f73 --- /dev/null +++ b/drivers/net/wireless/scsc/scsc_wifi_cm_if.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SCSC_WIFI_CM_IF_H__ +#define __SCSC_WIFI_CM_IF_H__ + +#include +#include +#include + +#include + +struct slsi_dev; + +/** + * CM interface States: + * STOPPED : (default) state, + * PROBING : + * PROBED : + * STARTING : + * STARTED : + * STOPPING : + * REMOVING : + * REMOVED : + * BLOCKED : + */ +enum scsc_wifi_cm_if_state { + SCSC_WIFI_CM_IF_STATE_STOPPED, + SCSC_WIFI_CM_IF_STATE_PROBING, + SCSC_WIFI_CM_IF_STATE_PROBED, + SCSC_WIFI_CM_IF_STATE_STARTING, + SCSC_WIFI_CM_IF_STATE_STARTED, + SCSC_WIFI_CM_IF_STATE_STOPPING, + SCSC_WIFI_CM_IF_STATE_REMOVING, + SCSC_WIFI_CM_IF_STATE_REMOVED, + SCSC_WIFI_CM_IF_STATE_BLOCKED +}; + +/** + * Notification Events + * SCSC_WIFI_STOP : Wifi service should freeze + * SCSC_WIFI_FAILURE_RESET : Failure has been handled + * SCSC_WIFI_SUSPEND: Host going in to suspend mode + * SCSC_WIFI_RESUME: Host resuming + */ +enum scsc_wifi_cm_if_notifier { + SCSC_WIFI_STOP, + SCSC_WIFI_FAILURE_RESET, + SCSC_WIFI_SUSPEND, + SCSC_WIFI_RESUME, + SCSC_MAX_NOTIFIER +}; + +struct scsc_wifi_cm_if { + struct slsi_dev *sdev; + /* a std mutex */ + struct mutex cm_if_mutex; + + struct kref kref; + + /* refer to enum scsc_wifi_cm_if_state */ + atomic_t cm_if_state; +}; + +/*********************************** API ************************************/ + +/** + * Driver's interface to cm_if + */ +struct slsi_dev *slsi_dev_attach(struct device *dev, struct scsc_mx *core, struct scsc_service_client *mx_wlan_client); +void slsi_dev_detach(struct slsi_dev *sdev); + +/** + * cm_if's interface to driver + */ +int slsi_sm_service_driver_register(void); +void slsi_sm_service_driver_unregister(void); +void slsi_sm_service_failed(struct slsi_dev *sdev, const char *reason); +int slsi_sm_wlan_service_open(struct slsi_dev *sdev); +int slsi_sm_wlan_service_start(struct slsi_dev *sdev); +void slsi_sm_wlan_service_stop(struct slsi_dev *sdev); +void slsi_sm_wlan_service_close(struct slsi_dev *sdev); +int slsi_wlan_service_notifier_register(struct notifier_block *nb); +int slsi_wlan_service_notifier_unregister(struct notifier_block *nb); + +#endif diff --git a/drivers/net/wireless/scsc/scsc_wifi_fcq.c b/drivers/net/wireless/scsc/scsc_wifi_fcq.c new file mode 100755 index 000000000000..71a2b2532a07 --- /dev/null +++ b/drivers/net/wireless/scsc/scsc_wifi_fcq.c @@ -0,0 +1,1159 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include "scsc_wifi_fcq.h" +#include "debug.h" +#include "dev.h" +#include "hip4_sampler.h" + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)) +#include "porting_imx.h" +#endif + +/* Queues hierarchy and control domains + * + * wlan p2p p2pX + * | | | + * | | | + * | | | + * | | | + * -------------------------------------- + * | + * \ Global domain + * | + * | + * ---------------------------------------- + * | | ... | + * | | ... | Smod Domain (vid, peer_id) + * \ \ ... \ + * | | ... | + * | | ... | + * ------------------------------------------------------ + * | | | | | | | | ... | | | | + * \ \ \ \ \ \ \ \ ... \ \ \ \ Qmod Domain + * | | | | | | | | ... | | | | + * ----------------------------------------------------- + * vi vo bk be vi vo bk be vi vo bk be + */ + +uint scsc_wifi_fcq_smod = 400; +module_param(scsc_wifi_fcq_smod, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_smod, "Initial value of unicast smod - peer normal (default = 400)"); + +uint scsc_wifi_fcq_mcast_smod = 100; +module_param(scsc_wifi_fcq_mcast_smod, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_mcast_smod, "Initial value of multicast smod - peer normal (default = 100)"); + +uint scsc_wifi_fcq_smod_power = 4; +module_param(scsc_wifi_fcq_smod_power, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_smod_power, "Initial powersave SMOD value - peer powersave (default = 4)"); + +uint scsc_wifi_fcq_mcast_smod_power = 4; +module_param(scsc_wifi_fcq_mcast_smod_power, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_mcast_smod_power, "Initial value of powersave multicast smod - peer normal (default = 4)"); + +uint scsc_wifi_fcq_qmod = 400; +module_param(scsc_wifi_fcq_qmod, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_qmod, "Initial value of unicast qmod - peer normal (default = 400)"); + +uint scsc_wifi_fcq_mcast_qmod = 100; +module_param(scsc_wifi_fcq_mcast_qmod, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_mcast_qmod, "Initial value of multicast qmod - peer normal (default = 100)"); + +uint scsc_wifi_fcq_minimum_smod = 50; +module_param(scsc_wifi_fcq_minimum_smod, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_minimum_smod, "Initial value of minimum smod - peer normal (default = 50)"); + +uint scsc_wifi_fcq_distribution_delay_ms; +module_param(scsc_wifi_fcq_distribution_delay_ms, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(scsc_wifi_fcq_distribution_delay_ms, "Distribution time in ms (default = 0)"); + +#define SCSC_WIFI_FCQ_SMOD_RESUME_HYSTERESIS 10 +#define SCSC_WIFI_FCQ_QMOD_RESUME_HYSTERESIS 10 +#define SCSC_WIFI_FCQ_GMOD_RESUME_HYSTERESIS 30 + +/* Protection guard before reaching Stop queues. */ +#define STOP_GUARD_GMOD 10 +#define STOP_GUARD_SMOD 1 +#define STOP_GUARD_QMOD 2 + +/* CLOSE_IN_OVERSHOOT could close the overshooted queues quickly, however could lead to ENOSPC on */ +/* a multithreaded environment */ +/* #define CLOSE_IN_OVERSHOOT 1 */ + +/* To optimally start/stop global queues */ +struct peers_cache { + struct list_head list; + struct net_device *dev; + int vif; + int peer_index; + struct scsc_wifi_fcq_data_qset *qs; + bool is_unicast; +}; + +LIST_HEAD(peers_cache_list); + +/* AC qmod mapping */ +/* 0 - indicates not active */ +/* > 0 - indicates active and the qmod value */ +static u32 ac_q_layout[8][4] = { + { 0, 0, 0, 1000}, + { 0, 0, 500, 500}, + { 0, 500, 0, 500}, + { 0, 333, 333, 333}, + { 500, 0, 0, 500}, + { 333, 0, 333, 333}, + { 333, 333, 0, 333}, + { 250, 250, 250, 250}, +}; + +/* Setting ENABLE_QCOD will include a second layer of flow control calculation for a specific queue. + * #define ENABLE_QCOD 1 + */ +#define ENABLE_QCOD 1 +#ifdef CONFIG_SCSC_DEBUG +/* Global debug counters */ +#define DOMAINS 3 +#define DOMAIN_G 0 +#define DOMAIN_S 1 +#define DOMAIN_Q 2 + +#define DIREC 2 +#define DIREC_TX 0 +#define DIREC_RX 1 + +#define AC_Q 4 + +static int td[DIREC][DOMAINS][AC_Q]; + +static inline void fcq_update_counters(int direction, int domain, int ac) +{ + td[direction][domain][ac] = td[direction][domain][ac] + 1; +} +#endif + +/* Setting ENABLE_CTRL_FCQ will include flow control on control queues. + * #define ENABLE_CTRL_FCQ 1 + */ + +/* POC */ +static int total; + +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION +/* Need to track the number of peer in smod */ +static int total_in_sleep; +#endif + +static inline bool is_gmod_active(struct slsi_dev *sdev) +{ + return atomic_read(&sdev->hip4_inst.hip_priv->gactive); +} + +static inline bool is_smod_active(struct scsc_wifi_fcq_data_qset *qs) +{ + return atomic_read(&qs->active); +} + +static inline bool is_qmod_active(struct scsc_wifi_fcq_data_q *queue) +{ + return atomic_read(&queue->head.active); +} + +static inline bool is_in_pause(struct slsi_dev *sdev) +{ + return atomic_read(&sdev->in_pause_state); +} + +/* Should be called from locked context */ +static inline void fcq_stop_all_queues(struct slsi_dev *sdev) +{ + int i; + struct peers_cache *pc_node, *next; + + list_for_each_entry_safe(pc_node, next, &peers_cache_list, list) { + /* Stop queues all queues */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "fcq_stop_all_queues vif %d peer_index %d ac %d\n", pc_node->vif, pc_node->peer_index, i); + netif_stop_subqueue(pc_node->dev, pc_node->qs->ac_q[i].head.netif_queue_id); + } + } +} + +/* Should be called from locked context */ +static inline void fcq_wake_all_queues(struct slsi_dev *sdev) +{ + int i; + struct peers_cache *pc_node, *next; + + list_for_each_entry_safe(pc_node, next, &peers_cache_list, list) { + /* Wake queues that reported to be active, leave stopped the others. Do not wake queues in pause state */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + if (is_gmod_active(sdev) && is_smod_active(pc_node->qs) && is_qmod_active(&pc_node->qs->ac_q[i]) && !is_in_pause(sdev)) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "fcq_wake_all_queues vif %d peer_index %d ac %d\n", pc_node->vif, pc_node->peer_index, i); + netif_wake_subqueue(pc_node->dev, pc_node->qs->ac_q[i].head.netif_queue_id); + } else { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "fcq_wake_all_queues vif %d peer_index %d ac %d not woken up!\n", pc_node->vif, pc_node->peer_index, i); + } + } + } +} + +void scsc_wifi_fcq_pause_queues(struct slsi_dev *sdev) +{ + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Pause queues\n"); + atomic_set(&sdev->in_pause_state, 1); + fcq_stop_all_queues(sdev); +} + +void scsc_wifi_fcq_unpause_queues(struct slsi_dev *sdev) +{ + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Unpause queues\n"); + atomic_set(&sdev->in_pause_state, 0); + fcq_wake_all_queues(sdev); +} + +#ifdef ENABLE_QCOD +/* Detects AC queues that have stopped and redistributes the qmod + * returns true if the redistribute succeed (qmod increases) + * returns false if qmod was not redistributed + */ +static bool fcq_redistribute_qmod_before_stopping(struct scsc_wifi_fcq_data_qset *qs) +{ + struct scsc_wifi_fcq_q_header *queue; + int i, j; + u32 *qmod_table; + u32 val; + + /* Only BE, so skip as nothing could be done */ + if (qs->ac_inuse == 1) + return false; + + /* Go through the list of possible candidates */ + for (i = 1; i < SLSI_NETIF_Q_PER_PEER; i++) { + queue = &qs->ac_q[i].head; + if (queue->can_be_distributed && + (ktime_compare(ktime_get(), ktime_add_ms(queue->empty_t, scsc_wifi_fcq_distribution_delay_ms)) > 0)) { + /* This queue could be redistributed */ + qs->ac_inuse &= ~(1 << i); + /* To prevent further reallocation */ + queue->can_be_distributed = false; + qmod_table = &ac_q_layout[qs->ac_inuse >> 1][0]; + for (j = 0; j < SLSI_NETIF_Q_PER_PEER; j++) { + queue = &qs->ac_q[j].head; + val = (atomic_read(&qs->smod) * qmod_table[SLSI_NETIF_Q_PER_PEER - 1 - j]) / 1000; + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Detected non-active ac queue (%d). Redistribute qmod[%d] %d qcod %d\n", i, j, val, atomic_read(&queue->qcod)); + atomic_set(&queue->qmod, val); + } + return true; + } + } + + return false; +} + +static void fcq_redistribute_qmod(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 peer_index, u8 vif) +{ + int i; + struct scsc_wifi_fcq_q_header *queue_redis; + u32 *qmod_table; + u32 val; + + qmod_table = &ac_q_layout[qs->ac_inuse >> 1][0]; + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + queue_redis = &qs->ac_q[i].head; + val = (atomic_read(&qs->smod) * qmod_table[SLSI_NETIF_Q_PER_PEER - 1 - i]) / 1000; + atomic_set(&queue_redis->qmod, val); +#ifdef CLOSE_IN_OVERSHOOT + if (val > 0) { + /* Stop queue that are overshooted because the new smod/qmod */ + if (atomic_read(&queue_redis->active) && (atomic_read(&queue_redis->qcod) >= (atomic_read(&queue_redis->qmod) - STOP_GUARD_QMOD))) { + netif_stop_subqueue(dev, queue_redis->netif_queue_id); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Closing overshoot queue for vif: %d peer: %d ac: %d qcod (%d) qmod (%d)\n", vif, peer_index, i, atomic_read(&queue_redis->qcod), atomic_read(&queue_redis->qmod)); + atomic_set(&queue_redis->active, 0); + } + } +#endif + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Redistribute new value %d: qmod[%d] %d qcod %d active %d\n", val, i, atomic_read(&queue_redis->qmod), atomic_read(&queue_redis->qcod), atomic_read(&queue_redis->active)); + } +} +#endif + +static void fcq_redistribute_smod(struct net_device *dev, struct slsi_dev *sdev, int total_to_distribute) +{ +#ifdef CLOSE_IN_OVERSHOOT + int i; +#endif + u32 new_smod; +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + int get_total = 0; +#endif + struct peers_cache *pc_node, *next; + struct scsc_wifi_fcq_data_qset *qs_redis; + + /* Redistribute smod - qmod */ + /* Go through the list of nodes and redistribute smod and qmod accordingly */ + /* First, get the nominal smod and divide it by total of peers */ +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + get_total = total_to_distribute - total_in_sleep; + if (get_total > 0) + new_smod = scsc_wifi_fcq_smod / get_total; + /* Use the nominal total in case of failure */ + else + new_smod = scsc_wifi_fcq_smod / total; +#else + new_smod = scsc_wifi_fcq_smod / total_to_distribute; +#endif + /* Saturate if number is lower than certian low level */ + if (new_smod < scsc_wifi_fcq_minimum_smod) + new_smod = scsc_wifi_fcq_minimum_smod; + list_for_each_entry_safe(pc_node, next, &peers_cache_list, list) { + if (pc_node->is_unicast) { + qs_redis = pc_node->qs; +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + if (qs_redis->in_sleep) + atomic_set(&qs_redis->smod, 0); + else + atomic_set(&qs_redis->smod, new_smod); +#else + atomic_set(&qs_redis->smod, new_smod); +#endif +#ifdef CLOSE_IN_OVERSHOOT + /* Stop queues to avoid overshooting if scod > smod */ + if (is_smod_active(qs_redis) && atomic_read(&qs_redis->scod) >= (atomic_read(&qs_redis->smod) - STOP_GUARD_SMOD)) { + /* Disable the qs that is in overshoot */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) + netif_stop_subqueue(dev, qs_redis->ac_q[i].head.netif_queue_id); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Closing overshoot qs for vif %d peer_index %d\n", pc_node->vif, pc_node->peer_index); + atomic_set(&qs_redis->active, 0); + } +#endif +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Redistributed smod = %d for vif %d peer_index %d total %d total_in_sleep %d\n", new_smod, pc_node->vif, pc_node->peer_index, total_to_distribute, total_in_sleep); +#else + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Redistributed smod = %d for vif %d peer_index %d total %d\n", new_smod, pc_node->vif, pc_node->peer_index, total_to_distribute); +#endif + /* Redistribute the qmod */ + fcq_redistribute_qmod(pc_node->dev, pc_node->qs, sdev, pc_node->peer_index, pc_node->vif); + } + } +} + +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION +static int fcq_redistribute_smod_before_stopping(struct net_device *dev, struct slsi_dev *sdev, int total_to_distribute) +{ + struct peers_cache *pc_node, *next; + + /* only one peer, skip */ + if (total_to_distribute == 1) + return false; + + /* Search for nodes that were empty and are candidates to be redistributed */ + list_for_each_entry_safe(pc_node, next, &peers_cache_list, list) { + if (pc_node->is_unicast) { + if (pc_node->qs->can_be_distributed && + (ktime_compare(ktime_get(), ktime_add_ms(pc_node->qs->empty_t, 5000)) > 0)) { + pc_node->qs->in_sleep = true; + total_in_sleep += 1; + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Smod qs empty. Can be redistributed for vif %d peer_index %d qs->can_be_distributed %d\n", pc_node->vif, pc_node->peer_index, pc_node->qs->can_be_distributed); + fcq_redistribute_smod(dev, sdev, total_to_distribute); + pc_node->qs->can_be_distributed = false; + return true; + } + } + } + return false; +} +#endif + +static int fcq_transmit_gmod_domain(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + int gcod; + int gmod; + + spin_lock(&sdev->hip4_inst.hip_priv->gbot_lock); + + /* Check first the global domain */ + if (sdev->hip4_inst.hip_priv->saturated) { +#ifdef CONFIG_SCSC_DEBUG + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx Global domain. No space. active: %d vif: %d peer: %d ac: %d gcod (%d) gmod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&sdev->hip4_inst.hip_priv->gactive), vif, peer_index, priority, atomic_read(&sdev->hip4_inst.hip_priv->gcod), atomic_read(&sdev->hip4_inst.hip_priv->gmod), + td[DIREC_TX][DOMAIN_G][0], td[DIREC_RX][DOMAIN_G][0], td[DIREC_TX][DOMAIN_G][2], td[DIREC_RX][DOMAIN_G][2], td[DIREC_TX][DOMAIN_G][3], td[DIREC_RX][DOMAIN_G][3]); + fcq_stop_all_queues(sdev); +#endif + spin_unlock(&sdev->hip4_inst.hip_priv->gbot_lock); + return -ENOSPC; + } + + if (!atomic_read(&sdev->hip4_inst.hip_priv->gactive) && sdev->hip4_inst.hip_priv->guard-- == 0) { +#ifdef CONFIG_SCSC_DEBUG + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx Global domain. Saturating Gmod. active: %d vif: %d peer: %d ac: %d gcod (%d) gmod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&sdev->hip4_inst.hip_priv->gactive), vif, peer_index, priority, atomic_read(&sdev->hip4_inst.hip_priv->gcod), atomic_read(&sdev->hip4_inst.hip_priv->gmod), + td[DIREC_TX][DOMAIN_G][0], td[DIREC_RX][DOMAIN_G][0], td[DIREC_TX][DOMAIN_G][2], td[DIREC_RX][DOMAIN_G][2], td[DIREC_TX][DOMAIN_G][3], td[DIREC_RX][DOMAIN_G][3]); +#endif + sdev->hip4_inst.hip_priv->saturated = true; + } + + gmod = atomic_read(&sdev->hip4_inst.hip_priv->gmod); + gcod = atomic_inc_return(&sdev->hip4_inst.hip_priv->gcod); +#ifdef CONFIG_SCSC_DEBUG + fcq_update_counters(DIREC_TX, DOMAIN_G, priority); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "tx: active: %d vif: %d peer: %d ac: %d gcod (%d) gmod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&sdev->hip4_inst.hip_priv->gactive), vif, peer_index, priority, gcod, gmod, + td[DIREC_TX][DOMAIN_G][0], td[DIREC_RX][DOMAIN_G][0], td[DIREC_TX][DOMAIN_G][2], td[DIREC_RX][DOMAIN_G][2], td[DIREC_TX][DOMAIN_G][3], td[DIREC_RX][DOMAIN_G][3]); +#endif + if (gcod >= (atomic_read(&sdev->hip4_inst.hip_priv->gmod) - STOP_GUARD_GMOD)) { + fcq_stop_all_queues(sdev); + if (atomic_read(&sdev->hip4_inst.hip_priv->gactive)) { + sdev->hip4_inst.hip_priv->guard = STOP_GUARD_GMOD; + /* if GUARD is zero, saturate inmmediatelly */ + if (sdev->hip4_inst.hip_priv->guard == 0) + sdev->hip4_inst.hip_priv->saturated = true; + } + atomic_set(&sdev->hip4_inst.hip_priv->gactive, 0); + SCSC_HIP4_SAMPLER_BOT_STOP_Q(sdev->minor_prof, peer_index << 2 | vif); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Global Queues Stopped. gcod (%d) >= gmod (%d) gactive(%d)\n", gcod, gmod, atomic_read(&sdev->hip4_inst.hip_priv->gactive)); + } + spin_unlock(&sdev->hip4_inst.hip_priv->gbot_lock); + + return 0; +} + +/* This function should be called in spinlock(qs) */ +static int fcq_transmit_smod_domain(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + int scod; + +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + if (qs->in_sleep) { + /* Queue was put in sleep and now has become active, need to redistribute smod */ + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Detected activity in sleep Qs active %d vif: %d peer: %d ac: %d\n", + atomic_read(&qs->active), vif, peer_index, priority); + /* Before redistributing need to update the redistribution parameters */ + qs->in_sleep = false; + if (total_in_sleep) + total_in_sleep -= 1; + fcq_redistribute_smod(dev, sdev, total); + } + /* If we transmit we consider the queue -not- empty */ + qs->can_be_distributed = false; +#endif + /* Check smod domain */ + if (qs->saturated) { + int i; + +#ifdef CONFIG_SCSC_DEBUG + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx Smod domain. No space. active %d vif: %d peer: %d ac: %d scod (%d) smod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&qs->active), vif, peer_index, priority, atomic_read(&qs->scod), atomic_read(&qs->smod), + td[DIREC_TX][DOMAIN_S][0], td[DIREC_RX][DOMAIN_S][0], td[DIREC_TX][DOMAIN_S][2], td[DIREC_RX][DOMAIN_S][2], td[DIREC_TX][DOMAIN_S][3], td[DIREC_RX][DOMAIN_S][3]); +#endif + + /* Close subqueues again */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) + netif_stop_subqueue(dev, qs->ac_q[i].head.netif_queue_id); + + return -ENOSPC; + } + /* Pass the frame until reaching the actual saturation */ + if (!atomic_read(&qs->active) && (qs->guard-- == 0)) { +#ifdef CONFIG_SCSC_DEBUG + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx Smod domain. Going into Saturation. active %d vif: %d peer: %d ac: %d scod (%d) smod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&qs->active), vif, peer_index, priority, atomic_read(&qs->scod), atomic_read(&qs->smod), + td[DIREC_TX][DOMAIN_S][0], td[DIREC_RX][DOMAIN_S][0], td[DIREC_TX][DOMAIN_S][2], td[DIREC_RX][DOMAIN_S][2], td[DIREC_TX][DOMAIN_S][3], td[DIREC_RX][DOMAIN_S][3]); +#endif + qs->saturated = true; + } + scod = atomic_inc_return(&qs->scod); + SCSC_HIP4_SAMPLER_BOT_TX(sdev->minor_prof, scod, atomic_read(&qs->smod), priority << 6 | (peer_index & 0xf) << 2 | vif); +#ifdef CONFIG_SCSC_DEBUG + fcq_update_counters(DIREC_TX, DOMAIN_S, priority); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "tx: active: %d vif: %d peer: %d ac: %d scod (%d) smod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&qs->active), vif, peer_index, priority, atomic_read(&qs->scod), atomic_read(&qs->smod), + td[DIREC_TX][DOMAIN_S][0], td[DIREC_RX][DOMAIN_S][0], td[DIREC_TX][DOMAIN_S][2], td[DIREC_RX][DOMAIN_S][2], td[DIREC_TX][DOMAIN_S][3], td[DIREC_RX][DOMAIN_S][3]); +#endif + if (scod >= (atomic_read(&qs->smod) - STOP_GUARD_SMOD)) { + int i; +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + /* Before closing check whether we could get slots from non used queues */ + if (fcq_redistribute_smod_before_stopping(dev, sdev, total)) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Skipped Stop vif: %d peer: %d. scod (%d) >= smod (%d)\n", vif, peer_index, atomic_read(&qs->scod), atomic_read(&qs->smod)); + return 0; + } +#endif + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) + netif_stop_subqueue(dev, qs->ac_q[i].head.netif_queue_id); + + if (atomic_read(&qs->active)) { + qs->guard = STOP_GUARD_SMOD; + /* if GUARD is zero, saturate inmmediatelly */ + if (qs->guard == 0) + qs->saturated = true; + } + atomic_set(&qs->active, 0); + qs->stats.netq_stops++; + SCSC_HIP4_SAMPLER_BOT_STOP_Q(sdev->minor_prof, peer_index << 2 | vif); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Smod Queues Stopped vif: %d peer: %d. scod (%d) >= smod (%d)\n", vif, peer_index, atomic_read(&qs->scod), atomic_read(&qs->smod)); + } + return 0; +} + +#if defined(ENABLE_CTRL_FCQ) || defined(ENABLE_QCOD) +/* This function should be called in spinlock(qs) */ +static int fcq_transmit_qmod_domain(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u16 priority, u8 peer_index, u8 vif) +{ + struct scsc_wifi_fcq_q_header *queue; + int qcod; + + queue = &qs->ac_q[priority].head; + + if (!(qs->ac_inuse & (1 << priority))) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "New AC detected: %d\n", priority); + qs->ac_inuse |= (1 << priority); + fcq_redistribute_qmod(dev, qs, sdev, peer_index, vif); + queue->can_be_distributed = false; + } + + if (queue->saturated) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx No space in ac: %d\n", priority); + /* Stop subqueue */ + netif_stop_subqueue(dev, queue->netif_queue_id); + return -ENOSPC; + } + + /* Pass the frame until reaching the actual saturation */ + if (!atomic_read(&queue->active) && (queue->guard-- == 0)) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx No space in ac: %d Saturated\n", priority); + queue->saturated = true; + } + + qcod = atomic_inc_return(&queue->qcod); + SCSC_HIP4_SAMPLER_BOT_QMOD_TX(sdev->minor_prof, qcod, atomic_read(&queue->qmod), priority << 6 | (peer_index & 0xf) << 2 | vif); +#ifdef CONFIG_SCSC_DEBUG + fcq_update_counters(DIREC_TX, DOMAIN_Q, priority); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "tx: active: %d vif: %d peer: %d ac: %d qcod (%d) qmod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&queue->active), vif, peer_index, priority, atomic_read(&queue->qcod), atomic_read(&queue->qmod), + td[DIREC_TX][DOMAIN_Q][0], td[DIREC_RX][DOMAIN_Q][0], td[DIREC_TX][DOMAIN_Q][2], td[DIREC_RX][DOMAIN_Q][2], td[DIREC_TX][DOMAIN_Q][3], td[DIREC_RX][DOMAIN_Q][3]); +#endif + if (atomic_read(&queue->active) && qcod >= (atomic_read(&queue->qmod) - STOP_GUARD_QMOD)) { + /* Before closing check whether we could get slots from non used queues */ +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + if (fcq_redistribute_smod_before_stopping(dev, sdev, total)) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Skipped Stop vif: %d peer: %d. scod (%d) >= smod (%d)\n", vif, peer_index, atomic_read(&qs->scod), atomic_read(&qs->smod)); + goto skip_stop; + } +#endif + if (fcq_redistribute_qmod_before_stopping(qs)) + goto skip_stop; + SCSC_HIP4_SAMPLER_BOT_QMOD_STOP(sdev->minor_prof, priority << 6 | (peer_index & 0xf) << 2 | vif); + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Stop subqueue vif: %d peer: %d ac: %d qcod (%d) qmod (%d)\n", vif, peer_index, priority, atomic_read(&queue->qcod), atomic_read(&queue->qmod)); + if (atomic_read(&queue->active)) { + queue->guard = STOP_GUARD_QMOD; + /* if GUARD is zero, saturate inmmediatelly */ + if (queue->guard == 0) + queue->saturated = true; + } + atomic_set(&queue->active, 0); + netif_stop_subqueue(dev, queue->netif_queue_id); + queue->stats.netq_stops++; + } +skip_stop: + return 0; +} +#endif + +int scsc_wifi_fcq_transmit_ctrl(struct net_device *dev, struct scsc_wifi_fcq_ctrl_q *queue) +{ + int rc = 0; + +#ifdef ENABLE_CTRL_FCQ + if (WARN_ON(!dev)) + return -EINVAL; + + if (WARN_ON(!queue)) + return -EINVAL; + + rc = fcq_transmit_qmod_domain(dev, &queue->head); +#endif + return rc; +} + +int scsc_wifi_fcq_transmit_data(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + int rc; + struct peers_cache *pc_node, *next; + + if (WARN_ON(!dev)) + return -EINVAL; + + if (WARN_ON(!qs)) + return -EINVAL; + + if (WARN_ON(priority >= ARRAY_SIZE(qs->ac_q))) + return -EINVAL; + + spin_lock_bh(&qs->cp_lock); + /* Check caller matches an existing peer record */ + list_for_each_entry_safe(pc_node, next, &peers_cache_list, list) { + if (pc_node->qs == qs && pc_node->peer_index == peer_index && + pc_node->vif == vif && pc_node->dev == dev) { + goto found; + } + } + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Packet dropped. Detected incorrect peer record\n"); + spin_unlock_bh(&qs->cp_lock); + return -EINVAL; +found: + /* Controlled port is not yet open; so can't send data frame */ + if (qs->controlled_port_state == SCSC_WIFI_FCQ_8021x_STATE_BLOCKED) { + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "8021x_STATE_BLOCKED\n"); + spin_unlock_bh(&qs->cp_lock); + return -EPERM; + } + rc = fcq_transmit_gmod_domain(dev, qs, priority, sdev, vif, peer_index); + if (rc) { + spin_unlock_bh(&qs->cp_lock); + return rc; + } + + rc = fcq_transmit_smod_domain(dev, qs, priority, sdev, vif, peer_index); + if (rc) { + /* Queue is full and was not active, so decrement gcod since + * this packet won't be transmitted, but the overall gcod + * resource is still available. This situation should never + * happen if flow control works as expected. + */ + atomic_dec(&sdev->hip4_inst.hip_priv->gcod); + spin_unlock_bh(&qs->cp_lock); + return rc; + } + +#ifdef ENABLE_QCOD + rc = fcq_transmit_qmod_domain(dev, qs, sdev, priority, peer_index, vif); + if (rc) { + /* Queue is full and was not active, so decrement scod since + * this packet won't be transmitted, but the overall scod + * resource is still available. This situation should never + * happen if flow control works as expected. + */ + atomic_dec(&qs->scod); + atomic_dec(&sdev->hip4_inst.hip_priv->gcod); + spin_unlock_bh(&qs->cp_lock); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx scsc_wifi_fcq_transmit_data: Flow control not respected. Packet will be dropped.\n"); + return rc; + } +#endif + + spin_unlock_bh(&qs->cp_lock); + return 0; +} + +static int fcq_receive_gmod_domain(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u16 priority, u8 peer_index, u8 vif) +{ + int gcod; + int gmod; + int gactive; + + spin_lock(&sdev->hip4_inst.hip_priv->gbot_lock); + /* Decrease first the global domain */ + gmod = atomic_read(&sdev->hip4_inst.hip_priv->gmod); + gcod = atomic_dec_return(&sdev->hip4_inst.hip_priv->gcod); + gactive = atomic_read(&sdev->hip4_inst.hip_priv->gactive); + if (unlikely(gcod < 0)) { + atomic_set(&sdev->hip4_inst.hip_priv->gcod, 0); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx scsc_wifi_fcq_receive: gcod is negative. Has been fixed\n"); + } + +#ifdef CONFIG_SCSC_DEBUG + fcq_update_counters(DIREC_RX, DOMAIN_G, priority); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "rx: active: %d vif: %d peer: %d ac: %d gcod (%d) gmod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d %s\n", + gactive, vif, peer_index, priority, gcod, gmod, + td[DIREC_TX][DOMAIN_G][0], td[DIREC_RX][DOMAIN_G][0], td[DIREC_TX][DOMAIN_G][2], td[DIREC_RX][DOMAIN_G][2], td[DIREC_TX][DOMAIN_G][3], td[DIREC_RX][DOMAIN_G][3], qs ? "" : "NO PEER"); +#endif + + if (!is_gmod_active(sdev) && (gcod + SCSC_WIFI_FCQ_GMOD_RESUME_HYSTERESIS / total < gmod)) { + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Global Queues Started. gcod (%d) < gmod (%d)\n", gcod, gmod); + sdev->hip4_inst.hip_priv->saturated = false; + atomic_set(&sdev->hip4_inst.hip_priv->gactive, 1); + fcq_wake_all_queues(sdev); + } + spin_unlock(&sdev->hip4_inst.hip_priv->gbot_lock); + + return 0; +} + +static int fcq_receive_smod_domain(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u16 priority, u8 peer_index, u8 vif) +{ + int scod; + + scod = atomic_dec_return(&qs->scod); + if (unlikely(scod < 0)) { + atomic_set(&qs->scod, 0); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx scsc_wifi_fcq_receive: scod is negative. Has been fixed\n"); + } + +#ifdef CONFIG_SCSC_DEBUG + fcq_update_counters(DIREC_RX, DOMAIN_S, priority); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "rx: active: %d vif: %d peer: %d ac: %d scod (%d) smod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&qs->active), vif, peer_index, priority, atomic_read(&qs->scod), atomic_read(&qs->smod), + td[DIREC_TX][DOMAIN_S][0], td[DIREC_RX][DOMAIN_S][0], td[DIREC_TX][DOMAIN_S][2], td[DIREC_RX][DOMAIN_S][2], td[DIREC_TX][DOMAIN_S][3], td[DIREC_RX][DOMAIN_S][3]); +#endif + /* Only support a maximum of 16 peers!!!!!!*/ + SCSC_HIP4_SAMPLER_BOT_RX(sdev->minor_prof, scod, atomic_read(&qs->smod), priority << 6 | (peer_index & 0xf) << 2 | vif); + if (!is_smod_active(qs) && (scod + SCSC_WIFI_FCQ_SMOD_RESUME_HYSTERESIS / total < atomic_read(&qs->smod))) { + int i; + /* Resume all queues for this peer that were active . Do not wake queues in pause state or closed in upper domains */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + if (is_gmod_active(sdev) && is_qmod_active(&qs->ac_q[i]) && !is_in_pause(sdev)) + netif_wake_subqueue(dev, qs->ac_q[i].head.netif_queue_id); + else + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "smod wake vif %d peer_index %d ac %d not woken up!\n", vif, peer_index, i); + } + + SCSC_HIP4_SAMPLER_BOT_START_Q(sdev->minor_prof, peer_index << 2 | vif); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Smod Queues Started vif: %d peer: %d. scod (%d) >= smod (%d)\n", vif, peer_index, atomic_read(&qs->scod), atomic_read(&qs->smod)); + /* Regardless the queue were not woken up, set the qs as active */ + qs->saturated = false; + atomic_set(&qs->active, 1); + qs->stats.netq_resumes++; + } + +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + if (scod == 0) { + /* Get the empty time */ + qs->empty_t = ktime_get(); + qs->can_be_distributed = true; + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Qs empty vif: %d peer: %d\n", vif, peer_index); + } else { + qs->can_be_distributed = false; + } +#endif + return 0; +} + +#ifdef ENABLE_QCOD +static int fcq_receive_qmod_domain(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u16 priority, u8 peer_index, u8 vif) +{ + struct scsc_wifi_fcq_q_header *queue; + int qcod; + + queue = &qs->ac_q[priority].head; + + qcod = atomic_dec_return(&queue->qcod); + if (unlikely(qcod < 0)) { + atomic_set(&queue->qcod, 0); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "xxxxxxxxxxxxxxxxxxxxxxx fcq_receive_qmod_domain: qcod is negative. Has been fixed\n"); + } + +#ifdef CONFIG_SCSC_DEBUG + fcq_update_counters(DIREC_RX, DOMAIN_Q, priority); + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "rx: active: %d vif: %d peer: %d ac: %d qcod (%d) qmod (%d) betx:%d berx:%d vitx:%d virx:%d votx:%d vorx:%d\n", + atomic_read(&queue->active), vif, peer_index, priority, atomic_read(&queue->qcod), atomic_read(&queue->qmod), + td[DIREC_TX][DOMAIN_Q][0], td[DIREC_RX][DOMAIN_Q][0], td[DIREC_TX][DOMAIN_Q][2], td[DIREC_RX][DOMAIN_Q][2], td[DIREC_TX][DOMAIN_Q][3], td[DIREC_RX][DOMAIN_Q][3]); +#endif + SCSC_HIP4_SAMPLER_BOT_QMOD_RX(sdev->minor_prof, qcod, atomic_read(&queue->qmod), priority << 6 | (peer_index & 0xf) << 2 | vif); + + if (!is_qmod_active(&qs->ac_q[priority]) && ((qcod + SCSC_WIFI_FCQ_QMOD_RESUME_HYSTERESIS / total) < atomic_read(&queue->qmod))) { + /* Do not wake queues in pause state or closed by other domain */ + if (is_gmod_active(sdev) && is_smod_active(qs) && !is_in_pause(sdev)) + netif_wake_subqueue(dev, queue->netif_queue_id); + /* Only support a maximum of 16 peers!!!!!!*/ + SCSC_HIP4_SAMPLER_BOT_QMOD_START(sdev->minor_prof, priority << 6 | (peer_index & 0xf) << 2 | vif); + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Start subqueue vif: %d peer: %d ac: %d qcod (%d) qmod (%d)\n", vif, peer_index, priority, atomic_read(&queue->qcod), atomic_read(&queue->qmod)); + queue->stats.netq_resumes++; + /* Regardless the queue was not woken up, set the queue as active */ + queue->saturated = false; + atomic_set(&queue->active, 1); + } + + /* Ignore priority BE as it it always active */ + if (qcod == 0 && priority != SLSI_TRAFFIC_Q_BE) { + /* Get the stop time */ + queue->empty_t = ktime_get(); + queue->can_be_distributed = true; + } else { + queue->can_be_distributed = false; + } + return 0; +} +#endif + +int scsc_wifi_fcq_receive_ctrl(struct net_device *dev, struct scsc_wifi_fcq_ctrl_q *queue) +{ + if (WARN_ON(!dev)) + return -EINVAL; + + if (WARN_ON(!queue)) + return -EINVAL; + +#ifdef ENABLE_CTRL_FCQ + /* return fcq_receive(dev, &queue->head, NULL, 0, 0, 0); */ +#endif + return 0; +} + +/* This function is to collect missing returning mbulks from a peer that has dissapeared so the qset is missing */ +int scsc_wifi_fcq_receive_data_no_peer(struct net_device *dev, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + fcq_receive_gmod_domain(dev, NULL, sdev, priority, peer_index, vif); +#ifdef CONFIG_SCSC_DEBUG + /* Update also S and Q domain */ + fcq_update_counters(DIREC_RX, DOMAIN_S, priority); + fcq_update_counters(DIREC_RX, DOMAIN_Q, priority); +#endif + return 0; +} + +int scsc_wifi_fcq_receive_data(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + int rc = 0; + + if (WARN_ON(!dev)) + return -EINVAL; + + if (WARN_ON(!qs)) + return -EINVAL; + + if (WARN_ON(priority >= ARRAY_SIZE(qs->ac_q))) + return -EINVAL; + + /* The read/modify/write of the scod here needs synchronisation. */ + spin_lock_bh(&qs->cp_lock); + + rc = fcq_receive_gmod_domain(dev, qs, sdev, priority, peer_index, vif); + if (rc) + goto end; + + rc = fcq_receive_smod_domain(dev, qs, sdev, priority, peer_index, vif); + if (rc) + goto end; + +#ifdef ENABLE_QCOD + rc = fcq_receive_qmod_domain(dev, qs, sdev, priority, peer_index, vif); +#endif +end: + spin_unlock_bh(&qs->cp_lock); + return rc; +} + +int scsc_wifi_fcq_update_smod(struct scsc_wifi_fcq_data_qset *qs, enum scsc_wifi_fcq_ps_state peer_ps_state, + enum scsc_wifi_fcq_queue_set_type type) +{ + if (WARN_ON(!qs)) + return -EINVAL; + + if (peer_ps_state == SCSC_WIFI_FCQ_PS_STATE_POWERSAVE) { + atomic_set(&qs->smod, type == SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST ? scsc_wifi_fcq_smod_power : scsc_wifi_fcq_mcast_smod_power); + qs->peer_ps_state = peer_ps_state; + qs->peer_ps_state_transitions++; + } else if (peer_ps_state == SCSC_WIFI_FCQ_PS_STATE_ACTIVE) { + atomic_set(&qs->smod, type == SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST ? scsc_wifi_fcq_smod : scsc_wifi_fcq_mcast_smod); + qs->peer_ps_state = peer_ps_state; + qs->peer_ps_state_transitions++; + } else + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Unknown sta_state %d\n", + peer_ps_state); + + return 0; +} + +int scsc_wifi_fcq_8021x_port_state(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, enum scsc_wifi_fcq_8021x_state state) +{ + if (WARN_ON(!dev)) + return -EINTR; + + if (WARN_ON(!qs)) + return -EINVAL; + + spin_lock_bh(&qs->cp_lock); + qs->controlled_port_state = state; + spin_unlock_bh(&qs->cp_lock); + SLSI_NET_DBG1(dev, SLSI_WIFI_FCQ, "802.1x: Queue set 0x%p is %s\n", qs, + state == SCSC_WIFI_FCQ_8021x_STATE_OPEN ? "Open" : "Blocked"); + return 0; +} + +/** + * Statistics + */ +int scsc_wifi_fcq_stat_queue(struct scsc_wifi_fcq_q_header *queue, + struct scsc_wifi_fcq_q_stat *queue_stat, + int *qmod, int *qcod) +{ + if (WARN_ON(!queue) || WARN_ON(!queue_stat) || WARN_ON(!qmod) || WARN_ON(!qmod)) + return -EINTR; + + memcpy(queue_stat, &queue->stats, sizeof(struct scsc_wifi_fcq_q_stat)); + *qmod = atomic_read(&queue->qmod); + *qcod = atomic_read(&queue->qcod); + return 0; +} + +int scsc_wifi_fcq_stat_queueset(struct scsc_wifi_fcq_data_qset *queue_set, + struct scsc_wifi_fcq_q_stat *queue_stat, + int *smod, int *scod, enum scsc_wifi_fcq_8021x_state *cp_state, + u32 *peer_ps_state_transitions) +{ + if (WARN_ON(!queue_set) || WARN_ON(!queue_stat) || WARN_ON(!smod) || WARN_ON(!scod) || + WARN_ON(!cp_state) || WARN_ON(!peer_ps_state_transitions)) + return -EINTR; + + memcpy(queue_stat, &queue_set->stats, sizeof(struct scsc_wifi_fcq_q_stat)); + *peer_ps_state_transitions = queue_set->peer_ps_state_transitions; + *cp_state = queue_set->controlled_port_state; + *smod = atomic_read(&queue_set->smod); + *scod = atomic_read(&queue_set->scod); + return 0; +} + +/** + * Queue and Queue Set init/deinit + */ +int scsc_wifi_fcq_ctrl_q_init(struct scsc_wifi_fcq_ctrl_q *queue) +{ + if (WARN_ON(!queue)) + return -EINVAL; + + /* Ensure that default qmod doesn't exceed 24 bit */ + if (WARN_ON(scsc_wifi_fcq_qmod >= 0x1000000)) + return -EINVAL; + + atomic_set(&queue->head.qmod, scsc_wifi_fcq_qmod); + atomic_set(&queue->head.qcod, 0); + queue->head.netif_queue_id = 0; + queue->head.stats.netq_stops = 0; + queue->head.stats.netq_resumes = 0; + atomic_set(&queue->head.active, 1); + + return 0; +} + +void scsc_wifi_fcq_ctrl_q_deinit(struct scsc_wifi_fcq_ctrl_q *queue) +{ + int qcod; + + WARN_ON(!queue); + + qcod = atomic_read(&queue->head.qcod); + if (qcod != 0) + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Ctrl queue (0x%p) deinit: qcod is %d, netif queue %d\n", + queue, qcod, queue->head.netif_queue_id); +} + +static int fcq_data_q_init(struct net_device *dev, struct slsi_dev *sdev, enum scsc_wifi_fcq_queue_set_type type, struct scsc_wifi_fcq_data_q *queue, + struct scsc_wifi_fcq_data_qset *qs, u8 qs_num, s16 ac) +{ + if (WARN_ON(!queue)) + return -EINVAL; + + if (WARN_ON(!qs)) + return -EINVAL; + + /* Ensure that default qmods don't exceed 24 bit */ + if (WARN_ON(scsc_wifi_fcq_qmod >= 0x1000000) || WARN_ON(scsc_wifi_fcq_mcast_qmod >= 0x1000000)) + return -EINVAL; + + atomic_set(&queue->head.qmod, type == SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST ? scsc_wifi_fcq_qmod : scsc_wifi_fcq_mcast_qmod); + + atomic_set(&queue->head.qcod, 0); + queue->qs = qs; + queue->head.netif_queue_id = type == SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST ? + slsi_netif_get_peer_queue(qs_num, ac) : slsi_netif_get_multicast_queue(ac); + + queue->head.stats.netq_stops = 0; + queue->head.stats.netq_resumes = 0; + + /* TODO: This could generate some ENOSPC if queues are full */ + if (!atomic_read(&sdev->in_pause_state)) + netif_wake_subqueue(dev, queue->head.netif_queue_id); + + queue->head.saturated = false; + atomic_set(&queue->head.active, 1); + + return 0; +} + +static void fcq_data_q_deinit(struct scsc_wifi_fcq_data_q *queue) +{ + int qcod; + + WARN_ON(!queue); + + qcod = atomic_read(&queue->head.qcod); + if (qcod != 0) + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Data queue (0x%p) deinit: qcod is %d, netif queue %d\n", + queue, qcod, queue->head.netif_queue_id); +} + +static void fcq_qset_init(struct net_device *dev, struct slsi_dev *sdev, enum scsc_wifi_fcq_queue_set_type type, struct scsc_wifi_fcq_data_qset *qs, u8 qs_num) +{ + int i; + struct scsc_wifi_fcq_data_q *queue; + + memset(qs, 0, sizeof(struct scsc_wifi_fcq_data_qset)); + spin_lock_init(&qs->cp_lock); + atomic_set(&qs->smod, type == SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST ? scsc_wifi_fcq_smod : scsc_wifi_fcq_mcast_smod); + atomic_set(&qs->scod, 0); + + spin_lock_bh(&qs->cp_lock); + qs->peer_ps_state = SCSC_WIFI_FCQ_PS_STATE_ACTIVE; + qs->saturated = false; + atomic_set(&qs->active, 1); +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + qs->in_sleep = false; + qs->can_be_distributed = false; +#endif + qs->controlled_port_state = SCSC_WIFI_FCQ_8021x_STATE_BLOCKED; + + /* Queues init */ + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) { + /* Clear all the bits */ + qs->ac_inuse &= ~(1 << i); + queue = &qs->ac_q[i]; + fcq_data_q_init(dev, sdev, type, queue, qs, qs_num, i); + } + /* Give all qmod to BE */ + qs->ac_inuse = 1; + spin_unlock_bh(&qs->cp_lock); +} + +int scsc_wifi_fcq_unicast_qset_init(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u8 qs_num, struct slsi_dev *sdev, u8 vif, struct slsi_peer *peer) +{ + struct peers_cache *pc_new_node; + + if (WARN_ON(!qs)) + return -EINVAL; + + /* Ensure that default smod doesn't exceed 24 bit */ + if (WARN_ON(scsc_wifi_fcq_smod >= 0x1000000)) + return -EINVAL; + + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Init unicast queue set 0x%p vif %d peer_index %d\n", qs, vif, peer->aid); + fcq_qset_init(dev, sdev, SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST, qs, qs_num); + SCSC_HIP4_SAMPLER_BOT_ADD(sdev->minor_prof, peer->address[4], peer->address[5], peer->aid << 2 | vif); + SCSC_HIP4_SAMPLER_BOT_TX(sdev->minor_prof, atomic_read(&qs->scod), atomic_read(&qs->smod), peer->aid << 2 | vif); + /* Cache the added peer to optimize the Global start/stop process */ + pc_new_node = kzalloc(sizeof(*pc_new_node), GFP_ATOMIC); + if (!pc_new_node) + return -ENOMEM; + pc_new_node->dev = dev; + pc_new_node->qs = qs; + pc_new_node->peer_index = peer->aid; + pc_new_node->vif = vif; + pc_new_node->is_unicast = true; + + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Add new peer qs %p vif %d peer->aid %d\n", qs, vif, peer->aid); + list_add_tail(&pc_new_node->list, &peers_cache_list); + + if (total == 0) { + /* No peers. Reset gcod. */ +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + total_in_sleep = 0; +#endif + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "First peer. Reset gcod.\n"); + atomic_set(&sdev->hip4_inst.hip_priv->gcod, 0); + atomic_set(&sdev->hip4_inst.hip_priv->gactive, 1); + sdev->hip4_inst.hip_priv->saturated = false; + } + + total++; + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Add New peer. Total %d\n", total); + fcq_redistribute_smod(dev, sdev, total); + + return 0; +} + +int scsc_wifi_fcq_multicast_qset_init(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 vif) +{ + struct peers_cache *pc_node; + + if (WARN_ON(!qs)) + return -EINVAL; + + /* Ensure that default smod doesn't exceed 24 bit */ + if (WARN_ON(scsc_wifi_fcq_mcast_smod >= 0x1000000)) + return -EINVAL; + + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Init multicast queue set 0x%p\n", qs); + fcq_qset_init(dev, sdev, SCSC_WIFI_FCQ_QUEUE_SET_TYPE_MULTICAST, qs, 0); + SCSC_HIP4_SAMPLER_BOT_ADD(sdev->minor_prof, 0, 0, vif); + SCSC_HIP4_SAMPLER_BOT_TX(sdev->minor_prof, atomic_read(&qs->scod), atomic_read(&qs->smod), vif); + + /* Cache the added peer to optimize the Global start/stop process */ + pc_node = kzalloc(sizeof(*pc_node), GFP_ATOMIC); + if (!pc_node) + return -ENOMEM; + pc_node->dev = dev; + pc_node->qs = qs; + pc_node->peer_index = 0; + pc_node->vif = vif; + pc_node->is_unicast = false; + + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Add Multicast Qset %p vif %d peer->aid 0\n", qs, vif); + list_add_tail(&pc_node->list, &peers_cache_list); + + fcq_redistribute_qmod(pc_node->dev, pc_node->qs, sdev, pc_node->peer_index, pc_node->vif); + + return 0; +} + +void scsc_wifi_fcq_qset_deinit(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 vif, struct slsi_peer *peer) +{ + struct peers_cache *pc_node, *next; + int i, scod; +#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING + int aid = 0; + u8 addr_4 = 0; + u8 addr_5 = 0; + + if (peer) { + aid = peer->aid; + addr_4 = peer->address[4]; + addr_5 = peer->address[5]; + } +#endif + + WARN_ON(!qs); + + if (!qs) + return; + + scod = atomic_read(&qs->scod); + if (scod != 0) + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Data set (0x%p) deinit: scod is %d\n", qs, scod); + + for (i = 0; i < SLSI_NETIF_Q_PER_PEER; i++) + fcq_data_q_deinit(&qs->ac_q[i]); + + qs->ac_inuse = 0; +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + /* Remove from total sleep if was in sleep */ + if (qs->in_sleep && total_in_sleep) + total_in_sleep -= 1; +#endif + SCSC_HIP4_SAMPLER_BOT_RX(sdev->minor_prof, 0, 0, (aid & 0xf) << 2 | vif); + SCSC_HIP4_SAMPLER_BOT_REMOVE(sdev->minor_prof, addr_4, addr_5, aid << 2 | vif); + + if (peer) + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Delete qs %p vif %d peer->aid %d\n", qs, vif, peer->aid); + else + SLSI_DBG1_NODEV(SLSI_WIFI_FCQ, "Delete qs %p vif %d Multicast\n", qs, vif); + + list_for_each_entry_safe(pc_node, next, &peers_cache_list, list) { + if (pc_node->qs == qs) { + list_del(&pc_node->list); + kfree(pc_node); + } + } + /* Only count unicast qs */ + if (total > 0 && peer) + total--; + + SLSI_DBG4_NODEV(SLSI_WIFI_FCQ, "Del peer. Total %d\n", total); + + if (total == 0) + return; + + fcq_redistribute_smod(dev, sdev, total); +} diff --git a/drivers/net/wireless/scsc/scsc_wifi_fcq.h b/drivers/net/wireless/scsc/scsc_wifi_fcq.h new file mode 100644 index 000000000000..9d82c158c4f4 --- /dev/null +++ b/drivers/net/wireless/scsc/scsc_wifi_fcq.h @@ -0,0 +1,115 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include +#include +#include "netif.h" + +#ifndef __SCSC_WIFI_FCQ_H +#define __SCSC_WIFI_FCQ_H + +enum scsc_wifi_fcq_8021x_state { + SCSC_WIFI_FCQ_8021x_STATE_BLOCKED = 0, + SCSC_WIFI_FCQ_8021x_STATE_OPEN = 1 +}; + +enum scsc_wifi_fcq_ps_state { + SCSC_WIFI_FCQ_PS_STATE_POWERSAVE = 1, + SCSC_WIFI_FCQ_PS_STATE_ACTIVE = 2 +}; + +struct scsc_wifi_fcq_q_stat { + u32 netq_stops; + u32 netq_resumes; +}; + +struct scsc_wifi_fcq_q_header { + atomic_t qmod; + atomic_t qcod; + u16 netif_queue_id; + atomic_t active; + struct scsc_wifi_fcq_q_stat stats; + ktime_t empty_t; + bool can_be_distributed; + bool saturated; + int guard; +}; + +struct scsc_wifi_fcq_data_q { + struct scsc_wifi_fcq_q_header head; + struct scsc_wifi_fcq_data_qset *qs; +}; + +struct scsc_wifi_fcq_ctrl_q { + struct scsc_wifi_fcq_q_header head; +}; + +enum scsc_wifi_fcq_queue_set_type { + SCSC_WIFI_FCQ_QUEUE_SET_TYPE_UNICAST = 0, + SCSC_WIFI_FCQ_QUEUE_SET_TYPE_MULTICAST = 1 +}; + +#define EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION 1 + +struct scsc_wifi_fcq_data_qset { + atomic_t active; + enum scsc_wifi_fcq_8021x_state controlled_port_state; + /* a std spinlock */ + spinlock_t cp_lock; + + struct scsc_wifi_fcq_data_q ac_q[SLSI_NETIF_Q_PER_PEER]; + /* Control AC usage (BE,BK,VI,VO) bitmap*/ + u8 ac_inuse; + atomic_t smod; + atomic_t scod; + + struct scsc_wifi_fcq_q_stat stats; /* Stats for smod */ + enum scsc_wifi_fcq_ps_state peer_ps_state; + u32 peer_ps_state_transitions; + +#ifdef EXPERIMENTAL_DYNAMIC_SMOD_ADAPTATION + ktime_t empty_t; + bool can_be_distributed; + bool in_sleep; +#endif + bool saturated; + int guard; +}; + +/* Queue and queue set management */ +int scsc_wifi_fcq_ctrl_q_init(struct scsc_wifi_fcq_ctrl_q *queue); +void scsc_wifi_fcq_ctrl_q_deinit(struct scsc_wifi_fcq_ctrl_q *queue); + +int scsc_wifi_fcq_unicast_qset_init(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u8 qs_num, struct slsi_dev *sdev, u8 vif, struct slsi_peer *peer); +int scsc_wifi_fcq_multicast_qset_init(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 vif); +void scsc_wifi_fcq_qset_deinit(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 vif, struct slsi_peer *peer); + +/* Transmit/receive bookkeeping and smod power save changes / 802.1x handling */ +int scsc_wifi_fcq_transmit_data(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index); +int scsc_wifi_fcq_receive_data(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index); +int scsc_wifi_fcq_receive_data_no_peer(struct net_device *dev, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index); + +void scsc_wifi_fcq_pause_queues(struct slsi_dev *sdev); +void scsc_wifi_fcq_unpause_queues(struct slsi_dev *sdev); + +int scsc_wifi_fcq_transmit_ctrl(struct net_device *dev, struct scsc_wifi_fcq_ctrl_q *queue); +int scsc_wifi_fcq_receive_ctrl(struct net_device *dev, struct scsc_wifi_fcq_ctrl_q *queue); + +int scsc_wifi_fcq_update_smod(struct scsc_wifi_fcq_data_qset *qs, enum scsc_wifi_fcq_ps_state peer_ps_state, + enum scsc_wifi_fcq_queue_set_type type); +int scsc_wifi_fcq_8021x_port_state(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, enum scsc_wifi_fcq_8021x_state state); + +/* Statistics */ +int scsc_wifi_fcq_stat_queue(struct scsc_wifi_fcq_q_header *queue, + struct scsc_wifi_fcq_q_stat *queue_stat, + int *qmod, int *qcod); + +int scsc_wifi_fcq_stat_queueset(struct scsc_wifi_fcq_data_qset *queue_set, + struct scsc_wifi_fcq_q_stat *queue_stat, + int *smod, int *scod, enum scsc_wifi_fcq_8021x_state *cp_state, + u32 *peer_ps_state_transitions); + +#endif /* #ifndef __SCSC_WIFI_FCQ_H */ diff --git a/drivers/net/wireless/scsc/src_sink.c b/drivers/net/wireless/scsc/src_sink.c new file mode 100755 index 000000000000..24b16179e536 --- /dev/null +++ b/drivers/net/wireless/scsc/src_sink.c @@ -0,0 +1,417 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "dev.h" /* sdev access */ +#include "src_sink.h" +#include "debug.h" +#include "fapi.h" +#include "mlme.h" +#include "mgt.h" + +static int slsi_src_sink_fake_sta_start(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + u8 device_address[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + u8 fake_peer_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + ndev_vif->iftype = NL80211_IFTYPE_STATION; + dev->ieee80211_ptr->iftype = NL80211_IFTYPE_STATION; + ndev_vif->vif_type = FAPI_VIFTYPE_STATION; + + if (WARN(slsi_mlme_add_vif(sdev, dev, dev->dev_addr, device_address) != 0, "add VIF failed")) { + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EFAULT; + } + + if (WARN(slsi_vif_activated(sdev, dev) != 0, "activate VIF failed")) { + slsi_mlme_del_vif(sdev, dev); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EFAULT; + } + + peer = slsi_peer_add(sdev, dev, fake_peer_mac, SLSI_STA_PEER_QUEUESET + 1); + if (WARN(!peer, "add fake peer failed")) { + slsi_vif_deactivated(sdev, dev); + slsi_mlme_del_vif(sdev, dev); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EFAULT; + } + peer->qos_enabled = true; + slsi_ps_port_control(sdev, dev, peer, SLSI_STA_CONN_STATE_CONNECTED); + netif_carrier_on(dev); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return 0; +} + +static void slsi_src_sink_fake_sta_stop(struct slsi_dev *sdev, struct net_device *dev) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); + + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + SLSI_NET_DBG1(dev, SLSI_SRC_SINK, "station stop(vif:%d)\n", ndev_vif->ifnum); + + if (WARN(!ndev_vif->activated, "not activated")) { + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return; + } + + netif_carrier_off(dev); + if (peer) + slsi_peer_remove(sdev, dev, peer); + slsi_vif_deactivated(sdev, dev); + slsi_mlme_del_vif(sdev, dev); + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); +} + +static int slsi_src_sink_loopback_start(struct slsi_dev *sdev) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + u8 i, queue_idx; + + for (i = SLSI_NET_INDEX_WLAN; i <= SLSI_NET_INDEX_P2P; i++) { + dev = slsi_get_netdev_locked(sdev, i); + if (WARN(!dev, "no netdev (index:%d)", i)) + return -EFAULT; + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* for p2p0 interface the peer database is not created during init; + * peer database is needed for dataplane to be functional. + * so create the peer records for p2p0 now and undo it during loopback stop + */ + if (i == SLSI_NET_INDEX_P2P) { + for (queue_idx = 0; queue_idx < SLSI_ADHOC_PEER_CONNECTIONS_MAX; queue_idx++) { + ndev_vif->peer_sta_record[queue_idx] = kzalloc(sizeof(*ndev_vif->peer_sta_record[queue_idx]), GFP_KERNEL); + if (!ndev_vif->peer_sta_record[queue_idx]) { + int j; + + SLSI_NET_ERR(dev, "Could not allocate memory for peer entry (queue_idx:%d)\n", queue_idx); + + /* free previously allocated peer database memory till current queue_idx */ + for (j = 0; j < queue_idx; j++) { + kfree(ndev_vif->peer_sta_record[j]); + ndev_vif->peer_sta_record[j] = NULL; + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + return -EFAULT; + } + } + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + if (WARN(slsi_src_sink_fake_sta_start(sdev, dev) != 0, "fake STA setup failed (vif:%d)\n", i)) + return -EFAULT; + } + return 0; +} + +static void slsi_src_sink_loopback_stop(struct slsi_dev *sdev) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + u8 i, queue_idx; + + for (i = SLSI_NET_INDEX_WLAN; i <= SLSI_NET_INDEX_P2P; i++) { + dev = slsi_get_netdev_locked(sdev, i); + if (WARN(!dev, "no netdev (index:%d)", i)) + return; + + slsi_src_sink_fake_sta_stop(sdev, dev); + + ndev_vif = netdev_priv(dev); + SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); + + /* undo peer database creation for p2p0 interface on loopback stop */ + if (i == SLSI_NET_INDEX_P2P) { + for (queue_idx = 0; queue_idx < SLSI_ADHOC_PEER_CONNECTIONS_MAX; queue_idx++) { + kfree(ndev_vif->peer_sta_record[queue_idx]); + ndev_vif->peer_sta_record[queue_idx] = NULL; + } + } + SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); + } +} + +long slsi_src_sink_cdev_ioctl_cfg(struct slsi_dev *sdev, unsigned long arg) +{ + long r = 0; + struct unifiio_src_sink_arg_t src_sink_arg; + struct net_device *dev; + struct netdev_vif *ndev_vif; + struct sk_buff *req = NULL; + struct sk_buff *ind = NULL; + + memset((void *)&src_sink_arg, 0, sizeof(struct unifiio_src_sink_arg_t)); + if (copy_from_user((void *)(&src_sink_arg), (void *)arg, sizeof(struct unifiio_src_sink_arg_t))) + return -EFAULT; + + SLSI_DBG2(sdev, SLSI_SRC_SINK, "Source/Sink\n"); + SLSI_DBG2(sdev, SLSI_SRC_SINK, "====================================================\n"); + SLSI_DBG2(sdev, SLSI_SRC_SINK, " #action : [0x%04X]\n", src_sink_arg.common.action); + SLSI_DBG2(sdev, SLSI_SRC_SINK, " #direction : [0x%04X]\n", src_sink_arg.common.direction); + SLSI_DBG2(sdev, SLSI_SRC_SINK, " #vif : [0x%04X]\n", src_sink_arg.common.vif); + SLSI_DBG2(sdev, SLSI_SRC_SINK, " #endpoint : [0x%04X]\n", src_sink_arg.common.endpoint); + SLSI_DBG2(sdev, SLSI_SRC_SINK, "====================================================\n"); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + dev = slsi_get_netdev_locked(sdev, src_sink_arg.common.vif); + if (WARN_ON(!dev)) { + SLSI_ERR(sdev, "netdev for input vif:%d is NULL\n", src_sink_arg.common.vif); + r = -ENODEV; + goto out_locked; + } + ndev_vif = netdev_priv(dev); + + switch (src_sink_arg.common.action) { + case SRC_SINK_ACTION_SINK_START: + if (WARN(slsi_src_sink_fake_sta_start(sdev, dev) != 0, "fake STA setup failed\n")) { + r = -EFAULT; + goto out_locked; + } + req = fapi_alloc(debug_pkt_sink_start_req, DEBUG_PKT_SINK_START_REQ, src_sink_arg.common.vif, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_sink_start_req.end_point, src_sink_arg.common.endpoint); + fapi_set_u16(req, u.debug_pkt_sink_start_req.direction, src_sink_arg.common.direction); + fapi_set_u32(req, u.debug_pkt_sink_start_req.interval, src_sink_arg.u.config.interval); + fapi_set_u16(req, u.debug_pkt_sink_start_req.packets_per_interval, src_sink_arg.u.config.pkts_per_int); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_sink_start_req->\n"); + r = slsi_mlme_req(sdev, dev, req); + break; + case SRC_SINK_ACTION_SINK_STOP: + req = fapi_alloc(debug_pkt_sink_stop_req, DEBUG_PKT_SINK_STOP_REQ, src_sink_arg.common.vif, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_sink_stop_req.direction, src_sink_arg.common.direction); + fapi_set_u16(req, u.debug_pkt_sink_stop_req.end_point, src_sink_arg.common.endpoint); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_sink_stop_req->\n"); + r = slsi_mlme_req(sdev, dev, req); + slsi_src_sink_fake_sta_stop(sdev, dev); + break; + case SRC_SINK_ACTION_GEN_START: + if (WARN(slsi_src_sink_fake_sta_start(sdev, dev) != 0, "fake STA setup failed\n")) { + r = -EFAULT; + goto out_locked; + } + req = fapi_alloc(debug_pkt_gen_start_req, DEBUG_PKT_GEN_START_REQ, src_sink_arg.common.vif, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_gen_start_req.direction, src_sink_arg.common.direction); + fapi_set_u16(req, u.debug_pkt_gen_start_req.end_point, src_sink_arg.common.endpoint); + fapi_set_u32(req, u.debug_pkt_gen_start_req.interval, src_sink_arg.u.config.interval); + fapi_set_u16(req, u.debug_pkt_gen_start_req.packets_per_interval, src_sink_arg.u.config.pkts_per_int); + fapi_set_u16(req, u.debug_pkt_gen_start_req.size, src_sink_arg.u.config.u.gen.size); + fapi_set_u16(req, u.debug_pkt_gen_start_req.use_streaming, src_sink_arg.u.config.u.gen.use_streaming); + fapi_set_u32(req, u.debug_pkt_gen_start_req.ipv4destination_address, src_sink_arg.u.config.u.gen.ipv4_dest); + fapi_set_u16(req, u.debug_pkt_gen_start_req.packets_per_interrupt, src_sink_arg.u.config.u.gen.pkts_per_intr); + + SLSI_DBG3(sdev, SLSI_SRC_SINK, + "int:%u, pkts_per_int:%u, vif:%u, size:%u, use_streaming:%u, ipv4_dest:0x%04X, pkts_per_intr:%u\n", + src_sink_arg.u.config.interval, + src_sink_arg.u.config.pkts_per_int, + src_sink_arg.common.vif, + src_sink_arg.u.config.u.gen.size, + src_sink_arg.u.config.u.gen.use_streaming, + src_sink_arg.u.config.u.gen.ipv4_dest, + src_sink_arg.u.config.u.gen.pkts_per_intr); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_gen_start_req->\n"); + r = slsi_mlme_req(sdev, dev, req); + break; + case SRC_SINK_ACTION_GEN_STOP: + req = fapi_alloc(debug_pkt_gen_stop_req, DEBUG_PKT_GEN_STOP_REQ, src_sink_arg.common.vif, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_gen_stop_req.direction, src_sink_arg.common.direction); + fapi_set_u16(req, u.debug_pkt_gen_stop_req.end_point, src_sink_arg.common.endpoint); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_gen_stop_req->\n"); + r = slsi_mlme_req(sdev, dev, req); + slsi_src_sink_fake_sta_stop(sdev, dev); + break; + case SRC_SINK_ACTION_LOOPBACK_START: + if (WARN(slsi_src_sink_loopback_start(sdev) != 0, "loopback setup failed\n")) { + r = -EFAULT; + goto out_locked; + } + req = fapi_alloc(debug_pkt_sink_start_req, DEBUG_PKT_SINK_START_REQ, 1, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_sink_start_req.end_point, SRC_SINK_ENDPOINT_HOSTIO); + fapi_set_u16(req, u.debug_pkt_sink_start_req.direction, SRC_SINK_DIRECTION_RX); + fapi_set_u32(req, u.debug_pkt_sink_start_req.interval, src_sink_arg.u.config.interval); + fapi_set_u16(req, u.debug_pkt_sink_start_req.packets_per_interval, src_sink_arg.u.config.pkts_per_int); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_loopback_start_req->\n"); + r = slsi_mlme_req(sdev, dev, req); + break; + case SRC_SINK_ACTION_LOOPBACK_STOP: + req = fapi_alloc(debug_pkt_sink_stop_req, DEBUG_PKT_SINK_STOP_REQ, 1, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_sink_stop_req.direction, SRC_SINK_DIRECTION_RX); + fapi_set_u16(req, u.debug_pkt_sink_stop_req.end_point, SRC_SINK_ENDPOINT_HOSTIO); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_loopback_stop_req->\n"); + r = slsi_mlme_req(sdev, dev, req); + slsi_src_sink_loopback_stop(sdev); + break; + case SRC_SINK_ACTION_SINK_REPORT: + req = fapi_alloc(debug_pkt_sink_report_req, DEBUG_PKT_SINK_REPORT_REQ, src_sink_arg.common.vif, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_sink_report_req.end_point, src_sink_arg.common.endpoint); + fapi_set_u16(req, u.debug_pkt_sink_report_req.direction, src_sink_arg.common.direction); + fapi_set_u32(req, u.debug_pkt_sink_report_req.report_interval, src_sink_arg.u.report.interval); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_sink_report_req->\n"); + + ind = slsi_mlme_req_ind(sdev, dev, req, DEBUG_PKT_SINK_REPORT_IND); + if (!ind) { + SLSI_ERR(sdev, "slsi_mlme_req_ind [SINK_REPORT] failed\n"); + r = -EIO; + break; + } + + src_sink_arg.u.report.duration = fapi_get_u32(ind, u.debug_pkt_sink_report_ind.duration); + src_sink_arg.u.report.count = fapi_get_u32(ind, u.debug_pkt_sink_report_ind.received_packets); + src_sink_arg.u.report.octet = fapi_get_u32(ind, u.debug_pkt_sink_report_ind.received_octets); + src_sink_arg.u.report.kbps = fapi_get_u32(ind, u.debug_pkt_sink_report_ind.kbps); + src_sink_arg.u.report.idle_ratio = fapi_get_u16(ind, u.debug_pkt_sink_report_ind.idle_ratio); + src_sink_arg.u.report.interrupt_latency = fapi_get_u16(ind, u.debug_pkt_sink_report_ind.int_latency); + src_sink_arg.u.report.free_kbytes = fapi_get_u16(ind, u.debug_pkt_sink_report_ind.free_kbytes); + src_sink_arg.u.report.timestamp = jiffies_to_msecs(jiffies); + + memset(&ndev_vif->src_sink_params.sink_report, 0, sizeof(struct unifiio_src_sink_report)); + + /* copy the report to userspace */ + if (copy_to_user((void *)arg, (void *)(&src_sink_arg), sizeof(struct unifiio_src_sink_arg_t))) + r = -EFAULT; + slsi_kfree_skb(ind); + break; + case SRC_SINK_ACTION_GEN_REPORT: + req = fapi_alloc(debug_pkt_gen_report_req, DEBUG_PKT_GEN_REPORT_REQ, src_sink_arg.common.vif, 0); + if (WARN_ON(!req)) { + r = -ENODEV; + goto out_locked; + } + fapi_set_u16(req, u.debug_pkt_gen_report_req.end_point, src_sink_arg.common.endpoint); + fapi_set_u16(req, u.debug_pkt_gen_report_req.direction, src_sink_arg.common.direction); + fapi_set_u32(req, u.debug_pkt_gen_report_req.report_interval, src_sink_arg.u.report.interval); + SLSI_DBG1(sdev, SLSI_SRC_SINK, "debug_pkt_gen_report_req->\n"); + + ind = slsi_mlme_req_ind(sdev, dev, req, DEBUG_PKT_GEN_REPORT_IND); + if (!ind) { + SLSI_ERR(sdev, "slsi_mlme_req_ind [GEN_REPORT] failed\n"); + r = -EIO; + break; + } + + src_sink_arg.u.report.duration = fapi_get_u32(ind, u.debug_pkt_gen_report_ind.duration); + src_sink_arg.u.report.count = fapi_get_u32(ind, u.debug_pkt_gen_report_ind.received_packets); + src_sink_arg.u.report.failed_count = fapi_get_u32(ind, u.debug_pkt_gen_report_ind.failed_count); + src_sink_arg.u.report.octet = fapi_get_u32(ind, u.debug_pkt_gen_report_ind.received_octets); + src_sink_arg.u.report.kbps = fapi_get_u32(ind, u.debug_pkt_gen_report_ind.kbps); + src_sink_arg.u.report.idle_ratio = fapi_get_u16(ind, u.debug_pkt_gen_report_ind.idle_ratio); + src_sink_arg.u.report.interrupt_latency = fapi_get_u16(ind, u.debug_pkt_gen_report_ind.int_latency); + src_sink_arg.u.report.free_kbytes = fapi_get_u16(ind, u.debug_pkt_gen_report_ind.free_kbytes); + + src_sink_arg.u.report.timestamp = jiffies_to_msecs(jiffies); + + memset(&ndev_vif->src_sink_params.gen_report, 0, sizeof(struct unifiio_src_sink_report)); + + /* copy the report to userspace */ + if (copy_to_user((void *)arg, (void *)(&src_sink_arg), sizeof(struct unifiio_src_sink_arg_t))) + r = -EFAULT; + slsi_kfree_skb(ind); + break; + case SRC_SINK_ACTION_SINK_REPORT_CACHED: + SLSI_DBG1(sdev, SLSI_SRC_SINK, "cached sink_report\n"); + memcpy(&src_sink_arg.u.report, &ndev_vif->src_sink_params.sink_report, sizeof(struct unifiio_src_sink_report)); + memset(&ndev_vif->src_sink_params.sink_report, 0, sizeof(struct unifiio_src_sink_report)); + + if (copy_to_user((void *)arg, (void *)(&src_sink_arg), sizeof(struct unifiio_src_sink_arg_t))) + r = -EFAULT; + + break; + case SRC_SINK_ACTION_GEN_REPORT_CACHED: + SLSI_DBG1(sdev, SLSI_SRC_SINK, "cached gen_report\n"); + memcpy(&src_sink_arg.u.report, &ndev_vif->src_sink_params.gen_report, sizeof(struct unifiio_src_sink_report)); + memset(&ndev_vif->src_sink_params.gen_report, 0, sizeof(struct unifiio_src_sink_report)); + + if (copy_to_user((void *)arg, (void *)(&src_sink_arg), sizeof(struct unifiio_src_sink_arg_t))) + r = -EFAULT; + break; + default: + SLSI_ERR(sdev, "Invalid input for action: 0x%04X\n", src_sink_arg.common.action); + r = -EINVAL; + break; + } + +out_locked: + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + if (r) + SLSI_ERR(sdev, "slsi_src_sink_cdev_ioctl_cfg(vif:%d) failed with %ld\n", src_sink_arg.common.vif, r); + return r; +} + +void slsi_rx_sink_report(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct unifiio_src_sink_report *report = &ndev_vif->src_sink_params.sink_report; + + SLSI_DBG3(sdev, SLSI_SRC_SINK, "RX debug_pkt_sink_report_ind\n"); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + memset(report, 0, sizeof(struct unifiio_src_sink_report)); + report->duration = fapi_get_u32(skb, u.debug_pkt_sink_report_ind.duration); + report->count = fapi_get_u32(skb, u.debug_pkt_sink_report_ind.received_packets); + report->octet = fapi_get_u32(skb, u.debug_pkt_sink_report_ind.received_octets); + report->kbps = fapi_get_u32(skb, u.debug_pkt_sink_report_ind.kbps); + report->idle_ratio = fapi_get_u16(skb, u.debug_pkt_sink_report_ind.idle_ratio); + report->interrupt_latency = fapi_get_u16(skb, u.debug_pkt_sink_report_ind.int_latency); + report->free_kbytes = fapi_get_u16(skb, u.debug_pkt_sink_report_ind.free_kbytes); + report->timestamp = jiffies_to_msecs(jiffies); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + slsi_kfree_skb(skb); +} + +void slsi_rx_gen_report(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct unifiio_src_sink_report *report = &ndev_vif->src_sink_params.gen_report; + + SLSI_DBG3(sdev, SLSI_SRC_SINK, "RX debug_pkt_gen_report_ind\n"); + + SLSI_MUTEX_LOCK(sdev->netdev_add_remove_mutex); + memset(report, 0, sizeof(struct unifiio_src_sink_report)); + report->duration = fapi_get_u32(skb, u.debug_pkt_gen_report_ind.duration); + report->count = fapi_get_u32(skb, u.debug_pkt_gen_report_ind.received_packets); + report->failed_count = fapi_get_u32(skb, u.debug_pkt_gen_report_ind.failed_count); + report->octet = fapi_get_u32(skb, u.debug_pkt_gen_report_ind.received_octets); + report->kbps = fapi_get_u32(skb, u.debug_pkt_gen_report_ind.kbps); + report->idle_ratio = fapi_get_u16(skb, u.debug_pkt_gen_report_ind.idle_ratio); + report->interrupt_latency = fapi_get_u16(skb, u.debug_pkt_gen_report_ind.int_latency); + report->free_kbytes = fapi_get_u16(skb, u.debug_pkt_gen_report_ind.free_kbytes); + report->timestamp = jiffies_to_msecs(jiffies); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + slsi_kfree_skb(skb); +} diff --git a/drivers/net/wireless/scsc/src_sink.h b/drivers/net/wireless/scsc/src_sink.h new file mode 100644 index 000000000000..d9387e87c251 --- /dev/null +++ b/drivers/net/wireless/scsc/src_sink.h @@ -0,0 +1,22 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_SRC_SINK_H__ +#define __SLSI_SRC_SINK_H__ + +#include "unifiio.h" + +struct slsi_src_sink_params { + struct unifiio_src_sink_report sink_report; + struct unifiio_src_sink_report gen_report; +}; + +long slsi_src_sink_cdev_ioctl_cfg(struct slsi_dev *sdev, unsigned long arg); + +void slsi_rx_sink_report(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_rx_gen_report(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); + +#endif /* __SLSI_SRC_SINK_H__ */ diff --git a/drivers/net/wireless/scsc/test/Makefile b/drivers/net/wireless/scsc/test/Makefile new file mode 100755 index 000000000000..8d36b9e6fe70 --- /dev/null +++ b/drivers/net/wireless/scsc/test/Makefile @@ -0,0 +1,108 @@ + # ---------------------------------------------------------------------------- +# FILE: Makefile +# +# PURPOSE: +# Build instructions for SCSC Wlan driver. +# +# +# Copyright (C) 2016 by Samsung Electronics Co., Ltd +# ---------------------------------------------------------------------------- + +CONFIG_SCSC_WLAN_MODULE=scsc_wlan_unittest + +# ---------------------------------------------------------------------------- +# Common Driver Files +# ---------------------------------------------------------------------------- +$(CONFIG_SCSC_WLAN_MODULE)-y += ../mib.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../mib_text_convert.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../debug.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../debug_frame.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../procfs.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../mgt.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../udi.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../log_clients.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../src_sink.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../fw_test.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../cac.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../ioctl.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../wakelock.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../traffic_monitor.o +# enable GSCAN +ifeq ($(CONFIG_SCSC_WLAN_GSCAN_ENABLE),y) +$(CONFIG_SCSC_WLAN_MODULE)-y += ../nl80211_vendor.o +ccflags-y += -DCONFIG_SCSC_WLAN_GSCAN_ENABLE +endif + +# enable roam offload +ifeq ($(CONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD),y) +ccflags-y += -DCONFIG_SCSC_WLAN_KEY_MGMT_OFFLOAD +endif + +# ---------------------------------------------------------------------------- +# Building for cfg802.11 +# ---------------------------------------------------------------------------- +$(CONFIG_SCSC_WLAN_MODULE)-y += ../cfg80211_ops.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../mlme.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../netif.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../rx.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../tx.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../ba.o + +# ---------------------------------------------------------------------------- +# Building for SAP +# ---------------------------------------------------------------------------- +$(CONFIG_SCSC_WLAN_MODULE)-y += ../sap_ma.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../sap_mlme.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../sap_dbg.o +$(CONFIG_SCSC_WLAN_MODULE)-y += ../sap_test.o + +# ---------------------------------------------------------------------------- +# unittest Driver +# ---------------------------------------------------------------------------- +$(CONFIG_SCSC_WLAN_MODULE)-y += ../dev.o +$(CONFIG_SCSC_WLAN_MODULE)-y += dev_test.o +$(CONFIG_SCSC_WLAN_MODULE)-y += hip_test.o +$(CONFIG_SCSC_WLAN_MODULE)-y += hydra_test.o +$(CONFIG_SCSC_WLAN_MODULE)-y += udi_test.o + +# Suppress -Wempty-body when not built with Debug enabled. +ifneq ($(CONFIG_SCSC_WLAN_DEBUG),y) +ccflags-y += -Wno-empty-body +else +ccflags-y += -g -ggdb +endif + +ccflags-y += -Wno-unused-parameter +ccflags-y += -Wno-sign-compare + +# ---------------------------------------------------------------------------- +# wlan configuration +# ---------------------------------------------------------------------------- +ccflags-$(CONFIG_SCSC_WLAN_DEBUG) += -DCONFIG_SCSC_WLAN_DEBUG +ccflags-$(CONFIG_SCSC_WLAN_SKB_TRACKING) += -DCONFIG_SCSC_WLAN_SKB_TRACKING +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_TRACE +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_SDIO_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_SDIO_TRACE +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_DATA_PLANE_PROFILE_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_DATA_PLANE_PROFILE_TRACE +ccflags-$(CONFIG_SCSC_WLAN_OFFLINE_TX_TRACE) += -DCONFIG_SCSC_WLAN_OFFLINE_TX_TRACE +ccflags-$(CONFIG_SCSC_WLAN_HIP_CLEAR_TH_SLOT_SUPPORT) += -DCONFIG_SCSC_WLAN_HIP_CLEAR_TH_SLOT_SUPPORT +ccflags-$(CONFIG_SCSC_WLAN_HIP_DEFER_SLOT_SUPPORT) += -DCONFIG_SCSC_WLAN_HIP_DEFER_SLOT_SUPPORT +ccflags-$(CONFIG_SCSC_WLAN_RX_NAPI) += -DCONFIG_SCSC_WLAN_RX_NAPI +ccflags-$(CONFIG_SCSC_WLAN_RX_NAPI_GRO) += -DCONFIG_SCSC_WLAN_RX_NAPI_GRO +ccflags-$(CONFIG_SCSC_WLAN_WES_NCHO) += -DCONFIG_SCSC_WLAN_WES_NCHO +ccflags-$(CONFIG_SCSC_WLAN_MUTEX_DEBUG) += -DCONFIG_SCSC_WLAN_MUTEX_DEBUG + +ccflags-y += -DCONFIG_SCSC_WLAN_MAX_INTERFACES=$(CONFIG_SCSC_WLAN_MAX_INTERFACES) + +# Android specific build options +ccflags-$(CONFIG_SCSC_WLAN_ANDROID) += -DCONFIG_SCSC_WLAN_ANDROID + +ccflags-y += -I$(src) -I$(src)/.. +ccflags-y += -I$(src)/../../../../../include -I$(src)/../../../../../include/scsc + +ccflags-y += -DSLSI_TEST_DEV +ccflags-y += $(CONFIG_SCSC_WLAN_EXTRA) + +# ---------------------------------------------------------------------------- +# module +# ---------------------------------------------------------------------------- +obj-$(CONFIG_SCSC_WLAN_UNITTEST) := $(CONFIG_SCSC_WLAN_MODULE).o diff --git a/drivers/net/wireless/scsc/test/dev_test.c b/drivers/net/wireless/scsc/test/dev_test.c new file mode 100755 index 000000000000..874132144a5b --- /dev/null +++ b/drivers/net/wireless/scsc/test/dev_test.c @@ -0,0 +1,511 @@ +/**************************************************************************** + * + * 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); + } + slsi_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)) { + slsi_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) + slsi_kfree_skb(skb); + } else { + route->configured = false; + slsi_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); +} diff --git a/drivers/net/wireless/scsc/test/hip_test.c b/drivers/net/wireless/scsc/test/hip_test.c new file mode 100755 index 000000000000..e576467f7420 --- /dev/null +++ b/drivers/net/wireless/scsc/test/hip_test.c @@ -0,0 +1,276 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include + +#include "unittest.h" +#include "hip.h" +#include "sap.h" +#include "debug.h" +#include "procfs.h" +#include "hip4.h" + +#define SLSI_TEST_DEV_SDIO_BLOCK_SIZE 500 +bool hip4_sampler_sample_start_stop_q = true; +struct hip4_hip_control test_hip_control; + +/* SAP implementations container. Local and static to hip */ +static struct hip_sap { + struct sap_api *sap[SAP_TOTAL]; +} hip_sap_cont; + +/* Register SAP with HIP layer */ +int slsi_hip_sap_register(struct sap_api *sap_api) +{ + u8 class = sap_api->sap_class; + + if (class >= SAP_TOTAL) + return -ENODEV; + + hip_sap_cont.sap[class] = sap_api; + + return 0; +} + +/* UNregister SAP with HIP layer */ +int slsi_hip_sap_unregister(struct sap_api *sap_api) +{ + u8 class = sap_api->sap_class; + + if (class >= SAP_TOTAL) + return -ENODEV; + + hip_sap_cont.sap[class] = NULL; + + return 0; +} + +int slsi_hip_sap_setup(struct slsi_dev *sdev) +{ + /* Execute callbacks to intorm Supported version */ + u16 version = 0; + + if (hip_sap_cont.sap[SAP_MLME]->sap_version_supported) { + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_mlme_ver); + if (hip_sap_cont.sap[SAP_MLME]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + if (hip_sap_cont.sap[SAP_MA]->sap_version_supported) { + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_ma_ver); + if (hip_sap_cont.sap[SAP_MA]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + if (hip_sap_cont.sap[SAP_DBG]->sap_version_supported) { + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_debug_ver); + if (hip_sap_cont.sap[SAP_DBG]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + if (hip_sap_cont.sap[SAP_TST]->sap_version_supported) { + version = scsc_wifi_get_hip_config_version_4_u16(&sdev->hip4_inst.hip_control->config_v4, sap_test_ver); + if (hip_sap_cont.sap[SAP_TST]->sap_version_supported(version)) + return -ENODEV; + } else { + return -ENODEV; + } + + /* Success */ + return 0; +} + +/* SAP rx proxy */ +int slsi_hip_rx(struct slsi_dev *sdev, struct sk_buff *skb) +{ + u16 pid; + + /* Udi test : If pid in UDI range then pass to UDI and ignore */ + slsi_log_clients_log_signal_fast(sdev, &sdev->log_clients, skb, SLSI_LOG_DIRECTION_TO_HOST); + pid = fapi_get_u16(skb, receiver_pid); + if (pid >= SLSI_TX_PROCESS_ID_UDI_MIN && pid <= SLSI_TX_PROCESS_ID_UDI_MAX) { + slsi_kfree_skb(skb); + return 0; + } + + if (fapi_is_ma(skb)) + return hip_sap_cont.sap[SAP_MA]->sap_handler(sdev, skb); + + if (fapi_is_mlme(skb)) + return hip_sap_cont.sap[SAP_MLME]->sap_handler(sdev, skb); + + if (fapi_is_debug(skb)) + return hip_sap_cont.sap[SAP_DBG]->sap_handler(sdev, skb); + + if (fapi_is_test(skb)) + return hip_sap_cont.sap[SAP_TST]->sap_handler(sdev, skb); + + return -EIO; +} + +/* value used at all levels in the driver */ +int slsi_hip_init(struct slsi_dev *sdev, struct device *dev) +{ + SLSI_UNUSED_PARAMETER(dev); + + memset(&sdev->hip, 0, sizeof(sdev->hip)); + + sdev->hip.sdev = sdev; + mutex_init(&sdev->hip.hip_mutex); + + sdev->hip4_inst.hip_control = &test_hip_control; + return 0; +} + +void slsi_hip_deinit(struct slsi_dev *sdev) +{ + mutex_destroy(&sdev->hip.hip_mutex); +} + +int slsi_hip_stop(struct slsi_dev *sdev) +{ + return 0; +} + +int hip4_free_ctrl_slots_count(struct slsi_hip4 *hip) +{ + return HIP4_CTL_SLOTS; +} + +int scsc_wifi_transmit_frame(struct slsi_hip4 *hip, bool ctrl_packet, struct sk_buff *skb) +{ + struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst); + + slsi_log_clients_log_signal_fast(sdev, &sdev->log_clients, skb, SLSI_LOG_DIRECTION_FROM_HOST); + + slsi_kfree_skb(skb); + + return 0; +} + +void slsi_test_bh_work_f(struct work_struct *work) +{ +} + +/* ALL Dummies to get UT build through goes below */ +bool hip4_sampler_sample_q; +bool hip4_sampler_sample_qref; +bool hip4_sampler_sample_int; +bool hip4_sampler_sample_fapi; +bool hip4_sampler_sample_through; +bool hip4_sampler_sample_start_stop_q; +bool hip4_sampler_sample_mbulk; +bool hip4_sampler_sample_qfull; +bool hip4_sampler_sample_mfull; +bool hip4_sampler_vif; +bool hip4_sampler_bot; +bool hip4_sampler_pkt_tx; + +void hip4_sampler_update_record(u32 minor, u8 param1, u8 param2, u8 param3, u8 param4) +{ +} + +void hip4_sampler_create(struct slsi_dev *sdev, struct scsc_mx *mx) +{ +} + +void hip4_sampler_destroy(struct slsi_dev *sdev, struct scsc_mx *mx) +{ +} + +int hip4_sampler_register_hip(struct scsc_mx *mx) +{ + return 0; +} + +int scsc_wifi_fcq_ctrl_q_init(struct scsc_wifi_fcq_ctrl_q *queue) +{ + return 0; +} + +void scsc_wifi_fcq_ctrl_q_deinit(struct scsc_wifi_fcq_ctrl_q *queue) +{ +} + +int scsc_wifi_fcq_unicast_qset_init(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u8 qs_num, struct slsi_dev *sdev, u8 vif, struct slsi_peer *peer) +{ + return 0; +} + +int scsc_wifi_fcq_multicast_qset_init(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 vif) +{ + return 0; +} + +void scsc_wifi_fcq_qset_deinit(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, struct slsi_dev *sdev, u8 vif, struct slsi_peer *peer) +{ +} + +int scsc_wifi_fcq_transmit_data(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + return 0; +} + +int scsc_wifi_fcq_receive_data(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + return 0; +} + +int scsc_wifi_fcq_receive_data_no_peer(struct net_device *dev, u16 priority, struct slsi_dev *sdev, u8 vif, u8 peer_index) +{ + return 0; +} + +void scsc_wifi_fcq_pause_queues(struct slsi_dev *sdev) +{ +} + +void scsc_wifi_fcq_unpause_queues(struct slsi_dev *sdev) +{ +} + +int scsc_wifi_fcq_transmit_ctrl(struct net_device *dev, struct scsc_wifi_fcq_ctrl_q *queue) +{ + return 0; +} + +int scsc_wifi_fcq_receive_ctrl(struct net_device *dev, struct scsc_wifi_fcq_ctrl_q *queue) +{ + return 0; +} + +int scsc_wifi_fcq_update_smod(struct scsc_wifi_fcq_data_qset *qs, enum scsc_wifi_fcq_ps_state peer_ps_state, + enum scsc_wifi_fcq_queue_set_type type) +{ + return 0; +} + +int scsc_wifi_fcq_8021x_port_state(struct net_device *dev, struct scsc_wifi_fcq_data_qset *qs, enum scsc_wifi_fcq_8021x_state state) +{ + return 0; +} + +int scsc_wifi_fcq_stat_queue(struct scsc_wifi_fcq_q_header *queue, + struct scsc_wifi_fcq_q_stat *queue_stat, + int *qmod, int *qcod) +{ + return 0; +} + +int scsc_wifi_fcq_stat_queueset(struct scsc_wifi_fcq_data_qset *queue_set, + struct scsc_wifi_fcq_q_stat *queue_stat, + int *smod, int *scod, enum scsc_wifi_fcq_8021x_state *cp_state, + u32 *peer_ps_state_transitions) +{ + return 0; +} + diff --git a/drivers/net/wireless/scsc/test/hydra_test.c b/drivers/net/wireless/scsc/test/hydra_test.c new file mode 100644 index 000000000000..d40d9c92b3bd --- /dev/null +++ b/drivers/net/wireless/scsc/test/hydra_test.c @@ -0,0 +1,74 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include "dev.h" +#include "unittest.h" +#include "debug.h" +#include "hip_bh.h" + +int slsi_sm_service_driver_register(void) +{ + int csr_result; + + csr_result = slsi_sdio_func_drv_register(); + if (csr_result != 0) { + SLSI_ERR_NODEV("Failed to register the pretend SDIO function driver: csrResult=%d\n", csr_result); + return -EIO; + } + + return 0; +} + +void slsi_sm_service_driver_unregister(void) +{ + slsi_sdio_func_drv_unregister(); +} + +void slsi_sm_service_failed(struct slsi_dev *sdev, const char *reason) +{ +} + +bool slsi_is_test_mode_enabled(void) +{ + return false; +} + +bool slsi_is_rf_test_mode_enabled(void) +{ + return false; +} + +int slsi_check_rf_test_mode(void) +{ + return 0; +} + +int slsi_sm_wlan_service_start(struct slsi_dev *sdev) +{ + return 0; +} + +void slsi_sm_wlan_service_stop(struct slsi_dev *sdev) +{ +} + +int slsi_sm_wlan_service_open(struct slsi_dev *sdev) +{ + return 0; +} + +int mx140_file_request_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_rel_path, const char *filename) +{ + return 0; +} + +void mx140_file_release_conf(struct scsc_mx *mx, const struct firmware *conf) +{ +} + +void slsi_sm_wlan_service_close(struct slsi_dev *sdev) +{ +} diff --git a/drivers/net/wireless/scsc/test/udi_test.c b/drivers/net/wireless/scsc/test/udi_test.c new file mode 100755 index 000000000000..229fd1d9c1b7 --- /dev/null +++ b/drivers/net/wireless/scsc/test/udi_test.c @@ -0,0 +1,632 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#include +#include +#include + +#include "dev.h" + +#include "hip.h" +#include "log_clients.h" +#include "debug.h" +#include "unittest.h" +#include "udi.h" + +#include "unifiio.h" + +#define UDI_CHAR_DEVICE_NAME "s5e7570unittesthip" +#define UDI_CLASS_NAME "s5e7570unittesthip" + +/** + * Control character device for debug + * ================================== + */ +#define NUM_CHAR_CLIENTS 1 /* 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_test_cdev_client; + +struct slsi_cdev { + int minor; + struct cdev cdev; + struct slsi_test_cdev_client *client[NUM_CHAR_CLIENTS]; + + struct slsi_test_dev *uftestdev; + struct device *parent; +}; + +struct slsi_test_cdev_client { + struct slsi_cdev *ufcdev; + int log_enabled; + + /* Flags set for special filtering of ma_packet data */ + u16 ma_packet_filter_config; + + struct sk_buff_head log_list; + wait_queue_head_t log_wq; +}; + +/** + * 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); + +static int slsi_test_cdev_open(struct inode *inode, struct file *file) +{ + struct slsi_cdev *uf_cdev; + struct slsi_test_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; + } + + if (!uf_cdev->uftestdev) { + SLSI_ERR_NODEV("uftestdev not set\n"); + return -EINVAL; + } + + for (indx = 0; indx < NUM_CHAR_CLIENTS; indx++) + if (uf_cdev->client[indx] == NULL) + break; + if (indx >= NUM_CHAR_CLIENTS) { + SLSI_ERR_NODEV("already opened\n"); + return -ENOTSUPP; + } + + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (client == NULL) + return -ENOMEM; + memset(client, 0, sizeof(struct slsi_test_cdev_client)); + + /* init other resource */ + skb_queue_head_init(&client->log_list); + init_waitqueue_head(&client->log_wq); + + client->ufcdev = uf_cdev; + uf_cdev->client[indx] = client; + file->private_data = client; + + slsi_test_dev_attach(client->ufcdev->uftestdev); + + return 0; +} + +static int slsi_test_cdev_release(struct inode *inode, struct file *filp) +{ + struct slsi_test_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 == NULL) + return -EINVAL; + + if (!client->ufcdev) { + SLSI_ERR_NODEV("ufcdev not set\n"); + return -EINVAL; + } + + if (!client->ufcdev->uftestdev) { + SLSI_ERR_NODEV("uftestdev not set\n"); + 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 && client->ufcdev->uftestdev->sdev) + slsi_log_client_unregister(client->ufcdev->uftestdev->sdev, client); + + slsi_test_dev_detach(client->ufcdev->uftestdev); + + slsi_skb_queue_purge(&client->log_list); + + /* free other resource */ + kfree(client); + + uf_cdev->client[indx] = NULL; + + return 0; +} + +static ssize_t slsi_test_cdev_read(struct file *filp, char *p, size_t len, loff_t *poff) +{ + struct slsi_test_cdev_client *client = (void *)filp->private_data; + int msglen; + struct sk_buff *skb; + + SLSI_UNUSED_PARAMETER(poff); + + if (client == NULL) + 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))) + return -ERESTARTSYS; + } + + skb = slsi_skb_dequeue(&client->log_list); + + msglen = skb->len; + if (msglen > (s32)len) { + SLSI_WARN_NODEV("truncated read to %d actual msg len is %lu\n", msglen, (unsigned long int)len); + msglen = len; + } + + SLSI_DBG_HEX_NODEV(SLSI_TEST, skb->data, skb->len, "cdev read skb:%p skb->data:%p\n", skb, skb->data); + if (copy_to_user(p, skb->data, msglen)) { + SLSI_ERR_NODEV("Failed to copy UDI log to user\n"); + slsi_kfree_skb(skb); + return -EFAULT; + } + + slsi_kfree_skb(skb); + return msglen; +} + +static ssize_t slsi_test_cdev_write(struct file *filp, const char *p, size_t len, loff_t *poff) +{ + struct slsi_test_cdev_client *client; + struct slsi_test_dev *uftestdev; + struct sk_buff *skb; + struct slsi_skb_cb *cb; + u8 *data; + + SLSI_UNUSED_PARAMETER(poff); + + client = (void *)filp->private_data; + if (client == NULL) { + SLSI_ERR_NODEV("filep private data not set\n"); + return -EINVAL; + } + + if (!client->ufcdev) { + SLSI_ERR_NODEV("ufcdev not set\n"); + return -EINVAL; + } + + uftestdev = client->ufcdev->uftestdev; + if (!uftestdev) { + SLSI_ERR_NODEV("uftestdev not set\n"); + return -EINVAL; + } + + skb = slsi_alloc_skb(len, GFP_KERNEL); + data = skb_put(skb, len); + if (copy_from_user(data, p, len)) { + SLSI_ERR_NODEV("copy from user failed\n"); + slsi_kfree_skb(skb); + return -EFAULT; + } + + if (skb->len < sizeof(struct fapi_signal_header)) { + SLSI_ERR_NODEV("Data(%d) too short for a signal\n", skb->len); + slsi_kfree_skb(skb); + return -EINVAL; + } + + SLSI_DBG_HEX_NODEV(SLSI_TEST, skb->data, skb->len, "cdev write skb:%p skb->data:%p\n", skb, skb->data); + + /* Intercept some requests */ + if (slsi_test_process_signal(uftestdev, skb)) + return len; + + { + struct slsi_dev *sdev; + + sdev = uftestdev->sdev; + if (!sdev) { + SLSI_ERR_NODEV("sdev not set\n"); + slsi_kfree_skb(skb); + return -EINVAL; + } + + cb = slsi_skb_cb_init(skb); + cb->sig_length = fapi_get_expected_size(skb); + cb->data_length = skb->len; + + if (WARN_ON(slsi_hip_rx(sdev, skb))) { + slsi_kfree_skb(skb); + return -EINVAL; + } + } + return len; +} + +static long slsi_test_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct slsi_test_cdev_client *client = (void *)filp->private_data; + struct slsi_test_dev *uftestdev; + struct slsi_dev *sdev; + long r = 0; + int int_param; + + if (client == NULL || client->ufcdev == NULL) + return -EINVAL; + + uftestdev = client->ufcdev->uftestdev; + if (!uftestdev) { + SLSI_ERR_NODEV("uftestdev not set\n"); + return -EINVAL; + } + + sdev = uftestdev->sdev; + if (!sdev) { + SLSI_ERR_NODEV("sdev not set\n"); + return -EINVAL; + } + + FUNC_ENTER_NODEV(); + + slsi_wakelock(&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; + } else { + slsi_log_client_unregister(sdev, client); + client->log_enabled = 0; + } + + break; + case UNIFI_SET_UDI_LOG_MASK: + { + 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"); + r = -EFAULT; + break; + } + if (filter.signal_ids_n) { + char *signal_filter_index; + int max; + int min; + int size = filter.signal_ids_n * sizeof(filter.signal_ids[0]); + u16 *signal_ids = kmalloc(size, GFP_KERNEL); + + if (!signal_ids) { + r = -ENOMEM; + break; + } + + max = signal_ids[0]; + min = signal_ids[0]; + + if (copy_from_user(signal_ids, filter.signal_ids, size)) { + SLSI_ERR(sdev, "UNIFI_SET_UDI_LOG_MASK: Failed to copy filter from userspace\n"); + kfree(signal_ids); + r = -EFAULT; + break; + } + /* find maximum and minimum signal id in filter */ + for (i = 0; i < filter.signal_ids_n; i++) { + if (signal_ids[i] & UDI_MA_UNITDATA_FILTER_ALLOW_MASK) { + client->ma_packet_filter_config |= signal_ids[i]; + continue; + } + if (signal_ids[i] > max) + max = signal_ids[i]; + else if (signal_ids[i] < min) + min = 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 (signal_ids[i] & UDI_MA_UNITDATA_FILTER_ALLOW_MASK) + continue; + signal_filter_index[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 { + r = -ENOMEM; + } + kfree(signal_ids); + } + break; + } + default: + SLSI_WARN(sdev, "Operation (%d) not supported\n", cmd); + r = -EINVAL; + } + + slsi_wakeunlock(&sdev->wlan_wl); + return r; +} + +static unsigned int slsi_test_cdev_poll(struct file *filp, poll_table *wait) +{ + struct slsi_test_cdev_client *client = (void *)filp->private_data; + unsigned int mask = 0; + int ready; + + ready = skb_queue_len(&client->log_list); + poll_wait(filp, &client->log_wq, wait); + if (ready) + mask |= POLLIN | POLLRDNORM; /* readable */ + + return mask; +} + +/* 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_u16(skb, id); + + 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_u16(skb, id); + + 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_test_cdev_client *client = log_client->log_client_ctx; + struct udi_msg_t msg; + struct udi_msg_t *msg_skb; + + if (WARN_ON(client == NULL)) + return -EINVAL; + if (WARN_ON(skb == NULL)) + return -EINVAL; + if (WARN_ON(skb->len == 0)) + return -EINVAL; + + skb = slsi_skb_copy_expand(skb, sizeof(msg), 0, GFP_ATOMIC); + if (WARN_ON(!skb)) + return -ENOMEM; + + /* Intercept some requests */ + if (slsi_test_process_signal(client->ufcdev->uftestdev, skb)) + return -ECANCELED; + + if (WARN_ON(skb_headroom(skb) < sizeof(msg))) + return -ENOMEM; + + msg.length = sizeof(msg) + skb->len; + msg.timestamp = jiffies_to_msecs(jiffies); + msg.direction = dir; + msg.signal_length = fapi_get_siglen(skb); + + msg_skb = (struct udi_msg_t *)skb_push(skb, sizeof(msg)); + *msg_skb = msg; + + slsi_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_test_cdev_fops = { + .owner = THIS_MODULE, + .open = slsi_test_cdev_open, + .release = slsi_test_cdev_release, + .read = slsi_test_cdev_read, + .write = slsi_test_cdev_write, + .unlocked_ioctl = slsi_test_cdev_ioctl, + .compat_ioctl = slsi_test_cdev_ioctl, + .poll = slsi_test_cdev_poll, +}; + +#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \ + device_create(_class, _parent, _devno, _priv, _fmt, _args) + +static int slsi_get_minor(void) +{ + int minor; + + for (minor = 0; minor < SLSI_UDI_MINOR_NODES; minor++) + if (uf_cdevs[minor] == 0) + return minor; + return -1; +} + +static int slsi_test_cdev_create(struct slsi_test_dev *uftestdev, struct device *parent) +{ + dev_t devno; + int ret; + struct slsi_cdev *pdev; + int minor; + + FUNC_ENTER_NODEV(); + minor = slsi_get_minor(); + if (minor < 0) { + SLSI_ERR_NODEV("no minor numbers available\n"); + return -ENOMEM; + } + + pdev = kmalloc(sizeof(*pdev), GFP_KERNEL); + if (pdev == NULL) + return -ENOMEM; + memset(pdev, 0, sizeof(*pdev)); + + cdev_init(&pdev->cdev, &slsi_test_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_NODEV("cdev_add failed with %d for minor %d\n", ret, minor); + kfree(pdev); + return ret; + } + + pdev->uftestdev = uftestdev; + 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; + } + uftestdev->uf_cdev = (void *)pdev; + uftestdev->device_minor_number = minor; + uf_cdevs[minor] = pdev; + return 0; +} + +static void slsi_test_cdev_destroy(struct slsi_test_dev *uftestdev) +{ + struct slsi_cdev *pdev = (struct slsi_cdev *)uftestdev->uf_cdev; + + FUNC_ENTER_NODEV(); + if (!pdev) + return; + device_destroy(class, pdev->cdev.dev); + cdev_del(&pdev->cdev); + uftestdev->uf_cdev = 0; + uf_cdevs[pdev->minor] = 0; + kfree(pdev); +} + +static int udi_initialised; + +int slsi_test_udi_init(void) +{ + int ret; + + 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 UniFi class */ + class = class_create(THIS_MODULE, UDI_CLASS_NAME); + if (IS_ERR(class)) { + SLSI_ERR_NODEV("Failed to create UniFi class\n"); + unregister_chrdev_region(major_number, SLSI_UDI_MINOR_NODES); + major_number = 0; + return -EINVAL; + } + + udi_initialised = 1; + + return 0; +} + +int slsi_test_udi_deinit(void) +{ + if (!udi_initialised) + return -1; + class_destroy(class); + unregister_chrdev_region(major_number, SLSI_UDI_MINOR_NODES); + udi_initialised = 0; + return 0; +} + +int slsi_test_udi_node_init(struct slsi_test_dev *uftestdev, struct device *parent) +{ + FUNC_ENTER_NODEV(); + if (!udi_initialised) + return -1; + return slsi_test_cdev_create(uftestdev, parent); +} + +int slsi_test_udi_node_reregister(struct slsi_test_dev *uftestdev) +{ + struct slsi_cdev *pdev = uftestdev->uf_cdev; + int indx; + + if (uftestdev->sdev) + for (indx = 0; indx < NUM_CHAR_CLIENTS; indx++) + if (pdev->client[indx] != NULL && pdev->client[indx]->log_enabled) + slsi_log_client_register(uftestdev->sdev, pdev->client[indx], udi_log_event, NULL, 0, 0); + + return 0; +} + +int slsi_test_udi_node_deinit(struct slsi_test_dev *uftestdev) +{ + FUNC_ENTER_NODEV(); + if (!udi_initialised) + return -1; + slsi_test_cdev_destroy(uftestdev); + return 0; +} diff --git a/drivers/net/wireless/scsc/test/unittest.h b/drivers/net/wireless/scsc/test/unittest.h new file mode 100755 index 000000000000..e7331c01ea6f --- /dev/null +++ b/drivers/net/wireless/scsc/test/unittest.h @@ -0,0 +1,129 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_UNITTEST_H__ +#define __SLSI_UNITTEST_H__ + +#include "dev.h" + +struct slsi_test_dev; +struct slsi_test_bh_work { + bool available; + struct slsi_test_dev *uftestdev; + struct workqueue_struct *workqueue; + struct work_struct work; + struct slsi_spinlock spinlock; +}; + +struct slsi_test_data_route { + bool configured; + u16 test_device_minor_number; /* index into slsi_test_devices[] */ + u8 mac[ETH_ALEN]; + u16 vif; + u8 ipsubnet; + u16 sequence_number; +}; + +struct slsi_test_dev { + /* This is used for: + * 1) The uf6kunittesthip chardevice number + * 2) The uf6kunittest chardevice number + * 3) The /procf/devices/unifi number + */ + int device_minor_number; + + void *uf_cdev; + struct device *dev; + struct slsi_dev *sdev; + + struct workqueue_struct *attach_detach_work_queue; + /* a std mutex */ + struct mutex attach_detach_mutex; + struct work_struct attach_work; + struct work_struct detach_work; + bool attached; + + u8 hw_addr[ETH_ALEN]; + struct slsi_test_bh_work bh_work; + + /* a std spinlock */ + spinlock_t route_spinlock; + struct slsi_test_data_route route[SLSI_AP_PEER_CONNECTIONS_MAX]; +}; + +void slsi_test_dev_attach(struct slsi_test_dev *uftestdev); +void slsi_test_dev_detach(struct slsi_test_dev *uftestdev); +bool slsi_test_process_signal(struct slsi_test_dev *uftestdev, struct sk_buff *skb); + +int slsi_test_udi_node_init(struct slsi_test_dev *uftestdev, struct device *parent); +int slsi_test_udi_node_reregister(struct slsi_test_dev *uftestdev); +int slsi_test_udi_node_deinit(struct slsi_test_dev *uftestdev); + +int slsi_test_udi_init(void); +int slsi_test_udi_deinit(void); + +void slsi_test_bh_work_f(struct work_struct *work); +static inline int slsi_test_bh_init(struct slsi_test_dev *uftestdev) +{ + uftestdev->bh_work.available = false; + uftestdev->bh_work.uftestdev = uftestdev; + slsi_spinlock_create(&uftestdev->bh_work.spinlock); + INIT_WORK(&uftestdev->bh_work.work, slsi_test_bh_work_f); + uftestdev->bh_work.workqueue = alloc_ordered_workqueue("slsi_wlan_unittest_bh", 0); + if (!uftestdev->bh_work.workqueue) + return -ENOMEM; + uftestdev->bh_work.available = true; + return 0; +} + +static inline void slsi_test_bh_start(struct slsi_test_dev *uftestdev) +{ + slsi_spinlock_lock(&uftestdev->bh_work.spinlock); + uftestdev->bh_work.available = true; + slsi_spinlock_unlock(&uftestdev->bh_work.spinlock); +} + +static inline void slsi_test_bh_run(struct slsi_test_dev *uftestdev) +{ + slsi_spinlock_lock(&uftestdev->bh_work.spinlock); + if (!uftestdev->bh_work.available) + goto exit; + queue_work(uftestdev->bh_work.workqueue, &uftestdev->bh_work.work); +exit: + slsi_spinlock_unlock(&uftestdev->bh_work.spinlock); +} + +static inline void slsi_test_bh_stop(struct slsi_test_dev *uftestdev) +{ + struct workqueue_struct *workqueue = NULL; + + slsi_spinlock_lock(&uftestdev->bh_work.spinlock); + uftestdev->bh_work.available = false; + workqueue = uftestdev->bh_work.workqueue; + uftestdev->bh_work.workqueue = NULL; + slsi_spinlock_unlock(&uftestdev->bh_work.spinlock); + + if (workqueue) + flush_workqueue(workqueue); +} + +static inline void slsi_test_bh_deinit(struct slsi_test_dev *uftestdev) +{ + struct workqueue_struct *workqueue = NULL; + + slsi_spinlock_lock(&uftestdev->bh_work.spinlock); + WARN_ON(uftestdev->bh_work.available); + uftestdev->bh_work.available = false; + workqueue = uftestdev->bh_work.workqueue; + uftestdev->bh_work.workqueue = NULL; + slsi_spinlock_unlock(&uftestdev->bh_work.spinlock); + if (workqueue) { + flush_workqueue(workqueue); + destroy_workqueue(workqueue); + } +} + +#endif diff --git a/drivers/net/wireless/scsc/traffic_monitor.c b/drivers/net/wireless/scsc/traffic_monitor.c new file mode 100644 index 000000000000..8913a623d2c0 --- /dev/null +++ b/drivers/net/wireless/scsc/traffic_monitor.c @@ -0,0 +1,330 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ +#include +#include "dev.h" +#include "debug.h" +#include "traffic_monitor.h" + +struct slsi_traffic_mon_client_entry { + struct list_head q; + void *client_ctx; + u32 throughput; + u32 state; + u32 hysteresis; + u32 mode; + u32 mid_tput; + u32 high_tput; + void (*traffic_mon_client_cb)(void *client_ctx, u32 state, u32 tput_tx, u32 tput_rx); +}; + +static inline void traffic_mon_invoke_client_callback(struct slsi_dev *sdev, u32 tput_tx, u32 tput_rx) +{ + struct list_head *pos, *n; + struct slsi_traffic_mon_client_entry *traffic_client; + + list_for_each_safe(pos, n, &sdev->traffic_mon_clients.client_list) { + traffic_client = list_entry(pos, struct slsi_traffic_mon_client_entry, q); + + if (traffic_client->mode == TRAFFIC_MON_CLIENT_MODE_PERIODIC) { + if (traffic_client->traffic_mon_client_cb) + traffic_client->traffic_mon_client_cb(traffic_client->client_ctx, TRAFFIC_MON_CLIENT_STATE_NONE, tput_tx, tput_rx); + } else if (traffic_client->mode == TRAFFIC_MON_CLIENT_MODE_EVENTS) { + if ((traffic_client->high_tput) && ((tput_tx + tput_rx) > traffic_client->high_tput)) { + if (traffic_client->state != TRAFFIC_MON_CLIENT_STATE_HIGH && + (traffic_client->hysteresis++ > SLSI_TRAFFIC_MON_HYSTERESIS_HIGH)) { + SLSI_DBG1(sdev, SLSI_HIP, "notify traffic event (tput:%u, state:%u --> HIGH)\n", (tput_tx + tput_rx), traffic_client->state); + traffic_client->hysteresis = 0; + traffic_client->state = TRAFFIC_MON_CLIENT_STATE_HIGH; + + if (traffic_client->traffic_mon_client_cb) + traffic_client->traffic_mon_client_cb(traffic_client->client_ctx, TRAFFIC_MON_CLIENT_STATE_HIGH, tput_tx, tput_rx); + } + } else if ((traffic_client->mid_tput) && ((tput_tx + tput_rx) > traffic_client->mid_tput)) { + if (traffic_client->state != TRAFFIC_MON_CLIENT_STATE_MID) { + if ((traffic_client->state == TRAFFIC_MON_CLIENT_STATE_LOW && (traffic_client->hysteresis++ > SLSI_TRAFFIC_MON_HYSTERESIS_HIGH)) || + (traffic_client->state == TRAFFIC_MON_CLIENT_STATE_HIGH && (traffic_client->hysteresis++ > SLSI_TRAFFIC_MON_HYSTERESIS_LOW))) { + SLSI_DBG1(sdev, SLSI_HIP, "notify traffic event (tput:%u, state:%u --> MID)\n", (tput_tx + tput_rx), traffic_client->state); + traffic_client->hysteresis = 0; + traffic_client->state = TRAFFIC_MON_CLIENT_STATE_MID; + if (traffic_client->traffic_mon_client_cb) + traffic_client->traffic_mon_client_cb(traffic_client->client_ctx, TRAFFIC_MON_CLIENT_STATE_MID, tput_tx, tput_rx); + } + } + } else if (traffic_client->state != TRAFFIC_MON_CLIENT_STATE_LOW && + (traffic_client->hysteresis++ > SLSI_TRAFFIC_MON_HYSTERESIS_LOW)) { + SLSI_DBG1(sdev, SLSI_HIP, "notify traffic event (tput:%u, state:%u --> LOW\n", (tput_tx + tput_rx), traffic_client->state); + traffic_client->hysteresis = 0; + traffic_client->state = TRAFFIC_MON_CLIENT_STATE_LOW; + if (traffic_client->traffic_mon_client_cb) + traffic_client->traffic_mon_client_cb(traffic_client->client_ctx, TRAFFIC_MON_CLIENT_STATE_LOW, tput_tx, tput_rx); + } + } + traffic_client->throughput = (tput_tx + tput_rx); + } +} + +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE +static void traffic_mon_timer(struct timer_list *t) +#else +static void traffic_mon_timer(unsigned long data) +#endif +{ +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + struct slsi_traffic_mon_clients *clients = from_timer(clients, t, timer); + struct slsi_dev *sdev = container_of(clients, typeof(*sdev), traffic_mon_clients); +#else + struct slsi_dev *sdev = (struct slsi_dev *)data; +#endif + struct net_device *dev; + struct netdev_vif *ndev_vif; + bool stop_monitor; + u8 i; + u32 tput_rx = 0; + u32 tput_tx = 0; + + if (!sdev) { + SLSI_ERR_NODEV("invalid sdev\n"); + return; + } + + spin_lock_bh(&sdev->traffic_mon_clients.lock); + + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + dev = sdev->netdev[i]; + if (dev) { + ndev_vif = netdev_priv(dev); + + if (ndev_vif) { + u32 time_in_ms = 0; + + /* the Timer is jiffies based so resolution is not High and it may + * be off by a few ms. So to accurately measure the throughput find + * the time diff between last timer and this one + */ + time_in_ms = ktime_to_ms(ktime_sub(ktime_get(), ndev_vif->last_timer_time)); + + /* the Timer may be any value but it still needs to calculate the + * throughput over a period of 1 second + */ + ndev_vif->num_bytes_rx_per_sec += ndev_vif->num_bytes_rx_per_timer; + ndev_vif->num_bytes_tx_per_sec += ndev_vif->num_bytes_tx_per_timer; + ndev_vif->report_time += time_in_ms; + if (ndev_vif->report_time >= 1000) { + ndev_vif->throughput_rx_bps = (ndev_vif->num_bytes_rx_per_sec * 8 / ndev_vif->report_time) * 1000; + ndev_vif->throughput_tx_bps = (ndev_vif->num_bytes_tx_per_sec * 8 / ndev_vif->report_time) * 1000; + ndev_vif->num_bytes_rx_per_sec = 0; + ndev_vif->num_bytes_tx_per_sec = 0; + ndev_vif->report_time = 0; + } + + /* throughput per timer interval is measured but extrapolated to 1 sec */ + ndev_vif->throughput_tx = (ndev_vif->num_bytes_tx_per_timer * 8 / time_in_ms) * 1000; + ndev_vif->throughput_rx = (ndev_vif->num_bytes_rx_per_timer * 8 / time_in_ms) * 1000; + + ndev_vif->num_bytes_tx_per_timer = 0; + ndev_vif->num_bytes_rx_per_timer = 0; + ndev_vif->last_timer_time = ktime_get(); + tput_tx += ndev_vif->throughput_tx; + tput_rx += ndev_vif->throughput_rx; + } + } + } + + traffic_mon_invoke_client_callback(sdev, tput_tx, tput_rx); + stop_monitor = list_empty(&sdev->traffic_mon_clients.client_list); + + spin_unlock_bh(&sdev->traffic_mon_clients.lock); + if (!stop_monitor) + mod_timer(&sdev->traffic_mon_clients.timer, jiffies + msecs_to_jiffies(SLSI_TRAFFIC_MON_TIMER_PERIOD)); +} + +inline void slsi_traffic_mon_event_rx(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + + /* Apply a correction to length to exclude IP and transport header. + * Can either peek into packets to derive the exact payload size + * or apply a rough correction to roughly calculate the throughput. + * rough correction is applied with a number inbetween IP header (20 bytes) + + * UDP header (8 bytes) or TCP header (can be 20 bytes to 60 bytes) i.e. 40 + */ + if (skb->len >= 40) + ndev_vif->num_bytes_rx_per_timer += (skb->len - 40); + else + ndev_vif->num_bytes_rx_per_timer += skb->len; +} + +inline void slsi_traffic_mon_event_tx(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb); + + if ((skb->len - cb->sig_length) >= 40) + ndev_vif->num_bytes_tx_per_timer += ((skb->len - 40) - cb->sig_length); + else + ndev_vif->num_bytes_tx_per_timer += (skb->len - cb->sig_length); +} + +u8 slsi_traffic_mon_is_running(struct slsi_dev *sdev) +{ + u8 is_running = 0; + + spin_lock_bh(&sdev->traffic_mon_clients.lock); + if (!list_empty(&sdev->traffic_mon_clients.client_list)) + is_running = 1; + spin_unlock_bh(&sdev->traffic_mon_clients.lock); + return is_running; +} + +int slsi_traffic_mon_client_register( + struct slsi_dev *sdev, + void *client_ctx, + u32 mode, + u32 mid_tput, + u32 high_tput, + void (*traffic_mon_client_cb)(void *client_ctx, u32 state, u32 tput_tx, u32 tput_rx)) +{ + struct slsi_traffic_mon_client_entry *traffic_mon_client; + bool start_monitor; + struct net_device *dev; + struct netdev_vif *ndev_vif; + u8 i; + + if (!client_ctx) { + SLSI_ERR(sdev, "A client context must be provided\n"); + return -EINVAL; + } + + spin_lock_bh(&sdev->traffic_mon_clients.lock); + SLSI_DBG1(sdev, SLSI_HIP, "client:%p, mode:%u, mid_tput:%u, high_tput:%u\n", client_ctx, mode, mid_tput, high_tput); + start_monitor = list_empty(&sdev->traffic_mon_clients.client_list); + traffic_mon_client = kmalloc(sizeof(*traffic_mon_client), GFP_ATOMIC); + if (!traffic_mon_client) { + SLSI_ERR(sdev, "could not allocate memory for Monitor client\n"); + spin_unlock_bh(&sdev->traffic_mon_clients.lock); + return -ENOMEM; + } + + traffic_mon_client->client_ctx = client_ctx; + traffic_mon_client->state = TRAFFIC_MON_CLIENT_STATE_LOW; + traffic_mon_client->hysteresis = 0; + traffic_mon_client->mode = mode; + traffic_mon_client->mid_tput = mid_tput; + traffic_mon_client->high_tput = high_tput; + traffic_mon_client->traffic_mon_client_cb = traffic_mon_client_cb; + + /* Add to tail of monitor clients queue */ + list_add_tail(&traffic_mon_client->q, &sdev->traffic_mon_clients.client_list); + + if (start_monitor) { + /* reset counters before starting Timer */ + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + dev = sdev->netdev[i]; + if (dev) { + ndev_vif = netdev_priv(dev); + + if (ndev_vif) { + ndev_vif->throughput_tx = 0; + ndev_vif->throughput_rx = 0; + ndev_vif->num_bytes_tx_per_timer = 0; + ndev_vif->num_bytes_rx_per_timer = 0; + ndev_vif->last_timer_time = ktime_get(); + ndev_vif->num_bytes_rx_per_sec = 0; + ndev_vif->num_bytes_tx_per_sec = 0; + ndev_vif->throughput_rx_bps = 0; + ndev_vif->throughput_tx_bps = 0; + ndev_vif->report_time = 0; + } + } + } + mod_timer(&sdev->traffic_mon_clients.timer, jiffies + msecs_to_jiffies(SLSI_TRAFFIC_MON_TIMER_PERIOD)); + } + + spin_unlock_bh(&sdev->traffic_mon_clients.lock); + return 0; +} + +void slsi_traffic_mon_client_unregister(struct slsi_dev *sdev, void *client_ctx) +{ + struct list_head *pos, *n; + struct slsi_traffic_mon_client_entry *traffic_mon_client; + struct net_device *dev; + struct netdev_vif *ndev_vif; + u8 i; + + spin_lock_bh(&sdev->traffic_mon_clients.lock); + SLSI_DBG1(sdev, SLSI_HIP, "client: %p\n", client_ctx); + list_for_each_safe(pos, n, &sdev->traffic_mon_clients.client_list) { + traffic_mon_client = list_entry(pos, struct slsi_traffic_mon_client_entry, q); + if (traffic_mon_client->client_ctx == client_ctx) { + SLSI_DBG1(sdev, SLSI_HIP, "delete: %p\n", traffic_mon_client->client_ctx); + list_del(pos); + kfree(traffic_mon_client); + } + } + + if (list_empty(&sdev->traffic_mon_clients.client_list)) { + /* reset counters */ + for (i = 1; i <= CONFIG_SCSC_WLAN_MAX_INTERFACES; i++) { + dev = sdev->netdev[i]; + if (dev) { + ndev_vif = netdev_priv(dev); + + if (ndev_vif) { + ndev_vif->throughput_tx = 0; + ndev_vif->throughput_rx = 0; + ndev_vif->num_bytes_tx_per_timer = 0; + ndev_vif->num_bytes_rx_per_timer = 0; + ndev_vif->num_bytes_rx_per_sec = 0; + ndev_vif->num_bytes_tx_per_sec = 0; + ndev_vif->throughput_rx_bps = 0; + ndev_vif->throughput_tx_bps = 0; + ndev_vif->report_time = 0; + } + } + } + spin_unlock_bh(&sdev->traffic_mon_clients.lock); + del_timer_sync(&sdev->traffic_mon_clients.timer); + spin_lock_bh(&sdev->traffic_mon_clients.lock); + } + spin_unlock_bh(&sdev->traffic_mon_clients.lock); +} + +void slsi_traffic_mon_clients_init(struct slsi_dev *sdev) +{ + if (!sdev) { + SLSI_ERR_NODEV("invalid sdev\n"); + return; + } +#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE + timer_setup(&sdev->traffic_mon_clients.timer, traffic_mon_timer, 0); +#else + setup_timer(&sdev->traffic_mon_clients.timer, traffic_mon_timer, (unsigned long)sdev); +#endif + INIT_LIST_HEAD(&sdev->traffic_mon_clients.client_list); + spin_lock_init(&sdev->traffic_mon_clients.lock); +} + +void slsi_traffic_mon_clients_deinit(struct slsi_dev *sdev) +{ + struct list_head *pos, *n; + struct slsi_traffic_mon_client_entry *traffic_mon_client; + + if (!sdev) { + SLSI_ERR_NODEV("invalid sdev\n"); + return; + } + + spin_lock_bh(&sdev->traffic_mon_clients.lock); + list_for_each_safe(pos, n, &sdev->traffic_mon_clients.client_list) { + traffic_mon_client = list_entry(pos, struct slsi_traffic_mon_client_entry, q); + SLSI_DBG1(sdev, SLSI_HIP, "delete: %p\n", traffic_mon_client->client_ctx); + list_del(pos); + kfree(traffic_mon_client); + } + spin_unlock_bh(&sdev->traffic_mon_clients.lock); + del_timer_sync(&sdev->traffic_mon_clients.timer); +} diff --git a/drivers/net/wireless/scsc/traffic_monitor.h b/drivers/net/wireless/scsc/traffic_monitor.h new file mode 100644 index 000000000000..21e9f4d2e650 --- /dev/null +++ b/drivers/net/wireless/scsc/traffic_monitor.h @@ -0,0 +1,77 @@ +/**************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __TRAFFIC_MONITOR_H__ +#define __TRAFFIC_MONITOR_H__ + +#define SLSI_TRAFFIC_MON_TIMER_PERIOD 100 /* in ms */ +#define SLSI_TRAFFIC_MON_HYSTERESIS_HIGH 2 /* in number of timer period */ +#define SLSI_TRAFFIC_MON_HYSTERESIS_LOW 10 /* in number of timer period */ + +enum { + TRAFFIC_MON_CLIENT_MODE_NONE, + TRAFFIC_MON_CLIENT_MODE_PERIODIC, + TRAFFIC_MON_CLIENT_MODE_EVENTS +}; + +enum { + TRAFFIC_MON_CLIENT_STATE_NONE, + TRAFFIC_MON_CLIENT_STATE_LOW, + TRAFFIC_MON_CLIENT_STATE_MID, + TRAFFIC_MON_CLIENT_STATE_HIGH +}; + +struct slsi_traffic_mon_clients { + /* client list lock */ + spinlock_t lock; + + struct timer_list timer; + struct list_head client_list; +}; + +void slsi_traffic_mon_event_rx(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); +void slsi_traffic_mon_event_tx(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb); + +/* Is traffic monitor running? + * + * A caller can seek whether the traffic monitor is running or not. + * If the traffic monitor is running, then the throughput for Rx and + * Tx is stored in netdev_vif structure and caller can access them. + * + * Returns: 1 - traffic monitor is running + * 0 - traffic monitor is NOT running + */ +u8 slsi_traffic_mon_is_running(struct slsi_dev *sdev); + +/* register a client to traffic monitor + * + * A client can register to traffic monitor to either get notification + * per timer period or to get notification when the throughput changes + * state from one state of LOW/MID/HIGH to another. + * + * client_ctx: client context that is passed back in callback + * Also, is an unique ID for the client. + * mode: can be periodic or event based + * mid_tput: Mid throughput level + * high_tput: High throughput level + * traffic_mon_client_cb: function to callback each period or on events + * This function is called back in Timer interrupt + * context. + */ +int slsi_traffic_mon_client_register( + struct slsi_dev *sdev, + void *client_ctx, + u32 mode, + u32 mid_tput, + u32 high_tput, + /* WARNING: THIS IS CALLED BACK IN TIMER INTERRUPT CONTEXT! */ + void (*traffic_mon_client_cb)(void *client_ctx, u32 state, u32 tput_tx, u32 tput_rx)); +void slsi_traffic_mon_client_unregister(struct slsi_dev *sdev, void *client_ctx); + +void slsi_traffic_mon_clients_init(struct slsi_dev *sdev); +void slsi_traffic_mon_clients_deinit(struct slsi_dev *sdev); + +#endif diff --git a/drivers/net/wireless/scsc/tx.c b/drivers/net/wireless/scsc/tx.c new file mode 100755 index 000000000000..e0eca7cb19cb --- /dev/null +++ b/drivers/net/wireless/scsc/tx.c @@ -0,0 +1,623 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#include "dev.h" +#include "debug.h" +#include "mgt.h" +#include "mlme.h" +#include "netif.h" +#include "log_clients.h" +#include "hip4_sampler.h" +#include "traffic_monitor.h" + +static bool msdu_enable = true; +module_param(msdu_enable, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(msdu_enable, "MSDU frame format, Y: enable (default), N: disable"); + +#ifdef CONFIG_ANDROID +#include "scsc_wifilogger_rings.h" +#endif + +/** + * Needed to get HIP4_DAT)SLOTS...should be part + * of initialization and callbacks registering + */ +#include "hip4.h" + +#include + +int slsi_get_dwell_time_for_wps(struct slsi_dev *sdev, struct netdev_vif *ndev_vif, u8 *eapol, u16 eap_length) +{ + /* Note that Message should not be M8.This check is to identify only WSC_START message or M1-M7 */ + /*Return 100ms If opcode type WSC msg and Msg Type M1-M7 or if opcode is WSC start.*/ + if (eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_REQUEST || + eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_RESPONSE) { + if (eapol[SLSI_EAP_TYPE_POS] == SLSI_EAP_TYPE_EXPANDED && eap_length >= SLSI_EAP_OPCODE_POS - 3 && + ((eapol[SLSI_EAP_OPCODE_POS] == SLSI_EAP_OPCODE_WSC_MSG && eap_length >= SLSI_EAP_MSGTYPE_POS - 3 && + eapol[SLSI_EAP_MSGTYPE_POS] != SLSI_EAP_MSGTYPE_M8) || + eapol[SLSI_EAP_OPCODE_POS] == SLSI_EAP_OPCODE_WSC_START)) + return SLSI_EAP_WPS_DWELL_TIME; + /* This is to check if a frame is EAP request identity and on P2P vif.If yes then set dwell time to 100ms */ + if (SLSI_IS_VIF_INDEX_P2P_GROUP(sdev, ndev_vif) && + eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_REQUEST && + eapol[SLSI_EAP_TYPE_POS] == SLSI_EAP_TYPE_IDENTITY) + return SLSI_EAP_WPS_DWELL_TIME; + } + return 0; +} + +static int slsi_tx_eapol(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + u8 *eapol = NULL; + u16 msg_type = 0; + u16 proto = ntohs(skb->protocol); + int ret = 0; + u32 dwell_time = sdev->fw_dwell_time; + u64 tx_bytes_tmp = 0; + u16 eap_length = 0; + + slsi_spinlock_lock(&ndev_vif->peer_lock); + peer = slsi_get_peer_from_mac(sdev, dev, eth_hdr(skb)->h_dest); + if (!peer) { + slsi_spinlock_unlock(&ndev_vif->peer_lock); + SLSI_NET_WARN(dev, "no peer record for %pM, drop EAP frame\n", eth_hdr(skb)->h_dest); + return -EINVAL; + } + + switch (proto) { + case ETH_P_PAE: + /* Detect if this is an EAPOL key frame. If so detect if + * it is an EAPOL-Key M4 packet + * In M4 packet, + * - MIC bit set in key info + * - Key type bit set in key info (pairwise=1, Group=0) + * - Key Data Length would be 0 + */ + if ((skb->len - sizeof(struct ethhdr)) >= 99) + eapol = skb->data + sizeof(struct ethhdr); + if (eapol && eapol[SLSI_EAPOL_IEEE8021X_TYPE_POS] == SLSI_IEEE8021X_TYPE_EAPOL_KEY) { + msg_type = FAPI_MESSAGETYPE_EAPOL_KEY_M123; + + if ((eapol[SLSI_EAPOL_TYPE_POS] == SLSI_EAPOL_TYPE_RSN_KEY || eapol[SLSI_EAPOL_TYPE_POS] == SLSI_EAPOL_TYPE_WPA_KEY) && + (eapol[SLSI_EAPOL_KEY_INFO_LOWER_BYTE_POS] & SLSI_EAPOL_KEY_INFO_KEY_TYPE_BIT_IN_LOWER_BYTE) && + (eapol[SLSI_EAPOL_KEY_INFO_HIGHER_BYTE_POS] & SLSI_EAPOL_KEY_INFO_MIC_BIT_IN_HIGHER_BYTE) && + (eapol[SLSI_EAPOL_KEY_DATA_LENGTH_HIGHER_BYTE_POS] == 0) && + (eapol[SLSI_EAPOL_KEY_DATA_LENGTH_LOWER_BYTE_POS] == 0)) { + msg_type = FAPI_MESSAGETYPE_EAPOL_KEY_M4; + dwell_time = 0; + } + } else { + msg_type = FAPI_MESSAGETYPE_EAP_MESSAGE; + if ((skb->len - sizeof(struct ethhdr)) >= 9) + eapol = skb->data + sizeof(struct ethhdr); + + dwell_time = 0; + if (eapol && eapol[SLSI_EAPOL_IEEE8021X_TYPE_POS] == SLSI_IEEE8021X_TYPE_EAP_PACKET) { + eap_length = (skb->len - sizeof(struct ethhdr)) - 4; + if (eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_REQUEST) + SLSI_INFO(sdev, "Send EAP-Request (%d)\n", eap_length); + else if (eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_RESPONSE) + SLSI_INFO(sdev, "Send EAP-Response (%d)\n", eap_length); + else if (eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_SUCCESS) + SLSI_INFO(sdev, "Send EAP-Success (%d)\n", eap_length); + else if (eapol[SLSI_EAP_CODE_POS] == SLSI_EAP_PACKET_FAILURE) + SLSI_INFO(sdev, "Send EAP-Failure (%d)\n", eap_length); + /* Need to set dwell time for wps exchange and EAP identity frame for P2P */ + dwell_time = slsi_get_dwell_time_for_wps(sdev, ndev_vif, eapol, eap_length); + } + } + break; + case ETH_P_WAI: + SLSI_NET_DBG1(dev, SLSI_MLME, "WAI protocol frame\n"); + msg_type = FAPI_MESSAGETYPE_WAI_MESSAGE; + if ((skb->data[17]) != 9) /*subtype 9 refers to unicast negotiation response*/ + dwell_time = 0; + break; + default: + SLSI_NET_WARN(dev, "protocol NOT supported\n"); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return -EOPNOTSUPP; + } + + /* EAPOL/WAI frames are send via the MLME */ + tx_bytes_tmp = skb->len; /*len copy to avoid null pointer of skb*/ + ret = slsi_mlme_send_frame_data(sdev, dev, skb, msg_type, 0, dwell_time, 0); + if (!ret) + peer->sinfo.tx_bytes += tx_bytes_tmp; //skb->len; + + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return ret; +} + +uint slsi_sg_host_align_mask; /* TODO -- this needs to be resolved! */ + +/** + * This function deals with TX of data frames. + * On success, skbs are properly FREED; on error skb is NO MORE freed. + * + * NOTE THAT currently ONLY the following set of err-codes will trigger + * a REQUEUE and RETRY by upper layers in Kernel NetStack: + * + * -ENOSPC + */ +int slsi_tx_data(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct slsi_skb_cb *cb; + struct netdev_vif *ndev_vif = netdev_priv(dev); + struct slsi_peer *peer; + struct sk_buff *original_skb = NULL; + u16 len = skb->len; + int ret = 0; + enum slsi_traffic_q tq; + u32 dwell_time = 0; + u8 *frame; + u16 arp_opcode; + u32 dhcp_message_type = SLSI_DHCP_MESSAGE_TYPE_INVALID; + + if (slsi_is_test_mode_enabled()) { + /* This signals is in XML file because parts of the Firmware need the symbols defined by them + * but this is actually not handled in wlanlite firmware. + */ + SLSI_NET_WARN(dev, "WlanLite: NOT supported\n"); + return -EOPNOTSUPP; + } + + if (!ndev_vif->activated) { + SLSI_NET_WARN(dev, "vif NOT activated\n"); + return -EINVAL; + } + + if ((ndev_vif->vif_type == FAPI_VIFTYPE_AP) && !ndev_vif->peer_sta_records) { + SLSI_NET_DBG3(dev, SLSI_TX, "AP with no STAs associated, drop Tx frame\n"); + return -EINVAL; + } + + /* check if it is an high important frame? At the moment EAPOL, DHCP + * and ARP are treated as high important frame and are sent over + * MLME for applying special rules in transmission. + */ + if (skb->queue_mapping == SLSI_NETIF_Q_PRIORITY) { + int proto = be16_to_cpu(eth_hdr(skb)->h_proto); + + switch (proto) { + default: + /* Only EAP packets and IP frames with DHCP are stored in SLSI_NETIF_Q_PRIORITY */ + SLSI_NET_ERR(dev, "Bad h_proto=0x%x in SLSI_NETIF_Q_PRIORITY\n", proto); + return -EINVAL; + case ETH_P_PAE: + case ETH_P_WAI: + SLSI_NET_DBG2(dev, SLSI_MLME, "transmit EAP packet from SLSI_NETIF_Q_PRIORITY\n"); + return slsi_tx_eapol(sdev, dev, skb); + case ETH_P_ARP: + SLSI_NET_DBG2(dev, SLSI_MLME, "transmit ARP frame from SLSI_NETIF_Q_PRIORITY\n"); + frame = skb->data + sizeof(struct ethhdr); + arp_opcode = frame[SLSI_ARP_OPCODE_OFFSET] << 8 | frame[SLSI_ARP_OPCODE_OFFSET + 1]; + if ((arp_opcode == SLSI_ARP_REQUEST_OPCODE) && + !SLSI_IS_GRATUITOUS_ARP(frame)) { +#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT + if (ndev_vif->enhanced_arp_detect_enabled && + !memcmp(&frame[SLSI_ARP_DEST_IP_ADDR_OFFSET], &ndev_vif->target_ip_addr, 4)) { + ndev_vif->enhanced_arp_stats.arp_req_count_from_netdev++; + } +#endif + dwell_time = sdev->fw_dwell_time; + } + return slsi_mlme_send_frame_data(sdev, dev, skb, FAPI_MESSAGETYPE_ARP, 0, dwell_time, 0); + case ETH_P_IP: + if (skb->len >= 285 && slsi_is_dhcp_packet(skb->data) != SLSI_TX_IS_NOT_DHCP) { + if (skb->data[42] == 1) /*opcode 1 refers to DHCP discover/request*/ + dwell_time = sdev->fw_dwell_time; + dhcp_message_type = skb->data[284]; + if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_DISCOVER) + SLSI_INFO(sdev, "Send DHCP [DISCOVER]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_OFFER) + SLSI_INFO(sdev, "Send DHCP [OFFER]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_REQUEST) + SLSI_INFO(sdev, "Send DHCP [REQUEST]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_DECLINE) + SLSI_INFO(sdev, "Send DHCP [DECLINE]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_ACK) + SLSI_INFO(sdev, "Send DHCP [ACK]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_NAK) + SLSI_INFO(sdev, "Send DHCP [NAK]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_RELEASE) + SLSI_INFO(sdev, "Send DHCP [RELEASE]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_INFORM) + SLSI_INFO(sdev, "Send DHCP [INFORM]\n"); + else if (dhcp_message_type == SLSI_DHCP_MESSAGE_TYPE_FORCERENEW) + SLSI_INFO(sdev, "Send DHCP [FORCERENEW]\n"); + else + SLSI_INFO(sdev, "Send DHCP [INVALID]\n"); + return slsi_mlme_send_frame_data(sdev, dev, skb, FAPI_MESSAGETYPE_DHCP, 0, dwell_time, + 0); + } + /* IP frame can have only DHCP packet in SLSI_NETIF_Q_PRIORITY */ + SLSI_NET_ERR(dev, "Bad IP frame in SLSI_NETIF_Q_PRIORITY\n"); + return -EINVAL; + } + } + + if (skb_headroom(skb) < (fapi_sig_size(ma_unitdata_req) + 160)) { + struct sk_buff *skb2 = NULL; + + skb2 = slsi_skb_realloc_headroom(skb, fapi_sig_size(ma_unitdata_req) + 160); + if (!skb2) { + SLSI_NET_WARN(dev, "failed to alloc SKB headroom, drop Tx frame\n"); + return -EINVAL; + } + /* Keep track of this copy...*/ + original_skb = skb; + skb = skb2; + } + + /* Align mac_header with skb->data */ + if (skb_headroom(skb) != skb->mac_header) + skb_pull(skb, skb->mac_header - skb_headroom(skb)); + + if (msdu_enable) + ethr_ii_to_subframe_msdu(skb); + + len = skb->len; + + (void)skb_push(skb, fapi_sig_size(ma_unitdata_req)); + tq = slsi_frame_priority_to_ac_queue(skb->priority); + fapi_set_u16(skb, id, MA_UNITDATA_REQ); + fapi_set_u16(skb, receiver_pid, 0); + fapi_set_u16(skb, sender_pid, SLSI_TX_PROCESS_ID_MIN); + fapi_set_u32(skb, fw_reference, 0); + fapi_set_u16(skb, u.ma_unitdata_req.vif, ndev_vif->ifnum); + fapi_set_u16(skb, u.ma_unitdata_req.host_tag, slsi_tx_host_tag(sdev, tq)); + fapi_set_u16(skb, u.ma_unitdata_req.peer_index, MAP_QS_TO_AID(slsi_netif_get_qs_from_queue + (skb->queue_mapping, tq))); + + SCSC_HIP4_SAMPLER_PKT_TX(sdev->minor_prof, fapi_get_u16(skb, u.ma_unitdata_req.host_tag)); + + /* by default the priority is set to contention. It is overridden and set appropriate + * priority if peer supports QoS. The broadcast/multicast frames are sent in non-QoS . + */ + fapi_set_u16(skb, u.ma_unitdata_req.priority, FAPI_PRIORITY_CONTENTION); + + if (msdu_enable) + fapi_set_u16(skb, u.ma_unitdata_req.data_unit_descriptor, FAPI_DATAUNITDESCRIPTOR_AMSDU_SUBFRAME); + else + fapi_set_u16(skb, u.ma_unitdata_req.data_unit_descriptor, FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME); + + SLSI_NET_DBG_HEX(dev, SLSI_TX, skb->data, skb->len < 128 ? skb->len : 128, "\n"); + + cb = slsi_skb_cb_init(skb); + cb->sig_length = fapi_sig_size(ma_unitdata_req); + cb->data_length = skb->len; + /* colour is defined as: */ + /* u16 register bits: + * 0 - do not use + * [2:1] - vif + * [7:3] - peer_index + * [10:8] - ac queue + */ + cb->colour = (slsi_frame_priority_to_ac_queue(skb->priority) << 8) | + (fapi_get_u16(skb, u.ma_unitdata_req.peer_index) << 3) | ndev_vif->ifnum << 1; + +#ifdef CONFIG_SCSC_WIFILOGGER + /* Log only the linear skb chunk ... unidata anywya will be truncated to 100.*/ + SCSC_WLOG_PKTFATE_LOG_TX_DATA_FRAME(fapi_get_u16(skb, u.ma_unitdata_req.host_tag), + skb->data, skb_headlen(skb)); +#endif + + /* ACCESS POINT MODE */ + if (ndev_vif->vif_type == FAPI_VIFTYPE_AP) { + struct ethhdr *ehdr = eth_hdr(skb); + + if (is_multicast_ether_addr(ehdr->h_dest)) { + ret = scsc_wifi_fcq_transmit_data(dev, + &ndev_vif->ap.group_data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3); + if (ret < 0) { + SLSI_NET_WARN(dev, "no fcq for groupcast, drop Tx frame\n"); + /* Free the local copy here ..if any */ + if (original_skb) + slsi_kfree_skb(skb); + return ret; + } + ret = scsc_wifi_transmit_frame(&sdev->hip4_inst, false, skb); + if (ret == NETDEV_TX_OK) { + /** + * Frees the original since the copy has already + * been freed downstream + */ + if (original_skb) + slsi_kfree_skb(original_skb); + return ret; + } else if (ret < 0) { + /* scsc_wifi_transmit_frame failed, decrement BoT counters */ + scsc_wifi_fcq_receive_data(dev, + &ndev_vif->ap.group_data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3); + if (original_skb) + slsi_kfree_skb(skb); + return ret; + } + if (original_skb) + slsi_kfree_skb(skb); + return -EIO; + } + } + slsi_spinlock_lock(&ndev_vif->peer_lock); + + peer = slsi_get_peer_from_mac(sdev, dev, eth_hdr(skb)->h_dest); + if (!peer) { + slsi_spinlock_unlock(&ndev_vif->peer_lock); + SLSI_NET_WARN(dev, "no peer record for %pM, drop Tx frame\n", eth_hdr(skb)->h_dest); + if (original_skb) + slsi_kfree_skb(skb); + return -EINVAL; + } + /** + * skb->priority will contain the priority obtained from the IP Diff/Serv field. + * The skb->priority field is defined in terms of the FAPI_PRIORITY_* definitions. + * For QoS enabled associations, this is the tid and is the value required in + * the ma_unitdata_req.priority field. For non-QoS assocations, the ma_unitdata_req. + * priority field requires FAPI_PRIORITY_CONTENTION. + */ + if (peer->qos_enabled) + fapi_set_u16(skb, u.ma_unitdata_req.priority, skb->priority); + + slsi_debug_frame(sdev, dev, skb, "TX"); + + ret = scsc_wifi_fcq_transmit_data(dev, &peer->data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3); + if (ret < 0) { + SLSI_NET_WARN(dev, "no fcq for %pM, drop Tx frame\n", eth_hdr(skb)->h_dest); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + if (original_skb) + slsi_kfree_skb(skb); + return ret; + } + + /* SKB is owned by scsc_wifi_transmit_frame() unless the transmission is + * unsuccesful. + */ + slsi_traffic_mon_event_tx(sdev, dev, skb); + ret = scsc_wifi_transmit_frame(&sdev->hip4_inst, false, skb); + if (ret != NETDEV_TX_OK) { + /* scsc_wifi_transmit_frame failed, decrement BoT counters */ + scsc_wifi_fcq_receive_data(dev, &peer->data_qs, slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3); + + if (ret == -ENOSPC) { + slsi_spinlock_unlock(&ndev_vif->peer_lock); + if (original_skb) + slsi_kfree_skb(skb); + return ret; + } + slsi_spinlock_unlock(&ndev_vif->peer_lock); + if (original_skb) + slsi_kfree_skb(skb); + return -EIO; + } + /* Frame has been successfully sent, and freed by lower layers */ + slsi_spinlock_unlock(&ndev_vif->peer_lock); + /* What about the original if we passed in a copy ? */ + if (original_skb) + slsi_kfree_skb(original_skb); + peer->sinfo.tx_bytes += len; + return ret; +} + +int slsi_tx_data_lower(struct slsi_dev *sdev, struct sk_buff *skb) +{ + struct net_device *dev; + struct netdev_vif *ndev_vif; + struct slsi_peer *peer; + u16 vif; + u8 *dest; + int ret; + struct slsi_skb_cb *cb = slsi_skb_cb_get(skb); + + vif = fapi_get_vif(skb); + + switch (fapi_get_u16(skb, u.ma_unitdata_req.data_unit_descriptor)) { + case FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME: + if (ntohs(eth_hdr(skb)->h_proto) == ETH_P_PAE || ntohs(eth_hdr(skb)->h_proto) == ETH_P_WAI) + return slsi_tx_control(sdev, NULL, skb); + dest = eth_hdr(skb)->h_dest; + break; + + case FAPI_DATAUNITDESCRIPTOR_AMSDU: + /* The AMSDU frame type is an AMSDU payload ready to be prepended by + * an 802.11 frame header by the firmware. The AMSDU subframe header + * is identical to an Ethernet header in terms of addressing, so it + * is safe to access the destination address through the ethernet + * structure. + */ + dest = eth_hdr(skb)->h_dest; + break; + case FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME: + dest = ieee80211_get_DA((struct ieee80211_hdr *)fapi_get_data(skb)); + break; + default: + SLSI_ERR(sdev, "data_unit_descriptor incorrectly set (0x%02x), dropping TX frame\n", + fapi_get_u16(skb, u.ma_unitdata_req.data_unit_descriptor)); + return -EINVAL; + } + + rcu_read_lock(); + dev = slsi_get_netdev_rcu(sdev, vif); + if (!dev) { + SLSI_ERR(sdev, "netdev(%d) No longer exists\n", vif); + rcu_read_unlock(); + return -EINVAL; + } + + ndev_vif = netdev_priv(dev); + rcu_read_unlock(); + + if (is_multicast_ether_addr(dest) && ((ndev_vif->vif_type == FAPI_VIFTYPE_AP))) { + if (scsc_wifi_fcq_transmit_data(dev, &ndev_vif->ap.group_data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3) < 0) { + SLSI_NET_DBG3(dev, SLSI_TX, "no fcq for groupcast, dropping TX frame\n"); + return -EINVAL; + } + ret = scsc_wifi_transmit_frame(&sdev->hip4_inst, false, skb); + if (ret == NETDEV_TX_OK) + return ret; + /** + * This should be NEVER RETRIED/REQUEUED and its' handled + * by the caller in UDI cdev_write + */ + if (ret == -ENOSPC) + SLSI_NET_DBG1(dev, SLSI_TX, "TX_LOWER...Queue Full... BUT Dropping packet\n"); + else + SLSI_NET_DBG1(dev, SLSI_TX, "TX_LOWER...Generic Error...Dropping packet\n"); + /* scsc_wifi_transmit_frame failed, decrement BoT counters */ + scsc_wifi_fcq_receive_data(dev, &ndev_vif->ap.group_data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3); + return ret; + } + + slsi_spinlock_lock(&ndev_vif->peer_lock); + peer = slsi_get_peer_from_mac(sdev, dev, dest); + if (!peer) { + SLSI_ERR(sdev, "no peer record for %02x:%02x:%02x:%02x:%02x:%02x, dropping TX frame\n", + dest[0], dest[1], dest[2], dest[3], dest[4], dest[5]); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return -EINVAL; + } + slsi_debug_frame(sdev, dev, skb, "TX"); + + if (fapi_get_u16(skb, u.ma_unitdata_req.priority) == FAPI_PRIORITY_CONTENTION) + skb->priority = FAPI_PRIORITY_QOS_UP0; + else + skb->priority = fapi_get_u16(skb, u.ma_unitdata_req.priority); + + if (scsc_wifi_fcq_transmit_data(dev, &peer->data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3) < 0) { + SLSI_NET_DBG3(dev, SLSI_TX, "no fcq for %02x:%02x:%02x:%02x:%02x:%02x, dropping TX frame\n", + eth_hdr(skb)->h_dest[0], eth_hdr(skb)->h_dest[1], eth_hdr(skb)->h_dest[2], eth_hdr(skb)->h_dest[3], eth_hdr(skb)->h_dest[4], eth_hdr(skb)->h_dest[5]); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return -EINVAL; + } + /* SKB is owned by scsc_wifi_transmit_frame() unless the transmission is + * unsuccesful. + */ + ret = scsc_wifi_transmit_frame(&sdev->hip4_inst, false, skb); + if (ret < 0) { + SLSI_NET_DBG1(dev, SLSI_TX, "%s (signal: %d)\n", ret == -ENOSPC ? "Queue is full. Flow control" : "Failed to transmit", fapi_get_sigid(skb)); + /* scsc_wifi_transmit_frame failed, decrement BoT counters */ + scsc_wifi_fcq_receive_data(dev, &ndev_vif->ap.group_data_qs, + slsi_frame_priority_to_ac_queue(skb->priority), + sdev, + (cb->colour & 0x6) >> 1, + (cb->colour & 0xf8) >> 3); + if (ret == -ENOSPC) + SLSI_NET_DBG1(dev, SLSI_TX, + "TX_LOWER...Queue Full...BUT Dropping packet\n"); + else + SLSI_NET_DBG1(dev, SLSI_TX, + "TX_LOWER...Generic Error...Dropping packet\n"); + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return ret; + } + + slsi_spinlock_unlock(&ndev_vif->peer_lock); + return 0; +} + +/** + * NOTE: + * 1. dev can be NULL + * 2. On error the SKB is NOT freed, NOR retried (ENOSPC dropped). + * Callers should take care to free the SKB eventually. + */ +int slsi_tx_control(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) +{ + struct slsi_skb_cb *cb; + int res = 0; + struct fapi_signal_header *hdr; + + if (WARN_ON(!skb)) { + res = -EINVAL; + goto exit; + } + + /** + * Sanity check of the skb - if it's not an MLME, MA, debug or test + * signal it will be discarded. + * Skip if test mode (wlanlite) is enabled. + */ + if (!slsi_is_test_mode_enabled()) + if (!fapi_is_mlme(skb) && !fapi_is_ma(skb) && !fapi_is_debug(skb) && !fapi_is_test(skb)) { + SLSI_NET_WARN(dev, "Discarding skb because it has type: 0x%04X\n", fapi_get_sigid(skb)); + return -EINVAL; + } + + 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. */ + hdr = (struct fapi_signal_header *)skb->data; + hdr->fw_reference = 0; + +#ifdef CONFIG_SCSC_WIFILOGGER + /* Log only the linear skb chunk */ + SCSC_WLOG_PKTFATE_LOG_TX_CTRL_FRAME(fapi_get_u16(skb, u.mlme_frame_transmission_ind.host_tag), + skb->data, skb_headlen(skb)); +#endif + + slsi_debug_frame(sdev, dev, skb, "TX"); + res = scsc_wifi_transmit_frame(&sdev->hip4_inst, true, skb); + if (res != NETDEV_TX_OK) { + char reason[80]; + + SLSI_NET_ERR(dev, "%s (signal %d)\n", res == -ENOSPC ? "Queue is full. Flow control" : "Failed to transmit", fapi_get_sigid(skb)); + + if (!in_interrupt()) { + snprintf(reason, sizeof(reason), "Failed to transmit signal 0x%04X (err:%d)", fapi_get_sigid(skb), res); + slsi_sm_service_failed(sdev, reason); + + res = -EIO; + } + } +exit: + return res; +} + +void slsi_tx_pause_queues(struct slsi_dev *sdev) +{ + if (!sdev) + return; + + scsc_wifi_fcq_pause_queues(sdev); +} + +void slsi_tx_unpause_queues(struct slsi_dev *sdev) +{ + if (!sdev) + return; + + scsc_wifi_fcq_unpause_queues(sdev); +} diff --git a/drivers/net/wireless/scsc/udi.c b/drivers/net/wireless/scsc/udi.c new file mode 100755 index 000000000000..8fb287cfc075 --- /dev/null +++ b/drivers/net/wireless/scsc/udi.c @@ -0,0 +1,1175 @@ +/**************************************************************************** + * + * 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" + +#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 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 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 = fapi_alloc_f(sizeof(struct fapi_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. + */ + slsi_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(); + +#ifdef 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); + + slsi_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(); + +#ifdef 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 = slsi_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"); + slsi_kfree_skb(skb); + return -EFAULT; + } + + slsi_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 = slsi_alloc_skb_headroom(len, GFP_KERNEL); + data = skb_put(skb, len); + if (copy_from_user(data, p, len)) { + SLSI_ERR(sdev, "copy from user failed\n"); + slsi_kfree_skb(skb); + return -EFAULT; + } + + cb = slsi_skb_cb_init(skb); + cb->sig_length = fapi_get_expected_size(skb); + cb->data_length = skb->len; + /* colour is defined as: */ + /* u16 register bits: + * 0 - do not use + * [2:1] - vif + * [7:3] - peer_index + * [10:8] - ac queue + */ + if (fapi_is_ma(skb)) + cb->colour = (slsi_frame_priority_to_ac_queue(skb->priority) << 8) | + (fapi_get_u16(skb, u.ma_unitdata_req.peer_index) << 3) | (fapi_get_u16(skb, u.ma_unitdata_req.vif) << 1); + + /* 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)) { + slsi_kfree_skb(skb); + return -EINVAL; + } + } else if (slsi_tx_control(sdev, NULL, skb)) { + slsi_kfree_skb(skb); + return -EINVAL; + } + } else if (slsi_hip_rx(sdev, skb)) { + slsi_kfree_skb(skb); + return -EINVAL; + } + + return len; +} + +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; + 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 (!client || !client->ufcdev) + return -EINVAL; + sdev = client->ufcdev->sdev; + + slsi_wakelock(&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: + { + 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"); + r = -EFAULT; + break; + } + + 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"); + r = -EFAULT; + break; + } + + 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 { + r = -ENOMEM; + } + } + break; + } + case UNIFI_SET_MIB: + { + struct net_device *dev = NULL; + + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { + SLSI_ERR(sdev, "UNIFI_SET_MIB: Device not yet available\n"); + r = -EFAULT; + break; + } + + /* 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"); + r = -EFAULT; + break; + } + + /* 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"); + r = -EFAULT; + break; + } + + /* 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"); + r = -EFAULT; + break; + } + + /* check if length is valid */ + if (unlikely(mib_data_length > UDI_MIB_SET_LEN_MAX || mib_data_size > UDI_MIB_SET_LEN_MAX)) { + SLSI_ERR(sdev, "UNIFI_SET_MIB: size too long (mib_data_length:%u mib_data_size:%u)\n", mib_data_length, mib_data_size); + r = -EFAULT; + break; + } + + mib_data = kmalloc(mib_data_size, GFP_KERNEL); + + /* 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); + r = -EFAULT; + break; + } + + 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); + r = -EFAULT; + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + break; + } + + r = slsi_mlme_set(sdev, dev, mib_data, mib_data_length); + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + kfree(mib_data); + break; + } + case UNIFI_GET_MIB: + { + struct net_device *dev = NULL; + + if (sdev->device_state != SLSI_DEVICE_STATE_STARTED) { + SLSI_ERR(sdev, "UNIFI_GET_MIB: Device not yet available\n"); + r = -EFAULT; + break; + } + + /* 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"); + r = -EFAULT; + break; + } + + /* 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"); + r = -EFAULT; + break; + } + + /* 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"); + r = -EFAULT; + break; + } + + /* check if length is valid */ + if (unlikely(mib_data_length > UDI_MIB_GET_LEN_MAX || mib_data_size > UDI_MIB_GET_LEN_MAX)) { + SLSI_ERR(sdev, "UNIFI_GET_MIB: size too long (mib_data_length:%u mib_data_size:%u)\n", mib_data_length, mib_data_size); + r = -EFAULT; + break; + } + + mib_data = kmalloc(mib_data_size, GFP_KERNEL); + + /* 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); + r = -EFAULT; + break; + } + + 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); + r = -EFAULT; + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + break; + } + if (slsi_mlme_get(sdev, dev, mib_data, mib_data_length, mib_data, mib_data_size, &mib_data_length)) { + kfree(mib_data); + r = -EINVAL; + SLSI_MUTEX_UNLOCK(sdev->netdev_add_remove_mutex); + break; + } + + 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); + r = -EINVAL; + break; + } + + /* 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); + r = -EINVAL; + break; + } + + /* 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); + r = -EINVAL; + break; + } + kfree(mib_data); + 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; + } + default: + SLSI_WARN(sdev, "Operation (%d) not supported\n", cmd); + r = -EINVAL; + } + + slsi_wakeunlock(&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 bool is_allowed_ip_frame(struct ethhdr *ehdr, u16 signal_id) +{ + u8 *ip_frame = ((u8 *)ehdr) + sizeof(struct ethhdr); + u8 *ip_data; + u16 ip_data_offset = 20; + /*u8 version = ip_frame[0] >> 4; */ + u8 hlen = ip_frame[0] & 0x0F; + /*u8 tos = ip_frame[1]; */ + /*u16 len = ip_frame[2] << 8 | frame[3]; */ + /*u16 id = ip_frame[4] << 8 | frame[5]; */ + /*u16 flags_foff = ip_frame[6] << 8 | frame[7]; */ + /*u8 ttl = ip_frame[8]; */ + u8 ip_proto = ip_frame[9]; + + /*u16 cksum = ip_frame[10] << 8 | frame[11]; */ + /*u8 *src_ip = &ip_frame[12];*/ + /*u8 *dest_ip = &ip_frame[16];*/ + + SLSI_UNUSED_PARAMETER(signal_id); + + if (hlen > 5) + ip_data_offset += (hlen - 5) * 4; + + ip_data = ip_frame + ip_data_offset; + + switch (ip_proto) { + case SLSI_IP_TYPE_UDP: + { + u16 srcport = ip_data[0] << 8 | ip_data[1]; + u16 dstport = ip_data[2] << 8 | ip_data[3]; + + SLSI_DBG3_NODEV(SLSI_UDI, "FILTER(0x%.4X) Key -> Proto(0x%.4X) -> IpProto(%d) ->UDP(s:%d, d:%d)\n", signal_id, ntohs(ehdr->h_proto), ip_proto, srcport, dstport); + if (srcport == SLSI_DHCP_CLIENT_PORT || srcport == SLSI_DHCP_SERVER_PORT || + dstport == SLSI_DHCP_CLIENT_PORT || dstport == SLSI_DHCP_SERVER_PORT) { + SLSI_DBG3_NODEV(SLSI_UDI, "FILTER(0x%.4X) Key -> Proto(0x%.4X) -> IpProto(%d) ->UDP(s:%d, d:%d) ALLOW\n", signal_id, ntohs(ehdr->h_proto), ip_proto, srcport, dstport); + return true; + } + } + default: + break; + } + + return false; +} + +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)) { + u16 frametype; + + SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X)\n", signal_id); + if (signal_id == MA_UNITDATA_REQ) + frametype = fapi_get_u16(skb, u.ma_unitdata_req.data_unit_descriptor); + else + frametype = fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor); + SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X) frametype:%d\n", signal_id, frametype); + + if (frametype == FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME) { + struct ethhdr *ehdr = (struct ethhdr *)fapi_get_data(skb); + + if (signal_id == MA_UNITDATA_REQ) + ehdr = (struct ethhdr *)fapi_get_data(skb); + + if (slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_EAPOL_ID) || + slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_KEY_ID)) { + SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X) Eap -> Proto(0x%.4X)\n", signal_id, ntohs(ehdr->h_proto)); + switch (ntohs(ehdr->h_proto)) { + case ETH_P_PAE: + case ETH_P_WAI: + SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X) Eap -> Proto(0x%.4X) ALLOW\n", signal_id, ntohs(ehdr->h_proto)); + goto allow_frame; + default: + break; + } + } + + if (slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_KEY_ID)) { + SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X) Key -> Proto(0x%.4X)\n", signal_id, ntohs(ehdr->h_proto)); + switch (ntohs(ehdr->h_proto)) { + case ETH_P_ARP: + SLSI_DBG4_NODEV(SLSI_UDI, "FILTER(0x%.4X) Key -> Proto(0x%.4X) -> Arp ALLOW\n", signal_id, ntohs(ehdr->h_proto)); + goto allow_frame; + case ETH_P_IP: + if (is_allowed_ip_frame(ehdr, signal_id)) + goto allow_frame; + default: + break; + } + } + } + if (frametype == FAPI_DATAUNITDESCRIPTOR_IEEE802_11_FRAME) + if (slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_MGT_ID)) + if (ieee80211_is_mgmt(fapi_get_mgmt(skb)->frame_control)) + goto allow_frame; + + 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; + } + + /* Special Filtering of MaPacketCfm. + * Only log ma_packet_cfm if the tx status != Success + */ + if (signal_id == MA_UNITDATA_CFM && slsi_cdev_unitdata_filter_allow(client, UDI_MA_UNITDATA_FILTER_ALLOW_CFM_ERROR_ID)) + if (fapi_get_u16(skb, u.ma_unitdata_cfm.transmission_status) == FAPI_TRANSMISSIONSTATUS_SUCCESSFUL) + 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; + +allow_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 = slsi_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; + + slsi_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 (uf_cdevs[minor]) + return -EINVAL; + } +#else + minor = slsi_get_minor(); +#endif + if (minor < 0) { + SLSI_ERR(sdev, "no minor numbers available\n"); + 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; +} diff --git a/drivers/net/wireless/scsc/udi.h b/drivers/net/wireless/scsc/udi.h new file mode 100644 index 000000000000..114b809c799e --- /dev/null +++ b/drivers/net/wireless/scsc/udi.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef __SLSI_UDI_H__ +#define __SLSI_UDI_H__ + +#include "dev.h" + +#ifdef SLSI_TEST_DEV + +/* Maximum number of nodes supported in UNIT TEST MODE + * arbitrarily set to 20, could increase this if needed + */ +#define SLSI_UDI_MINOR_NODES 20 + +#else +#define SLSI_UDI_MINOR_NODES 2 /* Maximum number of nodes supported. */ +#endif + +int slsi_udi_node_init(struct slsi_dev *sdev, struct device *parent); +int slsi_udi_node_deinit(struct slsi_dev *sdev); + +int slsi_udi_init(void); +int slsi_udi_deinit(void); +int slsi_kernel_to_user_space_event(struct slsi_log_client *log_client, u16 event, u32 data_length, const u8 *data); +int slsi_check_cdev_refs(void); + +#endif diff --git a/drivers/net/wireless/scsc/unifiio.h b/drivers/net/wireless/scsc/unifiio.h new file mode 100644 index 000000000000..d5cb1c091e13 --- /dev/null +++ b/drivers/net/wireless/scsc/unifiio.h @@ -0,0 +1,455 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2017 Samsung Electronics Co., Ltd and its Licensors. + * All rights reserved. + * + *****************************************************************************/ + +#ifndef __UNIFIIO_H__ +#define __UNIFIIO_H__ + +#include +/* The following include of linux/if.h is needed to get IFNAMSIZ. + * The conditional include before it is necessary to get the linux/if.h + * include to compile... + */ +#ifdef __KERNEL__ +#include +#include +#else +#include +#include +#endif +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define UNIFI_GET_UDI_ENABLE _IOR('u', 1, int) +#define UNIFI_SET_UDI_ENABLE _IOW('u', 2, int) +/* Values for UDI_ENABLE */ +#define UDI_ENABLE_DATA 0x1 +#define UDI_ENABLE_CONTROL 0x2 + +/* MIB set/get. Arg is a pointer to a varbind */ +#define UNIFI_GET_MIB _IOWR('u', 3, unsigned char) +#define UNIFI_SET_MIB _IOW('u', 4, unsigned char) +#define MAX_MIB_DATA_LENGTH 2048 + +/* Private IOCTLs */ +#define SIOCIWS80211POWERSAVEPRIV SIOCIWFIRSTPRIV +#define SIOCIWG80211POWERSAVEPRIV (SIOCIWFIRSTPRIV + 1) +#define SIOCIWS80211RELOADDEFAULTSPRIV (SIOCIWFIRSTPRIV + 2) +#define SIOCIWSCONFWAPIPRIV (SIOCIWFIRSTPRIV + 4) +#define SIOCIWSWAPIKEYPRIV (SIOCIWFIRSTPRIV + 6) +#define SIOCIWSSMEDEBUGPRIV (SIOCIWFIRSTPRIV + 8) +#define SIOCIWSAPCFGPRIV (SIOCIWFIRSTPRIV + 10) +#define SIOCIWSAPSTARTPRIV (SIOCIWFIRSTPRIV + 12) +#define SIOCIWSAPSTOPPRIV (SIOCIWFIRSTPRIV + 14) +#define SIOCIWSFWRELOADPRIV (SIOCIWFIRSTPRIV + 16) +#define SIOCIWSSTACKSTART (SIOCIWFIRSTPRIV + 18) +#define SIOCIWSSTACKSTOP (SIOCIWFIRSTPRIV + 20) + +#define IWPRIV_POWER_SAVE_MAX_STRING 32 +#define IWPRIV_SME_DEBUG_MAX_STRING 32 +#define IWPRIV_SME_MAX_STRING 120 + +/* Private configuration commands */ +#define UNIFI_CFG _IOWR('u', 5, unsigned char[4]) +/** + * <------------------ Read/Write Buffer --------------------> + * _____________________________________________________________ + * | Cmd | Arg | ... Buffer (opt) ... | + * ------------------------------------------------------------- + * <-- uint --><-- uint --><----- unsigned char buffer ------> + * + * Cmd: A enum unifi_cfg_command command. + * Arg: Out:Length if Cmd==UNIFI_CFG_GET + * In:PowerOnOff if Cmd==UNIFI_CFG_POWER + * In:PowerMode if Cmd==UNIFI_CFG_POWERSAVE + * In:Length if Cmd==UNIFI_CFG_FILTER + * In:WMM Qos Info if Cmd==UNIFI_CFG_WMM_QOS_INFO + * Buffer: Out:Data if Cmd==UNIFI_CFG_GET + * NULL if Cmd==UNIFI_CFG_POWER + * NULL if Cmd==UNIFI_CFG_POWERSAVE + * In:Filters if Cmd==UNIFI_CFG_FILTER + * + * where Filters is a struct uf_cfg_bcast_packet_filter structure + * followed by 0 - n struct tclas structures. The length of the struct tclas + * structures is obtained by struct uf_cfg_bcast_packet_filter::tclas_ies_length. + */ + +#define UNIFI_BUILD_TYPE _IOWR('u', 7, unsigned char) + +#define UNIFI_BUILD_NME 1 +#define UNIFI_BUILD_WEXT 2 +#define UNIFI_BUILD_AP 3 + +/* debugging */ +#define UNIFI_SET_DEBUG _IO('u', 0x11) +#define UNIFI_SET_TRACE _IO('u', 0x12) + +#define UNIFI_GET_INIT_STATUS _IOR('u', 0x15, int) +#define UNIFI_SET_UDI_LOG_CONFIG _IOR('u', 0x16, struct unifiio_udi_config_t) +#define UNIFI_SET_UDI_LOG_MASK _IOR('u', 0x18, struct unifiio_filter_t) + +#define UNIFI_INIT_HW _IOR('u', 0x13, unsigned char) +#define UNIFI_INIT_NETDEV _IOW('u', 0x14, unsigned char[6]) +#define UNIFI_SME_PRESENT _IOW('u', 0x19, int) + +#define UNIFI_CFG_PERIOD_TRAFFIC _IOW('u', 0x21, unsigned char[4]) +#define UNIFI_CFG_UAPSD_TRAFFIC _IOW('u', 0x22, unsigned char) + +#define UNIFI_SOFTMAC_CFG _IOWR('u', 0x30, unsigned char[4]) + +#define UNIFI_NETDEV_CFG _IOWR('u', 0x31, struct unifiio_netdev_cfg_t) +#define UNIFI_STREAM_CFG _IOWR('u', 0x32, struct unifiio_stream_cfg_t) +#define UNIFI_AMSDU_CFG _IOWR('u', 0x33, struct unifiio_amsdu_cfg_t) +#define UNIFI_BA_CFG _IOWR('u', 0x34, struct unifiio_ba_cfg_t) +#define UNIFI_SUSPEND_RESUME_CFG _IOWR('u', 0x35, struct unifiio_suspend_resume_cfg_t) +#define UNIFI_SRC_SINK_IOCTL _IOWR('u', 0x41, struct unifiio_src_sink_arg_t) + +#define src_sink_action uint16_t +#define SRC_SINK_ACTION_NONE ((src_sink_action)0x0000) +#define SRC_SINK_ACTION_ENABLE_BA ((src_sink_action)0x0001) +#define SRC_SINK_ACTION_SINK_START ((src_sink_action)0x0002) +#define SRC_SINK_ACTION_SINK_STOP ((src_sink_action)0x0003) +#define SRC_SINK_ACTION_GEN_START ((src_sink_action)0x0004) +#define SRC_SINK_ACTION_GEN_STOP ((src_sink_action)0x0005) +#define SRC_SINK_ACTION_SINK_REPORT ((src_sink_action)0x0006) +#define SRC_SINK_ACTION_GEN_REPORT ((src_sink_action)0x0007) +#define SRC_SINK_ACTION_SINK_REPORT_CACHED ((src_sink_action)0x0008) +#define SRC_SINK_ACTION_GEN_REPORT_CACHED ((src_sink_action)0x0009) +#define SRC_SINK_ACTION_LOOPBACK_START ((src_sink_action)0x000A) +#define SRC_SINK_ACTION_LOOPBACK_STOP ((src_sink_action)0x000B) + +#define src_sink_direction uint16_t +#define SRC_SINK_DIRECTION_TX ((src_sink_direction)0x0000) +#define SRC_SINK_DIRECTION_RX ((src_sink_direction)0x0001) + +#define src_sink_endpoint uint16_t +#define SRC_SINK_ENDPOINT_HOSTIO ((src_sink_endpoint)0x0000) +#define SRC_SINK_ENDPOINT_MACRAME ((src_sink_endpoint)0x0001) + +struct unifiio_src_sink_report { + /* total reporting time requested by the user in seconds*/ + uint32_t time; + /* requested report interval in microseconds */ + uint32_t interval; + + /* timestamp [jiffies in millisec] for each report returned by the driver */ + uint32_t timestamp; + /* actual interval used for the report calculations from the firmware */ + uint32_t duration; + /* number of packets during the above duration */ + uint32_t count; + /* number of octets during the above duration */ + uint32_t octet; + /* throughput in kbps */ + uint32_t kbps; + /* CPU idle ratios */ + uint16_t idle_ratio; + uint16_t interrupt_latency; + uint16_t free_kbytes; + /* only relevant for SRC a.k.a GEN mode */ + uint32_t failed_count; +}; + +/* fields specific to SRC a.k.a GEN IOCTLs */ +struct unifiio_src_sink_gen_config { + uint16_t size; + uint16_t use_streaming; + uint16_t pkts_per_intr; + uint32_t ipv4_dest; +}; + +/* fields specific to SRC/SINK CONFIG IOCTLs */ +struct unifiio_src_sink_config { + uint32_t interval; + uint16_t pkts_per_int; + union { + struct unifiio_src_sink_gen_config gen; + } u; +}; + +/* fields common to SRC/SINK IOCTLs */ +struct unifiio_src_sink_common { + /* one of SRC_SINK_ACTION_* for driver IOCTL */ + src_sink_action action; + uint16_t vif; + src_sink_direction direction; + src_sink_endpoint endpoint; +}; + +struct unifiio_src_sink_arg_t { + /* arg in */ + struct unifiio_src_sink_common common; + union { + struct unifiio_src_sink_config config; + struct unifiio_src_sink_report report; + } u; +}; + +/* Structure of data read from the unifi device. */ +struct udi_msg_t { + /* Length (in bytes) of entire structure including appended bulk data */ + int length; + + /* System time (in milliseconds) that signal was transferred */ + int timestamp; + + /* Direction in which signal was transferred. */ + int direction; +#define UDI_FROM_HOST 0 +#define UDI_TO_HOST 1 +#define UDI_CONFIG_IND 2 + + /* The length of the signal (in bytes) not including bulk data */ + int signal_length; + + /* Signal body follows, then any bulk data */ +}; + +/* Add these to the filter signal_ids to enable partial filtering of the MA_UNITDATA Data + * the top bit is set to indicate this is not a HIP Signal ID but a special Filter + */ +#define UDI_MA_UNITDATA_FILTER_ALLOW_MASK 0x8000 /* Filter MA_UNITDATA_REQ and MA_UNITDATA_IND */ +#define UDI_MA_UNITDATA_FILTER_ALLOW_MGT_ID 0x8001 /* Filter MA_UNITDATA_REQ and MA_UNITDATA_IND but Log management Frames */ +#define UDI_MA_UNITDATA_FILTER_ALLOW_KEY_ID 0x8002 /* Filter MA_UNITDATA_REQ and MA_UNITDATA_IND but Log Key Data Frames (Arp, Eapol, Dhcp etc) */ +#define UDI_MA_UNITDATA_FILTER_ALLOW_CFM_ERROR_ID 0x8004 /* Filter MA_UNITDATA_CFM but MA_UNITDATA_CFM(error) */ +#define UDI_MA_UNITDATA_FILTER_ALLOW_EAPOL_ID 0x8008 /* Filter MA_UNITDATA_REQ and MA_UNITDATA_IND but Log Eapol data */ + +/** + * Signals used to indicate to user space that the kernel module has been + * unloaded. The user space applications can based on this indication determine + * if they should unregister from the char device. + */ +#define UDI_DRV_SIGNAL_BASE 0xA000 +#define UDI_DRV_UNLOAD_IND (UDI_DRV_SIGNAL_BASE + 1) +#define UDI_DRV_DROPPED_FRAMES (UDI_DRV_SIGNAL_BASE + 2) +#define UDI_DRV_DROPPED_DATA_FRAMES (UDI_DRV_SIGNAL_BASE + 3) +#define UDI_DRV_SUSPEND_IND (UDI_DRV_SIGNAL_BASE + 4) +#define UDI_DRV_RESUME_IND (UDI_DRV_SIGNAL_BASE + 5) + +struct unifiio_udi_config_t { + uint16_t ma_unitdata_size_limit; /* if non-zero, the MA_UNITDATA_REQ and MA_UNITDATA_IND are capped by this size */ +}; + +struct unifiio_filter_t { + uint16_t log_listed_flag; /* if non-zero, log listed sigs and ignore others (otherwise vice versa) */ + uint16_t signal_ids_n; /* Number of elements in signal_ids[] */ + uint16_t signal_ids[5]; /* list of the signals to log */ +}; + +#define unifi_cfg_command uint32_t +#define UNIFI_CFG_GET ((unifi_cfg_command)0) +#define UNIFI_CFG_POWER ((unifi_cfg_command)1) +#define UNIFI_CFG_POWERSAVE ((unifi_cfg_command)2) +#define UNIFI_CFG_FILTER ((unifi_cfg_command)3) +#define UNIFI_CFG_POWERSUPPLY ((unifi_cfg_command)4) +#define UNIFI_CFG_WMM_QOSINFO ((unifi_cfg_command)5) +#define UNIFI_CFG_WMM_ADDTS ((unifi_cfg_command)6) +#define UNIFI_CFG_WMM_DELTS ((unifi_cfg_command)7) +#define UNIFI_CFG_STRICT_DRAFT_N ((unifi_cfg_command)8) +#define UNIFI_CFG_SET_AP_CONFIG ((unifi_cfg_command)9) +#define UNIFI_CFG_CORE_DUMP ((unifi_cfg_command)10) + +#define unifi_cfg_power uint32_t +#define UNIFI_CFG_POWER_UNSPECIFIED ((unifi_cfg_power)0) +#define UNIFI_CFG_POWER_OFF ((unifi_cfg_power)1) +#define UNIFI_CFG_POWER_ON ((unifi_cfg_power)2) + +#define unifi_cfg_powersupply uint32_t +#define UNIFI_CFG_POWERSUPPLY_UNSPECIFIED ((unifi_cfg_powersupply)0) +#define UNIFI_CFG_POWERSUPPLY_MAINS ((unifi_cfg_powersupply)1) +#define UNIFI_CFG_POWERSUPPLY_BATTERIES ((unifi_cfg_powersupply)2) + +#define unifi_cfg_powersave uint32_t +#define UNIFI_CFG_POWERSAVE_UNSPECIFIED ((unifi_cfg_powersave)0) +#define UNIFI_CFG_POWERSAVE_NONE ((unifi_cfg_powersave)1) +#define UNIFI_CFG_POWERSAVE_FAST ((unifi_cfg_powersave)2) +#define UNIFI_CFG_POWERSAVE_FULL ((unifi_cfg_powersave)3) +#define UNIFI_CFG_POWERSAVE_AUTO ((unifi_cfg_powersave)4) + +#define unifi_cfg_get uint32_t +#define UNIFI_CFG_GET_COEX ((unifi_cfg_get)0) +#define UNIFI_CFG_GET_POWER_MODE ((unifi_cfg_get)1) +#define UNIFI_CFG_GET_VERSIONS ((unifi_cfg_get)2) +#define UNIFI_CFG_GET_POWER_SUPPLY ((unifi_cfg_get)3) +#define UNIFI_CFG_GET_INSTANCE ((unifi_cfg_get)4) +#define UNIFI_CFG_GET_AP_CONFIG ((unifi_cfg_get)5) + +#define UNIFI_CFG_FILTER_NONE 0x0000 +#define UNIFI_CFG_FILTER_DHCP 0x0001 +#define UNIFI_CFG_FILTER_ARP 0x0002 +#define UNIFI_CFG_FILTER_NBNS 0x0004 +#define UNIFI_CFG_FILTER_NBDS 0x0008 +#define UNIFI_CFG_FILTER_CUPS 0x0010 +#define UNIFI_CFG_FILTER_ALL 0xFFFF + +#define uf_cfg_packet_filter_type uint32_t +#define UNIFI_CFG_FILTER_TYPE_NONE ((uf_cfg_packet_filter_type)0) +#define UNIFI_CFG_FILTER_TYPE_ACTIVE_HOST ((uf_cfg_packet_filter_type)1) +#define UNIFI_CFG_FILTER_TYPE_SUSPENDED_HOST ((uf_cfg_packet_filter_type)2) + +struct uf_cfg_bcast_packet_filter { + unsigned long filter_mode; /* as defined by HIP protocol */ + uf_cfg_packet_filter_type packet_filter_type; + unsigned char arp_filter; + unsigned char dhcp_filter; + unsigned long tclas_ies_length; /* length of tclas_ies in bytes */ + unsigned char tclas_ies[1]; /* variable length depending on above field */ +}; + +struct uf_cfg_ap_config { + uint8_t phySupportedBitmap; + uint8_t channel; + uint16_t beaconInterval; + uint8_t dtimPeriod; + bool wmmEnabled; + uint8_t shortSlotTimeEnabled; + uint16_t groupkeyTimeout; + bool strictGtkRekeyEnabled; + uint16_t gmkTimeout; + uint16_t responseTimeout; + uint8_t retransLimit; + uint8_t rxStbc; + bool rifsModeAllowed; + uint8_t dualCtsProtection; + uint8_t ctsProtectionType; + uint16_t maxListenInterval; +}; + +struct csr_wifi_cfg_coex_info { + bool hasTrafficData; + uint8_t currentTrafficType; + uint16_t currentPeriodMs; + uint8_t currentPowerSave; + uint16_t currentCoexPeriodMs; + uint16_t currentCoexLatencyMs; + bool hasBtDevice; + uint32_t currentBlackOutDurationUs; + uint32_t currentBlackOutPeriodUs; + uint8_t currentCoexScheme; +}; + +struct tcpip_clsfr { + __u8 cls_fr_type; + __u8 cls_fr_mask; + __u8 version; + __u8 source_ip_addr[4]; + __u8 dest_ip_addr[4]; + __u16 source_port; + __u16 dest_port; + __u8 dscp; + __u8 protocol; + __u8 reserved; +} __packed; +#define tcpip_clsfr_t struct tcpip_clsfr + +struct tclas { + __u8 element_id; + __u8 length; + __u8 user_priority; + tcpip_clsfr_t tcp_ip_cls_fr; +} __packed; + +#define CONFIG_IND_ERROR 0x01 +#define CONFIG_IND_EXIT 0x02 +#define CONFIG_SME_NOT_PRESENT 0x10 +#define CONFIG_SME_PRESENT 0x20 + +/* This is used by the UNIFI_NETDEV_CFG ioctl which is a Multi-netdev replacement for + * UNIFI_INIT_NETDEV. The all netdev are created statically and can be mapped/registered + * to a particular vif_index/MAC address/interface mode dynamically using this call. + * + * Configure operations (operation == 0): + * + * Attempting to operate on a negative interfaceTag is an attempt to + * create a NEW netdevice.A valid vif_index and macaddr must be supplied. + * interfaceMode will not be checked. The new interfaceTag and name will be + * copied back on success. + * + * Using an interfaceTag >=0 attempts to reconfigure an existing netdevice. The new + * vif_index, macaddr and interfaceMode will be applied if all is valid. Setting a + * vif_index of zero causes the given netdevice to be unregistered instead. + * The contents of the name[] field will be ignored as input, and the existing + * interface name copied back as output. + * + * Query operations (operation == 1): + * + * The first identifying field with a potentially valid value will be used to + * identify an interface, and if a match is found its values will be copied back + * into the structure and returned to the user. The fields are checked in this order: + * + * interfaceTag (valid if >= 0) + * vif_index (valid if non-zero) + * name (valid if first byte non-zero) + * macaddr (valid if not broadcast) + */ +struct unifiio_netdev_cfg_t { + uint8_t operation; /* 0 = configure, 1 = query */ + /* All other fields potentially bidirectional */ + int interfaceTag; /* <0 => create new, >=0 => reconfigure existing */ + uint8_t vif_index; /* VIF index to use (0 => remove netdev) */ + uint8_t interfaceMode; /* Values defined in csr_wifi_router_ctrl_prim.h */ + unsigned char macaddr[6]; /* MAC address */ + char name[IFNAMSIZ]; /* Interface name */ + unsigned char peer_macaddr[6]; /* peer MAC address */ + uint16_t association_id; /* Associate ID used for the peer */ +}; + +#define unifiio_stream_cfg_operation_type uint32_t +#define UNIFI_CFG_STREAM_OPERATION_TYPE_NONE ((unifiio_stream_cfg_operation_type)0) +#define UNIFI_CFG_STREAM_OPERATION_TYPE_CREATE ((unifiio_stream_cfg_operation_type)1) +#define UNIFI_CFG_STREAM_OPERATION_TYPE_STOP ((unifiio_stream_cfg_operation_type)2) +#define UNIFI_CFG_STREAM_OPERATION_TYPE_DELETE ((unifiio_stream_cfg_operation_type)3) + +struct unifiio_stream_cfg_t { + unifiio_stream_cfg_operation_type operation; + int interfaceTag; /* A valid interface tag */ + unsigned char peer_macaddr[6]; /* Peer STA MAC address */ + uint16_t tId; /* Traffic identifier user priority */ + uint16_t handle; /* Handle as indicated in MA-stream.indication */ + uint16_t size; /* Size as indicated in MA-stream.indication */ +}; + +#define unifiio_amsdu_cfg_operation_type uint32_t +#define UNIFI_CFG_AMSDU_OPERATION_TYPE_SET_MAX_SIZE ((unifiio_amsdu_cfg_operation_type)0) + +struct unifiio_amsdu_cfg_t { + unifiio_amsdu_cfg_operation_type operation; + int interfaceTag; /* A valid interface tag */ + unsigned char peer_macaddr[6]; /* Peer STA MAC address */ + uint16_t size; /* Maximum A-MSDU length for peer obtained from HT element */ +}; + +#define unifiio_ba_cfg_operation_type uint32_t +#define UNIFI_CFG_BA_SESSION_STOP ((unifiio_ba_cfg_operation_type)0) +#define UNIFI_CFG_BA_SESSION_START ((unifiio_ba_cfg_operation_type)1) + +struct unifiio_ba_cfg_t { + unifiio_ba_cfg_operation_type operation; + int interfaceTag; /* A valid interface tag */ + unsigned char peer_macaddr[6]; /* Peer STA MAC address */ + uint16_t tId; /* Traffic identifier */ + uint8_t role; /* Role - 0: originator, 1 - recipient */ + uint16_t window_size; /* Window size as negotiated in BA establish */ + uint16_t ssn; /* Starting sequence number as negotiated in BA establish */ +}; + +#define unifiio_suspend_resume_cfg_operation_type uint32_t +#define UNIFI_CFG_SUSPEND ((unifiio_suspend_resume_cfg_operation_type)0) +#define UNIFI_CFG_RESUME ((unifiio_suspend_resume_cfg_operation_type)1) +#define UNIFI_CFG_WAIT_FOR_RESUME ((unifiio_suspend_resume_cfg_operation_type)2) + +struct unifiio_suspend_resume_cfg_t { + unifiio_suspend_resume_cfg_operation_type operation; + unsigned timeout_ms; /* used with UNIFI_CFG_WAIT_FOR_RESUME only */ +}; + +#ifdef __cplusplus +} +#endif +#endif /* __UNIFIIO_H__ */ diff --git a/drivers/net/wireless/scsc/utils.h b/drivers/net/wireless/scsc/utils.h new file mode 100755 index 000000000000..b40653f2e037 --- /dev/null +++ b/drivers/net/wireless/scsc/utils.h @@ -0,0 +1,656 @@ +/****************************************************************************** + * + * Copyright (c) 2012 - 2018 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef SLSI_UTILS_H__ +#define SLSI_UTILS_H__ + +#include +#include +#include +#include +#include +#include + +#include "netif.h" +#include "wakelock.h" +#ifdef CONFIG_SCSC_SMAPPER +struct slsi_skb_cb { + u32 sig_length; + u32 data_length; + u32 frame_format; + u32 colour; + bool free_ma_unitdat; + struct sk_buff *skb_addr; +}; + +static inline struct slsi_skb_cb *slsi_skb_cb_get(struct sk_buff *skb) +{ + return (struct slsi_skb_cb *)skb->cb; +} + +static inline struct slsi_skb_cb *slsi_skb_cb_init(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct slsi_skb_cb) > sizeof(skb->cb)); + + memset(skb->cb, 0, sizeof(struct slsi_skb_cb)); + return slsi_skb_cb_get(skb); +} +#endif + +static inline u32 slsi_convert_tlv_data_to_value(u8 *data, u16 length) +{ + u32 value = 0; + int i; + + if (length > 4) + return 0; + for (i = 0; i < length; i++) + value |= ((u32)data[i]) << i * 8; + + return value; +} + +#ifdef __cplusplus +extern "C" { +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) +#define SLSI_ETHER_COPY(dst, src) ether_addr_copy((dst), (src)) +#define SLSI_ETHER_EQUAL(mac1, mac2) ether_addr_equal((mac1), (mac2)) +#else +#define SLSI_ETHER_COPY(dst, src) memcpy((dst), (src), ETH_ALEN) +#define SLSI_ETHER_EQUAL(mac1, mac2) (memcmp((mac1), (mac2), ETH_ALEN) == 0) +#endif + +extern uint slsi_sg_host_align_mask; +#define SLSI_HIP_FH_SIG_PREAMBLE_LEN 4 +#define SLSI_SKB_GET_ALIGNMENT_OFFSET(skb) (offset_in_page(skb->data + SLSI_NETIF_SKB_HEADROOM - SLSI_HIP_FH_SIG_PREAMBLE_LEN) \ + & slsi_sg_host_align_mask) + +/* Get the Compiler to ignore Unused parameters */ +#define SLSI_UNUSED_PARAMETER(x) ((void)(x)) + +/* Helper ERROR Macros */ +#define SLSI_ECR(func) \ + do { \ + int _err = (func); \ + if (_err != 0) { \ + SLSI_ERR_NODEV("e=%d\n", _err); \ + return _err; \ + } \ + } while (0) + +#define SLSI_EC(func) \ + do { \ + int _err = (func); \ + if (_err != 0) { \ + SLSI_ERR_NODEV("e=%d\n", _err); \ + return; \ + } \ + } while (0) + +#define SLSI_EC_GOTO(func, err, label) \ + do { \ + (err) = func; \ + if ((err) != 0) { \ + WARN_ON(1); \ + SLSI_ERR(sdev, "fail at line:%d\n", __LINE__); \ + goto label; \ + } \ + } while (0) + +/*------------------------------------------------------------------*/ +/* Endian conversion */ +/*------------------------------------------------------------------*/ +#define SLSI_BUFF_LE_TO_U16(ptr) (((u16)((u8 *)(ptr))[0]) | ((u16)((u8 *)(ptr))[1]) << 8) +#define SLSI_U16_TO_BUFF_LE(uint, ptr) \ + do { \ + u32 local_uint_tmp = (uint); \ + ((u8 *)(ptr))[0] = ((u8)((local_uint_tmp & 0x00FF))); \ + ((u8 *)(ptr))[1] = ((u8)(local_uint_tmp >> 8)); \ + } while (0) + +#define SLSI_U32_TO_BUFF_LE(uint, ptr) ((*(u32 *)ptr) = cpu_to_le32(uint)) + +#define SLSI_BUFF_LE_TO_U16_P(output, input) \ + do { \ + (output) = (u16)((((u16)(input)[1]) << 8) | ((u16)(input)[0])); \ + (input) += 2; \ + } while (0) + +#define SLSI_BUFF_LE_TO_U32_P(output, input) \ + do { \ + (output) = le32_to_cpu(*(u32 *)input); \ + (input) += 4; \ + } while (0) + +#define SLSI_U16_TO_BUFF_LE_P(output, input) \ + do { \ + (output)[0] = ((u8)((input) & 0x00FF)); \ + (output)[1] = ((u8)((input) >> 8)); \ + (output) += 2; \ + } while (0) + +#define SLSI_U32_TO_BUFF_LE_P(output, input) \ + do { \ + (*(u32 *)output) = cpu_to_le32(input); \ + (output) += 4; \ + } while (0) + +#ifdef CONFIG_SCSC_WLAN_SKB_TRACKING +void slsi_dbg_track_skb_init(void); +void slsi_dbg_track_skb_reset(void); +void slsi_dbg_track_skb_f(struct sk_buff *skb, gfp_t flags, const char *file, int line); +bool slsi_dbg_untrack_skb_f(struct sk_buff *skb, const char *file, int line); +bool slsi_dbg_track_skb_marker_f(struct sk_buff *skb, const char *file, int line); +#define slsi_dbg_track_skb(skb_, flags_) slsi_dbg_track_skb_f(skb_, flags_, __FILE__, __LINE__) +#define slsi_dbg_untrack_skb(skb_) slsi_dbg_untrack_skb_f(skb_, __FILE__, __LINE__) +#define slsi_dbg_track_skb_marker(skb_) slsi_dbg_track_skb_marker_f(skb_, __FILE__, __LINE__) +void slsi_dbg_track_skb_report(void); + +static inline struct sk_buff *slsi_dev_alloc_skb_f(unsigned int length, const char *file, int line) +{ + struct sk_buff *skb = dev_alloc_skb(SLSI_NETIF_SKB_HEADROOM + SLSI_NETIF_SKB_TAILROOM + length); + + if (skb) { +#ifdef CONFIG_SCSC_SMAPPER + slsi_skb_cb_init(skb); +#endif + skb_reserve(skb, SLSI_NETIF_SKB_HEADROOM - SLSI_SKB_GET_ALIGNMENT_OFFSET(skb)); + slsi_dbg_track_skb_f(skb, GFP_ATOMIC, file, line); + } + return skb; +} + +static inline struct sk_buff *slsi_alloc_skb_f(unsigned int size, gfp_t priority, const char *file, int line) +{ + struct sk_buff *skb = alloc_skb(size, priority); + + if (skb) { +#ifdef CONFIG_SCSC_SMAPPER + slsi_skb_cb_init(skb); +#endif + slsi_dbg_track_skb_f(skb, priority, file, line); + } + + return skb; +} + +static inline struct sk_buff *slsi_alloc_skb_headroom_f(unsigned int size, gfp_t priority, const char *file, int line) +{ + struct sk_buff *skb = alloc_skb(SLSI_NETIF_SKB_HEADROOM + SLSI_NETIF_SKB_TAILROOM + size, priority); + + if (skb) { +#ifdef CONFIG_SCSC_SMAPPER + slsi_skb_cb_init(skb); +#endif + skb_reserve(skb, SLSI_NETIF_SKB_HEADROOM - SLSI_SKB_GET_ALIGNMENT_OFFSET(skb)); + slsi_dbg_track_skb_f(skb, priority, file, line); + } + return skb; +} + +static inline void slsi_skb_unlink_f(struct sk_buff *skb, struct sk_buff_head *list, const char *file, int line) +{ + skb_unlink(skb, list); + slsi_dbg_track_skb_marker_f(skb, file, line); +} + +static inline void slsi_skb_queue_tail_f(struct sk_buff_head *list, struct sk_buff *skb, const char *file, int line) +{ + skb_queue_tail(list, skb); + slsi_dbg_track_skb_marker_f(skb, file, line); +} + +static inline void slsi_skb_queue_head_f(struct sk_buff_head *list, struct sk_buff *skb, const char *file, int line) +{ + skb_queue_head(list, skb); + slsi_dbg_track_skb_marker_f(skb, file, line); +} + +static inline struct sk_buff *slsi_skb_dequeue_f(struct sk_buff_head *list, const char *file, int line) +{ + struct sk_buff *skb = skb_dequeue(list); + + if (skb) + slsi_dbg_track_skb_marker_f(skb, file, line); + return skb; +} + +static inline struct sk_buff *slsi_skb_realloc_headroom_f(struct sk_buff *skb, unsigned int headroom, const char *file, int line) +{ + skb = skb_realloc_headroom(skb, headroom); + if (skb) { +#ifdef CONFIG_SCSC_SMAPPER + slsi_skb_cb_init(skb); +#endif + slsi_dbg_track_skb_f(skb, GFP_ATOMIC, file, line); + } + return skb; +} + +static inline struct sk_buff *slsi_skb_copy_f(struct sk_buff *skb, gfp_t priority, const char *file, int line) +{ + skb = skb_copy(skb, priority); + + if (skb) + slsi_dbg_track_skb_f(skb, priority, file, line); + return skb; +} + +static inline struct sk_buff *skb_copy_expand_f(struct sk_buff *skb, int newheadroom, int newtailroom, gfp_t priority, const char *file, int line) +{ + skb = skb_copy_expand(skb, newheadroom, newtailroom, priority); + + if (skb) + slsi_dbg_track_skb_f(skb, priority, file, line); + return skb; +} + +static inline struct sk_buff *slsi_skb_clone_f(struct sk_buff *skb, gfp_t priority, const char *file, int line) +{ + skb = skb_clone(skb, priority); + + if (skb) + slsi_dbg_track_skb_f(skb, priority, file, line); + return skb; +} + +static inline void slsi_kfree_skb_f(struct sk_buff *skb, const char *file, int line) +{ + /* If untrack fails we do not free the SKB + * This helps tracking bad pointers and double frees + */ +#ifdef CONFIG_SCSC_SMAPPER + struct slsi_skb_cb *cb; + + if (!skb) + return; + + cb = (struct slsi_skb_cb *)skb->cb; + + if (cb && !cb->free_ma_unitdat && cb->skb_addr && slsi_dbg_untrack_skb_f(cb->skb_addr, file, line)) { + kfree_skb(cb->skb_addr); + cb->skb_addr = NULL; + } +#endif + if (slsi_dbg_untrack_skb_f(skb, file, line)) + kfree_skb(skb); +} + +#define slsi_dev_alloc_skb(length_) slsi_dev_alloc_skb_f(length_, __FILE__, __LINE__) +#define slsi_alloc_skb(size_, priority_) slsi_alloc_skb_f(size_, priority_, __FILE__, __LINE__) +#define slsi_alloc_skb_headroom(size_, priority_) slsi_alloc_skb_headroom_f(size_, priority_, __FILE__, __LINE__) +#define slsi_skb_realloc_headroom(skb_, headroom_) slsi_skb_realloc_headroom_f(skb_, headroom_, __FILE__, __LINE__) +#define slsi_skb_copy(skb_, priority_) slsi_skb_copy_f(skb_, priority_, __FILE__, __LINE__) +#define slsi_skb_copy_expand(skb_, newheadroom_, newtailroom_, priority_) skb_copy_expand_f(skb_, newheadroom_, newtailroom_, priority_, __FILE__, __LINE__) +#define slsi_skb_clone(skb_, priority_) slsi_skb_clone_f(skb_, priority_, __FILE__, __LINE__) +#define slsi_kfree_skb(skb_) slsi_kfree_skb_f(skb_, __FILE__, __LINE__) +#define slsi_skb_unlink(skb_, list_) slsi_skb_unlink_f(skb_, list_, __FILE__, __LINE__) +#define slsi_skb_queue_tail(list_, skb_) slsi_skb_queue_tail_f(list_, skb_, __FILE__, __LINE__) +#define slsi_skb_queue_head(list_, skb_) slsi_skb_queue_head_f(list_, skb_, __FILE__, __LINE__) +#define slsi_skb_dequeue(list_) slsi_skb_dequeue_f(list_, __FILE__, __LINE__) + +static inline void slsi_skb_queue_purge(struct sk_buff_head *list) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(list)) != NULL) + slsi_kfree_skb(skb); +} + +#else +#define slsi_dbg_track_skb_init() +#define slsi_dbg_track_skb_reset() +#define slsi_dbg_track_skb(skb_, flags_) +#define slsi_dbg_untrack_skb(skb_) +#define slsi_dbg_track_skb_marker(skb_) +#define slsi_dbg_track_skb_report() + +static inline struct sk_buff *slsi_dev_alloc_skb_f(unsigned int length, const char *file, int line) +{ + struct sk_buff *skb = dev_alloc_skb(SLSI_NETIF_SKB_HEADROOM + SLSI_NETIF_SKB_TAILROOM + length); + + SLSI_UNUSED_PARAMETER(file); + SLSI_UNUSED_PARAMETER(line); + if (skb) { +#ifdef CONFIG_SCSC_SMAPPER + slsi_skb_cb_init(skb); +#endif + skb_reserve(skb, SLSI_NETIF_SKB_HEADROOM - SLSI_SKB_GET_ALIGNMENT_OFFSET(skb)); + } + return skb; +} + +static inline struct sk_buff *slsi_alloc_skb_f(unsigned int size, gfp_t priority, const char *file, int line) +{ + struct sk_buff *skb = alloc_skb(size, priority); + + SLSI_UNUSED_PARAMETER(file); + SLSI_UNUSED_PARAMETER(line); +#ifdef CONFIG_SCSC_SMAPPER + if (skb) + slsi_skb_cb_init(skb); +#endif + return skb; +} + +static inline struct sk_buff *slsi_alloc_skb_headroom_f(unsigned int size, gfp_t priority, const char *file, int line) +{ + struct sk_buff *skb = alloc_skb(SLSI_NETIF_SKB_HEADROOM + SLSI_NETIF_SKB_TAILROOM + size, priority); + + SLSI_UNUSED_PARAMETER(file); + SLSI_UNUSED_PARAMETER(line); + + if (skb) { +#ifdef CONFIG_SCSC_SMAPPER + slsi_skb_cb_init(skb); +#endif + skb_reserve(skb, SLSI_NETIF_SKB_HEADROOM - SLSI_SKB_GET_ALIGNMENT_OFFSET(skb)); + } + return skb; +} + +static inline void slsi_kfree_skb_f(struct sk_buff *skb) +{ + /* If untrack fails we do not free the SKB + * This helps tracking bad pointers and double frees + */ +#ifdef CONFIG_SCSC_SMAPPER + struct slsi_skb_cb *cb; + + if (!skb) + return; + + cb = (struct slsi_skb_cb *)skb->cb; + + if (cb && !cb->free_ma_unitdat && cb->skb_addr) { + kfree_skb(cb->skb_addr); + cb->skb_addr = NULL; + } +#endif + kfree_skb(skb); +} + +#define slsi_dev_alloc_skb(length_) slsi_dev_alloc_skb_f(length_, __FILE__, __LINE__) +#define slsi_alloc_skb(size_, priority_) slsi_alloc_skb_f(size_, priority_, __FILE__, __LINE__) +#define slsi_alloc_skb_headroom(size_, priority_) slsi_alloc_skb_headroom_f(size_, priority_, __FILE__, __LINE__) +#define slsi_skb_realloc_headroom(skb_, headroom_) skb_realloc_headroom(skb_, headroom_) +#define slsi_skb_copy(skb_, priority_) skb_copy(skb_, priority_) +#define slsi_skb_copy_expand(skb_, newheadroom_, newtailroom_, priority_) skb_copy_expand(skb_, newheadroom_, newtailroom_, priority_) +#define slsi_skb_clone(skb_, priority_) skb_clone(skb_, priority_) +#define slsi_kfree_skb(skb_) slsi_kfree_skb_f(skb_) +#define slsi_skb_unlink(skb_, list_) skb_unlink(skb_, list_) +#define slsi_skb_queue_tail(list_, skb_) skb_queue_tail(list_, skb_) +#define slsi_skb_queue_head(list_, skb_) skb_queue_head(list_, skb_) +#define slsi_skb_dequeue(list_) skb_dequeue(list_) +#define slsi_skb_queue_purge(list_) slsi_skb_queue_purge(list_) + +static inline void slsi_skb_queue_purge(struct sk_buff_head *list) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(list)) != NULL) + slsi_kfree_skb(skb); +} + +#endif + +struct slsi_spinlock { + /* a std spinlock */ + spinlock_t lock; + unsigned long flags; +}; + +/* Spinlock create can't fail, so return success regardless. */ +static inline void slsi_spinlock_create(struct slsi_spinlock *lock) +{ + spin_lock_init(&lock->lock); +} + +static inline void slsi_spinlock_lock(struct slsi_spinlock *lock) +{ + spin_lock_bh(&lock->lock); +} + +static inline void slsi_spinlock_unlock(struct slsi_spinlock *lock) +{ + spin_unlock_bh(&lock->lock); +} + +struct slsi_dev; +struct slsi_skb_work { + struct slsi_dev *sdev; + struct net_device *dev; /* This can be NULL */ + struct workqueue_struct *workqueue; + struct work_struct work; + struct sk_buff_head queue; + void __rcu *sync_ptr; +}; + +static inline int slsi_skb_work_init(struct slsi_dev *sdev, struct net_device *dev, struct slsi_skb_work *work, const char *name, void (*func)(struct work_struct *work)) +{ + rcu_assign_pointer(work->sync_ptr, (void *)sdev); + work->sdev = sdev; + work->dev = dev; + skb_queue_head_init(&work->queue); + INIT_WORK(&work->work, func); + work->workqueue = alloc_ordered_workqueue(name, 0); + + if (!work->workqueue) + return -ENOMEM; + return 0; +} + +static inline void slsi_skb_schedule_work(struct slsi_skb_work *work) +{ + queue_work(work->workqueue, &work->work); +} + +static inline void slsi_skb_work_enqueue_l(struct slsi_skb_work *work, struct sk_buff *skb) +{ + void *sync_ptr; + + rcu_read_lock(); + + sync_ptr = rcu_dereference(work->sync_ptr); + + if (WARN_ON(!sync_ptr)) { + slsi_kfree_skb(skb); + rcu_read_unlock(); + return; + } + skb_queue_tail(&work->queue, skb); + slsi_skb_schedule_work(work); + + rcu_read_unlock(); +} + +static inline struct sk_buff *slsi_skb_work_dequeue_l(struct slsi_skb_work *work) +{ + return skb_dequeue(&work->queue); +} + +static inline void slsi_skb_work_deinit(struct slsi_skb_work *work) +{ + rcu_read_lock(); + + if (WARN_ON(!work->sync_ptr)) { + rcu_read_unlock(); + return; + } + + rcu_assign_pointer(work->sync_ptr, NULL); + rcu_read_unlock(); + + synchronize_rcu(); + flush_workqueue(work->workqueue); + destroy_workqueue(work->workqueue); + work->workqueue = NULL; + slsi_skb_queue_purge(&work->queue); +} + +static inline void slsi_cfg80211_put_bss(struct wiphy *wiphy, struct cfg80211_bss *bss) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)) + cfg80211_put_bss(wiphy, bss); +#else + cfg80211_put_bss(bss); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)) */ +} + +#ifdef CONFIG_SCSC_WLAN_SKB_TRACKING +static inline void slsi_skb_work_enqueue_f(struct slsi_skb_work *work, struct sk_buff *skb, const char *file, int line) +{ + slsi_dbg_track_skb_marker_f(skb, file, line); + slsi_skb_work_enqueue_l(work, skb); +} + +static inline struct sk_buff *slsi_skb_work_dequeue_f(struct slsi_skb_work *work, const char *file, int line) +{ + struct sk_buff *skb; + + skb = slsi_skb_work_dequeue_l(work); + if (skb) + slsi_dbg_track_skb_marker_f(skb, file, line); + return skb; +} + +#define slsi_skb_work_enqueue(work_, skb_) slsi_skb_work_enqueue_f(work_, skb_, __FILE__, __LINE__) +#define slsi_skb_work_dequeue(work_) slsi_skb_work_dequeue_f(work_, __FILE__, __LINE__) +#else +#define slsi_skb_work_enqueue(work_, skb_) slsi_skb_work_enqueue_l(work_, skb_) +#define slsi_skb_work_dequeue(work_) slsi_skb_work_dequeue_l(work_) +#endif + +static inline void slsi_eth_zero_addr(u8 *addr) +{ + memset(addr, 0x00, ETH_ALEN); +} + +static inline void slsi_eth_broadcast_addr(u8 *addr) +{ + memset(addr, 0xff, ETH_ALEN); +} + +static inline int slsi_str_to_int(char *str, int *result) +{ + int i = 0; + + *result = 0; + if ((str[i] == '-') || ((str[i] >= '0') && (str[i] <= '9'))) { + if (str[0] == '-') + i++; + while (str[i] >= '0' && str[i] <= '9') { + *result *= 10; + *result += (int)str[i++] - '0'; + } + + *result = ((str[0] == '-') ? (-(*result)) : *result); + } + return i; +} + +#define P80211_OUI_LEN 3 + +struct ieee80211_snap_hdr { + u8 dsap; /* always 0xAA */ + u8 ssap; /* always 0xAA */ + u8 ctrl; /* always 0x03 */ + u8 oui[P80211_OUI_LEN]; /* organizational universal id */ +} __packed; + +struct msdu_hdr { + unsigned char da[ETH_ALEN]; + unsigned char sa[ETH_ALEN]; + __be16 length; + struct ieee80211_snap_hdr snap; + __be16 ether_type; +} __packed; + +#define ETHER_TYPE_SIZE 2 +#define MSDU_HLEN sizeof(struct msdu_hdr) +#define MSDU_LENGTH (sizeof(struct ieee80211_snap_hdr) + sizeof(__be16)) + +static inline int slsi_skb_msdu_to_ethhdr(struct sk_buff *skb) +{ + struct ethhdr *eth; + struct msdu_hdr *msdu; + + unsigned char da[ETH_ALEN]; + unsigned char sa[ETH_ALEN]; + __be16 proto; + + msdu = (struct msdu_hdr *)skb->data; + SLSI_ETHER_COPY(da, msdu->da); + SLSI_ETHER_COPY(sa, msdu->sa); + proto = msdu->ether_type; + + skb_pull(skb, MSDU_HLEN); + + eth = (struct ethhdr *)skb_push(skb, ETH_HLEN); + + SLSI_ETHER_COPY(eth->h_dest, da); + SLSI_ETHER_COPY(eth->h_source, sa); + eth->h_proto = proto; + + return 0; +} + +static inline int slsi_skb_ethhdr_to_msdu(struct sk_buff *skb) +{ + struct ethhdr *eth; + struct msdu_hdr *msdu; + unsigned int len; + __be16 ether_type; + + if (skb_headroom(skb) < (MSDU_HLEN - ETH_HLEN)) + return -EINVAL; + + eth = eth_hdr(skb); + ether_type = eth->h_proto; + + len = skb->len; + + skb_pull(skb, ETH_HLEN); + + msdu = (struct msdu_hdr *)skb_push(skb, MSDU_HLEN); + + SLSI_ETHER_COPY(msdu->da, eth->h_dest); + SLSI_ETHER_COPY(msdu->sa, eth->h_source); + msdu->length = htons(len - ETH_HLEN + MSDU_LENGTH); + memcpy(&msdu->snap, rfc1042_header, sizeof(struct ieee80211_snap_hdr)); + msdu->ether_type = ether_type; + + return 0; +} + +static inline u32 slsi_get_center_freq1(struct slsi_dev *sdev, u16 chann_info, u16 center_freq) +{ + u32 center_freq1 = 0x0000; + + SLSI_UNUSED_PARAMETER(sdev); + + switch (chann_info & 0xFF) { + case 40: + center_freq1 = center_freq - 20 * ((chann_info & 0xFF00) >> 8) + 10; + break; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9)) + case 80: + center_freq1 = center_freq - 20 * ((chann_info & 0xFF00) >> 8) + 30; + break; +#endif + default: + break; + } + return center_freq1; +} + +#ifdef __cplusplus +} +#endif + +#endif /* SLSI_UTILS_H__ */ diff --git a/drivers/net/wireless/scsc/wakelock.c b/drivers/net/wireless/scsc/wakelock.c new file mode 100644 index 000000000000..164f89182549 --- /dev/null +++ b/drivers/net/wireless/scsc/wakelock.c @@ -0,0 +1,117 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd and its Licensors. + * All rights reserved. + * + ****************************************************************************/ +#include +#include +#include "dev.h" +#include "debug.h" +#include "wakelock.h" +#include "utils.h" + +void slsi_wakelock(struct slsi_wake_lock *lock) +{ +#ifdef CONFIG_WAKELOCK + unsigned long flags; + + if (lock == NULL) { + SLSI_ERR_NODEV("Failed to take wakelock, lock is NULL"); + return; + } + spin_lock_irqsave(&lock->wl_spinlock, flags); + if (!lock->counter) + wake_lock(&lock->wl); + + lock->counter++; + spin_unlock_irqrestore(&lock->wl_spinlock, flags); +#else + SLSI_UNUSED_PARAMETER(lock); +#endif +} + +void slsi_wakeunlock(struct slsi_wake_lock *lock) +{ +#ifdef CONFIG_WAKELOCK + unsigned long flags; + + if (lock == NULL) { + SLSI_ERR_NODEV("Failed to unlock the wakelock, lock is NULL"); + return; + } + spin_lock_irqsave(&lock->wl_spinlock, flags); + + if (lock->counter) { + lock->counter--; + if (!lock->counter) + wake_unlock(&lock->wl); + } else { + SLSI_ERR_NODEV("Wakelock has already released!"); + } + spin_unlock_irqrestore(&lock->wl_spinlock, flags); +#else + SLSI_UNUSED_PARAMETER(lock); +#endif +} + +void slsi_wakelock_timeout(struct slsi_wake_lock *lock, int timeout) +{ +#ifdef CONFIG_WAKELOCK + if (lock == NULL) { + SLSI_ERR_NODEV("Failed to take wakelock timeout, lock is NULL"); + return; + } + lock->counter = 1; + wake_lock_timeout(&lock->wl, msecs_to_jiffies(timeout)); +#else + SLSI_UNUSED_PARAMETER(lock); + SLSI_UNUSED_PARAMETER(timeout); +#endif +} + +int slsi_is_wakelock_active(struct slsi_wake_lock *lock) +{ +#ifdef CONFIG_WAKELOCK + if (lock == NULL) { + SLSI_ERR_NODEV("Failed to check wakelock status, lock is NULL"); + return 0; + } + + if (wake_lock_active(&lock->wl)) + return 1; +#else + SLSI_UNUSED_PARAMETER(lock); +#endif + return 0; +} + +void slsi_wakelock_exit(struct slsi_wake_lock *lock) +{ +#ifdef CONFIG_WAKELOCK + if (lock == NULL) { + SLSI_ERR_NODEV("Failed to destroy the wakelock, lock is NULL"); + return; + } + + wake_lock_destroy(&lock->wl); +#else + SLSI_UNUSED_PARAMETER(lock); +#endif +} + +void slsi_wakelock_init(struct slsi_wake_lock *lock, char *name) +{ +#ifdef CONFIG_WAKELOCK + if (lock == NULL) { + SLSI_ERR_NODEV("Failed to init the wakelock, lock is NULL"); + return; + } + lock->counter = 0; + wake_lock_init(&lock->wl, WAKE_LOCK_SUSPEND, name); + spin_lock_init(&lock->wl_spinlock); +#else + SLSI_UNUSED_PARAMETER(lock); + SLSI_UNUSED_PARAMETER(name); +#endif +} diff --git a/drivers/net/wireless/scsc/wakelock.h b/drivers/net/wireless/scsc/wakelock.h new file mode 100644 index 000000000000..1c6a14a4b15f --- /dev/null +++ b/drivers/net/wireless/scsc/wakelock.h @@ -0,0 +1,31 @@ +/***************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. and its Licensors. + * All rights reserved. + * + ****************************************************************************/ + +#ifndef __SLSI_WAKELOCK_H__ +#define __SLSI_WAKELOCK_H__ + +#ifdef CONFIG_WAKELOCK +#include +#endif +#include + +struct slsi_wake_lock { +#ifdef CONFIG_WAKELOCK + struct wake_lock wl; +#endif + /* Spinlock to synchronize the access of the counter */ + spinlock_t wl_spinlock; + int counter; +}; + +void slsi_wakelock(struct slsi_wake_lock *lock); +void slsi_wakeunlock(struct slsi_wake_lock *lock); +void slsi_wakelock_timeout(struct slsi_wake_lock *lock, int timeout); +int slsi_is_wakelock_active(struct slsi_wake_lock *lock); +void slsi_wakelock_exit(struct slsi_wake_lock *lock); +void slsi_wakelock_init(struct slsi_wake_lock *lock, char *name); +#endif diff --git a/drivers/net/wireless/scsc/wl_result.h b/drivers/net/wireless/scsc/wl_result.h new file mode 100644 index 000000000000..2fbcf1124371 --- /dev/null +++ b/drivers/net/wireless/scsc/wl_result.h @@ -0,0 +1,28 @@ +/***************************************************************************** + * + * Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + * WLAN result codes + * + ****************************************************************************/ + +#ifndef WL_RESULT_H__ +#define WL_RESULT_H__ + +#define CsrResult u16 + +#define CSR_RESULT_SUCCESS ((CsrResult)0x0000) +#define CSR_RESULT_FAILURE ((CsrResult)0xffff) + +#define CSR_WIFI_HIP_RESULT_INVALID_VALUE ((CsrResult)1) /* Invalid argument value */ +#define CSR_WIFI_HIP_RESULT_NO_DEVICE ((CsrResult)2) /* The specified device is no longer present */ +#define CSR_WIFI_HIP_RESULT_NO_SPACE ((CsrResult)3) /* A queue or buffer is full */ +#define CSR_WIFI_HIP_RESULT_NO_MEMORY ((CsrResult)4) /* Fatal error, no memory */ +#define CSR_WIFI_HIP_RESULT_RANGE ((CsrResult)5) /* Request exceeds the range of a file or a buffer */ +#define CSR_WIFI_HIP_RESULT_NOT_FOUND ((CsrResult)6) /* A file (typically a f/w patch) is not found */ +#define CSR_WIFI_PS_RESULT_FULL ((CsrResult)7) /* Signal successfully queued, queue now full. */ +#define CSR_WIFI_PS_QSIG_NONE ((CsrResult)8) /* no qsig found, queue empty */ +#define CSR_WIFI_PS_QSIG_NO_SPACE ((CsrResult)9) /* no room for outstanding qsigs */ +#define CSR_WIFI_PS_QS_PAUSED_UNTIL_BOT_UPDATE ((CsrResult)10) /* BOT has disabled the qset and waiting for updates from the firmware */ + +#endif diff --git a/include/scsc/api/asmhcp.h b/include/scsc/api/asmhcp.h new file mode 100755 index 000000000000..dfb8cac4e164 --- /dev/null +++ b/include/scsc/api/asmhcp.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/**************************************************************************** + * FILE + * asmhcp.h - ANT Shared Memory Host Controller Protocol + * + * DESCRIPTION + * This file specifies the layout of the ANT Shared Memory + * Host Controller Protocol + */ + +#ifndef __ASMHCP_H__ +#define __ASMHCP_H__ + +#define ASMHCP_TRANSFER_RING_DATA_SIZE (16) +#define ASMHCP_TRANSFER_RING_CMD_SIZE (16) + +#define ASMHCP_BUFFER_SIZE (258) + +struct ASMHCP_TD_CONTROL { + uint16_t length; + uint8_t data[ASMHCP_BUFFER_SIZE]; +}; + +struct ASMHCP_HEADER { + /* AP RW - R4 RO - 64 octets */ + uint32_t magic_value; /* 0x00 */ + uint32_t mailbox_data_ctr_driv_read; /* 0x04 */ + uint32_t mailbox_data_driv_ctr_write; /* 0x08 */ + uint32_t mailbox_cmd_ctr_driv_read; /* 0x0C */ + uint32_t mailbox_cmd_driv_ctr_write; /* 0x10 */ + uint16_t ap_to_bg_int_src; /* 0x14 */ + uint16_t bg_to_ap_int_src; /* 0x16 */ + uint32_t mxlog_filter; /* 0x18 */ + uint32_t firmware_control; /* 0x1C */ + uint8_t reserved1[0x20]; /* 0x20 */ + + /* AP RO - R4 RW - 64 octets */ + uint32_t mailbox_cmd_driv_ctr_read; /* 0x40 */ + uint32_t mailbox_cmd_ctr_driv_write; /* 0x44 */ + uint32_t mailbox_data_driv_ctr_read; /* 0x48 */ + uint32_t mailbox_data_ctr_driv_write; /* 0x4C */ + uint32_t firmware_features; /* 0x50 */ + uint16_t panic_deathbed_confession; /* 0x54 */ + uint8_t reserved2[0x2A]; /* 0x56 */ +}; + +struct ASMHCP_PROTOCOL { + /* header offset: 0x00000000 */ + volatile struct ASMHCP_HEADER header; + /* from controller */ + struct ASMHCP_TD_CONTROL /* offset: 0x00000080 */ + cmd_controller_driver_transfer_ring[ASMHCP_TRANSFER_RING_CMD_SIZE]; + struct ASMHCP_TD_CONTROL /* offset: 0x000008A0 */ + data_controller_driver_transfer_ring[ASMHCP_TRANSFER_RING_DATA_SIZE]; + + /* Padding used to ensure minimum 32 octets between sections */ + uint8_t reserved[0x20]; /* offset: 0x000010C0 */ + + /* from driver */ + struct ASMHCP_TD_CONTROL /* offset: 0x000010E0 */ + cmd_driver_controller_transfer_ring[ASMHCP_TRANSFER_RING_CMD_SIZE]; + struct ASMHCP_TD_CONTROL /* offset: 0x00001900 */ + data_driver_controller_transfer_ring[ASMHCP_TRANSFER_RING_DATA_SIZE]; +}; + +#define ASMHCP_PROTOCOL_MAGICVALUE \ + ((ASMHCP_TRANSFER_RING_DATA_SIZE | (ASMHCP_TRANSFER_RING_CMD_SIZE << 4) | \ + (offsetof(struct ASMHCP_PROTOCOL, cmd_driver_controller_transfer_ring) << 15)) ^ \ + sizeof(struct ASMHCP_PROTOCOL)) + +#endif /* __ASMHCP_H__ */ diff --git a/include/scsc/api/bhcs.h b/include/scsc/api/bhcs.h new file mode 100644 index 000000000000..7101db580506 --- /dev/null +++ b/include/scsc/api/bhcs.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/**************************************************************************** + * FILE + * bhcs.h - Bluetooth Host Configuration Structure + * + * DESCRIPTION + * This file specifies the layout of the Bluetooth Host Configuration + * Structure. The structure is written by the host and passed to the + * firmware as an argument to the service start callback function in + * the form of an offset that must be converted to a local address. + * + * ASSUMPTIONS + * The host and the firmware has the same endiannes. + * The ABI on the host and the firmware results in the same memory + * layout of the defined structure. + * + */ + +#ifndef BHCS_H__ +#define BHCS_H__ + +/* The version of the BHCS structure. Must be written to the version field + * by the host and confirmed to match the define by the firmware. Increment + * the version when changing the layout of the structure. This also serves + * as a rudimentary endianess check. */ +#define BHCS_VERSION 2 + +struct BHCS { + uint32_t version; /* BHCS_VERSION */ + uint32_t bsmhcp_protocol_offset; /* BSMHCP_PROTOCOL structure offset */ + uint32_t bsmhcp_protocol_length; /* BSMHCP_PROTOCOL structure length */ + uint32_t configuration_offset; /* Binary configuration data offset */ + uint32_t configuration_length; /* Binary configuration data length */ + uint32_t bluetooth_address_lap; /* Lower Address Part 00..23 */ + uint8_t bluetooth_address_uap; /* Upper Address Part 24..31 */ + uint16_t bluetooth_address_nap; /* Non-significant 32..47 */ +}; + +#endif /* BHCS_H__ */ diff --git a/include/scsc/api/bsmhcp.h b/include/scsc/api/bsmhcp.h new file mode 100755 index 000000000000..b1bbbfc5ceaf --- /dev/null +++ b/include/scsc/api/bsmhcp.h @@ -0,0 +1,325 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +/**************************************************************************** + * FILE + * bsmhcp.h - Bluetooth Shared Memory Host Controller Protocol + * + * DESCRIPTION + * This file specifies the layout of the Bluetooth Shared Memory + * Host Controller Protocol as described in SC-505753-DD + */ + +#ifndef __BSMHCP_H__ +#define __BSMHCP_H__ + +#define BSMHCP_TRANSFER_RING_CMD_SIZE (8) +#define BSMHCP_TRANSFER_RING_EVT_SIZE (32) +#define BSMHCP_TRANSFER_RING_ACL_SIZE (32) +#define BSMHCP_TRANSFER_RING_AVDTP_SIZE (16) +#define BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE (8) + +/* Of the buffers in BSMHCP_TRANSFER_RING_ACL_SIZE, reserve a number for ULP + * operation. */ +#define BSMHCP_TRANSFER_RING_ACL_ULP_RESERVED (4) +#define BSMHCP_TRANSFER_RING_SCO_SIZE (0) +#define BSMHCP_TRANSFER_RING_ACL_COUNT (36) +#define BSMHCP_TRANSFER_RING_TIMING_COUNT (64) + +#define BSMHCP_CMD_EVT_BUFFER_SIZE (258) +#define BSMHCP_ACL_BUFFER_SIZE (1024) +#define BSMHCP_IQ_REPORT_BUFFER_SIZE (164) + +#define BSMHCP_ACL_PACKET_SIZE (1021) +#define BSMHCP_ULP_PACKET_SIZE BSMHCP_ACL_PACKET_SIZE +#define BSMHCP_SCO_PACKET_SIZE (0) + +#define BSMHCP_DATA_BUFFER_CMD_SIZE \ + BSMHCP_TRANSFER_RING_CMD_SIZE +#define BSMHCP_DATA_BUFFER_EVT_SIZE \ + BSMHCP_TRANSFER_RING_EVT_SIZE +#define BSMHCP_DATA_BUFFER_TX_ACL_SIZE \ + (BSMHCP_TRANSFER_RING_ACL_SIZE - 2) +#define BSMHCP_DATA_BUFFER_RX_ACL_SIZE \ + BSMHCP_TRANSFER_RING_ACL_SIZE + +#define BSMHCP_EVENT_TYPE_NONE (0x00) +#define BSMHCP_EVENT_TYPE_CONNECTED (0x01) +#define BSMHCP_EVENT_TYPE_DISCONNECTED (0x02) + +#define BSMHCP_ACL_BC_FLAG_BCAST_NON (0x00) +#define BSMHCP_ACL_BC_FLAG_BCAST_ACTIVE (0x40) +#define BSMHCP_ACL_BC_FLAG_BCAST_ALL (0x80) +#define BSMHCP_ACL_BC_FLAG_BCAST_RSVD (0xC0) +#define BSMHCP_ACL_BC_FLAG_BCAST_MASK (0xC0) + +#define BSMHCP_ACL_PB_FLAG_START_NONFLUSH (0x00) +#define BSMHCP_ACL_PB_FLAG_CONT (0x10) +#define BSMHCP_ACL_PB_FLAG_START_FLUSH (0x20) +#define BSMHCP_ACL_PB_FLAG_RSVD_3 (0x30) +#define BSMHCP_ACL_PB_FLAG_MASK (0x30) + +#define BSMHCP_ACL_L2CAP_FLAG_NON (0x00) +#define BSMHCP_ACL_L2CAP_FLAG_END (0x01) +#define BSMHCP_ACL_L2CAP_FLAG_MASK (0x01) + +#define BSMHCP_SERVICE_BT_STATE_INACTIVE (0x00) +#define BSMHCP_SERVICE_BT_STATE_ACTIVE (0x01) + +#define BSMHCP_CONTROLLER_STATE_ACTIVE (0x00000001) + +#define BSMHCP_HCI_CONNECTION_HANDLE_LOOPBACK (0x4000) + +#define BSMHCP_ALIGNMENT (32) + +#define BSMHCP_FEATURE_LPA2DP (0x00000001) +#define BSMHCP_FEATURE_M4_INTERRUPTS (0x00000002) +#define BSMHCP_FEATURE_FW_INFORMATION (0x00000004) +#define BSMHCP_FEATURE_AVDTP_TRANSFER_RING (0x00000008) + +#define BSMHCP_CONTROL_START_PANIC (0x10DEAD01) +#define BSMHCP_CONTROL_STOP_PANIC (0x0201DEAD) +#define BSMHCP_CONTROL_CONNECTION_PANIC (0xDEAD2002) + +#define BSMHCP_FW_INFO_USER_DEFINED_COUNT (96) + +#define BSMHCP_TIMING_SOURCE_ITIME (0) +#define BSMHCP_TIMING_SOURCE_ITIME_L1 (1) +#define BSMHCP_TIMING_SOURCE_RADIO_TX (2) +#define BSMHCP_TIMING_SOURCE_RADIO_RX (3) +#define BSMHCP_TIMING_SOURCE_RADIO_LC (4) +#define BSMHCP_TIMING_SOURCE_COUNT (5) + +#define BSMHCP_INCREASE_INDEX(index, limit) \ + ((index) = ((index) + 1) % (limit)) + +#define BSMHCP_HAS_ROOM(write, read, limit) \ + ((((write) + 1) % (limit)) != (read)) + +struct BSMHCP_TD_CONTROL { + uint16_t length; + uint8_t data[BSMHCP_CMD_EVT_BUFFER_SIZE]; +}; + +struct BSMHCP_TD_HCI_EVT { + uint16_t length; + uint16_t hci_connection_handle; + uint16_t event_type; + uint8_t data[BSMHCP_CMD_EVT_BUFFER_SIZE]; +}; + +struct BSMHCP_TD_ACL_RX { + uint16_t hci_connection_handle; + uint16_t length; + uint8_t broadcast_flag; + uint8_t packet_boundary; + uint8_t disconnected; + uint8_t reserved; + uint8_t data[BSMHCP_ACL_BUFFER_SIZE]; +}; + +struct BSMHCP_TD_ACL_TX_DATA { + uint16_t length; + uint8_t buffer_index; + uint8_t flags; + uint16_t hci_connection_handle; + uint16_t l2cap_cid; +}; + +struct BSMHCP_TD_ACL_TX_FREE { + uint8_t buffer_index; + uint8_t reserved; + uint16_t hci_connection_handle; +}; + +struct BSMHCP_TD_AVDTP { + uint32_t flags; + uint16_t l2cap_cid; + uint16_t hci_connection_handle; + uint32_t reserved; +}; + +struct BSMHCP_ACL_TR_DRV_INDEX { + uint32_t read_free; + uint32_t write_data; +}; + +struct BSMHCP_ACL_TR_CTRL_INDEX { + uint32_t read_data; + uint32_t write_free; +}; + +struct BSMHCP_INDEX { + uint32_t read; + uint32_t write; +}; + +struct BSMHCP_TIMING_PACKET { + uint16_t source; + uint16_t sequence_number; + uint32_t interrupt_enter; + uint32_t critical_section_enter; + uint32_t time[4]; + uint32_t critical_section_leave; + uint32_t interrupt_leave; +}; + +struct BSMHCP_FW_INFO { + uint32_t r4_from_ap_interrupt_count; + uint32_t m4_from_ap_interrupt_count; + uint32_t r4_to_ap_interrupt_count; + uint32_t m4_to_ap_interrupt_count; + uint32_t bt_deep_sleep_time_total; + uint32_t bt_deep_sleep_wakeup_duration; + uint32_t sched_n_messages; + uint32_t user_defined_count; + uint32_t user_defined[BSMHCP_FW_INFO_USER_DEFINED_COUNT]; +}; + +struct BSMHCP_TD_IQ_REPORTING_EVT { + uint8_t subevent_code; + uint8_t packet_status; + uint16_t connection_handle; + uint16_t sync_handle; + uint8_t rx_phy; + uint8_t channel_index; + int16_t rssi; + uint8_t rssi_antenna_id; + uint8_t cte_type; + uint8_t slot_durations; + uint8_t sample_count; + uint16_t event_count; + uint8_t data[BSMHCP_IQ_REPORT_BUFFER_SIZE]; +}; + +struct BSMHCP_HEADER { + /* AP RW - M4/R4 RO - 64 octets */ + uint32_t magic_value; /* 0x00 */ + uint16_t ap_to_fg_m4_int_src; /* 0x04 */ + uint8_t service_request; /* 0x06 */ + uint8_t reserved1; /* 0x07 */ + uint32_t acl_buffer_size; /* 0x08 */ + uint32_t cmd_evt_buffer_size; /* 0x0C */ + uint32_t acl_tx_buffers; /* 0x10 */ + uint16_t ap_to_bg_int_src; /* 0x14 */ + uint16_t ap_to_fg_int_src; /* 0x16 */ + uint16_t bg_to_ap_int_src; /* 0x18 */ + uint16_t fg_to_ap_int_src; /* 0x1A */ + uint32_t mailbox_offset; /* 0x1C */ + uint32_t reserved1_u32; /* 0x20 */ + uint32_t mailbox_hci_cmd_write; /* 0x24 */ + uint32_t mailbox_hci_evt_read; /* 0x28 */ + uint32_t mailbox_acl_tx_write; /* 0x2C */ + uint32_t mailbox_acl_free_read; /* 0x30 */ + uint32_t mailbox_acl_rx_read; /* 0x34 */ + uint32_t abox_offset; /* 0x38 */ + uint32_t abox_length; /* 0x3C */ + + /* AP RO - R4 RW - M4 NA - 32 octets */ + uint16_t panic_deathbed_confession; /* 0x40 */ + uint16_t panic_diatribe_value; /* 0x42 */ + uint32_t mailbox_hci_cmd_read; /* 0x44 */ + uint32_t mailbox_hci_evt_write; /* 0x48 */ + uint32_t controller_flags; /* 0x4C */ + uint32_t firmware_features; /* 0x50 */ + uint16_t reserved_u16; /* 0x54 */ + uint8_t service_state; /* 0x56 */ + uint8_t reserved_u8; /* 0x57 */ + uint32_t reserved4_u32; /* 0x58 */ + uint32_t mailbox_avdtp_read; /* 0x5C */ + + /* AP RO - R4 NA - M4 RW - 32 octets */ + uint32_t mailbox_acl_tx_read; /* 0x60 */ + uint32_t mailbox_acl_free_write; /* 0x64 */ + uint32_t mailbox_acl_rx_write; /* 0x68 */ + uint32_t mailbox_timing_write; /* 0x6C */ + uint32_t mailbox_iq_report_write; /* 0x70 */ + uint8_t reserved4[0x0C]; /* 0x74 */ + + + /* AP RO - R4/M4 RW - 32 octets */ + uint32_t reserved6_u32; /* 0x80 */ + uint8_t reserved5[0x1C]; /* 0x84 */ + + /* AP RW - M4/R4 RO */ + uint32_t mailbox_timing_read; /* 0xA0 */ + uint32_t mailbox_avdtp_write; /* 0xA4 */ + uint32_t mailbox_iq_report_read; /* 0xA8 */ + uint32_t reserved9_u32; /* 0xAC */ + uint32_t reserved10_u32; /* 0xB0 */ + uint32_t reserved11_u32; /* 0xB4 */ + uint32_t reserved12_u32; /* 0xB8 */ + uint16_t info_ap_to_bg_int_src; /* 0xBC */ + uint16_t info_bg_to_ap_int_src; /* 0xBE */ + uint32_t mxlog_filter; /* 0xC0 */ + uint32_t firmware_control; /* 0xC4 */ + uint8_t reserved6[0x24]; /* 0xC8 */ + + /* Obsolete region - not used */ + uint32_t smm_debug_read; /* 0xEC */ + uint32_t smm_debug_write; /* 0xF0 */ + uint32_t smm_exception; /* 0xF4 */ + uint32_t avdtp_detect_stream_id; /* 0xF8 */ + uint32_t smm_int_ready; /* 0xFE */ +}; + +struct BSMHCP_PROTOCOL { + /* header offset: 0x00000000 */ + volatile struct BSMHCP_HEADER header; + + /* from AP */ + struct BSMHCP_TD_CONTROL /* offset: 0x00000100 */ + hci_cmd_transfer_ring[BSMHCP_TRANSFER_RING_CMD_SIZE]; + + /* AVDTP detection */ + struct BSMHCP_TD_AVDTP /* offset: 0x00000920 */ + avdtp_transfer_ring[BSMHCP_TRANSFER_RING_AVDTP_SIZE]; + + uint8_t /* offset: 0x000009E0 */ + to_air_reserved[0x1FC0]; + + struct BSMHCP_TD_ACL_TX_DATA /* offset: 0x000029A0 */ + acl_tx_data_transfer_ring[BSMHCP_TRANSFER_RING_ACL_SIZE]; + + uint8_t /* offset: 0x00002AA0 */ + acl_tx_buffer[BSMHCP_DATA_BUFFER_TX_ACL_SIZE] + [BSMHCP_ACL_BUFFER_SIZE]; + + /* Padding used to ensure minimum 32 octets between sections */ + uint8_t reserved[0x20]; /* offset: 0x0000A2A0 */ + + /* to AP */ + struct BSMHCP_TD_HCI_EVT /* offset: 0x0000A2C0 */ + hci_evt_transfer_ring[BSMHCP_TRANSFER_RING_EVT_SIZE]; + + struct BSMHCP_TIMING_PACKET /* offset: 0x0000C3C0 */ + timing_transfer_ring[BSMHCP_TRANSFER_RING_TIMING_COUNT]; + + struct BSMHCP_FW_INFO /* offset: 0x0000CCC0 */ + information; + + uint8_t /* offset: 0x0000CCC0 + sizoef(struct BSMHCP_FW_INFO) */ + from_air_reserved[0x11E0 - sizeof(struct BSMHCP_FW_INFO)]; + + struct BSMHCP_TD_IQ_REPORTING_EVT /* offset: 0x0000DEA0 */ + iq_reporting_transfer_ring[BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE]; + + struct BSMHCP_TD_ACL_RX /* offset: 0x0000E440 */ + acl_rx_transfer_ring[BSMHCP_TRANSFER_RING_ACL_SIZE]; + + struct BSMHCP_TD_ACL_TX_FREE /* offset: 0x00016540 */ + acl_tx_free_transfer_ring[BSMHCP_TRANSFER_RING_ACL_SIZE]; +}; + +#define BSMHCP_TD_ACL_RX_CONTROL_SIZE \ + (sizeof(struct BSMHCP_TD_ACL_RX) - BSMHCP_ACL_BUFFER_SIZE) + +#define BSMHCP_PROTOCOL_MAGICVALUE \ + ((BSMHCP_ACL_BUFFER_SIZE | BSMHCP_CMD_EVT_BUFFER_SIZE | \ + (offsetof(struct BSMHCP_PROTOCOL, acl_tx_buffer) << 15)) ^ \ + sizeof(struct BSMHCP_PROTOCOL)) + +#endif /* __BSMHCP_H__ */ diff --git a/include/scsc/api/bt_audio.h b/include/scsc/api/bt_audio.h new file mode 100755 index 000000000000..00a3bc67655d --- /dev/null +++ b/include/scsc/api/bt_audio.h @@ -0,0 +1,133 @@ +#ifndef _BT_AUDIO_H +#define _BT_AUDIO_H + +/* Version number */ +#define SCSC_BT_AUDIO_ABOX_VERSION_MAJOR (0x01) +#define SCSC_BT_AUDIO_ABOX_VERSION_MINOR (0x01) + +/* The A-Box uses a ARM Cortex-A7 with a 64 bytes cache line whereas + * the WLBT uses a ARM Cortex-R4 with a 32 bytes cache line. The data + * needs to be aligned to the largest cache line + */ +#define SCSC_BT_AUDIO_ABOX_DCACHE_LINE_WIDTH (64) + +/* kernel page size used for memory alignment */ +#define SCSC_BT_AUDIO_PAGE_SIZE (PAGE_SIZE) + +/* Total size of the shared memory in one direction */ +#define SCSC_BT_AUDIO_ABOX_DATA_SIZE (128 * SCSC_BT_AUDIO_ABOX_DCACHE_LINE_WIDTH) + +/* Size of the buffer for each interface */ +#define SCSC_BT_AUDIO_ABOX_IF_0_SIZE (10 * SCSC_BT_AUDIO_ABOX_DCACHE_LINE_WIDTH) +#define SCSC_BT_AUDIO_ABOX_IF_1_SIZE (10 * SCSC_BT_AUDIO_ABOX_DCACHE_LINE_WIDTH) + +/* Feature mask */ +#define SCSC_BT_AUDIO_FEATURE_STREAMING_IF_0 (0x00000001) +#define SCSC_BT_AUDIO_FEATURE_STREAMING_IF_1 (0x00000002) +#define SCSC_BT_AUDIO_FEATURE_MESSAGING (0x00000004) +#define SCSC_BT_AUDIO_FEATURE_A2DP_OFFLOAD (0x00000008) + +struct scsc_bt_audio_abox { + /* AP RW - BT R4 RO - ABOX RO - 128 octets */ + + /* header */ + uint32_t magic_value; + uint16_t version_major; + uint16_t version_minor; + + /* align to cache line (32 bytes) */ + uint8_t reserved1[0x18]; + + /* streaming interface 0 */ + uint32_t abox_to_bt_streaming_if_0_size; + + /* offset in abox_to_bt_streaming_if_data */ + uint32_t abox_to_bt_streaming_if_0_offset; + + uint32_t bt_to_abox_streaming_if_0_size; + + /* offset in bt_to_abox_streaming_if_data */ + uint32_t bt_to_abox_streaming_if_0_offset; + + /* streaming interface 1 */ + uint32_t abox_to_bt_streaming_if_1_size; + + /* offset in abox_to_bt_streaming_if_data */ + uint32_t abox_to_bt_streaming_if_1_offset; + + uint32_t bt_to_abox_streaming_if_1_size; + + /* offset in bt_to_abox_streaming_if_data */ + uint32_t bt_to_abox_streaming_if_1_offset; + + /* reserved room for additional AP information (64 bytes) */ + uint8_t reserved2[0x40]; + + /* AP RO - BT R4 RO - ABOX RW - 64 octets */ + uint32_t abox_fw_features; + + /* BTWLAN audio and ABOX firmware may start at different time + * and a number of interrupts may be already triggered by ABOX + * firmware before BTWLAN audio can process them causing + * misalignment between the two systems (e.g. both accessing + * the same buffer at the same time). The fields below provide + * information about which half of the double buffer the ABOX + * firmware is processing using 0/1. + * filled by ABOX firmware at each interrupt (read/write) and + * initialised to 0 by BT driver. + */ + uint32_t bt_to_abox_streaming_if_0_current_index; + uint32_t abox_to_bt_streaming_if_0_current_index; + uint32_t bt_to_abox_streaming_if_1_current_index; + uint32_t abox_to_bt_streaming_if_1_current_index; + + /* align to cache line (64 bytes) */ + uint8_t reserved3[0x2C]; + + /* AP RO - BT R4 RW - ABOX RO - 64 octets */ + uint32_t bt_fw_features; + + /* sample rate (Hz) of the streaming interfaces */ + uint32_t streaming_if_0_sample_rate; + uint32_t streaming_if_1_sample_rate; + + uint8_t reserved4[0x34]; + + /* payload */ + + /* AP RO - BT R4 RO - ABOX RW - multiple of 64 octets */ + uint8_t abox_to_bt_streaming_if_data[SCSC_BT_AUDIO_ABOX_DATA_SIZE]; + + /* AP RO - BT R4 RW - ABOX RO - multiple of 64 octets */ + uint8_t bt_to_abox_streaming_if_data[SCSC_BT_AUDIO_ABOX_DATA_SIZE]; +}; + +/* Magic value */ +#define SCSC_BT_AUDIO_ABOX_MAGIC_VALUE \ + (((offsetof(struct scsc_bt_audio_abox, abox_to_bt_streaming_if_0_size) << 20) | \ + (offsetof(struct scsc_bt_audio_abox, bt_to_abox_streaming_if_0_current_index) << 10) | \ + offsetof(struct scsc_bt_audio_abox, abox_to_bt_streaming_if_data)) ^ \ + 0xBA12EF82) + +struct scsc_bt_audio { + struct device *dev; + struct scsc_bt_audio_abox *abox_virtual; + struct scsc_bt_audio_abox *abox_physical; + int (*dev_iommu_map)(struct device *, phys_addr_t, size_t); + void (*dev_iommu_unmap)(struct device *, size_t); +}; + +struct scsc_bt_audio_driver { + const char *name; + void (*probe)(struct scsc_bt_audio_driver *driver, struct scsc_bt_audio *bt_audio); + void (*remove)(struct scsc_bt_audio *bt_audio); +}; + +phys_addr_t scsc_bt_audio_get_paddr_buf(bool tx); +unsigned int scsc_bt_audio_get_rate(int id); +int scsc_bt_audio_register(struct device *dev, + int (*dev_iommu_map)(struct device *, phys_addr_t, size_t), + void (*dev_iommu_unmap)(struct device *, size_t)); +int scsc_bt_audio_unregister(struct device *dev); + +#endif /* _BT_AUDIO_H */ diff --git a/include/scsc/api/cme3_prim.h b/include/scsc/api/cme3_prim.h new file mode 100644 index 000000000000..8a8674ab2113 --- /dev/null +++ b/include/scsc/api/cme3_prim.h @@ -0,0 +1,375 @@ +/***************************************************************************** + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * Confidential information of Samsung Electronics Co., Ltd + * + * Refer to LICENSE.txt included with this source for details + * on the license terms. + * + * CME3_PRIM_H defines data structures and macros for coex signalling + * between BT Host and BT FW + * + *****************************************************************************/ +#ifndef CME3_PRIM_H__ +#define CME3_PRIM_H__ + +#if defined(XAP) +#include "types.h" +#else +#include +#include +#endif + +#define CME3_PRIM_ANY_SIZE 1 + +/******************************************************************************* + * + * NAME + * CME_Signal_Id + * + * DESCRIPTION + * + * VALUES + * profile_a2dp_start_ind - + * profile_a2dp_stop_ind - + * coex_stop_ind - + * coex_start_ind - + * + *******************************************************************************/ +typedef enum { + CME_SIGNAL_ID_PROFILE_A2DP_START_IND = 0, + CME_SIGNAL_ID_PROFILE_A2DP_STOP_IND = 1, + CME_SIGNAL_ID_COEX_STOP_IND = 2, + CME_SIGNAL_ID_COEX_START_IND = 3, +} CME_SIGNAL_ID; + +/******************************************************************************* + * + * NAME + * CME_CODEC_TYPE + * + * DESCRIPTION + * Codec types used for A2DP profile. + * + * VALUES + * CME_CODEC_TYPE_SBC - + * CME_CODEC_TYPE_APTX - + * CME_CODEC_TYPE_SSHD - + * + *******************************************************************************/ +typedef enum { + CME_CODEC_TYPE_SBC = 0, + CME_CODEC_TYPE_APTX = 1, + CME_CODEC_TYPE_SSHD = 2 +} CME_CODEC_TYPE; + + +/******************************************************************************* + * + * NAME + * CME_A2DP_ROLE + * + * DESCRIPTION + * A2DP Device role for A2DP profile. + * + * VALUES + * CME_A2DP_SOURCE - + * CME_A2DP_SINK - + * + *******************************************************************************/ +typedef enum { + CME_A2DP_SOURCE = 0, + CME_A2DP_SINK = 1 +} CME_A2DP_ROLE; + + +/******************************************************************************* + * + * NAME + * Cme_Header + * + * DESCRIPTION + * + * MEMBERS + * Signal_Id - + * Length - The length of the whole message in 16-bit words. + * + *******************************************************************************/ +typedef struct { + CME_SIGNAL_ID Signal_Id; + uint8_t Length; +} CME_HEADER; + +/* The following macros take CME_HEADER *cme_header_ptr or uint16_t *addr */ +#define CME_HEADER_SIGNAL_ID_WORD_OFFSET (0) + +#define CME_HEADER_SIGNAL_ID_GET(addr) \ + ((CME_SIGNAL_ID)((*(((volatile const uint16_t *)(addr))) & 0xff))) + +#define CME_HEADER_SIGNAL_ID_SET(addr, signal_id) \ + (*(((volatile uint16_t *)(addr))) = \ + (uint16_t)((*(((volatile uint16_t *)(addr))) & ~0xff) | (((signal_id)) & 0xff))) + +#define CME_HEADER_LENGTH_GET(addr) (((*(((volatile const uint16_t *)(addr))) & 0xff00) >> 8)) + +#define CME_HEADER_LENGTH_SET(addr, length) (*(((volatile uint16_t *)(addr))) = \ + (uint16_t)((*(((volatile uint16_t *)(addr))) & ~0xff00) | (((length) << 8) & 0xff00))) + +#define CME_HEADER_WORD_SIZE (1) + +/*lint -e(773) allow unparenthesized*/ +#define CME_HEADER_CREATE(Signal_Id, Length) \ + (uint16_t)(((Signal_Id)) & 0xff) | \ + (uint16_t)(((Length) << 8) & 0xff00) + +#define CME_HEADER_PACK(addr, Signal_Id, Length) \ + do { \ + *(((volatile uint16_t *)(addr))) = (uint16_t)((uint16_t)(((Signal_Id)) & 0xff) | \ + (uint16_t)(((Length) << 8) & 0xff00)); \ + } while (0) + +#define CME_HEADER_MARSHALL(addr, cme_header_ptr) \ + do { \ + *((addr)) = (uint16_t)((((cme_header_ptr)->Signal_Id)) & 0xff) | \ + (uint16_t)((((cme_header_ptr)->Length) << 8) & 0xff00); \ + } while (0) + +#define CME_HEADER_UNMARSHALL(addr, cme_header_ptr) \ + do { \ + (cme_header_ptr)->Signal_Id = CME_HEADER_SIGNAL_ID_GET(addr); \ + (cme_header_ptr)->Length = CME_HEADER_LENGTH_GET(addr); \ + } while (0) + + + + +/******************************************************************************* + * + * NAME + * CME_PROFILE_A2DP_START_IND + * + * DESCRIPTION + * Message from CME_BH to CME_BT to indicate that an A2DP profile has + * started a connection or that an existing connection has resumed with + * updated parameters. + * + * MEMBERS + * acl_handle - Identifies the ACL Link used for the profile + * connection. + * l2cap_connection_id - Identifies the remote L2CAP connection ID (as used on + * the air). + * bit_rate - Identifies the bit rate of the codec in kbps. + * codec_type - Identifies the codec type (CME_CODEC_TYPE enum). + * sdu_size - Identifies the maximum size of the A2DP SDU (MTU + * negotiated for the L2CAP link) in octets. + * period - Identifies the period in ms of codec data being + * available for transmission. + * role - Identifies the local device role, source or sink. + * spare - Spare. + * + *******************************************************************************/ +typedef struct { + CME_HEADER header; + uint16_t acl_handle; + uint16_t l2cap_connection_id; + uint16_t bit_rate; /* Only 12 bits used */ + CME_CODEC_TYPE codec_type; + uint16_t sdu_size; + uint8_t period; + CME_A2DP_ROLE role; +} CME_PROFILE_A2DP_START_IND; + +/* The following macros take + * CME_PROFILE_A2DP_START_IND *cme_profile_a2dp_start_ind_ptr or uint16_t *addr */ +#define CME_PROFILE_A2DP_START_IND_ACL_HANDLE_WORD_OFFSET (1) + +#define CME_PROFILE_A2DP_START_IND_ACL_HANDLE_GET(addr) (*((addr) + 1)) + +#define CME_PROFILE_A2DP_START_IND_ACL_HANDLE_SET(addr, acl_handle) \ + (*((addr) + 1) = (uint16_t)(acl_handle)) + +#define CME_PROFILE_A2DP_START_IND_L2CAP_CONNECTION_ID_WORD_OFFSET (2) + +#define CME_PROFILE_A2DP_START_IND_L2CAP_CONNECTION_ID_GET(addr) (*((addr) + 2)) + +#define CME_PROFILE_A2DP_START_IND_L2CAP_CONNECTION_ID_SET(addr, l2cap_connection_id) \ + (*((addr) + 2) = (uint16_t)(l2cap_connection_id)) +#define CME_PROFILE_A2DP_START_IND_BIT_RATE_WORD_OFFSET (3) + +#define CME_PROFILE_A2DP_START_IND_BIT_RATE_GET(addr) (((*((addr) + 3) & 0xfff))) + +#define CME_PROFILE_A2DP_START_IND_BIT_RATE_SET(addr, bit_rate) (*((addr) + 3) = \ + (uint16_t)((*((addr) + 3) & ~0xfff) | (((bit_rate)) & 0xfff))) + +#define CME_PROFILE_A2DP_START_IND_CODEC_TYPE_GET(addr) \ + ((CME_CODEC_TYPE)((*((addr) + 3) & 0x7000) >> 12)) + +#define CME_PROFILE_A2DP_START_IND_CODEC_TYPE_SET(addr, codec_type) \ + (*((addr) + 3) = \ + (uint16_t)((*((addr) + 3) & ~0x7000) | (((codec_type) << 12) & 0x7000))) + +#define CME_PROFILE_A2DP_START_IND_SDU_SIZE_WORD_OFFSET (4) + +#define CME_PROFILE_A2DP_START_IND_SDU_SIZE_GET(addr) (*((addr) + 4)) + +#define CME_PROFILE_A2DP_START_IND_SDU_SIZE_SET(addr, sdu_size) (*((addr) + 4) = \ + (uint16_t)(sdu_size)) + +#define CME_PROFILE_A2DP_START_IND_PERIOD_WORD_OFFSET (5) + +#define CME_PROFILE_A2DP_START_IND_PERIOD_GET(addr) (((*((addr) + 5) & 0xff))) + +#define CME_PROFILE_A2DP_START_IND_PERIOD_SET(addr, period) (*((addr) + 5) = \ + (uint16_t)((*((addr) + 5) & ~0xff) | (((period)) & 0xff))) + +#define CME_PROFILE_A2DP_START_IND_ROLE_GET(addr) \ + ((CME_A2DP_ROLE)((*((addr) + 5) & 0x100) >> 8)) + +#define CME_PROFILE_A2DP_START_IND_ROLE_SET(addr, role) (*((addr) + 5) = \ + (uint16_t)((*((addr) + 5) & ~0x100) | (((role) << 8) & 0x100))) + +#define CME_PROFILE_A2DP_START_IND_WORD_SIZE (6) + +#define CME_PROFILE_A2DP_START_IND_PACK(addr, acl_handle, l2cap_connection_id, \ + bit_rate, codec_type, sdu_size, period, role, sampling_freq) \ + do { \ + *((addr) + 1) = (uint16_t)((uint16_t)(acl_handle)); \ + *((addr) + 2) = (uint16_t)((uint16_t)(l2cap_connection_id)); \ + *((addr) + 3) = (uint16_t)((uint16_t)(((bit_rate)) & 0xfff) | \ + (uint16_t)(((codec_type) << 12) & 0x7000); \ + *((addr) + 4) = (uint16_t)((uint16_t)(sdu_size)); \ + *((addr) + 5) = (uint16_t)((uint16_t)(((period)) & 0xff) | \ + (uint16_t)(((role) << 8) & 0x100); \ + } while (0) + +#define CME_PROFILE_A2DP_START_IND_MARSHALL(addr, cme_profile_a2dp_start_ind_ptr) \ + do { \ + CME_HEADER_MARSHALL((addr), &((cme_profile_a2dp_start_ind_ptr)->header)); \ + *((addr) + 1) = (uint16_t)((cme_profile_a2dp_start_ind_ptr)->acl_handle); \ + *((addr) + 2) = (uint16_t)((cme_profile_a2dp_start_ind_ptr)->l2cap_connection_id); \ + *((addr) + 3) = (uint16_t)((((cme_profile_a2dp_start_ind_ptr)->bit_rate)) & 0xfff) | \ + (uint16_t)((((cme_profile_a2dp_start_ind_ptr)->codec_type) << 12) & 0x7000); \ + *((addr) + 4) = (uint16_t)((cme_profile_a2dp_start_ind_ptr)->sdu_size); \ + *((addr) + 5) = (uint16_t)((((cme_profile_a2dp_start_ind_ptr)->period)) & 0xff) | \ + (uint16_t)((((cme_profile_a2dp_start_ind_ptr)->role) << 8) & 0x100); \ + } while (0) + +#define CME_PROFILE_A2DP_START_IND_UNMARSHALL(addr, cme_profile_a2dp_start_ind_ptr) \ + do { \ + CME_HEADER_UNMARSHALL((addr), &((cme_profile_a2dp_start_ind_ptr)->header)); \ + (cme_profile_a2dp_start_ind_ptr)->acl_handle = CME_PROFILE_A2DP_START_IND_ACL_HANDLE_GET(addr); \ + (cme_profile_a2dp_start_ind_ptr)->l2cap_connection_id = CME_PROFILE_A2DP_START_IND_L2CAP_CONNECTION_ID_GET(addr); \ + (cme_profile_a2dp_start_ind_ptr)->bit_rate = CME_PROFILE_A2DP_START_IND_BIT_RATE_GET(addr); \ + (cme_profile_a2dp_start_ind_ptr)->codec_type = CME_PROFILE_A2DP_START_IND_CODEC_TYPE_GET(addr); \ + (cme_profile_a2dp_start_ind_ptr)->sdu_size = CME_PROFILE_A2DP_START_IND_SDU_SIZE_GET(addr); \ + (cme_profile_a2dp_start_ind_ptr)->period = CME_PROFILE_A2DP_START_IND_PERIOD_GET(addr); \ + (cme_profile_a2dp_start_ind_ptr)->role = CME_PROFILE_A2DP_START_IND_ROLE_GET(addr); \ + } while (0) + + +/******************************************************************************* + * + * NAME + * CME_PROFILE_A2DP_STOP_IND + * + * DESCRIPTION + * Message from CME_BH to CME_BT to indicate that an A2DP profile has + * stopped or paused. + * + * MEMBERS + * acl_handle - Identifies the ACL Link used for the profile + * connection. + * l2cap_connection_id - Identifies the remote L2CAP connection ID (as used on + * the air). + * + *******************************************************************************/ +typedef struct { + CME_HEADER header; + uint16_t acl_handle; + uint16_t l2cap_connection_id; +} CME_PROFILE_A2DP_STOP_IND; + +/* The following macros take + * CME_PROFILE_A2DP_STOP_IND *cme_profile_a2dp_stop_ind_ptr or uint16_t *addr */ +#define CME_PROFILE_A2DP_STOP_IND_ACL_HANDLE_WORD_OFFSET (1) + +#define CME_PROFILE_A2DP_STOP_IND_ACL_HANDLE_GET(addr) (*((addr) + 1)) + +#define CME_PROFILE_A2DP_STOP_IND_ACL_HANDLE_SET(addr, acl_handle) \ + (*((addr) + 1) = (uint16_t)(acl_handle)) + +#define CME_PROFILE_A2DP_STOP_IND_L2CAP_CONNECTION_ID_WORD_OFFSET (2) + +#define CME_PROFILE_A2DP_STOP_IND_L2CAP_CONNECTION_ID_GET(addr) (*((addr) + 2)) + +#define CME_PROFILE_A2DP_STOP_IND_L2CAP_CONNECTION_ID_SET(addr, l2cap_connection_id) \ + (*((addr) + 2) = (uint16_t)(l2cap_connection_id)) + +#define CME_PROFILE_A2DP_STOP_IND_WORD_SIZE (3) + +#define CME_PROFILE_A2DP_STOP_IND_PACK(addr, acl_handle, l2cap_connection_id) \ + do { \ + *((addr) + 1) = (uint16_t)((uint16_t)(acl_handle)); \ + *((addr) + 2) = (uint16_t)((uint16_t)(l2cap_connection_id)); \ + } while (0) + +#define CME_PROFILE_A2DP_STOP_IND_MARSHALL(addr, cme_profile_a2dp_stop_ind_ptr) \ + do { \ + CME_HEADER_MARSHALL((addr), &((cme_profile_a2dp_stop_ind_ptr)->header)); \ + *((addr) + 1) = (uint16_t)((cme_profile_a2dp_stop_ind_ptr)->acl_handle); \ + *((addr) + 2) = (uint16_t)((cme_profile_a2dp_stop_ind_ptr)->l2cap_connection_id); \ + } while (0) + +#define CME_PROFILE_A2DP_STOP_IND_UNMARSHALL(addr, cme_profile_a2dp_stop_ind_ptr) \ + do { \ + CME_HEADER_UNMARSHALL((addr), &((cme_profile_a2dp_stop_ind_ptr)->header)); \ + (cme_profile_a2dp_stop_ind_ptr)->acl_handle = CME_PROFILE_A2DP_STOP_IND_ACL_HANDLE_GET(addr); \ + (cme_profile_a2dp_stop_ind_ptr)->l2cap_connection_id = CME_PROFILE_A2DP_STOP_IND_L2CAP_CONNECTION_ID_GET(addr); \ + } while (0) + + + +/******************************************************************************* + * + * NAME + * CME_COEX_STOP_IND + * + * DESCRIPTION + * Message from CME_BT to CME_BH to indicate that the coex service is + * stopping. + * + * MEMBERS + * + *******************************************************************************/ +typedef struct { + CME_HEADER header; +} CME_COEX_STOP_IND; + +/* The following macros take + * CME_COEX_STOP_IND *cme_coex_stop_ind_ptr or uint16_t *addr */ +#define CME_COEX_STOP_IND_WORD_SIZE (1) + + +/******************************************************************************* + * + * NAME + * CME_COEX_START_IND + * + * DESCRIPTION + * Message from CME_BT to CME_BH to indicate that the coex service is + * starting. + * + * MEMBERS + * + *******************************************************************************/ +typedef struct { + CME_HEADER header; +} CME_COEX_START_IND; + +/* The following macros take + * CME_COEX_START_IND *cme_coex_start_ind_ptr or uint16_t *addr */ +#define CME_COEX_START_IND_WORD_SIZE (1) + + + +#endif /* CME3_PRIM_H__ */ diff --git a/include/scsc/kic/slsi_kic.h b/include/scsc/kic/slsi_kic.h new file mode 100755 index 000000000000..8769268cd7fe --- /dev/null +++ b/include/scsc/kic/slsi_kic.h @@ -0,0 +1,21 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_H +#define __SLSI_KIC_H + +#include + +int slsi_kic_system_event_ind(enum slsi_kic_system_event_category event_cat, + enum slsi_kic_system_events event, gfp_t flags); +int slsi_kic_service_information_ind(enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info); + +int slsi_kic_firmware_event_ind(uint16_t firmware_event_type, uint32_t tech_type, + uint32_t contain_type, + struct slsi_kic_firmware_event_ccp_host *event); + +#endif /* #ifndef __SLSI_KIC_H */ diff --git a/include/scsc/kic/slsi_kic_ant.h b/include/scsc/kic/slsi_kic_ant.h new file mode 100755 index 000000000000..134890c21247 --- /dev/null +++ b/include/scsc/kic/slsi_kic_ant.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_ANT_H +#define __SLSI_KIC_ANT_H + +#include + +/** + * struct slsi_kic_ant_ops - backend description for ANT service driver ops. + * + * This struct is registered by the ANT service driver during initilisation + * in order provide ANT specific services, which can be used by KIC. + * + * All callbacks except where otherwise noted should return 0 on success or a + * negative error code. + * + * @trigger_recovery: Trigger a ANT firmware subsystem recovery. The variable + * @type specifies the recovery type. + */ +struct slsi_kic_ant_ops { + int (*trigger_recovery)(void *priv, enum slsi_kic_test_recovery_type type); +}; + +#ifdef CONFIG_SAMSUNG_KIC + +/** + * slsi_kic_ant_ops_register - register ant_ops with KIC + * + * @priv: Private pointer, which will be included in all calls from KIC. + * @ant_ops: The ant_ops to register. + * + * Returns 0 on success or a negative error code. + */ +int slsi_kic_ant_ops_register(void *priv, struct slsi_kic_ant_ops *ant_ops); + +/** + * slsi_kic_ant_ops_unregister - unregister ant_ops with KIC + * + * @ant_ops: The ant_ops to unregister. + * + * After this call, no more requests can be made, but the call may sleep to wait + * for an outstanding request that is being handled. + */ +void slsi_kic_ant_ops_unregister(struct slsi_kic_ant_ops *ant_ops); + +#else + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + +#define slsi_kic_ant_ops_register(a, b) \ + do { \ + UNUSED(a); \ + UNUSED(b); \ + } while (0) + +#define slsi_kic_ant_ops_unregister(a) UNUSED(a) +#endif /* CONFIG_SAMSUNG_KIC */ + +#endif /* #ifndef __SLSI_KIC_ANT_H */ diff --git a/include/scsc/kic/slsi_kic_bt.h b/include/scsc/kic/slsi_kic_bt.h new file mode 100755 index 000000000000..c779f01827d0 --- /dev/null +++ b/include/scsc/kic/slsi_kic_bt.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_BT_H +#define __SLSI_KIC_BT_H + +#include + +/** + * struct slsi_kic_bt_ops - backend description for BT service driver ops. + * + * This struct is registered by the BT service driver during initilisation + * in order provide BT specific services, which can be used by KIC. + * + * All callbacks except where otherwise noted should return 0 on success or a + * negative error code. + * + * @trigger_recovery: Trigger a BT firmware subsystem recovery. The variable + * @type specifies the recovery type. + */ +struct slsi_kic_bt_ops { + int (*trigger_recovery)(void *priv, enum slsi_kic_test_recovery_type type); +}; + +#ifdef CONFIG_SAMSUNG_KIC + +/** + * slsi_kic_bt_ops_register - register bt_ops with KIC + * + * @priv: Private pointer, which will be included in all calls from KIC. + * @bt_ops: The bt_ops to register. + * + * Returns 0 on success or a negative error code. + */ +int slsi_kic_bt_ops_register(void *priv, struct slsi_kic_bt_ops *bt_ops); + +/** + * slsi_kic_bt_ops_unregister - unregister bt_ops with KIC + * + * @bt_ops: The bt_ops to unregister. + * + * After this call, no more requests can be made, but the call may sleep to wait + * for an outstanding request that is being handled. + */ +void slsi_kic_bt_ops_unregister(struct slsi_kic_bt_ops *bt_ops); + +#else + +#define slsi_kic_bt_ops_register(a, b) \ + do { \ + (void)(a); \ + (void)(b); \ + } while (0) + +#define slsi_kic_bt_ops_unregister(a) \ + do { \ + (void)(a); \ + } while (0) + +#endif /* CONFIG_SAMSUNG_KIC */ + +#endif /* #ifndef __SLSI_KIC_BT_H */ diff --git a/include/scsc/kic/slsi_kic_cm.h b/include/scsc/kic/slsi_kic_cm.h new file mode 100755 index 000000000000..de75c8e0aa1c --- /dev/null +++ b/include/scsc/kic/slsi_kic_cm.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_CM_H +#define __SLSI_KIC_CM_H + +#include + +/** + * struct slsi_kic_cm_ops - backend description for Chip Manager (CM) driver ops + * + * This struct is registered by the Chip Manager driver during initilisation + * in order provide CM specific services, which can be used by KIC. + * + * All callbacks except where otherwise noted should return 0 on success or a + * negative error code. + * + * @trigger_recovery: Trigger a firmware crash, which requires a full chip + * recovery. + * @type specifies the recovery type. + */ +struct slsi_kic_cm_ops { + int (*trigger_recovery)(void *priv, enum slsi_kic_test_recovery_type type); +}; + + +/** + * slsi_kic_cm_ops_register - register cm_ops with KIC + * + * @priv: Private pointer, which will be included in all calls from KIC. + * @wifi_ops: The wifi_ops to register. + * + * Returns 0 on success or a negative error code. + */ +int slsi_kic_cm_ops_register(void *priv, struct slsi_kic_cm_ops *cm_ops); + +/** + * slsi_kic_cm_ops_unregister - unregister cm_ops with KIC + * + * @cm_ops: The cm_ops to unregister. + * + * After this call, no more requests can be made, but the call may sleep to wait + * for an outstanding request that is being handled. + */ +void slsi_kic_cm_ops_unregister(struct slsi_kic_cm_ops *cm_ops); + +#endif /* #ifndef __SLSI_KIC_CM_H */ diff --git a/include/scsc/kic/slsi_kic_lib.h b/include/scsc/kic/slsi_kic_lib.h new file mode 100644 index 000000000000..1543c8956dbb --- /dev/null +++ b/include/scsc/kic/slsi_kic_lib.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_LIB_H +#define __SLSI_KIC_LIB_H + +#ifdef CONFIG_SLSI_KIC_API_ENABLED +#include +#endif + +#include + +/** + * Library functions for sending information to kernel KIC, which will process + * the event and take appropriate action, i.e. forward to relevant user + * processes etc. + */ +#ifdef CONFIG_SLSI_KIC_API_ENABLED + +static inline void slsi_kic_system_event(enum slsi_kic_system_event_category event_cat, + enum slsi_kic_system_events event, gfp_t flags) +{ + (void)slsi_kic_system_event_ind(event_cat, event, flags); +} + + +static inline void slsi_kic_service_information(enum slsi_kic_technology_type tech, + struct slsi_kic_service_info *info) +{ + (void)slsi_kic_service_information_ind(tech, info); +} + +static inline void slsi_kic_firmware_event(uint16_t firmware_event_type, + enum slsi_kic_technology_type tech_type, + uint32_t contain_type, + struct slsi_kic_firmware_event_ccp_host *event) +{ + (void)slsi_kic_firmware_event_ind(firmware_event_type, tech_type, + contain_type, event); +} + +#else + +#define slsi_kic_system_event(a, b, c) \ + do { \ + (void)(a); \ + (void)(b); \ + (void)(c); \ + } while (0) + +#define slsi_kic_service_information(a, b) \ + do { \ + (void)(a); \ + (void)(b); \ + } while (0) + +#define slsi_kic_firmware_event(a, b, c, d) \ + do { \ + (void)(a); \ + (void)(b); \ + (void)(c); \ + (void)(d); \ + } while (0) + +#endif + +#endif /* #ifndef __SLSI_KIC_LIB_H */ diff --git a/include/scsc/kic/slsi_kic_prim.h b/include/scsc/kic/slsi_kic_prim.h new file mode 100755 index 000000000000..cdee9c0f57ad --- /dev/null +++ b/include/scsc/kic/slsi_kic_prim.h @@ -0,0 +1,426 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_PRIM_H +#define __SLSI_KIC_PRIM_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +#define SLSI_KIC_INTERFACE_VERSION_MAJOR 1 +#define SLSI_KIC_INTERFACE_VERSION_MINOR 0 + +/** + * Common + */ +enum slsi_kic_technology_type { + slsi_kic_technology_type_curator, + slsi_kic_technology_type_bt, + slsi_kic_technology_type_wifi, + slsi_kic_technology_type_audio, + slsi_kic_technology_type_gnss, + slsi_kic_technology_type_nfc, + slsi_kic_technology_type_janitor, + slsi_kic_technology_type_common, + slsi_kic_technology_type_ant, + + /* keep last */ + slsi_kic_technology_type__after_last, + slsi_kic_technology_type_max_category = slsi_kic_technology_type__after_last - 1 +}; + +static const char *const slsi_kic_technology_type_text[slsi_kic_technology_type_max_category + 1] = { + "curator", + "bt", + "wifi", + "audio", + "gnss", + "nfc", + "janitor", + "ant" +}; + +/** + * System events + */ +enum slsi_kic_system_event_category { + slsi_kic_system_event_category_initialisation, + slsi_kic_system_event_category_deinitialisation, + slsi_kic_system_event_category_error, + slsi_kic_system_event_category_recovery, + + /* keep last */ + slsi_kic_system_category__after_last, + slsi_kic_system_category_max_category = slsi_kic_system_category__after_last - 1 +}; + +static const char *const slsi_kic_system_category_text[slsi_kic_system_category_max_category + 1] = { + "Initialisation", + "Deinitialisation", + "Error", + "Recovery" +}; + +enum slsi_kic_system_events { + slsi_kic_system_events_wifi_on, + slsi_kic_system_events_wifi_off, + slsi_kic_system_events_wifi_suspend, + slsi_kic_system_events_wifi_resume, + slsi_kic_system_events_wifi_service_driver_attached, + slsi_kic_system_events_wifi_service_driver_detached, + slsi_kic_system_events_wifi_firmware_patch_downloaded, + slsi_kic_system_events_wifi_service_driver_started, + slsi_kic_system_events_wifi_service_driver_stopped, + slsi_kic_system_events_bt_on, + slsi_kic_system_events_bt_off, + slsi_kic_system_events_bt_service_driver_attached, + slsi_kic_system_events_bt_service_driver_detached, + slsi_kic_system_events_bt_firmware_patch_downloaded, + slsi_kic_system_events_curator_firmware_patch_downloaded, + slsi_kic_system_events_sdio_powered, + slsi_kic_system_events_sdio_inserted, + slsi_kic_system_events_sdio_removed, + slsi_kic_system_events_sdio_powered_off, + slsi_kic_system_events_sdio_error, + slsi_kic_system_events_uart_powered, + slsi_kic_system_events_uart_powered_off, + slsi_kic_system_events_uart_error, + slsi_kic_system_events_coredump_in_progress, + slsi_kic_system_events_coredump_done, + slsi_kic_system_events_subsystem_crashed, + slsi_kic_system_events_subsystem_recovered, + slsi_kic_system_events_host_ready_ind, + slsi_kic_system_events_ant_on, + slsi_kic_system_events_ant_off, + + /* keep last */ + slsi_kic_system_events__after_last, + slsi_kic_system_events_max_event = slsi_kic_system_events__after_last - 1 +}; + +static const char *const slsi_kic_system_event_text[slsi_kic_system_events_max_event + 1] = { + "Wi-Fi on", + "Wi-Fi off", + "Wi-Fi suspend", + "Wi-Fi resume", + "Wi-Fi service driver attached", + "Wi-Fi service driver detached", + "Wi-Fi firmware patch downloaded", + "Wi-Fi service driver started", + "Wi-Fi service driver stopped", + "BT on", + "BT off", + "BT service driver attached", + "BT service driver detached", + "BT firmware patch downloaded", + "Curator firmware patch downloaded", + "SDIO powered", + "SDIO inserted", + "SDIO removed", + "SDIO powered off", + "SDIO error", + "UART powered", + "UART powered off", + "UART error", + "Coredump in progress", + "Coredump done", + "Subsystem has crashed", + "Subsystem has been recovered", + "CCP Host ready Ind sent", + "ANT on", + "ANT off" +}; + +/** + * Time stamp + */ +struct slsi_kic_timestamp { + uint64_t tv_sec; /* seconds */ + uint64_t tv_nsec; /* nanoseconds */ +}; + +/* Policy */ +enum slsi_kic_attr_timestamp_attributes { + __SLSI_KIC_ATTR_TIMESTAMP_INVALID, + SLSI_KIC_ATTR_TIMESTAMP_TV_SEC, + SLSI_KIC_ATTR_TIMESTAMP_TV_NSEC, + + /* keep last */ + __SLSI_KIC_ATTR_TIMESTAMP_AFTER_LAST, + SLSI_KIC_ATTR_TIMESTAMP_MAX = __SLSI_KIC_ATTR_TIMESTAMP_AFTER_LAST - 1 +}; + + +/** + * Firmware event + */ +enum slsi_kic_firmware_event_type { + slsi_kic_firmware_event_type_panic, + slsi_kic_firmware_event_type_fault, + + /* keep last */ + slsi_kic_firmware_events__after_last, + slsi_kic_firmware_events_max_event = slsi_kic_firmware_events__after_last - 1 +}; + +static const char *const slsi_kic_firmware_event_type_text[slsi_kic_firmware_events_max_event + 1] = { + "Firmware panic", + "Firmware fault" +}; + +enum slsi_kic_firmware_container_type { + slsi_kic_firmware_container_type_ccp_host, +}; + + +/* + * Firmware event: CCP host container + */ +#define SLSI_KIC_FIRMWARE_EVENT_CCP_HOST_ARG_LENGTH 16 +struct slsi_kic_firmware_event_ccp_host { + uint32_t id; + uint32_t level; + char *level_string; + uint32_t timestamp; + uint32_t cpu; + uint32_t occurences; + uint32_t arg_length; + uint8_t *arg; +}; + +/** + * Trigger recovery + */ +enum slsi_kic_test_recovery_type { + slsi_kic_test_recovery_type_subsystem_panic, + slsi_kic_test_recovery_type_emulate_firmware_no_response, + slsi_kic_test_recovery_type_watch_dog, + slsi_kic_test_recovery_type_chip_crash, + slsi_kic_test_recovery_type_service_start_panic, + slsi_kic_test_recovery_type_service_stop_panic, +}; + +enum slsi_kic_test_recovery_status { + slsi_kic_test_recovery_status_ok, + slsi_kic_test_recovery_status_error_invald_param, + slsi_kic_test_recovery_status_error_send_msg, +}; + + +/* Policy */ +enum slsi_kic_attr_firmware_event_ccp_host_attributes { + __SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_INVALID, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ID, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL_STRING, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_TIMESTAMP, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_CPU, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_OCCURENCES, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ARG, + + /* keep last */ + __SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_AFTER_LAST, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_MAX = __SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_AFTER_LAST - 1 +}; + + +/** + * System information + */ +struct slsi_kic_service_info { + char ver_str[128]; + uint16_t fw_api_major; + uint16_t fw_api_minor; + uint16_t release_product; + uint16_t host_release_iteration; + uint16_t host_release_candidate; +}; + +/* Policy */ +enum slsi_kic_attr_service_info_attributes { + __SLSI_KIC_ATTR_SERVICE_INFO_INVALID, + SLSI_KIC_ATTR_SERVICE_INFO_VER_STR, + SLSI_KIC_ATTR_SERVICE_INFO_FW_API_MAJOR, + SLSI_KIC_ATTR_SERVICE_INFO_FW_API_MINOR, + SLSI_KIC_ATTR_SERVICE_INFO_RELEASE_PRODUCT, + SLSI_KIC_ATTR_SERVICE_INFO_HOST_RELEASE_ITERATION, + SLSI_KIC_ATTR_SERVICE_INFO_HOST_RELEASE_CANDIDATE, + + /* keep last */ + __SLSI_KIC_ATTR_SERVICE_INFO_AFTER_LAST, + SLSI_KIC_ATTR_SERVICE_INFO_MAX = __SLSI_KIC_ATTR_SERVICE_INFO_AFTER_LAST - 1 +}; + + + +/** + * enum slsi_kic_commands - supported Samsung KIC commands + * + * @SLSI_KIC_CMD_UNSPEC: unspecified command to catch errors + * @SLSI_KIC_CMD_KIC_INTERFACE_VERSION_NUMBER_REQ: Requests the KIC interface + * version numbers to be send back. + * @SLSI_KIC_CMD_SYSTEM_EVENT_IND: Indicate a system event to user space. + * @SLSI_KIC_CMD_SERVICE_INFORMATION_REQ: Requests information for all + * already enabled subsystems. + * @SLSI_KIC_CMD_SERVICE_INFORMATION_IND: Indicate that a new subsystem has + * been started and the information for this subsystem. + * @SLSI_KIC_CMD_TEST_TRIGGER_RECOVERY_REQ: Triggers a recovery (crash) for a + * subsystem specified in the primitive. + * @SLSI_KIC_CMD_FIRMWARE_EVENT_IND: Indicates a firmware event to user space. + * @SLSI_KIC_CMD_ECHO_REQ: Request an echo (test primitive, which will be + * removed later). + * + * @SLSI_KIC_CMD_MAX: highest used command number + * @__SLSI_KIC_CMD_AFTER_LAST: internal use + */ +enum slsi_kic_commands { +/* Do not change the order or add anything between, this is ABI! */ + SLSI_KIC_CMD_UNSPEC, + + SLSI_KIC_CMD_KIC_INTERFACE_VERSION_NUMBER_REQ, + SLSI_KIC_CMD_SYSTEM_EVENT_IND, + SLSI_KIC_CMD_SERVICE_INFORMATION_REQ, + SLSI_KIC_CMD_SERVICE_INFORMATION_IND, + SLSI_KIC_CMD_TEST_TRIGGER_RECOVERY_REQ, + SLSI_KIC_CMD_FIRMWARE_EVENT_IND, + SLSI_KIC_CMD_ECHO_REQ, + + /* add new commands above here */ + + /* used to define SLSI_KIC_CMD_MAX below */ + __SLSI_KIC_CMD_AFTER_LAST, + SLSI_KIC_CMD_MAX = __SLSI_KIC_CMD_AFTER_LAST - 1 +}; + + +/** + * enum slsi_kic_attrs - Samsung KIC netlink attributes + * + * @SLSI_KIC_ATTR_UNSPEC: unspecified attribute to catch errors + * @SLSI_KIC_ATTR_KIC_VERSION_MAJOR: KIC version number - increments when the + * interface is updated with backward incompatible changes. + * @SLSI_KIC_ATTR_KIC_VERSION_MINOR: KIC version number - increments when the + * interface is updated with backward compatible changes. + * @SLSI_KIC_ATTR_TECHNOLOGY_TYPE: Technology type + * @SLSI_KIC_ATTR_SYSTEM_EVENT_CATEGORY: System event category + * @SLSI_KIC_ATTR_SYSTEM_EVENT: System event + * @SLSI_KIC_ATTR_SERVICE_INFO: Pass service info to user space. + * @SLSI_KIC_ATTR_NUMBER_OF_ENCODED_SERVICES: The attribute is used to determine + * the number of encoded services in the payload + * @SLSI_KIC_ATTR_TEST_RECOVERY_TYPE: Specifies the recovery type. + * @SLSI_KIC_ATTR_TIMESTAMP: A timestamp - ideally nano second resolution and + * precision, but it's platform dependent. + * @SLSI_KIC_ATTR_FIRMWARE_EVENT_TYPE: A firmware even type - panic or fault + * @SLSI_KIC_ATTR_FIRMWARE_CONTAINER_TYPE: Indicates container type carried in + * payload. + * @SLSI_KIC_ATTR_FIRMWARE_EVENT_CONTAINER_CCP_HOST: The firmware event data. + * @SLSI_KIC_ATTR_TRIGGER_RECOVERY_STATUS: Indicates if the recovery has been + * successfully triggered. The recovery signalling will happen afterwards as + * normal system events. + * @SLSI_KIC_ATTR_ECHO: An echo test primitive, which will be removed later. + * + * @SLSI_KIC_ATTR_MAX: highest attribute number currently defined + * @__SLSI_KIC_ATTR_AFTER_LAST: internal use + */ +enum slsi_kic_attrs { +/* Do not change the order or add anything between, this is ABI! */ + SLSI_KIC_ATTR_UNSPEC, + + SLSI_KIC_ATTR_KIC_VERSION_MAJOR, + SLSI_KIC_ATTR_KIC_VERSION_MINOR, + SLSI_KIC_ATTR_TECHNOLOGY_TYPE, + SLSI_KIC_ATTR_SYSTEM_EVENT_CATEGORY, + SLSI_KIC_ATTR_SYSTEM_EVENT, + SLSI_KIC_ATTR_SERVICE_INFO, + SLSI_KIC_ATTR_NUMBER_OF_ENCODED_SERVICES, + SLSI_KIC_ATTR_TEST_RECOVERY_TYPE, + SLSI_KIC_ATTR_TIMESTAMP, + SLSI_KIC_ATTR_FIRMWARE_EVENT_TYPE, + SLSI_KIC_ATTR_FIRMWARE_CONTAINER_TYPE, + SLSI_KIC_ATTR_FIRMWARE_EVENT_CONTAINER_CCP_HOST, + SLSI_KIC_ATTR_TRIGGER_RECOVERY_STATUS, + SLSI_KIC_ATTR_ECHO, + + /* Add attributes here, update the policy below */ + __SLSI_KIC_ATTR_AFTER_LAST, + SLSI_KIC_ATTR_MAX = __SLSI_KIC_ATTR_AFTER_LAST - 1 +}; + + +/* Policy for the attributes */ +static const struct nla_policy slsi_kic_attr_policy[SLSI_KIC_ATTR_MAX + 1] = { + [SLSI_KIC_ATTR_KIC_VERSION_MAJOR] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_KIC_VERSION_MINOR] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_TECHNOLOGY_TYPE] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_SYSTEM_EVENT_CATEGORY] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_SYSTEM_EVENT] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_SERVICE_INFO] = { .type = NLA_NESTED, }, + [SLSI_KIC_ATTR_NUMBER_OF_ENCODED_SERVICES] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_TEST_RECOVERY_TYPE] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_TIMESTAMP] = { .type = NLA_NESTED }, + [SLSI_KIC_ATTR_FIRMWARE_EVENT_TYPE] = { .type = NLA_U16 }, + [SLSI_KIC_ATTR_FIRMWARE_CONTAINER_TYPE] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CONTAINER_CCP_HOST] = { .type = NLA_NESTED }, + [SLSI_KIC_ATTR_TRIGGER_RECOVERY_STATUS] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_ECHO] = { .type = NLA_U32 }, +}; + +/* Policy for the slsi_kic_firmware_event_ccp_host attribute */ +static const struct nla_policy slsi_kic_attr_firmware_event_ccp_host_policy[SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_MAX + 1] = { + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ID] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL] = { .type = NLA_U32 }, +#ifdef __KERNEL__ + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL_STRING] = { .type = NLA_STRING, .len = 128 }, +#else + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_LEVEL_STRING] = { .type = NLA_STRING, .maxlen = 128 }, +#endif + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_TIMESTAMP] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_CPU] = { .type = NLA_U32 }, + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_OCCURENCES] = { .type = NLA_U32 }, +#ifdef __KERNEL__ + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ARG] = { .type = NLA_UNSPEC, + .len = SLSI_KIC_FIRMWARE_EVENT_CCP_HOST_ARG_LENGTH }, +#else + [SLSI_KIC_ATTR_FIRMWARE_EVENT_CCP_HOST_ARG] = { .type = NLA_UNSPEC, + .maxlen = SLSI_KIC_FIRMWARE_EVENT_CCP_HOST_ARG_LENGTH }, +#endif +}; + + +/* Policy for the slsi_kic_service_info attribute */ +static const struct nla_policy slsi_kic_attr_service_info_policy[SLSI_KIC_ATTR_SERVICE_INFO_MAX + 1] = { +#ifdef __KERNEL__ + [SLSI_KIC_ATTR_SERVICE_INFO_VER_STR] = { .type = NLA_STRING, .len = 128 }, +#else + [SLSI_KIC_ATTR_SERVICE_INFO_VER_STR] = { .type = NLA_STRING, .maxlen = 128 }, +#endif + [SLSI_KIC_ATTR_SERVICE_INFO_FW_API_MAJOR] = { .type = NLA_U16 }, + [SLSI_KIC_ATTR_SERVICE_INFO_FW_API_MINOR] = { .type = NLA_U16 }, + [SLSI_KIC_ATTR_SERVICE_INFO_RELEASE_PRODUCT] = { .type = NLA_U16 }, + [SLSI_KIC_ATTR_SERVICE_INFO_HOST_RELEASE_ITERATION] = { .type = NLA_U16 }, + [SLSI_KIC_ATTR_SERVICE_INFO_HOST_RELEASE_CANDIDATE] = { .type = NLA_U16 }, +}; + +/* Policy for the slsi_kic_timestamp attribute */ +static const struct nla_policy slsi_kic_attr_timestamp_policy[SLSI_KIC_ATTR_TIMESTAMP_MAX + 1] = { + [SLSI_KIC_ATTR_TIMESTAMP_TV_SEC] = { .type = NLA_U64 }, + [SLSI_KIC_ATTR_TIMESTAMP_TV_NSEC] = { .type = NLA_U64 }, +}; + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* #ifndef __SLSI_KIC_PRIM_H */ diff --git a/include/scsc/kic/slsi_kic_wifi.h b/include/scsc/kic/slsi_kic_wifi.h new file mode 100755 index 000000000000..8938505efa02 --- /dev/null +++ b/include/scsc/kic/slsi_kic_wifi.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SLSI_KIC_WIFI_H +#define __SLSI_KIC_WIFI_H + +#include + +/** + * struct slsi_kic_wifi_ops - backend description for Wi-Fi service driver ops. + * + * This struct is registered by the Wi-Fi service driver during initilisation + * in order provide Wi-Fi specific services, which can be used by KIC. + * + * All callbacks except where otherwise noted should return 0 on success or a + * negative error code. + * + * @trigger_recovery: Trigger a Wi-Fi firmware subsystem recovery. The variable + * @type specifies the recovery type. + */ +struct slsi_kic_wifi_ops { + int (*trigger_recovery)(void *priv, enum slsi_kic_test_recovery_type type); +}; + +#ifdef CONFIG_SAMSUNG_KIC +/** + * slsi_kic_wifi_ops_register - register wifi_ops with KIC + * + * @priv: Private pointer, which will be included in all calls from KIC. + * @wifi_ops: The wifi_ops to register. + * + * Returns 0 on success or a negative error code. + */ +int slsi_kic_wifi_ops_register(void *priv, struct slsi_kic_wifi_ops *wifi_ops); + +/** + * slsi_kic_wifi_ops_unregister - unregister wifi_ops with KIC + * + * @wifi_ops: The wifi_ops to unregister. + * + * After this call, no more requests can be made, but the call may sleep to wait + * for an outstanding request that is being handled. + */ +void slsi_kic_wifi_ops_unregister(struct slsi_kic_wifi_ops *wifi_ops); + +#else + +#define slsi_kic_wifi_ops_register(a, b) \ + do { \ + (void)(a); \ + (void)(b); \ + } while (0) + +#define slsi_kic_wifi_ops_unregister(a) \ + do { \ + (void)(a); \ + } while (0) + +#endif /* CONFIG_SAMSUNG_KIC */ + +#endif /* #ifndef __SLSI_KIC_WIFI_H */ diff --git a/include/scsc/scsc_log_collector.h b/include/scsc/scsc_log_collector.h new file mode 100644 index 000000000000..d2cb179081cf --- /dev/null +++ b/include/scsc/scsc_log_collector.h @@ -0,0 +1,146 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef __SCSC_LOG_COLLECTOR_H__ +#define __SCSC_LOG_COLLECTOR_H__ + +/* High nibble is Major, Low nibble is Minor */ +#define SCSC_LOG_HEADER_VERSION_MAJOR 0x03 +#define SCSC_LOG_HEADER_VERSION_MINOR 0x00 +/* Magic string. 4 bytes "SCSC"*/ +/* Header version. 1 byte */ +/* Num chunks. 1 byte */ +/* Offset first Chunk. 2 bytes */ +/* Collection reason. 1 byte */ +/* Reserved. 1 byte */ +/* Reason Code . 2 bytes */ +/* Observer present . 1 bytes */ +#define SCSC_LOG_HEADER_SIZE (13) +#define SCSC_LOG_FW_VERSION_SIZE (128) +#define SCSC_LOG_HOST_VERSION_SIZE (64) +#define SCSC_LOG_FAPI_VERSION_SIZE (64) +/* Reserved 2 . 4 byte */ +#define SCSC_LOG_RESERVED_2 3 +/* Ideally header + versions should be 16 bytes aligne*/ +#define SCSC_SUPPORTED_CHUNKS_HEADER 48 + +#define SCSC_LOG_CHUNK_ALIGN 1 +/* First chunk should be aligned */ +#define SCSC_LOG_OFFSET_FIRST_CHUNK (((SCSC_LOG_HEADER_SIZE + SCSC_LOG_FW_VERSION_SIZE + \ + SCSC_LOG_HOST_VERSION_SIZE + SCSC_LOG_FAPI_VERSION_SIZE + \ + SCSC_LOG_RESERVED_2 + SCSC_SUPPORTED_CHUNKS_HEADER) + \ + (SCSC_LOG_CHUNK_ALIGN - 1)) & ~(SCSC_LOG_CHUNK_ALIGN - 1)) +enum scsc_log_reason { + SCSC_LOG_UNKNOWN = 0, + SCSC_LOG_FW_PANIC, + SCSC_LOG_USER, + SCSC_LOG_FW, + SCSC_LOG_DUMPSTATE, + SCSC_LOG_HOST_WLAN, + SCSC_LOG_HOST_BT, + SCSC_LOG_HOST_COMMON, + SCSC_LOG_SYS_ERR, + /* Add others */ +}; + +extern const char *scsc_loc_reason_str[]; + +#define SCSC_CHUNK_DAT_LEN_SIZE 4 +#define SCSC_CHUNK_TYP_LEN_SIZE 4 +#define SCSC_CHUNK_HEADER_SIZE (SCSC_CHUNK_DAT_LEN_SIZE + SCSC_CHUNK_TYP_LEN_SIZE) + +/* CHUNKS WILL COLLECTED ON THIS ORDER - + * SYNC SHOULD BE THE FIRST CHUNK + * LOGRING SHOULD BE THE LAST ONE SO IT COULD CAPTURE COLLECTION ERRORS + */ +enum scsc_log_chunk_type { + SCSC_LOG_CHUNK_SYNC, /* SYNC should be the first chunk to collect */ + SCSC_LOG_MINIMOREDUMP, + /* Add other chunks */ + SCSC_LOG_CHUNK_IMP = 127, + SCSC_LOG_CHUNK_MXL, + SCSC_LOG_CHUNK_UDI, + SCSC_LOG_CHUNK_BT_HCF, + SCSC_LOG_CHUNK_WLAN_HCF, + SCSC_LOG_CHUNK_HIP4_SAMPLER, + SCSC_LOG_RESERVED_COMMON, + SCSC_LOG_RESERVED_BT, + SCSC_LOG_RESERVED_WLAN, + SCSC_LOG_RESERVED_RADIO, + /* Add other chunks */ + SCSC_LOG_CHUNK_LOGRING = 254, + SCSC_LOG_CHUNK_INVALID = 255, +}; + +#define SCSC_LOG_COLLECT_MAX_SIZE (16*1024*1024) + +/* ADD Collection codes here for HOST triggers */ +/* Consider moving the definitions to specific services if required */ + +/* Reason codes for SCSC_LOG_USER */ +#define SCSC_LOG_USER_REASON_PROC 0x0000 +/* Reason codes for SCSC_LOG_DUMPSTATE */ +#define SCSC_LOG_DUMPSTATE_REASON 0x0000 +#define SCSC_LOG_DUMPSTATE_REASON_DRIVERDEBUGDUMP 0x0001 +/* Reason codes for SCSC_LOG_HOST_WLAN */ +#define SCSC_LOG_HOST_WLAN_REASON_DISCONNECT 0x0000 +#define SCSC_LOG_HOST_WLAN_REASON_DISCONNECT_IND 0x0001 +#define SCSC_LOG_HOST_WLAN_REASON_DISCONNECTED_IND 0x0002 +#define SCSC_LOG_HOST_WLAN_REASON_DRIVERDEBUGDUMP 0x0003 +#define SCSC_LOG_HOST_WLAN_REASON_CONNECT_ERR 0x0004 +/* Reason codes for SCSC_LOG_HOST_BT */ +/* Reason codes for SCSC_LOG_HOST_COMMON */ +#define SCSC_LOG_HOST_COMMON_REASON_START 0x0000 +#define SCSC_LOG_HOST_COMMON_REASON_STOP 0x0001 +#define SCSC_LOG_HOST_COMMON_RECOVER_RST 0x0002 + +/* SBL HEADER v 0.0*/ +struct scsc_log_sbl_header { + char magic[4]; + u8 version_major; + u8 version_minor; + u8 num_chunks; + u8 trigger; + u16 offset_data; + char fw_version[SCSC_LOG_FW_VERSION_SIZE]; + char host_version[SCSC_LOG_HOST_VERSION_SIZE]; + char fapi_version[SCSC_LOG_FAPI_VERSION_SIZE]; + u16 reason_code; + bool observer; + u8 reserved2[SCSC_LOG_RESERVED_2]; + char supported_chunks[SCSC_SUPPORTED_CHUNKS_HEADER]; +} __packed; + +struct scsc_log_chunk_header { + char magic[3]; + u8 type; + u32 chunk_size; +} __packed; + +struct scsc_log_collector_client { + char *name; + enum scsc_log_chunk_type type; + int (*collect_init)(struct scsc_log_collector_client *collect_client); + int (*collect)(struct scsc_log_collector_client *collect_client, size_t size); + int (*collect_end)(struct scsc_log_collector_client *collect_client); + void *prv; +}; + +int scsc_log_collector_register_client(struct scsc_log_collector_client *collect_client); +int scsc_log_collector_unregister_client(struct scsc_log_collector_client *collect_client); + +/* Public method to get pointer of SBL RAM buffer. */ +unsigned char *scsc_log_collector_get_buffer(void); + +/* Public method to register FAPI version. */ +void scsc_log_collector_write_fapi(char __user *buf, size_t len); + +/* Public method to notify the presence/absense of observers */ +void scsc_log_collector_is_observer(bool observer); + +void scsc_log_collector_schedule_collection(enum scsc_log_reason reason, u16 reason_code); +int scsc_log_collector_write(char __user *buf, size_t count, u8 align); +#endif /* __SCSC_LOG_COLLECTOR_H__ */ diff --git a/include/scsc/scsc_logring.h b/include/scsc/scsc_logring.h new file mode 100644 index 000000000000..707fc46055fc --- /dev/null +++ b/include/scsc/scsc_logring.h @@ -0,0 +1,709 @@ +/***************************************************************************** + * + * Copyright (c) 2016-2018 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef _SCSC_LOGRING_H_ +#define _SCSC_LOGRING_H_ +#include + +#include +#include +#include +#include + +/* NOTE_CREATING_TAGS: when adding a tag here REMEMBER to add it also + * where required, taking care to maintain the same ordering. + * (Search 4 NOTE_CREATING_TAGS) + * + * You must update "int *scsc_droplevels[]" to match. + */ +enum scsc_logring_tags { + FIRST_TAG, + FIRST_BIN_TAG = FIRST_TAG, + BINARY = FIRST_BIN_TAG, + BIN_WIFI_CTRL_RX, + BIN_WIFI_DATA_RX, + BIN_WIFI_CTRL_TX, + BIN_WIFI_DATA_TX, + LAST_BIN_TAG = BIN_WIFI_DATA_TX, + NO_TAG, + WLBT = NO_TAG, + WIFI_RX, + WIFI_TX, + BT_COMMON, + BT_H4, + BT_FW, + BT_RX, + BT_TX, + CPKTBUFF, + FW_LOAD, + FW_PANIC, + GDB_TRANS, + MIF, + CLK20, + CLK20_TEST, + FM, + FM_TEST, + MX_FILE, + MX_FW, + MX_SAMPLER, + MXLOG_TRANS, + MXMAN, + MXMAN_TEST, + MXMGT_TRANS, + MX_MMAP, + MX_PROC, + PANIC_MON, + PCIE_MIF, + PLAT_MIF, + KIC_COMMON, + WLBTD, + WLOG, + LERNA, + MX_CFG, +#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY + SLSI_INIT_DEINIT, + SLSI_NETDEV, + SLSI_CFG80211, + SLSI_MLME, + SLSI_SUMMARY_FRAMES, + SLSI_HYDRA, + SLSI_TX, + SLSI_RX, + SLSI_UDI, + SLSI_WIFI_FCQ, + SLSI_HIP, + SLSI_HIP_INIT_DEINIT, + SLSI_HIP_FW_DL, + SLSI_HIP_SDIO_OP, + SLSI_HIP_PS, + SLSI_HIP_TH, + SLSI_HIP_FH, + SLSI_HIP_SIG, + SLSI_FUNC_TRACE, + SLSI_TEST, + SLSI_SRC_SINK, + SLSI_FW_TEST, + SLSI_RX_BA, + SLSI_TDLS, + SLSI_GSCAN, + SLSI_MBULK, + SLSI_FLOWC, + SLSI_SMAPPER, +#endif + TEST_ME, + MAX_TAG = TEST_ME /* keep it last */ +}; + + +#define NODEV_LABEL "" +#define SCSC_SDEV_2_DEV(sdev) \ + (((sdev) && (sdev)->wiphy) ? &((sdev)->wiphy->dev) : NULL) +#define SCSC_NDEV_2_DEV(ndev) \ + ((ndev) ? SCSC_SDEV_2_DEV(((struct netdev_vif *)netdev_priv(ndev))->sdev) : NULL) + +#define SCSC_PREFIX "wlbt: " /* prepended to log statements */ + +#define SCSC_TAG_FMT(tag, fmt) SCSC_PREFIX"[" # tag "]: %-5s: - %s: "fmt +#define SCSC_TAG_DBG_FMT(tag, fmt) SCSC_PREFIX"[" # tag "]: %s: "fmt +#define SCSC_DEV_FMT(fmt) SCSC_PREFIX"%-5s: - %s: "fmt +#define SCSC_DBG_FMT(fmt) SCSC_PREFIX"%s: "fmt + +int scsc_logring_enable(bool logging_enable); + +#ifdef CONFIG_SCSC_PRINTK + +int scsc_printk_tag(int force, int tag, const char *fmt, ...); +int scsc_printk_tag_dev(int force, int tag, struct device *dev, const char *fmt, ...); +int scsc_printk_tag_dev_lvl(int force, int tag, struct device *dev, int lvl, const char *fmt, ...); +int scsc_printk_tag_lvl(int tag, int lvl, const char *fmt, ...); +int scsc_printk_bin(int force, int tag, int dlev, const void *start, size_t len); + +/** + * This fields helps in trimming the behavior respect the kernel ring buffer: + * - NO_FORCE_PRK: the tag-based filtering mechanism is obeyed. + * - FORCE_PRK: the tag-based filtering is bypassed by this macro and message + * always get to the kernel ring buffer + * - NO_ECHO_PRK: disable completely the printk redirect. + */ +#define NO_FORCE_PRK 0 +#define FORCE_PRK 1 +#define NO_ECHO_PRK 2 + +#define SCSC_PRINTK(args ...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + args) + +#define SCSC_PRINTK_TAG(tag, args ...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + args) +#define SCSC_PRINTK_BIN(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, \ + -1, (start), (len)) + +#define SCSC_EMERG(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_EMERG SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_ALERT(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_ALERT SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_CRIT(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_CRIT SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_ERR(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_ERR SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_WARNING(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_WARNING SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_NOTICE(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_NOTICE SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_INFO(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_INFO SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_DEBUG(fmt, args...) scsc_printk_tag(NO_FORCE_PRK, WLBT, \ + KERN_DEBUG SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_TAG_EMERG(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_EMERG SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_ALERT(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_ALERT SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_CRIT(tag, fmt, args ...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_CRIT SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_ERR(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_ERR SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_WARNING(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_WARNING SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_NOTICE(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_NOTICE SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_INFO(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_INFO SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_DEBUG(tag, fmt, args...) scsc_printk_tag(NO_FORCE_PRK, (tag), \ + KERN_DEBUG SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_TAG_ERR_SDEV(sdev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_SDEV_2_DEV((sdev)), \ + KERN_ERR SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_WARNING_SDEV(sdev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_SDEV_2_DEV((sdev)), \ + KERN_WARNING SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_INFO_SDEV(sdev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_SDEV_2_DEV((sdev)), \ + KERN_INFO SCSC_DBG_FMT(fmt), \ + __func__, ## args) +#define SCSC_TAG_DEBUG_SDEV(sdev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_SDEV_2_DEV((sdev)), \ + KERN_DEBUG SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_TAG_ERR_NDEV(ndev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_NDEV_2_DEV((ndev)), \ + KERN_ERR SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) +#define SCSC_TAG_WARNING_NDEV(ndev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_NDEV_2_DEV((ndev)), \ + KERN_WARNING SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) +#define SCSC_TAG_INFO_NDEV(ndev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_NDEV_2_DEV((ndev)), \ + KERN_INFO SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) +#define SCSC_TAG_DEBUG_NDEV(ndev, tag, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), SCSC_NDEV_2_DEV((ndev)), \ + KERN_DEBUG SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) + +#define SCSC_TAG_ERR_DEV(tag, dev, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), dev, \ + KERN_ERR SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_TAG_WARNING_DEV(tag, dev, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), dev, \ + KERN_WARNING SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_TAG_INFO_DEV(tag, dev, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), dev, \ + KERN_INFO SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_TAG_DEBUG_DEV(tag, dev, fmt, args...) \ + scsc_printk_tag_dev(NO_FORCE_PRK, (tag), dev, \ + KERN_DEBUG SCSC_DBG_FMT(fmt), \ + __func__, ## args) + +#define SCSC_ERR_SDEV(sdev, fmt, args...) \ + SCSC_TAG_ERR_SDEV(sdev, WLBT, fmt, ## args) +#define SCSC_WARNING_SDEV(sdev, fmt, args...) \ + SCSC_TAG_WARNING_SDEV(sdev, WLBT, fmt, ## args) +#define SCSC_INFO_SDEV(sdev, fmt, args...) \ + SCSC_TAG_INFO_SDEV(sdev, WLBT, fmt, ## args) + +#define SCSC_ERR_NDEV(ndev, fmt, args...) \ + SCSC_TAG_ERR_NDEV(ndev, WLBT, fmt, ## args) +#define SCSC_WARNING_NDEV(ndev, fmt, args...) \ + SCSC_TAG_WARNING_NDEV(ndev, WLBT, fmt, ## args) +#define SCSC_INFO_NDEV(ndev, fmt, args...) \ + SCSC_TAG_INFO_NDEV(ndev, WLBT, fmt, ## args) + + +#define SCSC_BIN_EMERG(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 0, \ + (start), (len)) +#define SCSC_BIN_ALERT(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 1, \ + (start), (len)) +#define SCSC_BIN_CRIT(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 2, \ + (start), (len)) +#define SCSC_BIN_ERR(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 3, \ + (start), (len)) +#define SCSC_BIN_WARNING(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 4, \ + (start), (len)) +#define SCSC_BIN_NOTICE(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 5, \ + (start), (len)) +#define SCSC_BIN_INFO(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 6, \ + (start), (len)) +#define SCSC_BIN_DEBUG(start, len) scsc_printk_bin(NO_FORCE_PRK, BINARY, 7, \ + (start), (len)) + +#define SCSC_BIN_TAG_EMERG(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 0, \ + (start), (len)) +#define SCSC_BIN_TAG_ALERT(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 1, \ + (start), (len)) +#define SCSC_BIN_TAG_CRIT(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 2, \ + (start), (len)) +#define SCSC_BIN_TAG_ERR(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 3, \ + (start), (len)) +#define SCSC_BIN_TAG_WARNING(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 4, \ + (start), (len)) +#define SCSC_BIN_TAG_NOTICE(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 5, \ + (start), (len)) +#define SCSC_BIN_TAG_INFO(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 6, \ + (start), (len)) +#define SCSC_BIN_TAG_DEBUG(tag, start, len) scsc_printk_bin(NO_FORCE_PRK, (tag), 7, \ + (start), (len)) + + +/* + * These macros forces a redundant copy of their output to kernel log buffer and + * console through standard kernel facilities, NO matter how the tag-based + * filtering is configured and NO matter what the value in + * scsc_redirect_to_printk_droplvl module param. + */ +#define SCSC_PRINTK_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, args) +#define SCSC_PRINTK_TAG_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), args) +#define SCSC_PRINTK_BIN_FF(start, len) scsc_printk_bin(FORCE_PRK, -1, \ + (start), (len)) + +#define SCSC_EMERG_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_EMERG SCSC_PREFIX args) +#define SCSC_ALERT_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_ALERT SCSC_PREFIX args) +#define SCSC_CRIT_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_CRIT SCSC_PREFIX args) +#define SCSC_ERR_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_ERR SCSC_PREFIX args) +#define SCSC_WARNING_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_WARNING SCSC_PREFIX args) +#define SCSC_NOTICE_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_NOTICE SCSC_PREFIX args) +#define SCSC_INFO_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_INFO SCSC_PREFIX args) +#define SCSC_DEBUG_FF(args ...) scsc_printk_tag(FORCE_PRK, WLBT, \ + KERN_DEBUG SCSC_PREFIX args) + +#define SCSC_TAG_EMERG_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_EMERG SCSC_PREFIX args) +#define SCSC_TAG_ALERT_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_ALERT SCSC_PREFIX args) +#define SCSC_TAG_CRIT_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_CRIT SCSC_PREFIX args) +#define SCSC_TAG_ERR_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_ERR SCSC_PREFIX args) +#define SCSC_TAG_WARNING_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_WARNING SCSC_PREFIX args) +#define SCSC_TAG_NOTICE_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_NOTICE SCSC_PREFIX args) +#define SCSC_TAG_INFO_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_INFO SCSC_PREFIX args) +#define SCSC_TAG_DEBUG_FF(tag, args ...) scsc_printk_tag(FORCE_PRK, (tag), \ + KERN_DEBUG SCSC_PREFIX args) + +#define SCSC_BIN_EMERG_FF(start, len) scsc_printk_bin(FORCE_PRK, 0, \ + (start), (len)) +#define SCSC_BIN_ALERT_FF(start, len) scsc_printk_bin(FORCE_PRK, 1, \ + (start), (len)) +#define SCSC_BIN_CRIT_FF(start, len) scsc_printk_bin(FORCE_PRK, 2, \ + (start), (len)) +#define SCSC_BIN_ERR_FF(start, len) scsc_printk_bin(FORCE_PRK, 3, \ + (start), (len)) +#define SCSC_BIN_WARNING_FF(start, len) scsc_printk_bin(FORCE_PRK, 4, \ + (start), (len)) +#define SCSC_BIN_NOTICE_FF(start, len) scsc_printk_bin(FORCE_PRK, 5, \ + (start), (len)) +#define SCSC_BIN_INFO_FF(start, len) scsc_printk_bin(FORCE_PRK, 6, \ + (start), (len)) +#define SCSC_BIN_DEBUG_FF(start, len) scsc_printk_bin(FORCE_PRK, 7, \ + (start), (len)) + +#define SCSC_TAG_LVL(tag, lvl, fmt, args...) \ + scsc_printk_tag_lvl((tag), (lvl), fmt, ## args) + +#define SCSC_TAG_DEV_LVL(tag, lvl, dev, fmt, args...) \ + scsc_printk_tag_dev_lvl(NO_FORCE_PRK, (tag), (dev), (lvl), fmt, ## args) + + +#define SCSC_TAG_DBG1_SDEV(sdev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 7, SCSC_SDEV_2_DEV((sdev)), \ + SCSC_DBG_FMT(fmt), __func__, ## args) + +#define SCSC_TAG_DBG2_SDEV(sdev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 8, SCSC_SDEV_2_DEV((sdev)), \ + SCSC_DBG_FMT(fmt), __func__, ## args) + +#define SCSC_TAG_DBG3_SDEV(sdev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 9, SCSC_SDEV_2_DEV((sdev)), \ + SCSC_DBG_FMT(fmt), __func__, ## args) + +#define SCSC_TAG_DBG4_SDEV(sdev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 10, SCSC_SDEV_2_DEV((sdev)), \ + SCSC_DBG_FMT(fmt), __func__, ## args) + +#define SCSC_TAG_DBG1_NDEV(ndev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 7, SCSC_NDEV_2_DEV((ndev)), SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) + +#define SCSC_TAG_DBG2_NDEV(ndev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 8, SCSC_NDEV_2_DEV((ndev)), SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) + +#define SCSC_TAG_DBG3_NDEV(ndev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 9, SCSC_NDEV_2_DEV((ndev)), SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) + +#define SCSC_TAG_DBG4_NDEV(ndev, tag, fmt, args...) \ + SCSC_TAG_DEV_LVL((tag), 10, SCSC_NDEV_2_DEV((ndev)), SCSC_DEV_FMT(fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), \ + __func__, ## args) + +#define SCSC_TAG_DBG1(tag, fmt, args ...) \ + SCSC_TAG_LVL((tag), 7, fmt, ## args) + +#define SCSC_TAG_DBG2(tag, fmt, args ...) \ + SCSC_TAG_LVL((tag), 8, fmt, ## args) + +#define SCSC_TAG_DBG3(tag, fmt, args ...) \ + SCSC_TAG_LVL((tag), 9, fmt, ## args) + +#define SCSC_TAG_DBG4(tag, fmt, args ...) \ + SCSC_TAG_LVL((tag), 10, fmt, ## args) + +#else /* CONFIG_SCSC_PRINTK */ + +#define SCSC_TAG_LVL(tag, lvl, fmt, args...) \ + do {\ + if ((lvl) < 7)\ + printk_emit(0, (lvl), NULL, 0, fmt, ## args);\ + } while (0) + +#define SCSC_TAG_DEV_LVL(tag, lvl, dev, fmt, args...) \ + do {\ + if ((lvl) < 7)\ + dev_printk_emit((lvl), (dev), fmt, ## args);\ + } while (0) + +#define SCSC_PRINTK(fmt, args ...) printk(SCSC_PREFIX fmt, ## args) +#define SCSC_PRINTK_TAG(tag, fmt, args ...) printk(SCSC_PREFIX "[" # tag "] "fmt, ## args) +#define SCSC_PRINTK_BIN(start, len) print_hex_dump(KERN_INFO, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_PRINTK_FF(fmt, args ...) printk(SCSC_PREFIX fmt, ## args) +#define SCSC_PRINTK_TAG_FF(tag, fmt, args ...) printk(SCSC_PREFIX"[" # tag "] "fmt, ## args) +#define SCSC_PRINTK_BIN_FF(start, len) print_hex_dump(KERN_INFO, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_EMERG(fmt, args...) pr_emerg(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_ALERT(fmt, args...) pr_alert(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_CRIT(fmt, args...) pr_crit(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_ERR(fmt, args...) pr_err(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_WARNING(fmt, args...) pr_warn(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_NOTICE(fmt, args...) pr_notice(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_INFO(fmt, args...) pr_info(SCSC_DBG_FMT(fmt), __func__, ## args) +#define SCSC_DEBUG(args...) do {} while (0) + +/* Reverting to pr_* keeping the [tag] */ +#define SCSC_TAG_EMERG(tag, fmt, args...) \ + pr_emerg(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_ALERT(tag, fmt, args...) \ + pr_alert(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_CRIT(tag, fmt, args...) \ + pr_crit(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_ERR(tag, fmt, args...) \ + pr_err(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_WARNING(tag, fmt, args...) \ + pr_warn(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_NOTICE(tag, fmt, args...) \ + pr_notice(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_INFO(tag, fmt, args...) \ + pr_info(SCSC_TAG_DBG_FMT(tag, fmt), __func__, ## args) +#define SCSC_TAG_DEBUG(tag, fmt, args...) do {} while (0) + + +#define SCSC_BIN_EMERG(start, len) print_hex_dump(KERN_EMERG, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_ALERT(start, len) print_hex_dump(KERN_ALERT, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_CRIT(start, len) print_hex_dump(KERN_CRIT, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_ERR(start, len) print_hex_dump(KERN_ERR, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_WARNING(start, len) print_hex_dump(KERN_WARNING, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_NOTICE(start, len) print_hex_dump(KERN_NOTICE, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_INFO(start, len) print_hex_dump(KERN_INFO, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_DEBUG(start, len) do {} while (0) + + +#define SCSC_BIN_TAG_EMERG(tag, start, len) print_hex_dump(KERN_EMERG, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_TAG_ALERT(tag, start, len) print_hex_dump(KERN_ALERT, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_TAG_CRIT(tag, start, len) print_hex_dump(KERN_CRIT, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_TAG_ERR(tag, start, len) print_hex_dump(KERN_ERR, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_TAG_WARNING(tag, start, len) print_hex_dump(KERN_WARNING, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_TAG_NOTICE(tag, start, len) print_hex_dump(KERN_NOTICE, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_TAG_INFO(tag, start, len) print_hex_dump(KERN_INFO, \ + SCSC_PREFIX"[" # tag "]->|", \ + DUMP_PREFIX_NONE, \ + 16, 1, start, \ + len, false) + +#define SCSC_BIN_TAG_DEBUG(tag, start, len) do {} while (0) + + +#define SCSC_EMERG_FF(args ...) pr_emerg(SCSC_PREFIX args) +#define SCSC_ALERT_FF(args ...) pr_alert(SCSC_PREFIX args) +#define SCSC_CRIT_FF(args ...) pr_crit(SCSC_PREFIX args) +#define SCSC_ERR_FF(args ...) pr_err(SCSC_PREFIX args) +#define SCSC_WARNING_FF(args ...) pr_warn(SCSC_PREFIX args) +#define SCSC_NOTICE_FF(args ...) pr_notice(SCSC_PREFIX args) +#define SCSC_INFO_FF(args ...) pr_info(SCSC_PREFIX args) +#define SCSC_DEBUG_FF(args ...) do {} while (0) + + +#define SCSC_TAG_EMERG_FF(tag, fmt, args ...) pr_emerg(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_ALERT_FF(tag, fmt, args ...) pr_alert(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_CRIT_FF(tag, fmt, args ...) pr_crit(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_ERR_FF(tag, fmt, args ...) pr_err(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_WARNING_FF(tag, fmt, args ...) pr_warn(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_NOTICE_FF(tag, fmt, args ...) pr_notice(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_INFO_FF(tag, fmt, args ...) pr_info(SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) +#define SCSC_TAG_DEBUG_FF(tag, fmt, args ...) do {} while (0) + +#define SCSC_BIN_EMERG_FF(start, len) print_hex_dump(KERN_EMERG, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_ALERT_FF(start, len) print_hex_dump(KERN_ALERT, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_CRIT_FF(start, len) print_hex_dump(KERN_CRIT, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_ERR_FF(start, len) print_hex_dump(KERN_ERR, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_WARNING_FF(start, len) print_hex_dump(KERN_WARNING, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_NOTICE_FF(start, len) print_hex_dump(KERN_NOTICE, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_INFO_FF(start, len) print_hex_dump(KERN_INFO, \ + SCSC_PREFIX"[BINARY]->|", \ + DUMP_PREFIX_ADDRESS, \ + 16, 4, start, \ + len, true) + +#define SCSC_BIN_DEBUG_FF(start, len) do {} while (0) + + +#define SCSC_TAG_ERR_SDEV(sdev, tag, fmt, args...) \ + dev_err(SCSC_SDEV_2_DEV((sdev)), SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) + +#define SCSC_TAG_WARNING_SDEV(sdev, tag, fmt, args...) \ + dev_warn(SCSC_SDEV_2_DEV((sdev)), SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) + +#define SCSC_TAG_INFO_SDEV(sdev, tag, fmt, args...) \ + dev_info(SCSC_SDEV_2_DEV((sdev)), SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) + +#define SCSC_TAG_DEBUG_SDEV(sdev, tag, fmt, args...) do {} while (0) + + +#define SCSC_TAG_ERR_NDEV(ndev, tag, fmt, args...) \ + dev_err(SCSC_NDEV_2_DEV((ndev)), SCSC_TAG_FMT(tag, fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), __func__, ## args) +#define SCSC_TAG_WARNING_NDEV(ndev, tag, fmt, args...) \ + dev_warn(SCSC_NDEV_2_DEV((ndev)), SCSC_TAG_FMT(tag, fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), __func__, ## args) +#define SCSC_TAG_INFO_NDEV(ndev, tag, fmt, args...) \ + dev_info(SCSC_NDEV_2_DEV((ndev)), SCSC_TAG_FMT(tag, fmt), \ + ((ndev) ? netdev_name(ndev) : NODEV_LABEL), __func__, ## args) + +#define SCSC_TAG_DEBUG_NDEV(ndev, tag, fmt, args...) do {} while (0) + +#define SCSC_TAG_ERR_DEV(tag, dev, fmt, args...) \ + dev_err(dev, SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) + +#define SCSC_TAG_WARNING_DEV(tag, dev, fmt, args...) \ + dev_warn(dev, SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) + +#define SCSC_TAG_INFO_DEV(tag, dev, fmt, args...) \ + dev_info(dev, SCSC_TAG_DBG_FMT(tag, fmt), \ + __func__, ## args) + +#define SCSC_TAG_DEBUG_DEV(tag, dev, fmt, args...) do {} while (0) + +#define SCSC_ERR_SDEV(sdev, fmt, args...) \ + SCSC_TAG_ERR_SDEV(sdev, WLBT, fmt, ## args) +#define SCSC_WARNING_SDEV(sdev, fmt, args...) \ + SCSC_TAG_WARNING_SDEV(sdev, WLBT, fmt, ## args) +#define SCSC_INFO_SDEV(sdev, fmt, args...) \ + SCSC_TAG_INFO_SDEV(sdev, WLBT, fmt, ## args) + +#define SCSC_ERR_NDEV(ndev, fmt, args...) \ + SCSC_TAG_ERR_NDEV(ndev, WLBT, fmt, ## args) +#define SCSC_WARNING_NDEV(ndev, fmt, args...) \ + SCSC_TAG_WARNING_NDEV(ndev, WLBT, fmt, ## args) +#define SCSC_INFO_NDEV(ndev, fmt, args...) \ + SCSC_TAG_INFO_NDEV(ndev, WLBT, fmt, ## args) + + +#define SCSC_TAG_DBG1_SDEV(sdev, tag, fmt, args...) do {} while (0) +#define SCSC_TAG_DBG2_SDEV(sdev, tag, fmt, args...) do {} while (0) +#define SCSC_TAG_DBG3_SDEV(sdev, tag, fmt, args...) do {} while (0) +#define SCSC_TAG_DBG4_SDEV(sdev, tag, fmt, args...) do {} while (0) + +#define SCSC_TAG_DBG1_NDEV(ndev, tag, fmt, args...) do {} while (0) +#define SCSC_TAG_DBG2_NDEV(ndev, tag, fmt, args...) do {} while (0) +#define SCSC_TAG_DBG3_NDEV(ndev, tag, fmt, args...) do {} while (0) +#define SCSC_TAG_DBG4_NDEV(ndev, tag, fmt, args...) do {} while (0) + +#define SCSC_TAG_DBG1(tag, fmt, args ...) do {} while (0) +#define SCSC_TAG_DBG2(tag, fmt, args ...) do {} while (0) +#define SCSC_TAG_DBG3(tag, fmt, args ...) do {} while (0) +#define SCSC_TAG_DBG4(tag, fmt, args ...) do {} while (0) + +#endif + +#endif /* _SCSC_LOGRING_H_ */ diff --git a/include/scsc/scsc_mifram.h b/include/scsc/scsc_mifram.h new file mode 100644 index 000000000000..671028d2f5e0 --- /dev/null +++ b/include/scsc/scsc_mifram.h @@ -0,0 +1,16 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _SCSC_MIFRAM_H +#define _SCSC_MIFRAM_H + +/* + * Portable reference to DRAM address. + * In practice this should native R4 relative address. + */ + +typedef s32 scsc_mifram_ref; +#endif diff --git a/include/scsc/scsc_mx.h b/include/scsc/scsc_mx.h new file mode 100755 index 000000000000..95c6c3b26116 --- /dev/null +++ b/include/scsc/scsc_mx.h @@ -0,0 +1,442 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + ****************************************************************************/ + +#ifndef _SCSC_CORE_H +#define _SCSC_CORE_H + +#include +#include +#include "scsc_mifram.h" + +#define SCSC_PANIC_CODE_FW 0 +#define SCSC_PANIC_CODE_HOST 1 + +#define SCSC_FW_EVENT_FAILURE 0 +#define SCSC_FW_EVENT_MOREDUMP_COMPLETE 1 + +/** The following flags define the pools that can used for memory allocation. + * To be used with scsc_mx_service_mifram_alloc_extended **/ +/* Standard memory allocation */ +#define MIFRAMMAN_MEM_POOL_GENERIC 1 +/* Used for buffers containing logs that will not be dumped by moredump */ +#define MIFRAMMAN_MEM_POOL_LOGGING 2 + +struct device; +struct firmware; +struct scsc_mx; + +enum scsc_service_id { + SCSC_SERVICE_ID_NULL = 0, + SCSC_SERVICE_ID_WLAN = 1, + SCSC_SERVICE_ID_BT = 2, + SCSC_SERVICE_ID_ANT = 3, + SCSC_SERVICE_ID_R4DBG = 4, + SCSC_SERVICE_ID_ECHO = 5, + SCSC_SERVICE_ID_DBG_SAMPLER = 6, + SCSC_SERVICE_ID_CLK20MHZ = 7, + SCSC_SERVICE_ID_FM = 8, + SCSC_SERVICE_ID_INVALID = 0xff, +}; + +#ifdef CONFIG_SCSC_QOS +#define SCSC_SERVICE_TOTAL 9 +#endif + +enum scsc_module_client_reason { + SCSC_MODULE_CLIENT_REASON_HW_PROBE = 0, + SCSC_MODULE_CLIENT_REASON_HW_REMOVE = 1, + SCSC_MODULE_CLIENT_REASON_RECOVERY = 2, + SCSC_MODULE_CLIENT_REASON_INVALID = 0xff, +}; + +#ifdef CONFIG_SCSC_QOS +enum scsc_qos_config { + SCSC_QOS_DISABLED = 0, + SCSC_QOS_MIN = 1, + SCSC_QOS_MED = 2, + SCSC_QOS_MAX = 3, +}; +#endif + +/* Core Driver Module registration */ +struct scsc_mx_module_client { + char *name; + void (*probe)(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason); + void (*remove)(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason); +}; + +/* Service Client interface */ + +struct scsc_service_client; + +struct scsc_service_client { + /** Called on Maxwell failure. The Client should Stop all SDRAM & MIF + * Mailbox access as fast as possible and inform the Manager by calling + * client_stopped() */ + void (*stop_on_failure)(struct scsc_service_client *client); + /** Called when Maxwell failure has handled and the Maxwell has been + * reset. The Client should assume that any Maxwell resources it held are + * invalid */ + void (*failure_reset)(struct scsc_service_client *client, u16 scsc_panic_code); + /* called when AP processor is going into suspend. */ + int (*suspend)(struct scsc_service_client *client); + /* called when AP processor has resumed */ + int (*resume)(struct scsc_service_client *client); + /* called when log collection has been triggered */ + void (*log)(struct scsc_service_client *client, u16 reason); +}; + +#ifdef CONFIG_SCSC_FM +/* + * This must be used by FM Radio Service only. Other services must not use it. + * FM Radio client must allocate memory for this structure using scsc_mx_service_mifram_alloc() + * and pass this structure as a ref parameter to scsc_mx_service_start(). + * The version of fm_ldo_conf (the LDO configuration structure) must be written + * to the version field by the FM Radio Service and confirmed to match the define by the firmware. + * Increment the version (FM_LDO_CONFIG_VERSION) when changing the layout of the structure. + */ +#define FM_LDO_CONFIG_VERSION 0 + +struct fm_ldo_conf { + uint32_t version; /* FM_LDO_CONFIG_VERSION */ + uint32_t ldo_on; +}; + +/* Parameters to pass from FM radio client driver to WLBT drivers */ +struct wlbt_fm_params { + u32 freq; /* Frequency (Hz) in use by FM radio */ +}; + +#endif + +#define PANIC_RECORD_SIZE 64 +#define PANIC_RECORD_DUMP_BUFFER_SZ 4096 + +/* WARNING: THIS IS INTERRUPT CONTEXT! + * here: some serious warnings about not blocking or doing anything lengthy at all + */ +typedef void (*scsc_mifintrbit_handler)(int which_bit, void *data); + +/* + * Core Module Inteface + */ +int scsc_mx_module_register_client_module(struct scsc_mx_module_client *module_client); +void scsc_mx_module_unregister_client_module(struct scsc_mx_module_client *module_client); +int scsc_mx_module_reset(void); + +/* + * Core Instance interface + */ +/** 1st thing to do is call open and return service managment interface*/ +struct scsc_service *scsc_mx_service_open(struct scsc_mx *mx, enum scsc_service_id id, struct scsc_service_client *client, int *status); + +/* + * Service interface + */ +/** pass a portable dram reference and returns kernel pointer (basically is dealing with the pointers) */ +void *scsc_mx_service_mif_addr_to_ptr(struct scsc_service *service, scsc_mifram_ref ref); +void *scsc_mx_service_mif_addr_to_phys(struct scsc_service *service, scsc_mifram_ref ref); +int scsc_mx_service_mif_ptr_to_addr(struct scsc_service *service, void *mem_ptr, scsc_mifram_ref *ref); + +int scsc_mx_service_start(struct scsc_service *service, scsc_mifram_ref ref); +int scsc_mx_service_stop(struct scsc_service *service); +int scsc_mx_service_close(struct scsc_service *service); +int scsc_mx_service_mif_dump_registers(struct scsc_service *service); + +/** Signal a failure detected by the Client. This will trigger the systemwide + * failure handling procedure: _All_ Clients will be called back via + * their stop_on_failure() handler as a side-effect. */ +void scsc_mx_service_service_failed(struct scsc_service *service, const char *reason); + +/* MEMORY Interface*/ +/** Allocate a contiguous block of SDRAM accessible to Client Driver. The memory will be allocated + * from generic pool (MIFRAMMAN_MEM_POOL_GENERIC) */ +int scsc_mx_service_mifram_alloc(struct scsc_service *service, size_t nbytes, scsc_mifram_ref *ref, u32 align); +/* Same as scsc_mx_service_mifram_alloc but allows to specify flags (MIFRAMMAN_MEM_POOL_XX). + * So, for example, to allocate memory from the logging pool use MIFRAMMAN_MEM_POOL_LOGGING. */ +int scsc_mx_service_mifram_alloc_extended(struct scsc_service *service, size_t nbytes, scsc_mifram_ref *ref, u32 align, uint32_t flags); +struct scsc_bt_audio_abox *scsc_mx_service_get_bt_audio_abox(struct scsc_service *service); +struct mifabox *scsc_mx_service_get_aboxram(struct scsc_service *service); +/** Free a contiguous block of SDRAM */ +void scsc_mx_service_mifram_free(struct scsc_service *service, scsc_mifram_ref ref); +void scsc_mx_service_mifram_free_extended(struct scsc_service *service, scsc_mifram_ref ref, uint32_t flags); + +/* MBOX Interface */ +/** Allocate n contiguous mailboxes. Outputs index of first mbox, returns FALSE if can’t allocate n contiguous mailboxes. */ +bool scsc_mx_service_alloc_mboxes(struct scsc_service *service, int n, int *first_mbox_index); +/** Free n contiguous mailboxes. */ +void scsc_service_free_mboxes(struct scsc_service *service, int n, int first_mbox_index); + +/** Get kernel-space pointer to a mailbox. + * The pointer can be cached as it is guaranteed not to change between service start & stop. + **/ +u32 *scsc_mx_service_get_mbox_ptr(struct scsc_service *service, int mbox_index); + +/* IRQ Interface */ +/* Getters/Setters */ + +/* From R4/M4 */ +int scsc_service_mifintrbit_bit_mask_status_get(struct scsc_service *service); +int scsc_service_mifintrbit_get(struct scsc_service *service); +void scsc_service_mifintrbit_bit_clear(struct scsc_service *service, int which_bit); +void scsc_service_mifintrbit_bit_mask(struct scsc_service *service, int which_bit); +void scsc_service_mifintrbit_bit_unmask(struct scsc_service *service, int which_bit); + +/* To R4/M4 */ +enum scsc_mifintr_target { + SCSC_MIFINTR_TARGET_R4 = 0, + SCSC_MIFINTR_TARGET_M4 = 1 +}; + +void scsc_service_mifintrbit_bit_set(struct scsc_service *service, int which_bit, enum scsc_mifintr_target dir); + +/* Register an interrupt handler -TOHOST direction. + * Function returns the IRQ associated , -EIO if all interrupts have been assigned */ +int scsc_service_mifintrbit_register_tohost(struct scsc_service *service, scsc_mifintrbit_handler handler, void *data); +/* Unregister an interrupt handler associated with a bit -TOHOST direction */ +int scsc_service_mifintrbit_unregister_tohost(struct scsc_service *service, int which_bit); + +/* Get an interrupt bit associated with the target (R4/M4) -FROMHOST direction + * Function returns the IRQ bit associated , -EIO if error */ +int scsc_service_mifintrbit_alloc_fromhost(struct scsc_service *service, enum scsc_mifintr_target dir); +/* Free an interrupt bit associated with the target (R4/M4) -FROMHOST direction + * Function returns the 0 if succedes , -EIO if error */ +int scsc_service_mifintrbit_free_fromhost(struct scsc_service *service, int which_bit, enum scsc_mifintr_target dir); +/* + * Return a kernel device associated 1:1 with the Maxwell instance. + * This is published only for the purpose of associating service drivers + * with a Maxwell instance for logging purposes. Clients should not make + * any assumptions about the device type. In some configurations this may + * be the associated host-interface device (AXI/PCIe), + * but this may change in future. + */ +struct device *scsc_service_get_device(struct scsc_service *service); +struct device *scsc_service_get_device_by_mx(struct scsc_mx *mx); + +int scsc_service_force_panic(struct scsc_service *service); + +/* + * API to share /sys/wifi kobject between core and wifi driver modules. + * Depending upon the order of loading respective drivers, a kobject is + * created and shared with the other driver. This convoluted implementation + * is required as we need the common kobject associated with "/sys/wifi" directory + * when creating a file underneth. core driver (mxman.c) need to create "memdump" + * and wifi driver (dev.c,mgt.c) needs to create "mac_addr" files respectively. + */ +struct kobject *mxman_wifi_kobject_ref_get(void); +void mxman_wifi_kobject_ref_put(void); + +#ifdef CONFIG_SCSC_SMAPPER +/* SMAPPER Interface */ +/* Configure smapper. Function should configure smapper FW memory map, range, and granularity */ +void scsc_service_mifsmapper_configure(struct scsc_service *service, u32 granularity); +/* Allocate large/small entries bank. Outputs index of bank, returns -EIO if can’t allocate any banks. */ +/* Function also returns by the numbers of entries that could be used in the bank as the number of entries + * is HW dependent (entries/granurality/memory window in FW) + */ +int scsc_service_mifsmapper_alloc_bank(struct scsc_service *service, bool large_bank, u32 entry_size, u16 *entries); +/* Free large/small entries bank */ +int scsc_service_mifsmapper_free_bank(struct scsc_service *service, u8 bank); +/* Get number entries, returns error if entries have not been allocated */ +int scsc_service_mifsmapper_get_entries(struct scsc_service *service, u8 bank, u8 num_entries, u8 *entries); +/* Free number entries, returns error if entries have not been allocated */ +int scsc_service_mifsmapper_free_entries(struct scsc_service *service, u8 bank, u8 num_entries, u8 *entries); +/* Program SRAM entry */ +int scsc_service_mifsmapper_write_sram(struct scsc_service *service, u8 bank, u8 num_entries, u8 first_entry, dma_addr_t *addr); +u32 scsc_service_mifsmapper_get_bank_base_address(struct scsc_service *service, u8 bank); +/* Get SMAPPER aligment */ +u16 scsc_service_get_alignment(struct scsc_service *service); +#endif + +#ifdef CONFIG_SCSC_QOS +int scsc_service_pm_qos_add_request(struct scsc_service *service, enum scsc_qos_config config); +int scsc_service_pm_qos_update_request(struct scsc_service *service, enum scsc_qos_config config); +int scsc_service_pm_qos_remove_request(struct scsc_service *service); +#endif + +/* MXLOGGER API */ +/* If there is no service/mxman associated, register the observer as global (will affect all the mx instanes)*/ +/* Users of these functions should ensure that the registers/unregister functions are balanced (i.e. if observer is registed as global, + * it _has_ to unregister as global) */ +int scsc_service_register_observer(struct scsc_service *service, char *name); +/* Unregister an observer */ +int scsc_service_unregister_observer(struct scsc_service *service, char *name); + +/* Reads a configuration file into memory. + * + * Path is relative to the currently selected firmware configuration + * subdirectory. + * Returns pointer to data or NULL if file not found. + * Call mx140_file_release_conf()to release the memory. + */ +int mx140_file_request_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_path, const char *filename); + +/* Reads a debug configuration file into memory. + * + * Path is relative to the currently selected firmware configuration + * subdirectory. + * Returns pointer to data or NULL if file not found. + * Call mx140_file_release_conf()to release the memory. + */ +int mx140_file_request_debug_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_path); + +/* Read device configuration file into memory. + * + * Path is relative to the device configuration directory. + * Returns pointer to data or NULL if file not found. + * Call mx140_file_release_conf() to release the memory. + * This call is only used for configuration files that are + * device instance specific (e.g. mac addresses) + */ +int mx140_file_request_device_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_path); + +/* Release configuration file memory + * + * If conf is NULL, has no effect. + */ +void mx140_file_release_conf(struct scsc_mx *mx, const struct firmware *conf); + +/* Read device configuration file into memory. + * + * Path is absolute. + * Returns pointer to data or NULL if file not found. + * Call mx140_release_file() to release the memory. + */ +int mx140_request_file(struct scsc_mx *mx, char *path, const struct firmware **firmp); + +/* Release configuration file memory allocated with mx140_request_file() + * + * If firmp is NULL, has no effect. + */ +int mx140_release_file(struct scsc_mx *mx, const struct firmware *firmp); + +/* 20 MHz clock API. + * The mx140 device uses a clock that is also required by the USB driver. + * This API allows the USB/clock driver to inform the mx140 driver that the + * clock is required and that it must boot and/or keep the clock running. + */ + +enum mx140_clk20mhz_status { + MX140_CLK_SUCCESS = 0, /* Returned successfully */ + MX140_CLK_STARTED, /* mx140 has started the clock */ + MX140_CLK_STOPPED, /* mx140 has stopped the clock */ + MX140_CLK_NOT_STARTED, /* failed to start the clock */ + MX140_CLK_NOT_STOPPED, /* failed to stop the clock */ + MX140_CLK_ASYNC_FAIL, /* mx140 failure, async call */ +}; + +/* Register for 20 MHz clock API callbacks + * + * Parameters: + * client_cb: + * If client provides non-NULL client_cb, the request is asynchronous and + * the client will be called back when the clock service is started. + * If client_cb is NULL, the request is blocking. + * data: + * opaque context for the client, and will be passed back in any callback + * + * Note it is possible that the callback may be made in the context of the + * calling request/release function. + * + * Returns 0 on success + */ +int mx140_clk20mhz_register(void (*client_cb)(void *data, enum mx140_clk20mhz_status event), void *data); + +/* Unregister for 20 MHz clock API callbacks. + * After this call is made, the mx140 driver will no longer call back. + */ +void mx140_clk20mhz_unregister(void); + +/* Client request that the clock be available. + * + * If a callback was installed via mx140_clk20mhz_register(), the mx140 driver + * will call back when the clock is available. If no callback was installed, + * the request is blocking and will return when the clock is running. + * + * Returns: + * mx140_clk20mhz_status if a blocking attempt was made to start the clock, + * MX140_CLK_SUCCESS if the request will happen asynchronously, or, + * -ve error code on other error. + * + */ +int mx140_clk20mhz_request(void); + +/* Client informs that the clock is no longer needed + * + * Returns: + * mx140_clk20mhz_status if a blocking attempt was made to stop the clock, + * MX140_CLK_SUCCESS if the request will happen asynchronously, or, + * -ve error code on other error. + */ +int mx140_clk20mhz_release(void); + + +/* Client requests that FM LDO be available. + * + * Returns: + * 0 on success or -ve error code on error. + * + */ +int mx250_fm_request(void); + + +/* Client informs that the LDO is no longer needed + * + * Returns: + * 0 on success or -ve error code on error. + */ +int mx250_fm_release(void); + + +/* FM client informs of parameter change. + * + * mx250_fm_request() must have been called first. + * + * Returns: + * None + */ +void mx250_fm_set_params(struct wlbt_fm_params *info); + +/* + * for set test mode. + * + */ +bool slsi_is_rf_test_mode_enabled(void); + +int mx140_log_dump(void); + +void mxman_get_fw_version(char *version, size_t ver_sz); +void mxman_get_driver_version(char *version, size_t ver_sz); + +int mxman_register_firmware_notifier(struct notifier_block *nb); +int mxman_unregister_firmware_notifier(struct notifier_block *nb); + +/* Status of WLBT autorecovery on the platform + * + * Returns: + * false - enabled, true disabled + */ +bool mxman_recovery_disabled(void); + +/* function to provide string representation of uint8 trigger code */ +static inline const char *scsc_get_trigger_str(int code) +{ + switch (code) { + case 1: return "scsc_log_fw_panic"; + case 2: return "scsc_log_user"; + case 3: return "scsc_log_fw"; + case 4: return "scsc_log_dumpstate"; + case 5: return "scsc_log_host_wlan"; + case 6: return "scsc_log_host_bt"; + case 7: return "scsc_log_host_common"; + case 8: return "scsc_log_sys_error"; + case 0: + default: + return "unknown"; + } +}; + +#endif diff --git a/include/scsc/scsc_release.h b/include/scsc/scsc_release.h new file mode 100644 index 000000000000..1688e3ebc460 --- /dev/null +++ b/include/scsc/scsc_release.h @@ -0,0 +1,20 @@ +/**************************************************************************** + * + * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved + * + *****************************************************************************/ + +#ifndef _SCSC_RELEASE_H +#define _SCSC_RELEASE_H + +#define SCSC_RELEASE_SOLUTION "mx250" + +#define SCSC_RELEASE_PRODUCT 10 +#define SCSC_RELEASE_ITERATION 1 +#define SCSC_RELEASE_CANDIDATE 1 + +#define SCSC_RELEASE_POINT 0 + +#endif + + -- 2.20.1