From 48a61477bdc04896bd96d259388a0c42a7019943 Mon Sep 17 00:00:00 2001 From: Shahar Levi Date: Sun, 6 Mar 2011 16:32:08 +0200 Subject: wl12xx: 1281/1283 support - Add acx commands New acx command that sets: Rx fifo enable reduced bus transactions in RX path. Tx bus transactions padding to SDIO block size that improve preference in Tx and essential for working with SDIO HS (48Mhz). The max SDIO block size is 256 when working with Tx bus transactions padding to SDIO block. Add new ops to SDIO & SPI that handles the win size change in case of transactions padding (relevant only for SDIO). [Fix endianess issues; simplify sdio-specific block_size handling; minor changes in comments; use "aligned_len" in one calculation instead of "pad" to avoid confusion -- Luca] Signed-off-by: Shahar Levi Reviewed-by: Luciano Coelho Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index a3db755ceeda..50676b36ad26 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -1019,6 +1019,32 @@ out: return ret; } +int wl1271_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap) +{ + struct wl1271_acx_host_config_bitmap *bitmap_conf; + int ret; + + bitmap_conf = kzalloc(sizeof(*bitmap_conf), GFP_KERNEL); + if (!bitmap_conf) { + ret = -ENOMEM; + goto out; + } + + bitmap_conf->host_cfg_bitmap = cpu_to_le32(host_cfg_bitmap); + + ret = wl1271_cmd_configure(wl, ACX_HOST_IF_CFG_BITMAP, + bitmap_conf, sizeof(*bitmap_conf)); + if (ret < 0) { + wl1271_warning("wl1271 bitmap config opt failed: %d", ret); + goto out; + } + +out: + kfree(bitmap_conf); + + return ret; +} + int wl1271_acx_init_mem_config(struct wl1271 *wl) { int ret; -- cgit v1.2.3 From 13b107dd9808343d05627f0fba7fbc764c86738e Mon Sep 17 00:00:00 2001 From: Shahar Levi Date: Sun, 6 Mar 2011 16:32:12 +0200 Subject: wl12xx: 1281/1283 support - use dynamic memory for the RX/TX pools Separate the memory configuration to chip-specific structures and implement dynamic memory for wl128x. This feature allows us to move TX memory blocks to the RX pool when the RX path is overloaded. Thanks for Arik Nemtsov for helping simplify the wl1271_fw_status() code. [Rewrote the commit subject and message for clarity; improved some comments and changed "spare" to "padding" for consistency; added a FIXME for the AP memory configuration -- Luca] Signed-off-by: Shahar Levi Reviewed-by: Luciano Coelho Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 35 +++++++++++++++++--------- drivers/net/wireless/wl12xx/conf.h | 3 ++- drivers/net/wireless/wl12xx/main.c | 48 ++++++++++++++++++++++++++++++------ drivers/net/wireless/wl12xx/wl12xx.h | 5 ++++ 4 files changed, 70 insertions(+), 21 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index 50676b36ad26..e005aa40ef84 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -965,10 +965,13 @@ int wl1271_acx_ap_mem_cfg(struct wl1271 *wl) } /* memory config */ - mem_conf->num_stations = wl->conf.mem.num_stations; - mem_conf->rx_mem_block_num = wl->conf.mem.rx_block_num; - mem_conf->tx_min_mem_block_num = wl->conf.mem.tx_min_block_num; - mem_conf->num_ssid_profiles = wl->conf.mem.ssid_profiles; + /* FIXME: for now we always use mem_wl127x for AP, because it + * doesn't support dynamic memory and we don't have the + * optimal values for wl128x without dynamic memory yet */ + mem_conf->num_stations = wl->conf.mem_wl127x.num_stations; + mem_conf->rx_mem_block_num = wl->conf.mem_wl127x.rx_block_num; + mem_conf->tx_min_mem_block_num = wl->conf.mem_wl127x.tx_min_block_num; + mem_conf->num_ssid_profiles = wl->conf.mem_wl127x.ssid_profiles; mem_conf->total_tx_descriptors = cpu_to_le32(ACX_TX_DESCRIPTORS); ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, @@ -986,6 +989,7 @@ out: int wl1271_acx_sta_mem_cfg(struct wl1271 *wl) { struct wl1271_acx_sta_config_memory *mem_conf; + struct conf_memory_settings *mem; int ret; wl1271_debug(DEBUG_ACX, "wl1271 mem cfg"); @@ -996,16 +1000,21 @@ int wl1271_acx_sta_mem_cfg(struct wl1271 *wl) goto out; } + if (wl->chip.id == CHIP_ID_1283_PG20) + mem = &wl->conf.mem_wl128x; + else + mem = &wl->conf.mem_wl127x; + /* memory config */ - mem_conf->num_stations = wl->conf.mem.num_stations; - mem_conf->rx_mem_block_num = wl->conf.mem.rx_block_num; - mem_conf->tx_min_mem_block_num = wl->conf.mem.tx_min_block_num; - mem_conf->num_ssid_profiles = wl->conf.mem.ssid_profiles; + mem_conf->num_stations = mem->num_stations; + mem_conf->rx_mem_block_num = mem->rx_block_num; + mem_conf->tx_min_mem_block_num = mem->tx_min_block_num; + mem_conf->num_ssid_profiles = mem->ssid_profiles; mem_conf->total_tx_descriptors = cpu_to_le32(ACX_TX_DESCRIPTORS); - mem_conf->dyn_mem_enable = wl->conf.mem.dynamic_memory; - mem_conf->tx_free_req = wl->conf.mem.min_req_tx_blocks; - mem_conf->rx_free_req = wl->conf.mem.min_req_rx_blocks; - mem_conf->tx_min = wl->conf.mem.tx_min; + mem_conf->dyn_mem_enable = mem->dynamic_memory; + mem_conf->tx_free_req = mem->min_req_tx_blocks; + mem_conf->rx_free_req = mem->min_req_rx_blocks; + mem_conf->tx_min = mem->tx_min; ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, sizeof(*mem_conf)); @@ -1072,6 +1081,8 @@ int wl1271_acx_init_mem_config(struct wl1271 *wl) wl1271_debug(DEBUG_TX, "available tx blocks: %d", wl->tx_blocks_available); + wl->tx_new_total = wl->tx_blocks_available; + return 0; } diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index a00f22c6c74f..743bd0beb63b 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -1204,7 +1204,8 @@ struct conf_drv_settings { struct conf_scan_settings scan; struct conf_rf_settings rf; struct conf_ht_setting ht; - struct conf_memory_settings mem; + struct conf_memory_settings mem_wl127x; + struct conf_memory_settings mem_wl128x; }; #endif diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index fe0cf47a656c..3c381ceadb98 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -298,7 +298,7 @@ static struct conf_drv_settings default_conf = { .tx_ba_win_size = 64, .inactivity_timeout = 10000, }, - .mem = { + .mem_wl127x = { .num_stations = 1, .ssid_profiles = 1, .rx_block_num = 70, @@ -307,7 +307,17 @@ static struct conf_drv_settings default_conf = { .min_req_tx_blocks = 100, .min_req_rx_blocks = 22, .tx_min = 27, - } + }, + .mem_wl128x = { + .num_stations = 1, + .ssid_profiles = 1, + .rx_block_num = 40, + .tx_min_block_num = 40, + .dynamic_memory = 1, + .min_req_tx_blocks = 45, + .min_req_rx_blocks = 22, + .tx_min = 27, + }, }; static void __wl1271_op_remove_interface(struct wl1271 *wl); @@ -608,16 +618,27 @@ static void wl1271_fw_status(struct wl1271 *wl, { struct wl1271_fw_common_status *status = &full_status->common; struct timespec ts; + u32 old_tx_blk_count = wl->tx_blocks_available; u32 total = 0; int i; - if (wl->bss_type == BSS_TYPE_AP_BSS) + if (wl->bss_type == BSS_TYPE_AP_BSS) { wl1271_raw_read(wl, FW_STATUS_ADDR, status, sizeof(struct wl1271_fw_ap_status), false); - else + } else { wl1271_raw_read(wl, FW_STATUS_ADDR, status, sizeof(struct wl1271_fw_sta_status), false); + /* Update tx total blocks change */ + wl->tx_total_diff += + ((struct wl1271_fw_sta_status *)status)->tx_total - + wl->tx_new_total; + + /* Update total tx blocks */ + wl->tx_new_total = + ((struct wl1271_fw_sta_status *)status)->tx_total; + } + wl1271_debug(DEBUG_IRQ, "intr: 0x%x (fw_rx_counter = %d, " "drv_rx_counter = %d, tx_results_counter = %d)", status->intr, @@ -627,17 +648,28 @@ static void wl1271_fw_status(struct wl1271 *wl, /* update number of available TX blocks */ for (i = 0; i < NUM_TX_QUEUES; i++) { - u32 cnt = le32_to_cpu(status->tx_released_blks[i]) - + total += le32_to_cpu(status->tx_released_blks[i]) - wl->tx_blocks_freed[i]; wl->tx_blocks_freed[i] = le32_to_cpu(status->tx_released_blks[i]); - wl->tx_blocks_available += cnt; - total += cnt; + + } + + /* + * By adding the freed blocks to tx_total_diff we are actually + * moving them to the RX pool. + */ + wl->tx_total_diff += total; + + /* if we have positive difference, add the blocks to the TX pool */ + if (wl->tx_total_diff >= 0) { + wl->tx_blocks_available += wl->tx_total_diff; + wl->tx_total_diff = 0; } /* if more blocks are available now, tx work can be scheduled */ - if (total) + if (wl->tx_blocks_available > old_tx_blk_count) clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); /* for AP update num of allocated TX blocks per link and ps status */ diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 4b556776fd57..ad04b8337a25 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -267,6 +267,8 @@ struct wl1271_fw_sta_status { u8 tx_total; u8 reserved1; __le16 reserved2; + /* Total structure size is 68 bytes */ + u32 padding; } __packed; struct wl1271_fw_full_status { @@ -397,6 +399,9 @@ struct wl1271 { u32 tx_blocks_freed[NUM_TX_QUEUES]; u32 tx_blocks_available; u32 tx_results_count; + /* Indicates how many memory blocks should be moved to the RX pool */ + int tx_total_diff; + u32 tx_new_total; /* Transmitted TX packets counter for chipset interface */ u32 tx_packets_count; -- cgit v1.2.3 From d2f4d47d84f8c665ab9babb2cc84d2e7872a96e1 Mon Sep 17 00:00:00 2001 From: Ido Yariv Date: Thu, 31 Mar 2011 10:07:00 +0200 Subject: wl12xx: Simplify TX blocks accounting The total number of TX memory blocks may change when the dynamic memory option is enabled. The current implementation only tracks the available memory blocks, which over-complicates TX blocks accounting. By tracking the number of allocated blocks, calculation of the number of available blocks becomes simpler and cleaner. It simply equals the total number of TX memory blocks minus the allocated ones. Also, remove some unnecessary castings and use union member accesses instead. Signed-off-by: Ido Yariv Signed-off-by: Arik Nemtsov Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 2 -- drivers/net/wireless/wl12xx/main.c | 48 +++++++++++++++++------------------- drivers/net/wireless/wl12xx/tx.c | 1 + drivers/net/wireless/wl12xx/wl12xx.h | 4 +-- 4 files changed, 24 insertions(+), 31 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index e005aa40ef84..b277947400b5 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -1081,8 +1081,6 @@ int wl1271_acx_init_mem_config(struct wl1271 *wl) wl1271_debug(DEBUG_TX, "available tx blocks: %d", wl->tx_blocks_available); - wl->tx_new_total = wl->tx_blocks_available; - return 0; } diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 7dce24c0b331..1feb9551ef8f 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -622,7 +622,7 @@ static void wl1271_fw_status(struct wl1271 *wl, struct wl1271_fw_common_status *status = &full_status->common; struct timespec ts; u32 old_tx_blk_count = wl->tx_blocks_available; - u32 total = 0; + u32 freed_blocks = 0; int i; if (wl->bss_type == BSS_TYPE_AP_BSS) { @@ -631,15 +631,6 @@ static void wl1271_fw_status(struct wl1271 *wl, } else { wl1271_raw_read(wl, FW_STATUS_ADDR, status, sizeof(struct wl1271_fw_sta_status), false); - - /* Update tx total blocks change */ - wl->tx_total_diff += - ((struct wl1271_fw_sta_status *)status)->tx_total - - wl->tx_new_total; - - /* Update total tx blocks */ - wl->tx_new_total = - ((struct wl1271_fw_sta_status *)status)->tx_total; } wl1271_debug(DEBUG_IRQ, "intr: 0x%x (fw_rx_counter = %d, " @@ -651,34 +642,38 @@ static void wl1271_fw_status(struct wl1271 *wl, /* update number of available TX blocks */ for (i = 0; i < NUM_TX_QUEUES; i++) { - total += le32_to_cpu(status->tx_released_blks[i]) - - wl->tx_blocks_freed[i]; + freed_blocks += le32_to_cpu(status->tx_released_blks[i]) - + wl->tx_blocks_freed[i]; wl->tx_blocks_freed[i] = le32_to_cpu(status->tx_released_blks[i]); - } - /* - * By adding the freed blocks to tx_total_diff we are actually - * moving them to the RX pool. - */ - wl->tx_total_diff += total; + wl->tx_allocated_blocks -= freed_blocks; + + if (wl->bss_type == BSS_TYPE_AP_BSS) { + /* Update num of allocated TX blocks per link and ps status */ + wl1271_irq_update_links_status(wl, &full_status->ap); + wl->tx_blocks_available += freed_blocks; + } else { + int avail = full_status->sta.tx_total - wl->tx_allocated_blocks; - /* if we have positive difference, add the blocks to the TX pool */ - if (wl->tx_total_diff >= 0) { - wl->tx_blocks_available += wl->tx_total_diff; - wl->tx_total_diff = 0; + /* + * The FW might change the total number of TX memblocks before + * we get a notification about blocks being released. Thus, the + * available blocks calculation might yield a temporary result + * which is lower than the actual available blocks. Keeping in + * mind that only blocks that were allocated can be moved from + * TX to RX, tx_blocks_available should never decrease here. + */ + wl->tx_blocks_available = max((int)wl->tx_blocks_available, + avail); } /* if more blocks are available now, tx work can be scheduled */ if (wl->tx_blocks_available > old_tx_blk_count) clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); - /* for AP update num of allocated TX blocks per link and ps status */ - if (wl->bss_type == BSS_TYPE_AP_BSS) - wl1271_irq_update_links_status(wl, &full_status->ap); - /* update the host-chipset time offset */ getnstimeofday(&ts); wl->time_offset = (timespec_to_ns(&ts) >> 10) - @@ -1495,6 +1490,7 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl) wl->psm_entry_retry = 0; wl->power_level = WL1271_DEFAULT_POWER_LEVEL; wl->tx_blocks_available = 0; + wl->tx_allocated_blocks = 0; wl->tx_results_count = 0; wl->tx_packets_count = 0; wl->tx_security_last_seq = 0; diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c index 67245ad396ea..7a3339fd3415 100644 --- a/drivers/net/wireless/wl12xx/tx.c +++ b/drivers/net/wireless/wl12xx/tx.c @@ -203,6 +203,7 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra, desc->id = id; wl->tx_blocks_available -= total_blocks; + wl->tx_allocated_blocks += total_blocks; if (wl->bss_type == BSS_TYPE_AP_BSS) wl->links[hlid].allocated_blks += total_blocks; diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 1b430d2aec4e..9ccbcfdd0802 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -418,10 +418,8 @@ struct wl1271 { /* Accounting for allocated / available TX blocks on HW */ u32 tx_blocks_freed[NUM_TX_QUEUES]; u32 tx_blocks_available; + u32 tx_allocated_blocks; u32 tx_results_count; - /* Indicates how many memory blocks should be moved to the RX pool */ - int tx_total_diff; - u32 tx_new_total; /* Transmitted TX packets counter for chipset interface */ u32 tx_packets_count; -- cgit v1.2.3 From 47684808fd89d6809c0886e06f8ac324252499d8 Mon Sep 17 00:00:00 2001 From: Arik Nemtsov Date: Tue, 26 Apr 2011 23:21:51 +0300 Subject: wl12xx: support FW TX inactivity triggers In AP mode we register for the MAX_TX_RETRY and INACTIVE_STA events. Both are reported to the upper layers as a TX failure in the offending stations. In STA mode we register only for the MAX_TX_RETRY event. A TX failure is interpreted as a loss of connection. Support for IEEE80211_HW_REPORTS_TX_ACK_STATUS has been removed to avoid the inherent race condition of a mac80211 TX failure counter in addition to the FW counter. This patch depends on "mac80211: allow low level driver to report packet loss" Signed-off-by: Arik Nemtsov Signed-off-by: John W. Linville --- drivers/net/wireless/wl12xx/acx.c | 34 ++++++++++++++++++++++---- drivers/net/wireless/wl12xx/acx.h | 12 +++++++-- drivers/net/wireless/wl12xx/boot.c | 6 +++-- drivers/net/wireless/wl12xx/cmd.c | 2 +- drivers/net/wireless/wl12xx/conf.h | 12 +++++++-- drivers/net/wireless/wl12xx/event.c | 47 ++++++++++++++++++++++++++++++++++++ drivers/net/wireless/wl12xx/event.h | 12 ++++++++- drivers/net/wireless/wl12xx/init.c | 6 ++++- drivers/net/wireless/wl12xx/main.c | 10 ++++++-- drivers/net/wireless/wl12xx/wl12xx.h | 1 - 10 files changed, 125 insertions(+), 17 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index b277947400b5..a5c9c0aff83f 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -1524,22 +1524,46 @@ out: return ret; } -int wl1271_acx_max_tx_retry(struct wl1271 *wl) +int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl) { - struct wl1271_acx_max_tx_retry *acx = NULL; + struct wl1271_acx_ap_max_tx_retry *acx = NULL; int ret; - wl1271_debug(DEBUG_ACX, "acx max tx retry"); + wl1271_debug(DEBUG_ACX, "acx ap max tx retry"); acx = kzalloc(sizeof(*acx), GFP_KERNEL); if (!acx) return -ENOMEM; - acx->max_tx_retry = cpu_to_le16(wl->conf.tx.ap_max_tx_retries); + acx->max_tx_retry = cpu_to_le16(wl->conf.tx.max_tx_retries); ret = wl1271_cmd_configure(wl, ACX_MAX_TX_FAILURE, acx, sizeof(*acx)); if (ret < 0) { - wl1271_warning("acx max tx retry failed: %d", ret); + wl1271_warning("acx ap max tx retry failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_sta_max_tx_retry(struct wl1271 *wl) +{ + struct wl1271_acx_sta_max_tx_retry *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx sta max tx retry"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->max_tx_retry = wl->conf.tx.max_tx_retries; + + ret = wl1271_cmd_configure(wl, ACX_CONS_TX_FAILURE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx sta max tx retry failed: %d", ret); goto out; } diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index 0a40caeab2a2..942908cd53a3 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h @@ -1145,7 +1145,7 @@ struct wl1271_acx_fw_tsf_information { u8 padding[3]; } __packed; -struct wl1271_acx_max_tx_retry { +struct wl1271_acx_ap_max_tx_retry { struct acx_header header; /* @@ -1156,6 +1156,13 @@ struct wl1271_acx_max_tx_retry { u8 padding_1[2]; } __packed; +struct wl1271_acx_sta_max_tx_retry { + struct acx_header header; + + u8 max_tx_retry; + u8 padding_1[3]; +} __packed; + struct wl1271_acx_config_ps { struct acx_header header; @@ -1307,7 +1314,8 @@ int wl1271_acx_set_ba_session(struct wl1271 *wl, int wl1271_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, u16 ssn, bool enable); int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime); -int wl1271_acx_max_tx_retry(struct wl1271 *wl); +int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl); +int wl1271_acx_sta_max_tx_retry(struct wl1271 *wl); int wl1271_acx_config_ps(struct wl1271 *wl); int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); diff --git a/drivers/net/wireless/wl12xx/boot.c b/drivers/net/wireless/wl12xx/boot.c index 2b0cf85788b3..d263ebb6f974 100644 --- a/drivers/net/wireless/wl12xx/boot.c +++ b/drivers/net/wireless/wl12xx/boot.c @@ -478,10 +478,12 @@ static int wl1271_boot_run_firmware(struct wl1271 *wl) DISCONNECT_EVENT_COMPLETE_ID | RSSI_SNR_TRIGGER_0_EVENT_ID | PSPOLL_DELIVERY_FAILURE_EVENT_ID | - SOFT_GEMINI_SENSE_EVENT_ID; + SOFT_GEMINI_SENSE_EVENT_ID | + MAX_TX_RETRY_EVENT_ID; if (wl->bss_type == BSS_TYPE_AP_BSS) - wl->event_mask |= STA_REMOVE_COMPLETE_EVENT_ID; + wl->event_mask |= STA_REMOVE_COMPLETE_EVENT_ID | + INACTIVE_STA_EVENT_ID; else wl->event_mask |= DUMMY_PACKET_EVENT_ID; diff --git a/drivers/net/wireless/wl12xx/cmd.c b/drivers/net/wireless/wl12xx/cmd.c index 246804428517..d48331682e7e 100644 --- a/drivers/net/wireless/wl12xx/cmd.c +++ b/drivers/net/wireless/wl12xx/cmd.c @@ -1070,7 +1070,7 @@ int wl1271_cmd_start_bss(struct wl1271 *wl) memcpy(cmd->bssid, bss_conf->bssid, ETH_ALEN); - cmd->aging_period = cpu_to_le16(WL1271_AP_DEF_INACTIV_SEC); + cmd->aging_period = cpu_to_le16(wl->conf.tx.ap_aging_period); cmd->bss_index = WL1271_AP_BSS_INDEX; cmd->global_hlid = WL1271_AP_GLOBAL_HLID; cmd->broadcast_hlid = WL1271_AP_BROADCAST_HLID; diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index 6c9e3a673e6c..d16094f2604f 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -683,10 +683,18 @@ struct conf_tx_settings { struct conf_tx_rate_class ap_bcst_conf; /* - * AP-mode - allow this number of TX retries to a station before an + * Allow this number of TX retries to a connected station/AP before an * event is triggered from FW. + * In AP-mode the hlids of unreachable stations are given in the + * "sta_tx_retry_exceeded" member in the event mailbox. */ - u16 ap_max_tx_retries; + u8 max_tx_retries; + + /* + * AP-mode - after this number of seconds a connected station is + * considered inactive. + */ + u16 ap_aging_period; /* * Configuration for TID parameters. diff --git a/drivers/net/wireless/wl12xx/event.c b/drivers/net/wireless/wl12xx/event.c index ae69330e807c..d7be3aec6fc3 100644 --- a/drivers/net/wireless/wl12xx/event.c +++ b/drivers/net/wireless/wl12xx/event.c @@ -174,6 +174,8 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) u32 vector; bool beacon_loss = false; bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + bool disconnect_sta = false; + unsigned long sta_bitmap = 0; wl1271_event_mbox_dump(mbox); @@ -235,9 +237,54 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) wl1271_tx_dummy_packet(wl); } + /* + * "TX retries exceeded" has a different meaning according to mode. + * In AP mode the offending station is disconnected. In STA mode we + * report connection loss. + */ + if (vector & MAX_TX_RETRY_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "MAX_TX_RETRY_EVENT_ID"); + if (is_ap) { + sta_bitmap |= le16_to_cpu(mbox->sta_tx_retry_exceeded); + disconnect_sta = true; + } else { + beacon_loss = true; + } + } + + if ((vector & INACTIVE_STA_EVENT_ID) && is_ap) { + wl1271_debug(DEBUG_EVENT, "INACTIVE_STA_EVENT_ID"); + sta_bitmap |= le16_to_cpu(mbox->sta_aging_status); + disconnect_sta = true; + } + if (wl->vif && beacon_loss) ieee80211_connection_loss(wl->vif); + if (is_ap && disconnect_sta) { + u32 num_packets = wl->conf.tx.max_tx_retries; + struct ieee80211_sta *sta; + const u8 *addr; + int h; + + for (h = find_first_bit(&sta_bitmap, AP_MAX_LINKS); + h < AP_MAX_LINKS; + h = find_next_bit(&sta_bitmap, AP_MAX_LINKS, h+1)) { + if (!wl1271_is_active_sta(wl, h)) + continue; + + addr = wl->links[h].addr; + + rcu_read_lock(); + sta = ieee80211_find_sta(wl->vif, addr); + if (sta) { + wl1271_debug(DEBUG_EVENT, "remove sta %d", h); + ieee80211_report_low_ack(sta, num_packets); + } + rcu_read_unlock(); + } + } + return 0; } diff --git a/drivers/net/wireless/wl12xx/event.h b/drivers/net/wireless/wl12xx/event.h index b6cf06e565a4..7ae5a0821241 100644 --- a/drivers/net/wireless/wl12xx/event.h +++ b/drivers/net/wireless/wl12xx/event.h @@ -58,13 +58,16 @@ enum { CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(17), BSS_LOSE_EVENT_ID = BIT(18), REGAINED_BSS_EVENT_ID = BIT(19), - ROAMING_TRIGGER_MAX_TX_RETRY_EVENT_ID = BIT(20), + MAX_TX_RETRY_EVENT_ID = BIT(20), /* STA: dummy paket for dynamic mem blocks */ DUMMY_PACKET_EVENT_ID = BIT(21), /* AP: STA remove complete */ STA_REMOVE_COMPLETE_EVENT_ID = BIT(21), SOFT_GEMINI_SENSE_EVENT_ID = BIT(22), + /* STA: SG prediction */ SOFT_GEMINI_PREDICTION_EVENT_ID = BIT(23), + /* AP: Inactive STA */ + INACTIVE_STA_EVENT_ID = BIT(23), SOFT_GEMINI_AVALANCHE_EVENT_ID = BIT(24), PLT_RX_CALIBRATION_COMPLETE_EVENT_ID = BIT(25), DBG_EVENT_ID = BIT(26), @@ -119,7 +122,11 @@ struct event_mailbox { /* AP FW only */ u8 hlid_removed; + + /* a bitmap of hlids for stations that have been inactive too long */ __le16 sta_aging_status; + + /* a bitmap of hlids for stations which didn't respond to TX */ __le16 sta_tx_retry_exceeded; u8 reserved_5[24]; @@ -130,4 +137,7 @@ void wl1271_event_mbox_config(struct wl1271 *wl); int wl1271_event_handle(struct wl1271 *wl, u8 mbox); void wl1271_pspoll_work(struct work_struct *work); +/* Functions from main.c */ +bool wl1271_is_active_sta(struct wl1271 *wl, u8 hlid); + #endif diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index cf466074237d..ab3b1e21de29 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -375,6 +375,10 @@ static int wl1271_sta_hw_init(struct wl1271 *wl) if (ret < 0) return ret; + ret = wl1271_acx_sta_max_tx_retry(wl); + if (ret < 0) + return ret; + ret = wl1271_acx_sta_mem_cfg(wl); if (ret < 0) return ret; @@ -441,7 +445,7 @@ static int wl1271_ap_hw_init(struct wl1271 *wl) if (ret < 0) return ret; - ret = wl1271_acx_max_tx_retry(wl); + ret = wl1271_acx_ap_max_tx_retry(wl); if (ret < 0) return ret; diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 866453bc1d1f..0c69e959d0de 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -192,7 +192,8 @@ static struct conf_drv_settings default_conf = { .long_retry_limit = 10, .aflags = 0, }, - .ap_max_tx_retries = 100, + .max_tx_retries = 100, + .ap_aging_period = 300, .tid_conf_count = 4, .tid_conf = { [CONF_TX_AC_BE] = { @@ -2953,6 +2954,12 @@ static void wl1271_free_sta(struct wl1271 *wl, u8 hlid) __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); } +bool wl1271_is_active_sta(struct wl1271 *wl, u8 hlid) +{ + int id = hlid - WL1271_AP_STA_HLID_START; + return test_bit(id, wl->ap_hlid_map); +} + static int wl1271_op_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta) @@ -3535,7 +3542,6 @@ int wl1271_init_ieee80211(struct wl1271 *wl) IEEE80211_HW_HAS_RATE_CONTROL | IEEE80211_HW_CONNECTION_MONITOR | IEEE80211_HW_SUPPORTS_CQM_RSSI | - IEEE80211_HW_REPORTS_TX_ACK_STATUS | IEEE80211_HW_AP_LINK_PS; wl->hw->wiphy->cipher_suites = cipher_suites; diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index fb2b79fa42b4..7c521af58e7d 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -172,7 +172,6 @@ extern u32 wl12xx_debug_level; #define WL1271_PS_STA_MAX_BLOCKS (2 * 9) #define WL1271_AP_BSS_INDEX 0 -#define WL1271_AP_DEF_INACTIV_SEC 300 #define WL1271_AP_DEF_BEACON_EXP 20 #define ACX_TX_DESCRIPTORS 32 -- cgit v1.2.3 From ff86843dfbb368766d0aecd0147821d9a2b60edb Mon Sep 17 00:00:00 2001 From: Shahar Levi Date: Mon, 11 Apr 2011 15:41:46 +0300 Subject: wl12xx: FM WLAN coexistence Add support to FM WLAN coexistence (STA only). Some WiFi harmonics may interfere with FM operation, to avoid this problem special coexistence techniques are activated around some FM frequencies. Signed-off-by: Shahar Levi Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 40 +++++++++++++++++++++++++ drivers/net/wireless/wl12xx/acx.h | 61 ++++++++++++++++++++++++++++++++++++++ drivers/net/wireless/wl12xx/conf.h | 14 +++++++++ drivers/net/wireless/wl12xx/init.c | 5 ++++ drivers/net/wireless/wl12xx/main.c | 17 +++++++++++ 5 files changed, 137 insertions(+) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index a5c9c0aff83f..8a39d1e40a68 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -1626,3 +1626,43 @@ out: kfree(acx); return ret; } + +int wl1271_acx_fm_coex(struct wl1271 *wl) +{ + struct wl1271_acx_fm_coex *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx fm coex setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->enable = wl->conf.fm_coex.enable; + acx->swallow_period = wl->conf.fm_coex.swallow_period; + acx->n_divider_fref_set_1 = wl->conf.fm_coex.n_divider_fref_set_1; + acx->n_divider_fref_set_2 = wl->conf.fm_coex.n_divider_fref_set_2; + acx->m_divider_fref_set_1 = + cpu_to_le16(wl->conf.fm_coex.m_divider_fref_set_1); + acx->m_divider_fref_set_2 = + cpu_to_le16(wl->conf.fm_coex.m_divider_fref_set_2); + acx->coex_pll_stabilization_time = + cpu_to_le32(wl->conf.fm_coex.coex_pll_stabilization_time); + acx->ldo_stabilization_time = + cpu_to_le16(wl->conf.fm_coex.ldo_stabilization_time); + acx->fm_disturbed_band_margin = + wl->conf.fm_coex.fm_disturbed_band_margin; + acx->swallow_clk_diff = wl->conf.fm_coex.swallow_clk_diff; + + ret = wl1271_cmd_configure(wl, ACX_FM_COEX_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx fm coex setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index 942908cd53a3..2dde0346e955 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h @@ -1179,6 +1179,65 @@ struct wl1271_acx_inconnection_sta { u8 padding1[2]; } __packed; +/* + * ACX_FM_COEX_CFG + * set the FM co-existence parameters. + */ +struct wl1271_acx_fm_coex { + struct acx_header header; + /* enable(1) / disable(0) the FM Coex feature */ + u8 enable; + /* + * Swallow period used in COEX PLL swallowing mechanism. + * 0xFF = use FW default + */ + u8 swallow_period; + /* + * The N divider used in COEX PLL swallowing mechanism for Fref of + * 38.4/19.2 Mhz. 0xFF = use FW default + */ + u8 n_divider_fref_set_1; + /* + * The N divider used in COEX PLL swallowing mechanism for Fref of + * 26/52 Mhz. 0xFF = use FW default + */ + u8 n_divider_fref_set_2; + /* + * The M divider used in COEX PLL swallowing mechanism for Fref of + * 38.4/19.2 Mhz. 0xFFFF = use FW default + */ + __le16 m_divider_fref_set_1; + /* + * The M divider used in COEX PLL swallowing mechanism for Fref of + * 26/52 Mhz. 0xFFFF = use FW default + */ + __le16 m_divider_fref_set_2; + /* + * The time duration in uSec required for COEX PLL to stabilize. + * 0xFFFFFFFF = use FW default + */ + __le32 coex_pll_stabilization_time; + /* + * The time duration in uSec required for LDO to stabilize. + * 0xFFFFFFFF = use FW default + */ + __le16 ldo_stabilization_time; + /* + * The disturbed frequency band margin around the disturbed frequency + * center (single sided). + * For example, if 2 is configured, the following channels will be + * considered disturbed channel: + * 80 +- 0.1 MHz, 91 +- 0.1 MHz, 98 +- 0.1 MHz, 102 +- 0.1 MH + * 0xFF = use FW default + */ + u8 fm_disturbed_band_margin; + /* + * The swallow clock difference of the swallowing mechanism. + * 0xFF = use FW default + */ + u8 swallow_clk_diff; +} __packed; + enum { ACX_WAKE_UP_CONDITIONS = 0x0002, ACX_MEM_CFG = 0x0003, @@ -1208,6 +1267,7 @@ enum { ACX_BCN_DTIM_OPTIONS = 0x0031, ACX_SG_ENABLE = 0x0032, ACX_SG_CFG = 0x0033, + ACX_FM_COEX_CFG = 0x0034, ACX_BEACON_FILTER_TABLE = 0x0038, ACX_ARP_IP_FILTER = 0x0039, ACX_ROAMING_STATISTICS_TBL = 0x003B, @@ -1318,5 +1378,6 @@ int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl); int wl1271_acx_sta_max_tx_retry(struct wl1271 *wl); int wl1271_acx_config_ps(struct wl1271 *wl); int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); +int wl1271_acx_fm_coex(struct wl1271 *wl); #endif /* __WL1271_ACX_H__ */ diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index 5551d9d4016b..a4e0acff8674 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -1199,6 +1199,19 @@ struct conf_memory_settings { u8 tx_min; }; +struct conf_fm_coex { + u8 enable; + u8 swallow_period; + u8 n_divider_fref_set_1; + u8 n_divider_fref_set_2; + u16 m_divider_fref_set_1; + u16 m_divider_fref_set_2; + u32 coex_pll_stabilization_time; + u16 ldo_stabilization_time; + u8 fm_disturbed_band_margin; + u8 swallow_clk_diff; +}; + struct conf_drv_settings { struct conf_sg_settings sg; struct conf_rx_settings rx; @@ -1212,6 +1225,7 @@ struct conf_drv_settings { struct conf_ht_setting ht; struct conf_memory_settings mem_wl127x; struct conf_memory_settings mem_wl128x; + struct conf_fm_coex fm_coex; u8 hci_io_ds; }; diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index ab3b1e21de29..46fd7212b238 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -356,6 +356,11 @@ static int wl1271_sta_hw_init(struct wl1271 *wl) if (ret < 0) return ret; + /* FM WLAN coexistence */ + ret = wl1271_acx_fm_coex(wl); + if (ret < 0) + return ret; + /* Beacons and broadcast settings */ ret = wl1271_init_beacon_broadcast(wl); if (ret < 0) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 53a8e2357ccf..8a7a8c5fcf5b 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -320,6 +320,18 @@ static struct conf_drv_settings default_conf = { .min_req_rx_blocks = 22, .tx_min = 27, }, + .fm_coex = { + .enable = true, + .swallow_period = 5, + .n_divider_fref_set_1 = 0xff, /* default */ + .n_divider_fref_set_2 = 12, + .m_divider_fref_set_1 = 148, + .m_divider_fref_set_2 = 0xffff, /* default */ + .coex_pll_stabilization_time = 0xffffffff, /* default */ + .ldo_stabilization_time = 0xffff, /* default */ + .fm_disturbed_band_margin = 0xff, /* default */ + .swallow_clk_diff = 0xff, /* default */ + }, .hci_io_ds = HCI_IO_DS_6MA, }; @@ -508,6 +520,11 @@ static int wl1271_plt_init(struct wl1271 *wl) if (ret < 0) goto out_free_memmap; + /* FM WLAN coexistence */ + ret = wl1271_acx_fm_coex(wl); + if (ret < 0) + goto out_free_memmap; + /* Energy detection */ ret = wl1271_init_energy_detection(wl); if (ret < 0) -- cgit v1.2.3 From ae825e4ba81203e1b3d3159f24327cdc2629dbd8 Mon Sep 17 00:00:00 2001 From: Ido Yariv Date: Mon, 18 Apr 2011 16:40:14 +0300 Subject: wl12xx: Modify memory configuration for 128x/AP The 128x/AP firmware does not yet support dynamic memory. Temporarily, the memory configuration for the 127x was used both for 127x/AP as well as 128x/AP. Since the two chips don't have the same number of memory blocks, TP was significantly degraded. This hasn't been fine tuned yet, but using the base 128x numbers (without dynamic memory) seems to yield much better results (around 30% more). Additional fine tuning will be required in the future. Signed-off-by: Ido Yariv Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index 8a39d1e40a68..b1b5139139e7 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -954,6 +954,7 @@ out: int wl1271_acx_ap_mem_cfg(struct wl1271 *wl) { struct wl1271_acx_ap_config_memory *mem_conf; + struct conf_memory_settings *mem; int ret; wl1271_debug(DEBUG_ACX, "wl1271 mem cfg"); @@ -964,14 +965,21 @@ int wl1271_acx_ap_mem_cfg(struct wl1271 *wl) goto out; } + if (wl->chip.id == CHIP_ID_1283_PG20) + /* + * FIXME: The 128x AP FW does not yet support dynamic memory. + * Use the base memory configuration for 128x for now. This + * should be fine tuned in the future. + */ + mem = &wl->conf.mem_wl128x; + else + mem = &wl->conf.mem_wl127x; + /* memory config */ - /* FIXME: for now we always use mem_wl127x for AP, because it - * doesn't support dynamic memory and we don't have the - * optimal values for wl128x without dynamic memory yet */ - mem_conf->num_stations = wl->conf.mem_wl127x.num_stations; - mem_conf->rx_mem_block_num = wl->conf.mem_wl127x.rx_block_num; - mem_conf->tx_min_mem_block_num = wl->conf.mem_wl127x.tx_min_block_num; - mem_conf->num_ssid_profiles = wl->conf.mem_wl127x.ssid_profiles; + mem_conf->num_stations = mem->num_stations; + mem_conf->rx_mem_block_num = mem->rx_block_num; + mem_conf->tx_min_mem_block_num = mem->tx_min_block_num; + mem_conf->num_ssid_profiles = mem->ssid_profiles; mem_conf->total_tx_descriptors = cpu_to_le32(ACX_TX_DESCRIPTORS); ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, -- cgit v1.2.3 From 801f870bc0524bad7ebef9cea52d20e4d4992e4a Mon Sep 17 00:00:00 2001 From: Arik Nemtsov Date: Mon, 18 Apr 2011 14:15:20 +0300 Subject: wl12xx: add BT-coexistance for AP Initialize AP specific BT coexitance parameters to default values and enable them in AP mode. Signed-off-by: Arik Nemtsov Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 40 ++++++++++++++++++++++---- drivers/net/wireless/wl12xx/acx.h | 16 +++++++++-- drivers/net/wireless/wl12xx/conf.h | 35 +++++++++++++++++++++-- drivers/net/wireless/wl12xx/init.c | 15 ++++++---- drivers/net/wireless/wl12xx/main.c | 57 +++++++++++++++++++++++++++++++++++++- 5 files changed, 146 insertions(+), 17 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index b1b5139139e7..2b5fb3d2df1b 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -540,13 +540,13 @@ out: return ret; } -int wl1271_acx_sg_cfg(struct wl1271 *wl) +int wl1271_acx_sta_sg_cfg(struct wl1271 *wl) { - struct acx_bt_wlan_coex_param *param; + struct acx_sta_bt_wlan_coex_param *param; struct conf_sg_settings *c = &wl->conf.sg; int i, ret; - wl1271_debug(DEBUG_ACX, "acx sg cfg"); + wl1271_debug(DEBUG_ACX, "acx sg sta cfg"); param = kzalloc(sizeof(*param), GFP_KERNEL); if (!param) { @@ -555,8 +555,38 @@ int wl1271_acx_sg_cfg(struct wl1271 *wl) } /* BT-WLAN coext parameters */ - for (i = 0; i < CONF_SG_PARAMS_MAX; i++) - param->params[i] = cpu_to_le32(c->params[i]); + for (i = 0; i < CONF_SG_STA_PARAMS_MAX; i++) + param->params[i] = cpu_to_le32(c->sta_params[i]); + param->param_idx = CONF_SG_PARAMS_ALL; + + ret = wl1271_cmd_configure(wl, ACX_SG_CFG, param, sizeof(*param)); + if (ret < 0) { + wl1271_warning("failed to set sg config: %d", ret); + goto out; + } + +out: + kfree(param); + return ret; +} + +int wl1271_acx_ap_sg_cfg(struct wl1271 *wl) +{ + struct acx_ap_bt_wlan_coex_param *param; + struct conf_sg_settings *c = &wl->conf.sg; + int i, ret; + + wl1271_debug(DEBUG_ACX, "acx sg ap cfg"); + + param = kzalloc(sizeof(*param), GFP_KERNEL); + if (!param) { + ret = -ENOMEM; + goto out; + } + + /* BT-WLAN coext parameters */ + for (i = 0; i < CONF_SG_AP_PARAMS_MAX; i++) + param->params[i] = cpu_to_le32(c->ap_params[i]); param->param_idx = CONF_SG_PARAMS_ALL; ret = wl1271_cmd_configure(wl, ACX_SG_CFG, param, sizeof(*param)); diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index 2dde0346e955..cd7548dacd59 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h @@ -370,14 +370,23 @@ struct acx_bt_wlan_coex { u8 pad[3]; } __packed; -struct acx_bt_wlan_coex_param { +struct acx_sta_bt_wlan_coex_param { struct acx_header header; - __le32 params[CONF_SG_PARAMS_MAX]; + __le32 params[CONF_SG_STA_PARAMS_MAX]; u8 param_idx; u8 padding[3]; } __packed; +struct acx_ap_bt_wlan_coex_param { + struct acx_header header; + + __le32 params[CONF_SG_AP_PARAMS_MAX]; + u8 param_idx; + u8 padding[3]; +} __packed; + + struct acx_dco_itrim_params { struct acx_header header; @@ -1330,7 +1339,8 @@ int wl1271_acx_beacon_filter_opt(struct wl1271 *wl, bool enable_filter); int wl1271_acx_beacon_filter_table(struct wl1271 *wl); int wl1271_acx_conn_monit_params(struct wl1271 *wl, bool enable); int wl1271_acx_sg_enable(struct wl1271 *wl, bool enable); -int wl1271_acx_sg_cfg(struct wl1271 *wl); +int wl1271_acx_sta_sg_cfg(struct wl1271 *wl); +int wl1271_acx_ap_sg_cfg(struct wl1271 *wl); int wl1271_acx_cca_threshold(struct wl1271 *wl); int wl1271_acx_bcn_dtim_options(struct wl1271 *wl); int wl1271_acx_aid(struct wl1271 *wl, u16 aid); diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index a4e0acff8674..2ffbe3e06019 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -396,12 +396,43 @@ enum { CONF_SG_TEMP_PARAM_3, CONF_SG_TEMP_PARAM_4, CONF_SG_TEMP_PARAM_5, - CONF_SG_PARAMS_MAX, + + /* + * AP beacon miss + * + * Range: 0 - 255 + */ + CONF_SG_AP_BEACON_MISS_TX, + + /* + * AP RX window length + * + * Range: 0 - 50 + */ + CONF_SG_RX_WINDOW_LENGTH, + + /* + * AP connection protection time + * + * Range: 0 - 5000 + */ + CONF_SG_AP_CONNECTION_PROTECTION_TIME, + + CONF_SG_TEMP_PARAM_6, + CONF_SG_TEMP_PARAM_7, + CONF_SG_TEMP_PARAM_8, + CONF_SG_TEMP_PARAM_9, + CONF_SG_TEMP_PARAM_10, + + CONF_SG_STA_PARAMS_MAX = CONF_SG_TEMP_PARAM_5 + 1, + CONF_SG_AP_PARAMS_MAX = CONF_SG_TEMP_PARAM_10 + 1, + CONF_SG_PARAMS_ALL = 0xff }; struct conf_sg_settings { - u32 params[CONF_SG_PARAMS_MAX]; + u32 sta_params[CONF_SG_STA_PARAMS_MAX]; + u32 ap_params[CONF_SG_AP_PARAMS_MAX]; u8 state; }; diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index 46fd7212b238..1be656914785 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -285,7 +285,10 @@ int wl1271_init_pta(struct wl1271 *wl) { int ret; - ret = wl1271_acx_sg_cfg(wl); + if (wl->bss_type == BSS_TYPE_AP_BSS) + ret = wl1271_acx_ap_sg_cfg(wl); + else + ret = wl1271_acx_sta_sg_cfg(wl); if (ret < 0) return ret; @@ -351,11 +354,6 @@ static int wl1271_sta_hw_init(struct wl1271 *wl) if (ret < 0) return ret; - /* Bluetooth WLAN coexistence */ - ret = wl1271_init_pta(wl); - if (ret < 0) - return ret; - /* FM WLAN coexistence */ ret = wl1271_acx_fm_coex(wl); if (ret < 0) @@ -572,6 +570,11 @@ int wl1271_hw_init(struct wl1271 *wl) if (ret < 0) return ret; + /* Bluetooth WLAN coexistence */ + ret = wl1271_init_pta(wl); + if (ret < 0) + return ret; + /* Default memory configuration */ ret = wl1271_acx_init_mem_config(wl); if (ret < 0) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index a8770102a74a..5f8bb35c6475 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -51,7 +51,7 @@ static struct conf_drv_settings default_conf = { .sg = { - .params = { + .sta_params = { [CONF_SG_BT_PER_THRESHOLD] = 7500, [CONF_SG_HV3_MAX_OVERRIDE] = 0, [CONF_SG_BT_NFS_SAMPLE_INTERVAL] = 400, @@ -101,6 +101,61 @@ static struct conf_drv_settings default_conf = { [CONF_SG_DHCP_TIME] = 5000, [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP] = 100, }, + .ap_params = { + [CONF_SG_BT_PER_THRESHOLD] = 7500, + [CONF_SG_HV3_MAX_OVERRIDE] = 0, + [CONF_SG_BT_NFS_SAMPLE_INTERVAL] = 400, + [CONF_SG_BT_LOAD_RATIO] = 50, + [CONF_SG_AUTO_PS_MODE] = 1, + [CONF_SG_AUTO_SCAN_PROBE_REQ] = 170, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3] = 50, + [CONF_SG_ANTENNA_CONFIGURATION] = 0, + [CONF_SG_BEACON_MISS_PERCENT] = 60, + [CONF_SG_RATE_ADAPT_THRESH] = 64, + [CONF_SG_RATE_ADAPT_SNR] = 1, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_BR] = 10, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_BR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_BR] = 25, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_BR] = 20, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_BR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_BR] = 25, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_EDR] = 7, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_EDR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_EDR] = 25, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_EDR] = 8, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_EDR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_EDR] = 25, + [CONF_SG_RXT] = 1200, + [CONF_SG_TXT] = 1000, + [CONF_SG_ADAPTIVE_RXT_TXT] = 1, + [CONF_SG_PS_POLL_TIMEOUT] = 10, + [CONF_SG_UPSD_TIMEOUT] = 10, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MIN_EDR] = 7, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MAX_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_MASTER_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MIN_EDR] = 8, + [CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MAX_EDR] = 20, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_SLAVE_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MIN_BR] = 20, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MAX_BR] = 50, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_BR] = 10, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3] = 200, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP] = 800, + [CONF_SG_PASSIVE_SCAN_A2DP_BT_TIME] = 75, + [CONF_SG_PASSIVE_SCAN_A2DP_WLAN_TIME] = 15, + [CONF_SG_HV3_MAX_SERVED] = 6, + [CONF_SG_DHCP_TIME] = 5000, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP] = 100, + [CONF_SG_TEMP_PARAM_1] = 0, + [CONF_SG_TEMP_PARAM_2] = 0, + [CONF_SG_TEMP_PARAM_3] = 0, + [CONF_SG_TEMP_PARAM_4] = 0, + [CONF_SG_TEMP_PARAM_5] = 0, + [CONF_SG_AP_BEACON_MISS_TX] = 3, + [CONF_SG_RX_WINDOW_LENGTH] = 6, + [CONF_SG_AP_CONNECTION_PROTECTION_TIME] = 50, + [CONF_SG_TEMP_PARAM_6] = 1, + }, .state = CONF_SG_PROTECTIVE, }, .rx = { -- cgit v1.2.3 From 5f704d180e448d05859e1cb6572822ba27dbcdc7 Mon Sep 17 00:00:00 2001 From: Arik Nemtsov Date: Mon, 18 Apr 2011 14:15:21 +0300 Subject: wl12xx: use wiphy values for setting rts, frag thresholds on init Use the wiphy RTS and fragmentation thresholds for initializing the FW when possible. This mitigates a bug where previously set values are forgotten after interface down/up. Add checks before settings these values to ensure they are valid. Use default values when invalid thresholds are configured. Update the default RTS threshold to the maximum value given by the specification. Signed-off-by: Arik Nemtsov Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 26 ++++++++++++++++++++------ drivers/net/wireless/wl12xx/acx.h | 4 ++-- drivers/net/wireless/wl12xx/init.c | 4 ++-- drivers/net/wireless/wl12xx/main.c | 6 +++--- 4 files changed, 27 insertions(+), 13 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index 2b5fb3d2df1b..729f72a7b912 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -325,12 +325,19 @@ out: return ret; } -int wl1271_acx_rts_threshold(struct wl1271 *wl, u16 rts_threshold) +int wl1271_acx_rts_threshold(struct wl1271 *wl, u32 rts_threshold) { struct acx_rts_threshold *rts; int ret; - wl1271_debug(DEBUG_ACX, "acx rts threshold"); + /* + * If the RTS threshold is not configured or out of range, use the + * default value. + */ + if (rts_threshold > IEEE80211_MAX_RTS_THRESHOLD) + rts_threshold = wl->conf.rx.rts_threshold; + + wl1271_debug(DEBUG_ACX, "acx rts threshold: %d", rts_threshold); rts = kzalloc(sizeof(*rts), GFP_KERNEL); if (!rts) { @@ -338,7 +345,7 @@ int wl1271_acx_rts_threshold(struct wl1271 *wl, u16 rts_threshold) goto out; } - rts->threshold = cpu_to_le16(rts_threshold); + rts->threshold = cpu_to_le16((u16)rts_threshold); ret = wl1271_cmd_configure(wl, DOT11_RTS_THRESHOLD, rts, sizeof(*rts)); if (ret < 0) { @@ -928,12 +935,19 @@ out: return ret; } -int wl1271_acx_frag_threshold(struct wl1271 *wl, u16 frag_threshold) +int wl1271_acx_frag_threshold(struct wl1271 *wl, u32 frag_threshold) { struct acx_frag_threshold *acx; int ret = 0; - wl1271_debug(DEBUG_ACX, "acx frag threshold"); + /* + * If the fragmentation is not configured or out of range, use the + * default value. + */ + if (frag_threshold > IEEE80211_MAX_FRAG_THRESHOLD) + frag_threshold = wl->conf.tx.frag_threshold; + + wl1271_debug(DEBUG_ACX, "acx frag threshold: %d", frag_threshold); acx = kzalloc(sizeof(*acx), GFP_KERNEL); @@ -942,7 +956,7 @@ int wl1271_acx_frag_threshold(struct wl1271 *wl, u16 frag_threshold) goto out; } - acx->frag_threshold = cpu_to_le16(frag_threshold); + acx->frag_threshold = cpu_to_le16((u16)frag_threshold); ret = wl1271_cmd_configure(wl, ACX_FRAG_CFG, acx, sizeof(*acx)); if (ret < 0) { wl1271_warning("Setting of frag threshold failed: %d", ret); diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index cd7548dacd59..828367d62668 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h @@ -1333,7 +1333,7 @@ int wl1271_acx_slot(struct wl1271 *wl, enum acx_slot_type slot_time); int wl1271_acx_group_address_tbl(struct wl1271 *wl, bool enable, void *mc_list, u32 mc_list_len); int wl1271_acx_service_period_timeout(struct wl1271 *wl); -int wl1271_acx_rts_threshold(struct wl1271 *wl, u16 rts_threshold); +int wl1271_acx_rts_threshold(struct wl1271 *wl, u32 rts_threshold); int wl1271_acx_dco_itrim_params(struct wl1271 *wl); int wl1271_acx_beacon_filter_opt(struct wl1271 *wl, bool enable_filter); int wl1271_acx_beacon_filter_table(struct wl1271 *wl); @@ -1357,7 +1357,7 @@ int wl1271_acx_ac_cfg(struct wl1271 *wl, u8 ac, u8 cw_min, u16 cw_max, int wl1271_acx_tid_cfg(struct wl1271 *wl, u8 queue_id, u8 channel_type, u8 tsid, u8 ps_scheme, u8 ack_policy, u32 apsd_conf0, u32 apsd_conf1); -int wl1271_acx_frag_threshold(struct wl1271 *wl, u16 frag_threshold); +int wl1271_acx_frag_threshold(struct wl1271 *wl, u32 frag_threshold); int wl1271_acx_tx_config_options(struct wl1271 *wl); int wl1271_acx_ap_mem_cfg(struct wl1271 *wl); int wl1271_acx_sta_mem_cfg(struct wl1271 *wl); diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index 1be656914785..060ca31818eb 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -258,7 +258,7 @@ int wl1271_init_phy_config(struct wl1271 *wl) if (ret < 0) return ret; - ret = wl1271_acx_rts_threshold(wl, wl->conf.rx.rts_threshold); + ret = wl1271_acx_rts_threshold(wl, wl->hw->wiphy->rts_threshold); if (ret < 0) return ret; @@ -614,7 +614,7 @@ int wl1271_hw_init(struct wl1271 *wl) goto out_free_memmap; /* Default fragmentation threshold */ - ret = wl1271_acx_frag_threshold(wl, wl->conf.tx.frag_threshold); + ret = wl1271_acx_frag_threshold(wl, wl->hw->wiphy->frag_threshold); if (ret < 0) goto out_free_memmap; diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 5f8bb35c6475..81a0c8ed5a4a 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -163,7 +163,7 @@ static struct conf_drv_settings default_conf = { .packet_detection_threshold = 0, .ps_poll_timeout = 15, .upsd_timeout = 15, - .rts_threshold = 2347, + .rts_threshold = IEEE80211_MAX_RTS_THRESHOLD, .rx_cca_threshold = 0, .irq_blk_threshold = 0xFFFF, .irq_pkt_threshold = 0, @@ -2360,7 +2360,7 @@ static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) if (ret < 0) goto out; - ret = wl1271_acx_frag_threshold(wl, (u16)value); + ret = wl1271_acx_frag_threshold(wl, value); if (ret < 0) wl1271_warning("wl1271_op_set_frag_threshold failed: %d", ret); @@ -2388,7 +2388,7 @@ static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) if (ret < 0) goto out; - ret = wl1271_acx_rts_threshold(wl, (u16) value); + ret = wl1271_acx_rts_threshold(wl, value); if (ret < 0) wl1271_warning("wl1271_op_set_rts_threshold failed: %d", ret); -- cgit v1.2.3 From 521a4a23261354885c01bf75b42150629004ed83 Mon Sep 17 00:00:00 2001 From: Arik Nemtsov Date: Mon, 18 Apr 2011 14:15:22 +0300 Subject: wl12xx: AP-mode - disable beacon filtering on start up New AP-mode FWs filter external beacons by default. Disable this filtering on start up so we can properly configure ERP protection. Signed-off-by: Arik Nemtsov Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 25 +++++++++++++++++++++++++ drivers/net/wireless/wl12xx/acx.h | 10 +++++++++- drivers/net/wireless/wl12xx/init.c | 8 ++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index 729f72a7b912..6860d7e9df70 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -1679,6 +1679,31 @@ out: return ret; } +int wl1271_acx_set_ap_beacon_filter(struct wl1271 *wl, bool enable) +{ + struct acx_ap_beacon_filter *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx set ap beacon filter: %d", enable); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->enable = enable ? 1 : 0; + + ret = wl1271_cmd_configure(wl, ACX_AP_BEACON_FILTER_OPT, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx set ap beacon filter failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + int wl1271_acx_fm_coex(struct wl1271 *wl) { struct wl1271_acx_fm_coex *acx; diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index 828367d62668..75338f9947cf 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h @@ -303,7 +303,6 @@ struct acx_beacon_filter_option { struct acx_header header; u8 enable; - /* * The number of beacons without the unicast TIM * bit set that the firmware buffers before @@ -1188,6 +1187,13 @@ struct wl1271_acx_inconnection_sta { u8 padding1[2]; } __packed; +struct acx_ap_beacon_filter { + struct acx_header header; + + u8 enable; + u8 pad[3]; +} __packed; + /* * ACX_FM_COEX_CFG * set the FM co-existence parameters. @@ -1265,6 +1271,7 @@ enum { ACX_TID_CFG = 0x001A, ACX_PS_RX_STREAMING = 0x001B, ACX_BEACON_FILTER_OPT = 0x001F, + ACX_AP_BEACON_FILTER_OPT = 0x0020, ACX_NOISE_HIST = 0x0021, ACX_HDK_VERSION = 0x0022, /* ??? */ ACX_PD_THRESHOLD = 0x0023, @@ -1388,6 +1395,7 @@ int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl); int wl1271_acx_sta_max_tx_retry(struct wl1271 *wl); int wl1271_acx_config_ps(struct wl1271 *wl); int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); +int wl1271_acx_set_ap_beacon_filter(struct wl1271 *wl, bool enable); int wl1271_acx_fm_coex(struct wl1271 *wl); #endif /* __WL1271_ACX_H__ */ diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index 060ca31818eb..e0de041e38f1 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -475,6 +475,14 @@ static int wl1271_ap_hw_init_post_mem(struct wl1271 *wl) if (ret < 0) return ret; + /* + * when operating as AP we want to receive external beacons for + * configuring ERP protection. + */ + ret = wl1271_acx_set_ap_beacon_filter(wl, false); + if (ret < 0) + return ret; + return 0; } -- cgit v1.2.3 From 70f474241b3d5fb633635a2ce39ea9da4afeea6c Mon Sep 17 00:00:00 2001 From: Arik Nemtsov Date: Mon, 18 Apr 2011 14:15:25 +0300 Subject: wl12xx: AP-mode - overhaul rate policy configuration Use the minimal rate configured in the basic rates set as the AP broadcast and multicast rate. The minimal rate is used to ensure weak links can still communicate. When the basic rates contains at least one OFDM rate, configure all unicast TX rates to OFDM only. Unify rate configuration on initialization and on change notification into a single function. Signed-off-by: Arik Nemtsov Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/acx.c | 3 +- drivers/net/wireless/wl12xx/conf.h | 22 ++++-------- drivers/net/wireless/wl12xx/init.c | 71 ++++++++++++++++++++++++++++---------- drivers/net/wireless/wl12xx/init.h | 1 + drivers/net/wireless/wl12xx/main.c | 53 ++-------------------------- 5 files changed, 65 insertions(+), 85 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index 6860d7e9df70..ec0156b3e276 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -841,7 +841,8 @@ int wl1271_acx_ap_rate_policy(struct wl1271 *wl, struct conf_tx_rate_class *c, struct acx_ap_rate_policy *acx; int ret = 0; - wl1271_debug(DEBUG_ACX, "acx ap rate policy"); + wl1271_debug(DEBUG_ACX, "acx ap rate policy %d rates 0x%x", + idx, c->enabled_rates); acx = kzalloc(sizeof(*acx), GFP_KERNEL); if (!acx) { diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index 2ffbe3e06019..c0045f0b8b4e 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -540,6 +540,12 @@ struct conf_rx_settings { CONF_HW_BIT_RATE_36MBPS | CONF_HW_BIT_RATE_48MBPS | \ CONF_HW_BIT_RATE_54MBPS) +#define CONF_TX_OFDM_RATES (CONF_HW_BIT_RATE_6MBPS | \ + CONF_HW_BIT_RATE_12MBPS | CONF_HW_BIT_RATE_24MBPS | \ + CONF_HW_BIT_RATE_36MBPS | CONF_HW_BIT_RATE_48MBPS | \ + CONF_HW_BIT_RATE_54MBPS) + + /* * Default rates for management traffic when operating in AP mode. This * should be configured according to the basic rate set of the AP @@ -704,22 +710,6 @@ struct conf_tx_settings { u8 ac_conf_count; struct conf_tx_ac_category ac_conf[CONF_TX_MAX_AC_COUNT]; - /* - * Configuration for rate classes in AP-mode. These rate classes - * are for the AC TX queues - */ - struct conf_tx_rate_class ap_rc_conf[CONF_TX_MAX_AC_COUNT]; - - /* - * Management TX rate class for AP-mode. - */ - struct conf_tx_rate_class ap_mgmt_conf; - - /* - * Broadcast TX rate class for AP-mode. - */ - struct conf_tx_rate_class ap_bcst_conf; - /* * Allow this number of TX retries to a connected station/AP before an * event is triggered from FW. diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index e0de041e38f1..5d0ecd2018bf 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -417,7 +417,7 @@ static int wl1271_sta_hw_init_post_mem(struct wl1271 *wl) static int wl1271_ap_hw_init(struct wl1271 *wl) { - int ret, i; + int ret; ret = wl1271_ap_init_templates_config(wl); if (ret < 0) @@ -428,23 +428,7 @@ static int wl1271_ap_hw_init(struct wl1271 *wl) if (ret < 0) return ret; - /* Configure initial TX rate classes */ - for (i = 0; i < wl->conf.tx.ac_conf_count; i++) { - ret = wl1271_acx_ap_rate_policy(wl, - &wl->conf.tx.ap_rc_conf[i], i); - if (ret < 0) - return ret; - } - - ret = wl1271_acx_ap_rate_policy(wl, - &wl->conf.tx.ap_mgmt_conf, - ACX_TX_AP_MODE_MGMT_RATE); - if (ret < 0) - return ret; - - ret = wl1271_acx_ap_rate_policy(wl, - &wl->conf.tx.ap_bcst_conf, - ACX_TX_AP_MODE_BCST_RATE); + ret = wl1271_init_ap_rates(wl); if (ret < 0) return ret; @@ -486,6 +470,57 @@ static int wl1271_ap_hw_init_post_mem(struct wl1271 *wl) return 0; } +int wl1271_init_ap_rates(struct wl1271 *wl) +{ + int i, ret; + struct conf_tx_rate_class rc; + u32 supported_rates; + + wl1271_debug(DEBUG_AP, "AP basic rate set: 0x%x", wl->basic_rate_set); + + if (wl->basic_rate_set == 0) + return -EINVAL; + + rc.enabled_rates = wl->basic_rate_set; + rc.long_retry_limit = 10; + rc.short_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, ACX_TX_AP_MODE_MGMT_RATE); + if (ret < 0) + return ret; + + /* use the min basic rate for AP broadcast/multicast */ + rc.enabled_rates = wl1271_tx_min_rate_get(wl); + rc.short_retry_limit = 10; + rc.long_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, ACX_TX_AP_MODE_BCST_RATE); + if (ret < 0) + return ret; + + /* + * If the basic rates contain OFDM rates, use OFDM only + * rates for unicast TX as well. Else use all supported rates. + */ + if ((wl->basic_rate_set & CONF_TX_OFDM_RATES)) + supported_rates = CONF_TX_OFDM_RATES; + else + supported_rates = CONF_TX_AP_ENABLED_RATES; + + /* configure unicast TX rate classes */ + for (i = 0; i < wl->conf.tx.ac_conf_count; i++) { + rc.enabled_rates = supported_rates; + rc.short_retry_limit = 10; + rc.long_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, i); + if (ret < 0) + return ret; + } + + return 0; +} + static void wl1271_check_ba_support(struct wl1271 *wl) { /* validate FW cose ver x.x.x.50-60.x */ diff --git a/drivers/net/wireless/wl12xx/init.h b/drivers/net/wireless/wl12xx/init.h index 4975270a91ab..0dd2414a30d1 100644 --- a/drivers/net/wireless/wl12xx/init.h +++ b/drivers/net/wireless/wl12xx/init.h @@ -33,5 +33,6 @@ int wl1271_init_pta(struct wl1271 *wl); int wl1271_init_energy_detection(struct wl1271 *wl); int wl1271_chip_specific_init(struct wl1271 *wl); int wl1271_hw_init(struct wl1271 *wl); +int wl1271_init_ap_rates(struct wl1271 *wl); #endif diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 7b88dd2e85ee..e9d4cf48ba35 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -209,44 +209,6 @@ static struct conf_drv_settings default_conf = { .tx_op_limit = 1504, }, }, - .ap_rc_conf = { - [0] = { - .enabled_rates = CONF_TX_AP_ENABLED_RATES, - .short_retry_limit = 10, - .long_retry_limit = 10, - .aflags = 0, - }, - [1] = { - .enabled_rates = CONF_TX_AP_ENABLED_RATES, - .short_retry_limit = 10, - .long_retry_limit = 10, - .aflags = 0, - }, - [2] = { - .enabled_rates = CONF_TX_AP_ENABLED_RATES, - .short_retry_limit = 10, - .long_retry_limit = 10, - .aflags = 0, - }, - [3] = { - .enabled_rates = CONF_TX_AP_ENABLED_RATES, - .short_retry_limit = 10, - .long_retry_limit = 10, - .aflags = 0, - }, - }, - .ap_mgmt_conf = { - .enabled_rates = CONF_TX_AP_DEFAULT_MGMT_RATES, - .short_retry_limit = 10, - .long_retry_limit = 10, - .aflags = 0, - }, - .ap_bcst_conf = { - .enabled_rates = CONF_HW_BIT_RATE_1MBPS, - .short_retry_limit = 10, - .long_retry_limit = 10, - .aflags = 0, - }, .max_tx_retries = 100, .ap_aging_period = 300, .tid_conf_count = 4, @@ -2532,22 +2494,13 @@ static void wl1271_bss_info_changed_ap(struct wl1271 *wl, if ((changed & BSS_CHANGED_BASIC_RATES)) { u32 rates = bss_conf->basic_rates; - struct conf_tx_rate_class mgmt_rc; wl->basic_rate_set = wl1271_tx_enabled_rates_get(wl, rates); wl->basic_rate = wl1271_tx_min_rate_get(wl); - wl1271_debug(DEBUG_AP, "basic rates: 0x%x", - wl->basic_rate_set); - - /* update the AP management rate policy with the new rates */ - mgmt_rc.enabled_rates = wl->basic_rate_set; - mgmt_rc.long_retry_limit = 10; - mgmt_rc.short_retry_limit = 10; - mgmt_rc.aflags = 0; - ret = wl1271_acx_ap_rate_policy(wl, &mgmt_rc, - ACX_TX_AP_MODE_MGMT_RATE); + + ret = wl1271_init_ap_rates(wl); if (ret < 0) { - wl1271_error("AP mgmt policy change failed %d", ret); + wl1271_error("AP rate policy change failed %d", ret); goto out; } } -- cgit v1.2.3 From 25eaea30cd7b009ba2ca693708330d2f395cbc4d Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Mon, 2 May 2011 12:37:33 +0300 Subject: Revert "wl12xx: support FW TX inactivity triggers" This reverts commit 47684808fd89d6809c0886e06f8ac324252499d8. Conflicts: drivers/net/wireless/wl12xx/conf.h drivers/net/wireless/wl12xx/main.c --- drivers/net/wireless/wl12xx/acx.c | 34 ++++---------------------- drivers/net/wireless/wl12xx/acx.h | 12 ++------- drivers/net/wireless/wl12xx/boot.c | 6 ++--- drivers/net/wireless/wl12xx/cmd.c | 2 +- drivers/net/wireless/wl12xx/conf.h | 12 ++------- drivers/net/wireless/wl12xx/event.c | 47 ------------------------------------ drivers/net/wireless/wl12xx/event.h | 12 +-------- drivers/net/wireless/wl12xx/init.c | 6 +---- drivers/net/wireless/wl12xx/main.c | 10 ++------ drivers/net/wireless/wl12xx/wl12xx.h | 1 + 10 files changed, 17 insertions(+), 125 deletions(-) (limited to 'drivers/net/wireless/wl12xx/acx.c') diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index ec0156b3e276..c6ee530e5bf7 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c @@ -1577,46 +1577,22 @@ out: return ret; } -int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl) +int wl1271_acx_max_tx_retry(struct wl1271 *wl) { - struct wl1271_acx_ap_max_tx_retry *acx = NULL; + struct wl1271_acx_max_tx_retry *acx = NULL; int ret; - wl1271_debug(DEBUG_ACX, "acx ap max tx retry"); + wl1271_debug(DEBUG_ACX, "acx max tx retry"); acx = kzalloc(sizeof(*acx), GFP_KERNEL); if (!acx) return -ENOMEM; - acx->max_tx_retry = cpu_to_le16(wl->conf.tx.max_tx_retries); + acx->max_tx_retry = cpu_to_le16(wl->conf.tx.ap_max_tx_retries); ret = wl1271_cmd_configure(wl, ACX_MAX_TX_FAILURE, acx, sizeof(*acx)); if (ret < 0) { - wl1271_warning("acx ap max tx retry failed: %d", ret); - goto out; - } - -out: - kfree(acx); - return ret; -} - -int wl1271_acx_sta_max_tx_retry(struct wl1271 *wl) -{ - struct wl1271_acx_sta_max_tx_retry *acx = NULL; - int ret; - - wl1271_debug(DEBUG_ACX, "acx sta max tx retry"); - - acx = kzalloc(sizeof(*acx), GFP_KERNEL); - if (!acx) - return -ENOMEM; - - acx->max_tx_retry = wl->conf.tx.max_tx_retries; - - ret = wl1271_cmd_configure(wl, ACX_CONS_TX_FAILURE, acx, sizeof(*acx)); - if (ret < 0) { - wl1271_warning("acx sta max tx retry failed: %d", ret); + wl1271_warning("acx max tx retry failed: %d", ret); goto out; } diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index 75338f9947cf..9a895e3cc613 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h @@ -1153,7 +1153,7 @@ struct wl1271_acx_fw_tsf_information { u8 padding[3]; } __packed; -struct wl1271_acx_ap_max_tx_retry { +struct wl1271_acx_max_tx_retry { struct acx_header header; /* @@ -1164,13 +1164,6 @@ struct wl1271_acx_ap_max_tx_retry { u8 padding_1[2]; } __packed; -struct wl1271_acx_sta_max_tx_retry { - struct acx_header header; - - u8 max_tx_retry; - u8 padding_1[3]; -} __packed; - struct wl1271_acx_config_ps { struct acx_header header; @@ -1391,8 +1384,7 @@ int wl1271_acx_set_ba_session(struct wl1271 *wl, int wl1271_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, u16 ssn, bool enable); int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime); -int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl); -int wl1271_acx_sta_max_tx_retry(struct wl1271 *wl); +int wl1271_acx_max_tx_retry(struct wl1271 *wl); int wl1271_acx_config_ps(struct wl1271 *wl); int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); int wl1271_acx_set_ap_beacon_filter(struct wl1271 *wl, bool enable); diff --git a/drivers/net/wireless/wl12xx/boot.c b/drivers/net/wireless/wl12xx/boot.c index d263ebb6f974..2b0cf85788b3 100644 --- a/drivers/net/wireless/wl12xx/boot.c +++ b/drivers/net/wireless/wl12xx/boot.c @@ -478,12 +478,10 @@ static int wl1271_boot_run_firmware(struct wl1271 *wl) DISCONNECT_EVENT_COMPLETE_ID | RSSI_SNR_TRIGGER_0_EVENT_ID | PSPOLL_DELIVERY_FAILURE_EVENT_ID | - SOFT_GEMINI_SENSE_EVENT_ID | - MAX_TX_RETRY_EVENT_ID; + SOFT_GEMINI_SENSE_EVENT_ID; if (wl->bss_type == BSS_TYPE_AP_BSS) - wl->event_mask |= STA_REMOVE_COMPLETE_EVENT_ID | - INACTIVE_STA_EVENT_ID; + wl->event_mask |= STA_REMOVE_COMPLETE_EVENT_ID; else wl->event_mask |= DUMMY_PACKET_EVENT_ID; diff --git a/drivers/net/wireless/wl12xx/cmd.c b/drivers/net/wireless/wl12xx/cmd.c index d8596ae3a64f..2116a376c3f3 100644 --- a/drivers/net/wireless/wl12xx/cmd.c +++ b/drivers/net/wireless/wl12xx/cmd.c @@ -1080,7 +1080,7 @@ int wl1271_cmd_start_bss(struct wl1271 *wl) memcpy(cmd->bssid, bss_conf->bssid, ETH_ALEN); - cmd->aging_period = cpu_to_le16(wl->conf.tx.ap_aging_period); + cmd->aging_period = cpu_to_le16(WL1271_AP_DEF_INACTIV_SEC); cmd->bss_index = WL1271_AP_BSS_INDEX; cmd->global_hlid = WL1271_AP_GLOBAL_HLID; cmd->broadcast_hlid = WL1271_AP_BROADCAST_HLID; diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index c0045f0b8b4e..1f947368f9ee 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -711,18 +711,10 @@ struct conf_tx_settings { struct conf_tx_ac_category ac_conf[CONF_TX_MAX_AC_COUNT]; /* - * Allow this number of TX retries to a connected station/AP before an + * AP-mode - allow this number of TX retries to a station before an * event is triggered from FW. - * In AP-mode the hlids of unreachable stations are given in the - * "sta_tx_retry_exceeded" member in the event mailbox. */ - u8 max_tx_retries; - - /* - * AP-mode - after this number of seconds a connected station is - * considered inactive. - */ - u16 ap_aging_period; + u16 ap_max_tx_retries; /* * Configuration for TID parameters. diff --git a/drivers/net/wireless/wl12xx/event.c b/drivers/net/wireless/wl12xx/event.c index d7be3aec6fc3..ae69330e807c 100644 --- a/drivers/net/wireless/wl12xx/event.c +++ b/drivers/net/wireless/wl12xx/event.c @@ -174,8 +174,6 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) u32 vector; bool beacon_loss = false; bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); - bool disconnect_sta = false; - unsigned long sta_bitmap = 0; wl1271_event_mbox_dump(mbox); @@ -237,54 +235,9 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) wl1271_tx_dummy_packet(wl); } - /* - * "TX retries exceeded" has a different meaning according to mode. - * In AP mode the offending station is disconnected. In STA mode we - * report connection loss. - */ - if (vector & MAX_TX_RETRY_EVENT_ID) { - wl1271_debug(DEBUG_EVENT, "MAX_TX_RETRY_EVENT_ID"); - if (is_ap) { - sta_bitmap |= le16_to_cpu(mbox->sta_tx_retry_exceeded); - disconnect_sta = true; - } else { - beacon_loss = true; - } - } - - if ((vector & INACTIVE_STA_EVENT_ID) && is_ap) { - wl1271_debug(DEBUG_EVENT, "INACTIVE_STA_EVENT_ID"); - sta_bitmap |= le16_to_cpu(mbox->sta_aging_status); - disconnect_sta = true; - } - if (wl->vif && beacon_loss) ieee80211_connection_loss(wl->vif); - if (is_ap && disconnect_sta) { - u32 num_packets = wl->conf.tx.max_tx_retries; - struct ieee80211_sta *sta; - const u8 *addr; - int h; - - for (h = find_first_bit(&sta_bitmap, AP_MAX_LINKS); - h < AP_MAX_LINKS; - h = find_next_bit(&sta_bitmap, AP_MAX_LINKS, h+1)) { - if (!wl1271_is_active_sta(wl, h)) - continue; - - addr = wl->links[h].addr; - - rcu_read_lock(); - sta = ieee80211_find_sta(wl->vif, addr); - if (sta) { - wl1271_debug(DEBUG_EVENT, "remove sta %d", h); - ieee80211_report_low_ack(sta, num_packets); - } - rcu_read_unlock(); - } - } - return 0; } diff --git a/drivers/net/wireless/wl12xx/event.h b/drivers/net/wireless/wl12xx/event.h index 7ae5a0821241..b6cf06e565a4 100644 --- a/drivers/net/wireless/wl12xx/event.h +++ b/drivers/net/wireless/wl12xx/event.h @@ -58,16 +58,13 @@ enum { CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(17), BSS_LOSE_EVENT_ID = BIT(18), REGAINED_BSS_EVENT_ID = BIT(19), - MAX_TX_RETRY_EVENT_ID = BIT(20), + ROAMING_TRIGGER_MAX_TX_RETRY_EVENT_ID = BIT(20), /* STA: dummy paket for dynamic mem blocks */ DUMMY_PACKET_EVENT_ID = BIT(21), /* AP: STA remove complete */ STA_REMOVE_COMPLETE_EVENT_ID = BIT(21), SOFT_GEMINI_SENSE_EVENT_ID = BIT(22), - /* STA: SG prediction */ SOFT_GEMINI_PREDICTION_EVENT_ID = BIT(23), - /* AP: Inactive STA */ - INACTIVE_STA_EVENT_ID = BIT(23), SOFT_GEMINI_AVALANCHE_EVENT_ID = BIT(24), PLT_RX_CALIBRATION_COMPLETE_EVENT_ID = BIT(25), DBG_EVENT_ID = BIT(26), @@ -122,11 +119,7 @@ struct event_mailbox { /* AP FW only */ u8 hlid_removed; - - /* a bitmap of hlids for stations that have been inactive too long */ __le16 sta_aging_status; - - /* a bitmap of hlids for stations which didn't respond to TX */ __le16 sta_tx_retry_exceeded; u8 reserved_5[24]; @@ -137,7 +130,4 @@ void wl1271_event_mbox_config(struct wl1271 *wl); int wl1271_event_handle(struct wl1271 *wl, u8 mbox); void wl1271_pspoll_work(struct work_struct *work); -/* Functions from main.c */ -bool wl1271_is_active_sta(struct wl1271 *wl, u8 hlid); - #endif diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index b1242a6de27b..a8f4f156c055 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c @@ -378,10 +378,6 @@ static int wl1271_sta_hw_init(struct wl1271 *wl) if (ret < 0) return ret; - ret = wl1271_acx_sta_max_tx_retry(wl); - if (ret < 0) - return ret; - ret = wl1271_acx_sta_mem_cfg(wl); if (ret < 0) return ret; @@ -432,7 +428,7 @@ static int wl1271_ap_hw_init(struct wl1271 *wl) if (ret < 0) return ret; - ret = wl1271_acx_ap_max_tx_retry(wl); + ret = wl1271_acx_max_tx_retry(wl); if (ret < 0) return ret; diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 6dd42c987663..6dab6f0c91bc 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -209,8 +209,7 @@ static struct conf_drv_settings default_conf = { .tx_op_limit = 1504, }, }, - .max_tx_retries = 100, - .ap_aging_period = 300, + .ap_max_tx_retries = 100, .tid_conf_count = 4, .tid_conf = { [CONF_TX_AC_BE] = { @@ -3021,12 +3020,6 @@ static void wl1271_free_sta(struct wl1271 *wl, u8 hlid) __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); } -bool wl1271_is_active_sta(struct wl1271 *wl, u8 hlid) -{ - int id = hlid - WL1271_AP_STA_HLID_START; - return test_bit(id, wl->ap_hlid_map); -} - static int wl1271_op_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta) @@ -3632,6 +3625,7 @@ int wl1271_init_ieee80211(struct wl1271 *wl) IEEE80211_HW_HAS_RATE_CONTROL | IEEE80211_HW_CONNECTION_MONITOR | IEEE80211_HW_SUPPORTS_CQM_RSSI | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | IEEE80211_HW_AP_LINK_PS; wl->hw->wiphy->cipher_suites = cipher_suites; diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index f3de96212b96..b7601438ecac 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -172,6 +172,7 @@ extern u32 wl12xx_debug_level; #define WL1271_PS_STA_MAX_BLOCKS (2 * 9) #define WL1271_AP_BSS_INDEX 0 +#define WL1271_AP_DEF_INACTIV_SEC 300 #define WL1271_AP_DEF_BEACON_EXP 20 #define ACX_TX_DESCRIPTORS 32 -- cgit v1.2.3