This patch adds IEEE802.11e/WMM Traffic Stream (TS) Management and Direct Link Setup (DLS) non-AP QSTA mode support for mac80211. Signed-off-by: Zhu Yi <yi.zhu@xxxxxxxxx> --- net/mac80211/ieee80211.c | 18 +- net/mac80211/ieee80211_i.h | 53 +++ net/mac80211/ieee80211_iface.c | 8 + net/mac80211/ieee80211_sta.c | 792 +++++++++++++++++++++++++++++++++++++++- net/mac80211/sta_info.c | 34 ++- net/mac80211/sta_info.h | 10 +- net/mac80211/wme.c | 38 ++- 7 files changed, 931 insertions(+), 22 deletions(-) diff --git a/net/mac80211/ieee80211.c b/net/mac80211/ieee80211.c index 805c49a..e903197 100644 --- a/net/mac80211/ieee80211.c +++ b/net/mac80211/ieee80211.c @@ -1557,11 +1557,18 @@ static int ieee80211_subif_start_xmit(struct sk_buff *skb, memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN); hdrlen = 30; } else if (sdata->type == IEEE80211_IF_TYPE_STA) { - fc |= IEEE80211_FCTL_TODS; - /* BSSID SA DA */ - memcpy(hdr.addr1, sdata->u.sta.bssid, ETH_ALEN); - memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN); - memcpy(hdr.addr3, skb->data, ETH_ALEN); + if (dls_link_status(local, skb->data) == DLS_STATUS_OK){ + /* DA SA BSSID */ + memcpy(hdr.addr1, skb->data, ETH_ALEN); + memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN); + memcpy(hdr.addr3, sdata->u.sta.bssid, ETH_ALEN); + } else { + fc |= IEEE80211_FCTL_TODS; + /* BSSID SA DA */ + memcpy(hdr.addr1, sdata->u.sta.bssid, ETH_ALEN); + memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN); + memcpy(hdr.addr3, skb->data, ETH_ALEN); + } hdrlen = 24; } else if (sdata->type == IEEE80211_IF_TYPE_IBSS) { /* DA SA BSSID */ @@ -2281,6 +2288,7 @@ static void ieee80211_if_shutdown(struct net_device *dev) case IEEE80211_IF_TYPE_IBSS: sdata->u.sta.state = IEEE80211_DISABLED; del_timer_sync(&sdata->u.sta.timer); + del_timer_sync(&sdata->u.sta.admit_timer); skb_queue_purge(&sdata->u.sta.skb_queue); if (!local->ops->hw_scan && local->scan_dev == sdata->dev) { diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 8b939d0..3d40645 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -59,6 +59,10 @@ struct ieee80211_local; * increased memory use (about 2 kB of RAM per entry). */ #define IEEE80211_FRAGMENT_MAX 4 +/* Minimum and Maximum TSID used by EDCA. EDCA uses 0~7; HCCA uses 8~15 */ +#define EDCA_TSID_MIN 0 +#define EDCA_TSID_MAX 7 + struct ieee80211_fragment_entry { unsigned long first_frag_time; unsigned int seq; @@ -178,6 +182,19 @@ struct ieee80211_tx_stored_packet { unsigned int last_frag_rate_ctrl_probe:1; }; +struct sta_ts_data { + enum { + TS_STATUS_UNUSED = 0, + TS_STATUS_ACTIVE = 1, + TS_STATUS_INACTIVE = 2, + TS_STATUS_THROTTLING = 3, + } status; + u8 dialog_token; + u8 up; + u32 admitted_time_usec; + u32 used_time_usec; +}; + typedef ieee80211_txrx_result (*ieee80211_tx_handler) (struct ieee80211_txrx_data *tx); @@ -222,6 +239,7 @@ struct ieee80211_if_sta { } state; struct timer_list timer; struct work_struct work; + struct timer_list admit_timer; /* Recompute EDCA admitted time */ u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN]; u8 ssid[IEEE80211_MAX_SSID_LEN]; size_t ssid_len; @@ -271,6 +289,13 @@ struct ieee80211_if_sta { u32 supp_rates_bits; int wmm_last_param_set; + + u32 dot11EDCAAveragingPeriod; + u32 MPDUExchangeTime; +#define STA_TSID_NUM 16 +#define STA_TSDIR_NUM 2 + /* EDCA: 0~7, HCCA: 8~15 */ + struct sta_ts_data ts_data[STA_TSID_NUM][STA_TSDIR_NUM]; }; @@ -640,6 +665,11 @@ struct ieee80211_local { #endif }; +enum sta_link_direction { + STA_TS_UPLINK = 0, + STA_TS_DOWNLINK = 1, +}; + static inline struct ieee80211_local *hw_to_local( struct ieee80211_hw *hw) { @@ -774,6 +804,7 @@ int ieee80211_set_channel(struct ieee80211_local *local, int channel, int freq); /* ieee80211_sta.c */ void ieee80211_sta_timer(unsigned long data); void ieee80211_sta_work(struct work_struct *work); +void ieee80211_admit_refresh(unsigned long ptr); void ieee80211_sta_scan_work(struct work_struct *work); void ieee80211_sta_rx_mgmt(struct net_device *dev, struct sk_buff *skb, struct ieee80211_rx_status *rx_status); @@ -794,6 +825,28 @@ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev, u8 *addr); int ieee80211_sta_deauthenticate(struct net_device *dev, u16 reason); int ieee80211_sta_disassociate(struct net_device *dev, u16 reason); +void ieee80211_send_addts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tspec); +void wmm_send_addts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tspec); +void ieee80211_send_delts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tp); +void wmm_send_delts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tp); +void ieee80211_send_dls_req(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + u8 *addr, u32 timeout); +void ieee80211_send_dls_teardown(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + u8 *mac, u16 reason); +struct sta_info *dls_info_get(struct ieee80211_local *local, u8 *addr); +void dls_info_add(struct ieee80211_if_sta *ifsta, struct sta_info *dls); +void dls_info_stop(struct ieee80211_if_sta *ifsta); +int dls_link_status(struct ieee80211_local *local, u8 *addr); /* ieee80211_iface.c */ int ieee80211_if_add(struct net_device *dev, const char *name, diff --git a/net/mac80211/ieee80211_iface.c b/net/mac80211/ieee80211_iface.c index a8612a8..c5c8bab 100644 --- a/net/mac80211/ieee80211_iface.c +++ b/net/mac80211/ieee80211_iface.c @@ -182,6 +182,10 @@ void ieee80211_if_set_type(struct net_device *dev, int type) (unsigned long) sdata); skb_queue_head_init(&ifsta->skb_queue); + init_timer(&ifsta->admit_timer); + ifsta->admit_timer.data = (unsigned long) dev; + ifsta->admit_timer.function = ieee80211_admit_refresh; + ifsta->capab = WLAN_CAPABILITY_ESS; ifsta->auth_algs = IEEE80211_AUTH_ALG_OPEN | IEEE80211_AUTH_ALG_SHARED_KEY; @@ -191,6 +195,10 @@ void ieee80211_if_set_type(struct net_device *dev, int type) ifsta->auto_channel_sel = 1; ifsta->auto_bssid_sel = 1; + /* Initialize non-AP QSTA QoS Params */ + ifsta->dot11EDCAAveragingPeriod = 5; + ifsta->MPDUExchangeTime = 0; + msdata = IEEE80211_DEV_TO_SUB_IF(sdata->local->mdev); sdata->bss = &msdata->u.ap; break; diff --git a/net/mac80211/ieee80211_sta.c b/net/mac80211/ieee80211_sta.c index 6036f08..34f50bc 100644 --- a/net/mac80211/ieee80211_sta.c +++ b/net/mac80211/ieee80211_sta.c @@ -111,6 +111,8 @@ struct ieee802_11_elems { u8 wmm_info_len; u8 *wmm_param; u8 wmm_param_len; + u8 *tspec; + u8 tspec_len; }; typedef enum { ParseOK = 0, ParseUnknown = 1, ParseFailed = -1 } ParseRes; @@ -179,17 +181,34 @@ static ParseRes ieee802_11_parse_elems(u8 *start, size_t len, if (elen >= 4 && pos[0] == 0x00 && pos[1] == 0x50 && pos[2] == 0xf2) { /* Microsoft OUI (00:50:F2) */ - if (pos[3] == 1) { + if (pos[3] == WIFI_OUI_TYPE_WPA) { /* OUI Type 1 - WPA IE */ elems->wpa = pos; elems->wpa_len = elen; - } else if (elen >= 5 && pos[3] == 2) { - if (pos[4] == 0) { + } else if (elen >= 5 && + pos[3] == WIFI_OUI_TYPE_WMM) { + switch (pos[4]) { + case WIFI_OUI_STYPE_WMM_INFO: elems->wmm_info = pos; elems->wmm_info_len = elen; - } else if (pos[4] == 1) { + break; + case WIFI_OUI_STYPE_WMM_PARAM: elems->wmm_param = pos; elems->wmm_param_len = elen; + break; + case WIFI_OUI_STYPE_WMM_TSPEC: + if (elen != 61) { + printk(KERN_ERR "Wrong " + "TSPEC size.\n"); + break; + } + elems->tspec = pos + 6; + elems->tspec_len = elen - 6; + break; + default: + //printk(KERN_ERR "Unsupported " + // "WiFi OUI %d\n", pos[4]); + break; } } } @@ -214,6 +233,14 @@ static ParseRes ieee802_11_parse_elems(u8 *start, size_t len, elems->ht_extra_param = pos; elems->ht_extra_param_len = elen; break; + case WLAN_EID_TSPEC: + if (elen != 55) { + printk(KERN_ERR "Wrong TSPEC size.\n"); + break; + } + elems->tspec = pos; + elems->tspec_len = elen; + break; default: #if 0 printk(KERN_DEBUG "IEEE 802.11 element parse ignored " @@ -687,6 +714,402 @@ static void ieee80211_send_disassoc(struct net_device *dev, } +int ieee80211_ts_index(u8 direction) +{ + if (direction == WLAN_TSINFO_DOWNLINK || + direction == WLAN_TSINFO_DIRECTLINK) + return STA_TS_DOWNLINK; + return STA_TS_UPLINK; /* UP and Bidirectional LINK */ +} + + +void ieee80211_send_addts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tspec) +{ + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + static u8 token; + struct ieee80211_elem_tspec *ptspec; + u8 *pos; + + skb = dev_alloc_skb(sizeof(*mgmt) + sizeof(*tspec)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for addts " + "frame\n", dev->name); + return; + } + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + + skb_put(skb, 1 + sizeof(mgmt->u.action.u.addts_req)); + mgmt->u.action.category = WLAN_CATEGORY_QOS; + mgmt->u.action.u.addts_req.action_code = WLAN_ACTION_QOS_ADDTS_REQ; + mgmt->u.action.u.addts_req.dialog_token = ++token % 127; + + skb_put(skb, 2 + sizeof(*tspec)); + pos = mgmt->u.action.u.addts_req.variable; + pos[0] = WLAN_EID_TSPEC; + pos[1] = sizeof(*tspec); + pos += 2; + ptspec = (struct ieee80211_elem_tspec *)pos; + memcpy(ptspec, tspec, sizeof(*tspec)); + + ieee80211_sta_tx(dev, skb, 0); +} + + +void wmm_send_addts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tspec) +{ + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + static u8 token; + struct ieee80211_elem_tspec *ptspec; + u8 *pos; + + skb = dev_alloc_skb(sizeof(*mgmt) + 2 + 6 + sizeof(*tspec)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for addts " + "frame\n", dev->name); + return; + } + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + + skb_put(skb, 1 + sizeof(mgmt->u.action.u.wme_action)); + mgmt->u.action.category = WLAN_CATEGORY_WMM; + mgmt->u.action.u.wme_action.action_code = WLAN_ACTION_QOS_ADDTS_REQ; + mgmt->u.action.u.wme_action.dialog_token = ++token % 127; + mgmt->u.action.u.wme_action.status_code = 0; + + skb_put(skb, 2 + 6 + sizeof(*tspec)); + pos = mgmt->u.action.u.wme_action.variable; + pos[0] = WLAN_EID_GENERIC; + pos[1] = 61; + pos += 2; + pos[0] = 0x00; pos[1] = 0x50; pos[2] = 0xf2; /* Wi-Fi OUI (00:50:F2)*/ + pos += 3; + pos[0] = WIFI_OUI_TYPE_WMM; + pos[1] = WIFI_OUI_STYPE_WMM_TSPEC; + pos[2] = 1; /* Version */ + pos += 3; + ptspec = (struct ieee80211_elem_tspec *)pos; + memcpy(ptspec, tspec, sizeof(*tspec)); + + ieee80211_sta_tx(dev, skb, 0); +} + + +void ieee80211_send_delts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tp) +{ + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + u8 tsid = IEEE80211_TSINFO_TSID(tp->ts_info); + u8 direction = IEEE80211_TSINFO_DIR(tp->ts_info); + u32 medium_time = tp->medium_time; + u8 index = ieee80211_ts_index(direction); + + if (ifsta->ts_data[tsid][index].status == TS_STATUS_UNUSED) { + printk(KERN_DEBUG "%s: Tring to delete an ACM disabled TS " + "(%u:%u)\n", dev->name, tsid, direction); + return; + } + skb = dev_alloc_skb(sizeof(*mgmt)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for delts " + "frame\n", dev->name); + return; + } + + /* recompute admitted time */ + ifsta->ts_data[tsid][index].admitted_time_usec -= + ifsta->dot11EDCAAveragingPeriod * medium_time * 32; + if ((s32)(ifsta->ts_data[tsid][index].admitted_time_usec) < 0) + ifsta->ts_data[tsid][index].admitted_time_usec = 0; + + ifsta->ts_data[tsid][index].status = TS_STATUS_INACTIVE; + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + skb_put(skb, 1 + sizeof(mgmt->u.action.u.delts)); + mgmt->u.action.category = WLAN_CATEGORY_QOS; + mgmt->u.action.u.delts.action_code = WLAN_ACTION_QOS_DELTS; + mgmt->u.action.u.delts.reason_code = 0; + memset(&mgmt->u.action.u.delts.ts_info, 0, + sizeof(struct ieee80211_ts_info)); + + SET_TSINFO_TSID(tp->ts_info, tsid); + SET_TSINFO_DIR(tp->ts_info, direction); + SET_TSINFO_POLICY(tp->ts_info, WLAN_TSINFO_EDCA); + SET_TSINFO_APSD(tp->ts_info, WLAN_TSINFO_PSB_LEGACY); + SET_TSINFO_UP(tp->ts_info, ifsta->ts_data[tsid][index].up); + + ieee80211_sta_tx(dev, skb, 0); +} + + +void wmm_send_delts(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_elem_tspec *tp) +{ + struct ieee80211_mgmt *mgmt; + struct ieee80211_elem_tspec *tspec; + struct sk_buff *skb; + u8 tsid = IEEE80211_TSINFO_TSID(tp->ts_info); + u8 direction = IEEE80211_TSINFO_DIR(tp->ts_info); + u32 medium_time = tp->medium_time; + u8 index = ieee80211_ts_index(direction); + u8 *pos; + + if (ifsta->ts_data[tsid][index].status == TS_STATUS_UNUSED) { + printk(KERN_DEBUG "%s: Tring to delete a non-Actived TS " + "(%u %u)\n", dev->name, tsid, direction); + return; + } + skb = dev_alloc_skb(sizeof(*mgmt) + 2 + 6 + sizeof(*tspec)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for delts " + "frame\n", dev->name); + return; + } + + /* recompute admitted time */ + ifsta->ts_data[tsid][index].admitted_time_usec -= + ifsta->dot11EDCAAveragingPeriod * medium_time * 32; + if ((s32)(ifsta->ts_data[tsid][index].admitted_time_usec < 0)) + ifsta->ts_data[tsid][index].admitted_time_usec = 0; + + ifsta->ts_data[tsid][index].status = TS_STATUS_INACTIVE; + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + + skb_put(skb, 1 + sizeof(mgmt->u.action.u.wme_action)); + mgmt->u.action.category = WLAN_CATEGORY_WMM; + mgmt->u.action.u.wme_action.action_code = WLAN_ACTION_QOS_DELTS; + mgmt->u.action.u.wme_action.dialog_token = 0; + mgmt->u.action.u.wme_action.status_code = 0; + + skb_put(skb, 2 + 6 + sizeof(*tspec)); + pos = mgmt->u.action.u.wme_action.variable; + pos[0] = WLAN_EID_GENERIC; + pos[1] = 61; + pos += 2; + pos[0] = 0x00; pos[1] = 0x50; pos[2] = 0xf2; /* Wi-Fi OUI (00:50:F2)*/ + pos += 3; + pos[0] = WIFI_OUI_TYPE_WMM; + pos[1] = WIFI_OUI_STYPE_WMM_TSPEC; + pos[2] = 1; /* Version */ + pos += 3; + tspec = (struct ieee80211_elem_tspec *)pos; + memset(tspec, 0, sizeof(*tspec)); + + SET_TSINFO_TSID(tspec->ts_info, tsid); + SET_TSINFO_DIR(tspec->ts_info, direction); + SET_TSINFO_POLICY(tspec->ts_info, WLAN_TSINFO_EDCA); + SET_TSINFO_APSD(tspec->ts_info, WLAN_TSINFO_PSB_LEGACY); + SET_TSINFO_UP(tspec->ts_info, ifsta->ts_data[tsid][index].up); + + ieee80211_sta_tx(dev, skb, 0); +} + + +void ieee80211_send_dls_req(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + u8 *addr, u32 timeout) +{ + struct ieee80211_hw_mode *mode; + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + u8 *pos, *supp_rates, *esupp_rates = NULL; + int i; + + skb = dev_alloc_skb(sizeof(*mgmt) + 200 /* rates + ext_rates Size */); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for DLS REQ " + "frame\n", dev->name); + return; + } + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + + skb_put(skb, 1 + sizeof(mgmt->u.action.u.dls_req)); + mgmt->u.action.category = WLAN_CATEGORY_DLS; + mgmt->u.action.u.dls_req.action_code = WLAN_ACTION_DLS_REQ; + memcpy(mgmt->u.action.u.dls_req.dest, addr, ETH_ALEN); + memcpy(mgmt->u.action.u.dls_req.src, dev->dev_addr, ETH_ALEN); + mgmt->u.action.u.dls_req.capab_info = cpu_to_le16(ifsta->ap_capab); + mgmt->u.action.u.dls_req.timeout = timeout; + + /* Add supported rates and extended supported rates */ + supp_rates = skb_put(skb, 2); + supp_rates[0] = WLAN_EID_SUPP_RATES; + supp_rates[1] = 0; + mode = local->oper_hw_mode; + for (i = 0; i < mode->num_rates; i++) { + struct ieee80211_rate *rate = &mode->rates[i]; + if (!(rate->flags & IEEE80211_RATE_SUPPORTED)) + continue; + if (esupp_rates) { + pos = skb_put(skb, 1); + esupp_rates[1]++; + } else if (supp_rates[1] == 8) { + esupp_rates = skb_put(skb, 3); + esupp_rates[0] = WLAN_EID_EXT_SUPP_RATES; + esupp_rates[1] = 1; + pos = &esupp_rates[2]; + } else { + pos = skb_put(skb, 1); + supp_rates[1]++; + } + if (local->hw.conf.phymode == MODE_ATHEROS_TURBO) + *pos = rate->rate / 10; + else + *pos = rate->rate / 5; + } + + ieee80211_sta_tx(dev, skb, 0); +} + + +static void ieee80211_send_dls_resp(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + u8 *mac_addr, u16 status) +{ + struct ieee80211_hw_mode *mode; + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + u8 *pos, *supp_rates, *esupp_rates = NULL; + int i; + + skb = dev_alloc_skb(sizeof(*mgmt) + 200 /* rates + ext_rates Size */); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for dls resp " + "frame\n", dev->name); + return; + } + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + + skb_put(skb, 1 + sizeof(mgmt->u.action.u.dls_resp)); + mgmt->u.action.category = WLAN_CATEGORY_DLS; + mgmt->u.action.u.dls_resp.action_code = WLAN_ACTION_DLS_RESP; + memcpy(mgmt->u.action.u.dls_resp.dest, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->u.action.u.dls_resp.src, mac_addr, ETH_ALEN); + mgmt->u.action.u.dls_resp.status_code = cpu_to_le16(status); + + if (!mgmt->u.action.u.dls_resp.status_code) { + ieee80211_sta_tx(dev, skb, 0); + return; + } + + /* Add capability information */ + pos = skb_put(skb, 2); + *(__le16 *)pos = cpu_to_le16(ifsta->ap_capab); + + /* Add supported rates and extended supported rates */ + supp_rates = skb_put(skb, 2); + supp_rates[0] = WLAN_EID_SUPP_RATES; + supp_rates[1] = 0; + mode = local->oper_hw_mode; + for (i = 0; i < mode->num_rates; i++) { + struct ieee80211_rate *rate = &mode->rates[i]; + if (!(rate->flags & IEEE80211_RATE_SUPPORTED)) + continue; + if (esupp_rates) { + pos = skb_put(skb, 1); + esupp_rates[1]++; + } else if (supp_rates[1] == 8) { + esupp_rates = skb_put(skb, 3); + esupp_rates[0] = WLAN_EID_EXT_SUPP_RATES; + esupp_rates[1] = 1; + pos = &esupp_rates[2]; + } else { + pos = skb_put(skb, 1); + supp_rates[1]++; + } + if (local->hw.conf.phymode == MODE_ATHEROS_TURBO) + *pos = rate->rate / 10; + else + *pos = rate->rate / 5; + } + + ieee80211_sta_tx(dev, skb, 0); +} + + +void ieee80211_send_dls_teardown(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + u8 *mac_addr, u16 reason) +{ + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + + skb = dev_alloc_skb(sizeof(*mgmt)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for DLS " + "Teardown frame\n", dev->name); + return; + } + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ACTION); + skb_put(skb, 1 + sizeof(mgmt->u.action.u.dls_teardown)); + mgmt->u.action.category = WLAN_CATEGORY_DLS; + mgmt->u.action.u.dls_teardown.action_code = WLAN_ACTION_DLS_TEARDOWN; + memcpy(mgmt->u.action.u.dls_teardown.dest, mac_addr, ETH_ALEN); + memcpy(mgmt->u.action.u.dls_teardown.src, dev->dev_addr, ETH_ALEN); + mgmt->u.action.u.dls_teardown.reason_code = cpu_to_le16(reason); + + ieee80211_sta_tx(dev, skb, 0); +} + + static int ieee80211_privacy_mismatch(struct net_device *dev, struct ieee80211_if_sta *ifsta) { @@ -1284,6 +1707,258 @@ static void ieee80211_rx_mgmt_assoc_resp(struct net_device *dev, ieee80211_associated(dev, ifsta); } +static u32 calculate_mpdu_exchange_time(struct ieee80211_local *local, + struct ieee80211_elem_tspec *tspec) +{ + /* + * FIXME: MPDUExchangeTime = duration(Nominal MSDU Size, Min PHY Rate) + + * SIFS + ACK duration + */ + int extra = 0; /* SIFS + ACK */ + + switch (local->hw.conf.phymode) { + case MODE_IEEE80211A: + extra = 16 + 24; + break; + case MODE_IEEE80211B: + extra = 10 + 203; + break; + case MODE_IEEE80211G: + default: + extra = 10 + 30; + break; + } + return (tspec->nominal_msdu_size * 8) / + (tspec->min_phy_rate / 1000000) + extra; +} + +static void sta_update_tspec(struct ieee80211_local *local, + struct ieee80211_if_sta *ifsta, + int action, struct ieee80211_elem_tspec *tspec) +{ + u8 tsid = IEEE80211_TSINFO_TSID(tspec->ts_info); + u8 index = ieee80211_ts_index(IEEE80211_TSINFO_DIR(tspec->ts_info)); + + switch (action) { + case WLAN_ACTION_QOS_ADDTS_RESP: + ifsta->ts_data[tsid][index].status = TS_STATUS_ACTIVE; + ifsta->ts_data[tsid][index].up = + IEEE80211_TSINFO_UP(tspec->ts_info); + ifsta->ts_data[tsid][index].used_time_usec = 0; + ifsta->ts_data[tsid][index].admitted_time_usec += + ifsta->dot11EDCAAveragingPeriod * tspec->medium_time * 32; + ifsta->MPDUExchangeTime = + calculate_mpdu_exchange_time(local, tspec); + break; + case WLAN_ACTION_QOS_DELTS: + ifsta->ts_data[tsid][index].status = TS_STATUS_INACTIVE; + ifsta->ts_data[tsid][index].used_time_usec = 0; + ifsta->ts_data[tsid][index].admitted_time_usec -= + ifsta->dot11EDCAAveragingPeriod * tspec->medium_time * 32; + if (ifsta->ts_data[tsid][index].admitted_time_usec < 0) + ifsta->ts_data[tsid][index].admitted_time_usec = 0; + ifsta->MPDUExchangeTime = 0; + break; + default: + printk(KERN_ERR "%s: invalid action type %d\n", __FUNCTION__, + action); + break; + } +} + +static void sta_parse_tspec(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, size_t len, u8 prefix, + struct ieee80211_elem_tspec *tspec) +{ + struct ieee802_11_elems elems; + u8 *pos; + + /* + printk(KERN_DEBUG "Dialog_token: %d, TID: %u, Direction: %u, PSB: %d, " + "UP: %d\n", mgmt->u.action.u.wme_action.dialog_token, + IEEE80211_TSINFO_TSID(tspec->ts_info), + IEEE80211_TSINFO_DIR(tspec->ts_info), + IEEE80211_TSINFO_APSD(tspec->ts_info), + IEEE80211_TSINFO_UP(tspec->ts_info)); + */ + + if (mgmt->u.action.category == WLAN_CATEGORY_QOS) + pos = mgmt->u.action.u.addts_resp.variable + prefix; + else + pos = mgmt->u.action.u.wme_action.variable + prefix; + + if (ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), &elems) + == ParseFailed) { + printk(KERN_DEBUG "%s: failed to parse TSPEC\n", dev->name); + return; + } + memcpy(tspec, elems.tspec, sizeof(*tspec)); +} + +int dls_link_status(struct ieee80211_local *local, u8 *addr) +{ + struct sta_info *dls; + int ret = DLS_STATUS_NOLINK; + + if ((dls = dls_info_get(local, addr)) != NULL) { + ret = dls->dls_status; + sta_info_put(dls); + } + return ret; +} + +static void sta_process_dls_req(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, size_t len) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct sta_info *dls; + u8 *src = mgmt->u.action.u.dls_req.src; + struct ieee802_11_elems elems; + struct ieee80211_rate *rates; + size_t baselen, num_rates; + int i, j; + struct ieee80211_hw_mode *mode; + u32 supp_rates = 0; + + printk(KERN_DEBUG "Receive DLS request from " + "%02X:%02X:%02X:%02X:%02X:%02X\n", + src[0], src[1], src[2], src[3], src[4], src[5]); + + baselen = (u8 *)mgmt->u.action.u.dls_req.variable - (u8 *)mgmt; + if (baselen > len) + return; + + if (ieee802_11_parse_elems(mgmt->u.action.u.dls_req.variable, + len - baselen, &elems) == ParseFailed) { + printk(KERN_ERR "DLS Parse support rates failed.\n"); + return; + } + mode = local->sta_scanning ? + local->scan_hw_mode : local->oper_hw_mode; + rates = mode->rates; + num_rates = mode->num_rates; + + for (i = 0; i < elems.supp_rates_len + elems.ext_supp_rates_len; i++) { + u8 rate = 0; + if (i < elems.supp_rates_len) + rate = elems.supp_rates[i]; + else if (elems.ext_supp_rates) + rate = elems.ext_supp_rates[i - elems.supp_rates_len]; + rate = 5 * (rate & 0x7f); + if (mode->mode == MODE_ATHEROS_TURBO) + rate *= 2; + for (j = 0; j < num_rates; j++) + if (rates[j].rate == rate) + supp_rates |= BIT(j); + } + if (supp_rates == 0) { + /* Send DLS failed Response to the peer because + * the supported rates are mismatch */ + ieee80211_send_dls_resp(dev, ifsta, src, + WLAN_REASON_QSTA_NOT_USE); + return; + } + + dls = dls_info_get(local, src); + if (!dls) + dls = sta_info_add(local, dev, src, GFP_ATOMIC); + if (!dls) + return; + + dls->dls_status = DLS_STATUS_OK; + dls->dls_timeout = le16_to_cpu(mgmt->u.action.u.dls_req.timeout); + dls->supp_rates = supp_rates; + + /* Send DLS successful Response to the peer */ + ieee80211_send_dls_resp(dev, ifsta, src, 0); +} + + +static void sta_process_dls_resp(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, size_t len) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct sta_info *dls; + u8 *src = mgmt->u.action.u.dls_resp.src; + struct ieee802_11_elems elems; + struct ieee80211_rate *rates; + size_t baselen, num_rates; + int i, j; + struct ieee80211_hw_mode *mode; + u32 supp_rates = 0; + + printk(KERN_DEBUG "Receive DLS response from " + "%02X:%02X:%02X:%02X:%02X:%02X\n", + src[0], src[1], src[2], src[3], src[4], src[5]); + + if (mgmt->u.action.u.dls_resp.status_code) { + printk(KERN_ERR "DLS setup refused by peer. Reason %d\n", + mgmt->u.action.u.dls_resp.status_code); + return; + } + + baselen = (u8 *)mgmt->u.action.u.dls_resp.variable - (u8 *)mgmt; + if (baselen > len) + return; + + if (ieee802_11_parse_elems(mgmt->u.action.u.dls_resp.variable, + len - baselen, &elems) == ParseFailed) { + printk(KERN_ERR "DLS Parse support rates failed.\n"); + return; + } + mode = local->sta_scanning ? + local->scan_hw_mode : local->oper_hw_mode; + rates = mode->rates; + num_rates = mode->num_rates; + + for (i = 0; i < elems.supp_rates_len + elems.ext_supp_rates_len; i++) { + u8 rate = 0; + if (i < elems.supp_rates_len) + rate = elems.supp_rates[i]; + else if (elems.ext_supp_rates) + rate = elems.ext_supp_rates[i - elems.supp_rates_len]; + rate = 5 * (rate & 0x7f); + if (mode->mode == MODE_ATHEROS_TURBO) + rate *= 2; + for (j = 0; j < num_rates; j++) + if (rates[j].rate == rate) + supp_rates |= BIT(j); + } + + dls = dls_info_get(local, src); + if (!dls) + dls = sta_info_add(local, dev, src, GFP_ATOMIC); + if (!dls) + return; + + dls->supp_rates = supp_rates; + dls->dls_status = DLS_STATUS_OK; + sta_info_put(dls); +} + + +static void sta_process_dls_teardown(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, size_t len) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + u8 *src = mgmt->u.action.u.dls_teardown.src; + struct sta_info *dls; + + printk(KERN_DEBUG "DLS Teardown received from " + "%02X:%02X:%02X:%02X:%02X:%02X. Reason %d\n", + src[0], src[1], src[2], src[3], src[4], src[5], + mgmt->u.action.u.dls_teardown.reason_code); + + dls = dls_info_get(local, src); + if (dls) + sta_info_free(dls, 0); + return; +} + /* Caller must hold local->sta_bss_lock */ static void __ieee80211_rx_bss_hash_add(struct net_device *dev, @@ -1805,7 +2480,7 @@ static void ieee80211_send_addba_resp(struct net_device *dev, skb = dev_alloc_skb(sizeof(*mgmt) + local->hw.extra_tx_headroom); if (!skb) { printk(KERN_DEBUG "%s: failed to allocate buffer " - "for addts frame\n", dev->name); + "for addba resp frame\n", dev->name); return; } @@ -1839,12 +2514,82 @@ static void ieee80211_rx_mgmt_action(struct net_device *dev, struct ieee80211_mgmt *mgmt, size_t len) { + u8 prefix = 0; struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_elem_tspec tspec; if (len < IEEE80211_MIN_ACTION_SIZE) return; switch (mgmt->u.action.category) { + case WLAN_CATEGORY_QOS: + case WLAN_CATEGORY_WMM: + if (len < 24 + 4) { + printk(KERN_DEBUG "%s: too short (%zd) QoS category " + "frame received from " MAC_FMT " - ignored\n", + dev->name, len, MAC_ARG(mgmt->sa)); + return; + } + switch (mgmt->u.action.u.wme_action.action_code) { + case WLAN_ACTION_QOS_ADDTS_REQ: + printk(KERN_DEBUG "%s: WLAN_ACTION_QOS_ADDTS_REQ " + "received in Non-AP STA mode!\n", dev->name); + return; + case WLAN_ACTION_QOS_ADDTS_RESP: + if (mgmt->u.action.u.wme_action.status_code == 47) { + /* TODO: handle TS Delay */ + prefix = 6; + } + /* TODO: handle TCLAS, TCLAS Porcessing here */ + + if (mgmt->u.action.u.wme_action.status_code == 0) { + /* TODO: handle Schedule */ + sta_parse_tspec(dev, ifsta, mgmt, len, + prefix, &tspec); + sta_update_tspec(local, ifsta, + WLAN_ACTION_QOS_ADDTS_RESP, + &tspec); + mod_timer(&ifsta->admit_timer, jiffies + + ifsta->dot11EDCAAveragingPeriod * HZ); + } + break; + case WLAN_ACTION_QOS_DELTS: + sta_parse_tspec(dev, ifsta, mgmt, len, prefix, &tspec); + sta_update_tspec(local, ifsta, + WLAN_ACTION_QOS_DELTS, &tspec); + break; + default: + printk(KERN_ERR "%s: unsupported QoS action code %d\n", + dev->name, + mgmt->u.action.u.wme_action.action_code); + break; + } + break; + + case WLAN_CATEGORY_DLS: + if (len < 24 + 16) { + printk(KERN_DEBUG "%s: too short (%zd) DLS category " + "frame received from " MAC_FMT " - ignored\n", + dev->name, len, MAC_ARG(mgmt->sa)); + return; + } + switch (mgmt->u.action.u.dls_req.action_code) { + case WLAN_ACTION_DLS_REQ: + sta_process_dls_req(dev, ifsta, mgmt, len); + break; + case WLAN_ACTION_DLS_RESP: + sta_process_dls_resp(dev, ifsta, mgmt, len); + break; + case WLAN_ACTION_DLS_TEARDOWN: + sta_process_dls_teardown(dev, ifsta, mgmt, len); + break; + default: + printk(KERN_ERR "%s: unsupported DLS action code %d\n", + dev->name, mgmt->u.action.u.dls_req.action_code); + break; + } + break; + case WLAN_CATEGORY_BACK: switch (mgmt->u.action.u.addba_req.action_code) { case WLAN_ACTION_ADDBA_REQ: @@ -2150,6 +2895,43 @@ void ieee80211_sta_work(struct work_struct *work) } +void ieee80211_admit_refresh(unsigned long ptr) +{ + struct net_device *dev; + struct ieee80211_sub_if_data *sdata; + struct ieee80211_if_sta *ifsta; + int i, j, find = 0; + + dev = (struct net_device *) ptr; + sdata = IEEE80211_DEV_TO_SUB_IF(dev); + ifsta = &sdata->u.sta; + + for (i = 0; i < STA_TSID_NUM; i++) { + for (j = 0; j < STA_TSDIR_NUM; j++) { + if ((ifsta->ts_data[i][j].status != TS_STATUS_ACTIVE) && + (ifsta->ts_data[i][j].status != TS_STATUS_THROTTLING)) + continue; + find = 1; + + ifsta->ts_data[i][j].used_time_usec -= + ifsta->ts_data[i][j].admitted_time_usec; + if ((s32)(ifsta->ts_data[i][j].used_time_usec) < 0) + ifsta->ts_data[i][j].used_time_usec = 0; + + ifsta->ts_data[i][j].status = + (ifsta->ts_data[i][j].used_time_usec >= + ifsta->ts_data[i][j].admitted_time_usec) ? + TS_STATUS_THROTTLING : + TS_STATUS_ACTIVE; + } + } + + if (find) + mod_timer(&ifsta->admit_timer, jiffies + + ifsta->dot11EDCAAveragingPeriod * HZ); +} + + static void ieee80211_sta_reset_auth(struct net_device *dev, struct ieee80211_if_sta *ifsta) { diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index ab7b1f0..3d21ea0 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -33,7 +33,7 @@ static void sta_info_hash_add(struct ieee80211_local *local, /* Caller must hold local->sta_lock */ static void sta_info_hash_del(struct ieee80211_local *local, - struct sta_info *sta) + struct sta_info *sta, int dls) { struct sta_info *s; @@ -41,15 +41,19 @@ static void sta_info_hash_del(struct ieee80211_local *local, if (!s) return; if (memcmp(s->addr, sta->addr, ETH_ALEN) == 0) { + if (dls && !s->dls_sta) + return; local->sta_hash[STA_HASH(sta->addr)] = s->hnext; return; } while (s->hnext && memcmp(s->hnext->addr, sta->addr, ETH_ALEN) != 0) s = s->hnext; - if (s->hnext) + if (s->hnext) { + if (dls && !s->hnext->dls_sta) + return; s->hnext = s->hnext->hnext; - else + } else printk(KERN_ERR "%s: could not remove STA " MAC_FMT " from " "hash table\n", local->mdev->name, MAC_ARG(sta->addr)); } @@ -78,6 +82,28 @@ struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr) } EXPORT_SYMBOL(sta_info_get); +struct sta_info *dls_info_get(struct ieee80211_local *local, u8 *addr) +{ + struct sta_info *sta; + + spin_lock_bh(&local->sta_lock); + sta = local->sta_hash[STA_HASH(addr)]; + while (sta) { + if (memcmp(sta->addr, addr, ETH_ALEN) == 0) { + if (!sta->dls_sta) { + sta = NULL; + break; + } + __sta_info_get(sta); + break; + } + sta = sta->hnext; + } + spin_unlock_bh(&local->sta_lock); + + return sta; +} + int sta_info_min_txrate_get(struct ieee80211_local *local) { struct sta_info *sta; @@ -218,7 +244,7 @@ static void sta_info_remove(struct sta_info *sta) struct ieee80211_local *local = sta->local; struct ieee80211_sub_if_data *sdata; - sta_info_hash_del(local, sta); + sta_info_hash_del(local, sta, 0); list_del(&sta->list); sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); if (sta->flags & WLAN_STA_PS) { diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 6a067a0..3eb3649 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -102,8 +102,14 @@ struct sta_info { #ifdef CONFIG_MAC80211_DEBUGFS int debugfs_registered; #endif - int assoc_ap; /* whether this is an AP that we are - * associated with as a client */ + int assoc_ap:1; /* whether this is an AP that we are + * associated with as a client */ + int dls_sta:1; /* whether this stations is a DLS peer of us */ + +#define DLS_STATUS_OK 0 +#define DLS_STATUS_NOLINK 1 + int dls_status; + u32 dls_timeout; #ifdef CONFIG_HOSTAPD_WPA_TESTING u32 wpa_trigger; diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c index ef3b3b0..9ff35e8 100644 --- a/net/mac80211/wme.c +++ b/net/mac80211/wme.c @@ -166,11 +166,13 @@ static inline int wme_downgrade_ac(struct sk_buff *skb) static inline int classify80211(struct sk_buff *skb, struct Qdisc *qd) { struct ieee80211_local *local = wdev_priv(qd->dev->ieee80211_ptr); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(qd->dev); + struct ieee80211_if_sta *ifsta = &sdata->u.sta; struct ieee80211_tx_packet_data *pkt_data = (struct ieee80211_tx_packet_data *) skb->cb; struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; unsigned short fc = le16_to_cpu(hdr->frame_control); - int qos; + int qos, tsid, dir; const int ieee802_1d_to_ac[8] = { 2, 3, 3, 2, 1, 1, 0, 0 }; /* see if frame is data or non data frame */ @@ -197,14 +199,38 @@ static inline int classify80211(struct sk_buff *skb, struct Qdisc *qd) } /* use the data classifier to determine what 802.1d tag the - * data frame has */ + * data frame has */ skb->priority = classify_1d(skb, qd); + tsid = 8 + skb->priority; + + /* FIXME: only uplink needs to be checked for Tx */ + dir = STA_TS_UPLINK; + + if ((sdata->type == IEEE80211_IF_TYPE_STA) && + (local->wmm_acm & BIT(skb->priority))) { + switch (ifsta->ts_data[tsid][dir].status) { + case TS_STATUS_ACTIVE: + /* if TS Management is enabled, update used_time */ + ifsta->ts_data[tsid][dir].used_time_usec += + ifsta->MPDUExchangeTime; + break; + case TS_STATUS_THROTTLING: + /* if admitted time is used up, refuse to send more */ + if (net_ratelimit()) + printk(KERN_DEBUG "QoS packet throttling\n"); + break; + default: + break; + } + } - /* incase we are a client verify acm is not set for this ac */ - while (unlikely(local->wmm_acm & BIT(skb->priority))) { + /* in case we are a client verify acm is not set for this ac */ + while ((local->wmm_acm & BIT(skb->priority)) && + !((sdata->type == IEEE80211_IF_TYPE_STA) && + (ifsta->ts_data[skb->priority + EDCA_TSID_MIN][dir].status + == TS_STATUS_ACTIVE))) { if (wme_downgrade_ac(skb)) { - /* No AC with lower priority has acm=0, - * drop packet. */ + /* No AC with lower priority has acm=0, drop packet. */ return -1; } } -- 1.5.0.rc2.g73a2 - To unsubscribe from this list: send the line "unsubscribe linux-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html