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