]>
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; | |
75 | struct gvt_firmware_header *h; | |
76 | void *firmware; | |
77 | void *p; | |
aa4ce449 | 78 | unsigned long size, crc32_start; |
579cea5f ZW |
79 | int i; |
80 | int ret; | |
81 | ||
aa4ce449 | 82 | size = sizeof(*h) + info->mmio_size + info->cfg_space_size; |
ec162aa8 | 83 | firmware = vzalloc(size); |
579cea5f ZW |
84 | if (!firmware) |
85 | return -ENOMEM; | |
86 | ||
87 | h = firmware; | |
88 | ||
89 | h->magic = VGT_MAGIC; | |
90 | h->version = FIRMWARE_VERSION; | |
91 | h->cfg_space_size = info->cfg_space_size; | |
92 | h->cfg_space_offset = offsetof(struct gvt_firmware_header, data); | |
93 | h->mmio_size = info->mmio_size; | |
94 | h->mmio_offset = h->cfg_space_offset + h->cfg_space_size; | |
95 | ||
96 | p = firmware + h->cfg_space_offset; | |
97 | ||
98 | for (i = 0; i < h->cfg_space_size; i += 4) | |
99 | pci_read_config_dword(pdev, i, p + i); | |
100 | ||
101 | memcpy(gvt->firmware.cfg_space, p, info->cfg_space_size); | |
102 | ||
103 | p = firmware + h->mmio_offset; | |
104 | ||
105 | hash_for_each(gvt->mmio.mmio_info_table, i, e, node) { | |
106 | int j; | |
107 | ||
108 | for (j = 0; j < e->length; j += 4) | |
109 | *(u32 *)(p + e->offset + j) = | |
14473025 | 110 | I915_READ_NOTRACE(_MMIO(e->offset + j)); |
579cea5f ZW |
111 | } |
112 | ||
113 | memcpy(gvt->firmware.mmio, p, info->mmio_size); | |
114 | ||
aa4ce449 ZW |
115 | crc32_start = offsetof(struct gvt_firmware_header, crc32) + 4; |
116 | h->crc32 = crc32_le(0, firmware + crc32_start, size - crc32_start); | |
117 | ||
579cea5f ZW |
118 | firmware_attr.size = size; |
119 | firmware_attr.private = firmware; | |
120 | ||
121 | ret = device_create_bin_file(&pdev->dev, &firmware_attr); | |
122 | if (ret) { | |
123 | vfree(firmware); | |
124 | return ret; | |
125 | } | |
126 | return 0; | |
127 | } | |
128 | ||
129 | static void clean_firmware_sysfs(struct intel_gvt *gvt) | |
130 | { | |
131 | struct pci_dev *pdev = gvt->dev_priv->drm.pdev; | |
132 | ||
133 | device_remove_bin_file(&pdev->dev, &firmware_attr); | |
134 | vfree(firmware_attr.private); | |
135 | } | |
136 | ||
137 | /** | |
138 | * intel_gvt_free_firmware - free GVT firmware | |
139 | * @gvt: intel gvt device | |
140 | * | |
141 | */ | |
142 | void intel_gvt_free_firmware(struct intel_gvt *gvt) | |
143 | { | |
144 | if (!gvt->firmware.firmware_loaded) | |
145 | clean_firmware_sysfs(gvt); | |
146 | ||
147 | kfree(gvt->firmware.cfg_space); | |
148 | kfree(gvt->firmware.mmio); | |
149 | } | |
150 | ||
151 | static int verify_firmware(struct intel_gvt *gvt, | |
152 | const struct firmware *fw) | |
153 | { | |
154 | struct intel_gvt_device_info *info = &gvt->device_info; | |
155 | struct drm_i915_private *dev_priv = gvt->dev_priv; | |
156 | struct pci_dev *pdev = dev_priv->drm.pdev; | |
157 | struct gvt_firmware_header *h; | |
158 | unsigned long id, crc32_start; | |
159 | const void *mem; | |
160 | const char *item; | |
161 | u64 file, request; | |
162 | ||
163 | h = (struct gvt_firmware_header *)fw->data; | |
164 | ||
165 | crc32_start = offsetof(struct gvt_firmware_header, crc32) + 4; | |
166 | mem = fw->data + crc32_start; | |
167 | ||
168 | #define VERIFY(s, a, b) do { \ | |
169 | item = (s); file = (u64)(a); request = (u64)(b); \ | |
170 | if ((a) != (b)) \ | |
171 | goto invalid_firmware; \ | |
172 | } while (0) | |
173 | ||
174 | VERIFY("magic number", h->magic, VGT_MAGIC); | |
175 | VERIFY("version", h->version, FIRMWARE_VERSION); | |
176 | VERIFY("crc32", h->crc32, crc32_le(0, mem, fw->size - crc32_start)); | |
177 | VERIFY("cfg space size", h->cfg_space_size, info->cfg_space_size); | |
178 | VERIFY("mmio size", h->mmio_size, info->mmio_size); | |
179 | ||
180 | mem = (fw->data + h->cfg_space_offset); | |
181 | ||
182 | id = *(u16 *)(mem + PCI_VENDOR_ID); | |
183 | VERIFY("vender id", id, pdev->vendor); | |
184 | ||
185 | id = *(u16 *)(mem + PCI_DEVICE_ID); | |
186 | VERIFY("device id", id, pdev->device); | |
187 | ||
188 | id = *(u8 *)(mem + PCI_REVISION_ID); | |
189 | VERIFY("revision id", id, pdev->revision); | |
190 | ||
191 | #undef VERIFY | |
192 | return 0; | |
193 | ||
194 | invalid_firmware: | |
195 | gvt_dbg_core("Invalid firmware: %s [file] 0x%llx [request] 0x%llx\n", | |
196 | item, file, request); | |
197 | return -EINVAL; | |
198 | } | |
199 | ||
200 | #define GVT_FIRMWARE_PATH "i915/gvt" | |
201 | ||
202 | /** | |
203 | * intel_gvt_load_firmware - load GVT firmware | |
204 | * @gvt: intel gvt device | |
205 | * | |
206 | */ | |
207 | int intel_gvt_load_firmware(struct intel_gvt *gvt) | |
208 | { | |
209 | struct intel_gvt_device_info *info = &gvt->device_info; | |
210 | struct drm_i915_private *dev_priv = gvt->dev_priv; | |
211 | struct pci_dev *pdev = dev_priv->drm.pdev; | |
212 | struct intel_gvt_firmware *firmware = &gvt->firmware; | |
213 | struct gvt_firmware_header *h; | |
214 | const struct firmware *fw; | |
215 | char *path; | |
321927db | 216 | void *mem; |
579cea5f ZW |
217 | int ret; |
218 | ||
219 | path = kmalloc(PATH_MAX, GFP_KERNEL); | |
220 | if (!path) | |
221 | return -ENOMEM; | |
222 | ||
223 | mem = kmalloc(info->cfg_space_size, GFP_KERNEL); | |
224 | if (!mem) { | |
225 | kfree(path); | |
226 | return -ENOMEM; | |
227 | } | |
228 | ||
229 | firmware->cfg_space = mem; | |
230 | ||
231 | mem = kmalloc(info->mmio_size, GFP_KERNEL); | |
232 | if (!mem) { | |
233 | kfree(path); | |
234 | kfree(firmware->cfg_space); | |
235 | return -ENOMEM; | |
236 | } | |
237 | ||
238 | firmware->mmio = mem; | |
239 | ||
aa4ce449 | 240 | sprintf(path, "%s/vid_0x%04x_did_0x%04x_rid_0x%02x.golden_hw_state", |
579cea5f ZW |
241 | GVT_FIRMWARE_PATH, pdev->vendor, pdev->device, |
242 | pdev->revision); | |
243 | ||
244 | gvt_dbg_core("request hw state firmware %s...\n", path); | |
245 | ||
246 | ret = request_firmware(&fw, path, &dev_priv->drm.pdev->dev); | |
247 | kfree(path); | |
248 | ||
249 | if (ret) | |
250 | goto expose_firmware; | |
251 | ||
252 | gvt_dbg_core("success.\n"); | |
253 | ||
254 | ret = verify_firmware(gvt, fw); | |
255 | if (ret) | |
256 | goto out_free_fw; | |
257 | ||
258 | gvt_dbg_core("verified.\n"); | |
259 | ||
260 | h = (struct gvt_firmware_header *)fw->data; | |
261 | ||
262 | memcpy(firmware->cfg_space, fw->data + h->cfg_space_offset, | |
263 | h->cfg_space_size); | |
264 | memcpy(firmware->mmio, fw->data + h->mmio_offset, | |
265 | h->mmio_size); | |
266 | ||
267 | release_firmware(fw); | |
268 | firmware->firmware_loaded = true; | |
579cea5f ZW |
269 | return 0; |
270 | ||
271 | out_free_fw: | |
272 | release_firmware(fw); | |
273 | expose_firmware: | |
14473025 | 274 | expose_firmware_sysfs(gvt); |
579cea5f ZW |
275 | return 0; |
276 | } |