]>
Commit | Line | Data |
---|---|---|
579cea5f ZW |
1 | /* |
2 | * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. | |
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 (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
21 | * SOFTWARE. | |
22 | * | |
23 | * Authors: | |
24 | * Zhi Wang <zhi.a.wang@intel.com> | |
25 | * | |
26 | * Contributors: | |
27 | * Changbin Du <changbin.du@intel.com> | |
28 | * | |
29 | */ | |
30 | ||
31 | #include <linux/firmware.h> | |
32 | #include <linux/crc32.h> | |
33 | ||
34 | #include "i915_drv.h" | |
feddf6e8 ZW |
35 | #include "gvt.h" |
36 | #include "i915_pvinfo.h" | |
579cea5f ZW |
37 | |
38 | #define FIRMWARE_VERSION (0x0) | |
39 | ||
40 | struct gvt_firmware_header { | |
41 | u64 magic; | |
42 | u32 crc32; /* protect the data after this field */ | |
43 | u32 version; | |
44 | u64 cfg_space_size; | |
45 | u64 cfg_space_offset; /* offset in the file */ | |
46 | u64 mmio_size; | |
47 | u64 mmio_offset; /* offset in the file */ | |
48 | unsigned char data[1]; | |
49 | }; | |
50 | ||
579cea5f ZW |
51 | #define dev_to_drm_minor(d) dev_get_drvdata((d)) |
52 | ||
53 | static ssize_t | |
54 | gvt_firmware_read(struct file *filp, struct kobject *kobj, | |
55 | struct bin_attribute *attr, char *buf, | |
56 | loff_t offset, size_t count) | |
57 | { | |
58 | memcpy(buf, attr->private + offset, count); | |
59 | return count; | |
60 | } | |
61 | ||
62 | static struct bin_attribute firmware_attr = { | |
63 | .attr = {.name = "gvt_firmware", .mode = (S_IRUSR)}, | |
64 | .read = gvt_firmware_read, | |
65 | .write = NULL, | |
66 | .mmap = NULL, | |
67 | }; | |
68 | ||
14473025 | 69 | static int expose_firmware_sysfs(struct intel_gvt *gvt) |
579cea5f | 70 | { |
14473025 | 71 | struct drm_i915_private *dev_priv = gvt->dev_priv; |
579cea5f ZW |
72 | struct intel_gvt_device_info *info = &gvt->device_info; |
73 | struct pci_dev *pdev = gvt->dev_priv->drm.pdev; | |
74 | struct intel_gvt_mmio_info *e; | |
02b6ed44 TZ |
75 | struct gvt_mmio_block *block = gvt->mmio.mmio_block; |
76 | int num = gvt->mmio.num_mmio_block; | |
579cea5f ZW |
77 | struct gvt_firmware_header *h; |
78 | void *firmware; | |
79 | void *p; | |
aa4ce449 | 80 | unsigned long size, crc32_start; |
02b6ed44 | 81 | int i, j; |
579cea5f ZW |
82 | int ret; |
83 | ||
aa4ce449 | 84 | size = sizeof(*h) + info->mmio_size + info->cfg_space_size; |
ec162aa8 | 85 | firmware = vzalloc(size); |
579cea5f ZW |
86 | if (!firmware) |
87 | return -ENOMEM; | |
88 | ||
89 | h = firmware; | |
90 | ||
91 | h->magic = VGT_MAGIC; | |
92 | h->version = FIRMWARE_VERSION; | |
93 | h->cfg_space_size = info->cfg_space_size; | |
94 | h->cfg_space_offset = offsetof(struct gvt_firmware_header, data); | |
95 | h->mmio_size = info->mmio_size; | |
96 | h->mmio_offset = h->cfg_space_offset + h->cfg_space_size; | |
97 | ||
98 | p = firmware + h->cfg_space_offset; | |
99 | ||
100 | for (i = 0; i < h->cfg_space_size; i += 4) | |
101 | pci_read_config_dword(pdev, i, p + i); | |
102 | ||
103 | memcpy(gvt->firmware.cfg_space, p, info->cfg_space_size); | |
104 | ||
105 | p = firmware + h->mmio_offset; | |
106 | ||
d8d94ba3 CD |
107 | hash_for_each(gvt->mmio.mmio_info_table, i, e, node) |
108 | *(u32 *)(p + e->offset) = I915_READ_NOTRACE(_MMIO(e->offset)); | |
579cea5f | 109 | |
02b6ed44 TZ |
110 | for (i = 0; i < num; i++, block++) { |
111 | for (j = 0; j < block->size; j += 4) | |
112 | *(u32 *)(p + INTEL_GVT_MMIO_OFFSET(block->offset) + j) = | |
113 | I915_READ_NOTRACE(_MMIO(INTEL_GVT_MMIO_OFFSET( | |
114 | block->offset) + j)); | |
115 | } | |
116 | ||
579cea5f ZW |
117 | memcpy(gvt->firmware.mmio, p, info->mmio_size); |
118 | ||
aa4ce449 ZW |
119 | crc32_start = offsetof(struct gvt_firmware_header, crc32) + 4; |
120 | h->crc32 = crc32_le(0, firmware + crc32_start, size - crc32_start); | |
121 | ||
579cea5f ZW |
122 | firmware_attr.size = size; |
123 | firmware_attr.private = firmware; | |
124 | ||
125 | ret = device_create_bin_file(&pdev->dev, &firmware_attr); | |
126 | if (ret) { | |
127 | vfree(firmware); | |
128 | return ret; | |
129 | } | |
130 | return 0; | |
131 | } | |
132 | ||
133 | static void clean_firmware_sysfs(struct intel_gvt *gvt) | |
134 | { | |
135 | struct pci_dev *pdev = gvt->dev_priv->drm.pdev; | |
136 | ||
137 | device_remove_bin_file(&pdev->dev, &firmware_attr); | |
138 | vfree(firmware_attr.private); | |
139 | } | |
140 | ||
141 | /** | |
142 | * intel_gvt_free_firmware - free GVT firmware | |
143 | * @gvt: intel gvt device | |
144 | * | |
145 | */ | |
146 | void intel_gvt_free_firmware(struct intel_gvt *gvt) | |
147 | { | |
148 | if (!gvt->firmware.firmware_loaded) | |
149 | clean_firmware_sysfs(gvt); | |
150 | ||
151 | kfree(gvt->firmware.cfg_space); | |
152 | kfree(gvt->firmware.mmio); | |
153 | } | |
154 | ||
155 | static int verify_firmware(struct intel_gvt *gvt, | |
156 | const struct firmware *fw) | |
157 | { | |
158 | struct intel_gvt_device_info *info = &gvt->device_info; | |
159 | struct drm_i915_private *dev_priv = gvt->dev_priv; | |
160 | struct pci_dev *pdev = dev_priv->drm.pdev; | |
161 | struct gvt_firmware_header *h; | |
162 | unsigned long id, crc32_start; | |
163 | const void *mem; | |
164 | const char *item; | |
165 | u64 file, request; | |
166 | ||
167 | h = (struct gvt_firmware_header *)fw->data; | |
168 | ||
169 | crc32_start = offsetof(struct gvt_firmware_header, crc32) + 4; | |
170 | mem = fw->data + crc32_start; | |
171 | ||
172 | #define VERIFY(s, a, b) do { \ | |
173 | item = (s); file = (u64)(a); request = (u64)(b); \ | |
174 | if ((a) != (b)) \ | |
175 | goto invalid_firmware; \ | |
176 | } while (0) | |
177 | ||
178 | VERIFY("magic number", h->magic, VGT_MAGIC); | |
179 | VERIFY("version", h->version, FIRMWARE_VERSION); | |
180 | VERIFY("crc32", h->crc32, crc32_le(0, mem, fw->size - crc32_start)); | |
181 | VERIFY("cfg space size", h->cfg_space_size, info->cfg_space_size); | |
182 | VERIFY("mmio size", h->mmio_size, info->mmio_size); | |
183 | ||
184 | mem = (fw->data + h->cfg_space_offset); | |
185 | ||
186 | id = *(u16 *)(mem + PCI_VENDOR_ID); | |
187 | VERIFY("vender id", id, pdev->vendor); | |
188 | ||
189 | id = *(u16 *)(mem + PCI_DEVICE_ID); | |
190 | VERIFY("device id", id, pdev->device); | |
191 | ||
192 | id = *(u8 *)(mem + PCI_REVISION_ID); | |
193 | VERIFY("revision id", id, pdev->revision); | |
194 | ||
195 | #undef VERIFY | |
196 | return 0; | |
197 | ||
198 | invalid_firmware: | |
199 | gvt_dbg_core("Invalid firmware: %s [file] 0x%llx [request] 0x%llx\n", | |
200 | item, file, request); | |
201 | return -EINVAL; | |
202 | } | |
203 | ||
204 | #define GVT_FIRMWARE_PATH "i915/gvt" | |
205 | ||
206 | /** | |
207 | * intel_gvt_load_firmware - load GVT firmware | |
208 | * @gvt: intel gvt device | |
209 | * | |
210 | */ | |
211 | int intel_gvt_load_firmware(struct intel_gvt *gvt) | |
212 | { | |
213 | struct intel_gvt_device_info *info = &gvt->device_info; | |
214 | struct drm_i915_private *dev_priv = gvt->dev_priv; | |
215 | struct pci_dev *pdev = dev_priv->drm.pdev; | |
216 | struct intel_gvt_firmware *firmware = &gvt->firmware; | |
217 | struct gvt_firmware_header *h; | |
218 | const struct firmware *fw; | |
219 | char *path; | |
321927db | 220 | void *mem; |
579cea5f ZW |
221 | int ret; |
222 | ||
223 | path = kmalloc(PATH_MAX, GFP_KERNEL); | |
224 | if (!path) | |
225 | return -ENOMEM; | |
226 | ||
227 | mem = kmalloc(info->cfg_space_size, GFP_KERNEL); | |
228 | if (!mem) { | |
229 | kfree(path); | |
230 | return -ENOMEM; | |
231 | } | |
232 | ||
233 | firmware->cfg_space = mem; | |
234 | ||
235 | mem = kmalloc(info->mmio_size, GFP_KERNEL); | |
236 | if (!mem) { | |
237 | kfree(path); | |
238 | kfree(firmware->cfg_space); | |
239 | return -ENOMEM; | |
240 | } | |
241 | ||
242 | firmware->mmio = mem; | |
243 | ||
aa4ce449 | 244 | sprintf(path, "%s/vid_0x%04x_did_0x%04x_rid_0x%02x.golden_hw_state", |
579cea5f ZW |
245 | GVT_FIRMWARE_PATH, pdev->vendor, pdev->device, |
246 | pdev->revision); | |
247 | ||
248 | gvt_dbg_core("request hw state firmware %s...\n", path); | |
249 | ||
250 | ret = request_firmware(&fw, path, &dev_priv->drm.pdev->dev); | |
251 | kfree(path); | |
252 | ||
253 | if (ret) | |
254 | goto expose_firmware; | |
255 | ||
256 | gvt_dbg_core("success.\n"); | |
257 | ||
258 | ret = verify_firmware(gvt, fw); | |
259 | if (ret) | |
260 | goto out_free_fw; | |
261 | ||
262 | gvt_dbg_core("verified.\n"); | |
263 | ||
264 | h = (struct gvt_firmware_header *)fw->data; | |
265 | ||
266 | memcpy(firmware->cfg_space, fw->data + h->cfg_space_offset, | |
267 | h->cfg_space_size); | |
268 | memcpy(firmware->mmio, fw->data + h->mmio_offset, | |
269 | h->mmio_size); | |
270 | ||
271 | release_firmware(fw); | |
272 | firmware->firmware_loaded = true; | |
579cea5f ZW |
273 | return 0; |
274 | ||
275 | out_free_fw: | |
276 | release_firmware(fw); | |
277 | expose_firmware: | |
14473025 | 278 | expose_firmware_sysfs(gvt); |
579cea5f ZW |
279 | return 0; |
280 | } |