Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8474b025 authored by Mikko Perttunen's avatar Mikko Perttunen Committed by Thierry Reding
Browse files

gpu: host1x: Refactor channel allocation code



This is largely a rewrite of the Host1x channel allocation code, bringing
several changes:

- The previous code could deadlock due to an interaction
  between the 'reflock' mutex and CDMA timeout handling.
  This gets rid of the mutex.
- Support for more than 32 channels, required for Tegra186
- General refactoring, including better encapsulation
  of channel ownership handling into channel.c

Signed-off-by: default avatarMikko Perttunen <mperttunen@nvidia.com>
Reviewed-by: default avatarDmitry Osipenko <digetx@gmail.com>
Tested-by: default avatarDmitry Osipenko <digetx@gmail.com>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 03f0de77
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ static int gr2d_init(struct host1x_client *client)

	client->syncpts[0] = host1x_syncpt_request(client->dev, flags);
	if (!client->syncpts[0]) {
		host1x_channel_free(gr2d->channel);
		host1x_channel_put(gr2d->channel);
		return -ENOMEM;
	}

@@ -57,7 +57,7 @@ static int gr2d_exit(struct host1x_client *client)
		return err;

	host1x_syncpt_free(client->syncpts[0]);
	host1x_channel_free(gr2d->channel);
	host1x_channel_put(gr2d->channel);

	return 0;
}
+2 −2
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ static int gr3d_init(struct host1x_client *client)

	client->syncpts[0] = host1x_syncpt_request(client->dev, flags);
	if (!client->syncpts[0]) {
		host1x_channel_free(gr3d->channel);
		host1x_channel_put(gr3d->channel);
		return -ENOMEM;
	}

@@ -67,7 +67,7 @@ static int gr3d_exit(struct host1x_client *client)
		return err;

	host1x_syncpt_free(client->syncpts[0]);
	host1x_channel_free(gr3d->channel);
	host1x_channel_put(gr3d->channel);

	return 0;
}
+2 −2
Original line number Diff line number Diff line
@@ -182,7 +182,7 @@ static int vic_init(struct host1x_client *client)
free_syncpt:
	host1x_syncpt_free(client->syncpts[0]);
free_channel:
	host1x_channel_free(vic->channel);
	host1x_channel_put(vic->channel);
detach_device:
	if (tegra->domain)
		iommu_detach_device(tegra->domain, vic->dev);
