brintos

brintos / linux-shallow public Read only

0
0
Text · 9.6 KiB · b596bec Raw
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