]> mj.ucw.cz Git - pciutils.git/blob - lib/mmio-ports.c
Merge remote-tracking branch 'pali/intel-conf1-memio'
[pciutils.git] / lib / mmio-ports.c
1 /*
2  *      The PCI Library -- Direct Configuration access via memory mapped ports
3  *
4  *      Copyright (c) 2022 Pali Rohár <pali@kernel.org>
5  *
6  *      Can be freely distributed and used under the terms of the GNU GPL.
7  */
8
9 /*
10  * Tell 32-bit platforms that we are interested in 64-bit variant of off_t type
11  * as 32-bit variant of off_t type is signed and so it cannot represent all
12  * possible 32-bit offsets. It is required because off_t type is used by mmap().
13  */
14 #define _FILE_OFFSET_BITS 64
15
16 #include "internal.h"
17
18 #include <ctype.h>
19 #include <errno.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <limits.h>
23
24 #include <sys/mman.h>
25 #include <sys/types.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28
29 #ifndef OFF_MAX
30 #define OFF_MAX (off_t)((1ULL << (sizeof(off_t) * CHAR_BIT - 1)) - 1)
31 #endif
32
33 struct mmio_cache
34 {
35   off_t addr_page;
36   off_t data_page;
37   void *addr_map;
38   void *data_map;
39 };
40
41 static long pagesize;
42
43 static void
44 munmap_regs(struct pci_access *a)
45 {
46   struct mmio_cache *cache = a->aux;
47
48   if (!cache)
49     return;
50
51   munmap(cache->addr_map, pagesize);
52   if (cache->addr_page != cache->data_page)
53     munmap(cache->data_map, pagesize);
54
55   pci_mfree(a->aux);
56   a->aux = NULL;
57 }
58
59 static int
60 mmap_regs(struct pci_access *a, off_t addr_reg, off_t data_reg, int data_off, volatile void **addr, volatile void **data)
61 {
62   struct mmio_cache *cache = a->aux;
63   off_t addr_page = addr_reg & ~(pagesize-1);
64   off_t data_page = data_reg & ~(pagesize-1);
65   void *addr_map = MAP_FAILED;
66   void *data_map = MAP_FAILED;
67
68   if (cache && cache->addr_page == addr_page)
69     addr_map = cache->addr_map;
70
71   if (cache && cache->data_page == data_page)
72     data_map = cache->data_map;
73
74   if (addr_map == MAP_FAILED)
75     addr_map = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, a->fd, addr_page);
76
77   if (addr_map == MAP_FAILED)
78     return 0;
79
80   if (data_map == MAP_FAILED)
81     {
82       if (data_page == addr_page)
83         data_map = addr_map;
84       else
85         data_map = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, a->fd, data_page);
86     }
87
88   if (data_map == MAP_FAILED)
89     {
90       if (!cache || cache->addr_map != addr_map)
91         munmap(addr_map, pagesize);
92       return 0;
93     }
94
95   if (cache && cache->addr_page != addr_page)
96     munmap(cache->addr_map, pagesize);
97
98   if (cache && cache->data_page != data_page && cache->data_page != cache->addr_page)
99     munmap(cache->data_map, pagesize);
100
101   if (!cache)
102     cache = a->aux = pci_malloc(a, sizeof(*cache));
103
104   cache->addr_page = addr_page;
105   cache->data_page = data_page;
106   cache->addr_map = addr_map;
107   cache->data_map = data_map;
108
109   *addr = (unsigned char *)addr_map + (addr_reg & (pagesize-1));
110   *data = (unsigned char *)data_map + (data_reg & (pagesize-1)) + data_off;
111   return 1;
112 }
113
114 static void
115 writeb(unsigned char value, volatile void *addr)
116 {
117   *(volatile unsigned char *)addr = value;
118 }
119
120 static void
121 writew(unsigned short value, volatile void *addr)
122 {
123   *(volatile unsigned short *)addr = value;
124 }
125
126 static void
127 writel(unsigned long value, volatile void *addr)
128 {
129   *(volatile unsigned long *)addr = value;
130 }
131
132 static unsigned char
133 readb(volatile void *addr)
134 {
135   return *(volatile unsigned char *)addr;
136 }
137
138 static unsigned short
139 readw(volatile void *addr)
140 {
141   return *(volatile unsigned short *)addr;
142 }
143
144 static unsigned long
145 readl(volatile void *addr)
146 {
147   return *(volatile unsigned long *)addr;
148 }
149
150 static int
151 validate_addrs(const char *addrs)
152 {
153   const char *sep, *next;
154   unsigned long long num;
155   char *endptr;
156
157   if (!*addrs)
158     return 0;
159
160   while (1)
161     {
162       next = strchr(addrs, ',');
163       if (!next)
164         next = addrs + strlen(addrs);
165
166       sep = strchr(addrs, '/');
167       if (!sep)
168         return 0;
169
170       if (!isxdigit(*addrs) || !isxdigit(*(sep+1)))
171         return 0;
172
173       errno = 0;
174       num = strtoull(addrs, &endptr, 16);
175       if (errno || endptr != sep || (num & 3) || num > OFF_MAX)
176         return 0;
177
178       errno = 0;
179       num = strtoull(sep+1, &endptr, 16);
180       if (errno || endptr != next || (num & 3) || num > OFF_MAX)
181         return 0;
182
183       if (!*next)
184         return 1;
185
186       addrs = next + 1;
187     }
188 }
189
190 static int
191 get_domain_count(const char *addrs)
192 {
193   int count = 1;
194   while (addrs = strchr(addrs, ','))
195     {
196       addrs++;
197       count++;
198     }
199   return count;
200 }
201
202 static int
203 get_domain_addr(const char *addrs, int domain, off_t *addr_reg, off_t *data_reg)
204 {
205   char *endptr;
206
207   while (domain-- > 0)
208     {
209       addrs = strchr(addrs, ',');
210       if (!addrs)
211         return 0;
212       addrs++;
213     }
214
215   *addr_reg = strtoull(addrs, &endptr, 16);
216   *data_reg = strtoull(endptr+1, NULL, 16);
217
218   return 1;
219 }
220
221 static void
222 conf1_config(struct pci_access *a)
223 {
224   pci_define_param(a, "devmem.path", PCI_PATH_DEVMEM_DEVICE, "Path to the /dev/mem device");
225   pci_define_param(a, "mmio-conf1.addrs", "", "Physical addresses of memory mapped Intel conf1 interface"); /* format: 0xaddr1/0xdata1,0xaddr2/0xdata2,... */
226 }
227
228 static int
229 conf1_detect(struct pci_access *a)
230 {
231   char *addrs = pci_get_param(a, "mmio-conf1.addrs");
232   char *devmem = pci_get_param(a, "devmem.path");
233
234   if (!*addrs)
235     {
236       a->debug("mmio-conf1.addrs was not specified");
237       return 0;
238     }
239
240   if (!validate_addrs(addrs))
241     {
242       a->debug("mmio-conf1.addrs has invalid address format %s", addrs);
243       return 0;
244     }
245
246   if (access(devmem, R_OK | W_OK))
247     {
248       a->debug("cannot access %s: %s", devmem, strerror(errno));
249       return 0;
250     }
251
252   a->debug("using %s with %s", devmem, addrs);
253   return 1;
254 }
255
256 static void
257 conf1_init(struct pci_access *a)
258 {
259   char *addrs = pci_get_param(a, "mmio-conf1.addrs");
260   char *devmem = pci_get_param(a, "devmem.path");
261
262   pagesize = sysconf(_SC_PAGESIZE);
263   if (pagesize < 0)
264     a->error("Cannot get page size: %s", strerror(errno));
265
266   if (!*addrs)
267     a->error("Option mmio-conf1.addrs was not specified.");
268
269   if (!validate_addrs(addrs))
270     a->error("Option mmio-conf1.addrs has invalid address format \"%s\".", addrs);
271
272   a->fd = open(devmem, O_RDWR | O_DSYNC); /* O_DSYNC bypass CPU cache for mmap() on Linux */
273   if (a->fd < 0)
274     a->error("Cannot open %s: %s.", devmem, strerror(errno));
275 }
276
277 static void
278 conf1_cleanup(struct pci_access *a)
279 {
280   if (a->fd < 0)
281     return;
282
283   munmap_regs(a);
284   close(a->fd);
285   a->fd = -1;
286 }
287
288 static void
289 conf1_scan(struct pci_access *a)
290 {
291   char *addrs = pci_get_param(a, "mmio-conf1.addrs");
292   int domain_count = get_domain_count(addrs);
293   int domain;
294
295   for (domain = 0; domain < domain_count; domain++)
296     pci_generic_scan_domain(a, domain);
297 }
298
299 static int
300 conf1_read(struct pci_dev *d, int pos, byte *buf, int len)
301 {
302   char *addrs = pci_get_param(d->access, "mmio-conf1.addrs");
303   volatile void *addr, *data;
304   off_t addr_reg, data_reg;
305
306   if (pos >= 256)
307     return 0;
308
309   if (len != 1 && len != 2 && len != 4)
310     return pci_generic_block_read(d, pos, buf, len);
311
312   if (!get_domain_addr(addrs, d->domain, &addr_reg, &data_reg))
313     return 0;
314
315   if (!mmap_regs(d->access, addr_reg, data_reg, pos&3, &addr, &data))
316     return 0;
317
318   writel(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), addr);
319   readl(addr); /* write barrier for address */
320
321   switch (len)
322     {
323     case 1:
324       buf[0] = readb(data);
325       break;
326     case 2:
327       ((u16 *) buf)[0] = readw(data);
328       break;
329     case 4:
330       ((u32 *) buf)[0] = readl(data);
331       break;
332     }
333
334   return 1;
335 }
336
337 static int
338 conf1_write(struct pci_dev *d, int pos, byte *buf, int len)
339 {
340   char *addrs = pci_get_param(d->access, "mmio-conf1.addrs");
341   volatile void *addr, *data;
342   off_t addr_reg, data_reg;
343
344   if (pos >= 256)
345     return 0;
346
347   if (len != 1 && len != 2 && len != 4)
348     return pci_generic_block_write(d, pos, buf, len);
349
350   if (!get_domain_addr(addrs, d->domain, &addr_reg, &data_reg))
351     return 0;
352
353   if (!mmap_regs(d->access, addr_reg, data_reg, pos&3, &addr, &data))
354     return 0;
355
356   writel(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), addr);
357   readl(addr); /* write barrier for address */
358
359   switch (len)
360     {
361     case 1:
362       writeb(buf[0], data);
363       break;
364     case 2:
365       writew(((u16 *) buf)[0], data);
366       break;
367     case 4:
368       writel(((u32 *) buf)[0], data);
369       break;
370     }
371
372   /*
373    * write barrier for data
374    * Note that we cannot read from data port because it may have side effect.
375    * Instead we read from address port (which should not have side effect) to
376    * create a barrier between two conf1_write() calls. But this does not have
377    * to be 100% correct as it does not ensure barrier on data port itself.
378    * Correct way is to issue CPU instruction for full hw sync barrier but gcc
379    * does not provide any (builtin) function yet.
380    */
381   readl(addr);
382
383   return 1;
384 }
385
386 struct pci_methods pm_mmio_conf1 = {
387   "mmio-conf1",
388   "Raw memory mapped I/O port access using Intel conf1 interface",
389   conf1_config,
390   conf1_detect,
391   conf1_init,
392   conf1_cleanup,
393   conf1_scan,
394   pci_generic_fill_info,
395   conf1_read,
396   conf1_write,
397   NULL,                                 /* read_vpd */
398   NULL,                                 /* init_dev */
399   NULL                                  /* cleanup_dev */
400 };