From: Jay Fenlason <fenlason@redhat.com> Date: Wed, 5 Nov 2008 14:01:51 -0500 Subject: [firewire] various bug and module unload hang fixes Message-id: 20081105190151.GA3440@redhat.com O-Subject: [PATCH RHEL5.3 BZ#469710 - Various firewire bugs fixed upstream and BZ#469711 - firewire module unload hangs Bugzilla: 469710 469711 RH-Acked-by: Prarit Bhargava <prarit@redhat.com> RH-Acked-by: Jarod Wilson <jarod@redhat.com> RH-Acked-by: Pete Zaitcev <zaitcev@redhat.com> diff --git a/drivers/firewire/fw-card.c b/drivers/firewire/fw-card.c index 8f1bccc..4f41477 100644 --- a/drivers/firewire/fw-card.c +++ b/drivers/firewire/fw-card.c @@ -248,6 +248,17 @@ static const char gap_count_table[] = { 63, 5, 7, 8, 10, 13, 16, 18, 21, 24, 26, 29, 32, 35, 37, 40 }; +void +fw_schedule_bm_work(struct fw_card *card, bool delay) +{ + int scheduled; + + fw_card_get(card); + scheduled = schedule_delayed_work(&card->work, delay ? DIV_ROUND_UP(HZ, 10) : 0 ); + if (!scheduled) + fw_card_put(card); +} + static void fw_card_bm_work(void *w) { @@ -266,7 +277,7 @@ fw_card_bm_work(void *w) if (local_node == NULL) { spin_unlock_irqrestore(&card->lock, flags); - return; + goto out_put_card; } fw_node_get(local_node); fw_node_get(root_node); @@ -340,7 +351,7 @@ fw_card_bm_work(void *w) * this task 100ms from now. */ spin_unlock_irqrestore(&card->lock, flags); - schedule_delayed_work(&card->work, DIV_ROUND_UP(HZ, 10)); + fw_schedule_bm_work(card, true); goto out; } @@ -415,6 +426,8 @@ fw_card_bm_work(void *w) fw_device_put(root_device); fw_node_put(root_node); fw_node_put(local_node); + out_put_card: + fw_card_put(card); } static void @@ -437,6 +450,7 @@ fw_card_initialize(struct fw_card *card, const struct fw_card_driver *driver, card->current_tlabel = 0; card->tlabel_mask = 0; card->color = 0; + card->bm_retries = 0; card->broadcast_channel = BROADCAST_CHANNEL_INITIAL; kref_init(&card->kref); @@ -570,7 +584,6 @@ fw_core_remove_card(struct fw_card *card) fw_card_put(card); wait_for_completion(&card->done); - cancel_rearming_delayed_work(&card->work); WARN_ON(!list_empty(&card->transaction_list)); del_timer_sync(&card->flush_timer); } diff --git a/drivers/firewire/fw-cdev.c b/drivers/firewire/fw-cdev.c index 2e6d584..ed03234 100644 --- a/drivers/firewire/fw-cdev.c +++ b/drivers/firewire/fw-cdev.c @@ -720,8 +720,8 @@ static int ioctl_create_iso_context(struct client *client, void *buffer) #define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff) #define GET_INTERRUPT(v) (((v) >> 16) & 0x01) #define GET_SKIP(v) (((v) >> 17) & 0x01) -#define GET_TAG(v) (((v) >> 18) & 0x02) -#define GET_SY(v) (((v) >> 20) & 0x04) +#define GET_TAG(v) (((v) >> 18) & 0x03) +#define GET_SY(v) (((v) >> 20) & 0x0f) #define GET_HEADER_LENGTH(v) (((v) >> 24) & 0xff) static int ioctl_queue_iso(struct client *client, void *buffer) @@ -913,7 +913,7 @@ dispatch_ioctl(struct client *client, unsigned int cmd, void __user *arg) return -EFAULT; } - return 0; + return retval; } static long diff --git a/drivers/firewire/fw-device.c b/drivers/firewire/fw-device.c index 29c3653..dedf820 100644 --- a/drivers/firewire/fw-device.c +++ b/drivers/firewire/fw-device.c @@ -690,7 +690,7 @@ static void fw_device_init(void *w) fw_notify("giving up on config rom for node id %x\n", device->node_id); if (device->node == device->card->root_node) - schedule_delayed_work(&device->card->work, 0); + fw_schedule_bm_work(device->card, false); fw_device_release(&device->device); } return; @@ -760,7 +760,7 @@ static void fw_device_init(void *w) * pretty harmless. */ if (device->node == device->card->root_node) - schedule_delayed_work(&device->card->work, 0); + fw_schedule_bm_work(device->card, false); return; @@ -896,7 +896,7 @@ static void fw_device_refresh(void *w) fw_device_shutdown(work); out: if (node_id == card->root_node->node_id) - schedule_delayed_work(&card->work, 0); + fw_schedule_bm_work(card, false); } void fw_node_event(struct fw_card *card, struct fw_node *node, int event) diff --git a/drivers/firewire/fw-ohci.c b/drivers/firewire/fw-ohci.c index d5c610e..3e9a011 100644 --- a/drivers/firewire/fw-ohci.c +++ b/drivers/firewire/fw-ohci.c @@ -476,6 +476,7 @@ static int ar_context_add_page(struct ar_context *ctx) if (ab == NULL) return -ENOMEM; + ab->next = NULL; memset(&ab->descriptor, 0, sizeof(ab->descriptor)); ab->descriptor.control = cpu_to_le16(DESCRIPTOR_INPUT_MORE | DESCRIPTOR_STATUS | @@ -495,6 +496,20 @@ static int ar_context_add_page(struct ar_context *ctx) return 0; } +static void ar_context_release(struct ar_context *ctx) +{ + struct ar_buffer *ab, *ab_next; + size_t offset; + dma_addr_t ab_bus; + + for (ab = ctx->current_buffer; ab; ab = ab_next) { + ab_next = ab->next; + offset = offsetof(struct ar_buffer, data); + ab_bus = le32_to_cpu(ab->descriptor.data_address) - offset; + dma_free_coherent(ctx->ohci->card.device, PAGE_SIZE, + ab, ab_bus); + } +} #if defined(CONFIG_PPC_PMAC) && defined(CONFIG_PPC32) #define cond_le32_to_cpu(v) \ @@ -2348,8 +2363,8 @@ pci_probe(struct pci_dev *dev, const struct pci_device_id *ent) ohci = kzalloc(sizeof(*ohci), GFP_KERNEL); if (ohci == NULL) { - fw_error("Could not malloc fw_ohci data.\n"); - return -ENOMEM; + err = -ENOMEM; + goto fail; } fw_card_initialize(&ohci->card, &ohci_driver, &dev->dev); @@ -2358,7 +2373,7 @@ pci_probe(struct pci_dev *dev, const struct pci_device_id *ent) err = pci_enable_device(dev); if (err) { - fw_error("Failed to enable OHCI hardware.\n"); + fw_error("Failed to enable OHCI hardware\n"); goto fail_free; } @@ -2426,9 +2441,8 @@ pci_probe(struct pci_dev *dev, const struct pci_device_id *ent) ohci->ir_context_list = kzalloc(size, GFP_KERNEL); if (ohci->it_context_list == NULL || ohci->ir_context_list == NULL) { - fw_error("Out of memory for it/ir contexts.\n"); err = -ENOMEM; - goto fail_registers; + goto fail_contexts; } /* self-id dma buffer allocation */ @@ -2437,9 +2451,8 @@ pci_probe(struct pci_dev *dev, const struct pci_device_id *ent) &ohci->self_id_bus, GFP_KERNEL); if (ohci->self_id_cpu == NULL) { - fw_error("Out of memory for self ID buffer.\n"); err = -ENOMEM; - goto fail_registers; + goto fail_contexts; } bus_options = reg_read(ohci, OHCI1394_BusOptions); @@ -2459,9 +2472,13 @@ pci_probe(struct pci_dev *dev, const struct pci_device_id *ent) fail_self_id: dma_free_coherent(ohci->card.device, SELF_ID_BUF_SIZE, ohci->self_id_cpu, ohci->self_id_bus); - fail_registers: - kfree(ohci->it_context_list); + fail_contexts: kfree(ohci->ir_context_list); + kfree(ohci->it_context_list); + context_release(&ohci->at_response_ctx); + context_release(&ohci->at_request_ctx); + ar_context_release(&ohci->ar_response_ctx); + ar_context_release(&ohci->ar_request_ctx); pci_iounmap(dev, ohci->registers); fail_iomem: pci_release_region(dev, 0); @@ -2470,6 +2487,9 @@ pci_probe(struct pci_dev *dev, const struct pci_device_id *ent) fail_free: kfree(&ohci->card); ohci_pmac_off(dev); + fail: + if (err == -ENOMEM) + fw_error("Out of memory\n"); return err; } @@ -2490,8 +2510,18 @@ static void pci_remove(struct pci_dev *dev) software_reset(ohci); free_irq(dev->irq, ohci); + if (ohci->next_config_rom && ohci->next_config_rom != ohci->config_rom) + dma_free_coherent(ohci->card.device, CONFIG_ROM_SIZE, + ohci->next_config_rom, ohci->next_config_rom_bus); + if (ohci->config_rom) + dma_free_coherent(ohci->card.device, CONFIG_ROM_SIZE, + ohci->config_rom, ohci->config_rom_bus); dma_free_coherent(ohci->card.device, SELF_ID_BUF_SIZE, ohci->self_id_cpu, ohci->self_id_bus); + ar_context_release(&ohci->ar_request_ctx); + ar_context_release(&ohci->ar_response_ctx); + context_release(&ohci->at_request_ctx); + context_release(&ohci->at_response_ctx); kfree(ohci->it_context_list); kfree(ohci->ir_context_list); pci_iounmap(dev, ohci->registers); diff --git a/drivers/firewire/fw-sbp2.c b/drivers/firewire/fw-sbp2.c index a5d0de8..ca65cf7 100644 --- a/drivers/firewire/fw-sbp2.c +++ b/drivers/firewire/fw-sbp2.c @@ -174,6 +174,9 @@ struct sbp2_target { int blocked; /* ditto */ }; +/* Impossible login_id, to detect logout attempt before successful login */ +#define INVALID_LOGIN_ID 0x10000 + /* * Per section 7.4.8 of the SBP-2 spec, a mgt_ORB_timeout value can be * provided in the config rom. Most devices do provide a value, which @@ -182,8 +185,8 @@ struct sbp2_target { #define SBP2_MIN_LOGIN_ORB_TIMEOUT 5000U /* Timeout in ms */ #define SBP2_MAX_LOGIN_ORB_TIMEOUT 40000U /* Timeout in ms */ #define SBP2_ORB_TIMEOUT 2000U /* Timeout in ms */ -#define SBP2_ORB_NULL 0x80000000 #define SBP2_MAX_SG_ELEMENT_LENGTH 0xf000 +#define SBP2_ORB_NULL 0x80000000 #define SBP2_RETRY_LIMIT 0xf /* 15 retries */ #define SBP2_CYCLE_LIMIT (0xc8 << 12) /* 200 125us cycles */ @@ -796,9 +799,20 @@ static void sbp2_release_target(struct kref *kref) scsi_remove_device(sdev); scsi_device_put(sdev); } - sbp2_send_management_orb(lu, tgt->node_id, lu->generation, - SBP2_LOGOUT_REQUEST, lu->login_id, NULL); - + if (lu->login_id != INVALID_LOGIN_ID) { + int generation, node_id; + /* + * tgt->node_id may be obsolete here if we failed + * during initial login or after a bus reset where + * the topology changed. + */ + generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + node_id = device->node_id; + sbp2_send_management_orb(lu, node_id, generation, + SBP2_LOGOUT_REQUEST, + lu->login_id, NULL); + } fw_core_remove_address_handler(&lu->address_handler); list_del(&lu->link); kfree(lu); @@ -813,19 +827,20 @@ static void sbp2_release_target(struct kref *kref) static struct workqueue_struct *sbp2_wq; +static void sbp2_target_put(struct sbp2_target *tgt) +{ + kref_put(&tgt->kref, sbp2_release_target); +} + /* * Always get the target's kref when scheduling work on one its units. * Each workqueue job is responsible to call sbp2_target_put() upon return. */ static void sbp2_queue_work(struct sbp2_logical_unit *lu, unsigned long delay) { - if (queue_delayed_work(sbp2_wq, &lu->work, delay)) - kref_get(&lu->tgt->kref); -} - -static void sbp2_target_put(struct sbp2_target *tgt) -{ - kref_put(&tgt->kref, sbp2_release_target); + kref_get(&lu->tgt->kref); + if (!queue_delayed_work(sbp2_wq, &lu->work, delay)) + sbp2_target_put(lu->tgt); } /* @@ -987,6 +1002,7 @@ static int sbp2_add_logical_unit(struct sbp2_target *tgt, int lun_entry) lu->tgt = tgt; lu->lun = lun_entry & 0xffff; + lu->login_id = INVALID_LOGIN_ID; lu->retries = 0; lu->has_sdev = false; lu->blocked = false; @@ -1152,7 +1168,7 @@ static int sbp2_probe(struct device *dev) /* Do the login in a workqueue so we can easily reschedule retries. */ list_for_each_entry(lu, &tgt->lu_list, link) - sbp2_queue_work(lu, 0); + sbp2_queue_work(lu, DIV_ROUND_UP(HZ, 5)); return 0; fail_tgt_put: @@ -1451,7 +1467,7 @@ static int sbp2_scsi_queuecommand(struct scsi_cmnd *cmd, scsi_done_fn_t done) struct fw_device *device = fw_device(lu->tgt->unit->device.parent); struct sbp2_command_orb *orb; unsigned int max_payload; - int retval = SCSI_MLQUEUE_HOST_BUSY; + int generation, retval = SCSI_MLQUEUE_HOST_BUSY; /* * Bidirectional commands are not yet implemented, and unknown @@ -1495,6 +1511,9 @@ static int sbp2_scsi_queuecommand(struct scsi_cmnd *cmd, scsi_done_fn_t done) if (cmd->sc_data_direction == DMA_FROM_DEVICE) orb->request.misc |= cpu_to_be32(COMMAND_ORB_DIRECTION); + generation = device->generation; + smp_rmb(); /* sbp2_map_scatterlist looks at tgt->address_high */ + if (scsi_sg_count(cmd) && sbp2_map_scatterlist(orb, device, lu) < 0) goto out; @@ -1507,7 +1526,7 @@ static int sbp2_scsi_queuecommand(struct scsi_cmnd *cmd, scsi_done_fn_t done) if (dma_mapping_error(orb->base.request_bus)) goto out; - sbp2_send_orb(&orb->base, lu, lu->tgt->node_id, lu->generation, + sbp2_send_orb(&orb->base, lu, lu->tgt->node_id, generation, lu->command_block_agent_address + SBP2_ORB_POINTER); retval = 0; out: diff --git a/drivers/firewire/fw-topology.c b/drivers/firewire/fw-topology.c index e41da22..01f6766 100644 --- a/drivers/firewire/fw-topology.c +++ b/drivers/firewire/fw-topology.c @@ -413,7 +413,7 @@ static void update_tree(struct fw_card *card, struct fw_node *root) { struct list_head list0, list1; - struct fw_node *node0, *node1; + struct fw_node *node0, *node1, *next1; int i, event; INIT_LIST_HEAD(&list0); @@ -485,7 +485,9 @@ update_tree(struct fw_card *card, struct fw_node *root) } node0 = fw_node(node0->link.next); - node1 = fw_node(node1->link.next); + next1 = fw_node(node1->link.next); + fw_node_put(node1); + node1 = next1; } } @@ -540,7 +542,7 @@ fw_core_handle_bus_reset(struct fw_card *card, smp_wmb(); card->generation = generation; card->reset_jiffies = jiffies; - schedule_delayed_work(&card->work, 0); + fw_schedule_bm_work(card, false); local_node = build_tree(card, self_ids, self_id_count); diff --git a/drivers/firewire/fw-transaction.h b/drivers/firewire/fw-transaction.h index fcf4439..9c7ec75 100644 --- a/drivers/firewire/fw-transaction.h +++ b/drivers/firewire/fw-transaction.h @@ -214,6 +214,7 @@ void fw_fill_response(struct fw_packet *response, u32 *request_header, int rcode, void *payload, size_t length); void fw_send_response(struct fw_card *card, struct fw_request *request, int rcode); +extern void fw_schedule_bm_work(struct fw_card *card, bool delay); extern struct bus_type fw_bus_type; @@ -248,7 +249,7 @@ struct fw_card { struct fw_node *local_node; struct fw_node *root_node; struct fw_node *irm_node; - int color; + u8 color; /* must be u8 to match the definition in struct fw_node */ int gap_count; int beta_repeaters_present;