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>
This commit is contained in:
parent
72e318e07e
commit
730c32f58b
@ -474,6 +474,12 @@ dummy_send_response(struct fw_card *card, struct fw_packet *packet)
|
|||||||
packet->callback(packet, card, -ENODEV);
|
packet->callback(packet, card, -ENODEV);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dummy_cancel_packet(struct fw_card *card, struct fw_packet *packet)
|
||||||
|
{
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dummy_enable_phys_dma(struct fw_card *card,
|
dummy_enable_phys_dma(struct fw_card *card,
|
||||||
int node_id, int generation)
|
int node_id, int generation)
|
||||||
@ -487,6 +493,7 @@ static struct fw_card_driver dummy_driver = {
|
|||||||
.update_phy_reg = dummy_update_phy_reg,
|
.update_phy_reg = dummy_update_phy_reg,
|
||||||
.set_config_rom = dummy_set_config_rom,
|
.set_config_rom = dummy_set_config_rom,
|
||||||
.send_request = dummy_send_request,
|
.send_request = dummy_send_request,
|
||||||
|
.cancel_packet = dummy_cancel_packet,
|
||||||
.send_response = dummy_send_response,
|
.send_response = dummy_send_response,
|
||||||
.enable_phys_dma = dummy_enable_phys_dma,
|
.enable_phys_dma = dummy_enable_phys_dma,
|
||||||
};
|
};
|
||||||
|
@ -79,6 +79,7 @@ struct at_context {
|
|||||||
struct fw_ohci *ohci;
|
struct fw_ohci *ohci;
|
||||||
dma_addr_t descriptor_bus;
|
dma_addr_t descriptor_bus;
|
||||||
dma_addr_t buffer_bus;
|
dma_addr_t buffer_bus;
|
||||||
|
struct fw_packet *current_packet;
|
||||||
|
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
|
|
||||||
@ -489,6 +490,7 @@ at_context_setup_packet(struct at_context *ctx, struct list_head *list)
|
|||||||
ctx->descriptor_bus | z);
|
ctx->descriptor_bus | z);
|
||||||
reg_write(ctx->ohci, control_set(ctx->regs),
|
reg_write(ctx->ohci, control_set(ctx->regs),
|
||||||
CONTEXT_RUN | CONTEXT_WAKE);
|
CONTEXT_RUN | CONTEXT_WAKE);
|
||||||
|
ctx->current_packet = packet;
|
||||||
} else {
|
} else {
|
||||||
/* We dont return error codes from this function; all
|
/* We dont return error codes from this function; all
|
||||||
* transmission errors are reported through the
|
* transmission errors are reported through the
|
||||||
@ -524,6 +526,12 @@ static void at_context_tasklet(unsigned long data)
|
|||||||
|
|
||||||
at_context_stop(ctx);
|
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) {
|
if (packet->payload_length > 0) {
|
||||||
dma_unmap_single(ohci->card.device, packet->payload_bus,
|
dma_unmap_single(ohci->card.device, packet->payload_bus,
|
||||||
packet->payload_length, DMA_TO_DEVICE);
|
packet->payload_length, DMA_TO_DEVICE);
|
||||||
@ -564,6 +572,7 @@ static void at_context_tasklet(unsigned long data)
|
|||||||
} else
|
} else
|
||||||
complete_transmission(packet, evt - 16, &list);
|
complete_transmission(packet, evt - 16, &list);
|
||||||
|
|
||||||
|
skip_to_next:
|
||||||
/* If more packets are queued, set up the next one. */
|
/* If more packets are queued, set up the next one. */
|
||||||
if (!list_empty(&ctx->list))
|
if (!list_empty(&ctx->list))
|
||||||
at_context_setup_packet(ctx, &list);
|
at_context_setup_packet(ctx, &list);
|
||||||
@ -1012,6 +1021,29 @@ static void ohci_send_response(struct fw_card *card, struct fw_packet *packet)
|
|||||||
at_context_transmit(&ohci->at_response_ctx, packet);
|
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
|
static int
|
||||||
ohci_enable_phys_dma(struct fw_card *card, int node_id, int generation)
|
ohci_enable_phys_dma(struct fw_card *card, int node_id, int generation)
|
||||||
{
|
{
|
||||||
@ -1339,6 +1371,7 @@ static const struct fw_card_driver ohci_driver = {
|
|||||||
.set_config_rom = ohci_set_config_rom,
|
.set_config_rom = ohci_set_config_rom,
|
||||||
.send_request = ohci_send_request,
|
.send_request = ohci_send_request,
|
||||||
.send_response = ohci_send_response,
|
.send_response = ohci_send_response,
|
||||||
|
.cancel_packet = ohci_cancel_packet,
|
||||||
.enable_phys_dma = ohci_enable_phys_dma,
|
.enable_phys_dma = ohci_enable_phys_dma,
|
||||||
|
|
||||||
.allocate_iso_context = ohci_allocate_iso_context,
|
.allocate_iso_context = ohci_allocate_iso_context,
|
||||||
|
@ -348,6 +348,9 @@ static void sbp2_cancel_orbs(struct fw_unit *unit)
|
|||||||
spin_unlock_irqrestore(&device->card->lock, flags);
|
spin_unlock_irqrestore(&device->card->lock, flags);
|
||||||
|
|
||||||
list_for_each_entry_safe(orb, next, &list, link) {
|
list_for_each_entry_safe(orb, next, &list, link) {
|
||||||
|
if (fw_cancel_transaction(device->card, &orb->t) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
orb->rcode = RCODE_CANCELLED;
|
orb->rcode = RCODE_CANCELLED;
|
||||||
orb->callback(orb, NULL);
|
orb->callback(orb, NULL);
|
||||||
}
|
}
|
||||||
|
@ -59,20 +59,52 @@
|
|||||||
#define phy_config_root_id(node_id) ((((node_id) & 0x3f) << 24) | (1 << 23))
|
#define phy_config_root_id(node_id) ((((node_id) & 0x3f) << 24) | (1 << 23))
|
||||||
#define phy_identifier(id) ((id) << 30)
|
#define phy_identifier(id) ((id) << 30)
|
||||||
|
|
||||||
static void
|
static int
|
||||||
close_transaction(struct fw_transaction *t, struct fw_card *card, int rcode,
|
close_transaction(struct fw_transaction *transaction,
|
||||||
u32 * payload, size_t length)
|
struct fw_card *card, int rcode,
|
||||||
|
u32 *payload, size_t length)
|
||||||
{
|
{
|
||||||
|
struct fw_transaction *t;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
spin_lock_irqsave(&card->lock, flags);
|
spin_lock_irqsave(&card->lock, flags);
|
||||||
card->tlabel_mask &= ~(1 << t->tlabel);
|
list_for_each_entry(t, &card->transaction_list, link) {
|
||||||
list_del(&t->link);
|
if (t == transaction) {
|
||||||
|
list_del(&t->link);
|
||||||
|
card->tlabel_mask &= ~(1 << t->tlabel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
spin_unlock_irqrestore(&card->lock, flags);
|
spin_unlock_irqrestore(&card->lock, flags);
|
||||||
|
|
||||||
t->callback(card, rcode, payload, length, t->callback_data);
|
if (&t->link != &card->transaction_list) {
|
||||||
|
t->callback(card, rcode, payload, length, t->callback_data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Only valid for transactions that are potentially pending (ie have
|
||||||
|
* been sent). */
|
||||||
|
int
|
||||||
|
fw_cancel_transaction(struct fw_card *card,
|
||||||
|
struct fw_transaction *transaction)
|
||||||
|
{
|
||||||
|
/* Cancel the packet transmission if it's still queued. That
|
||||||
|
* will call the packet transmission callback which cancels
|
||||||
|
* the transaction. */
|
||||||
|
|
||||||
|
if (card->driver->cancel_packet(card, &transaction->packet) == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* If the request packet has already been sent, we need to see
|
||||||
|
* if the transaction is still pending and remove it in that case. */
|
||||||
|
|
||||||
|
return close_transaction(transaction, card, RCODE_CANCELLED, NULL, 0);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(fw_cancel_transaction);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
transmit_complete_callback(struct fw_packet *packet,
|
transmit_complete_callback(struct fw_packet *packet,
|
||||||
struct fw_card *card, int status)
|
struct fw_card *card, int status)
|
||||||
@ -162,6 +194,7 @@ fw_fill_request(struct fw_packet *packet, int tcode, int tlabel,
|
|||||||
|
|
||||||
packet->speed = speed;
|
packet->speed = speed;
|
||||||
packet->generation = generation;
|
packet->generation = generation;
|
||||||
|
packet->ack = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,8 +331,14 @@ void fw_flush_transactions(struct fw_card *card)
|
|||||||
card->tlabel_mask = 0;
|
card->tlabel_mask = 0;
|
||||||
spin_unlock_irqrestore(&card->lock, flags);
|
spin_unlock_irqrestore(&card->lock, flags);
|
||||||
|
|
||||||
list_for_each_entry_safe(t, next, &list, link)
|
list_for_each_entry_safe(t, next, &list, link) {
|
||||||
|
card->driver->cancel_packet(card, &t->packet);
|
||||||
|
|
||||||
|
/* At this point cancel_packet will never call the
|
||||||
|
* transaction callback, since we just took all the
|
||||||
|
* transactions out of the list. So do it here.*/
|
||||||
t->callback(card, RCODE_CANCELLED, NULL, 0, t->callback_data);
|
t->callback(card, RCODE_CANCELLED, NULL, 0, t->callback_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct fw_address_handler *
|
static struct fw_address_handler *
|
||||||
@ -531,6 +570,7 @@ allocate_request(struct fw_packet *p)
|
|||||||
request->response.speed = p->speed;
|
request->response.speed = p->speed;
|
||||||
request->response.timestamp = t;
|
request->response.timestamp = t;
|
||||||
request->response.generation = p->generation;
|
request->response.generation = p->generation;
|
||||||
|
request->response.ack = 0;
|
||||||
request->response.callback = free_response_callback;
|
request->response.callback = free_response_callback;
|
||||||
request->ack = p->ack;
|
request->ack = p->ack;
|
||||||
request->length = length;
|
request->length = length;
|
||||||
|
@ -391,6 +391,8 @@ struct fw_card_driver {
|
|||||||
|
|
||||||
void (*send_request) (struct fw_card *card, struct fw_packet *packet);
|
void (*send_request) (struct fw_card *card, struct fw_packet *packet);
|
||||||
void (*send_response) (struct fw_card *card, struct fw_packet *packet);
|
void (*send_response) (struct fw_card *card, struct fw_packet *packet);
|
||||||
|
/* Calling cancel is valid once a packet has been submitted. */
|
||||||
|
int (*cancel_packet) (struct fw_card *card, struct fw_packet *packet);
|
||||||
|
|
||||||
/* Allow the specified node ID to do direct DMA out and in of
|
/* Allow the specified node ID to do direct DMA out and in of
|
||||||
* host memory. The card will disable this for all node when
|
* host memory. The card will disable this for all node when
|
||||||
@ -421,6 +423,9 @@ fw_send_request(struct fw_card *card, struct fw_transaction *t,
|
|||||||
void *data, size_t length,
|
void *data, size_t length,
|
||||||
fw_transaction_callback_t callback, void *callback_data);
|
fw_transaction_callback_t callback, void *callback_data);
|
||||||
|
|
||||||
|
int fw_cancel_transaction(struct fw_card *card,
|
||||||
|
struct fw_transaction *transaction);
|
||||||
|
|
||||||
void fw_flush_transactions(struct fw_card *card);
|
void fw_flush_transactions(struct fw_card *card);
|
||||||
|
|
||||||
void fw_send_phy_config(struct fw_card *card,
|
void fw_send_phy_config(struct fw_card *card,
|
||||||
|
Loading…
Reference in New Issue
Block a user