1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * arch/alpha/kernel/pci-sysfs.c |
4 | * |
5 | * Copyright (C) 2009 Ivan Kokshaysky |
6 | * |
7 | * Alpha PCI resource files. |
8 | * |
9 | * Loosely based on generic HAVE_PCI_MMAP implementation in |
10 | * drivers/pci/pci-sysfs.c |
11 | */ |
12 | |
13 | #include <linux/sched.h> |
14 | #include <linux/stat.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/pci.h> |
17 | |
18 | static int hose_mmap_page_range(struct pci_controller *hose, |
19 | struct vm_area_struct *vma, |
20 | enum pci_mmap_state mmap_type, int sparse) |
21 | { |
22 | unsigned long base; |
23 | |
24 | if (mmap_type == pci_mmap_mem) |
25 | base = sparse ? hose->sparse_mem_base : hose->dense_mem_base; |
26 | else |
27 | base = sparse ? hose->sparse_io_base : hose->dense_io_base; |
28 | |
29 | vma->vm_pgoff += base >> PAGE_SHIFT; |
30 | |
31 | return io_remap_pfn_range(vma, addr: vma->vm_start, pfn: vma->vm_pgoff, |
32 | size: vma->vm_end - vma->vm_start, |
33 | prot: vma->vm_page_prot); |
34 | } |
35 | |
36 | static int __pci_mmap_fits(struct pci_dev *pdev, int num, |
37 | struct vm_area_struct *vma, int sparse) |
38 | { |
39 | unsigned long nr, start, size; |
40 | int shift = sparse ? 5 : 0; |
41 | |
42 | nr = vma_pages(vma); |
43 | start = vma->vm_pgoff; |
44 | size = ((pci_resource_len(pdev, num) - 1) >> (PAGE_SHIFT - shift)) + 1; |
45 | |
46 | if (start < size && size - start >= nr) |
47 | return 1; |
48 | WARN(1, "process \"%s\" tried to map%s 0x%08lx-0x%08lx on %s BAR %d " |
49 | "(size 0x%08lx)\n" , |
50 | current->comm, sparse ? " sparse" : "" , start, start + nr, |
51 | pci_name(pdev), num, size); |
52 | return 0; |
53 | } |
54 | |
55 | /** |
56 | * pci_mmap_resource - map a PCI resource into user memory space |
57 | * @kobj: kobject for mapping |
58 | * @attr: struct bin_attribute for the file being mapped |
59 | * @vma: struct vm_area_struct passed into the mmap |
60 | * @sparse: address space type |
61 | * |
62 | * Use the bus mapping routines to map a PCI resource into userspace. |
63 | * |
64 | * Return: %0 on success, negative error code otherwise |
65 | */ |
66 | static int pci_mmap_resource(struct kobject *kobj, |
67 | struct bin_attribute *attr, |
68 | struct vm_area_struct *vma, int sparse) |
69 | { |
70 | struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); |
71 | struct resource *res = attr->private; |
72 | enum pci_mmap_state mmap_type; |
73 | struct pci_bus_region bar; |
74 | int i; |
75 | |
76 | for (i = 0; i < PCI_STD_NUM_BARS; i++) |
77 | if (res == &pdev->resource[i]) |
78 | break; |
79 | if (i >= PCI_STD_NUM_BARS) |
80 | return -ENODEV; |
81 | |
82 | if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(addr: res->start)) |
83 | return -EINVAL; |
84 | |
85 | if (!__pci_mmap_fits(pdev, num: i, vma, sparse)) |
86 | return -EINVAL; |
87 | |
88 | pcibios_resource_to_bus(bus: pdev->bus, region: &bar, res); |
89 | vma->vm_pgoff += bar.start >> (PAGE_SHIFT - (sparse ? 5 : 0)); |
90 | mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io; |
91 | |
92 | return hose_mmap_page_range(hose: pdev->sysdata, vma, mmap_type, sparse); |
93 | } |
94 | |
95 | static int pci_mmap_resource_sparse(struct file *filp, struct kobject *kobj, |
96 | struct bin_attribute *attr, |
97 | struct vm_area_struct *vma) |
98 | { |
99 | return pci_mmap_resource(kobj, attr, vma, sparse: 1); |
100 | } |
101 | |
102 | static int pci_mmap_resource_dense(struct file *filp, struct kobject *kobj, |
103 | struct bin_attribute *attr, |
104 | struct vm_area_struct *vma) |
105 | { |
106 | return pci_mmap_resource(kobj, attr, vma, sparse: 0); |
107 | } |
108 | |
109 | /** |
110 | * pci_remove_resource_files - cleanup resource files |
111 | * @pdev: pci_dev to cleanup |
112 | * |
113 | * If we created resource files for @dev, remove them from sysfs and |
114 | * free their resources. |
115 | */ |
116 | void pci_remove_resource_files(struct pci_dev *pdev) |
117 | { |
118 | int i; |
119 | |
120 | for (i = 0; i < PCI_STD_NUM_BARS; i++) { |
121 | struct bin_attribute *res_attr; |
122 | |
123 | res_attr = pdev->res_attr[i]; |
124 | if (res_attr) { |
125 | sysfs_remove_bin_file(kobj: &pdev->dev.kobj, attr: res_attr); |
126 | kfree(objp: res_attr); |
127 | } |
128 | |
129 | res_attr = pdev->res_attr_wc[i]; |
130 | if (res_attr) { |
131 | sysfs_remove_bin_file(kobj: &pdev->dev.kobj, attr: res_attr); |
132 | kfree(objp: res_attr); |
133 | } |
134 | } |
135 | } |
136 | |
137 | static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num) |
138 | { |
139 | struct pci_bus_region bar; |
140 | struct pci_controller *hose = pdev->sysdata; |
141 | long dense_offset; |
142 | unsigned long sparse_size; |
143 | |
144 | pcibios_resource_to_bus(bus: pdev->bus, region: &bar, res: &pdev->resource[num]); |
145 | |
146 | /* All core logic chips have 4G sparse address space, except |
147 | CIA which has 16G (see xxx_SPARSE_MEM and xxx_DENSE_MEM |
148 | definitions in asm/core_xxx.h files). This corresponds |
149 | to 128M or 512M of the bus space. */ |
150 | dense_offset = (long)(hose->dense_mem_base - hose->sparse_mem_base); |
151 | sparse_size = dense_offset >= 0x400000000UL ? 0x20000000 : 0x8000000; |
152 | |
153 | return bar.end < sparse_size; |
154 | } |
155 | |
156 | static int pci_create_one_attr(struct pci_dev *pdev, int num, char *name, |
157 | char *suffix, struct bin_attribute *res_attr, |
158 | unsigned long sparse) |
159 | { |
160 | size_t size = pci_resource_len(pdev, num); |
161 | |
162 | sprintf(buf: name, fmt: "resource%d%s" , num, suffix); |
163 | res_attr->mmap = sparse ? pci_mmap_resource_sparse : |
164 | pci_mmap_resource_dense; |
165 | res_attr->attr.name = name; |
166 | res_attr->attr.mode = S_IRUSR | S_IWUSR; |
167 | res_attr->size = sparse ? size << 5 : size; |
168 | res_attr->private = &pdev->resource[num]; |
169 | return sysfs_create_bin_file(kobj: &pdev->dev.kobj, attr: res_attr); |
170 | } |
171 | |
172 | static int pci_create_attr(struct pci_dev *pdev, int num) |
173 | { |
174 | /* allocate attribute structure, piggyback attribute name */ |
175 | int retval, nlen1, nlen2 = 0, res_count = 1; |
176 | unsigned long sparse_base, dense_base; |
177 | struct bin_attribute *attr; |
178 | struct pci_controller *hose = pdev->sysdata; |
179 | char *suffix, *attr_name; |
180 | |
181 | suffix = "" ; /* Assume bwx machine, normal resourceN files. */ |
182 | nlen1 = 10; |
183 | |
184 | if (pdev->resource[num].flags & IORESOURCE_MEM) { |
185 | sparse_base = hose->sparse_mem_base; |
186 | dense_base = hose->dense_mem_base; |
187 | if (sparse_base && !sparse_mem_mmap_fits(pdev, num)) { |
188 | sparse_base = 0; |
189 | suffix = "_dense" ; |
190 | nlen1 = 16; /* resourceN_dense */ |
191 | } |
192 | } else { |
193 | sparse_base = hose->sparse_io_base; |
194 | dense_base = hose->dense_io_base; |
195 | } |
196 | |
197 | if (sparse_base) { |
198 | suffix = "_sparse" ; |
199 | nlen1 = 17; |
200 | if (dense_base) { |
201 | nlen2 = 16; /* resourceN_dense */ |
202 | res_count = 2; |
203 | } |
204 | } |
205 | |
206 | attr = kzalloc(size: sizeof(*attr) * res_count + nlen1 + nlen2, GFP_ATOMIC); |
207 | if (!attr) |
208 | return -ENOMEM; |
209 | |
210 | /* Create bwx, sparse or single dense file */ |
211 | attr_name = (char *)(attr + res_count); |
212 | pdev->res_attr[num] = attr; |
213 | retval = pci_create_one_attr(pdev, num, name: attr_name, suffix, res_attr: attr, |
214 | sparse: sparse_base); |
215 | if (retval || res_count == 1) |
216 | return retval; |
217 | |
218 | /* Create dense file */ |
219 | attr_name += nlen1; |
220 | attr++; |
221 | pdev->res_attr_wc[num] = attr; |
222 | return pci_create_one_attr(pdev, num, name: attr_name, suffix: "_dense" , res_attr: attr, sparse: 0); |
223 | } |
224 | |
225 | /** |
226 | * pci_create_resource_files - create resource files in sysfs for @pdev |
227 | * @pdev: pci_dev in question |
228 | * |
229 | * Walk the resources in @dev creating files for each resource available. |
230 | * |
231 | * Return: %0 on success, or negative error code |
232 | */ |
233 | int pci_create_resource_files(struct pci_dev *pdev) |
234 | { |
235 | int i; |
236 | int retval; |
237 | |
238 | /* Expose the PCI resources from this device as files */ |
239 | for (i = 0; i < PCI_STD_NUM_BARS; i++) { |
240 | |
241 | /* skip empty resources */ |
242 | if (!pci_resource_len(pdev, i)) |
243 | continue; |
244 | |
245 | retval = pci_create_attr(pdev, num: i); |
246 | if (retval) { |
247 | pci_remove_resource_files(pdev); |
248 | return retval; |
249 | } |
250 | } |
251 | return 0; |
252 | } |
253 | |
254 | /* Legacy I/O bus mapping stuff. */ |
255 | |
256 | static int __legacy_mmap_fits(struct pci_controller *hose, |
257 | struct vm_area_struct *vma, |
258 | unsigned long res_size, int sparse) |
259 | { |
260 | unsigned long nr, start, size; |
261 | |
262 | nr = vma_pages(vma); |
263 | start = vma->vm_pgoff; |
264 | size = ((res_size - 1) >> PAGE_SHIFT) + 1; |
265 | |
266 | if (start < size && size - start >= nr) |
267 | return 1; |
268 | WARN(1, "process \"%s\" tried to map%s 0x%08lx-0x%08lx on hose %d " |
269 | "(size 0x%08lx)\n" , |
270 | current->comm, sparse ? " sparse" : "" , start, start + nr, |
271 | hose->index, size); |
272 | return 0; |
273 | } |
274 | |
275 | static inline int has_sparse(struct pci_controller *hose, |
276 | enum pci_mmap_state mmap_type) |
277 | { |
278 | unsigned long base; |
279 | |
280 | base = (mmap_type == pci_mmap_mem) ? hose->sparse_mem_base : |
281 | hose->sparse_io_base; |
282 | |
283 | return base != 0; |
284 | } |
285 | |
286 | int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma, |
287 | enum pci_mmap_state mmap_type) |
288 | { |
289 | struct pci_controller *hose = bus->sysdata; |
290 | int sparse = has_sparse(hose, mmap_type); |
291 | unsigned long res_size; |
292 | |
293 | res_size = (mmap_type == pci_mmap_mem) ? bus->legacy_mem->size : |
294 | bus->legacy_io->size; |
295 | if (!__legacy_mmap_fits(hose, vma, res_size, sparse)) |
296 | return -EINVAL; |
297 | |
298 | return hose_mmap_page_range(hose, vma, mmap_type, sparse); |
299 | } |
300 | |
301 | /** |
302 | * pci_adjust_legacy_attr - adjustment of legacy file attributes |
303 | * @bus: bus to create files under |
304 | * @mmap_type: I/O port or memory |
305 | * |
306 | * Adjust file name and size for sparse mappings. |
307 | */ |
308 | void pci_adjust_legacy_attr(struct pci_bus *bus, enum pci_mmap_state mmap_type) |
309 | { |
310 | struct pci_controller *hose = bus->sysdata; |
311 | |
312 | if (!has_sparse(hose, mmap_type)) |
313 | return; |
314 | |
315 | if (mmap_type == pci_mmap_mem) { |
316 | bus->legacy_mem->attr.name = "legacy_mem_sparse" ; |
317 | bus->legacy_mem->size <<= 5; |
318 | } else { |
319 | bus->legacy_io->attr.name = "legacy_io_sparse" ; |
320 | bus->legacy_io->size <<= 5; |
321 | } |
322 | return; |
323 | } |
324 | |
325 | /* Legacy I/O bus read/write functions */ |
326 | int pci_legacy_read(struct pci_bus *bus, loff_t port, u32 *val, size_t size) |
327 | { |
328 | struct pci_controller *hose = bus->sysdata; |
329 | |
330 | port += hose->io_space->start; |
331 | |
332 | switch(size) { |
333 | case 1: |
334 | *((u8 *)val) = inb(port); |
335 | return 1; |
336 | case 2: |
337 | if (port & 1) |
338 | return -EINVAL; |
339 | *((u16 *)val) = inw(port); |
340 | return 2; |
341 | case 4: |
342 | if (port & 3) |
343 | return -EINVAL; |
344 | *((u32 *)val) = inl(port); |
345 | return 4; |
346 | } |
347 | return -EINVAL; |
348 | } |
349 | |
350 | int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, size_t size) |
351 | { |
352 | struct pci_controller *hose = bus->sysdata; |
353 | |
354 | port += hose->io_space->start; |
355 | |
356 | switch(size) { |
357 | case 1: |
358 | outb(value: port, port: val); |
359 | return 1; |
360 | case 2: |
361 | if (port & 1) |
362 | return -EINVAL; |
363 | outw(value: port, port: val); |
364 | return 2; |
365 | case 4: |
366 | if (port & 3) |
367 | return -EINVAL; |
368 | outl(value: port, port: val); |
369 | return 4; |
370 | } |
371 | return -EINVAL; |
372 | } |
373 | |