416 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Connection Management Procedures (IEC 61883-1) helper functions4 *5 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>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/* MPR common fields */18#define MPR_SPEED_MASK 0xc000000019#define MPR_SPEED_SHIFT 3020#define MPR_XSPEED_MASK 0x0000006021#define MPR_XSPEED_SHIFT 522#define MPR_PLUGS_MASK 0x0000001f23 24/* PCR common fields */25#define PCR_ONLINE 0x8000000026#define PCR_BCAST_CONN 0x4000000027#define PCR_P2P_CONN_MASK 0x3f00000028#define PCR_P2P_CONN_SHIFT 2429#define PCR_CHANNEL_MASK 0x003f000030#define PCR_CHANNEL_SHIFT 1631 32/* oPCR specific fields */33#define OPCR_XSPEED_MASK 0x00C0000034#define OPCR_XSPEED_SHIFT 2235#define OPCR_SPEED_MASK 0x0000C00036#define OPCR_SPEED_SHIFT 1437#define OPCR_OVERHEAD_ID_MASK 0x00003C0038#define OPCR_OVERHEAD_ID_SHIFT 1039 40enum bus_reset_handling {41 ABORT_ON_BUS_RESET,42 SUCCEED_ON_BUS_RESET,43};44 45static __printf(2, 3)46void cmp_error(struct cmp_connection *c, const char *fmt, ...)47{48 va_list va;49 50 va_start(va, fmt);51 dev_err(&c->resources.unit->device, "%cPCR%u: %pV",52 (c->direction == CMP_INPUT) ? 'i' : 'o',53 c->pcr_index, &(struct va_format){ fmt, &va });54 va_end(va);55}56 57static u64 mpr_address(struct cmp_connection *c)58{59 if (c->direction == CMP_INPUT)60 return CSR_REGISTER_BASE + CSR_IMPR;61 else62 return CSR_REGISTER_BASE + CSR_OMPR;63}64 65static u64 pcr_address(struct cmp_connection *c)66{67 if (c->direction == CMP_INPUT)68 return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index);69 else70 return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index);71}72 73static int pcr_modify(struct cmp_connection *c,74 __be32 (*modify)(struct cmp_connection *c, __be32 old),75 int (*check)(struct cmp_connection *c, __be32 pcr),76 enum bus_reset_handling bus_reset_handling)77{78 __be32 old_arg, buffer[2];79 int err;80 81 buffer[0] = c->last_pcr_value;82 for (;;) {83 old_arg = buffer[0];84 buffer[1] = modify(c, buffer[0]);85 86 err = snd_fw_transaction(87 c->resources.unit, TCODE_LOCK_COMPARE_SWAP,88 pcr_address(c), buffer, 8,89 FW_FIXED_GENERATION | c->resources.generation);90 91 if (err < 0) {92 if (err == -EAGAIN &&93 bus_reset_handling == SUCCEED_ON_BUS_RESET)94 err = 0;95 return err;96 }97 98 if (buffer[0] == old_arg) /* success? */99 break;100 101 if (check) {102 err = check(c, buffer[0]);103 if (err < 0)104 return err;105 }106 }107 c->last_pcr_value = buffer[1];108 109 return 0;110}111 112 113/**114 * cmp_connection_init - initializes a connection manager115 * @c: the connection manager to initialize116 * @unit: a unit of the target device117 * @direction: input or output118 * @pcr_index: the index of the iPCR/oPCR on the target device119 */120int cmp_connection_init(struct cmp_connection *c,121 struct fw_unit *unit,122 enum cmp_direction direction,123 unsigned int pcr_index)124{125 __be32 mpr_be;126 u32 mpr;127 int err;128 129 c->direction = direction;130 err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,131 mpr_address(c), &mpr_be, 4, 0);132 if (err < 0)133 return err;134 mpr = be32_to_cpu(mpr_be);135 136 if (pcr_index >= (mpr & MPR_PLUGS_MASK))137 return -EINVAL;138 139 err = fw_iso_resources_init(&c->resources, unit);140 if (err < 0)141 return err;142 143 c->connected = false;144 mutex_init(&c->mutex);145 c->last_pcr_value = cpu_to_be32(0x80000000);146 c->pcr_index = pcr_index;147 c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT;148 if (c->max_speed == SCODE_BETA)149 c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT;150 151 return 0;152}153EXPORT_SYMBOL(cmp_connection_init);154 155/**156 * cmp_connection_check_used - check connection is already esablished or not157 * @c: the connection manager to be checked158 * @used: the pointer to store the result of checking the connection159 */160int cmp_connection_check_used(struct cmp_connection *c, bool *used)161{162 __be32 pcr;163 int err;164 165 err = snd_fw_transaction(166 c->resources.unit, TCODE_READ_QUADLET_REQUEST,167 pcr_address(c), &pcr, 4, 0);168 if (err >= 0)169 *used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN |170 PCR_P2P_CONN_MASK));171 172 return err;173}174EXPORT_SYMBOL(cmp_connection_check_used);175 176/**177 * cmp_connection_destroy - free connection manager resources178 * @c: the connection manager179 */180void cmp_connection_destroy(struct cmp_connection *c)181{182 WARN_ON(c->connected);183 mutex_destroy(&c->mutex);184 fw_iso_resources_destroy(&c->resources);185}186EXPORT_SYMBOL(cmp_connection_destroy);187 188int cmp_connection_reserve(struct cmp_connection *c,189 unsigned int max_payload_bytes)190{191 int err;192 193 mutex_lock(&c->mutex);194 195 if (WARN_ON(c->resources.allocated)) {196 err = -EBUSY;197 goto end;198 }199 200 c->speed = min(c->max_speed,201 fw_parent_device(c->resources.unit)->max_speed);202 203 err = fw_iso_resources_allocate(&c->resources, max_payload_bytes,204 c->speed);205end:206 mutex_unlock(&c->mutex);207 208 return err;209}210EXPORT_SYMBOL(cmp_connection_reserve);211 212void cmp_connection_release(struct cmp_connection *c)213{214 mutex_lock(&c->mutex);215 fw_iso_resources_free(&c->resources);216 mutex_unlock(&c->mutex);217}218EXPORT_SYMBOL(cmp_connection_release);219 220static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr)221{222 ipcr &= ~cpu_to_be32(PCR_BCAST_CONN |223 PCR_P2P_CONN_MASK |224 PCR_CHANNEL_MASK);225 ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT);226 ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);227 228 return ipcr;229}230 231static int get_overhead_id(struct cmp_connection *c)232{233 int id;234 235 /*236 * apply "oPCR overhead ID encoding"237 * the encoding table can convert up to 512.238 * here the value over 512 is converted as the same way as 512.239 */240 for (id = 1; id < 16; id++) {241 if (c->resources.bandwidth_overhead < (id << 5))242 break;243 }244 if (id == 16)245 id = 0;246 247 return id;248}249 250static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr)251{252 unsigned int spd, xspd;253 254 /* generate speed and extended speed field value */255 if (c->speed > SCODE_400) {256 spd = SCODE_800;257 xspd = c->speed - SCODE_800;258 } else {259 spd = c->speed;260 xspd = 0;261 }262 263 opcr &= ~cpu_to_be32(PCR_BCAST_CONN |264 PCR_P2P_CONN_MASK |265 OPCR_XSPEED_MASK |266 PCR_CHANNEL_MASK |267 OPCR_SPEED_MASK |268 OPCR_OVERHEAD_ID_MASK);269 opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT);270 opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT);271 opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);272 opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT);273 opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT);274 275 return opcr;276}277 278static int pcr_set_check(struct cmp_connection *c, __be32 pcr)279{280 if (pcr & cpu_to_be32(PCR_BCAST_CONN |281 PCR_P2P_CONN_MASK)) {282 cmp_error(c, "plug is already in use\n");283 return -EBUSY;284 }285 if (!(pcr & cpu_to_be32(PCR_ONLINE))) {286 cmp_error(c, "plug is not on-line\n");287 return -ECONNREFUSED;288 }289 290 return 0;291}292 293/**294 * cmp_connection_establish - establish a connection to the target295 * @c: the connection manager296 *297 * This function establishes a point-to-point connection from the local298 * computer to the target by allocating isochronous resources (channel and299 * bandwidth) and setting the target's input/output plug control register.300 * When this function succeeds, the caller is responsible for starting301 * transmitting packets.302 */303int cmp_connection_establish(struct cmp_connection *c)304{305 int err;306 307 mutex_lock(&c->mutex);308 309 if (WARN_ON(c->connected)) {310 mutex_unlock(&c->mutex);311 return -EISCONN;312 }313 314retry_after_bus_reset:315 if (c->direction == CMP_OUTPUT)316 err = pcr_modify(c, opcr_set_modify, pcr_set_check,317 ABORT_ON_BUS_RESET);318 else319 err = pcr_modify(c, ipcr_set_modify, pcr_set_check,320 ABORT_ON_BUS_RESET);321 322 if (err == -EAGAIN) {323 err = fw_iso_resources_update(&c->resources);324 if (err >= 0)325 goto retry_after_bus_reset;326 }327 if (err >= 0)328 c->connected = true;329 330 mutex_unlock(&c->mutex);331 332 return err;333}334EXPORT_SYMBOL(cmp_connection_establish);335 336/**337 * cmp_connection_update - update the connection after a bus reset338 * @c: the connection manager339 *340 * This function must be called from the driver's .update handler to341 * reestablish any connection that might have been active.342 *343 * Returns zero on success, or a negative error code. On an error, the344 * connection is broken and the caller must stop transmitting iso packets.345 */346int cmp_connection_update(struct cmp_connection *c)347{348 int err;349 350 mutex_lock(&c->mutex);351 352 if (!c->connected) {353 mutex_unlock(&c->mutex);354 return 0;355 }356 357 err = fw_iso_resources_update(&c->resources);358 if (err < 0)359 goto err_unconnect;360 361 if (c->direction == CMP_OUTPUT)362 err = pcr_modify(c, opcr_set_modify, pcr_set_check,363 SUCCEED_ON_BUS_RESET);364 else365 err = pcr_modify(c, ipcr_set_modify, pcr_set_check,366 SUCCEED_ON_BUS_RESET);367 368 if (err < 0)369 goto err_unconnect;370 371 mutex_unlock(&c->mutex);372 373 return 0;374 375err_unconnect:376 c->connected = false;377 mutex_unlock(&c->mutex);378 379 return err;380}381EXPORT_SYMBOL(cmp_connection_update);382 383static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr)384{385 return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK);386}387 388/**389 * cmp_connection_break - break the connection to the target390 * @c: the connection manager391 *392 * This function deactives the connection in the target's input/output plug393 * control register, and frees the isochronous resources of the connection.394 * Before calling this function, the caller should cease transmitting packets.395 */396void cmp_connection_break(struct cmp_connection *c)397{398 int err;399 400 mutex_lock(&c->mutex);401 402 if (!c->connected) {403 mutex_unlock(&c->mutex);404 return;405 }406 407 err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);408 if (err < 0)409 cmp_error(c, "plug is still connected\n");410 411 c->connected = false;412 413 mutex_unlock(&c->mutex);414}415EXPORT_SYMBOL(cmp_connection_break);416