Commit 32eb7c73 authored by Xose Vazquez Perez's avatar Xose Vazquez Perez Committed by Ben Hutchings
Browse files

linux-firmware: add carl9170 firmware



GPLv2 firmware for carl9170, Atheros AR9170 802.11 draft-n USB driver.

Cc: Christian Lamparter <chunkeey@googlemail.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Ben Hutchings <ben@decadent.org.uk>
Signed-off-by: default avatarXose Vazquez Perez <xose.vazquez@gmail.com>
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 8cf14f0a
.globl _jump_to_bootcode
.type _jump_to_bootcode, @function
_jump_to_bootcode:
mov.l stack_start, r0
mov.l @r0, sp
mov.l eeprom_start, r0
mov.l @r0, r0
jmp @r0
.align 4
stack_start: .long 0x00000004
eeprom_start: .long 0x00000000
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* PHY and RF functions
*
* Copyright (c) 2000-2005 ZyDAS Technology Corporation
* Copyright (c) 2007-2009 Atheros Communications, Inc.
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "timer.h"
#include "printf.h"
#include "rf.h"
#include "shared/phy.h"
#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
static void set_channel_end(void)
{
/* Manipulate CCA threshold to resume transmission */
set(AR9170_PHY_REG_CCA_THRESHOLD, 0x0);
/* Disable Virtual CCA */
andl(AR9170_MAC_REG_QOS_PRIORITY_VIRTUAL_CCA,
~AR9170_MAC_VIRTUAL_CCA_ALL);
fw.phy.state = CARL9170_PHY_ON;
}
void rf_notify_set_channel(void)
{
/* Manipulate CCA threshold to stop transmission */
set(AR9170_PHY_REG_CCA_THRESHOLD, 0x300);
/* Enable Virtual CCA */
orl(AR9170_MAC_REG_QOS_PRIORITY_VIRTUAL_CCA,
AR9170_MAC_VIRTUAL_CCA_ALL);
/* reset CCA stats */
fw.tally.active = 0;
fw.tally.cca = 0;
fw.tally.tx_time = 0;
fw.phy.state = CARL9170_PHY_OFF;
}
/*
* Update delta slope coeff man and exp
*/
static void hw_turn_off_dyn(const uint32_t delta_slope_coeff_exp,
const uint32_t delta_slope_coeff_man,
const uint32_t delta_slope_coeff_exp_shgi,
const uint32_t delta_slope_coeff_man_shgi)
{
uint32_t tmp;
tmp = get_async(AR9170_PHY_REG_TIMING3) & 0x00001fff;
tmp |= (delta_slope_coeff_man << AR9170_PHY_TIMING3_DSC_MAN_S) &
AR9170_PHY_TIMING3_DSC_MAN;
tmp |= (delta_slope_coeff_exp << AR9170_PHY_TIMING3_DSC_EXP_S) &
AR9170_PHY_TIMING3_DSC_EXP;
set(AR9170_PHY_REG_TIMING3, tmp);
tmp = (delta_slope_coeff_man_shgi << AR9170_PHY_HALFGI_DSC_MAN_S) &
AR9170_PHY_HALFGI_DSC_MAN;
tmp |= (delta_slope_coeff_exp_shgi << AR9170_PHY_HALFGI_DSC_EXP_S) &
AR9170_PHY_HALFGI_DSC_EXP;
set(AR9170_PHY_REG_HALFGI, tmp);
}
static void program_ADDAC(void)
{
/* ??? Select Internal ADDAC ??? (is external radio) */
set(AR9170_PHY_REG_ADC_SERIAL_CTL, AR9170_PHY_ADC_SCTL_SEL_EXTERNAL_RADIO);
delay(10);
set(0x1c589c, 0x00000000); /*# 7-0 */
set(0x1c589c, 0x00000000); /*# 15-8 */
set(0x1c589c, 0x00000000); /*# 23-16 */
set(0x1c589c, 0x00000000); /*# 31- */
set(0x1c589c, 0x00000000); /*# 39- */
set(0x1c589c, 0x00000000); /*# 47- */
set(0x1c589c, 0x00000000); /*# 55- [48]:doubles the xtalosc bias current */
set(0x1c589c, 0x00000000); /*# 63- */
set(0x1c589c, 0x00000000); /*# 71- */
set(0x1c589c, 0x00000000); /*# 79- */
set(0x1c589c, 0x00000000); /*# 87- */
set(0x1c589c, 0x00000000); /*# 95- */
set(0x1c589c, 0x00000000); /*# 103- */
set(0x1c589c, 0x00000000); /*# 111- */
set(0x1c589c, 0x00000000); /*# 119- */
set(0x1c589c, 0x00000000); /*# 127- */
set(0x1c589c, 0x00000000); /*# 135- */
set(0x1c589c, 0x00000000); /*# 143- */
set(0x1c589c, 0x00000000); /*# 151- */
set(0x1c589c, 0x00000030); /*# 159- #[158:156]=xlnabufmode */
set(0x1c589c, 0x00000004); /*# 167- [162]:disable clkp_driver to flow */
set(0x1c589c, 0x00000000); /*# 175- */
set(0x1c589c, 0x00000000); /*# 183-176 */
set(0x1c589c, 0x00000000); /*# 191-184 */
set(0x1c589c, 0x00000000); /*# 199- */
set(0x1c589c, 0x00000000); /*# 207- */
set(0x1c589c, 0x00000000); /*# 215- */
set(0x1c589c, 0x00000000); /*# 223- */
set(0x1c589c, 0x00000000); /*# 231- */
set(0x1c58c4, 0x00000000); /*# 233-232 */
delay(10);
/* Select External Flow ???? (is internal addac??) */
set(AR9170_PHY_REG_ADC_SERIAL_CTL, AR9170_PHY_ADC_SCTL_SEL_INTERNAL_ADDAC);
}
static uint32_t AGC_calibration(uint32_t loop)
{
uint32_t wrdata;
uint32_t ret;
#define AGC_CAL_NF (AR9170_PHY_AGC_CONTROL_CAL | AR9170_PHY_AGC_CONTROL_NF)
wrdata = get_async(AR9170_PHY_REG_AGC_CONTROL) | AGC_CAL_NF;
set(AR9170_PHY_REG_AGC_CONTROL, wrdata);
ret = get_async(AR9170_PHY_REG_AGC_CONTROL) & AGC_CAL_NF;
/* sitesurvey : 100 ms / current connected 200 ms */
while ((ret != 0) && loop--) {
udelay(100);
ret = get_async(AR9170_PHY_REG_AGC_CONTROL) & AGC_CAL_NF;
}
/* return the AGC/Noise calibration state to the driver */
return ret;
}
#define EIGHTY_FLAG (CARL9170FW_PHY_HT_ENABLE | CARL9170FW_PHY_HT_DYN2040)
static uint32_t rf_init(const uint32_t delta_slope_coeff_exp,
const uint32_t delta_slope_coeff_man,
const uint32_t delta_slope_coeff_exp_shgi,
const uint32_t delta_slope_coeff_man_shgi,
const uint32_t finiteLoopCount,
const bool initialize)
{
uint32_t ret;
hw_turn_off_dyn(delta_slope_coeff_exp,
delta_slope_coeff_man,
delta_slope_coeff_exp_shgi,
delta_slope_coeff_man_shgi);
if (initialize) {
/* Real Chip */
program_ADDAC();
/* inverse chain 0 <-> chain 2 */
set(AR9170_PHY_REG_ANALOG_SWAP, AR9170_PHY_ANALOG_SWAP_AB);
/* swap chain 0 and chain 2 */
set(AR9170_PHY_REG_ANALOG_SWAP, AR9170_PHY_ANALOG_SWAP_AB |
AR9170_PHY_ANALOG_SWAP_ALT_CHAIN);
/* Activate BB */
set(AR9170_PHY_REG_ACTIVE, AR9170_PHY_ACTIVE_EN);
delay(10);
}
ret = AGC_calibration(finiteLoopCount);
set_channel_end();
return ret;
}
void rf_cmd(const struct carl9170_cmd *cmd, struct carl9170_rsp *resp)
{
uint32_t ret;
fw.phy.ht_settings = cmd->rf_init.ht_settings;
fw.phy.frequency = cmd->rf_init.freq;
/*
* Is the clock controlled by the PHY?
*/
if ((fw.phy.ht_settings & EIGHTY_FLAG) == EIGHTY_FLAG)
clock_set(AHB_80_88MHZ, true);
else
clock_set(AHB_40_44MHZ, true);
ret = rf_init(le32_to_cpu(cmd->rf_init.delta_slope_coeff_exp),
le32_to_cpu(cmd->rf_init.delta_slope_coeff_man),
le32_to_cpu(cmd->rf_init.delta_slope_coeff_exp_shgi),
le32_to_cpu(cmd->rf_init.delta_slope_coeff_man_shgi),
le32_to_cpu(cmd->rf_init.finiteLoopCount),
cmd->hdr.cmd == CARL9170_CMD_RF_INIT);
resp->hdr.len = sizeof(struct carl9170_rf_init_result);
resp->rf_init_res.ret = cpu_to_le32(ret);
}
void rf_psm(void)
{
u32 bank3;
if (fw.phy.psm.state == CARL9170_PSM_SOFTWARE) {
/* not enabled by the driver */
return;
}
if (fw.phy.psm.state & CARL9170_PSM_SLEEP) {
fw.phy.psm.state &= ~CARL9170_PSM_SLEEP;
/* disable all agc gain and offset updates to a2 */
set(AR9170_PHY_REG_TEST2, 0x8000000);
/* power down ADDAC */
set(AR9170_PHY_REG_ADC_CTL,
AR9170_PHY_ADC_CTL_OFF_PWDDAC |
AR9170_PHY_ADC_CTL_OFF_PWDADC |
0xa0000000);
/* Synthesizer off + RX off */
bank3 = 0x00400018;
fw.phy.state = CARL9170_PHY_OFF;
} else {
/* advance to the next PSM step */
fw.phy.psm.state--;
if (fw.phy.psm.state == CARL9170_PSM_WAKE) {
/* wake up ADDAC */
set(AR9170_PHY_REG_ADC_CTL,
AR9170_PHY_ADC_CTL_OFF_PWDDAC |
AR9170_PHY_ADC_CTL_OFF_PWDADC);
/* enable all agc gain and offset updates to a2 */
set(AR9170_PHY_REG_TEST2, 0x0);
/* Synthesizer on + RX on */
bank3 = 0x01420098;
fw.phy.state = CARL9170_PHY_ON;
} else {
return ;
}
}
if (fw.phy.frequency < 3000000)
bank3 |= 0x00800000;
set(0x1c58f0, bank3);
}
#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* UART debug interface functions.
*
* Copyright (c) 2000-2005 ZyDAS Technology Corporation
* Copyright (c) 2007-2009 Atheros Communications, Inc.
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "uart.h"
#include "io.h"
#ifdef CONFIG_CARL9170FW_DEBUG_UART
void uart_putc(const char c)
{
set(AR9170_UART_REG_TX_HOLDING, c);
while (get(AR9170_UART_REG_LINE_STATUS) &
AR9170_UART_LINE_STS_TX_FIFO_ALMOST_EMPTY) {
/*
* wait until the byte has made it
*/
}
}
void uart_print_hex_dump(const void *buf, const int len)
{
unsigned int offset = 0;
uart_putc('H');
uart_putc('D');
uart_putc(':');
while (len > 0) {
uart_putc(*((uint8_t *) buf + offset));
offset++;
}
}
void uart_init(void)
{
unsigned int timeout = 0;
#ifdef CONFIG_CARL9170FW_UART_CLOCK_25M
set(AR9170_UART_REG_DIVISOR_LSB, 0xc);
#elif CONFIG_CARL9170FW_UART_CLOCK_40M
set(AR9170_UART_REG_DIVISOR_LSB, 0x14); /* 40 MHz */
set(AR9170_UART_REG_REMAINDER, 0xb38e);
#else
#error "Unsupported UART clock"
#endif /* CARL9170FW_UART_CLOCK_25M */
while (get(AR9170_UART_REG_LINE_STATUS) &
AR9170_UART_LINE_STS_TRANSMITTER_EMPTY) {
if (timeout++ >= 10000)
break;
}
}
#endif /* CONFIG_CARL9170FW_DEBUG_UART */
/* Copyright (C) 2006 Free Software Foundation, Inc.
This file is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file into combinations with other programs,
and to distribute those combinations without any restriction coming
from the use of this file. (The General Public License restrictions
do apply in other respects; for example, they cover modification of
the file, and distribution when not linked into a combine
executable.)
This file 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; see the file COPYING. If not, write to
the Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA. */
/* Moderately Space-optimized libgcc routines for the Renesas SH /
STMicroelectronics ST40 CPUs.
Contributed by J"orn Rennecke joern.rennecke@st.com. */
/* Size: 186 bytes jointly for udivsi3_i4i and sdivsi3_i4i
sh4-200 run times:
udiv small divisor: 55 cycles
udiv large divisor: 52 cycles
sdiv small divisor, positive result: 59 cycles
sdiv large divisor, positive result: 56 cycles
sdiv small divisor, negative result: 65 cycles (*)
sdiv large divisor, negative result: 62 cycles (*)
(*): r2 is restored in the rts delay slot and has a lingering latency
of two more cycles. */
.balign 4
.global ___udivsi3_i4i
.global ___udivsi3_i4
.set ___udivsi3_i4, ___udivsi3_i4i
.type ___udivsi3_i4i, @function
.type ___sdivsi3_i4i, @function
___udivsi3_i4i:
sts pr,r1
mov.l r4,@-r15
extu.w r5,r0
cmp/eq r5,r0
swap.w r4,r0
shlr16 r4
bf/s large_divisor
div0u
mov.l r5,@-r15
shll16 r5
sdiv_small_divisor:
div1 r5,r4
bsr div6
div1 r5,r4
div1 r5,r4
bsr div6
div1 r5,r4
xtrct r4,r0
xtrct r0,r4
bsr div7
swap.w r4,r4
div1 r5,r4
bsr div7
div1 r5,r4
xtrct r4,r0
mov.l @r15+,r5
swap.w r0,r0
mov.l @r15+,r4
jmp @r1
rotcl r0
div7:
div1 r5,r4
div6:
div1 r5,r4; div1 r5,r4; div1 r5,r4
div1 r5,r4; div1 r5,r4; rts; div1 r5,r4
divx3:
rotcl r0
div1 r5,r4
rotcl r0
div1 r5,r4
rotcl r0
rts
div1 r5,r4
large_divisor:
mov.l r5,@-r15
sdiv_large_divisor:
xor r4,r0
.rept 4
rotcl r0
bsr divx3
div1 r5,r4
.endr
mov.l @r15+,r5
mov.l @r15+,r4
jmp @r1
rotcl r0
.global __sdivsi3_i4i
.global __sdivsi3_i4
.global __sdivsi3
.set __sdivsi3_i4, __sdivsi3_i4i
.set __sdivsi3, __sdivsi3_i4i
__sdivsi3_i4i:
mov.l r4,@-r15
cmp/pz r5
mov.l r5,@-r15
bt/s pos_divisor
cmp/pz r4
neg r5,r5
extu.w r5,r0
bt/s neg_result
cmp/eq r5,r0
neg r4,r4
pos_result:
swap.w r4,r0
bra sdiv_check_divisor
sts pr,r1
pos_divisor:
extu.w r5,r0
bt/s pos_result
cmp/eq r5,r0
neg r4,r4
neg_result:
mova negate_result,r0
;
mov r0,r1
swap.w r4,r0
lds r2,macl
sts pr,r2
sdiv_check_divisor:
shlr16 r4
bf/s sdiv_large_divisor
div0u
bra sdiv_small_divisor
shll16 r5
.balign 4
negate_result:
neg r0,r0
jmp @r2
sts macl,r2
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* Interface to the WLAN part of the chip
*
* Copyright (c) 2000-2005 ZyDAS Technology Corporation
* Copyright (c) 2007-2009 Atheros Communications, Inc.
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "shared/phy.h"
#include "hostif.h"
#include "timer.h"
#include "wl.h"
#include "printf.h"
#include "rf.h"
#include "linux/ieee80211.h"
#include "wol.h"
static void wlan_txunstuck(unsigned int queue)
{
set_wlan_txq_dma_addr(queue, ((uint32_t) fw.wlan.tx_queue[queue].head) | 1);
}
#ifdef CONFIG_CARL9170FW_DMA_QUEUE_BUMP
static void wlan_txupdate(unsigned int queue)
{
set_wlan_txq_dma_addr(queue, ((uint32_t) fw.wlan.tx_queue[queue].head));
}
static void wlan_dma_bump(unsigned int qidx)
{
unsigned int offset = qidx;
uint32_t status, trigger;
status = get(AR9170_MAC_REG_DMA_STATUS) >> 12;
trigger = get(AR9170_MAC_REG_DMA_TRIGGER) >> 12;
while (offset != 0) {
status >>= 4;
trigger >>= 4;
offset--;
}
status &= 0xf;
trigger &= 0xf;
if ((trigger == 0xa) && (status == 0x8)) {
DBG("UNSTUCK");
wlan_txunstuck(qidx);
} else {
DBG("UPDATE");
wlan_txupdate(qidx);
}
}
#endif /* CONFIG_CARL9170FW_DMA_QUEUE_BUMP */
#ifdef CONFIG_CARL9170FW_DEBUG
static void wlan_dump_queue(unsigned int qidx)
{
struct dma_desc *desc;
struct carl9170_tx_superframe *super;
int entries = 0;
__for_each_desc(desc, &fw.wlan.tx_queue[qidx]) {
super = get_super(desc);
DBG("%d: %p s:%x c:%x tl:%x ds:%x n:%p l:%p ", entries, desc,
desc->status, desc->ctrl, desc->totalLen,
desc->dataSize, desc->nextAddr, desc->lastAddr);
DBG("c:%x tr:%d ri:%d l:%x m:%x p:%x fc:%x",
super->s.cookie, super->s.cnt, super->s.rix,
super->f.hdr.length, super->f.hdr.mac.set,
(unsigned int) le32_to_cpu(super->f.hdr.phy.set),
super->f.data.i3e.frame_control);
entries++;
}
desc = get_wlan_txq_addr(qidx);
DBG("Queue: %d: te:%d td:%d h:%p c:%p t:%p",
qidx, entries, queue_len(&fw.wlan.tx_queue[qidx]),
fw.wlan.tx_queue[qidx].head,
desc, fw.wlan.tx_queue[qidx].terminator);
DBG("HW: t:%x s:%x ac:%x c:%x",
(unsigned int) get(AR9170_MAC_REG_DMA_TRIGGER),
(unsigned int) get(AR9170_MAC_REG_DMA_STATUS),
(unsigned int) get(AR9170_MAC_REG_AMPDU_COUNT),
(unsigned int) get(AR9170_MAC_REG_DMA_TXQX_ADDR_CURR));
}
#endif /* CONFIG_CARL9170FW_DEBUG */
static void wlan_send_buffered_tx_status(void)
{
unsigned int len;
while (fw.wlan.tx_status_pending) {
len = min((unsigned int)fw.wlan.tx_status_pending,
CARL9170_RSP_TX_STATUS_NUM);
len = min(len, CARL9170_TX_STATUS_NUM - fw.wlan.tx_status_head_idx);
/*
* rather than memcpy each individual request into a large buffer,
* we _splice_ them all together.
*
* The only downside is however that we have to be careful around
* the edges of the tx_status_cache.
*
* Note:
* Each tx_status is about 2 bytes. However every command package
* must have a size which is a multiple of 4.
*/
send_cmd_to_host((len * sizeof(struct carl9170_tx_status) + 3) & ~3,
CARL9170_RSP_TXCOMP, len, (void *)
&fw.wlan.tx_status_cache[fw.wlan.tx_status_head_idx]);
fw.wlan.tx_status_pending -= len;
fw.wlan.tx_status_head_idx += len;
fw.wlan.tx_status_head_idx %= CARL9170_TX_STATUS_NUM;
}
}
static struct carl9170_tx_status *wlan_get_tx_status_buffer(void)
{
struct carl9170_tx_status *tmp;
tmp = &fw.wlan.tx_status_cache[fw.wlan.tx_status_tail_idx++];
fw.wlan.tx_status_tail_idx %= CARL9170_TX_STATUS_NUM;
if (fw.wlan.tx_status_pending == CARL9170_TX_STATUS_NUM)
wlan_send_buffered_tx_status();
fw.wlan.tx_status_pending++;
return tmp;
}
/* generate _aggregated_ tx_status for the host */
void wlan_tx_complete(struct carl9170_tx_superframe *super,
bool txs)
{
struct carl9170_tx_status *status;
status = wlan_get_tx_status_buffer();
/*
* The *unique* cookie and AC_ID is used by the driver for
* frame lookup.
*/
status->cookie = super->s.cookie;
status->queue = super->s.queue;
super->s.cookie = 0;
/*
* This field holds the number of tries of the rate in
* the rate index field (rix).
*/
status->rix = super->s.rix;
status->tries = super->s.cnt;
status->success = (txs) ? 1 : 0;
}
static bool wlan_tx_consume_retry(struct carl9170_tx_superframe *super)
{
/* check if this was the last possible retry with this rate */
if (unlikely(super->s.cnt >= super->s.ri[super->s.rix].tries)) {
/* end of the road - indicate tx failure */
if (unlikely(super->s.rix == CARL9170_TX_MAX_RETRY_RATES))
return false;
/* check if there are alternative rates available */
if (!super->s.rr[super->s.rix].set)
return false;
/* try next retry rate */
super->f.hdr.phy.set = super->s.rr[super->s.rix].set;
/* finally - mark the old rate as USED */
super->s.rix++;
/* update MAC flags */
super->f.hdr.mac.erp_prot = super->s.ri[super->s.rix].erp_prot;
super->f.hdr.mac.ampdu = super->s.ri[super->s.rix].ampdu;
/* reinitialize try counter */
super->s.cnt = 1;
} else {
/* just increase retry counter */
super->s.cnt++;
}
return true;
}
static inline u16 get_tid(struct ieee80211_hdr *hdr)
{
return (ieee80211_get_qos_ctl(hdr))[0] & IEEE80211_QOS_CTL_TID_MASK;
}
/* This function will only work on uint32_t-aligned pointers! */
static bool same_hdr(const void *_d0, const void *_d1)
{
const uint32_t *d0 = _d0;
const uint32_t *d1 = _d1;
/* BUG_ON((unsigned long)d0 & 3 || (unsigned long)d1 & 3)) */
return !((d0[0] ^ d1[0]) | /* FC + DU */
(d0[1] ^ d1[1]) | /* addr1 */
(d0[2] ^ d1[2]) | (d0[3] ^ d1[3]) | /* addr2 + addr3 */
(d0[4] ^ d1[4])); /* addr3 */
}
static inline bool same_aggr(struct ieee80211_hdr *a, struct ieee80211_hdr *b)
{
return (get_tid(a) == get_tid(b)) || same_hdr(a, b);
}
static void wlan_tx_ampdu_reset(unsigned int qidx)
{
fw.wlan.ampdu_prev[qidx] = NULL;
}
static void wlan_tx_ampdu_end(unsigned int qidx)
{
struct carl9170_tx_superframe *ht_prev = fw.wlan.ampdu_prev[qidx];
if (ht_prev)
ht_prev->f.hdr.mac.ba_end = 1;
wlan_tx_ampdu_reset(qidx);
}
static void wlan_tx_ampdu(struct carl9170_tx_superframe *super)
{
unsigned int qidx = super->s.queue;
struct carl9170_tx_superframe *ht_prev = fw.wlan.ampdu_prev[qidx];
if (super->f.hdr.mac.ampdu) {
if (ht_prev &&
!same_aggr(&super->f.data.i3e, &ht_prev->f.data.i3e))
ht_prev->f.hdr.mac.ba_end = 1;
else
super->f.hdr.mac.ba_end = 0;
fw.wlan.ampdu_prev[qidx] = super;
} else {
wlan_tx_ampdu_end(qidx);
}
}
/* for all tries */
static void __wlan_tx(struct dma_desc *desc)
{
struct carl9170_tx_superframe *super = get_super(desc);
if (unlikely(super->s.fill_in_tsf)) {
struct ieee80211_mgmt *mgmt = (void *) &super->f.data.i3e;
uint32_t *tsf = (uint32_t *) &mgmt->u.probe_resp.timestamp;
/*
* Truth be told: this is a hack.
*
* The *real* TSF is definitely going to be higher/older.
* But this hardware emulation code is head and shoulders
* above anything a driver can possibly do.
*
* (even, if it's got an accurate atomic clock source).
*/
read_tsf(tsf);
}
wlan_tx_ampdu(super);
#ifdef CONFIG_CARL9170FW_DEBUG
BUG_ON(fw.phy.psm.state != CARL9170_PSM_WAKE);
#endif /* CONFIG_CARL9170FW_DEBUG */
/* insert desc into the right queue */
dma_put(&fw.wlan.tx_queue[super->s.queue], desc);
}
static void wlan_assign_seq(struct ieee80211_hdr *hdr, unsigned int vif)
{
hdr->seq_ctrl &= cpu_to_le16(~IEEE80211_SCTL_SEQ);
hdr->seq_ctrl |= cpu_to_le16(fw.wlan.sequence[vif]);
if (ieee80211_is_first_frag(hdr->seq_ctrl))
fw.wlan.sequence[vif] += 0x10;
}
/* prepares frame for the first transmission */
static void _wlan_tx(struct dma_desc *desc)
{
struct carl9170_tx_superframe *super = get_super(desc);
if (unlikely(super->s.assign_seq))
wlan_assign_seq(&super->f.data.i3e, super->s.vif_id);
if (unlikely(super->s.ampdu_commit_density)) {
set(AR9170_MAC_REG_AMPDU_DENSITY,
MOD_VAL(AR9170_MAC_AMPDU_DENSITY,
get(AR9170_MAC_REG_AMPDU_DENSITY),
super->s.ampdu_density));
}
if (unlikely(super->s.ampdu_commit_factor)) {
set(AR9170_MAC_REG_AMPDU_FACTOR,
MOD_VAL(AR9170_MAC_AMPDU_FACTOR,
get(AR9170_MAC_REG_AMPDU_FACTOR),
8 << super->s.ampdu_factor));
}
}
/* propagate transmission status back to the driver */
static bool wlan_tx_status(struct dma_queue *queue,
struct dma_desc *desc)
{
struct carl9170_tx_superframe *super = get_super(desc);
unsigned int qidx = super->s.queue;
bool txfail = false, success;
success = true;
/* update hangcheck */
fw.wlan.last_super_num[qidx] = 0;
/*
* Note:
* There could be a corner case when the TXFAIL is set
* even though the frame was properly ACKed by the peer:
* a BlockAckReq with the immediate policy will cause
* the receiving peer to produce a BlockACK unfortunately
* the MAC in this chip seems to be expecting a legacy
* ACK and marks the BAR as failed!
*/
if (!!(desc->ctrl & AR9170_CTRL_FAIL)) {
txfail = !!(desc->ctrl & AR9170_CTRL_TXFAIL);
/* reset retry indicator flags */
desc->ctrl &= ~(AR9170_CTRL_TXFAIL | AR9170_CTRL_BAFAIL);
/*
* Note: wlan_tx_consume_retry will override the old
* phy [CCK,OFDM, HT, BW20/40, MCS...] and mac vectors
* [AMPDU,RTS/CTS,...] therefore be careful when they
* are used.
*/
if (wlan_tx_consume_retry(super)) {
/*
* retry for simple and aggregated 802.11 frames.
*
* Note: We must not mess up the original frame
* order.
*/
if (!super->f.hdr.mac.ampdu) {
/*
* 802.11 - 7.1.3.1.5.
* set "Retry Field" for consecutive attempts
*
* Note: For AMPDU see:
* 802.11n 9.9.1.6 "Retransmit Procedures"
*/
super->f.data.i3e.frame_control |=
cpu_to_le16(IEEE80211_FCTL_RETRY);
}
if (txfail) {
/* Normal TX Failure */
/* demise descriptor ownership back to the hardware */
dma_rearm(desc);
/*
* And this will get the queue going again.
* To understand why: you have to get the HW
* specs... But sadly I never saw them.
*/
wlan_txunstuck(qidx);
/* abort cycle - this is necessary due to HW design */
return false;
} else {
/* (HT-) BlockACK failure */
/*
* Unlink the failed attempt and put it into
* the retry queue. The caller routine must
* be aware of this so the frames don't get lost.
*/
#ifndef CONFIG_CARL9170FW_DEBUG
dma_unlink_head(queue);
#else /* CONFIG_CARL9170FW_DEBUG */
BUG_ON(dma_unlink_head(queue) != desc);
#endif /* CONFIG_CARL9170FW_DEBUG */
dma_put(&fw.wlan.tx_retry, desc);
return true;
}
} else {
/* out of frame attempts - discard frame */
success = false;
}
}
#ifndef CONFIG_CARL9170FW_DEBUG
dma_unlink_head(queue);
#else /* CONFIG_CARL9170FW_DEBUG */
BUG_ON(dma_unlink_head(queue) != desc);
#endif /* CONFIG_CARL9170FW_DEBUG */
if (txfail) {
/*
* Issue the queue bump,
* We need to do this in case this was the frame's last
* possible retry attempt and it unfortunately: it failed.
*/
wlan_txunstuck(qidx);
}
unhide_super(desc);
if (unlikely(super == fw.wlan.fw_desc_data)) {
fw.wlan.fw_desc = desc;
fw.wlan.fw_desc_available = 1;
if (fw.wlan.fw_desc_callback)
fw.wlan.fw_desc_callback(super, success);
return true;
}
#ifdef CONFIG_CARL9170FW_CAB_QUEUE
if (unlikely(super->s.cab))
fw.wlan.cab_queue_len[super->s.vif_id]--;
#endif /* CONFIG_CARL9170FW_CAB_QUEUE */
wlan_tx_complete(super, success);
if (ieee80211_is_back_req(super->f.data.i3e.frame_control)) {
fw.wlan.queued_bar--;
}
/* recycle freed descriptors */
dma_reclaim(&fw.pta.down_queue, desc);
down_trigger();
return true;
}
static void handle_tx_completion(void)
{
struct dma_desc *desc;
int i;
for (i = AR9170_TXQ_SPECIAL; i >= AR9170_TXQ0; i--) {
__while_desc_bits(desc, &fw.wlan.tx_queue[i], AR9170_OWN_BITS_SW) {
if (!wlan_tx_status(&fw.wlan.tx_queue[i], desc)) {
/* termination requested. */
break;
}
}
wlan_tx_ampdu_reset(i);
for_each_desc(desc, &fw.wlan.tx_retry)
__wlan_tx(desc);
wlan_tx_ampdu_end(i);
if (!queue_empty(&fw.wlan.tx_queue[i]))
wlan_trigger(BIT(i));
}
}
void __hot wlan_tx(struct dma_desc *desc)
{
struct carl9170_tx_superframe *super = DESC_PAYLOAD(desc);
if (ieee80211_is_back_req(super->f.data.i3e.frame_control)) {
fw.wlan.queued_bar++;
}
/* initialize rate control struct */
super->s.rix = 0;
super->s.cnt = 1;
hide_super(desc);
#ifdef CONFIG_CARL9170FW_CAB_QUEUE
if (unlikely(super->s.cab)) {
fw.wlan.cab_queue_len[super->s.vif_id]++;
dma_put(&fw.wlan.cab_queue[super->s.vif_id], desc);
return;
}
#endif /* CONFIG_CARL9170FW_CAB_QUEUE */
_wlan_tx(desc);
__wlan_tx(desc);
wlan_trigger(BIT(super->s.queue));
}
void wlan_tx_fw(struct carl9170_tx_superdesc *super, fw_desc_callback_t cb)
{
if (!fw.wlan.fw_desc_available)
return;
fw.wlan.fw_desc_available = 0;
/* Format BlockAck */
fw.wlan.fw_desc->ctrl = AR9170_CTRL_FS_BIT | AR9170_CTRL_LS_BIT;
fw.wlan.fw_desc->status = AR9170_OWN_BITS_SW;
fw.wlan.fw_desc->totalLen = fw.wlan.fw_desc->dataSize = super->len;
fw.wlan.fw_desc_data = fw.wlan.fw_desc->dataAddr = super;
fw.wlan.fw_desc->nextAddr = fw.wlan.fw_desc->lastAddr =
fw.wlan.fw_desc;
fw.wlan.fw_desc_callback = cb;
wlan_tx(fw.wlan.fw_desc);
}
static void wlan_send_buffered_ba(void)
{
struct carl9170_tx_ba_superframe *baf = &dma_mem.reserved.ba.ba;
struct ieee80211_ba *ba = (struct ieee80211_ba *) &baf->f.ba;
struct carl9170_bar_ctx *ctx;
if (likely(!fw.wlan.queued_ba))
return;
/* there's no point to continue when the ba_desc is not available. */
if (!fw.wlan.fw_desc_available)
return;
ctx = &fw.wlan.ba_cache[fw.wlan.ba_head_idx];
fw.wlan.ba_head_idx++;
fw.wlan.ba_head_idx %= CONFIG_CARL9170FW_BACK_REQS_NUM;
fw.wlan.queued_ba--;
baf->s.len = sizeof(struct carl9170_tx_superdesc) +
sizeof(struct ar9170_tx_hwdesc) +
sizeof(struct ieee80211_ba);
baf->s.ri[0].tries = 1;
baf->s.cookie = 0;
baf->s.queue = AR9170_TXQ_VO;
baf->f.hdr.length = sizeof(struct ieee80211_ba) + FCS_LEN;
baf->f.hdr.mac.no_ack = 1;
baf->f.hdr.phy.modulation = 1; /* OFDM */
baf->f.hdr.phy.tx_power = 34; /* 17 dBm */
baf->f.hdr.phy.chains = 1;
baf->f.hdr.phy.mcs = AR9170_TXRX_PHY_RATE_OFDM_6M;
/* format outgoing BA */
ba->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_BACK);
ba->duration = cpu_to_le16(0);
/* the BAR contains all necessary MACs. All we need is to swap them */
memcpy(ba->ra, ctx->ta, 6);
memcpy(ba->ta, ctx->ra, 6);
/*
* Unfortunately, we cannot look into the hardware's scoreboard.
* Therefore we have to proceed as described in 802.11n 9.10.7.5
* and send a null BlockAck.
*/
memset(ba->bitmap, 0x0, sizeof(ba->bitmap));
/*
* Both, the original firmare and ath9k set the NO ACK flag in
* the BA Ack Policy subfield.
*/
ba->control = ctx->control | cpu_to_le16(1);
ba->start_seq_num = ctx->start_seq_num;
wlan_tx_fw(&baf->s, NULL);
}
static struct carl9170_bar_ctx *wlan_get_bar_cache_buffer(void)
{
struct carl9170_bar_ctx *tmp;
tmp = &fw.wlan.ba_cache[fw.wlan.ba_tail_idx];
fw.wlan.ba_tail_idx++;
fw.wlan.ba_tail_idx %= CONFIG_CARL9170FW_BACK_REQS_NUM;
if (fw.wlan.queued_ba < CONFIG_CARL9170FW_BACK_REQS_NUM)
fw.wlan.queued_ba++;
return tmp;
}
static void handle_bar(struct dma_desc *desc __unused, struct ieee80211_hdr *hdr,
unsigned int len, unsigned int mac_err)
{
struct ieee80211_bar *bar;
struct carl9170_bar_ctx *ctx;
if (unlikely(mac_err)) {
/*
* This check does a number of things:
* 1. checks if the frame is in good nick
* 2. checks if the RA (MAC) matches
*/
return ;
}
if (unlikely(len < (sizeof(struct ieee80211_bar) + FCS_LEN))) {
/*
* Sneaky, corrupted BARs... but not with us!
*/
return ;
}
bar = (void *) hdr;
if ((bar->control & cpu_to_le16(IEEE80211_BAR_CTRL_MULTI_TID)) ||
!(bar->control & cpu_to_le16(IEEE80211_BAR_CTRL_CBMTID_COMPRESSED_BA))) {
/* not implemented yet */
return ;
}
ctx = wlan_get_bar_cache_buffer();
memcpy(ctx->ra, bar->ra, 6);
memcpy(ctx->ta, bar->ta, 6);
ctx->control = bar->control;
ctx->start_seq_num = bar->start_seq_num;
}
static void wlan_check_rx_overrun(void)
{
uint32_t overruns, total;
fw.tally.rx_total += total = get(AR9170_MAC_REG_RX_TOTAL);
fw.tally.rx_overrun += overruns = get(AR9170_MAC_REG_RX_OVERRUN);
if (unlikely(overruns)) {
if (overruns == total) {
DBG("RX Overrun");
fw.wlan.mac_reset++;
}
wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
}
}
static unsigned int wlan_rx_filter(struct dma_desc *desc)
{
struct ieee80211_hdr *hdr;
unsigned int data_len;
unsigned int rx_filter;
unsigned int mac_err;
data_len = ar9170_get_rx_mpdu_len(desc);
mac_err = ar9170_get_rx_macstatus_error(desc);
#define AR9170_RX_ERROR_BAD (AR9170_RX_ERROR_FCS | AR9170_RX_ERROR_PLCP)
if (unlikely(data_len < (4 + 6 + FCS_LEN) ||
desc->totalLen > CONFIG_CARL9170FW_RX_FRAME_LEN) ||
mac_err & AR9170_RX_ERROR_BAD) {
/*
* This frame is too damaged to do anything
* useful with it.
*/
return CARL9170_RX_FILTER_BAD;
}
rx_filter = 0;
if (mac_err & AR9170_RX_ERROR_WRONG_RA)
rx_filter |= CARL9170_RX_FILTER_OTHER_RA;
if (mac_err & AR9170_RX_ERROR_DECRYPT)
rx_filter |= CARL9170_RX_FILTER_DECRY_FAIL;
hdr = ar9170_get_rx_i3e(desc);
if (likely(ieee80211_is_data(hdr->frame_control))) {
rx_filter |= CARL9170_RX_FILTER_DATA;
} else if (ieee80211_is_ctl(hdr->frame_control)) {
switch (le16_to_cpu(hdr->frame_control) & IEEE80211_FCTL_STYPE) {
case IEEE80211_STYPE_BACK_REQ:
handle_bar(desc, hdr, data_len, mac_err);
rx_filter |= CARL9170_RX_FILTER_CTL_BACKR;
break;
case IEEE80211_STYPE_PSPOLL:
rx_filter |= CARL9170_RX_FILTER_CTL_PSPOLL;
break;
case IEEE80211_STYPE_BACK:
if (fw.wlan.queued_bar) {
/*
* Don't filter block acks when the application
* has queued BARs. This is because the firmware
* can't do the accouting and the application
* has to sort out if the BA belongs to any BARs.
*/
break;
}
/* otherwise fall through */
default:
rx_filter |= CARL9170_RX_FILTER_CTL_OTHER;
break;
}
} else {
/* ieee80211_is_mgmt */
rx_filter |= CARL9170_RX_FILTER_MGMT;
}
if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) {
wol_rx(rx_filter, hdr, min(data_len,
(unsigned int)AR9170_BLOCK_SIZE));
}
#undef AR9170_RX_ERROR_BAD
return rx_filter;
}
static void handle_rx(void)
{
struct dma_desc *desc;
for_each_desc_not_bits(desc, &fw.wlan.rx_queue, AR9170_OWN_BITS_HW) {
if (!(wlan_rx_filter(desc) & fw.wlan.rx_filter)) {
dma_put(&fw.pta.up_queue, desc);
up_trigger();
} else {
dma_reclaim(&fw.wlan.rx_queue, desc);
wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
}
}
}
#ifdef CONFIG_CARL9170FW_CAB_QUEUE
void wlan_cab_flush_queue(const unsigned int vif)
{
struct dma_queue *cab_queue = &fw.wlan.cab_queue[vif];
struct dma_desc *desc;
/* move queued frames into the main tx queues */
for_each_desc(desc, cab_queue) {
struct carl9170_tx_superframe *super = get_super(desc);
if (!queue_empty(cab_queue)) {
/*
* Set MOREDATA flag for all,
* but the last queued frame.
* see: 802.11-2007 11.2.1.5 f)
*
* This is actually the reason to why
* we need to prevent the reentry.
*/
super->f.data.i3e.frame_control |=
cpu_to_le16(IEEE80211_FCTL_MOREDATA);
} else {
super->f.data.i3e.frame_control &=
cpu_to_le16(~IEEE80211_FCTL_MOREDATA);
}
/* ready to roll! */
_wlan_tx(desc);
__wlan_tx(desc);
wlan_trigger(BIT(super->s.queue));
}
}
static uint8_t *beacon_find_ie(uint8_t ie, void *addr,
const unsigned int len)
{
struct ieee80211_mgmt *mgmt = addr;
uint8_t *pos, *end;
pos = mgmt->u.beacon.variable;
end = (uint8_t *) ((unsigned long)mgmt + (len - FCS_LEN));
while (pos < end) {
if (pos + 2 + pos[1] > end)
return NULL;
if (pos[0] == ie)
return pos;
pos += pos[1] + 2;
}
return NULL;
}
void wlan_modify_beacon(const unsigned int vif,
const unsigned int addr, const unsigned int len)
{
uint8_t *_ie;
struct ieee80211_tim_ie *ie;
_ie = beacon_find_ie(WLAN_EID_TIM, (void *)addr, len);
if (likely(_ie)) {
ie = (struct ieee80211_tim_ie *) &_ie[2];
if (!queue_empty(&fw.wlan.cab_queue[vif]) && (ie->dtim_count == 0)) {
/* schedule DTIM transfer */
fw.wlan.cab_flush_trigger[vif] = CARL9170_CAB_TRIGGER_ARMED;
} else if ((fw.wlan.cab_queue_len[vif] == 0) && (fw.wlan.cab_flush_trigger[vif])) {
/* undo all chances to the beacon structure */
ie->bitmap_ctrl &= ~0x1;
fw.wlan.cab_flush_trigger[vif] = CARL9170_CAB_TRIGGER_EMPTY;
}
/* Triggered by CARL9170_CAB_TRIGGER_ARMED || CARL9170_CAB_TRIGGER_DEFER */
if (fw.wlan.cab_flush_trigger[vif]) {
/* Set the almighty Multicast Traffic Indication Bit. */
ie->bitmap_ctrl |= 0x1;
}
}
/*
* Ideally, the sequence number should be assigned by the TX arbiter
* hardware. But AFAIK that's not possible, so we have to go for the
* next best thing and write it into the beacon fifo during the open
* beacon update window.
*/
wlan_assign_seq((struct ieee80211_hdr *)addr, vif);
}
static void wlan_send_buffered_cab(void)
{
unsigned int i;
for (i = 0; i < CARL9170_INTF_NUM; i++) {
if (unlikely(fw.wlan.cab_flush_trigger[i] == CARL9170_CAB_TRIGGER_ARMED)) {
/*
* This is hardcoded into carl9170usb driver.
*
* The driver must set the PRETBTT event to beacon_interval -
* CARL9170_PRETBTT_KUS (usually 6) Kus.
*
* But still, we can only do so much about 802.11-2007 9.3.2.1 &
* 11.2.1.6. Let's hope the current solution is adequate enough.
*/
if (is_after_msecs(fw.wlan.cab_flush_time, (CARL9170_TBTT_DELTA))) {
wlan_cab_flush_queue(i);
/*
* This prevents the code from sending new BC/MC frames
* which were queued after the previous buffered traffic
* has been sent out... They will have to wait until the
* next DTIM beacon comes along.
*/
fw.wlan.cab_flush_trigger[i] = CARL9170_CAB_TRIGGER_DEFER;
}
}
}
}
#endif /* CONFIG_CARL9170FW_CAB_QUEUE */
static void handle_beacon_config(void)
{
uint32_t bcn_count;
bcn_count = get(AR9170_MAC_REG_BCN_COUNT);
send_cmd_to_host(4, CARL9170_RSP_BEACON_CONFIG, 0x00,
(uint8_t *) &bcn_count);
}
static void handle_pretbtt(void)
{
#ifdef CONFIG_CARL9170FW_CAB_QUEUE
fw.wlan.cab_flush_time = get_clock_counter();
#endif /* CONFIG_CARL9170FW_CAB_QUEUE */
#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
rf_psm();
send_cmd_to_host(4, CARL9170_RSP_PRETBTT, 0x00,
(uint8_t *) &fw.phy.psm.state);
#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
}
static void handle_atim(void)
{
send_cmd_to_host(0, CARL9170_RSP_ATIM, 0x00, NULL);
}
#ifdef CONFIG_CARL9170FW_DEBUG
static void handle_qos(void)
{
/*
* What is the QoS Bit used for?
* Is it only an indicator for TXOP & Burst, or
* should we do something here?
*/
}
static void handle_radar(void)
{
send_cmd_to_host(0, CARL9170_RSP_RADAR, 0x00, NULL);
}
#endif /* CONFIG_CARL9170FW_DEBUG */
static void wlan_janitor(void)
{
#ifdef CONFIG_CARL9170FW_CAB_QUEUE
wlan_send_buffered_cab();
#endif /* CONFIG_CARL9170FW_CAB_QUEUE */
wlan_send_buffered_tx_status();
wlan_send_buffered_ba();
wol_janitor();
}
void handle_wlan(void)
{
uint32_t intr;
intr = get(AR9170_MAC_REG_INT_CTRL);
/* ACK Interrupt */
set(AR9170_MAC_REG_INT_CTRL, intr);
#define HANDLER(intr, flag, func) \
do { \
if ((intr & flag) != 0) { \
func(); \
} \
} while (0)
intr |= fw.wlan.soft_int;
fw.wlan.soft_int = 0;
HANDLER(intr, AR9170_MAC_INT_PRETBTT, handle_pretbtt);
HANDLER(intr, AR9170_MAC_INT_ATIM, handle_atim);
HANDLER(intr, AR9170_MAC_INT_RXC, handle_rx);
HANDLER(intr, (AR9170_MAC_INT_TXC | AR9170_MAC_INT_RETRY_FAIL),
handle_tx_completion);
#ifdef CONFIG_CARL9170FW_DEBUG
HANDLER(intr, AR9170_MAC_INT_QOS, handle_qos);
HANDLER(intr, AR9170_MAC_INT_RADAR, handle_radar);
#endif /* CONFIG_CARL9170FW_DEBUG */
HANDLER(intr, AR9170_MAC_INT_CFG_BCN, handle_beacon_config);
if (unlikely(intr))
DBG("Unhandled Interrupt %x\n", (unsigned int) intr);
wlan_janitor();
#undef HANDLER
}
enum {
CARL9170FW_TX_MAC_BUMP = 4,
CARL9170FW_TX_MAC_DEBUG = 6,
CARL9170FW_TX_MAC_RESET = 7,
};
static void wlan_check_hang(void)
{
struct dma_desc *desc;
int i;
for (i = AR9170_TXQ_SPECIAL; i >= AR9170_TXQ0; i--) {
if (queue_empty(&fw.wlan.tx_queue[i])) {
/* Nothing to do here... move along */
continue;
}
/* fetch the current DMA queue position */
desc = (struct dma_desc *)get_wlan_txq_addr(i);
/* Stuck frame detection */
if (unlikely(DESC_PAYLOAD(desc) == fw.wlan.last_super[i])) {
fw.wlan.last_super_num[i]++;
if (unlikely(fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_RESET)) {
/*
* schedule MAC reset (aka OFF/ON => dead)
*
* This will almost certainly kill
* the device for good, but it's the
* recommended thing to do...
*/
fw.wlan.mac_reset++;
}
#ifdef CONFIG_CARL9170FW_DEBUG
if (unlikely(fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_DEBUG)) {
/*
* Sigh, the queue is almost certainly
* dead. Dump the queue content to the
* user, maybe we find out why it got
* so stuck.
*/
wlan_dump_queue(i);
}
#endif /* CONFIG_CARL9170FW_DEBUG */
#ifdef CONFIG_CARL9170FW_DMA_QUEUE_BUMP
if (unlikely(fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_BUMP)) {
/*
* Hrrm, bump the queue a bit.
* maybe this will get it going again.
*/
wlan_dma_bump(i);
wlan_trigger(BIT(i));
}
#endif /* CONFIG_CARL9170FW_DMA_QUEUE_BUMP */
} else {
/* Nothing stuck */
fw.wlan.last_super[i] = DESC_PAYLOAD(desc);
fw.wlan.last_super_num[i] = 0;
}
}
}
#ifdef CONFIG_CARL9170FW_FW_MAC_RESET
/*
* NB: Resetting the MAC is a two-edged sword.
* On most occasions, it does what it is supposed to do.
* But there is a chance that this will make it
* even worse and the radio dies silently.
*/
static void wlan_mac_reset(void)
{
uint32_t val;
uint32_t agg_wait_counter;
uint32_t agg_density;
uint32_t bcn_start_addr;
uint32_t rctl, rcth;
uint32_t cam_mode;
uint32_t ack_power;
uint32_t rts_cts_tpc;
uint32_t rts_cts_rate;
int i;
#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
uint32_t rx_BB;
#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
#ifdef CONFIG_CARL9170FW_NOISY_MAC_RESET
INFO("MAC RESET");
#endif /* CONFIG_CARL9170FW_NOISY_MAC_RESET */
/* Save aggregation parameters */
agg_wait_counter = get(AR9170_MAC_REG_AMPDU_FACTOR);
agg_density = get(AR9170_MAC_REG_AMPDU_DENSITY);
bcn_start_addr = get(AR9170_MAC_REG_BCN_ADDR);
cam_mode = get(AR9170_MAC_REG_CAM_MODE);
rctl = get(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L);
rcth = get(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H);
ack_power = get(AR9170_MAC_REG_ACK_TPC);
rts_cts_tpc = get(AR9170_MAC_REG_RTS_CTS_TPC);
rts_cts_rate = get(AR9170_MAC_REG_RTS_CTS_RATE);
#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
/* 0x1c8960 write only */
rx_BB = get(AR9170_PHY_REG_SWITCH_CHAIN_0);
#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
/* TX/RX must be stopped by now */
val = get(AR9170_MAC_REG_POWER_STATE_CTRL);
val |= AR9170_MAC_POWER_STATE_CTRL_RESET;
/*
* Manipulate CCA threshold to stop transmission
*
* set(AR9170_PHY_REG_CCA_THRESHOLD, 0x300);
*/
/*
* check Rx state in 0(idle) 9(disable)
*
* chState = (get(AR9170_MAC_REG_MISC_684) >> 16) & 0xf;
* while( (chState != 0) && (chState != 9)) {
* chState = (get(AR9170_MAC_REG_MISC_684) >> 16) & 0xf;
* }
*/
set(AR9170_MAC_REG_POWER_STATE_CTRL, val);
delay(2);
/* Restore aggregation parameters */
set(AR9170_MAC_REG_AMPDU_FACTOR, agg_wait_counter);
set(AR9170_MAC_REG_AMPDU_DENSITY, agg_density);
set(AR9170_MAC_REG_BCN_ADDR, bcn_start_addr);
set(AR9170_MAC_REG_CAM_MODE, cam_mode);
set(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L, rctl);
set(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H, rcth);
set(AR9170_MAC_REG_RTS_CTS_TPC, rts_cts_tpc);
set(AR9170_MAC_REG_ACK_TPC, ack_power);
set(AR9170_MAC_REG_RTS_CTS_RATE, rts_cts_rate);
#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
set(AR9170_PHY_REG_SWITCH_CHAIN_2, rx_BB);
#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
/*
* Manipulate CCA threshold to resume transmission
*
* set(AR9170_PHY_REG_CCA_THRESHOLD, 0x0);
*/
val = AR9170_DMA_TRIGGER_RXQ;
/* Reinitialize all WLAN TX DMA queues. */
for (i = AR9170_TXQ_SPECIAL; i >= AR9170_TXQ0; i--) {
struct dma_desc *iter;
__for_each_desc_bits(iter, &fw.wlan.tx_queue[i], AR9170_OWN_BITS_SW);
/* kill the stuck frame */
if (!is_terminator(&fw.wlan.tx_queue[i], iter) &&
fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_RESET &&
fw.wlan.last_super[i] == DESC_PAYLOAD(iter)) {
struct carl9170_tx_superframe *super = get_super(iter);
iter->status = AR9170_OWN_BITS_SW;
/*
* Mark the frame as failed.
* The BAFAIL flag allows the frame to sail through
* wlan_tx_status without much "unstuck" trouble.
*/
iter->ctrl &= ~(AR9170_CTRL_FAIL);
iter->ctrl |= AR9170_CTRL_BAFAIL;
super->s.cnt = CARL9170_TX_MAX_RATE_TRIES;
super->s.rix = CARL9170_TX_MAX_RETRY_RATES;
fw.wlan.last_super_num[i] = 0;
fw.wlan.last_super[i] = NULL;
iter = iter->lastAddr->nextAddr;
}
set_wlan_txq_dma_addr(i, (uint32_t) iter);
if (!is_terminator(&fw.wlan.tx_queue[i], iter))
val |= BIT(i);
DBG("Q:%d l:%d h:%p t:%p cu:%p it:%p ct:%x st:%x\n", i, queue_len(&fw.wlan.tx_queue[i]),
fw.wlan.tx_queue[i].head, fw.wlan.tx_queue[i].terminator,
get_wlan_txq_addr(i), iter, iter->ctrl, iter->status);
}
fw.wlan.soft_int |= AR9170_MAC_INT_RXC | AR9170_MAC_INT_TXC |
AR9170_MAC_INT_RETRY_FAIL;
set(AR9170_MAC_REG_DMA_RXQ_ADDR, (uint32_t) fw.wlan.rx_queue.head);
wlan_trigger(val);
}
#else
static void wlan_mac_reset(void)
{
/* The driver takes care of reinitializing the device */
BUG("MAC RESET");
}
#endif /* CONFIG_CARL9170FW_FW_MAC_RESET */
void __cold wlan_timer(void)
{
unsigned int cached_mac_reset;
cached_mac_reset = fw.wlan.mac_reset;
/* TX Queue Hang check */
wlan_check_hang();
/* RX Overrun check */
wlan_check_rx_overrun();
if (unlikely(fw.wlan.mac_reset >= CARL9170_MAC_RESET_RESET)) {
wlan_mac_reset();
fw.wlan.mac_reset = CARL9170_MAC_RESET_OFF;
} else {
if (fw.wlan.mac_reset && cached_mac_reset == fw.wlan.mac_reset)
fw.wlan.mac_reset--;
}
}
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* WakeUp on WLAN functions
*
* Copyright 2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "shared/phy.h"
#include "timer.h"
#include "wl.h"
#include "printf.h"
#include "rf.h"
#include "wol.h"
#include "linux/ieee80211.h"
#ifdef CONFIG_CARL9170FW_WOL
void wol_cmd(const struct carl9170_wol_cmd *cmd)
{
memcpy(&fw.wol.cmd, cmd, sizeof(cmd));
}
void wol_prepare(void)
{
/* set MAC filter */
memcpy((void *)AR9170_MAC_REG_MAC_ADDR_L, fw.wol.cmd.mac, 6);
memcpy((void *)AR9170_MAC_REG_BSSID_L, fw.wol.cmd.bssid, 6);
set(AR9170_MAC_REG_RX_CONTROL, AR9170_MAC_RX_CTRL_DEAGG);
/* set filter policy to: discard everything */
fw.wlan.rx_filter = CARL9170_RX_FILTER_EVERYTHING;
/* reenable rx dma */
wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
/* initialize the last_beacon timer */
fw.wol.last_null = fw.wol.last_beacon = get_clock_counter();
}
#ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS
static bool wlan_rx_wol_magic_packet(const struct ieee80211_hdr *hdr, const unsigned int len)
{
const unsigned char *data, *end, *mac;
unsigned int found = 0;
/*
* LIMITATION:
* We can only scan the first AR9170_BLOCK_SIZE [=~320] bytes
* for MAGIC patterns!
*/
mac = (const unsigned char *) AR9170_MAC_REG_MAC_ADDR_L;
data = (u8 *)((unsigned long)hdr + ieee80211_hdrlen(hdr->frame_control));
end = (u8 *)((unsigned long)hdr + len);
/*
* scan for standard WOL Magic frame
*
* "A physical WakeOnLAN (Magic Packet) will look like this:
* ---------------------------------------------------------------
* | Synchronization Stream | Target MAC | Password (optional) |
* | 6 octets | 96 octets | 0, 4 or 6 |
* ---------------------------------------------------------------
*
* The Synchronization Stream is defined as 6 bytes of FFh.
* The Target MAC block contains 16 duplications of the IEEEaddress
* of the target, with no breaks or interruptions.
*
* The Password field is optional, but if present, contains either
* 4 bytes or 6 bytes. The WakeOnLAN dissector was implemented to
* dissect the password, if present, according to the command-line
* format that ether-wake uses, therefore, if a 4-byte password is
* present, it will be dissected as an IPv4 address and if a 6-byte
* password is present, it will be dissected as an Ethernet address.
*
* <http://wiki.wireshark.org/WakeOnLAN>
*/
while (data < end) {
if (found >= 6) {
if (*data == mac[found % 6])
found++;
else
found = 0;
}
/* previous check might reset found counter */
if (found < 6) {
if (*data == 0xff)
found++;
else
found = 0;
}
if (found == (6 + 16 * 6))
return true;
data++;
}
return false;
}
static void wlan_wol_connect_callback(void __unused *dummy, bool success)
{
if (success)
fw.wol.lost_null = 0;
else
fw.wol.lost_null++;
}
static void wlan_wol_connection_monitor(void)
{
struct carl9170_tx_null_superframe *nullf = &dma_mem.reserved.cmd.null;
struct ieee80211_hdr *null = (struct ieee80211_hdr *) &nullf->f.null;
if (!fw.wlan.fw_desc_available)
return;
memset(nullf, 0, sizeof(*nullf));
nullf->s.len = sizeof(struct carl9170_tx_superdesc) +
sizeof(struct ar9170_tx_hwdesc) +
sizeof(struct ieee80211_hdr);
nullf->s.ri[0].tries = 3;
nullf->s.assign_seq = true;
nullf->s.queue = AR9170_TXQ_VO;
nullf->f.hdr.length = sizeof(struct ieee80211_hdr) + FCS_LEN;
nullf->f.hdr.mac.backoff = 1;
nullf->f.hdr.mac.hw_duration = 1;
nullf->f.hdr.mac.erp_prot = AR9170_TX_MAC_PROT_RTS;
nullf->f.hdr.phy.modulation = AR9170_TX_PHY_MOD_OFDM;
nullf->f.hdr.phy.bandwidth = AR9170_TX_PHY_BW_20MHZ;
nullf->f.hdr.phy.chains = AR9170_TX_PHY_TXCHAIN_2;
nullf->f.hdr.phy.tx_power = 29; /* 14.5 dBm */
nullf->f.hdr.phy.mcs = AR9170_TXRX_PHY_RATE_OFDM_6M;
/* format outgoing nullfunc */
null->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
IEEE80211_STYPE_NULLFUNC | IEEE80211_FCTL_TODS);
memcpy(null->addr1, fw.wol.cmd.bssid, 6);
memcpy(null->addr2, fw.wol.cmd.mac, 6);
memcpy(null->addr3, fw.wol.cmd.bssid, 6);
wlan_tx_fw(&nullf->s, wlan_wol_connect_callback);
}
static bool wlan_rx_wol_disconnect(const unsigned int rx_filter,
const struct ieee80211_hdr *hdr,
const unsigned int __unused len)
{
const unsigned char *bssid;
bssid = (const unsigned char *) AR9170_MAC_REG_BSSID_L;
/* should catch both broadcast and unicast MLMEs */
if (!(rx_filter & CARL9170_RX_FILTER_OTHER_RA)) {
if (ieee80211_is_deauth(hdr->frame_control) ||
ieee80211_is_disassoc(hdr->frame_control))
return true;
}
if (ieee80211_is_beacon(hdr->frame_control) &&
compare_ether_address(hdr->addr3, bssid)) {
fw.wol.last_beacon = get_clock_counter();
}
return false;
}
#endif /* CARL9170FW_WOL_NL80211_TRIGGERS */
#ifdef CONFIG_CARL9170FW_WOL_PROBE_REQUEST
/*
* Note: CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID is not a real
* string. We have to be careful not to add a \0 at the end.
*/
static const struct {
u8 ssid_ie;
u8 ssid_len;
u8 ssid[sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1];
} __packed probe_req = {
.ssid_ie = WLAN_EID_SSID,
.ssid_len = sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1,
.ssid = CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID,
};
static bool wlan_rx_wol_probe_ssid(const struct ieee80211_hdr *hdr, const unsigned int len)
{
const unsigned char *data, *end, *scan = (void *) &probe_req;
/*
* IEEE 802.11-2007 7.3.2.1 specifies that the SSID is no
* longer than 32 octets.
*/
BUILD_BUG_ON((sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1) > 32);
if (ieee80211_is_probe_req(hdr->frame_control)) {
unsigned int i;
end = (u8 *)((unsigned long)hdr + len);
/*
* The position of the SSID information element inside
* a probe request frame is more or less "fixed".
*/
data = (u8 *)((struct ieee80211_mgmt *)hdr)->u.probe_req.variable;
for (i = 0; i < (unsigned int)(probe_req.ssid_len + 1); i++) {
if (data > end || scan[i] != data[i])
return false;
}
return true;
}
return false;
}
#endif /* CONFIG_CARL9170FW_WOL_PROBE_REQUEST */
void wol_rx(const unsigned int rx_filter __unused, const struct ieee80211_hdr *hdr __unused, const unsigned int len __unused)
{
#ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS
/* Disconnect is always enabled */
if (fw.wol.cmd.flags & CARL9170_WOL_DISCONNECT &&
rx_filter & CARL9170_RX_FILTER_MGMT)
fw.wol.wake_up |= wlan_rx_wol_disconnect(rx_filter, hdr, len);
if (fw.wol.cmd.flags & CARL9170_WOL_MAGIC_PKT &&
rx_filter & CARL9170_RX_FILTER_DATA)
fw.wol.wake_up |= wlan_rx_wol_magic_packet(hdr, len);
#endif /* CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS */
#ifdef CONFIG_CARL9170FW_WOL_PROBE_REQUEST
if (rx_filter & CARL9170_RX_FILTER_MGMT)
fw.wol.wake_up |= wlan_rx_wol_probe_ssid(hdr, len);
#endif /* CONFIG_CARL9170FW_WOL_PROBE_REQUEST */
}
void wol_janitor(void)
{
if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) {
#ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS
if (fw.wol.cmd.flags & CARL9170_WOL_DISCONNECT) {
/*
* connection lost after 10sec without receiving
* a beacon
*/
if (is_after_msecs(fw.wol.last_beacon, 10000))
fw.wol.wake_up |= true;
if (fw.wol.cmd.null_interval &&
is_after_msecs(fw.wol.last_null, fw.wol.cmd.null_interval))
wlan_wol_connection_monitor();
if (fw.wol.lost_null >= 5)
fw.wol.wake_up |= true;
}
#endif /* CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS */
if (fw.wol.wake_up) {
fw.suspend_mode = CARL9170_AWAKE_HOST;
set(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE);
}
}
}
#else
#endif /* CONFIG_CARL9170FW_WOL */
menu "USB Firmware Configuration Settings"
config CARL9170FW_USB_STANDARD_CMDS
def_bool y
prompt "Basic USB Interface"
---help---
Allows the device to be queried about Standard USB 2.0 Device
Description Descriptors.
Say Y, unless you don't care if lsusb -v fails.
config CARL9170FW_USB_UP_STREAM
def_bool y
prompt "USB Upload Stream"
---help---
This features allows the USB silicon to combine small, single
frames into bigger transfers. This can help to reduce
some per-transfer overhead in the application.
Say Y, unless you have experienced strange rx corruptions.
config CARL9170FW_USB_DN_STREAM
def_bool n
prompt "USB Download Stream"
config CARL9170FW_DEBUG_USB
def_bool y
prompt "Pass debug messages through USB transport"
---help---
Report all firmware messages through the USB transport.
But there is a catch: In case of a BUG, the USB transport
needs to be functional, otherwise the application won't
receive anything.
Say Y.
endmenu
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* Copyright (c) 2000-2005 ZyDAS Technology Corporation
* Copyright (c) 2007-2009 Atheros Communications, Inc.
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "printf.h"
#include "rom.h"
#include "usb_fifo.h"
/* TODO / TOTEST */
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
static inline void usb_ep_map(const uint8_t ep, const uint8_t map)
{
setb(AR9170_USB_REG_EP_MAP + (ep - 1), map);
}
static inline void usb_fifo_map(const uint8_t fifo, const uint8_t map)
{
setb(AR9170_USB_REG_FIFO_MAP + (fifo - 1), map);
}
static inline void usb_fifo_config(const uint8_t fifo, const uint8_t cfg)
{
setb(AR9170_USB_REG_FIFO_CONFIG + (fifo - 1), cfg);
}
static inline void usb_ep_packet_size_hi(const uint8_t ep, const uint8_t dir,
const uint16_t size)
{
setb(AR9170_USB_REG_EP_IN_MAX_SIZE_HIGH + (((dir * 0x20) + ep) << 1),
(size >> 8) & 0xf);
}
static inline void usb_ep_packet_size_lo(const uint8_t ep, const uint8_t dir,
const uint16_t size)
{
setb(AR9170_USB_REG_EP_IN_MAX_SIZE_LOW + (((dir * 0x20) + ep) << 1),
size & 0xff);
}
static void usb_ep_in_highbandset(const uint8_t ep, const uint8_t dir,
const uint16_t size)
{
andb(AR9170_USB_REG_EP_IN_MAX_SIZE_HIGH + (ep << 1), ~(BIT(6) | BIT(5)));
switch (dir) {
case DIRECTION_IN:
setb(AR9170_USB_REG_EP_IN_MAX_SIZE_HIGH + (ep << 1),
((size >> 11) + 1) << 5);
break;
case DIRECTION_OUT:
default:
break;
}
}
/*
* vUsbFIFO_EPxCfg_HS(void)
* Description:
* 1. Configure the FIFO and EPx map
* input: none
* output: none
*/
void usb_init_highspeed_fifo_cfg(void)
{
int i;
/* EP 1 */
usb_ep_map(1, HS_C1_I0_A0_EP1_MAP);
usb_fifo_map(HS_C1_I0_A0_EP1_FIFO_START, HS_C1_I0_A0_EP1_FIFO_MAP);
usb_fifo_config(HS_C1_I0_A0_EP1_FIFO_START, HS_C1_I0_A0_EP1_FIFO_CONFIG);
for (i = HS_C1_I0_A0_EP1_FIFO_START + 1;
i < HS_C1_I0_A0_EP1_FIFO_START + HS_C1_I0_A0_EP1_FIFO_NO; i++) {
usb_fifo_config(i, (HS_C1_I0_A0_EP1_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(1, HS_C1_I0_A0_EP1_DIRECTION, (HS_C1_I0_A0_EP1_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(1, HS_C1_I0_A0_EP1_DIRECTION, (HS_C1_I0_A0_EP1_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(1, HS_C1_I0_A0_EP1_DIRECTION, HS_C1_I0_A0_EP1_MAX_PACKET);
/* EP 2 */
usb_ep_map(2, HS_C1_I0_A0_EP2_MAP);
usb_fifo_map(HS_C1_I0_A0_EP2_FIFO_START, HS_C1_I0_A0_EP2_FIFO_MAP);
usb_fifo_config(HS_C1_I0_A0_EP2_FIFO_START, HS_C1_I0_A0_EP2_FIFO_CONFIG);
for (i = HS_C1_I0_A0_EP2_FIFO_START + 1;
i < HS_C1_I0_A0_EP2_FIFO_START + HS_C1_I0_A0_EP2_FIFO_NO; i++) {
usb_fifo_config(i, (HS_C1_I0_A0_EP2_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(2, HS_C1_I0_A0_EP2_DIRECTION, (HS_C1_I0_A0_EP2_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(2, HS_C1_I0_A0_EP2_DIRECTION, (HS_C1_I0_A0_EP2_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(2, HS_C1_I0_A0_EP2_DIRECTION, HS_C1_I0_A0_EP2_MAX_PACKET);
/* EP 3 */
usb_ep_map(3, HS_C1_I0_A0_EP3_MAP);
usb_fifo_map(HS_C1_I0_A0_EP3_FIFO_START, HS_C1_I0_A0_EP3_FIFO_MAP);
usb_fifo_config(HS_C1_I0_A0_EP3_FIFO_START, HS_C1_I0_A0_EP3_FIFO_CONFIG);
for (i = HS_C1_I0_A0_EP3_FIFO_START + 1;
i < HS_C1_I0_A0_EP3_FIFO_START + HS_C1_I0_A0_EP3_FIFO_NO; i++) {
usb_fifo_config(i, (HS_C1_I0_A0_EP3_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(3, HS_C1_I0_A0_EP3_DIRECTION, (HS_C1_I0_A0_EP3_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(3, HS_C1_I0_A0_EP3_DIRECTION, (HS_C1_I0_A0_EP3_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(3, HS_C1_I0_A0_EP3_DIRECTION, HS_C1_I0_A0_EP3_MAX_PACKET);
/* EP 4 */
usb_ep_map(4, HS_C1_I0_A0_EP4_MAP);
usb_fifo_map(HS_C1_I0_A0_EP4_FIFO_START, HS_C1_I0_A0_EP4_FIFO_MAP);
usb_fifo_config(HS_C1_I0_A0_EP4_FIFO_START, HS_C1_I0_A0_EP4_FIFO_CONFIG);
for (i = HS_C1_I0_A0_EP4_FIFO_START + 1;
i < HS_C1_I0_A0_EP4_FIFO_START + HS_C1_I0_A0_EP4_FIFO_NO; i++) {
usb_fifo_config(i, (HS_C1_I0_A0_EP4_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(4, HS_C1_I0_A0_EP4_DIRECTION, (HS_C1_I0_A0_EP4_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(4, HS_C1_I0_A0_EP4_DIRECTION, (HS_C1_I0_A0_EP4_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(4, HS_C1_I0_A0_EP4_DIRECTION, HS_C1_I0_A0_EP4_MAX_PACKET);
}
void usb_init_fullspeed_fifo_cfg(void)
{
int i;
/* EP 1 */
usb_ep_map(1, FS_C1_I0_A0_EP1_MAP);
usb_fifo_map(FS_C1_I0_A0_EP1_FIFO_START, FS_C1_I0_A0_EP1_FIFO_MAP);
usb_fifo_config(FS_C1_I0_A0_EP1_FIFO_START, FS_C1_I0_A0_EP1_FIFO_CONFIG);
for (i = FS_C1_I0_A0_EP1_FIFO_START + 1;
i < FS_C1_I0_A0_EP1_FIFO_START + FS_C1_I0_A0_EP1_FIFO_NO; i++) {
usb_fifo_config(i, (FS_C1_I0_A0_EP1_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(1, FS_C1_I0_A0_EP1_DIRECTION, (FS_C1_I0_A0_EP1_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(1, FS_C1_I0_A0_EP1_DIRECTION, (FS_C1_I0_A0_EP1_MAX_PACKET & 0x7ff));
/* ``.JWEI 2003/04/29 */
usb_ep_in_highbandset(1, FS_C1_I0_A0_EP1_DIRECTION, FS_C1_I0_A0_EP1_MAX_PACKET);
/* EP 2 */
usb_ep_map(2, FS_C1_I0_A0_EP2_MAP);
usb_fifo_map(FS_C1_I0_A0_EP2_FIFO_START, FS_C1_I0_A0_EP2_FIFO_MAP);
usb_fifo_config(FS_C1_I0_A0_EP2_FIFO_START, FS_C1_I0_A0_EP2_FIFO_CONFIG);
for (i = FS_C1_I0_A0_EP2_FIFO_START + 1;
i < FS_C1_I0_A0_EP2_FIFO_START + FS_C1_I0_A0_EP2_FIFO_NO; i++) {
usb_fifo_config(i, (FS_C1_I0_A0_EP2_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(2, FS_C1_I0_A0_EP2_DIRECTION, (FS_C1_I0_A0_EP2_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(2, FS_C1_I0_A0_EP2_DIRECTION, (FS_C1_I0_A0_EP2_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(2, FS_C1_I0_A0_EP2_DIRECTION, FS_C1_I0_A0_EP2_MAX_PACKET);
/* EP 3 */
usb_ep_map(3, FS_C1_I0_A0_EP3_MAP);
usb_fifo_map(FS_C1_I0_A0_EP3_FIFO_START, FS_C1_I0_A0_EP3_FIFO_MAP);
usb_fifo_config(FS_C1_I0_A0_EP3_FIFO_START, FS_C1_I0_A0_EP3_FIFO_CONFIG);
for (i = FS_C1_I0_A0_EP3_FIFO_START + 1;
i < FS_C1_I0_A0_EP3_FIFO_START + FS_C1_I0_A0_EP3_FIFO_NO; i++) {
usb_fifo_config(i, (FS_C1_I0_A0_EP3_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(3, FS_C1_I0_A0_EP3_DIRECTION, (FS_C1_I0_A0_EP3_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(3, FS_C1_I0_A0_EP3_DIRECTION, (FS_C1_I0_A0_EP3_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(3, FS_C1_I0_A0_EP3_DIRECTION, FS_C1_I0_A0_EP3_MAX_PACKET);
/* EP 4 */
usb_ep_map(4, FS_C1_I0_A0_EP4_MAP);
usb_fifo_map(FS_C1_I0_A0_EP4_FIFO_START, FS_C1_I0_A0_EP4_FIFO_MAP);
usb_fifo_config(FS_C1_I0_A0_EP4_FIFO_START, FS_C1_I0_A0_EP4_FIFO_CONFIG);
for (i = FS_C1_I0_A0_EP4_FIFO_START + 1;
i < FS_C1_I0_A0_EP4_FIFO_START + FS_C1_I0_A0_EP4_FIFO_NO; i++) {
usb_fifo_config(i, (FS_C1_I0_A0_EP4_FIFO_CONFIG & (~BIT(7))));
}
usb_ep_packet_size_hi(4, FS_C1_I0_A0_EP4_DIRECTION, (FS_C1_I0_A0_EP4_MAX_PACKET & 0x7ff));
usb_ep_packet_size_lo(4, FS_C1_I0_A0_EP4_DIRECTION, (FS_C1_I0_A0_EP4_MAX_PACKET & 0x7ff));
usb_ep_in_highbandset(4, FS_C1_I0_A0_EP4_DIRECTION, FS_C1_I0_A0_EP4_MAX_PACKET);
}
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* Copyright (c) 2000-2005 ZyDAS Technology Corporation
* Copyright (c) 2007-2009 Atheros Communications, Inc.
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "shared/phy.h"
#include "hostif.h"
#include "printf.h"
#include "timer.h"
#include "rom.h"
#include "wl.h"
#include "wol.h"
#ifdef CONFIG_CARL9170FW_DEBUG_USB
void usb_putc(const char c)
{
fw.usb.put_buffer[fw.usb.put_index++] = (uint8_t) c;
if (fw.usb.put_index == CARL9170_MAX_CMD_PAYLOAD_LEN || c == '\0') {
fw.usb.put_buffer[fw.usb.put_index] = 0;
send_cmd_to_host(__roundup(fw.usb.put_index, 4),
CARL9170_RSP_TEXT, fw.usb.put_index,
fw.usb.put_buffer);
fw.usb.put_index = 0;
}
}
void usb_print_hex_dump(const void *buf, int len)
{
unsigned int offset = 0, block = 0;
while (len > 0) {
block = min(__roundup(len, 4), CARL9170_MAX_CMD_PAYLOAD_LEN);
send_cmd_to_host(block, CARL9170_RSP_HEXDUMP, len,
(const uint8_t *) buf + offset);
offset += block;
len -= block;
}
}
#endif /* CONFIG_CARL9170FW_DEBUG_USB */
/* grab a buffer from the interrupt in queue ring-buffer */
static struct carl9170_rsp *get_int_buf(void)
{
struct carl9170_rsp *tmp;
/* fetch the _oldest_ buffer from the ring */
tmp = &fw.usb.int_buf[fw.usb.int_tail_index];
/* assign a unique sequence for every response/trap */
tmp->hdr.seq = fw.usb.int_tail_index;
fw.usb.int_tail_index++;
fw.usb.int_tail_index %= CARL9170_INT_RQ_CACHES;
if (fw.usb.int_pending != CARL9170_INT_RQ_CACHES)
fw.usb.int_pending++;
return tmp;
}
/* Pop up data from Interrupt IN Queue to USB Response buffer */
static struct carl9170_rsp *dequeue_int_buf(unsigned int space)
{
struct carl9170_rsp *tmp = NULL;
if (fw.usb.int_pending > 0) {
tmp = &fw.usb.int_buf[fw.usb.int_head_index];
if ((unsigned int)(tmp->hdr.len + 8) > space)
return NULL;
fw.usb.int_head_index++;
fw.usb.int_head_index %= CARL9170_INT_RQ_CACHES;
fw.usb.int_pending--;
}
return tmp;
}
static void usb_data_in(void)
{
}
static void usb_reg_out(void)
{
uint32_t *regaddr = (uint32_t *) &dma_mem.reserved.cmd;
uint16_t usbfifolen, i;
usb_reset_out();
usbfifolen = getb(AR9170_USB_REG_EP4_BYTE_COUNT_LOW) |
getb(AR9170_USB_REG_EP4_BYTE_COUNT_HIGH) << 8;
if (usbfifolen & 0x3)
usbfifolen = (usbfifolen >> 2) + 1;
else
usbfifolen = usbfifolen >> 2;
for (i = 0; i < usbfifolen; i++)
*regaddr++ = get(AR9170_USB_REG_EP4_DATA);
handle_cmd(get_int_buf());
usb_trigger_in();
}
static void usb_status_in(void)
{
struct carl9170_rsp *rsp;
unsigned int rem, tlen, elen;
if (!fw.usb.int_desc_available)
return ;
fw.usb.int_desc_available = 0;
rem = AR9170_BLOCK_SIZE - AR9170_INT_MAGIC_HEADER_SIZE;
tlen = AR9170_INT_MAGIC_HEADER_SIZE;
usb_reset_in();
while (fw.usb.int_pending) {
rsp = dequeue_int_buf(rem);
if (!rsp)
break;
elen = rsp->hdr.len + 4;
memcpy(DESC_PAYLOAD_OFF(fw.usb.int_desc, tlen), rsp, elen);
rem -= elen;
tlen += elen;
}
if (tlen == AR9170_INT_MAGIC_HEADER_SIZE) {
DBG("attempted to send an empty int response!\n");
goto reclaim;
}
fw.usb.int_desc->ctrl = AR9170_CTRL_FS_BIT | AR9170_CTRL_LS_BIT;
fw.usb.int_desc->totalLen = tlen;
fw.usb.int_desc->dataSize = tlen;
/* Put to UpQ */
dma_put(&fw.pta.up_queue, fw.usb.int_desc);
/* Trigger PTA UP DMA */
set(AR9170_PTA_REG_UP_DMA_TRIGGER, 1);
usb_trigger_out();
return ;
reclaim:
/* TODO: not sure what to do here */
fw.usb.int_desc_available = 1;
}
void send_cmd_to_host(const uint8_t len, const uint8_t type,
const uint8_t ext, const uint8_t *body)
{
struct carl9170_cmd *resp;
#ifdef CONFIG_CARL9170FW_DEBUG
if (unlikely(len > sizeof(resp->data))) {
DBG("CMD too long:%x %d\n", type, len);
return ;
}
/* Element length must be a multiple of 4. */
if (unlikely(len & 0x3)) {
DBG("CMD length not mult. of 4:%x %d\n", type, len);
return ;
}
#endif /* CONFIG_CARL9170FW_DEBUG */
resp = (struct carl9170_cmd *) get_int_buf();
if (unlikely(resp == NULL)) {
/* not very helpful for NON UART users */
DBG("out of msg buffers\n");
return ;
}
resp->hdr.len = len;
resp->hdr.cmd = type;
resp->hdr.ext = ext;
memcpy(resp->data, body, len);
usb_trigger_in();
}
/* Turn off ADDA/RF power, PLL */
static void turn_power_off(void)
{
set(AR9170_PHY_REG_ACTIVE, AR9170_PHY_ACTIVE_DIS);
set(AR9170_PHY_REG_ADC_CTL, 0xa0000000 |
AR9170_PHY_ADC_CTL_OFF_PWDADC | AR9170_PHY_ADC_CTL_OFF_PWDDAC);
/* This will also turn-off the LEDs */
set(AR9170_GPIO_REG_PORT_DATA, 0);
set(AR9170_GPIO_REG_PORT_TYPE, 0xf);
set(AR9170_PWR_REG_BASE, 0x40021);
set(AR9170_MAC_REG_DMA_TRIGGER, 0);
andl(AR9170_USB_REG_DMA_CTL, ~(AR9170_USB_DMA_CTL_ENABLE_TO_DEVICE |
AR9170_USB_DMA_CTL_ENABLE_FROM_DEVICE |
AR9170_USB_DMA_CTL_UP_PACKET_MODE |
AR9170_USB_DMA_CTL_DOWN_STREAM));
/* Do a software reset to PTA component */
orl(AR9170_PTA_REG_DMA_MODE_CTRL, AR9170_PTA_DMA_MODE_CTRL_RESET);
andl(AR9170_PTA_REG_DMA_MODE_CTRL, ~AR9170_PTA_DMA_MODE_CTRL_RESET);
orl(AR9170_PTA_REG_DMA_MODE_CTRL, AR9170_PTA_DMA_MODE_CTRL_DISABLE_USB);
set(AR9170_MAC_REG_POWER_STATE_CTRL,
AR9170_MAC_POWER_STATE_CTRL_RESET);
/* Reset USB FIFO */
set(AR9170_PWR_REG_RESET, AR9170_PWR_RESET_COMMIT_RESET_MASK |
AR9170_PWR_RESET_DMA_MASK |
AR9170_PWR_RESET_WLAN_MASK);
set(AR9170_PWR_REG_RESET, 0x0);
clock_set(AHB_20_22MHZ, false);
set(AR9170_PWR_REG_PLL_ADDAC, 0x5163); /* 0x502b; */
set(AR9170_PHY_REG_ADC_SERIAL_CTL, AR9170_PHY_ADC_SCTL_SEL_EXTERNAL_RADIO);
set(0x1c589c, 0); /* 7-0 */
set(0x1c589c, 0); /* 15-8 */
set(0x1c589c, 0); /* 23-16 */
set(0x1c589c, 0); /* 31- */
set(0x1c589c, 0); /* 39- */
set(0x1c589c, 0); /* 47- */
set(0x1c589c, 0); /* 55- */
set(0x1c589c, 0xf8); /* 63- */
set(0x1c589c, 0x27); /* 0x24; 71- modified */
set(0x1c589c, 0xf9); /* 79- */
set(0x1c589c, 0x90); /* 87- */
set(0x1c589c, 0x04); /* 95- */
set(0x1c589c, 0x48); /* 103- */
set(0x1c589c, 0x19); /* 0; 111- modified */
set(0x1c589c, 0); /* 119- */
set(0x1c589c, 0); /* 127- */
set(0x1c589c, 0); /* 135- */
set(0x1c589c, 0); /* 143- */
set(0x1c589c, 0); /* 151- */
set(0x1c589c, 0x70); /* 159- */
set(0x1c589c, 0x0c); /* 167- */
set(0x1c589c, 0); /* 175- */
set(0x1c589c, 0); /* 183-176 */
set(0x1c589c, 0); /* 191-184 */
set(0x1c589c, 0); /* 199- */
set(0x1c589c, 0); /* 207- */
set(0x1c589c, 0); /* 215- */
set(0x1c589c, 0); /* 223- */
set(0x1c589c, 0); /* 231- */
set(0x1c58c4, 0); /* 233- 232 */
set(AR9170_PHY_REG_ADC_SERIAL_CTL, AR9170_PHY_ADC_SCTL_SEL_INTERNAL_ADDAC);
}
static void disable_watchdog(void)
{
if (!fw.watchdog_enable)
return;
/* write watchdog magic pattern for suspend */
andl(AR9170_PWR_REG_WATCH_DOG_MAGIC, 0xffff);
orl(AR9170_PWR_REG_WATCH_DOG_MAGIC, 0x98760000);
/* Disable watchdog */
set(AR9170_TIMER_REG_WATCH_DOG, 0xffff);
}
void __noreturn reboot(void)
{
disable_watchdog();
/* Turn off power */
turn_power_off();
/* clean bootloader workspace */
memset(&dma_mem, 0, sizeof(dma_mem));
/* add by ygwei for work around USB PHY chirp sequence problem */
set(0x10f100, 0x12345678);
/* Jump to boot code */
jump_to_bootcode();
}
/* service USB events and re-enable USB interrupt */
static void usb_handler(uint8_t usb_interrupt_level1)
{
uint8_t usb_interrupt_level2;
if (usb_interrupt_level1 & BIT(5))
usb_data_in();
if (usb_interrupt_level1 & BIT(4))
usb_reg_out();
if (usb_interrupt_level1 & BIT(6))
usb_status_in();
if (usb_interrupt_level1 & BIT(0)) {
usb_interrupt_level2 = getb(AR9170_USB_REG_INTR_SOURCE_0);
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC0_SETUP)
usb_ep0setup();
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC0_IN)
usb_ep0tx();
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC0_OUT)
usb_ep0rx();
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC0_ABORT) {
/* Clear the command abort interrupt */
andb(AR9170_USB_REG_INTR_SOURCE_0, (uint8_t)
~AR9170_USB_INTR_SRC0_ABORT);
}
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC0_FAIL ||
fw.usb.ep0_action & CARL9170_EP0_STALL) {
/*
* transmission failure.
* stall ep 0
*/
setb(AR9170_USB_REG_CX_CONFIG_STATUS, BIT(2));
fw.usb.ep0_action &= ~CARL9170_EP0_STALL;
}
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC0_END ||
fw.usb.ep0_action & CARL9170_EP0_TRIGGER) {
/*
* transmission done.
* set DONE bit.
*/
setb(AR9170_USB_REG_CX_CONFIG_STATUS, BIT(0));
fw.usb.ep0_action &= ~CARL9170_EP0_TRIGGER;
}
}
if (usb_interrupt_level1 & BIT(7)) {
usb_interrupt_level2 = getb(AR9170_USB_REG_INTR_SOURCE_7);
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_RX0BYTE)
usb_data_out0Byte();
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_TX0BYTE)
usb_data_in0Byte();
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_RESET) {
usb_reset_ack();
reboot();
}
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_SUSPEND) {
usb_suspend_ack();
fw.suspend_mode = CARL9170_HOST_SUSPENDED;
#ifdef CONFIG_CARL9170FW_WOL
if (!(fw.usb.device_feature & USB_DEVICE_REMOTE_WAKEUP) ||
!fw.wol.cmd.flags) {
disable_watchdog();
/* GO_TO_SUSPEND stops the CPU clock too. */
orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND);
} else {
wol_prepare();
}
#else /* CONFIG_CARL9170FW_WOL */
disable_watchdog();
/* GO_TO_SUSPEND stops the CPU clock too. */
orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND);
#endif /* CONFIG_CARL9170FW_WOL */
}
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_RESUME) {
usb_resume_ack();
fw.suspend_mode = CARL9170_HOST_AWAKE;
set(AR9170_USB_REG_WAKE_UP, 0);
reboot();
}
}
}
void handle_usb(void)
{
uint8_t usb_interrupt_level1;
usb_interrupt_level1 = getb(AR9170_USB_REG_INTR_GROUP);
if (usb_interrupt_level1)
usb_handler(usb_interrupt_level1);
if (fw.usb.int_pending > 0)
usb_trigger_in();
}
void usb_timer(void)
{
}
/*
* carl9170 firmware - used by the ar9170 wireless device
*
* USB Controller
*
* Copyright (c) 2000-2005 ZyDAS Technology Corporation
* Copyright (c) 2007-2009 Atheros Communications, Inc.
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "carl9170.h"
#include "usb.h"
#include "printf.h"
#include "rom.h"
/*
* NB: The firmware has to write into these structures
* so don't try to make them "const".
*/
static struct ar9170_usb_config usb_config_highspeed = {
.cfg = {
.bLength = USB_DT_CONFIG_SIZE,
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength = cpu_to_le16(sizeof(usb_config_highspeed)),
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_CONFIG_ATT_ONE |
#ifdef CONFIG_CARL9170FW_WOL
USB_CONFIG_ATT_WAKEUP |
#endif /* CONFIG_CARL9170FW_WOL */
0,
.bMaxPower = 0xfa, /* 500 mA */
},
.intf = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = AR9170_USB_NUM_EXTRA_EP,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
.bInterfaceProtocol = 0,
.iInterface = 0,
},
.ep = {
{ /* EP 1 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT | AR9170_USB_EP_TX,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(512),
.bInterval = 0,
},
{ /* EP 2 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN | AR9170_USB_EP_RX,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(512),
.bInterval = 0,
},
{ /* EP 3 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN | AR9170_USB_EP_IRQ,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = 1,
},
{ /* EP 4 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT | AR9170_USB_EP_CMD,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = 1,
},
},
};
static struct ar9170_usb_config usb_config_fullspeed = {
.cfg = {
.bLength = USB_DT_CONFIG_SIZE,
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength = cpu_to_le16(sizeof(usb_config_fullspeed)),
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_CONFIG_ATT_ONE |
#ifdef CONFIG_CARL9170FW_WOL
USB_CONFIG_ATT_WAKEUP |
#endif /* CONFIG_CARL9170FW_WOL */
0,
.bMaxPower = 0xfa, /* 500 mA */
},
.intf = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = AR9170_USB_NUM_EXTRA_EP,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
.bInterfaceProtocol = 0,
.iInterface = 0,
},
.ep = {
{ /* EP 1 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT | AR9170_USB_EP_TX,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = 0,
},
{ /* EP 2 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN | AR9170_USB_EP_RX,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = 0,
},
{ /* EP 3 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN | AR9170_USB_EP_IRQ,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = 1,
},
{ /* EP 4 */
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT | AR9170_USB_EP_CMD,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = 1,
},
},
};
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
static void usb_reset_eps(void)
{
unsigned int i;
/* clear all EPs' toggle bit */
for (i = 1; i < __AR9170_USB_NUM_MAX_EP; i++) {
usb_set_input_ep_toggle(i);
usb_clear_input_ep_toggle(i);
}
/*
* NB: I've no idea why this cannot be integrated into the
* previous loop?
*/
for (i = 1; i < __AR9170_USB_NUM_MAX_EP; i++) {
usb_set_output_ep_toggle(i);
usb_clear_output_ep_toggle(i);
}
}
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
static void usb_pta_init(void)
{
unsigned int usb_dma_ctrl = 0;
/* Set PTA mode to USB */
andl(AR9170_PTA_REG_DMA_MODE_CTRL,
~AR9170_PTA_DMA_MODE_CTRL_DISABLE_USB);
/* Do a software reset to PTA component */
orl(AR9170_PTA_REG_DMA_MODE_CTRL, AR9170_PTA_DMA_MODE_CTRL_RESET);
andl(AR9170_PTA_REG_DMA_MODE_CTRL, ~AR9170_PTA_DMA_MODE_CTRL_RESET);
if (usb_detect_highspeed()) {
fw.usb.os_cfg_desc = &usb_config_fullspeed;
fw.usb.cfg_desc = &usb_config_highspeed;
/* 512 Byte DMA transfers */
usb_dma_ctrl |= AR9170_USB_DMA_CTL_HIGH_SPEED;
} else {
fw.usb.cfg_desc = &usb_config_fullspeed;
fw.usb.os_cfg_desc = &usb_config_highspeed;
}
#ifdef CONFIG_CARL9170FW_USB_UP_STREAM
# if (CONFIG_CARL9170FW_RX_FRAME_LEN == 4096)
usb_dma_ctrl |= AR9170_USB_DMA_CTL_UP_STREAM_4K;
# elif (CONFIG_CARL9170FW_RX_FRAME_LEN == 8192)
usb_dma_ctrl |= AR9170_USB_DMA_CTL_UP_STREAM_8K;
# elif (CONFIG_CARL9170FW_RX_FRAME_LEN == 16384)
usb_dma_ctrl |= AR9170_USB_DMA_CTL_UP_STREAM_16K;
# elif (CONFIG_CARL9170FW_RX_FRAME_LEN == 32768)
usb_dma_ctrl |= AR9170_USB_DMA_CTL_UP_STREAM_32K;
# else
# error "Invalid AR9170_RX_FRAME_LEN setting"
# endif
#else /* CONFIG_CARL9170FW_USB_UP_STREAM */
usb_dma_ctrl |= AR9170_USB_DMA_CTL_UP_PACKET_MODE;
#endif /* CONFIG_CARL9170FW_USB_UP_STREAM */
#ifdef CONFIG_CARL9170FW_USB_DOWN_STREAM
/* Enable down stream mode */
usb_dma_ctrl |= AR9170_USB_DMA_CTL_DOWN_STREAM;
#endif /* CONFIG_CARL9170FW_USB_DOWN_STREAM */
#ifdef CONFIG_CARL9170FW_USB_UP_STREAM
/* Set the up stream mode maximum aggregate number */
set(AR9170_USB_REG_MAX_AGG_UPLOAD, 4);
/*
* Set the up stream mode timeout value.
* NB: The vendor driver (otus) set 0x80?
*/
set(AR9170_USB_REG_UPLOAD_TIME_CTL, 0x80);
#endif /* CONFIG_CARL9170FW_USB_UP_STREAM */
/* Enable up stream and down stream */
usb_dma_ctrl |= AR9170_USB_DMA_CTL_ENABLE_TO_DEVICE |
AR9170_USB_DMA_CTL_ENABLE_FROM_DEVICE;
set(AR9170_USB_REG_DMA_CTL, usb_dma_ctrl);
}
void usb_init(void)
{
usb_pta_init();
fw.usb.config = 1;
/*
* The fw structure is always initialized with "0"
* during boot(); No need to waste precious bytes here.
*
* fw.usb.interface_setting = 0;
* fw.usb.alternate_interface_setting = 0;
* fw.usb.device_feature = 0;
*/
#ifdef CONFIG_CARL9170FW_WOL
fw.usb.device_feature |= USB_DEVICE_REMOTE_WAKEUP;
usb_enable_remote_wakeup();
#endif /* CONFIG_CARL9170FW_WOL */
}
#define GET_ARRAY(a, o) ((uint32_t *) (((unsigned long) data) + offset))
static void usb_ep0rx_data(const void *data, const unsigned int len)
{
unsigned int offset;
uint32_t value;
BUG_ON(len > AR9170_USB_EP_CTRL_MAX);
BUILD_BUG_ON(len > AR9170_USB_EP_CTRL_MAX);
for (offset = 0; offset < ((len + 3) & ~3); offset += 4) {
value = get(AR9170_USB_REG_EP0_DATA);
memcpy(GET_ARRAY(data, offset), &value,
min(len - offset, (unsigned int)4));
}
}
static int usb_ep0tx_data(const void *data, const unsigned int len)
{
unsigned int offset = 0, block, last_block = 0;
uint32_t value;
BUG_ON(len > AR9170_USB_EP_CTRL_MAX);
BUILD_BUG_ON(len > AR9170_USB_EP_CTRL_MAX);
block = min(len, (unsigned int) 4);
offset = 0;
while (offset < len) {
if (last_block != block || block < 4)
setb(AR9170_USB_REG_FIFO_SIZE, (1 << block) - 1);
memcpy(&value, GET_ARRAY(data, offset), block);
set(AR9170_USB_REG_EP0_DATA, value);
offset += block;
last_block = block = min(len - offset, (unsigned int) 4);
}
setb(AR9170_USB_REG_FIFO_SIZE, 0xf);
/* this will push the data to the host */
return 1;
}
#undef GET_ARRAY
#ifdef CONFIG_CARL9170FW_USB_STANDARD_CMDS
static int usb_get_status(const struct usb_ctrlrequest *ctrl)
{
__le16 status = cpu_to_le16(fw.usb.device_feature);
if ((ctrl->bRequestType & USB_DIR_MASK) != USB_DIR_IN)
return -1;
switch (ctrl->bRequestType & USB_RECIP_MASK) {
case USB_RECIP_DEVICE:
status &= cpu_to_le16(~USB_DEVICE_SELF_POWERED);
status &= cpu_to_le16(~USB_DEVICE_REMOTE_WAKEUP);
break;
case USB_RECIP_INTERFACE:
/* USB spec: This is reserved for future use. */
status = cpu_to_le16(0);
break;
case USB_RECIP_ENDPOINT:
case USB_RECIP_OTHER:
default:
break;
}
return usb_ep0tx_data((const void *) &status, sizeof(status));
}
static int usb_get_string_desc(const struct usb_ctrlrequest *ctrl)
{
const struct usb_string_descriptor *string_desc = NULL;
switch (le16_to_cpu(ctrl->wValue) & 0xff) {
case 0x00:
string_desc = (const struct usb_string_descriptor *)
rom.hw.usb.string0_desc;
break;
case 0x10:
string_desc = (const struct usb_string_descriptor *)
rom.hw.usb.string1_desc;
break;
case 0x20:
string_desc = (const struct usb_string_descriptor *)
rom.hw.usb.string2_desc;
break;
case 0x30:
string_desc = (const struct usb_string_descriptor *)
rom.hw.usb.string3_desc;
break;
default:
break;
}
if (string_desc)
return usb_ep0tx_data(string_desc, string_desc->bLength);
return -1;
}
static int usb_get_device_desc(const struct usb_ctrlrequest *ctrl __unused)
{
return usb_ep0tx_data(&rom.hw.usb.device_desc,
rom.hw.usb.device_desc.bLength);
}
static int usb_get_config_desc(const struct usb_ctrlrequest *ctrl __unused)
{
fw.usb.cfg_desc->cfg.bDescriptorType = USB_DT_CONFIG;
return usb_ep0tx_data(fw.usb.cfg_desc,
le16_to_cpu(fw.usb.cfg_desc->cfg.wTotalLength));
}
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
static int usb_get_otherspeed_desc(const struct usb_ctrlrequest *ctrl __unused)
{
fw.usb.os_cfg_desc->cfg.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG;
return usb_ep0tx_data(fw.usb.os_cfg_desc,
le16_to_cpu(fw.usb.os_cfg_desc->cfg.wTotalLength));
}
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
static int usb_get_qualifier_desc(const struct usb_ctrlrequest *ctrl __unused)
{
struct usb_qualifier_descriptor qual;
/*
* The qualifier descriptor shares some structural details
* with the main device descriptor.
*/
memcpy(&qual, &rom.hw.usb.device_desc, sizeof(qual));
/* (Re)-Initialize fields */
qual.bDescriptorType = USB_DT_DEVICE_QUALIFIER;
qual.bLength = sizeof(qual);
qual.bNumConfigurations = rom.hw.usb.device_desc.bNumConfigurations;
qual.bRESERVED = 0;
return usb_ep0tx_data(&qual, qual.bLength);
}
#define USB_CHECK_REQTYPE(ctrl, recip, dir) \
(((ctrl->bRequestType & USB_RECIP_MASK) != recip) || \
((ctrl->bRequestType & USB_DIR_MASK) != dir))
static int usb_get_descriptor(const struct usb_ctrlrequest *ctrl)
{
int status = -1;
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_IN))
return status;
switch (le16_to_cpu(ctrl->wValue) >> 8) {
case USB_DT_DEVICE:
status = usb_get_device_desc(ctrl);
break;
case USB_DT_CONFIG:
status = usb_get_config_desc(ctrl);
break;
case USB_DT_STRING:
status = usb_get_string_desc(ctrl);
break;
case USB_DT_INTERFACE:
break;
case USB_DT_ENDPOINT:
break;
case USB_DT_DEVICE_QUALIFIER:
status = usb_get_qualifier_desc(ctrl);
break;
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
case USB_DT_OTHER_SPEED_CONFIG:
status = usb_get_otherspeed_desc(ctrl);
break;
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
default:
break;
}
return status;
}
static int usb_get_configuration(const struct usb_ctrlrequest *ctrl)
{
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_IN))
return -1;
return usb_ep0tx_data(&fw.usb.config, 1);
}
static int usb_set_configuration(const struct usb_ctrlrequest *ctrl)
{
unsigned int config;
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_OUT))
return -1;
config = le16_to_cpu(ctrl->wValue);
switch (config) {
case 0:
/* Disable Device */
andb(AR9170_USB_REG_DEVICE_ADDRESS,
(uint8_t) ~(AR9170_USB_DEVICE_ADDRESS_CONFIGURE));
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
case 1:
fw.usb.config = config;
if (usb_detect_highspeed()) {
/* High Speed Configuration */
usb_init_highspeed_fifo_cfg();
} else {
/* Full Speed Configuration */
usb_init_fullspeed_fifo_cfg();
}
break;
default:
return -1;
}
/* usb_pta_init() ? */
usb_reset_eps();
orb(AR9170_USB_REG_DEVICE_ADDRESS,
(AR9170_USB_DEVICE_ADDRESS_CONFIGURE));
usb_enable_global_int();
usb_trigger_out();
return 1;
#else
default:
return -1;
}
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
}
static int usb_set_address(const struct usb_ctrlrequest *ctrl)
{
unsigned int address;
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_OUT))
return -1;
address = le16_to_cpu(ctrl->wValue);
/*
* The original firmware used 0x100 (which is, of course,
* too big to fit into uint8_t).
* However based on the available information (hw.h), BIT(7)
* is used as some sort of flag and should not be
* part of the device address.
*/
if (address >= BIT(7))
return -1;
setb(AR9170_USB_REG_DEVICE_ADDRESS, (uint8_t) address);
return 1;
}
static int usb_get_interface(const struct usb_ctrlrequest *ctrl)
{
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_INTERFACE, USB_DIR_IN))
return -1;
if (usb_configured() == false)
return -1;
switch (fw.usb.config) {
case 1:
break;
default:
return -1;
}
return usb_ep0tx_data(&fw.usb.alternate_interface_setting, 1);
}
static int usb_manipulate_feature(const struct usb_ctrlrequest *ctrl, bool __unused clear)
{
unsigned int feature;
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_OUT))
return -1;
if (usb_configured() == false)
return -1;
feature = le16_to_cpu(ctrl->wValue);
#ifdef CONFIG_CARL9170FW_WOL
if (feature & USB_DEVICE_REMOTE_WAKEUP) {
if (clear)
usb_disable_remote_wakeup();
else
usb_enable_remote_wakeup();
}
#endif /* CONFIG_CARL9170FW_WOL */
if (clear)
fw.usb.device_feature &= ~feature;
else
fw.usb.device_feature |= feature;
return 1;
}
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
static int usb_set_interface(const struct usb_ctrlrequest *ctrl)
{
unsigned int intf, alt_intf;
if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_INTERFACE, USB_DIR_OUT))
return -1;
if (usb_configured() == false)
return -1;
intf = le16_to_cpu(ctrl->wIndex);
alt_intf = le16_to_cpu(ctrl->wValue);
switch (intf) {
case 0:
if (alt_intf != fw.usb.cfg_desc->intf.bAlternateSetting)
return -1;
fw.usb.interface_setting = (uint8_t) intf;
fw.usb.alternate_interface_setting = (uint8_t) alt_intf;
if (usb_detect_highspeed())
usb_init_highspeed_fifo_cfg();
else
usb_init_fullspeed_fifo_cfg();
usb_reset_eps();
usb_enable_global_int();
usb_trigger_out();
return 1;
default:
return -1;
}
}
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
#endif /* CONFIG_CARL9170FW_USB_STANDARD_CMDS */
static int usb_standard_command(const struct usb_ctrlrequest *ctrl __unused)
{
int status = -1;
#ifdef CONFIG_CARL9170FW_USB_STANDARD_CMDS
switch (ctrl->bRequest) {
case USB_REQ_GET_STATUS:
status = usb_get_status(ctrl);
break;
case USB_REQ_CLEAR_FEATURE:
case USB_REQ_SET_FEATURE:
usb_manipulate_feature(ctrl, ctrl->bRequest == USB_REQ_CLEAR_FEATURE);
break;
case USB_REQ_SET_ADDRESS:
status = usb_set_address(ctrl);
break;
case USB_REQ_GET_DESCRIPTOR:
status = usb_get_descriptor(ctrl);
break;
case USB_REQ_SET_DESCRIPTOR:
break;
case USB_REQ_GET_CONFIGURATION:
status = usb_get_configuration(ctrl);
break;
case USB_REQ_SET_CONFIGURATION:
status = usb_set_configuration(ctrl);
break;
case USB_REQ_GET_INTERFACE:
status = usb_get_interface(ctrl);
break;
case USB_REQ_SET_INTERFACE:
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
status = usb_set_interface(ctrl);
#endif /* CONFIG_CARL9170FW_USB_MODESWITCH */
break;
case USB_REQ_SYNCH_FRAME:
break;
default:
break;
}
#endif /* CONFIG_CARL9170FW_USB_STANDARD_CMDS */
return status;
}
static int usb_class_command(const struct usb_ctrlrequest *ctrl __unused)
{
return -1;
}
static int usb_vendor_command(const struct usb_ctrlrequest *ctrl __unused)
{
/*
* Note: Firmware upload/boot is not implemented.
* It's impossible to replace the current image
* in place.
*/
return -1;
}
#undef USB_CHECK_TYPE
void usb_ep0setup(void)
{
struct usb_ctrlrequest ctrl;
int status = -1;
usb_ep0rx_data(&ctrl, sizeof(ctrl));
switch (ctrl.bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
status = usb_standard_command(&ctrl);
break;
case USB_TYPE_CLASS:
status = usb_class_command(&ctrl);
break;
case USB_TYPE_VENDOR:
status = usb_vendor_command(&ctrl);
break;
default:
break;
}
if (status < 0)
fw.usb.ep0_action |= CARL9170_EP0_STALL;
#ifdef CONFIG_CARL9170FW_USB_STANDARD_CMDS
if (status > 0)
fw.usb.ep0_action |= CARL9170_EP0_TRIGGER;
#endif /* CONFIG_CARL9170FW_USB_STANDARD_CMDS */
}
void usb_ep0rx(void)
{
if (BUG_ON(!fw.usb.ep0_txrx_buffer || !fw.usb.ep0_txrx_len))
return ;
usb_ep0rx_data(fw.usb.ep0_txrx_buffer, fw.usb.ep0_txrx_len);
fw.usb.ep0_txrx_pos = fw.usb.ep0_txrx_len;
}
void usb_ep0tx(void)
{
if (BUG_ON(!fw.usb.ep0_txrx_buffer || !fw.usb.ep0_txrx_len))
return ;
usb_ep0tx_data(fw.usb.ep0_txrx_buffer, fw.usb.ep0_txrx_len);
fw.usb.ep0_txrx_pos = fw.usb.ep0_txrx_len;
}
cmake_minimum_required(VERSION 2.8)
project(config)
#set(CMAKE_VERBOSE_MAKEFILE ON)
find_package(BISON REQUIRED)
find_package(FLEX REQUIRED)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../include/generated")
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../extra")
FIND_PACKAGE(GPERF REQUIRED)
BISON_TARGET(zconf zconf.y zconf.tab.c COMPILE_FLAGS "-l -b zconf -p zconf -t")
FLEX_TARGET(zconfscan zconf.l zconf.lex.c COMPILE_FLAGS "-Pzconf -L")
GPERF_TARGET(zconfhash zconf.gperf zconf.hash.c)
SET(zconf_deps ${FLEX_zconfscan_OUTPUTS} ${GPERF_zconfhash_OUTPUTS})
SET_SOURCE_FILES_PROPERTIES(${BISON_zconf_OUTPUTS}
PROPERTIES OBJECT_DEPENDS "${zconf_deps}")
set(conf_src conf.c ${BISON_zconf_OUTPUTS})
add_executable(conf ${conf_src})
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "lkc.h"
static void conf(struct menu *menu);
static void check_conf(struct menu *menu);
static void xfgets(char *str, int size, FILE *in);
enum input_mode {
oldaskconfig,
oldconfig,
allnoconfig,
allyesconfig,
allmodconfig,
alldefconfig,
randconfig,
defconfig,
savedefconfig,
listnewconfig,
oldnoconfig,
} input_mode = oldaskconfig;
static int indent = 1;
static int valid_stdin = 1;
static int conf_cnt;
static char line[128];
static struct menu *rootEntry;
static void print_help(struct menu *menu)
{
struct gstr help = str_new();
menu_get_ext_help(menu, &help);
printf("\n%s\n", str_get(&help));
str_free(&help);
}
static void strip(char *str)
{
char *p = str;
int l;
while ((isspace(*p)))
p++;
l = strlen(p);
if (p != str)
memmove(str, p, l + 1);
if (!l)
return;
p = str + l - 1;
while ((isspace(*p)))
*p-- = 0;
}
static void check_stdin(void)
{
if (!valid_stdin) {
printf(_("aborted!\n\n"));
printf(_("Console input/output is redirected. "));
printf(_("Run 'make config' to update configuration.\n\n"));
exit(1);
}
}
static int conf_askvalue(struct symbol *sym, const char *def)
{
enum symbol_type type = sym_get_type(sym);
if (!sym_has_value(sym))
printf(_("(NEW) "));
line[0] = '\n';
line[1] = 0;
if (!sym_is_changable(sym)) {
printf("%s\n", def);
line[0] = '\n';
line[1] = 0;
return 0;
}
switch (input_mode) {
case oldconfig:
if (sym_has_value(sym)) {
printf("%s\n", def);
return 0;
}
check_stdin();
/* fall through */
case oldaskconfig:
fflush(stdout);
xfgets(line, 128, stdin);
return 1;
default:
break;
}
switch (type) {
case S_INT:
case S_HEX:
case S_STRING:
printf("%s\n", def);
return 1;
default:
;
}
printf("%s", line);
return 1;
}
static int conf_string(struct menu *menu)
{
struct symbol *sym = menu->sym;
const char *def;
while (1) {
printf("%*s%s ", indent - 1, "", _(menu->prompt->text));
printf("(%s) ", sym->name);
def = sym_get_string_value(sym);
if (sym_get_string_value(sym))
printf("[%s] ", def);
if (!conf_askvalue(sym, def))
return 0;
switch (line[0]) {
case '\n':
break;
case '?':
/* print help */
if (line[1] == '\n') {
print_help(menu);
def = NULL;
break;
}
/* fall through */
default:
line[strlen(line)-1] = 0;
def = line;
}
if (def && sym_set_string_value(sym, def))
return 0;
}
}
static int conf_sym(struct menu *menu)
{
struct symbol *sym = menu->sym;
tristate oldval, newval;
while (1) {
printf("%*s%s ", indent - 1, "", _(menu->prompt->text));
if (sym->name)
printf("(%s) ", sym->name);
putchar('[');
oldval = sym_get_tristate_value(sym);
switch (oldval) {
case no:
putchar('N');
break;
case mod:
putchar('M');
break;
case yes:
putchar('Y');
break;
}
if (oldval != no && sym_tristate_within_range(sym, no))
printf("/n");
if (oldval != mod && sym_tristate_within_range(sym, mod))
printf("/m");
if (oldval != yes && sym_tristate_within_range(sym, yes))
printf("/y");
if (menu_has_help(menu))
printf("/?");
printf("] ");
if (!conf_askvalue(sym, sym_get_string_value(sym)))
return 0;
strip(line);
switch (line[0]) {
case 'n':
case 'N':
newval = no;
if (!line[1] || !strcmp(&line[1], "o"))
break;
continue;
case 'm':
case 'M':
newval = mod;
if (!line[1])
break;
continue;
case 'y':
case 'Y':
newval = yes;
if (!line[1] || !strcmp(&line[1], "es"))
break;
continue;
case 0:
newval = oldval;
break;
case '?':
goto help;
default:
continue;
}
if (sym_set_tristate_value(sym, newval))
return 0;
help:
print_help(menu);
}
}
static int conf_choice(struct menu *menu)
{
struct symbol *sym, *def_sym;
struct menu *child;
bool is_new;
sym = menu->sym;
is_new = !sym_has_value(sym);
if (sym_is_changable(sym)) {
conf_sym(menu);
sym_calc_value(sym);
switch (sym_get_tristate_value(sym)) {
case no:
return 1;
case mod:
return 0;
case yes:
break;
}
} else {
switch (sym_get_tristate_value(sym)) {
case no:
return 1;
case mod:
printf("%*s%s\n", indent - 1, "", _(menu_get_prompt(menu)));
return 0;
case yes:
break;
}
}
while (1) {
int cnt, def;
printf("%*s%s\n", indent - 1, "", _(menu_get_prompt(menu)));
def_sym = sym_get_choice_value(sym);
cnt = def = 0;
line[0] = 0;
for (child = menu->list; child; child = child->next) {
if (!menu_is_visible(child))
continue;
if (!child->sym) {
printf("%*c %s\n", indent, '*', _(menu_get_prompt(child)));
continue;
}
cnt++;
if (child->sym == def_sym) {
def = cnt;
printf("%*c", indent, '>');
} else
printf("%*c", indent, ' ');
printf(" %d. %s", cnt, _(menu_get_prompt(child)));
if (child->sym->name)
printf(" (%s)", child->sym->name);
if (!sym_has_value(child->sym))
printf(_(" (NEW)"));
printf("\n");
}
printf(_("%*schoice"), indent - 1, "");
if (cnt == 1) {
printf("[1]: 1\n");
goto conf_childs;
}
printf("[1-%d", cnt);
if (menu_has_help(menu))
printf("?");
printf("]: ");
switch (input_mode) {
case oldconfig:
if (!is_new) {
cnt = def;
printf("%d\n", cnt);
break;
}
check_stdin();
/* fall through */
case oldaskconfig:
fflush(stdout);
xfgets(line, 128, stdin);
strip(line);
if (line[0] == '?') {
print_help(menu);
continue;
}
if (!line[0])
cnt = def;
else if (isdigit(line[0]))
cnt = atoi(line);
else
continue;
break;
default:
break;
}
conf_childs:
for (child = menu->list; child; child = child->next) {
if (!child->sym || !menu_is_visible(child))
continue;
if (!--cnt)
break;
}
if (!child)
continue;
if (line[0] && line[strlen(line) - 1] == '?') {
print_help(child);
continue;
}
sym_set_choice_value(sym, child->sym);
for (child = child->list; child; child = child->next) {
indent += 2;
conf(child);
indent -= 2;
}
return 1;
}
}
static void conf(struct menu *menu)
{
struct symbol *sym;
struct property *prop;
struct menu *child;
if (!menu_is_visible(menu))
return;
sym = menu->sym;
prop = menu->prompt;
if (prop) {
const char *prompt;
switch (prop->type) {
case P_MENU:
if ((input_mode == listnewconfig ||
input_mode == oldnoconfig) &&
rootEntry != menu) {
check_conf(menu);
return;
}
/* fall through */
case P_COMMENT:
prompt = menu_get_prompt(menu);
if (prompt)
printf("%*c\n%*c %s\n%*c\n",
indent, '*',
indent, '*', _(prompt),
indent, '*');
default:
;
}
}
if (!sym)
goto conf_childs;
if (sym_is_choice(sym)) {
conf_choice(menu);
if (sym->curr.tri != mod)
return;
goto conf_childs;
}
switch (sym->type) {
case S_INT:
case S_HEX:
case S_STRING:
conf_string(menu);
break;
default:
conf_sym(menu);
break;
}
conf_childs:
if (sym)
indent += 2;
for (child = menu->list; child; child = child->next)
conf(child);
if (sym)
indent -= 2;
}
static void check_conf(struct menu *menu)
{
struct symbol *sym;
struct menu *child;
if (!menu_is_visible(menu))
return;
sym = menu->sym;
if (sym && !sym_has_value(sym)) {
if (sym_is_changable(sym) ||
(sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) {
if (input_mode == listnewconfig) {
if (sym->name && !sym_is_choice_value(sym)) {
printf("%s%s\n", CONFIG_, sym->name);
}
} else if (input_mode != oldnoconfig) {
if (!conf_cnt++)
printf(_("*\n* Restart config...\n*\n"));
rootEntry = menu_get_parent_menu(menu);
conf(rootEntry);
}
}
}
for (child = menu->list; child; child = child->next)
check_conf(child);
}
static struct option long_opts[] = {
{"askconfig", no_argument, NULL, oldaskconfig},
{"config", no_argument, NULL, oldconfig},
{"defconfig", optional_argument, NULL, defconfig},
{"savedefconfig", required_argument, NULL, savedefconfig},
{"allnoconfig", no_argument, NULL, allnoconfig},
{"allyesconfig", no_argument, NULL, allyesconfig},
{"allmodconfig", no_argument, NULL, allmodconfig},
{"alldefconfig", no_argument, NULL, alldefconfig},
{"randconfig", no_argument, NULL, randconfig},
{"listnewconfig", no_argument, NULL, listnewconfig},
{"noconfig", no_argument, NULL, oldnoconfig},
{NULL, 0, NULL, 0}
};
static void conf_usage(const char *progname)
{
printf("Usage: %s [option] <kconfig-file>\n", progname);
printf("[option] is _one_ of the following:\n");
printf(" --listnewconfig List new options\n");
printf(" --askconfig Start a new configuration using a line-oriented program\n");
printf(" --config Update a configuration using a provided .config as base\n");
printf(" --silentconfig Same as config, but quietly, additionally update deps\n");
printf(" --noconfig Same as silentconfig but set new symbols to no\n");
printf(" --defconfig <file> New config with default defined in <file>\n");
printf(" --savedefconfig <file> Save the minimal current configuration to <file>\n");
printf(" --allnoconfig New config where all options are answered with no\n");
printf(" --allyesconfig New config where all options are answered with yes\n");
printf(" --allmodconfig New config where all options are answered with mod\n");
printf(" --alldefconfig New config with all symbols set to default\n");
printf(" --randconfig New config with random answer to all options\n");
}
int main(int ac, char **av)
{
const char *progname = av[0];
int opt;
const char *name, *defconfig_file = NULL /* gcc uninit */;
struct stat tmpstat;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
while ((opt = getopt_long(ac, av, "", long_opts, NULL)) != -1) {
input_mode = (enum input_mode)opt;
switch (opt) {
case defconfig:
case savedefconfig:
defconfig_file = optarg;
break;
case randconfig:
{
struct timeval now;
unsigned int seed;
/*
* Use microseconds derived seed,
* compensate for systems where it may be zero
*/
gettimeofday(&now, NULL);
seed = (unsigned int)((now.tv_sec + 1) * (now.tv_usec + 1));
srand(seed);
break;
}
case oldaskconfig:
case oldconfig:
case allnoconfig:
case allyesconfig:
case allmodconfig:
case alldefconfig:
case listnewconfig:
case oldnoconfig:
break;
case '?':
conf_usage(progname);
exit(1);
break;
}
}
if (ac == optind) {
printf(_("%s: Kconfig file missing\n"), av[0]);
conf_usage(progname);
exit(1);
}
name = av[optind];
conf_parse(name);
//zconfdump(stdout);
switch (input_mode) {
case defconfig:
if (!defconfig_file)
defconfig_file = conf_get_default_confname();
if (conf_read(defconfig_file)) {
printf(_("***\n"
"*** Can't find default configuration \"%s\"!\n"
"***\n"), defconfig_file);
exit(1);
}
break;
case savedefconfig:
case oldaskconfig:
case oldconfig:
case listnewconfig:
case oldnoconfig:
conf_read(NULL);
break;
case allnoconfig:
case allyesconfig:
case allmodconfig:
case alldefconfig:
case randconfig:
name = getenv("KCONFIG_ALLCONFIG");
if (name && !stat(name, &tmpstat)) {
conf_read_simple(name, S_DEF_USER);
break;
}
switch (input_mode) {
case allnoconfig: name = "allno.config"; break;
case allyesconfig: name = "allyes.config"; break;
case allmodconfig: name = "allmod.config"; break;
case alldefconfig: name = "alldef.config"; break;
case randconfig: name = "allrandom.config"; break;
default: break;
}
if (!stat(name, &tmpstat))
conf_read_simple(name, S_DEF_USER);
else if (!stat("all.config", &tmpstat))
conf_read_simple("all.config", S_DEF_USER);
break;
default:
break;
}
valid_stdin = isatty(0) && isatty(1) && isatty(2);
switch (input_mode) {
case allnoconfig:
conf_set_all_new_symbols(def_no);
break;
case allyesconfig:
conf_set_all_new_symbols(def_yes);
break;
case allmodconfig:
conf_set_all_new_symbols(def_mod);
break;
case alldefconfig:
conf_set_all_new_symbols(def_default);
break;
case randconfig:
conf_set_all_new_symbols(def_random);
break;
case defconfig:
conf_set_all_new_symbols(def_default);
break;
case savedefconfig:
break;
case oldaskconfig:
rootEntry = &rootmenu;
conf(&rootmenu);
input_mode = oldconfig;
/* fall through */
case oldconfig:
case listnewconfig:
case oldnoconfig:
/* Update until a loop caused no more changes */
do {
conf_cnt = 0;
check_conf(&rootmenu);
} while (conf_cnt &&
(input_mode != listnewconfig &&
input_mode != oldnoconfig));
break;
}
if (input_mode == savedefconfig) {
if (conf_write_defconfig(defconfig_file)) {
fprintf(stderr, _("n*** Error while saving defconfig to: %s\n\n"),
defconfig_file);
return 1;
}
} else if (input_mode != listnewconfig) {
/*
* build so we shall update autoconf.
*/
if (conf_write(NULL)) {
fprintf(stderr, _("\n*** Error during writing of the configuration.\n\n"));
exit(1);
}
if (conf_write_autoconf()) {
fprintf(stderr, _("\n*** Error during update of the configuration.\n\n"));
return 1;
}
}
return 0;
}
/*
* Helper function to facilitate fgets() by Jean Sacren.
*/
void xfgets(char *str, int size, FILE *in)
{
if (fgets(str, size, in) == NULL)
fprintf(stderr, "\nError in reading or end of file.\n");
}
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "lkc.h"
static void conf_warning(const char *fmt, ...)
__attribute__ ((format (printf, 1, 2)));
static void conf_message(const char *fmt, ...)
__attribute__ ((format (printf, 1, 2)));
static const char *conf_filename;
static int conf_lineno, conf_warnings, conf_unsaved;
const char conf_defname[] = "include/generated/defconfig";
static void conf_warning(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "%s:%d:warning: ", conf_filename, conf_lineno);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
conf_warnings++;
}
static void conf_default_message_callback(const char *fmt, va_list ap)
{
printf("#\n# ");
vprintf(fmt, ap);
printf("\n#\n");
}
static void (*conf_message_callback) (const char *fmt, va_list ap) =
conf_default_message_callback;
void conf_set_message_callback(void (*fn) (const char *fmt, va_list ap))
{
conf_message_callback = fn;
}
static void conf_message(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (conf_message_callback)
conf_message_callback(fmt, ap);
}
const char *conf_get_configname(void)
{
char *name = getenv("KCONFIG_CONFIG");
return name ? name : ".config";
}
const char *conf_get_autoconfig_name(void)
{
char *name = getenv("KCONFIG_AUTOCONFIG");
return name ? name : "include/generated/auto.conf";
}
static char *conf_expand_value(const char *in)
{
struct symbol *sym;
const char *src;
static char res_value[SYMBOL_MAXLENGTH];
char *dst, name[SYMBOL_MAXLENGTH];
res_value[0] = 0;
dst = name;
while ((src = strchr(in, '$'))) {
strncat(res_value, in, src - in);
src++;
dst = name;
while (isalnum(*src) || *src == '_')
*dst++ = *src++;
*dst = 0;
sym = sym_lookup(name, 0);
sym_calc_value(sym);
strcat(res_value, sym_get_string_value(sym));
in = src;
}
strcat(res_value, in);
return res_value;
}
char *conf_get_default_confname(void)
{
struct stat buf;
static char fullname[PATH_MAX+1];
char *env, *name;
name = conf_expand_value(conf_defname);
env = getenv(SRCTREE);
if (env) {
sprintf(fullname, "%s/%s", env, name);
if (!stat(fullname, &buf))
return fullname;
}
return name;
}
static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
{
char *p2;
switch (sym->type) {
case S_TRISTATE:
if (p[0] == 'm') {
sym->def[def].tri = mod;
sym->flags |= def_flags;
break;
}
/* fall through */
case S_BOOLEAN:
if (p[0] == 'y') {
sym->def[def].tri = yes;
sym->flags |= def_flags;
break;
}
if (p[0] == 'n') {
sym->def[def].tri = no;
sym->flags |= def_flags;
break;
}
conf_warning("symbol value '%s' invalid for %s", p, sym->name);
return 1;
case S_OTHER:
if (*p != '"') {
for (p2 = p; *p2 && !isspace(*p2); p2++)
;
sym->type = S_STRING;
goto done;
}
/* fall through */
case S_STRING:
if (*p++ != '"')
break;
for (p2 = p; (p2 = strpbrk(p2, "\"\\")); p2++) {
if (*p2 == '"') {
*p2 = 0;
break;
}
memmove(p2, p2 + 1, strlen(p2));
}
if (!p2) {
conf_warning("invalid string found");
return 1;
}
/* fall through */
case S_INT:
case S_HEX:
done:
if (sym_string_valid(sym, p)) {
sym->def[def].val = strdup(p);
sym->flags |= def_flags;
} else {
conf_warning("symbol value '%s' invalid for %s", p, sym->name);
return 1;
}
break;
default:
;
}
return 0;
}
int conf_read_simple(const char *name, int def)
{
FILE *in = NULL;
char line[1024];
char *p, *p2;
struct symbol *sym;
int i, def_flags;
if (name) {
in = zconf_fopen(name);
} else {
struct property *prop;
name = conf_get_configname();
in = zconf_fopen(name);
if (in)
goto load;
sym_add_change_count(1);
if (!sym_defconfig_list) {
if (modules_sym)
sym_calc_value(modules_sym);
return 1;
}
for_all_defaults(sym_defconfig_list, prop) {
if (expr_calc_value(prop->visible.expr) == no ||
prop->expr->type != E_SYMBOL)
continue;
name = conf_expand_value(prop->expr->left.sym->name);
in = zconf_fopen(name);
if (in) {
conf_message(_("using defaults found in %s"),
name);
goto load;
}
}
}
if (!in)
return 1;
load:
conf_filename = name;
conf_lineno = 0;
conf_warnings = 0;
conf_unsaved = 0;
def_flags = SYMBOL_DEF << def;
for_all_symbols(i, sym) {
sym->flags |= SYMBOL_CHANGED;
sym->flags &= ~(def_flags|SYMBOL_VALID);
if (sym_is_choice(sym))
sym->flags |= def_flags;
switch (sym->type) {
case S_INT:
case S_HEX:
case S_STRING:
if (sym->def[def].val)
free(sym->def[def].val);
/* fall through */
default:
sym->def[def].val = NULL;
sym->def[def].tri = no;
}
}
while (fgets(line, sizeof(line), in)) {
conf_lineno++;
sym = NULL;
if (line[0] == '#') {
if (memcmp(line + 2, CONFIG_, strlen(CONFIG_)))
continue;
p = strchr(line + 2 + strlen(CONFIG_), ' ');
if (!p)
continue;
*p++ = 0;
if (strncmp(p, "is not set", 10))
continue;
if (def == S_DEF_USER) {
sym = sym_find(line + 2 + strlen(CONFIG_));
if (!sym) {
sym_add_change_count(1);
goto setsym;
}
} else {
sym = sym_lookup(line + 2 + strlen(CONFIG_), 0);
if (sym->type == S_UNKNOWN)
sym->type = S_BOOLEAN;
}
if (sym->flags & def_flags) {
conf_warning("override: reassigning to symbol %s", sym->name);
}
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
sym->def[def].tri = no;
sym->flags |= def_flags;
break;
default:
;
}
} else if (memcmp(line, CONFIG_, strlen(CONFIG_)) == 0) {
p = strchr(line + strlen(CONFIG_), '=');
if (!p)
continue;
*p++ = 0;
p2 = strchr(p, '\n');
if (p2) {
*p2-- = 0;
if (*p2 == '\r')
*p2 = 0;
}
if (def == S_DEF_USER) {
sym = sym_find(line + strlen(CONFIG_));
if (!sym) {
sym_add_change_count(1);
goto setsym;
}
} else {
sym = sym_lookup(line + strlen(CONFIG_), 0);
if (sym->type == S_UNKNOWN)
sym->type = S_OTHER;
}
if (sym->flags & def_flags) {
conf_warning("override: reassigning to symbol %s", sym->name);
}
if (conf_set_sym_val(sym, def, def_flags, p))
continue;
} else {
if (line[0] != '\r' && line[0] != '\n')
conf_warning("unexpected data");
continue;
}
setsym:
if (sym && sym_is_choice_value(sym)) {
struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym));
switch (sym->def[def].tri) {
case no:
break;
case mod:
if (cs->def[def].tri == yes) {
conf_warning("%s creates inconsistent choice state", sym->name);
cs->flags &= ~def_flags;
}
break;
case yes:
if (cs->def[def].tri != no)
conf_warning("override: %s changes choice state", sym->name);
cs->def[def].val = sym;
break;
}
cs->def[def].tri = EXPR_OR(cs->def[def].tri, sym->def[def].tri);
}
}
fclose(in);
if (modules_sym)
sym_calc_value(modules_sym);
return 0;
}
int conf_read(const char *name)
{
struct symbol *sym;
int i;
sym_set_change_count(0);
if (conf_read_simple(name, S_DEF_USER))
return 1;
for_all_symbols(i, sym) {
sym_calc_value(sym);
if (sym_is_choice(sym) || (sym->flags & SYMBOL_AUTO))
continue;
if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) {
/* check that calculated value agrees with saved value */
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
if (sym->def[S_DEF_USER].tri != sym_get_tristate_value(sym))
break;
if (!sym_is_choice(sym))
continue;
/* fall through */
default:
if (!strcmp(sym->curr.val, sym->def[S_DEF_USER].val))
continue;
break;
}
} else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE))
/* no previous value and not saved */
continue;
conf_unsaved++;
/* maybe print value in verbose mode... */
}
for_all_symbols(i, sym) {
if (sym_has_value(sym) && !sym_is_choice_value(sym)) {
/* Reset values of generates values, so they'll appear
* as new, if they should become visible, but that
* doesn't quite work if the Kconfig and the saved
* configuration disagree.
*/
if (sym->visible == no && !conf_unsaved)
sym->flags &= ~SYMBOL_DEF_USER;
switch (sym->type) {
case S_STRING:
case S_INT:
case S_HEX:
/* Reset a string value if it's out of range */
if (sym_string_within_range(sym, sym->def[S_DEF_USER].val))
break;
sym->flags &= ~(SYMBOL_VALID|SYMBOL_DEF_USER);
conf_unsaved++;
break;
default:
break;
}
}
}
sym_add_change_count(conf_warnings || conf_unsaved);
return 0;
}
/*
* Kconfig configuration printer
*
* This printer is used when generating the resulting configuration after
* kconfig invocation and `defconfig' files. Unset symbol might be omitted by
* passing a non-NULL argument to the printer.
*
*/
static void
kconfig_print_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg)
{
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
if (*value == 'n') {
bool skip_unset = (arg != NULL);
if (!skip_unset)
fprintf(fp, "# %s%s is not set\n",
CONFIG_, sym->name);
return;
}
break;
default:
break;
}
fprintf(fp, "%s%s=%s\n", CONFIG_, sym->name, value);
}
static void
kconfig_print_cmake_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg)
{
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
if (*value == 'n') {
bool skip_unset = (arg != NULL);
if (!skip_unset)
fprintf(fp, "set(%s%s false)\n",
CONFIG_, sym->name, value);
return;
} else if (*value == 'm') {
abort();
} else {
fprintf(fp, "set(%s%s true)\n", CONFIG_, sym->name, value);
}
break;
case S_HEX: {
const char *prefix = "";
if (value[0] != '0' || (value[1] != 'x' && value[1] != 'X'))
prefix = "0x";
fprintf(fp, "set(%s%s %s%s)\n",
CONFIG_, sym->name, prefix, value);
break;
}
case S_STRING:
case S_INT:
fprintf(fp, "set(%s%s %s)\n",
CONFIG_, sym->name, value);
break;
default:
break;
}
}
static void
kconfig_print_comment(FILE *fp, const char *value, void *arg)
{
const char *p = value;
size_t l;
for (;;) {
l = strcspn(p, "\n");
fprintf(fp, "#");
if (l) {
fprintf(fp, " ");
xfwrite(p, l, 1, fp);
p += l;
}
fprintf(fp, "\n");
if (*p++ == '\0')
break;
}
}
static struct conf_printer kconfig_printer_cb =
{
.print_symbol = kconfig_print_symbol,
.print_comment = kconfig_print_comment,
};
static struct conf_printer kconfig_printer_cmake_cb =
{
.print_symbol = kconfig_print_cmake_symbol,
.print_comment = kconfig_print_comment,
};
/*
* Header printer
*
* This printer is used when generating the `include/generated/autoconf.h' file.
*/
static void
header_print_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg)
{
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE: {
const char *suffix = "";
switch (*value) {
case 'n':
break;
case 'm':
suffix = "_MODULE";
/* fall through */
default:
fprintf(fp, "#define %s%s%s 1\n",
CONFIG_, sym->name, suffix);
}
break;
}
case S_HEX: {
const char *prefix = "";
if (value[0] != '0' || (value[1] != 'x' && value[1] != 'X'))
prefix = "0x";
fprintf(fp, "#define %s%s %s%s\n",
CONFIG_, sym->name, prefix, value);
break;
}
case S_STRING:
case S_INT:
fprintf(fp, "#define %s%s %s\n",
CONFIG_, sym->name, value);
break;
default:
break;
}
}
static void
header_print_comment(FILE *fp, const char *value, void *arg)
{
const char *p = value;
size_t l;
fprintf(fp, "/*\n");
for (;;) {
l = strcspn(p, "\n");
fprintf(fp, " *");
if (l) {
fprintf(fp, " ");
xfwrite(p, l, 1, fp);
p += l;
}
fprintf(fp, "\n");
if (*p++ == '\0')
break;
}
fprintf(fp, " */\n");
}
static struct conf_printer header_printer_cb =
{
.print_symbol = header_print_symbol,
.print_comment = header_print_comment,
};
/*
* Tristate printer
*
* This printer is used when generating the `include/generated/tristate.conf' file.
*/
static void
tristate_print_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg)
{
if (sym->type == S_TRISTATE && *value != 'n')
fprintf(fp, "%s%s=%c\n", CONFIG_, sym->name, (char)toupper(*value));
}
static struct conf_printer tristate_printer_cb =
{
.print_symbol = tristate_print_symbol,
.print_comment = kconfig_print_comment,
};
static void conf_write_symbol(FILE *fp, struct symbol *sym,
struct conf_printer *printer, void *printer_arg)
{
const char *str;
switch (sym->type) {
case S_OTHER:
case S_UNKNOWN:
break;
case S_STRING:
str = sym_get_string_value(sym);
str = sym_escape_string_value(str);
printer->print_symbol(fp, sym, str, printer_arg);
free((void *)str);
break;
default:
str = sym_get_string_value(sym);
printer->print_symbol(fp, sym, str, printer_arg);
}
}
static void
conf_write_heading(FILE *fp, struct conf_printer *printer, void *printer_arg)
{
char buf[256];
snprintf(buf, sizeof(buf),
"\n"
"Automatically generated file; DO NOT EDIT.\n"
"%s\n",
rootmenu.prompt->text);
printer->print_comment(fp, buf, printer_arg);
}
/*
* Write out a minimal config.
* All values that has default values are skipped as this is redundant.
*/
int conf_write_defconfig(const char *filename)
{
struct symbol *sym;
struct menu *menu;
FILE *out;
out = fopen(filename, "w");
if (!out)
return 1;
sym_clear_all_valid();
/* Traverse all menus to find all relevant symbols */
menu = rootmenu.list;
while (menu != NULL)
{
sym = menu->sym;
if (sym == NULL) {
if (!menu_is_visible(menu))
goto next_menu;
} else if (!sym_is_choice(sym)) {
sym_calc_value(sym);
if (!(sym->flags & SYMBOL_WRITE))
goto next_menu;
sym->flags &= ~SYMBOL_WRITE;
/* If we cannot change the symbol - skip */
if (!sym_is_changable(sym))
goto next_menu;
/* If symbol equals to default value - skip */
if (strcmp(sym_get_string_value(sym), sym_get_string_default(sym)) == 0)
goto next_menu;
/*
* If symbol is a choice value and equals to the
* default for a choice - skip.
* But only if value is bool and equal to "y" and
* choice is not "optional".
* (If choice is "optional" then all values can be "n")
*/
if (sym_is_choice_value(sym)) {
struct symbol *cs;
struct symbol *ds;
cs = prop_get_symbol(sym_get_choice_prop(sym));
ds = sym_choice_default(cs);
if (!sym_is_optional(cs) && sym == ds) {
if ((sym->type == S_BOOLEAN) &&
sym_get_tristate_value(sym) == yes)
goto next_menu;
}
}
conf_write_symbol(out, sym, &kconfig_printer_cb, NULL);
}
next_menu:
if (menu->list != NULL) {
menu = menu->list;
}
else if (menu->next != NULL) {
menu = menu->next;
} else {
while ((menu = menu->parent)) {
if (menu->next != NULL) {
menu = menu->next;
break;
}
}
}
}
fclose(out);
return 0;
}
int conf_write(const char *name)
{
FILE *out;
struct symbol *sym;
struct menu *menu;
const char *basename;
const char *str;
char dirname[PATH_MAX+1], tmpname[PATH_MAX+1], newname[PATH_MAX+1];
char *env;
dirname[0] = 0;
if (name && name[0]) {
struct stat st;
char *slash;
if (!stat(name, &st) && S_ISDIR(st.st_mode)) {
strcpy(dirname, name);
strcat(dirname, "/");
basename = conf_get_configname();
} else if ((slash = strrchr(name, '/'))) {
int size = slash - name + 1;
memcpy(dirname, name, size);
dirname[size] = 0;
if (slash[1])
basename = slash + 1;
else
basename = conf_get_configname();
} else
basename = name;
} else
basename = conf_get_configname();
sprintf(newname, "%s%s", dirname, basename);
env = getenv("KCONFIG_OVERWRITECONFIG");
if (!env || !*env) {
sprintf(tmpname, "%s.tmpconfig.%d", dirname, (int)getpid());
out = fopen(tmpname, "w");
} else {
*tmpname = 0;
out = fopen(newname, "w");
}
if (!out)
return 1;
conf_write_heading(out, &kconfig_printer_cb, NULL);
if (!conf_get_changed())
sym_clear_all_valid();
menu = rootmenu.list;
while (menu) {
sym = menu->sym;
if (!sym) {
if (!menu_is_visible(menu))
goto next;
str = menu_get_prompt(menu);
fprintf(out, "\n"
"#\n"
"# %s\n"
"#\n", str);
} else if (!(sym->flags & SYMBOL_CHOICE)) {
sym_calc_value(sym);
if (!(sym->flags & SYMBOL_WRITE))
goto next;
sym->flags &= ~SYMBOL_WRITE;
conf_write_symbol(out, sym, &kconfig_printer_cb, NULL);
}
next:
if (menu->list) {
menu = menu->list;
continue;
}
if (menu->next)
menu = menu->next;
else while ((menu = menu->parent)) {
if (menu->next) {
menu = menu->next;
break;
}
}
}
fclose(out);
if (*tmpname) {
strcat(dirname, basename);
strcat(dirname, ".old");
rename(newname, dirname);
if (rename(tmpname, newname))
return 1;
}
conf_message(_("configuration written to %s"), newname);
sym_set_change_count(0);
return 0;
}
static int conf_split_config(void)
{
const char *name;
char path[PATH_MAX+1];
char *s, *d, c;
struct symbol *sym;
struct stat sb;
int res, i, fd;
name = conf_get_autoconfig_name();
conf_read_simple(name, S_DEF_AUTO);
if (chdir("include/generated"))
return 1;
res = 0;
for_all_symbols(i, sym) {
sym_calc_value(sym);
if ((sym->flags & SYMBOL_AUTO) || !sym->name)
continue;
if (sym->flags & SYMBOL_WRITE) {
if (sym->flags & SYMBOL_DEF_AUTO) {
/*
* symbol has old and new value,
* so compare them...
*/
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
if (sym_get_tristate_value(sym) ==
sym->def[S_DEF_AUTO].tri)
continue;
break;
case S_STRING:
case S_HEX:
case S_INT:
if (!strcmp(sym_get_string_value(sym),
sym->def[S_DEF_AUTO].val))
continue;
break;
default:
break;
}
} else {
/*
* If there is no old value, only 'no' (unset)
* is allowed as new value.
*/
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
if (sym_get_tristate_value(sym) == no)
continue;
break;
default:
break;
}
}
} else if (!(sym->flags & SYMBOL_DEF_AUTO))
/* There is neither an old nor a new value. */
continue;
/* else
* There is an old value, but no new value ('no' (unset)
* isn't saved in auto.conf, so the old value is always
* different from 'no').
*/
/* Replace all '_' and append ".h" */
s = sym->name;
d = path;
while ((c = *s++)) {
c = tolower(c);
*d++ = (c == '_') ? '/' : c;
}
strcpy(d, ".h");
/* Assume directory path already exists. */
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
if (errno != ENOENT) {
res = 1;
break;
}
/*
* Create directory components,
* unless they exist already.
*/
d = path;
while ((d = strchr(d, '/'))) {
*d = 0;
if (stat(path, &sb) && mkdir(path, 0755)) {
res = 1;
goto out;
}
*d++ = '/';
}
/* Try it again. */
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
res = 1;
break;
}
}
close(fd);
}
out:
if (chdir("../.."))
return 1;
return res;
}
int conf_write_autoconf(void)
{
struct symbol *sym;
const char *name;
FILE *out, *tristate, *out_h, *out_c;
int i;
sym_clear_all_valid();
file_write_dep("include/generated/auto.conf.cmd");
if (conf_split_config())
return 1;
out = fopen(".tmpconfig", "w");
if (!out)
return 1;
tristate = fopen(".tmpconfig_tristate", "w");
if (!tristate) {
fclose(out);
return 1;
}
out_h = fopen(".tmpconfig.h", "w");
if (!out_h) {
fclose(out);
fclose(tristate);
return 1;
}
out_c = fopen(".tmpconfig.cmake", "w");
if (!out_c) {
fclose(out);
fclose(tristate);
fclose(out_h);
}
conf_write_heading(out, &kconfig_printer_cb, NULL);
conf_write_heading(tristate, &tristate_printer_cb, NULL);
conf_write_heading(out_h, &header_printer_cb, NULL);
conf_write_heading(out_c, &kconfig_printer_cmake_cb, NULL);
for_all_symbols(i, sym) {
sym_calc_value(sym);
if (!(sym->flags & SYMBOL_WRITE) || !sym->name)
continue;
/* write symbol to auto.conf, tristate and header files */
conf_write_symbol(out, sym, &kconfig_printer_cb, (void *)1);
conf_write_symbol(tristate, sym, &tristate_printer_cb, (void *)1);
conf_write_symbol(out_h, sym, &header_printer_cb, NULL);
conf_write_symbol(out_c, sym, &kconfig_printer_cmake_cb, NULL);
}
fclose(out);
fclose(tristate);
fclose(out_h);
fclose(out_c);
name = getenv("KCONFIG_AUTOHEADER");
if (!name)
name = "include/generated/autoconf.h";
if (rename(".tmpconfig.h", name))
return 1;
name = getenv("KCONFIG_TRISTATE");
if (!name)
name = "include/generated/tristate.conf";
if (rename(".tmpconfig_tristate", name))
return 1;
name = getenv("KCONFIG_CMAKE");
if (!name)
name = "config.cmake";
if (rename(".tmpconfig.cmake", name))
return 1;
name = conf_get_autoconfig_name();
/*
* This must be the last step, kbuild has a dependency on auto.conf
* and this marks the successful completion of the previous steps.
*/
if (rename(".tmpconfig", name))
return 1;
return 0;
}
static int sym_change_count;
static void (*conf_changed_callback)(void);
void sym_set_change_count(int count)
{
int _sym_change_count = sym_change_count;
sym_change_count = count;
if (conf_changed_callback &&
(bool)_sym_change_count != (bool)count)
conf_changed_callback();
}
void sym_add_change_count(int count)
{
sym_set_change_count(count + sym_change_count);
}
bool conf_get_changed(void)
{
return sym_change_count;
}
void conf_set_changed_callback(void (*fn)(void))
{
conf_changed_callback = fn;
}
static void randomize_choice_values(struct symbol *csym)
{
struct property *prop;
struct symbol *sym;
struct expr *e;
int cnt, def;
/*
* If choice is mod then we may have more items selected
* and if no then no-one.
* In both cases stop.
*/
if (csym->curr.tri != yes)
return;
prop = sym_get_choice_prop(csym);
/* count entries in choice block */
cnt = 0;
expr_list_for_each_sym(prop->expr, e, sym)
cnt++;
/*
* find a random value and set it to yes,
* set the rest to no so we have only one set
*/
def = (rand() % cnt);
cnt = 0;
expr_list_for_each_sym(prop->expr, e, sym) {
if (def == cnt++) {
sym->def[S_DEF_USER].tri = yes;
csym->def[S_DEF_USER].val = sym;
}
else {
sym->def[S_DEF_USER].tri = no;
}
}
csym->flags |= SYMBOL_DEF_USER;
/* clear VALID to get value calculated */
csym->flags &= ~(SYMBOL_VALID);
}
static void set_all_choice_values(struct symbol *csym)
{
struct property *prop;
struct symbol *sym;
struct expr *e;
prop = sym_get_choice_prop(csym);
/*
* Set all non-assinged choice values to no
*/
expr_list_for_each_sym(prop->expr, e, sym) {
if (!sym_has_value(sym))
sym->def[S_DEF_USER].tri = no;
}
csym->flags |= SYMBOL_DEF_USER;
/* clear VALID to get value calculated */
csym->flags &= ~(SYMBOL_VALID);
}
void conf_set_all_new_symbols(enum conf_def_mode mode)
{
struct symbol *sym, *csym;
int i, cnt;
for_all_symbols(i, sym) {
if (sym_has_value(sym))
continue;
switch (sym_get_type(sym)) {
case S_BOOLEAN:
case S_TRISTATE:
switch (mode) {
case def_yes:
sym->def[S_DEF_USER].tri = yes;
break;
case def_mod:
sym->def[S_DEF_USER].tri = mod;
break;
case def_no:
sym->def[S_DEF_USER].tri = no;
break;
case def_random:
cnt = sym_get_type(sym) == S_TRISTATE ? 3 : 2;
sym->def[S_DEF_USER].tri = (tristate)(rand() % cnt);
break;
default:
continue;
}
if (!(sym_is_choice(sym) && mode == def_random))
sym->flags |= SYMBOL_DEF_USER;
break;
default:
break;
}
}
sym_clear_all_valid();
/*
* We have different type of choice blocks.
* If curr.tri equals to mod then we can select several
* choice symbols in one block.
* In this case we do nothing.
* If curr.tri equals yes then only one symbol can be
* selected in a choice block and we set it to yes,
* and the rest to no.
*/
for_all_symbols(i, csym) {
if (sym_has_value(csym) || !sym_is_choice(csym))
continue;
sym_calc_value(csym);
if (mode == def_random)
randomize_choice_values(csym);
else
set_all_choice_values(csym);
}
}
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lkc.h"
#define DEBUG_EXPR 0
struct expr *expr_alloc_symbol(struct symbol *sym)
{
struct expr *e = calloc(1, sizeof(*e));
e->type = E_SYMBOL;
e->left.sym = sym;
return e;
}
struct expr *expr_alloc_one(enum expr_type type, struct expr *ce)
{
struct expr *e = calloc(1, sizeof(*e));
e->type = type;
e->left.expr = ce;
return e;
}
struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2)
{
struct expr *e = calloc(1, sizeof(*e));
e->type = type;
e->left.expr = e1;
e->right.expr = e2;
return e;
}
struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2)
{
struct expr *e = calloc(1, sizeof(*e));
e->type = type;
e->left.sym = s1;
e->right.sym = s2;
return e;
}
struct expr *expr_alloc_and(struct expr *e1, struct expr *e2)
{
if (!e1)
return e2;
return e2 ? expr_alloc_two(E_AND, e1, e2) : e1;
}
struct expr *expr_alloc_or(struct expr *e1, struct expr *e2)
{
if (!e1)
return e2;
return e2 ? expr_alloc_two(E_OR, e1, e2) : e1;
}
struct expr *expr_copy(const struct expr *org)
{
struct expr *e;
if (!org)
return NULL;
e = malloc(sizeof(*org));
memcpy(e, org, sizeof(*org));
switch (org->type) {
case E_SYMBOL:
e->left = org->left;
break;
case E_NOT:
e->left.expr = expr_copy(org->left.expr);
break;
case E_EQUAL:
case E_UNEQUAL:
e->left.sym = org->left.sym;
e->right.sym = org->right.sym;
break;
case E_AND:
case E_OR:
case E_LIST:
e->left.expr = expr_copy(org->left.expr);
e->right.expr = expr_copy(org->right.expr);
break;
default:
printf("can't copy type %d\n", e->type);
free(e);
e = NULL;
break;
}
return e;
}
void expr_free(struct expr *e)
{
if (!e)
return;
switch (e->type) {
case E_SYMBOL:
break;
case E_NOT:
expr_free(e->left.expr);
return;
case E_EQUAL:
case E_UNEQUAL:
break;
case E_OR:
case E_AND:
expr_free(e->left.expr);
expr_free(e->right.expr);
break;
default:
printf("how to free type %d?\n", e->type);
break;
}
free(e);
}
static int trans_count;
#define e1 (*ep1)
#define e2 (*ep2)
static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2)
{
if (e1->type == type) {
__expr_eliminate_eq(type, &e1->left.expr, &e2);
__expr_eliminate_eq(type, &e1->right.expr, &e2);
return;
}
if (e2->type == type) {
__expr_eliminate_eq(type, &e1, &e2->left.expr);
__expr_eliminate_eq(type, &e1, &e2->right.expr);
return;
}
if (e1->type == E_SYMBOL && e2->type == E_SYMBOL &&
e1->left.sym == e2->left.sym &&
(e1->left.sym == &symbol_yes || e1->left.sym == &symbol_no))
return;
if (!expr_eq(e1, e2))
return;
trans_count++;
expr_free(e1); expr_free(e2);
switch (type) {
case E_OR:
e1 = expr_alloc_symbol(&symbol_no);
e2 = expr_alloc_symbol(&symbol_no);
break;
case E_AND:
e1 = expr_alloc_symbol(&symbol_yes);
e2 = expr_alloc_symbol(&symbol_yes);
break;
default:
;
}
}
void expr_eliminate_eq(struct expr **ep1, struct expr **ep2)
{
if (!e1 || !e2)
return;
switch (e1->type) {
case E_OR:
case E_AND:
__expr_eliminate_eq(e1->type, ep1, ep2);
default:
;
}
if (e1->type != e2->type) switch (e2->type) {
case E_OR:
case E_AND:
__expr_eliminate_eq(e2->type, ep1, ep2);
default:
;
}
e1 = expr_eliminate_yn(e1);
e2 = expr_eliminate_yn(e2);
}
#undef e1
#undef e2
int expr_eq(struct expr *e1, struct expr *e2)
{
int res, old_count;
if (e1->type != e2->type)
return 0;
switch (e1->type) {
case E_EQUAL:
case E_UNEQUAL:
return e1->left.sym == e2->left.sym && e1->right.sym == e2->right.sym;
case E_SYMBOL:
return e1->left.sym == e2->left.sym;
case E_NOT:
return expr_eq(e1->left.expr, e2->left.expr);
case E_AND:
case E_OR:
e1 = expr_copy(e1);
e2 = expr_copy(e2);
old_count = trans_count;
expr_eliminate_eq(&e1, &e2);
res = (e1->type == E_SYMBOL && e2->type == E_SYMBOL &&
e1->left.sym == e2->left.sym);
expr_free(e1);
expr_free(e2);
trans_count = old_count;
return res;
case E_LIST:
case E_RANGE:
case E_NONE:
/* panic */;
}
if (DEBUG_EXPR) {
expr_fprint(e1, stdout);
printf(" = ");
expr_fprint(e2, stdout);
printf(" ?\n");
}
return 0;
}
struct expr *expr_eliminate_yn(struct expr *e)
{
struct expr *tmp;
if (e) switch (e->type) {
case E_AND:
e->left.expr = expr_eliminate_yn(e->left.expr);
e->right.expr = expr_eliminate_yn(e->right.expr);
if (e->left.expr->type == E_SYMBOL) {
if (e->left.expr->left.sym == &symbol_no) {
expr_free(e->left.expr);
expr_free(e->right.expr);
e->type = E_SYMBOL;
e->left.sym = &symbol_no;
e->right.expr = NULL;
return e;
} else if (e->left.expr->left.sym == &symbol_yes) {
free(e->left.expr);
tmp = e->right.expr;
*e = *(e->right.expr);
free(tmp);
return e;
}
}
if (e->right.expr->type == E_SYMBOL) {
if (e->right.expr->left.sym == &symbol_no) {
expr_free(e->left.expr);
expr_free(e->right.expr);
e->type = E_SYMBOL;
e->left.sym = &symbol_no;
e->right.expr = NULL;
return e;
} else if (e->right.expr->left.sym == &symbol_yes) {
free(e->right.expr);
tmp = e->left.expr;
*e = *(e->left.expr);
free(tmp);
return e;
}
}
break;
case E_OR:
e->left.expr = expr_eliminate_yn(e->left.expr);
e->right.expr = expr_eliminate_yn(e->right.expr);
if (e->left.expr->type == E_SYMBOL) {
if (e->left.expr->left.sym == &symbol_no) {
free(e->left.expr);
tmp = e->right.expr;
*e = *(e->right.expr);
free(tmp);
return e;
} else if (e->left.expr->left.sym == &symbol_yes) {
expr_free(e->left.expr);
expr_free(e->right.expr);
e->type = E_SYMBOL;
e->left.sym = &symbol_yes;
e->right.expr = NULL;
return e;
}
}
if (e->right.expr->type == E_SYMBOL) {
if (e->right.expr->left.sym == &symbol_no) {
free(e->right.expr);
tmp = e->left.expr;
*e = *(e->left.expr);
free(tmp);
return e;
} else if (e->right.expr->left.sym == &symbol_yes) {
expr_free(e->left.expr);
expr_free(e->right.expr);
e->type = E_SYMBOL;
e->left.sym = &symbol_yes;
e->right.expr = NULL;
return e;
}
}
break;
default:
;
}
return e;
}
/*
* bool FOO!=n => FOO
*/
struct expr *expr_trans_bool(struct expr *e)
{
if (!e)
return NULL;
switch (e->type) {
case E_AND:
case E_OR:
case E_NOT:
e->left.expr = expr_trans_bool(e->left.expr);
e->right.expr = expr_trans_bool(e->right.expr);
break;
case E_UNEQUAL:
// FOO!=n -> FOO
if (e->left.sym->type == S_TRISTATE) {
if (e->right.sym == &symbol_no) {
e->type = E_SYMBOL;
e->right.sym = NULL;
}
}
break;
default:
;
}
return e;
}
/*
* e1 || e2 -> ?
*/
static struct expr *expr_join_or(struct expr *e1, struct expr *e2)
{
struct expr *tmp;
struct symbol *sym1, *sym2;
if (expr_eq(e1, e2))
return expr_copy(e1);
if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT)
return NULL;
if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT)
return NULL;
if (e1->type == E_NOT) {
tmp = e1->left.expr;
if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL)
return NULL;
sym1 = tmp->left.sym;
} else
sym1 = e1->left.sym;
if (e2->type == E_NOT) {
if (e2->left.expr->type != E_SYMBOL)
return NULL;
sym2 = e2->left.expr->left.sym;
} else
sym2 = e2->left.sym;
if (sym1 != sym2)
return NULL;
if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE)
return NULL;
if (sym1->type == S_TRISTATE) {
if (e1->type == E_EQUAL && e2->type == E_EQUAL &&
((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) ||
(e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) {
// (a='y') || (a='m') -> (a!='n')
return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_no);
}
if (e1->type == E_EQUAL && e2->type == E_EQUAL &&
((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) ||
(e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) {
// (a='y') || (a='n') -> (a!='m')
return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_mod);
}
if (e1->type == E_EQUAL && e2->type == E_EQUAL &&
((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) ||
(e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) {
// (a='m') || (a='n') -> (a!='y')
return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_yes);
}
}
if (sym1->type == S_BOOLEAN && sym1 == sym2) {
if ((e1->type == E_NOT && e1->left.expr->type == E_SYMBOL && e2->type == E_SYMBOL) ||
(e2->type == E_NOT && e2->left.expr->type == E_SYMBOL && e1->type == E_SYMBOL))
return expr_alloc_symbol(&symbol_yes);
}
if (DEBUG_EXPR) {
printf("optimize (");
expr_fprint(e1, stdout);
printf(") || (");
expr_fprint(e2, stdout);
printf(")?\n");
}
return NULL;
}
static struct expr *expr_join_and(struct expr *e1, struct expr *e2)
{
struct expr *tmp;
struct symbol *sym1, *sym2;
if (expr_eq(e1, e2))
return expr_copy(e1);
if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT)
return NULL;
if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT)
return NULL;
if (e1->type == E_NOT) {
tmp = e1->left.expr;
if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL)
return NULL;
sym1 = tmp->left.sym;
} else
sym1 = e1->left.sym;
if (e2->type == E_NOT) {
if (e2->left.expr->type != E_SYMBOL)
return NULL;
sym2 = e2->left.expr->left.sym;
} else
sym2 = e2->left.sym;
if (sym1 != sym2)
return NULL;
if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE)
return NULL;
if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_yes) ||
(e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_yes))
// (a) && (a='y') -> (a='y')
return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes);
if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_no) ||
(e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_no))
// (a) && (a!='n') -> (a)
return expr_alloc_symbol(sym1);
if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_mod) ||
(e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_mod))
// (a) && (a!='m') -> (a='y')
return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes);
if (sym1->type == S_TRISTATE) {
if (e1->type == E_EQUAL && e2->type == E_UNEQUAL) {
// (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b'
sym2 = e1->right.sym;
if ((e2->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST))
return sym2 != e2->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2)
: expr_alloc_symbol(&symbol_no);
}
if (e1->type == E_UNEQUAL && e2->type == E_EQUAL) {
// (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b'
sym2 = e2->right.sym;
if ((e1->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST))
return sym2 != e1->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2)
: expr_alloc_symbol(&symbol_no);
}
if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL &&
((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) ||
(e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes)))
// (a!='y') && (a!='n') -> (a='m')
return expr_alloc_comp(E_EQUAL, sym1, &symbol_mod);
if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL &&
((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) ||
(e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes)))
// (a!='y') && (a!='m') -> (a='n')
return expr_alloc_comp(E_EQUAL, sym1, &symbol_no);
if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL &&
((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) ||
(e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod)))
// (a!='m') && (a!='n') -> (a='m')
return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes);
if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_mod) ||
(e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_mod) ||
(e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_yes) ||
(e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_yes))
return NULL;
}
if (DEBUG_EXPR) {
printf("optimize (");
expr_fprint(e1, stdout);
printf(") && (");
expr_fprint(e2, stdout);
printf(")?\n");
}
return NULL;
}
static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2)
{
#define e1 (*ep1)
#define e2 (*ep2)
struct expr *tmp;
if (e1->type == type) {
expr_eliminate_dups1(type, &e1->left.expr, &e2);
expr_eliminate_dups1(type, &e1->right.expr, &e2);
return;
}
if (e2->type == type) {
expr_eliminate_dups1(type, &e1, &e2->left.expr);
expr_eliminate_dups1(type, &e1, &e2->right.expr);
return;
}
if (e1 == e2)
return;
switch (e1->type) {
case E_OR: case E_AND:
expr_eliminate_dups1(e1->type, &e1, &e1);
default:
;
}
switch (type) {
case E_OR:
tmp = expr_join_or(e1, e2);
if (tmp) {
expr_free(e1); expr_free(e2);
e1 = expr_alloc_symbol(&symbol_no);
e2 = tmp;
trans_count++;
}
break;
case E_AND:
tmp = expr_join_and(e1, e2);
if (tmp) {
expr_free(e1); expr_free(e2);
e1 = expr_alloc_symbol(&symbol_yes);
e2 = tmp;
trans_count++;
}
break;
default:
;
}
#undef e1
#undef e2
}
static void expr_eliminate_dups2(enum expr_type type, struct expr **ep1, struct expr **ep2)
{
#define e1 (*ep1)
#define e2 (*ep2)
struct expr *tmp, *tmp1, *tmp2;
if (e1->type == type) {
expr_eliminate_dups2(type, &e1->left.expr, &e2);
expr_eliminate_dups2(type, &e1->right.expr, &e2);
return;
}
if (e2->type == type) {
expr_eliminate_dups2(type, &e1, &e2->left.expr);
expr_eliminate_dups2(type, &e1, &e2->right.expr);
}
if (e1 == e2)
return;
switch (e1->type) {
case E_OR:
expr_eliminate_dups2(e1->type, &e1, &e1);
// (FOO || BAR) && (!FOO && !BAR) -> n
tmp1 = expr_transform(expr_alloc_one(E_NOT, expr_copy(e1)));
tmp2 = expr_copy(e2);
tmp = expr_extract_eq_and(&tmp1, &tmp2);
if (expr_is_yes(tmp1)) {
expr_free(e1);
e1 = expr_alloc_symbol(&symbol_no);
trans_count++;
}
expr_free(tmp2);
expr_free(tmp1);
expr_free(tmp);
break;
case E_AND:
expr_eliminate_dups2(e1->type, &e1, &e1);
// (FOO && BAR) || (!FOO || !BAR) -> y
tmp1 = expr_transform(expr_alloc_one(E_NOT, expr_copy(e1)));
tmp2 = expr_copy(e2);
tmp = expr_extract_eq_or(&tmp1, &tmp2);
if (expr_is_no(tmp1)) {
expr_free(e1);
e1 = expr_alloc_symbol(&symbol_yes);
trans_count++;
}
expr_free(tmp2);
expr_free(tmp1);
expr_free(tmp);
break;
default:
;
}
#undef e1
#undef e2
}
struct expr *expr_eliminate_dups(struct expr *e)
{
int oldcount;
if (!e)
return e;
oldcount = trans_count;
while (1) {
trans_count = 0;
switch (e->type) {
case E_OR: case E_AND:
expr_eliminate_dups1(e->type, &e, &e);
expr_eliminate_dups2(e->type, &e, &e);
default:
;
}
if (!trans_count)
break;
e = expr_eliminate_yn(e);
}
trans_count = oldcount;
return e;
}
struct expr *expr_transform(struct expr *e)
{
struct expr *tmp;
if (!e)
return NULL;
switch (e->type) {
case E_EQUAL:
case E_UNEQUAL:
case E_SYMBOL:
case E_LIST:
break;
default:
e->left.expr = expr_transform(e->left.expr);
e->right.expr = expr_transform(e->right.expr);
}
switch (e->type) {
case E_EQUAL:
if (e->left.sym->type != S_BOOLEAN)
break;
if (e->right.sym == &symbol_no) {
e->type = E_NOT;
e->left.expr = expr_alloc_symbol(e->left.sym);
e->right.sym = NULL;
break;
}
if (e->right.sym == &symbol_mod) {
printf("boolean symbol %s tested for 'm'? test forced to 'n'\n", e->left.sym->name);
e->type = E_SYMBOL;
e->left.sym = &symbol_no;
e->right.sym = NULL;
break;
}
if (e->right.sym == &symbol_yes) {
e->type = E_SYMBOL;
e->right.sym = NULL;
break;
}
break;
case E_UNEQUAL:
if (e->left.sym->type != S_BOOLEAN)
break;
if (e->right.sym == &symbol_no) {
e->type = E_SYMBOL;
e->right.sym = NULL;
break;
}
if (e->right.sym == &symbol_mod) {
printf("boolean symbol %s tested for 'm'? test forced to 'y'\n", e->left.sym->name);
e->type = E_SYMBOL;
e->left.sym = &symbol_yes;
e->right.sym = NULL;
break;
}
if (e->right.sym == &symbol_yes) {
e->type = E_NOT;
e->left.expr = expr_alloc_symbol(e->left.sym);
e->right.sym = NULL;
break;
}
break;
case E_NOT:
switch (e->left.expr->type) {
case E_NOT:
// !!a -> a
tmp = e->left.expr->left.expr;
free(e->left.expr);
free(e);
e = tmp;
e = expr_transform(e);
break;
case E_EQUAL:
case E_UNEQUAL:
// !a='x' -> a!='x'
tmp = e->left.expr;
free(e);
e = tmp;
e->type = e->type == E_EQUAL ? E_UNEQUAL : E_EQUAL;
break;
case E_OR:
// !(a || b) -> !a && !b
tmp = e->left.expr;
e->type = E_AND;
e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr);
tmp->type = E_NOT;
tmp->right.expr = NULL;
e = expr_transform(e);
break;
case E_AND:
// !(a && b) -> !a || !b
tmp = e->left.expr;
e->type = E_OR;
e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr);
tmp->type = E_NOT;
tmp->right.expr = NULL;
e = expr_transform(e);
break;
case E_SYMBOL:
if (e->left.expr->left.sym == &symbol_yes) {
// !'y' -> 'n'
tmp = e->left.expr;
free(e);
e = tmp;
e->type = E_SYMBOL;
e->left.sym = &symbol_no;
break;
}
if (e->left.expr->left.sym == &symbol_mod) {
// !'m' -> 'm'
tmp = e->left.expr;
free(e);
e = tmp;
e->type = E_SYMBOL;
e->left.sym = &symbol_mod;
break;
}
if (e->left.expr->left.sym == &symbol_no) {
// !'n' -> 'y'
tmp = e->left.expr;
free(e);
e = tmp;
e->type = E_SYMBOL;
e->left.sym = &symbol_yes;
break;
}
break;
default:
;
}
break;
default:
;
}
return e;
}
int expr_contains_symbol(struct expr *dep, struct symbol *sym)
{
if (!dep)
return 0;
switch (dep->type) {
case E_AND:
case E_OR:
return expr_contains_symbol(dep->left.expr, sym) ||
expr_contains_symbol(dep->right.expr, sym);
case E_SYMBOL:
return dep->left.sym == sym;
case E_EQUAL:
case E_UNEQUAL:
return dep->left.sym == sym ||
dep->right.sym == sym;
case E_NOT:
return expr_contains_symbol(dep->left.expr, sym);
default:
;
}
return 0;
}
bool expr_depends_symbol(struct expr *dep, struct symbol *sym)
{
if (!dep)
return false;
switch (dep->type) {
case E_AND:
return expr_depends_symbol(dep->left.expr, sym) ||
expr_depends_symbol(dep->right.expr, sym);
case E_SYMBOL:
return dep->left.sym == sym;
case E_EQUAL:
if (dep->left.sym == sym) {
if (dep->right.sym == &symbol_yes || dep->right.sym == &symbol_mod)
return true;
}
break;
case E_UNEQUAL:
if (dep->left.sym == sym) {
if (dep->right.sym == &symbol_no)
return true;
}
break;
default:
;
}
return false;
}
struct expr *expr_extract_eq_and(struct expr **ep1, struct expr **ep2)
{
struct expr *tmp = NULL;
expr_extract_eq(E_AND, &tmp, ep1, ep2);
if (tmp) {
*ep1 = expr_eliminate_yn(*ep1);
*ep2 = expr_eliminate_yn(*ep2);
}
return tmp;
}
struct expr *expr_extract_eq_or(struct expr **ep1, struct expr **ep2)
{
struct expr *tmp = NULL;
expr_extract_eq(E_OR, &tmp, ep1, ep2);
if (tmp) {
*ep1 = expr_eliminate_yn(*ep1);
*ep2 = expr_eliminate_yn(*ep2);
}
return tmp;
}
void expr_extract_eq(enum expr_type type, struct expr **ep, struct expr **ep1, struct expr **ep2)
{
#define e1 (*ep1)
#define e2 (*ep2)
if (e1->type == type) {
expr_extract_eq(type, ep, &e1->left.expr, &e2);
expr_extract_eq(type, ep, &e1->right.expr, &e2);
return;
}
if (e2->type == type) {
expr_extract_eq(type, ep, ep1, &e2->left.expr);
expr_extract_eq(type, ep, ep1, &e2->right.expr);
return;
}
if (expr_eq(e1, e2)) {
*ep = *ep ? expr_alloc_two(type, *ep, e1) : e1;
expr_free(e2);
if (type == E_AND) {
e1 = expr_alloc_symbol(&symbol_yes);
e2 = expr_alloc_symbol(&symbol_yes);
} else if (type == E_OR) {
e1 = expr_alloc_symbol(&symbol_no);
e2 = expr_alloc_symbol(&symbol_no);
}
}
#undef e1
#undef e2
}
struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym)
{
struct expr *e1, *e2;
if (!e) {
e = expr_alloc_symbol(sym);
if (type == E_UNEQUAL)
e = expr_alloc_one(E_NOT, e);
return e;
}
switch (e->type) {
case E_AND:
e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym);
e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym);
if (sym == &symbol_yes)
e = expr_alloc_two(E_AND, e1, e2);
if (sym == &symbol_no)
e = expr_alloc_two(E_OR, e1, e2);
if (type == E_UNEQUAL)
e = expr_alloc_one(E_NOT, e);
return e;
case E_OR:
e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym);
e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym);
if (sym == &symbol_yes)
e = expr_alloc_two(E_OR, e1, e2);
if (sym == &symbol_no)
e = expr_alloc_two(E_AND, e1, e2);
if (type == E_UNEQUAL)
e = expr_alloc_one(E_NOT, e);
return e;
case E_NOT:
return expr_trans_compare(e->left.expr, type == E_EQUAL ? E_UNEQUAL : E_EQUAL, sym);
case E_UNEQUAL:
case E_EQUAL:
if (type == E_EQUAL) {
if (sym == &symbol_yes)
return expr_copy(e);
if (sym == &symbol_mod)
return expr_alloc_symbol(&symbol_no);
if (sym == &symbol_no)
return expr_alloc_one(E_NOT, expr_copy(e));
} else {
if (sym == &symbol_yes)
return expr_alloc_one(E_NOT, expr_copy(e));
if (sym == &symbol_mod)
return expr_alloc_symbol(&symbol_yes);
if (sym == &symbol_no)
return expr_copy(e);
}
break;
case E_SYMBOL:
return expr_alloc_comp(type, e->left.sym, sym);
case E_LIST:
case E_RANGE:
case E_NONE:
/* panic */;
}
return NULL;
}
tristate expr_calc_value(struct expr *e)
{
tristate val1, val2;
const char *str1, *str2;
if (!e)
return yes;
switch (e->type) {
case E_SYMBOL:
sym_calc_value(e->left.sym);
return e->left.sym->curr.tri;
case E_AND:
val1 = expr_calc_value(e->left.expr);
val2 = expr_calc_value(e->right.expr);
return EXPR_AND(val1, val2);
case E_OR:
val1 = expr_calc_value(e->left.expr);
val2 = expr_calc_value(e->right.expr);
return EXPR_OR(val1, val2);
case E_NOT:
val1 = expr_calc_value(e->left.expr);
return EXPR_NOT(val1);
case E_EQUAL:
sym_calc_value(e->left.sym);
sym_calc_value(e->right.sym);
str1 = sym_get_string_value(e->left.sym);
str2 = sym_get_string_value(e->right.sym);
return !strcmp(str1, str2) ? yes : no;
case E_UNEQUAL:
sym_calc_value(e->left.sym);
sym_calc_value(e->right.sym);
str1 = sym_get_string_value(e->left.sym);
str2 = sym_get_string_value(e->right.sym);
return !strcmp(str1, str2) ? no : yes;
default:
printf("expr_calc_value: %d?\n", e->type);
return no;
}
}
int expr_compare_type(enum expr_type t1, enum expr_type t2)
{
#if 0
return 1;
#else
if (t1 == t2)
return 0;
switch (t1) {
case E_EQUAL:
case E_UNEQUAL:
if (t2 == E_NOT)
return 1;
case E_NOT:
if (t2 == E_AND)
return 1;
case E_AND:
if (t2 == E_OR)
return 1;
case E_OR:
if (t2 == E_LIST)
return 1;
case E_LIST:
if (t2 == 0)
return 1;
default:
return -1;
}
printf("[%dgt%d?]", t1, t2);
return 0;
#endif
}
static inline struct expr *
expr_get_leftmost_symbol(const struct expr *e)
{
if (e == NULL)
return NULL;
while (e->type != E_SYMBOL)
e = e->left.expr;
return expr_copy(e);
}
/*
* Given expression `e1' and `e2', returns the leaf of the longest
* sub-expression of `e1' not containing 'e2.
*/
struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2)
{
struct expr *ret;
switch (e1->type) {
case E_OR:
return expr_alloc_and(
expr_simplify_unmet_dep(e1->left.expr, e2),
expr_simplify_unmet_dep(e1->right.expr, e2));
case E_AND: {
struct expr *e;
e = expr_alloc_and(expr_copy(e1), expr_copy(e2));
e = expr_eliminate_dups(e);
ret = (!expr_eq(e, e1)) ? e1 : NULL;
expr_free(e);
break;
}
default:
ret = e1;
break;
}
return expr_get_leftmost_symbol(ret);
}
void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken)
{
if (!e) {
fn(data, NULL, "y");
return;
}
if (expr_compare_type(prevtoken, e->type) > 0)
fn(data, NULL, "(");
switch (e->type) {
case E_SYMBOL:
if (e->left.sym->name)
fn(data, e->left.sym, e->left.sym->name);
else
fn(data, NULL, "<choice>");
break;
case E_NOT:
fn(data, NULL, "!");
expr_print(e->left.expr, fn, data, E_NOT);
break;
case E_EQUAL:
if (e->left.sym->name)
fn(data, e->left.sym, e->left.sym->name);
else
fn(data, NULL, "<choice>");
fn(data, NULL, "=");
fn(data, e->right.sym, e->right.sym->name);
break;
case E_UNEQUAL:
if (e->left.sym->name)
fn(data, e->left.sym, e->left.sym->name);
else
fn(data, NULL, "<choice>");
fn(data, NULL, "!=");
fn(data, e->right.sym, e->right.sym->name);
break;
case E_OR:
expr_print(e->left.expr, fn, data, E_OR);
fn(data, NULL, " || ");
expr_print(e->right.expr, fn, data, E_OR);
break;
case E_AND:
expr_print(e->left.expr, fn, data, E_AND);
fn(data, NULL, " && ");
expr_print(e->right.expr, fn, data, E_AND);
break;
case E_LIST:
fn(data, e->right.sym, e->right.sym->name);
if (e->left.expr) {
fn(data, NULL, " ^ ");
expr_print(e->left.expr, fn, data, E_LIST);
}
break;
case E_RANGE:
fn(data, NULL, "[");
fn(data, e->left.sym, e->left.sym->name);
fn(data, NULL, " ");
fn(data, e->right.sym, e->right.sym->name);
fn(data, NULL, "]");
break;
default:
{
char buf[32];
sprintf(buf, "<unknown type %d>", e->type);
fn(data, NULL, buf);
break;
}
}
if (expr_compare_type(prevtoken, e->type) > 0)
fn(data, NULL, ")");
}
static void expr_print_file_helper(void *data, struct symbol *sym, const char *str)
{
xfwrite(str, strlen(str), 1, data);
}
void expr_fprint(struct expr *e, FILE *out)
{
expr_print(e, expr_print_file_helper, out, E_NONE);
}
static void expr_print_gstr_helper(void *data, struct symbol *sym, const char *str)
{
struct gstr *gs = (struct gstr*)data;
const char *sym_str = NULL;
if (sym)
sym_str = sym_get_string_value(sym);
if (gs->max_width) {
unsigned extra_length = strlen(str);
const char *last_cr = strrchr(gs->s, '\n');
unsigned last_line_length;
if (sym_str)
extra_length += 4 + strlen(sym_str);
if (!last_cr)
last_cr = gs->s;
last_line_length = strlen(gs->s) - (last_cr - gs->s);
if ((last_line_length + extra_length) > gs->max_width)
str_append(gs, "\\\n");
}
str_append(gs, str);
if (sym && sym->type != S_UNKNOWN)
str_printf(gs, " [=%s]", sym_str);
}
void expr_gstr_print(struct expr *e, struct gstr *gs)
{
expr_print(e, expr_print_gstr_helper, gs, E_NONE);
}
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#ifndef EXPR_H
#define EXPR_H
#ifdef __cplusplus
extern "C" {
#endif
#include <assert.h>
#include <stdio.h>
#ifndef __cplusplus
#include <stdbool.h>
#endif
struct file {
struct file *next;
struct file *parent;
const char *name;
int lineno;
};
typedef enum tristate {
no, mod, yes
} tristate;
enum expr_type {
E_NONE, E_OR, E_AND, E_NOT, E_EQUAL, E_UNEQUAL, E_LIST, E_SYMBOL, E_RANGE
};
union expr_data {
struct expr *expr;
struct symbol *sym;
};
struct expr {
enum expr_type type;
union expr_data left, right;
};
#define EXPR_OR(dep1, dep2) (((dep1)>(dep2))?(dep1):(dep2))
#define EXPR_AND(dep1, dep2) (((dep1)<(dep2))?(dep1):(dep2))
#define EXPR_NOT(dep) (2-(dep))
#define expr_list_for_each_sym(l, e, s) \
for (e = (l); e && (s = e->right.sym); e = e->left.expr)
struct expr_value {
struct expr *expr;
tristate tri;
};
struct symbol_value {
void *val;
tristate tri;
};
enum symbol_type {
S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING, S_OTHER
};
/* enum values are used as index to symbol.def[] */
enum {
S_DEF_USER, /* main user value */
S_DEF_AUTO, /* values read from auto.conf */
S_DEF_DEF3, /* Reserved for UI usage */
S_DEF_DEF4, /* Reserved for UI usage */
S_DEF_COUNT
};
struct symbol {
struct symbol *next;
char *name;
enum symbol_type type;
struct symbol_value curr;
struct symbol_value def[S_DEF_COUNT];
tristate visible;
int flags;
struct property *prop;
struct expr_value dir_dep;
struct expr_value rev_dep;
};
#define for_all_symbols(i, sym) for (i = 0; i < SYMBOL_HASHSIZE; i++) for (sym = symbol_hash[i]; sym; sym = sym->next) if (sym->type != S_OTHER)
#define SYMBOL_CONST 0x0001 /* symbol is const */
#define SYMBOL_CHECK 0x0008 /* used during dependency checking */
#define SYMBOL_CHOICE 0x0010 /* start of a choice block (null name) */
#define SYMBOL_CHOICEVAL 0x0020 /* used as a value in a choice block */
#define SYMBOL_VALID 0x0080 /* set when symbol.curr is calculated */
#define SYMBOL_OPTIONAL 0x0100 /* choice is optional - values can be 'n' */
#define SYMBOL_WRITE 0x0200 /* ? */
#define SYMBOL_CHANGED 0x0400 /* ? */
#define SYMBOL_AUTO 0x1000 /* value from environment variable */
#define SYMBOL_CHECKED 0x2000 /* used during dependency checking */
#define SYMBOL_WARNED 0x8000 /* warning has been issued */
/* Set when symbol.def[] is used */
#define SYMBOL_DEF 0x10000 /* First bit of SYMBOL_DEF */
#define SYMBOL_DEF_USER 0x10000 /* symbol.def[S_DEF_USER] is valid */
#define SYMBOL_DEF_AUTO 0x20000 /* symbol.def[S_DEF_AUTO] is valid */
#define SYMBOL_DEF3 0x40000 /* symbol.def[S_DEF_3] is valid */
#define SYMBOL_DEF4 0x80000 /* symbol.def[S_DEF_4] is valid */
#define SYMBOL_MAXLENGTH 256
#define SYMBOL_HASHSIZE 9973
/* A property represent the config options that can be associated
* with a config "symbol".
* Sample:
* config FOO
* default y
* prompt "foo prompt"
* select BAR
* config BAZ
* int "BAZ Value"
* range 1..255
*/
enum prop_type {
P_UNKNOWN,
P_PROMPT, /* prompt "foo prompt" or "BAZ Value" */
P_COMMENT, /* text associated with a comment */
P_MENU, /* prompt associated with a menuconfig option */
P_DEFAULT, /* default y */
P_CHOICE, /* choice value */
P_SELECT, /* select BAR */
P_RANGE, /* range 7..100 (for a symbol) */
P_ENV, /* value from environment variable */
P_SYMBOL, /* where a symbol is defined */
};
struct property {
struct property *next; /* next property - null if last */
struct symbol *sym; /* the symbol for which the property is associated */
enum prop_type type; /* type of property */
const char *text; /* the prompt value - P_PROMPT, P_MENU, P_COMMENT */
struct expr_value visible;
struct expr *expr; /* the optional conditional part of the property */
struct menu *menu; /* the menu the property are associated with
* valid for: P_SELECT, P_RANGE, P_CHOICE,
* P_PROMPT, P_DEFAULT, P_MENU, P_COMMENT */
struct file *file; /* what file was this property defined */
int lineno; /* what lineno was this property defined */
};
#define for_all_properties(sym, st, tok) \
for (st = sym->prop; st; st = st->next) \
if (st->type == (tok))
#define for_all_defaults(sym, st) for_all_properties(sym, st, P_DEFAULT)
#define for_all_choices(sym, st) for_all_properties(sym, st, P_CHOICE)
#define for_all_prompts(sym, st) \
for (st = sym->prop; st; st = st->next) \
if (st->text)
struct menu {
struct menu *next;
struct menu *parent;
struct menu *list;
struct symbol *sym;
struct property *prompt;
struct expr *visibility;
struct expr *dep;
unsigned int flags;
char *help;
struct file *file;
int lineno;
void *data;
};
#define MENU_CHANGED 0x0001
#define MENU_ROOT 0x0002
extern struct file *file_list;
extern struct file *current_file;
struct file *lookup_file(const char *name);
extern struct symbol symbol_yes, symbol_no, symbol_mod;
extern struct symbol *modules_sym;
extern struct symbol *sym_defconfig_list;
extern int cdebug;
struct expr *expr_alloc_symbol(struct symbol *sym);
struct expr *expr_alloc_one(enum expr_type type, struct expr *ce);
struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2);
struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2);
struct expr *expr_alloc_and(struct expr *e1, struct expr *e2);
struct expr *expr_alloc_or(struct expr *e1, struct expr *e2);
struct expr *expr_copy(const struct expr *org);
void expr_free(struct expr *e);
int expr_eq(struct expr *e1, struct expr *e2);
void expr_eliminate_eq(struct expr **ep1, struct expr **ep2);
tristate expr_calc_value(struct expr *e);
struct expr *expr_eliminate_yn(struct expr *e);
struct expr *expr_trans_bool(struct expr *e);
struct expr *expr_eliminate_dups(struct expr *e);
struct expr *expr_transform(struct expr *e);
int expr_contains_symbol(struct expr *dep, struct symbol *sym);
bool expr_depends_symbol(struct expr *dep, struct symbol *sym);
struct expr *expr_extract_eq_and(struct expr **ep1, struct expr **ep2);
struct expr *expr_extract_eq_or(struct expr **ep1, struct expr **ep2);
void expr_extract_eq(enum expr_type type, struct expr **ep, struct expr **ep1, struct expr **ep2);
struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym);
struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2);
void expr_fprint(struct expr *e, FILE *out);
struct gstr; /* forward */
void expr_gstr_print(struct expr *e, struct gstr *gs);
static inline int expr_is_yes(struct expr *e)
{
return !e || (e->type == E_SYMBOL && e->left.sym == &symbol_yes);
}
static inline int expr_is_no(struct expr *e)
{
return e && (e->type == E_SYMBOL && e->left.sym == &symbol_no);
}
#ifdef __cplusplus
}
#endif
#endif /* EXPR_H */
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#ifndef LKC_H
#define LKC_H
#include "expr.h"
#ifndef KBUILD_NO_NLS
# include <libintl.h>
#else
static inline const char *gettext(const char *txt) { return txt; }
static inline void textdomain(const char *domainname) {}
static inline void bindtextdomain(const char *name, const char *dir) {}
static inline char *bind_textdomain_codeset(const char *dn, char *c) { return c; }
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define P(name,type,arg) extern type name arg
#include "lkc_proto.h"
#undef P
#define SRCTREE "srctree"
#ifndef PACKAGE
#define PACKAGE "linux"
#endif
#define LOCALEDIR "/usr/share/locale"
#define _(text) gettext(text)
#define N_(text) (text)
#ifndef CONFIG_
#define CONFIG_ "CONFIG_"
#endif
#define TF_COMMAND 0x0001
#define TF_PARAM 0x0002
#define TF_OPTION 0x0004
enum conf_def_mode {
def_default,
def_yes,
def_mod,
def_no,
def_random
};
#define T_OPT_MODULES 1
#define T_OPT_DEFCONFIG_LIST 2
#define T_OPT_ENV 3
struct kconf_id {
int name;
int token;
unsigned int flags;
enum symbol_type stype;
};
extern int zconfdebug;
int zconfparse(void);
void zconfdump(FILE *out);
void zconf_starthelp(void);
FILE *zconf_fopen(const char *name);
void zconf_initscan(const char *name);
void zconf_nextfile(const char *name);
int zconf_lineno(void);
const char *zconf_curname(void);
/* confdata.c */
const char *conf_get_configname(void);
const char *conf_get_autoconfig_name(void);
char *conf_get_default_confname(void);
void sym_set_change_count(int count);
void sym_add_change_count(int count);
void conf_set_all_new_symbols(enum conf_def_mode mode);
struct conf_printer {
void (*print_symbol)(FILE *, struct symbol *, const char *, void *);
void (*print_comment)(FILE *, const char *, void *);
};
/* confdata.c and expr.c */
static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out)
{
assert(len != 0);
if (fwrite(str, len, count, out) != count)
fprintf(stderr, "Error in writing or end of file.\n");
}
/* menu.c */
void _menu_init(void);
void menu_warn(struct menu *menu, const char *fmt, ...);
struct menu *menu_add_menu(void);
void menu_end_menu(void);
void menu_add_entry(struct symbol *sym);
void menu_end_entry(void);
void menu_add_dep(struct expr *dep);
void menu_add_visibility(struct expr *dep);
struct property *menu_add_prop(enum prop_type type, char *prompt, struct expr *expr, struct expr *dep);
struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep);
void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep);
void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep);
void menu_add_option(int token, char *arg);
void menu_finalize(struct menu *parent);
void menu_set_type(int type);
/* util.c */
struct file *file_lookup(const char *name);
int file_write_dep(const char *name);
struct gstr {
size_t len;
char *s;
/*
* when max_width is not zero long lines in string s (if any) get
* wrapped not to exceed the max_width value
*/
int max_width;
};
struct gstr str_new(void);
struct gstr str_assign(const char *s);
void str_free(struct gstr *gs);
void str_append(struct gstr *gs, const char *s);
void str_printf(struct gstr *gs, const char *fmt, ...);
const char *str_get(struct gstr *gs);
/* symbol.c */
extern struct expr *sym_env_list;
void sym_init(void);
void sym_clear_all_valid(void);
void sym_set_all_changed(void);
void sym_set_changed(struct symbol *sym);
struct symbol *sym_choice_default(struct symbol *sym);
const char *sym_get_string_default(struct symbol *sym);
struct symbol *sym_check_deps(struct symbol *sym);
struct property *prop_alloc(enum prop_type type, struct symbol *sym);
struct symbol *prop_get_symbol(struct property *prop);
struct property *sym_get_env_prop(struct symbol *sym);
static inline tristate sym_get_tristate_value(struct symbol *sym)
{
return sym->curr.tri;
}
static inline struct symbol *sym_get_choice_value(struct symbol *sym)
{
return (struct symbol *)sym->curr.val;
}
static inline bool sym_set_choice_value(struct symbol *ch, struct symbol *chval)
{
return sym_set_tristate_value(chval, yes);
}
static inline bool sym_is_choice(struct symbol *sym)
{
return sym->flags & SYMBOL_CHOICE ? true : false;
}
static inline bool sym_is_choice_value(struct symbol *sym)
{
return sym->flags & SYMBOL_CHOICEVAL ? true : false;
}
static inline bool sym_is_optional(struct symbol *sym)
{
return sym->flags & SYMBOL_OPTIONAL ? true : false;
}
static inline bool sym_has_value(struct symbol *sym)
{
return sym->flags & SYMBOL_DEF_USER ? true : false;
}
#ifdef __cplusplus
}
#endif
#endif /* LKC_H */
#include <stdarg.h>
/* confdata.c */
P(conf_parse,void,(const char *name));
P(conf_read,int,(const char *name));
P(conf_read_simple,int,(const char *name, int));
P(conf_write_defconfig,int,(const char *name));
P(conf_write,int,(const char *name));
P(conf_write_autoconf,int,(void));
P(conf_get_changed,bool,(void));
P(conf_set_changed_callback, void,(void (*fn)(void)));
P(conf_set_message_callback, void,(void (*fn)(const char *fmt, va_list ap)));
/* menu.c */
P(rootmenu,struct menu,);
P(menu_is_visible, bool, (struct menu *menu));
P(menu_has_prompt, bool, (struct menu *menu));
P(menu_get_prompt,const char *,(struct menu *menu));
P(menu_get_root_menu,struct menu *,(struct menu *menu));
P(menu_get_parent_menu,struct menu *,(struct menu *menu));
P(menu_has_help,bool,(struct menu *menu));
P(menu_get_help,const char *,(struct menu *menu));
P(get_symbol_str, void, (struct gstr *r, struct symbol *sym));
P(get_relations_str, struct gstr, (struct symbol **sym_arr));
P(menu_get_ext_help,void,(struct menu *menu, struct gstr *help));
/* symbol.c */
P(symbol_hash,struct symbol *,[SYMBOL_HASHSIZE]);
P(sym_lookup,struct symbol *,(const char *name, int flags));
P(sym_find,struct symbol *,(const char *name));
P(sym_expand_string_value,const char *,(const char *in));
P(sym_escape_string_value, const char *,(const char *in));
P(sym_re_search,struct symbol **,(const char *pattern));
P(sym_type_name,const char *,(enum symbol_type type));
P(sym_calc_value,void,(struct symbol *sym));
P(sym_get_type,enum symbol_type,(struct symbol *sym));
P(sym_tristate_within_range,bool,(struct symbol *sym,tristate tri));
P(sym_set_tristate_value,bool,(struct symbol *sym,tristate tri));
P(sym_toggle_tristate_value,tristate,(struct symbol *sym));
P(sym_string_valid,bool,(struct symbol *sym, const char *newval));
P(sym_string_within_range,bool,(struct symbol *sym, const char *str));
P(sym_set_string_value,bool,(struct symbol *sym, const char *newval));
P(sym_is_changable,bool,(struct symbol *sym));
P(sym_get_choice_prop,struct property *,(struct symbol *sym));
P(sym_get_default_prop,struct property *,(struct symbol *sym));
P(sym_get_string_value,const char *,(struct symbol *sym));
P(prop_get_type_name,const char *,(enum prop_type type));
/* expr.c */
P(expr_compare_type,int,(enum expr_type t1, enum expr_type t2));
P(expr_print,void,(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken));
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "lkc.h"
static const char nohelp_text[] = "There is no help available for this option.";
struct menu rootmenu;
static struct menu **last_entry_ptr;
struct file *file_list;
struct file *current_file;
void menu_warn(struct menu *menu, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "%s:%d:warning: ", menu->file->name, menu->lineno);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
static void prop_warn(struct property *prop, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "%s:%d:warning: ", prop->file->name, prop->lineno);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
void _menu_init(void)
{
current_entry = current_menu = &rootmenu;
last_entry_ptr = &rootmenu.list;
}
void menu_add_entry(struct symbol *sym)
{
struct menu *menu;
menu = malloc(sizeof(*menu));
memset(menu, 0, sizeof(*menu));
menu->sym = sym;
menu->parent = current_menu;
menu->file = current_file;
menu->lineno = zconf_lineno();
*last_entry_ptr = menu;
last_entry_ptr = &menu->next;
current_entry = menu;
if (sym)
menu_add_symbol(P_SYMBOL, sym, NULL);
}
void menu_end_entry(void)
{
}
struct menu *menu_add_menu(void)
{
menu_end_entry();
last_entry_ptr = &current_entry->list;
return current_menu = current_entry;
}
void menu_end_menu(void)
{
last_entry_ptr = &current_menu->next;
current_menu = current_menu->parent;
}
static struct expr *menu_check_dep(struct expr *e)
{
if (!e)
return e;
switch (e->type) {
case E_NOT:
e->left.expr = menu_check_dep(e->left.expr);
break;
case E_OR:
case E_AND:
e->left.expr = menu_check_dep(e->left.expr);
e->right.expr = menu_check_dep(e->right.expr);
break;
case E_SYMBOL:
/* change 'm' into 'm' && MODULES */
if (e->left.sym == &symbol_mod)
return expr_alloc_and(e, expr_alloc_symbol(modules_sym));
break;
default:
break;
}
return e;
}
void menu_add_dep(struct expr *dep)
{
current_entry->dep = expr_alloc_and(current_entry->dep, menu_check_dep(dep));
}
void menu_set_type(int type)
{
struct symbol *sym = current_entry->sym;
if (sym->type == type)
return;
if (sym->type == S_UNKNOWN) {
sym->type = type;
return;
}
menu_warn(current_entry, "type of '%s' redefined from '%s' to '%s'",
sym->name ? sym->name : "<choice>",
sym_type_name(sym->type), sym_type_name(type));
}
struct property *menu_add_prop(enum prop_type type, char *prompt, struct expr *expr, struct expr *dep)
{
struct property *prop = prop_alloc(type, current_entry->sym);
prop->menu = current_entry;
prop->expr = expr;
prop->visible.expr = menu_check_dep(dep);
if (prompt) {
if (isspace(*prompt)) {
prop_warn(prop, "leading whitespace ignored");
while (isspace(*prompt))
prompt++;
}
if (current_entry->prompt && current_entry != &rootmenu)
prop_warn(prop, "prompt redefined");
/* Apply all upper menus' visibilities to actual prompts. */
if(type == P_PROMPT) {
struct menu *menu = current_entry;
while ((menu = menu->parent) != NULL) {
if (!menu->visibility)
continue;
prop->visible.expr
= expr_alloc_and(prop->visible.expr,
menu->visibility);
}
}
current_entry->prompt = prop;
}
prop->text = prompt;
return prop;
}
struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep)
{
return menu_add_prop(type, prompt, NULL, dep);
}
void menu_add_visibility(struct expr *expr)
{
current_entry->visibility = expr_alloc_and(current_entry->visibility,
expr);
}
void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep)
{
menu_add_prop(type, NULL, expr, dep);
}
void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep)
{
menu_add_prop(type, NULL, expr_alloc_symbol(sym), dep);
}
void menu_add_option(int token, char *arg)
{
struct property *prop;
switch (token) {
case T_OPT_MODULES:
prop = prop_alloc(P_DEFAULT, modules_sym);
prop->expr = expr_alloc_symbol(current_entry->sym);
break;
case T_OPT_DEFCONFIG_LIST:
if (!sym_defconfig_list)
sym_defconfig_list = current_entry->sym;
else if (sym_defconfig_list != current_entry->sym)
zconf_error("trying to redefine defconfig symbol");
break;
case T_OPT_ENV:
prop_add_env(arg);
break;
}
}
static int menu_validate_number(struct symbol *sym, struct symbol *sym2)
{
return sym2->type == S_INT || sym2->type == S_HEX ||
(sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name));
}
static void sym_check_prop(struct symbol *sym)
{
struct property *prop;
struct symbol *sym2;
for (prop = sym->prop; prop; prop = prop->next) {
switch (prop->type) {
case P_DEFAULT:
if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) &&
prop->expr->type != E_SYMBOL)
prop_warn(prop,
"default for config symbol '%s'"
" must be a single symbol", sym->name);
if (prop->expr->type != E_SYMBOL)
break;
sym2 = prop_get_symbol(prop);
if (sym->type == S_HEX || sym->type == S_INT) {
if (!menu_validate_number(sym, sym2))
prop_warn(prop,
"'%s': number is invalid",
sym->name);
}
break;
case P_SELECT:
sym2 = prop_get_symbol(prop);
if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE)
prop_warn(prop,
"config symbol '%s' uses select, but is "
"not boolean or tristate", sym->name);
else if (sym2->type != S_UNKNOWN &&
sym2->type != S_BOOLEAN &&
sym2->type != S_TRISTATE)
prop_warn(prop,
"'%s' has wrong type. 'select' only "
"accept arguments of boolean and "
"tristate type", sym2->name);
break;
case P_RANGE:
if (sym->type != S_INT && sym->type != S_HEX)
prop_warn(prop, "range is only allowed "
"for int or hex symbols");
if (!menu_validate_number(sym, prop->expr->left.sym) ||
!menu_validate_number(sym, prop->expr->right.sym))
prop_warn(prop, "range is invalid");
break;
default:
;
}
}
}
void menu_finalize(struct menu *parent)
{
struct menu *menu, *last_menu;
struct symbol *sym;
struct property *prop;
struct expr *parentdep, *basedep, *dep, *dep2, **ep;
sym = parent->sym;
if (parent->list) {
if (sym && sym_is_choice(sym)) {
if (sym->type == S_UNKNOWN) {
/* find the first choice value to find out choice type */
current_entry = parent;
for (menu = parent->list; menu; menu = menu->next) {
if (menu->sym && menu->sym->type != S_UNKNOWN) {
menu_set_type(menu->sym->type);
break;
}
}
}
/* set the type of the remaining choice values */
for (menu = parent->list; menu; menu = menu->next) {
current_entry = menu;
if (menu->sym && menu->sym->type == S_UNKNOWN)
menu_set_type(sym->type);
}
parentdep = expr_alloc_symbol(sym);
} else if (parent->prompt)
parentdep = parent->prompt->visible.expr;
else
parentdep = parent->dep;
for (menu = parent->list; menu; menu = menu->next) {
basedep = expr_transform(menu->dep);
basedep = expr_alloc_and(expr_copy(parentdep), basedep);
basedep = expr_eliminate_dups(basedep);
menu->dep = basedep;
if (menu->sym)
prop = menu->sym->prop;
else
prop = menu->prompt;
for (; prop; prop = prop->next) {
if (prop->menu != menu)
continue;
dep = expr_transform(prop->visible.expr);
dep = expr_alloc_and(expr_copy(basedep), dep);
dep = expr_eliminate_dups(dep);
if (menu->sym && menu->sym->type != S_TRISTATE)
dep = expr_trans_bool(dep);
prop->visible.expr = dep;
if (prop->type == P_SELECT) {
struct symbol *es = prop_get_symbol(prop);
es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr,
expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep)));
}
}
}
for (menu = parent->list; menu; menu = menu->next)
menu_finalize(menu);
} else if (sym) {
basedep = parent->prompt ? parent->prompt->visible.expr : NULL;
basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no);
basedep = expr_eliminate_dups(expr_transform(basedep));
last_menu = NULL;
for (menu = parent->next; menu; menu = menu->next) {
dep = menu->prompt ? menu->prompt->visible.expr : menu->dep;
if (!expr_contains_symbol(dep, sym))
break;
if (expr_depends_symbol(dep, sym))
goto next;
dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no);
dep = expr_eliminate_dups(expr_transform(dep));
dep2 = expr_copy(basedep);
expr_eliminate_eq(&dep, &dep2);
expr_free(dep);
if (!expr_is_yes(dep2)) {
expr_free(dep2);
break;
}
expr_free(dep2);
next:
menu_finalize(menu);
menu->parent = parent;
last_menu = menu;
}
if (last_menu) {
parent->list = parent->next;
parent->next = last_menu->next;
last_menu->next = NULL;
}
sym->dir_dep.expr = expr_alloc_or(sym->dir_dep.expr, parent->dep);
}
for (menu = parent->list; menu; menu = menu->next) {
if (sym && sym_is_choice(sym) &&
menu->sym && !sym_is_choice_value(menu->sym)) {
current_entry = menu;
menu->sym->flags |= SYMBOL_CHOICEVAL;
if (!menu->prompt)
menu_warn(menu, "choice value must have a prompt");
for (prop = menu->sym->prop; prop; prop = prop->next) {
if (prop->type == P_DEFAULT)
prop_warn(prop, "defaults for choice "
"values not supported");
if (prop->menu == menu)
continue;
if (prop->type == P_PROMPT &&
prop->menu->parent->sym != sym)
prop_warn(prop, "choice value used outside its choice group");
}
/* Non-tristate choice values of tristate choices must
* depend on the choice being set to Y. The choice
* values' dependencies were propagated to their
* properties above, so the change here must be re-
* propagated.
*/
if (sym->type == S_TRISTATE && menu->sym->type != S_TRISTATE) {
basedep = expr_alloc_comp(E_EQUAL, sym, &symbol_yes);
menu->dep = expr_alloc_and(basedep, menu->dep);
for (prop = menu->sym->prop; prop; prop = prop->next) {
if (prop->menu != menu)
continue;
prop->visible.expr = expr_alloc_and(expr_copy(basedep),
prop->visible.expr);
}
}
menu_add_symbol(P_CHOICE, sym, NULL);
prop = sym_get_choice_prop(sym);
for (ep = &prop->expr; *ep; ep = &(*ep)->left.expr)
;
*ep = expr_alloc_one(E_LIST, NULL);
(*ep)->right.sym = menu->sym;
}
if (menu->list && (!menu->prompt || !menu->prompt->text)) {
for (last_menu = menu->list; ; last_menu = last_menu->next) {
last_menu->parent = parent;
if (!last_menu->next)
break;
}
last_menu->next = menu->next;
menu->next = menu->list;
menu->list = NULL;
}
}
if (sym && !(sym->flags & SYMBOL_WARNED)) {
if (sym->type == S_UNKNOWN)
menu_warn(parent, "config symbol defined without type");
if (sym_is_choice(sym) && !parent->prompt)
menu_warn(parent, "choice must have a prompt");
/* Check properties connected to this symbol */
sym_check_prop(sym);
sym->flags |= SYMBOL_WARNED;
}
if (sym && !sym_is_optional(sym) && parent->prompt) {
sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr,
expr_alloc_and(parent->prompt->visible.expr,
expr_alloc_symbol(&symbol_mod)));
}
}
bool menu_has_prompt(struct menu *menu)
{
if (!menu->prompt)
return false;
return true;
}
bool menu_is_visible(struct menu *menu)
{
struct menu *child;
struct symbol *sym;
tristate visible;
if (!menu->prompt)
return false;
if (menu->visibility) {
if (expr_calc_value(menu->visibility) == no)
return no;
}
sym = menu->sym;
if (sym) {
sym_calc_value(sym);
visible = menu->prompt->visible.tri;
} else
visible = menu->prompt->visible.tri = expr_calc_value(menu->prompt->visible.expr);
if (visible != no)
return true;
if (!sym || sym_get_tristate_value(menu->sym) == no)
return false;
for (child = menu->list; child; child = child->next) {
if (menu_is_visible(child)) {
if (sym)
sym->flags |= SYMBOL_DEF_USER;
return true;
}
}
return false;
}
const char *menu_get_prompt(struct menu *menu)
{
if (menu->prompt)
return menu->prompt->text;
else if (menu->sym)
return menu->sym->name;
return NULL;
}
struct menu *menu_get_root_menu(struct menu *menu)
{
return &rootmenu;
}
struct menu *menu_get_parent_menu(struct menu *menu)
{
enum prop_type type;
for (; menu != &rootmenu; menu = menu->parent) {
type = menu->prompt ? menu->prompt->type : 0;
if (type == P_MENU)
break;
}
return menu;
}
bool menu_has_help(struct menu *menu)
{
return menu->help != NULL;
}
const char *menu_get_help(struct menu *menu)
{
if (menu->help)
return menu->help;
else
return "";
}
static void get_prompt_str(struct gstr *r, struct property *prop)
{
int i, j;
struct menu *submenu[8], *menu;
str_printf(r, _("Prompt: %s\n"), _(prop->text));
str_printf(r, _(" Defined at %s:%d\n"), prop->menu->file->name,
prop->menu->lineno);
if (!expr_is_yes(prop->visible.expr)) {
str_append(r, _(" Depends on: "));
expr_gstr_print(prop->visible.expr, r);
str_append(r, "\n");
}
menu = prop->menu->parent;
for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent)
submenu[i++] = menu;
if (i > 0) {
str_printf(r, _(" Location:\n"));
for (j = 4; --i >= 0; j += 2) {
menu = submenu[i];
str_printf(r, "%*c-> %s", j, ' ', _(menu_get_prompt(menu)));
if (menu->sym) {
str_printf(r, " (%s [=%s])", menu->sym->name ?
menu->sym->name : _("<choice>"),
sym_get_string_value(menu->sym));
}
str_append(r, "\n");
}
}
}
void get_symbol_str(struct gstr *r, struct symbol *sym)
{
bool hit;
struct property *prop;
if (sym && sym->name) {
str_printf(r, "Symbol: %s [=%s]\n", sym->name,
sym_get_string_value(sym));
str_printf(r, "Type : %s\n", sym_type_name(sym->type));
if (sym->type == S_INT || sym->type == S_HEX) {
prop = sym_get_range_prop(sym);
if (prop) {
str_printf(r, "Range : ");
expr_gstr_print(prop->expr, r);
str_append(r, "\n");
}
}
}
for_all_prompts(sym, prop)
get_prompt_str(r, prop);
hit = false;
for_all_properties(sym, prop, P_SELECT) {
if (!hit) {
str_append(r, " Selects: ");
hit = true;
} else
str_printf(r, " && ");
expr_gstr_print(prop->expr, r);
}
if (hit)
str_append(r, "\n");
if (sym->rev_dep.expr) {
str_append(r, _(" Selected by: "));
expr_gstr_print(sym->rev_dep.expr, r);
str_append(r, "\n");
}
str_append(r, "\n\n");
}
struct gstr get_relations_str(struct symbol **sym_arr)
{
struct symbol *sym;
struct gstr res = str_new();
int i;
for (i = 0; sym_arr && (sym = sym_arr[i]); i++)
get_symbol_str(&res, sym);
if (!i)
str_append(&res, _("No matches found.\n"));
return res;
}
void menu_get_ext_help(struct menu *menu, struct gstr *help)
{
struct symbol *sym = menu->sym;
const char *help_text = nohelp_text;
if (menu_has_help(menu)) {
if (sym->name)
str_printf(help, "%s%s:\n\n", CONFIG_, sym->name);
help_text = menu_get_help(menu);
}
str_printf(help, "%s\n", _(help_text));
if (sym)
get_symbol_str(help, sym);
}
/*
* Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
* Released under the terms of the GNU GPL v2.0.
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include <sys/utsname.h>
#include "lkc.h"
struct symbol symbol_yes = {
.name = "y",
.curr = { "y", yes },
.flags = SYMBOL_CONST|SYMBOL_VALID,
}, symbol_mod = {
.name = "m",
.curr = { "m", mod },
.flags = SYMBOL_CONST|SYMBOL_VALID,
}, symbol_no = {
.name = "n",
.curr = { "n", no },
.flags = SYMBOL_CONST|SYMBOL_VALID,
}, symbol_empty = {
.name = "",
.curr = { "", no },
.flags = SYMBOL_VALID,
};
struct symbol *sym_defconfig_list;
struct symbol *modules_sym;
tristate modules_val;
struct expr *sym_env_list;
static void sym_add_default(struct symbol *sym, const char *def)
{
struct property *prop = prop_alloc(P_DEFAULT, sym);
prop->expr = expr_alloc_symbol(sym_lookup(def, SYMBOL_CONST));
}
void sym_init(void)
{
struct symbol *sym;
struct utsname uts;
static bool inited = false;
if (inited)
return;
inited = true;
uname(&uts);
sym = sym_lookup("UNAME_RELEASE", 0);
sym->type = S_STRING;
sym->flags |= SYMBOL_AUTO;
sym_add_default(sym, uts.release);
}
enum symbol_type sym_get_type(struct symbol *sym)
{
enum symbol_type type = sym->type;
if (type == S_TRISTATE) {
if (sym_is_choice_value(sym) && sym->visible == yes)
type = S_BOOLEAN;
else if (modules_val == no)
type = S_BOOLEAN;
}
return type;
}
const char *sym_type_name(enum symbol_type type)
{
switch (type) {
case S_BOOLEAN:
return "boolean";
case S_TRISTATE:
return "tristate";
case S_INT:
return "integer";
case S_HEX:
return "hex";
case S_STRING:
return "string";
case S_UNKNOWN:
return "unknown";
case S_OTHER:
break;
}
return "???";
}
struct property *sym_get_choice_prop(struct symbol *sym)
{
struct property *prop;
for_all_choices(sym, prop)
return prop;
return NULL;
}
struct property *sym_get_env_prop(struct symbol *sym)
{
struct property *prop;
for_all_properties(sym, prop, P_ENV)
return prop;
return NULL;
}
struct property *sym_get_default_prop(struct symbol *sym)
{
struct property *prop;
for_all_defaults(sym, prop) {
prop->visible.tri = expr_calc_value(prop->visible.expr);
if (prop->visible.tri != no)
return prop;
}
return NULL;
}
static struct property *sym_get_range_prop(struct symbol *sym)
{
struct property *prop;
for_all_properties(sym, prop, P_RANGE) {
prop->visible.tri = expr_calc_value(prop->visible.expr);
if (prop->visible.tri != no)
return prop;
}
return NULL;
}
static int sym_get_range_val(struct symbol *sym, int base)
{
sym_calc_value(sym);
switch (sym->type) {
case S_INT:
base = 10;
break;
case S_HEX:
base = 16;
break;
default:
break;
}
return strtol(sym->curr.val, NULL, base);
}
static void sym_validate_range(struct symbol *sym)
{
struct property *prop;
int base, val, val2;
char str[64];
switch (sym->type) {
case S_INT:
base = 10;
break;
case S_HEX:
base = 16;
break;
default:
return;
}
prop = sym_get_range_prop(sym);
if (!prop)
return;
val = strtol(sym->curr.val, NULL, base);
val2 = sym_get_range_val(prop->expr->left.sym, base);
if (val >= val2) {
val2 = sym_get_range_val(prop->expr->right.sym, base);
if (val <= val2)
return;
}
if (sym->type == S_INT)
sprintf(str, "%d", val2);
else
sprintf(str, "0x%x", val2);
sym->curr.val = strdup(str);
}
static void sym_calc_visibility(struct symbol *sym)
{
struct property *prop;
tristate tri;
/* any prompt visible? */
tri = no;
for_all_prompts(sym, prop) {
prop->visible.tri = expr_calc_value(prop->visible.expr);
tri = EXPR_OR(tri, prop->visible.tri);
}
if (tri == mod && (sym->type != S_TRISTATE || modules_val == no))
tri = yes;
if (sym->visible != tri) {
sym->visible = tri;
sym_set_changed(sym);
}
if (sym_is_choice_value(sym))
return;
/* defaulting to "yes" if no explicit "depends on" are given */
tri = yes;
if (sym->dir_dep.expr)
tri = expr_calc_value(sym->dir_dep.expr);
if (tri == mod)
tri = yes;
if (sym->dir_dep.tri != tri) {
sym->dir_dep.tri = tri;
sym_set_changed(sym);
}
tri = no;
if (sym->rev_dep.expr)
tri = expr_calc_value(sym->rev_dep.expr);
if (tri == mod && sym_get_type(sym) == S_BOOLEAN)
tri = yes;
if (sym->rev_dep.tri != tri) {
sym->rev_dep.tri = tri;
sym_set_changed(sym);
}
}
/*
* Find the default symbol for a choice.
* First try the default values for the choice symbol
* Next locate the first visible choice value
* Return NULL if none was found
*/
struct symbol *sym_choice_default(struct symbol *sym)
{
struct symbol *def_sym;
struct property *prop;
struct expr *e;
/* any of the defaults visible? */
for_all_defaults(sym, prop) {
prop->visible.tri = expr_calc_value(prop->visible.expr);
if (prop->visible.tri == no)
continue;
def_sym = prop_get_symbol(prop);
if (def_sym->visible != no)
return def_sym;
}
/* just get the first visible value */
prop = sym_get_choice_prop(sym);
expr_list_for_each_sym(prop->expr, e, def_sym)
if (def_sym->visible != no)
return def_sym;
/* failed to locate any defaults */
return NULL;
}
static struct symbol *sym_calc_choice(struct symbol *sym)
{
struct symbol *def_sym;
struct property *prop;
struct expr *e;
int flags;
/* first calculate all choice values' visibilities */
flags = sym->flags;
prop = sym_get_choice_prop(sym);
expr_list_for_each_sym(prop->expr, e, def_sym) {
sym_calc_visibility(def_sym);
if (def_sym->visible != no)
flags &= def_sym->flags;
}
sym->flags &= flags | ~SYMBOL_DEF_USER;
/* is the user choice visible? */
def_sym = sym->def[S_DEF_USER].val;
if (def_sym && def_sym->visible != no)
return def_sym;
def_sym = sym_choice_default(sym);
if (def_sym == NULL)
/* no choice? reset tristate value */
sym->curr.tri = no;
return def_sym;
}
void sym_calc_value(struct symbol *sym)
{
struct symbol_value newval, oldval;
struct property *prop;
struct expr *e;
if (!sym)
return;
if (sym->flags & SYMBOL_VALID)
return;
sym->flags |= SYMBOL_VALID;
oldval = sym->curr;
switch (sym->type) {
case S_INT:
case S_HEX:
case S_STRING:
newval = symbol_empty.curr;
break;
case S_BOOLEAN:
case S_TRISTATE:
newval = symbol_no.curr;
break;
default:
sym->curr.val = sym->name;
sym->curr.tri = no;
return;
}
if (!sym_is_choice_value(sym))
sym->flags &= ~SYMBOL_WRITE;
sym_calc_visibility(sym);
/* set default if recursively called */
sym->curr = newval;
switch (sym_get_type(sym)) {
case S_BOOLEAN:
case S_TRISTATE:
if (sym_is_choice_value(sym) && sym->visible == yes) {
prop = sym_get_choice_prop(sym);
newval.tri = (prop_get_symbol(prop)->curr.val == sym) ? yes : no;
} else {
if (sym->visible != no) {
/* if the symbol is visible use the user value
* if available, otherwise try the default value
*/
sym->flags |= SYMBOL_WRITE;
if (sym_has_value(sym)) {
newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
sym->visible);
goto calc_newval;
}
}
if (sym->rev_dep.tri != no)
sym->flags |= SYMBOL_WRITE;
if (!sym_is_choice(sym)) {
prop = sym_get_default_prop(sym);
if (prop) {
sym->flags |= SYMBOL_WRITE;
newval.tri = EXPR_AND(expr_calc_value(prop->expr),
prop->visible.tri);
}
}
calc_newval:
if (sym->dir_dep.tri == no && sym->rev_dep.tri != no) {
struct expr *e;
e = expr_simplify_unmet_dep(sym->rev_dep.expr,
sym->dir_dep.expr);
fprintf(stderr, "warning: (");
expr_fprint(e, stderr);
fprintf(stderr, ") selects %s which has unmet direct dependencies (",
sym->name);
expr_fprint(sym->dir_dep.expr, stderr);
fprintf(stderr, ")\n");
expr_free(e);
}
newval.tri = EXPR_OR(newval.tri, sym->rev_dep.tri);
}
if (newval.tri == mod && sym_get_type(sym) == S_BOOLEAN)
newval.tri = yes;
break;
case S_STRING:
case S_HEX:
case S_INT:
if (sym->visible != no) {
sym->flags |= SYMBOL_WRITE;
if (sym_has_value(sym)) {
newval.val = sym->def[S_DEF_USER].val;
break;
}
}
prop = sym_get_default_prop(sym);
if (prop) {
struct symbol *ds = prop_get_symbol(prop);
if (ds) {
sym->flags |= SYMBOL_WRITE;
sym_calc_value(ds);
newval.val = ds->curr.val;
}
}
break;
default:
;
}
sym->curr = newval;
if (sym_is_choice(sym) && newval.tri == yes)
sym->curr.val = sym_calc_choice(sym);
sym_validate_range(sym);
if (memcmp(&oldval, &sym->curr, sizeof(oldval))) {
sym_set_changed(sym);
if (modules_sym == sym) {
sym_set_all_changed();
modules_val = modules_sym->curr.tri;
}
}
if (sym_is_choice(sym)) {
struct symbol *choice_sym;
prop = sym_get_choice_prop(sym);
expr_list_for_each_sym(prop->expr, e, choice_sym) {
if ((sym->flags & SYMBOL_WRITE) &&
choice_sym->visible != no)
choice_sym->flags |= SYMBOL_WRITE;
if (sym->flags & SYMBOL_CHANGED)
sym_set_changed(choice_sym);
}
}
if (sym->flags & SYMBOL_AUTO)
sym->flags &= ~SYMBOL_WRITE;
}
void sym_clear_all_valid(void)
{
struct symbol *sym;
int i;
for_all_symbols(i, sym)
sym->flags &= ~SYMBOL_VALID;
sym_add_change_count(1);
if (modules_sym)
sym_calc_value(modules_sym);
}
void sym_set_changed(struct symbol *sym)
{
struct property *prop;
sym->flags |= SYMBOL_CHANGED;
for (prop = sym->prop; prop; prop = prop->next) {
if (prop->menu)
prop->menu->flags |= MENU_CHANGED;
}
}
void sym_set_all_changed(void)
{
struct symbol *sym;
int i;
for_all_symbols(i, sym)
sym_set_changed(sym);
}
bool sym_tristate_within_range(struct symbol *sym, tristate val)
{
int type = sym_get_type(sym);
if (sym->visible == no)
return false;
if (type != S_BOOLEAN && type != S_TRISTATE)
return false;
if (type == S_BOOLEAN && val == mod)
return false;
if (sym->visible <= sym->rev_dep.tri)
return false;
if (sym_is_choice_value(sym) && sym->visible == yes)
return val == yes;
return val >= sym->rev_dep.tri && val <= sym->visible;
}
bool sym_set_tristate_value(struct symbol *sym, tristate val)
{
tristate oldval = sym_get_tristate_value(sym);
if (oldval != val && !sym_tristate_within_range(sym, val))
return false;
if (!(sym->flags & SYMBOL_DEF_USER)) {
sym->flags |= SYMBOL_DEF_USER;
sym_set_changed(sym);
}
/*
* setting a choice value also resets the new flag of the choice
* symbol and all other choice values.
*/
if (sym_is_choice_value(sym) && val == yes) {
struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym));
struct property *prop;
struct expr *e;
cs->def[S_DEF_USER].val = sym;
cs->flags |= SYMBOL_DEF_USER;
prop = sym_get_choice_prop(cs);
for (e = prop->expr; e; e = e->left.expr) {
if (e->right.sym->visible != no)
e->right.sym->flags |= SYMBOL_DEF_USER;
}
}
sym->def[S_DEF_USER].tri = val;
if (oldval != val)
sym_clear_all_valid();
return true;
}
tristate sym_toggle_tristate_value(struct symbol *sym)
{
tristate oldval, newval;
oldval = newval = sym_get_tristate_value(sym);
do {
switch (newval) {
case no:
newval = mod;
break;
case mod:
newval = yes;
break;
case yes:
newval = no;
break;
}
if (sym_set_tristate_value(sym, newval))
break;
} while (oldval != newval);
return newval;
}
bool sym_string_valid(struct symbol *sym, const char *str)
{
signed char ch;
switch (sym->type) {
case S_STRING:
return true;
case S_INT:
ch = *str++;
if (ch == '-')
ch = *str++;
if (!isdigit(ch))
return false;
if (ch == '0' && *str != 0)
return false;
while ((ch = *str++)) {
if (!isdigit(ch))
return false;
}
return true;
case S_HEX:
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
str += 2;
ch = *str++;
do {
if (!isxdigit(ch))
return false;
} while ((ch = *str++));
return true;
case S_BOOLEAN:
case S_TRISTATE:
switch (str[0]) {
case 'y': case 'Y':
case 'm': case 'M':
case 'n': case 'N':
return true;
}
return false;
default:
return false;
}
}
bool sym_string_within_range(struct symbol *sym, const char *str)
{
struct property *prop;
int val;
switch (sym->type) {
case S_STRING:
return sym_string_valid(sym, str);
case S_INT:
if (!sym_string_valid(sym, str))
return false;
prop = sym_get_range_prop(sym);
if (!prop)
return true;
val = strtol(str, NULL, 10);
return val >= sym_get_range_val(prop->expr->left.sym, 10) &&
val <= sym_get_range_val(prop->expr->right.sym, 10);
case S_HEX:
if (!sym_string_valid(sym, str))
return false;
prop = sym_get_range_prop(sym);
if (!prop)
return true;
val = strtol(str, NULL, 16);
return val >= sym_get_range_val(prop->expr->left.sym, 16) &&
val <= sym_get_range_val(prop->expr->right.sym, 16);
case S_BOOLEAN:
case S_TRISTATE:
switch (str[0]) {
case 'y': case 'Y':
return sym_tristate_within_range(sym, yes);
case 'm': case 'M':
return sym_tristate_within_range(sym, mod);
case 'n': case 'N':
return sym_tristate_within_range(sym, no);
}
return false;
default:
return false;
}
}
bool sym_set_string_value(struct symbol *sym, const char *newval)
{
const char *oldval;
char *val;
int size;
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
switch (newval[0]) {
case 'y': case 'Y':
return sym_set_tristate_value(sym, yes);
case 'm': case 'M':
return sym_set_tristate_value(sym, mod);
case 'n': case 'N':
return sym_set_tristate_value(sym, no);
}
return false;
default:
;
}
if (!sym_string_within_range(sym, newval))
return false;
if (!(sym->flags & SYMBOL_DEF_USER)) {
sym->flags |= SYMBOL_DEF_USER;
sym_set_changed(sym);
}
oldval = sym->def[S_DEF_USER].val;
size = strlen(newval) + 1;
if (sym->type == S_HEX && (newval[0] != '0' || (newval[1] != 'x' && newval[1] != 'X'))) {
size += 2;
sym->def[S_DEF_USER].val = val = malloc(size);
*val++ = '0';
*val++ = 'x';
} else if (!oldval || strcmp(oldval, newval))
sym->def[S_DEF_USER].val = val = malloc(size);
else
return true;
strcpy(val, newval);
free((void *)oldval);
sym_clear_all_valid();
return true;
}
/*
* Find the default value associated to a symbol.
* For tristate symbol handle the modules=n case
* in which case "m" becomes "y".
* If the symbol does not have any default then fallback
* to the fixed default values.
*/
const char *sym_get_string_default(struct symbol *sym)
{
struct property *prop;
struct symbol *ds;
const char *str;
tristate val;
sym_calc_visibility(sym);
sym_calc_value(modules_sym);
val = symbol_no.curr.tri;
str = symbol_empty.curr.val;
/* If symbol has a default value look it up */
prop = sym_get_default_prop(sym);
if (prop != NULL) {
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
/* The visibility may limit the value from yes => mod */
val = EXPR_AND(expr_calc_value(prop->expr), prop->visible.tri);
break;
default:
/*
* The following fails to handle the situation
* where a default value is further limited by
* the valid range.
*/
ds = prop_get_symbol(prop);
if (ds != NULL) {
sym_calc_value(ds);
str = (const char *)ds->curr.val;
}
}
}
/* Handle select statements */
val = EXPR_OR(val, sym->rev_dep.tri);
/* transpose mod to yes if modules are not enabled */
if (val == mod)
if (!sym_is_choice_value(sym) && modules_sym->curr.tri == no)
val = yes;
/* transpose mod to yes if type is bool */
if (sym->type == S_BOOLEAN && val == mod)
val = yes;
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
switch (val) {
case no: return "n";
case mod: return "m";
case yes: return "y";
}
case S_INT:
case S_HEX:
return str;
case S_STRING:
return str;
case S_OTHER:
case S_UNKNOWN:
break;
}
return "";
}
const char *sym_get_string_value(struct symbol *sym)
{
tristate val;
switch (sym->type) {
case S_BOOLEAN:
case S_TRISTATE:
val = sym_get_tristate_value(sym);
switch (val) {
case no:
return "n";
case mod:
sym_calc_value(modules_sym);
return (modules_sym->curr.tri == no) ? "n" : "m";
case yes:
return "y";
}
break;
default:
;
}
return (const char *)sym->curr.val;
}
bool sym_is_changable(struct symbol *sym)
{
return sym->visible > sym->rev_dep.tri;
}
static unsigned strhash(const char *s)
{
/* fnv32 hash */
unsigned hash = 2166136261U;
for (; *s; s++)
hash = (hash ^ *s) * 0x01000193;
return hash;
}
struct symbol *sym_lookup(const char *name, int flags)
{
struct symbol *symbol;
char *new_name;
int hash;
if (name) {
if (name[0] && !name[1]) {
switch (name[0]) {
case 'y': return &symbol_yes;
case 'm': return &symbol_mod;
case 'n': return &symbol_no;
}
}
hash = strhash(name) % SYMBOL_HASHSIZE;
for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) {
if (symbol->name &&
!strcmp(symbol->name, name) &&
(flags ? symbol->flags & flags
: !(symbol->flags & (SYMBOL_CONST|SYMBOL_CHOICE))))
return symbol;
}
new_name = strdup(name);
} else {
new_name = NULL;
hash = 0;
}
symbol = malloc(sizeof(*symbol));
memset(symbol, 0, sizeof(*symbol));
symbol->name = new_name;
symbol->type = S_UNKNOWN;
symbol->flags |= flags;
symbol->next = symbol_hash[hash];
symbol_hash[hash] = symbol;
return symbol;
}
struct symbol *sym_find(const char *name)
{
struct symbol *symbol = NULL;
int hash = 0;
if (!name)
return NULL;
if (name[0] && !name[1]) {
switch (name[0]) {
case 'y': return &symbol_yes;
case 'm': return &symbol_mod;
case 'n': return &symbol_no;
}
}
hash = strhash(name) % SYMBOL_HASHSIZE;
for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) {
if (symbol->name &&
!strcmp(symbol->name, name) &&
!(symbol->flags & SYMBOL_CONST))
break;
}
return symbol;
}
/*
* Expand symbol's names embedded in the string given in argument. Symbols'
* name to be expanded shall be prefixed by a '$'. Unknown symbol expands to
* the empty string.
*/
const char *sym_expand_string_value(const char *in)
{
const char *src;
char *res;
size_t reslen;
reslen = strlen(in) + 1;
res = malloc(reslen);
res[0] = '\0';
while ((src = strchr(in, '$'))) {
char *p, name[SYMBOL_MAXLENGTH];
const char *symval = "";
struct symbol *sym;
size_t newlen;
strncat(res, in, src - in);
src++;
p = name;
while (isalnum(*src) || *src == '_')
*p++ = *src++;
*p = '\0';
sym = sym_find(name);
if (sym != NULL) {
sym_calc_value(sym);
symval = sym_get_string_value(sym);
}
newlen = strlen(res) + strlen(symval) + strlen(src) + 1;
if (newlen > reslen) {
reslen = newlen;
res = realloc(res, reslen);
}
strcat(res, symval);
in = src;
}
strcat(res, in);
return res;
}
const char *sym_escape_string_value(const char *in)
{
const char *p;
size_t reslen;
char *res;
size_t l;
reslen = strlen(in) + strlen("\"\"") + 1;
p = in;
for (;;) {
l = strcspn(p, "\"\\");
p += l;
if (p[0] == '\0')
break;
reslen++;
p++;
}
res = malloc(reslen);
res[0] = '\0';
strcat(res, "\"");
p = in;
for (;;) {
l = strcspn(p, "\"\\");
strncat(res, p, l);
p += l;
if (p[0] == '\0')
break;
strcat(res, "\\");
strncat(res, p++, 1);
}
strcat(res, "\"");
return res;
}
struct symbol **sym_re_search(const char *pattern)
{
struct symbol *sym, **sym_arr = NULL;
int i, cnt, size;
regex_t re;
cnt = size = 0;
/* Skip if empty */
if (strlen(pattern) == 0)
return NULL;
if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB|REG_ICASE))
return NULL;
for_all_symbols(i, sym) {
if (sym->flags & SYMBOL_CONST || !sym->name)
continue;
if (regexec(&re, sym->name, 0, NULL, 0))
continue;
if (cnt + 1 >= size) {
void *tmp = sym_arr;
size += 16;
sym_arr = realloc(sym_arr, size * sizeof(struct symbol *));
if (!sym_arr) {
free(tmp);
return NULL;
}
}
sym_calc_value(sym);
sym_arr[cnt++] = sym;
}
if (sym_arr)
sym_arr[cnt] = NULL;
regfree(&re);
return sym_arr;
}
/*
* When we check for recursive dependencies we use a stack to save
* current state so we can print out relevant info to user.
* The entries are located on the call stack so no need to free memory.
* Note inser() remove() must always match to properly clear the stack.
*/
static struct dep_stack {
struct dep_stack *prev, *next;
struct symbol *sym;
struct property *prop;
struct expr *expr;
} *check_top;
static void dep_stack_insert(struct dep_stack *stack, struct symbol *sym)
{
memset(stack, 0, sizeof(*stack));
if (check_top)
check_top->next = stack;
stack->prev = check_top;
stack->sym = sym;
check_top = stack;
}
static void dep_stack_remove(void)
{
check_top = check_top->prev;
if (check_top)
check_top->next = NULL;
}
/*
* Called when we have detected a recursive dependency.
* check_top point to the top of the stact so we use
* the ->prev pointer to locate the bottom of the stack.
*/
static void sym_check_print_recursive(struct symbol *last_sym)
{
struct dep_stack *stack;
struct symbol *sym, *next_sym;
struct menu *menu = NULL;
struct property *prop;
struct dep_stack cv_stack;
if (sym_is_choice_value(last_sym)) {
dep_stack_insert(&cv_stack, last_sym);
last_sym = prop_get_symbol(sym_get_choice_prop(last_sym));
}
for (stack = check_top; stack != NULL; stack = stack->prev)
if (stack->sym == last_sym)
break;
if (!stack) {
fprintf(stderr, "unexpected recursive dependency error\n");
return;
}
for (; stack; stack = stack->next) {
sym = stack->sym;
next_sym = stack->next ? stack->next->sym : last_sym;
prop = stack->prop;
if (prop == NULL)
prop = stack->sym->prop;
/* for choice values find the menu entry (used below) */
if (sym_is_choice(sym) || sym_is_choice_value(sym)) {
for (prop = sym->prop; prop; prop = prop->next) {
menu = prop->menu;
if (prop->menu)
break;
}
}
if (stack->sym == last_sym)
fprintf(stderr, "%s:%d:error: recursive dependency detected!\n",
prop->file->name, prop->lineno);
if (stack->expr) {
fprintf(stderr, "%s:%d:\tsymbol %s %s value contains %s\n",
prop->file->name, prop->lineno,
sym->name ? sym->name : "<choice>",
prop_get_type_name(prop->type),
next_sym->name ? next_sym->name : "<choice>");
} else if (stack->prop) {
fprintf(stderr, "%s:%d:\tsymbol %s depends on %s\n",
prop->file->name, prop->lineno,
sym->name ? sym->name : "<choice>",
next_sym->name ? next_sym->name : "<choice>");
} else if (sym_is_choice(sym)) {
fprintf(stderr, "%s:%d:\tchoice %s contains symbol %s\n",
menu->file->name, menu->lineno,
sym->name ? sym->name : "<choice>",
next_sym->name ? next_sym->name : "<choice>");
} else if (sym_is_choice_value(sym)) {
fprintf(stderr, "%s:%d:\tsymbol %s is part of choice %s\n",
menu->file->name, menu->lineno,
sym->name ? sym->name : "<choice>",
next_sym->name ? next_sym->name : "<choice>");
} else {
fprintf(stderr, "%s:%d:\tsymbol %s is selected by %s\n",
prop->file->name, prop->lineno,
sym->name ? sym->name : "<choice>",
next_sym->name ? next_sym->name : "<choice>");
}
}
if (check_top == &cv_stack)
dep_stack_remove();
}
static struct symbol *sym_check_expr_deps(struct expr *e)
{
struct symbol *sym;
if (!e)
return NULL;
switch (e->type) {
case E_OR:
case E_AND:
sym = sym_check_expr_deps(e->left.expr);
if (sym)
return sym;
return sym_check_expr_deps(e->right.expr);
case E_NOT:
return sym_check_expr_deps(e->left.expr);
case E_EQUAL:
case E_UNEQUAL:
sym = sym_check_deps(e->left.sym);
if (sym)
return sym;
return sym_check_deps(e->right.sym);
case E_SYMBOL:
return sym_check_deps(e->left.sym);
default:
break;
}
printf("Oops! How to check %d?\n", e->type);
return NULL;
}
/* return NULL when dependencies are OK */
static struct symbol *sym_check_sym_deps(struct symbol *sym)
{
struct symbol *sym2;
struct property *prop;
struct dep_stack stack;
dep_stack_insert(&stack, sym);
sym2 = sym_check_expr_deps(sym->rev_dep.expr);
if (sym2)
goto out;
for (prop = sym->prop; prop; prop = prop->next) {
if (prop->type == P_CHOICE || prop->type == P_SELECT)
continue;
stack.prop = prop;
sym2 = sym_check_expr_deps(prop->visible.expr);
if (sym2)
break;
if (prop->type != P_DEFAULT || sym_is_choice(sym))
continue;
stack.expr = prop->expr;
sym2 = sym_check_expr_deps(prop->expr);
if (sym2)
break;
stack.expr = NULL;
}
out:
dep_stack_remove();
return sym2;
}
static struct symbol *sym_check_choice_deps(struct symbol *choice)
{
struct symbol *sym, *sym2;
struct property *prop;
struct expr *e;
struct dep_stack stack;
dep_stack_insert(&stack, choice);
prop = sym_get_choice_prop(choice);
expr_list_for_each_sym(prop->expr, e, sym)
sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED);
choice->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED);
sym2 = sym_check_sym_deps(choice);
choice->flags &= ~SYMBOL_CHECK;
if (sym2)
goto out;
expr_list_for_each_sym(prop->expr, e, sym) {
sym2 = sym_check_sym_deps(sym);
if (sym2)
break;
}
out:
expr_list_for_each_sym(prop->expr, e, sym)
sym->flags &= ~SYMBOL_CHECK;
if (sym2 && sym_is_choice_value(sym2) &&
prop_get_symbol(sym_get_choice_prop(sym2)) == choice)
sym2 = choice;
dep_stack_remove();
return sym2;
}
struct symbol *sym_check_deps(struct symbol *sym)
{
struct symbol *sym2;
struct property *prop;
if (sym->flags & SYMBOL_CHECK) {
sym_check_print_recursive(sym);
return sym;
}
if (sym->flags & SYMBOL_CHECKED)
return NULL;
if (sym_is_choice_value(sym)) {
struct dep_stack stack;
/* for choice groups start the check with main choice symbol */
dep_stack_insert(&stack, sym);
prop = sym_get_choice_prop(sym);
sym2 = sym_check_deps(prop_get_symbol(prop));
dep_stack_remove();
} else if (sym_is_choice(sym)) {
sym2 = sym_check_choice_deps(sym);
} else {
sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED);
sym2 = sym_check_sym_deps(sym);
sym->flags &= ~SYMBOL_CHECK;
}
if (sym2 && sym2 == sym)
sym2 = NULL;
return sym2;
}
struct property *prop_alloc(enum prop_type type, struct symbol *sym)
{
struct property *prop;
struct property **propp;
prop = malloc(sizeof(*prop));
memset(prop, 0, sizeof(*prop));
prop->type = type;
prop->sym = sym;
prop->file = current_file;
prop->lineno = zconf_lineno();
/* append property to the prop list of symbol */
if (sym) {
for (propp = &sym->prop; *propp; propp = &(*propp)->next)
;
*propp = prop;
}
return prop;
}
struct symbol *prop_get_symbol(struct property *prop)
{
if (prop->expr && (prop->expr->type == E_SYMBOL ||
prop->expr->type == E_LIST))
return prop->expr->left.sym;
return NULL;
}
const char *prop_get_type_name(enum prop_type type)
{
switch (type) {
case P_PROMPT:
return "prompt";
case P_ENV:
return "env";
case P_COMMENT:
return "comment";
case P_MENU:
return "menu";
case P_DEFAULT:
return "default";
case P_CHOICE:
return "choice";
case P_SELECT:
return "select";
case P_RANGE:
return "range";
case P_SYMBOL:
return "symbol";
case P_UNKNOWN:
break;
}
return "unknown";
}
static void prop_add_env(const char *env)
{
struct symbol *sym, *sym2;
struct property *prop;
char *p;
sym = current_entry->sym;
sym->flags |= SYMBOL_AUTO;
for_all_properties(sym, prop, P_ENV) {
sym2 = prop_get_symbol(prop);
if (strcmp(sym2->name, env))
menu_warn(current_entry, "redefining environment symbol from %s",
sym2->name);
return;
}
prop = prop_alloc(P_ENV, sym);
prop->expr = expr_alloc_symbol(sym_lookup(env, SYMBOL_CONST));
sym_env_list = expr_alloc_one(E_LIST, sym_env_list);
sym_env_list->right.sym = sym;
p = getenv(env);
if (p)
sym_add_default(sym, p);
else
menu_warn(current_entry, "environment variable %s undefined", env);
}
/*
* Copyright (C) 2002-2005 Roman Zippel <zippel@linux-m68k.org>
* Copyright (C) 2002-2005 Sam Ravnborg <sam@ravnborg.org>
*
* Released under the terms of the GNU GPL v2.0.
*/
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "lkc.h"
/* file already present in list? If not add it */
struct file *file_lookup(const char *name)
{
struct file *file;
const char *file_name = sym_expand_string_value(name);
for (file = file_list; file; file = file->next) {
if (!strcmp(name, file->name)) {
free((void *)file_name);
return file;
}
}
file = malloc(sizeof(*file));
memset(file, 0, sizeof(*file));
file->name = file_name;
file->next = file_list;
file_list = file;
return file;
}
/* write a dependency file as used by kbuild to track dependencies */
int file_write_dep(const char *name)
{
struct symbol *sym, *env_sym;
struct expr *e;
struct file *file;
FILE *out;
if (!name)
name = ".kconfig.d";
out = fopen("..config.tmp", "w");
if (!out)
return 1;
fprintf(out, "deps_config := \\\n");
for (file = file_list; file; file = file->next) {
if (file->next)
fprintf(out, "\t%s \\\n", file->name);
else
fprintf(out, "\t%s\n", file->name);
}
fprintf(out, "\n%s: \\\n"
"\t$(deps_config)\n\n", conf_get_autoconfig_name());
expr_list_for_each_sym(sym_env_list, e, sym) {
struct property *prop;
const char *value;
prop = sym_get_env_prop(sym);
env_sym = prop_get_symbol(prop);
if (!env_sym)
continue;
value = getenv(env_sym->name);
if (!value)
value = "";
fprintf(out, "ifneq \"$(%s)\" \"%s\"\n", env_sym->name, value);
fprintf(out, "%s: FORCE\n", conf_get_autoconfig_name());
fprintf(out, "endif\n");
}
fprintf(out, "\n$(deps_config): ;\n");
fclose(out);
rename("..config.tmp", name);
return 0;
}
/* Allocate initial growable string */
struct gstr str_new(void)
{
struct gstr gs;
gs.s = malloc(sizeof(char) * 64);
gs.len = 64;
gs.max_width = 0;
strcpy(gs.s, "\0");
return gs;
}
/* Allocate and assign growable string */
struct gstr str_assign(const char *s)
{
struct gstr gs;
gs.s = strdup(s);
gs.len = strlen(s) + 1;
gs.max_width = 0;
return gs;
}
/* Free storage for growable string */
void str_free(struct gstr *gs)
{
if (gs->s)
free(gs->s);
gs->s = NULL;
gs->len = 0;
}
/* Append to growable string */
void str_append(struct gstr *gs, const char *s)
{
size_t l;
if (s) {
l = strlen(gs->s) + strlen(s) + 1;
if (l > gs->len) {
gs->s = realloc(gs->s, l);
gs->len = l;
}
strcat(gs->s, s);
}
}
/* Append printf formatted string to growable string */
void str_printf(struct gstr *gs, const char *fmt, ...)
{
va_list ap;
char s[10000]; /* big enough... */
va_start(ap, fmt);
vsnprintf(s, sizeof(s), fmt, ap);
str_append(gs, s);
va_end(ap);
}
/* Retrieve value of growable string */
const char *str_get(struct gstr *gs)
{
return gs->s;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment