Analysis of CISCO AIRONET Driver program

by Zhibin Wu

Introduction
  1. Config, Status, and Statistics
  2. Compile a Module

Modification Example

  1. Delay , Queue
  2. Tx-Power Adjust

New airo drivers


Sourcecode Index: v1.0.1, v1.0.2, v1.0.3, v1.0.4, v1.0.5, v1.1.0, v2.0.1, v2.0.2, v2.0.3, v3.0.1, v3.0.2, v3.0.3, v3.0.4, v3.0.5 v.3.0.6 v5.0.1
Archive of Sourcecode: ZIP file


Introduction

The Linux driver sourcecode (ususlaly located in /usr/src/linux-?.?.?/drivers/net/wireless/) airo.c is the only open source for us to know how MAC controller of CISCO Aironet 340 (350) card is working.

Generally speaking, MAC controller is a chip, which provide an interface (Host interface) to the CPU. Those interface includes some registers (command register, etc), transmit and receiver FIFOs which is accessible by CPU. CPU can send bytes (words) through the interface to issue a command, set and clear interrupts , and send data to chip tx buffers, etc. Also, CPU read the values of registers and know the status of chip, know the interrupt vector... etc.
Those detail issues shoud be in a technical specification of the chip. however , currently those important information for developer seems unavailable for public.

In spite of this adversity, we still get to know a lot form the source code airo.c which is enclosed in the linux-2.4.18 package.

Some Detail Descriptions of how the driver works is listed below

1. Initialize
When a device is to be initialized, a " airo_init_module " is called. In this function, "init_airo_card" is executed to hook many functions implemented locally in the driver to the general calls in the net_device. For example, "airo_start_xmit" is hooked to "hard_start-xmit" of the device. This ensures that the transimission will be performed according to the codes of this driver.

2. Transmit

When the system is going to transmit a packet using this device , he will  put the packet data in a "sk_buff"  structure. This parameter and another
parameter "dev", which is a pointer of "net_device" structure, are passed to the "airo_start_xmit" function to begin a transmit.
When getting access to the skb, the length and other important information can be obtained . Also, the skb includes a pointer to "dev", which means
actually, only this parameter is enough! This feature is utilized when I wrote the function for "airo_delay_xmit", I use only one parameter.
The underlying communication to the chip (mac controller ) is fully encapsulated in the function "transmit_802_3_packet"

3 Receiver
There are no specific reciver functions. Reception of the packets are handled in the interruput handler. An interrupt will be generated when a packet
arrives and the driver program will access interrupt registers to handle it.

4. Using Timer
A good way to set timers in MAC layer is  using kernel timers. There is a structure of  "timer_list". Set the timer with timer.expire parameter, and using "add_timer " to start the timer. When timer is out, it will call the function you specified in "timer_list" structuer and the parameters of the
function is just the "data" , another variable in the structure.
Although it is named a timer_list. I don't use it as a list. I use it as a single timer. The "data" I specified is the "skb" pointer. So, when timeout happens,  this "skb" will be used for transmitting. Another way to specify "data" is just use "dev" or "airo_info" structure's pointer, then everything you need to read or write is handily available, too. Following is an example of timer, compiled in Linux-2.4.21.

struct airo_info {
....
struct timer_list timer3;
....
}
...
static u16 setup_card(struct airo_info *ai, u8 *mac)
{
if( ...&& !timer_pending(&ai->timer3) )
ai->timer3.expires = RUN_AT(HZ*1);
add_timer(&ai->timer3);

...
}


static void timer3_func( u_long data )
{
//ConfigRid config; /* Configuration info */
struct airo_info *apriv = (struct airo_info *)data;
ConfigRid config = apriv->config;

//printk ("Timer function is being called! \n");
if (apriv->power_flag == 0 )
{
config.txPower = 100;
apriv->power_flag = 1;
}
else
{
config.txPower = 5;
apriv->power_flag = 0;
}

checkThrottle(apriv);
writeConfigRid(apriv);

/* Schedule next power adjust */
apriv->timer.expires = RUN_AT(HZ*1);
add_timer(&apriv->timer3);

}




