299 lines · 6.9 KB · C
RawDownload
/* * Connection Management Procedures (IEC 61883-1) helper functions * * Copyright (c) Clemens Ladisch <clemens@ladisch.de> * Licensed under the terms of the GNU General Public License, version 2. */ #include <linux/device.h>#include <linux/firewire.h>#include <linux/firewire-constants.h>#include <linux/module.h>#include <linux/sched.h>#include "lib.h"#include "iso-resources.h"#include "cmp.h" #define IMPR_SPEED_MASK		0xc0000000#define IMPR_SPEED_SHIFT	30#define IMPR_XSPEED_MASK	0x00000060#define IMPR_XSPEED_SHIFT	5#define IMPR_PLUGS_MASK		0x0000001f #define IPCR_ONLINE		0x80000000#define IPCR_BCAST_CONN		0x40000000#define IPCR_P2P_CONN_MASK	0x3f000000#define IPCR_P2P_CONN_SHIFT	24#define IPCR_CHANNEL_MASK	0x003f0000#define IPCR_CHANNEL_SHIFT	16 enum bus_reset_handling {	ABORT_ON_BUS_RESET,	SUCCEED_ON_BUS_RESET,}; static __printf(2, 3)void cmp_error(struct cmp_connection *c, const char *fmt, ...){	va_list va; 	va_start(va, fmt);	dev_err(&c->resources.unit->device, "%cPCR%u: %pV",		'i', c->pcr_index, &(struct va_format){ fmt, &va });	va_end(va);} static int pcr_modify(struct cmp_connection *c,		      __be32 (*modify)(struct cmp_connection *c, __be32 old),		      int (*check)(struct cmp_connection *c, __be32 pcr),		      enum bus_reset_handling bus_reset_handling){	__be32 old_arg, buffer[2];	int err; 	buffer[0] = c->last_pcr_value;	for (;;) {		old_arg = buffer[0];		buffer[1] = modify(c, buffer[0]); 		err = snd_fw_transaction(				c->resources.unit, TCODE_LOCK_COMPARE_SWAP,				CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index),				buffer, 8,				FW_FIXED_GENERATION | c->resources.generation); 		if (err < 0) {			if (err == -EAGAIN &&			    bus_reset_handling == SUCCEED_ON_BUS_RESET)				err = 0;			return err;		} 		if (buffer[0] == old_arg) /* success? */			break; 		if (check) {			err = check(c, buffer[0]);			if (err < 0)				return err;		}	}	c->last_pcr_value = buffer[1]; 	return 0;}  /** * cmp_connection_init - initializes a connection manager * @c: the connection manager to initialize * @unit: a unit of the target device * @ipcr_index: the index of the iPCR on the target device */int cmp_connection_init(struct cmp_connection *c,			struct fw_unit *unit,			unsigned int ipcr_index){	__be32 impr_be;	u32 impr;	int err; 	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,				 CSR_REGISTER_BASE + CSR_IMPR,				 &impr_be, 4, 0);	if (err < 0)		return err;	impr = be32_to_cpu(impr_be); 	if (ipcr_index >= (impr & IMPR_PLUGS_MASK))		return -EINVAL; 	err = fw_iso_resources_init(&c->resources, unit);	if (err < 0)		return err; 	c->connected = false;	mutex_init(&c->mutex);	c->last_pcr_value = cpu_to_be32(0x80000000);	c->pcr_index = ipcr_index;	c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT;	if (c->max_speed == SCODE_BETA)		c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; 	return 0;}EXPORT_SYMBOL(cmp_connection_init); /** * cmp_connection_destroy - free connection manager resources * @c: the connection manager */void cmp_connection_destroy(struct cmp_connection *c){	WARN_ON(c->connected);	mutex_destroy(&c->mutex);	fw_iso_resources_destroy(&c->resources);}EXPORT_SYMBOL(cmp_connection_destroy);  static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr){	ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN |			     IPCR_P2P_CONN_MASK |			     IPCR_CHANNEL_MASK);	ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT);	ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); 	return ipcr;} static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr){	if (ipcr & cpu_to_be32(IPCR_BCAST_CONN |			       IPCR_P2P_CONN_MASK)) {		cmp_error(c, "plug is already in use\n");		return -EBUSY;	}	if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) {		cmp_error(c, "plug is not on-line\n");		return -ECONNREFUSED;	} 	return 0;} /** * cmp_connection_establish - establish a connection to the target * @c: the connection manager * @max_payload_bytes: the amount of data (including CIP headers) per packet * * This function establishes a point-to-point connection from the local * computer to the target by allocating isochronous resources (channel and * bandwidth) and setting the target's input plug control register.  When this * function succeeds, the caller is responsible for starting transmitting * packets. */int cmp_connection_establish(struct cmp_connection *c,			     unsigned int max_payload_bytes){	int err; 	if (WARN_ON(c->connected))		return -EISCONN; 	c->speed = min(c->max_speed,		       fw_parent_device(c->resources.unit)->max_speed); 	mutex_lock(&c->mutex); retry_after_bus_reset:	err = fw_iso_resources_allocate(&c->resources,					max_payload_bytes, c->speed);	if (err < 0)		goto err_mutex; 	err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,			 ABORT_ON_BUS_RESET);	if (err == -EAGAIN) {		fw_iso_resources_free(&c->resources);		goto retry_after_bus_reset;	}	if (err < 0)		goto err_resources; 	c->connected = true; 	mutex_unlock(&c->mutex); 	return 0; err_resources:	fw_iso_resources_free(&c->resources);err_mutex:	mutex_unlock(&c->mutex); 	return err;}EXPORT_SYMBOL(cmp_connection_establish); /** * cmp_connection_update - update the connection after a bus reset * @c: the connection manager * * This function must be called from the driver's .update handler to reestablish * any connection that might have been active. * * Returns zero on success, or a negative error code.  On an error, the * connection is broken and the caller must stop transmitting iso packets. */int cmp_connection_update(struct cmp_connection *c){	int err; 	mutex_lock(&c->mutex); 	if (!c->connected) {		mutex_unlock(&c->mutex);		return 0;	} 	err = fw_iso_resources_update(&c->resources);	if (err < 0)		goto err_unconnect; 	err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,			 SUCCEED_ON_BUS_RESET);	if (err < 0)		goto err_resources; 	mutex_unlock(&c->mutex); 	return 0; err_resources:	fw_iso_resources_free(&c->resources);err_unconnect:	c->connected = false;	mutex_unlock(&c->mutex); 	return err;}EXPORT_SYMBOL(cmp_connection_update);  static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr){	return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK);} /** * cmp_connection_break - break the connection to the target * @c: the connection manager * * This function deactives the connection in the target's input plug control * register, and frees the isochronous resources of the connection.  Before * calling this function, the caller should cease transmitting packets. */void cmp_connection_break(struct cmp_connection *c){	int err; 	mutex_lock(&c->mutex); 	if (!c->connected) {		mutex_unlock(&c->mutex);		return;	} 	err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);	if (err < 0)		cmp_error(c, "plug is still connected\n"); 	fw_iso_resources_free(&c->resources); 	c->connected = false; 	mutex_unlock(&c->mutex);}EXPORT_SYMBOL(cmp_connection_break);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t