firewire: cdev: add PHY packet transmission
authorStefan Richter <stefanr@s5r6.in-berlin.de>
Fri, 16 Jul 2010 20:25:14 +0000 (22:25 +0200)
committerStefan Richter <stefanr@s5r6.in-berlin.de>
Fri, 23 Jul 2010 11:36:28 +0000 (13:36 +0200)
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>
drivers/firewire/core-cdev.c
include/linux/firewire-cdev.h

index acf4fa1..f957199 100644 (file)
@@ -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,
index fde9568..5bc051b 100644 (file)
@@ -34,6 +34,7 @@
 
 /* available since kernel version 2.6.36 */
 #define FW_CDEV_EVENT_REQUEST2                 0x06
+#define FW_CDEV_EVENT_PHY_PACKET_SENT          0x07
 
 /**
  * struct fw_cdev_event_common - Common part of all fw_cdev_event_ types
@@ -284,6 +285,19 @@ struct fw_cdev_event_iso_resource {
 };
 
 /**
+ * struct fw_cdev_event_phy_packet - A PHY packet was transmitted
+ * @closure:   See &fw_cdev_event_common;
+ *             set by %FW_CDEV_IOC_SEND_PHY_PACKET ioctl
+ * @type:      %FW_CDEV_EVENT_PHY_PACKET_SENT
+ * @rcode:     %RCODE_..., indicates success or failure of transmission
+ */
+struct fw_cdev_event_phy_packet {
+       __u64 closure;
+       __u32 type;
+       __u32 rcode;
+};
+
+/**
  * union fw_cdev_event - Convenience union of fw_cdev_event_ types
  * @common:        Valid for all types
  * @bus_reset:     Valid if @common.type == %FW_CDEV_EVENT_BUS_RESET
@@ -294,6 +308,7 @@ struct fw_cdev_event_iso_resource {
  * @iso_resource:  Valid if @common.type ==
  *                             %FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED or
  *                             %FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED
+ * @phy_packet:    Valid if @common.type == %FW_CDEV_EVENT_PHY_PACKET_SENT
  *
  * Convenience union for userspace use.  Events could be read(2) into an
  * appropriately aligned char buffer and then cast to this union for further
@@ -311,6 +326,7 @@ union fw_cdev_event {
        struct fw_cdev_event_request2           request2;     /* added in 2.6.36 */
        struct fw_cdev_event_iso_interrupt      iso_interrupt;
        struct fw_cdev_event_iso_resource       iso_resource; /* added in 2.6.30 */
+       struct fw_cdev_event_phy_packet         phy_packet;   /* added in 2.6.36 */
 };
 
 /* available since kernel version 2.6.22 */
@@ -342,6 +358,9 @@ union fw_cdev_event {
 /* available since kernel version 2.6.34 */
 #define FW_CDEV_IOC_GET_CYCLE_TIMER2   _IOWR('#', 0x14, struct fw_cdev_get_cycle_timer2)
 
+/* available since kernel version 2.6.36 */
+#define FW_CDEV_IOC_SEND_PHY_PACKET    _IOWR('#', 0x15, struct fw_cdev_send_phy_packet)
+
 /*
  * ABI version history
  *  1  (2.6.22)  - initial version
@@ -357,8 +376,9 @@ union fw_cdev_event {
  *               - shared use and auto-response for FCP registers
  *  3  (2.6.34)  - made &fw_cdev_get_cycle_timer reliable
  *               - added %FW_CDEV_IOC_GET_CYCLE_TIMER2
- *  4  (2.6.36)  - added %FW_CDEV_EVENT_REQUEST2
+ *  4  (2.6.36)  - added %FW_CDEV_EVENT_REQUEST2, %FW_CDEV_EVENT_PHY_PACKET_SENT
  *               - implemented &fw_cdev_event_bus_reset.bm_node_id
+ *               - added %FW_CDEV_IOC_SEND_PHY_PACKET
  */
 #define FW_CDEV_VERSION 3 /* Meaningless; don't use this macro. */
 
@@ -808,4 +828,26 @@ struct fw_cdev_send_stream_packet {
        __u32 speed;
 };
 
+/**
+ * struct fw_cdev_send_phy_packet - send a PHY packet
+ * @closure:   Passed back to userspace in the PHY-packet-sent event
+ * @data:      First and second quadlet of the PHY packet
+ * @generation:        The bus generation where packet is valid
+ *
+ * The %FW_CDEV_IOC_SEND_PHY_PACKET ioctl sends a PHY packet to all nodes
+ * on the same card as this device.  After transmission, an
+ * %FW_CDEV_EVENT_PHY_PACKET_SENT event is generated.
+ *
+ * The payload @data[] shall be specified in host byte order.  Usually,
+ * @data[1] needs to be the bitwise inverse of @data[0].  VersaPHY packets
+ * are an exception to this rule.
+ *
+ * The ioctl is only permitted on device files which represent a local node.
+ */
+struct fw_cdev_send_phy_packet {
+       __u64 closure;
+       __u32 data[2];
+       __u32 generation;
+};
+
 #endif /* _LINUX_FIREWIRE_CDEV_H */