/* * Intel Wireless Multicomm 3200 WiFi driver * * Copyright (C) 2009 Intel Corporation * Samuel Ortiz * Zhu Yi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #include #include #include #include #include #include #include #include "iwm.h" #include "umac.h" #include "commands.h" #include "debug.h" static struct iw_statistics *iwm_get_wireless_stats(struct net_device *dev) { struct iwm_priv *iwm = ndev_to_iwm(dev); struct iw_statistics *wstats = &iwm->wstats; if (!test_bit(IWM_STATUS_ASSOCIATED, &iwm->status)) { memset(wstats, 0, sizeof(struct iw_statistics)); wstats->qual.updated = IW_QUAL_ALL_INVALID; } return wstats; } static int iwm_wext_siwfreq(struct net_device *dev, struct iw_request_info *info, struct iw_freq *freq, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); if (freq->flags == IW_FREQ_AUTO) return 0; /* frequency/channel can only be set in IBSS mode */ if (iwm->conf.mode != UMAC_MODE_IBSS) return -EOPNOTSUPP; return cfg80211_ibss_wext_siwfreq(dev, info, freq, extra); } static int iwm_wext_giwfreq(struct net_device *dev, struct iw_request_info *info, struct iw_freq *freq, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); if (iwm->conf.mode == UMAC_MODE_IBSS) return cfg80211_ibss_wext_giwfreq(dev, info, freq, extra); freq->e = 0; freq->m = iwm->channel; return 0; } static int iwm_wext_siwap(struct net_device *dev, struct iw_request_info *info, struct sockaddr *ap_addr, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); int ret; if (iwm->conf.mode == UMAC_MODE_IBSS) return cfg80211_ibss_wext_siwap(dev, info, ap_addr, extra); if (!test_bit(IWM_STATUS_READY, &iwm->status)) return -EIO; if (is_zero_ether_addr(ap_addr->sa_data) || is_broadcast_ether_addr(ap_addr->sa_data)) { IWM_DBG_WEXT(iwm, DBG, "clear mandatory bssid %pM\n", iwm->umac_profile->bssid[0]); memset(&iwm->umac_profile->bssid[0], 0, ETH_ALEN); iwm->umac_profile->bss_num = 0; } else { IWM_DBG_WEXT(iwm, DBG, "add mandatory bssid %pM\n", ap_addr->sa_data); memcpy(&iwm->umac_profile->bssid[0], ap_addr->sa_data, ETH_ALEN); iwm->umac_profile->bss_num = 1; } if (iwm->umac_profile_active) { int i; if (!memcmp(&iwm->umac_profile->bssid[0], iwm->bssid, ETH_ALEN)) return 0; /* * If we're clearing the BSSID, and we're associated, * we have to clear the keys as they're no longer valid. */ if (is_zero_ether_addr(ap_addr->sa_data)) { for (i = 0; i < IWM_NUM_KEYS; i++) iwm->keys[i].in_use = 0; } ret = iwm_invalidate_mlme_profile(iwm); if (ret < 0) { IWM_ERR(iwm, "Couldn't invalidate profile\n"); return ret; } } if (iwm->umac_profile->ssid.ssid_len) return iwm_send_mlme_profile(iwm); return 0; } static int iwm_wext_giwap(struct net_device *dev, struct iw_request_info *info, struct sockaddr *ap_addr, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); switch (iwm->conf.mode) { case UMAC_MODE_IBSS: return cfg80211_ibss_wext_giwap(dev, info, ap_addr, extra); case UMAC_MODE_BSS: if (test_bit(IWM_STATUS_ASSOCIATED, &iwm->status)) { ap_addr->sa_family = ARPHRD_ETHER; memcpy(&ap_addr->sa_data, iwm->bssid, ETH_ALEN); } else memset(&ap_addr->sa_data, 0, ETH_ALEN); break; default: return -EOPNOTSUPP; } return 0; } static int iwm_wext_siwessid(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *ssid) { struct iwm_priv *iwm = ndev_to_iwm(dev); size_t len = data->length; int ret; if (iwm->conf.mode == UMAC_MODE_IBSS) return cfg80211_ibss_wext_siwessid(dev, info, data, ssid); if (!test_bit(IWM_STATUS_READY, &iwm->status)) return -EIO; if (len > 0 && ssid[len - 1] == '\0') len--; if (iwm->umac_profile_active) { if (iwm->umac_profile->ssid.ssid_len == len && !memcmp(iwm->umac_profile->ssid.ssid, ssid, len)) return 0; ret = iwm_invalidate_mlme_profile(iwm); if (ret < 0) { IWM_ERR(iwm, "Couldn't invalidate profile\n"); return ret; } } iwm->umac_profile->ssid.ssid_len = len; memcpy(iwm->umac_profile->ssid.ssid, ssid, len); return iwm_send_mlme_profile(iwm); } static int iwm_wext_giwessid(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *ssid) { struct iwm_priv *iwm = ndev_to_iwm(dev); if (iwm->conf.mode == UMAC_MODE_IBSS) return cfg80211_ibss_wext_giwessid(dev, info, data, ssid); if (!test_bit(IWM_STATUS_READY, &iwm->status)) return -EIO; data->length = iwm->umac_profile->ssid.ssid_len; if (data->length) { memcpy(ssid, iwm->umac_profile->ssid.ssid, data->length); data->flags = 1; } else data->flags = 0; return 0; } static struct iwm_key * iwm_key_init(struct iwm_priv *iwm, u8 key_idx, bool in_use, struct iw_encode_ext *ext, u8 alg) { struct iwm_key *key = &iwm->keys[key_idx]; memset(key, 0, sizeof(struct iwm_key)); memcpy(key->hdr.mac, ext->addr.sa_data, ETH_ALEN); key->hdr.key_idx = key_idx; if (is_broadcast_ether_addr(ext->addr.sa_data)) key->hdr.multicast = 1; key->in_use = in_use; key->flags = ext->ext_flags; key->alg = alg; key->key_len = ext->key_len; memcpy(key->key, ext->key, ext->key_len); return key; } static int iwm_wext_giwrate(struct net_device *dev, struct iw_request_info *info, struct iw_param *rate, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); rate->value = iwm->rate * 1000000; return 0; } static int iwm_wext_siwencode(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *key_buf) { struct iwm_priv *iwm = ndev_to_iwm(dev); struct iwm_key *uninitialized_var(key); int idx, i, uninitialized_var(alg), remove = 0, ret; IWM_DBG_WEXT(iwm, DBG, "key len: %d\n", erq->length); IWM_DBG_WEXT(iwm, DBG, "flags: 0x%x\n", erq->flags); if (!iwm->umac_profile) { IWM_ERR(iwm, "UMAC profile not allocated yet\n"); return -ENODEV; } if (erq->length == WLAN_KEY_LEN_WEP40) { alg = UMAC_CIPHER_TYPE_WEP_40; iwm->umac_profile->sec.ucast_cipher = UMAC_CIPHER_TYPE_WEP_40; iwm->umac_profile->sec.mcast_cipher = UMAC_CIPHER_TYPE_WEP_40; } else if (erq->length == WLAN_KEY_LEN_WEP104) { alg = UMAC_CIPHER_TYPE_WEP_104; iwm->umac_profile->sec.ucast_cipher = UMAC_CIPHER_TYPE_WEP_104; iwm->umac_profile->sec.mcast_cipher = UMAC_CIPHER_TYPE_WEP_104; } if (erq->flags & IW_ENCODE_RESTRICTED) iwm->umac_profile->sec.auth_type = UMAC_AUTH_TYPE_LEGACY_PSK; else iwm->umac_profile->sec.auth_type = UMAC_AUTH_TYPE_OPEN; idx = erq->flags & IW_ENCODE_INDEX; if (idx == 0) { if (iwm->default_key) for (i = 0; i < IWM_NUM_KEYS; i++) { if (iwm->default_key == &iwm->keys[i]) { idx = i; break; } } else iwm->default_key = &iwm->keys[idx]; } else if (idx < 1 || idx > 4) { return -EINVAL; } else idx--; if (erq->flags & IW_ENCODE_DISABLED) remove = 1; else if (erq->length == 0) { if (!iwm->keys[idx].in_use) return -EINVAL; iwm->default_key = &iwm->keys[idx]; } if (erq->length) { key = &iwm->keys[idx]; memset(key, 0, sizeof(struct iwm_key)); memset(key->hdr.mac, 0xff, ETH_ALEN); key->hdr.key_idx = idx; key->hdr.multicast = 1; key->in_use = !remove; key->alg = alg; key->key_len = erq->length; memcpy(key->key, key_buf, erq->length); IWM_DBG_WEXT(iwm, DBG, "Setting key %d, default: %d\n", idx, !!iwm->default_key); } if (remove) { if ((erq->flags & IW_ENCODE_NOKEY) || (erq->length == 0)) { int j; for (j = 0; j < IWM_NUM_KEYS; j++) if (iwm->keys[j].in_use) { struct iwm_key *k = &iwm->keys[j]; k->in_use = 0; ret = iwm_set_key(iwm, remove, 0, k); if (ret < 0) return ret; } iwm->umac_profile->sec.ucast_cipher = UMAC_CIPHER_TYPE_NONE; iwm->umac_profile->sec.mcast_cipher = UMAC_CIPHER_TYPE_NONE; iwm->umac_profile->sec.auth_type = UMAC_AUTH_TYPE_OPEN; return 0; } else { key->in_use = 0; return iwm_set_key(iwm, remove, 0, key); } } /* * If we havent set a profile yet, we cant set keys. * Keys will be pushed after we're associated. */ if (!iwm->umac_profile_active) return 0; /* * If there is a current active profile, but no * default key, it's not worth trying to associate again. */ if (!iwm->default_key) return 0; /* * Here we have an active profile, but a key setting changed. * We thus have to invalidate the current profile, and push the * new one. Keys will be pushed when association takes place. */ ret = iwm_invalidate_mlme_profile(iwm); if (ret < 0) { IWM_ERR(iwm, "Couldn't invalidate profile\n"); return ret; } return iwm_send_mlme_profile(iwm); } static int iwm_wext_giwencode(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *key) { struct iwm_priv *iwm = ndev_to_iwm(dev); int idx, i; idx = erq->flags & IW_ENCODE_INDEX; if (idx < 1 || idx > 4) { idx = -1; if (!iwm->default_key) { erq->length = 0; erq->flags |= IW_ENCODE_NOKEY; return 0; } else for (i = 0; i < IWM_NUM_KEYS; i++) { if (iwm->default_key == &iwm->keys[i]) { idx = i; break; } } if (idx < 0) return -EINVAL; } else idx--; erq->flags = idx + 1; if (!iwm->keys[idx].in_use) { erq->length = 0; erq->flags |= IW_ENCODE_DISABLED; return 0; } memcpy(key, iwm->keys[idx].key, min_t(int, erq->length, iwm->keys[idx].key_len)); erq->length = iwm->keys[idx].key_len; erq->flags |= IW_ENCODE_ENABLED; if (iwm->umac_profile->mode == UMAC_MODE_BSS) { switch (iwm->umac_profile->sec.auth_type) { case UMAC_AUTH_TYPE_OPEN: erq->flags |= IW_ENCODE_OPEN; break; default: erq->flags |= IW_ENCODE_RESTRICTED; break; } } return 0; } static int iwm_set_wpa_version(struct iwm_priv *iwm, u8 wpa_version) { if (wpa_version & IW_AUTH_WPA_VERSION_WPA2) iwm->umac_profile->sec.flags = UMAC_SEC_FLG_RSNA_ON_MSK; else if (wpa_version & IW_AUTH_WPA_VERSION_WPA) iwm->umac_profile->sec.flags = UMAC_SEC_FLG_WPA_ON_MSK; else iwm->umac_profile->sec.flags = UMAC_SEC_FLG_LEGACY_PROFILE; return 0; } static int iwm_wext_siwpower(struct net_device *dev, struct iw_request_info *info, struct iw_param *wrq, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); u32 power_index; if (wrq->disabled) { power_index = IWM_POWER_INDEX_MIN; goto set; } else power_index = IWM_POWER_INDEX_DEFAULT; switch (wrq->flags & IW_POWER_MODE) { case IW_POWER_ON: case IW_POWER_MODE: case IW_POWER_ALL_R: break; default: return -EINVAL; } set: if (power_index == iwm->conf.power_index) return 0; iwm->conf.power_index = power_index; return iwm_umac_set_config_fix(iwm, UMAC_PARAM_TBL_CFG_FIX, CFG_POWER_INDEX, iwm->conf.power_index); } static int iwm_wext_giwpower(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); wrqu->power.disabled = (iwm->conf.power_index == IWM_POWER_INDEX_MIN); return 0; } static int iwm_set_key_mgt(struct iwm_priv *iwm, u8 key_mgt) { u8 *auth_type = &iwm->umac_profile->sec.auth_type; if (key_mgt == IW_AUTH_KEY_MGMT_802_1X) *auth_type = UMAC_AUTH_TYPE_8021X; else if (key_mgt == IW_AUTH_KEY_MGMT_PSK) { if (iwm->umac_profile->sec.flags & (UMAC_SEC_FLG_WPA_ON_MSK | UMAC_SEC_FLG_RSNA_ON_MSK)) *auth_type = UMAC_AUTH_TYPE_RSNA_PSK; else *auth_type = UMAC_AUTH_TYPE_LEGACY_PSK; } else { IWM_ERR(iwm, "Invalid key mgt: 0x%x\n", key_mgt); return -EINVAL; } return 0; } static int iwm_set_cipher(struct iwm_priv *iwm, u8 cipher, u8 ucast) { u8 *profile_cipher = ucast ? &iwm->umac_profile->sec.ucast_cipher : &iwm->umac_profile->sec.mcast_cipher; switch (cipher) { case IW_AUTH_CIPHER_NONE: *profile_cipher = UMAC_CIPHER_TYPE_NONE; break; case IW_AUTH_CIPHER_WEP40: *profile_cipher = UMAC_CIPHER_TYPE_WEP_40; break; case IW_AUTH_CIPHER_TKIP: *profile_cipher = UMAC_CIPHER_TYPE_TKIP; break; case IW_AUTH_CIPHER_CCMP: *profile_cipher = UMAC_CIPHER_TYPE_CCMP; break; case IW_AUTH_CIPHER_WEP104: *profile_cipher = UMAC_CIPHER_TYPE_WEP_104; break; default: IWM_ERR(iwm, "Unsupported cipher: 0x%x\n", cipher); return -ENOTSUPP; } return 0; } static int iwm_set_auth_alg(struct iwm_priv *iwm, u8 auth_alg) { u8 *auth_type = &iwm->umac_profile->sec.auth_type; switch (auth_alg) { case IW_AUTH_ALG_OPEN_SYSTEM: *auth_type = UMAC_AUTH_TYPE_OPEN; break; case IW_AUTH_ALG_SHARED_KEY: if (iwm->umac_profile->sec.flags & (UMAC_SEC_FLG_WPA_ON_MSK | UMAC_SEC_FLG_RSNA_ON_MSK)) { if (*auth_type == UMAC_AUTH_TYPE_8021X) return -EINVAL; *auth_type = UMAC_AUTH_TYPE_RSNA_PSK; } else { *auth_type = UMAC_AUTH_TYPE_LEGACY_PSK; } break; case IW_AUTH_ALG_LEAP: default: IWM_ERR(iwm, "Unsupported auth alg: 0x%x\n", auth_alg); return -ENOTSUPP; } return 0; } static int iwm_wext_siwauth(struct net_device *dev, struct iw_request_info *info, struct iw_param *data, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); int ret; if ((data->flags) & (IW_AUTH_WPA_VERSION | IW_AUTH_KEY_MGMT | IW_AUTH_WPA_ENABLED | IW_AUTH_80211_AUTH_ALG)) { /* We need to invalidate the current profile */ if (iwm->umac_profile_active) { ret = iwm_invalidate_mlme_profile(iwm); if (ret < 0) { IWM_ERR(iwm, "Couldn't invalidate profile\n"); return ret; } } } switch (data->flags & IW_AUTH_INDEX) { case IW_AUTH_WPA_VERSION: return iwm_set_wpa_version(iwm, data->value); break; case IW_AUTH_CIPHER_PAIRWISE: return iwm_set_cipher(iwm, data->value, 1); break; case IW_AUTH_CIPHER_GROUP: return iwm_set_cipher(iwm, data->value, 0); break; case IW_AUTH_KEY_MGMT: return iwm_set_key_mgt(iwm, data->value); break; case IW_AUTH_80211_AUTH_ALG: return iwm_set_auth_alg(iwm, data->value); break; default: return -ENOTSUPP; } return 0; } static int iwm_wext_giwauth(struct net_device *dev, struct iw_request_info *info, struct iw_param *data, char *extra) { return 0; } static int iwm_wext_siwencodeext(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *extra) { struct iwm_priv *iwm = ndev_to_iwm(dev); struct iwm_key *key; struct iw_encode_ext *ext = (struct iw_encode_ext *) extra; int uninitialized_var(alg), idx, i, remove = 0; IWM_DBG_WEXT(iwm, DBG, "alg: 0x%x\n", ext->alg); IWM_DBG_WEXT(iwm, DBG, "key len: %d\n", ext->key_len); IWM_DBG_WEXT(iwm, DBG, "ext_flags: 0x%x\n", ext->ext_flags); IWM_DBG_WEXT(iwm, DBG, "flags: 0x%x\n", erq->flags); IWM_DBG_WEXT(iwm, DBG, "length: 0x%x\n", erq->length); switch (ext->alg) { case IW_ENCODE_ALG_NONE: remove = 1; break; case IW_ENCODE_ALG_WEP: if (ext->key_len == WLAN_KEY_LEN_WEP40) alg = UMAC_CIPHER_TYPE_WEP_40; else if (ext->key_len == WLAN_KEY_LEN_WEP104) alg = UMAC_CIPHER_TYPE_WEP_104; else { IWM_ERR(iwm, "Invalid key length: %d\n", ext->key_len); return -EINVAL; } break; case IW_ENCODE_ALG_TKIP: alg = UMAC_CIPHER_TYPE_TKIP; break; case IW_ENCODE_ALG_CCMP: alg = UMAC_CIPHER_TYPE_CCMP; break; default: return -EOPNOTSUPP; } idx = erq->flags & IW_ENCODE_INDEX; if (idx == 0) { if (iwm->default_key) for (i = 0; i < IWM_NUM_KEYS; i++) { if (iwm->default_key == &iwm->keys[i]) { idx = i; break; } } } else if (idx < 1 || idx > 4) { return -EINVAL; } else idx--; if (erq->flags & IW_ENCODE_DISABLED) remove = 1; else if ((erq->length == 0) || (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY)) { iwm->default_key = &iwm->keys[idx]; if (iwm->umac_profile_active && ext->alg == IW_ENCODE_ALG_WEP) return iwm_set_tx_key(iwm, idx); } key = iwm_key_init(iwm, idx, !remove, ext, alg); return iwm_set_key(iwm, remove, !iwm->default_key, key); } static const iw_handler iwm_handlers[] = { (iw_handler) NULL, /* SIOCSIWCOMMIT */ (iw_handler) cfg80211_wext_giwname, /* SIOCGIWNAME */ (iw_handler) NULL, /* SIOCSIWNWID */ (iw_handler) NULL, /* SIOCGIWNWID */ (iw_handler) iwm_wext_siwfreq, /* SIOCSIWFREQ */ (iw_handler) iwm_wext_giwfreq, /* SIOCGIWFREQ */ (iw_handler) cfg80211_wext_siwmode, /* SIOCSIWMODE */ (iw_handler) cfg80211_wext_giwmode, /* SIOCGIWMODE */ (iw_handler) NULL, /* SIOCSIWSENS */ (iw_handler) NULL, /* SIOCGIWSENS */ (iw_handler) NULL /* not used */, /* SIOCSIWRANGE */ (iw_handler) cfg80211_wext_giwrange, /* SIOCGIWRANGE */ (iw_handler) NULL /* not used */, /* SIOCSIWPRIV */ (iw_handler) NULL /* kernel code */, /* SIOCGIWPRIV */ (iw_handler) NULL /* not used */, /* SIOCSIWSTATS */ (iw_handler) NULL /* kernel code */, /* SIOCGIWSTATS */ (iw_handler) NULL, /* SIOCSIWSPY */ (iw_handler) NULL, /* SIOCGIWSPY */ (iw_handler) NULL, /* SIOCSIWTHRSPY */ (iw_handler) NULL, /* SIOCGIWTHRSPY */ (iw_handler) iwm_wext_siwap, /* SIOCSIWAP */ (iw_handler) iwm_wext_giwap, /* SIOCGIWAP */ (iw_handler) NULL, /* SIOCSIWMLME */ (iw_handler) NULL, /* SIOCGIWAPLIST */ (iw_handler) cfg80211_wext_siwscan, /* SIOCSIWSCAN */ (iw_handler) cfg80211_wext_giwscan, /* SIOCGIWSCAN */ (iw_handler) iwm_wext_siwessid, /* SIOCSIWESSID */ (iw_handler) iwm_wext_giwessid, /* SIOCGIWESSID */ (iw_handler) NULL, /* SIOCSIWNICKN */ (iw_handler) NULL, /* SIOCGIWNICKN */ (iw_handler) NULL, /* -- hole -- */ (iw_handler) NULL, /* -- hole -- */ (iw_handler) NULL, /* SIOCSIWRATE */ (iw_handler) iwm_wext_giwrate, /* SIOCGIWRATE */ (iw_handler) cfg80211_wext_siwrts, /* SIOCSIWRTS */ (iw_handler) cfg80211_wext_giwrts, /* SIOCGIWRTS */ (iw_handler) cfg80211_wext_siwfrag, /* SIOCSIWFRAG */ (iw_handler) cfg80211_wext_giwfrag, /* SIOCGIWFRAG */ (iw_handler) NULL, /* SIOCSIWTXPOW */ (iw_handler) NULL, /* SIOCGIWTXPOW */ (iw_handler) NULL, /* SIOCSIWRETRY */ (iw_handler) NULL, /* SIOCGIWRETRY */ (iw_handler) iwm_wext_siwencode, /* SIOCSIWENCODE */ (iw_handler) iwm_wext_giwencode, /* SIOCGIWENCODE */ (iw_handler) iwm_wext_siwpower, /* SIOCSIWPOWER */ (iw_handler) iwm_wext_giwpower, /* SIOCGIWPOWER */ (iw_handler) NULL, /* -- hole -- */ (iw_handler) NULL, /* -- hole -- */ (iw_handler) NULL, /* SIOCSIWGENIE */ (iw_handler) NULL, /* SIOCGIWGENIE */ (iw_handler) iwm_wext_siwauth, /* SIOCSIWAUTH */ (iw_handler) iwm_wext_giwauth, /* SIOCGIWAUTH */ (iw_handler) iwm_wext_siwencodeext, /* SIOCSIWENCODEEXT */ (iw_handler) NULL, /* SIOCGIWENCODEEXT */ (iw_handler) NULL, /* SIOCSIWPMKSA */ (iw_handler) NULL, /* -- hole -- */ }; const struct iw_handler_def iwm_iw_handler_def = { .num_standard = ARRAY_SIZE(iwm_handlers), .standard = (iw_handler *) iwm_handlers, .get_wireless_stats = iwm_get_wireless_stats, };