diff options
Diffstat (limited to 'drivers/firewire/core-card.c')
-rw-r--r-- | drivers/firewire/core-card.c | 130 |
1 files changed, 91 insertions, 39 deletions
diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c index 6ac5ff20a2fe..01354b9de8b2 100644 --- a/drivers/firewire/core-card.c +++ b/drivers/firewire/core-card.c @@ -23,6 +23,7 @@ #include <asm/byteorder.h> #include "core.h" +#include <trace/events/firewire.h> #define define_fw_printk_level(func, kern_level) \ void func(const struct fw_card *card, const char *fmt, ...) \ @@ -167,7 +168,6 @@ static size_t required_space(struct fw_descriptor *desc) int fw_core_add_descriptor(struct fw_descriptor *desc) { size_t i; - int ret; /* * Check descriptor is valid; the length of all blocks in the @@ -181,29 +181,25 @@ int fw_core_add_descriptor(struct fw_descriptor *desc) if (i != desc->length) return -EINVAL; - mutex_lock(&card_mutex); + guard(mutex)(&card_mutex); - if (config_rom_length + required_space(desc) > 256) { - ret = -EBUSY; - } else { - list_add_tail(&desc->link, &descriptor_list); - config_rom_length += required_space(desc); - descriptor_count++; - if (desc->immediate > 0) - descriptor_count++; - update_config_roms(); - ret = 0; - } + if (config_rom_length + required_space(desc) > 256) + return -EBUSY; - mutex_unlock(&card_mutex); + list_add_tail(&desc->link, &descriptor_list); + config_rom_length += required_space(desc); + descriptor_count++; + if (desc->immediate > 0) + descriptor_count++; + update_config_roms(); - return ret; + return 0; } EXPORT_SYMBOL(fw_core_add_descriptor); void fw_core_remove_descriptor(struct fw_descriptor *desc) { - mutex_lock(&card_mutex); + guard(mutex)(&card_mutex); list_del(&desc->link); config_rom_length -= required_space(desc); @@ -211,8 +207,6 @@ void fw_core_remove_descriptor(struct fw_descriptor *desc) if (desc->immediate > 0) descriptor_count--; update_config_roms(); - - mutex_unlock(&card_mutex); } EXPORT_SYMBOL(fw_core_remove_descriptor); @@ -221,11 +215,15 @@ static int reset_bus(struct fw_card *card, bool short_reset) int reg = short_reset ? 5 : 1; int bit = short_reset ? PHY_BUS_SHORT_RESET : PHY_BUS_RESET; + trace_bus_reset_initiate(card->index, card->generation, short_reset); + return card->driver->update_phy_reg(card, reg, 0, bit); } void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset) { + trace_bus_reset_schedule(card->index, card->generation, short_reset); + /* We don't try hard to sort out requests of long vs. short resets. */ card->br_short = short_reset; @@ -244,6 +242,8 @@ static void br_work(struct work_struct *work) /* Delay for 2s after last reset per IEEE 1394 clause 8.2.1. */ if (card->reset_jiffies != 0 && time_before64(get_jiffies_64(), card->reset_jiffies + 2 * HZ)) { + trace_bus_reset_postpone(card->index, card->generation, card->br_short); + if (!queue_delayed_work(fw_workqueue, &card->br_work, 2 * HZ)) fw_card_put(card); return; @@ -374,11 +374,11 @@ static void bm_work(struct work_struct *work) bm_id = be32_to_cpu(transaction_data[0]); - spin_lock_irq(&card->lock); - if (rcode == RCODE_COMPLETE && generation == card->generation) - card->bm_node_id = - bm_id == 0x3f ? local_id : 0xffc0 | bm_id; - spin_unlock_irq(&card->lock); + scoped_guard(spinlock_irq, &card->lock) { + if (rcode == RCODE_COMPLETE && generation == card->generation) + card->bm_node_id = + bm_id == 0x3f ? local_id : 0xffc0 | bm_id; + } if (rcode == RCODE_COMPLETE && bm_id != 0x3f) { /* Somebody else is BM. Only act as IRM. */ @@ -429,7 +429,23 @@ static void bm_work(struct work_struct *work) */ card->bm_generation = generation; - if (root_device == NULL) { + if (card->gap_count == 0) { + /* + * If self IDs have inconsistent gap counts, do a + * bus reset ASAP. The config rom read might never + * complete, so don't wait for it. However, still + * send a PHY configuration packet prior to the + * bus reset. The PHY configuration packet might + * fail, but 1394-2008 8.4.5.2 explicitly permits + * it in this case, so it should be safe to try. + */ + new_root_id = local_id; + /* + * We must always send a bus reset if the gap count + * is inconsistent, so bypass the 5-reset limit. + */ + card->bm_retries = 0; + } else if (root_device == NULL) { /* * Either link_on is false, or we failed to read the * config rom. In either case, pick another root. @@ -484,7 +500,19 @@ static void bm_work(struct work_struct *work) fw_notice(card, "phy config: new root=%x, gap_count=%d\n", new_root_id, gap_count); fw_send_phy_config(card, new_root_id, generation, gap_count); - reset_bus(card, true); + /* + * Where possible, use a short bus reset to minimize + * disruption to isochronous transfers. But in the event + * of a gap count inconsistency, use a long bus reset. + * + * As noted in 1394a 8.4.6.2, nodes on a mixed 1394/1394a bus + * may set different gap counts after a bus reset. On a mixed + * 1394/1394a bus, a short bus reset can get doubled. Some + * nodes may treat the double reset as one bus reset and others + * may treat it as two, causing a gap count inconsistency + * again. Using a long bus reset prevents this. + */ + reset_bus(card, card->gap_count != 0); /* Will allocate broadcast channel after the reset. */ goto out; } @@ -543,25 +571,47 @@ void fw_card_initialize(struct fw_card *card, } EXPORT_SYMBOL(fw_card_initialize); -int fw_card_add(struct fw_card *card, - u32 max_receive, u32 link_speed, u64 guid) +int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid, + unsigned int supported_isoc_contexts) { + struct workqueue_struct *isoc_wq; int ret; + // This workqueue should be: + // * != WQ_BH Sleepable. + // * == WQ_UNBOUND Any core can process data for isoc context. The + // implementation of unit protocol could consumes the core + // longer somehow. + // * != WQ_MEM_RECLAIM Not used for any backend of block device. + // * == WQ_FREEZABLE Isochronous communication is at regular interval in real + // time, thus should be drained if possible at freeze phase. + // * == WQ_HIGHPRI High priority to process semi-realtime timestamped data. + // * == WQ_SYSFS Parameters are available via sysfs. + // * max_active == n_it + n_ir A hardIRQ could notify events for multiple isochronous + // contexts if they are scheduled to the same cycle. + isoc_wq = alloc_workqueue("firewire-isoc-card%u", + WQ_UNBOUND | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS, + supported_isoc_contexts, card->index); + if (!isoc_wq) + return -ENOMEM; + card->max_receive = max_receive; card->link_speed = link_speed; card->guid = guid; - mutex_lock(&card_mutex); + guard(mutex)(&card_mutex); generate_config_rom(card, tmp_config_rom); ret = card->driver->enable(card, tmp_config_rom, config_rom_length); - if (ret == 0) - list_add_tail(&card->link, &card_list); + if (ret < 0) { + destroy_workqueue(isoc_wq); + return ret; + } - mutex_unlock(&card_mutex); + card->isoc_wq = isoc_wq; + list_add_tail(&card->link, &card_list); - return ret; + return 0; } EXPORT_SYMBOL(fw_card_add); @@ -679,29 +729,31 @@ EXPORT_SYMBOL_GPL(fw_card_release); void fw_core_remove_card(struct fw_card *card) { struct fw_card_driver dummy_driver = dummy_driver_template; - unsigned long flags; + + might_sleep(); card->driver->update_phy_reg(card, 4, PHY_LINK_ACTIVE | PHY_CONTENDER, 0); fw_schedule_bus_reset(card, false, true); - mutex_lock(&card_mutex); - list_del_init(&card->link); - mutex_unlock(&card_mutex); + scoped_guard(mutex, &card_mutex) + list_del_init(&card->link); /* Switch off most of the card driver interface. */ dummy_driver.free_iso_context = card->driver->free_iso_context; dummy_driver.stop_iso = card->driver->stop_iso; card->driver = &dummy_driver; + drain_workqueue(card->isoc_wq); - spin_lock_irqsave(&card->lock, flags); - fw_destroy_nodes(card); - spin_unlock_irqrestore(&card->lock, flags); + scoped_guard(spinlock_irqsave, &card->lock) + fw_destroy_nodes(card); /* Wait for all users, especially device workqueue jobs, to finish. */ fw_card_put(card); wait_for_completion(&card->done); + destroy_workqueue(card->isoc_wq); + WARN_ON(!list_empty(&card->transaction_list)); } EXPORT_SYMBOL(fw_core_remove_card); |