| /* |
| * isochronous resources 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/jiffies.h> |
| #include <linux/mutex.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include "iso-resources.h" |
| |
| /** |
| * fw_iso_resources_init - initializes a &struct fw_iso_resources |
| * @r: the resource manager to initialize |
| * @unit: the device unit for which the resources will be needed |
| * |
| * If the device does not support all channel numbers, change @r->channels_mask |
| * after calling this function. |
| */ |
| int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit) |
| { |
| r->buffer = kmalloc(2 * 4, GFP_KERNEL); |
| if (!r->buffer) |
| return -ENOMEM; |
| |
| r->channels_mask = ~0uLL; |
| r->unit = fw_unit_get(unit); |
| mutex_init(&r->mutex); |
| r->allocated = false; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(fw_iso_resources_init); |
| |
| /** |
| * fw_iso_resources_destroy - destroy a resource manager |
| * @r: the resource manager that is no longer needed |
| */ |
| void fw_iso_resources_destroy(struct fw_iso_resources *r) |
| { |
| WARN_ON(r->allocated); |
| kfree(r->buffer); |
| mutex_destroy(&r->mutex); |
| fw_unit_put(r->unit); |
| } |
| EXPORT_SYMBOL(fw_iso_resources_destroy); |
| |
| static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed) |
| { |
| unsigned int bytes, s400_bytes; |
| |
| /* iso packets have three header quadlets and quadlet-aligned payload */ |
| bytes = 3 * 4 + ALIGN(max_payload_bytes, 4); |
| |
| /* convert to bandwidth units (quadlets at S1600 = bytes at S400) */ |
| if (speed <= SCODE_400) |
| s400_bytes = bytes * (1 << (SCODE_400 - speed)); |
| else |
| s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400)); |
| |
| return s400_bytes; |
| } |
| |
| static int current_bandwidth_overhead(struct fw_card *card) |
| { |
| /* |
| * Under the usual pessimistic assumption (cable length 4.5 m), the |
| * isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or |
| * 88.3 + N * 24.3 in bandwidth units. |
| * |
| * The calculation below tries to deduce N from the current gap count. |
| * If the gap count has been optimized by measuring the actual packet |
| * transmission time, this derived overhead should be near the actual |
| * overhead as well. |
| */ |
| return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512; |
| } |
| |
| static int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card) |
| { |
| for (;;) { |
| s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64(); |
| if (delay <= 0) |
| return 0; |
| if (schedule_timeout_interruptible(delay) > 0) |
| return -ERESTARTSYS; |
| } |
| } |
| |
| /** |
| * fw_iso_resources_allocate - allocate isochronous channel and bandwidth |
| * @r: the resource manager |
| * @max_payload_bytes: the amount of data (including CIP headers) per packet |
| * @speed: the speed (e.g., SCODE_400) at which the packets will be sent |
| * |
| * This function allocates one isochronous channel and enough bandwidth for the |
| * specified packet size. |
| * |
| * Returns the channel number that the caller must use for streaming, or |
| * a negative error code. Due to potentionally long delays, this function is |
| * interruptible and can return -ERESTARTSYS. On success, the caller is |
| * responsible for calling fw_iso_resources_update() on bus resets, and |
| * fw_iso_resources_free() when the resources are not longer needed. |
| */ |
| int fw_iso_resources_allocate(struct fw_iso_resources *r, |
| unsigned int max_payload_bytes, int speed) |
| { |
| struct fw_card *card = fw_parent_device(r->unit)->card; |
| int bandwidth, channel, err; |
| |
| if (WARN_ON(r->allocated)) |
| return -EBADFD; |
| |
| r->bandwidth = packet_bandwidth(max_payload_bytes, speed); |
| |
| retry_after_bus_reset: |
| spin_lock_irq(&card->lock); |
| r->generation = card->generation; |
| r->bandwidth_overhead = current_bandwidth_overhead(card); |
| spin_unlock_irq(&card->lock); |
| |
| err = wait_isoch_resource_delay_after_bus_reset(card); |
| if (err < 0) |
| return err; |
| |
| mutex_lock(&r->mutex); |
| |
| bandwidth = r->bandwidth + r->bandwidth_overhead; |
| fw_iso_resource_manage(card, r->generation, r->channels_mask, |
| &channel, &bandwidth, true, r->buffer); |
| if (channel == -EAGAIN) { |
| mutex_unlock(&r->mutex); |
| goto retry_after_bus_reset; |
| } |
| if (channel >= 0) { |
| r->channel = channel; |
| r->allocated = true; |
| } else { |
| if (channel == -EBUSY) |
| dev_err(&r->unit->device, |
| "isochronous resources exhausted\n"); |
| else |
| dev_err(&r->unit->device, |
| "isochronous resource allocation failed\n"); |
| } |
| |
| mutex_unlock(&r->mutex); |
| |
| return channel; |
| } |
| EXPORT_SYMBOL(fw_iso_resources_allocate); |
| |
| /** |
| * fw_iso_resources_update - update resource allocations after a bus reset |
| * @r: the resource manager |
| * |
| * This function must be called from the driver's .update handler to reallocate |
| * any resources that were allocated before the bus reset. It is safe to call |
| * this function if no resources are currently allocated. |
| * |
| * Returns a negative error code on failure. If this happens, the caller must |
| * stop streaming. |
| */ |
| int fw_iso_resources_update(struct fw_iso_resources *r) |
| { |
| struct fw_card *card = fw_parent_device(r->unit)->card; |
| int bandwidth, channel; |
| |
| mutex_lock(&r->mutex); |
| |
| if (!r->allocated) { |
| mutex_unlock(&r->mutex); |
| return 0; |
| } |
| |
| spin_lock_irq(&card->lock); |
| r->generation = card->generation; |
| r->bandwidth_overhead = current_bandwidth_overhead(card); |
| spin_unlock_irq(&card->lock); |
| |
| bandwidth = r->bandwidth + r->bandwidth_overhead; |
| |
| fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, |
| &channel, &bandwidth, true, r->buffer); |
| /* |
| * When another bus reset happens, pretend that the allocation |
| * succeeded; we will try again for the new generation later. |
| */ |
| if (channel < 0 && channel != -EAGAIN) { |
| r->allocated = false; |
| if (channel == -EBUSY) |
| dev_err(&r->unit->device, |
| "isochronous resources exhausted\n"); |
| else |
| dev_err(&r->unit->device, |
| "isochronous resource allocation failed\n"); |
| } |
| |
| mutex_unlock(&r->mutex); |
| |
| return channel; |
| } |
| EXPORT_SYMBOL(fw_iso_resources_update); |
| |
| /** |
| * fw_iso_resources_free - frees allocated resources |
| * @r: the resource manager |
| * |
| * This function deallocates the channel and bandwidth, if allocated. |
| */ |
| void fw_iso_resources_free(struct fw_iso_resources *r) |
| { |
| struct fw_card *card = fw_parent_device(r->unit)->card; |
| int bandwidth, channel; |
| |
| mutex_lock(&r->mutex); |
| |
| if (r->allocated) { |
| bandwidth = r->bandwidth + r->bandwidth_overhead; |
| fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, |
| &channel, &bandwidth, false, r->buffer); |
| if (channel < 0) |
| dev_err(&r->unit->device, |
| "isochronous resource deallocation failed\n"); |
| |
| r->allocated = false; |
| } |
| |
| mutex_unlock(&r->mutex); |
| } |
| EXPORT_SYMBOL(fw_iso_resources_free); |