Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package xone for openSUSE:Factory checked in at 2026-03-15 14:32:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/xone (Old) and /work/SRC/openSUSE:Factory/.xone.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "xone" Sun Mar 15 14:32:33 2026 rev:4 rq:1339041 version:0.5.7 Changes: -------- --- /work/SRC/openSUSE:Factory/xone/xone.changes 2026-02-16 13:14:44.699560717 +0100 +++ /work/SRC/openSUSE:Factory/.xone.new.8177/xone.changes 2026-03-15 14:33:30.210195179 +0100 @@ -1,0 +2,12 @@ +Sat Mar 14 11:51:44 UTC 2026 - Tobias Görgens <[email protected]> + +- Update to release 0.5.7 + * dongle, mt76: fix cold/warm firmware init and enable automatic controller reconnection + * Change timeout_secs to milliseconds for pairing work + * Simplify firmware loading cancellation logic +- Update to release 0.5.6 + * Add more info to the active_clients sysfs + * Fix small dongle pairing - add pairing scan channel rotation and diagnostics + * Add sleep before firmware reset + +------------------------------------------------------------------- Old: ---- v0.5.5.tar.gz New: ---- v0.5.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ xone.spec ++++++ --- /var/tmp/diff_new_pack.Jn9cge/_old 2026-03-15 14:33:30.706215596 +0100 +++ /var/tmp/diff_new_pack.Jn9cge/_new 2026-03-15 14:33:30.710215761 +0100 @@ -17,7 +17,7 @@ Name: xone -Version: 0.5.5 +Version: 0.5.7 Release: 0 Summary: Driver for Xbox One and Xbox Series X|S controllers License: GPL-2.0-or-later ++++++ v0.5.5.tar.gz -> v0.5.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xone-0.5.5/README.md new/xone-0.5.7/README.md --- old/xone-0.5.5/README.md 2026-02-05 13:22:01.000000000 +0100 +++ new/xone-0.5.7/README.md 2026-02-27 13:40:31.000000000 +0100 @@ -10,7 +10,10 @@ `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**: This is a fork, please support the upstream project. +> [!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 @@ -229,6 +232,18 @@ 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). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xone-0.5.5/transport/dongle.c new/xone-0.5.7/transport/dongle.c --- old/xone-0.5.5/transport/dongle.c 2026-02-05 13:22:01.000000000 +0100 +++ new/xone-0.5.7/transport/dongle.c 2026-02-27 13:40:31.000000000 +0100 @@ -30,7 +30,8 @@ #define XONE_DONGLE_MAX_CLIENTS 16 -#define XONE_DONGLE_PAIRING_TIMEOUT msecs_to_jiffies(60000) +#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 @@ -91,7 +92,10 @@ /* 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; @@ -107,9 +111,22 @@ u16 product; }; -static int xone_dongle_power_off_client(struct xone_dongle *dongle, int index); +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) @@ -233,7 +250,8 @@ .set_encryption_key = xone_dongle_set_encryption_key, }; -static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable) +static int xone_dongle_pairing_handler(struct xone_dongle *dongle, bool enable, + u8 timeout_secs) { enum xone_mt76_led_mode led; int err = 0; @@ -262,9 +280,16 @@ dev_dbg(dongle->mt.dev, "%s: enabled=%d\n", __func__, enable); dongle->pairing = enable; - if (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, - XONE_DONGLE_PAIRING_TIMEOUT); + 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); @@ -272,6 +297,18 @@ 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 int xone_dongle_enable_pairing(struct xone_dongle *dongle, + u8 timeout_secs) +{ + return xone_dongle_pairing_handler(dongle, true, timeout_secs); +} + static void xone_dongle_pairing_timeout(struct work_struct *work) { struct xone_dongle *dongle = container_of(to_delayed_work(work), @@ -288,6 +325,68 @@ __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) { @@ -321,8 +420,23 @@ { 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; - return sysfs_emit(buf, "%u\n", atomic_read(&dongle->client_count)); + 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, @@ -354,7 +468,7 @@ if (val == -1) err = xone_dongle_power_off_clients(dongle); else - err = xone_dongle_power_off_client(dongle, val); + err = xone_dongle_power_off_client(dongle, val, false); return err ? err : count; } @@ -588,8 +702,17 @@ spin_lock_irqsave(&dongle->clients_lock, flags); client = dongle->clients[wcid - 1]; - if (client) + 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); @@ -600,6 +723,9 @@ { 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; @@ -642,6 +768,9 @@ 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: @@ -670,6 +799,15 @@ { 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; @@ -715,6 +853,14 @@ 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); @@ -1012,8 +1158,15 @@ return; } - err = xone_mt76_init_radio(mt); - if (err){ + 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; @@ -1022,6 +1175,22 @@ 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 for 10 seconds so controllers present at boot or + * after a replug reconnect automatically without requiring a manual + * button press. The pairing timeout (XONE_DONGLE_PAIRING_TIMEOUT) + * disables it again once the window expires. + */ + err = xone_dongle_enable_pairing(dongle, 10); + if (err) + dev_err(mt->dev, "%s: enable pairing failed: %d\n", + __func__, err); } static int xone_dongle_init(struct xone_dongle *dongle) @@ -1036,7 +1205,8 @@ return 0; } -static int xone_dongle_power_off_client(struct xone_dongle *dongle, int index) +static int xone_dongle_power_off_client(struct xone_dongle *dongle, int index, + bool silent) { unsigned long flags = 0; int err = 0; @@ -1048,6 +1218,8 @@ 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; @@ -1055,16 +1227,11 @@ static int xone_dongle_power_off_clients(struct xone_dongle *dongle) { - int err; - if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) return 0; - for (int i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++){ - err = xone_dongle_power_off_client(dongle, i); - if (err) - return err; - } + 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, @@ -1081,21 +1248,18 @@ struct urb *urb; int i; - usb_kill_anchored_urbs(&dongle->urbs_in_busy); - destroy_workqueue(dongle->event_wq); - cancel_delayed_work(&dongle->pairing_work); - 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; - pr_debug("%s: Waiting for fw load work to finish", __func__); - - while (dongle->fw_state == XONE_DONGLE_FW_STATE_STOP_LOADING) - msleep(500); - - pr_debug("%s: FW loading cancelled", __func__); } + 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) @@ -1142,11 +1306,23 @@ 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); - usb_reset_device(dongle->mt.udev); + /* + * 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); @@ -1200,7 +1376,8 @@ usb_kill_anchored_urbs(&dongle->urbs_in_busy); usb_kill_anchored_urbs(&dongle->urbs_out_busy); - cancel_delayed_work(&dongle->pairing_work); + cancel_delayed_work_sync(&dongle->pairing_work); + cancel_delayed_work_sync(&dongle->pairing_scan_work); return xone_mt76_suspend_radio(&dongle->mt); } @@ -1268,7 +1445,8 @@ if (dongle->fw_state != XONE_DONGLE_FW_STATE_READY) dongle->fw_state = XONE_DONGLE_FW_STATE_STOP_LOADING; - cancel_delayed_work(&dongle->pairing_work); + 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); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xone-0.5.5/transport/mt76.c new/xone-0.5.7/transport/mt76.c --- old/xone-0.5.5/transport/mt76.c 2026-02-05 13:22:01.000000000 +0100 +++ new/xone-0.5.7/transport/mt76.c 2026-02-27 13:40:31.000000000 +0100 @@ -376,8 +376,8 @@ return xone_mt76_send_command(mt, skb, MT_CMD_WOW_FEATURE); } -static int xone_mt76_switch_channel(struct xone_mt76 *mt, - struct xone_mt76_channel *chan) +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 = {}; @@ -514,23 +514,64 @@ 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)) { + msleep(2000); dev_dbg(mt->dev, "%s: resetting firmware...\n", __func__); - return xone_mt76_reset_firmware(mt); + 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; @@ -1042,6 +1083,11 @@ 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) @@ -1079,6 +1125,11 @@ { 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; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xone-0.5.5/transport/mt76.h new/xone-0.5.7/transport/mt76.h --- old/xone-0.5.5/transport/mt76.h 2026-02-05 13:22:01.000000000 +0100 +++ new/xone-0.5.7/transport/mt76.h 2026-02-27 13:40:31.000000000 +0100 @@ -70,6 +70,8 @@ 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);
