summaryrefslogtreecommitdiffstats
path: root/drivers/firewire
diff options
context:
space:
mode:
authorStefan Richter <stefanr@s5r6.in-berlin.de>2010-07-16 22:25:14 +0200
committerStefan Richter <stefanr@s5r6.in-berlin.de>2010-07-23 13:36:28 +0200
commit850bb6f23b93c04ce1e4509a87fa607dc17d97c1 (patch)
tree4ec66cea35e15ff095799bae4aec7c1071d4faa4 /drivers/firewire
parentfirewire: core: use C99 initializer in array of ioctl handlers (diff)
downloadlinux-850bb6f23b93c04ce1e4509a87fa607dc17d97c1.tar.xz
linux-850bb6f23b93c04ce1e4509a87fa607dc17d97c1.zip
firewire: cdev: add PHY packet transmission
Add an FW_CDEV_IOC_SEND_PHY_PACKET ioctl() for /dev/fw* which can be used to implement bus management related functionality in userspace. This is also half of the functionality (the transmit part) that is needed to support a userspace implementation of a VersaPHY transaction layer. Safety considerations: - PHY packets are generally broadcasts and may have interesting effects on PHYs and the bus, e.g. make asynchronous arbitration impossible due to too low gap count. Hence some kind of elevated privileges should be required of a process to be able to send PHY packets. This implementation assumes that a process that is allowed to open the /dev/fw* of a local node does have this privilege. There was an inconclusive discussion about introducing POSIX capabilities as a means to check for user privileges for these kinds of operations. - The kernel does not check integrity of the supplied packet data. That would be far too much code, considering the many kinds of PHY packets. A process which got the privilege to send these packets is trusted to do it correctly. Just like with the other "send packet" ioctls, a non-blocking API is chosen; i.e. the ioctl may return even before AT DMA started. After transmission, an event for poll()/read() is enqueued. Most users are going to need a blocking API, but a blocking userspace wrapper is easy to implement, and the second of the two existing libraw1394 calls raw1394_phy_packet_write() and raw1394_start_phy_packet_write() can be better supported that way. Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire')
-rw-r--r--drivers/firewire/core-cdev.c64
1 files changed, 64 insertions, 0 deletions
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c
index acf4fa1f3f8c..f95719926487 100644
--- a/drivers/firewire/core-cdev.c
+++ b/drivers/firewire/core-cdev.c
@@ -194,6 +194,13 @@ struct iso_resource_event {
struct fw_cdev_event_iso_resource iso_resource;
};
+struct outbound_phy_packet_event {
+ struct event event;
+ struct client *client;
+ struct fw_packet p;
+ struct fw_cdev_event_phy_packet phy_packet;
+};
+
static inline void __user *u64_to_uptr(__u64 value)
{
return (void __user *)(unsigned long)value;
@@ -396,6 +403,7 @@ union ioctl_arg {
struct fw_cdev_allocate_iso_resource allocate_iso_resource;
struct fw_cdev_send_stream_packet send_stream_packet;
struct fw_cdev_get_cycle_timer2 get_cycle_timer2;
+ struct fw_cdev_send_phy_packet send_phy_packet;
};
static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
@@ -1384,6 +1392,61 @@ static int ioctl_send_stream_packet(struct client *client, union ioctl_arg *arg)
return init_request(client, &request, dest, a->speed);
}
+static void outbound_phy_packet_callback(struct fw_packet *packet,
+ struct fw_card *card, int status)
+{
+ struct outbound_phy_packet_event *e =
+ container_of(packet, struct outbound_phy_packet_event, p);
+
+ switch (status) {
+ /* expected: */
+ case ACK_COMPLETE: e->phy_packet.rcode = RCODE_COMPLETE; break;
+ /* should never happen with PHY packets: */
+ case ACK_PENDING: e->phy_packet.rcode = RCODE_COMPLETE; break;
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B: e->phy_packet.rcode = RCODE_BUSY; break;
+ case ACK_DATA_ERROR: e->phy_packet.rcode = RCODE_DATA_ERROR; break;
+ case ACK_TYPE_ERROR: e->phy_packet.rcode = RCODE_TYPE_ERROR; break;
+ /* stale generation; cancelled; on certain controllers: no ack */
+ default: e->phy_packet.rcode = status; break;
+ }
+
+ queue_event(e->client, &e->event,
+ &e->phy_packet, sizeof(e->phy_packet), NULL, 0);
+ client_put(e->client);
+}
+
+static int ioctl_send_phy_packet(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_send_phy_packet *a = &arg->send_phy_packet;
+ struct fw_card *card = client->device->card;
+ struct outbound_phy_packet_event *e;
+
+ /* Access policy: Allow this ioctl only on local nodes' device files. */
+ if (!client->device->is_local)
+ return -ENOSYS;
+
+ e = kzalloc(sizeof(*e), GFP_KERNEL);
+ if (e == NULL)
+ return -ENOMEM;
+
+ client_get(client);
+ e->client = client;
+ e->p.speed = SCODE_100;
+ e->p.generation = a->generation;
+ e->p.header[0] = a->data[0];
+ e->p.header[1] = a->data[1];
+ e->p.header_length = 8;
+ e->p.callback = outbound_phy_packet_callback;
+ e->phy_packet.closure = a->closure;
+ e->phy_packet.type = FW_CDEV_EVENT_PHY_PACKET_SENT;
+
+ card->driver->send_request(card, &e->p);
+
+ return 0;
+}
+
static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
[0x00] = ioctl_get_info,
[0x01] = ioctl_send_request,
@@ -1406,6 +1469,7 @@ static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
[0x12] = ioctl_send_broadcast_request,
[0x13] = ioctl_send_stream_packet,
[0x14] = ioctl_get_cycle_timer2,
+ [0x15] = ioctl_send_phy_packet,
};
static int dispatch_ioctl(struct client *client,