]> mj.ucw.cz Git - pciutils.git/blob - lib/hurd.c
Update license comments and added SPDX license identifiers
[pciutils.git] / lib / hurd.c
1 /*
2  *      The PCI Library -- Hurd access via RPCs
3  *
4  *      Copyright (c) 2017 Joan Lledó <jlledom@member.fsf.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 #define _GNU_SOURCE
12
13 #include "internal.h"
14
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <dirent.h>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <hurd.h>
24 #include <hurd/pci.h>
25 #include <hurd/paths.h>
26
27 /* Server path */
28 #define _SERVERS_BUS_PCI        _SERVERS_BUS "/pci"
29
30 /* File names */
31 #define FILE_CONFIG_NAME "config"
32 #define FILE_ROM_NAME "rom"
33
34 /* Level in the fs tree */
35 typedef enum
36 {
37   LEVEL_NONE,
38   LEVEL_DOMAIN,
39   LEVEL_BUS,
40   LEVEL_DEV,
41   LEVEL_FUNC
42 } tree_level;
43
44 /* Check whether there's a pci server */
45 static int
46 hurd_detect(struct pci_access *a)
47 {
48   int err;
49   struct stat st;
50
51   err = stat(_SERVERS_BUS_PCI, &st);
52   if (err)
53     {
54       a->error("Could not open file `%s'", _SERVERS_BUS_PCI);
55       return 0;
56     }
57
58   /* The node must be a directory and a translator */
59   return S_ISDIR(st.st_mode) && ((st.st_mode & S_ITRANS) == S_IROOT);
60 }
61
62 /* Empty callbacks, we don't need any special init or cleanup */
63 static void
64 hurd_init(struct pci_access *a UNUSED)
65 {
66 }
67
68 static void
69 hurd_cleanup(struct pci_access *a UNUSED)
70 {
71 }
72
73 /* Each device has its own server path. Allocate space for the port. */
74 static void
75 hurd_init_dev(struct pci_dev *d)
76 {
77   d->aux = pci_malloc(d->access, sizeof(mach_port_t));
78   *((mach_port_t *) d->aux) = MACH_PORT_NULL;
79 }
80
81 /* Deallocate the port and free its space */
82 static void
83 hurd_cleanup_dev(struct pci_dev *d)
84 {
85   mach_port_t device_port;
86
87   device_port = *((mach_port_t *) d->aux);
88   mach_port_deallocate(mach_task_self(), device_port);
89
90   pci_mfree(d->aux);
91 }
92
93 static mach_port_t
94 device_port_lookup(struct pci_dev *d)
95 {
96   char server[NAME_MAX];
97   mach_port_t device_port = *((mach_port_t *) d->aux);
98
99   if (device_port != MACH_PORT_NULL)
100     return device_port;
101
102   snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s",
103     _SERVERS_BUS_PCI, d->domain, d->bus, d->dev, d->func,
104     FILE_CONFIG_NAME);
105   device_port = file_name_lookup(server, 0, 0);
106
107   if (device_port == MACH_PORT_NULL)
108     d->access->error("Cannot find the PCI arbiter");
109
110   *((mach_port_t *) d->aux) = device_port;
111   return device_port;
112 }
113
114 /* Walk through the FS tree to see what is allowed for us */
115 static void
116 enum_devices(const char *parent, struct pci_access *a, int domain, int bus,
117              int dev, int func, tree_level lev)
118 {
119   int ret;
120   DIR *dir;
121   struct dirent *entry;
122   char path[NAME_MAX];
123   struct pci_dev *d;
124
125   dir = opendir(parent);
126   if (!dir)
127     {
128       if (errno == EPERM || errno == EACCES)
129         /* The client lacks the permissions to access this function, skip */
130         return;
131       else
132         a->error("Cannot open directory: %s (%s)", parent, strerror(errno));
133     }
134
135   while ((entry = readdir(dir)) != 0)
136     {
137       snprintf(path, NAME_MAX, "%s/%s", parent, entry->d_name);
138       if (entry->d_type == DT_DIR)
139         {
140           if (!strncmp(entry->d_name, ".", NAME_MAX)
141               || !strncmp(entry->d_name, "..", NAME_MAX))
142             continue;
143
144           errno = 0;
145           ret = strtol(entry->d_name, 0, 16);
146           if (errno)
147             {
148               if (closedir(dir) < 0)
149                 a->warning("Cannot close directory: %s (%s)", parent,
150                            strerror(errno));
151               a->error("Wrong directory name: %s (number expected) probably "
152                        "not connected to an arbiter", entry->d_name);
153             }
154
155           /*
156            * We found a valid directory.
157            * Update the address and switch to the next level.
158            */
159           switch (lev)
160             {
161             case LEVEL_DOMAIN:
162               domain = ret;
163               break;
164             case LEVEL_BUS:
165               bus = ret;
166               break;
167             case LEVEL_DEV:
168               dev = ret;
169               break;
170             case LEVEL_FUNC:
171               func = ret;
172               break;
173             default:
174               if (closedir(dir) < 0)
175                 a->warning("Cannot close directory: %s (%s)", parent,
176                            strerror(errno));
177               a->error("Wrong directory tree, probably not connected to an arbiter");
178             }
179
180           enum_devices(path, a, domain, bus, dev, func, lev + 1);
181         }
182       else
183         {
184           if (strncmp(entry->d_name, FILE_CONFIG_NAME, NAME_MAX))
185             /* We are looking for the config file */
186             continue;
187
188           /* We found an available virtual device, add it to our list */
189           d = pci_alloc_dev(a);
190           d->domain = domain;
191           d->bus = bus;
192           d->dev = dev;
193           d->func = func;
194           pci_link_dev(a, d);
195         }
196     }
197
198   if (closedir(dir) < 0)
199     a->error("Cannot close directory: %s (%s)", parent, strerror(errno));
200 }
201
202 /* Enumerate devices */
203 static void
204 hurd_scan(struct pci_access *a)
205 {
206   enum_devices(_SERVERS_BUS_PCI, a, -1, -1, -1, -1, LEVEL_DOMAIN);
207 }
208
209 /*
210  * Read `len' bytes to `buf'.
211  *
212  * Returns error when the number of read bytes does not match `len'.
213  */
214 static int
215 hurd_read(struct pci_dev *d, int pos, byte * buf, int len)
216 {
217   int err;
218   size_t nread;
219   char *data;
220   mach_port_t device_port = device_port_lookup(d);
221
222   if (len > 4)
223     return pci_generic_block_read(d, pos, buf, len);
224
225   data = (char *) buf;
226   err = pci_conf_read(device_port, pos, &data, &nread, len);
227
228   if (data != (char *) buf)
229     {
230       if (nread > (size_t) len) /* Sanity check for bogus server.  */
231         {
232           vm_deallocate(mach_task_self(), (vm_address_t) data, nread);
233           return 0;
234         }
235
236       memcpy(buf, data, nread);
237       vm_deallocate(mach_task_self(), (vm_address_t) data, nread);
238     }
239
240   return !err && nread == (size_t) len;
241 }
242
243 /*
244  * Write `len' bytes from `buf'.
245  *
246  * Returns error when the number of written bytes does not match `len'.
247  */
248 static int
249 hurd_write(struct pci_dev *d, int pos, byte * buf, int len)
250 {
251   int err;
252   size_t nwrote;
253   mach_port_t device_port = device_port_lookup(d);
254
255   if (len > 4)
256     return pci_generic_block_write(d, pos, buf, len);
257
258   err = pci_conf_write(device_port, pos, (char *) buf, len, &nwrote);
259
260   return !err && nwrote == (size_t) len;
261 }
262
263 /* Get requested info from the server */
264
265 static int
266 hurd_fill_regions(struct pci_dev *d)
267 {
268   mach_port_t device_port = device_port_lookup(d);
269   struct pci_bar regions[6];
270   char *buf = (char *) &regions;
271   size_t size = sizeof(regions);
272
273   int err = pci_get_dev_regions(device_port, &buf, &size);
274   if (err)
275     return 0;
276
277   if ((char *) &regions != buf)
278     {
279       /* Sanity check for bogus server.  */
280       if (size > sizeof(regions))
281         {
282           vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
283           return 0;
284         }
285
286       memcpy(&regions, buf, size);
287       vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
288     }
289
290   for (int i = 0; i < 6; i++)
291     {
292       if (regions[i].size == 0)
293         continue;
294
295       d->base_addr[i] = regions[i].base_addr;
296       d->base_addr[i] |= regions[i].is_IO;
297       d->base_addr[i] |= regions[i].is_64 << 2;
298       d->base_addr[i] |= regions[i].is_prefetchable << 3;
299
300       d->size[i] = regions[i].size;
301     }
302
303   return 1;
304 }
305
306 static int
307 hurd_fill_rom(struct pci_dev *d)
308 {
309   struct pci_xrom_bar rom;
310   mach_port_t device_port = device_port_lookup(d);
311   char *buf = (char *) &rom;
312   size_t size = sizeof(rom);
313
314   int err = pci_get_dev_rom(device_port, &buf, &size);
315   if (err)
316     return 0;
317
318   if ((char *) &rom != buf)
319     {
320       /* Sanity check for bogus server.  */
321       if (size > sizeof(rom))
322         {
323           vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
324           return 0;
325         }
326
327       memcpy(&rom, buf, size);
328       vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
329     }
330
331   d->rom_base_addr = rom.base_addr;
332   d->rom_size = rom.size;
333
334   return 1;
335 }
336
337 static void
338 hurd_fill_info(struct pci_dev *d, unsigned int flags)
339 {
340   if (!d->access->buscentric)
341     {
342       if (want_fill(d, flags, PCI_FILL_BASES | PCI_FILL_SIZES))
343         {
344           if (hurd_fill_regions(d))
345             clear_fill(d, PCI_FILL_BASES | PCI_FILL_SIZES);
346         }
347       if (want_fill(d, flags, PCI_FILL_ROM_BASE))
348         {
349           if (hurd_fill_rom(d))
350             clear_fill(d, PCI_FILL_ROM_BASE);
351         }
352     }
353
354   pci_generic_fill_info(d, flags);
355 }
356
357 struct pci_methods pm_hurd = {
358   "hurd",
359   "Hurd access using RPCs",
360   NULL,                         /* config */
361   hurd_detect,
362   hurd_init,
363   hurd_cleanup,
364   hurd_scan,
365   hurd_fill_info,
366   hurd_read,
367   hurd_write,
368   NULL,                         /* read_vpd */
369   hurd_init_dev,
370   hurd_cleanup_dev
371 };