libertas_spi: Use workqueue in hw_host_to_card

Use workqueue to perform SPI xfers, it's necessary to fix
nasty "BUG: scheduling while atomic", because
spu_write() calls spi_sync() and spi_sync() may sleep, but
hw_host_to_card() callback can be called from atomic context.
Remove kthread completely, workqueue now does its job.
Restore intermediate buffers which were removed in commit
86c34fe89e that introduced
mentioned bug.

Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Vasily Khoruzhick 2011-01-21 22:44:48 +02:00 committed by John W. Linville
parent dbd98308a5
commit 16f775befc

View File

@ -20,10 +20,8 @@
#include <linux/moduleparam.h>
#include <linux/firmware.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <linux/spi/libertas_spi.h>
#include <linux/spi/spi.h>
@ -34,6 +32,12 @@
#include "dev.h"
#include "if_spi.h"
struct if_spi_packet {
struct list_head list;
u16 blen;
u8 buffer[0] __attribute__((aligned(4)));
};
struct if_spi_card {
struct spi_device *spi;
struct lbs_private *priv;
@ -51,18 +55,36 @@ struct if_spi_card {
unsigned long spu_reg_delay;
/* Handles all SPI communication (except for FW load) */
struct task_struct *spi_thread;
int run_thread;
/* Used to wake up the spi_thread */
struct semaphore spi_ready;
struct semaphore spi_thread_terminated;
struct workqueue_struct *workqueue;
struct work_struct packet_work;
u8 cmd_buffer[IF_SPI_CMD_BUF_SIZE];
/* A buffer of incoming packets from libertas core.
* Since we can't sleep in hw_host_to_card, we have to buffer
* them. */
struct list_head cmd_packet_list;
struct list_head data_packet_list;
/* Protects cmd_packet_list and data_packet_list */
spinlock_t buffer_lock;
};
static void free_if_spi_card(struct if_spi_card *card)
{
struct list_head *cursor, *next;
struct if_spi_packet *packet;
list_for_each_safe(cursor, next, &card->cmd_packet_list) {
packet = container_of(cursor, struct if_spi_packet, list);
list_del(&packet->list);
kfree(packet);
}
list_for_each_safe(cursor, next, &card->data_packet_list) {
packet = container_of(cursor, struct if_spi_packet, list);
list_del(&packet->list);
kfree(packet);
}
spi_set_drvdata(card->spi, NULL);
kfree(card);
}
@ -622,7 +644,7 @@ static int if_spi_prog_main_firmware(struct if_spi_card *card,
/*
* SPI Transfer Thread
*
* The SPI thread handles all SPI transfers, so there is no need for a lock.
* The SPI worker handles all SPI transfers, so there is no need for a lock.
*/
/* Move a command from the card to the host */
@ -742,6 +764,40 @@ static int if_spi_c2h_data(struct if_spi_card *card)
return err;
}
/* Move data or a command from the host to the card. */
static void if_spi_h2c(struct if_spi_card *card,
struct if_spi_packet *packet, int type)
{
int err = 0;
u16 int_type, port_reg;
switch (type) {
case MVMS_DAT:
int_type = IF_SPI_CIC_TX_DOWNLOAD_OVER;
port_reg = IF_SPI_DATA_RDWRPORT_REG;
break;
case MVMS_CMD:
int_type = IF_SPI_CIC_CMD_DOWNLOAD_OVER;
port_reg = IF_SPI_CMD_RDWRPORT_REG;
break;
default:
lbs_pr_err("can't transfer buffer of type %d\n", type);
err = -EINVAL;
goto out;
}
/* Write the data to the card */
err = spu_write(card, port_reg, packet->buffer, packet->blen);
if (err)
goto out;
out:
kfree(packet);
if (err)
lbs_pr_err("%s: error %d\n", __func__, err);
}
/* Inform the host about a card event */
static void if_spi_e2h(struct if_spi_card *card)
{
@ -766,71 +822,88 @@ static void if_spi_e2h(struct if_spi_card *card)
lbs_pr_err("%s: error %d\n", __func__, err);
}
static int lbs_spi_thread(void *data)
static void if_spi_host_to_card_worker(struct work_struct *work)
{
int err;
struct if_spi_card *card = data;
struct if_spi_card *card;
u16 hiStatus;
unsigned long flags;
struct if_spi_packet *packet;
while (1) {
/* Wait to be woken up by one of two things. First, our ISR
* could tell us that something happened on the WLAN.
* Secondly, libertas could call hw_host_to_card with more
* data, which we might be able to send.
*/
do {
err = down_interruptible(&card->spi_ready);
if (!card->run_thread) {
up(&card->spi_thread_terminated);
do_exit(0);
}
} while (err == -EINTR);
card = container_of(work, struct if_spi_card, packet_work);
/* Read the host interrupt status register to see what we
* can do. */
err = spu_read_u16(card, IF_SPI_HOST_INT_STATUS_REG,
&hiStatus);
if (err) {
lbs_pr_err("I/O error\n");
lbs_deb_enter(LBS_DEB_SPI);
/* Read the host interrupt status register to see what we
* can do. */
err = spu_read_u16(card, IF_SPI_HOST_INT_STATUS_REG,
&hiStatus);
if (err) {
lbs_pr_err("I/O error\n");
goto err;
}
if (hiStatus & IF_SPI_HIST_CMD_UPLOAD_RDY) {
err = if_spi_c2h_cmd(card);
if (err)
goto err;
}
}
if (hiStatus & IF_SPI_HIST_RX_UPLOAD_RDY) {
err = if_spi_c2h_data(card);
if (err)
goto err;
}
if (hiStatus & IF_SPI_HIST_CMD_UPLOAD_RDY) {
err = if_spi_c2h_cmd(card);
if (err)
goto err;
}
if (hiStatus & IF_SPI_HIST_RX_UPLOAD_RDY) {
err = if_spi_c2h_data(card);
if (err)
goto err;
}
/* workaround: in PS mode, the card does not set the Command
* Download Ready bit, but it sets TX Download Ready. */
if (hiStatus & IF_SPI_HIST_CMD_DOWNLOAD_RDY ||
(card->priv->psstate != PS_STATE_FULL_POWER &&
(hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY))) {
/* This means two things. First of all,
* if there was a previous command sent, the card has
* successfully received it.
* Secondly, it is now ready to download another
* command.
*/
lbs_host_to_card_done(card->priv);
/* workaround: in PS mode, the card does not set the Command
* Download Ready bit, but it sets TX Download Ready. */
if (hiStatus & IF_SPI_HIST_CMD_DOWNLOAD_RDY ||
(card->priv->psstate != PS_STATE_FULL_POWER &&
(hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY))) {
lbs_host_to_card_done(card->priv);
/* Do we have any command packets from the host to
* send? */
packet = NULL;
spin_lock_irqsave(&card->buffer_lock, flags);
if (!list_empty(&card->cmd_packet_list)) {
packet = (struct if_spi_packet *)(card->
cmd_packet_list.next);
list_del(&packet->list);
}
spin_unlock_irqrestore(&card->buffer_lock, flags);
if (hiStatus & IF_SPI_HIST_CARD_EVENT)
if_spi_e2h(card);
if (packet)
if_spi_h2c(card, packet, MVMS_CMD);
}
if (hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY) {
/* Do we have any data packets from the host to
* send? */
packet = NULL;
spin_lock_irqsave(&card->buffer_lock, flags);
if (!list_empty(&card->data_packet_list)) {
packet = (struct if_spi_packet *)(card->
data_packet_list.next);
list_del(&packet->list);
}
spin_unlock_irqrestore(&card->buffer_lock, flags);
if (packet)
if_spi_h2c(card, packet, MVMS_DAT);
}
if (hiStatus & IF_SPI_HIST_CARD_EVENT)
if_spi_e2h(card);
err:
if (err)
lbs_pr_err("%s: got error %d\n", __func__, err);
}
}
if (err)
lbs_pr_err("%s: got error %d\n", __func__, err);
/* Block until lbs_spi_thread thread has terminated */
static void if_spi_terminate_spi_thread(struct if_spi_card *card)
{
/* It would be nice to use kthread_stop here, but that function
* can't wake threads waiting for a semaphore. */
card->run_thread = 0;
up(&card->spi_ready);
down(&card->spi_thread_terminated);
lbs_deb_leave(LBS_DEB_SPI);
}
/*
@ -842,18 +915,40 @@ static int if_spi_host_to_card(struct lbs_private *priv,
u8 type, u8 *buf, u16 nb)
{
int err = 0;
unsigned long flags;
struct if_spi_card *card = priv->card;
struct if_spi_packet *packet;
u16 blen;
lbs_deb_enter_args(LBS_DEB_SPI, "type %d, bytes %d", type, nb);
nb = ALIGN(nb, 4);
if (nb == 0) {
lbs_pr_err("%s: invalid size requested: %d\n", __func__, nb);
err = -EINVAL;
goto out;
}
blen = ALIGN(nb, 4);
packet = kzalloc(sizeof(struct if_spi_packet) + blen, GFP_ATOMIC);
if (!packet) {
err = -ENOMEM;
goto out;
}
packet->blen = blen;
memcpy(packet->buffer, buf, nb);
memset(packet->buffer + nb, 0, blen - nb);
switch (type) {
case MVMS_CMD:
err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG, buf, nb);
priv->dnld_sent = DNLD_CMD_SENT;
spin_lock_irqsave(&card->buffer_lock, flags);
list_add_tail(&packet->list, &card->cmd_packet_list);
spin_unlock_irqrestore(&card->buffer_lock, flags);
break;
case MVMS_DAT:
err = spu_write(card, IF_SPI_DATA_RDWRPORT_REG, buf, nb);
priv->dnld_sent = DNLD_DATA_SENT;
spin_lock_irqsave(&card->buffer_lock, flags);
list_add_tail(&packet->list, &card->data_packet_list);
spin_unlock_irqrestore(&card->buffer_lock, flags);
break;
default:
lbs_pr_err("can't transfer buffer of type %d", type);
@ -861,6 +956,9 @@ static int if_spi_host_to_card(struct lbs_private *priv,
break;
}
/* Queue spi xfer work */
queue_work(card->workqueue, &card->packet_work);
out:
lbs_deb_leave_args(LBS_DEB_SPI, "err=%d", err);
return err;
}
@ -869,13 +967,14 @@ static int if_spi_host_to_card(struct lbs_private *priv,
* Host Interrupts
*
* Service incoming interrupts from the WLAN device. We can't sleep here, so
* don't try to talk on the SPI bus, just wake up the SPI thread.
* don't try to talk on the SPI bus, just queue the SPI xfer work.
*/
static irqreturn_t if_spi_host_interrupt(int irq, void *dev_id)
{
struct if_spi_card *card = dev_id;
up(&card->spi_ready);
queue_work(card->workqueue, &card->packet_work);
return IRQ_HANDLED;
}
@ -883,16 +982,87 @@ static irqreturn_t if_spi_host_interrupt(int irq, void *dev_id)
* SPI callbacks
*/
static int if_spi_init_card(struct if_spi_card *card)
{
struct spi_device *spi = card->spi;
int err, i;
u32 scratch;
const struct firmware *helper = NULL;
const struct firmware *mainfw = NULL;
lbs_deb_enter(LBS_DEB_SPI);
err = spu_init(card, card->pdata->use_dummy_writes);
if (err)
goto out;
err = spu_get_chip_revision(card, &card->card_id, &card->card_rev);
if (err)
goto out;
err = spu_read_u32(card, IF_SPI_SCRATCH_4_REG, &scratch);
if (err)
goto out;
if (scratch == SUCCESSFUL_FW_DOWNLOAD_MAGIC)
lbs_deb_spi("Firmware is already loaded for "
"Marvell WLAN 802.11 adapter\n");
else {
/* Check if we support this card */
for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
if (card->card_id == fw_table[i].model)
break;
}
if (i == ARRAY_SIZE(fw_table)) {
lbs_pr_err("Unsupported chip_id: 0x%02x\n",
card->card_id);
err = -ENODEV;
goto out;
}
err = lbs_get_firmware(&card->spi->dev, NULL, NULL,
card->card_id, &fw_table[0], &helper,
&mainfw);
if (err) {
lbs_pr_err("failed to find firmware (%d)\n", err);
goto out;
}
lbs_deb_spi("Initializing FW for Marvell WLAN 802.11 adapter "
"(chip_id = 0x%04x, chip_rev = 0x%02x) "
"attached to SPI bus_num %d, chip_select %d. "
"spi->max_speed_hz=%d\n",
card->card_id, card->card_rev,
spi->master->bus_num, spi->chip_select,
spi->max_speed_hz);
err = if_spi_prog_helper_firmware(card, helper);
if (err)
goto out;
err = if_spi_prog_main_firmware(card, mainfw);
if (err)
goto out;
lbs_deb_spi("loaded FW for Marvell WLAN 802.11 adapter\n");
}
err = spu_set_interrupt_mode(card, 0, 1);
if (err)
goto out;
out:
if (helper)
release_firmware(helper);
if (mainfw)
release_firmware(mainfw);
lbs_deb_leave_args(LBS_DEB_SPI, "err %d\n", err);
return err;
}
static int __devinit if_spi_probe(struct spi_device *spi)
{
struct if_spi_card *card;
struct lbs_private *priv = NULL;
struct libertas_spi_platform_data *pdata = spi->dev.platform_data;
int err = 0, i;
u32 scratch;
struct sched_param param = { .sched_priority = 1 };
const struct firmware *helper = NULL;
const struct firmware *mainfw = NULL;
int err = 0;
lbs_deb_enter(LBS_DEB_SPI);
@ -911,69 +1081,21 @@ static int __devinit if_spi_probe(struct spi_device *spi)
card = kzalloc(sizeof(struct if_spi_card), GFP_KERNEL);
if (!card) {
err = -ENOMEM;
goto out;
goto teardown;
}
spi_set_drvdata(spi, card);
card->pdata = pdata;
card->spi = spi;
card->prev_xfer_time = jiffies;
sema_init(&card->spi_ready, 0);
sema_init(&card->spi_thread_terminated, 0);
INIT_LIST_HEAD(&card->cmd_packet_list);
INIT_LIST_HEAD(&card->data_packet_list);
spin_lock_init(&card->buffer_lock);
/* Initialize the SPI Interface Unit */
err = spu_init(card, pdata->use_dummy_writes);
if (err)
goto free_card;
err = spu_get_chip_revision(card, &card->card_id, &card->card_rev);
if (err)
goto free_card;
/* Firmware load */
err = spu_read_u32(card, IF_SPI_SCRATCH_4_REG, &scratch);
if (err)
goto free_card;
if (scratch == SUCCESSFUL_FW_DOWNLOAD_MAGIC)
lbs_deb_spi("Firmware is already loaded for "
"Marvell WLAN 802.11 adapter\n");
else {
/* Check if we support this card */
for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
if (card->card_id == fw_table[i].model)
break;
}
if (i == ARRAY_SIZE(fw_table)) {
lbs_pr_err("Unsupported chip_id: 0x%02x\n",
card->card_id);
err = -ENODEV;
goto free_card;
}
err = lbs_get_firmware(&card->spi->dev, NULL, NULL,
card->card_id, &fw_table[0], &helper,
&mainfw);
if (err) {
lbs_pr_err("failed to find firmware (%d)\n", err);
goto free_card;
}
lbs_deb_spi("Initializing FW for Marvell WLAN 802.11 adapter "
"(chip_id = 0x%04x, chip_rev = 0x%02x) "
"attached to SPI bus_num %d, chip_select %d. "
"spi->max_speed_hz=%d\n",
card->card_id, card->card_rev,
spi->master->bus_num, spi->chip_select,
spi->max_speed_hz);
err = if_spi_prog_helper_firmware(card, helper);
if (err)
goto free_card;
err = if_spi_prog_main_firmware(card, mainfw);
if (err)
goto free_card;
lbs_deb_spi("loaded FW for Marvell WLAN 802.11 adapter\n");
}
err = spu_set_interrupt_mode(card, 0, 1);
err = if_spi_init_card(card);
if (err)
goto free_card;
@ -993,27 +1115,16 @@ static int __devinit if_spi_probe(struct spi_device *spi)
priv->fw_ready = 1;
/* Initialize interrupt handling stuff. */
card->run_thread = 1;
card->spi_thread = kthread_run(lbs_spi_thread, card, "lbs_spi_thread");
if (IS_ERR(card->spi_thread)) {
card->run_thread = 0;
err = PTR_ERR(card->spi_thread);
lbs_pr_err("error creating SPI thread: err=%d\n", err);
goto remove_card;
}
if (sched_setscheduler(card->spi_thread, SCHED_FIFO, &param))
lbs_pr_err("Error setting scheduler, using default.\n");
card->workqueue = create_workqueue("libertas_spi");
INIT_WORK(&card->packet_work, if_spi_host_to_card_worker);
err = request_irq(spi->irq, if_spi_host_interrupt,
IRQF_TRIGGER_FALLING, "libertas_spi", card);
if (err) {
lbs_pr_err("can't get host irq line-- request_irq failed\n");
goto terminate_thread;
goto terminate_workqueue;
}
/* poke the IRQ handler so that we don't miss the first interrupt */
up(&card->spi_ready);
/* Start the card.
* This will call register_netdev, and we'll start
* getting interrupts... */
@ -1028,18 +1139,16 @@ static int __devinit if_spi_probe(struct spi_device *spi)
release_irq:
free_irq(spi->irq, card);
terminate_thread:
if_spi_terminate_spi_thread(card);
remove_card:
terminate_workqueue:
flush_workqueue(card->workqueue);
destroy_workqueue(card->workqueue);
lbs_remove_card(priv); /* will call free_netdev */
free_card:
free_if_spi_card(card);
teardown:
if (pdata->teardown)
pdata->teardown(spi);
out:
if (helper)
release_firmware(helper);
if (mainfw)
release_firmware(mainfw);
lbs_deb_leave_args(LBS_DEB_SPI, "err %d\n", err);
return err;
}
@ -1056,7 +1165,8 @@ static int __devexit libertas_spi_remove(struct spi_device *spi)
lbs_remove_card(priv); /* will call free_netdev */
free_irq(spi->irq, card);
if_spi_terminate_spi_thread(card);
flush_workqueue(card->workqueue);
destroy_workqueue(card->workqueue);
if (card->pdata->teardown)
card->pdata->teardown(spi);
free_if_spi_card(card);