summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/msm/msm_gpu.c
blob: 673bf1db5ac7ebf99c9726b66df8e24d9733cd4d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*
 * Copyright (C) 2013 Red Hat
 * Author: Rob Clark <robdclark@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "msm_gpu.h"
#include "msm_gem.h"

/*
 * Cmdstream submission/retirement:
 */

static void retire_worker(struct work_struct *work)
{
	struct msm_gpu *gpu = container_of(work, struct msm_gpu, retire_work);
	struct drm_device *dev = gpu->dev;
	uint32_t fence = gpu->funcs->last_fence(gpu->gpu);

	mutex_lock(&dev->struct_mutex);

	while (!list_empty(&gpu->active_list)) {
		struct msm_gem_object *obj;

		obj = list_first_entry(&gpu->active_list,
				struct msm_gem_object, mm_list);

		if (obj->fence <= fence) {
			/* move to inactive: */
			msm_gem_move_to_inactive(&obj->base);
			msm_gem_put_iova(&obj->base, gpu->id);
			drm_gem_object_unreference(&obj->base);
		} else {
			break;
		}
	}

	msm_update_fence(gpu->dev, fence);

	mutex_unlock(&dev->struct_mutex);
}

/* call from irq handler to schedule work to retire bo's */
static void msm_gpu_retire(void *gem_priv)
{
	struct msm_gpu *gpu = gem_priv;
	struct msm_drm_private *priv = gpu->dev->dev_private;
	queue_work(priv->wq, &gpu->retire_work);
}

/* add bo's to gpu's ring, and kick gpu: */
int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
		struct msm_file_private *filp)
{
	struct drm_device *dev = gpu->dev;
	struct msm_drm_private *priv = dev->dev_private;
	int i, ret;

	mutex_lock(&dev->struct_mutex);

	submit->base.fence = ++priv->next_fence;

	ret = gpu->funcs->submit(gpu->gpu, &submit->base, &filp->ctx);

	for (i = 0; i < submit->nr_bos; i++) {
		struct msm_gem_object *msm_obj = submit->bos[i].obj;

		/* can't happen yet.. but when we add 2d support we'll have
		 * to deal w/ cross-ring synchronization:
		 */
		WARN_ON(is_active(msm_obj) && (msm_obj->gpu != gpu));

		if (!is_active(msm_obj)) {
			uint32_t iova;

			/* ring takes a reference to the bo and iova: */
			drm_gem_object_reference(&msm_obj->base);
			msm_gem_get_iova_locked(&msm_obj->base,
					submit->gpu->id, &iova);
		}

		msm_gem_move_to_active(&msm_obj->base, gpu, submit->base.fence);
	}

	mutex_unlock(&dev->struct_mutex);

	return ret;
}

static struct drm_gem_object *
gem_new(void *gem_priv, uint32_t size, uint32_t flags)
{
	struct msm_gpu *gpu = gem_priv;
	return msm_gem_new(gpu->dev, size, flags);
}

static int
gem_get_iova_locked(void *gem_priv, struct drm_gem_object *obj, uint32_t *iova)
{
	struct msm_gpu *gpu = gem_priv;
	return msm_gem_get_iova_locked(obj, gpu->id, iova);
}

static int
gem_get_iova(void *gem_priv, struct drm_gem_object *obj, uint32_t *iova)
{
	struct msm_gpu *gpu = gem_priv;
	return msm_gem_get_iova(obj, gpu->id, iova);
}

static void
gem_put_iova(void *gem_priv, struct drm_gem_object *obj)
{
	struct msm_gpu *gpu = gem_priv;
	return msm_gem_put_iova(obj, gpu->id);
}

static void *
gem_vaddr_locked(void *gem_priv, struct drm_gem_object *obj)
{
	return msm_gem_vaddr_locked(obj);
}

static void *
gem_vaddr(void *gem_priv, struct drm_gem_object *obj)
{
	return msm_gem_vaddr(obj);
}

/*
 * Init/Cleanup:
 */

static const char *iommu_ports[] = {
		"gfx3d_user", "gfx3d_priv",
		"gfx3d1_user", "gfx3d1_priv",
};

int msm_gpu_init(struct drm_device *drm, struct msm_gpu **pgpu)
{
	struct msm_gpu *gpu = *pgpu;
	int ret;

	gpu = kzalloc(sizeof(*gpu), GFP_KERNEL);
	if (!gpu)
		return -ENOMEM;

	gpu->dev = drm;

	INIT_LIST_HEAD(&gpu->active_list);
	INIT_WORK(&gpu->retire_work, retire_worker);

	/* init GEM funcs for this device */
	gpu->gem.priv 			= gpu;
	gpu->gem.gem_new		= gem_new;
	gpu->gem.gem_get_iova		= gem_get_iova;
	gpu->gem.gem_get_iova_locked	= gem_get_iova_locked;
	gpu->gem.gem_put_iova		= gem_put_iova;
	gpu->gem.gem_vaddr		= gem_vaddr;
	gpu->gem.gem_vaddr_locked	= gem_vaddr_locked;
	gpu->gem.gem_retire		= msm_gpu_retire;

	/* Setup IOMMU.. eventually we will (I think) do this once per context
	 * and have separate page tables per context.  For now, to keep things
	 * simple and to get something working, just use a single address space:
	 */
	gpu->iommu = iommu_domain_alloc(&platform_bus_type);
	if (!gpu->iommu) {
		dev_err(drm->dev, "failed to allocate IOMMU\n");
		ret = -ENOMEM;
		goto fail;
	}
	gpu->id = msm_register_iommu(drm, gpu->iommu);

	ret = msm_iommu_attach(drm, gpu->iommu,
			iommu_ports, ARRAY_SIZE(iommu_ports));
	if (ret)
		goto fail;

	return 0;

fail:
	return ret;
}

void msm_gpu_cleanup(struct msm_gpu *gpu)
{
	DBG("%s", adreno_get_name(gpu->gpu));

	WARN_ON(!list_empty(&gpu->active_list));

	if (gpu->iommu)
		iommu_domain_free(gpu->iommu);
}