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