pax_global_header00006660000000000000000000000064151550037460014520gustar00rootroot0000000000000052 comment=f2aa9fe01103d7600553b505b298ff0bd47ff280 dlundqvist-xone-f2aa9fe/000077500000000000000000000000001515500374600154435ustar00rootroot00000000000000dlundqvist-xone-f2aa9fe/.clang-format000066400000000000000000000572451515500374600200330ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # # clang-format configuration file. Intended for clang-format >= 11. # # For more information, see: # # Documentation/dev-tools/clang-format.rst # https://clang.llvm.org/docs/ClangFormat.html # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false # Taken from: # git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \ # | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ # | LC_ALL=C sort -u ForEachMacros: - '__ata_qc_for_each' - '__bio_for_each_bvec' - '__bio_for_each_segment' - '__evlist__for_each_entry' - '__evlist__for_each_entry_continue' - '__evlist__for_each_entry_from' - '__evlist__for_each_entry_reverse' - '__evlist__for_each_entry_safe' - '__for_each_mem_range' - '__for_each_mem_range_rev' - '__for_each_thread' - '__hlist_for_each_rcu' - '__map__for_each_symbol_by_name' - '__pci_bus_for_each_res0' - '__pci_bus_for_each_res1' - '__pci_dev_for_each_res0' - '__pci_dev_for_each_res1' - '__perf_evlist__for_each_entry' - '__perf_evlist__for_each_entry_reverse' - '__perf_evlist__for_each_entry_safe' - '__rq_for_each_bio' - '__shost_for_each_device' - '__sym_for_each' - '_for_each_counter' - 'apei_estatus_for_each_section' - 'ata_for_each_dev' - 'ata_for_each_link' - 'ata_qc_for_each' - 'ata_qc_for_each_raw' - 'ata_qc_for_each_with_internal' - 'ax25_for_each' - 'ax25_uid_for_each' - 'bio_for_each_bvec' - 'bio_for_each_bvec_all' - 'bio_for_each_folio_all' - 'bio_for_each_integrity_vec' - 'bio_for_each_segment' - 'bio_for_each_segment_all' - 'bio_list_for_each' - 'bip_for_each_vec' - 'bond_for_each_slave' - 'bond_for_each_slave_rcu' - 'bpf_for_each' - 'bpf_for_each_reg_in_vstate' - 'bpf_for_each_reg_in_vstate_mask' - 'bpf_for_each_spilled_reg' - 'bpf_object__for_each_map' - 'bpf_object__for_each_program' - 'btree_for_each_safe128' - 'btree_for_each_safe32' - 'btree_for_each_safe64' - 'btree_for_each_safel' - 'card_for_each_dev' - 'cgroup_taskset_for_each' - 'cgroup_taskset_for_each_leader' - 'cpu_aggr_map__for_each_idx' - 'cpufreq_for_each_efficient_entry_idx' - 'cpufreq_for_each_entry' - 'cpufreq_for_each_entry_idx' - 'cpufreq_for_each_valid_entry' - 'cpufreq_for_each_valid_entry_idx' - 'css_for_each_child' - 'css_for_each_descendant_post' - 'css_for_each_descendant_pre' - 'damon_for_each_region' - 'damon_for_each_region_from' - 'damon_for_each_region_safe' - 'damon_for_each_scheme' - 'damon_for_each_scheme_safe' - 'damon_for_each_target' - 'damon_for_each_target_safe' - 'damos_for_each_filter' - 'damos_for_each_filter_safe' - 'damos_for_each_ops_filter' - 'damos_for_each_ops_filter_safe' - 'damos_for_each_quota_goal' - 'damos_for_each_quota_goal_safe' - 'data__for_each_file' - 'data__for_each_file_new' - 'data__for_each_file_start' - 'def_for_each_cpu' - 'device_for_each_child_node' - 'device_for_each_child_node_scoped' - 'dma_fence_array_for_each' - 'dma_fence_chain_for_each' - 'dma_fence_unwrap_for_each' - 'dma_resv_for_each_fence' - 'dma_resv_for_each_fence_unlocked' - 'do_for_each_ftrace_op' - 'drm_atomic_crtc_for_each_plane' - 'drm_atomic_crtc_state_for_each_plane' - 'drm_atomic_crtc_state_for_each_plane_state' - 'drm_atomic_for_each_plane_damage' - 'drm_client_for_each_connector_iter' - 'drm_client_for_each_modeset' - 'drm_connector_for_each_possible_encoder' - 'drm_exec_for_each_locked_object' - 'drm_exec_for_each_locked_object_reverse' - 'drm_for_each_bridge_in_chain' - 'drm_for_each_connector_iter' - 'drm_for_each_crtc' - 'drm_for_each_crtc_reverse' - 'drm_for_each_encoder' - 'drm_for_each_encoder_mask' - 'drm_for_each_fb' - 'drm_for_each_legacy_plane' - 'drm_for_each_plane' - 'drm_for_each_plane_mask' - 'drm_for_each_privobj' - 'drm_gem_for_each_gpuvm_bo' - 'drm_gem_for_each_gpuvm_bo_safe' - 'drm_gpusvm_for_each_range' - 'drm_gpuva_for_each_op' - 'drm_gpuva_for_each_op_from_reverse' - 'drm_gpuva_for_each_op_reverse' - 'drm_gpuva_for_each_op_safe' - 'drm_gpuvm_bo_for_each_va' - 'drm_gpuvm_bo_for_each_va_safe' - 'drm_gpuvm_for_each_va' - 'drm_gpuvm_for_each_va_range' - 'drm_gpuvm_for_each_va_range_safe' - 'drm_gpuvm_for_each_va_safe' - 'drm_mm_for_each_hole' - 'drm_mm_for_each_node' - 'drm_mm_for_each_node_in_range' - 'drm_mm_for_each_node_safe' - 'dsa_switch_for_each_available_port' - 'dsa_switch_for_each_cpu_port' - 'dsa_switch_for_each_cpu_port_continue_reverse' - 'dsa_switch_for_each_port' - 'dsa_switch_for_each_port_continue_reverse' - 'dsa_switch_for_each_port_safe' - 'dsa_switch_for_each_user_port' - 'dsa_switch_for_each_user_port_continue_reverse' - 'dsa_tree_for_each_cpu_port' - 'dsa_tree_for_each_user_port' - 'dsa_tree_for_each_user_port_continue_reverse' - 'dso__for_each_symbol' - 'elf_hash_for_each_possible' - 'elf_symtab__for_each_symbol' - 'evlist__for_each_cpu' - 'evlist__for_each_entry' - 'evlist__for_each_entry_continue' - 'evlist__for_each_entry_from' - 'evlist__for_each_entry_reverse' - 'evlist__for_each_entry_safe' - 'flow_action_for_each' - 'for_each_acpi_consumer_dev' - 'for_each_acpi_dev_match' - 'for_each_active_dev_scope' - 'for_each_active_drhd_unit' - 'for_each_active_iommu' - 'for_each_active_irq' - 'for_each_active_route' - 'for_each_aggr_pgid' - 'for_each_alloc_capable_rdt_resource' - 'for_each_and_bit' - 'for_each_andnot_bit' - 'for_each_available_child_of_node' - 'for_each_available_child_of_node_scoped' - 'for_each_bench' - 'for_each_bio' - 'for_each_board_func_rsrc' - 'for_each_btf_ext_rec' - 'for_each_btf_ext_sec' - 'for_each_bvec' - 'for_each_capable_rdt_resource' - 'for_each_card_auxs' - 'for_each_card_auxs_safe' - 'for_each_card_components' - 'for_each_card_dapms' - 'for_each_card_pre_auxs' - 'for_each_card_prelinks' - 'for_each_card_rtds' - 'for_each_card_rtds_safe' - 'for_each_card_widgets' - 'for_each_card_widgets_safe' - 'for_each_cgroup_storage_type' - 'for_each_child_of_node' - 'for_each_child_of_node_scoped' - 'for_each_child_of_node_with_prefix' - 'for_each_clear_bit' - 'for_each_clear_bit_from' - 'for_each_clear_bitrange' - 'for_each_clear_bitrange_from' - 'for_each_cmd' - 'for_each_cmsghdr' - 'for_each_collection' - 'for_each_comp_order' - 'for_each_compatible_node' - 'for_each_component_dais' - 'for_each_component_dais_safe' - 'for_each_conduit' - 'for_each_console' - 'for_each_console_srcu' - 'for_each_cpu' - 'for_each_cpu_and' - 'for_each_cpu_andnot' - 'for_each_cpu_from' - 'for_each_cpu_or' - 'for_each_cpu_wrap' - 'for_each_dapm_widgets' - 'for_each_dedup_cand' - 'for_each_dev_addr' - 'for_each_dev_scope' - 'for_each_dma_cap_mask' - 'for_each_dpcm_be' - 'for_each_dpcm_be_rollback' - 'for_each_dpcm_be_safe' - 'for_each_dpcm_fe' - 'for_each_drhd_unit' - 'for_each_dss_dev' - 'for_each_efi_memory_desc' - 'for_each_efi_memory_desc_in_map' - 'for_each_element' - 'for_each_element_extid' - 'for_each_element_id' - 'for_each_enabled_cpu' - 'for_each_endpoint_of_node' - 'for_each_event' - 'for_each_event_tps' - 'for_each_evictable_lru' - 'for_each_fib6_node_rt_rcu' - 'for_each_fib6_walker_rt' - 'for_each_file_lock' - 'for_each_free_mem_pfn_range_in_zone_from' - 'for_each_free_mem_range' - 'for_each_free_mem_range_reverse' - 'for_each_func_rsrc' - 'for_each_gpiochip_node' - 'for_each_group_evsel' - 'for_each_group_evsel_head' - 'for_each_group_member' - 'for_each_group_member_head' - 'for_each_hstate' - 'for_each_hwgpio' - 'for_each_hwgpio_in_range' - 'for_each_if' - 'for_each_inject_fn' - 'for_each_insn' - 'for_each_insn_op_loc' - 'for_each_insn_prefix' - 'for_each_intid' - 'for_each_iommu' - 'for_each_ip_tunnel_rcu' - 'for_each_irq_desc' - 'for_each_irq_nr' - 'for_each_lang' - 'for_each_link_ch_maps' - 'for_each_link_codecs' - 'for_each_link_cpus' - 'for_each_link_platforms' - 'for_each_lru' - 'for_each_matching_node' - 'for_each_matching_node_and_match' - 'for_each_media_entity_data_link' - 'for_each_mem_pfn_range' - 'for_each_mem_range' - 'for_each_mem_range_rev' - 'for_each_mem_region' - 'for_each_member' - 'for_each_memory' - 'for_each_migratetype_order' - 'for_each_missing_reg' - 'for_each_mle_subelement' - 'for_each_mod_mem_type' - 'for_each_mon_capable_rdt_resource' - 'for_each_mp_bvec' - 'for_each_net' - 'for_each_net_continue_reverse' - 'for_each_net_rcu' - 'for_each_netdev' - 'for_each_netdev_continue' - 'for_each_netdev_continue_rcu' - 'for_each_netdev_continue_reverse' - 'for_each_netdev_dump' - 'for_each_netdev_feature' - 'for_each_netdev_in_bond_rcu' - 'for_each_netdev_rcu' - 'for_each_netdev_reverse' - 'for_each_netdev_safe' - 'for_each_new_connector_in_state' - 'for_each_new_crtc_in_state' - 'for_each_new_mst_mgr_in_state' - 'for_each_new_plane_in_state' - 'for_each_new_plane_in_state_reverse' - 'for_each_new_private_obj_in_state' - 'for_each_new_reg' - 'for_each_nhlt_endpoint' - 'for_each_nhlt_endpoint_fmtcfg' - 'for_each_nhlt_fmtcfg' - 'for_each_node' - 'for_each_node_by_name' - 'for_each_node_by_type' - 'for_each_node_mask' - 'for_each_node_numadist' - 'for_each_node_state' - 'for_each_node_with_cpus' - 'for_each_node_with_property' - 'for_each_nonreserved_multicast_dest_pgid' - 'for_each_numa_hop_mask' - 'for_each_of_allnodes' - 'for_each_of_allnodes_from' - 'for_each_of_cpu_node' - 'for_each_of_graph_port' - 'for_each_of_graph_port_endpoint' - 'for_each_of_pci_range' - 'for_each_old_connector_in_state' - 'for_each_old_crtc_in_state' - 'for_each_old_mst_mgr_in_state' - 'for_each_old_plane_in_state' - 'for_each_old_private_obj_in_state' - 'for_each_oldnew_connector_in_state' - 'for_each_oldnew_crtc_in_state' - 'for_each_oldnew_mst_mgr_in_state' - 'for_each_oldnew_plane_in_state' - 'for_each_oldnew_plane_in_state_reverse' - 'for_each_oldnew_private_obj_in_state' - 'for_each_online_cpu' - 'for_each_online_cpu_wrap' - 'for_each_online_node' - 'for_each_online_pgdat' - 'for_each_or_bit' - 'for_each_page_ext' - 'for_each_path' - 'for_each_pci_bridge' - 'for_each_pci_dev' - 'for_each_pcm_streams' - 'for_each_physmem_range' - 'for_each_populated_zone' - 'for_each_possible_cpu' - 'for_each_possible_cpu_wrap' - 'for_each_present_blessed_reg' - 'for_each_present_cpu' - 'for_each_present_section_nr' - 'for_each_prime_number' - 'for_each_prime_number_from' - 'for_each_probe_cache_entry' - 'for_each_process' - 'for_each_process_thread' - 'for_each_prop_codec_conf' - 'for_each_prop_dai_codec' - 'for_each_prop_dai_cpu' - 'for_each_prop_dlc_codecs' - 'for_each_prop_dlc_cpus' - 'for_each_prop_dlc_platforms' - 'for_each_property_of_node' - 'for_each_rdt_resource' - 'for_each_reg' - 'for_each_reg_filtered' - 'for_each_reloc' - 'for_each_reloc_from' - 'for_each_requested_gpio' - 'for_each_requested_gpio_in_range' - 'for_each_reserved_child_of_node' - 'for_each_reserved_mem_range' - 'for_each_reserved_mem_region' - 'for_each_rtd_ch_maps' - 'for_each_rtd_codec_dais' - 'for_each_rtd_components' - 'for_each_rtd_cpu_dais' - 'for_each_rtd_dais' - 'for_each_rtd_dais_reverse' - 'for_each_sband_iftype_data' - 'for_each_script' - 'for_each_sec' - 'for_each_set_bit' - 'for_each_set_bit_from' - 'for_each_set_bit_wrap' - 'for_each_set_bitrange' - 'for_each_set_bitrange_from' - 'for_each_set_clump8' - 'for_each_sg' - 'for_each_sg_dma_page' - 'for_each_sg_page' - 'for_each_sgtable_dma_page' - 'for_each_sgtable_dma_sg' - 'for_each_sgtable_page' - 'for_each_sgtable_sg' - 'for_each_sibling_event' - 'for_each_sta_active_link' - 'for_each_subelement' - 'for_each_subelement_extid' - 'for_each_subelement_id' - 'for_each_sublist' - 'for_each_subsystem' - 'for_each_suite' - 'for_each_supported_activate_fn' - 'for_each_supported_inject_fn' - 'for_each_sym' - 'for_each_thread' - 'for_each_token' - 'for_each_unicast_dest_pgid' - 'for_each_valid_link' - 'for_each_vif_active_link' - 'for_each_vma' - 'for_each_vma_range' - 'for_each_vsi' - 'for_each_wakeup_source' - 'for_each_zone' - 'for_each_zone_zonelist' - 'for_each_zone_zonelist_nodemask' - 'func_for_each_insn' - 'fwnode_for_each_available_child_node' - 'fwnode_for_each_child_node' - 'fwnode_for_each_parent_node' - 'fwnode_graph_for_each_endpoint' - 'gadget_for_each_ep' - 'genradix_for_each' - 'genradix_for_each_from' - 'genradix_for_each_reverse' - 'hash_for_each' - 'hash_for_each_possible' - 'hash_for_each_possible_rcu' - 'hash_for_each_possible_rcu_notrace' - 'hash_for_each_possible_safe' - 'hash_for_each_rcu' - 'hash_for_each_safe' - 'hashmap__for_each_entry' - 'hashmap__for_each_entry_safe' - 'hashmap__for_each_key_entry' - 'hashmap__for_each_key_entry_safe' - 'hctx_for_each_ctx' - 'hists__for_each_format' - 'hists__for_each_sort_list' - 'hlist_bl_for_each_entry' - 'hlist_bl_for_each_entry_rcu' - 'hlist_bl_for_each_entry_safe' - 'hlist_for_each' - 'hlist_for_each_entry' - 'hlist_for_each_entry_continue' - 'hlist_for_each_entry_continue_rcu' - 'hlist_for_each_entry_continue_rcu_bh' - 'hlist_for_each_entry_from' - 'hlist_for_each_entry_from_rcu' - 'hlist_for_each_entry_rcu' - 'hlist_for_each_entry_rcu_bh' - 'hlist_for_each_entry_rcu_notrace' - 'hlist_for_each_entry_safe' - 'hlist_for_each_entry_srcu' - 'hlist_for_each_safe' - 'hlist_nulls_for_each_entry' - 'hlist_nulls_for_each_entry_from' - 'hlist_nulls_for_each_entry_rcu' - 'hlist_nulls_for_each_entry_safe' - 'i3c_bus_for_each_i2cdev' - 'i3c_bus_for_each_i3cdev' - 'idr_for_each_entry' - 'idr_for_each_entry_continue' - 'idr_for_each_entry_continue_ul' - 'idr_for_each_entry_ul' - 'iio_for_each_active_channel' - 'in_dev_for_each_ifa_rcu' - 'in_dev_for_each_ifa_rtnl' - 'in_dev_for_each_ifa_rtnl_net' - 'inet_bind_bucket_for_each' - 'interval_tree_for_each_span' - 'intlist__for_each_entry' - 'intlist__for_each_entry_safe' - 'kcore_copy__for_each_phdr' - 'key_for_each' - 'key_for_each_safe' - 'klp_for_each_func' - 'klp_for_each_func_safe' - 'klp_for_each_func_static' - 'klp_for_each_object' - 'klp_for_each_object_safe' - 'klp_for_each_object_static' - 'kunit_suite_for_each_test_case' - 'kvm_for_each_memslot' - 'kvm_for_each_memslot_in_gfn_range' - 'kvm_for_each_vcpu' - 'libbpf_nla_for_each_attr' - 'list_for_each' - 'list_for_each_codec' - 'list_for_each_codec_safe' - 'list_for_each_continue' - 'list_for_each_entry' - 'list_for_each_entry_continue' - 'list_for_each_entry_continue_rcu' - 'list_for_each_entry_continue_reverse' - 'list_for_each_entry_from' - 'list_for_each_entry_from_rcu' - 'list_for_each_entry_from_reverse' - 'list_for_each_entry_lockless' - 'list_for_each_entry_rcu' - 'list_for_each_entry_reverse' - 'list_for_each_entry_safe' - 'list_for_each_entry_safe_continue' - 'list_for_each_entry_safe_from' - 'list_for_each_entry_safe_reverse' - 'list_for_each_entry_srcu' - 'list_for_each_from' - 'list_for_each_prev' - 'list_for_each_prev_safe' - 'list_for_each_rcu' - 'list_for_each_safe' - 'llist_for_each' - 'llist_for_each_entry' - 'llist_for_each_entry_safe' - 'llist_for_each_safe' - 'lwq_for_each_safe' - 'map__for_each_symbol' - 'map__for_each_symbol_by_name' - 'mas_for_each' - 'mas_for_each_rev' - 'mci_for_each_dimm' - 'media_device_for_each_entity' - 'media_device_for_each_intf' - 'media_device_for_each_link' - 'media_device_for_each_pad' - 'media_entity_for_each_pad' - 'media_pipeline_for_each_entity' - 'media_pipeline_for_each_pad' - 'mlx5_lag_for_each_peer_mdev' - 'mptcp_for_each_subflow' - 'msi_domain_for_each_desc' - 'msi_for_each_desc' - 'mt_for_each' - 'nanddev_io_for_each_block' - 'nanddev_io_for_each_page' - 'neigh_for_each_in_bucket' - 'neigh_for_each_in_bucket_rcu' - 'neigh_for_each_in_bucket_safe' - 'netdev_for_each_lower_dev' - 'netdev_for_each_lower_private' - 'netdev_for_each_lower_private_rcu' - 'netdev_for_each_mc_addr' - 'netdev_for_each_synced_mc_addr' - 'netdev_for_each_synced_uc_addr' - 'netdev_for_each_uc_addr' - 'netdev_for_each_upper_dev_rcu' - 'netdev_hw_addr_list_for_each' - 'nft_rule_for_each_expr' - 'nla_for_each_attr' - 'nla_for_each_attr_type' - 'nla_for_each_nested' - 'nla_for_each_nested_type' - 'nlmsg_for_each_attr' - 'nlmsg_for_each_msg' - 'nr_neigh_for_each' - 'nr_neigh_for_each_safe' - 'nr_node_for_each' - 'nr_node_for_each_safe' - 'of_for_each_phandle' - 'of_property_for_each_string' - 'of_property_for_each_u32' - 'pci_bus_for_each_resource' - 'pci_dev_for_each_resource' - 'pcl_for_each_chunk' - 'pcl_for_each_segment' - 'pcm_for_each_format' - 'perf_config_items__for_each_entry' - 'perf_config_sections__for_each_entry' - 'perf_config_set__for_each_entry' - 'perf_cpu_map__for_each_cpu' - 'perf_cpu_map__for_each_cpu_skip_any' - 'perf_cpu_map__for_each_idx' - 'perf_evlist__for_each_entry' - 'perf_evlist__for_each_entry_reverse' - 'perf_evlist__for_each_entry_safe' - 'perf_evlist__for_each_evsel' - 'perf_evlist__for_each_mmap' - 'perf_evsel_for_each_per_thread_period_safe' - 'perf_hpp_list__for_each_format' - 'perf_hpp_list__for_each_format_safe' - 'perf_hpp_list__for_each_sort_list' - 'perf_hpp_list__for_each_sort_list_safe' - 'plist_for_each' - 'plist_for_each_continue' - 'plist_for_each_entry' - 'plist_for_each_entry_continue' - 'plist_for_each_entry_safe' - 'plist_for_each_safe' - 'pnp_for_each_card' - 'pnp_for_each_dev' - 'protocol_for_each_card' - 'protocol_for_each_dev' - 'queue_for_each_hw_ctx' - 'radix_tree_for_each_slot' - 'radix_tree_for_each_tagged' - 'rb_for_each' - 'rbtree_postorder_for_each_entry_safe' - 'rdma_for_each_block' - 'rdma_for_each_port' - 'rdma_umem_for_each_dma_block' - 'resource_list_for_each_entry' - 'resource_list_for_each_entry_safe' - 'rhl_for_each_entry_rcu' - 'rhl_for_each_rcu' - 'rht_for_each' - 'rht_for_each_entry' - 'rht_for_each_entry_from' - 'rht_for_each_entry_rcu' - 'rht_for_each_entry_rcu_from' - 'rht_for_each_entry_safe' - 'rht_for_each_from' - 'rht_for_each_rcu' - 'rht_for_each_rcu_from' - 'rq_for_each_bvec' - 'rq_for_each_segment' - 'rq_list_for_each' - 'rq_list_for_each_safe' - 'sample_read_group__for_each' - 'scsi_for_each_prot_sg' - 'scsi_for_each_sg' - 'sctp_for_each_hentry' - 'sctp_skb_for_each' - 'sec_for_each_insn' - 'sec_for_each_insn_continue' - 'sec_for_each_insn_from' - 'sec_for_each_sym' - 'shdma_for_each_chan' - 'shost_for_each_device' - 'sk_for_each' - 'sk_for_each_bound' - 'sk_for_each_bound_safe' - 'sk_for_each_entry_offset_rcu' - 'sk_for_each_from' - 'sk_for_each_rcu' - 'sk_for_each_safe' - 'sk_nulls_for_each' - 'sk_nulls_for_each_from' - 'sk_nulls_for_each_rcu' - 'snd_array_for_each' - 'snd_pcm_group_for_each_entry' - 'snd_soc_dapm_widget_for_each_path' - 'snd_soc_dapm_widget_for_each_path_safe' - 'snd_soc_dapm_widget_for_each_sink_path' - 'snd_soc_dapm_widget_for_each_source_path' - 'sparsebit_for_each_set_range' - 'strlist__for_each_entry' - 'strlist__for_each_entry_safe' - 'sym_for_each_insn' - 'sym_for_each_insn_continue_reverse' - 'symbols__for_each_entry' - 'tb_property_for_each' - 'tcf_act_for_each_action' - 'tcf_exts_for_each_action' - 'test_suite__for_each_test_case' - 'tool_pmu__for_each_event' - 'ttm_bo_lru_for_each_reserved_guarded' - 'ttm_resource_manager_for_each_res' - 'udp_lrpa_for_each_entry_rcu' - 'udp_portaddr_for_each_entry' - 'udp_portaddr_for_each_entry_rcu' - 'usb_hub_for_each_child' - 'v4l2_device_for_each_subdev' - 'v4l2_m2m_for_each_dst_buf' - 'v4l2_m2m_for_each_dst_buf_safe' - 'v4l2_m2m_for_each_src_buf' - 'v4l2_m2m_for_each_src_buf_safe' - 'virtio_device_for_each_vq' - 'vkms_config_for_each_connector' - 'vkms_config_for_each_crtc' - 'vkms_config_for_each_encoder' - 'vkms_config_for_each_plane' - 'vkms_config_connector_for_each_possible_encoder' - 'vkms_config_encoder_for_each_possible_crtc' - 'vkms_config_plane_for_each_possible_crtc' - 'while_for_each_ftrace_op' - 'workloads__for_each' - 'xa_for_each' - 'xa_for_each_marked' - 'xa_for_each_range' - 'xa_for_each_start' - 'xas_for_each' - 'xas_for_each_conflict' - 'xas_for_each_marked' - 'xbc_array_for_each_value' - 'xbc_for_each_key_value' - 'xbc_node_for_each_array_value' - 'xbc_node_for_each_child' - 'xbc_node_for_each_key_value' - 'xbc_node_for_each_subkey' - 'ynl_attr_for_each' - 'ynl_attr_for_each_nested' - 'ynl_attr_for_each_payload' - 'zorro_for_each_dev' IncludeBlocks: Preserve IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentGotoLabels: false IndentPPDirectives: None IndentWidth: 8 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 8 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true # Taken from git's rules PenaltyBreakAssignment: 10 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatementsExceptForEachMacros SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 8 UseTab: Always ... dlundqvist-xone-f2aa9fe/.gitignore000066400000000000000000000001121515500374600174250ustar00rootroot00000000000000internal/* .vscode/* *.o *.cmd *.order *.symvers *.ko *.mod *.mod.c *.o.d dlundqvist-xone-f2aa9fe/Kbuild000066400000000000000000000011661515500374600166040ustar00rootroot00000000000000xone_gip-y := bus/bus.o bus/protocol.o auth/auth.o auth/crypto.o driver/common.o xone_wired-y := transport/wired.o xone_dongle-y := transport/dongle.o transport/mt76.o xone_gip_gamepad-y := driver/gamepad.o xone_gip_headset-y := driver/headset.o xone_gip_chatpad-y := driver/chatpad.o xone_gip_madcatz_strat-y := driver/madcatz_strat.o xone_gip_madcatz_glam-y := driver/madcatz_glam.o xone_gip_pdp_jaguar-y := driver/pdp_jaguar.o obj-m := xone_gip.o \ xone_wired.o \ xone_dongle.o \ xone_gip_gamepad.o \ xone_gip_headset.o \ xone_gip_chatpad.o \ xone_gip_madcatz_strat.o \ xone_gip_madcatz_glam.o \ xone_gip_pdp_jaguar.o dlundqvist-xone-f2aa9fe/LICENSE000066400000000000000000000432541515500374600164600ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. dlundqvist-xone-f2aa9fe/Makefile000066400000000000000000000010671515500374600171070ustar00rootroot00000000000000KVERSION := $(shell uname -r) KDIR := /lib/modules/${KVERSION}/build MAKEFLAGS+="-j $(shell nproc)" default: clean $(MAKE) -C $(KDIR) M=$$PWD debug: clean $(MAKE) -C $(KDIR) M=$$PWD ccflags-y="-Og -g3 -DDEBUG" clean: $(MAKE) -C $(KDIR) M=$$PWD clean unload: ./modules_load.sh unload load: unload ./modules_load.sh test: $(MAKE) debug &&\ $(MAKE) load $(MAKE) clean remove: clean ./uninstall.sh install: clean ./install.sh ./install/firmware.sh --skip-disclaimer install-debug: clean ./install.sh --debug ./install/firmware.sh --skip-disclaimer dlundqvist-xone-f2aa9fe/README.md000066400000000000000000000203301515500374600167200ustar00rootroot00000000000000

Logo

Release Badge Discord Badge

`xone` is a Linux kernel driver for Xbox One and Xbox Series X|S accessories. It serves as a modern replacement for `xpad`, aiming to be compatible with Microsoft's *Game Input Protocol* (GIP). > [!NOTE] > The original project is in maintance mode, please refer to this one for updates and issues. > > Huge thanks to medusalix for all the work and creating this driver! ## Compatibility - [x] Wired devices (via USB) - [x] Wireless devices (with Xbox Wireless Dongle) - [ ] Bluetooth devices (check out [`xpadneo`](https://github.com/atar-axis/xpadneo)) Installing `xone` will disable the `xpad` kernel driver. If you are still using Xbox or Xbox 360 peripherals, you will have to install [`xpad-noone`](https://github.com/forkymcforkface/xpad-noone) as a replacement for `xpad`. ## Important notes This driver is still in active development. Use at your own risk! If you are running `xow` upgrading to `xone` is *highly recommended*! Always update your Xbox devices to the latest firmware version! **Any feedback including bug reports, suggestions or ideas is [*greatly appreciated*](https://discord.gg/J7kgN5Wm).** ## Features - [x] Input and force feedback (rumble) - [x] Battery reporting (`UPower` integration) - [x] LED control (using `/sys/class/leds`) - [x] Audio capture/playback (through `ALSA`) - [x] Power management (suspend/resume and remote/wireless wakeup) ## Supported devices - [x] Gamepads - [x] Xbox One Controllers - [x] Xbox Series X|S Controllers - [x] Xbox Adaptive Controller - [x] Third party controllers (PowerA, PDP, etc.) - [x] Headsets - [x] Xbox One Chat Headset - [x] Xbox One Stereo Headset (adapter or jack) - [x] Xbox Wireless Headset - [x] Third party wired and wireless headsets (SteelSeries, Razer, etc.) - [x] Guitars & Drums - [x] Mad Catz Rock Band 4 Wireless Fender Stratocaster - [x] Mad Catz Rock Band 4 Wireless Drum Kit - [x] PDP Rock Band 4 Wireless Fender Jaguar - [x] Xbox One Chatpad - [ ] Third party racing wheels (Thrustmaster, Logitech, etc.) ## Releases [![Packaging status](https://repology.org/badge/vertical-allrepos/xone.svg)](https://repology.org/project/xone/versions) Feel free to package `xone` for any Linux distribution or hardware you like. Any issues regarding the packaging should be reported to the respective maintainers. ## Building and testing ### Prerequisites - Linux 6.5+ - Linux headers ### Automagically Build the driver with debug flags, load modules, cleanup working directory ```shell sudo make test ``` ### Manually Build the driver ```shell make # with debug make debug ``` Load modules from the build directory ```shell sudo make load ``` Unload all xone modules ```shell # called automatically during load as well sudo make unload ``` Clean all build files ```shell make clean ``` ## Installation ### Prerequisites - Linux (kernel 5.13+ and headers) - DKMS - curl (for firmware download) - bsdtar (for firmware extraction) - For SecureBoot-enabled systems see [SecureBoot dkms guide](https://github.com/dell/dkms#secure-boot) ### Guide 1. Unplug your Xbox devices. 2. Clone the repository: ``` git clone https://github.com/dlundqvist/xone ``` 3. Install `xone`: ``` cd xone sudo make install ``` **NOTE:** You can use the `install-debug` target instead to enable debug logging. 4. Download the firmware for the wireless dongle (optional, makefile automatically installs firmware): ``` sudo install/firmware.sh ``` > [!NOTE] > The `--skip-disclaimer` flag might be useful for scripting purposes. > [!TIP] > The `xone-dongle.fw_override=0x0000` module paramter can be used to load a different firmware file than the one selected automatically by the driver. The value is the USB PID contained in the fw file eg. `xone_dongle_02fe.bin` 5. Plug in your Xbox devices. ### Updating Just run the install script again after pulling the newset changes from the repository. ``` git pull sudo make install ``` Reboot is highly suggested ### Steam Deck/SteamOS #### Automatic install First, let's set a password ```bash # (optional, skip if you've already done this in the past) passwd deck ``` Run installation script ```bash sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/dlundqvist/xone/master/install/steam-deck-install.sh)" ``` #### Uninstall: ```bash sudo pacman -Rcns xone-dkms ``` Optionally, lock your deck and remove password ```bash steamos-readonly enable # enter current one and leave the new password blank passwd deck ``` ### Using Xbox 360 controllers with xone `xone` doesn't support Xbox 360 controllers at all. On top of that, `xone` needs to disable `xpad` driver to work properly, which would normally support Xbox 360 controllers. This is due to `xpad` also trying to handle Xbox One controllers, which `xone` aims to support. To fix that, there is a fork of [xpad](https://github.com/paroj/xpad) driver, called [xpad-noone](https://github.com/forkymcforkface/xpad-noone) that has disabled support for Xbox One controllers, so it can coexist with `xone` driver. If you're using Xbox 360 controllers, it is recommended to use it to replace the standard `xpad` driver. ## Wireless pairing Xbox devices have to be paired to the wireless dongle. They will not automatically connect to the dongle if they have been previously plugged into a USB port or used via Bluetooth. Instructions for pairing your devices can be found [here](https://support.xbox.com/en-US/help/hardware-network/controller/connect-xbox-wireless-controller-to-pc) (see the section on *Xbox Wireless*). ## Kernel interface ### LED control The guide button LED can be controlled via `sysfs`: ``` echo 2 | sudo tee /sys/class/leds/gip*/mode echo 5 | sudo tee /sys/class/leds/gip*/brightness ``` Changing the LED in the above way is temporary, it will only last until the device disconnects. To apply these settings automatically when a device connects, you can create a new `udev` rule in `/etc/udev/rules.d/50-xone.rules` with the following content: ``` ACTION=="add", SUBSYSTEM=="leds", KERNEL=="gip*", ATTR{mode}="2", ATTR{brightness}="5" ``` Replace the wildcard (`gip*`) if you want to control the LED of a specific device. The modes and the maximum brightness can vary from device to device. ### Pairing mode The pairing mode of the dongle can be queried via `sysfs`: ``` cat /sys/bus/usb/drivers/xone-dongle/*/pairing ``` You can enable (`1`) or disable (`0`) the pairing using the following command: ``` echo 1 | sudo tee /sys/bus/usb/drivers/xone-dongle/*/pairing ``` ## Number of active clients and forcing poweroff ``` # show number of connected controllers cat /sys/bus/usb/drivers/xone-dongle/*/active_clients # power off selected client (possible values from 0 to 15) sudo tee /sys/bus/usb/drivers/xone-dongle/*/poweroff <<< 1 # power off all connected clients sudo tee /sys/bus/usb/drivers/xone-dongle/*/poweroff <<< -1 ``` ## Troubleshooting Uninstall the release version and install a debug build of `xone` (see installation guide). Run `sudo dmesg` to gather logs and check for any error messages related to `xone`. If `xone` is not being loaded automatically you might have to reboot your system. ### Error messages - `Direct firmware load for xow_dongle.bin failed with error -2` - Download the firmware for the wireless dongle (see installation guide). ### Input issues You can use `evtest` and `fftest` to check the input and force feedback functionality of your devices. ### Other problems Please join the [Discord server](https://discord.gg/T3dSC3ReuS) in case of any other problems. ## License `xone` is released under the [GNU General Public License, Version 2](LICENSE). ``` Copyright (C) 2021 Severin von Wnuck-Lipinski This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. ``` dlundqvist-xone-f2aa9fe/auth/000077500000000000000000000000001515500374600164045ustar00rootroot00000000000000dlundqvist-xone-f2aa9fe/auth/auth.c000066400000000000000000000423241515500374600175160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2023 Severin von Wnuck-Lipinski */ #include #include #include "auth.h" #include "crypto.h" #include "../bus/bus.h" enum gip_auth_context { GIP_AUTH_CTX_HANDSHAKE = 0x00, GIP_AUTH_CTX_CONTROL = 0x01, }; enum gip_auth_command_handshake { GIP_AUTH_CMD_HOST_HELLO = 0x01, GIP_AUTH_CMD_CLIENT_HELLO = 0x02, GIP_AUTH_CMD_CLIENT_CERTIFICATE = 0x03, GIP_AUTH_CMD_HOST_SECRET = 0x05, GIP_AUTH_CMD_HOST_FINISH = 0x07, GIP_AUTH_CMD_CLIENT_FINISH = 0x08, GIP_AUTH2_CMD_HOST_HELLO = 0x21, GIP_AUTH2_CMD_CLIENT_HELLO = 0x22, GIP_AUTH2_CMD_CLIENT_CERTIFICATE = 0x23, GIP_AUTH2_CMD_CLIENT_PUBKEY = 0x24, GIP_AUTH2_CMD_HOST_PUBKEY = 0x25, GIP_AUTH2_CMD_HOST_FINISH = 0x26, GIP_AUTH2_CMD_CLIENT_FINISH = 0x27, }; enum gip_auth_command_control { GIP_AUTH_CTRL_COMPLETE = 0x00, GIP_AUTH_CTRL_RESET = 0x01, }; enum gip_auth_option { GIP_AUTH_OPT_ACKNOWLEDGE = BIT(0), GIP_AUTH_OPT_REQUEST = BIT(1), GIP_AUTH_OPT_FROM_HOST = BIT(6), GIP_AUTH_OPT_FROM_CLIENT = BIT(6) | BIT(7), }; struct gip_auth_header_handshake { u8 context; u8 options; u8 error; u8 command; __be16 length; } __packed; struct gip_auth_header_data { u8 command; u8 version; __be16 length; } __packed; struct gip_auth_header_full { struct gip_auth_header_handshake handshake; struct gip_auth_header_data data; } __packed; struct gip_auth_header_control { u8 context; u8 control; } __packed; struct gip_auth_request { struct gip_auth_header_handshake header; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth_pkt_host_hello { struct gip_auth_header_full header; u8 random[GIP_AUTH_RANDOM_LEN]; u8 unknown1[4]; u8 unknown2[4]; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth_pkt_host_secret { struct gip_auth_header_full header; u8 encrypted_pms[GIP_AUTH_ENCRYPTED_PMS_LEN]; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth_pkt_host_finish { struct gip_auth_header_full header; u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth_pkt_client_hello { u8 random[GIP_AUTH_RANDOM_LEN]; u8 unknown[48]; } __packed; struct gip_auth_pkt_client_finish { u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; u8 unknown[32]; } __packed; struct gip_auth2_pkt_host_hello { struct gip_auth_header_full header; u8 random[GIP_AUTH_RANDOM_LEN]; u8 unknown[4]; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth2_pkt_host_pubkey { struct gip_auth_header_full header; u8 pubkey[GIP_AUTH2_PUBKEY_LEN]; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth2_pkt_host_finish { struct gip_auth_header_full header; u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; u8 trailer[GIP_AUTH_TRAILER_LEN]; } __packed; struct gip_auth2_pkt_client_hello { u8 random[GIP_AUTH_RANDOM_LEN]; u8 unknown1[108]; u8 unknown2[32]; } __packed; struct gip_auth2_pkt_client_cert { char header[4]; u8 unknown1[136]; char chip[32]; char revision[20]; u8 unknown2[576]; } __packed; struct gip_auth2_pkt_client_pubkey { u8 pubkey[GIP_AUTH2_PUBKEY_LEN]; u8 unknown[64]; } __packed; struct gip_auth2_pkt_client_finish { u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; u8 unknown[32]; } __packed; static int gip_auth_send_pkt(struct gip_auth *auth, enum gip_auth_command_handshake cmd, void *pkt, u16 len) { struct gip_auth_header_full *hdr = pkt; u16 data_len = len - sizeof(hdr->handshake) - GIP_AUTH_TRAILER_LEN; hdr->handshake.context = GIP_AUTH_CTX_HANDSHAKE; hdr->handshake.options = GIP_AUTH_OPT_ACKNOWLEDGE | GIP_AUTH_OPT_FROM_HOST; hdr->handshake.command = cmd; hdr->handshake.length = cpu_to_be16(data_len); hdr->data.command = cmd; hdr->data.version = cmd >= GIP_AUTH2_CMD_HOST_HELLO ? 0x02 : 0x01; hdr->data.length = cpu_to_be16(data_len - sizeof(hdr->data)); auth->last_sent_command = cmd; crypto_shash_update(auth->shash_transcript, pkt + sizeof(hdr->handshake), data_len); return gip_send_authenticate(auth->client, pkt, len, true); } static int gip_auth_request_pkt(struct gip_auth *auth, enum gip_auth_command_handshake cmd, u16 len) { struct gip_auth_request req = {}; u16 data_len = len + sizeof(struct gip_auth_header_data); req.header.context = GIP_AUTH_CTX_HANDSHAKE; req.header.options = GIP_AUTH_OPT_REQUEST | GIP_AUTH_OPT_FROM_HOST; req.header.command = cmd; req.header.length = cpu_to_be16(data_len); return gip_send_authenticate(auth->client, &req, sizeof(req), true); } static int gip_auth2_send_hello(struct gip_auth *auth) { struct gip_auth2_pkt_host_hello pkt = {}; get_random_bytes(auth->random_host, sizeof(auth->random_host)); memcpy(pkt.random, auth->random_host, sizeof(pkt.random)); return gip_auth_send_pkt(auth, GIP_AUTH2_CMD_HOST_HELLO, &pkt, sizeof(pkt)); } static int gip_auth2_handle_pkt_hello(struct gip_auth *auth, void *data, u32 len) { struct gip_auth2_pkt_client_hello *pkt = data; if (len < sizeof(*pkt)) return -EINVAL; memcpy(auth->random_client, pkt->random, sizeof(auth->random_client)); return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_CERTIFICATE, sizeof(struct gip_auth2_pkt_client_cert)); } static int gip_auth2_handle_pkt_certificate(struct gip_auth *auth, void *data, u32 len) { struct gip_auth2_pkt_client_cert *pkt = data; if (len < sizeof(*pkt)) return -EINVAL; dev_dbg(&auth->client->dev, "%s: header=%.*s, chip=%.*s, revision=%.*s\n", __func__, (int)sizeof(pkt->header), pkt->header, (int)sizeof(pkt->chip), pkt->chip, (int)sizeof(pkt->revision), pkt->revision); return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_PUBKEY, sizeof(struct gip_auth2_pkt_client_pubkey)); } static int gip_auth2_handle_pkt_pubkey(struct gip_auth *auth, void *data, u32 len) { struct gip_auth2_pkt_client_pubkey *pkt = data; if (len < sizeof(*pkt)) return -EINVAL; memcpy(auth->pubkey_client2, pkt->pubkey, sizeof(pkt->pubkey)); schedule_work(&auth->work_exchange_ecdh); return 0; } static void gip_auth2_exchange_ecdh(struct work_struct *work) { struct gip_auth *auth = container_of(work, typeof(*auth), work_exchange_ecdh); struct gip_auth2_pkt_host_pubkey pkt = {}; u8 random[GIP_AUTH_RANDOM_LEN * 2]; u8 secret[GIP_AUTH2_SECRET_LEN]; int err; memcpy(random, auth->random_host, sizeof(auth->random_host)); memcpy(random + sizeof(auth->random_host), auth->random_client, sizeof(auth->random_client)); err = gip_auth_compute_ecdh(auth->pubkey_client2, pkt.pubkey, sizeof(pkt.pubkey), secret); if (err) { dev_err(&auth->client->dev, "%s: compute ECDH failed: %d\n", __func__, err); return; } err = gip_auth_compute_prf(auth->shash_prf, "Master Secret", secret, sizeof(secret), random, sizeof(random), auth->master_secret, sizeof(auth->master_secret)); if (err) { dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", __func__, err); return; } err = gip_auth_send_pkt(auth, GIP_AUTH2_CMD_HOST_PUBKEY, &pkt, sizeof(pkt)); if (err) dev_err(&auth->client->dev, "%s: send pkt failed: %d\n", __func__, err); } static int gip_auth_send_pkt_hello(struct gip_auth *auth) { struct gip_auth_pkt_host_hello pkt = {}; get_random_bytes(auth->random_host, sizeof(auth->random_host)); memcpy(pkt.random, auth->random_host, sizeof(pkt.random)); return gip_auth_send_pkt(auth, GIP_AUTH_CMD_HOST_HELLO, &pkt, sizeof(pkt)); } static int gip_auth_send_pkt_finish(struct gip_auth *auth, enum gip_auth_command_handshake cmd) { struct gip_auth_pkt_host_finish pkt = {}; u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; int err; err = gip_auth_get_transcript(auth->shash_transcript, transcript); if (err) { dev_err(&auth->client->dev, "%s: get transcript failed: %d\n", __func__, err); return err; } err = gip_auth_compute_prf(auth->shash_prf, "Host Finished", auth->master_secret, sizeof(auth->master_secret), transcript, sizeof(transcript), pkt.transcript, sizeof(pkt.transcript)); if (err) { dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", __func__, err); return err; } return gip_auth_send_pkt(auth, cmd, &pkt, sizeof(pkt)); } static int gip_auth_handle_pkt_acknowledge(struct gip_auth *auth) { switch (auth->last_sent_command) { case GIP_AUTH2_CMD_HOST_HELLO: return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_HELLO, sizeof(struct gip_auth2_pkt_client_hello)); case GIP_AUTH2_CMD_HOST_PUBKEY: return gip_auth_send_pkt_finish(auth, GIP_AUTH2_CMD_HOST_FINISH); case GIP_AUTH2_CMD_HOST_FINISH: return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_FINISH, sizeof(struct gip_auth2_pkt_client_finish)); case GIP_AUTH_CMD_HOST_HELLO: return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_HELLO, sizeof(struct gip_auth_pkt_client_hello)); case GIP_AUTH_CMD_HOST_SECRET: return gip_auth_send_pkt_finish(auth, GIP_AUTH_CMD_HOST_FINISH); case GIP_AUTH_CMD_HOST_FINISH: return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_FINISH, sizeof(struct gip_auth_pkt_client_finish)); default: return -EPROTO; } } static int gip_auth_handle_pkt_hello(struct gip_auth *auth, void *data, u32 len) { struct gip_auth_pkt_client_hello *pkt = data; if (len < sizeof(*pkt)) return -EINVAL; memcpy(auth->random_client, pkt->random, sizeof(pkt->random)); return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_CERTIFICATE, GIP_AUTH_CERTIFICATE_MAX_LEN); } static int gip_auth_handle_pkt_certificate(struct gip_auth *auth, void *data, u32 len) { /* ASN.1 SEQUENCE (len = 0x04 + 0x010a) */ u8 asn1_seq[] = { 0x30, 0x82, 0x01, 0x0a }; int i; if (len > GIP_AUTH_CERTIFICATE_MAX_LEN) return -EINVAL; /* * Poor way of extracting a pubkey from an X.509 certificate. * The certificates issued by Microsoft do not comply with RFC 5280. * They have an empty subject and no subjectAltName. * This is explicitly forbidden by section 4.2.1.6 of the RFC. * The kernel's ASN.1 parser will fail when using x509_cert_parse. */ for (i = 0; i + sizeof(asn1_seq) <= len; i++) { if (memcmp(data + i, asn1_seq, sizeof(asn1_seq))) continue; if (i + GIP_AUTH_PUBKEY_LEN > len) return -EINVAL; memcpy(auth->pubkey_client, data + i, GIP_AUTH_PUBKEY_LEN); schedule_work(&auth->work_exchange_rsa); return 0; } return -EPROTO; } static int gip_auth_handle_pkt_finish(struct gip_auth *auth, void *data, u32 len) { struct gip_auth_pkt_client_finish *pkt = data; u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; u8 finished[GIP_AUTH_TRANSCRIPT_LEN]; int err; if (len < sizeof(*pkt)) return -EINVAL; err = gip_auth_get_transcript(auth->shash_transcript, transcript); if (err) { dev_err(&auth->client->dev, "%s: get transcript failed: %d\n", __func__, err); return err; } err = gip_auth_compute_prf(auth->shash_prf, "Device Finished", auth->master_secret, sizeof(auth->master_secret), transcript, sizeof(transcript), finished, sizeof(finished)); if (err) { dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", __func__, err); return err; } if (memcmp(pkt->transcript, finished, sizeof(finished))) { dev_err(&auth->client->dev, "%s: transcript mismatch\n", __func__); return -EPROTO; } schedule_work(&auth->work_complete); return 0; } static void gip_auth_exchange_rsa(struct work_struct *work) { struct gip_auth *auth = container_of(work, typeof(*auth), work_exchange_rsa); struct gip_auth_pkt_host_secret pkt = {}; u8 random[GIP_AUTH_RANDOM_LEN * 2]; int err; memcpy(random, auth->random_host, sizeof(auth->random_host)); memcpy(random + sizeof(auth->random_host), auth->random_client, sizeof(auth->random_client)); /* get random premaster secret */ get_random_bytes(auth->pms, sizeof(auth->pms)); err = gip_auth_encrypt_rsa(auth->pubkey_client, sizeof(auth->pubkey_client), auth->pms, sizeof(auth->pms), pkt.encrypted_pms, sizeof(pkt.encrypted_pms)); if (err) { dev_err(&auth->client->dev, "%s: encrypt RSA failed: %d\n", __func__, err); return; } err = gip_auth_compute_prf(auth->shash_prf, "Master Secret", auth->pms, sizeof(auth->pms), random, sizeof(random), auth->master_secret, sizeof(auth->master_secret)); if (err) { dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", __func__, err); return; } err = gip_auth_send_pkt(auth, GIP_AUTH_CMD_HOST_SECRET, &pkt, sizeof(pkt)); if (err) dev_err(&auth->client->dev, "%s: send pkt failed: %d\n", __func__, err); } int gip_auth_send_complete(struct gip_client *client) { struct gip_auth_header_control hdr = {}; hdr.context = GIP_AUTH_CTX_CONTROL; hdr.control = GIP_AUTH_CTRL_COMPLETE; return gip_send_authenticate(client, &hdr, sizeof(hdr), false); } EXPORT_SYMBOL_GPL(gip_auth_send_complete); static void gip_auth_complete_handshake(struct work_struct *work) { struct gip_auth *auth = container_of(work, typeof(*auth), work_complete); u8 random[GIP_AUTH_RANDOM_LEN * 2]; u8 key[GIP_AUTH_SESSION_KEY_LEN]; int err; memcpy(random, auth->random_host, sizeof(auth->random_host)); memcpy(random + sizeof(auth->random_host), auth->random_client, sizeof(auth->random_client)); err = gip_auth_compute_prf(auth->shash_prf, "EXPORTER DAWN data channel session key for controller", auth->master_secret, sizeof(auth->master_secret), random, sizeof(random), key, sizeof(key)); if (err) { dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", __func__, err); return; } dev_dbg(&auth->client->dev, "%s: key=%*phD\n", __func__, (int)sizeof(key), key); err = gip_auth_send_complete(auth->client); if (err) { dev_err(&auth->client->dev, "%s: send complete failed: %d\n", __func__, err); return; } err = gip_set_encryption_key(auth->client, key, sizeof(key)); if (err) dev_err(&auth->client->dev, "%s: set encryption key failed: %d\n", __func__, err); } static int gip_auth_dispatch_pkt(struct gip_auth *auth, enum gip_auth_command_handshake cmd, void *data, u32 len) { switch (cmd) { case GIP_AUTH2_CMD_CLIENT_HELLO: return gip_auth2_handle_pkt_hello(auth, data, len); case GIP_AUTH2_CMD_CLIENT_CERTIFICATE: return gip_auth2_handle_pkt_certificate(auth, data, len); case GIP_AUTH2_CMD_CLIENT_PUBKEY: return gip_auth2_handle_pkt_pubkey(auth, data, len); case GIP_AUTH2_CMD_CLIENT_FINISH: return gip_auth_handle_pkt_finish(auth, data, len); case GIP_AUTH_CMD_CLIENT_HELLO: return gip_auth_handle_pkt_hello(auth, data, len); case GIP_AUTH_CMD_CLIENT_CERTIFICATE: return gip_auth_handle_pkt_certificate(auth, data, len); case GIP_AUTH_CMD_CLIENT_FINISH: return gip_auth_handle_pkt_finish(auth, data, len); default: return -EPROTO; } } static int gip_auth_process_pkt_data(struct gip_auth *auth, void *data, u32 len) { struct gip_auth_header_full *hdr = data; int err; if (len < sizeof(*hdr)) return -EINVAL; /* client uses auth v2 */ if (hdr->handshake.command != hdr->data.command) { /* reset transcript hash and restart handshake */ dev_dbg(&auth->client->dev, "%s: protocol upgrade\n", __func__); crypto_shash_init(auth->shash_transcript); return gip_auth2_send_hello(auth); } err = gip_auth_dispatch_pkt(auth, hdr->data.command, data + sizeof(*hdr), len - sizeof(*hdr)); if (err) return err; return crypto_shash_update(auth->shash_transcript, data + sizeof(hdr->handshake), len - sizeof(hdr->handshake)); } int gip_auth_process_pkt(struct gip_auth *auth, void *data, u32 len) { struct gip_auth_header_handshake *hdr = data; if (!auth->client) return -ENODEV; if (len < sizeof(*hdr)) return -EINVAL; if (hdr->error) return -EPROTO; if (hdr->options & GIP_AUTH_OPT_ACKNOWLEDGE) { if (hdr->command == 0x01) return gip_auth_handle_pkt_acknowledge(auth); dev_err(&auth->client->dev, "%s: handshake failed: 0x%02x\n", __func__, hdr->command); return -EPROTO; } return gip_auth_process_pkt_data(auth, data, len); } EXPORT_SYMBOL_GPL(gip_auth_process_pkt); static void gip_auth_release(void *res) { struct gip_auth *auth = res; cancel_work_sync(&auth->work_exchange_rsa); cancel_work_sync(&auth->work_exchange_ecdh); cancel_work_sync(&auth->work_complete); crypto_free_shash(auth->shash_transcript->tfm); crypto_free_shash(auth->shash_prf->tfm); kfree(auth->shash_transcript); kfree(auth->shash_prf); auth->client = NULL; auth->shash_transcript = NULL; auth->shash_prf = NULL; } int gip_auth_start_handshake(struct gip_auth *auth, struct gip_client *client) { struct shash_desc *shash_transcript, *shash_prf; int err; shash_transcript = gip_auth_alloc_shash("sha256"); if (IS_ERR(shash_transcript)) return PTR_ERR(shash_transcript); shash_prf = gip_auth_alloc_shash("hmac(sha256)"); if (IS_ERR(shash_prf)) { crypto_free_shash(shash_transcript->tfm); kfree(shash_transcript); return PTR_ERR(shash_prf); } auth->client = client; auth->shash_transcript = shash_transcript; auth->shash_prf = shash_prf; INIT_WORK(&auth->work_exchange_rsa, gip_auth_exchange_rsa); INIT_WORK(&auth->work_exchange_ecdh, gip_auth2_exchange_ecdh); INIT_WORK(&auth->work_complete, gip_auth_complete_handshake); err = devm_add_action_or_reset(&client->dev, gip_auth_release, auth); if (err) return err; return gip_auth_send_pkt_hello(auth); } EXPORT_SYMBOL_GPL(gip_auth_start_handshake); dlundqvist-xone-f2aa9fe/auth/auth.h000066400000000000000000000024241515500374600175200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2023 Severin von Wnuck-Lipinski */ #pragma once #include #include /* trailer is required for v1 clients */ #define GIP_AUTH_TRAILER_LEN 8 #define GIP_AUTH_RANDOM_LEN 32 #define GIP_AUTH_CERTIFICATE_MAX_LEN 1024 #define GIP_AUTH_PUBKEY_LEN 270 #define GIP_AUTH_SECRET_LEN 48 #define GIP_AUTH_ENCRYPTED_PMS_LEN 256 #define GIP_AUTH_TRANSCRIPT_LEN 32 #define GIP_AUTH_SESSION_KEY_LEN 16 #define GIP_AUTH2_PUBKEY_LEN 64 #define GIP_AUTH2_SECRET_LEN 32 struct gip_client; struct gip_auth { struct gip_client *client; struct shash_desc *shash_transcript; struct shash_desc *shash_prf; struct work_struct work_exchange_rsa; struct work_struct work_exchange_ecdh; struct work_struct work_complete; u8 last_sent_command; u8 random_host[GIP_AUTH_RANDOM_LEN]; u8 random_client[GIP_AUTH_RANDOM_LEN]; u8 pubkey_client[GIP_AUTH_PUBKEY_LEN]; u8 pubkey_client2[GIP_AUTH2_PUBKEY_LEN]; u8 pms[GIP_AUTH_SECRET_LEN]; u8 master_secret[GIP_AUTH_SECRET_LEN]; }; int gip_auth_send_complete(struct gip_client *client); int gip_auth_process_pkt(struct gip_auth *auth, void *data, u32 len); int gip_auth_start_handshake(struct gip_auth *auth, struct gip_client *client); dlundqvist-xone-f2aa9fe/auth/crypto.c000066400000000000000000000124251515500374600200740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2023 Severin von Wnuck-Lipinski */ #include #include #include #include #include #include #include #include "crypto.h" #define GIP_AUTH_ECDH_SECRET_LEN 32 struct shash_desc *gip_auth_alloc_shash(const char *alg) { struct crypto_shash *tfm; struct shash_desc *desc; tfm = crypto_alloc_shash(alg, 0, 0); if (IS_ERR(tfm)) return ERR_CAST(tfm); desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); if (!desc) { crypto_free_shash(tfm); return ERR_PTR(-ENOMEM); } desc->tfm = tfm; crypto_shash_init(desc); return desc; } int gip_auth_get_transcript(struct shash_desc *desc, void *transcript) { void *state = kzalloc(crypto_shash_descsize(desc->tfm), GFP_KERNEL); int err; err = crypto_shash_export(desc, state); if (err) goto get_transcript_error; err = crypto_shash_final(desc, transcript); if (err) goto get_transcript_error; err = crypto_shash_import(desc, state); get_transcript_error: kfree(state); return err; } int gip_auth_compute_prf(struct shash_desc *desc, const char *label, u8 *key, int key_len, u8 *seed, int seed_len, u8 *out, int out_len) { u8 hash[SHA256_DIGEST_SIZE], hash_out[SHA256_DIGEST_SIZE]; int err; err = crypto_shash_setkey(desc->tfm, key, key_len); if (err) return err; crypto_shash_init(desc); crypto_shash_update(desc, label, strlen(label)); crypto_shash_update(desc, seed, seed_len); crypto_shash_final(desc, hash); while (out_len > 0) { crypto_shash_init(desc); crypto_shash_update(desc, hash, sizeof(hash)); crypto_shash_update(desc, label, strlen(label)); crypto_shash_update(desc, seed, seed_len); crypto_shash_final(desc, hash_out); memcpy(out, hash_out, min_t(int, out_len, sizeof(hash))); out += sizeof(hash); out_len -= sizeof(hash); crypto_shash_digest(desc, hash, sizeof(hash), hash); } return 0; } int gip_auth_encrypt_rsa(u8 *key, int key_len, u8 *in, int in_len, u8 *out, int out_len) { struct crypto_akcipher *tfm; int err; tfm = crypto_alloc_akcipher("pkcs1pad(rsa)", 0, 0); if (IS_ERR(tfm)) return PTR_ERR(tfm); err = crypto_akcipher_set_pub_key(tfm, key, key_len); if (err) goto err_free_tfm; err = crypto_akcipher_sync_encrypt(tfm, in, in_len, out, out_len); err_free_tfm: crypto_free_akcipher(tfm); return err; } static int gip_auth_ecdh_get_pubkey(struct crypto_kpp *tfm, u8 *out, int len) { struct kpp_request *req; struct scatterlist dest; struct ecdh key = {}; DECLARE_CRYPTO_WAIT(wait); void *privkey, *pubkey; unsigned int privkey_len; int err = 0; privkey_len = crypto_ecdh_key_len(&key); privkey = kzalloc(privkey_len, GFP_KERNEL); if (!privkey) return -ENOMEM; pubkey = kzalloc(len, GFP_KERNEL); if (!pubkey){ err = -ENOMEM; goto err_free_privkey; } /* generate private key */ err = crypto_ecdh_encode_key(privkey, privkey_len, &key); if (err) goto err_free_pubkey; err = crypto_kpp_set_secret(tfm, privkey, privkey_len); if (err) goto err_free_pubkey; req = kpp_request_alloc(tfm, GFP_KERNEL); if (!req) { err = -ENOMEM; goto err_free_pubkey; } sg_init_one(&dest, pubkey, len); kpp_request_set_input(req, NULL, 0); kpp_request_set_output(req, &dest, len); kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, crypto_req_done, &wait); err = crypto_wait_req(crypto_kpp_generate_public_key(req), &wait); if (!err) memcpy(out, pubkey, len); kpp_request_free(req); err_free_pubkey: kfree(pubkey); err_free_privkey: kfree(privkey); return err; } static int gip_auth_ecdh_get_secret(struct crypto_kpp *tfm, u8 *pubkey, int pubkey_len, u8 *secret, int secret_len) { struct kpp_request *req; struct scatterlist src, dest; DECLARE_CRYPTO_WAIT(wait); int err; req = kpp_request_alloc(tfm, GFP_KERNEL); if (!req) return -ENOMEM; sg_init_one(&src, pubkey, pubkey_len); sg_init_one(&dest, secret, secret_len); kpp_request_set_input(req, &src, pubkey_len); kpp_request_set_output(req, &dest, secret_len); kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, crypto_req_done, &wait); err = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait); kpp_request_free(req); return err; } int gip_auth_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, int pubkey_len, u8 *secret_hash) { struct crypto_kpp *tfm_ecdh; struct crypto_shash *tfm_sha; u8 *secret; int err; secret = kzalloc(GIP_AUTH_ECDH_SECRET_LEN, GFP_KERNEL); if (!secret) return -ENOMEM; tfm_ecdh = crypto_alloc_kpp("ecdh-nist-p256", 0, 0); if (IS_ERR(tfm_ecdh)) { err = PTR_ERR(tfm_ecdh); goto err_free_secret; } tfm_sha = crypto_alloc_shash("sha256", 0, 0); if (IS_ERR(tfm_sha)) { err = PTR_ERR(tfm_sha); goto err_free_ecdh; } err = gip_auth_ecdh_get_pubkey(tfm_ecdh, pubkey_out, pubkey_len); if (err) goto err_free_sha; err = gip_auth_ecdh_get_secret(tfm_ecdh, pubkey_in, pubkey_len, secret, GIP_AUTH_ECDH_SECRET_LEN); if (err) goto err_free_sha; crypto_shash_tfm_digest(tfm_sha, secret, GIP_AUTH_ECDH_SECRET_LEN, secret_hash); err_free_sha: crypto_free_shash(tfm_sha); err_free_ecdh: crypto_free_kpp(tfm_ecdh); err_free_secret: kfree(secret); return err; } dlundqvist-xone-f2aa9fe/auth/crypto.h000066400000000000000000000012051515500374600200730ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2023 Severin von Wnuck-Lipinski */ #pragma once #include struct shash_desc *gip_auth_alloc_shash(const char *alg); int gip_auth_get_transcript(struct shash_desc *desc, void *transcript); int gip_auth_compute_prf(struct shash_desc *desc, const char *label, u8 *key, int key_len, u8 *seed, int seed_len, u8 *out, int out_len); int gip_auth_encrypt_rsa(u8 *key, int key_len, u8 *in, int in_len, u8 *out, int out_len); int gip_auth_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, int pubkey_len, u8 *secret_hash); dlundqvist-xone-f2aa9fe/bus/000077500000000000000000000000001515500374600162345ustar00rootroot00000000000000dlundqvist-xone-f2aa9fe/bus/bus.c000066400000000000000000000201061515500374600171700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include #include #include "bus.h" #define to_gip_adapter(d) container_of(d, struct gip_adapter, dev) #define to_gip_client(d) container_of(d, struct gip_client, dev) #define to_gip_driver(d) container_of(d, struct gip_driver, drv) static DEFINE_IDA(gip_adapter_ida); static void gip_adapter_release(struct device *dev) { kfree(to_gip_adapter(dev)); } static struct device_type gip_adapter_type = { .release = gip_adapter_release, }; static int gip_client_uevent(const struct device *dev, struct kobj_uevent_env *env) { struct gip_client *client = to_gip_client(dev); struct gip_classes *classes = client->classes; if (!classes || !classes->count) return -EINVAL; return add_uevent_var(env, "MODALIAS=gip:%s", classes->strings[0]); } static void gip_client_release(struct device *dev) { struct gip_client *client = to_gip_client(dev); gip_free_client_info(client); kfree(client->chunk_buf_out); kfree(client->chunk_buf_in); kfree(client); } static struct device_type gip_client_type = { .uevent = gip_client_uevent, .release = gip_client_release, }; #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) static int gip_bus_match(struct device *dev, struct device_driver *driver) #else static int gip_bus_match(struct device *dev, const struct device_driver *driver) #endif { struct gip_client *client; struct gip_driver *drv; int i; if (dev->type != &gip_client_type) return false; client = to_gip_client(dev); drv = to_gip_driver(driver); for (i = 0; i < client->classes->count; i++) if (!strcmp(client->classes->strings[i], drv->class)) return true; return false; } static int gip_bus_probe(struct device *dev) { struct gip_client *client = to_gip_client(dev); struct gip_driver *drv = to_gip_driver(dev->driver); int err = 0; if (down_interruptible(&client->drv_lock)) return -EINTR; if (!client->drv) { err = drv->probe(client); if (!err) client->drv = drv; } up(&client->drv_lock); return err; } static void gip_bus_remove(struct device *dev) { struct gip_client *client = to_gip_client(dev); struct gip_driver *drv; down(&client->drv_lock); drv = client->drv; if (drv) { client->drv = NULL; if (drv->remove) drv->remove(client); } up(&client->drv_lock); } static struct bus_type gip_bus_type = { .name = "xone-gip", .match = gip_bus_match, .probe = gip_bus_probe, .remove = gip_bus_remove, }; struct gip_adapter *gip_create_adapter(struct device *parent, struct gip_adapter_ops *ops, int audio_pkts) { struct gip_adapter *adap; int err; adap = kzalloc(sizeof(*adap), GFP_KERNEL); if (!adap) return ERR_PTR(-ENOMEM); adap->id = ida_alloc(&gip_adapter_ida, GFP_KERNEL); if (adap->id < 0) { err = adap->id; goto err_put_device; } adap->clients_wq = alloc_ordered_workqueue("gip%d", 0, adap->id); if (!adap->clients_wq) { err = -ENOMEM; goto err_remove_ida; } adap->dev.parent = parent; adap->dev.type = &gip_adapter_type; adap->dev.bus = &gip_bus_type; adap->ops = ops; adap->audio_packet_count = audio_pkts; dev_set_name(&adap->dev, "gip%d", adap->id); spin_lock_init(&adap->send_lock); err = device_register(&adap->dev); if (err) goto err_destroy_queue; dev_dbg(&adap->dev, "%s: registered\n", __func__); return adap; err_destroy_queue: destroy_workqueue(adap->clients_wq); err_remove_ida: ida_free(&gip_adapter_ida, adap->id); err_put_device: put_device(&adap->dev); return ERR_PTR(err); } EXPORT_SYMBOL_GPL(gip_create_adapter); int gip_power_off_adapter(struct gip_adapter *adap) { struct gip_client *client = adap->clients[0]; if (!client) return 0; /* power off main client */ return gip_set_power_mode(client, GIP_PWR_OFF); } EXPORT_SYMBOL_GPL(gip_power_off_adapter); void gip_destroy_adapter(struct gip_adapter *adap) { struct gip_client *client; int i; /* ensure all state changes have been processed */ flush_workqueue(adap->clients_wq); for (i = GIP_MAX_CLIENTS - 1; i >= 0; i--) { client = adap->clients[i]; if (!client || !device_is_registered(&client->dev)) continue; device_unregister(&client->dev); } ida_free(&gip_adapter_ida, adap->id); destroy_workqueue(adap->clients_wq); dev_dbg(&adap->dev, "%s: unregistered\n", __func__); device_unregister(&adap->dev); } EXPORT_SYMBOL_GPL(gip_destroy_adapter); static void gip_register_client(struct work_struct *work) { struct gip_client *client = container_of(work, typeof(*client), work_register); int err; /* * Sometimes, after wakeup from sleep gamepads are unresponsive, Turns * out, it has to do something with how fast this function is performed. * Our best guess for now is that some kind of data is just not getting * there fast enough with some hubs that lose power and some timeouts * that normally asre used to init the device, aren't there to help. * * The unfortunate workaround, that at least works reliably is to add a * delay here. Since this is for human input device, one second is fine. */ msleep(700); client->dev.parent = &client->adapter->dev; client->dev.type = &gip_client_type; client->dev.bus = &gip_bus_type; sema_init(&client->drv_lock, 1); dev_set_name(&client->dev, "gip%d.%u", client->adapter->id, client->id); err = device_register(&client->dev); if (err) dev_err(&client->dev, "%s: register failed: %d\n", __func__, err); else dev_dbg(&client->dev, "%s: registered\n", __func__); } static void gip_unregister_client(struct work_struct *work) { struct gip_client *client = container_of(work, typeof(*client), work_unregister); if (!device_is_registered(&client->dev)) return; dev_dbg(&client->dev, "%s: unregistered\n", __func__); device_unregister(&client->dev); } struct gip_client *gip_get_client(struct gip_adapter *adap, u8 id) { struct gip_client *client; client = adap->clients[id]; if (client) return client; client = kzalloc(sizeof(*client), GFP_ATOMIC); if (!client) return ERR_PTR(-ENOMEM); client->id = id; client->adapter = adap; sema_init(&client->drv_lock, 1); INIT_WORK(&client->work_register, gip_register_client); INIT_WORK(&client->work_unregister, gip_unregister_client); adap->clients[id] = client; dev_dbg(&client->adapter->dev, "%s: initialized client %u\n", __func__, id); return client; } void gip_add_client(struct gip_client *client) { queue_work(client->adapter->clients_wq, &client->work_register); } void gip_remove_client(struct gip_client *client) { client->adapter->clients[client->id] = NULL; queue_work(client->adapter->clients_wq, &client->work_unregister); } void gip_free_client_info(struct gip_client *client) { int i; kfree(client->client_commands); kfree(client->firmware_versions); kfree(client->audio_formats); kfree(client->capabilities_out); kfree(client->capabilities_in); if (client->classes) for (i = 0; i < client->classes->count; i++) kfree(client->classes->strings[i]); kfree(client->classes); kfree(client->interfaces); kfree(client->hid_descriptor); client->client_commands = NULL; client->audio_formats = NULL; client->capabilities_out = NULL; client->capabilities_in = NULL; client->classes = NULL; client->interfaces = NULL; client->hid_descriptor = NULL; } int __gip_register_driver(struct gip_driver *drv, struct module *owner, const char *mod_name) { drv->drv.name = drv->name; drv->drv.bus = &gip_bus_type; drv->drv.owner = owner; drv->drv.mod_name = mod_name; return driver_register(&drv->drv); } EXPORT_SYMBOL_GPL(__gip_register_driver); void gip_unregister_driver(struct gip_driver *drv) { driver_unregister(&drv->drv); } EXPORT_SYMBOL_GPL(gip_unregister_driver); static int __init gip_bus_init(void) { return bus_register(&gip_bus_type); } static void __exit gip_bus_exit(void) { bus_unregister(&gip_bus_type); } module_init(gip_bus_init); module_exit(gip_bus_exit); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/bus/bus.h000066400000000000000000000071641515500374600172060ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once #include #include #include #include "protocol.h" #define GIP_MAX_CLIENTS 16 #define gip_register_driver(drv) \ __gip_register_driver(drv, THIS_MODULE, KBUILD_MODNAME) #define module_gip_driver(drv) \ module_driver(drv, gip_register_driver, gip_unregister_driver) struct gip_adapter_buffer { enum gip_adapter_buffer_type { GIP_BUF_DATA, GIP_BUF_AUDIO, } type; void *context; void *data; int length; }; struct gip_adapter_ops { int (*get_buffer)(struct gip_adapter *adap, struct gip_adapter_buffer *buf); int (*submit_buffer)(struct gip_adapter *adap, struct gip_adapter_buffer *buf); int (*set_encryption_key)(struct gip_adapter *adap, u8 *key, int len); int (*enable_audio)(struct gip_adapter *adap); int (*init_audio_in)(struct gip_adapter *adap); int (*init_audio_out)(struct gip_adapter *adap, int pkt_len); int (*disable_audio)(struct gip_adapter *adap); }; struct gip_adapter { struct device dev; int id; struct gip_adapter_ops *ops; int audio_packet_count; struct gip_client *clients[GIP_MAX_CLIENTS]; struct workqueue_struct *clients_wq; /* serializes access to data sequence number */ spinlock_t send_lock; u8 data_sequence; u8 audio_sequence; }; struct gip_client { struct device dev; u8 id; struct gip_adapter *adapter; struct gip_driver *drv; struct semaphore drv_lock; struct work_struct work_register; struct work_struct work_unregister; struct gip_chunk_buffer *chunk_buf_out; struct gip_chunk_buffer *chunk_buf_in; struct gip_hardware hardware; struct gip_info_element *client_commands; struct gip_info_element *firmware_versions; struct gip_info_element *audio_formats; struct gip_info_element *capabilities_out; struct gip_info_element *capabilities_in; struct gip_classes *classes; struct gip_info_element *interfaces; struct gip_info_element *hid_descriptor; struct gip_audio_config audio_config_in; struct gip_audio_config audio_config_out; struct gip_serial_number serial; }; struct gip_driver_ops { int (*battery)(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level); int (*authenticate)(struct gip_client *client, void *data, u32 len); int (*authenticated)(struct gip_client *client); int (*guide_button)(struct gip_client *client, bool down); int (*audio_ready)(struct gip_client *client); int (*audio_volume)(struct gip_client *client, u8 in, u8 out); int (*hid_report)(struct gip_client *client, void *data, u32 len); int (*input)(struct gip_client *client, void *data, u32 len); int (*firmware)(struct gip_client *client, void *data, u32 len); int (*audio_samples)(struct gip_client *client, void *data, u32 len); }; struct gip_driver { struct device_driver drv; const char *name; const char *class; struct gip_driver_ops ops; int (*probe)(struct gip_client *client); void (*remove)(struct gip_client *client); }; struct gip_adapter *gip_create_adapter(struct device *parent, struct gip_adapter_ops *ops, int audio_pkts); int gip_power_off_adapter(struct gip_adapter *adap); void gip_destroy_adapter(struct gip_adapter *adap); struct gip_client *gip_get_client(struct gip_adapter *adap, u8 id); void gip_add_client(struct gip_client *client); void gip_remove_client(struct gip_client *client); void gip_free_client_info(struct gip_client *client); int __gip_register_driver(struct gip_driver *drv, struct module *owner, const char *mod_name); void gip_unregister_driver(struct gip_driver *drv); dlundqvist-xone-f2aa9fe/bus/protocol.c000066400000000000000000001154661515500374600202560ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include "bus.h" #define GIP_HDR_CLIENT_ID GENMASK(3, 0) #define GIP_HDR_MIN_LENGTH 3 /* max length, even for wireless packets (except audio) */ #define GIP_PKT_MAX_LENGTH 58 /* reliable packet transmission coalesce count */ #define GIP_PKT_COALESCE_COUNT 5 #define GIP_CHUNK_BUF_MAX_LENGTH 0xffff #define GIP_BATT_LEVEL GENMASK(1, 0) #define GIP_BATT_TYPE GENMASK(3, 2) #define GIP_STATUS_CONNECTED BIT(7) #define GIP_VKEY_LEFT_WIN 0x5b #define gip_dbg(client, ...) dev_dbg(&(client)->adapter->dev, __VA_ARGS__) #define gip_warn(client, ...) dev_warn(&(client)->adapter->dev, __VA_ARGS__) #define gip_err(client, ...) dev_err(&(client)->adapter->dev, __VA_ARGS__) enum gip_command_core { GIP_CMD_ACKNOWLEDGE = 0x01, GIP_CMD_ANNOUNCE = 0x02, GIP_CMD_STATUS = 0x03, GIP_CMD_IDENTIFY = 0x04, GIP_CMD_POWER = 0x05, GIP_CMD_AUTHENTICATE = 0x06, GIP_CMD_VIRTUAL_KEY = 0x07, GIP_CMD_AUDIO_CONTROL = 0x08, GIP_CMD_LED = 0x0a, GIP_CMD_HID_REPORT = 0x0b, GIP_CMD_FIRMWARE = 0x0c, GIP_CMD_EXTENDED = 0x1e, GIP_CMD_AUDIO_SAMPLES = 0x60, }; enum gip_command_client { GIP_CMD_RUMBLE = 0x09, GIP_CMD_INPUT = 0x20, }; enum gip_option { GIP_OPT_ACKNOWLEDGE = BIT(4), GIP_OPT_INTERNAL = BIT(5), GIP_OPT_CHUNK_START = BIT(6), GIP_OPT_CHUNK = BIT(7), }; enum gip_audio_control { GIP_AUD_CTRL_VOLUME_CHAT = 0x00, GIP_AUD_CTRL_FORMAT_CHAT = 0x01, GIP_AUD_CTRL_FORMAT = 0x02, GIP_AUD_CTRL_VOLUME = 0x03, }; enum gip_audio_volume_mute { GIP_AUD_VOLUME_UNMUTED = 0x04, GIP_AUD_VOLUME_MIC_MUTED = 0x05, }; static uint gip_audio_sample_rate[] = { 0, 8000, 8000, 12000, 12000, 16000, 16000, 20000, 20000, 24000, 24000, 32000, 32000, 40000, 40000, 48000, 48000, }; enum gip_extended_command { GIP_EXT_GET_CAPABILITIES = 0x00, GIP_EXT_GET_TELEMETRY = 0x01, GIP_EXT_GET_SERIAL_NUMBER = 0x04, }; enum gip_extended_status { GIP_EXT_STATUS_OK, GIP_EXT_STATUS_NOT_SUPPORTED, GIP_EXT_STATUS_NOT_READY, GIP_EXT_STATUS_ACCESS_DENIED, GIP_EXT_STATUS_COMMAND_FAILED, }; struct gip_pkt_acknowledge { u8 unknown; u8 command; u8 options; __le16 length; u8 padding[2]; __le16 remaining; } __packed; struct gip_pkt_announce { u8 address[6]; __le16 unknown; __le16 vendor_id; __le16 product_id; struct gip_version { __le16 major; __le16 minor; __le16 build; __le16 revision; } __packed fw_version, hw_version; } __packed; struct gip_pkt_status { u8 status; u8 unknown[3]; } __packed; struct gip_pkt_identify { u8 unknown[16]; __le16 client_commands_offset; __le16 firmware_versions_offset; __le16 audio_formats_offset; __le16 capabilities_out_offset; __le16 capabilities_in_offset; __le16 classes_offset; __le16 interfaces_offset; __le16 hid_descriptor_offset; } __packed; struct gip_pkt_power { u8 mode; } __packed; struct gip_pkt_virtual_key { u8 down; u8 key; } __packed; struct gip_pkt_audio_control { u8 subcommand; } __packed; struct gip_pkt_audio_volume_chat { struct gip_pkt_audio_control control; u8 mute; u8 gain_out; u8 out; u8 in; } __packed; struct gip_pkt_audio_format_chat { struct gip_pkt_audio_control control; u8 in_out; } __packed; struct gip_pkt_audio_format { struct gip_pkt_audio_control control; u8 in; u8 out; } __packed; struct gip_pkt_audio_volume { struct gip_pkt_audio_control control; u8 mute; u8 out; u8 chat; u8 in; u8 unknown1; u8 unknown2[2]; } __packed; struct gip_pkt_led { u8 unknown; u8 mode; u8 brightness; } __packed; struct gip_pkt_get_serial_number { u8 command; } __packed; struct gip_pkt_serial_number { u8 command; u8 status; char serial[GIP_PKT_MAX_SERIAL_LENGTH]; } __packed; struct gip_pkt_audio_samples { __le16 flow_rate; u8 samples[]; } __packed; struct gip_command_descriptor { u8 marker; u8 unknown1; u8 command; u8 length; u8 unknown2[3]; u8 options; u8 unknown3[15]; } __packed; struct gip_firmware_version { __le16 major; __le16 minor; } __packed; static int gip_encode_varint(u8 *buf, u32 val) { int i; /* encode variable-length integer */ for (i = 0; i < sizeof(val); i++) { buf[i] = val; if (val > GENMASK(6, 0)) buf[i] |= BIT(7); val >>= 7; if (!val) break; } return i + 1; } static int gip_decode_varint(u8 *data, int len, u32 *val) { int i; /* decode variable-length integer */ for (i = 0; i < sizeof(*val) && i < len; i++) { *val |= (data[i] & GENMASK(6, 0)) << (i * 7); if (!(data[i] & BIT(7))) break; } return i + 1; } static int gip_get_actual_header_length(struct gip_header *hdr) { u32 pkt_len = hdr->packet_length; u32 chunk_offset = hdr->chunk_offset; int len = GIP_HDR_MIN_LENGTH; do { len++; pkt_len >>= 7; } while (pkt_len); if (hdr->options & GIP_OPT_CHUNK) { while (chunk_offset) { len++; chunk_offset >>= 7; } } return len; } static int gip_get_header_length(struct gip_header *hdr) { int len = gip_get_actual_header_length(hdr); /* round up to nearest even length */ return len + (len % 2); } static void gip_encode_header(struct gip_header *hdr, u8 *buf) { int hdr_len = 0; buf[hdr_len++] = hdr->command; buf[hdr_len++] = hdr->options; buf[hdr_len++] = hdr->sequence; hdr_len += gip_encode_varint(buf + hdr_len, hdr->packet_length); /* header length must be even */ if (gip_get_actual_header_length(hdr) % 2) { buf[hdr_len - 1] |= BIT(7); buf[hdr_len++] = 0; } if (hdr->options & GIP_OPT_CHUNK) gip_encode_varint(buf + hdr_len, hdr->chunk_offset); } static int gip_decode_header(struct gip_header *hdr, u8 *data, int len) { int hdr_len = 0; hdr->command = data[hdr_len++]; hdr->options = data[hdr_len++]; hdr->sequence = data[hdr_len++]; hdr->packet_length = 0; hdr->chunk_offset = 0; hdr_len += gip_decode_varint(data + hdr_len, len - hdr_len, &hdr->packet_length); if (hdr->options & GIP_OPT_CHUNK) hdr_len += gip_decode_varint(data + hdr_len, len - hdr_len, &hdr->chunk_offset); return hdr_len; } static int gip_init_chunk_buffer(struct gip_client *client, struct gip_header *hdr, struct gip_chunk_buffer **buf, bool is_input) { /* offset is total length of all chunks */ if (hdr->chunk_offset > GIP_CHUNK_BUF_MAX_LENGTH) return -EINVAL; if (*buf) { gip_err(client, "%s: already initialized (%s)\n", __func__, is_input ? "in" : "out"); kfree(*buf); *buf = NULL; } *buf = kzalloc(sizeof(**buf) + hdr->chunk_offset, GFP_ATOMIC); if (!*buf) return -ENOMEM; (*buf)->header = *hdr; /* clear GIP_OPT_ACKNOWLEDGE and GIP_OPT_CHUNK_START */ (*buf)->header.options &= ~(GIP_OPT_ACKNOWLEDGE | GIP_OPT_CHUNK_START); (*buf)->length = hdr->chunk_offset; gip_dbg(client, "%s[%s]: command=0x%02x, length=0x%04x\n", __func__, is_input ? "in" : "out", (*buf)->header.command, (*buf)->length); return 0; } static int gip_send_pkt_simple(struct gip_client *client, struct gip_header *hdr, void *data) { struct gip_adapter *adap = client->adapter; struct gip_adapter_buffer buf = {}; int hdr_len, err; unsigned long flags; buf.type = GIP_BUF_DATA; spin_lock_irqsave(&adap->send_lock, flags); err = adap->ops->get_buffer(adap, &buf); if (err) { gip_err(client, "%s: get buffer failed: %d\n", __func__, err); goto err_unlock; } hdr_len = gip_get_header_length(hdr); if (buf.length < hdr_len + hdr->packet_length) { err = -ENOSPC; goto err_unlock; } /* sequence number is always greater than zero */ while (!hdr->sequence) hdr->sequence = adap->data_sequence++; gip_encode_header(hdr, buf.data); if (data) memcpy(buf.data + hdr_len, data, hdr->packet_length); /* set actual length */ buf.length = hdr_len + hdr->packet_length; /* debug message sent */ // gip_dbg(client, "%s: cmd=0x%02x len=0x%04x seq=0x%02x offset=0x%04x\n", // __func__, hdr->command, buf.length, hdr->sequence, // hdr->chunk_offset); /* always fails on adapter removal */ err = adap->ops->submit_buffer(adap, &buf); if (err) gip_dbg(client, "%s: submit buffer failed: %d\n", __func__, err); err_unlock: spin_unlock_irqrestore(&adap->send_lock, flags); return err; } static int gip_send_pkt(struct gip_client *client, struct gip_header *hdr, void *data) { int err; /* packet fits into single buffer */ if (hdr->packet_length <= GIP_PKT_MAX_LENGTH) return gip_send_pkt_simple(client, hdr, data); /* chunk offset of first chunk is total length */ hdr->options |= GIP_OPT_ACKNOWLEDGE | GIP_OPT_CHUNK_START | GIP_OPT_CHUNK; hdr->chunk_offset = hdr->packet_length; hdr->packet_length = GIP_PKT_MAX_LENGTH; err = gip_send_pkt_simple(client, hdr, data); if (err) return err; /* allocate output buffer for all chunks */ err = gip_init_chunk_buffer(client, hdr, &client->chunk_buf_in, false); if (err) return err; if (data) memcpy(client->chunk_buf_in->data, data, hdr->chunk_offset); return 0; } static int gip_acknowledge_pkt(struct gip_client *client, struct gip_header *ack) { struct gip_chunk_buffer *buf = client->chunk_buf_out; struct gip_header hdr = {}; struct gip_pkt_acknowledge pkt = {}; u32 len = ack->chunk_offset + ack->packet_length; hdr.command = GIP_CMD_ACKNOWLEDGE; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.sequence = ack->sequence; hdr.packet_length = sizeof(pkt); pkt.command = ack->command; pkt.options = client->id | GIP_OPT_INTERNAL; pkt.length = cpu_to_le16(len); if ((ack->options & GIP_OPT_CHUNK) && buf) pkt.remaining = cpu_to_le16(buf->length - len); // gip_dbg(client, "%s: ACME(host) command=0x%02x, length=0x%04x\n", // __func__, pkt.command, len); return gip_send_pkt(client, &hdr, &pkt); } static int gip_request_identification(struct gip_client *client) { struct gip_header hdr = {}; hdr.command = GIP_CMD_IDENTIFY; hdr.options = client->id | GIP_OPT_INTERNAL; return gip_send_pkt(client, &hdr, NULL); } int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode) { struct gip_header hdr = {}; struct gip_pkt_power pkt = {}; hdr.command = GIP_CMD_POWER; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = sizeof(pkt); pkt.mode = mode; return gip_send_pkt(client, &hdr, &pkt); } EXPORT_SYMBOL_GPL(gip_set_power_mode); int gip_send_authenticate(struct gip_client *client, void *pkt, u32 len, bool acknowledge) { struct gip_header hdr = {}; hdr.command = GIP_CMD_AUTHENTICATE; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = len; if (acknowledge) hdr.options |= GIP_OPT_ACKNOWLEDGE; return gip_send_pkt(client, &hdr, pkt); } static int gip_set_audio_format_chat(struct gip_client *client, enum gip_audio_format in_out) { struct gip_header hdr = {}; struct gip_pkt_audio_format_chat pkt = {}; hdr.command = GIP_CMD_AUDIO_CONTROL; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = sizeof(pkt); pkt.control.subcommand = GIP_AUD_CTRL_FORMAT_CHAT; pkt.in_out = in_out; return gip_send_pkt(client, &hdr, &pkt); } static int gip_set_audio_format(struct gip_client *client, enum gip_audio_format in, enum gip_audio_format out) { struct gip_header hdr = {}; struct gip_pkt_audio_format pkt = {}; hdr.command = GIP_CMD_AUDIO_CONTROL; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = sizeof(pkt); pkt.control.subcommand = GIP_AUD_CTRL_FORMAT; pkt.in = in; pkt.out = out; return gip_send_pkt(client, &hdr, &pkt); } int gip_suggest_audio_format(struct gip_client *client, enum gip_audio_format in, enum gip_audio_format out, bool chat) { int err; /* special handling for the chat headset */ if (chat) err = gip_set_audio_format_chat(client, GIP_AUD_FORMAT_12KHZ_STEREO); else err = gip_set_audio_format(client, in, out); if (err) { gip_err(client, "%s: set format failed: %d\n", __func__, err); return err; } client->audio_config_in.format = in; client->audio_config_out.format = out; return 0; } EXPORT_SYMBOL_GPL(gip_suggest_audio_format); int gip_set_audio_volume(struct gip_client *client, u8 in, u8 chat, u8 out) { struct gip_header hdr = {}; struct gip_pkt_audio_volume pkt = {}; hdr.command = GIP_CMD_AUDIO_CONTROL; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = sizeof(pkt); pkt.control.subcommand = GIP_AUD_CTRL_VOLUME; pkt.mute = GIP_AUD_VOLUME_UNMUTED; pkt.out = out; pkt.chat = chat; pkt.in = in; return gip_send_pkt(client, &hdr, &pkt); } EXPORT_SYMBOL_GPL(gip_set_audio_volume); int gip_send_rumble(struct gip_client *client, void *pkt, u32 len) { struct gip_header hdr = {}; hdr.command = GIP_CMD_RUMBLE; hdr.options = client->id; hdr.packet_length = len; return gip_send_pkt(client, &hdr, pkt); } EXPORT_SYMBOL_GPL(gip_send_rumble); int gip_set_led_mode(struct gip_client *client, enum gip_led_mode mode, u8 brightness) { struct gip_header hdr = {}; struct gip_pkt_led pkt = {}; hdr.command = GIP_CMD_LED; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = sizeof(pkt); pkt.mode = mode; pkt.brightness = brightness; return gip_send_pkt(client, &hdr, &pkt); } EXPORT_SYMBOL_GPL(gip_set_led_mode); int gip_send_get_serial_number(struct gip_client *client) { struct gip_header hdr = { .command = GIP_CMD_EXTENDED, .options = client->id | GIP_OPT_INTERNAL, .packet_length = sizeof(struct gip_pkt_get_serial_number) }; struct gip_pkt_get_serial_number pkt = { .command = GIP_EXT_GET_SERIAL_NUMBER }; gip_dbg(client, "%s: sending get serial number packet", __func__); return gip_send_pkt(client, &hdr, &pkt); } EXPORT_SYMBOL_GPL(gip_send_get_serial_number); static void gip_copy_audio_samples(struct gip_client *client, void *samples, void *buf) { struct gip_audio_config *cfg = &client->audio_config_out; struct gip_header hdr = {}; void *src, *dest; int hdr_len, i; hdr.command = GIP_CMD_AUDIO_SAMPLES; hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = cfg->fragment_size; hdr_len = gip_get_header_length(&hdr); for (i = 0; i < client->adapter->audio_packet_count; i++) { src = samples + i * cfg->fragment_size; dest = buf + i * cfg->packet_size; /* sequence number is always greater than zero */ if (!++client->adapter->audio_sequence) ++client->adapter->audio_sequence; hdr.sequence = client->adapter->audio_sequence; gip_encode_header(&hdr, dest); memcpy(dest + hdr_len, src, cfg->fragment_size); } } int gip_send_audio_samples(struct gip_client *client, void *samples) { struct gip_adapter *adap = client->adapter; struct gip_adapter_buffer buf = {}; int err; buf.type = GIP_BUF_AUDIO; /* returns ENOSPC if no buffer is available */ err = adap->ops->get_buffer(adap, &buf); if (err) { gip_err(client, "%s: get buffer failed: %d\n", __func__, err); return err; } gip_copy_audio_samples(client, samples, buf.data); /* set actual length */ buf.length = client->audio_config_out.packet_size * adap->audio_packet_count; /* always fails on adapter removal */ err = adap->ops->submit_buffer(adap, &buf); if (err) gip_dbg(client, "%s: submit buffer failed: %d\n", __func__, err); return err; } EXPORT_SYMBOL_GPL(gip_send_audio_samples); bool gip_has_interface(struct gip_client *client, const guid_t *guid) { int i; for (i = 0; i < client->interfaces->count; i++) { if (guid_equal((guid_t *)client->interfaces->data + i, guid)) return true; } return false; } EXPORT_SYMBOL_GPL(gip_has_interface); int gip_set_encryption_key(struct gip_client *client, u8 *key, int len) { struct gip_adapter *adap = client->adapter; int err; if (!adap->ops->set_encryption_key) { gip_dbg(client, "%s: no callback, notifying driver.\n", __func__); if (client->drv->ops.authenticated) return client->drv->ops.authenticated(client); return 0; } err = adap->ops->set_encryption_key(adap, key, len); if (err) { gip_err(client, "%s: set key failed: %d\n", __func__, err); return err; } if (client->drv->ops.authenticated) client->drv->ops.authenticated(client); return 0; } int gip_enable_audio(struct gip_client *client) { struct gip_adapter *adap = client->adapter; int err; if (!adap->ops->enable_audio) return 0; err = adap->ops->enable_audio(adap); if (err) gip_err(client, "%s: enable failed: %d\n", __func__, err); return err; } EXPORT_SYMBOL_GPL(gip_enable_audio); int gip_init_audio_in(struct gip_client *client) { struct gip_adapter *adap = client->adapter; int err; if (!adap->ops->init_audio_in) return 0; err = adap->ops->init_audio_in(adap); if (err) gip_err(client, "%s: init failed: %d\n", __func__, err); return err; } EXPORT_SYMBOL_GPL(gip_init_audio_in); int gip_init_audio_out(struct gip_client *client) { struct gip_adapter *adap = client->adapter; int err; if (!adap->ops->init_audio_out) return 0; err = adap->ops->init_audio_out(adap, client->audio_config_out.packet_size); if (err) gip_err(client, "%s: init failed: %d\n", __func__, err); return err; } EXPORT_SYMBOL_GPL(gip_init_audio_out); int gip_init_extra_data(struct gip_client *client) { struct gip_header hdr = { .command = 0x4d, // ??? .options = GIP_OPT_ACKNOWLEDGE, // Because 4 .sequence = 1, .packet_length = 2, }; u8 packet_data[] = { 0x07, 0x00 }; return gip_send_pkt(client, &hdr, &packet_data); } EXPORT_SYMBOL_GPL(gip_init_extra_data); void gip_disable_audio(struct gip_client *client) { struct gip_adapter *adap = client->adapter; int err; if (!adap->ops->disable_audio) return; /* always fails on adapter removal */ err = adap->ops->disable_audio(adap); if (err) gip_dbg(client, "%s: disable failed: %d\n", __func__, err); } EXPORT_SYMBOL_GPL(gip_disable_audio); static int gip_make_audio_config(struct gip_client *client, struct gip_audio_config *cfg) { struct gip_header hdr = {}; if (cfg->format <= 0 || cfg->format > GIP_AUD_FORMAT_48KHZ_STEREO) { gip_err(client, "%s: unknown format: 0x%02x\n", __func__, cfg->format); return -ENOTSUPP; } /* even-indexed formats are stereo, uneven are mono */ cfg->channels = 2 - (cfg->format & 1); cfg->sample_rate = gip_audio_sample_rate[cfg->format]; cfg->buffer_size = cfg->sample_rate * cfg->channels * sizeof(s16) * GIP_AUDIO_INTERVAL / MSEC_PER_SEC; cfg->fragment_size = cfg->buffer_size / client->adapter->audio_packet_count; /* pseudo header for length calculation */ hdr.packet_length = cfg->fragment_size; cfg->packet_size = gip_get_header_length(&hdr) + cfg->fragment_size; /* set initial flow rate value */ cfg->flow_rate = cfg->buffer_size; gip_dbg(client, "%s: rate=%d/%d, buffer=%d\n", __func__, cfg->sample_rate, cfg->channels, cfg->buffer_size); return 0; } static struct gip_info_element *gip_parse_info_element(u8 *data, u32 len, __le16 offset, int item_length) { struct gip_info_element *elem; u16 off = le16_to_cpu(offset); u8 count; int total; if (!off) return ERR_PTR(-ENOTSUPP); if (len < off + sizeof(count)) return ERR_PTR(-EINVAL); count = data[off++]; if (!count) return ERR_PTR(-ENOTSUPP); total = count * item_length; if (len < off + total) return ERR_PTR(-EINVAL); elem = kzalloc(struct_size(elem, data, total), GFP_ATOMIC); if (!elem) return ERR_PTR(-ENOMEM); elem->count = count; memcpy(elem->data, data + off, total); return elem; } static int gip_parse_client_commands(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_info_element *cmds; struct gip_command_descriptor *desc; int i; cmds = gip_parse_info_element(data, len, pkt->client_commands_offset, sizeof(*desc)); if (IS_ERR(cmds)) { if (PTR_ERR(cmds) == -ENOTSUPP) return 0; gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(cmds)); return PTR_ERR(cmds); } for (i = 0; i < cmds->count; i++) { desc = (struct gip_command_descriptor *)cmds->data + i; gip_dbg(client, "%s: command=0x%02x, length=0x%02x, options=0x%02x\n", __func__, desc->command, desc->length, desc->options); } client->client_commands = cmds; return 0; } static int gip_parse_firmware_versions(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_info_element *vers; struct gip_firmware_version *ver; int i; vers = gip_parse_info_element(data, len, pkt->firmware_versions_offset, sizeof(*ver)); if (IS_ERR(vers)) { gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(vers)); return PTR_ERR(vers); } for (i = 0; i < vers->count; i++) { ver = (struct gip_firmware_version *)vers->data + i; gip_dbg(client, "%s: version=%u.%u\n", __func__, le16_to_cpu(ver->major), le16_to_cpu(ver->minor)); } client->firmware_versions = vers; return 0; } static int gip_parse_audio_formats(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_info_element *fmts; fmts = gip_parse_info_element(data, len, pkt->audio_formats_offset, 2); if (IS_ERR(fmts)) { if (PTR_ERR(fmts) == -ENOTSUPP) return 0; gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(fmts)); return PTR_ERR(fmts); } gip_dbg(client, "%s: formats=%*phD\n", __func__, fmts->count * 2, fmts->data); client->audio_formats = fmts; return 0; } static int gip_parse_capabilities(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_info_element *caps; caps = gip_parse_info_element(data, len, pkt->capabilities_out_offset, 1); if (IS_ERR(caps)) { gip_err(client, "%s: parse out failed: %ld\n", __func__, PTR_ERR(caps)); return PTR_ERR(caps); } gip_dbg(client, "%s: out=%*phD\n", __func__, caps->count, caps->data); client->capabilities_out = caps; caps = gip_parse_info_element(data, len, pkt->capabilities_in_offset, 1); if (IS_ERR(caps)) { gip_err(client, "%s: parse in failed: %ld\n", __func__, PTR_ERR(caps)); return PTR_ERR(caps); } gip_dbg(client, "%s: in=%*phD\n", __func__, caps->count, caps->data); client->capabilities_in = caps; return 0; } static int gip_parse_classes(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_classes *classes; u16 off = le16_to_cpu(pkt->classes_offset); u8 count; u16 str_len; char *str; if (len < off + sizeof(count)) return -EINVAL; /* number of individual strings */ count = data[off++]; if (!count) return -EINVAL; classes = kzalloc(struct_size(classes, strings, count), GFP_ATOMIC); if (!classes) return -ENOMEM; client->classes = classes; while (classes->count < count) { if (len < off + sizeof(str_len)) return -EINVAL; str_len = le16_to_cpup((__le16 *)(data + off)); off += sizeof(str_len); if (!str_len || len < off + str_len) return -EINVAL; /* null-terminated string */ str = kzalloc(str_len + 1, GFP_ATOMIC); if (!str) return -ENOMEM; memcpy(str, data + off, str_len); classes->strings[classes->count] = str; classes->count++; off += str_len; gip_dbg(client, "%s: class=%s\n", __func__, str); } return 0; } static int gip_parse_interfaces(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_info_element *intfs; guid_t *guid; int i; intfs = gip_parse_info_element(data, len, pkt->interfaces_offset, sizeof(guid_t)); if (IS_ERR(intfs)) { gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(intfs)); return PTR_ERR(intfs); } for (i = 0; i < intfs->count; i++) { guid = (guid_t *)intfs->data + i; gip_dbg(client, "%s: guid=%pUb\n", __func__, guid); } client->interfaces = intfs; return 0; } static int gip_parse_hid_descriptor(struct gip_client *client, struct gip_pkt_identify *pkt, u8 *data, u32 len) { struct gip_info_element *desc; desc = gip_parse_info_element(data, len, pkt->hid_descriptor_offset, 1); if (IS_ERR(desc)) { if (PTR_ERR(desc) == -ENOTSUPP) return 0; gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(desc)); return PTR_ERR(desc); } gip_dbg(client, "%s: length=0x%02x\n", __func__, desc->count); client->hid_descriptor = desc; return 0; } static int gip_send_remaining_chunks(struct gip_client *client, u32 offset) { struct gip_chunk_buffer *buf = client->chunk_buf_in; struct gip_header hdr = buf->header; u32 len = buf->length - offset; int err; gip_dbg(client, "%s: sending chunk 0x%04x/0x%04x/0x%04x\n", __func__, len, hdr.chunk_offset, buf->length); int coalesce_count = GIP_PKT_COALESCE_COUNT; while (len && coalesce_count) { /* require acknowledgment for last chunk */ if (len <= GIP_PKT_MAX_LENGTH) hdr.options |= GIP_OPT_ACKNOWLEDGE; hdr.packet_length = min_t(u32, len, GIP_PKT_MAX_LENGTH); hdr.chunk_offset = buf->length - len; err = gip_send_pkt_simple(client, &hdr, buf->data + hdr.chunk_offset); if (err) return err; len -= hdr.packet_length; coalesce_count--; } return 0; } static int gip_handle_pkt_acknowledge(struct gip_client *client, void *data, u32 len) { struct gip_pkt_acknowledge *pkt = data; struct gip_chunk_buffer *buf = client->chunk_buf_in; struct gip_header hdr; if (len != sizeof(*pkt)) return -EINVAL; if (!buf) return 0; gip_dbg(client, "%s: ACME(dev) cmd=0x%02x/0x%02x, len=0x%04x/0x%04x\n", __func__, pkt->command, buf->header.command, le16_to_cpu(pkt->length), buf->length); /* acknowledgment for different command */ if (pkt->command != buf->header.command) return 0; /* * offset comes from the device and may be malicious or invalid * so sanitize value to prevent buffer overflow. */ if (le16_to_cpu(pkt->length) < buf->length) return gip_send_remaining_chunks(client, le16_to_cpu(pkt->length)); gip_dbg(client, "%s: all chunks sent\n", __func__); /* empty chunk signals the completion of the transfer */ hdr = buf->header; hdr.packet_length = 0; hdr.chunk_offset = buf->length; kfree(buf); client->chunk_buf_in = NULL; return gip_send_pkt_simple(client, &hdr, NULL); } static int gip_handle_pkt_announce(struct gip_client *client, void *data, u32 len) { struct gip_pkt_announce *pkt = data; struct gip_hardware *hw = &client->hardware; if (len != sizeof(*pkt)) return -EINVAL; if (!hw->vendor && !hw->product && !hw->version) { hw->vendor = le16_to_cpu(pkt->vendor_id); hw->product = le16_to_cpu(pkt->product_id); hw->version = (le16_to_cpu(pkt->fw_version.major) << 8) | le16_to_cpu(pkt->fw_version.minor); } gip_dbg(client, "%s: address=%pM, vendor=0x%04x, product=0x%04x\n", __func__, pkt->address, hw->vendor, hw->product); gip_dbg(client, "%s: firmware=%u.%u.%u.%u, hardware=%u.%u.%u.%u\n", __func__, le16_to_cpu(pkt->fw_version.major), le16_to_cpu(pkt->fw_version.minor), le16_to_cpu(pkt->fw_version.build), le16_to_cpu(pkt->fw_version.revision), le16_to_cpu(pkt->hw_version.major), le16_to_cpu(pkt->hw_version.minor), le16_to_cpu(pkt->hw_version.build), le16_to_cpu(pkt->hw_version.revision)); return gip_request_identification(client); } static int gip_handle_pkt_status(struct gip_client *client, void *data, u32 len) { struct gip_pkt_status *pkt = data; int err = 0; u8 batt_type, batt_lvl; /* some devices occasionally send larger status packets */ if (len < sizeof(*pkt)) return -EINVAL; if (!(pkt->status & GIP_STATUS_CONNECTED)) { gip_dbg(client, "%s: disconnected\n", __func__); gip_remove_client(client); return 0; } batt_type = FIELD_GET(GIP_BATT_TYPE, pkt->status); batt_lvl = FIELD_GET(GIP_BATT_LEVEL, pkt->status); if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.battery) err = client->drv->ops.battery(client, batt_type, batt_lvl); up(&client->drv_lock); return err; } static int gip_handle_pkt_extended(struct gip_client *client, void *data, u32 len) { struct gip_pkt_serial_number *pkt = data; if (len > sizeof(*pkt) || len < GIP_PKT_MIN_SERIAL_LENGTH + 2) return -EINVAL; if (pkt->command != GIP_EXT_GET_SERIAL_NUMBER) { gip_dbg(client, "%s: extended command not Get Serial Number\n", __func__); return 0; } if (pkt->status != GIP_EXT_STATUS_OK) { gip_dbg(client, "%s: extended command status not OK\n", __func__); return 1; } if (down_trylock(&client->drv_lock)) return -EBUSY; client->serial.len = len - 2; memcpy(client->serial.data, pkt->serial, client->serial.len); client->serial.data[client->serial.len] = '\0'; up(&client->drv_lock); gip_dbg(client, "%s: serial number length: %u\n", __func__, client->serial.len); gip_dbg(client, "%s: serial number: %s\n", __func__, client->serial.data); return 0; } static int gip_handle_pkt_identify(struct gip_client *client, void *data, u32 len) { struct gip_pkt_identify *pkt = data; int err; if (len < sizeof(*pkt)) return -EINVAL; if (client->classes) { gip_warn(client, "%s: already identified\n", __func__); return 0; } /* skip unknown header */ data += sizeof(pkt->unknown); len -= sizeof(pkt->unknown); err = gip_parse_client_commands(client, pkt, data, len); if (err) goto err_free_info; err = gip_parse_firmware_versions(client, pkt, data, len); if (err) goto err_free_info; err = gip_parse_audio_formats(client, pkt, data, len); if (err) goto err_free_info; err = gip_parse_capabilities(client, pkt, data, len); if (err) goto err_free_info; err = gip_parse_classes(client, pkt, data, len); if (err) goto err_free_info; err = gip_parse_interfaces(client, pkt, data, len); if (err) goto err_free_info; err = gip_parse_hid_descriptor(client, pkt, data, len); if (err) goto err_free_info; gip_add_client(client); return 0; err_free_info: gip_free_client_info(client); return err; } static int gip_handle_pkt_authenticate(struct gip_client *client, void *data, u32 len) { int err = 0; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.authenticate) err = client->drv->ops.authenticate(client, data, len); up(&client->drv_lock); return err; } static int gip_handle_pkt_virtual_key(struct gip_client *client, void *data, u32 len) { struct gip_pkt_virtual_key *pkt = data; int err = 0; if (len != sizeof(*pkt)) return -EINVAL; if (pkt->key != GIP_VKEY_LEFT_WIN) return -EINVAL; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.guide_button) err = client->drv->ops.guide_button(client, pkt->down); up(&client->drv_lock); return err; } static int gip_handle_pkt_audio_format_chat(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_format_chat *pkt = data; struct gip_audio_config *in = &client->audio_config_in; struct gip_audio_config *out = &client->audio_config_out; int err; if (len != sizeof(*pkt)) return -EINVAL; /* chat headsets apparently default to 24 kHz */ if (pkt->in_out != GIP_AUD_FORMAT_12KHZ_STEREO || in->buffer_size || out->buffer_size) return -EPROTO; err = gip_make_audio_config(client, in); if (err) return err; err = gip_make_audio_config(client, out); if (err) return err; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.audio_ready) err = client->drv->ops.audio_ready(client); up(&client->drv_lock); return err; } static int gip_handle_pkt_audio_volume_chat(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_volume_chat *pkt = data; int err = 0; if (len != sizeof(*pkt)) return -EINVAL; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.audio_volume) err = client->drv->ops.audio_volume(client, pkt->in, pkt->out); up(&client->drv_lock); return err; } static int gip_handle_pkt_audio_format(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_format *pkt = data; struct gip_audio_config *in = &client->audio_config_in; struct gip_audio_config *out = &client->audio_config_out; int err; if (len != sizeof(*pkt)) return -EINVAL; /* format has already been accepted */ if (in->buffer_size || out->buffer_size) return -EPROTO; /* client rejected format, accept new format */ if (pkt->in != in->format || pkt->out != out->format) { gip_warn(client, "%s: rejected: 0x%02x/0x%02x\n", __func__, in->format, out->format); return gip_suggest_audio_format(client, pkt->in, pkt->out, false); } err = gip_make_audio_config(client, in); if (err) return err; err = gip_make_audio_config(client, out); if (err) return err; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.audio_ready) err = client->drv->ops.audio_ready(client); up(&client->drv_lock); return err; } static int gip_handle_pkt_audio_volume(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_volume *pkt = data; int err = 0; if (len != sizeof(*pkt)) return -EINVAL; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.audio_volume) err = client->drv->ops.audio_volume(client, pkt->in, pkt->out); up(&client->drv_lock); return err; } static int gip_handle_pkt_audio_control(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_control *pkt = data; if (len < sizeof(*pkt)) return -EINVAL; switch (pkt->subcommand) { case GIP_AUD_CTRL_FORMAT_CHAT: return gip_handle_pkt_audio_format_chat(client, data, len); case GIP_AUD_CTRL_VOLUME_CHAT: return gip_handle_pkt_audio_volume_chat(client, data, len); case GIP_AUD_CTRL_FORMAT: return gip_handle_pkt_audio_format(client, data, len); case GIP_AUD_CTRL_VOLUME: return gip_handle_pkt_audio_volume(client, data, len); } gip_err(client, "%s: unknown subcommand: 0x%02x\n", __func__, pkt->subcommand); return -EPROTO; } static int gip_handle_pkt_hid_report(struct gip_client *client, void *data, u32 len) { int err = 0; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.hid_report) err = client->drv->ops.hid_report(client, data, len); up(&client->drv_lock); return err; } static int gip_handle_pkt_input(struct gip_client *client, void *data, u32 len) { int err = 0; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.input) err = client->drv->ops.input(client, data, len); up(&client->drv_lock); return err; } static int gip_handle_pkt_audio_samples(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_samples *pkt = data; struct gip_audio_config *out = &client->audio_config_out; int err = 0; if (len < sizeof(*pkt)) return -EINVAL; if (down_trylock(&client->drv_lock)) return -EBUSY; out->flow_rate = le16_to_cpu(pkt->flow_rate); if (out->flow_rate != out->buffer_size) gip_dbg(client, "%s: Unusual flow rate control -> %u", __func__, out->flow_rate); if (client->drv && client->drv->ops.audio_samples) err = client->drv->ops.audio_samples(client, pkt->samples, len - sizeof(*pkt)); up(&client->drv_lock); return err; } static int gip_handle_pkt_firmware(struct gip_client *client, void *data, u32 len) { int err = 0; if (down_trylock(&client->drv_lock)) return -EBUSY; if (client->drv && client->drv->ops.firmware) err = client->drv->ops.firmware(client, data, len); up(&client->drv_lock); return err; } static int gip_dispatch_pkt(struct gip_client *client, struct gip_header *hdr, void *data, u32 len) { if (hdr->options & GIP_OPT_INTERNAL) { switch (hdr->command) { case GIP_CMD_ACKNOWLEDGE: return gip_handle_pkt_acknowledge(client, data, len); case GIP_CMD_ANNOUNCE: return gip_handle_pkt_announce(client, data, len); case GIP_CMD_STATUS: return gip_handle_pkt_status(client, data, len); case GIP_CMD_IDENTIFY: return gip_handle_pkt_identify(client, data, len); case GIP_CMD_AUTHENTICATE: return gip_handle_pkt_authenticate(client, data, len); case GIP_CMD_VIRTUAL_KEY: return gip_handle_pkt_virtual_key(client, data, len); case GIP_CMD_AUDIO_CONTROL: return gip_handle_pkt_audio_control(client, data, len); case GIP_CMD_HID_REPORT: return gip_handle_pkt_hid_report(client, data, len); case GIP_CMD_AUDIO_SAMPLES: return gip_handle_pkt_audio_samples(client, data, len); case GIP_CMD_EXTENDED: return gip_handle_pkt_extended(client, data, len); default: return 0; } } switch (hdr->command) { case GIP_CMD_INPUT: return gip_handle_pkt_input(client, data, len); case GIP_CMD_FIRMWARE: return gip_handle_pkt_firmware(client, data, len); default: pr_debug("%s: Unknown hdr command: 0x%02x", __func__, hdr->command); } return 0; } static int gip_process_pkt_chunked(struct gip_client *client, struct gip_header *hdr, void *data) { struct gip_chunk_buffer *buf = client->chunk_buf_out; int err; u32 len; gip_dbg(client, "%s: flags=[%s %s %s], offset=0x%04x, length=0x%04x\n", __func__, hdr->options & GIP_OPT_CHUNK_START? "Ini":"...", hdr->options & GIP_OPT_ACKNOWLEDGE? "Ack":"...", hdr->options & GIP_OPT_INTERNAL? "Sys":"...", hdr->chunk_offset, hdr->packet_length); if (!buf) { /* older gamepads occasionally send spurious completions */ if (!hdr->packet_length) return 0; gip_err(client, "%s: buffer not allocated\n", __func__); return -EPROTO; } if (hdr->command != buf->header.command) { gip_err(client, "%s: conflicting packet\n", __func__); return -EALREADY; } len = hdr->chunk_offset + hdr->packet_length; if (buf->length < len) { gip_err(client, "%s: buffer too small\n", __func__); return -EINVAL; } if (hdr->packet_length) { /* * acknowledge last non-empty chunked packet even when * not asked in current chunk header */ bool last_chunk = (buf->length == len); if ((hdr->options & GIP_OPT_ACKNOWLEDGE) || last_chunk) { err = gip_acknowledge_pkt(client, hdr); if (err) return err; } memcpy(buf->data + hdr->chunk_offset, data, hdr->packet_length); return 0; } /* empty chunk signals the completion of the transfer */ err = gip_dispatch_pkt(client, hdr, buf->data, buf->length); kfree(buf); client->chunk_buf_out = NULL; return err; } static int gip_process_pkt(struct gip_client *client, struct gip_header *hdr, void *data) { int err; if (hdr->options & GIP_OPT_CHUNK_START) { err = gip_init_chunk_buffer(client, hdr, &client->chunk_buf_out, true); if (err) return err; hdr->chunk_offset = 0; } if (hdr->options & GIP_OPT_CHUNK) return gip_process_pkt_chunked(client, hdr, data); if (hdr->options & GIP_OPT_ACKNOWLEDGE) { err = gip_acknowledge_pkt(client, hdr); if (err) return err; } return gip_dispatch_pkt(client, hdr, data, hdr->packet_length); } int gip_process_buffer(struct gip_adapter *adap, void *data, int len) { struct gip_header hdr; struct gip_client *client; int hdr_len, err; while (len > GIP_HDR_MIN_LENGTH) { hdr_len = gip_decode_header(&hdr, data, len); if (len < hdr_len + hdr.packet_length) return -EINVAL; client = gip_get_client(adap, hdr.options & GIP_HDR_CLIENT_ID); if (IS_ERR(client)) return PTR_ERR(client); err = gip_process_pkt(client, &hdr, data + hdr_len); if (err) return err; data += hdr_len + hdr.packet_length; len -= hdr_len + hdr.packet_length; } return 0; } EXPORT_SYMBOL_GPL(gip_process_buffer); dlundqvist-xone-f2aa9fe/bus/protocol.h000066400000000000000000000063701515500374600202540ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once #include #include #define GIP_VID_MICROSOFT 0x045e /* time between audio packets in ms */ #define GIP_AUDIO_INTERVAL 8 #define GIP_PKT_MIN_SERIAL_LENGTH 12 #define GIP_PKT_MAX_SERIAL_LENGTH 32 enum gip_battery_type { GIP_BATT_TYPE_NONE = 0x00, GIP_BATT_TYPE_STANDARD = 0x01, GIP_BATT_TYPE_KIT = 0x02, }; enum gip_battery_level { GIP_BATT_LEVEL_LOW = 0x00, GIP_BATT_LEVEL_NORMAL = 0x01, GIP_BATT_LEVEL_HIGH = 0x02, GIP_BATT_LEVEL_FULL = 0x03, }; enum gip_power_mode { GIP_PWR_ON = 0x00, GIP_PWR_SLEEP = 0x01, GIP_PWR_OFF = 0x04, GIP_PWR_RESET = 0x07, }; enum gip_audio_format { GIP_AUD_FORMAT_NONE, GIP_AUD_FORMAT_8KHZ_MONO, GIP_AUD_FORMAT_8KHZ_STEREO, GIP_AUD_FORMAT_12KHZ_MONO, GIP_AUD_FORMAT_12KHZ_STEREO, GIP_AUD_FORMAT_16KHZ_MONO, GIP_AUD_FORMAT_16KHZ_STEREO, GIP_AUD_FORMAT_20KHZ_MONO, GIP_AUD_FORMAT_20KHZ_STEREO, GIP_AUD_FORMAT_24KHZ_MONO, GIP_AUD_FORMAT_24KHZ_STEREO, GIP_AUD_FORMAT_32KHZ_MONO, GIP_AUD_FORMAT_32KHZ_STEREO, GIP_AUD_FORMAT_40KHZ_MONO, GIP_AUD_FORMAT_40KHZ_STEREO, GIP_AUD_FORMAT_48KHZ_MONO, GIP_AUD_FORMAT_48KHZ_STEREO, }; enum gip_led_mode { GIP_LED_OFF = 0x00, GIP_LED_ON = 0x01, GIP_LED_BLINK_FAST = 0x02, GIP_LED_BLINK_NORMAL = 0x03, GIP_LED_BLINK_SLOW = 0x04, GIP_LED_FADE_SLOW = 0x08, GIP_LED_FADE_FAST = 0x09, }; struct gip_header { u8 command; u8 options; u8 sequence; u32 packet_length; u32 chunk_offset; }; struct gip_chunk_buffer { struct gip_header header; u32 length; u8 data[]; }; struct gip_hardware { u16 vendor; u16 product; u16 version; }; struct gip_info_element { u8 count; u8 data[]; }; struct gip_serial_number { u8 len; char data[GIP_PKT_MAX_SERIAL_LENGTH + 1]; }; struct gip_audio_config { enum gip_audio_format format; int channels; int sample_rate; int buffer_size; int fragment_size; int packet_size; /* only used for rendered audio (out) */ u16 flow_rate; }; struct gip_classes { u8 count; const char *strings[]; }; struct gip_client; struct gip_adapter; int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode); int gip_send_authenticate(struct gip_client *client, void *pkt, u32 len, bool acknowledge); int gip_suggest_audio_format(struct gip_client *client, enum gip_audio_format in, enum gip_audio_format out, bool chat); int gip_set_audio_volume(struct gip_client *client, u8 in, u8 chat, u8 out); int gip_send_rumble(struct gip_client *client, void *pkt, u32 len); int gip_set_led_mode(struct gip_client *client, enum gip_led_mode mode, u8 brightness); int gip_send_audio_samples(struct gip_client *client, void *samples); int gip_init_extra_data(struct gip_client *client); int gip_send_get_serial_number(struct gip_client *client); bool gip_has_interface(struct gip_client *client, const guid_t *guid); int gip_set_encryption_key(struct gip_client *client, u8 *key, int len); int gip_enable_audio(struct gip_client *client); int gip_init_audio_in(struct gip_client *client); int gip_init_audio_out(struct gip_client *client); void gip_disable_audio(struct gip_client *client); int gip_process_buffer(struct gip_adapter *adap, void *data, int len); dlundqvist-xone-f2aa9fe/dkms.conf000066400000000000000000000016461515500374600172570ustar00rootroot00000000000000PACKAGE_NAME="xone" PACKAGE_VERSION="#VERSION#" BUILT_MODULE_NAME[0]="xone_gip" BUILT_MODULE_NAME[1]="xone_wired" BUILT_MODULE_NAME[2]="xone_dongle" BUILT_MODULE_NAME[3]="xone_gip_gamepad" BUILT_MODULE_NAME[4]="xone_gip_headset" BUILT_MODULE_NAME[5]="xone_gip_chatpad" BUILT_MODULE_NAME[6]="xone_gip_madcatz_strat" BUILT_MODULE_NAME[7]="xone_gip_madcatz_glam" BUILT_MODULE_NAME[8]="xone_gip_pdp_jaguar" DEST_MODULE_LOCATION[0]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[1]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[2]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[3]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[4]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[5]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[6]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[7]="/kernel/drivers/input/joystick" DEST_MODULE_LOCATION[8]="/kernel/drivers/input/joystick" AUTOINSTALL="yes" dlundqvist-xone-f2aa9fe/driver/000077500000000000000000000000001515500374600167365ustar00rootroot00000000000000dlundqvist-xone-f2aa9fe/driver/chatpad.c000066400000000000000000000113141515500374600205060ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include "common.h" #define GIP_CP_NAME "Microsoft Xbox Chatpad" struct gip_chatpad { struct gip_client *client; struct gip_input input; struct hid_device *hid_dev; }; static int gip_chatpad_hid_start(struct hid_device *dev) { return 0; } static void gip_chatpad_hid_stop(struct hid_device *dev) { } static int gip_chatpad_hid_open(struct hid_device *dev) { return 0; } static void gip_chatpad_hid_close(struct hid_device *dev) { } static int gip_chatpad_hid_parse(struct hid_device *dev) { struct gip_chatpad *chatpad = dev->driver_data; struct gip_client *client = chatpad->client; struct gip_info_element *desc_info = client->hid_descriptor; struct hid_descriptor *desc = (struct hid_descriptor *)desc_info->data; if (desc->bLength < sizeof(*desc) || desc->bNumDescriptors != 1) { dev_err(&client->dev, "%s: invalid descriptor\n", __func__); return -EINVAL; } dev->version = le16_to_cpu(desc->bcdHID); dev->country = desc->bCountryCode; return hid_parse_report(dev, desc_info->data + sizeof(*desc), desc_info->count - sizeof(*desc)); } static int gip_chatpad_hid_raw_request(struct hid_device *dev, unsigned char report_num, __u8 *buf, size_t len, unsigned char report_type, int request_type) { return 0; } static struct hid_ll_driver gip_chatpad_hid_driver = { .start = gip_chatpad_hid_start, .stop = gip_chatpad_hid_stop, .open = gip_chatpad_hid_open, .close = gip_chatpad_hid_close, .parse = gip_chatpad_hid_parse, .raw_request = gip_chatpad_hid_raw_request, }; static int gip_chatpad_init_input(struct gip_chatpad *chatpad) { int err; input_set_capability(chatpad->input.dev, EV_KEY, BTN_MODE); err = input_register_device(chatpad->input.dev); if (err) dev_err(&chatpad->client->dev, "%s: register failed: %d\n", __func__, err); return err; } static int gip_chatpad_init_hid(struct gip_chatpad *chatpad) { struct gip_client *client = chatpad->client; struct hid_device *dev; int err; dev = hid_allocate_device(); if (IS_ERR(dev)) { dev_err(&client->dev, "%s: allocate failed: %ld\n", __func__, PTR_ERR(dev)); return PTR_ERR(dev); } dev->bus = BUS_USB; dev->vendor = client->hardware.vendor; dev->product = client->hardware.product; dev->version = client->hardware.version; dev->dev.parent = &client->dev; dev->ll_driver = &gip_chatpad_hid_driver; strscpy(dev->name, GIP_CP_NAME, sizeof(dev->name)); snprintf(dev->phys, sizeof(dev->phys), "%s/input1", dev_name(&client->dev)); dev->driver_data = chatpad; err = hid_add_device(dev); if (err) { dev_err(&client->dev, "%s: add failed: %d\n", __func__, err); hid_destroy_device(dev); return err; } chatpad->hid_dev = dev; return 0; } static int gip_chatpad_op_guide_button(struct gip_client *client, bool down) { struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); input_report_key(chatpad->input.dev, BTN_MODE, down); input_sync(chatpad->input.dev); return 0; } static int gip_chatpad_op_hid_report(struct gip_client *client, void *data, u32 len) { struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); return hid_input_report(chatpad->hid_dev, HID_INPUT_REPORT, data, len, true); } static int gip_chatpad_probe(struct gip_client *client) { struct gip_chatpad *chatpad; struct gip_info_element *hid_desc = client->hid_descriptor; int err; if (!hid_desc || hid_desc->count < sizeof(struct hid_descriptor)) return -ENODEV; chatpad = devm_kzalloc(&client->dev, sizeof(*chatpad), GFP_KERNEL); if (!chatpad) return -ENOMEM; chatpad->client = client; err = gip_set_power_mode(client, GIP_PWR_ON); if (err) return err; err = gip_init_input(&chatpad->input, client, GIP_CP_NAME); if (err) return err; err = gip_chatpad_init_input(chatpad); if (err) return err; err = gip_chatpad_init_hid(chatpad); if (err) return err; dev_set_drvdata(&client->dev, chatpad); return 0; } static void gip_chatpad_remove(struct gip_client *client) { struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); hid_destroy_device(chatpad->hid_dev); } static struct gip_driver gip_chatpad_driver = { .name = "xone-gip-chatpad", .class = "Windows.Xbox.Input.Chatpad", .ops = { .guide_button = gip_chatpad_op_guide_button, .hid_report = gip_chatpad_op_hid_report, }, .probe = gip_chatpad_probe, .remove = gip_chatpad_remove, }; module_gip_driver(gip_chatpad_driver); MODULE_ALIAS("gip:Windows.Xbox.Input.Chatpad"); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP chatpad driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/driver/common.c000066400000000000000000000132661515500374600204020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include "common.h" #define GIP_LED_BRIGHTNESS_DEFAULT 20 #define GIP_LED_BRIGHTNESS_MAX 50 static enum power_supply_property gip_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_MODEL_NAME, }; static int gip_get_battery_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct gip_battery *batt = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = batt->status; break; case POWER_SUPPLY_PROP_CAPACITY_LEVEL: val->intval = batt->capacity; break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = batt->name; break; default: return -EINVAL; } return 0; } int gip_init_battery(struct gip_battery *batt, struct gip_client *client, const char *name) { struct power_supply_config cfg = {}; batt->name = name; batt->status = POWER_SUPPLY_STATUS_UNKNOWN; batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; batt->desc.name = dev_name(&client->dev); batt->desc.type = POWER_SUPPLY_TYPE_BATTERY; batt->desc.properties = gip_battery_props; batt->desc.num_properties = ARRAY_SIZE(gip_battery_props); batt->desc.get_property = gip_get_battery_prop; cfg.drv_data = batt; batt->supply = devm_power_supply_register(&client->dev, &batt->desc, &cfg); if (IS_ERR(batt->supply)) { dev_err(&client->dev, "%s: register failed: %ld\n", __func__, PTR_ERR(batt->supply)); return PTR_ERR(batt->supply); } power_supply_powers(batt->supply, &client->dev); return 0; } EXPORT_SYMBOL_GPL(gip_init_battery); void gip_report_battery(struct gip_battery *batt, enum gip_battery_type type, enum gip_battery_level level) { if (type == GIP_BATT_TYPE_NONE) batt->status = POWER_SUPPLY_STATUS_NOT_CHARGING; else batt->status = POWER_SUPPLY_STATUS_DISCHARGING; if (type == GIP_BATT_TYPE_NONE) batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; else if (level == GIP_BATT_LEVEL_LOW) batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_LOW; else if (level == GIP_BATT_LEVEL_NORMAL) batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; else if (level == GIP_BATT_LEVEL_HIGH) batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; else if (level == GIP_BATT_LEVEL_FULL) batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_FULL; if (batt->supply) power_supply_changed(batt->supply); } EXPORT_SYMBOL_GPL(gip_report_battery); static void gip_led_brightness_set(struct led_classdev *dev, enum led_brightness brightness) { struct gip_led *led = container_of(dev, typeof(*led), dev); int err; if (dev->flags & LED_UNREGISTERING) return; dev_dbg(&led->client->dev, "%s: brightness=%d\n", __func__, brightness); err = gip_set_led_mode(led->client, led->mode, brightness); if (err) dev_err(&led->client->dev, "%s: set LED mode failed: %d\n", __func__, err); } static ssize_t gip_led_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *cdev = dev_get_drvdata(dev); struct gip_led *led = container_of(cdev, typeof(*led), dev); return sysfs_emit(buf, "%u\n", led->mode); } static ssize_t gip_led_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *cdev = dev_get_drvdata(dev); struct gip_led *led = container_of(cdev, typeof(*led), dev); u8 mode; int err; err = kstrtou8(buf, 10, &mode); if (err) return err; dev_dbg(&led->client->dev, "%s: mode=%u\n", __func__, mode); led->mode = mode; err = gip_set_led_mode(led->client, mode, cdev->brightness); if (err) { dev_err(&led->client->dev, "%s: set LED mode failed: %d\n", __func__, err); return err; } return count; } static struct device_attribute gip_led_attr_mode = __ATTR(mode, 0644, gip_led_mode_show, gip_led_mode_store); static struct attribute *gip_led_attrs[] = { &gip_led_attr_mode.attr, NULL, }; ATTRIBUTE_GROUPS(gip_led); int gip_init_led(struct gip_led *led, struct gip_client *client) { int err; /* set default brightness */ err = gip_set_led_mode(client, GIP_LED_ON, GIP_LED_BRIGHTNESS_DEFAULT); if (err) { dev_err(&client->dev, "%s: set brightness failed: %d\n", __func__, err); return err; } led->dev.name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s:white:status", dev_name(&client->dev)); if (!led->dev.name) return -ENOMEM; led->dev.brightness = GIP_LED_BRIGHTNESS_DEFAULT; led->dev.max_brightness = GIP_LED_BRIGHTNESS_MAX; led->dev.brightness_set = gip_led_brightness_set; led->dev.groups = gip_led_groups; led->client = client; led->mode = GIP_LED_ON; err = devm_led_classdev_register(&client->dev, &led->dev); if (err) dev_err(&client->dev, "%s: register failed: %d\n", __func__, err); return err; } EXPORT_SYMBOL_GPL(gip_init_led); int gip_init_input(struct gip_input *input, struct gip_client *client, const char *name) { input->dev = devm_input_allocate_device(&client->dev); if (!input->dev) return -ENOMEM; input->dev->phys = devm_kasprintf(&client->dev, GFP_KERNEL, "%s/input0", dev_name(&client->dev)); if (!input->dev->phys) return -ENOMEM; gip_send_get_serial_number(client); input->dev->name = name; input->dev->id.bustype = BUS_VIRTUAL; input->dev->id.vendor = client->hardware.vendor; input->dev->id.product = client->hardware.product; input->dev->id.version = client->hardware.version; input->dev->dev.parent = &client->dev; input->dev->uniq = client->serial.data; return 0; } EXPORT_SYMBOL_GPL(gip_init_input); dlundqvist-xone-f2aa9fe/driver/common.h000066400000000000000000000017031515500374600204000ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once #include #include #include #include "../bus/bus.h" struct gip_battery { struct power_supply *supply; struct power_supply_desc desc; const char *name; int status; int capacity; }; struct gip_led { struct led_classdev dev; struct gip_client *client; enum gip_led_mode mode; }; struct gip_input { struct input_dev *dev; }; struct gip_vidpid { u16 vendor; u16 product; }; int gip_init_battery(struct gip_battery *batt, struct gip_client *client, const char *name); void gip_report_battery(struct gip_battery *batt, enum gip_battery_type type, enum gip_battery_level level); int gip_init_led(struct gip_led *led, struct gip_client *client); int gip_init_input(struct gip_input *input, struct gip_client *client, const char *name); dlundqvist-xone-f2aa9fe/driver/gamepad.c000066400000000000000000000366231515500374600205120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include #include "common.h" #include "../auth/auth.h" #define GIP_GP_NAME "Microsoft Xbox Controller" #define GIP_VENDOR_MICROSOFT 0x045e #define GIP_PRODUCT_ELITE_SERIES_2 0x0b00 #define GIP_PRODUCT_ELITE 0x02e3 /* * Various versions of the Elite Series 2 firmware have changed the way paddle * states are sent. Paddle support is only reported up to this firmware * version. */ #define GIP_ELITE_SERIES_2_4X_FIRMWARE 0x04FF #define GIP_ELITE_SERIES_2_510_FIRMWARE 0x050A #define GIP_GP_RUMBLE_DELAY msecs_to_jiffies(10) #define GIP_GP_RUMBLE_MAX 100 /* button offset from end of packet */ #define GIP_GP_BTN_SHARE_OFFSET 18 /* fallback for kernels < 6.17 */ #ifndef BTN_GRIPR #define BTN_GRIPR BTN_TRIGGER_HAPPY5 #define BTN_GRIPR2 BTN_TRIGGER_HAPPY6 #define BTN_GRIPL BTN_TRIGGER_HAPPY7 #define BTN_GRIPL2 BTN_TRIGGER_HAPPY8 #endif static const guid_t gip_gamepad_guid_share = GUID_INIT(0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); static const guid_t gip_gamepad_guid_dli = GUID_INIT(0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); enum gip_gamepad_button { GIP_GP_BTN_MENU = BIT(2), GIP_GP_BTN_VIEW = BIT(3), GIP_GP_BTN_A = BIT(4), GIP_GP_BTN_B = BIT(5), GIP_GP_BTN_X = BIT(6), GIP_GP_BTN_Y = BIT(7), GIP_GP_BTN_DPAD_U = BIT(8), GIP_GP_BTN_DPAD_D = BIT(9), GIP_GP_BTN_DPAD_L = BIT(10), GIP_GP_BTN_DPAD_R = BIT(11), GIP_GP_BTN_BUMPER_L = BIT(12), GIP_GP_BTN_BUMPER_R = BIT(13), GIP_GP_BTN_STICK_L = BIT(14), GIP_GP_BTN_STICK_R = BIT(15), }; enum gip_gamepad_paddle { GIP_GP_BTN_P1 = BIT(0), GIP_GP_BTN_P2 = BIT(1), GIP_GP_BTN_P3 = BIT(2), GIP_GP_BTN_P4 = BIT(3), }; enum gip_gamepad_motor { GIP_GP_MOTOR_R = BIT(0), GIP_GP_MOTOR_L = BIT(1), GIP_GP_MOTOR_RT = BIT(2), GIP_GP_MOTOR_LT = BIT(3), }; /* * Remember, xpad keeps the 4 bytes. * Paddles are at [18] in xpad, so, [14] here. * Pad 14 bytes. */ struct gip_gamepad_pkt_firmware { u8 unknown[14]; u8 paddles; u8 profile; } __packed; struct gip_gamepad_pkt_input { __le16 buttons; __le16 trigger_left; __le16 trigger_right; __le16 stick_left_x; __le16 stick_left_y; __le16 stick_right_x; __le16 stick_right_y; } __packed; struct gip_gamepad_pkt_dli { u32 counter_us1; u32 counter_us2; } __packed; struct gip_gamepad_pkt_rumble { u8 unknown; u8 motors; u8 left_trigger; u8 right_trigger; u8 left; u8 right; u8 duration; u8 delay; u8 repeat; } __packed; typedef enum PaddleCapability { PADDLE_NONE, PADDLE_ELITE, PADDLE_ELITE2_4X, // Still in the same packet PADDLE_ELITE2_510, // Same packet, different location PADDLE_ELITE2_511, // Different packet entirely. } PaddleCapability; struct gip_gamepad_rumble { /* serializes access to rumble packet */ spinlock_t lock; unsigned long last; struct timer_list timer; struct gip_gamepad_pkt_rumble pkt; struct gip_gamepad *parent; }; struct gip_gamepad { struct gip_client *client; struct gip_battery battery; struct gip_auth auth; struct gip_led led; struct gip_input input; bool supports_share; bool supports_dli; PaddleCapability paddle_support; struct gip_gamepad_rumble rumble; }; static void gip_gamepad_send_rumble(struct timer_list *timer) { // from_timer() has been renamed to timer_container_of() in linux 6.16 struct gip_gamepad_rumble *rumble = #if LINUX_VERSION_CODE < KERNEL_VERSION(6,16,0) from_timer(rumble, timer, timer); #else timer_container_of(rumble, timer, timer); #endif struct gip_gamepad *gamepad = rumble->parent; unsigned long flags; spin_lock_irqsave(&rumble->lock, flags); gip_send_rumble(gamepad->client, &rumble->pkt, sizeof(rumble->pkt)); rumble->last = jiffies; spin_unlock_irqrestore(&rumble->lock, flags); } static int gip_gamepad_queue_rumble(struct input_dev *dev, void *data, struct ff_effect *effect) { struct gip_gamepad_rumble *rumble = input_get_drvdata(dev); u16 mag_left = effect->u.rumble.strong_magnitude; u16 mag_right = effect->u.rumble.weak_magnitude; unsigned long flags; if (effect->type != FF_RUMBLE) return 0; spin_lock_irqsave(&rumble->lock, flags); rumble->pkt.left = mag_left * GIP_GP_RUMBLE_MAX / U16_MAX; rumble->pkt.right = mag_right * GIP_GP_RUMBLE_MAX / U16_MAX; /* delay rumble to work around firmware bug */ if (!timer_pending(&rumble->timer)) mod_timer(&rumble->timer, rumble->last + GIP_GP_RUMBLE_DELAY); spin_unlock_irqrestore(&rumble->lock, flags); return 0; } static int gip_gamepad_init_rumble(struct gip_gamepad *gamepad) { struct gip_gamepad_rumble *rumble = &gamepad->rumble; struct input_dev *dev = gamepad->input.dev; spin_lock_init(&rumble->lock); timer_setup(&rumble->timer, gip_gamepad_send_rumble, 0); /* stop rumble (required for some exotic gamepads to start input) */ rumble->pkt.motors = GIP_GP_MOTOR_R | GIP_GP_MOTOR_L | GIP_GP_MOTOR_RT | GIP_GP_MOTOR_LT; rumble->pkt.duration = 0xff; rumble->pkt.repeat = 0xeb; rumble->parent = gamepad; gip_gamepad_send_rumble(&rumble->timer); input_set_capability(dev, EV_FF, FF_RUMBLE); input_set_drvdata(dev, rumble); return input_ff_create_memless(dev, NULL, gip_gamepad_queue_rumble); } static int gip_gamepad_init_extra_data(struct gip_gamepad *gamepad) { return gip_init_extra_data(gamepad->client); } static void gip_gamepad_query_paddles(struct gip_gamepad *gamepad) { struct gip_hardware hardware = gamepad->client->hardware; gamepad->paddle_support = PADDLE_NONE; if (hardware.vendor != GIP_VENDOR_MICROSOFT) return; if (hardware.product == GIP_PRODUCT_ELITE) { pr_debug("%s: Elite Series 1\n", __func__); gamepad->paddle_support = PADDLE_ELITE; return; } if (hardware.product != GIP_PRODUCT_ELITE_SERIES_2) { pr_debug("%s: MS controller, no paddle support", __func__); return; } pr_debug("%s: Elite Series 2\n", __func__); if (hardware.version <= GIP_ELITE_SERIES_2_4X_FIRMWARE) gamepad->paddle_support = PADDLE_ELITE2_4X; else if (hardware.version <= GIP_ELITE_SERIES_2_510_FIRMWARE) gamepad->paddle_support = PADDLE_ELITE2_510; // If new revisions come, this should become LTE new max else if (hardware.version > GIP_ELITE_SERIES_2_510_FIRMWARE) { pr_debug("%s: FW > 5.10\n", __func__); gamepad->paddle_support = PADDLE_ELITE2_511; } } static int gip_gamepad_init_input(struct gip_gamepad *gamepad) { struct input_dev *dev = gamepad->input.dev; int err; gamepad->supports_share = gip_has_interface(gamepad->client, &gip_gamepad_guid_share); gamepad->supports_dli = gip_has_interface(gamepad->client, &gip_gamepad_guid_dli); if (gamepad->supports_share) input_set_capability(dev, EV_KEY, KEY_RECORD); if (gamepad->paddle_support) { pr_debug("%s: Paddle support detected", __func__); input_set_capability(dev, EV_KEY, BTN_GRIPR); input_set_capability(dev, EV_KEY, BTN_GRIPR2); input_set_capability(dev, EV_KEY, BTN_GRIPL); input_set_capability(dev, EV_KEY, BTN_GRIPL2); } input_set_capability(dev, EV_KEY, BTN_MODE); input_set_capability(dev, EV_KEY, BTN_START); input_set_capability(dev, EV_KEY, BTN_SELECT); input_set_capability(dev, EV_KEY, BTN_A); input_set_capability(dev, EV_KEY, BTN_B); input_set_capability(dev, EV_KEY, BTN_X); input_set_capability(dev, EV_KEY, BTN_Y); input_set_capability(dev, EV_KEY, BTN_TL); input_set_capability(dev, EV_KEY, BTN_TR); input_set_capability(dev, EV_KEY, BTN_THUMBL); input_set_capability(dev, EV_KEY, BTN_THUMBR); input_set_abs_params(dev, ABS_X, -32768, 32767, 16, 128); input_set_abs_params(dev, ABS_RX, -32768, 32767, 16, 128); input_set_abs_params(dev, ABS_Y, -32768, 32767, 16, 128); input_set_abs_params(dev, ABS_RY, -32768, 32767, 16, 128); input_set_abs_params(dev, ABS_Z, 0, 1023, 0, 0); input_set_abs_params(dev, ABS_RZ, 0, 1023, 0, 0); input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); err = gip_gamepad_init_rumble(gamepad); if (err) { dev_err(&gamepad->client->dev, "%s: init rumble failed: %d\n", __func__, err); goto err_delete_timer; } err = input_register_device(dev); if (err) { dev_err(&gamepad->client->dev, "%s: register failed: %d\n", __func__, err); goto err_delete_timer; } return 0; err_delete_timer: #if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) del_timer_sync(&gamepad->rumble.timer); #else timer_delete_sync(&gamepad->rumble.timer); #endif return err; } static int gip_gamepad_op_battery(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); gip_report_battery(&gamepad->battery, type, level); return 0; } static int gip_gamepad_op_authenticate(struct gip_client *client, void *data, u32 len) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); return gip_auth_process_pkt(&gamepad->auth, data, len); } static int gip_gamepad_op_guide_button(struct gip_client *client, bool down) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); input_report_key(gamepad->input.dev, BTN_MODE, down); input_sync(gamepad->input.dev); return 0; } static int gip_gamepad_op_authenticated(struct gip_client *client) { return 0; } static int gip_gamepad_op_firmware(struct gip_client *client, void *data, u32 len) { // First, ensure the data is of the correct size. struct gip_gamepad_pkt_firmware *pkt = data; if (len < sizeof(*pkt)) return -EINVAL; // Grab our controller struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); struct input_dev *dev = gamepad->input.dev; input_report_key(dev, BTN_GRIPR, pkt->paddles & GIP_GP_BTN_P1); input_report_key(dev, BTN_GRIPR2, pkt->paddles & GIP_GP_BTN_P2); input_report_key(dev, BTN_GRIPL, pkt->paddles & GIP_GP_BTN_P3); input_report_key(dev, BTN_GRIPL2, pkt->paddles & GIP_GP_BTN_P4); input_sync(dev); return 0; } static int gip_gamepad_op_input(struct gip_client *client, void *data, u32 len) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); struct gip_gamepad_pkt_input *pkt = data; struct input_dev *dev = gamepad->input.dev; u16 buttons; u8 share_offset = GIP_GP_BTN_SHARE_OFFSET; if (len < sizeof(*pkt)) return -EINVAL; buttons = le16_to_cpu(pkt->buttons); /* share button byte is always at fixed offset from end of packet */ if (gamepad->supports_share) { if (gamepad->supports_dli) share_offset += sizeof(struct gip_gamepad_pkt_dli); if (len < share_offset) return -EINVAL; input_report_key(dev, KEY_RECORD, ((u8 *)data)[len - share_offset]); } input_report_key(dev, BTN_START, buttons & GIP_GP_BTN_MENU); input_report_key(dev, BTN_SELECT, buttons & GIP_GP_BTN_VIEW); input_report_key(dev, BTN_A, buttons & GIP_GP_BTN_A); input_report_key(dev, BTN_B, buttons & GIP_GP_BTN_B); input_report_key(dev, BTN_X, buttons & GIP_GP_BTN_X); input_report_key(dev, BTN_Y, buttons & GIP_GP_BTN_Y); input_report_key(dev, BTN_TL, buttons & GIP_GP_BTN_BUMPER_L); input_report_key(dev, BTN_TR, buttons & GIP_GP_BTN_BUMPER_R); input_report_key(dev, BTN_THUMBL, buttons & GIP_GP_BTN_STICK_L); input_report_key(dev, BTN_THUMBR, buttons & GIP_GP_BTN_STICK_R); /* * For anyone comparing to xpad's paddle handling source, xone strips * four bytes of header off of the beginning that xpad doesn't, so all * offsets are 4 less later revisions put paddle support in the firmware * packet, check gip_gamepad_op_WTFEVER * * For 5.10 and below, the paddle data is in various locations within * the main input packet, for 5.11 and above the data is stored in a * separate packet and handeled by gip_gamepad_op_firmware(). */ int report_paddles = 0, series_1 = 0; u8 paddles; // Assume the controller might not send profile data, check length if (gamepad->paddle_support == PADDLE_ELITE2_510 && len > 18) { /* * On the Elite Series 2 with newer-ISH firmware (<=5.10) * paddles are stored at byte 18 (22) */ paddles = ((u8 *)data)[18]; report_paddles = 1; } else if (gamepad->paddle_support == PADDLE_ELITE2_4X && len > 14) { /* * On the Elite Series 2 with older firmware (<5.0) * paddles are stored at byte 14 (18) */ paddles = ((u8 *)data)[14]; report_paddles = 1; } else if (gamepad->paddle_support == PADDLE_ELITE && len > 28) { // On the original Elite, paddles are stored at byte 28 paddles = ((u8 *)data)[28]; report_paddles = 1; series_1 = 1; } // Series 1 reports paddles as different buttons than newer ones if (report_paddles) { input_report_key(dev, BTN_GRIPR, paddles & (series_1 ? GIP_GP_BTN_P2 : GIP_GP_BTN_P1)); input_report_key(dev, BTN_GRIPR2, paddles & (series_1 ? GIP_GP_BTN_P4 : GIP_GP_BTN_P2)); input_report_key(dev, BTN_GRIPL, paddles & (series_1 ? GIP_GP_BTN_P1 : GIP_GP_BTN_P3)); input_report_key(dev, BTN_GRIPL2, paddles & (series_1 ? GIP_GP_BTN_P3 : GIP_GP_BTN_P4)); } input_report_abs(dev, ABS_X, (s16)le16_to_cpu(pkt->stick_left_x)); input_report_abs(dev, ABS_RX, (s16)le16_to_cpu(pkt->stick_right_x)); input_report_abs(dev, ABS_Y, ~(s16)le16_to_cpu(pkt->stick_left_y)); input_report_abs(dev, ABS_RY, ~(s16)le16_to_cpu(pkt->stick_right_y)); input_report_abs(dev, ABS_Z, le16_to_cpu(pkt->trigger_left)); input_report_abs(dev, ABS_RZ, le16_to_cpu(pkt->trigger_right)); input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GP_BTN_DPAD_R) - !!(buttons & GIP_GP_BTN_DPAD_L)); input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GP_BTN_DPAD_D) - !!(buttons & GIP_GP_BTN_DPAD_U)); input_sync(dev); return 0; } static int gip_gamepad_probe(struct gip_client *client) { struct gip_gamepad *gamepad; int err; gamepad = devm_kzalloc(&client->dev, sizeof(*gamepad), GFP_KERNEL); if (!gamepad) return -ENOMEM; gamepad->client = client; err = gip_set_power_mode(client, GIP_PWR_ON); if (err) return err; gip_gamepad_query_paddles(gamepad); /* * xpad sends this for all Elite 2 firmware versions, * but it seems to be only necessary for 5.11 paddles. */ if(gamepad->paddle_support == PADDLE_ELITE2_511) { err = gip_gamepad_init_extra_data(gamepad); if (err) return err; } err = gip_init_battery(&gamepad->battery, client, GIP_GP_NAME); if (err) return err; err = gip_init_led(&gamepad->led, client); if (err) return err; err = gip_auth_start_handshake(&gamepad->auth, client); if (err) return err; err = gip_init_input(&gamepad->input, client, GIP_GP_NAME); if (err) return err; err = gip_gamepad_init_input(gamepad); if (err) return err; dev_set_drvdata(&client->dev, gamepad); return 0; } static void gip_gamepad_remove(struct gip_client *client) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); #if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) del_timer_sync(&gamepad->rumble.timer); #else timer_delete_sync(&gamepad->rumble.timer); #endif } static struct gip_driver gip_gamepad_driver = { .name = "xone-gip-gamepad", .class = "Windows.Xbox.Input.Gamepad", .ops = { .battery = gip_gamepad_op_battery, .authenticate = gip_gamepad_op_authenticate, .authenticated = gip_gamepad_op_authenticated, .guide_button = gip_gamepad_op_guide_button, .input = gip_gamepad_op_input, .firmware = gip_gamepad_op_firmware, }, .probe = gip_gamepad_probe, .remove = gip_gamepad_remove, }; module_gip_driver(gip_gamepad_driver); MODULE_ALIAS("gip:Windows.Xbox.Input.Gamepad"); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP gamepad driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/driver/headset.c000066400000000000000000000407751515500374600205340ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include #include #include #include "common.h" #include "../auth/auth.h" #define GIP_HS_NAME "Microsoft Xbox Headset" #define GIP_HS_NUM_BUFFERS 128 /* product ID for the chat headset */ #define GIP_HS_PID_CHAT 0x0111 #define GIP_HS_MAX_RETRIES 6 #define GIP_HS_POWER_ON_DELAY msecs_to_jiffies(250) #define GIP_HS_START_DELAY msecs_to_jiffies(500) static struct gip_vidpid GIP_HS_CHECK_AUTH_IDS[] = { { 0x1532, 0x0a16 }, // Razer Thresher { 0x1532, 0x0a25 }, // Razer Kaira Pro { 0x1532, 0x0a27 }, // Razer Kaira Pro { 0x2f12, 0x0023 }, // LucidSound LS35X }; static const struct snd_pcm_hardware gip_headset_pcm_hw = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_CONTINUOUS, .periods_min = 2, .periods_max = GIP_HS_NUM_BUFFERS, }; struct gip_headset { struct gip_client *client; struct gip_battery battery; struct gip_auth auth; bool chat_headset; struct work_struct work_config; struct delayed_work work_power_on; struct work_struct work_register; bool got_authenticated; int start_counter; bool got_initial_volume; bool got_audio_packet; struct hrtimer timer; struct hrtimer start_audio_timer; void *buffer; struct gip_headset_stream { struct snd_pcm_substream *substream; snd_pcm_uframes_t pointer; snd_pcm_uframes_t period; } playback, capture; }; static int gip_headset_pcm_open(struct snd_pcm_substream *sub) { struct gip_headset *headset = snd_pcm_substream_chip(sub); struct gip_audio_config *cfg; struct snd_pcm_hardware hw = gip_headset_pcm_hw; if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) cfg = &headset->client->audio_config_out; else cfg = &headset->client->audio_config_in; hw.rate_min = cfg->sample_rate; hw.rate_max = cfg->sample_rate; hw.channels_min = cfg->channels; hw.channels_max = cfg->channels; hw.buffer_bytes_max = cfg->buffer_size * GIP_HS_NUM_BUFFERS; hw.period_bytes_min = cfg->buffer_size; hw.period_bytes_max = cfg->buffer_size * 2; hw.periods_min = 1; hw.periods_max = 8; sub->runtime->hw = hw; return 0; } static int gip_headset_pcm_close(struct snd_pcm_substream *sub) { return 0; } static int gip_headset_pcm_prepare(struct snd_pcm_substream *sub) { return 0; } static int gip_headset_pcm_trigger(struct snd_pcm_substream *sub, int cmd) { struct gip_headset *headset = snd_pcm_substream_chip(sub); struct gip_headset_stream *stream; if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) stream = &headset->playback; else stream = &headset->capture; stream->pointer = 0; stream->period = 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: stream->substream = sub; break; case SNDRV_PCM_TRIGGER_STOP: stream->substream = NULL; break; default: return -EINVAL; } if (!stream->substream && sub->stream == SNDRV_PCM_STREAM_PLAYBACK) memset(headset->buffer, 0, headset->client->audio_config_out.buffer_size); return 0; } static snd_pcm_uframes_t gip_headset_pcm_pointer(struct snd_pcm_substream *sub) { struct gip_headset *headset = snd_pcm_substream_chip(sub); struct gip_headset_stream *stream; if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) stream = &headset->playback; else stream = &headset->capture; return bytes_to_frames(sub->runtime, stream->pointer); } static const struct snd_pcm_ops gip_headset_pcm_ops = { .open = gip_headset_pcm_open, .close = gip_headset_pcm_close, .prepare = gip_headset_pcm_prepare, .trigger = gip_headset_pcm_trigger, .pointer = gip_headset_pcm_pointer, }; static bool gip_headset_advance_pointer(struct gip_headset_stream *stream, int len, size_t buf_size) { snd_pcm_uframes_t period = stream->substream->runtime->period_size; stream->pointer += len; if (stream->pointer >= buf_size) stream->pointer -= buf_size; stream->period += len; if (stream->period >= period) { stream->period -= period; return true; } return false; } static bool gip_headset_copy_playback(struct gip_headset_stream *stream, unsigned char *data, int len) { unsigned char *src = stream->substream->runtime->dma_area; size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); size_t remaining = buf_size - stream->pointer; if (len <= remaining) { memcpy(data, src + stream->pointer, len); } else { memcpy(data, src + stream->pointer, remaining); memcpy(data + remaining, src, len - remaining); } return gip_headset_advance_pointer(stream, len, buf_size); } static bool gip_headset_copy_capture(struct gip_headset_stream *stream, unsigned char *data, int len) { unsigned char *dest = stream->substream->runtime->dma_area; size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); size_t remaining = buf_size - stream->pointer; if (len <= remaining) { memcpy(dest + stream->pointer, data, len); } else { memcpy(dest + stream->pointer, data, remaining); memcpy(dest, data + remaining, len - remaining); } return gip_headset_advance_pointer(stream, len, buf_size); } static enum hrtimer_restart gip_headset_send_samples(struct hrtimer *timer) { struct gip_headset *headset = container_of(timer, typeof(*headset), timer); struct gip_audio_config *cfg = &headset->client->audio_config_out; struct snd_pcm_substream *sub = headset->playback.substream; bool elapsed = false; int err; unsigned long flags; if (sub) { snd_pcm_stream_lock_irqsave(sub, flags); if (sub->runtime && snd_pcm_running(sub)) elapsed = gip_headset_copy_playback(&headset->playback, headset->buffer, cfg->buffer_size); snd_pcm_stream_unlock_irqrestore(sub, flags); if (elapsed) snd_pcm_period_elapsed(sub); } if (headset->got_authenticated) { /* retry if driver runs out of buffers */ err = gip_send_audio_samples(headset->client, headset->buffer); if (err && err != -ENOSPC) return HRTIMER_NORESTART; } hrtimer_forward_now(timer, ms_to_ktime(GIP_AUDIO_INTERVAL)); return HRTIMER_RESTART; } /* * start pcm devices then launch the work that * sends START every 500ms until an audio packet is received * or audio volume control command is received * or time out of 3 seconds (5 start message + 500ms timeout) */ static enum hrtimer_restart gip_headset_start_audio(struct hrtimer *timer) { struct gip_headset *headset = container_of(timer, typeof(*headset), start_audio_timer); int err; /* * check if the number of retries are elapsed (5) : * start audio anyway */ bool max_retries_reached = (headset->start_counter > GIP_HS_MAX_RETRIES ? true : false); /* check here if audio was started : HRTIMER_NORESTART */ if (headset->got_initial_volume || headset->got_audio_packet || max_retries_reached) { dev_dbg(&headset->client->dev, "%s: start audio try %d/%d, audio = %d, vol = %d.\n", __func__, headset->start_counter, GIP_HS_MAX_RETRIES, headset->got_audio_packet, headset->got_initial_volume); /* start work handling pcm config and audio timer */ schedule_work(&headset->work_register); return HRTIMER_NORESTART; } // otherwise resend START and wait for another GIP_HS_START_DELAY ms headset->start_counter++; dev_dbg(&headset->client->dev, "%s: send device start, try %d/%d.\n", __func__, headset->start_counter, GIP_HS_MAX_RETRIES); err = gip_set_power_mode(headset->client, GIP_PWR_ON); if (err) dev_err(&headset->client->dev, "%s: set power mode failed: %d\n", __func__, err); hrtimer_forward_now(timer, ms_to_ktime(GIP_HS_START_DELAY)); return HRTIMER_RESTART; } static int gip_headset_init_pcm(struct gip_headset *headset) { struct snd_card *card; struct snd_pcm *pcm; int err; err = snd_devm_card_new(&headset->client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &card); if (err) return err; strscpy(card->driver, "xone-gip-headset", sizeof(card->driver)); strscpy(card->shortname, GIP_HS_NAME, sizeof(card->shortname)); snprintf(card->longname, sizeof(card->longname), "%s at %s", GIP_HS_NAME, dev_name(&headset->client->dev)); err = snd_pcm_new(card, GIP_HS_NAME, 0, 1, 1, &pcm); if (err) return err; strscpy(pcm->name, GIP_HS_NAME, sizeof(pcm->name)); pcm->private_data = headset; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &gip_headset_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &gip_headset_pcm_ops); snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); return snd_card_register(card); } static void gip_headset_config(struct work_struct *work) { struct gip_headset *headset = container_of(work, typeof(*headset), work_config); struct gip_client *client = headset->client; struct gip_info_element *fmts = client->audio_formats; int err; dev_dbg(&client->dev, "%s: format=0x%02x/0x%02x\n", __func__, fmts->data[0], fmts->data[1]); /* force headset in idle mode */ err = gip_set_power_mode(client, GIP_PWR_SLEEP); if (err) dev_err(&client->dev, "%s: set headset power mode to IDLE failed: %d\n", __func__, err); /* suggest initial audio format */ dev_dbg(&client->dev, "%s: suggest format.\n", __func__); err = gip_suggest_audio_format(client, fmts->data[0], fmts->data[1], headset->chat_headset); if (err) dev_err(&client->dev, "%s: suggest format failed: %d\n", __func__, err); } static void gip_headset_power_on(struct work_struct *work) { struct gip_headset *headset = container_of( to_delayed_work(work), typeof(*headset), work_power_on); struct gip_client *client = headset->client; const struct device *dev = &client->adapter->dev; int err; dev_dbg(dev, "Headset vendor: 0x%04x\n", client->hardware.vendor); dev_dbg(dev, "Headset product: 0x%04x\n", client->hardware.product); /* Check if headset needs authentication before receiving audio samples */ headset->got_authenticated = true; for (int i = 0; i < ARRAY_SIZE(GIP_HS_CHECK_AUTH_IDS); i++) if (client->hardware.vendor == GIP_HS_CHECK_AUTH_IDS[i].vendor && client->hardware.product == GIP_HS_CHECK_AUTH_IDS[i].product) { headset->got_authenticated = false; dev_dbg(dev, "Headset needs auth before receiving audio"); break; } /* not a standalone headset */ if (client->id) { dev_dbg(dev, "Headset is not a standalone headset\n"); return; } err = gip_init_battery(&headset->battery, client, GIP_HS_NAME); if (err) { dev_err(&client->dev, "%s: init battery failed: %d\n", __func__, err); return; } err = gip_auth_start_handshake(&headset->auth, client); if (err) dev_err(&client->dev, "%s: start handshake failed: %d\n", __func__, err); } static void gip_headset_register(struct work_struct *work) { struct gip_headset *headset = container_of(work, typeof(*headset), work_register); struct gip_client *client = headset->client; int err; headset->buffer = devm_kzalloc(&client->dev, client->audio_config_out.buffer_size, GFP_KERNEL); if (!headset->buffer) return; dev_dbg(&client->dev, "%s: init pcm device.\n", __func__); err = snd_card_free_on_error(&client->dev, gip_headset_init_pcm(headset)); if (err) { dev_err(&client->dev, "%s: init PCM failed: %d\n", __func__, err); return; } /* set hardware volume to maximum for headset jack */ /* standalone & chat headsets have physical volume controls */ if (client->id && !headset->chat_headset) { err = gip_set_audio_volume(client, 100, 50, 100); if (err) { dev_err(&client->dev, "%s: set volume failed: %d\n", __func__, err); return; } } dev_dbg(&client->dev, "%s: init audio out.\n", __func__); err = gip_init_audio_out(client); if (err) { dev_err(&client->dev, "%s: init audio out failed: %d\n", __func__, err); return; } dev_dbg(&client->dev, "%s: init audio in.\n", __func__); err = gip_init_audio_in(client); if (err) { dev_err(&client->dev, "%s: init audio in failed: %d\n", __func__, err); return; } /* start audio timer */ hrtimer_start(&headset->timer, 0, HRTIMER_MODE_REL); } static int gip_headset_op_battery(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level) { struct gip_headset *headset = dev_get_drvdata(&client->dev); gip_report_battery(&headset->battery, type, level); return 0; } static int gip_headset_op_authenticate(struct gip_client *client, void *data, u32 len) { struct gip_headset *headset = dev_get_drvdata(&client->dev); return gip_auth_process_pkt(&headset->auth, data, len); } static int gip_headset_op_authenticated(struct gip_client *client) { struct gip_headset *headset = dev_get_drvdata(&client->dev); headset->got_authenticated = true; return 0; } /* * headset reported supported audio formats so * we can allocate buffer with proper size */ static int gip_headset_op_audio_ready(struct gip_client *client) { struct gip_headset *headset = dev_get_drvdata(&client->dev); dev_dbg(&client->dev, "%s: audio ready : initialize start sequence.\n", __func__); headset->start_counter = 0; hrtimer_start(&headset->start_audio_timer, 0, HRTIMER_MODE_REL); /* start auth handshake after GIP_HS_POWER_ON_DELAY */ schedule_delayed_work(&headset->work_power_on, GIP_HS_POWER_ON_DELAY); return 0; } static int gip_headset_op_audio_volume(struct gip_client *client, u8 in, u8 out) { struct gip_headset *headset = dev_get_drvdata(&client->dev); /* headset reported initial volume, ready to start audio I/O */ headset->got_initial_volume = true; /* ignore hardware volume, let software handle volume changes */ return 0; } static int gip_headset_op_audio_samples(struct gip_client *client, void *data, u32 len) { struct gip_headset *headset = dev_get_drvdata(&client->dev); struct snd_pcm_substream *sub = headset->capture.substream; bool elapsed = false; unsigned long flags; headset->got_audio_packet = true; if (!sub) return 0; snd_pcm_stream_lock_irqsave(sub, flags); if (sub->runtime && snd_pcm_running(sub)) elapsed = gip_headset_copy_capture(&headset->capture, data, len); snd_pcm_stream_unlock_irqrestore(sub, flags); if (elapsed) snd_pcm_period_elapsed(sub); return 0; } static int gip_headset_probe(struct gip_client *client) { struct gip_headset *headset; struct gip_info_element *fmts = client->audio_formats; int err; if (!fmts || !fmts->count) return -ENODEV; headset = devm_kzalloc(&client->dev, sizeof(*headset), GFP_KERNEL); if (!headset) return -ENOMEM; headset->client = client; headset->chat_headset = client->hardware.vendor == GIP_VID_MICROSOFT && client->hardware.product == GIP_HS_PID_CHAT; INIT_WORK(&headset->work_config, gip_headset_config); INIT_DELAYED_WORK(&headset->work_power_on, gip_headset_power_on); INIT_WORK(&headset->work_register, gip_headset_register); #if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) hrtimer_init(&headset->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); headset->timer.function = gip_headset_send_samples; hrtimer_init(&headset->start_audio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); headset->start_audio_timer.function = gip_headset_start_audio; #else hrtimer_setup(&headset->timer, gip_headset_send_samples, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hrtimer_setup(&headset->start_audio_timer, gip_headset_start_audio, CLOCK_MONOTONIC, HRTIMER_MODE_REL); #endif err = gip_enable_audio(client); if (err) return err; dev_set_drvdata(&client->dev, headset); /* start audio configuration */ schedule_work(&headset->work_config); return 0; } static void gip_headset_remove(struct gip_client *client) { struct gip_headset *headset = dev_get_drvdata(&client->dev); cancel_work_sync(&headset->work_config); cancel_delayed_work_sync(&headset->work_power_on); cancel_work_sync(&headset->work_register); hrtimer_cancel(&headset->timer); hrtimer_cancel(&headset->start_audio_timer); gip_disable_audio(client); } static struct gip_driver gip_headset_driver = { .name = "xone-gip-headset", .class = "Windows.Xbox.Input.Headset", .ops = { .battery = gip_headset_op_battery, .authenticate = gip_headset_op_authenticate, .authenticated = gip_headset_op_authenticated, .audio_ready = gip_headset_op_audio_ready, .audio_volume = gip_headset_op_audio_volume, .audio_samples = gip_headset_op_audio_samples, }, .probe = gip_headset_probe, .remove = gip_headset_remove, }; module_gip_driver(gip_headset_driver); MODULE_ALIAS("gip:Windows.Xbox.Input.Headset"); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP headset driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/driver/madcatz_glam.c000066400000000000000000000135321515500374600215310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2024 Severin von Wnuck-Lipinski */ #include #include "common.h" #include "../auth/auth.h" #define GIP_GL_NAME "Mad Catz Rock Band 4 Drum Kit" enum gip_glam_button { GIP_GL_BTN_MENU = BIT(2), GIP_GL_BTN_VIEW = BIT(3), GIP_GL_BTN_A = BIT(4), GIP_GL_BTN_B = BIT(5), /* swapped X and Y buttons */ GIP_GL_BTN_X = BIT(7), GIP_GL_BTN_Y = BIT(6), GIP_GL_BTN_DPAD_U = BIT(8), GIP_GL_BTN_DPAD_D = BIT(9), GIP_GL_BTN_DPAD_L = BIT(10), GIP_GL_BTN_DPAD_R = BIT(11), GIP_GL_BTN_KICK_1 = BIT(12), GIP_GL_BTN_KICK_2 = BIT(13), }; enum gip_glam_pad { GIP_GL_PAD_YELLOW = BIT(0) | BIT(1) | BIT(2), GIP_GL_PAD_RED = BIT(4) | BIT(5) | BIT(6), GIP_GL_PAD_GREEN = BIT(8) | BIT(9) | BIT(10), GIP_GL_PAD_BLUE = BIT(12) | BIT(13) | BIT(14), }; enum gip_glam_cymbal { GIP_GL_CBL_BLUE = BIT(0) | BIT(1) | BIT(2), GIP_GL_CBL_YELLOW = BIT(4) | BIT(5) | BIT(6), GIP_GL_CBL_GREEN = BIT(12) | BIT(13) | BIT(14), }; struct gip_glam_pkt_input { __le16 buttons; __le16 pads; __le16 cymbals; } __packed; struct gip_glam { struct gip_client *client; struct gip_battery battery; struct gip_input input; }; static int gip_glam_init_input(struct gip_glam *glam) { struct input_dev *dev = glam->input.dev; int err; input_set_capability(dev, EV_KEY, BTN_MODE); input_set_capability(dev, EV_KEY, BTN_START); input_set_capability(dev, EV_KEY, BTN_SELECT); input_set_capability(dev, EV_KEY, BTN_A); input_set_capability(dev, EV_KEY, BTN_B); input_set_capability(dev, EV_KEY, BTN_X); input_set_capability(dev, EV_KEY, BTN_Y); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); err = input_register_device(dev); if (err) dev_err(&glam->client->dev, "%s: register failed: %d\n", __func__, err); return err; } static int gip_glam_op_battery(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level) { struct gip_glam *glam = dev_get_drvdata(&client->dev); gip_report_battery(&glam->battery, type, level); return 0; } static int gip_glam_op_guide_button(struct gip_client *client, bool down) { struct gip_glam *glam = dev_get_drvdata(&client->dev); input_report_key(glam->input.dev, BTN_MODE, down); input_sync(glam->input.dev); return 0; } static int gip_glam_op_input(struct gip_client *client, void *data, u32 len) { struct gip_glam *glam = dev_get_drvdata(&client->dev); struct gip_glam_pkt_input *pkt = data; struct input_dev *dev = glam->input.dev; u16 buttons; u16 pads; u16 cymbals; if (len < sizeof(*pkt)) return -EINVAL; buttons = le16_to_cpu(pkt->buttons); pads = le16_to_cpu(pkt->pads); cymbals = le16_to_cpu(pkt->cymbals); input_report_key(dev, BTN_START, buttons & GIP_GL_BTN_MENU); input_report_key(dev, BTN_SELECT, buttons & GIP_GL_BTN_VIEW); input_report_key(dev, BTN_A, buttons & GIP_GL_BTN_A); input_report_key(dev, BTN_B, buttons & GIP_GL_BTN_B); input_report_key(dev, BTN_X, buttons & GIP_GL_BTN_X); input_report_key(dev, BTN_Y, buttons & GIP_GL_BTN_Y); input_report_key(dev, BTN_TRIGGER_HAPPY1, buttons & GIP_GL_BTN_KICK_1); input_report_key(dev, BTN_TRIGGER_HAPPY2, buttons & GIP_GL_BTN_KICK_2); input_report_key(dev, BTN_TRIGGER_HAPPY3, pads & GIP_GL_PAD_RED); input_report_key(dev, BTN_TRIGGER_HAPPY4, pads & GIP_GL_PAD_YELLOW); input_report_key(dev, BTN_TRIGGER_HAPPY5, pads & GIP_GL_PAD_BLUE); input_report_key(dev, BTN_TRIGGER_HAPPY6, pads & GIP_GL_PAD_GREEN); input_report_key(dev, BTN_TRIGGER_HAPPY7, cymbals & GIP_GL_CBL_YELLOW); input_report_key(dev, BTN_TRIGGER_HAPPY8, cymbals & GIP_GL_CBL_BLUE); input_report_key(dev, BTN_TRIGGER_HAPPY9, cymbals & GIP_GL_CBL_GREEN); input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GL_BTN_DPAD_R) - !!(buttons & GIP_GL_BTN_DPAD_L)); input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GL_BTN_DPAD_D) - !!(buttons & GIP_GL_BTN_DPAD_U)); input_sync(dev); return 0; } static int gip_glam_probe(struct gip_client *client) { struct gip_glam *glam; int err; glam = devm_kzalloc(&client->dev, sizeof(*glam), GFP_KERNEL); if (!glam) return -ENOMEM; glam->client = client; err = gip_set_power_mode(client, GIP_PWR_ON); if (err) return err; err = gip_init_battery(&glam->battery, client, GIP_GL_NAME); if (err) return err; /* * The Drum Kit sends auth chunks without specifying the * acknowledgment option while still expecting an acknowledgment. * The Windows driver handles this by sending an acknowledgment * after 100 ms when no further chunks are received. * We skip the handshake instead, as it is not required. */ err = gip_auth_send_complete(client); if (err) return err; err = gip_init_input(&glam->input, client, GIP_GL_NAME); if (err) return err; err = gip_glam_init_input(glam); if (err) return err; dev_set_drvdata(&client->dev, glam); return 0; } static struct gip_driver gip_glam_driver = { .name = "xone-gip-madcatz-glam", .class = "MadCatz.Xbox.Drums.Glam", .ops = { .battery = gip_glam_op_battery, .guide_button = gip_glam_op_guide_button, .input = gip_glam_op_input, }, .probe = gip_glam_probe, }; module_gip_driver(gip_glam_driver); MODULE_ALIAS("gip:MadCatz.Xbox.Drums.Glam"); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP Mad Catz Drum Kit driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/driver/madcatz_strat.c000066400000000000000000000131501515500374600217420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2022 Severin von Wnuck-Lipinski */ #include #include "common.h" #include "../auth/auth.h" #define GIP_ST_NAME "Mad Catz Rock Band 4 Stratocaster" enum gip_strat_button { GIP_ST_BTN_MENU = BIT(2), GIP_ST_BTN_VIEW = BIT(3), GIP_ST_BTN_DPAD_U = BIT(8), GIP_ST_BTN_DPAD_D = BIT(9), GIP_ST_BTN_DPAD_L = BIT(10), GIP_ST_BTN_DPAD_R = BIT(11), }; enum gip_strat_fret { GIP_ST_FRET_GREEN = BIT(0), GIP_ST_FRET_RED = BIT(1), GIP_ST_FRET_YELLOW = BIT(2), GIP_ST_FRET_BLUE = BIT(3), GIP_ST_FRET_ORANGE = BIT(4), }; struct gip_strat_pkt_input { __le16 buttons; u8 tilt; u8 whammy; u8 slider; u8 fret_upper; u8 fret_lower; } __packed; struct gip_strat { struct gip_client *client; struct gip_battery battery; struct gip_input input; }; static int gip_strat_init_input(struct gip_strat *strat) { struct input_dev *dev = strat->input.dev; int err; input_set_capability(dev, EV_KEY, BTN_MODE); input_set_capability(dev, EV_KEY, BTN_START); input_set_capability(dev, EV_KEY, BTN_SELECT); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY10); input_set_abs_params(dev, ABS_X, 0, 64, 0, 0); input_set_abs_params(dev, ABS_Y, 0, 255, 0, 0); input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); err = input_register_device(dev); if (err) dev_err(&strat->client->dev, "%s: register failed: %d\n", __func__, err); return err; } static int gip_strat_op_battery(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level) { struct gip_strat *strat = dev_get_drvdata(&client->dev); gip_report_battery(&strat->battery, type, level); return 0; } static int gip_strat_op_guide_button(struct gip_client *client, bool down) { struct gip_strat *strat = dev_get_drvdata(&client->dev); input_report_key(strat->input.dev, BTN_MODE, down); input_sync(strat->input.dev); return 0; } static int gip_strat_op_input(struct gip_client *client, void *data, u32 len) { struct gip_strat *strat = dev_get_drvdata(&client->dev); struct gip_strat_pkt_input *pkt = data; struct input_dev *dev = strat->input.dev; u16 buttons; if (len < sizeof(*pkt)) return -EINVAL; buttons = le16_to_cpu(pkt->buttons); input_report_key(dev, BTN_START, buttons & GIP_ST_BTN_MENU); input_report_key(dev, BTN_SELECT, buttons & GIP_ST_BTN_VIEW); input_report_key(dev, BTN_TRIGGER_HAPPY1, pkt->fret_upper & GIP_ST_FRET_GREEN); input_report_key(dev, BTN_TRIGGER_HAPPY2, pkt->fret_upper & GIP_ST_FRET_RED); input_report_key(dev, BTN_TRIGGER_HAPPY3, pkt->fret_upper & GIP_ST_FRET_YELLOW); input_report_key(dev, BTN_TRIGGER_HAPPY4, pkt->fret_upper & GIP_ST_FRET_BLUE); input_report_key(dev, BTN_TRIGGER_HAPPY5, pkt->fret_upper & GIP_ST_FRET_ORANGE); input_report_key(dev, BTN_TRIGGER_HAPPY6, pkt->fret_lower & GIP_ST_FRET_GREEN); input_report_key(dev, BTN_TRIGGER_HAPPY7, pkt->fret_lower & GIP_ST_FRET_RED); input_report_key(dev, BTN_TRIGGER_HAPPY8, pkt->fret_lower & GIP_ST_FRET_YELLOW); input_report_key(dev, BTN_TRIGGER_HAPPY9, pkt->fret_lower & GIP_ST_FRET_BLUE); input_report_key(dev, BTN_TRIGGER_HAPPY10, pkt->fret_lower & GIP_ST_FRET_ORANGE); input_report_abs(dev, ABS_X, pkt->slider); input_report_abs(dev, ABS_Y, pkt->whammy); input_report_abs(dev, ABS_Z, pkt->tilt); input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_ST_BTN_DPAD_R) - !!(buttons & GIP_ST_BTN_DPAD_L)); input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_ST_BTN_DPAD_D) - !!(buttons & GIP_ST_BTN_DPAD_U)); input_sync(dev); return 0; } static int gip_strat_probe(struct gip_client *client) { struct gip_strat *strat; int err; strat = devm_kzalloc(&client->dev, sizeof(*strat), GFP_KERNEL); if (!strat) return -ENOMEM; strat->client = client; err = gip_set_power_mode(client, GIP_PWR_ON); if (err) return err; err = gip_init_battery(&strat->battery, client, GIP_ST_NAME); if (err) return err; /* * The Stratocaster sends auth chunks without specifying the * acknowledgment option while still expecting an acknowledgment. * The Windows driver handles this by sending an acknowledgment * after 100 ms when no further chunks are received. * We skip the handshake instead, as it is not required. */ err = gip_auth_send_complete(client); if (err) return err; err = gip_init_input(&strat->input, client, GIP_ST_NAME); if (err) return err; err = gip_strat_init_input(strat); if (err) return err; dev_set_drvdata(&client->dev, strat); return 0; } static struct gip_driver gip_strat_driver = { .name = "xone-gip-madcatz-strat", .class = "MadCatz.Xbox.Guitar.Stratocaster", .ops = { .battery = gip_strat_op_battery, .guide_button = gip_strat_op_guide_button, .input = gip_strat_op_input, }, .probe = gip_strat_probe, }; module_gip_driver(gip_strat_driver); MODULE_ALIAS("gip:MadCatz.Xbox.Guitar.Stratocaster"); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP Mad Catz Stratocaster driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/driver/pdp_jaguar.c000066400000000000000000000132661515500374600212260ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2022 Severin von Wnuck-Lipinski * Copyright (C) 2023 Scott K Logan */ #include #include "common.h" #include "../auth/auth.h" #define GIP_JA_NAME "PDP Rock Band 4 Jaguar" enum gip_jaguar_button { GIP_JA_BTN_MENU = BIT(2), GIP_JA_BTN_VIEW = BIT(3), GIP_JA_BTN_DPAD_U = BIT(8), GIP_JA_BTN_DPAD_D = BIT(9), GIP_JA_BTN_DPAD_L = BIT(10), GIP_JA_BTN_DPAD_R = BIT(11), }; enum gip_jaguar_fret { GIP_JA_FRET_GREEN = BIT(4), GIP_JA_FRET_RED = BIT(5), GIP_JA_FRET_BLUE = BIT(6), GIP_JA_FRET_YELLOW = BIT(7), GIP_JA_FRET_ORANGE = BIT(12), GIP_JA_FRET_LOWER = BIT(14), }; struct gip_jaguar_pkt_input { __le16 buttons; u8 tilt; u8 whammy; } __packed; struct gip_jaguar { struct gip_client *client; struct gip_battery battery; struct gip_auth auth; struct gip_input input; }; static int gip_jaguar_init_input(struct gip_jaguar *guitar) { struct input_dev *dev = guitar->input.dev; int err; input_set_capability(dev, EV_KEY, BTN_MODE); input_set_capability(dev, EV_KEY, BTN_START); input_set_capability(dev, EV_KEY, BTN_SELECT); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY10); input_set_abs_params(dev, ABS_Y, 0, 255, 0, 0); input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); err = input_register_device(dev); if (err) dev_err(&guitar->client->dev, "%s: register failed: %d\n", __func__, err); return err; } static int gip_jaguar_op_battery(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level) { struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); gip_report_battery(&guitar->battery, type, level); return 0; } static int gip_jaguar_op_authenticate(struct gip_client *client, void *data, u32 len) { struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); return gip_auth_process_pkt(&guitar->auth, data, len); } static int gip_jaguar_op_guide_button(struct gip_client *client, bool down) { struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); input_report_key(guitar->input.dev, BTN_MODE, down); input_sync(guitar->input.dev); return 0; } static int gip_jaguar_op_input(struct gip_client *client, void *data, u32 len) { struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); struct gip_jaguar_pkt_input *pkt = data; struct input_dev *dev = guitar->input.dev; u16 buttons; bool lower; if (len < sizeof(*pkt)) return -EINVAL; buttons = le16_to_cpu(pkt->buttons); lower = buttons & GIP_JA_FRET_LOWER; input_report_key(dev, BTN_START, buttons & GIP_JA_BTN_MENU); input_report_key(dev, BTN_SELECT, buttons & GIP_JA_BTN_VIEW); input_report_key(dev, BTN_TRIGGER_HAPPY1, (buttons & GIP_JA_FRET_GREEN) && !lower); input_report_key(dev, BTN_TRIGGER_HAPPY2, (buttons & GIP_JA_FRET_RED) && !lower); input_report_key(dev, BTN_TRIGGER_HAPPY3, (buttons & GIP_JA_FRET_YELLOW) && !lower); input_report_key(dev, BTN_TRIGGER_HAPPY4, (buttons & GIP_JA_FRET_BLUE) && !lower); input_report_key(dev, BTN_TRIGGER_HAPPY5, (buttons & GIP_JA_FRET_ORANGE) && !lower); input_report_key(dev, BTN_TRIGGER_HAPPY6, (buttons & GIP_JA_FRET_GREEN) && lower); input_report_key(dev, BTN_TRIGGER_HAPPY7, (buttons & GIP_JA_FRET_RED) && lower); input_report_key(dev, BTN_TRIGGER_HAPPY8, (buttons & GIP_JA_FRET_YELLOW) && lower); input_report_key(dev, BTN_TRIGGER_HAPPY9, (buttons & GIP_JA_FRET_BLUE) && lower); input_report_key(dev, BTN_TRIGGER_HAPPY10, (buttons & GIP_JA_FRET_ORANGE) && lower); input_report_abs(dev, ABS_Y, pkt->whammy); input_report_abs(dev, ABS_Z, pkt->tilt); input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_JA_BTN_DPAD_R) - !!(buttons & GIP_JA_BTN_DPAD_L)); input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_JA_BTN_DPAD_D) - !!(buttons & GIP_JA_BTN_DPAD_U)); input_sync(dev); return 0; } static int gip_jaguar_probe(struct gip_client *client) { struct gip_jaguar *guitar; int err; guitar = devm_kzalloc(&client->dev, sizeof(*guitar), GFP_KERNEL); if (!guitar) return -ENOMEM; guitar->client = client; err = gip_set_power_mode(client, GIP_PWR_ON); if (err) return err; err = gip_init_battery(&guitar->battery, client, GIP_JA_NAME); if (err) return err; err = gip_auth_start_handshake(&guitar->auth, client); if (err) return err; err = gip_init_input(&guitar->input, client, GIP_JA_NAME); if (err) return err; err = gip_jaguar_init_input(guitar); if (err) return err; dev_set_drvdata(&client->dev, guitar); return 0; } static struct gip_driver gip_jaguar_driver = { .name = "xone-gip-pdp-jaguar", .class = "PDP.Xbox.Guitar.Jaguar", .ops = { .battery = gip_jaguar_op_battery, .authenticate = gip_jaguar_op_authenticate, .guide_button = gip_jaguar_op_guide_button, .input = gip_jaguar_op_input, }, .probe = gip_jaguar_probe, }; module_gip_driver(gip_jaguar_driver); MODULE_ALIAS("gip:PDP.Xbox.Guitar.Jaguar"); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_AUTHOR("Scott K Logan "); MODULE_DESCRIPTION("xone GIP PDP Jaguar driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/install.sh000077500000000000000000000036611515500374600174560ustar00rootroot00000000000000#! /usr/bin/env bash set -eu if [ "$(id -u)" -ne 0 ]; then echo 'This script must be run as root!' >&2 exit 1 fi if ! [ -x "$(command -v dkms)" ]; then echo 'This script requires DKMS!' >&2 exit 1 fi if [ -f /usr/local/bin/xow ]; then echo 'Please uninstall xow!' >&2 exit 1 fi if [ -n "${SUDO_USER:-}" ]; then # Run as normal user to prevent "unsafe repository" error version=$(sudo -u "$SUDO_USER" git describe --tags 2> /dev/null || echo unknown) else version=unknown fi # remove "v" prefix version=${version##v} source="/usr/src/xone-$version" log="/var/lib/dkms/xone/$version/build/make.log" if [ -n "$(dkms status xone)" ]; then echo -e 'Driver is already installed, uninstalling...\n' ./uninstall.sh --no-firmware fi echo "Installing xone $version..." cp -r . "$source" find "$source" -type f \( -name dkms.conf -o -name '*.c' \) -exec sed -i "s/#VERSION#/$version/" {} + # The MAKE line in dkms.conf is required for kernels built using clang. # Add it if the kernel is built using gcc - i.e. "clang" is in the kernel # version string. if [ -n "$(cat /proc/version | grep clang)" ]; then echo 'MAKE[0]="make V=1 LLVM=1 -C ${kernel_source_dir}'\ 'M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build"'\ >> "$source/dkms.conf" fi if [ "${1:-}" == --debug ]; then echo 'ccflags-y += -DDEBUG' >> "$source/Kbuild" fi if dkms install -m xone -v "$version" --force; then # The blacklist should be placed in /usr/local/lib/modprobe.d for kmod 29+ install -D -m 644 install/modprobe.conf /etc/modprobe.d/xone-blacklist.conf # Avoid conflicts between xpad and xone if lsmod | grep -q '^xpad'; then modprobe -r xpad fi # Avoid conflicts between mt76x2u and xone if lsmod | grep -q '^mt76x2u'; then modprobe -r mt76x2u fi else if [ -r "$log" ]; then cat "$log" >&2 fi exit 1 fi echo -e "\nxone installation finished\n" dlundqvist-xone-f2aa9fe/install/000077500000000000000000000000001515500374600171115ustar00rootroot00000000000000dlundqvist-xone-f2aa9fe/install/firmware.sh000077500000000000000000000045631515500374600212740ustar00rootroot00000000000000#! /usr/bin/env bash set -eu if [ "$(id -u)" -ne 0 ]; then echo 'This script must be run as root!' >&2 exit 1 fi if ! [ -x "$(command -v curl)" ]; then echo 'This script requires curl!' >&2 exit 1 fi if ! [ -x "$(command -v bsdtar)" ]; then echo 'This script requires bsdtar!' >&2 exit 1 fi if [ "${1:-}" != --skip-disclaimer ]; then echo "The firmware for the wireless dongle is subject to Microsoft's Terms of Use:" echo 'https://www.microsoft.com/en-us/legal/terms-of-use' echo echo 'Press enter to continue!' read -r _ fi echo -e "Dongle firmware installation\n" urls=( 'https://catalog.s.download.windowsupdate.com/d/msdownload/update/driver/drvs/2017/03/2ea9591b-f751-442c-80ce-8f4692cdc67b_6b555a3a288153cf04aec6e03cba360afe2fce34.cab' 'https://catalog.s.download.windowsupdate.com/c/msdownload/update/driver/drvs/2017/07/1cd6a87c-623f-4407-a52d-c31be49e925c_e19f60808bdcbfbd3c3df6be3e71ffc52e43261e.cab' 'https://catalog.s.download.windowsupdate.com/c/msdownload/update/driver/drvs/2017/06/1dbd7cb4-53bc-4857-a5b0-5955c8acaf71_9081931e7d664429a93ffda0db41b7545b7ac257.cab' 'https://catalog.s.download.windowsupdate.com/d/msdownload/update/driver/drvs/2017/08/aeff215c-3bc4-4d36-a3ea-e14bfa8fa9d2_e58550c4f74a27e51e5cb6868b10ff633fa77164.cab' ) hashes=( '080ce4091e53a4ef3e5fe29939f51fd91f46d6a88be6d67eb6e99a5723b3a223' '48084d9fa53b9bb04358f3bb127b7495dc8f7bb0b3ca1437bd24ef2b6eabdf66' '0023a7bae02974834500c665a281e25b1ba52c9226c84989f9084fa5ce591d9b' 'e2710daf81e7b36d35985348f68a81d18bc537a2b0c508ffdfde6ac3eae1bad7' ) filenames=('FW_ACC_00U.bin' 'FW_ACC_00U.bin' 'FW_ACC_CL.bin' 'FW_ACC_BR.bin') pids=('02e6' '02fe' '02f9' '091e') function download_firmware() { local firmware_name="xone_dongle_${1}.bin" local dest_file="/lib/firmware/$firmware_name" if [[ -f $dest_file ]]; then echo -e "$firmware_name found. Skipping download" return 0 fi echo -n "Downloading $firmware_name..." curl -s -L -o driver.cab "$2" bsdtar -xf driver.cab "$3" > /dev/null 2>&1 echo -n " Checking sha256..." echo "$4" "$3" | sha256sum -c --quiet mv "$3" $dest_file rm driver.cab echo -e " Done!" } for ((i = 0 ; i < "${#urls[@]}" ; i++)); do download_firmware "${pids[$i]}" "${urls[$i]}" "${filenames[$i]}" "${hashes[$i]}" done echo -e "\nDongle firmwares installed!" dlundqvist-xone-f2aa9fe/install/modprobe.conf000066400000000000000000000000411515500374600215620ustar00rootroot00000000000000blacklist xpad blacklist mt76x2u dlundqvist-xone-f2aa9fe/install/steam-deck-install.sh000066400000000000000000000036151515500374600231330ustar00rootroot00000000000000#! /usr/bin/env bash if [[ $1 == "remove" ]]; then sudo pacman -Rcns xone-dkms echo "" echo "" echo "Done!" echo "Just reboot your Deck :)" exit 0 fi ro_status=$(steamos-readonly status) if [[ $ro_status == "enabled" ]]; then echo "Disabling readonly" echo "" steamos-readonly disable fi pacman-key --init pacman-key --populate archlinux pacman-key --populate holo mkdir xone-install cd xone-install || exit 1 AUR_LINK="https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=" ITER=0 while [[ ! -e PKGBUILD_XONE && "$ITER" -lt 5 ]]; do curl "${AUR_LINK}xone-dkms" -o PKGBUILD_XONE ITER=$(( ITER + 1 )) done if [[ $ITER -eq 5 ]]; then echo "Error when downloading PKGBUILD for xone. Exiting..." exit 1 fi ITER=0 while [[ ! -e PKGBUILD_FIRMWARE && "$ITER" -lt 5 ]]; do curl "${AUR_LINK}xone-dongle-firmware" -o PKGBUILD_FIRMWARE ITER=$(( ITER + 1 )) done if [[ $ITER -eq 5 ]]; then echo "Error when downloading PKGBUILD for xone firmware. Exiting..." exit 1 fi # to ABSOLUTELY make sure we have acces when running sudo -u deck chown -R deck:deck . chmod 777 . echo "" echo "Don't worry about \"error: command failed to execute correctly\"" echo "" linux=$(pacman -Qsq linux-neptune | grep -e "[0-9]$" | tail -n 1) pacman -Syu --noconfirm base-devel fakeroot glibc git \ "$linux" "$linux-headers" linux-api-headers # Install build dependencies manually pacman -Syu --noconfirm --asdeps dkms w3m html-xml-utils # build and install seaprately to avoid repeated password prompts sudo -u deck makepkg -Ccf -p PKGBUILD_XONE sudo -u deck makepkg -Ccf -p PKGBUILD_FIRMWARE pacman -U --noconfirm --asdeps xone-dongle-firmware-*.tar.zst pacman -U --noconfirm xone-dkms-*.tar.zst # Remove unneeded build dependencies pacman -Rcns --noconfirm w3m html-xml-utils echo "" echo "Again, don't worry about this ^" cd .. rm -rf xone-install echo "" echo "" echo "Done!" echo "Just reboot your Deck :)" dlundqvist-xone-f2aa9fe/logo.svg000066400000000000000000000165051515500374600171330ustar00rootroot00000000000000 dlundqvist-xone-f2aa9fe/modules_load.sh000077500000000000000000000020351515500374600204510ustar00rootroot00000000000000#! /usr/bin/env bash OPERATION="insmod" SUFFIX=".ko" MESSAGE="Loading" mapfile -t MODULES_TMP < modules.order MODULES=("${MODULES_TMP[@]}") LOADED_MODULES=$(lsmod | cut -d " " -f 1) if [[ $1 == "unload" ]]; then OPERATION="rmmod -f" SUFFIX="" MESSAGE="Unloading" # array reversing for rmmod len=${#MODULES[@]} for ((i = 0 ; i < $len ; i++)); do MODULES[$i]=${MODULES_TMP[(( $len - $i - 1 ))]} done [[ $LOADED_MODULES =~ "xpad" ]] && rmmod -f xpad [[ $LOADED_MODULES =~ "mt76x2u" ]] && rmmod -f mt76x2u fi # make sure ff-memless is loaded as it exports some needed symbols if [[ $1 != "unload" && ! "$LOADED_MODULES" =~ "ff-memless" ]]; then modprobe ff-memless fi for module in "${MODULES[@]}"; do module="${module%.o}$SUFFIX" # remove path and leave only base name [[ $1 == "unload" ]] && module=${module##*/} # skip rmmod if module is not loaded [[ $1 == "unload" && ! "$LOADED_MODULES" =~ "$module" ]] && continue echo "$MESSAGE $module" $OPERATION "$module" done dlundqvist-xone-f2aa9fe/transport/000077500000000000000000000000001515500374600174775ustar00rootroot00000000000000dlundqvist-xone-f2aa9fe/transport/dongle.c000066400000000000000000001201561515500374600211200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include #include #include #include #include #include #include #include #include "mt76.h" #include "../bus/bus.h" ushort fw_override_pid = 0; MODULE_PARM_DESC(fw_override, "Use firmware for the provided product ID instead of the one detected automatically"); module_param_named(fw_override, fw_override_pid, ushort, 0600); #define XONE_DONGLE_NUM_IN_URBS 12 #define XONE_DONGLE_NUM_OUT_URBS 12 #define XONE_DONGLE_LEN_CMD_PKT 0x0654 #define XONE_DONGLE_LEN_WLAN_PKT 0x8400 #define XONE_DONGLE_MAX_CLIENTS 16 #define XONE_DONGLE_PAIRING_TIMEOUT 60 // seconds #define XONE_DONGLE_PAIR_SCAN_INTERVAL msecs_to_jiffies(2000) #define XONE_DONGLE_PWR_OFF_TIMEOUT msecs_to_jiffies(5000) #define XONE_DONGLE_FW_REQ_TIMEOUT_MS 3000 #define XONE_DONGLE_FW_REQ_RETRIES 11 // 30 seconds #define XONE_DONGLE_FW_LOAD_RETRIES 3 enum xone_dongle_queue { XONE_DONGLE_QUEUE_DATA = 0x00, XONE_DONGLE_QUEUE_AUDIO = 0x02, }; enum xone_dongle_fw_state { XONE_DONGLE_FW_STATE_PENDING, XONE_DONGLE_FW_STATE_STOP_LOADING, XONE_DONGLE_FW_STATE_ERROR, XONE_DONGLE_FW_STATE_READY, }; struct xone_dongle_skb_cb { struct xone_dongle *dongle; struct urb *urb; }; struct xone_dongle_client { struct xone_dongle *dongle; u8 wcid; u8 address[ETH_ALEN]; bool encryption_enabled; struct gip_adapter *adapter; }; enum xone_dongle_event_type { XONE_DONGLE_EVT_ADD_CLIENT, XONE_DONGLE_EVT_REMOVE_CLIENT, XONE_DONGLE_EVT_PAIR_CLIENT, XONE_DONGLE_EVT_ENABLE_PAIRING, XONE_DONGLE_EVT_ENABLE_ENCRYPTION, }; struct xone_dongle_event { enum xone_dongle_event_type type; struct xone_dongle *dongle; u8 address[ETH_ALEN]; u8 wcid; struct work_struct work; }; struct xone_dongle { struct xone_mt76 mt; struct usb_anchor urbs_in_idle; struct usb_anchor urbs_in_busy; struct usb_anchor urbs_out_idle; struct usb_anchor urbs_out_busy; /* serializes pairing changes */ struct mutex pairing_lock; struct delayed_work pairing_work; struct delayed_work pairing_scan_work; bool pairing; unsigned long last_wlan_rx; u8 pairing_scan_idx; /* serializes access to clients array */ spinlock_t clients_lock; struct xone_dongle_client *clients[XONE_DONGLE_MAX_CLIENTS]; atomic_t client_count; wait_queue_head_t disconnect_wait; struct workqueue_struct *event_wq; struct work_struct load_fw_work; enum xone_dongle_fw_state fw_state; u16 vendor; u16 product; }; static int xone_dongle_power_off_client(struct xone_dongle *dongle, int index, bool silent); static int xone_dongle_power_off_clients(struct xone_dongle *dongle); static u8 xone_dongle_find_channel_idx(struct xone_dongle *dongle) { if (!dongle->mt.channel) return 0; for (int i = 0; i < XONE_MT_NUM_CHANNELS; i++) { if (dongle->mt.channels[i].index == dongle->mt.channel->index) return i; } return 0; } static void xone_dongle_prep_packet(struct xone_dongle_client *client, struct sk_buff *skb, enum xone_dongle_queue queue) { struct ieee80211_qos_hdr hdr = {}; struct mt76_txwi txwi = {}; u8 data[] = { 0x00, 0x00, queue, client->wcid - 1, 0x00, 0x00, 0x00, 0x00, }; /* frame is sent from AP (DS) */ /* duration is the time required to transmit (in μs) */ hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA | IEEE80211_FCTL_FROMDS); /* encrypt frame on transmission */ if (client->encryption_enabled) hdr.frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); hdr.duration_id = cpu_to_le16(144); memcpy(hdr.addr1, client->address, ETH_ALEN); memcpy(hdr.addr2, client->dongle->mt.address, ETH_ALEN); memcpy(hdr.addr3, client->dongle->mt.address, ETH_ALEN); /* wait for acknowledgment */ txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, IEEE80211_HT_MPDU_DENSITY_4)); txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; txwi.wcid = client->wcid - 1; txwi.len_ctl = cpu_to_le16(sizeof(hdr) + skb->len); memset(skb_push(skb, 2), 0, 2); memcpy(skb_push(skb, sizeof(hdr)), &hdr, sizeof(hdr)); memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi)); memcpy(skb_push(skb, sizeof(data)), data, sizeof(data)); xone_mt76_prep_command(skb, 0); } static int xone_dongle_get_buffer(struct gip_adapter *adap, struct gip_adapter_buffer *buf) { struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); struct xone_dongle_skb_cb *cb; struct urb *urb; struct sk_buff *skb; urb = usb_get_from_anchor(&client->dongle->urbs_out_idle); if (!urb) return -ENOSPC; skb = xone_mt76_alloc_message(XONE_DONGLE_LEN_CMD_PKT, GFP_ATOMIC); if (!skb) return -ENOMEM; /* command header + WCID data + TXWI + QoS header + padding */ /* see xone_dongle_prep_packet and xone_mt76_prep_message */ skb_reserve(skb, MT_CMD_HDR_LEN + 8 + sizeof(struct mt76_txwi) + sizeof(struct ieee80211_qos_hdr) + 2 + MT_CMD_HDR_LEN); cb = (struct xone_dongle_skb_cb *)skb->cb; cb->dongle = client->dongle; cb->urb = urb; buf->context = skb; buf->data = skb->data; buf->length = skb_tailroom(skb); return 0; } static int xone_dongle_submit_buffer(struct gip_adapter *adap, struct gip_adapter_buffer *buf) { struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); struct xone_dongle_skb_cb *cb; struct sk_buff *skb = buf->context; int err; skb_put(skb, buf->length); if (buf->type == GIP_BUF_DATA) xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_DATA); else if (buf->type == GIP_BUF_AUDIO) xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_AUDIO); else return -EINVAL; cb = (struct xone_dongle_skb_cb *)skb->cb; cb->urb->context = skb; cb->urb->transfer_buffer = skb->data; cb->urb->transfer_buffer_length = skb->len; usb_anchor_urb(cb->urb, &client->dongle->urbs_out_busy); err = usb_submit_urb(cb->urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cb->urb); usb_anchor_urb(cb->urb, &client->dongle->urbs_out_idle); dev_kfree_skb_any(skb); } usb_free_urb(cb->urb); return err; } static int xone_dongle_set_encryption_key(struct gip_adapter *adap, u8 *key, int len) { struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); return xone_mt76_set_client_key(&client->dongle->mt, client->wcid, key, len); } static struct gip_adapter_ops xone_dongle_adapter_ops = { .get_buffer = xone_dongle_get_buffer, .submit_buffer = xone_dongle_submit_buffer, .set_encryption_key = xone_dongle_set_encryption_key, }; static int xone_dongle_pairing_handler(struct xone_dongle *dongle, bool enable, u8 timeout_secs) { enum xone_mt76_led_mode led; int err = 0; mutex_lock(&dongle->pairing_lock); /* pairing is already enabled/disabled */ if (dongle->pairing == enable) goto err_unlock; err = xone_mt76_set_pairing(&dongle->mt, enable); if (err) goto err_unlock; if (enable) led = XONE_MT_LED_BLINK; else if (atomic_read(&dongle->client_count)) led = XONE_MT_LED_ON; else led = XONE_MT_LED_OFF; err = xone_mt76_set_led_mode(&dongle->mt, led); if (err) goto err_unlock; dev_dbg(dongle->mt.dev, "%s: enabled=%d\n", __func__, enable); dongle->pairing = enable; if (enable) { dongle->last_wlan_rx = jiffies; dongle->pairing_scan_idx = xone_dongle_find_channel_idx(dongle); mod_delayed_work(system_wq, &dongle->pairing_work, msecs_to_jiffies(timeout_secs * 1000)); mod_delayed_work(system_wq, &dongle->pairing_scan_work, XONE_DONGLE_PAIR_SCAN_INTERVAL); } else { cancel_delayed_work(&dongle->pairing_scan_work); } err_unlock: mutex_unlock(&dongle->pairing_lock); return err; } static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable) { return xone_dongle_pairing_handler(dongle, enable, XONE_DONGLE_PAIRING_TIMEOUT); } static void xone_dongle_pairing_timeout(struct work_struct *work) { struct xone_dongle *dongle = container_of(to_delayed_work(work), typeof(*dongle), pairing_work); int err; if (!dongle) return; err = xone_dongle_toggle_pairing(dongle, false); if (err) dev_err(dongle->mt.dev, "%s: disable pairing failed: %d\n", __func__, err); } static void xone_dongle_pairing_scan(struct work_struct *work) { struct xone_dongle *dongle = container_of(to_delayed_work(work), typeof(*dongle), pairing_scan_work); struct xone_mt76_channel *chan; u8 next_idx; u8 prev_chan = 0; u8 next_chan = 0; int err; mutex_lock(&dongle->pairing_lock); if (!dongle->pairing) goto out_unlock; /* * Once a controller has sent an association request the dongle and * controller are both on the same channel. Switching channels while * a client is connecting or actively communicating breaks the GIP * handshake: the controller keeps transmitting on the old channel * while the dongle is listening on the new one. Keep the channel * stable for as long as any client slot is occupied. */ if (atomic_read(&dongle->client_count) > 0) goto out_resched; if (time_before(jiffies, dongle->last_wlan_rx + XONE_DONGLE_PAIR_SCAN_INTERVAL)) goto out_resched; next_idx = (dongle->pairing_scan_idx + 1) % XONE_MT_NUM_CHANNELS; chan = &dongle->mt.channels[next_idx]; if (dongle->mt.channel) prev_chan = dongle->mt.channel->index; next_chan = chan->index; err = xone_mt76_switch_channel(&dongle->mt, chan); if (err) { dev_dbg(dongle->mt.dev, "%s: switch failed: %d\n", __func__, err); } else { dongle->mt.channel = chan; dev_dbg(dongle->mt.dev, "%s: channel switch %u -> %u\n", __func__, prev_chan, next_chan); } dongle->pairing_scan_idx = next_idx; dongle->last_wlan_rx = jiffies; out_resched: mod_delayed_work(system_wq, &dongle->pairing_scan_work, XONE_DONGLE_PAIR_SCAN_INTERVAL); out_unlock: mutex_unlock(&dongle->pairing_lock); } static ssize_t pairing_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct xone_dongle *dongle = usb_get_intfdata(intf); return sysfs_emit(buf, "%d\n", dongle->pairing); } static ssize_t pairing_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct usb_interface *intf = to_usb_interface(dev); struct xone_dongle *dongle = usb_get_intfdata(intf); bool enable; int err; err = kstrtobool(buf, &enable); if (err) return err; err = xone_dongle_toggle_pairing(dongle, enable); if (err) return err; return count; } static ssize_t active_clients_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct xone_dongle *dongle = usb_get_intfdata(intf); int half = XONE_DONGLE_MAX_CLIENTS / 2; char local_buf[150] = {}; char temp_buf[10] = {}; sprintf(local_buf, "Active clients: %u\n", atomic_read(&dongle->client_count)); for (int i = 0; i < half; ++i) { bool active1 = dongle->clients[i] != NULL; bool active2 = dongle->clients[i + half] != NULL; sprintf(temp_buf, "[%.2d]%s\t", i, active1 ? "*" : ""); strcat(local_buf, temp_buf); sprintf(temp_buf, "[%.2d]%s\n", i + half, active2 ? "*" : ""); strcat(local_buf, temp_buf); } return sysfs_emit(buf, local_buf); } static ssize_t poweroff_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%s\n%s\n%s\n", "To power off clients please write:", "0-15 -> client with given index", "-1 -> all clients"); } static ssize_t poweroff_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct usb_interface *intf = to_usb_interface(dev); struct xone_dongle *dongle = usb_get_intfdata(intf); int err, val; if (count > 3) return -E2BIG; if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) return -ENODEV; err = kstrtoint(buf, 10, &val); if (err) return err; if (val == -1) err = xone_dongle_power_off_clients(dongle); else err = xone_dongle_power_off_client(dongle, val, false); return err ? err : count; } DEVICE_ATTR_RW(pairing); DEVICE_ATTR_RO(active_clients); DEVICE_ATTR_RW(poweroff); static struct attribute *xone_dongle_attrs[] = { &dev_attr_pairing.attr, &dev_attr_active_clients.attr, &dev_attr_poweroff.attr, NULL, }; ATTRIBUTE_GROUPS(xone_dongle); static struct xone_dongle_client * xone_dongle_create_client(struct xone_dongle *dongle, u8 *addr) { struct xone_dongle_client *client; int i, err; /* find free WCID */ for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) if (!dongle->clients[i]) break; if (i == XONE_DONGLE_MAX_CLIENTS) return ERR_PTR(-ENOSPC); client = kzalloc(sizeof(*client), GFP_KERNEL); if (!client) return ERR_PTR(-ENOMEM); client->dongle = dongle; client->wcid = i + 1; memcpy(client->address, addr, ETH_ALEN); client->adapter = gip_create_adapter(dongle->mt.dev, &xone_dongle_adapter_ops, 1); if (IS_ERR(client->adapter)) { err = PTR_ERR(client->adapter); kfree(client); return ERR_PTR(err); } dev_set_drvdata(&client->adapter->dev, client); return client; } static int xone_dongle_add_client(struct xone_dongle *dongle, u8 *addr) { struct xone_dongle_client *client; int i, err; unsigned long flags; /* reject duplicate: controller already has a WCID slot */ for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) if (dongle->clients[i] && ether_addr_equal(dongle->clients[i]->address, addr)) return 0; client = xone_dongle_create_client(dongle, addr); if (IS_ERR(client)) return PTR_ERR(client); err = xone_mt76_associate_client(&dongle->mt, client->wcid, addr); if (err) goto err_free_client; if (!dongle->pairing) { err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_ON); if (err) goto err_free_client; } dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", __func__, client->wcid, addr); spin_lock_irqsave(&dongle->clients_lock, flags); dongle->clients[client->wcid - 1] = client; spin_unlock_irqrestore(&dongle->clients_lock, flags); atomic_inc(&dongle->client_count); return 0; err_free_client: gip_destroy_adapter(client->adapter); kfree(client); return err; } static int xone_dongle_remove_client(struct xone_dongle *dongle, u8 wcid) { struct xone_dongle_client *client; int err; unsigned long flags; client = dongle->clients[wcid - 1]; if (!client) return 0; dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", __func__, wcid, client->address); spin_lock_irqsave(&dongle->clients_lock, flags); dongle->clients[wcid - 1] = NULL; spin_unlock_irqrestore(&dongle->clients_lock, flags); gip_destroy_adapter(client->adapter); kfree(client); err = xone_mt76_remove_client(&dongle->mt, wcid); if (err) dev_err(dongle->mt.dev, "%s: remove failed: %d\n", __func__, err); /* turn off LED if all clients have disconnected */ if (atomic_dec_and_test(&dongle->client_count) && !dongle->pairing) err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_OFF); wake_up(&dongle->disconnect_wait); return err; } static int xone_dongle_pair_client(struct xone_dongle *dongle, u8 *addr) { int err; dev_dbg(dongle->mt.dev, "%s: address=%pM\n", __func__, addr); err = xone_mt76_pair_client(&dongle->mt, addr); if (err) return err; return xone_dongle_toggle_pairing(dongle, false); } static int xone_dongle_enable_client_encryption(struct xone_dongle *dongle, u8 wcid) { struct xone_dongle_client *client; u8 data[] = { 0x00, 0x00 }; int err; client = dongle->clients[wcid - 1]; if (!client) return -EINVAL; dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", __func__, wcid, client->address); err = xone_mt76_send_client_command(&dongle->mt, wcid, client->address, XONE_MT_CLIENT_ENABLE_ENCRYPTION, data, sizeof(data)); if (err) return err; client->encryption_enabled = true; return 0; } static void xone_dongle_handle_event(struct work_struct *work) { struct xone_dongle_event *evt = container_of(work, typeof(*evt), work); int err = 0; /* Do not process events when firmware is not ready */ if (evt->dongle->fw_state < XONE_DONGLE_FW_STATE_READY) { pr_debug("%s: firmware not loaded yet", __func__); goto handle_event_free; } switch (evt->type) { case XONE_DONGLE_EVT_ADD_CLIENT: pr_debug("%s: XONE_DONGLE_EVT_ADD_CLIENT", __func__); err = xone_dongle_add_client(evt->dongle, evt->address); break; case XONE_DONGLE_EVT_REMOVE_CLIENT: pr_debug("%s: XONE_DONGLE_EVT_REMOVE_CLIENT", __func__); err = xone_dongle_remove_client(evt->dongle, evt->wcid); break; case XONE_DONGLE_EVT_PAIR_CLIENT: pr_debug("%s: XONE_DONGLE_EVT_PAIR_CLIENT", __func__); err = xone_dongle_pair_client(evt->dongle, evt->address); break; case XONE_DONGLE_EVT_ENABLE_PAIRING: pr_debug("%s: XONE_DONGLE_EVT_ENABLE_PAIRING", __func__); err = xone_dongle_toggle_pairing(evt->dongle, true); break; case XONE_DONGLE_EVT_ENABLE_ENCRYPTION: pr_debug("%s: XONE_DONGLE_EVT_ENABLE_ENCRYPTION", __func__); err = xone_dongle_enable_client_encryption(evt->dongle, evt->wcid); break; } if (err) dev_err(evt->dongle->mt.dev, "%s: handle event failed: %d\n", __func__, err); handle_event_free: kfree(evt); } static struct xone_dongle_event * xone_dongle_alloc_event(struct xone_dongle *dongle, enum xone_dongle_event_type type) { struct xone_dongle_event *evt; evt = kzalloc(sizeof(*evt), GFP_ATOMIC); if (!evt) return NULL; evt->type = type; evt->dongle = dongle; INIT_WORK(&evt->work, xone_dongle_handle_event); return evt; } static int xone_dongle_handle_qos_data(struct xone_dongle *dongle, struct sk_buff *skb, u8 wcid) { struct xone_dongle_client *client; int err = 0; unsigned long flags; if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) return 0; spin_lock_irqsave(&dongle->clients_lock, flags); client = dongle->clients[wcid - 1]; if (client) { /* * Active data traffic is the strongest signal that we are on * the right channel. Refresh last_wlan_rx so the pairing scan * does not rotate away while a controller is mid-handshake, * complementing the client_count guard in pairing_scan. */ if (dongle->pairing) dongle->last_wlan_rx = jiffies; err = gip_process_buffer(client->adapter, skb->data, skb->len); } spin_unlock_irqrestore(&dongle->clients_lock, flags); return err; } static int xone_dongle_handle_association(struct xone_dongle *dongle, u8 *addr) { struct xone_dongle_event *evt; if (dongle->pairing) dongle->last_wlan_rx = jiffies; evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ADD_CLIENT); if (!evt) return -ENOMEM; memcpy(evt->address, addr, ETH_ALEN); queue_work(dongle->event_wq, &evt->work); return 0; } static int xone_dongle_handle_disassociation(struct xone_dongle *dongle, u8 wcid) { struct xone_dongle_event *evt; if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) return 0; evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_REMOVE_CLIENT); if (!evt) return -ENOMEM; evt->wcid = wcid; queue_work(dongle->event_wq, &evt->work); return 0; } static int xone_dongle_handle_client_command(struct xone_dongle *dongle, struct sk_buff *skb, u8 wcid, u8 *addr) { struct xone_dongle_event *evt; enum xone_dongle_event_type evt_type; if (skb->len < 2 || skb->data[0] != XONE_MT_WLAN_RESERVED) return -EINVAL; switch (skb->data[1]) { case XONE_MT_CLIENT_PAIR_REQ: if (dongle->pairing) dongle->last_wlan_rx = jiffies; evt_type = XONE_DONGLE_EVT_PAIR_CLIENT; break; case XONE_MT_CLIENT_ENABLE_ENCRYPTION: if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) return -EINVAL; evt_type = XONE_DONGLE_EVT_ENABLE_ENCRYPTION; break; default: return 0; } evt = xone_dongle_alloc_event(dongle, evt_type); if (!evt) return -ENOMEM; evt->wcid = wcid; memcpy(evt->address, addr, ETH_ALEN); queue_work(dongle->event_wq, &evt->work); return 0; } static int xone_dongle_handle_button(struct xone_dongle *dongle) { struct xone_dongle_event *evt; /* * Refresh last_wlan_rx immediately on a physical button press so the * pairing scan does not rotate to a different channel in the narrow * window between this event being queued and toggle_pairing() being * called by the event handler. */ if (dongle->pairing) dongle->last_wlan_rx = jiffies; evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ENABLE_PAIRING); if (!evt) return -ENOMEM; queue_work(dongle->event_wq, &evt->work); return 0; } static int xone_dongle_handle_loss(struct xone_dongle *dongle, struct sk_buff *skb) { u8 wcid; if (skb->len < sizeof(wcid)) return -EINVAL; wcid = skb->data[0]; if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) return 0; dev_dbg(dongle->mt.dev, "%s: wcid=%d\n", __func__, wcid); return xone_dongle_handle_disassociation(dongle, wcid); } static int xone_dongle_process_frame(struct xone_dongle *dongle, struct sk_buff *skb, unsigned int hdr_len, u8 wcid) { struct ieee80211_hdr_3addr *hdr = (struct ieee80211_hdr_3addr *)skb->data; u16 type; /* ignore invalid frames */ if (skb->len < hdr_len || hdr_len < sizeof(*hdr)) return 0; skb_pull(skb, hdr_len); type = le16_to_cpu(hdr->frame_control); switch (type & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) { case IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA: return xone_dongle_handle_qos_data(dongle, skb, wcid); case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ASSOC_REQ: /* * The channel scan can trigger spurious association frames * carrying multicast or all-zero source addresses (addr2). * A real controller always uses a valid unicast address. * Accepting invalid addresses exhausts the client table. */ if (!is_valid_ether_addr(hdr->addr2)) return 0; return xone_dongle_handle_association(dongle, hdr->addr2); case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_DISASSOC: return xone_dongle_handle_disassociation(dongle, wcid); case IEEE80211_FTYPE_MGMT | XONE_MT_WLAN_RESERVED: return xone_dongle_handle_client_command(dongle, skb, wcid, hdr->addr2); } return 0; } static int xone_dongle_process_wlan(struct xone_dongle *dongle, struct sk_buff *skb) { struct mt76_rxwi *rxwi = (struct mt76_rxwi *)skb->data; unsigned int hdr_len; u32 ctl; if (skb->len < sizeof(*rxwi)) return -EINVAL; skb_pull(skb, sizeof(*rxwi)); hdr_len = ieee80211_get_hdrlen_from_skb(skb); /* 2 bytes of padding after 802.11 header */ if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_L2PAD)) { if (skb->len < hdr_len + 2) return -EINVAL; memmove(skb->data + 2, skb->data, hdr_len); skb_pull(skb, 2); } ctl = le32_to_cpu(rxwi->ctl); /* drop frames where RXWI claims more data than the USB transfer * actually delivered — commonly the chip's own beacon loopback */ if (FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl) > skb->len) return 0; skb_trim(skb, FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl)); return xone_dongle_process_frame(dongle, skb, hdr_len, FIELD_GET(MT_RXWI_CTL_WCID, ctl)); } static int xone_dongle_process_message(struct xone_dongle *dongle, struct sk_buff *skb) { enum mt76_dma_msg_port port; u32 info; /* command header + trailer */ if (skb->len < MT_CMD_HDR_LEN * 2) return -EINVAL; info = get_unaligned_le32(skb->data); port = FIELD_GET(MT_RX_FCE_INFO_D_PORT, info); /* ignore command reponses */ if (FIELD_GET(MT_RX_FCE_INFO_CMD_SEQ, info) == 0x01) return 0; /* remove header + trailer */ skb_pull(skb, MT_CMD_HDR_LEN); skb_trim(skb, skb->len - MT_CMD_HDR_LEN); if (port == MT_WLAN_PORT) return xone_dongle_process_wlan(dongle, skb); if (port != MT_CPU_RX_PORT) return 0; switch (FIELD_GET(MT_RX_FCE_INFO_EVT_TYPE, info)) { case XONE_MT_EVT_BUTTON: return xone_dongle_handle_button(dongle); case XONE_MT_EVT_PACKET_RX: return xone_dongle_process_wlan(dongle, skb); case XONE_MT_EVT_CLIENT_LOST: return xone_dongle_handle_loss(dongle, skb); } return 0; } static int xone_dongle_process_buffer(struct xone_dongle *dongle, void *data, int len) { struct sk_buff *skb; int err; if (!len) return 0; skb = dev_alloc_skb(len); if (!skb) return -ENOMEM; skb_put_data(skb, data, len); err = xone_dongle_process_message(dongle, skb); if (err) { dev_err(dongle->mt.dev, "%s: process failed: %d\n", __func__, err); print_hex_dump_bytes("xone-dongle packet: ", DUMP_PREFIX_NONE, data, len); } dev_kfree_skb(skb); return err; } static void xone_dongle_complete_in(struct urb *urb) { struct xone_dongle *dongle = urb->context; int err; switch (urb->status) { case 0: break; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: usb_anchor_urb(urb, &dongle->urbs_in_idle); return; default: goto resubmit; } err = xone_dongle_process_buffer(dongle, urb->transfer_buffer, urb->actual_length); if (err) dev_err(dongle->mt.dev, "%s: process failed: %d\n", __func__, err); resubmit: /* can fail during USB device removal */ err = usb_submit_urb(urb, GFP_ATOMIC); if (err) { dev_dbg(dongle->mt.dev, "%s: submit failed: %d\n", __func__, err); usb_anchor_urb(urb, &dongle->urbs_in_idle); } else { usb_anchor_urb(urb, &dongle->urbs_in_busy); } } static void xone_dongle_complete_out(struct urb *urb) { struct sk_buff *skb = urb->context; struct xone_dongle_skb_cb *cb = (struct xone_dongle_skb_cb *)skb->cb; usb_anchor_urb(urb, &cb->dongle->urbs_out_idle); dev_consume_skb_any(skb); } static int xone_dongle_init_urbs_in(struct xone_dongle *dongle, int ep, int buf_len) { struct xone_mt76 *mt = &dongle->mt; struct urb *urb; void *buf; int i, err; for (i = 0; i < XONE_DONGLE_NUM_IN_URBS; i++) { urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; usb_anchor_urb(urb, &dongle->urbs_in_busy); usb_free_urb(urb); buf = usb_alloc_coherent(mt->udev, buf_len, GFP_KERNEL, &urb->transfer_dma); if (!buf) return -ENOMEM; usb_fill_bulk_urb(urb, mt->udev, usb_rcvbulkpipe(mt->udev, ep), buf, buf_len, xone_dongle_complete_in, dongle); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; err = usb_submit_urb(urb, GFP_KERNEL); if (err) return err; } return 0; } static int xone_dongle_init_urbs_out(struct xone_dongle *dongle) { struct xone_mt76 *mt = &dongle->mt; struct urb *urb; int i; for (i = 0; i < XONE_DONGLE_NUM_OUT_URBS; i++) { urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; usb_fill_bulk_urb(urb, mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), NULL, 0, xone_dongle_complete_out, NULL); usb_anchor_urb(urb, &dongle->urbs_out_idle); usb_free_urb(urb); } return 0; } static int xone_dongle_fw_requester(const struct firmware **fw, struct xone_dongle *dongle, const char *fwname) { struct device *dev = dongle->mt.dev; int err; dev_dbg(dev, "%s: trying to load firmware %s\n", __func__, fwname); for (int i = 0; i < XONE_DONGLE_FW_REQ_RETRIES; ++i) { if (dongle->fw_state == XONE_DONGLE_FW_STATE_STOP_LOADING) { pr_debug("%s: Stopping firmware load on demand", __func__); return 1; } dev_dbg(dev, "%s: attempt: %d\n", __func__, i + 1); err = request_firmware(fw, fwname, dev); if (!err) return 0; msleep(XONE_DONGLE_FW_REQ_TIMEOUT_MS); } return err; } static void xone_dongle_fw_load(struct work_struct *work) { struct xone_dongle *dongle = container_of(work, struct xone_dongle, load_fw_work); struct xone_mt76 *mt = &dongle->mt; const struct firmware *fw; char fwname[21]; int err; u16 fw_product = dongle->product; if (fw_override_pid) { dev_info(mt->dev, "Firmware overriden with PID=0x%04x", fw_override_pid); fw_product = fw_override_pid; } sprintf(fwname, "xone_dongle_%04x.bin", fw_product); err = xone_dongle_fw_requester(&fw, dongle, fwname); if (dongle->fw_state == XONE_DONGLE_FW_STATE_STOP_LOADING) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; return; } if (err) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; dev_err(mt->dev, "%s: request firmware failed: %d\n", __func__, err); return; } dev_dbg(mt->dev, "%s: firmware requested successfully\n", __func__); for (int i = 0; i < 5; ++i) { err = xone_mt76_load_firmware(mt, fw); if (!err) break; ssleep(1); } release_firmware(fw); if (err) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; dev_err(mt->dev, "%s: load firmware failed: %d\n", __func__, err); return; } err = xone_dongle_init_urbs_out(dongle); if (err) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; return; } err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_CMD, XONE_DONGLE_LEN_CMD_PKT); if (err) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; return; } err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_WLAN, XONE_DONGLE_LEN_WLAN_PKT); if (err) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; return; } for (int i = 0; i < 3; i++) { err = xone_mt76_init_radio(mt); if (err != -ETIMEDOUT) break; dev_dbg(mt->dev, "%s: init radio timed out, retrying (%d/3)\n", __func__, i + 1); msleep(500); } if (err) { dongle->fw_state = XONE_DONGLE_FW_STATE_ERROR; dev_err(mt->dev, "%s: init radio failed: %d\n", __func__, err); return; } dongle->fw_state = XONE_DONGLE_FW_STATE_READY; device_wakeup_enable(&dongle->mt.udev->dev); /* * xone_mt76_init_radio() ends with xone_mt76_set_pairing(false), * which sets the beacon pair flag to 0 and a restrictive RX filter. * In this state already-paired controllers cannot reconnect: they see * the beacon but are rejected by the filter. * * Enable pairing so controllers present at boot or after a replug * reconnect automatically without requiring a manual button press. * The channel scan cycles through all 12 channels at 2 s each * (24 s per full cycle), so the timeout must be long enough for * at least two full cycles. Use the default 60 s timeout. */ err = xone_dongle_toggle_pairing(dongle, true); if (err) dev_err(mt->dev, "%s: enable pairing failed: %d\n", __func__, err); } static int xone_dongle_init(struct xone_dongle *dongle) { init_usb_anchor(&dongle->urbs_out_idle); init_usb_anchor(&dongle->urbs_out_busy); init_usb_anchor(&dongle->urbs_in_idle); init_usb_anchor(&dongle->urbs_in_busy); dongle->fw_state = XONE_DONGLE_FW_STATE_PENDING; schedule_work(&dongle->load_fw_work); return 0; } static int xone_dongle_power_off_client(struct xone_dongle *dongle, int index, bool silent) { unsigned long flags = 0; int err = 0; if (index < 0 || index >= XONE_DONGLE_MAX_CLIENTS) return -EINVAL; spin_lock_irqsave(&dongle->clients_lock, flags); if (dongle->clients[index]) err = gip_power_off_adapter(dongle->clients[index]->adapter); else if (!silent) err = -ENODEV; spin_unlock_irqrestore(&dongle->clients_lock, flags); return err; } static int xone_dongle_power_off_clients(struct xone_dongle *dongle) { if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) return 0; for (int i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) xone_dongle_power_off_client(dongle, i, true); /* can time out if new client connects */ if (!wait_event_timeout(dongle->disconnect_wait, !atomic_read(&dongle->client_count), XONE_DONGLE_PWR_OFF_TIMEOUT)) return -ETIMEDOUT; return xone_dongle_toggle_pairing(dongle, false); } static void xone_dongle_destroy(struct xone_dongle *dongle) { struct xone_dongle_client *client; struct urb *urb; int i; if (dongle->fw_state < XONE_DONGLE_FW_STATE_ERROR) { pr_debug("%s: Firmware not loaded, stopping work", __func__); dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; } usb_kill_anchored_urbs(&dongle->urbs_in_busy); /* cancel fw load before destroying workqueues to avoid use-after-free */ cancel_work_sync(&dongle->load_fw_work); destroy_workqueue(dongle->event_wq); cancel_delayed_work_sync(&dongle->pairing_work); cancel_delayed_work_sync(&dongle->pairing_scan_work); for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { client = dongle->clients[i]; if (!client) continue; gip_destroy_adapter(client->adapter); kfree(client); dongle->clients[i] = NULL; } usb_kill_anchored_urbs(&dongle->urbs_out_busy); while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle))) usb_free_urb(urb); while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { usb_free_coherent(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); usb_free_urb(urb); } mutex_destroy(&dongle->pairing_lock); } static int xone_dongle_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct xone_dongle *dongle; int err; dongle = devm_kzalloc(&intf->dev, sizeof(*dongle), GFP_KERNEL); if (!dongle) return -ENOMEM; dongle->mt.dev = &intf->dev; dongle->mt.udev = interface_to_usbdev(intf); dongle->vendor = id->idVendor; dongle->product = id->idProduct; dongle->event_wq = alloc_ordered_workqueue("xone_dongle", 0); if (!dongle->event_wq) return -ENOMEM; mutex_init(&dongle->pairing_lock); INIT_DELAYED_WORK(&dongle->pairing_work, xone_dongle_pairing_timeout); INIT_DELAYED_WORK(&dongle->pairing_scan_work, xone_dongle_pairing_scan); INIT_WORK(&dongle->load_fw_work, xone_dongle_fw_load); spin_lock_init(&dongle->clients_lock); init_waitqueue_head(&dongle->disconnect_wait); /* * Do not call usb_reset_device() here. On cold boot the MT76 chip * disconnects from USB as a normal part of its firmware startup * sequence (inside xone_mt76_load_ivb). A preceding USB reset leaves * the XHCI port in a state where it cannot cleanly handle that * subsequent disconnect/reconnect cycle, causing the chip to * permanently disappear from the USB bus until a physical replug. * * On warm reboot the firmware survives in RAM, so the chip does not * disconnect at all and the faster xone_mt76_reset_firmware() path * is taken instead — a reset is equally unnecessary there. */ err = xone_dongle_init(dongle); if (err) { xone_dongle_destroy(dongle); return err; } usb_set_intfdata(intf, dongle); err = device_add_groups(&intf->dev, xone_dongle_groups); if (err) { xone_dongle_destroy(dongle); return err; } /* enable USB remote wakeup and autosuspend */ intf->needs_remote_wakeup = true; return 0; } static void xone_dongle_disconnect(struct usb_interface *intf) { struct xone_dongle *dongle = usb_get_intfdata(intf); int err; device_remove_groups(&intf->dev, xone_dongle_groups); /* can fail during USB device removal */ err = xone_dongle_power_off_clients(dongle); if (err) dev_dbg(dongle->mt.dev, "%s: power off failed: %d\n", __func__, err); xone_dongle_destroy(dongle); usb_set_intfdata(intf, NULL); } static int xone_dongle_suspend(struct usb_interface *intf, pm_message_t message) { struct xone_dongle *dongle = usb_get_intfdata(intf); int err; if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY){ pr_debug("%s: Skipping radio suspend", __func__); return 0; } err = xone_dongle_power_off_clients(dongle); if (err) dev_err(dongle->mt.dev, "%s: power off failed: %d\n", __func__, err); usb_kill_anchored_urbs(&dongle->urbs_in_busy); usb_kill_anchored_urbs(&dongle->urbs_out_busy); cancel_delayed_work_sync(&dongle->pairing_work); cancel_delayed_work_sync(&dongle->pairing_scan_work); return xone_mt76_suspend_radio(&dongle->mt); } static int xone_dongle_resume(struct usb_interface *intf) { struct xone_dongle *dongle = usb_get_intfdata(intf); struct urb *urb; int err; if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) { pr_debug("%s: Skipping radio resume", __func__); return 0; } msleep(1500); while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { usb_anchor_urb(urb, &dongle->urbs_in_busy); usb_free_urb(urb); err = usb_submit_urb(urb, GFP_KERNEL); if (err) return err; } msleep(1000); return xone_mt76_resume_radio(&dongle->mt); } #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) static void xone_dongle_shutdown(struct device *dev) { struct usb_interface *intf = to_usb_interface(dev); #else static void xone_dongle_shutdown(struct usb_interface *intf) { #endif struct xone_dongle *dongle = usb_get_intfdata(intf); int err; if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; if (system_state == SYSTEM_RESTART) return; err = xone_dongle_power_off_clients(dongle); if (err) dev_err(dongle->mt.dev, "%s: power off failed: %d\n", __func__, err); } static int xone_dongle_pre_reset(struct usb_interface *intf) { struct xone_dongle *dongle = usb_get_intfdata(intf); struct urb *urb; pr_debug("%s", __func__); /* For reset during probe */ if (!dongle) return 0; if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; cancel_delayed_work_sync(&dongle->pairing_work); cancel_delayed_work_sync(&dongle->pairing_scan_work); usb_kill_anchored_urbs(&dongle->urbs_in_busy); usb_kill_anchored_urbs(&dongle->urbs_out_busy); while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle))) usb_free_urb(urb); while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { usb_free_coherent(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); usb_free_urb(urb); } return 0; } static int xone_dongle_post_reset(struct usb_interface *intf) { struct xone_dongle *dongle = usb_get_intfdata(intf); pr_debug("%s", __func__); /* For reset during probe */ if (!dongle) return 0; pr_debug("%s: Re-initializing dongle after reset", __func__); return xone_dongle_init(dongle); } static int xone_dongle_reset_resume(struct usb_interface *intf) { struct xone_dongle *dongle = usb_get_intfdata(intf); struct xone_dongle_client *client; struct urb *urb; int i; pr_debug("%s", __func__); /* * The kernel already reset the USB device before calling * reset_resume — a second usb_reset_device() is redundant and * can leave the XHCI port in a bad state (the same class of bug * as the former usb_reset_device() call in probe). * * Instead, clean up all stale state and reinitialize from scratch. * This also ensures old GIP adapters are destroyed so the * reconnecting controller gets a fresh input device. */ if (dongle->fw_state < XONE_DONGLE_FW_STATE_ERROR) dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; usb_kill_anchored_urbs(&dongle->urbs_in_busy); cancel_work_sync(&dongle->load_fw_work); /* * If load_fw_work raced past the STOP_LOADING check and created * new URBs before cancel_work_sync returned, kill them now. */ usb_kill_anchored_urbs(&dongle->urbs_in_busy); drain_workqueue(dongle->event_wq); cancel_delayed_work_sync(&dongle->pairing_work); cancel_delayed_work_sync(&dongle->pairing_scan_work); for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { client = dongle->clients[i]; if (!client) continue; gip_destroy_adapter(client->adapter); kfree(client); dongle->clients[i] = NULL; } atomic_set(&dongle->client_count, 0); usb_kill_anchored_urbs(&dongle->urbs_out_busy); while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle))) usb_free_urb(urb); while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) { usb_free_coherent(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); usb_free_urb(urb); } dongle->pairing = false; dongle->pairing_scan_idx = 0; dongle->last_wlan_rx = 0; return xone_dongle_init(dongle); } static const struct usb_device_id xone_dongle_id_table[] = { { USB_DEVICE(0x045e, 0x02e6) }, /* old dongle */ { USB_DEVICE(0x045e, 0x02fe) }, /* new dongle */ { USB_DEVICE(0x045e, 0x02f9) }, /* built-in dongle (ASUS, Lenovo) */ { USB_DEVICE(0x045e, 0x091e) }, /* built-in dongle (Surface Book 2) */ { }, }; static struct usb_driver xone_dongle_driver = { .name = "xone-dongle", .probe = xone_dongle_probe, .disconnect = xone_dongle_disconnect, .id_table = xone_dongle_id_table, #ifdef CONFIG_PM .suspend = xone_dongle_suspend, .resume = xone_dongle_resume, .reset_resume = xone_dongle_reset_resume, #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) .drvwrap.driver.shutdown = xone_dongle_shutdown, #elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) .driver.shutdown = xone_dongle_shutdown, #else .shutdown = xone_dongle_shutdown, #endif .pre_reset = xone_dongle_pre_reset, .post_reset = xone_dongle_post_reset, .supports_autosuspend = false, .disable_hub_initiated_lpm = true, .soft_unbind = true, }; module_usb_driver(xone_dongle_driver); MODULE_DEVICE_TABLE(usb, xone_dongle_id_table); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone dongle driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/transport/mt76.c000066400000000000000000001110371515500374600204430ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include #include #include #include "mt76.h" /* bulk transfer timeout in ms */ #define XONE_MT_USB_TIMEOUT 1000 #define XONE_MT_POLL_RETRIES 50 #define XONE_MT_RF_PATCH 0x0130 #define XONE_MT_FW_LOAD_IVB 0x12 #define XONE_MT_FW_ILM_OFFSET 0x080000 #define XONE_MT_FW_DLM_OFFSET 0x110800 #define XONE_MT_FW_CHUNK_SIZE 0x3800 /* wireless channel bands */ #define XONE_MT_CH_2G_LOW 0x01 #define XONE_MT_CH_2G_MID 0x02 #define XONE_MT_CH_2G_HIGH 0x03 #define XONE_MT_CH_5G_LOW 0x01 #define XONE_MT_CH_5G_HIGH 0x02 #define XONE_MT_WCID_KEY_LEN 16 /* commands specific to the dongle's firmware */ enum xone_mt76_ms_command { XONE_MT_SET_MAC_ADDRESS = 0x00, XONE_MT_ADD_CLIENT = 0x01, XONE_MT_REMOVE_CLIENT = 0x02, XONE_MT_SET_IDLE_TIME = 0x05, XONE_MT_SET_CHAN_CANDIDATES = 0x07, }; enum xone_mt76_wow_feature { XONE_MT_WOW_ENABLE = 0x01, XONE_MT_WOW_TRAFFIC = 0x03, }; enum xone_mt76_wow_traffic { XONE_MT_WOW_TO_FIRMWARE = 0x00, XONE_MT_WOW_TO_HOST = 0x01, }; struct xone_mt76_msg_load_cr { u8 mode; u8 temperature; u8 channel; u8 padding; } __packed; struct xone_mt76_msg_switch_channel { u8 channel; u8 padding1[3]; __le16 tx_rx_setting; u8 padding2[10]; u8 bandwidth; u8 tx_power; u8 scan; u8 unknown; } __packed; static char override_mac[ETH_ALEN] = { 0 }; module_param_array(override_mac, byte, NULL, 0444); MODULE_PARM_DESC(override_mac, "Override MAC address (6 bytes), helps deconflict counterfeit adapters"); static u32 xone_mt76_read_register(struct xone_mt76 *mt, u32 addr) { u8 req = MT_VEND_MULTI_READ; int ret; if (addr & MT_VEND_TYPE_CFG) { req = MT_VEND_READ_CFG; addr &= ~MT_VEND_TYPE_CFG; } ret = usb_control_msg(mt->udev, usb_rcvctrlpipe(mt->udev, 0), req, USB_DIR_IN | USB_TYPE_VENDOR, addr >> 16, addr, &mt->control_data, sizeof(mt->control_data), XONE_MT_USB_TIMEOUT); if (ret != sizeof(mt->control_data)) ret = -EREMOTEIO; if (ret < 0) { dev_err(mt->dev, "%s: control message failed: %d\n", __func__, ret); return 0; } return le32_to_cpu(mt->control_data); } static void xone_mt76_write_register(struct xone_mt76 *mt, u32 addr, u32 val) { u8 req = MT_VEND_MULTI_WRITE; int ret; if (addr & MT_VEND_TYPE_CFG) { req = MT_VEND_WRITE_CFG; addr &= ~MT_VEND_TYPE_CFG; } mt->control_data = cpu_to_le32(val); ret = usb_control_msg(mt->udev, usb_sndctrlpipe(mt->udev, 0), req, USB_DIR_OUT | USB_TYPE_VENDOR, addr >> 16, addr, &mt->control_data, sizeof(mt->control_data), XONE_MT_USB_TIMEOUT); if (ret != sizeof(mt->control_data)) ret = -EREMOTEIO; if (ret < 0) dev_err(mt->dev, "%s: control message failed: %d\n", __func__, ret); } static int xone_mt76_load_ivb(struct xone_mt76 *mt) { /* load interrupt vector block */ return usb_control_msg(mt->udev, usb_sndctrlpipe(mt->udev, 0), MT_VEND_DEV_MODE, USB_DIR_OUT | USB_TYPE_VENDOR, XONE_MT_FW_LOAD_IVB, 0, NULL, 0, XONE_MT_USB_TIMEOUT); } static bool xone_mt76_poll(struct xone_mt76 *mt, u32 offset, u32 mask, u32 val) { int i; u32 reg; for (i = 0; i < XONE_MT_POLL_RETRIES; i++) { reg = xone_mt76_read_register(mt, offset); if ((reg & mask) == val) return true; usleep_range(10000, 20000); } return false; } static int xone_mt76_read_efuse(struct xone_mt76 *mt, u16 addr, void *data, int len) { u32 ctrl, offset, val; int i, remaining; ctrl = xone_mt76_read_register(mt, MT_EFUSE_CTRL); ctrl &= ~(MT_EFUSE_CTRL_AIN | MT_EFUSE_CTRL_MODE); ctrl |= MT_EFUSE_CTRL_KICK; ctrl |= FIELD_PREP(MT_EFUSE_CTRL_AIN, addr & ~0x0f); ctrl |= FIELD_PREP(MT_EFUSE_CTRL_MODE, MT_EE_READ); xone_mt76_write_register(mt, MT_EFUSE_CTRL, ctrl); if (!xone_mt76_poll(mt, MT_EFUSE_CTRL, MT_EFUSE_CTRL_KICK, 0)) return -ETIMEDOUT; for (i = 0; i < len; i += sizeof(u32)) { /* block data offset (multiple of 32 bits) */ offset = (addr & GENMASK(3, 2)) + i; val = xone_mt76_read_register(mt, MT_EFUSE_DATA_BASE + offset); remaining = min_t(int, len - i, sizeof(u32)); memcpy(data + i, &val, remaining); } return 0; } struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp) { struct sk_buff *skb; /* up to 4 bytes of padding */ skb = alloc_skb(MT_CMD_HDR_LEN + len + sizeof(u32) + MT_CMD_HDR_LEN, gfp); if (!skb) return NULL; skb_reserve(skb, MT_CMD_HDR_LEN); return skb; } static void xone_mt76_prep_message(struct sk_buff *skb, u32 info) { int len, pad; /* padding and trailer */ len = round_up(skb->len, sizeof(u32)); pad = len - skb->len + MT_CMD_HDR_LEN; put_unaligned_le32(info | FIELD_PREP(MT_MCU_MSG_LEN, len), skb_push(skb, MT_CMD_HDR_LEN)); memset(skb_put(skb, pad), 0, pad); } void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd) { xone_mt76_prep_message(skb, MT_MCU_MSG_TYPE_CMD | FIELD_PREP(MT_MCU_MSG_PORT, MT_CPU_TX_PORT) | FIELD_PREP(MT_MCU_MSG_CMD_TYPE, cmd)); } static int xone_mt76_send_command(struct xone_mt76 *mt, struct sk_buff *skb, enum mt76_mcu_cmd cmd) { int err; xone_mt76_prep_command(skb, cmd); err = usb_bulk_msg(mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), skb->data, skb->len, NULL, XONE_MT_USB_TIMEOUT); consume_skb(skb); return err; } static int xone_mt76_send_wlan(struct xone_mt76 *mt, struct sk_buff *skb) { struct mt76_txwi txwi = {}; int err; /* wait for acknowledgment */ /* ignore wireless client identifier (WCID) */ txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, IEEE80211_HT_MPDU_DENSITY_4)); txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; txwi.wcid = 0xff; txwi.len_ctl = cpu_to_le16(skb->len); memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi)); /* enhanced distributed channel access (EDCA) */ /* wireless information valid (WIV) */ xone_mt76_prep_message(skb, FIELD_PREP(MT_TXD_INFO_DPORT, MT_WLAN_PORT) | FIELD_PREP(MT_TXD_INFO_QSEL, MT_QSEL_EDCA) | MT_TXD_INFO_WIV | MT_TXD_INFO_80211); err = usb_bulk_msg(mt->udev, usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT), skb->data, skb->len, NULL, XONE_MT_USB_TIMEOUT); consume_skb(skb); return err; } static int xone_mt76_select_function(struct xone_mt76 *mt, enum mt76_mcu_function func, u32 val) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32) * 2, GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(func, skb_put(skb, sizeof(u32))); put_unaligned_le32(val, skb_put(skb, sizeof(u32))); return xone_mt76_send_command(mt, skb, MT_CMD_FUN_SET_OP); } static int xone_mt76_load_cr(struct xone_mt76 *mt, enum mt76_mcu_cr_mode mode) { struct sk_buff *skb; struct xone_mt76_msg_load_cr msg = {}; skb = xone_mt76_alloc_message(sizeof(msg), GFP_KERNEL); if (!skb) return -ENOMEM; msg.mode = mode; skb_put_data(skb, &msg, sizeof(msg)); return xone_mt76_send_command(mt, skb, MT_CMD_LOAD_CR); } static int xone_mt76_send_ms_command(struct xone_mt76 *mt, enum xone_mt76_ms_command cmd, void *data, int len) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32) + len, GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(cmd, skb_put(skb, sizeof(u32))); skb_put_data(skb, data, len); /* send command to Microsoft's proprietary firmware */ return xone_mt76_send_command(mt, skb, MT_CMD_INIT_GAIN_OP); } static int xone_mt76_write_burst(struct xone_mt76 *mt, u32 idx, void *data, int len) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(idx) + len, GFP_KERNEL); if (!skb) return -ENOMEM; /* register offset in memory */ put_unaligned_le32(idx + MT_MCU_MEMMAP_WLAN, skb_put(skb, sizeof(idx))); skb_put_data(skb, data, len); return xone_mt76_send_command(mt, skb, MT_CMD_BURST_WRITE); } int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32), GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(mode, skb_put(skb, sizeof(u32))); return xone_mt76_send_command(mt, skb, MT_CMD_LED_MODE_OP); } static int xone_mt76_set_power_mode(struct xone_mt76 *mt, enum mt76_mcu_power_mode mode) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32), GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(mode, skb_put(skb, sizeof(u32))); return xone_mt76_send_command(mt, skb, MT_CMD_POWER_SAVING_OP); } static int xone_mt76_set_wow_enable(struct xone_mt76 *mt, bool enable) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32) + sizeof(u8) * 2, GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(XONE_MT_WOW_ENABLE, skb_put(skb, sizeof(u32))); skb_put_u8(skb, enable); skb_put_u8(skb, mt->channel->index); return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); } static int xone_mt76_set_wow_traffic(struct xone_mt76 *mt, enum xone_mt76_wow_traffic traffic) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32) + sizeof(u8), GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(XONE_MT_WOW_TRAFFIC, skb_put(skb, sizeof(u32))); skb_put_u8(skb, traffic); return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); } int xone_mt76_switch_channel(struct xone_mt76 *mt, struct xone_mt76_channel *chan) { struct sk_buff *skb; struct xone_mt76_msg_switch_channel msg = {}; skb = xone_mt76_alloc_message(sizeof(msg), GFP_KERNEL); if (!skb) return -ENOMEM; /* select TX and RX stream 1 */ /* enable or disable scanning (unknown purpose) */ msg.channel = chan->index; msg.tx_rx_setting = cpu_to_le16(0x0101); msg.bandwidth = chan->bandwidth; msg.tx_power = chan->power; msg.scan = chan->scan; skb_put_data(skb, &msg, sizeof(msg)); return xone_mt76_send_command(mt, skb, MT_CMD_SWITCH_CHANNEL_OP); } static int xone_mt76_calibrate(struct xone_mt76 *mt, enum mt76_mcu_calibration calib, u32 val) { struct sk_buff *skb; skb = xone_mt76_alloc_message(sizeof(u32) * 2, GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(calib, skb_put(skb, sizeof(u32))); put_unaligned_le32(val, skb_put(skb, sizeof(u32))); return xone_mt76_send_command(mt, skb, MT_CMD_CALIBRATION_OP); } static int xone_mt76_send_firmware_part(struct xone_mt76 *mt, u32 offset, const u8 *data, u32 len) { struct sk_buff *skb; u32 pos, chunk_len, complete; int err; for (pos = 0; pos < len; pos += XONE_MT_FW_CHUNK_SIZE) { chunk_len = min_t(u32, len - pos, XONE_MT_FW_CHUNK_SIZE); skb = xone_mt76_alloc_message(chunk_len, GFP_KERNEL); if (!skb) return -ENOMEM; skb_put_data(skb, data + pos, chunk_len); chunk_len = roundup(chunk_len, sizeof(u32)); xone_mt76_write_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, offset + pos); xone_mt76_write_register(mt, MT_FCE_DMA_LEN | MT_VEND_TYPE_CFG, chunk_len << 16); err = xone_mt76_send_command(mt, skb, 0); if (err) return err; complete = 0xc0000000 | (chunk_len << 16); if (!xone_mt76_poll(mt, MT_FCE_DMA_LEN | MT_VEND_TYPE_CFG, 0xffffffff, complete)) return -ETIMEDOUT; } return 0; } static int xone_mt76_send_firmware(struct xone_mt76 *mt, const struct firmware *fw) { const struct mt76_fw_header *hdr; u32 ilm_len, dlm_len; int err; if (fw->size < sizeof(*hdr)) return -EINVAL; hdr = (const struct mt76_fw_header *)fw->data; ilm_len = le32_to_cpu(hdr->ilm_len); dlm_len = le32_to_cpu(hdr->dlm_len); if (fw->size != sizeof(*hdr) + ilm_len + dlm_len) return -EINVAL; dev_dbg(mt->dev, "%s: build=%.16s\n", __func__, hdr->build_time); /* configure DMA, enable FCE and packet DMA */ xone_mt76_write_register(mt, MT_USB_U3DMA_CFG | MT_VEND_TYPE_CFG, MT_USB_DMA_CFG_TX_BULK_EN | MT_USB_DMA_CFG_RX_BULK_EN); xone_mt76_write_register(mt, MT_FCE_PSE_CTRL, 0x01); xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_BASE_PTR, 0x00400230); xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_MAX_COUNT, 0x01); xone_mt76_write_register(mt, MT_TX_CPU_FROM_FCE_CPU_DESC_IDX, 0x01); xone_mt76_write_register(mt, MT_FCE_PDMA_GLOBAL_CONF, 0x44); xone_mt76_write_register(mt, MT_FCE_SKIP_FS, 0x03); /* send instruction local memory */ err = xone_mt76_send_firmware_part(mt, XONE_MT_FW_ILM_OFFSET, fw->data + sizeof(*hdr), ilm_len); if (err) return err; /* send data local memory */ return xone_mt76_send_firmware_part(mt, XONE_MT_FW_DLM_OFFSET, fw->data + sizeof(*hdr) + ilm_len, dlm_len); } static int xone_mt76_reset_firmware(struct xone_mt76 *mt) { u32 val; int err; /* apply power-on RF patch */ val = xone_mt76_read_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG); xone_mt76_write_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG, val & ~BIT(19)); err = xone_mt76_load_ivb(mt); if (err) return err; /* wait for reset */ if (!xone_mt76_poll(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0x80000000, 0x80000000)) return -ETIMEDOUT; return 0; } int xone_mt76_load_firmware(struct xone_mt76 *mt, const struct firmware *fw) { u32 val; int err; if (xone_mt76_read_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG)) { dev_dbg(mt->dev, "%s: resetting firmware...\n", __func__); err = xone_mt76_reset_firmware(mt); if (err) return err; /* * The MCU needs time to complete its startup sequence after the * firmware reset before it can handle the bulk USB commands sent * by xone_mt76_init_radio(). Without this delay init_radio * reliably times out (-ETIMEDOUT) on warm reboot. */ msleep(500); return 0; } dev_dbg(mt->dev, "%s: loading firmware...\n", __func__); err = xone_mt76_send_firmware(mt, fw); if (err) return err; xone_mt76_write_register(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0); /* * The warm-boot path (reset_firmware) applies an RF patch before * triggering MCU execution via load_ivb. Without this patch the RF * subsystem does not initialise correctly and the chip is silent — * it accepts all subsequent MCU commands (init_radio appears to * succeed) but never transmits beacons, so controllers cannot * discover the dongle. Apply the same patch here before load_ivb * so cold-boot firmware startup leaves the RF in the same state as * a warm reset. */ val = xone_mt76_read_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG); xone_mt76_write_register(mt, XONE_MT_RF_PATCH | MT_VEND_TYPE_CFG, val & ~BIT(19)); err = xone_mt76_load_ivb(mt); if (err) return err; /* * After xone_mt76_load_ivb() the MT76 chip briefly disconnects from * USB as part of its firmware startup sequence. Without a delay the * poll below can read FCE_DMA_ADDR=0x01 in the narrow window before * the kernel has marked the device as disconnected, producing a false * success. xone_mt76_init_radio() then runs against a device the * kernel considers gone and fails with -ENODEV. * * Wait long enough for the disconnect/reconnect cycle to complete so * the poll reflects the true post-startup state of the device. */ msleep(500); if (!xone_mt76_poll(mt, MT_FCE_DMA_ADDR | MT_VEND_TYPE_CFG, 0x01, 0x01)) err = -ETIMEDOUT; return err; } static const struct xone_mt76_channel xone_mt76_channels[XONE_MT_NUM_CHANNELS] = { { 0x01, XONE_MT_CH_2G_LOW, MT_PHY_BW_20, 0, true, 0 }, { 0x06, XONE_MT_CH_2G_MID, MT_PHY_BW_20, 0, true, 0 }, { 0x0b, XONE_MT_CH_2G_HIGH, MT_PHY_BW_20, 0, true, 0 }, { 0x24, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, true, 0 }, { 0x28, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, false, 0 }, { 0x2c, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, true, 0 }, { 0x30, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, false, 0 }, { 0x95, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, true, 0 }, { 0x99, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, { 0x9d, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, true, 0 }, { 0xa1, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, { 0xa5, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, }; static int xone_mt76_set_channel_candidates(struct xone_mt76 *mt) { struct sk_buff *skb; u8 best_chan = mt->channel->index; u8 chan; int i, err; skb = alloc_skb(sizeof(u32) * 2 + sizeof(u32) * XONE_MT_NUM_CHANNELS, GFP_KERNEL); if (!skb) return -ENOMEM; put_unaligned_le32(1, skb_put(skb, sizeof(u32))); put_unaligned_le32(best_chan, skb_put(skb, sizeof(u32))); put_unaligned_le32(XONE_MT_NUM_CHANNELS - 1, skb_put(skb, sizeof(u32))); for (i = 0; i < XONE_MT_NUM_CHANNELS; i++) { chan = mt->channels[i].index; if (chan != best_chan) put_unaligned_le32(chan, skb_put(skb, sizeof(u32))); } err = xone_mt76_send_ms_command(mt, XONE_MT_SET_CHAN_CANDIDATES, skb->data, skb->len); consume_skb(skb); return err; } static int xone_mt76_get_channel_power(struct xone_mt76 *mt, struct xone_mt76_channel *chan) { u16 addr; u8 idx, target, offset; u8 entry[8]; int err; if (chan->bandwidth == MT_PHY_BW_20) { addr = MT_EE_TX_POWER_0_START_2G; idx = 4; } else { /* each group has its own power table */ addr = MT_EE_TX_POWER_0_START_5G + chan->group * MT_TX_POWER_GROUP_SIZE_5G; idx = 5; } err = xone_mt76_read_efuse(mt, addr, entry, sizeof(entry)); if (err) { dev_err(mt->dev, "%s: read EFUSE failed: %d\n", __func__, err); return err; } target = entry[idx]; offset = entry[idx + chan->band]; /* increase or decrease power by offset (in 0.5 dB steps) */ if (offset & BIT(7)) chan->power = (offset & BIT(6)) ? target + (offset & GENMASK(5, 0)) : target - (offset & GENMASK(5, 0)); else chan->power = target; return 0; } static int xone_mt76_evaluate_channels(struct xone_mt76 *mt) { struct xone_mt76_channel *chan; int i, err, pow = 0; mt->channel = NULL; memcpy(mt->channels, xone_mt76_channels, sizeof(xone_mt76_channels)); for (i = 0; i < XONE_MT_NUM_CHANNELS; i++) { chan = &mt->channels[i]; /* original driver increases power for channels 0x24 to 0x30 */ err = xone_mt76_get_channel_power(mt, chan); if (err) return err; err = xone_mt76_switch_channel(mt, chan); if (err) return err; /* pick the highest power channel seen first */ /* the last channel might not be the best one */ if (chan->power > pow) { mt->channel = chan; pow = chan->power; } dev_dbg(mt->dev, "%s: channel=%u, power=%u\n", __func__, chan->index, chan->power); } if (mt->channel == NULL) mt->channel = chan; return 0; } static int xone_mt76_init_channels(struct xone_mt76 *mt) { int err; /* enable promiscuous mode */ xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x014f13); err = xone_mt76_evaluate_channels(mt); if (err) return err; /* disable promiscuous mode */ xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); dev_dbg(mt->dev, "%s: channel=%u\n", __func__, mt->channel->index); mt->channel->scan = true; err = xone_mt76_switch_channel(mt, mt->channel); if (err) return err; err = xone_mt76_set_power_mode(mt, MT_RADIO_OFF); if (err) return err; msleep(50); err = xone_mt76_set_power_mode(mt, MT_RADIO_ON); if (err) return err; mt->channel->scan = false; err = xone_mt76_switch_channel(mt, mt->channel); if (err) return err; return xone_mt76_set_channel_candidates(mt); } static int xone_mt76_set_idle_time(struct xone_mt76 *mt) { __le32 time = cpu_to_le32(64); /* prevent wireless clients from disconnecting when idle */ return xone_mt76_send_ms_command(mt, XONE_MT_SET_IDLE_TIME, &time, sizeof(time)); } /* * There are a number of knockoff adapters out there that share the same MAC address(es). * This will create problems if two of them are used within range of each other. * So far, the following MACs are known to be associated with knockoff adapters: */ static const u8 xone_counterfeit_macs[][ETH_ALEN] = { {0x62, 0x45, 0xb4, 0xe7, 0xa4, 0xef}, }; static int xone_mt76_mac_looks_counterfeit(const u8* addr) { for (int i = 0; i < ARRAY_SIZE(xone_counterfeit_macs); i++) { if (ether_addr_equal(addr, xone_counterfeit_macs[i])) return true; } return false; } static int xone_mt76_init_address(struct xone_mt76 *mt) { int err; err = xone_mt76_read_efuse(mt, MT_EE_MAC_ADDR, mt->address, sizeof(mt->address)); if (err) return err; dev_dbg(mt->dev, "%s: fuse_address=%pM\n", __func__, mt->address); if (xone_mt76_mac_looks_counterfeit(mt->address) && is_zero_ether_addr(override_mac)) dev_warn(mt->dev, "%s: MAC address %pM looks suspicious. Counterfeit " "adapter? That may be fine, but consider passing override_mac= to " "deconflict with others nearby\n", __func__, mt->address); /* Override MAC address if present */ if (!is_zero_ether_addr(override_mac)) { memcpy(mt->address, override_mac, ETH_ALEN); dev_dbg(mt->dev, "%s: overriding MAC address to %pM\n", __func__, mt->address); } /* some addresses start with 6c:5d:3a */ /* clients only connect to 62:45:bx:xx:xx:xx */ if (mt->address[0] != 0x62) { mt->address[0] = 0x62; mt->address[1] = 0x45; mt->address[2] = 0xbd; dev_dbg(mt->dev, "%s: address=%pM\n", __func__, mt->address); } err = xone_mt76_write_burst(mt, MT_MAC_ADDR_DW0, mt->address, sizeof(mt->address)); if (err) return err; err = xone_mt76_write_burst(mt, MT_MAC_BSSID_DW0, mt->address, sizeof(mt->address)); if (err) return err; return xone_mt76_send_ms_command(mt, XONE_MT_SET_MAC_ADDRESS, mt->address, sizeof(mt->address)); } static int xone_mt76_calibrate_crystal(struct xone_mt76 *mt) { u8 trim[4]; u16 val; s8 offset; u32 ctrl; int err; err = xone_mt76_read_efuse(mt, MT_EE_XTAL_TRIM_2, trim, sizeof(trim)); if (err) return err; val = (trim[3] << 8) | trim[2]; offset = val & GENMASK(6, 0); if ((val & 0xff) == 0xff) offset = 0; else if (val & BIT(7)) offset = -offset; val >>= 8; if (!val || val == 0xff) { err = xone_mt76_read_efuse(mt, MT_EE_XTAL_TRIM_1, trim, sizeof(trim)); if (err) return err; val = (trim[3] << 8) | trim[2]; val &= 0xff; if (!val || val == 0xff) val = 0x14; } val = (val & GENMASK(6, 0)) + offset; ctrl = xone_mt76_read_register(mt, MT_XO_CTRL5 | MT_VEND_TYPE_CFG); xone_mt76_write_register(mt, MT_XO_CTRL5 | MT_VEND_TYPE_CFG, (ctrl & ~MT_XO_CTRL5_C2_VAL) | (val << 8)); xone_mt76_write_register(mt, MT_XO_CTRL6 | MT_VEND_TYPE_CFG, MT_XO_CTRL6_C2_CTRL); xone_mt76_write_register(mt, MT_CMB_CTRL, 0x0091a7ff); return 0; } static int xone_mt76_calibrate_radio(struct xone_mt76 *mt) { int err; /* configure automatic gain control (AGC) */ xone_mt76_write_register(mt, MT_BBP(AGC, 8), 0x18365efa); xone_mt76_write_register(mt, MT_BBP(AGC, 9), 0x18365efa); /* reset required for reliable WLAN associations */ xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); xone_mt76_write_register(mt, MT_RF_BYPASS_0, 0); xone_mt76_write_register(mt, MT_RF_SETTING_0, 0); err = xone_mt76_calibrate(mt, MT_MCU_CAL_TEMP_SENSOR, 0); if (err) return err; err = xone_mt76_calibrate(mt, MT_MCU_CAL_RXDCOC, 1); if (err) return err; err = xone_mt76_calibrate(mt, MT_MCU_CAL_RC, 0); if (err) return err; xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_RX | MT_MAC_SYS_CTRL_ENABLE_TX); return 0; } static void xone_mt76_init_registers(struct xone_mt76 *mt) { xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_RESET_BBP | MT_MAC_SYS_CTRL_RESET_CSR); xone_mt76_write_register(mt, MT_USB_DMA_CFG, 0); xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); xone_mt76_write_register(mt, MT_PWR_PIN_CFG, 0); xone_mt76_write_register(mt, MT_LDO_CTRL_1, 0x6b006464); xone_mt76_write_register(mt, MT_WPDMA_GLO_CFG, 0x70); xone_mt76_write_register(mt, MT_WMM_AIFSN, 0x2273); xone_mt76_write_register(mt, MT_WMM_CWMIN, 0x2344); xone_mt76_write_register(mt, MT_WMM_CWMAX, 0x34aa); xone_mt76_write_register(mt, MT_FCE_DMA_ADDR, 0x041200); xone_mt76_write_register(mt, MT_TSO_CTRL, 0); xone_mt76_write_register(mt, MT_PBF_SYS_CTRL, 0x080c00); xone_mt76_write_register(mt, MT_PBF_TX_MAX_PCNT, 0x1fbf1f1f); xone_mt76_write_register(mt, MT_FCE_PSE_CTRL, 0x01); xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_RX | MT_MAC_SYS_CTRL_ENABLE_TX); xone_mt76_write_register(mt, MT_AUTO_RSP_CFG, 0x13); xone_mt76_write_register(mt, MT_MAX_LEN_CFG, 0x3e3fff); xone_mt76_write_register(mt, MT_AMPDU_MAX_LEN_20M1S, 0xfffc9855); xone_mt76_write_register(mt, MT_AMPDU_MAX_LEN_20M2S, 0xff); xone_mt76_write_register(mt, MT_BKOFF_SLOT_CFG, 0x0109); xone_mt76_write_register(mt, MT_PWR_PIN_CFG, 0); xone_mt76_write_register(mt, MT_EDCA_CFG_AC(0), 0x064320); xone_mt76_write_register(mt, MT_EDCA_CFG_AC(1), 0x0a4700); xone_mt76_write_register(mt, MT_EDCA_CFG_AC(2), 0x043238); xone_mt76_write_register(mt, MT_EDCA_CFG_AC(3), 0x03212f); xone_mt76_write_register(mt, MT_TX_PIN_CFG, 0x150f0f); xone_mt76_write_register(mt, MT_TX_SW_CFG0, 0x101001); xone_mt76_write_register(mt, MT_TX_SW_CFG1, 0x010000); xone_mt76_write_register(mt, MT_TXOP_CTRL_CFG, 0x10583f); xone_mt76_write_register(mt, MT_TX_TIMEOUT_CFG, 0x0a0f90); xone_mt76_write_register(mt, MT_TX_RETRY_CFG, 0x47d01f0f); xone_mt76_write_register(mt, MT_CCK_PROT_CFG, 0x03f40003); xone_mt76_write_register(mt, MT_OFDM_PROT_CFG, 0x03f40003); xone_mt76_write_register(mt, MT_MM20_PROT_CFG, 0x01742004); xone_mt76_write_register(mt, MT_GF20_PROT_CFG, 0x01742004); xone_mt76_write_register(mt, MT_GF40_PROT_CFG, 0x03f42084); xone_mt76_write_register(mt, MT_EXP_ACK_TIME, 0x2c00dc); xone_mt76_write_register(mt, MT_TX_ALC_CFG_2, 0x22160a00); xone_mt76_write_register(mt, MT_TX_ALC_CFG_3, 0x22160a76); xone_mt76_write_register(mt, MT_TX_ALC_CFG_0, 0x3f3f1818); xone_mt76_write_register(mt, MT_TX_ALC_CFG_4, 0x0606); xone_mt76_write_register(mt, MT_PIFS_TX_CFG, 0x060fff); xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); xone_mt76_write_register(mt, MT_LEGACY_BASIC_RATE, 0x017f); xone_mt76_write_register(mt, MT_HT_BASIC_RATE, 0x8003); xone_mt76_write_register(mt, MT_PN_PAD_MODE, 0x02); xone_mt76_write_register(mt, MT_TXOP_HLDR_ET, 0x02); xone_mt76_write_register(mt, MT_TX_PROT_CFG6, 0xe3f42004); xone_mt76_write_register(mt, MT_TX_PROT_CFG7, 0xe3f42084); xone_mt76_write_register(mt, MT_TX_PROT_CFG8, 0xe3f42104); xone_mt76_write_register(mt, MT_DACCLK_EN_DLY_CFG, 0); xone_mt76_write_register(mt, MT_RF_PA_MODE_ADJ0, 0xee000000); xone_mt76_write_register(mt, MT_RF_PA_MODE_ADJ1, 0xee000000); xone_mt76_write_register(mt, MT_TX0_RF_GAIN_CORR, 0x0f3c3c3c); xone_mt76_write_register(mt, MT_TX1_RF_GAIN_CORR, 0x0f3c3c3c); xone_mt76_write_register(mt, MT_PBF_CFG, 0x1efebcf5); xone_mt76_write_register(mt, MT_PAUSE_ENABLE_CONTROL1, 0x0a); xone_mt76_write_register(mt, MT_RF_BYPASS_0, 0x7f000000); xone_mt76_write_register(mt, MT_RF_SETTING_0, 0x1a800000); xone_mt76_write_register(mt, MT_XIFS_TIME_CFG, 0x33a40e0a); xone_mt76_write_register(mt, MT_FCE_L2_STUFF, 0x03ff0223); xone_mt76_write_register(mt, MT_TX_RTS_CFG, 0); xone_mt76_write_register(mt, MT_BEACON_TIME_CFG, 0x0640); xone_mt76_write_register(mt, MT_EXT_CCA_CFG, 0xf0e4); xone_mt76_write_register(mt, MT_CH_TIME_CFG, 0x015f); } static u16 xone_mt76_get_chip_id(struct xone_mt76 *mt) { u8 id[4]; if (xone_mt76_read_efuse(mt, MT_EE_CHIP_ID, &id, sizeof(id))) return 0; return (id[1] << 8) | id[2]; } int xone_mt76_init_radio(struct xone_mt76 *mt) { int err; dev_dbg(mt->dev, "%s: id=0x%04x\n", __func__, xone_mt76_get_chip_id(mt)); err = xone_mt76_select_function(mt, MT_Q_SELECT, 1); if (err) return err; err = xone_mt76_set_power_mode(mt, MT_RADIO_ON); if (err) return err; err = xone_mt76_load_cr(mt, MT_RF_BBP_CR); if (err) return err; xone_mt76_init_registers(mt); err = xone_mt76_calibrate_crystal(mt); if (err) return err; err = xone_mt76_init_address(mt); if (err) return err; err = xone_mt76_set_idle_time(mt); if (err) return err; err = xone_mt76_calibrate_radio(mt); if (err) return err; err = xone_mt76_init_channels(mt); if (err) return err; /* mandatory delay after channel change */ msleep(1000); /* * After S3 sleep the reset_resume path does a warm firmware MCU reset * (reset_firmware) rather than a full USB re-enumeration. The MT76 * chip's hardware DMA routing registers are NOT reset by the MCU soft * reset, so any WoW configuration applied by suspend_radio() may * persist: specifically, set_wow_traffic(TO_HOST) reconfigures the * hardware to forward received frames via the wake-on-wireless path * rather than the normal firmware-processed path. With that routing * still active, incoming QoS data frames (GIP ANNOUNCE from the * controller) are silently dropped before they reach the host driver, * so the GIP handshake never completes and the controller light stays * solid but never dims. * * Explicitly restore normal traffic routing before setting up the * beacon. On cold boot (no prior WoW state) these are harmless no-ops. * mt->channel is valid here because init_channels() has already run. */ err = xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_FIRMWARE); if (err) return err; err = xone_mt76_set_wow_enable(mt, false); if (err) return err; return xone_mt76_set_pairing(mt, false); } int xone_mt76_suspend_radio(struct xone_mt76 *mt) { int err; xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, 0); /* enable wake-on-wireless */ err = xone_mt76_set_wow_enable(mt, true); if (err) return err; err = xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_HOST); if (err) return err; dev_dbg(mt->dev, "%s: suspended\n", __func__); return 0; } int xone_mt76_resume_radio(struct xone_mt76 *mt) { int err; err = xone_mt76_set_wow_traffic(mt, XONE_MT_WOW_TO_FIRMWARE); if (err) return err; /* disable wake-on-wireless */ err = xone_mt76_set_wow_enable(mt, false); if (err) return err; err = xone_mt76_switch_channel(mt, mt->channel); if (err) return err; err = xone_mt76_set_pairing(mt, false); if (err) return err; xone_mt76_write_register(mt, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_RX | MT_MAC_SYS_CTRL_ENABLE_TX); dev_dbg(mt->dev, "%s: resumed\n", __func__); return 0; } static int xone_mt76_write_beacon(struct xone_mt76 *mt, bool pair) { struct sk_buff *skb; struct mt76_txwi txwi = {}; struct ieee80211_mgmt mgmt = {}; u8 data[] = { /* information element with Microsoft's OUI (00:50:f2) */ /* probably includes the selected channel pair */ 0x00, 0x00, 0xdd, 0x10, 0x00, 0x50, 0xf2, 0x11, 0x01, 0x10, pair, 0xa5, 0x30, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; int mgmt_len = sizeof(struct ieee80211_hdr_3addr) + sizeof(mgmt.u.beacon); int err; u8 chan = 1; if (mt->channel) chan = mt->channel->index; data[14] = chan; skb = alloc_skb(sizeof(txwi) + mgmt_len + sizeof(data), GFP_KERNEL); if (!skb) return -ENOMEM; /* generate beacon timestamp */ /* use hardware sequence control */ txwi.flags = cpu_to_le16(MT_TXWI_FLAGS_TS); txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); txwi.ack_ctl = MT_TXWI_ACK_CTL_NSEQ; txwi.len_ctl = cpu_to_le16(mgmt_len + sizeof(data)); mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_BEACON); eth_broadcast_addr(mgmt.da); memcpy(mgmt.sa, mt->address, ETH_ALEN); memcpy(mgmt.bssid, mt->address, ETH_ALEN); /* default beacon interval (100 ms) */ /* original capability info */ mgmt.u.beacon.beacon_int = cpu_to_le16(100); mgmt.u.beacon.capab_info = cpu_to_le16(0xc631); skb_put_data(skb, &txwi, sizeof(txwi)); skb_put_data(skb, &mgmt, mgmt_len); skb_put_data(skb, data, sizeof(data)); err = xone_mt76_write_burst(mt, MT_BEACON_BASE, skb->data, skb->len); consume_skb(skb); return err; } int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable) { int err; if (enable) xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x014f13); else xone_mt76_write_register(mt, MT_RX_FILTR_CFG, 0x017f17); err = xone_mt76_write_beacon(mt, enable); if (err) return err; /* enable timing synchronization function (TSF) timer */ /* enable target beacon transmission time (TBTT) timer */ /* set TSF timer to AP mode */ /* activate beacon transmission */ xone_mt76_write_register(mt, MT_BEACON_TIME_CFG, MT_BEACON_TIME_CFG_BEACON_TX | MT_BEACON_TIME_CFG_TBTT_EN | MT_BEACON_TIME_CFG_SYNC_MODE | MT_BEACON_TIME_CFG_TIMER_EN | FIELD_PREP(MT_BEACON_TIME_CFG_INTVAL, 0x0640)); return 0; } int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr) { struct sk_buff *skb; struct ieee80211_hdr_3addr hdr = {}; u8 data[] = { 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, 0xff, 0x87, 0x1f }; skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + sizeof(hdr) + sizeof(u8) * 2 + sizeof(data), GFP_KERNEL); if (!skb) return -ENOMEM; hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | XONE_MT_WLAN_RESERVED); memcpy(hdr.addr1, addr, ETH_ALEN); memcpy(hdr.addr2, mt->address, ETH_ALEN); memcpy(hdr.addr3, mt->address, ETH_ALEN); skb_reserve(skb, sizeof(struct mt76_txwi)); skb_put_data(skb, &hdr, sizeof(hdr)); skb_put_u8(skb, XONE_MT_WLAN_RESERVED); skb_put_u8(skb, XONE_MT_CLIENT_PAIR_RESP); skb_put_data(skb, data, sizeof(data)); return xone_mt76_send_wlan(mt, skb); } int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr) { struct sk_buff *skb; struct ieee80211_mgmt mgmt = {}; u8 data[] = { wcid - 1, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x00, 0x00 }; int mgmt_len = sizeof(struct ieee80211_hdr_3addr) + sizeof(mgmt.u.assoc_resp); int err; skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + mgmt_len + 8, GFP_KERNEL); if (!skb) return -ENOMEM; mgmt.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ASSOC_RESP); memcpy(mgmt.da, addr, ETH_ALEN); memcpy(mgmt.sa, mt->address, ETH_ALEN); memcpy(mgmt.bssid, mt->address, ETH_ALEN); /* original status code and association ID */ mgmt.u.assoc_resp.status_code = cpu_to_le16(0x0110); mgmt.u.assoc_resp.aid = cpu_to_le16(0x0f00); skb_reserve(skb, sizeof(struct mt76_txwi)); skb_put_data(skb, &mgmt, mgmt_len); memset(skb_put(skb, 8), 0, 8); err = xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, ETH_ALEN); if (err) goto err_free_skb; err = xone_mt76_send_ms_command(mt, XONE_MT_ADD_CLIENT, data, sizeof(data)); if (err) goto err_free_skb; return xone_mt76_send_wlan(mt, skb); err_free_skb: kfree_skb(skb); return err; } int xone_mt76_send_client_command(struct xone_mt76 *mt, u8 wcid, u8 *addr, enum xone_mt76_client_command cmd, u8 *data, int len) { struct sk_buff *skb; struct mt76_txwi txwi = {}; struct ieee80211_hdr_3addr hdr = {}; u8 info[] = { 0x00, 0x00, 0x00, wcid - 1, 0x00, 0x00, 0x00, 0x00, }; skb = xone_mt76_alloc_message(sizeof(info) + sizeof(txwi) + sizeof(hdr) + sizeof(u8) * 2 + len, GFP_KERNEL); if (!skb) return -ENOMEM; /* wait for acknowledgment */ txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, IEEE80211_HT_MPDU_DENSITY_4)); txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; txwi.wcid = wcid - 1; txwi.len_ctl = cpu_to_le16(sizeof(hdr) + sizeof(u8) * 2 + len); hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | XONE_MT_WLAN_RESERVED); memcpy(hdr.addr1, addr, ETH_ALEN); memcpy(hdr.addr2, mt->address, ETH_ALEN); memcpy(hdr.addr3, mt->address, ETH_ALEN); skb_put_data(skb, info, sizeof(info)); skb_put_data(skb, &txwi, sizeof(txwi)); skb_put_data(skb, &hdr, sizeof(hdr)); skb_put_u8(skb, XONE_MT_WLAN_RESERVED); skb_put_u8(skb, cmd); if (data) skb_put_data(skb, data, len); return xone_mt76_send_command(mt, skb, 0); } int xone_mt76_set_client_key(struct xone_mt76 *mt, u8 wcid, u8 *key, int len) { u8 iv[] = { 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00 }; __le32 attr = cpu_to_le32(FIELD_PREP(MT_WCID_ATTR_PKEY_MODE, MT_CIPHER_AES_CCMP) | MT_WCID_ATTR_PAIRWISE); int err; if (len != XONE_MT_WCID_KEY_LEN) return -EINVAL; err = xone_mt76_write_burst(mt, MT_WCID_KEY(wcid), key, len); if (err) return err; err = xone_mt76_write_burst(mt, MT_WCID_IV(wcid), iv, sizeof(iv)); if (err) return err; return xone_mt76_write_burst(mt, MT_WCID_ATTR(wcid), &attr, sizeof(attr)); } int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid) { u8 data[] = { wcid - 1, 0x00, 0x00, 0x00 }; u8 addr[ETH_ALEN] = {}; u8 iv[8] = {}; u32 attr = 0; u8 key[XONE_MT_WCID_KEY_LEN] = {}; int err; err = xone_mt76_send_ms_command(mt, XONE_MT_REMOVE_CLIENT, data, sizeof(data)); if (err) return err; err = xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, sizeof(addr)); if (err) return err; err = xone_mt76_write_burst(mt, MT_WCID_IV(wcid), iv, sizeof(iv)); if (err) return err; err = xone_mt76_write_burst(mt, MT_WCID_ATTR(wcid), &attr, sizeof(attr)); if (err) return err; return xone_mt76_write_burst(mt, MT_WCID_KEY(wcid), key, sizeof(key)); } dlundqvist-xone-f2aa9fe/transport/mt76.h000066400000000000000000000045411515500374600204510ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once #include "mt76_defs.h" #define XONE_MT_EP_IN_CMD 0x05 #define XONE_MT_EP_IN_WLAN 0x04 #define XONE_MT_EP_OUT 0x04 #define XONE_MT_NUM_CHANNELS 12 /* 802.11 frame subtype: reserved */ #define XONE_MT_WLAN_RESERVED 0x70 enum xone_mt76_led_mode { XONE_MT_LED_BLINK = 0x00, XONE_MT_LED_ON = 0x01, XONE_MT_LED_OFF = 0x02, }; enum xone_mt76_event { XONE_MT_EVT_BUTTON = 0x04, XONE_MT_EVT_CHANNELS = 0x0a, XONE_MT_EVT_PACKET_RX = 0x0c, XONE_MT_EVT_COREDUMP = 0x0d, XONE_MT_EVT_CLIENT_LOST = 0x0e, }; enum xone_mt76_client_command { XONE_MT_CLIENT_PAIR_REQ = 0x01, XONE_MT_CLIENT_PAIR_RESP = 0x02, XONE_MT_CLIENT_CHANGE_CHAN_REQ = 0x03, XONE_MT_CLIENT_CHANGE_CHAN_RESP = 0x04, XONE_MT_CLIENT_STATISTICS_REQ = 0x05, XONE_MT_CLIENT_STATISTICS_RESP = 0x06, XONE_MT_CLIENT_SCAN_CHAN_REQ = 0x07, XONE_MT_CLIENT_SCAN_CHAN_RESP = 0x08, XONE_MT_CLIENT_ENABLE_ENCRYPTION = 0x10, }; struct xone_mt76_channel { u8 index; u8 band; enum mt76_phy_bandwidth bandwidth; enum mt76_cal_channel_group group; bool scan; u8 power; }; struct xone_mt76 { struct device *dev; struct usb_device *udev; __le32 control_data; u8 address[ETH_ALEN]; struct xone_mt76_channel channels[XONE_MT_NUM_CHANNELS]; struct xone_mt76_channel *channel; }; struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp); void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd); int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode); int xone_mt76_load_firmware(struct xone_mt76 *mt, const struct firmware *fw); int xone_mt76_init_radio(struct xone_mt76 *mt); int xone_mt76_suspend_radio(struct xone_mt76 *mt); int xone_mt76_resume_radio(struct xone_mt76 *mt); int xone_mt76_switch_channel(struct xone_mt76 *mt, struct xone_mt76_channel *chan); int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable); int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr); int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr); int xone_mt76_send_client_command(struct xone_mt76 *mt, u8 wcid, u8 *addr, enum xone_mt76_client_command cmd, u8 *data, int len); int xone_mt76_set_client_key(struct xone_mt76 *mt, u8 wcid, u8 *key, int len); int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid); dlundqvist-xone-f2aa9fe/transport/mt76_defs.h000066400000000000000000000727721515500374600214650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Based on code from the open source mt76 driver with minor modifications. * * Copyright (C) 2021 Severin von Wnuck-Lipinski * * Special thanks to the authors of the mt76 driver: * * Copyright (C) Felix Fietkau * Copyright (C) Lorenzo Bianconi * Copyright (C) Stanislaw Gruszka */ #pragma once #include #define MT_ASIC_VERSION 0x0000 #define MT_CMB_CTRL 0x0020 #define MT_CMB_CTRL_XTAL_RDY BIT(22) #define MT_CMB_CTRL_PLL_LD BIT(23) #define MT_EFUSE_CTRL 0x0024 #define MT_EFUSE_CTRL_AOUT GENMASK(5, 0) #define MT_EFUSE_CTRL_MODE GENMASK(7, 6) #define MT_EFUSE_CTRL_LDO_OFF_TIME GENMASK(13, 8) #define MT_EFUSE_CTRL_LDO_ON_TIME GENMASK(15, 14) #define MT_EFUSE_CTRL_AIN GENMASK(25, 16) #define MT_EFUSE_CTRL_KICK BIT(30) #define MT_EFUSE_CTRL_SEL BIT(31) #define MT_EFUSE_DATA_BASE 0x0028 #define MT_EFUSE_DATA(n) (MT_EFUSE_DATA_BASE + ((n) << 2)) #define MT_COEXCFG0 0x0040 #define MT_COEXCFG0_COEX_EN BIT(0) #define MT_WLAN_FUN_CTRL 0x0080 #define MT_WLAN_FUN_CTRL_WLAN_EN BIT(0) #define MT_WLAN_FUN_CTRL_WLAN_CLK_EN BIT(1) #define MT_WLAN_FUN_CTRL_WLAN_RESET_RF BIT(2) #define MT_COEXCFG3 0x004c #define MT_LDO_CTRL_0 0x006c #define MT_LDO_CTRL_1 0x0070 #define MT_WLAN_FUN_CTRL_CSR_F20M_CKEN BIT(3) #define MT_WLAN_FUN_CTRL_PCIE_CLK_REQ BIT(4) #define MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL BIT(5) #define MT_WLAN_FUN_CTRL_INV_ANT_SEL BIT(6) #define MT_WLAN_FUN_CTRL_WAKE_HOST BIT(7) #define MT_WLAN_FUN_CTRL_THERM_RST BIT(8) #define MT_WLAN_FUN_CTRL_THERM_CKEN BIT(9) #define MT_XO_CTRL0 0x0100 #define MT_XO_CTRL1 0x0104 #define MT_XO_CTRL2 0x0108 #define MT_XO_CTRL3 0x010c #define MT_XO_CTRL4 0x0110 #define MT_XO_CTRL5 0x0114 #define MT_XO_CTRL5_C2_VAL GENMASK(14, 8) #define MT_XO_CTRL6 0x0118 #define MT_XO_CTRL6_C2_CTRL GENMASK(14, 8) #define MT_XO_CTRL7 0x011c #define MT_IOCFG_6 0x0124 #define MT_USB_U3DMA_CFG 0x9018 #define MT_USB_DMA_CFG_RX_BULK_AGG_TOUT GENMASK(7, 0) #define MT_USB_DMA_CFG_RX_BULK_AGG_LMT GENMASK(15, 8) #define MT_USB_DMA_CFG_UDMA_TX_WL_DROP BIT(16) #define MT_USB_DMA_CFG_WAKE_UP_EN BIT(17) #define MT_USB_DMA_CFG_RX_DROP_OR_PAD BIT(18) #define MT_USB_DMA_CFG_TX_CLR BIT(19) #define MT_USB_DMA_CFG_TXOP_HALT BIT(20) #define MT_USB_DMA_CFG_RX_BULK_AGG_EN BIT(21) #define MT_USB_DMA_CFG_RX_BULK_EN BIT(22) #define MT_USB_DMA_CFG_TX_BULK_EN BIT(23) #define MT_USB_DMA_CFG_EP_OUT_VALID GENMASK(29, 24) #define MT_USB_DMA_CFG_RX_BUSY BIT(30) #define MT_USB_DMA_CFG_TX_BUSY BIT(31) #define MT_WLAN_MTC_CTRL 0x010148 #define MT_WLAN_MTC_CTRL_MTCMOS_PWR_UP BIT(0) #define MT_WLAN_MTC_CTRL_PWR_ACK BIT(12) #define MT_WLAN_MTC_CTRL_PWR_ACK_S BIT(13) #define MT_WLAN_MTC_CTRL_BBP_MEM_PD GENMASK(19, 16) #define MT_WLAN_MTC_CTRL_PBF_MEM_PD BIT(20) #define MT_WLAN_MTC_CTRL_FCE_MEM_PD BIT(21) #define MT_WLAN_MTC_CTRL_TSO_MEM_PD BIT(22) #define MT_WLAN_MTC_CTRL_BBP_MEM_RB BIT(24) #define MT_WLAN_MTC_CTRL_PBF_MEM_RB BIT(25) #define MT_WLAN_MTC_CTRL_FCE_MEM_RB BIT(26) #define MT_WLAN_MTC_CTRL_TSO_MEM_RB BIT(27) #define MT_WLAN_MTC_CTRL_STATE_UP BIT(28) #define MT_INT_SOURCE_CSR 0x0200 #define MT_INT_MASK_CSR 0x0204 #define MT_INT_RX_DONE(n) BIT(n) #define MT_INT_RX_DONE_ALL GENMASK(1, 0) #define MT_INT_TX_DONE_ALL GENMASK(13, 4) #define MT_INT_TX_DONE(n) BIT((n) + 4) #define MT_INT_RX_COHERENT BIT(16) #define MT_INT_TX_COHERENT BIT(17) #define MT_INT_ANY_COHERENT BIT(18) #define MT_INT_MCU_CMD BIT(19) #define MT_INT_TBTT BIT(20) #define MT_INT_PRE_TBTT BIT(21) #define MT_INT_TX_STAT BIT(22) #define MT_INT_AUTO_WAKEUP BIT(23) #define MT_INT_GPTIMER BIT(24) #define MT_INT_RXDELAYINT BIT(26) #define MT_INT_TXDELAYINT BIT(27) #define MT_WPDMA_GLO_CFG 0x0208 #define MT_WPDMA_GLO_CFG_TX_DMA_EN BIT(0) #define MT_WPDMA_GLO_CFG_TX_DMA_BUSY BIT(1) #define MT_WPDMA_GLO_CFG_RX_DMA_EN BIT(2) #define MT_WPDMA_GLO_CFG_RX_DMA_BUSY BIT(3) #define MT_WPDMA_GLO_CFG_DMA_BURST_SIZE GENMASK(5, 4) #define MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE BIT(6) #define MT_WPDMA_GLO_CFG_BIG_ENDIAN BIT(7) #define MT_WPDMA_GLO_CFG_HDR_SEG_LEN GENMASK(15, 8) #define MT_WPDMA_GLO_CFG_CLK_GATE_DIS BIT(30) #define MT_WPDMA_GLO_CFG_RX_2B_OFFSET BIT(31) #define MT_WPDMA_RST_IDX 0x020c #define MT_WPDMA_DELAY_INT_CFG 0x0210 #define MT_WMM_AIFSN 0x0214 #define MT_WMM_AIFSN_MASK GENMASK(3, 0) #define MT_WMM_AIFSN_SHIFT(n) ((n) * 4) #define MT_WMM_CWMIN 0x0218 #define MT_WMM_CWMIN_MASK GENMASK(3, 0) #define MT_WMM_CWMIN_SHIFT(n) ((n) * 4) #define MT_WMM_CWMAX 0x021c #define MT_WMM_CWMAX_MASK GENMASK(3, 0) #define MT_WMM_CWMAX_SHIFT(n) ((n) * 4) #define MT_WMM_TXOP_BASE 0x0220 #define MT_WMM_TXOP(n) (MT_WMM_TXOP_BASE + (((n) / 2) << 2)) #define MT_WMM_TXOP_SHIFT(n) (((n) & 1) * 16) #define MT_WMM_TXOP_MASK GENMASK(15, 0) #define MT_FCE_DMA_ADDR 0x0230 #define MT_FCE_DMA_LEN 0x0234 #define MT_USB_DMA_CFG 0x0238 #define MT_TSO_CTRL 0x0250 #define MT_HEADER_TRANS_CTRL_REG 0x0260 #define MT_US_CYC_CFG 0x02a4 #define MT_US_CYC_CNT GENMASK(7, 0) #define MT_TX_RING_BASE 0x0300 #define MT_RX_RING_BASE 0x03c0 #define MT_PBF_SYS_CTRL 0x0400 #define MT_PBF_SYS_CTRL_MCU_RESET BIT(0) #define MT_PBF_SYS_CTRL_DMA_RESET BIT(1) #define MT_PBF_SYS_CTRL_MAC_RESET BIT(2) #define MT_PBF_SYS_CTRL_PBF_RESET BIT(3) #define MT_PBF_SYS_CTRL_ASY_RESET BIT(4) #define MT_PBF_CFG 0x0404 #define MT_PBF_CFG_TX0Q_EN BIT(0) #define MT_PBF_CFG_TX1Q_EN BIT(1) #define MT_PBF_CFG_TX2Q_EN BIT(2) #define MT_PBF_CFG_TX3Q_EN BIT(3) #define MT_PBF_CFG_RX0Q_EN BIT(4) #define MT_PBF_CFG_RX_DROP_EN BIT(8) #define MT_PBF_TX_MAX_PCNT 0x0408 #define MT_PBF_RX_MAX_PCNT 0x040c #define MT_BCN_OFFSET_BASE 0x041c #define MT_BCN_OFFSET(n) (MT_BCN_OFFSET_BASE + ((n) << 2)) #define MT_RXQ_STA 0x0430 #define MT_TXQ_STA 0x0434 #define MT_RF_CSR_CFG 0x0500 #define MT_RF_CSR_CFG_DATA GENMASK(7, 0) #define MT_RF_CSR_CFG_REG_ID GENMASK(14, 8) #define MT_RF_CSR_CFG_REG_BANK GENMASK(17, 15) #define MT_RF_CSR_CFG_WR BIT(30) #define MT_RF_CSR_CFG_KICK BIT(31) #define MT_RF_BYPASS_0 0x0504 #define MT_RF_BYPASS_1 0x0508 #define MT_RF_SETTING_0 0x050c #define MT_RF_MISC 0x0518 #define MT_RF_DATA_WRITE 0x0524 #define MT_RF_CTRL 0x0528 #define MT_RF_CTRL_ADDR GENMASK(11, 0) #define MT_RF_CTRL_WRITE BIT(12) #define MT_RF_CTRL_BUSY BIT(13) #define MT_RF_CTRL_IDX BIT(16) #define MT_RF_DATA_READ 0x052c #define MT_COM_REG0 0x0730 #define MT_COM_REG1 0x0734 #define MT_COM_REG2 0x0738 #define MT_COM_REG3 0x073c #define MT_LED_CTRL 0x0770 #define MT_LED_CTRL_REPLAY(n) BIT(0 + (8 * (n))) #define MT_LED_CTRL_POLARITY(n) BIT(1 + (8 * (n))) #define MT_LED_CTRL_TX_BLINK_MODE(n) BIT(2 + (8 * (n))) #define MT_LED_CTRL_KICK(n) BIT(7 + (8 * (n))) #define MT_LED_TX_BLINK_0 0x0774 #define MT_LED_TX_BLINK_1 0x0778 #define MT_LED_S0_BASE 0x077c #define MT_LED_S0(n) (MT_LED_S0_BASE + 8 * (n)) #define MT_LED_S1_BASE 0x0780 #define MT_LED_S1(n) (MT_LED_S1_BASE + 8 * (n)) #define MT_LED_STATUS_OFF GENMASK(31, 24) #define MT_LED_STATUS_ON GENMASK(23, 16) #define MT_LED_STATUS_DURATION GENMASK(15, 8) #define MT_FCE_PSE_CTRL 0x0800 #define MT_FCE_PARAMETERS 0x0804 #define MT_FCE_CSO 0x0808 #define MT_FCE_L2_STUFF 0x080c #define MT_FCE_L2_STUFF_HT_L2_EN BIT(0) #define MT_FCE_L2_STUFF_QOS_L2_EN BIT(1) #define MT_FCE_L2_STUFF_RX_STUFF_EN BIT(2) #define MT_FCE_L2_STUFF_TX_STUFF_EN BIT(3) #define MT_FCE_L2_STUFF_WR_MPDU_LEN_EN BIT(4) #define MT_FCE_L2_STUFF_MVINV_BSWAP BIT(5) #define MT_FCE_L2_STUFF_TS_CMD_QSEL_EN GENMASK(15, 8) #define MT_FCE_L2_STUFF_TS_LEN_EN GENMASK(23, 16) #define MT_FCE_L2_STUFF_OTHER_PORT GENMASK(25, 24) #define MT_FCE_WLAN_FLOW_CONTROL1 0x0824 #define MT_TX_CPU_FROM_FCE_BASE_PTR 0x09a0 #define MT_TX_CPU_FROM_FCE_MAX_COUNT 0x09a4 #define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 #define MT_FCE_PDMA_GLOBAL_CONF 0x09c4 #define MT_FCE_SKIP_FS 0x0a6c #define MT_PAUSE_ENABLE_CONTROL1 0x0a38 #define MT_MAC_CSR0 0x1000 #define MT_MAC_SYS_CTRL 0x1004 #define MT_MAC_SYS_CTRL_RESET_CSR BIT(0) #define MT_MAC_SYS_CTRL_RESET_BBP BIT(1) #define MT_MAC_SYS_CTRL_ENABLE_TX BIT(2) #define MT_MAC_SYS_CTRL_ENABLE_RX BIT(3) #define MT_MAC_ADDR_DW0 0x1008 #define MT_MAC_ADDR_DW1 0x100c #define MT_MAC_ADDR_DW1_U2ME_MASK GENMASK(23, 16) #define MT_MAC_BSSID_DW0 0x1010 #define MT_MAC_BSSID_DW1 0x1014 #define MT_MAC_BSSID_DW1_ADDR GENMASK(15, 0) #define MT_MAC_BSSID_DW1_MBSS_MODE GENMASK(17, 16) #define MT_MAC_BSSID_DW1_MBEACON_N GENMASK(20, 18) #define MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT BIT(21) #define MT_MAC_BSSID_DW1_MBSS_MODE_B2 BIT(22) #define MT_MAC_BSSID_DW1_MBEACON_N_B3 BIT(23) #define MT_MAC_BSSID_DW1_MBSS_IDX_BYTE GENMASK(26, 24) #define MT_MAX_LEN_CFG 0x1018 #define MT_MAX_LEN_CFG_AMPDU GENMASK(13, 12) #define MT_LED_CFG 0x102c #define MT_AMPDU_MAX_LEN_20M1S 0x1030 #define MT_AMPDU_MAX_LEN_20M2S 0x1034 #define MT_AMPDU_MAX_LEN_40M1S 0x1038 #define MT_AMPDU_MAX_LEN_40M2S 0x103c #define MT_AMPDU_MAX_LEN 0x1040 #define MT_WCID_DROP_BASE 0x106c #define MT_WCID_DROP(n) (MT_WCID_DROP_BASE + ((n) >> 5) * 4) #define MT_WCID_DROP_MASK(n) BIT((n) % 32) #define MT_BCN_BYPASS_MASK 0x108c #define MT_MAC_APC_BSSID_BASE 0x1090 #define MT_MAC_APC_BSSID_L(n) (MT_MAC_APC_BSSID_BASE + ((n) * 8)) #define MT_MAC_APC_BSSID_H(n) (MT_MAC_APC_BSSID_BASE + ((n) * 8 + 4)) #define MT_MAC_APC_BSSID_H_ADDR GENMASK(15, 0) #define MT_MAC_APC_BSSID0_H_EN BIT(16) #define MT_XIFS_TIME_CFG 0x1100 #define MT_XIFS_TIME_CFG_CCK_SIFS GENMASK(7, 0) #define MT_XIFS_TIME_CFG_OFDM_SIFS GENMASK(15, 8) #define MT_XIFS_TIME_CFG_OFDM_XIFS GENMASK(19, 16) #define MT_XIFS_TIME_CFG_EIFS GENMASK(28, 20) #define MT_XIFS_TIME_CFG_BB_RXEND_EN BIT(29) #define MT_BKOFF_SLOT_CFG 0x1104 #define MT_BKOFF_SLOT_CFG_SLOTTIME GENMASK(7, 0) #define MT_BKOFF_SLOT_CFG_CC_DELAY GENMASK(11, 8) #define MT_CH_TIME_CFG 0x110c #define MT_CH_TIME_CFG_TIMER_EN BIT(0) #define MT_CH_TIME_CFG_TX_AS_BUSY BIT(1) #define MT_CH_TIME_CFG_RX_AS_BUSY BIT(2) #define MT_CH_TIME_CFG_NAV_AS_BUSY BIT(3) #define MT_CH_TIME_CFG_EIFS_AS_BUSY BIT(4) #define MT_CH_TIME_CFG_MDRDY_CNT_EN BIT(5) #define MT_CH_CCA_RC_EN BIT(6) #define MT_CH_TIME_CFG_CH_TIMER_CLR GENMASK(9, 8) #define MT_CH_TIME_CFG_MDRDY_CLR GENMASK(11, 10) #define MT_PBF_LIFE_TIMER 0x1110 #define MT_BEACON_TIME_CFG 0x1114 #define MT_BEACON_TIME_CFG_INTVAL GENMASK(15, 0) #define MT_BEACON_TIME_CFG_TIMER_EN BIT(16) #define MT_BEACON_TIME_CFG_SYNC_MODE GENMASK(18, 17) #define MT_BEACON_TIME_CFG_TBTT_EN BIT(19) #define MT_BEACON_TIME_CFG_BEACON_TX BIT(20) #define MT_BEACON_TIME_CFG_TSF_COMP GENMASK(31, 24) #define MT_TBTT_SYNC_CFG 0x1118 #define MT_TSF_TIMER_DW0 0x111c #define MT_TSF_TIMER_DW1 0x1120 #define MT_TBTT_TIMER 0x1124 #define MT_TBTT_TIMER_VAL GENMASK(16, 0) #define MT_INT_TIMER_CFG 0x1128 #define MT_INT_TIMER_CFG_PRE_TBTT GENMASK(15, 0) #define MT_INT_TIMER_CFG_GP_TIMER GENMASK(31, 16) #define MT_INT_TIMER_EN 0x112c #define MT_INT_TIMER_EN_PRE_TBTT_EN BIT(0) #define MT_INT_TIMER_EN_GP_TIMER_EN BIT(1) #define MT_CH_IDLE 0x1130 #define MT_CH_BUSY 0x1134 #define MT_EXT_CH_BUSY 0x1138 #define MT_ED_CCA_TIMER 0x1140 #define MT_MAC_STATUS 0x1200 #define MT_MAC_STATUS_TX BIT(0) #define MT_MAC_STATUS_RX BIT(1) #define MT_PWR_PIN_CFG 0x1204 #define MT_AUX_CLK_CFG 0x120c #define MT_BB_PA_MODE_CFG0 0x1214 #define MT_BB_PA_MODE_CFG1 0x1218 #define MT_RF_PA_MODE_CFG0 0x121c #define MT_RF_PA_MODE_CFG1 0x1220 #define MT_RF_PA_MODE_ADJ0 0x1228 #define MT_RF_PA_MODE_ADJ1 0x122c #define MT_DACCLK_EN_DLY_CFG 0x1264 #define MT_EDCA_CFG_BASE 0x1300 #define MT_EDCA_CFG_AC(n) (MT_EDCA_CFG_BASE + ((n) << 2)) #define MT_EDCA_CFG_TXOP GENMASK(7, 0) #define MT_EDCA_CFG_AIFSN GENMASK(11, 8) #define MT_EDCA_CFG_CWMIN GENMASK(15, 12) #define MT_EDCA_CFG_CWMAX GENMASK(19, 16) #define MT_TX_PWR_CFG_0 0x1314 #define MT_TX_PWR_CFG_1 0x1318 #define MT_TX_PWR_CFG_2 0x131c #define MT_TX_PWR_CFG_3 0x1320 #define MT_TX_PWR_CFG_4 0x1324 #define MT_TX_PIN_CFG 0x1328 #define MT_TX_PIN_CFG_TXANT GENMASK(3, 0) #define MT_TX_PIN_CFG_RXANT GENMASK(11, 8) #define MT_TX_PIN_RFTR_EN BIT(16) #define MT_TX_PIN_TRSW_EN BIT(18) #define MT_TX_BAND_CFG 0x132c #define MT_TX_BAND_CFG_UPPER_40M BIT(0) #define MT_TX_BAND_CFG_5G BIT(1) #define MT_TX_BAND_CFG_2G BIT(2) #define MT_HT_FBK_TO_LEGACY 0x1384 #define MT_TX_MPDU_ADJ_INT 0x1388 #define MT_TX_PWR_CFG_7 0x13d4 #define MT_TX_PWR_CFG_8 0x13d8 #define MT_TX_PWR_CFG_9 0x13dc #define MT_TX_SW_CFG0 0x1330 #define MT_TX_SW_CFG1 0x1334 #define MT_TX_SW_CFG2 0x1338 #define MT_TXOP_CTRL_CFG 0x1340 #define MT_TXOP_TRUN_EN GENMASK(5, 0) #define MT_TXOP_EXT_CCA_DLY GENMASK(15, 8) #define MT_TXOP_ED_CCA_EN BIT(20) #define MT_TX_RTS_CFG 0x1344 #define MT_TX_RTS_CFG_RETRY_LIMIT GENMASK(7, 0) #define MT_TX_RTS_CFG_THRESH GENMASK(23, 8) #define MT_TX_RTS_FALLBACK BIT(24) #define MT_TX_TIMEOUT_CFG 0x1348 #define MT_TX_TIMEOUT_CFG_ACKTO GENMASK(15, 8) #define MT_TX_RETRY_CFG 0x134c #define MT_TX_LINK_CFG 0x1350 #define MT_TX_CFACK_EN BIT(12) #define MT_VHT_HT_FBK_CFG0 0x1354 #define MT_VHT_HT_FBK_CFG1 0x1358 #define MT_LG_FBK_CFG0 0x135c #define MT_LG_FBK_CFG1 0x1360 #define MT_PROT_CFG_RATE GENMASK(15, 0) #define MT_PROT_CFG_CTRL GENMASK(17, 16) #define MT_PROT_CFG_NAV GENMASK(19, 18) #define MT_PROT_CFG_TXOP_ALLOW GENMASK(25, 20) #define MT_PROT_CFG_RTS_THRESH BIT(26) #define MT_CCK_PROT_CFG 0x1364 #define MT_OFDM_PROT_CFG 0x1368 #define MT_MM20_PROT_CFG 0x136c #define MT_MM40_PROT_CFG 0x1370 #define MT_GF20_PROT_CFG 0x1374 #define MT_GF40_PROT_CFG 0x1378 #define MT_PROT_RATE GENMASK(15, 0) #define MT_PROT_CTRL_RTS_CTS BIT(16) #define MT_PROT_CTRL_CTS2SELF BIT(17) #define MT_PROT_NAV_SHORT BIT(18) #define MT_PROT_NAV_LONG BIT(19) #define MT_PROT_TXOP_ALLOW_CCK BIT(20) #define MT_PROT_TXOP_ALLOW_OFDM BIT(21) #define MT_PROT_TXOP_ALLOW_MM20 BIT(22) #define MT_PROT_TXOP_ALLOW_MM40 BIT(23) #define MT_PROT_TXOP_ALLOW_GF20 BIT(24) #define MT_PROT_TXOP_ALLOW_GF40 BIT(25) #define MT_PROT_RTS_THR_EN BIT(26) #define MT_PROT_RATE_CCK_11 0x0003 #define MT_PROT_RATE_OFDM_6 0x2000 #define MT_PROT_RATE_OFDM_24 0x2004 #define MT_PROT_RATE_DUP_OFDM_24 0x2084 #define MT_PROT_RATE_SGI_OFDM_24 0x2104 #define MT_PROT_TXOP_ALLOW_ALL GENMASK(25, 20) #define MT_PROT_TXOP_ALLOW_BW20 (MT_PROT_TXOP_ALLOW_ALL & \ ~MT_PROT_TXOP_ALLOW_MM40 & \ ~MT_PROT_TXOP_ALLOW_GF40) #define MT_EXP_ACK_TIME 0x1380 #define MT_TX_PWR_CFG_0_EXT 0x1390 #define MT_TX_PWR_CFG_1_EXT 0x1394 #define MT_TX_FBK_LIMIT 0x1398 #define MT_TX_FBK_LIMIT_MPDU_FBK GENMASK(7, 0) #define MT_TX_FBK_LIMIT_AMPDU_FBK GENMASK(15, 8) #define MT_TX_FBK_LIMIT_MPDU_UP_CLEAR BIT(16) #define MT_TX_FBK_LIMIT_AMPDU_UP_CLEAR BIT(17) #define MT_TX_FBK_LIMIT_RATE_LUT BIT(18) #define MT_TX0_RF_GAIN_CORR 0x13a0 #define MT_TX1_RF_GAIN_CORR 0x13a4 #define MT_TX0_RF_GAIN_ATTEN 0x13a8 #define MT_TX_ALC_CFG_0 0x13b0 #define MT_TX_ALC_CFG_0_CH_INIT_0 GENMASK(5, 0) #define MT_TX_ALC_CFG_0_CH_INIT_1 GENMASK(13, 8) #define MT_TX_ALC_CFG_0_LIMIT_0 GENMASK(21, 16) #define MT_TX_ALC_CFG_0_LIMIT_1 GENMASK(29, 24) #define MT_TX_ALC_CFG_1 0x13b4 #define MT_TX_ALC_CFG_1_TEMP_COMP GENMASK(5, 0) #define MT_TX_ALC_CFG_2 0x13a8 #define MT_TX_ALC_CFG_2_TEMP_COMP GENMASK(5, 0) #define MT_TX_ALC_CFG_3 0x13ac #define MT_TX_ALC_CFG_4 0x13c0 #define MT_TX_ALC_CFG_4_LOWGAIN_CH_EN BIT(31) #define MT_TX_ALC_VGA3 0x13c8 #define MT_TX_PROT_CFG6 0x13e0 #define MT_TX_PROT_CFG7 0x13e4 #define MT_TX_PROT_CFG8 0x13e8 #define MT_PIFS_TX_CFG 0x13ec #define MT_RX_FILTR_CFG 0x1400 #define MT_RX_FILTR_CFG_CRC_ERR BIT(0) #define MT_RX_FILTR_CFG_PHY_ERR BIT(1) #define MT_RX_FILTR_CFG_PROMISC BIT(2) #define MT_RX_FILTR_CFG_OTHER_BSS BIT(3) #define MT_RX_FILTR_CFG_VER_ERR BIT(4) #define MT_RX_FILTR_CFG_MCAST BIT(5) #define MT_RX_FILTR_CFG_BCAST BIT(6) #define MT_RX_FILTR_CFG_DUP BIT(7) #define MT_RX_FILTR_CFG_CFACK BIT(8) #define MT_RX_FILTR_CFG_CFEND BIT(9) #define MT_RX_FILTR_CFG_ACK BIT(10) #define MT_RX_FILTR_CFG_CTS BIT(11) #define MT_RX_FILTR_CFG_RTS BIT(12) #define MT_RX_FILTR_CFG_PSPOLL BIT(13) #define MT_RX_FILTR_CFG_BA BIT(14) #define MT_RX_FILTR_CFG_BAR BIT(15) #define MT_RX_FILTR_CFG_CTRL_RSV BIT(16) #define MT_AUTO_RSP_CFG 0x1404 #define MT_AUTO_RSP_EN BIT(0) #define MT_AUTO_RSP_PREAMB_SHORT BIT(4) #define MT_LEGACY_BASIC_RATE 0x1408 #define MT_HT_BASIC_RATE 0x140c #define MT_HT_CTRL_CFG 0x1410 #define MT_RX_PARSER_CFG 0x1418 #define MT_RX_PARSER_RX_SET_NAV_ALL BIT(0) #define MT_EXT_CCA_CFG 0x141c #define MT_EXT_CCA_CFG_CCA0 GENMASK(1, 0) #define MT_EXT_CCA_CFG_CCA1 GENMASK(3, 2) #define MT_EXT_CCA_CFG_CCA2 GENMASK(5, 4) #define MT_EXT_CCA_CFG_CCA3 GENMASK(7, 6) #define MT_EXT_CCA_CFG_CCA_MASK GENMASK(11, 8) #define MT_EXT_CCA_CFG_ED_CCA_MASK GENMASK(15, 12) #define MT_TX_SW_CFG3 0x1478 #define MT_PN_PAD_MODE 0x150c #define MT_TXOP_HLDR_ET 0x1608 #define MT_TXOP_HLDR_TX40M_BLK_EN BIT(1) #define MT_PROT_AUTO_TX_CFG 0x1648 #define MT_PROT_AUTO_TX_CFG_PROT_PADJ GENMASK(11, 8) #define MT_PROT_AUTO_TX_CFG_AUTO_PADJ GENMASK(27, 24) #define MT_RX_STAT_0 0x1700 #define MT_RX_STAT_0_CRC_ERRORS GENMASK(15, 0) #define MT_RX_STAT_0_PHY_ERRORS GENMASK(31, 16) #define MT_RX_STAT_1 0x1704 #define MT_RX_STAT_1_CCA_ERRORS GENMASK(15, 0) #define MT_RX_STAT_1_PLCP_ERRORS GENMASK(31, 16) #define MT_RX_STAT_2 0x1708 #define MT_RX_STAT_2_DUP_ERRORS GENMASK(15, 0) #define MT_RX_STAT_2_OVERFLOW_ERRORS GENMASK(31, 16) #define MT_TX_STA_0 0x170c #define MT_TX_STA_1 0x1710 #define MT_TX_STA_2 0x1714 #define MT_TX_STAT_FIFO 0x1718 #define MT_TX_STAT_FIFO_VALID BIT(0) #define MT_TX_STAT_FIFO_SUCCESS BIT(5) #define MT_TX_STAT_FIFO_AGGR BIT(6) #define MT_TX_STAT_FIFO_ACKREQ BIT(7) #define MT_TX_STAT_FIFO_WCID GENMASK(15, 8) #define MT_TX_STAT_FIFO_RATE GENMASK(31, 16) #define MT_TX_AGG_STAT 0x171c #define MT_TX_AGG_CNT_BASE0 0x1720 #define MT_MPDU_DENSITY_CNT 0x1740 #define MT_TX_AGG_CNT_BASE1 0x174c #define MT_TX_STAT_FIFO_EXT 0x1798 #define MT_TX_STAT_FIFO_EXT_RETRY GENMASK(7, 0) #define MT_TX_STAT_FIFO_EXT_PKTID GENMASK(15, 8) #define MT_WCID_TX_RATE_BASE 0x1c00 #define MT_WCID_TX_RATE(i) (MT_WCID_TX_RATE_BASE + ((i) << 3)) #define MT_BBP_CORE_BASE 0x2000 #define MT_BBP_IBI_BASE 0x2100 #define MT_BBP_AGC_BASE 0x2300 #define MT_BBP_TXC_BASE 0x2400 #define MT_BBP_RXC_BASE 0x2500 #define MT_BBP_TXO_BASE 0x2600 #define MT_BBP_TXBE_BASE 0x2700 #define MT_BBP_RXFE_BASE 0x2800 #define MT_BBP_RXO_BASE 0x2900 #define MT_BBP_DFS_BASE 0x2a00 #define MT_BBP_TR_BASE 0x2b00 #define MT_BBP_CAL_BASE 0x2c00 #define MT_BBP_DSC_BASE 0x2e00 #define MT_BBP_PFMU_BASE 0x2f00 #define MT_BBP(type, n) (MT_BBP_##type##_BASE + ((n) << 2)) #define MT_BBP_CORE_R1_BW GENMASK(4, 3) #define MT_BBP_AGC_R0_CTRL_CHAN GENMASK(9, 8) #define MT_BBP_AGC_R0_BW GENMASK(14, 12) /* AGC, R4/R5 */ #define MT_BBP_AGC_LNA_HIGH_GAIN GENMASK(21, 16) #define MT_BBP_AGC_LNA_MID_GAIN GENMASK(13, 8) #define MT_BBP_AGC_LNA_LOW_GAIN GENMASK(5, 0) /* AGC, R6/R7 */ #define MT_BBP_AGC_LNA_ULOW_GAIN GENMASK(5, 0) /* AGC, R8/R9 */ #define MT_BBP_AGC_LNA_GAIN_MODE GENMASK(7, 6) #define MT_BBP_AGC_GAIN GENMASK(14, 8) #define MT_BBP_AGC20_RSSI0 GENMASK(7, 0) #define MT_BBP_AGC20_RSSI1 GENMASK(15, 8) #define MT_BBP_TXBE_R0_CTRL_CHAN GENMASK(1, 0) #define MT_WCID_ADDR_BASE 0x1800 #define MT_WCID_ADDR(n) (MT_WCID_ADDR_BASE + (n) * 8) #define MT_SRAM_BASE 0x4000 #define MT_WCID_KEY_BASE 0x8000 #define MT_WCID_KEY(n) (MT_WCID_KEY_BASE + (n) * 32) #define MT_WCID_IV_BASE 0xa000 #define MT_WCID_IV(n) (MT_WCID_IV_BASE + (n) * 8) #define MT_WCID_ATTR_BASE 0xa800 #define MT_WCID_ATTR(n) (MT_WCID_ATTR_BASE + (n) * 4) #define MT_WCID_ATTR_PAIRWISE BIT(0) #define MT_WCID_ATTR_PKEY_MODE GENMASK(3, 1) #define MT_WCID_ATTR_BSS_IDX GENMASK(6, 4) #define MT_WCID_ATTR_RXWI_UDF GENMASK(9, 7) #define MT_WCID_ATTR_PKEY_MODE_EXT BIT(10) #define MT_WCID_ATTR_BSS_IDX_EXT BIT(11) #define MT_WCID_ATTR_WAPI_MCBC BIT(15) #define MT_WCID_ATTR_WAPI_KEYID GENMASK(31, 24) #define MT_SKEY_BASE_0 0xac00 #define MT_SKEY_BASE_1 0xb400 #define MT_SKEY_0(bss, idx) (MT_SKEY_BASE_0 + (4 * (bss) + (idx)) * 32) #define MT_SKEY_1(bss, idx) (MT_SKEY_BASE_1 + (4 * ((bss) & 7) + (idx)) * 32) #define MT_SKEY_MODE_BASE_0 0xb000 #define MT_SKEY_MODE_BASE_1 0xb3f0 #define MT_SKEY_MODE_0(bss) (MT_SKEY_MODE_BASE_0 + (((bss) / 2) << 2)) #define MT_SKEY_MODE_1(bss) (MT_SKEY_MODE_BASE_1 + ((((bss) & 7) / 2) << 2)) #define MT_SKEY_MODE_MASK GENMASK(3, 0) #define MT_SKEY_MODE_SHIFT(bss, idx) (4 * ((idx) + 4 * ((bss) & 1))) #define MT_BEACON_BASE 0xc000 #define MT_TEMP_SENSOR 0x01d000 #define MT_TEMP_SENSOR_VAL GENMASK(6, 0) #define MT_MCU_RESET_CTL 0x070c #define MT_MCU_INT_LEVEL 0x0718 #define MT_MCU_COM_REG0 0x0730 #define MT_MCU_COM_REG1 0x0734 #define MT_MCU_COM_REG2 0x0738 #define MT_MCU_COM_REG3 0x073c #define MT_MCU_MEMMAP_WLAN 0x410000 #define MT_TXD_INFO_LEN GENMASK(15, 0) #define MT_TXD_INFO_NEXT_VLD BIT(16) #define MT_TXD_INFO_TX_BURST BIT(17) #define MT_TXD_INFO_80211 BIT(19) #define MT_TXD_INFO_TSO BIT(20) #define MT_TXD_INFO_CSO BIT(21) #define MT_TXD_INFO_WIV BIT(24) #define MT_TXD_INFO_QSEL GENMASK(26, 25) #define MT_TXD_INFO_DPORT GENMASK(29, 27) #define MT_TXD_INFO_TYPE GENMASK(31, 30) #define MT_RX_FCE_INFO_LEN GENMASK(13, 0) #define MT_RX_FCE_INFO_SELF_GEN BIT(15) #define MT_RX_FCE_INFO_CMD_SEQ GENMASK(19, 16) #define MT_RX_FCE_INFO_EVT_TYPE GENMASK(23, 20) #define MT_RX_FCE_INFO_PCIE_INTR BIT(24) #define MT_RX_FCE_INFO_QSEL GENMASK(26, 25) #define MT_RX_FCE_INFO_D_PORT GENMASK(29, 27) #define MT_RX_FCE_INFO_TYPE GENMASK(31, 30) #define MT_MCU_MSG_LEN GENMASK(15, 0) #define MT_MCU_MSG_CMD_SEQ GENMASK(19, 16) #define MT_MCU_MSG_CMD_TYPE GENMASK(26, 20) #define MT_MCU_MSG_PORT GENMASK(29, 27) #define MT_MCU_MSG_TYPE GENMASK(31, 30) #define MT_MCU_MSG_TYPE_CMD BIT(30) #define MT_FCE_DMA_ADDR 0x0230 #define MT_FCE_DMA_LEN 0x0234 #define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 #define MT_PKTID_RATE GENMASK(4, 0) #define MT_PKTID_AC GENMASK(6, 5) #define MT_RXINFO_BA BIT(0) #define MT_RXINFO_DATA BIT(1) #define MT_RXINFO_NULL BIT(2) #define MT_RXINFO_FRAG BIT(3) #define MT_RXINFO_UNICAST BIT(4) #define MT_RXINFO_MULTICAST BIT(5) #define MT_RXINFO_BROADCAST BIT(6) #define MT_RXINFO_MYBSS BIT(7) #define MT_RXINFO_CRCERR BIT(8) #define MT_RXINFO_ICVERR BIT(9) #define MT_RXINFO_MICERR BIT(10) #define MT_RXINFO_AMSDU BIT(11) #define MT_RXINFO_HTC BIT(12) #define MT_RXINFO_RSSI BIT(13) #define MT_RXINFO_L2PAD BIT(14) #define MT_RXINFO_AMPDU BIT(15) #define MT_RXINFO_DECRYPT BIT(16) #define MT_RXINFO_BSSIDX3 BIT(17) #define MT_RXINFO_WAPI_KEY BIT(18) #define MT_RXINFO_PN_LEN GENMASK(21, 19) #define MT_RXINFO_SW_FTYPE0 BIT(22) #define MT_RXINFO_SW_FTYPE1 BIT(23) #define MT_RXINFO_PROBE_RESP BIT(24) #define MT_RXINFO_BEACON BIT(25) #define MT_RXINFO_DISASSOC BIT(26) #define MT_RXINFO_DEAUTH BIT(27) #define MT_RXINFO_ACTION BIT(28) #define MT_RXINFO_TCP_SUM_ERR BIT(30) #define MT_RXINFO_IP_SUM_ERR BIT(31) #define MT_RXWI_CTL_WCID GENMASK(7, 0) #define MT_RXWI_CTL_KEY_IDX GENMASK(9, 8) #define MT_RXWI_CTL_BSS_IDX GENMASK(12, 10) #define MT_RXWI_CTL_UDF GENMASK(15, 13) #define MT_RXWI_CTL_MPDU_LEN GENMASK(29, 16) #define MT_RXWI_CTL_EOF BIT(31) #define MT_RXWI_TID GENMASK(3, 0) #define MT_RXWI_SN GENMASK(15, 4) #define MT_RXWI_RATE_INDEX GENMASK(5, 0) #define MT_RXWI_RATE_LDPC BIT(6) #define MT_RXWI_RATE_BW GENMASK(8, 7) #define MT_RXWI_RATE_SGI BIT(9) #define MT_RXWI_RATE_STBC BIT(10) #define MT_RXWI_RATE_LDPC_EXSYM BIT(11) #define MT_RXWI_RATE_PHY GENMASK(15, 13) #define MT_RATE_INDEX_VHT_IDX GENMASK(3, 0) #define MT_RATE_INDEX_VHT_NSS GENMASK(5, 4) #define MT_TX_PWR_ADJ GENMASK(3, 0) #define MT_TXWI_FLAGS_FRAG BIT(0) #define MT_TXWI_FLAGS_MMPS BIT(1) #define MT_TXWI_FLAGS_CFACK BIT(2) #define MT_TXWI_FLAGS_TS BIT(3) #define MT_TXWI_FLAGS_AMPDU BIT(4) #define MT_TXWI_FLAGS_MPDU_DENSITY GENMASK(7, 5) #define MT_TXWI_FLAGS_TXOP GENMASK(9, 8) #define MT_TXWI_FLAGS_NDPS BIT(10) #define MT_TXWI_FLAGS_RTSBWSIG BIT(11) #define MT_TXWI_FLAGS_NDP_BW GENMASK(13, 12) #define MT_TXWI_FLAGS_SOUND BIT(14) #define MT_TXWI_FLAGS_TX_RATE_LUT BIT(15) #define MT_TXWI_ACK_CTL_REQ BIT(0) #define MT_TXWI_ACK_CTL_NSEQ BIT(1) #define MT_TXWI_ACK_CTL_BA_WINDOW GENMASK(7, 2) #define MT_EE_ANTENNA_DUAL BIT(15) #define MT_EE_NIC_CONF_0_RX_PATH GENMASK(3, 0) #define MT_EE_NIC_CONF_0_TX_PATH GENMASK(7, 4) #define MT_EE_NIC_CONF_0_PA_TYPE GENMASK(9, 8) #define MT_EE_NIC_CONF_0_PA_INT_2G BIT(8) #define MT_EE_NIC_CONF_0_PA_INT_5G BIT(9) #define MT_EE_NIC_CONF_0_PA_IO_CURRENT BIT(10) #define MT_EE_NIC_CONF_0_BOARD_TYPE GENMASK(13, 12) #define MT_EE_NIC_CONF_1_HW_RF_CTRL BIT(0) #define MT_EE_NIC_CONF_1_TEMP_TX_ALC BIT(1) #define MT_EE_NIC_CONF_1_LNA_EXT_2G BIT(2) #define MT_EE_NIC_CONF_1_LNA_EXT_5G BIT(3) #define MT_EE_NIC_CONF_1_TX_ALC_EN BIT(13) #define MT_EE_NIC_CONF_2_ANT_OPT BIT(3) #define MT_EE_NIC_CONF_2_ANT_DIV BIT(4) #define MT_EE_NIC_CONF_2_XTAL_OPTION GENMASK(10, 9) #define MT_VEND_TYPE_CFG BIT(30) #define MT_CMD_HDR_LEN 4 enum mt76_vendor_req { MT_VEND_DEV_MODE = 0x01, MT_VEND_WRITE = 0x02, MT_VEND_POWER_ON = 0x04, MT_VEND_MULTI_WRITE = 0x06, MT_VEND_MULTI_READ = 0x07, MT_VEND_READ_EEPROM = 0x09, MT_VEND_WRITE_FCE = 0x42, MT_VEND_WRITE_CFG = 0x46, MT_VEND_READ_CFG = 0x47, MT_VEND_READ_EXT = 0x63, MT_VEND_WRITE_EXT = 0x66, MT_VEND_FEATURE_SET = 0x91, }; enum mt76_dma_msg_port { MT_WLAN_PORT, MT_CPU_RX_PORT, MT_CPU_TX_PORT, MT_HOST_PORT, MT_VIRTUAL_CPU_RX_PORT, MT_VIRTUAL_CPU_TX_PORT, MT_DISCARD, }; enum mt76_mcu_cmd { MT_CMD_FUN_SET_OP = 1, MT_CMD_LOAD_CR = 2, MT_CMD_INIT_GAIN_OP = 3, MT_CMD_DYNC_VGA_OP = 6, MT_CMD_TDLS_CH_SW = 7, MT_CMD_BURST_WRITE = 8, MT_CMD_READ_MODIFY_WRITE = 9, MT_CMD_RANDOM_READ = 10, MT_CMD_BURST_READ = 11, MT_CMD_RANDOM_WRITE = 12, MT_CMD_LED_MODE_OP = 16, MT_CMD_POWER_SAVING_OP = 20, MT_CMD_WOW_CONFIG = 21, MT_CMD_WOW_QUERY = 22, MT_CMD_WOW_FEATURE = 24, MT_CMD_CARRIER_DETECT_OP = 28, MT_CMD_RADOR_DETECT_OP = 29, MT_CMD_SWITCH_CHANNEL_OP = 30, MT_CMD_CALIBRATION_OP = 31, MT_CMD_BEACON_OP = 32, MT_CMD_ANTENNA_OP = 33, }; enum mt76_mcu_function { MT_Q_SELECT = 1, MT_BW_SETTING = 2, MT_USB2_SW_DISCONNECT = 2, MT_USB3_SW_DISCONNECT = 3, MT_LOG_FW_DEBUG_MSG = 4, MT_GET_FW_VERSION = 5, }; enum mt76_mcu_cr_mode { MT_RF_CR, MT_BBP_CR, MT_RF_BBP_CR, MT_HL_TEMP_CR_UPDATE, }; enum mt76_mcu_power_mode { MT_RADIO_OFF = 0x30, MT_RADIO_ON = 0x31, MT_RADIO_OFF_AUTO_WAKEUP = 0x32, MT_RADIO_OFF_ADVANCE = 0x33, MT_RADIO_ON_ADVANCE = 0x34, }; enum mt76_mcu_calibration { MT_MCU_CAL_R = 1, MT_MCU_CAL_TEMP_SENSOR, MT_MCU_CAL_RXDCOC, MT_MCU_CAL_RC, MT_MCU_CAL_SX_LOGEN, MT_MCU_CAL_LC, MT_MCU_CAL_TX_LOFT, MT_MCU_CAL_TXIQ, MT_MCU_CAL_TSSI, MT_MCU_CAL_TSSI_COMP, MT_MCU_CAL_DPD, MT_MCU_CAL_RXIQC_FI, MT_MCU_CAL_RXIQC_FD, MT_MCU_CAL_PWRON, MT_MCU_CAL_TX_SHAPING, }; enum mt76_eeprom_mode { MT_EE_READ, MT_EE_PHYSICAL_READ, }; enum mt76_eeprom_field { MT_EE_CHIP_ID = 0x0000, MT_EE_VERSION = 0x0002, MT_EE_MAC_ADDR = 0x0004, MT_EE_PCI_ID = 0x000a, MT_EE_ANTENNA = 0x0022, MT_EE_CFG1_INIT = 0x0024, MT_EE_NIC_CONF_0 = 0x0034, MT_EE_NIC_CONF_1 = 0x0036, MT_EE_COUNTRY_REGION_5GHZ = 0x0038, MT_EE_COUNTRY_REGION_2GHZ = 0x0039, MT_EE_FREQ_OFFSET = 0x003a, MT_EE_NIC_CONF_2 = 0x0042, MT_EE_XTAL_TRIM_1 = 0x003a, MT_EE_XTAL_TRIM_2 = 0x009e, MT_EE_LNA_GAIN = 0x0044, MT_EE_RSSI_OFFSET_2G_0 = 0x0046, MT_EE_RSSI_OFFSET_2G_1 = 0x0048, MT_EE_LNA_GAIN_5GHZ_1 = 0x0049, MT_EE_RSSI_OFFSET_5G_0 = 0x004a, MT_EE_RSSI_OFFSET_5G_1 = 0x004c, MT_EE_LNA_GAIN_5GHZ_2 = 0x004d, MT_EE_TX_POWER_DELTA_BW40 = 0x0050, MT_EE_TX_POWER_DELTA_BW80 = 0x0052, MT_EE_TX_POWER_EXT_PA_5G = 0x0054, MT_EE_TX_POWER_0_START_2G = 0x0056, MT_EE_TX_POWER_1_START_2G = 0x005c, #define MT_TX_POWER_GROUP_SIZE_5G 5 #define MT_TX_POWER_GROUPS_5G 6 MT_EE_TX_POWER_0_START_5G = 0x0062, MT_EE_TSSI_SLOPE_2G = 0x006e, MT_EE_TX_POWER_0_GRP3_TX_POWER_DELTA = 0x0074, MT_EE_TX_POWER_0_GRP4_TSSI_SLOPE = 0x0076, MT_EE_TX_POWER_1_START_5G = 0x0080, MT_EE_TX_POWER_CCK = 0x00a0, MT_EE_TX_POWER_OFDM_2G_6M = 0x00a2, MT_EE_TX_POWER_OFDM_2G_24M = 0x00a4, MT_EE_TX_POWER_OFDM_5G_6M = 0x00b2, MT_EE_TX_POWER_OFDM_5G_24M = 0x00b4, MT_EE_TX_POWER_HT_MCS0 = 0x00a6, MT_EE_TX_POWER_HT_MCS4 = 0x00a8, MT_EE_TX_POWER_HT_MCS8 = 0x00aa, MT_EE_TX_POWER_HT_MCS12 = 0x00ac, MT_EE_TX_POWER_VHT_MCS0 = 0x00ba, MT_EE_TX_POWER_VHT_MCS4 = 0x00bc, MT_EE_TX_POWER_VHT_MCS8 = 0x00be, MT_EE_2G_TARGET_POWER = 0x00d0, MT_EE_TEMP_OFFSET = 0x00d1, MT_EE_5G_TARGET_POWER = 0x00d2, MT_EE_TSSI_BOUND1 = 0x00d4, MT_EE_TSSI_BOUND2 = 0x00d6, MT_EE_TSSI_BOUND3 = 0x00d8, MT_EE_TSSI_BOUND4 = 0x00da, MT_EE_FREQ_OFFSET_COMPENSATION = 0x00db, MT_EE_TSSI_BOUND5 = 0x00dc, MT_EE_TX_POWER_BYRATE_BASE = 0x00de, MT_EE_TSSI_SLOPE_5G = 0x00f0, MT_EE_RF_TEMP_COMP_SLOPE_5G = 0x00f2, MT_EE_RF_TEMP_COMP_SLOPE_2G = 0x00f4, MT_EE_RF_2G_TSSI_OFF_TXPOWER = 0x00f6, MT_EE_RF_2G_RX_HIGH_GAIN = 0x00f8, MT_EE_RF_5G_GRP0_1_RX_HIGH_GAIN = 0x00fa, MT_EE_RF_5G_GRP2_3_RX_HIGH_GAIN = 0x00fc, MT_EE_RF_5G_GRP4_5_RX_HIGH_GAIN = 0x00fe, MT_EE_BT_RCAL_RESULT = 0x0138, MT_EE_BT_VCDL_CALIBRATION = 0x013c, MT_EE_BT_PMUCFG = 0x013e, MT_EE_USAGE_MAP_START = 0x01e0, MT_EE_USAGE_MAP_END = 0x01fc, }; enum mt76_phy_type { MT_PHY_TYPE_CCK, MT_PHY_TYPE_OFDM, MT_PHY_TYPE_HT, MT_PHY_TYPE_HT_GF, MT_PHY_TYPE_VHT, MT_PHY_TYPE_HE_SU = 8, MT_PHY_TYPE_HE_EXT_SU, MT_PHY_TYPE_HE_TB, MT_PHY_TYPE_HE_MU, }; enum mt76_phy_bandwidth { MT_PHY_BW_20, MT_PHY_BW_40, MT_PHY_BW_80, }; enum mt76_cal_channel_group { MT_CH_5G_JAPAN, MT_CH_5G_UNII_1, MT_CH_5G_UNII_2, MT_CH_5G_UNII_2E_1, MT_CH_5G_UNII_2E_2, MT_CH_5G_UNII_3, }; enum mt76_qsel { MT_QSEL_MGMT, MT_QSEL_HCCA, MT_QSEL_EDCA, MT_QSEL_EDCA_2, }; enum mt76_cipher_type { MT_CIPHER_NONE, MT_CIPHER_WEP40, MT_CIPHER_WEP104, MT_CIPHER_TKIP, MT_CIPHER_AES_CCMP, MT_CIPHER_CKIP40, MT_CIPHER_CKIP104, MT_CIPHER_CKIP128, MT_CIPHER_WAPI, }; struct mt76_fw_header { __le32 ilm_len; __le32 dlm_len; __le16 build_ver; __le16 fw_ver; u8 pad[4]; char build_time[16]; } __packed; struct mt76_rxwi { __le32 rxinfo; __le32 ctl; __le16 tid_sn; __le16 rate; u8 rssi[4]; __le32 bbp_rxinfo[4]; } __packed; struct mt76_txwi { __le16 flags; __le16 rate; u8 ack_ctl; u8 wcid; __le16 len_ctl; __le32 iv; __le32 eiv; u8 aid; u8 txstream; u8 ctl2; u8 pktid; } __packed; dlundqvist-xone-f2aa9fe/transport/wired.c000066400000000000000000000345141515500374600207640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include #include "../bus/bus.h" #define XONE_WIRED_INTF_DATA 0 #define XONE_WIRED_INTF_AUDIO 1 #define XONE_WIRED_NUM_DATA_URBS 12 #define XONE_WIRED_NUM_AUDIO_URBS 12 #define XONE_WIRED_NUM_AUDIO_PKTS 8 #define XONE_WIRED_LEN_DATA_PKT 64 #define XONE_WIRED_VENDOR(vendor) \ .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \ USB_DEVICE_ID_MATCH_INT_INFO | \ USB_DEVICE_ID_MATCH_INT_NUMBER, \ .idVendor = vendor, \ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ .bInterfaceSubClass = 0x47, \ .bInterfaceProtocol = 0xd0, \ .bInterfaceNumber = XONE_WIRED_INTF_DATA, struct xone_wired { struct usb_device *udev; struct xone_wired_port { struct device *dev; struct usb_endpoint_descriptor *ep_in; struct usb_endpoint_descriptor *ep_out; struct urb *urb_in; struct usb_anchor urbs_out_idle; struct usb_anchor urbs_out_busy; int buffer_length_out; } data_port, audio_port; struct gip_adapter *adapter; }; static void xone_wired_complete_data_in(struct urb *urb) { struct xone_wired *wired = urb->context; struct device *dev = wired->data_port.dev; int err; switch (urb->status) { case 0: break; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: return; default: goto resubmit; } if (!urb->actual_length) goto resubmit; err = gip_process_buffer(wired->adapter, urb->transfer_buffer, urb->actual_length); if (err) { dev_err(dev, "%s: process failed: %d\n", __func__, err); print_hex_dump_bytes("xone-wired packet: ", DUMP_PREFIX_NONE, urb->transfer_buffer, urb->actual_length); } resubmit: /* can fail during USB device removal */ err = usb_submit_urb(urb, GFP_ATOMIC); if (err) dev_dbg(dev, "%s: submit failed: %d\n", __func__, err); } static void xone_wired_complete_audio_in(struct urb *urb) { struct xone_wired *wired = urb->context; struct device *dev = wired->audio_port.dev; struct usb_iso_packet_descriptor *desc; int i, err; if (urb->status) return; for (i = 0; i < urb->number_of_packets; i++) { desc = &urb->iso_frame_desc[i]; /* device reset after system sleep can cause xHCI errors */ if (desc->status == -EPROTO) { dev_warn_once(dev, "%s: protocol error\n", __func__); break; } if (!desc->actual_length) continue; err = gip_process_buffer(wired->adapter, urb->transfer_buffer + desc->offset, desc->actual_length); if (err) dev_err(dev, "%s: process failed: %d\n", __func__, err); } /* can fail during USB device removal */ err = usb_submit_urb(urb, GFP_ATOMIC); if (err) dev_dbg(dev, "%s: submit failed: %d\n", __func__, err); } static void xone_wired_complete_out(struct urb *urb) { struct xone_wired_port *port = urb->context; usb_anchor_urb(urb, &port->urbs_out_idle); } static int xone_wired_init_data_in(struct xone_wired *wired) { struct xone_wired_port *port = &wired->data_port; struct urb *urb; void *buf; urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; port->urb_in = urb; buf = usb_alloc_coherent(wired->udev, XONE_WIRED_LEN_DATA_PKT, GFP_KERNEL, &urb->transfer_dma); if (!buf) return -ENOMEM; usb_fill_int_urb(urb, wired->udev, usb_rcvintpipe(wired->udev, port->ep_in->bEndpointAddress), buf, XONE_WIRED_LEN_DATA_PKT, xone_wired_complete_data_in, wired, port->ep_in->bInterval); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; return usb_submit_urb(urb, GFP_KERNEL); } static int xone_wired_init_data_out(struct xone_wired *wired) { struct xone_wired_port *port = &wired->data_port; struct urb *urb; void *buf; int i; port->buffer_length_out = XONE_WIRED_LEN_DATA_PKT; for (i = 0; i < XONE_WIRED_NUM_DATA_URBS; i++) { urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; usb_anchor_urb(urb, &port->urbs_out_idle); usb_free_urb(urb); buf = usb_alloc_coherent(wired->udev, XONE_WIRED_LEN_DATA_PKT, GFP_KERNEL, &urb->transfer_dma); if (!buf) return -ENOMEM; usb_fill_int_urb(urb, wired->udev, usb_sndintpipe(wired->udev, port->ep_out->bEndpointAddress), buf, XONE_WIRED_LEN_DATA_PKT, xone_wired_complete_out, port, port->ep_out->bInterval); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; } return 0; } static void xone_wired_free_urbs(struct xone_wired_port *port) { struct urb *urb = port->urb_in; if (urb) { usb_free_coherent(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); usb_free_urb(urb); port->urb_in = NULL; } while ((urb = usb_get_from_anchor(&port->urbs_out_idle))) { usb_free_coherent(urb->dev, port->buffer_length_out, urb->transfer_buffer, urb->transfer_dma); usb_free_urb(urb); } } static int xone_wired_get_buffer(struct gip_adapter *adap, struct gip_adapter_buffer *buf) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct xone_wired_port *port; struct urb *urb; if (buf->type == GIP_BUF_DATA) port = &wired->data_port; else if (buf->type == GIP_BUF_AUDIO) port = &wired->audio_port; else return -EINVAL; urb = usb_get_from_anchor(&port->urbs_out_idle); if (!urb) return -ENOSPC; buf->context = urb; buf->data = urb->transfer_buffer; buf->length = port->buffer_length_out; return 0; } static int xone_wired_submit_buffer(struct gip_adapter *adap, struct gip_adapter_buffer *buf) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct xone_wired_port *port; struct urb *urb = buf->context; int err; if (buf->type == GIP_BUF_DATA) port = &wired->data_port; else if (buf->type == GIP_BUF_AUDIO) port = &wired->audio_port; else return -EINVAL; urb->transfer_buffer_length = buf->length; usb_anchor_urb(urb, &port->urbs_out_busy); err = usb_submit_urb(urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(urb); usb_anchor_urb(urb, &port->urbs_out_idle); } usb_free_urb(urb); return err; } static int xone_wired_enable_audio(struct gip_adapter *adap) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct usb_interface *intf; if (!wired->audio_port.dev) return -ENOTSUPP; intf = to_usb_interface(wired->audio_port.dev); if (intf->cur_altsetting->desc.bAlternateSetting == 1) return -EALREADY; return usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 1); } static int xone_wired_init_audio_in(struct gip_adapter *adap) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct xone_wired_port *port = &wired->audio_port; struct urb *urb; void *buf; int len, i; if (!port->ep_in) return -ENOTSUPP; urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL); if (!urb) return -ENOMEM; port->urb_in = urb; len = usb_endpoint_maxp(port->ep_in); buf = usb_alloc_coherent(wired->udev, len * XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL, &urb->transfer_dma); if (!buf) return -ENOMEM; urb->dev = wired->udev; urb->pipe = usb_rcvisocpipe(wired->udev, port->ep_in->bEndpointAddress); urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; urb->transfer_buffer = buf; urb->transfer_buffer_length = len * XONE_WIRED_NUM_AUDIO_PKTS; urb->number_of_packets = XONE_WIRED_NUM_AUDIO_PKTS; urb->interval = port->ep_in->bInterval; urb->context = wired; urb->complete = xone_wired_complete_audio_in; for (i = 0; i < XONE_WIRED_NUM_AUDIO_PKTS; i++) { urb->iso_frame_desc[i].offset = i * len; urb->iso_frame_desc[i].length = len; } return usb_submit_urb(urb, GFP_KERNEL); } static int xone_wired_init_audio_out(struct gip_adapter *adap, int pkt_len) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct xone_wired_port *port = &wired->audio_port; struct urb *urb; void *buf; int i, j; if (!port->ep_out) return -ENOTSUPP; port->buffer_length_out = pkt_len * XONE_WIRED_NUM_AUDIO_PKTS; for (i = 0; i < XONE_WIRED_NUM_AUDIO_URBS; i++) { urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL); if (!urb) return -ENOMEM; usb_anchor_urb(urb, &port->urbs_out_idle); usb_free_urb(urb); buf = usb_alloc_coherent(wired->udev, port->buffer_length_out, GFP_KERNEL, &urb->transfer_dma); if (!buf) return -ENOMEM; urb->dev = wired->udev; urb->pipe = usb_sndisocpipe(wired->udev, port->ep_out->bEndpointAddress); urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; urb->transfer_buffer = buf; urb->transfer_buffer_length = port->buffer_length_out; urb->number_of_packets = XONE_WIRED_NUM_AUDIO_PKTS; urb->interval = port->ep_out->bInterval; urb->context = port; urb->complete = xone_wired_complete_out; for (j = 0; j < XONE_WIRED_NUM_AUDIO_PKTS; j++) { urb->iso_frame_desc[j].offset = j * pkt_len; urb->iso_frame_desc[j].length = pkt_len; } } return 0; } static int xone_wired_disable_audio(struct gip_adapter *adap) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct xone_wired_port *port = &wired->audio_port; struct usb_interface *intf; if (!port->dev) return -ENOTSUPP; intf = to_usb_interface(port->dev); if (!intf->cur_altsetting->desc.bAlternateSetting) return -EALREADY; usb_kill_urb(port->urb_in); usb_kill_anchored_urbs(&port->urbs_out_busy); xone_wired_free_urbs(port); return usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 0); } static struct gip_adapter_ops xone_wired_adapter_ops = { .get_buffer = xone_wired_get_buffer, .submit_buffer = xone_wired_submit_buffer, .enable_audio = xone_wired_enable_audio, .init_audio_in = xone_wired_init_audio_in, .init_audio_out = xone_wired_init_audio_out, .disable_audio = xone_wired_disable_audio, }; static struct usb_driver xone_wired_driver; static int xone_wired_find_isoc_endpoints(struct usb_host_interface *alt, struct usb_endpoint_descriptor **in, struct usb_endpoint_descriptor **out) { struct usb_endpoint_descriptor *ep; int i; for (i = 0; i < alt->desc.bNumEndpoints; i++) { ep = &alt->endpoint[i].desc; if (usb_endpoint_is_isoc_in(ep)) *in = ep; else if (usb_endpoint_is_isoc_out(ep)) *out = ep; if (*in && *out) return 0; } return -ENXIO; } static int xone_wired_init_data_port(struct xone_wired *wired, struct usb_interface *intf) { struct xone_wired_port *port = &wired->data_port; int err; init_usb_anchor(&port->urbs_out_idle); init_usb_anchor(&port->urbs_out_busy); err = usb_find_common_endpoints(intf->cur_altsetting, NULL, NULL, &port->ep_in, &port->ep_out); if (err) return err; port->dev = &intf->dev; return 0; } static int xone_wired_init_audio_port(struct xone_wired *wired) { struct xone_wired_port *port = &wired->audio_port; struct usb_interface *intf; struct usb_host_interface *alt; int err; init_usb_anchor(&port->urbs_out_idle); init_usb_anchor(&port->urbs_out_busy); intf = usb_ifnum_to_if(wired->udev, XONE_WIRED_INTF_AUDIO); if (!intf) { dev_dbg(&wired->udev->dev, "%s: audio unavailable\n", __func__); return 0; } alt = usb_altnum_to_altsetting(intf, 1); if (!alt) return -ENXIO; err = usb_driver_claim_interface(&xone_wired_driver, intf, NULL); if (err) return err; /* disable the audio interface */ /* mandatory for certain third party devices */ err = usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 0); if (err) return err; err = xone_wired_find_isoc_endpoints(alt, &port->ep_in, &port->ep_out); if (err) return err; port->dev = &intf->dev; return 0; } static int xone_wired_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct xone_wired *wired; int err; wired = devm_kzalloc(&intf->dev, sizeof(*wired), GFP_KERNEL); if (!wired) return -ENOMEM; wired->udev = interface_to_usbdev(intf); /* newer devices require a reset after system sleep */ usb_reset_device(wired->udev); err = xone_wired_init_data_port(wired, intf); if (err) return err; err = xone_wired_init_audio_port(wired); if (err) return err; wired->adapter = gip_create_adapter(&intf->dev, &xone_wired_adapter_ops, XONE_WIRED_NUM_AUDIO_PKTS); if (IS_ERR(wired->adapter)) return PTR_ERR(wired->adapter); dev_set_drvdata(&wired->adapter->dev, wired); err = xone_wired_init_data_out(wired); if (err) goto err_free_urbs; err = xone_wired_init_data_in(wired); if (err) goto err_free_urbs; usb_set_intfdata(intf, wired); /* enable USB remote wakeup */ device_wakeup_enable(&wired->udev->dev); return 0; err_free_urbs: xone_wired_free_urbs(&wired->data_port); gip_destroy_adapter(wired->adapter); return err; } static void xone_wired_disconnect(struct usb_interface *intf) { struct xone_wired *wired = usb_get_intfdata(intf); /* ignore audio interface unbind */ if (!wired) return; usb_kill_urb(wired->data_port.urb_in); usb_kill_urb(wired->audio_port.urb_in); /* also disables the audio interface */ gip_destroy_adapter(wired->adapter); usb_kill_anchored_urbs(&wired->data_port.urbs_out_busy); xone_wired_free_urbs(&wired->data_port); usb_set_intfdata(intf, NULL); } static const struct usb_device_id xone_wired_id_table[] = { { XONE_WIRED_VENDOR(0x045e) }, /* Microsoft */ { XONE_WIRED_VENDOR(0x0738) }, /* Mad Catz */ { XONE_WIRED_VENDOR(0x0e6f) }, /* PDP */ { XONE_WIRED_VENDOR(0x0f0d) }, /* Hori */ { XONE_WIRED_VENDOR(0x1532) }, /* Razer */ { XONE_WIRED_VENDOR(0x24c6) }, /* PowerA */ { XONE_WIRED_VENDOR(0x20d6) }, /* BDA */ { XONE_WIRED_VENDOR(0x044f) }, /* Thrustmaster */ { XONE_WIRED_VENDOR(0x10f5) }, /* Turtle Beach */ { XONE_WIRED_VENDOR(0x2e24) }, /* Hyperkin */ { XONE_WIRED_VENDOR(0x3285) }, /* Nacon */ { XONE_WIRED_VENDOR(0x2dc8) }, /* 8BitDo */ { XONE_WIRED_VENDOR(0x2e95) }, /* SCUF */ { XONE_WIRED_VENDOR(0x3537) }, /* GameSir */ { XONE_WIRED_VENDOR(0x11c1) }, /* ??? */ { XONE_WIRED_VENDOR(0x294b) }, /* Snakebyte */ { XONE_WIRED_VENDOR(0x2c16) }, /* Priferential */ { XONE_WIRED_VENDOR(0x0b05) }, /* ASUS */ { XONE_WIRED_VENDOR(0x413d) }, /* BIGBIG WON */ { XONE_WIRED_VENDOR(0x046d) }, /* Logitech Astro */ { XONE_WIRED_VENDOR(0x0079) }, /* EasySMX */ { XONE_WIRED_VENDOR(0x1038) }, /* SteelSeries ApS */ { XONE_WIRED_VENDOR(0x1b1c) }, /* Corsair */ { }, }; static struct usb_driver xone_wired_driver = { .name = "xone-wired", .probe = xone_wired_probe, .disconnect = xone_wired_disconnect, .id_table = xone_wired_id_table, }; module_usb_driver(xone_wired_driver); MODULE_DEVICE_TABLE(usb, xone_wired_id_table); MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone wired driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); dlundqvist-xone-f2aa9fe/uninstall.sh000077500000000000000000000016411515500374600200150ustar00rootroot00000000000000#! /usr/bin/env bash set -eu get_version() { echo $(dkms status | grep xone | head -n 1 | tr -s ',:/' ' ' | cut -d ' ' -f 2) } if [ "$(id -u)" -ne 0 ]; then echo 'This script must be run as root!' >&2 exit 1 fi modules=$(lsmod | grep '^xone_' | cut -d ' ' -f 1 | tr '\n' ' ') if [ -n "$modules" ]; then echo "Unloading modules: $modules..." # shellcheck disable=SC2086 modprobe -r -a $modules || true fi version=$(get_version) while [[ -n $version ]]; do echo -e "Uninstalling xone $version...\n" dkms remove -m xone -v "$version" --all version=$(get_version) done rm -rf /usr/src/xone* || true rm -rf /etc/modprobe.d/xone-blacklist.conf || true rm -rf /var/lib/dkms/xone* || true echo -e "All xone versions removed\n" [[ ${1:-} == "--no-firmware" ]] && exit 0 rm -rf /lib/firmware/xow_dongle* || true rm -rf /lib/firmware/xone_dongle* || true echo -e "All dongle firmwares removed\n"