Skip to content

Commit db5d247

Browse files
Clemens LadischStefan Richter
authored andcommitted
firewire: fix use of multiple AV/C devices, allow multiple FCP listeners
Control of more than one AV/C device at once --- e.g. camcorders, tape decks, audio devices, TV tuners --- failed or worked only unreliably, depending on driver implementation. This affected kernelspace and userspace drivers alike and was caused by firewire-core's inability to accept multiple registrations of FCP listeners. The fix allows multiple address handlers to be registered for the FCP command and response registers. When a request for these registers is received, all handlers are invoked, and the Firewire response is generated by the core and not by any handler. The cdev API does not change, i.e., userspace is still expected to send a response for FCP requests; this response is silently ignored. Signed-off-by: Clemens Ladisch <[email protected]> Signed-off-by: Stefan Richter <[email protected]> (changelog, rebased, whitespace)
1 parent 6b7b284 commit db5d247

File tree

5 files changed

+119
-44
lines changed

5 files changed

+119
-44
lines changed

drivers/firewire/core-cdev.c

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -601,8 +601,9 @@ static void release_request(struct client *client,
601601
struct inbound_transaction_resource *r = container_of(resource,
602602
struct inbound_transaction_resource, resource);
603603

604-
fw_send_response(client->device->card, r->request,
605-
RCODE_CONFLICT_ERROR);
604+
if (r->request)
605+
fw_send_response(client->device->card, r->request,
606+
RCODE_CONFLICT_ERROR);
606607
kfree(r);
607608
}
608609

@@ -645,7 +646,8 @@ static void handle_request(struct fw_card *card, struct fw_request *request,
645646
failed:
646647
kfree(r);
647648
kfree(e);
648-
fw_send_response(card, request, RCODE_CONFLICT_ERROR);
649+
if (request)
650+
fw_send_response(card, request, RCODE_CONFLICT_ERROR);
649651
}
650652

