Commit c3da63f3 authored by David S. Miller's avatar David S. Miller
parents 73e42897 a97f4424
...@@ -111,6 +111,8 @@ following attributes: ...@@ -111,6 +111,8 @@ following attributes:
name: Name assigned by driver to this key (interface or driver name). name: Name assigned by driver to this key (interface or driver name).
type: Driver type string ("wlan", "bluetooth", etc). type: Driver type string ("wlan", "bluetooth", etc).
persistent: Whether the soft blocked state is initialised from
non-volatile storage at startup.
state: Current state of the transmitter state: Current state of the transmitter
0: RFKILL_STATE_SOFT_BLOCKED 0: RFKILL_STATE_SOFT_BLOCKED
transmitter is turned off by software transmitter is turned off by software
......
...@@ -940,7 +940,7 @@ M: me@bobcopeland.com ...@@ -940,7 +940,7 @@ M: me@bobcopeland.com
L: linux-wireless@vger.kernel.org L: linux-wireless@vger.kernel.org
L: ath5k-devel@lists.ath5k.org L: ath5k-devel@lists.ath5k.org
S: Maintained S: Maintained
F: drivers/net/wireless/ath5k/ F: drivers/net/wireless/ath/ath5k/
ATHEROS ATH9K WIRELESS DRIVER ATHEROS ATH9K WIRELESS DRIVER
P: Luis R. Rodriguez P: Luis R. Rodriguez
...@@ -956,7 +956,7 @@ M: senthilkumar@atheros.com ...@@ -956,7 +956,7 @@ M: senthilkumar@atheros.com
L: linux-wireless@vger.kernel.org L: linux-wireless@vger.kernel.org
L: ath9k-devel@lists.ath9k.org L: ath9k-devel@lists.ath9k.org
S: Supported S: Supported
F: drivers/net/wireless/ath9k/ F: drivers/net/wireless/ath/ath9k/
ATHEROS AR9170 WIRELESS DRIVER ATHEROS AR9170 WIRELESS DRIVER
P: Christian Lamparter P: Christian Lamparter
...@@ -964,7 +964,7 @@ M: chunkeey@web.de ...@@ -964,7 +964,7 @@ M: chunkeey@web.de
L: linux-wireless@vger.kernel.org L: linux-wireless@vger.kernel.org
W: http://wireless.kernel.org/en/users/Drivers/ar9170 W: http://wireless.kernel.org/en/users/Drivers/ar9170
S: Maintained S: Maintained
F: drivers/net/wireless/ar9170/ F: drivers/net/wireless/ath/ar9170/
ATI_REMOTE2 DRIVER ATI_REMOTE2 DRIVER
P: Ville Syrjala P: Ville Syrjala
......
...@@ -538,6 +538,7 @@ ath5k_pci_probe(struct pci_dev *pdev, ...@@ -538,6 +538,7 @@ ath5k_pci_probe(struct pci_dev *pdev,
sc->iobase = mem; /* So we can unmap it on detach */ sc->iobase = mem; /* So we can unmap it on detach */
sc->cachelsz = csz * sizeof(u32); /* convert to bytes */ sc->cachelsz = csz * sizeof(u32); /* convert to bytes */
sc->opmode = NL80211_IFTYPE_STATION; sc->opmode = NL80211_IFTYPE_STATION;
sc->bintval = 1000;
mutex_init(&sc->lock); mutex_init(&sc->lock);
spin_lock_init(&sc->rxbuflock); spin_lock_init(&sc->rxbuflock);
spin_lock_init(&sc->txbuflock); spin_lock_init(&sc->txbuflock);
...@@ -686,6 +687,13 @@ ath5k_pci_resume(struct pci_dev *pdev) ...@@ -686,6 +687,13 @@ ath5k_pci_resume(struct pci_dev *pdev)
if (err) if (err)
return err; return err;
/*
* Suspend/Resume resets the PCI configuration space, so we have to
* re-disable the RETRY_TIMEOUT register (0x41) to keep
* PCI Tx retries from interfering with C3 CPU state
*/
pci_write_config_byte(pdev, 0x41, 0);
err = request_irq(pdev->irq, ath5k_intr, IRQF_SHARED, "ath", sc); err = request_irq(pdev->irq, ath5k_intr, IRQF_SHARED, "ath", sc);
if (err) { if (err) {
ATH5K_ERR(sc, "request_irq failed\n"); ATH5K_ERR(sc, "request_irq failed\n");
...@@ -2748,9 +2756,6 @@ static int ath5k_add_interface(struct ieee80211_hw *hw, ...@@ -2748,9 +2756,6 @@ static int ath5k_add_interface(struct ieee80211_hw *hw,
goto end; goto end;
} }
/* Set to a reasonable value. Note that this will
* be set to mac80211's value at ath5k_config(). */
sc->bintval = 1000;
ath5k_hw_set_lladdr(sc->ah, conf->mac_addr); ath5k_hw_set_lladdr(sc->ah, conf->mac_addr);
ret = 0; ret = 0;
......
...@@ -1196,8 +1196,8 @@ void ath_radio_disable(struct ath_softc *sc) ...@@ -1196,8 +1196,8 @@ void ath_radio_disable(struct ath_softc *sc)
ath9k_hw_phy_disable(ah); ath9k_hw_phy_disable(ah);
ath9k_hw_configpcipowersave(ah, 1); ath9k_hw_configpcipowersave(ah, 1);
ath9k_hw_setpower(ah, ATH9K_PM_FULL_SLEEP);
ath9k_ps_restore(sc); ath9k_ps_restore(sc);
ath9k_hw_setpower(ah, ATH9K_PM_FULL_SLEEP);
} }
/*******************/ /*******************/
......
...@@ -87,6 +87,7 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) ...@@ -87,6 +87,7 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
struct ath_softc *sc; struct ath_softc *sc;
struct ieee80211_hw *hw; struct ieee80211_hw *hw;
u8 csz; u8 csz;
u32 val;
int ret = 0; int ret = 0;
struct ath_hw *ah; struct ath_hw *ah;
...@@ -133,6 +134,14 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) ...@@ -133,6 +134,14 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
pci_set_master(pdev); pci_set_master(pdev);
/*
* Disable the RETRY_TIMEOUT register (0x41) to keep
* PCI Tx retries from interfering with C3 CPU state.
*/
pci_read_config_dword(pdev, 0x40, &val);
if ((val & 0x0000ff00) != 0)
pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
ret = pci_request_region(pdev, 0, "ath9k"); ret = pci_request_region(pdev, 0, "ath9k");
if (ret) { if (ret) {
dev_err(&pdev->dev, "PCI memory region reserve error\n"); dev_err(&pdev->dev, "PCI memory region reserve error\n");
...@@ -239,12 +248,21 @@ static int ath_pci_resume(struct pci_dev *pdev) ...@@ -239,12 +248,21 @@ static int ath_pci_resume(struct pci_dev *pdev)
struct ieee80211_hw *hw = pci_get_drvdata(pdev); struct ieee80211_hw *hw = pci_get_drvdata(pdev);
struct ath_wiphy *aphy = hw->priv; struct ath_wiphy *aphy = hw->priv;
struct ath_softc *sc = aphy->sc; struct ath_softc *sc = aphy->sc;
u32 val;
int err; int err;
err = pci_enable_device(pdev); err = pci_enable_device(pdev);
if (err) if (err)
return err; return err;
pci_restore_state(pdev); pci_restore_state(pdev);
/*
* Suspend/Resume resets the PCI configuration space, so we have to
* re-disable the RETRY_TIMEOUT register (0x41) to keep
* PCI Tx retries from interfering with C3 CPU state
*/
pci_read_config_dword(pdev, 0x40, &val);
if ((val & 0x0000ff00) != 0)
pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
/* Enable LED */ /* Enable LED */
ath9k_hw_cfg_output(sc->sc_ah, ATH_LED_PIN, ath9k_hw_cfg_output(sc->sc_ah, ATH_LED_PIN,
......
...@@ -539,11 +539,14 @@ static void ath_rx_ps_beacon(struct ath_softc *sc, struct sk_buff *skb) ...@@ -539,11 +539,14 @@ static void ath_rx_ps_beacon(struct ath_softc *sc, struct sk_buff *skb)
if (ath_beacon_dtim_pending_cab(skb)) { if (ath_beacon_dtim_pending_cab(skb)) {
/* /*
* Remain awake waiting for buffered broadcast/multicast * Remain awake waiting for buffered broadcast/multicast
* frames. * frames. If the last broadcast/multicast frame is not
* received properly, the next beacon frame will work as
* a backup trigger for returning into NETWORK SLEEP state,
* so we are waiting for it as well.
*/ */
DPRINTF(sc, ATH_DBG_PS, "Received DTIM beacon indicating " DPRINTF(sc, ATH_DBG_PS, "Received DTIM beacon indicating "
"buffered broadcast/multicast frame(s)\n"); "buffered broadcast/multicast frame(s)\n");
sc->sc_flags |= SC_OP_WAIT_FOR_CAB; sc->sc_flags |= SC_OP_WAIT_FOR_CAB | SC_OP_WAIT_FOR_BEACON;
return; return;
} }
......
...@@ -288,6 +288,7 @@ struct iwm_priv { ...@@ -288,6 +288,7 @@ struct iwm_priv {
u8 *eeprom; u8 *eeprom;
struct timer_list watchdog; struct timer_list watchdog;
struct work_struct reset_worker; struct work_struct reset_worker;
struct mutex mutex;
struct rfkill *rfkill; struct rfkill *rfkill;
char private[0] __attribute__((__aligned__(NETDEV_ALIGN))); char private[0] __attribute__((__aligned__(NETDEV_ALIGN)));
...@@ -315,8 +316,11 @@ extern const struct iw_handler_def iwm_iw_handler_def; ...@@ -315,8 +316,11 @@ extern const struct iw_handler_def iwm_iw_handler_def;
void *iwm_if_alloc(int sizeof_bus, struct device *dev, void *iwm_if_alloc(int sizeof_bus, struct device *dev,
struct iwm_if_ops *if_ops); struct iwm_if_ops *if_ops);
void iwm_if_free(struct iwm_priv *iwm); void iwm_if_free(struct iwm_priv *iwm);
int iwm_if_add(struct iwm_priv *iwm);
void iwm_if_remove(struct iwm_priv *iwm);
int iwm_mode_to_nl80211_iftype(int mode); int iwm_mode_to_nl80211_iftype(int mode);
int iwm_priv_init(struct iwm_priv *iwm); int iwm_priv_init(struct iwm_priv *iwm);
void iwm_priv_deinit(struct iwm_priv *iwm);
void iwm_reset(struct iwm_priv *iwm); void iwm_reset(struct iwm_priv *iwm);
void iwm_tx_credit_init_pools(struct iwm_priv *iwm, void iwm_tx_credit_init_pools(struct iwm_priv *iwm,
struct iwm_umac_notif_alive *alive); struct iwm_umac_notif_alive *alive);
......
...@@ -112,6 +112,9 @@ static void iwm_statistics_request(struct work_struct *work) ...@@ -112,6 +112,9 @@ static void iwm_statistics_request(struct work_struct *work)
iwm_send_umac_stats_req(iwm, 0); iwm_send_umac_stats_req(iwm, 0);
} }
int __iwm_up(struct iwm_priv *iwm);
int __iwm_down(struct iwm_priv *iwm);
static void iwm_reset_worker(struct work_struct *work) static void iwm_reset_worker(struct work_struct *work)
{ {
struct iwm_priv *iwm; struct iwm_priv *iwm;
...@@ -120,6 +123,19 @@ static void iwm_reset_worker(struct work_struct *work) ...@@ -120,6 +123,19 @@ static void iwm_reset_worker(struct work_struct *work)
iwm = container_of(work, struct iwm_priv, reset_worker); iwm = container_of(work, struct iwm_priv, reset_worker);
/*
* XXX: The iwm->mutex is introduced purely for this reset work,
* because the other users for iwm_up and iwm_down are only netdev
* ndo_open and ndo_stop which are already protected by rtnl.
* Please remove iwm->mutex together if iwm_reset_worker() is not
* required in the future.
*/
if (!mutex_trylock(&iwm->mutex)) {
IWM_WARN(iwm, "We are in the middle of interface bringing "
"UP/DOWN. Skip driver resetting.\n");
return;
}
if (iwm->umac_profile_active) { if (iwm->umac_profile_active) {
profile = kmalloc(sizeof(struct iwm_umac_profile), GFP_KERNEL); profile = kmalloc(sizeof(struct iwm_umac_profile), GFP_KERNEL);
if (profile) if (profile)
...@@ -128,10 +144,10 @@ static void iwm_reset_worker(struct work_struct *work) ...@@ -128,10 +144,10 @@ static void iwm_reset_worker(struct work_struct *work)
IWM_ERR(iwm, "Couldn't alloc memory for profile\n"); IWM_ERR(iwm, "Couldn't alloc memory for profile\n");
} }
iwm_down(iwm); __iwm_down(iwm);
while (retry++ < 3) { while (retry++ < 3) {
ret = iwm_up(iwm); ret = __iwm_up(iwm);
if (!ret) if (!ret)
break; break;
...@@ -142,7 +158,7 @@ static void iwm_reset_worker(struct work_struct *work) ...@@ -142,7 +158,7 @@ static void iwm_reset_worker(struct work_struct *work)
IWM_WARN(iwm, "iwm_up() failed: %d\n", ret); IWM_WARN(iwm, "iwm_up() failed: %d\n", ret);
kfree(profile); kfree(profile);
return; goto out;
} }
if (profile) { if (profile) {
...@@ -151,6 +167,9 @@ static void iwm_reset_worker(struct work_struct *work) ...@@ -151,6 +167,9 @@ static void iwm_reset_worker(struct work_struct *work)
iwm_send_mlme_profile(iwm); iwm_send_mlme_profile(iwm);
kfree(profile); kfree(profile);
} }
out:
mutex_unlock(&iwm->mutex);
} }
static void iwm_watchdog(unsigned long data) static void iwm_watchdog(unsigned long data)
...@@ -215,10 +234,21 @@ int iwm_priv_init(struct iwm_priv *iwm) ...@@ -215,10 +234,21 @@ int iwm_priv_init(struct iwm_priv *iwm)
init_timer(&iwm->watchdog); init_timer(&iwm->watchdog);
iwm->watchdog.function = iwm_watchdog; iwm->watchdog.function = iwm_watchdog;
iwm->watchdog.data = (unsigned long)iwm; iwm->watchdog.data = (unsigned long)iwm;
mutex_init(&iwm->mutex);
return 0; return 0;
} }
void iwm_priv_deinit(struct iwm_priv *iwm)
{
int i;
for (i = 0; i < IWM_TX_QUEUES; i++)
destroy_workqueue(iwm->txq[i].wq);
destroy_workqueue(iwm->rx_wq);
}
/* /*
* We reset all the structures, and we reset the UMAC. * We reset all the structures, and we reset the UMAC.
* After calling this routine, you're expected to reload * After calling this routine, you're expected to reload
...@@ -466,7 +496,7 @@ void iwm_link_off(struct iwm_priv *iwm) ...@@ -466,7 +496,7 @@ void iwm_link_off(struct iwm_priv *iwm)
iwm_rx_free(iwm); iwm_rx_free(iwm);
cancel_delayed_work(&iwm->stats_request); cancel_delayed_work_sync(&iwm->stats_request);
memset(wstats, 0, sizeof(struct iw_statistics)); memset(wstats, 0, sizeof(struct iw_statistics));
wstats->qual.updated = IW_QUAL_ALL_INVALID; wstats->qual.updated = IW_QUAL_ALL_INVALID;
...@@ -511,7 +541,7 @@ static int iwm_channels_init(struct iwm_priv *iwm) ...@@ -511,7 +541,7 @@ static int iwm_channels_init(struct iwm_priv *iwm)
return 0; return 0;
} }
int iwm_up(struct iwm_priv *iwm) int __iwm_up(struct iwm_priv *iwm)
{ {
int ret; int ret;
struct iwm_notif *notif_reboot, *notif_ack = NULL; struct iwm_notif *notif_reboot, *notif_ack = NULL;
...@@ -647,7 +677,18 @@ int iwm_up(struct iwm_priv *iwm) ...@@ -647,7 +677,18 @@ int iwm_up(struct iwm_priv *iwm)
return -EIO; return -EIO;
} }
int iwm_down(struct iwm_priv *iwm) int iwm_up(struct iwm_priv *iwm)
{
int ret;
mutex_lock(&iwm->mutex);
ret = __iwm_up(iwm);
mutex_unlock(&iwm->mutex);
return ret;
}
int __iwm_down(struct iwm_priv *iwm)
{ {
int ret; int ret;
...@@ -678,3 +719,14 @@ int iwm_down(struct iwm_priv *iwm) ...@@ -678,3 +719,14 @@ int iwm_down(struct iwm_priv *iwm)
return 0; return 0;
} }
int iwm_down(struct iwm_priv *iwm)
{
int ret;
mutex_lock(&iwm->mutex);
ret = __iwm_down(iwm);
mutex_unlock(&iwm->mutex);
return ret;
}
...@@ -114,32 +114,31 @@ void *iwm_if_alloc(int sizeof_bus, struct device *dev, ...@@ -114,32 +114,31 @@ void *iwm_if_alloc(int sizeof_bus, struct device *dev,
iwm = wdev_to_iwm(wdev); iwm = wdev_to_iwm(wdev);
iwm->bus_ops = if_ops; iwm->bus_ops = if_ops;
iwm->wdev = wdev; iwm->wdev = wdev;
iwm_priv_init(iwm);
ret = iwm_priv_init(iwm);
if (ret) {
dev_err(dev, "failed to init iwm_priv\n");
goto out_wdev;
}
wdev->iftype = iwm_mode_to_nl80211_iftype(iwm->conf.mode); wdev->iftype = iwm_mode_to_nl80211_iftype(iwm->conf.mode);
ndev = alloc_netdev_mq(0, "wlan%d", ether_setup, ndev = alloc_netdev_mq(0, "wlan%d", ether_setup, IWM_TX_QUEUES);
IWM_TX_QUEUES);
if (!ndev) { if (!ndev) {
dev_err(dev, "no memory for network device instance\n"); dev_err(dev, "no memory for network device instance\n");
goto out_wdev; goto out_priv;
} }
ndev->netdev_ops = &iwm_netdev_ops; ndev->netdev_ops = &iwm_netdev_ops;
ndev->wireless_handlers = &iwm_iw_handler_def; ndev->wireless_handlers = &iwm_iw_handler_def;
ndev->ieee80211_ptr = wdev; ndev->ieee80211_ptr = wdev;
SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy)); SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy));
ret = register_netdev(ndev);
if (ret < 0) {
dev_err(dev, "Failed to register netdev: %d\n", ret);
goto out_ndev;
}
wdev->netdev = ndev; wdev->netdev = ndev;
return iwm; return iwm;
out_ndev: out_priv:
free_netdev(ndev); iwm_priv_deinit(iwm);
out_wdev: out_wdev:
iwm_wdev_free(iwm); iwm_wdev_free(iwm);
...@@ -148,15 +147,29 @@ void *iwm_if_alloc(int sizeof_bus, struct device *dev, ...@@ -148,15 +147,29 @@ void *iwm_if_alloc(int sizeof_bus, struct device *dev,
void iwm_if_free(struct iwm_priv *iwm) void iwm_if_free(struct iwm_priv *iwm)
{ {
int i;
if (!iwm_to_ndev(iwm)) if (!iwm_to_ndev(iwm))
return; return;
unregister_netdev(iwm_to_ndev(iwm));
free_netdev(iwm_to_ndev(iwm)); free_netdev(iwm_to_ndev(iwm));
iwm_wdev_free(iwm); iwm_wdev_free(iwm);
destroy_workqueue(iwm->rx_wq); iwm_priv_deinit(iwm);
for (i = 0; i < IWM_TX_QUEUES; i++) }
destroy_workqueue(iwm->txq[i].wq);
int iwm_if_add(struct iwm_priv *iwm)
{
struct net_device *ndev = iwm_to_ndev(iwm);
int ret;
ret = register_netdev(ndev);
if (ret < 0) {
dev_err(&ndev->dev, "Failed to register netdev: %d\n", ret);
return ret;
}
return 0;
}
void iwm_if_remove(struct iwm_priv *iwm)
{
unregister_netdev(iwm_to_ndev(iwm));
} }
...@@ -454,10 +454,18 @@ static int iwm_sdio_probe(struct sdio_func *func, ...@@ -454,10 +454,18 @@ static int iwm_sdio_probe(struct sdio_func *func,
INIT_WORK(&hw->isr_worker, iwm_sdio_isr_worker); INIT_WORK(&hw->isr_worker, iwm_sdio_isr_worker);
ret = iwm_if_add(iwm);
if (ret) {
dev_err(dev, "add SDIO interface failed\n");
goto destroy_wq;
}
dev_info(dev, "IWM SDIO probe\n"); dev_info(dev, "IWM SDIO probe\n");
return 0; return 0;
destroy_wq:
destroy_workqueue(hw->isr_wq);
debugfs_exit: debugfs_exit:
iwm_debugfs_exit(iwm); iwm_debugfs_exit(iwm);
if_free: if_free:
...@@ -471,9 +479,10 @@ static void iwm_sdio_remove(struct sdio_func *func) ...@@ -471,9 +479,10 @@ static void iwm_sdio_remove(struct sdio_func *func)
struct iwm_priv *iwm = hw_to_iwm(hw); struct iwm_priv *iwm = hw_to_iwm(hw);
struct device *dev = &func->dev; struct device *dev = &func->dev;
iwm_if_remove(iwm);
destroy_workqueue(hw->isr_wq);
iwm_debugfs_exit(iwm); iwm_debugfs_exit(iwm);
iwm_if_free(iwm); iwm_if_free(iwm);
destroy_workqueue(hw->isr_wq);
sdio_set_drvdata(func, NULL); sdio_set_drvdata(func, NULL);
......
...@@ -67,6 +67,7 @@ static struct usb_device_id usb_ids[] = { ...@@ -67,6 +67,7 @@ static struct usb_device_id usb_ids[] = {
{ USB_DEVICE(0x079b, 0x0062), .driver_info = DEVICE_ZD1211B }, { USB_DEVICE(0x079b, 0x0062), .driver_info = DEVICE_ZD1211B },
{ USB_DEVICE(0x1582, 0x6003), .driver_info = DEVICE_ZD1211B }, { USB_DEVICE(0x1582, 0x6003), .driver_info = DEVICE_ZD1211B },
{ USB_DEVICE(0x050d, 0x705c), .driver_info = DEVICE_ZD1211B }, { USB_DEVICE(0x050d, 0x705c), .driver_info = DEVICE_ZD1211B },
{ USB_DEVICE(0x083a, 0xe503), .driver_info = DEVICE_ZD1211B },
{ USB_DEVICE(0x083a, 0xe506), .driver_info = DEVICE_ZD1211B }, { USB_DEVICE(0x083a, 0xe506), .driver_info = DEVICE_ZD1211B },
{ USB_DEVICE(0x083a, 0x4505), .driver_info = DEVICE_ZD1211B }, { USB_DEVICE(0x083a, 0x4505), .driver_info = DEVICE_ZD1211B },
{ USB_DEVICE(0x0471, 0x1236), .driver_info = DEVICE_ZD1211B }, { USB_DEVICE(0x0471, 0x1236), .driver_info = DEVICE_ZD1211B },
......
...@@ -958,12 +958,12 @@ static void acer_rfkill_update(struct work_struct *ignored) ...@@ -958,12 +958,12 @@ static void acer_rfkill_update(struct work_struct *ignored)
status = get_u32(&state, ACER_CAP_WIRELESS); status = get_u32(&state, ACER_CAP_WIRELESS);
if (ACPI_SUCCESS(status)) if (ACPI_SUCCESS(status))
rfkill_set_sw_state(wireless_rfkill, !!state); rfkill_set_sw_state(wireless_rfkill, !state);
if (has_cap(ACER_CAP_BLUETOOTH)) { if (has_cap(ACER_CAP_BLUETOOTH)) {
status = get_u32(&state, ACER_CAP_BLUETOOTH); status = get_u32(&state, ACER_CAP_BLUETOOTH);
if (ACPI_SUCCESS(status)) if (ACPI_SUCCESS(status))
rfkill_set_sw_state(bluetooth_rfkill, !!state); rfkill_set_sw_state(bluetooth_rfkill, !state);
} }
schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ)); schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));
......
...@@ -180,6 +180,7 @@ static struct key_entry eeepc_keymap[] = { ...@@ -180,6 +180,7 @@ static struct key_entry eeepc_keymap[] = {
*/ */
static int eeepc_hotk_add(struct acpi_device *device); static int eeepc_hotk_add(struct acpi_device *device);
static int eeepc_hotk_remove(struct acpi_device *device, int type); static int eeepc_hotk_remove(struct acpi_device *device, int type);
static int eeepc_hotk_resume(struct acpi_device *device);
static const struct acpi_device_id eeepc_device_ids[] = { static const struct acpi_device_id eeepc_device_ids[] = {
{EEEPC_HOTK_HID, 0}, {EEEPC_HOTK_HID, 0},
...@@ -194,6 +195,7 @@ static struct acpi_driver eeepc_hotk_driver = { ...@@ -194,6 +195,7 @@ static struct acpi_driver eeepc_hotk_driver = {
.ops = { .ops = {
.add = eeepc_hotk_add, .add = eeepc_hotk_add,
.remove = eeepc_hotk_remove, .remove = eeepc_hotk_remove,
.resume = eeepc_hotk_resume,
}, },
}; };
...@@ -512,15 +514,12 @@ static int notify_brn(void) ...@@ -512,15 +514,12 @@ static int notify_brn(void)
return -1; return -1;
} }
static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) static void eeepc_rfkill_hotplug(void)
{ {
struct pci_dev *dev; struct pci_dev *dev;
struct pci_bus *bus = pci_find_bus(0, 1); struct pci_bus *bus = pci_find_bus(0, 1);
bool blocked; bool blocked;
if (event != ACPI_NOTIFY_BUS_CHECK)
return;
if (!bus) { if (!bus) {
printk(EEEPC_WARNING "Unable to find PCI bus 1?\n"); printk(EEEPC_WARNING "Unable to find PCI bus 1?\n");
return; return;
...@@ -551,6 +550,14 @@ static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) ...@@ -551,6 +550,14 @@ static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, blocked); rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, blocked);
} }
static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
{
if (event != ACPI_NOTIFY_BUS_CHECK)
return;
eeepc_rfkill_hotplug();
}
static void eeepc_hotk_notify(acpi_handle handle, u32 event, void *data) static void eeepc_hotk_notify(acpi_handle handle, u32 event, void *data)
{ {
static struct key_entry *key; static struct key_entry *key;
...@@ -675,8 +682,8 @@ static int eeepc_hotk_add(struct acpi_device *device) ...@@ -675,8 +682,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_wlan_rfkill) if (!ehotk->eeepc_wlan_rfkill)
goto wlan_fail; goto wlan_fail;
rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, rfkill_init_sw_state(ehotk->eeepc_wlan_rfkill,
get_acpi(CM_ASL_WLAN) != 1); get_acpi(CM_ASL_WLAN) != 1);
result = rfkill_register(ehotk->eeepc_wlan_rfkill); result = rfkill_register(ehotk->eeepc_wlan_rfkill);
if (result) if (result)
goto wlan_fail; goto wlan_fail;
...@@ -693,8 +700,8 @@ static int eeepc_hotk_add(struct acpi_device *device) ...@@ -693,8 +700,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_bluetooth_rfkill) if (!ehotk->eeepc_bluetooth_rfkill)
goto bluetooth_fail; goto bluetooth_fail;
rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill, rfkill_init_sw_state(ehotk->eeepc_bluetooth_rfkill,
get_acpi(CM_ASL_BLUETOOTH) != 1); get_acpi(CM_ASL_BLUETOOTH) != 1);
result = rfkill_register(ehotk->eeepc_bluetooth_rfkill); result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);
if (result) if (result)
goto bluetooth_fail; goto bluetooth_fail;
...@@ -734,6 +741,33 @@ static int eeepc_hotk_remove(struct acpi_device *device, int type) ...@@ -734,6 +741,33 @@ static int eeepc_hotk_remove(struct acpi_device *device, int type)
return 0; return 0;
} }
static int eeepc_hotk_resume(struct acpi_device *device)
{
if (ehotk->eeepc_wlan_rfkill) {
bool wlan;
/* Workaround - it seems that _PTS disables the wireless
without notification or changing the value read by WLAN.
Normally this is fine because the correct value is restored
from the non-volatile storage on resume, but we need to do
it ourself if case suspend is aborted, or we lose wireless.
*/
wlan = get_acpi(CM_ASL_WLAN);
set_acpi(CM_ASL_WLAN, wlan);
rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill,
wlan != 1);
eeepc_rfkill_hotplug();
}
if (ehotk->eeepc_bluetooth_rfkill)
rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill,
get_acpi(CM_ASL_BLUETOOTH) != 1);
return 0;
}
/* /*
* Hwmon * Hwmon
*/ */
......
...@@ -1163,8 +1163,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, ...@@ -1163,8 +1163,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
{ {
struct tpacpi_rfk *atp_rfk; struct tpacpi_rfk *atp_rfk;
int res; int res;
bool initial_sw_state = false; bool sw_state = false;
int initial_sw_status; int sw_status;
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
...@@ -1185,17 +1185,17 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, ...@@ -1185,17 +1185,17 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
atp_rfk->id = id; atp_rfk->id = id;
atp_rfk->ops = tp_rfkops; atp_rfk->ops = tp_rfkops;
initial_sw_status = (tp_rfkops->get_status)(); sw_status = (tp_rfkops->get_status)();
if (initial_sw_status < 0) { if (sw_status < 0) {
printk(TPACPI_ERR printk(TPACPI_ERR
"failed to read initial state for %s, error %d\n", "failed to read initial state for %s, error %d\n",
name, initial_sw_status); name, sw_status);
} else { } else {
initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF); sw_state = (sw_status == TPACPI_RFK_RADIO_OFF);
if (set_default) { if (set_default) {
/* try to keep the initial state, since we ask the /* try to keep the initial state, since we ask the
* firmware to preserve it across S5 in NVRAM */ * firmware to preserve it across S5 in NVRAM */
rfkill_set_sw_state(atp_rfk->rfkill, initial_sw_state); rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
} }
} }
rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state()); rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
......
...@@ -160,8 +160,9 @@ struct rfkill * __must_check rfkill_alloc(const char *name, ...@@ -160,8 +160,9 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
* the rfkill structure. Before calling this function the driver needs * the rfkill structure. Before calling this function the driver needs
* to be ready to service method calls from rfkill. * to be ready to service method calls from rfkill.
* *
* If the software blocked state is not set before registration, * If rfkill_init_sw_state() is not called before registration,
* set_block will be called to initialize it to a default value. * set_block() will be called to initialize the software blocked state
* to a default value.
* *
* If the hardware blocked state is not set before registration, * If the hardware blocked state is not set before registration,
* it is assumed to be unblocked. * it is assumed to be unblocked.
...@@ -234,9 +235,11 @@ bool __must_check rfkill_set_hw_state(struct rfkill *rfkill, bool blocked); ...@@ -234,9 +235,11 @@ bool __must_check rfkill_set_hw_state(struct rfkill *rfkill, bool blocked);
* rfkill drivers that get events when the soft-blocked state changes * rfkill drivers that get events when the soft-blocked state changes
* (yes, some platforms directly act on input but allow changing again) * (yes, some platforms directly act on input but allow changing again)
* use this function to notify the rfkill core (and through that also * use this function to notify the rfkill core (and through that also
* userspace) of the current state. It is not necessary to notify on * userspace) of the current state.
* resume; since hibernation can always change the soft-blocked state, *
* the rfkill core will unconditionally restore the previous state. * Drivers should also call this function after resume if the state has
* been changed by the user. This only makes sense for "persistent"
* devices (see rfkill_init_sw_state()).
* *
* This function can be called in any context, even from within rfkill * This function can be called in any context, even from within rfkill
* callbacks. * callbacks.
...@@ -246,6 +249,22 @@ bool __must_check rfkill_set_hw_state(struct rfkill *rfkill, bool blocked); ...@@ -246,6 +249,22 @@ bool __must_check rfkill_set_hw_state(struct rfkill *rfkill, bool blocked);
*/ */
bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked); bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked);
/**
* rfkill_init_sw_state - Initialize persistent software block state
* @rfkill: pointer to the rfkill class to modify.
* @state: the current software block state to set
*
* rfkill drivers that preserve their software block state over power off
* use this function to notify the rfkill core (and through that also
* userspace) of their initial state. It should only be used before
* registration.
*
* In addition, it marks the device as "persistent", an attribute which
* can be read by userspace. Persistent devices are expected to preserve
* their own state when suspended.
*/
void rfkill_init_sw_state(struct rfkill *rfkill, bool blocked);
/** /**
* rfkill_set_states - Set the internal rfkill block states * rfkill_set_states - Set the internal rfkill block states
* @rfkill: pointer to the rfkill class to modify. * @rfkill: pointer to the rfkill class to modify.
...@@ -307,6 +326,10 @@ static inline bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked) ...@@ -307,6 +326,10 @@ static inline bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
return blocked; return blocked;
} }
static inline void rfkill_init_sw_state(struct rfkill *rfkill, bool blocked)
{
}
static inline void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw) static inline void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
{ {
} }
......
...@@ -56,7 +56,6 @@ struct rfkill { ...@@ -56,7 +56,6 @@ struct rfkill {
u32 idx; u32 idx;
bool registered; bool registered;
bool suspended;
bool persistent; bool persistent;
const struct rfkill_ops *ops; const struct rfkill_ops *ops;
...@@ -224,7 +223,7 @@ static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op) ...@@ -224,7 +223,7 @@ static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
static void rfkill_event(struct rfkill *rfkill) static void rfkill_event(struct rfkill *rfkill)
{ {
if (!rfkill->registered || rfkill->suspended) if (!rfkill->registered)
return; return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE); kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
...@@ -270,6 +269,9 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked) ...@@ -270,6 +269,9 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
unsigned long flags; unsigned long flags;
int err; int err;
if (unlikely(rfkill->dev.power.power_state.event & PM_EVENT_SLEEP))
return;
/* /*
* Some platforms (...!) generate input events which affect the * Some platforms (...!) generate input events which affect the
* _hard_ kill state -- whenever something tries to change the * _hard_ kill state -- whenever something tries to change the
...@@ -292,9 +294,6 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked) ...@@ -292,9 +294,6 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
rfkill->state |= RFKILL_BLOCK_SW_SETCALL; rfkill->state |= RFKILL_BLOCK_SW_SETCALL;
spin_unlock_irqrestore(&rfkill->lock, flags); spin_unlock_irqrestore(&rfkill->lock, flags);
if (unlikely(rfkill->dev.power.power_state.event & PM_EVENT_SLEEP))
return;
err = rfkill->ops->set_block(rfkill->data, blocked); err = rfkill->ops->set_block(rfkill->data, blocked);
spin_lock_irqsave(&rfkill->lock, flags); spin_lock_irqsave(&rfkill->lock, flags);
...@@ -508,19 +507,32 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked) ...@@ -508,19 +507,32 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
blocked = blocked || hwblock; blocked = blocked || hwblock;
spin_unlock_irqrestore(&rfkill->lock, flags); spin_unlock_irqrestore(&rfkill->lock, flags);
if (!rfkill->registered) { if (!rfkill->registered)
rfkill->persistent = true; return blocked;
} else {
if (prev != blocked && !hwblock)
schedule_work(&rfkill->uevent_work);
rfkill_led_trigger_event(rfkill); if (prev != blocked && !hwblock)
} schedule_work(&rfkill->uevent_work);
rfkill_led_trigger_event(rfkill);
return blocked; return blocked;
} }
EXPORT_SYMBOL(rfkill_set_sw_state); EXPORT_SYMBOL(rfkill_set_sw_state);
void rfkill_init_sw_state(struct rfkill *rfkill, bool blocked)
{
unsigned long flags;
BUG_ON(!rfkill);
BUG_ON(rfkill->registered);
spin_lock_irqsave(&rfkill->lock, flags);
__rfkill_set_sw_state(rfkill, blocked);
rfkill->persistent = true;
spin_unlock_irqrestore(&rfkill->lock, flags);
}
EXPORT_SYMBOL(rfkill_init_sw_state);
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw) void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
{ {
unsigned long flags; unsigned long flags;
...@@ -598,6 +610,15 @@ static ssize_t rfkill_idx_show(struct device *dev, ...@@ -598,6 +610,15 @@ static ssize_t rfkill_idx_show(struct device *dev,
return sprintf(buf, "%d\n", rfkill->idx); return sprintf(buf, "%d\n", rfkill->idx);
} }
static ssize_t rfkill_persistent_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%d\n", rfkill->persistent);
}
static u8 user_state_from_blocked(unsigned long state) static u8 user_state_from_blocked(unsigned long state)
{ {
if (state & RFKILL_BLOCK_HW) if (state & RFKILL_BLOCK_HW)
...@@ -656,6 +677,7 @@ static struct device_attribute rfkill_dev_attrs[] = { ...@@ -656,6 +677,7 @@ static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL), __ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL), __ATTR(type, S_IRUGO, rfkill_type_show, NULL),
__ATTR(index, S_IRUGO, rfkill_idx_show, NULL), __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(persistent, S_IRUGO, rfkill_persistent_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store), __ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store), __ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL __ATTR_NULL
...@@ -718,8 +740,6 @@ static int rfkill_suspend(struct device *dev, pm_message_t state) ...@@ -718,8 +740,6 @@ static int rfkill_suspend(struct device *dev, pm_message_t state)
rfkill_pause_polling(rfkill); rfkill_pause_polling(rfkill);
rfkill->suspended = true;
return 0; return 0;
} }
...@@ -728,10 +748,10 @@ static int rfkill_resume(struct device *dev) ...@@ -728,10 +748,10 @@ static int rfkill_resume(struct device *dev)
struct rfkill *rfkill = to_rfkill(dev); struct rfkill *rfkill = to_rfkill(dev);
bool cur; bool cur;
cur = !!(rfkill->state & RFKILL_BLOCK_SW); if (!rfkill->persistent) {
rfkill_set_block(rfkill, cur); cur = !!(rfkill->state & RFKILL_BLOCK_SW);
rfkill_set_block(rfkill, cur);
rfkill->suspended = false; }
rfkill_resume_polling(rfkill); rfkill_resume_polling(rfkill);
......
...@@ -1687,13 +1687,52 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) ...@@ -1687,13 +1687,52 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
if (err) if (err)
goto out_rtnl; goto out_rtnl;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP && err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN) { if (err)
err = -EINVAL;
goto out; goto out;
/* validate settings */
err = 0;
switch (dev->ieee80211_ptr->iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
/* disallow mesh-specific things */
if (params.plink_action)
err = -EINVAL;
break;
case NL80211_IFTYPE_STATION:
/* disallow everything but AUTHORIZED flag */
if (params.plink_action)
err = -EINVAL;
if (params.vlan)
err = -EINVAL;
if (params.supported_rates)
err = -EINVAL;
if (params.ht_capa)
err = -EINVAL;
if (params.listen_interval >= 0)
err = -EINVAL;
if (params.sta_flags_mask & ~BIT(NL80211_STA_FLAG_AUTHORIZED))
err = -EINVAL;
break;
case NL80211_IFTYPE_MESH_POINT:
/* disallow things mesh doesn't support */
if (params.vlan)
err = -EINVAL;
if (params.ht_capa)
err = -EINVAL;
if (params.listen_interval >= 0)
err = -EINVAL;
if (params.supported_rates)
err = -EINVAL;
if (params.sta_flags_mask)
err = -EINVAL;
break;
default:
err = -EINVAL;
} }
err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
if (err) if (err)
goto out; goto out;
...@@ -1728,9 +1767,6 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) ...@@ -1728,9 +1767,6 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
if (!info->attrs[NL80211_ATTR_MAC]) if (!info->attrs[NL80211_ATTR_MAC])
return -EINVAL; return -EINVAL;
if (!info->attrs[NL80211_ATTR_STA_AID])
return -EINVAL;
if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]) if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
return -EINVAL; return -EINVAL;
...@@ -1745,9 +1781,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) ...@@ -1745,9 +1781,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
params.listen_interval = params.listen_interval =
nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]); nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]); if (info->attrs[NL80211_ATTR_STA_AID]) {
if (!params.aid || params.aid > IEEE80211_MAX_AID) params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
return -EINVAL; if (!params.aid || params.aid > IEEE80211_MAX_AID)
return -EINVAL;
}
if (info->attrs[NL80211_ATTR_HT_CAPABILITY]) if (info->attrs[NL80211_ATTR_HT_CAPABILITY])
params.ht_capa = params.ht_capa =
...@@ -1762,13 +1800,39 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) ...@@ -1762,13 +1800,39 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
if (err) if (err)
goto out_rtnl; goto out_rtnl;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP && err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN) { if (err)
err = -EINVAL;
goto out; goto out;
/* validate settings */
err = 0;
switch (dev->ieee80211_ptr->iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
/* all ok but must have AID */
if (!params.aid)
err = -EINVAL;
break;
case NL80211_IFTYPE_MESH_POINT:
/* disallow things mesh doesn't support */
if (params.vlan)
err = -EINVAL;
if (params.aid)
err = -EINVAL;
if (params.ht_capa)
err = -EINVAL;
if (params.listen_interval >= 0)
err = -EINVAL;
if (params.supported_rates)
err = -EINVAL;
if (params.sta_flags_mask)
err = -EINVAL;
break;
default:
err = -EINVAL;
} }
err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
if (err) if (err)
goto out; goto out;
...@@ -1812,7 +1876,8 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info) ...@@ -1812,7 +1876,8 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
goto out_rtnl; goto out_rtnl;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP && if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN) { dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN &&
dev->ieee80211_ptr->iftype != NL80211_IFTYPE_MESH_POINT) {
err = -EINVAL; err = -EINVAL;
goto out; goto out;
} }
......
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