@@ -203,7 +203,7 @@ static int vic_exit(struct host1x_client *client)
		return err;

	host1x_syncpt_free(client->syncpts[0]);
	host1x_channel_free(vic->channel);
	host1x_channel_put(vic->channel);

	if (vic->domain) {
		iommu_detach_device(vic->domain, vic->dev);
+91 −56
Original line number Diff line number Diff line
@@ -24,19 +24,33 @@
#include "job.h"

/* Constructor for the host1x device list */
int host1x_channel_list_init(struct host1x *host)
int host1x_channel_list_init(struct host1x_channel_list *chlist,
			     unsigned int num_channels)
{
	INIT_LIST_HEAD(&host->chlist.list);
	mutex_init(&host->chlist_mutex);

	if (host->info->nb_channels > BITS_PER_LONG) {
		WARN(1, "host1x hardware has more channels than supported by the driver\n");
		return -ENOSYS;
	chlist->channels = kcalloc(num_channels, sizeof(struct host1x_channel),
				   GFP_KERNEL);
	if (!chlist->channels)
		return -ENOMEM;

	chlist->allocated_channels =
		kcalloc(BITS_TO_LONGS(num_channels), sizeof(unsigned long),
			GFP_KERNEL);
	if (!chlist->allocated_channels) {
		kfree(chlist->channels);
		return -ENOMEM;
	}

	bitmap_zero(chlist->allocated_channels, num_channels);

	return 0;
}

void host1x_channel_list_free(struct host1x_channel_list *chlist)
{
	kfree(chlist->allocated_channels);
	kfree(chlist->channels);
}

int host1x_job_submit(struct host1x_job *job)
{
	struct host1x *host = dev_get_drvdata(job->channel->dev->parent);
@@ -47,86 +61,107 @@ EXPORT_SYMBOL(host1x_job_submit);

struct host1x_channel *host1x_channel_get(struct host1x_channel *channel)
{
	int err = 0;

	mutex_lock(&channel->reflock);
	kref_get(&channel->refcount);

	if (channel->refcount == 0)
		err = host1x_cdma_init(&channel->cdma);
	return channel;
}
EXPORT_SYMBOL(host1x_channel_get);

	if (!err)
		channel->refcount++;
/**
 * host1x_channel_get_index() - Attempt to get channel reference by index
 * @host: Host1x device object
 * @index: Index of channel
 *
 * If channel number @index is currently allocated, increase its refcount
 * and return a pointer to it. Otherwise, return NULL.
 */
struct host1x_channel *host1x_channel_get_index(struct host1x *host,
						unsigned int index)
{
	struct host1x_channel *ch = &host->channel_list.channels[index];

	mutex_unlock(&channel->reflock);
	if (!kref_get_unless_zero(&ch->refcount))
		return NULL;

	return err ? NULL : channel;
	return ch;
}
EXPORT_SYMBOL(host1x_channel_get);

void host1x_channel_put(struct host1x_channel *channel)
static void release_channel(struct kref *kref)
{
	mutex_lock(&channel->reflock);

	if (channel->refcount == 1) {
	struct host1x_channel *channel =
		container_of(kref, struct host1x_channel, refcount);
	struct host1x *host = dev_get_drvdata(channel->dev->parent);
	struct host1x_channel_list *chlist = &host->channel_list;

	host1x_hw_cdma_stop(host, &channel->cdma);
	host1x_cdma_deinit(&channel->cdma);
	}

	channel->refcount--;
	clear_bit(channel->id, chlist->allocated_channels);
}

	mutex_unlock(&channel->reflock);
void host1x_channel_put(struct host1x_channel *channel)
{
	kref_put(&channel->refcount, release_channel);
}
EXPORT_SYMBOL(host1x_channel_put);

static struct host1x_channel *acquire_unused_channel(struct host1x *host)
{
	struct host1x_channel_list *chlist = &host->channel_list;
	unsigned int max_channels = host->info->nb_channels;
	unsigned int index;

	index = find_first_zero_bit(chlist->allocated_channels, max_channels);
	if (index >= max_channels) {
		dev_err(host->dev, "failed to find free channel\n");
		return NULL;
	}

	chlist->channels[index].id = index;

	set_bit(index, chlist->allocated_channels);

	return &chlist->channels[index];
}

/**
 * host1x_channel_request() - Allocate a channel
 * @device: Host1x unit this channel will be used to send commands to
 *
 * Allocates a new host1x channel for @device. If there are no free channels,
 * this will sleep until one becomes available. May return NULL if CDMA
 * initialization fails.
 */
struct host1x_channel *host1x_channel_request(struct device *dev)
{
	struct host1x *host = dev_get_drvdata(dev->parent);
	unsigned int max_channels = host->info->nb_channels;
	struct host1x_channel *channel = NULL;
	unsigned long index;
	struct host1x_channel_list *chlist = &host->channel_list;
	struct host1x_channel *channel;
	int err;

	mutex_lock(&host->chlist_mutex);
	channel = acquire_unused_channel(host);
	if (!channel)
		return NULL;

	index = find_first_zero_bit(&host->allocated_channels, max_channels);
	if (index >= max_channels)
		goto fail;
	kref_init(&channel->refcount);
	mutex_init(&channel->submitlock);
	channel->dev = dev;

	channel = kzalloc(sizeof(*channel), GFP_KERNEL);
	if (!channel)
	err = host1x_hw_channel_init(host, channel, channel->id);
	if (err < 0)
		goto fail;

	err = host1x_hw_channel_init(host, channel, index);
	err = host1x_cdma_init(&channel->cdma);
	if (err < 0)
		goto fail;

	/* Link device to host1x_channel */
	channel->dev = dev;

	/* Add to channel list */
	list_add_tail(&channel->list, &host->chlist.list);

	host->allocated_channels |= BIT(index);

	mutex_unlock(&host->chlist_mutex);
	return channel;

fail:
	dev_err(dev, "failed to init channel\n");
	kfree(channel);
	mutex_unlock(&host->chlist_mutex);
	return NULL;
}
EXPORT_SYMBOL(host1x_channel_request);
	clear_bit(channel->id, chlist->allocated_channels);

void host1x_channel_free(struct host1x_channel *channel)
{
	struct host1x *host = dev_get_drvdata(channel->dev->parent);
	dev_err(dev, "failed to initialize channel\n");

	host->allocated_channels &= ~BIT(channel->id);
	list_del(&channel->list);
	kfree(channel);
	return NULL;
}
EXPORT_SYMBOL(host1x_channel_free);
EXPORT_SYMBOL(host1x_channel_request);
+13 −8
Original line number Diff line number Diff line
@@ -20,17 +20,21 @@
#define __HOST1X_CHANNEL_H

#include <linux/io.h>
#include <linux/kref.h>

#include "cdma.h"

struct host1x;
struct host1x_channel;

struct host1x_channel {
	struct list_head list;
struct host1x_channel_list {
	struct host1x_channel *channels;
	unsigned long *allocated_channels;
};

	unsigned int refcount;
struct host1x_channel {
	struct kref refcount;
	unsigned int id;
	struct mutex reflock;
	struct mutex submitlock;
	void __iomem *regs;
	struct device *dev;
@@ -38,9 +42,10 @@ struct host1x_channel {
};

/* channel list operations */
int host1x_channel_list_init(struct host1x *host);

#define host1x_for_each_channel(host, channel)				\
	list_for_each_entry(channel, &host->chlist.list, list)
int host1x_channel_list_init(struct host1x_channel_list *chlist,
			     unsigned int num_channels);
void host1x_channel_list_free(struct host1x_channel_list *chlist);
struct host1x_channel *host1x_channel_get_index(struct host1x *host,
						unsigned int index);

#endif
Loading