This document describes in detail how the rt2x00-2.0.0-b3 WiFi PCI driver probes the card, initializes it, configures hardware, hooks into the network stack, sends packets from the IP layer through the WiFi card, receives packets from the card up to the IP layer, and handles interrupts. All code references are from the rt2x00-2.0.0-b3 source tree.
Table of Contents
- Overview
- PCI Probe and Device Discovery
- Initialization and Configuration
- Callback and Hook Registration
- TX Path: IP Layer → WiFi Card
- RX Path: WiFi Card → IP Layer
- Interrupt Handling
- Data Structures and Rings
- Summary Diagrams
- SSID Discovery (Scanning), Encryption, and Connecting to an SSID
1. Overview
1.1 Driver Layout
The rt2x00-2.0.0-b3 driver consists of:
| Component | Files | Role |
|---|---|---|
| PCI layer | rt2400pci.c, rt2500pci.c, rt2x00pci.h |
PCI probe/remove, MMIO, DMA rings, interrupt, device-specific init |
| Core | rt2x00core.c, rt2x00core.h |
net_device lifecycle, TX/RX workqueues, ieee80211 glue, config |
| Device structs | rt2x00.h, rt2x00dev.h |
Shared types, capabilities, ring helpers |
| 802.11 stack | ieee80211/* |
ieee80211_xmit, ieee80211_rx, management, encryption |
The network device is an alloc_ieee80211() net_device. The IP layer uses it like any other Ethernet-style interface. The driver plugs into the legacy ieee80211 stack (not mac80211) via ieee80211->hard_start_xmit and related callbacks.
1.2 Supported Hardware (rt2400pci)
- Chipset: Ralink RT2460 (802.11b)
- PCI IDs:
0x1814:0x0101(seert2400pci.crt2x00_device_pci_tbl)
2. PCI Probe and Device Discovery
2.1 Module Init and Driver Registration
// rt2400pci.c
static int __init rt2x00_pci_init(void)
{
printk(KERN_INFO "Loading module: %s\n", version);
return pci_register_driver(&rt2x00_pci_driver);
}
static struct pci_driver rt2x00_pci_driver = {
.name = DRV_NAME, // "rt2400pci"
.id_table = rt2x00_device_pci_tbl,
.probe = rt2x00_pci_probe,
.remove = rt2x00_pci_remove,
.suspend = rt2x00_pci_suspend, // CONFIG_PM
.resume = rt2x00_pci_resume,
};
static struct pci_device_id rt2x00_device_pci_tbl[] = {
{ PCI_DEVICE(0x1814, 0x0101), .driver_data = RT2460 },
{ 0, }
};
pci_register_driver()registers the driver with the PCI subsystem.- When the PCI core finds a device matching
rt2x00_device_pci_tbl, it callsrt2x00_pci_probe().
2.2 Probe Flow: rt2x00_pci_probe()
rt2x00_pci_probe() (in rt2400pci.c) does the following:
1. Validate driver_data (RT2460)
2. pci_enable_device(pci_dev) → enable device, I/O and memory space
3. pci_set_master(pci_dev) → enable bus mastering (DMA)
4. pci_set_mwi(pci_dev) → Memory-Write-Invalidate (optional)
5. pci_set_dma_mask(64-bit then 32-bit) → DMA addressing
6. pci_request_regions(pci_dev) → reserve BARs (MMIO)
7. rt2x00_core_probe(...) → allocate net_device, run dev_probe, register netdev
8. net_dev->irq = pci_dev->irq → store IRQ for later use
9. pci_set_drvdata(pci_dev, net_dev)
If any step fails, the probe unwinds (release regions, disable device, etc.) and returns an error.
Important: rt2x00_dev_probe() runs during core probe (at load time) and does MMIO mapping, EEPROM, and MAC read. DMA rings, BBP init, and the IRQ are set up later in rt2x00_dev_radio_on(), which runs when the interface is brought up (ifconfig wlan0 up).
2.3 Core Probe: rt2x00_core_probe()
rt2x00_core_probe() (in rt2x00core.c) allocates the net_device and wires the driver into the stack:
1. Check handler has all required callbacks (dev_radio_on, dev_radio_off, dev_update_config, ...).
2. net_dev = alloc_ieee80211(sizeof_dev + sizeof(device) + sizeof(core))
→ alloc_etherdev + ieee80211 allocation; dev->hard_start_xmit = ieee80211_xmit.
3. device = ieee80211_priv(net_dev), core = (void*)device + sizeof(*device).
4. Initialize device, core, handler, workqueue, packet rings (rx, tx, atim, prio, beacon).
5. rt2x00_init_config(core).
6. handler->dev_probe(device, &core->config, priv) → rt2x00_dev_probe()
7. Set ieee80211 callbacks (hard_start_xmit, handle_*, set_security, etc.).
8. net_dev->open = rt2x00_open, ->stop = rt2x00_stop, ->tx_timeout = rt2x00_tx_timeout, etc.
9. register_netdev(net_dev).
10. set_bit(DEVICE_AWAKE), rt2x00_activate_config(device).
So rt2x00_dev_probe() is the first device-specific hook. It runs during core probe, i.e. at module load / PCI probe time, before ifconfig up.
3. Initialization and Configuration
3.1 Device Probe: rt2x00_dev_probe()
rt2x00_dev_probe() (in rt2400pci.c) runs during rt2x00_core_probe():
1. priv = pci_dev; clear rt2x00pci; set rx/tx/atim/prio/beacon .data_addr = NULL.
2. csr_addr = ioremap(pci_resource_start(pci_dev, 0), pci_resource_len(pci_dev, 0))
→ map BAR0 (MMIO) into kernel virtual address space.
3. rt2x00_init_eeprom(rt2x00pci, config)
→ detect EEPROM width, read RF type, antenna config, BBP data from EEPROM.
4. rt2x00_init_read_mac(rt2x00pci, device->net_dev)
→ read MAC address from CSR3/CSR4 into net_dev->dev_addr.
5. Reject unsupported RF (e.g. must be RF2420 or RF2421).
6. set_bit(DEVICE_CAP_802_11B).
MMIO access uses rt2x00_register_read / rt2x00_register_write (in rt2x00pci.h), which wrap readl / writel on csr_addr + offset. BBP and RF are accessed indirectly via BBPCSR / RFCSR with busy-wait.
3.2 Radio On: rt2x00_dev_radio_on()
When the user runs ifconfig wlan0 up, the net_device open callback runs:
// rt2x00core.c
net_dev->open = rt2x00_open;
rt2x00_open() calls rt2x00_radio_on(device), which:
rt2x00_enable_ring()for rx, tx, atim, prio, beacon (packet rings).- Calls
handler->dev_radio_on(device)→rt2x00_dev_radio_on()(inrt2400pci.c). - Sets
DEVICE_RADIO_ON.
rt2x00_dev_radio_on() is where DMA rings, hardware init, and the interrupt handler are set up:
1. rt2x00_pci_alloc_rings(device)
→ dma_alloc_coherent for rx, tx, atim, prio, beacon rings (descriptor + data).
2. rt2x00_clear_ring() for each ring
→ zero rings; for RX: set RXD W0 (OWNER_NIC=1), W1 (buffer addr), W2 (length);
for TX: set TXD W0 (VALID=0, OWNER_NIC=0), W1, W2.
3. rt2x00_init_ring_register(rt2x00pci)
→ write TXCSR2–TXCSR6, RXCSR1–RXCSR2 with ring sizes and DMA addresses.
4. rt2x00_init_registers(rt2x00pci)
→ PWRCSR0, PSCSR0–3, TIMECSR, CSR9, CNT3, RXCSR0, MACCSR0–2, RXCSR3, RALINKCSR, LEDCSR,
then CSR1 soft reset and CSR1 host ready.
5. rt2x00_init_write_mac(rt2x00pci, net_dev)
→ write MAC address to CSR3/CSR4.
6. rt2x00_init_bbp(rt2x00pci)
→ write BBP init values (and EEPROM BBP overrides).
7. Read CSR7, then write it back to clear any pending interrupts.
8. request_irq(pci_dev->irq, rt2x00_interrupt, SA_SHIRQ, net_dev->name, device)
→ register shared IRQ; dev_id = device.
9. Enable interrupts in CSR8 (clear mask bits for TBCN_EXPIRE, TXDONE_*, RXDONE).
If any step fails, rt2x00_dev_radio_on() frees the rings and returns an error; the interface does not come up.
3.3 Configuration Updates
At runtime, configuration changes (channel, rate, BSSID, etc.) go through rt2x00_update_config() → handler->dev_update_config() → rt2x00_dev_update_config(). It dispatches on update_flags and updates BBP/RF/CSR as needed (e.g. UPDATE_CHANNEL → rt2x00_dev_update_channel).
4. Callback and Hook Registration
4.1 PCI / Core Handler Table
The PCI driver provides a handler table to the core:
// rt2400pci.c
static struct _rt2x00_dev_handler rt2x00_pci_handler = {
.dev_module = THIS_MODULE,
.dev_probe = rt2x00_dev_probe,
.dev_remove = rt2x00_dev_remove,
.dev_radio_on = rt2x00_dev_radio_on,
.dev_radio_off = rt2x00_dev_radio_off,
.dev_update_config = rt2x00_dev_update_config,
.dev_update_stats = rt2x00_dev_update_stats,
.dev_test_tx = rt2x00_dev_test_tx,
.dev_xmit_packet = rt2x00_dev_xmit_packet,
};
This is passed to rt2x00_core_probe(&rt2x00_pci_handler, pci_dev, ...). The core stores device->handler and calls these callbacks at the appropriate times.
4.2 ieee80211 Hooks
During rt2x00_core_probe(), the core overrides ieee80211 callbacks:
core->ieee80211->hard_start_xmit = rt2x00_start_xmit;
core->ieee80211->set_security = rt2x00_set_security;
core->ieee80211->handle_auth = rt2x00_handle_auth;
core->ieee80211->handle_deauth = rt2x00_handle_deauth;
// ... handle_disassoc, handle_probe_request, handle_assoc_response,
// handle_beacon, handle_probe_response
The network device still uses ieee80211_xmit as dev->hard_start_xmit (set in alloc_ieee80211). The ieee80211 stack converts an incoming skb into ieee80211_txb fragments and then calls ieee->hard_start_xmit, which is rt2x00_start_xmit. So the TX entry point from the IP layer is:
IP → netdev → ieee80211_xmit → ieee80211_txb → ieee->hard_start_xmit (= rt2x00_start_xmit)
4.3 net_device Ops
The core also sets:
net_dev->open = rt2x00_open;
net_dev->stop = rt2x00_stop;
net_dev->change_mtu = rt2x00_change_mtu;
net_dev->tx_timeout = rt2x00_tx_timeout;
net_dev->get_stats = rt2x00_get_stats;
So open/close and TX timeout are handled by the rt2x00 core, which in turn uses the PCI handler for radio and ring operations.
5. TX Path: IP Layer → WiFi Card
5.1 High-Level TX Flow
[IP/Network stack]
│
▼
dev_queue_xmit(skb) → net_device
│
▼
dev->hard_start_xmit = ieee80211_xmit(skb, dev)
│
▼
[ieee80211 stack: build 802.11 headers, encrypt, fragment]
│
▼
ieee->hard_start_xmit(txb, dev, priority) = rt2x00_start_xmit(txb, net_dev, pri)
│
▼
[rt2x00: queue skbs to core->tx, schedule work]
│
▼
rt2x00_delayed_tx (workqueue)
│
▼
rt2x00_xmit_packet → handler->dev_xmit_packet = rt2x00_dev_xmit_packet
│
▼
[PCI: copy to DMA ring, write TX descriptor, kick hardware via TXCSR0]
│
▼
[Hardware transmits over air]
5.2 ieee80211_xmit (IP → 802.11)
ieee80211_xmit(skb, dev)is thehard_start_xmitfor the net_device.- It pulls Ethernet header, builds 802.11 header (addresses, duration, etc.), optionally encrypts, fragments into
ieee80211_txb. - It then calls
ieee->hard_start_xmit(txb, dev, priority), which isrt2x00_start_xmit.
5.3 rt2x00_start_xmit
// rt2x00core.c
static int rt2x00_start_xmit(struct ieee80211_txb *txb, struct net_device *net_dev, int pri)
{
struct _rt2x00_device *device = rt2x00_device(net_dev);
struct _rt2x00_core *core = rt2x00_core(device);
...
if (!test_bit(DEVICE_RADIO_ON, &device->flags))
return -ENOTCONN;
spin_lock(&core->tx.lock);
for (each fragment in txb) {
skb = txb->fragments[counter];
set seq_ctl, skb->cb (id, packets), priority;
skb_queue_tail(&core->tx.queue, skb);
}
if (core->tx.ready)
queue_work(core->workqueue, &core->tx.work); // → rt2x00_delayed_tx
spin_unlock(&core->tx.lock);
ieee80211_txb_free(txb);
return 0;
}
So TX frames are queued in core->tx (a packet ring), and rt2x00_delayed_tx is scheduled on the rt2x00 workqueue. rt2x00_kick_ring(&core->tx) also ensures the TX work is run when there are packets and the ring is ready.
5.4 rt2x00_delayed_tx (Workqueue)
rt2x00_delayed_txruns oncore->workqueue.- It drains
core->tx.queue, and for each packet (or fragment group): handler->dev_test_tx(device, fragments)→rt2x00_dev_test_tx: checksrt2x00_ring_free_entries(&rt2x00pci->tx); returns non‑zero if there isn’t enough space in the hardware TX ring.rt2x00_xmit_packet(core, ring, rate, xmit_flags):- Dequeues an skb, fills duration, IFS, etc.
- Calls
handler->dev_xmit_packet(device, skb, RING_TX, rate, xmit_flags).
- After processing queued skbs, it calls
dev_xmit_packet(device, NULL, RING_TX, 0, XMIT_START)to kick the hardware.
5.5 rt2x00_dev_xmit_packet (PCI → Hardware)
// rt2400pci.c
static int rt2x00_dev_xmit_packet(struct _rt2x00_device *device, struct sk_buff *skb,
u8 ring_type, u16 rate, u16 xmit_flags)
{
struct _rt2x00_pci *rt2x00pci = rt2x00_priv(device);
struct _data_ring *ring;
struct _txd *txd;
void *data;
u32 reg;
rt2x00_register_read(rt2x00pci, TXCSR0, ®);
// Set TXCSR0_KICK_TX / KICK_PRIO / KICK_ATIM depending on ring_type.
if (skb) {
ring = &rt2x00pci->tx; // (or prio, atim, beacon)
txd = DESC_ADDR(ring);
data = DATA_ADDR(ring);
if (TXD still owned by NIC or VALID)
return -ENOMEM;
memcpy(data, skb->data, skb->len);
rt2x00_write_tx_desc(rt2x00pci, txd, skb->len, rate, skb->priority, xmit_flags);
rt2x00_ring_index_inc(ring);
}
if (xmit_flags & XMIT_START)
rt2x00_register_write(rt2x00pci, TXCSR0, reg);
return 0;
}
DESC_ADDR/DATA_ADDRindex into the coherent DMA ring (descriptors first, then data buffers).rt2x00_write_tx_descfills the TXD (valid, length, ack, retry, IFS, PLCP fields, etc.) and setsTXD_W0_OWNER_NIC = 1so the hardware owns the descriptor.rt2x00_register_write(..., TXCSR0, reg)withTXCSR0_KICK_TX(or KICK_PRIO/KICK_ATIM) starts the hardware TX engine on that ring.
So the TX path is: IP → ieee80211_xmit → rt2x00_start_xmit → queue to core TX → rt2x00_delayed_tx → rt2x00_dev_xmit_packet → DMA ring + TXCSR0 kick → hardware.
6. RX Path: WiFi Card → IP Layer
6.1 High-Level RX Flow
[Hardware receives 802.11 frame, DMA into RX ring]
│
▼
[Hardware sets RXD, raises interrupt]
│
▼
rt2x00_interrupt(irq, dev_instance)
│
▼
rt2x00_interrupt_rxdone(&rt2x00pci->rx)
│
▼
[For each completed RXD: rt2x00_ring_rx_packet(device, size, data, rssi)]
│
▼
rt2x00_rx(device, skb) → rt2x00_queue_ring(&core->rx, skb)
│
▼
queue_work(workqueue, &core->rx.work) → rt2x00_delayed_rx
│
▼
[ieee80211_rx or ieee80211_rx_mgt]
│
▼
netif_rx(skb) → [IP layer]
6.2 Interrupt and rt2x00_interrupt_rxdone
When the device raises an interrupt, rt2x00_interrupt runs (see § 7). It reads CSR7 (interrupt source), writes it back to clear, then handles CSR7_RXDONE by calling rt2x00_interrupt_rxdone(&rt2x00pci->rx).
rt2x00_interrupt_rxdone (in rt2400pci.c):
while (1) {
rxd = DESC_ADDR(ring);
data = DATA_ADDR(ring);
if (rt2x00_get_field32(rxd->word0, RXD_W0_OWNER_NIC))
break; // descriptor still owned by NIC → no more RX
size = rt2x00_get_field32(rxd->word0, RXD_W0_DATABYTE_COUNT);
rssi = rt2x00_get_field32(rxd->word2, RXD_W2_RSSI);
if (CRC error / physical error / low RSSI)
update stats;
else
rt2x00_ring_rx_packet(ring->device, size, data, rssi);
rt2x00_set_field32(&rxd->word0, RXD_W0_OWNER_NIC, 1); // give back to NIC
rt2x00_ring_index_inc(&rt2x00pci->rx);
}
RXD_W0_OWNER_NIC: 0 = driver can use descriptor; 1 = NIC owns it. The loop breaks when the next descriptor is still owned by the NIC.- Valid frames are passed to
rt2x00_ring_rx_packet.
6.3 rt2x00_ring_rx_packet and rt2x00_rx
rt2x00_ring_rx_packet (in rt2x00dev.h):
skb = dev_alloc_skb(size + NET_IP_ALIGN);
skb_reserve(skb, NET_IP_ALIGN);
memcpy(skb_put(skb, size), data, size);
((struct _skb_cb *)skb->cb)->rssi = rssi;
rt2x00_rx(device, skb);
rt2x00_rx (in rt2x00core.c):
void rt2x00_rx(struct _rt2x00_device *device, struct sk_buff *skb)
{
rt2x00_queue_ring(&core->rx, skb);
}
rt2x00_queue_ring (in rt2x00core.h):
skb_queue_tail(&ring->queue, skb);
if (ring->ready)
queue_work(ring->core->workqueue, &ring->work);
So the RX packet ring is core->rx. When ring->ready is set, rt2x00_delayed_rx is run on the workqueue.
6.4 rt2x00_delayed_rx and Upstream to IP
rt2x00_delayed_rx:
- Drains
core->rx.queue. - Fills
ieee80211_rx_stats(rate, channel, freq, rssi, etc.). - For each skb:
- Management frames (and not monitor):
ieee80211_rx_mgtorrt2x00_handle_assoc, thendev_kfree_skb_any. - Data / other:
ieee80211_rx(ieee, skb, &rx_stats).ieee80211_rxdecrypts (if needed), strips 802.11 header, converts to Ethernet format, and passes the skb tonetif_rx(skb), which delivers it to the IP layer.
So RX path: Hardware → interrupt → rxdone → ring_rx_packet → queue_ring → delayed_rx → ieee80211_rx → netif_rx → IP.
7. Interrupt Handling
7.1 Registration
The interrupt is registered in rt2x00_dev_radio_on():
request_irq(rt2x00pci->pci_dev->irq, rt2x00_interrupt, SA_SHIRQ,
device->net_dev->name, device);
- Handler:
rt2x00_interrupt. - SA_SHIRQ: shared interrupt.
- dev_id:
device(struct _rt2x00_device). - Freed in
rt2x00_dev_radio_off()viafree_irq(rt2x00pci->pci_dev->irq, device).
7.2 Interrupt Registers
CSR7 (offset 0x1c): Interrupt source (read to see what fired; write same value to clear).
CSR8 (offset 0x20): Interrupt mask. Bit = 1 disables that source.
Relevant bits (from rt2400pci.h):
| Bit | CSR7 / CSR8 | Meaning |
|---|---|---|
| 0 | TBCN_EXPIRE | Beacon timer expired |
| 1 | TWAKE_EXPIRE | Wakeup timer expired |
| 2 | TATIMW_EXPIRE | ATIM window timer |
| 3 | TXDONE_TXRING | TX ring done |
| 4 | TXDONE_ATIMRING | ATIM ring done |
| 5 | TXDONE_PRIORING | Priority ring done |
| 6 | RXDONE | RX done |
On radio on, the driver reads CSR7, writes it back (clear), then configures CSR8 so that the mask bits for these sources are 0 (interrupts enabled). On radio off, it sets those mask bits to 1 (disable), then free_irq.
7.3 Handler Logic: rt2x00_interrupt
static irqreturn_t rt2x00_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
{
struct _rt2x00_device *device = (struct _rt2x00_device *)dev_instance;
struct _rt2x00_pci *rt2x00pci = rt2x00_priv(device);
u32 reg = 0;
u8 ring_type = 0;
rt2x00_register_read(rt2x00pci, CSR7, ®);
rt2x00_register_write(rt2x00pci, CSR7, reg); // clear by write-back
if (!reg)
return IRQ_NONE;
if (CSR7_TBCN_EXPIRE) rt2x00_tx(device, RING_BEACON);
if (CSR7_RXDONE) rt2x00_interrupt_rxdone(&rt2x00pci->rx);
if (CSR7_TXDONE_ATIMRING) { rt2x00_interrupt_txdone(&rt2x00pci->atim); ring_type |= RING_ATIM; }
if (CSR7_TXDONE_PRIORING) { rt2x00_interrupt_txdone(&rt2x00pci->prio); ring_type |= RING_PRIO; }
if (CSR7_TXDONE_TXRING) { rt2x00_interrupt_txdone(&rt2x00pci->tx); ring_type |= RING_TX; }
if (ring_type)
rt2x00_tx(device, ring_type);
return IRQ_HANDLED;
}
- Read CSR7 → write same value clears pending interrupts.
- RXDONE →
rt2x00_interrupt_rxdone(process RX ring, queue skbs tocore->rx, schedulert2x00_delayed_rx). - TXDONE_ →
rt2x00_interrupt_txdonefor the corresponding data ring (atim, prio, tx).rt2x00_interrupt_txdonewalks completed TX descriptors, updates stats, clears VALID, advancesindex_done*. - Then
rt2x00_tx(device, ring_type)is called to kick the packet rings (tx/prio/atim) so thatrt2x00_delayed_txcan push more packets into the hardware TX ring.
7.4 TX Done Processing: rt2x00_interrupt_txdone
static void rt2x00_interrupt_txdone(struct _data_ring *ring)
{
do {
txd = DESC_ADDR_DONE(ring);
if (TXD_W0_OWNER_NIC || !TXD_W0_VALID)
break;
// update stats from TXD_W0_RESULT, TXD_W0_RETRY_COUNT
rt2x00_set_field32(&txd->word0, TXD_W0_VALID, 0);
rt2x00_ring_index_done_inc(ring);
} while (!rt2x00_ring_empty(ring));
}
DESC_ADDR_DONEusesindex_done;DESC_ADDRusesindex.- Driver reclaims descriptors by clearing VALID and advancing
index_done. This frees space in the hardware TX ring fordev_test_tx/dev_xmit_packet.
8. Data Structures and Rings
8.1 Two Ring Layers
-
Packet rings (
struct _packet_ringin core):
core->rx,core->tx, atim, prio, beacon.
- Hold skb queues and work structs forrt2x00_delayed_rx/rt2x00_delayed_tx/rt2x00_delayed_mgmt. -
Data rings (
struct _data_ringin PCI):
rt2x00pci->rx, tx, atim, prio, beacon.
- DMA coherent memory: descriptors + data. - Descriptors: RXD (rx) or TXD (tx); DATA holds raw frame bytes. -index/index_donemanage producer/consumer.
8.2 Descriptor Layout (Conceptual)
- RXD: word0 (OWNER_NIC, CRC, DATABYTE_COUNT, …), word1 (buffer address), word2 (RSSI, …).
- TXD: word0 (VALID, OWNER_NIC, IFS, ACK, …), word1 (buffer address), word2 (length), word3/ word4 (PLCP, etc.).
DESC_BASE / DATA_BASE in rt2x00pci.h define the layout; __DESC_ADDR / __DATA_ADDR compute per‑entry pointers.
8.3 Ring Sizes (rt2400pci)
Defined in rt2x00pci.h via rt2x00_pci_alloc_ring:
- RX: RX_ENTRIES, DATA_FRAME_SIZE (2432), SIZE_DESCRIPTOR.
- TX / ATIM / PRIO / BEACON: TX_ENTRIES, ATIM_ENTRIES, etc., MGMT_FRAME_SIZE (256) or DATA_FRAME_SIZE.
9. Summary Diagrams
9.1 Probe and Init
insmod rt2400pci
│
▼
pci_register_driver
│
▼
PCI core finds 1814:0101 → rt2x00_pci_probe
│
├─ pci_enable_device, set_master, request_regions
│
▼
rt2x00_core_probe(handler, pci_dev, ...)
│
├─ alloc_ieee80211 → net_dev
├─ init packet rings (rx,tx,atim,prio,beacon)
├─ handler->dev_probe → rt2x00_dev_probe
│ ├─ ioremap BAR0 → csr_addr
│ ├─ init EEPROM, read MAC
│ └─ set device caps
├─ Override ieee80211 hard_start_xmit = rt2x00_start_xmit
├─ net_dev->open = rt2x00_open, etc.
└─ register_netdev(net_dev)
ifconfig wlan0 up
│
▼
rt2x00_open → rt2x00_radio_on
│
├─ rt2x00_enable_ring(rx,tx,atim,prio,beacon) [packet rings]
└─ handler->dev_radio_on → rt2x00_dev_radio_on
├─ pci alloc DMA rings, clear, init ring regs
├─ init regs, BBP, MAC
├─ request_irq(rt2x00_interrupt)
└─ enable interrupts in CSR8
│
▼
rt2x00_activate_config → rt2x00_link_up (scan / monitor)
│
└─ rt2x00_connect (when network chosen) configures BSS, etc.
9.2 TX Path (Simplified)
IP → dev_queue_xmit
→ ieee80211_xmit (build 802.11, encrypt, fragment)
→ rt2x00_start_xmit (queue to core->tx, queue_work)
→ rt2x00_delayed_tx
→ dev_test_tx / rt2x00_xmit_packet
→ rt2x00_dev_xmit_packet (copy to DMA, write TXD, kick TXCSR0)
→ hardware TX
9.3 RX Path (Simplified)
hardware RX → DMA to RX ring → raise IRQ
→ rt2x00_interrupt
→ rt2x00_interrupt_rxdone (read RXD, copy to skb)
→ rt2x00_ring_rx_packet → rt2x00_rx → rt2x00_queue_ring(&core->rx)
→ queue_work(rt2x00_delayed_rx)
→ ieee80211_rx / ieee80211_rx_mgt
→ netif_rx → IP
9.4 Interrupt Handling
IRQ → rt2x00_interrupt
├─ read CSR7, write back (clear)
├─ RXDONE → rt2x00_interrupt_rxdone(rx)
├─ TXDONE_TX → rt2x00_interrupt_txdone(tx); rt2x00_tx(RING_TX)
├─ TXDONE_PRIO→ rt2x00_interrupt_txdone(prio); rt2x00_tx(RING_PRIO)
├─ TXDONE_ATIM→ rt2x00_interrupt_txdone(atim); rt2x00_tx(RING_ATIM)
└─ TBCN_EXPIRE → rt2x00_tx(RING_BEACON)
10. SSID Discovery (Scanning), Encryption, and Connecting to an SSID
This section describes how the driver finds SSIDs (scanning), supports different encryption methods (WEP, WPA/WPA2, TKIP, CCMP), and connects to an SSID (auth, assoc, config).
10.1 How the Driver Finds SSIDs (Scanning)
10.1.1 Triggering a Scan
- Userspace requests a scan via
SIOCSIWSCAN(e.g.iwlist wlan0 scan). The handler isrt2x00_ioctl_set_scan. - If the device is not yet on, it calls
rt2x00_radio_onandrt2x00_activate_config. - It then calls
rt2x00_start_scan(device, scan_req, flags).
scan_reqcan beNULL(default scan) or astruct iw_scan_reqwith optional ESSID, BSSID, channel list, and scan type (active vs passive).
10.1.2 Scan Request Setup
rt2x00_start_scan (in rt2x00core.c):
- Sets
DEVICE_SCANNING, takescore->scan_sem, allocatescore->scan_req. IW_SCAN_DEFAULT: BSSID = broadcast (0xff...); no fixed ESSID.IW_SCAN_THIS_ESSID: usesscan_req->essidandessid_lenfor a directed probe (only that SSID).IW_SCAN_THIS_FREQ: scan only the current channel; otherwise it builds a channel list fromieee80211_geo(2.4 GHz and/or 5 GHz) or fromscan_req->channel_list.- Switches to the first channel, calls
rt2x00_update_config(so hardware uses that channel). - Active scan:
rt2x00_mgmt_snd_probe_reqsends a Probe Request with: - SSID IE fromscan_req->essid(or empty for broadcast); - Rates IE; - DA =scan_req->bssid(broadcast or specific AP). - Schedules
rt2x00_scan_periodicon the workqueue afterSCAN_TIME(HZ/5).
So the driver finds SSIDs by either listening for Beacons (passive) or sending Probe Requests (active). Probe Requests can target any SSID (empty IE) or a specific SSID when the user asks to scan for one.
10.1.3 Periodic Scan and Channel Switching
rt2x00_scan_periodic:
- If
IW_SCAN_THIS_FREQor only one channel:rt2x00_stop_scan→ scan done,SIOCGIWSCANevent. - Otherwise: switch to next channel in
scan_req->channel_list,rt2x00_update_config, and againrt2x00_mgmt_snd_probe_reqwhen active. - Re-schedules itself after
SCAN_TIMEuntil all channels are done, thenrt2x00_stop_scan.
10.1.4 Where SSIDs Come From: Beacons and Probe Responses
- Beacons and Probe Responses received from APs are handled in
rt2x00_delayed_rx→ieee80211_rx_mgt/rt2x00_handle_beacon/rt2x00_handle_probe_resp. - The ieee80211 stack parses Information Elements and fills
struct ieee80211_network: MFIE_TYPE_SSID→network->ssid,ssid_len;MFIE_TYPE_DS_SET→network->channel;MFIE_TYPE_RATES→ supported rates;MFIE_TYPE_GENERIC(OUI00:50:f2:01) → WPA IE →network->wpa_ie,wpa_ie_len;MFIE_TYPE_RSN→ RSN IE →network->rsn_ie,rsn_ie_len;WLAN_CAPABILITY_PRIVACY→ network uses encryption.- These
ieee80211_networkentries are stored inieee80211->network_listand exposed to userspace viaSIOCGIWSCAN(rt2x00_ioctl_get_scan→ieee80211_wx_get_scan).
So SSID discovery is: scan (passive/active) → receive Beacons/Probe Responses → parse IEs → store networks → userspace reads scan results.
10.1.5 Choosing an SSID to Connect To
- User sets ESSID via
SIOCSIWESSID(rt2x00_ioctl_set_essid): copiesextraintocore->config.user.essid, setsCONFIG_FIX_ESSID,COMMIT_ESSID. - Optional BSSID via
SIOCSIWAP(rt2x00_ioctl_set_wap):CONFIG_FIX_BSSID,COMMIT_BSSID. rt2x00_link_periodic(link tuner) callsrt2x00_search_scanto pick anieee80211_networkfrom the scan list.rt2x00_validate_scan_entryfilters by:CONFIG_FIX_ESSID: SSID must matchconfig->active.essid(and length);CONFIG_FIX_BSSID: BSSID must match;CONFIG_FIX_CHANNEL: channel must match;- mode (Infra vs Ad‑hoc) and capability (ESS/IBSS);
- encryption:
WLAN_CAPABILITY_PRIVACYvsieee80211->sec.enabled(andsec.level,auth_mode) must be consistent. - The best match (e.g. by RSSI) is used; then
rt2x00_connect(device, network, mode)runs.
10.2 Encryption Methods and Security Setup
10.2.1 Supported Algorithms
The driver works with the ieee80211 crypto layer. range->enc_capa advertises:
range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2
| IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP;
encoding_size: 5 (64‑bit WEP), 13 (128‑bit WEP); max_encoding_tokens = 4 (WEP keys).
| Type | Module | Cipher | Use |
|---|---|---|---|
| WEP | ieee80211_crypt_wep |
RC4 | Open/shared key, 64/128 bit |
| TKIP | ieee80211_crypt_tkip |
TKIP | WPA (and WPA2) |
| CCMP | ieee80211_crypt_ccmp |
AES-CCMP | WPA2 |
Keys and algorithms are configured via Wireless Extensions (SIOCSIWENCODE, SIOCSIWENCODEEXT, etc.). The ieee80211 stack maintains ieee->crypt[] and selects ieee80211_get_crypto_ops (e.g. "WEP", "TKIP", "CCMP"). When host encryption/decryption is used, ieee80211_xmit encrypts and ieee80211_rx decrypts via crypt->ops->encrypt_* / decrypt_*.
10.2.2 Driver Security Hook: rt2x00_set_security
The core sets ieee->set_security = rt2x00_set_security. When the ieee80211 stack updates keys/algos (e.g. after SIOCSIWENCODE), it calls set_security(ieee->dev, &sec).
rt2x00_set_security (in rt2x00core.c):
- Copies keys, key sizes, encode alg per key from
secintocore->ieee80211->sec. - Updates active key, auth mode, enabled, encrypt, level.
- Sets
COMMIT_ENCRYPTIONsort2x00_activate_config/rt2x00_update_configapply security-related config.
So encryption is configured by userspace → Wireless Extensions → ieee80211 crypto → set_security → driver sec and COMMIT_ENCRYPTION.
10.2.3 WPA / WPA2 and WPA IE
- WPA IE (and RSN) are set via
SIOCSIWGENIE(rt2x00_ioctl_set_genie): stored incore->ieee80211->wpa_ie,wpa_ie_len;COMMIT_ENCRYPTION. IW_AUTH_*(rt2x00_ioctl_set_auth): WPA version (WPA vs WPA2), drop unencrypted, open WEP, WPA enabled, RX unencrypted EAPOL, etc.- For Auth (and Auth shake) and Assoc,
rt2x00_create_mgmt_buffercan add WPA/RSN IE whenMGMT_BUFFER_CHALLENGEandwpa_ieare set, matchingwpa_version(WPA →MFIE_TYPE_GENERIC, WPA2 →MFIE_TYPE_RSN).
Management frames (e.g. WPA Auth) can be encrypted via rt2x00_encrypt_mgmt_frame, which uses ieee->crypt[tx_keyidx] and encrypt_msdu / encrypt_mpdu.
10.3 Connecting to an SSID (Auth, Assoc, Config)
10.3.1 High-Level Flow
- User sets ESSID (and optionally BSSID, channel), encryption (WEP/WPA/WPA2, keys, WPA IE), then
ifconfig wlan0 upor similar. rt2x00_link_upstartsrt2x00_start_scan(orrt2x00_connectin monitor mode).- Link tuner (
rt2x00_link_periodic): when not connected,rt2x00_search_scanpicks a matchingieee80211_network(SSID, BSSID, channel, encryption). Thenrt2x00_connect(device, network, mode). rt2x00_connect: - Storescore->network, setsiw_mode, BSSID; -rt2x00_link_up_config: copies SSID, BSSID, channel, rates fromcore->networkintoconfig.active; -rt2x00_update_config→dev_update_config(e.g. BBP channel, BSSID filter); - Sends Auth (rt2x00_mgmt_snd_auth); for WPA, Auth shake (rt2x00_mgmt_snd_auth_shake) with WPA IE.rt2x00_handle_auth: on Auth response, checkssec(encryption, level) and ieee802_1x; thenrt2x00_mgmt_snd_assoc.rt2x00_handle_assoc_resp(viahandle_assoc_response):rt2x00_start_connection→netif_carrier_on,netif_start_queue, enable beacon (Ad‑hoc), etc.
10.3.2 Summary
| Step | Driver / stack |
|---|---|
| Find SSIDs | Scan (SIOCSIWSCAN) → Probe Req (optional SSID) / Beacons → parse IEs → network_list → SIOCGIWSCAN |
| Select SSID | SIOCSIWESSID / SIOCSIWAP → config.user; rt2x00_search_scan + rt2x00_validate_scan_entry (SSID, BSSID, channel, encryption) |
| Set encryption | SIOCSIWENCODE / SIOCSIWENCODEEXT, SIOCSIWGENIE, IW_AUTH_ → ieee->crypt + wpa_ie → rt2x00_set_security → COMMIT_ENCRYPTION* |
| Connect | rt2x00_connect → link_up_config → Auth (WPA IE if WPA/WPA2) → Assoc → start_connection |
So the driver helps find SSIDs via scanning (active/passive), stores them from Beacons/Probe Responses (including WPA/RSN IE), filters by SSID/BSSID/channel/encryption when connecting, and supports WEP, WPA, WPA2, TKIP, and CCMP through the ieee80211 crypto layer and set_security / WPA IE path.
References (rt2x00-2.0.0-b3)
| Topic | File(s) | Relevant symbols |
|---|---|---|
| PCI driver, probe, remove | rt2400pci.c |
rt2x00_pci_probe, rt2x00_pci_remove, rt2x00_pci_init |
| PCI handler, rings, MMIO | rt2x00pci.h |
rt2x00_pci_alloc_ring, rt2x00_register_read/_write, DESC_ADDR |
| Core probe, open, start_xmit | rt2x00core.c |
rt2x00_core_probe, rt2x00_open, rt2x00_start_xmit |
| TX/RX work | rt2x00core.c |
rt2x00_delayed_tx, rt2x00_delayed_rx, rt2x00_xmit_packet |
| Interrupt, RX/TX done | rt2400pci.c |
rt2x00_interrupt, rt2x00_interrupt_rxdone, rt2x00_interrupt_txdone |
| Device probe, radio on/off | rt2400pci.c |
rt2x00_dev_probe, rt2x00_dev_radio_on, rt2x00_dev_radio_off |
| Xmit packet, TX desc | rt2400pci.c |
rt2x00_dev_xmit_packet, rt2x00_write_tx_desc |
| RX packet, queue | rt2x00dev.h, rt2x00core.h |
rt2x00_ring_rx_packet, rt2x00_rx, rt2x00_queue_ring |
| ieee80211 TX/RX | ieee80211/ieee80211_tx.c, ieee80211_rx.c |
ieee80211_xmit, ieee80211_rx, netif_rx |
| Registers | rt2400pci.h |
CSR7, CSR8, TXCSR0, RXD_W0_*, TXD_W0_* |
| Scan, SSID, connect | rt2x00core.c |
rt2x00_start_scan, rt2x00_scan_periodic, rt2x00_mgmt_snd_probe_req, rt2x00_search_scan, rt2x00_validate_scan_entry, rt2x00_connect |
| Encryption, WPA IE | rt2x00core.c, ieee80211/ieee80211_wx.c |
rt2x00_set_security, rt2x00_create_mgmt_buffer, rt2x00_encrypt_mgmt_frame, ieee->crypt, wpa_ie |
| Crypto modules | ieee80211/ieee80211_crypt_*.c |
ieee80211_crypt_wep, ieee80211_crypt_tkip, ieee80211_crypt_ccmp |
Document describes the rt2x00-2.0.0-b3 driver and its PCI probe, initialization, configuration, callbacks, TX/RX paths, interrupt handling, SSID discovery (scanning), encryption support, and connecting to an SSID.