static int add_airo_dev( struct net_device *dev ) {
{

struct airo_info *apriv=dev->priv;
struct timer_list *timer3 = &apriv->timer3;


...
timer3->data = (u_long)apriv;
timer3->function = timer3_func;
init_timer(timer3);

...
}

void stop_airo_card( struct net_device *dev, int freeres )
{
struct airo_info *ai = dev->priv;
...
del_timer_sync(&ai->timer3);
...
}




5. Access private data
The private data in the net_device is very important. it is an airo_info structure.

Implement a Trivial FIFO Queue

Queue = delay. Both sides of the driver, up and down,  the "netif" of kernel and the "chip" of hardware has FIFO queues, thus, if additional delay is needed in the dirver, we don't need to have a real Queue, just protect the "skb" buffer to be unaltered during the delay.
The basic things to be done:

Implementing Multiple Queues and Scheduling

If we are design mechanisms to distinguish different flows to different destinations. This require multiple Queues for one server ( the MAC controller chip). For this purpose, we have to implement following:

A prototype of queue:
define a new function to check the buffer is full, the avialable pos for store the packet is returned. 
struct * airo info
{
.....
int pos[BUFFER_NUM]; /* at most 100 packets will be buffered */
int pktlen[BUFFER_NUM];
int head_pos; //current head of the queue
int tail_pos;
char* d_buffer;


...
}


static int queue_pos_for_pkt (struct airo_info *ai, int pkt_length)
{

int new_tail, head;
head = ai->pos[ai->head_pos] ;
new_tail = ai->pos[ai->tail_pos] + ai->pktlen1[ai->tail_pos];
if (BUF_SIZE - new_tail > pkt_length )
return new_tail;
if ( head > pkt_length)
return 0;
// if no places for the packet
return -1;
}



The enqueue function and its call

static int enqueue (struct airo_info *priv, struct sk_buff *skb )
{

int buf_pos;
if ( priv->head_pos == -1) // if queue is empty
{
buf_pos = 0 ;
memcpy (priv->d_buffer+buf_pos, skb->data, len* sizeof(char) );
priv->head_pos = 0;
priv->tail_pos = 0;
priv->pos[0] = 0 ;
priv->pktlen[0] = len ;
}
else //queue is not empty
{
buf_pos = queue_pos_for_pkt (priv, len) ;
if (buf_pos == -1)
{ // no buffer space for new packet, discard it
//dev_kfree_skb(skb);
return 0;
}
memcpy (priv->d_buffer1+buf_pos, skb->data, len*sizeof(char) );
priv->tail_pos++;
if ( priv->tail_pos == BUFFER_NUM ) priv->tail_pos = 0;
priv->pos[priv->tail_pos] = buf_pos;
priv->pktlen[priv->tail_pos] = len;
}
return 1;

}


static int airo_start_xmit(struct sk_buff *skb, struct net_device *dev) {
{
...
struct airo_info *priv = dev->priv;
int res;

if (mac_sch_enable)
{
res = enqueue ( priv, skb);
// set the timer;
dev_kfree_skb(skb);
return 0;
}
...
}

Dequeue funcuton is also implemented as:

static int dequeue (struct airo_info *priv, char * data)
{

if ( priv->head_pos != -1 )
{

pkt_pos = priv->pos[priv->head_pos];
len = priv->pktlen[priv->head_pos];
priv->pos[priv->head_pos] = -1;
priv->pktlen[priv->head_pos] = -1;
(priv->head_pos)++;
if ( priv->head_pos == BUFFER_NUM ) priv->head_pos = 0;
if ( priv->pos[priv->head_pos] == -1 ) priv->head_pos = -1;

//airo_send (priv->d_buffer3+pkt_pos, len, priv);
data = priv->d_buffer+pkt_pos ;
return len;
}
return 0;
}



Get Statistics:

1. RSSI
typedef struct {
...
u16 normalizedSignalStrength;
...
} StatusRid;

The value is in percent 0-100%, the conversion formula to "dbm" is dbm = Rssi/2- 95. For example, if RSSI = 70, dbm = -60dbm.

2. Retry_Count;
 typedef struct {
u16 len;
u16 spacer;
u32 vals[100];
} StatsRid;

vals[0-99]
0: "RxOverrun", // euqivalent to RX_FIFO_ERROR , overrun error
IGNLABEL "RxPlcpCrcErr",
IGNLABEL "RxPlcpFormatErr",
IGNLABEL "RxPlcpLengthErr",
"RxMacCrcErr",
"RxMacCrcOk",
"RxWepErr",
"RxWepOk",
8 "RetryLong", // Long retransmission
9 "RetryShort", // short retransmissions
10: "MaxRetries", // number of packets exceed Max retransmission limit and discarded
11 "NoAck", //number of Ack lost, means packets go unacknowledged
"NoCts",
"RxAck",
"RxCts",
"TxAck",
"TxRts",
"TxCts",
"TxMc",
"TxBc",
"TxUcFrags",
"TxUcPackets",
"TxBeacon",
"RxBeacon",
"TxSinColl",
"TxMulColl",
"DefersNo",
"DefersProt",
"DefersEngy",
"DupFram",
"RxFragDisc",
"TxAged",
"RxAged",
"LostSync-MaxRetry",
"LostSync-MissedBeacons",
"LostSync-ArlExceeded",
"LostSync-Deauth",
"LostSync-Disassoced",
"LostSync-TsfTiming",
39 "HostTxMc",
40 "HostTxBc",
41 "HostTxUc",
42 "HostTxFail", //Tx failures, exclufing tx-fifo-errors(tx-queue dropping), this mean hardware failures leads to tx fail, usually 0
43 "HostRxMc",
44 "HostRxBc",
45 "HostRxUc",
"HostRxDiscard",
IGNLABEL "HmacTxMc",
IGNLABEL "HmacTxBc",
IGNLABEL "HmacTxUc",
IGNLABEL "HmacTxFail",
IGNLABEL "HmacRxMc",
IGNLABEL "HmacRxBc",
IGNLABEL "HmacRxUc",
IGNLABEL "HmacRxDiscard",
IGNLABEL "HmacRxAccepted",
"SsidMismatch",
"ApMismatch",
"RatesMismatch",
"AuthReject",
"AuthTimeout",
"AssocReject",
"AssocTimeout",
IGNLABEL "ReasonOutsideTable",
IGNLABEL "ReasonStatus1",
IGNLABEL "ReasonStatus2",
IGNLABEL "ReasonStatus3",
IGNLABEL "ReasonStatus4",
IGNLABEL "ReasonStatus5",
IGNLABEL "ReasonStatus6",
IGNLABEL "ReasonStatus7",
IGNLABEL "ReasonStatus8",
IGNLABEL "ReasonStatus9",
IGNLABEL "ReasonStatus10",
IGNLABEL "ReasonStatus11",
IGNLABEL "ReasonStatus12",
IGNLABEL "ReasonStatus13",
IGNLABEL "ReasonStatus14",
IGNLABEL "ReasonStatus15",
IGNLABEL "ReasonStatus16",
IGNLABEL "ReasonStatus17",
IGNLABEL "ReasonStatus18",
IGNLABEL "ReasonStatus19",
"RxMan",
"TxMan",
"RxRefresh",
"TxRefresh",
"RxPoll",
"TxPoll",
89: "HostRetries", // actually the collisions the host sensed? how to sense???...
90: "LostSync-HostReq",
91: "HostTxBytes", //number of bytes transmitted
92: "HostRxBytes", //number of bytes received
"ElapsedUsec",
"ElapsedSec",
"LostSyncBetterAP",
"PrivacyMismatch",
"Jammed",
"DiscRxNotWepped",
99: "PhyEleMismatch",
(char*)-1 };
So, we have following candiates for the retry_count:



Compile

When you do "make" and "make install" in the source directory of Linux. A module ario.o will appear in the /lib/modules/2.4.18/kernel/drivers/net/wireless directory. And ario_cs.o is the module for cardservice.
Airo module is a loadable module which can be loaded with "insmod" command. It is  Also, this module (airo.o) will be loaded when you insert the card into PCMCIA slot and will be removed if the card is removed.
 

Dynamical TX-Power Configuration in Driver;

Instead of using "iwconfig", following code make the card switch between "15dbm" and "0dbm" with a 1 second cycle, the resulting PER variation is also going to be printed in /var/log/messages  as an indicator of Long retry count.
Another basic thing is that ConfigureRid and Transmit both use BAP1. So you cannot really configure the card while it is transmitting. So, first disable it and enable it afterwards.
	struct airo_info *apriv = (struct airo_info *)data;
Resp rsp;
StatsRid stats_rid;
/* Get stats out of the card */
readStatsRid(apriv, &stats_rid, RID_STATS);

//printk ("Timer function is being called! \n");
if (apriv->power_flag == 0 )
{
apriv->config.txPower = 30;
apriv->power_flag = 1;
}
else
{
apriv->config.txPower = 1;
apriv->power_flag = 0;
}

apriv->need_commit = 1;
disable_MAC(apriv);
writeConfigRid(apriv);
enable_MAC(apriv, &rsp);



// rc = writeConfigRid(apriv);

printk ( "New long and short retries: %d,%d\n",
stats_rid.vals[8]-apriv->long_retry_count,
stats_rid.vals[9]-apriv->short_retry_count );
apriv->long_retry_count = stats_rid.vals[8] ;
apriv->short_retry_count = stats_rid.vals[9] ;


/* Schedule next power adjust */
apriv->timer3.expires = RUN_AT(HZ*1);
add_timer(&apriv->timer3);


My Developing History for Aironet Driver:

Airo.c Difference Between 2.4.17 and 2.4.18-14

The driver program of Cisco Aironet Card for Linux 2.4.18.THe only difference of this C program from the one in 2.4.17 is that:
it adds function to handling of an wireless tool ioctl. The funciton is "static int netdev_ethtool_ioctl". Another difference is that in Line 4248-4251
it adds an switch to call this function as below:
case SIOCETHTOOL:
return netdev_ethtool_ioctl(dev, (void *) rq->ifr_data);

Version 1.0.1 ( print RSSI value with "printk")

THis version will include the files to monitor RSSI value by sending N packers per second.
The monitor program will only be called when "xmit" function is called.
airo.c: from original 2.4.18 version, the only change is to add "printk" in "xmit" function.
test_tx programming is keeping sending N packets per second. the packet length is random, and read from the file "rn1.dat".
However, the message contents is filled with 0x7e.
N is taken as an argument from command prompt.
Thus, the test program should be run as:
$test_tx 200
UDP packets are bound to sending to 10.0.0.1:4950. No receiving programming is running at that IP address.
 

 ......
static int airo_start_xmit(struct sk_buff *skb, struct net_device *dev) {
......

/* ZHIBIN WU Mobnets */
StatusRid status_rid;
/* ZHIBIN WU * call ReadStatusrid function */
readStatusRid(priv, &status_rid);
/* ZHIBIN WU *, then printf the RSSI value, which is assume to be the normalizedSignalStrength */
printk ("Current RSSI value is %d \n", status_rid.normalizedSignalStrength);
........
}

Version 1.0.2 ( 100ms delay)

--------------------------------------------
Jan.27  version 1.0.2 w
airo.c: from original 2.4.18 version,
in this version, airo.c includes a short delay 100ms based on RSSI value and packet length. To test this program , test_tx in version 0.0.5 could be used.
The whole new function "airo_delay_xmit" is a clone of the old function "airo_start_xmit", because I have to make some changes in the original old funciton, this new function is only called by the "timer2_function"

Version 1.0.3

This version 1.0.3 differs from the 1.0.2 version only because it's based on 2.4.17 version of Linux driver program .
airo.c: from original 2.4.17 version,
in this version, airo.c includes a short delay 100ms based on RSSI value and packet length.
to test this program , test_tx in version 0.0.5 could be used.


Version 1.0.4

The new version 1.0.4 is buffered when RSSI is <=100,
in 1.0.4 only one packet is buffered and send after 100ms
the only difference form 1.0.3 is that a new approach to access for "skb" is tried and when timer function is called, a message is printed with "printk"!

Version 1.0.5

The new version 1.0.5 differed from 1.0.4 is that it could buffer up to 10 packets in delay. However, those buffers are not circular buffers, which means that all the buffered packets will be send after 100ms no matter what RSSI is.
Until the buffered packets are sent, we cannot buffer new packets.
this version is used for Linux 2.4.17. if some "comment out" are deleted, then it would also apply for 2.4.18.
 

Version 1.1.0

In this version 1.1.0
Airo.c is performing an basic scheduling function as discarding long packets with low RSSI value.
test_ap.c is the Rx program which calculating the channel throughput.
In this version, test_ap program will first send "START" message to slave node to negotiate with test parameters.
test_fn.c is the Tx program which is running on the slave node who is sending unidirectional packets.
The slave node is synced with START message and the 1 sec SIGNAL in this program to ensure the test period is ~60 seconds.
test_rn_gen.c is the C program who is generating random numbers and store them in a file, because the rand() function in C is requiring that each random number is produced every second, it is so slow for real-time purpose, so we'd better have a handy RN table already.

Version 2.0.1

this is an obsolete version
the purpose of this source code "io_test.c" is to see if "ioctl" is good at handling requests from user-space program to get access to data structure within the driver. the result is not optimistic, there are many problems existing.
 

 //------------------ CODE ----------------------//
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#define PORT 3490 /* the port client will be connecting to */
#define MAXDATASIZE 100 /* max number of bytes we can get at once */

/* copied from <wlan/wlan_ioctl.h> */
#define WLAN_TEST (SIOCDEVPRIVATE + 0)

typedef unsigned short UINT16;

typedef struct wlan_req
{
char name[16];
void *data;
UINT16 result;
UINT16 len;
} wlan_req_t;

int main(int argc, char *argv[])
{
wlan_req_t req;
int result;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if ( fd != -1 )
{
/* Test that there is a card */
strcpy( req.name, argv[1]);
req.result = 0;
req.data = NULL;
req.len = 0;
//result = ioctl( fd, WLAN_TEST, &req);
result = ioctl( fd, SIOCGIFFLAGS, &req);

printf("%d \n", result);
printf("%d \n", req.len);
printf ("%d \n", errno);
}
else
{
printf("socket error! /n");

}
return 0;
}

Version 2.0.2

this version 2.0.2 has nothing to do with 2.0.1
this is an temporary trail version fro know how the driver will be called if an application send a packet to his own IP address.
The answer is NO. Which means that if the application wants to talk with the local driver, it must create a packet with destination other than itself, to let the driver intercept this packets. Otherwise, it has to call ioctl() to control the driver.

Version 2.0.3

Version 2.0.3 is obsolete.
this airo.c driver program tries to loop back the packets from Tx to Rx direct by swapping the MAC address of the packet. But it does not work. This change entails disastrous consequence to the kernel and the system must reset.

Version 3.0.1

the file is from airo_18. and the modification is to modify Tx and Rx to adding
RSSI value in the last byte of packets, because both IP and UDP does not contain any Checksum (except header)
it is feasible for a test program running UDP. UDP does has check sum, but we can set it to 0x0000,

so, packets are actually modified , but the IP and UDP does not detect it.
then, our user space program get a chance to see the results. of RSSI
also, Retry_count as Statistics from hardware is also written as the last second byte in the packet, then user program could see it.
test_tx and test_rx is derived from version 0.0.7
the simple change in test_tx.c is that the packet length is at least 50 bytes.
the change in test_rx.c is that we will record RSSI and RETRY_COUNT in two files as" rssi.dat" and "collison.dat" to view how it change during the test.
 

Version 3.0.2
ZHIBIN WU Apr. 3rd
-----------------------
This version 3.0.2 is going to handle Retry-count based packet delay:
A buffers will be used to buffer up to 100 packets.
packet delay will be handle in the "airo_delay_xmit" function and raw data will be copied to the buffer.
the buffer is like a link-list, with a head and tail, new packets are placed in the position tail and when timer is up, packet in the head position will always be transmitted.
The delay is triggered by the condition:
1. retry_count increase at least 5
2. retry_count increase < 100 ( it is impossible without card error, however sometime card register provide such weird values)
The same procedure is something link 1.0.5, especially the part handling the timer2.
When the delay is triggered, there will be some messages appeared in /var/log/messages, becasue I use printk functions.

Version 3.0.3

ZHIBIN WU MAY 10
Airo.c: This is the final version used for IAB demo.
the idea is still keep two separate queues, and make decision
based on the head of the queue address.

In IAB demo, Netperf is used instead of my own socket test program.

Version 3.0.4

ZHiBIN WU May 19th,2003
3.0.4 is an optimal version than 3.0.3
the codes in function "airo_delay_xmit" are optimized.
3- queues are kept, 2 for major receivers and another queue for all other and broadcasting packets.
 

Version 3.0.5 ( Airo.c)

ZHIBIN WU may 28th,
Version 3.0.5 is a final version used for IAB demo.
The bug of abnormal retry_count increase is cleaned in this version

Version 3.0.6 ( Airo.c)

ZHIBIN WU Aug. 2003
Version 3.0.6 uses a generized way to handle multiple queues.

Version 5.0.1

ZHIBIN WU Feb 6, 2004
Version 5.0.1 implements a way to dynamically adjust TX-power of the Cisco card. The power adjust is controlled by a timer. The code is compiled in Linux Kernel 2.4.21

 


New in 2.4.21  provide extra experiment chances

The new driver implement some new functions.
Since March 2003. Cisco is providing is own Linux driver for Aironet card and ACU software in Linux.
For example, 340-350-PCMCIA-LMC-PCI-v53017.exe  is the newest firmware for Nov.17 which is believed to fix the bugs about RSSI report error and MAC error reports.