diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2010-07-29 12:19:22 -0400 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2010-07-29 17:09:18 -0400 |
commit | 872e330e38806d835bd6c311c93ab998e2fb9058 (patch) | |
tree | 92497ce79b1157761b1aebdb63b8d74f68d42c15 /drivers/firewire/core-cdev.c | |
parent | ae2a97661482c1d0f1aa41b837da95054d0e9a1b (diff) |
firewire: add isochronous multichannel reception
This adds the DMA context programming and userspace ABI for multichannel
reception, i.e. for listening on multiple channel numbers by means of a
single DMA context.
The use case is reception of more streams than there are IR DMA units
offered by the link layer. This is already implemented by the older
ohci1394 + ieee1394 + raw1394 stack. And as discussed recently on
linux1394-devel, this feature is occasionally used in practice.
The big drawbacks of this mode are that buffer layout and interrupt
generation necessarily differ from single-channel reception: Headers
and trailers are not stripped from packets, packets are not aligned with
buffer chunks, interrupts are per buffer chunk, not per packet.
These drawbacks also cause a rather hefty code footprint to support this
rarely used OHCI-1394 feature. (367 lines added, among them 94 lines of
added userspace ABI documentation.)
This implementation enforces that a multichannel reception context may
only listen to channels to which no single-channel context on the same
link layer is presently listening to. OHCI-1394 would allow to overlay
single-channel contexts by the multi-channel context, but this would be
a departure from the present first-come-first-served policy of IR
context creation.
The implementation is heavily based on an earlier one by Jay Fenlason.
Thanks Jay.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire/core-cdev.c')
-rw-r--r-- | drivers/firewire/core-cdev.c | 93 |
1 files changed, 72 insertions, 21 deletions
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index cf989e1635e1..ba23646bb108 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c | |||
@@ -193,6 +193,11 @@ struct iso_interrupt_event { | |||
193 | struct fw_cdev_event_iso_interrupt interrupt; | 193 | struct fw_cdev_event_iso_interrupt interrupt; |
194 | }; | 194 | }; |
195 | 195 | ||
196 | struct iso_interrupt_mc_event { | ||
197 | struct event event; | ||
198 | struct fw_cdev_event_iso_interrupt_mc interrupt; | ||
199 | }; | ||
200 | |||
196 | struct iso_resource_event { | 201 | struct iso_resource_event { |
197 | struct event event; | 202 | struct event event; |
198 | struct fw_cdev_event_iso_resource iso_resource; | 203 | struct fw_cdev_event_iso_resource iso_resource; |
@@ -415,6 +420,7 @@ union ioctl_arg { | |||
415 | struct fw_cdev_get_cycle_timer2 get_cycle_timer2; | 420 | struct fw_cdev_get_cycle_timer2 get_cycle_timer2; |
416 | struct fw_cdev_send_phy_packet send_phy_packet; | 421 | struct fw_cdev_send_phy_packet send_phy_packet; |
417 | struct fw_cdev_receive_phy_packets receive_phy_packets; | 422 | struct fw_cdev_receive_phy_packets receive_phy_packets; |
423 | struct fw_cdev_set_iso_channels set_iso_channels; | ||
418 | }; | 424 | }; |
419 | 425 | ||
420 | static int ioctl_get_info(struct client *client, union ioctl_arg *arg) | 426 | static int ioctl_get_info(struct client *client, union ioctl_arg *arg) |
@@ -932,26 +938,54 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle, | |||
932 | sizeof(e->interrupt) + header_length, NULL, 0); | 938 | sizeof(e->interrupt) + header_length, NULL, 0); |
933 | } | 939 | } |
934 | 940 | ||
941 | static void iso_mc_callback(struct fw_iso_context *context, | ||
942 | dma_addr_t completed, void *data) | ||
943 | { | ||
944 | struct client *client = data; | ||
945 | struct iso_interrupt_mc_event *e; | ||
946 | |||
947 | e = kmalloc(sizeof(*e), GFP_ATOMIC); | ||
948 | if (e == NULL) { | ||
949 | fw_notify("Out of memory when allocating event\n"); | ||
950 | return; | ||
951 | } | ||
952 | e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL; | ||
953 | e->interrupt.closure = client->iso_closure; | ||
954 | e->interrupt.completed = fw_iso_buffer_lookup(&client->buffer, | ||
955 | completed); | ||
956 | queue_event(client, &e->event, &e->interrupt, | ||
957 | sizeof(e->interrupt), NULL, 0); | ||
958 | } | ||
959 | |||
935 | static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) | 960 | static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) |
936 | { | 961 | { |
937 | struct fw_cdev_create_iso_context *a = &arg->create_iso_context; | 962 | struct fw_cdev_create_iso_context *a = &arg->create_iso_context; |
938 | struct fw_iso_context *context; | 963 | struct fw_iso_context *context; |
964 | fw_iso_callback_t cb; | ||
939 | 965 | ||
940 | BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT || | 966 | BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT || |
941 | FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE); | 967 | FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE || |
942 | 968 | FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL != | |
943 | if (a->channel > 63) | 969 | FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL); |
944 | return -EINVAL; | ||
945 | 970 | ||
946 | switch (a->type) { | 971 | switch (a->type) { |
947 | case FW_ISO_CONTEXT_RECEIVE: | 972 | case FW_ISO_CONTEXT_TRANSMIT: |
948 | if (a->header_size < 4 || (a->header_size & 3)) | 973 | if (a->speed > SCODE_3200 || a->channel > 63) |
949 | return -EINVAL; | 974 | return -EINVAL; |
975 | |||
976 | cb = iso_callback; | ||
950 | break; | 977 | break; |
951 | 978 | ||
952 | case FW_ISO_CONTEXT_TRANSMIT: | 979 | case FW_ISO_CONTEXT_RECEIVE: |
953 | if (a->speed > SCODE_3200) | 980 | if (a->header_size < 4 || (a->header_size & 3) || |
981 | a->channel > 63) | ||
954 | return -EINVAL; | 982 | return -EINVAL; |
983 | |||
984 | cb = iso_callback; | ||
985 | break; | ||
986 | |||
987 | case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL: | ||
988 | cb = (fw_iso_callback_t)iso_mc_callback; | ||
955 | break; | 989 | break; |
956 | 990 | ||
957 | default: | 991 | default: |
@@ -959,8 +993,7 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) | |||
959 | } | 993 | } |
960 | 994 | ||
961 | context = fw_iso_context_create(client->device->card, a->type, | 995 | context = fw_iso_context_create(client->device->card, a->type, |
962 | a->channel, a->speed, a->header_size, | 996 | a->channel, a->speed, a->header_size, cb, client); |
963 | iso_callback, client); | ||
964 | if (IS_ERR(context)) | 997 | if (IS_ERR(context)) |
965 | return PTR_ERR(context); | 998 | return PTR_ERR(context); |
966 | 999 | ||
@@ -980,6 +1013,17 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) | |||
980 | return 0; | 1013 | return 0; |
981 | } | 1014 | } |
982 | 1015 | ||
1016 | static int ioctl_set_iso_channels(struct client *client, union ioctl_arg *arg) | ||
1017 | { | ||
1018 | struct fw_cdev_set_iso_channels *a = &arg->set_iso_channels; | ||
1019 | struct fw_iso_context *ctx = client->iso_context; | ||
1020 | |||
1021 | if (ctx == NULL || a->handle != 0) | ||
1022 | return -EINVAL; | ||
1023 | |||
1024 | return fw_iso_context_set_channels(ctx, &a->channels); | ||
1025 | } | ||
1026 | |||
983 | /* Macros for decoding the iso packet control header. */ | 1027 | /* Macros for decoding the iso packet control header. */ |
984 | #define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff) | 1028 | #define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff) |
985 | #define GET_INTERRUPT(v) (((v) >> 16) & 0x01) | 1029 | #define GET_INTERRUPT(v) (((v) >> 16) & 0x01) |
@@ -993,7 +1037,7 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) | |||
993 | struct fw_cdev_queue_iso *a = &arg->queue_iso; | 1037 | struct fw_cdev_queue_iso *a = &arg->queue_iso; |
994 | struct fw_cdev_iso_packet __user *p, *end, *next; | 1038 | struct fw_cdev_iso_packet __user *p, *end, *next; |
995 | struct fw_iso_context *ctx = client->iso_context; | 1039 | struct fw_iso_context *ctx = client->iso_context; |
996 | unsigned long payload, buffer_end, transmit_header_bytes; | 1040 | unsigned long payload, buffer_end, transmit_header_bytes = 0; |
997 | u32 control; | 1041 | u32 control; |
998 | int count; | 1042 | int count; |
999 | struct { | 1043 | struct { |
@@ -1013,7 +1057,6 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) | |||
1013 | * use the indirect payload, the iso buffer need not be mapped | 1057 | * use the indirect payload, the iso buffer need not be mapped |
1014 | * and the a->data pointer is ignored. | 1058 | * and the a->data pointer is ignored. |
1015 | */ | 1059 | */ |
1016 | |||
1017 | payload = (unsigned long)a->data - client->vm_start; | 1060 | payload = (unsigned long)a->data - client->vm_start; |
1018 | buffer_end = client->buffer.page_count << PAGE_SHIFT; | 1061 | buffer_end = client->buffer.page_count << PAGE_SHIFT; |
1019 | if (a->data == 0 || client->buffer.pages == NULL || | 1062 | if (a->data == 0 || client->buffer.pages == NULL || |
@@ -1022,8 +1065,10 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) | |||
1022 | buffer_end = 0; | 1065 | buffer_end = 0; |
1023 | } | 1066 | } |
1024 | 1067 | ||
1025 | p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets); | 1068 | if (ctx->type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL && payload & 3) |
1069 | return -EINVAL; | ||
1026 | 1070 | ||
1071 | p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets); | ||
1027 | if (!access_ok(VERIFY_READ, p, a->size)) | 1072 | if (!access_ok(VERIFY_READ, p, a->size)) |
1028 | return -EFAULT; | 1073 | return -EFAULT; |
1029 | 1074 | ||
@@ -1039,19 +1084,24 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) | |||
1039 | u.packet.sy = GET_SY(control); | 1084 | u.packet.sy = GET_SY(control); |
1040 | u.packet.header_length = GET_HEADER_LENGTH(control); | 1085 | u.packet.header_length = GET_HEADER_LENGTH(control); |
1041 | 1086 | ||
1042 | if (ctx->type == FW_ISO_CONTEXT_TRANSMIT) { | 1087 | switch (ctx->type) { |
1043 | if (u.packet.header_length % 4 != 0) | 1088 | case FW_ISO_CONTEXT_TRANSMIT: |
1089 | if (u.packet.header_length & 3) | ||
1044 | return -EINVAL; | 1090 | return -EINVAL; |
1045 | transmit_header_bytes = u.packet.header_length; | 1091 | transmit_header_bytes = u.packet.header_length; |
1046 | } else { | 1092 | break; |
1047 | /* | 1093 | |
1048 | * We require that header_length is a multiple of | 1094 | case FW_ISO_CONTEXT_RECEIVE: |
1049 | * the fixed header size, ctx->header_size. | ||
1050 | */ | ||
1051 | if (u.packet.header_length == 0 || | 1095 | if (u.packet.header_length == 0 || |
1052 | u.packet.header_length % ctx->header_size != 0) | 1096 | u.packet.header_length % ctx->header_size != 0) |
1053 | return -EINVAL; | 1097 | return -EINVAL; |
1054 | transmit_header_bytes = 0; | 1098 | break; |
1099 | |||
1100 | case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL: | ||
1101 | if (u.packet.payload_length == 0 || | ||
1102 | u.packet.payload_length & 3) | ||
1103 | return -EINVAL; | ||
1104 | break; | ||
1055 | } | 1105 | } |
1056 | 1106 | ||
1057 | next = (struct fw_cdev_iso_packet __user *) | 1107 | next = (struct fw_cdev_iso_packet __user *) |
@@ -1534,6 +1584,7 @@ static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = { | |||
1534 | [0x14] = ioctl_get_cycle_timer2, | 1584 | [0x14] = ioctl_get_cycle_timer2, |
1535 | [0x15] = ioctl_send_phy_packet, | 1585 | [0x15] = ioctl_send_phy_packet, |
1536 | [0x16] = ioctl_receive_phy_packets, | 1586 | [0x16] = ioctl_receive_phy_packets, |
1587 | [0x17] = ioctl_set_iso_channels, | ||
1537 | }; | 1588 | }; |
1538 | 1589 | ||
1539 | static int dispatch_ioctl(struct client *client, | 1590 | static int dispatch_ioctl(struct client *client, |