1 /*
2 * The PCI Library -- Hurd access via RPCs
3 *
4 * Copyright (c) 2017 Joan Lledó <[email protected]>
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
hurd_detect(struct pci_access * a)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
hurd_init(struct pci_access * a UNUSED)64 hurd_init(struct pci_access *a UNUSED)
65 {
66 }
67
68 static void
hurd_cleanup(struct pci_access * a UNUSED)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
hurd_init_dev(struct pci_dev * d)75 hurd_init_dev(struct pci_dev *d)
76 {
77 d->backend_data = pci_malloc(d->access, sizeof(mach_port_t));
78 *((mach_port_t *) d->backend_data) = MACH_PORT_NULL;
79 }
80
81 /* Deallocate the port and free its space */
82 static void
hurd_cleanup_dev(struct pci_dev * d)83 hurd_cleanup_dev(struct pci_dev *d)
84 {
85 mach_port_t device_port;
86
87 device_port = *((mach_port_t *) d->backend_data);
88 mach_port_deallocate(mach_task_self(), device_port);
89
90 pci_mfree(d->backend_data);
91 d->backend_data = NULL;
92 }
93
94 static mach_port_t
device_port_lookup(struct pci_dev * d)95 device_port_lookup(struct pci_dev *d)
96 {
97 char server[NAME_MAX];
98 mach_port_t device_port = *((mach_port_t *) d->backend_data);
99
100 if (device_port != MACH_PORT_NULL)
101 return device_port;
102
103 snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s",
104 _SERVERS_BUS_PCI, d->domain, d->bus, d->dev, d->func,
105 FILE_CONFIG_NAME);
106 device_port = file_name_lookup(server, 0, 0);
107
108 if (device_port == MACH_PORT_NULL)
109 d->access->error("Cannot find the PCI arbiter");
110
111 *((mach_port_t *) d->backend_data) = device_port;
112 return device_port;
113 }
114
115 /* Walk through the FS tree to see what is allowed for us */
116 static void
enum_devices(const char * parent,struct pci_access * a,int domain,int bus,int dev,int func,tree_level lev)117 enum_devices(const char *parent, struct pci_access *a, int domain, int bus,
118 int dev, int func, tree_level lev)
119 {
120 int ret;
121 DIR *dir;
122 struct dirent *entry;
123 char path[NAME_MAX];
124 struct pci_dev *d;
125
126 dir = opendir(parent);
127 if (!dir)
128 {
129 if (errno == EPERM || errno == EACCES)
130 /* The client lacks the permissions to access this function, skip */
131 return;
132 else
133 a->error("Cannot open directory: %s (%s)", parent, strerror(errno));
134 }
135
136 while ((entry = readdir(dir)) != 0)
137 {
138 snprintf(path, NAME_MAX, "%s/%s", parent, entry->d_name);
139 if (entry->d_type == DT_DIR)
140 {
141 if (!strncmp(entry->d_name, ".", NAME_MAX)
142 || !strncmp(entry->d_name, "..", NAME_MAX))
143 continue;
144
145 errno = 0;
146 ret = strtol(entry->d_name, 0, 16);
147 if (errno)
148 {
149 if (closedir(dir) < 0)
150 a->warning("Cannot close directory: %s (%s)", parent,
151 strerror(errno));
152 a->error("Wrong directory name: %s (number expected) probably "
153 "not connected to an arbiter", entry->d_name);
154 }
155
156 /*
157 * We found a valid directory.
158 * Update the address and switch to the next level.
159 */
160 switch (lev)
161 {
162 case LEVEL_DOMAIN:
163 domain = ret;
164 break;
165 case LEVEL_BUS:
166 bus = ret;
167 break;
168 case LEVEL_DEV:
169 dev = ret;
170 break;
171 case LEVEL_FUNC:
172 func = ret;
173 break;
174 default:
175 if (closedir(dir) < 0)
176 a->warning("Cannot close directory: %s (%s)", parent,
177 strerror(errno));
178 a->error("Wrong directory tree, probably not connected to an arbiter");
179 }
180
181 enum_devices(path, a, domain, bus, dev, func, lev + 1);
182 }
183 else
184 {
185 if (strncmp(entry->d_name, FILE_CONFIG_NAME, NAME_MAX))
186 /* We are looking for the config file */
187 continue;
188
189 /* We found an available virtual device, add it to our list */
190 d = pci_alloc_dev(a);
191 d->domain = domain;
192 d->bus = bus;
193 d->dev = dev;
194 d->func = func;
195 pci_link_dev(a, d);
196 }
197 }
198
199 if (closedir(dir) < 0)
200 a->error("Cannot close directory: %s (%s)", parent, strerror(errno));
201 }
202
203 /* Enumerate devices */
204 static void
hurd_scan(struct pci_access * a)205 hurd_scan(struct pci_access *a)
206 {
207 enum_devices(_SERVERS_BUS_PCI, a, -1, -1, -1, -1, LEVEL_DOMAIN);
208 }
209
210 /*
211 * Read `len' bytes to `buf'.
212 *
213 * Returns error when the number of read bytes does not match `len'.
214 */
215 static int
hurd_read(struct pci_dev * d,int pos,byte * buf,int len)216 hurd_read(struct pci_dev *d, int pos, byte * buf, int len)
217 {
218 int err;
219 size_t nread;
220 char *data;
221 mach_port_t device_port = device_port_lookup(d);
222
223 if (len > 4)
224 return pci_generic_block_read(d, pos, buf, len);
225
226 data = (char *) buf;
227 err = pci_conf_read(device_port, pos, &data, &nread, len);
228
229 if (data != (char *) buf)
230 {
231 if (nread > (size_t) len) /* Sanity check for bogus server. */
232 {
233 vm_deallocate(mach_task_self(), (vm_address_t) data, nread);
234 return 0;
235 }
236
237 memcpy(buf, data, nread);
238 vm_deallocate(mach_task_self(), (vm_address_t) data, nread);
239 }
240
241 return !err && nread == (size_t) len;
242 }
243
244 /*
245 * Write `len' bytes from `buf'.
246 *
247 * Returns error when the number of written bytes does not match `len'.
248 */
249 static int
hurd_write(struct pci_dev * d,int pos,byte * buf,int len)250 hurd_write(struct pci_dev *d, int pos, byte * buf, int len)
251 {
252 int err;
253 size_t nwrote;
254 mach_port_t device_port = device_port_lookup(d);
255
256 if (len > 4)
257 return pci_generic_block_write(d, pos, buf, len);
258
259 err = pci_conf_write(device_port, pos, (char *) buf, len, &nwrote);
260
261 return !err && nwrote == (size_t) len;
262 }
263
264 /* Get requested info from the server */
265
266 static int
hurd_fill_regions(struct pci_dev * d)267 hurd_fill_regions(struct pci_dev *d)
268 {
269 mach_port_t device_port = device_port_lookup(d);
270 struct pci_bar regions[6];
271 char *buf = (char *) ®ions;
272 size_t size = sizeof(regions);
273
274 int err = pci_get_dev_regions(device_port, &buf, &size);
275 if (err)
276 return 0;
277
278 if ((char *) ®ions != buf)
279 {
280 /* Sanity check for bogus server. */
281 if (size > sizeof(regions))
282 {
283 vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
284 return 0;
285 }
286
287 memcpy(®ions, buf, size);
288 vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
289 }
290
291 for (int i = 0; i < 6; i++)
292 {
293 if (regions[i].size == 0)
294 continue;
295
296 d->base_addr[i] = regions[i].base_addr;
297 d->base_addr[i] |= regions[i].is_IO;
298 d->base_addr[i] |= regions[i].is_64 << 2;
299 d->base_addr[i] |= regions[i].is_prefetchable << 3;
300
301 d->size[i] = regions[i].size;
302 }
303
304 return 1;
305 }
306
307 static int
hurd_fill_rom(struct pci_dev * d)308 hurd_fill_rom(struct pci_dev *d)
309 {
310 struct pci_xrom_bar rom;
311 mach_port_t device_port = device_port_lookup(d);
312 char *buf = (char *) &rom;
313 size_t size = sizeof(rom);
314
315 int err = pci_get_dev_rom(device_port, &buf, &size);
316 if (err)
317 return 0;
318
319 if ((char *) &rom != buf)
320 {
321 /* Sanity check for bogus server. */
322 if (size > sizeof(rom))
323 {
324 vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
325 return 0;
326 }
327
328 memcpy(&rom, buf, size);
329 vm_deallocate(mach_task_self(), (vm_address_t) buf, size);
330 }
331
332 d->rom_base_addr = rom.base_addr;
333 d->rom_size = rom.size;
334
335 return 1;
336 }
337
338 static void
hurd_fill_info(struct pci_dev * d,unsigned int flags)339 hurd_fill_info(struct pci_dev *d, unsigned int flags)
340 {
341 if (!d->access->buscentric)
342 {
343 if (want_fill(d, flags, PCI_FILL_BASES | PCI_FILL_SIZES))
344 {
345 if (hurd_fill_regions(d))
346 clear_fill(d, PCI_FILL_BASES | PCI_FILL_SIZES);
347 }
348 if (want_fill(d, flags, PCI_FILL_ROM_BASE))
349 {
350 if (hurd_fill_rom(d))
351 clear_fill(d, PCI_FILL_ROM_BASE);
352 }
353 }
354
355 pci_generic_fill_info(d, flags);
356 }
357
358 struct pci_methods pm_hurd = {
359 .name = "hurd",
360 .help = "Hurd access using RPCs",
361 .detect = hurd_detect,
362 .init = hurd_init,
363 .cleanup = hurd_cleanup,
364 .scan = hurd_scan,
365 .fill_info = hurd_fill_info,
366 .read = hurd_read,
367 .write = hurd_write,
368 .init_dev = hurd_init_dev,
369 .cleanup_dev = hurd_cleanup_dev
370 };
371