651653
static void release_address_handler(struct client *client,
@@ -715,15 +717,17 @@ static int ioctl_send_response(struct client *client, void *buffer)
715717

716718
r = container_of(resource, struct inbound_transaction_resource,
717719
resource);
718-
if (request->length < r->length)
719-
r->length = request->length;
720-
721-
if (copy_from_user(r->data, u64_to_uptr(request->data), r->length)) {
722-
ret = -EFAULT;
723-
goto out;
720+
if (r->request) {
721+
if (request->length < r->length)
722+
r->length = request->length;
723+
if (copy_from_user(r->data, u64_to_uptr(request->data),
724+
r->length)) {
725+
ret = -EFAULT;
726+
goto out;
727+
}
728+
fw_send_response(client->device->card, r->request,
729+
request->rcode);
724730
}
725-
726-
fw_send_response(client->device->card, r->request, request->rcode);
727731
out:
728732
kfree(r);
729733

drivers/firewire/core-transaction.c

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -432,14 +432,20 @@ static struct fw_address_handler *lookup_overlapping_address_handler(
432432
return NULL;
433433
}
434434

435+
static bool is_enclosing_handler(struct fw_address_handler *handler,
436+
unsigned long long offset, size_t length)
437+
{
438+
return handler->offset <= offset &&
439+
offset + length <= handler->offset + handler->length;
440+
}
441+
435442
static struct fw_address_handler *lookup_enclosing_address_handler(
436443
struct list_head *list, unsigned long long offset, size_t length)
437444
{
438445
struct fw_address_handler *handler;
439446

440447
list_for_each_entry(handler, list, link) {
441-
if (handler->offset <= offset &&
442-
offset + length <= handler->offset + handler->length)
448+
if (is_enclosing_handler(handler, offset, length))
443449
return handler;
444450
}
445451

@@ -465,6 +471,12 @@ const struct fw_address_region fw_unit_space_region =
465471
{ .start = 0xfffff0000900ULL, .end = 0x1000000000000ULL, };
466472
#endif /* 0 */
467473

474+
static bool is_in_fcp_region(u64 offset, size_t length)
475+
{
476+
return offset >= (CSR_REGISTER_BASE | CSR_FCP_COMMAND) &&
477+
offset + length <= (CSR_REGISTER_BASE | CSR_FCP_END);
478+
}
479+
468480
/**
469481
* fw_core_add_address_handler - register for incoming requests
470482
* @handler: callback
@@ -477,8 +489,11 @@ const struct fw_address_region fw_unit_space_region =
477489
* give the details of the particular request.
478490
*
479491
* Return value: 0 on success, non-zero otherwise.
492+
*
480493
* The start offset of the handler's address region is determined by
481494
* fw_core_add_address_handler() and is returned in handler->offset.
495+
*
496+
* Address allocations are exclusive, except for the FCP registers.
482497
*/
483498
int fw_core_add_address_handler(struct fw_address_handler *handler,
484499
const struct fw_address_region *region)
@@ -498,10 +513,12 @@ int fw_core_add_address_handler(struct fw_address_handler *handler,
498513

499514
handler->offset = region->start;
500515
while (handler->offset + handler->length <= region->end) {
501-
other =
502-
lookup_overlapping_address_handler(&address_handler_list,
503-
handler->offset,
504-
handler->length);
516+
if (is_in_fcp_region(handler->offset, handler->length))
517+
other = NULL;
518+
else
519+
other = lookup_overlapping_address_handler
520+
(&address_handler_list,
521+
handler->offset, handler->length);
505522
if (other != NULL) {
506523
handler->offset += other->length;
507524
} else {
@@ -668,6 +685,9 @@ static struct fw_request *allocate_request(struct fw_packet *p)
668685
void fw_send_response(struct fw_card *card,
669686
struct fw_request *request, int rcode)
670687
{
688+
if (WARN_ONCE(!request, "invalid for FCP address handlers"))
689+
return;
690+
671691
/* unified transaction or broadcast transaction: don't respond */
672692
if (request->ack != ACK_PENDING ||
673693
HEADER_DESTINATION_IS_BROADCAST(request->request_header[0])) {
@@ -686,26 +706,15 @@ void fw_send_response(struct fw_card *card,
686706
}
687707
EXPORT_SYMBOL(fw_send_response);
688708

689-
void fw_core_handle_request(struct fw_card *card, struct fw_packet *p)
709+
static void handle_exclusive_region_request(struct fw_card *card,
710+
struct fw_packet *p,
711+
struct fw_request *request,
712+
unsigned long long offset)
690713
{
691714
struct fw_address_handler *handler;
692-
struct fw_request *request;
693-
unsigned long long offset;
694715
unsigned long flags;
695716
int tcode, destination, source;
696717

697-
if (p->ack != ACK_PENDING && p->ack != ACK_COMPLETE)
698-
return;
699-
700-
request = allocate_request(p);
701-
if (request == NULL) {
702-
/* FIXME: send statically allocated busy packet. */
703-
return;
704-
}
705-
706-
offset =
707-
((unsigned long long)
708-
HEADER_GET_OFFSET_HIGH(p->header[1]) << 32) | p->header[2];
709718
tcode = HEADER_GET_TCODE(p->header[0]);
710719
destination = HEADER_GET_DESTINATION(p->header[0]);
711720
source = HEADER_GET_SOURCE(p->header[1]);
@@ -732,6 +741,73 @@ void fw_core_handle_request(struct fw_card *card, struct fw_packet *p)
732741
request->data, request->length,
733742
handler->callback_data);
734743
}
744+
745+
static void handle_fcp_region_request(struct fw_card *card,
746+
struct fw_packet *p,
747+
struct fw_request *request,
748+
unsigned long long offset)
749+
{
750+
struct fw_address_handler *handler;
751+
unsigned long flags;
752+
int tcode, destination, source;
753+
754+
if ((offset != (CSR_REGISTER_BASE | CSR_FCP_COMMAND) &&
755+
offset != (CSR_REGISTER_BASE | CSR_FCP_RESPONSE)) ||
756+
request->length > 0x200) {
757+
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
758+
759+
return;
760+
}
761+
762+
tcode = HEADER_GET_TCODE(p->header[0]);
763+
destination = HEADER_GET_DESTINATION(p->header[0]);
764+
source = HEADER_GET_SOURCE(p->header[1]);
765+
766+
if (tcode != TCODE_WRITE_QUADLET_REQUEST &&
767+
tcode != TCODE_WRITE_BLOCK_REQUEST) {
768+
fw_send_response(card, request, RCODE_TYPE_ERROR);
769+
770+
return;
771+
}
772+
773+
spin_lock_irqsave(&address_handler_lock, flags);
774+
list_for_each_entry(handler, &address_handler_list, link) {
775+
if (is_enclosing_handler(handler, offset, request->length))
776+
handler->address_callback(card, NULL, tcode,
777+
destination, source,
778+
p->generation, p->speed,
779+
offset, request->data,
780+
request->length,
781+
handler->callback_data);
782+
}
783+
spin_unlock_irqrestore(&address_handler_lock, flags);
784+
785+
fw_send_response(card, request, RCODE_COMPLETE);
786+
}
787+
788+
void fw_core_handle_request(struct fw_card *card, struct fw_packet *p)
789+
{
790+
struct fw_request *request;
791+
unsigned long long offset;
792+
793+
if (p->ack != ACK_PENDING && p->ack != ACK_COMPLETE)
794+
return;
795+
796+
request = allocate_request(p);
797+
if (request == NULL) {
798+
/* FIXME: send statically allocated busy packet. */
799+
return;
800+
}
801+
802+
offset = ((u64)HEADER_GET_OFFSET_HIGH(p->header[1]) << 32) |
803+
p->header[2];
804+
805+
if (!is_in_fcp_region(offset, request->length))
806+
handle_exclusive_region_request(card, p, request, offset);
807+
else
808+
handle_fcp_region_request(card, p, request, offset);
809+
810+
}
735811
EXPORT_SYMBOL(fw_core_handle_request);
736812

737813
void fw_core_handle_response(struct fw_card *card, struct fw_packet *p)

drivers/media/dvb/firewire/firedtv-fw.c

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,8 @@ static void handle_fcp(struct fw_card *card, struct fw_request *request,
202202
unsigned long flags;
203203
int su;
204204

205-
if ((tcode != TCODE_WRITE_QUADLET_REQUEST &&
206-
tcode != TCODE_WRITE_BLOCK_REQUEST) ||
207-
offset != CSR_REGISTER_BASE + CSR_FCP_RESPONSE ||
208-
length == 0 ||
209-
(((u8 *)payload)[0] & 0xf0) != 0) {
210-
fw_send_response(card, request, RCODE_TYPE_ERROR);
205+
if (length < 2 || (((u8 *)payload)[0] & 0xf0) != 0)
211206
return;
212-
}
213207

214208
su = ((u8 *)payload)[1] & 0x7;
215209

@@ -230,10 +224,8 @@ static void handle_fcp(struct fw_card *card, struct fw_request *request,
230224
}
231225
spin_unlock_irqrestore(&node_list_lock, flags);
232226

233-
if (fdtv) {
227+
if (fdtv)
234228
avc_recv(fdtv, payload, length);
235-
fw_send_response(card, request, RCODE_COMPLETE);
236-
}
237229
}
238230

239231
static struct fw_address_handler fcp_handler = {

include/linux/firewire-cdev.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,9 @@ struct fw_cdev_send_response {
340340
* The @closure field is passed back to userspace in the response event.
341341
* The @handle field is an out parameter, returning a handle to the allocated
342342
* range to be used for later deallocation of the range.
343+
*
344+
* The address range is allocated on all local nodes. The address allocation
345+
* is exclusive except for the FCP command and response registers.
343346
*/
344347
struct fw_cdev_allocate {
345348
__u64 offset;

include/linux/firewire.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ typedef void (*fw_transaction_callback_t)(struct fw_card *card, int rcode,
248248
void *data, size_t length,
249249
void *callback_data);
250250
/*
251-
* Important note: The callback must guarantee that either fw_send_response()
252-
* or kfree() is called on the @request.
251+
* Important note: Except for the FCP registers, the callback must guarantee
252+
* that either fw_send_response() or kfree() is called on the @request.
253253
*/
254254
typedef void (*fw_address_callback_t)(struct fw_card *card,
255255
struct fw_request *request,

0 commit comments

Comments
 (0)