18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * isochronous resources helper functions
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/device.h>
98c2ecf20Sopenharmony_ci#include <linux/firewire.h>
108c2ecf20Sopenharmony_ci#include <linux/firewire-constants.h>
118c2ecf20Sopenharmony_ci#include <linux/export.h>
128c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
138c2ecf20Sopenharmony_ci#include <linux/mutex.h>
148c2ecf20Sopenharmony_ci#include <linux/sched.h>
158c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
168c2ecf20Sopenharmony_ci#include "iso-resources.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/**
198c2ecf20Sopenharmony_ci * fw_iso_resources_init - initializes a &struct fw_iso_resources
208c2ecf20Sopenharmony_ci * @r: the resource manager to initialize
218c2ecf20Sopenharmony_ci * @unit: the device unit for which the resources will be needed
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * If the device does not support all channel numbers, change @r->channels_mask
248c2ecf20Sopenharmony_ci * after calling this function.
258c2ecf20Sopenharmony_ci */
268c2ecf20Sopenharmony_ciint fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	r->channels_mask = ~0uLL;
298c2ecf20Sopenharmony_ci	r->unit = unit;
308c2ecf20Sopenharmony_ci	mutex_init(&r->mutex);
318c2ecf20Sopenharmony_ci	r->allocated = false;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	return 0;
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fw_iso_resources_init);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/**
388c2ecf20Sopenharmony_ci * fw_iso_resources_destroy - destroy a resource manager
398c2ecf20Sopenharmony_ci * @r: the resource manager that is no longer needed
408c2ecf20Sopenharmony_ci */
418c2ecf20Sopenharmony_civoid fw_iso_resources_destroy(struct fw_iso_resources *r)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	WARN_ON(r->allocated);
448c2ecf20Sopenharmony_ci	mutex_destroy(&r->mutex);
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fw_iso_resources_destroy);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	unsigned int bytes, s400_bytes;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	/* iso packets have three header quadlets and quadlet-aligned payload */
538c2ecf20Sopenharmony_ci	bytes = 3 * 4 + ALIGN(max_payload_bytes, 4);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	/* convert to bandwidth units (quadlets at S1600 = bytes at S400) */
568c2ecf20Sopenharmony_ci	if (speed <= SCODE_400)
578c2ecf20Sopenharmony_ci		s400_bytes = bytes * (1 << (SCODE_400 - speed));
588c2ecf20Sopenharmony_ci	else
598c2ecf20Sopenharmony_ci		s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400));
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	return s400_bytes;
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic int current_bandwidth_overhead(struct fw_card *card)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	/*
678c2ecf20Sopenharmony_ci	 * Under the usual pessimistic assumption (cable length 4.5 m), the
688c2ecf20Sopenharmony_ci	 * isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or
698c2ecf20Sopenharmony_ci	 * 88.3 + N * 24.3 in bandwidth units.
708c2ecf20Sopenharmony_ci	 *
718c2ecf20Sopenharmony_ci	 * The calculation below tries to deduce N from the current gap count.
728c2ecf20Sopenharmony_ci	 * If the gap count has been optimized by measuring the actual packet
738c2ecf20Sopenharmony_ci	 * transmission time, this derived overhead should be near the actual
748c2ecf20Sopenharmony_ci	 * overhead as well.
758c2ecf20Sopenharmony_ci	 */
768c2ecf20Sopenharmony_ci	return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512;
778c2ecf20Sopenharmony_ci}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_cistatic int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	for (;;) {
828c2ecf20Sopenharmony_ci		s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64();
838c2ecf20Sopenharmony_ci		if (delay <= 0)
848c2ecf20Sopenharmony_ci			return 0;
858c2ecf20Sopenharmony_ci		if (schedule_timeout_interruptible(delay) > 0)
868c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci/**
918c2ecf20Sopenharmony_ci * fw_iso_resources_allocate - allocate isochronous channel and bandwidth
928c2ecf20Sopenharmony_ci * @r: the resource manager
938c2ecf20Sopenharmony_ci * @max_payload_bytes: the amount of data (including CIP headers) per packet
948c2ecf20Sopenharmony_ci * @speed: the speed (e.g., SCODE_400) at which the packets will be sent
958c2ecf20Sopenharmony_ci *
968c2ecf20Sopenharmony_ci * This function allocates one isochronous channel and enough bandwidth for the
978c2ecf20Sopenharmony_ci * specified packet size.
988c2ecf20Sopenharmony_ci *
998c2ecf20Sopenharmony_ci * Returns the channel number that the caller must use for streaming, or
1008c2ecf20Sopenharmony_ci * a negative error code.  Due to potentionally long delays, this function is
1018c2ecf20Sopenharmony_ci * interruptible and can return -ERESTARTSYS.  On success, the caller is
1028c2ecf20Sopenharmony_ci * responsible for calling fw_iso_resources_update() on bus resets, and
1038c2ecf20Sopenharmony_ci * fw_iso_resources_free() when the resources are not longer needed.
1048c2ecf20Sopenharmony_ci */
1058c2ecf20Sopenharmony_ciint fw_iso_resources_allocate(struct fw_iso_resources *r,
1068c2ecf20Sopenharmony_ci			      unsigned int max_payload_bytes, int speed)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	struct fw_card *card = fw_parent_device(r->unit)->card;
1098c2ecf20Sopenharmony_ci	int bandwidth, channel, err;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (WARN_ON(r->allocated))
1128c2ecf20Sopenharmony_ci		return -EBADFD;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	r->bandwidth = packet_bandwidth(max_payload_bytes, speed);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ciretry_after_bus_reset:
1178c2ecf20Sopenharmony_ci	spin_lock_irq(&card->lock);
1188c2ecf20Sopenharmony_ci	r->generation = card->generation;
1198c2ecf20Sopenharmony_ci	r->bandwidth_overhead = current_bandwidth_overhead(card);
1208c2ecf20Sopenharmony_ci	spin_unlock_irq(&card->lock);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	err = wait_isoch_resource_delay_after_bus_reset(card);
1238c2ecf20Sopenharmony_ci	if (err < 0)
1248c2ecf20Sopenharmony_ci		return err;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	mutex_lock(&r->mutex);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	bandwidth = r->bandwidth + r->bandwidth_overhead;
1298c2ecf20Sopenharmony_ci	fw_iso_resource_manage(card, r->generation, r->channels_mask,
1308c2ecf20Sopenharmony_ci			       &channel, &bandwidth, true);
1318c2ecf20Sopenharmony_ci	if (channel == -EAGAIN) {
1328c2ecf20Sopenharmony_ci		mutex_unlock(&r->mutex);
1338c2ecf20Sopenharmony_ci		goto retry_after_bus_reset;
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci	if (channel >= 0) {
1368c2ecf20Sopenharmony_ci		r->channel = channel;
1378c2ecf20Sopenharmony_ci		r->allocated = true;
1388c2ecf20Sopenharmony_ci	} else {
1398c2ecf20Sopenharmony_ci		if (channel == -EBUSY)
1408c2ecf20Sopenharmony_ci			dev_err(&r->unit->device,
1418c2ecf20Sopenharmony_ci				"isochronous resources exhausted\n");
1428c2ecf20Sopenharmony_ci		else
1438c2ecf20Sopenharmony_ci			dev_err(&r->unit->device,
1448c2ecf20Sopenharmony_ci				"isochronous resource allocation failed\n");
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	mutex_unlock(&r->mutex);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	return channel;
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fw_iso_resources_allocate);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci/**
1548c2ecf20Sopenharmony_ci * fw_iso_resources_update - update resource allocations after a bus reset
1558c2ecf20Sopenharmony_ci * @r: the resource manager
1568c2ecf20Sopenharmony_ci *
1578c2ecf20Sopenharmony_ci * This function must be called from the driver's .update handler to reallocate
1588c2ecf20Sopenharmony_ci * any resources that were allocated before the bus reset.  It is safe to call
1598c2ecf20Sopenharmony_ci * this function if no resources are currently allocated.
1608c2ecf20Sopenharmony_ci *
1618c2ecf20Sopenharmony_ci * Returns a negative error code on failure.  If this happens, the caller must
1628c2ecf20Sopenharmony_ci * stop streaming.
1638c2ecf20Sopenharmony_ci */
1648c2ecf20Sopenharmony_ciint fw_iso_resources_update(struct fw_iso_resources *r)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	struct fw_card *card = fw_parent_device(r->unit)->card;
1678c2ecf20Sopenharmony_ci	int bandwidth, channel;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	mutex_lock(&r->mutex);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	if (!r->allocated) {
1728c2ecf20Sopenharmony_ci		mutex_unlock(&r->mutex);
1738c2ecf20Sopenharmony_ci		return 0;
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	spin_lock_irq(&card->lock);
1778c2ecf20Sopenharmony_ci	r->generation = card->generation;
1788c2ecf20Sopenharmony_ci	r->bandwidth_overhead = current_bandwidth_overhead(card);
1798c2ecf20Sopenharmony_ci	spin_unlock_irq(&card->lock);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	bandwidth = r->bandwidth + r->bandwidth_overhead;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	fw_iso_resource_manage(card, r->generation, 1uLL << r->channel,
1848c2ecf20Sopenharmony_ci			       &channel, &bandwidth, true);
1858c2ecf20Sopenharmony_ci	/*
1868c2ecf20Sopenharmony_ci	 * When another bus reset happens, pretend that the allocation
1878c2ecf20Sopenharmony_ci	 * succeeded; we will try again for the new generation later.
1888c2ecf20Sopenharmony_ci	 */
1898c2ecf20Sopenharmony_ci	if (channel < 0 && channel != -EAGAIN) {
1908c2ecf20Sopenharmony_ci		r->allocated = false;
1918c2ecf20Sopenharmony_ci		if (channel == -EBUSY)
1928c2ecf20Sopenharmony_ci			dev_err(&r->unit->device,
1938c2ecf20Sopenharmony_ci				"isochronous resources exhausted\n");
1948c2ecf20Sopenharmony_ci		else
1958c2ecf20Sopenharmony_ci			dev_err(&r->unit->device,
1968c2ecf20Sopenharmony_ci				"isochronous resource allocation failed\n");
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	mutex_unlock(&r->mutex);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	return channel;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fw_iso_resources_update);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci/**
2068c2ecf20Sopenharmony_ci * fw_iso_resources_free - frees allocated resources
2078c2ecf20Sopenharmony_ci * @r: the resource manager
2088c2ecf20Sopenharmony_ci *
2098c2ecf20Sopenharmony_ci * This function deallocates the channel and bandwidth, if allocated.
2108c2ecf20Sopenharmony_ci */
2118c2ecf20Sopenharmony_civoid fw_iso_resources_free(struct fw_iso_resources *r)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	struct fw_card *card;
2148c2ecf20Sopenharmony_ci	int bandwidth, channel;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	/* Not initialized. */
2178c2ecf20Sopenharmony_ci	if (r->unit == NULL)
2188c2ecf20Sopenharmony_ci		return;
2198c2ecf20Sopenharmony_ci	card = fw_parent_device(r->unit)->card;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	mutex_lock(&r->mutex);
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	if (r->allocated) {
2248c2ecf20Sopenharmony_ci		bandwidth = r->bandwidth + r->bandwidth_overhead;
2258c2ecf20Sopenharmony_ci		fw_iso_resource_manage(card, r->generation, 1uLL << r->channel,
2268c2ecf20Sopenharmony_ci				       &channel, &bandwidth, false);
2278c2ecf20Sopenharmony_ci		if (channel < 0)
2288c2ecf20Sopenharmony_ci			dev_err(&r->unit->device,
2298c2ecf20Sopenharmony_ci				"isochronous resource deallocation failed\n");
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci		r->allocated = false;
2328c2ecf20Sopenharmony_ci	}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	mutex_unlock(&r->mutex);
2358c2ecf20Sopenharmony_ci}
2368c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fw_iso_resources_free);
237