1 | /* |
2 | * Copyright 2013 Red Hat Inc. |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice shall be included in |
12 | * all copies or substantial portions of the Software. |
13 | * |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
20 | * OTHER DEALINGS IN THE SOFTWARE. |
21 | * |
22 | * Authors: Dave Airlie |
23 | * Alon Levy |
24 | */ |
25 | |
26 | #include <linux/gfp.h> |
27 | #include <linux/slab.h> |
28 | |
29 | #include "qxl_drv.h" |
30 | #include "qxl_object.h" |
31 | |
32 | static int |
33 | qxl_allocate_chunk(struct qxl_device *qdev, |
34 | struct qxl_release *release, |
35 | struct qxl_drm_image *image, |
36 | unsigned int chunk_size) |
37 | { |
38 | struct qxl_drm_chunk *chunk; |
39 | int ret; |
40 | |
41 | chunk = kmalloc(size: sizeof(struct qxl_drm_chunk), GFP_KERNEL); |
42 | if (!chunk) |
43 | return -ENOMEM; |
44 | |
45 | ret = qxl_alloc_bo_reserved(qdev, release, size: chunk_size, bo: &chunk->bo); |
46 | if (ret) { |
47 | kfree(objp: chunk); |
48 | return ret; |
49 | } |
50 | |
51 | list_add_tail(new: &chunk->head, head: &image->chunk_list); |
52 | return 0; |
53 | } |
54 | |
55 | int |
56 | qxl_image_alloc_objects(struct qxl_device *qdev, |
57 | struct qxl_release *release, |
58 | struct qxl_drm_image **image_ptr, |
59 | int height, int stride) |
60 | { |
61 | struct qxl_drm_image *image; |
62 | int ret; |
63 | |
64 | image = kmalloc(size: sizeof(struct qxl_drm_image), GFP_KERNEL); |
65 | if (!image) |
66 | return -ENOMEM; |
67 | |
68 | INIT_LIST_HEAD(list: &image->chunk_list); |
69 | |
70 | ret = qxl_alloc_bo_reserved(qdev, release, size: sizeof(struct qxl_image), bo: &image->bo); |
71 | if (ret) { |
72 | kfree(objp: image); |
73 | return ret; |
74 | } |
75 | |
76 | ret = qxl_allocate_chunk(qdev, release, image, chunk_size: sizeof(struct qxl_data_chunk) + stride * height); |
77 | if (ret) { |
78 | qxl_bo_unref(bo: &image->bo); |
79 | kfree(objp: image); |
80 | return ret; |
81 | } |
82 | *image_ptr = image; |
83 | return 0; |
84 | } |
85 | |
86 | void qxl_image_free_objects(struct qxl_device *qdev, struct qxl_drm_image *dimage) |
87 | { |
88 | struct qxl_drm_chunk *chunk, *tmp; |
89 | |
90 | list_for_each_entry_safe(chunk, tmp, &dimage->chunk_list, head) { |
91 | qxl_bo_unref(bo: &chunk->bo); |
92 | kfree(objp: chunk); |
93 | } |
94 | |
95 | qxl_bo_unref(bo: &dimage->bo); |
96 | kfree(objp: dimage); |
97 | } |
98 | |
99 | static int |
100 | qxl_image_init_helper(struct qxl_device *qdev, |
101 | struct qxl_release *release, |
102 | struct qxl_drm_image *dimage, |
103 | const uint8_t *data, |
104 | int width, int height, |
105 | int depth, unsigned int hash, |
106 | int stride) |
107 | { |
108 | struct qxl_drm_chunk *drv_chunk; |
109 | struct qxl_image *image; |
110 | struct qxl_data_chunk *chunk; |
111 | int i; |
112 | int chunk_stride; |
113 | int linesize = width * depth / 8; |
114 | struct qxl_bo *chunk_bo, *image_bo; |
115 | void *ptr; |
116 | /* Chunk */ |
117 | /* FIXME: Check integer overflow */ |
118 | /* TODO: variable number of chunks */ |
119 | |
120 | drv_chunk = list_first_entry(&dimage->chunk_list, struct qxl_drm_chunk, head); |
121 | |
122 | chunk_bo = drv_chunk->bo; |
123 | chunk_stride = stride; /* TODO: should use linesize, but it renders |
124 | wrong (check the bitmaps are sent correctly |
125 | first) */ |
126 | |
127 | ptr = qxl_bo_kmap_atomic_page(qdev, bo: chunk_bo, page_offset: 0); |
128 | chunk = ptr; |
129 | chunk->data_size = height * chunk_stride; |
130 | chunk->prev_chunk = 0; |
131 | chunk->next_chunk = 0; |
132 | qxl_bo_kunmap_atomic_page(qdev, bo: chunk_bo, map: ptr); |
133 | |
134 | { |
135 | void *k_data, *i_data; |
136 | int remain; |
137 | int page; |
138 | int size; |
139 | |
140 | if (stride == linesize && chunk_stride == stride) { |
141 | remain = linesize * height; |
142 | page = 0; |
143 | i_data = (void *)data; |
144 | |
145 | while (remain > 0) { |
146 | ptr = qxl_bo_kmap_atomic_page(qdev, bo: chunk_bo, page_offset: page << PAGE_SHIFT); |
147 | |
148 | if (page == 0) { |
149 | chunk = ptr; |
150 | k_data = chunk->data; |
151 | size = PAGE_SIZE - offsetof(struct qxl_data_chunk, data); |
152 | } else { |
153 | k_data = ptr; |
154 | size = PAGE_SIZE; |
155 | } |
156 | size = min(size, remain); |
157 | |
158 | memcpy(k_data, i_data, size); |
159 | |
160 | qxl_bo_kunmap_atomic_page(qdev, bo: chunk_bo, map: ptr); |
161 | i_data += size; |
162 | remain -= size; |
163 | page++; |
164 | } |
165 | } else { |
166 | unsigned int page_base, page_offset, out_offset; |
167 | |
168 | for (i = 0 ; i < height ; ++i) { |
169 | i_data = (void *)data + i * stride; |
170 | remain = linesize; |
171 | out_offset = offsetof(struct qxl_data_chunk, data) + i * chunk_stride; |
172 | |
173 | while (remain > 0) { |
174 | page_base = out_offset & PAGE_MASK; |
175 | page_offset = offset_in_page(out_offset); |
176 | size = min((int)(PAGE_SIZE - page_offset), remain); |
177 | |
178 | ptr = qxl_bo_kmap_atomic_page(qdev, bo: chunk_bo, page_offset: page_base); |
179 | k_data = ptr + page_offset; |
180 | memcpy(k_data, i_data, size); |
181 | qxl_bo_kunmap_atomic_page(qdev, bo: chunk_bo, map: ptr); |
182 | remain -= size; |
183 | i_data += size; |
184 | out_offset += size; |
185 | } |
186 | } |
187 | } |
188 | } |
189 | qxl_bo_vunmap_locked(bo: chunk_bo); |
190 | |
191 | image_bo = dimage->bo; |
192 | ptr = qxl_bo_kmap_atomic_page(qdev, bo: image_bo, page_offset: 0); |
193 | image = ptr; |
194 | |
195 | image->descriptor.id = 0; |
196 | image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP; |
197 | |
198 | image->descriptor.flags = 0; |
199 | image->descriptor.width = width; |
200 | image->descriptor.height = height; |
201 | |
202 | switch (depth) { |
203 | case 1: |
204 | /* TODO: BE? check by arch? */ |
205 | image->u.bitmap.format = SPICE_BITMAP_FMT_1BIT_BE; |
206 | break; |
207 | case 24: |
208 | image->u.bitmap.format = SPICE_BITMAP_FMT_24BIT; |
209 | break; |
210 | case 32: |
211 | image->u.bitmap.format = SPICE_BITMAP_FMT_32BIT; |
212 | break; |
213 | default: |
214 | DRM_ERROR("unsupported image bit depth\n" ); |
215 | qxl_bo_kunmap_atomic_page(qdev, bo: image_bo, map: ptr); |
216 | return -EINVAL; |
217 | } |
218 | image->u.bitmap.flags = QXL_BITMAP_TOP_DOWN; |
219 | image->u.bitmap.x = width; |
220 | image->u.bitmap.y = height; |
221 | image->u.bitmap.stride = chunk_stride; |
222 | image->u.bitmap.palette = 0; |
223 | image->u.bitmap.data = qxl_bo_physical_address(qdev, bo: chunk_bo, offset: 0); |
224 | |
225 | qxl_bo_kunmap_atomic_page(qdev, bo: image_bo, map: ptr); |
226 | |
227 | return 0; |
228 | } |
229 | |
230 | int qxl_image_init(struct qxl_device *qdev, |
231 | struct qxl_release *release, |
232 | struct qxl_drm_image *dimage, |
233 | const uint8_t *data, |
234 | int x, int y, int width, int height, |
235 | int depth, int stride) |
236 | { |
237 | data += y * stride + x * (depth / 8); |
238 | return qxl_image_init_helper(qdev, release, dimage, data, |
239 | width, height, depth, hash: 0, stride); |
240 | } |
241 | |