]> mj.ucw.cz Git - pciutils.git/blob - lib/mmio-ports.c
Merge remote-tracking branch 'pali/mmio-ports'
[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(u32 value, volatile void *addr)
128 {
129   *(volatile u32 *)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 u32
145 readl(volatile void *addr)
146 {
147   return *(volatile u32 *)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 void
229 conf1_ext_config(struct pci_access *a)
230 {
231   pci_define_param(a, "devmem.path", PCI_PATH_DEVMEM_DEVICE, "Path to the /dev/mem device");
232   pci_define_param(a, "mmio-conf1-ext.addrs", "", "Physical addresses of memory mapped Intel conf1 extended interface"); /* format: 0xaddr1/0xdata1,0xaddr2/0xdata2,... */
233 }
234
235 static int
236 detect(struct pci_access *a, char *addrs_param_name)
237 {
238   char *addrs = pci_get_param(a, addrs_param_name);
239   char *devmem = pci_get_param(a, "devmem.path");
240
241   if (!*addrs)
242     {
243       a->debug("%s was not specified", addrs_param_name);
244       return 0;
245     }
246
247   if (!validate_addrs(addrs))
248     {
249       a->debug("%s has invalid address format %s", addrs_param_name, addrs);
250       return 0;
251     }
252
253   if (access(devmem, R_OK | W_OK))
254     {
255       a->debug("cannot access %s: %s", devmem, strerror(errno));
256       return 0;
257     }
258
259   a->debug("using %s with %s", devmem, addrs);
260   return 1;
261 }
262
263 static int
264 conf1_detect(struct pci_access *a)
265 {
266   return detect(a, "mmio-conf1.addrs");
267 }
268
269 static int
270 conf1_ext_detect(struct pci_access *a)
271 {
272   return detect(a, "mmio-conf1-ext.addrs");
273 }
274
275 static char*
276 get_addrs_param_name(struct pci_access *a)
277 {
278   if (a->methods->config == conf1_ext_config)
279     return "mmio-conf1-ext.addrs";
280   else
281     return "mmio-conf1.addrs";
282 }
283
284 static void
285 conf1_init(struct pci_access *a)
286 {
287   char *addrs_param_name = get_addrs_param_name(a);
288   char *addrs = pci_get_param(a, addrs_param_name);
289   char *devmem = pci_get_param(a, "devmem.path");
290
291   pagesize = sysconf(_SC_PAGESIZE);
292   if (pagesize < 0)
293     a->error("Cannot get page size: %s", strerror(errno));
294
295   if (!*addrs)
296     a->error("Option %s was not specified.", addrs_param_name);
297
298   if (!validate_addrs(addrs))
299     a->error("Option %s has invalid address format \"%s\".", addrs_param_name, addrs);
300
301   a->fd = open(devmem, O_RDWR | O_DSYNC); /* O_DSYNC bypass CPU cache for mmap() on Linux */
302   if (a->fd < 0)
303     a->error("Cannot open %s: %s.", devmem, strerror(errno));
304 }
305
306 static void
307 conf1_cleanup(struct pci_access *a)
308 {
309   if (a->fd < 0)
310     return;
311
312   munmap_regs(a);
313   close(a->fd);
314   a->fd = -1;
315 }
316
317 static void
318 conf1_scan(struct pci_access *a)
319 {
320   char *addrs_param_name = get_addrs_param_name(a);
321   char *addrs = pci_get_param(a, addrs_param_name);
322   int domain_count = get_domain_count(addrs);
323   int domain;
324
325   for (domain = 0; domain < domain_count; domain++)
326     pci_generic_scan_domain(a, domain);
327 }
328
329 static int
330 conf1_ext_read(struct pci_dev *d, int pos, byte *buf, int len)
331 {
332   char *addrs_param_name = get_addrs_param_name(d->access);
333   char *addrs = pci_get_param(d->access, addrs_param_name);
334   volatile void *addr, *data;
335   off_t addr_reg, data_reg;
336
337   if (pos >= 4096)
338     return 0;
339
340   if (len != 1 && len != 2 && len != 4)
341     return pci_generic_block_read(d, pos, buf, len);
342
343   if (!get_domain_addr(addrs, d->domain, &addr_reg, &data_reg))
344     return 0;
345
346   if (!mmap_regs(d->access, addr_reg, data_reg, pos&3, &addr, &data))
347     return 0;
348
349   writel(0x80000000 | ((pos & 0xf00) << 16) | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), addr);
350   readl(addr); /* write barrier for address */
351
352   switch (len)
353     {
354     case 1:
355       buf[0] = readb(data);
356       break;
357     case 2:
358       ((u16 *) buf)[0] = readw(data);
359       break;
360     case 4:
361       ((u32 *) buf)[0] = readl(data);
362       break;
363     }
364
365   return 1;
366 }
367
368 static int
369 conf1_read(struct pci_dev *d, int pos, byte *buf, int len)
370 {
371   if (pos >= 256)
372     return 0;
373
374   return conf1_ext_read(d, pos, buf, len);
375 }
376
377 static int
378 conf1_ext_write(struct pci_dev *d, int pos, byte *buf, int len)
379 {
380   char *addrs_param_name = get_addrs_param_name(d->access);
381   char *addrs = pci_get_param(d->access, addrs_param_name);
382   volatile void *addr, *data;
383   off_t addr_reg, data_reg;
384
385   if (pos >= 4096)
386     return 0;
387
388   if (len != 1 && len != 2 && len != 4)
389     return pci_generic_block_write(d, pos, buf, len);
390
391   if (!get_domain_addr(addrs, d->domain, &addr_reg, &data_reg))
392     return 0;
393
394   if (!mmap_regs(d->access, addr_reg, data_reg, pos&3, &addr, &data))
395     return 0;
396
397   writel(0x80000000 | ((pos & 0xf00) << 16) | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), addr);
398   readl(addr); /* write barrier for address */
399
400   switch (len)
401     {
402     case 1:
403       writeb(buf[0], data);
404       break;
405     case 2:
406       writew(((u16 *) buf)[0], data);
407       break;
408     case 4:
409       writel(((u32 *) buf)[0], data);
410       break;
411     }
412
413   /*
414    * write barrier for data
415    * Note that we cannot read from data port because it may have side effect.
416    * Instead we read from address port (which should not have side effect) to
417    * create a barrier between two conf1_write() calls. But this does not have
418    * to be 100% correct as it does not ensure barrier on data port itself.
419    * Correct way is to issue CPU instruction for full hw sync barrier but gcc
420    * does not provide any (builtin) function yet.
421    */
422   readl(addr);
423
424   return 1;
425 }
426
427 static int
428 conf1_write(struct pci_dev *d, int pos, byte *buf, int len)
429 {
430   if (pos >= 256)
431     return 0;
432
433   return conf1_ext_write(d, pos, buf, len);
434 }
435
436 struct pci_methods pm_mmio_conf1 = {
437   "mmio-conf1",
438   "Raw memory mapped I/O port access using Intel conf1 interface",
439   conf1_config,
440   conf1_detect,
441   conf1_init,
442   conf1_cleanup,
443   conf1_scan,
444   pci_generic_fill_info,
445   conf1_read,
446   conf1_write,
447   NULL,                                 /* read_vpd */
448   NULL,                                 /* init_dev */
449   NULL                                  /* cleanup_dev */
450 };
451
452 struct pci_methods pm_mmio_conf1_ext = {
453   "mmio-conf1-ext",
454   "Raw memory mapped I/O port access using Intel conf1 extended interface",
455   conf1_ext_config,
456   conf1_ext_detect,
457   conf1_init,
458   conf1_cleanup,
459   conf1_scan,
460   pci_generic_fill_info,
461   conf1_ext_read,
462   conf1_ext_write,
463   NULL,                                 /* read_vpd */
464   NULL,                                 /* init_dev */
465   NULL                                  /* cleanup_dev */
466 };