aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
authorMark Brown <broonie@kernel.org>2026-05-29 22:42:33 +0100
committerMark Brown <broonie@kernel.org>2026-05-29 22:42:33 +0100
commita774f9abf146d5fc76baa6e6ce9d63f4b0da3696 (patch)
treed12da6b6b07e359e4023143673f8dc47dc393e80 /drivers
parent769e39f484145599bb4a2757165a019a60d27823 (diff)
parent21e163988c87219b3973efe9ca934b7acf0e4fe8 (diff)
downloadlinux-next-history-a774f9abf146d5fc76baa6e6ce9d63f4b0da3696.tar.gz
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394.git
Diffstat (limited to 'drivers')
-rw-r--r--drivers/firewire/core-cdev.c361
1 files changed, 219 insertions, 142 deletions
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c
index f791db4c8dfff..e49d8a58be09e 100644
--- a/drivers/firewire/core-cdev.c
+++ b/drivers/firewire/core-cdev.c
@@ -128,19 +128,37 @@ struct descriptor_resource {
u32 data[];
};
-struct iso_resource {
+struct iso_resource_params {
+ u64 channels_mask;
+ s32 bandwidth;
+};
+
+struct iso_resource_auto {
struct client_resource resource;
struct client *client;
/* Schedule work and access todo only with client->lock held. */
struct delayed_work work;
- enum {ISO_RES_ALLOC, ISO_RES_REALLOC, ISO_RES_DEALLOC,
- ISO_RES_ALLOC_ONCE, ISO_RES_DEALLOC_ONCE,} todo;
+ enum {
+ ISO_RES_AUTO_ALLOC,
+ ISO_RES_AUTO_REALLOC,
+ ISO_RES_AUTO_DEALLOC,
+ } todo;
int generation;
- u64 channels;
- s32 bandwidth;
+ struct iso_resource_params params;
struct iso_resource_event *e_alloc, *e_dealloc;
};
+struct iso_resource_once {
+ struct client *client;
+ struct work_struct work;
+ enum {
+ ISO_RES_ONCE_ALLOC,
+ ISO_RES_ONCE_DEALLOC,
+ } todo;
+ struct iso_resource_params params;
+ struct iso_resource_event *event;
+};
+
static struct address_handler_resource *to_address_handler_resource(struct client_resource *resource)
{
return container_of(resource, struct address_handler_resource, resource);
@@ -156,16 +174,16 @@ static struct descriptor_resource *to_descriptor_resource(struct client_resource
return container_of(resource, struct descriptor_resource, resource);
}
-static struct iso_resource *to_iso_resource(struct client_resource *resource)
+static struct iso_resource_auto *to_iso_resource_auto(struct client_resource *resource)
{
- return container_of(resource, struct iso_resource, resource);
+ return container_of(resource, struct iso_resource_auto, resource);
}
-static void release_iso_resource(struct client *, struct client_resource *);
+static void release_iso_resource_auto(struct client *, struct client_resource *);
-static int is_iso_resource(const struct client_resource *resource)
+static int is_iso_resource_auto(const struct client_resource *resource)
{
- return resource->release == release_iso_resource;
+ return resource->release == release_iso_resource_auto;
}
static void release_transaction(struct client *client,
@@ -176,7 +194,7 @@ static int is_outbound_transaction_resource(const struct client_resource *resour
return resource->release == release_transaction;
}
-static void schedule_iso_resource(struct iso_resource *r, unsigned long delay)
+static void schedule_iso_resource_auto(struct iso_resource_auto *r, unsigned long delay)
{
client_get(r->client);
if (!queue_delayed_work(fw_workqueue, &r->work, delay))
@@ -424,8 +442,8 @@ static void queue_bus_reset_event(struct client *client)
guard(spinlock_irq)(&client->lock);
xa_for_each(&client->resource_xa, index, resource) {
- if (is_iso_resource(resource))
- schedule_iso_resource(to_iso_resource(resource), 0);
+ if (is_iso_resource_auto(resource))
+ schedule_iso_resource_auto(to_iso_resource_auto(resource), 0);
}
}
@@ -507,31 +525,28 @@ static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
static int add_client_resource(struct client *client, struct client_resource *resource,
gfp_t gfp_mask)
{
- int ret;
-
scoped_guard(spinlock_irqsave, &client->lock) {
u32 index;
+ int ret;
+
+ if (client->in_shutdown)
+ return -ECANCELED;
- if (client->in_shutdown) {
- ret = -ECANCELED;
+ if (gfpflags_allow_blocking(gfp_mask)) {
+ ret = xa_alloc(&client->resource_xa, &index, resource, xa_limit_32b,
+ GFP_NOWAIT);
} else {
- if (gfpflags_allow_blocking(gfp_mask)) {
- ret = xa_alloc(&client->resource_xa, &index, resource, xa_limit_32b,
- GFP_NOWAIT);
- } else {
- ret = xa_alloc_bh(&client->resource_xa, &index, resource,
- xa_limit_32b, GFP_NOWAIT);
- }
- }
- if (ret >= 0) {
- resource->handle = index;
- client_get(client);
- if (is_iso_resource(resource))
- schedule_iso_resource(to_iso_resource(resource), 0);
+ ret = xa_alloc_bh(&client->resource_xa, &index, resource,
+ xa_limit_32b, GFP_NOWAIT);
}
+ if (ret < 0)
+ return ret;
+
+ resource->handle = index;
+ client_get(client);
}
- return ret < 0 ? ret : 0;
+ return 0;
}
static int release_client_resource(struct client *client, u32 handle,
@@ -1293,83 +1308,106 @@ static int ioctl_get_cycle_timer(struct client *client, union ioctl_arg *arg)
return 0;
}
-static void iso_resource_work(struct work_struct *work)
+static int fill_iso_resource_params(struct iso_resource_params *params,
+ struct fw_cdev_allocate_iso_resource *request)
+{
+ if ((request->channels == 0 && request->bandwidth == 0) ||
+ request->bandwidth > BANDWIDTH_AVAILABLE_INITIAL)
+ return -EINVAL;
+
+ params->channels_mask = request->channels;
+ params->bandwidth = request->bandwidth;
+
+ return 0;
+}
+
+static void iso_resource_auto_work(struct work_struct *work)
{
struct iso_resource_event *e;
- struct iso_resource *r = from_work(r, work, work.work);
+ struct iso_resource_auto *r = from_work(r, work, work.work);
struct client *client = r->client;
unsigned long index = r->resource.handle;
- int generation, channel, bandwidth, todo;
- bool skip, free, success;
+ int current_generation, resource_generation, channel, bandwidth, todo;
+ u64 reset_jiffies;
+ bool free;
scoped_guard(spinlock_irq, &client->lock) {
- generation = client->device->generation;
+ reset_jiffies = client->device->card->reset_jiffies;
+ current_generation = client->device->generation;
+ resource_generation = r->generation;
+ r->generation = current_generation;
todo = r->todo;
+ }
+
+ switch (todo) {
+ case ISO_RES_AUTO_ALLOC:
// Allow 1000ms grace period for other reallocations.
- if (todo == ISO_RES_ALLOC &&
- time_is_after_jiffies64(client->device->card->reset_jiffies + secs_to_jiffies(1))) {
- schedule_iso_resource(r, msecs_to_jiffies(333));
- skip = true;
- } else {
- // We could be called twice within the same generation.
- skip = todo == ISO_RES_REALLOC &&
- r->generation == generation;
+ if (time_is_after_jiffies64(reset_jiffies + secs_to_jiffies(1))) {
+ schedule_iso_resource_auto(r, msecs_to_jiffies(333));
+ goto out;
}
- free = todo == ISO_RES_DEALLOC ||
- todo == ISO_RES_ALLOC_ONCE ||
- todo == ISO_RES_DEALLOC_ONCE;
- r->generation = generation;
+ break;
+ case ISO_RES_AUTO_REALLOC:
+ // We could be called twice within the same generation.
+ if (resource_generation == current_generation)
+ goto out;
+ break;
+ case ISO_RES_AUTO_DEALLOC:
+ default:
+ break;
}
- if (skip)
- goto out;
+ bandwidth = r->params.bandwidth;
- bandwidth = r->bandwidth;
+ fw_iso_resource_manage(client->device->card, current_generation, r->params.channels_mask,
+ &channel, &bandwidth, todo != ISO_RES_AUTO_DEALLOC);
- fw_iso_resource_manage(client->device->card, generation,
- r->channels, &channel, &bandwidth,
- todo == ISO_RES_ALLOC ||
- todo == ISO_RES_REALLOC ||
- todo == ISO_RES_ALLOC_ONCE);
- /*
- * Is this generation outdated already? As long as this resource sticks
- * in the xarray, it will be scheduled again for a newer generation or at
- * shutdown.
- */
- if (channel == -EAGAIN &&
- (todo == ISO_RES_ALLOC || todo == ISO_RES_REALLOC))
- goto out;
+ if (todo == ISO_RES_AUTO_DEALLOC) {
+ free = true;
+ e = r->e_dealloc;
+ r->e_dealloc = NULL;
+ } else {
+ free = false;
- success = channel >= 0 || bandwidth > 0;
+ // Is this generation outdated already? As long as this resource sticks in the
+ // xarray, it will be scheduled again for a newer generation or at shutdown.
+ if (channel == -EAGAIN)
+ goto out;
- scoped_guard(spinlock_irq, &client->lock) {
- // Transit from allocation to reallocation, except if the client
- // requested deallocation in the meantime.
- if (r->todo == ISO_RES_ALLOC)
- r->todo = ISO_RES_REALLOC;
- // Allocation or reallocation failure? Pull this resource out of the
- // xarray and prepare for deletion, unless the client is shutting down.
- if (r->todo == ISO_RES_REALLOC && !success &&
- !client->in_shutdown &&
- xa_erase(&client->resource_xa, index)) {
- client_put(client);
- free = true;
+ bool success = channel >= 0 || bandwidth > 0;
+
+ if (!success) {
+ // Allocation or reallocation failure? Pull this resource out of the
+ // xarray and prepare for deletion, unless the client is shutting down.
+ scoped_guard(spinlock_irq, &client->lock) {
+ if (!client->in_shutdown && xa_erase(&client->resource_xa, index)) {
+ client_put(client);
+ free = true;
+ }
+ }
}
- }
- if (todo == ISO_RES_ALLOC && channel >= 0)
- r->channels = 1ULL << channel;
+ if (todo == ISO_RES_AUTO_REALLOC) {
+ if (success)
+ goto out;
- if (todo == ISO_RES_REALLOC && success)
- goto out;
+ // Notify the userspace client of the failure through a deallocation event.
+ e = r->e_dealloc;
+ r->e_dealloc = NULL;
+ } else {
+ // Transit from allocation to reallocation, except if the client requested
+ // deallocation in the meantime.
+ scoped_guard(spinlock_irq, &client->lock)
+ r->todo = ISO_RES_AUTO_REALLOC;
- if (todo == ISO_RES_ALLOC || todo == ISO_RES_ALLOC_ONCE) {
- e = r->e_alloc;
- r->e_alloc = NULL;
- } else {
- e = r->e_dealloc;
- r->e_dealloc = NULL;
+ if (channel >= 0)
+ r->params.channels_mask = BIT_ULL(channel);
+
+ e = r->e_alloc;
+ r->e_alloc = NULL;
+ }
}
+
e->iso_resource.handle = r->resource.handle;
e->iso_resource.channel = channel;
e->iso_resource.bandwidth = bandwidth;
@@ -1387,97 +1425,136 @@ static void iso_resource_work(struct work_struct *work)
client_put(client);
}
-static void release_iso_resource(struct client *client,
- struct client_resource *resource)
+static void release_iso_resource_auto(struct client *client, struct client_resource *resource)
{
- struct iso_resource *r = to_iso_resource(resource);
+ struct iso_resource_auto *r = to_iso_resource_auto(resource);
guard(spinlock_irq)(&client->lock);
- r->todo = ISO_RES_DEALLOC;
- schedule_iso_resource(r, 0);
+ r->todo = ISO_RES_AUTO_DEALLOC;
+ schedule_iso_resource_auto(r, 0);
}
-static int init_iso_resource(struct client *client,
- struct fw_cdev_allocate_iso_resource *request, int todo)
+static int ioctl_allocate_iso_resource(struct client *client, union ioctl_arg *arg)
{
- struct iso_resource_event *e1, *e2;
- struct iso_resource *r;
- int ret;
+ struct fw_cdev_allocate_iso_resource *request = &arg->allocate_iso_resource;
+ struct iso_resource_event *e1 __free(kfree) = kmalloc_obj(*e1);
+ struct iso_resource_event *e2 __free(kfree) = kmalloc_obj(*e2);
+ struct iso_resource_auto *r __free(kfree) = kmalloc_obj(*r);
+ int err;
- if ((request->channels == 0 && request->bandwidth == 0) ||
- request->bandwidth > BANDWIDTH_AVAILABLE_INITIAL)
- return -EINVAL;
+ if (!r || !e1 || !e2)
+ return -ENOMEM;
- r = kmalloc_obj(*r);
- e1 = kmalloc_obj(*e1);
- e2 = kmalloc_obj(*e2);
- if (r == NULL || e1 == NULL || e2 == NULL) {
- ret = -ENOMEM;
- goto fail;
- }
+ err = fill_iso_resource_params(&r->params, request);
+ if (err < 0)
+ return err;
- INIT_DELAYED_WORK(&r->work, iso_resource_work);
+ INIT_DELAYED_WORK(&r->work, iso_resource_auto_work);
r->client = client;
- r->todo = todo;
- r->generation = -1;
- r->channels = request->channels;
- r->bandwidth = request->bandwidth;
+ r->todo = ISO_RES_AUTO_ALLOC;
r->e_alloc = e1;
r->e_dealloc = e2;
e1->iso_resource.closure = request->closure;
- e1->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED;
+ e1->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED;
e2->iso_resource.closure = request->closure;
- e2->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED;
+ e2->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED;
- if (todo == ISO_RES_ALLOC) {
- r->resource.release = release_iso_resource;
- ret = add_client_resource(client, &r->resource, GFP_KERNEL);
- if (ret < 0)
- goto fail;
- } else {
- r->resource.release = NULL;
- r->resource.handle = -1;
- schedule_iso_resource(r, 0);
- }
+ r->resource.release = release_iso_resource_auto;
+ err = add_client_resource(client, &r->resource, GFP_KERNEL);
+ if (err < 0)
+ return err;
request->handle = r->resource.handle;
+ retain_and_null_ptr(e1);
+ retain_and_null_ptr(e2);
+ schedule_iso_resource_auto(no_free_ptr(r), 0);
+
return 0;
- fail:
- kfree(r);
- kfree(e1);
- kfree(e2);
+}
- return ret;
+static int ioctl_deallocate_iso_resource(struct client *client,
+ union ioctl_arg *arg)
+{
+ return release_client_resource(client,
+ arg->deallocate.handle, release_iso_resource_auto, NULL);
}
-static int ioctl_allocate_iso_resource(struct client *client,
- union ioctl_arg *arg)
+#define UNAVAILABLE_HANDLE -1
+
+static void iso_resource_once_work(struct work_struct *work)
{
- return init_iso_resource(client,
- &arg->allocate_iso_resource, ISO_RES_ALLOC);
+ struct iso_resource_once *r = from_work(r, work, work);
+ struct client *client = r->client;
+ struct iso_resource_event *e = r->event;
+ int generation, channel, bandwidth;
+
+ scoped_guard(spinlock_irq, &client->lock)
+ generation = client->device->generation;
+
+ bandwidth = r->params.bandwidth;
+
+ fw_iso_resource_manage(client->device->card, generation, r->params.channels_mask, &channel,
+ &bandwidth, r->todo == ISO_RES_ONCE_ALLOC);
+
+ e->iso_resource.handle = UNAVAILABLE_HANDLE;
+ e->iso_resource.channel = channel;
+ e->iso_resource.bandwidth = bandwidth;
+
+ queue_event(client, &e->event, &e->iso_resource, sizeof(e->iso_resource), NULL, 0);
+
+ cancel_work(&r->work);
+ kfree(r);
+
+ client_put(client);
}
-static int ioctl_deallocate_iso_resource(struct client *client,
- union ioctl_arg *arg)
+static int init_iso_resource_once(struct client *client,
+ struct fw_cdev_allocate_iso_resource *request, int todo)
{
- return release_client_resource(client,
- arg->deallocate.handle, release_iso_resource, NULL);
+ struct iso_resource_event *e __free(kfree) = kmalloc_obj(*e);
+ struct iso_resource_once *r __free(kfree) = kmalloc_obj(*r);
+ int err;
+
+ if (!r || !e)
+ return -ENOMEM;
+
+ err = fill_iso_resource_params(&r->params, request);
+ if (err < 0)
+ return err;
+
+ INIT_WORK(&r->work, iso_resource_once_work);
+ r->client = client;
+ r->todo = todo;
+
+ if (todo == ISO_RES_ONCE_ALLOC)
+ e->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED;
+ else
+ e->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED;
+ e->iso_resource.closure = request->closure;
+ r->event = no_free_ptr(e);
+
+ // Keep the client until work item finishing.
+ client_get(r->client);
+
+ queue_work(fw_workqueue, &no_free_ptr(r)->work);
+
+ request->handle = UNAVAILABLE_HANDLE;
+
+ return 0;
}
static int ioctl_allocate_iso_resource_once(struct client *client,
union ioctl_arg *arg)
{
- return init_iso_resource(client,
- &arg->allocate_iso_resource, ISO_RES_ALLOC_ONCE);
+ return init_iso_resource_once(client, &arg->allocate_iso_resource, ISO_RES_ONCE_ALLOC);
}
static int ioctl_deallocate_iso_resource_once(struct client *client,
union ioctl_arg *arg)
{
- return init_iso_resource(client,
- &arg->allocate_iso_resource, ISO_RES_DEALLOC_ONCE);
+ return init_iso_resource_once(client, &arg->allocate_iso_resource, ISO_RES_ONCE_DEALLOC);
}
/*