Using the D-Link DWA-X1850 Wi-Fi 6 USB adapter on Linux (RTL8832AU 802.11ax)

After the introduction of wireless routers, phones and PCIe cards featuring the next generation of Wi-Fi, known as 802.11ax (or simply Wi-Fi 6), finally the first USB 3.0 adapters have arrived on the market:

The ASUS USB-AX56 adapter has two external antennas, while the D-Link DWA-X1850 comes in a more compact case with built-in antennas.
Both support 2 streams dual band on 2.4GHz and 5GHz, labelled “AX1800”, which translates as:

  • 40 MHz channel width on 2.4GHz, using MCS index 11 and short GI (0.8µs), resulting in a nominal rate of 573.5 Mbps
  • 80 MHz channel width on 5 GHz, also MCS 11 with short GI (0.8µs), resulting in the nominal rate of 1201 Mbps

Combining both rates, these are marketed as AX1800, although you typically can’t bond connections and will rather end up with a Link Rate of 1201 Mbit/s only, which may be somewhere around 500 Mbit/s of real throughput (all while assuming you are located very close to the router, and none of your neighbours are transmitting on the same wifi channel, of course).

While the Link Rate will degrade quickly when a few walls need to be penetrated, the receive sensitivity of this new generation of chips can still be considered superior to previous ac adapters, although the limitation to 2 streams might be an issue. Especially if your router supports AC with 3×3 or 4×4, older adapters like the infamous “Death Star” D-Link DWA-192 might still be a better choice.
It uses RTL8814AU and supports AC1900, i.e.

  • 3 spatial streams with 40 MHz channel width on 2.4GHz, using MCS index 9 and short GI (0.4µs), resulting in 600Mbps
  • 3 spatial streams with 80 MHz channel width on 5GHz, using MCS index 9 and short GI (0.4µs), resulting in 1300Mbps

This may seem like not much of a difference, but keep in mind that MCS11 will degrade more quickly with a few walls in between, and when the connection is at MCS9, 2 streams of AX will only be a rate of 960.Mbps compared to 1300 with 3 streams AC.

But back to the first USB AX adapters hitting the markets right now:
Both the ASUS USB-AX56 and D-Link DWA-X1850 are based on the Realtek RTL8832AU chipset, which is the 2-stream variant of the RTL8852AU – this is the most important information when it comes to finding a driver that works on Linux.

When searching github, you will find the repository
which contains the driver source code from Realtek. There are also a few documents that explain how to introduce your device’s USB VID and PID into the driver:
The relevant file to modify here is os_dep/linux/usb_intf.c, the USB VID e.g. for D-Link is 0x2001, the PID for the DWA-X1850 adapter is 0x3321. For the ASUS USB-AX56, it would be 0B05:1997.

You can find this already patched in my fork of the repo on github:

// Update: The most current maintained fork of this driver is now available from the repo of lwfinger, please use this instead:

Now just clone that repo, make and sudo make install, then re-plug the dongle.
After being plugged in, the DWA-X1850 is in USB Mass Storage mode, which can be switched to network adapter mode by ejecting the virtual drive (eventually, it seems this device should be added to the USB modeswitch project).

The blue status LED is currently not working, maybe this needs some addition GPIO definition which is specific to this device.

Also keep in mind that this driver, although being built from source, uses the Realtek proprietary structure, which may not be what the Linux Wireless developers are aiming for.

A corresponding Linux Wireless compatible driver would be “rtw89”, which currently supports only the PCIe version of RTL8852, but I’m sure this may soon be updated to include the USB variants, and one day be available with Ubuntu and other common distributions. For now, at least we have the Realtek driver.

