firewire: Implement proper transaction cancelation.

Drivers such as fw-sbp2 had no way to properly cancel in-progress
transactions, which could leave a pending transaction or an unset
packet in the low-level queues after kfree'ing the containing
structure. fw_cancel_transaction() lets drivers cancel a submitted
transaction.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
diff --git a/drivers/firewire/fw-ohci.c b/drivers/firewire/fw-ohci.c
index 02b2b69..e6fa349 100644
--- a/drivers/firewire/fw-ohci.c
+++ b/drivers/firewire/fw-ohci.c
@@ -79,6 +79,7 @@
 	struct fw_ohci *ohci;
 	dma_addr_t descriptor_bus;
 	dma_addr_t buffer_bus;
+	struct fw_packet *current_packet;
 
 	struct list_head list;
 
@@ -489,6 +490,7 @@
 			  ctx->descriptor_bus | z);
 		reg_write(ctx->ohci, control_set(ctx->regs),
 			  CONTEXT_RUN | CONTEXT_WAKE);
+		ctx->current_packet = packet;
 	} else {
 		/* We dont return error codes from this function; all
 		 * transmission errors are reported through the
@@ -524,6 +526,12 @@
 
 	at_context_stop(ctx);
 
+	/* If the head of the list isn't the packet that just got
+	 * transmitted, the packet got cancelled before we finished
+	 * transmitting it. */
+	if (ctx->current_packet != packet)
+		goto skip_to_next;
+
 	if (packet->payload_length > 0) {
 		dma_unmap_single(ohci->card.device, packet->payload_bus,
 				 packet->payload_length, DMA_TO_DEVICE);
@@ -564,6 +572,7 @@
 	} else
 		complete_transmission(packet, evt - 16, &list);
 
+ skip_to_next:
 	/* If more packets are queued, set up the next one. */
 	if (!list_empty(&ctx->list))
 		at_context_setup_packet(ctx, &list);
@@ -1012,6 +1021,29 @@
 	at_context_transmit(&ohci->at_response_ctx, packet);
 }
 
+static int ohci_cancel_packet(struct fw_card *card, struct fw_packet *packet)
+{
+	struct fw_ohci *ohci = fw_ohci(card);
+	LIST_HEAD(list);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ohci->lock, flags);
+
+	if (packet->ack == 0) {
+		fw_notify("cancelling packet %p (header[0]=%08x)\n",
+			  packet, packet->header[0]);
+
+		complete_transmission(packet, RCODE_CANCELLED, &list);
+	}
+
+	spin_unlock_irqrestore(&ohci->lock, flags);
+
+	do_packet_callbacks(ohci, &list);
+
+	/* Return success if we actually cancelled something. */
+	return list_empty(&list) ? -ENOENT : 0;
+}
+
 static int
 ohci_enable_phys_dma(struct fw_card *card, int node_id, int generation)
 {
@@ -1339,6 +1371,7 @@
 	.set_config_rom		= ohci_set_config_rom,
 	.send_request		= ohci_send_request,
 	.send_response		= ohci_send_response,
+	.cancel_packet		= ohci_cancel_packet,
 	.enable_phys_dma	= ohci_enable_phys_dma,
 
 	.allocate_iso_context	= ohci_allocate_iso_context,