linux/sound/firewire/cmp.c
<<
>>
Prefs
   1/*
   2 * Connection Management Procedures (IEC 61883-1) helper functions
   3 *
   4 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
   5 * Licensed under the terms of the GNU General Public License, version 2.
   6 */
   7
   8#include <linux/device.h>
   9#include <linux/firewire.h>
  10#include <linux/firewire-constants.h>
  11#include <linux/module.h>
  12#include <linux/sched.h>
  13#include "lib.h"
  14#include "iso-resources.h"
  15#include "cmp.h"
  16
  17#define IMPR_SPEED_MASK         0xc0000000
  18#define IMPR_SPEED_SHIFT        30
  19#define IMPR_XSPEED_MASK        0x00000060
  20#define IMPR_XSPEED_SHIFT       5
  21#define IMPR_PLUGS_MASK         0x0000001f
  22
  23#define IPCR_ONLINE             0x80000000
  24#define IPCR_BCAST_CONN         0x40000000
  25#define IPCR_P2P_CONN_MASK      0x3f000000
  26#define IPCR_P2P_CONN_SHIFT     24
  27#define IPCR_CHANNEL_MASK       0x003f0000
  28#define IPCR_CHANNEL_SHIFT      16
  29
  30enum bus_reset_handling {
  31        ABORT_ON_BUS_RESET,
  32        SUCCEED_ON_BUS_RESET,
  33};
  34
  35static __attribute__((format(printf, 2, 3)))
  36void cmp_error(struct cmp_connection *c, const char *fmt, ...)
  37{
  38        va_list va;
  39
  40        va_start(va, fmt);
  41        dev_err(&c->resources.unit->device, "%cPCR%u: %pV",
  42                'i', c->pcr_index, &(struct va_format){ fmt, &va });
  43        va_end(va);
  44}
  45
  46static int pcr_modify(struct cmp_connection *c,
  47                      __be32 (*modify)(struct cmp_connection *c, __be32 old),
  48                      int (*check)(struct cmp_connection *c, __be32 pcr),
  49                      enum bus_reset_handling bus_reset_handling)
  50{
  51        struct fw_device *device = fw_parent_device(c->resources.unit);
  52        __be32 *buffer = c->resources.buffer;
  53        int generation = c->resources.generation;
  54        int rcode, errors = 0;
  55        __be32 old_arg;
  56        int err;
  57
  58        buffer[0] = c->last_pcr_value;
  59        for (;;) {
  60                old_arg = buffer[0];
  61                buffer[1] = modify(c, buffer[0]);
  62
  63                rcode = fw_run_transaction(
  64                                device->card, TCODE_LOCK_COMPARE_SWAP,
  65                                device->node_id, generation, device->max_speed,
  66                                CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index),
  67                                buffer, 8);
  68
  69                if (rcode == RCODE_COMPLETE) {
  70                        if (buffer[0] == old_arg) /* success? */
  71                                break;
  72
  73                        if (check) {
  74                                err = check(c, buffer[0]);
  75                                if (err < 0)
  76                                        return err;
  77                        }
  78                } else if (rcode == RCODE_GENERATION)
  79                        goto bus_reset;
  80                else if (rcode_is_permanent_error(rcode) || ++errors >= 3)
  81                        goto io_error;
  82        }
  83        c->last_pcr_value = buffer[1];
  84
  85        return 0;
  86
  87io_error:
  88        cmp_error(c, "transaction failed: %s\n", rcode_string(rcode));
  89        return -EIO;
  90
  91bus_reset:
  92        return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0;
  93}
  94
  95
  96/**
  97 * cmp_connection_init - initializes a connection manager
  98 * @c: the connection manager to initialize
  99 * @unit: a unit of the target device
 100 * @ipcr_index: the index of the iPCR on the target device
 101 */
 102int cmp_connection_init(struct cmp_connection *c,
 103                        struct fw_unit *unit,
 104                        unsigned int ipcr_index)
 105{
 106        __be32 impr_be;
 107        u32 impr;
 108        int err;
 109
 110        err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
 111                                 CSR_REGISTER_BASE + CSR_IMPR,
 112                                 &impr_be, 4);
 113        if (err < 0)
 114                return err;
 115        impr = be32_to_cpu(impr_be);
 116
 117        if (ipcr_index >= (impr & IMPR_PLUGS_MASK))
 118                return -EINVAL;
 119
 120        err = fw_iso_resources_init(&c->resources, unit);
 121        if (err < 0)
 122                return err;
 123
 124        c->connected = false;
 125        mutex_init(&c->mutex);
 126        c->last_pcr_value = cpu_to_be32(0x80000000);
 127        c->pcr_index = ipcr_index;
 128        c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT;
 129        if (c->max_speed == SCODE_BETA)
 130                c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT;
 131
 132        return 0;
 133}
 134EXPORT_SYMBOL(cmp_connection_init);
 135
 136/**
 137 * cmp_connection_destroy - free connection manager resources
 138 * @c: the connection manager
 139 */
 140void cmp_connection_destroy(struct cmp_connection *c)
 141{
 142        WARN_ON(c->connected);
 143        mutex_destroy(&c->mutex);
 144        fw_iso_resources_destroy(&c->resources);
 145}
 146EXPORT_SYMBOL(cmp_connection_destroy);
 147
 148
 149static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr)
 150{
 151        ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN |
 152                             IPCR_P2P_CONN_MASK |
 153                             IPCR_CHANNEL_MASK);
 154        ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT);
 155        ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT);
 156
 157        return ipcr;
 158}
 159
 160static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr)
 161{
 162        if (ipcr & cpu_to_be32(IPCR_BCAST_CONN |
 163                               IPCR_P2P_CONN_MASK)) {
 164                cmp_error(c, "plug is already in use\n");
 165                return -EBUSY;
 166        }
 167        if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) {
 168                cmp_error(c, "plug is not on-line\n");
 169                return -ECONNREFUSED;
 170        }
 171
 172        return 0;
 173}
 174
 175/**
 176 * cmp_connection_establish - establish a connection to the target
 177 * @c: the connection manager
 178 * @max_payload_bytes: the amount of data (including CIP headers) per packet
 179 *
 180 * This function establishes a point-to-point connection from the local
 181 * computer to the target by allocating isochronous resources (channel and
 182 * bandwidth) and setting the target's input plug control register.  When this
 183 * function succeeds, the caller is responsible for starting transmitting
 184 * packets.
 185 */
 186int cmp_connection_establish(struct cmp_connection *c,
 187                             unsigned int max_payload_bytes)
 188{
 189        int err;
 190
 191        if (WARN_ON(c->connected))
 192                return -EISCONN;
 193
 194        c->speed = min(c->max_speed,
 195                       fw_parent_device(c->resources.unit)->max_speed);
 196
 197        mutex_lock(&c->mutex);
 198
 199retry_after_bus_reset:
 200        err = fw_iso_resources_allocate(&c->resources,
 201                                        max_payload_bytes, c->speed);
 202        if (err < 0)
 203                goto err_mutex;
 204
 205        err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,
 206                         ABORT_ON_BUS_RESET);
 207        if (err == -EAGAIN) {
 208                fw_iso_resources_free(&c->resources);
 209                goto retry_after_bus_reset;
 210        }
 211        if (err < 0)
 212                goto err_resources;
 213
 214        c->connected = true;
 215
 216        mutex_unlock(&c->mutex);
 217
 218        return 0;
 219
 220err_resources:
 221        fw_iso_resources_free(&c->resources);
 222err_mutex:
 223        mutex_unlock(&c->mutex);
 224
 225        return err;
 226}
 227EXPORT_SYMBOL(cmp_connection_establish);
 228
 229/**
 230 * cmp_connection_update - update the connection after a bus reset
 231 * @c: the connection manager
 232 *
 233 * This function must be called from the driver's .update handler to reestablish
 234 * any connection that might have been active.
 235 *
 236 * Returns zero on success, or a negative error code.  On an error, the
 237 * connection is broken and the caller must stop transmitting iso packets.
 238 */
 239int cmp_connection_update(struct cmp_connection *c)
 240{
 241        int err;
 242
 243        mutex_lock(&c->mutex);
 244
 245        if (!c->connected) {
 246                mutex_unlock(&c->mutex);
 247                return 0;
 248        }
 249
 250        err = fw_iso_resources_update(&c->resources);
 251        if (err < 0)
 252                goto err_unconnect;
 253
 254        err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,
 255                         SUCCEED_ON_BUS_RESET);
 256        if (err < 0)
 257                goto err_resources;
 258
 259        mutex_unlock(&c->mutex);
 260
 261        return 0;
 262
 263err_resources:
 264        fw_iso_resources_free(&c->resources);
 265err_unconnect:
 266        c->connected = false;
 267        mutex_unlock(&c->mutex);
 268
 269        return err;
 270}
 271EXPORT_SYMBOL(cmp_connection_update);
 272
 273
 274static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr)
 275{
 276        return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK);
 277}
 278
 279/**
 280 * cmp_connection_break - break the connection to the target
 281 * @c: the connection manager
 282 *
 283 * This function deactives the connection in the target's input plug control
 284 * register, and frees the isochronous resources of the connection.  Before
 285 * calling this function, the caller should cease transmitting packets.
 286 */
 287void cmp_connection_break(struct cmp_connection *c)
 288{
 289        int err;
 290
 291        mutex_lock(&c->mutex);
 292
 293        if (!c->connected) {
 294                mutex_unlock(&c->mutex);
 295                return;
 296        }
 297
 298        err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);
 299        if (err < 0)
 300                cmp_error(c, "plug is still connected\n");
 301
 302        fw_iso_resources_free(&c->resources);
 303
 304        c->connected = false;
 305
 306        mutex_unlock(&c->mutex);
 307}
 308EXPORT_SYMBOL(cmp_connection_break);
 309