5 thoughts on “Using the D-Link DWA-X1850 Wi-Fi 6 USB adapter on Linux (RTL8832AU 802.11ax)”

  1. First of all, thank for the article. Compiling on a Raspi4 with Kernel 5.10.63-v8+ works without erors. So i thin isn´t my problem.
    But when i load the driver i get a lot of dmesg errors concerning the firmware and the card won´´t work. Do you have hints?
    best regards

  2. Hello,

    The patch below enables compilation of driver under kernel-5.16.
    However, I don’t have skill to check whether this patch might cause other unexpected issue or not.
    Please review the patch.

    [code] rtw8852au_kernel-5.16.patch
    — RTL8852AU_WiFi_linux_v1.15.0.1-0-g487ee886.20210714-dwa-x1850/core/rtw_br_ext.c 2021-12-04 17:39:48.000000000 +0900
    +++ 2022-02-13 20:41:20.076717012 +0900
    @@ -17,7 +17,10 @@
    #ifdef __KERNEL__
    + #include
    @@ -159,6 +162,7 @@

    +#ifdef _NET_INET_IPX_H_
    static __inline__ void __nat25_generate_ipv4_network_addr(unsigned char *networkAddr,
    unsigned int *ipAddr)
    @@ -189,6 +193,7 @@
    _rtw_memcpy(networkAddr + 1, (unsigned char *)ipxNetAddr, 4);
    _rtw_memcpy(networkAddr + 5, (unsigned char *)ipxSocketAddr, 2);

    static __inline__ void __nat25_generate_apple_network_addr(unsigned char *networkAddr,
    @@ -330,6 +335,7 @@
    x = networkAddr[7] ^ networkAddr[8] ^ networkAddr[9] ^ networkAddr[10];

    return x & (NAT25_HASH_SIZE – 1);
    +#ifdef _NET_INET_IPX_H_
    } else if (networkAddr[0] == NAT25_IPX) {
    unsigned long x;

    @@ -337,6 +343,7 @@
    networkAddr[6] ^ networkAddr[7] ^ networkAddr[8] ^ networkAddr[9] ^ networkAddr[10];

    return x & (NAT25_HASH_SIZE – 1);
    } else if (networkAddr[0] == NAT25_APPLE) {
    unsigned long x;

    @@ -782,7 +789,7 @@
    if (iph->saddr == 0)
    return 0;
    RTW_INFO(“NAT25: Insert IP, SA=%08x, DA=%08x\n”, iph->saddr, iph->daddr);
    – __nat25_generate_ipv4_network_addr(networkAddr, &iph->saddr);
    + __nat25_generate_ipv6_network_addr(networkAddr, &iph->saddr);
    /* record source IP address and , source mac address into db */
    __nat25_db_network_insert(priv, skb->data + ETH_ALEN, networkAddr);

    @@ -800,7 +807,7 @@
    – __nat25_generate_ipv4_network_addr(networkAddr, &iph->daddr);
    + __nat25_generate_ipv6_network_addr(networkAddr, &iph->daddr);

    if (!__nat25_db_network_lookup_and_replace(priv, skb, networkAddr)) {
    if (*((unsigned char *)&iph->daddr + 3) == 0xff) {
    @@ -854,7 +861,7 @@
    arp_ptr += arp->ar_hln;
    sender = (unsigned int *)arp_ptr;

    – __nat25_generate_ipv4_network_addr(networkAddr, sender);
    + __nat25_generate_ipv6_network_addr(networkAddr, sender);

    __nat25_db_network_insert(priv, skb->data + ETH_ALEN, networkAddr);

    @@ -870,7 +877,7 @@
    arp_ptr += (arp->ar_hln + arp->ar_pln);
    target = (unsigned int *)arp_ptr;

    – __nat25_generate_ipv4_network_addr(networkAddr, target);
    + __nat25_generate_ipv6_network_addr(networkAddr, target);

    __nat25_db_network_lookup_and_replace(priv, skb, networkAddr);

    @@ -886,6 +893,7 @@

    +#ifdef _NET_INET_IPX_H_
    /* Handle IPX and Apple Talk frame */
    @@ -1106,6 +1114,7 @@

    return -1;

    /* Handle PPPoE frame */
    @@ -1558,7 +1567,7 @@

    /* _rtw_spinlock_bh(&priv->br_ext_lock); */

    – __nat25_generate_ipv4_network_addr(networkAddr, (unsigned int *)ipAddr);
    + __nat25_generate_ipv6_network_addr(networkAddr, (unsigned int *)ipAddr);
    hash = __nat25_network_hash(networkAddr);
    db = priv->nethash[hash];
    while (db != NULL) {

  3. I can’t get this sucker to compile on Rocky 8.5 kernel 4.18.0-348.20

    /os_dep/linux/os_intfs.c:313:22: error: initialization of ‘u16 (*)(struct net_device *, struct sk_buff *, struct net_device *, u16 (*)(struct net_device *, struct sk_buff *, struct net_device *))’ {aka ‘short unsigned int (*)(struct net_device *, struct sk_buff *, struct net_device *, short unsigned int (*)(struct net_device *, struct sk_buff *, struct net_device *))’} from incompatible pointer type ‘u16 (*)(struct net_device *, struct sk_buff *, void *, u16 (*)(struct net_device *, struct sk_buff *, struct net_device *))’ {aka ‘short unsigned int (*)(struct net_device *, struct sk_buff *, void *, short unsigned int (*)(struct net_device *, struct sk_buff *, struct net_device *))’} [-Werror=incompatible-pointer-types]
    .ndo_select_queue = rtw_select_queue,

    Got around this error from

    but I just keep running into more issues 🙁

  4. Hi everyone,
    sorry for not spotting your comments earlier, I really should have checked the admin panel for comments awaiting moderation on a regular basis.

    There have been multiple forks of this code in the meantime, mostly to add new device VID/PIDs, or to fix build with latest Linux Kernels.
    For now, the most reliable fork is by lwfinger, I have updated this in the post. Please try this repo instead:

    He is the maintainer of the rtw89 in-tree driver for the Linux Kernel, which currently supports only the PCIe variants of this chip. At one time it even seemed feasible to have the USB-based chipsets support by that driver as well, but it seems the are a lot more differences, so we only have this out-of-tree driver for now, forked from the original code provided by Realtek.

    Cheers, Sebastian

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.