1 /*
2 * Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 /*
8 * @file linux/device.c
9 * @brief Linux libmetal device operations.
10 */
11
12 #include <metal/device.h>
13 #include <metal/sys.h>
14 #include <metal/utilities.h>
15 #include <metal/irq.h>
16
17 #define MAX_DRIVERS 64
18
19 struct linux_bus;
20 struct linux_device;
21
22 struct linux_driver {
23 const char *drv_name;
24 const char *mod_name;
25 const char *cls_name;
26 struct sysfs_driver *sdrv;
27 int (*dev_open)(struct linux_bus *lbus,
28 struct linux_device *ldev);
29 void (*dev_close)(struct linux_bus *lbus,
30 struct linux_device *ldev);
31 void (*dev_irq_ack)(struct linux_bus *lbus,
32 struct linux_device *ldev,
33 int irq);
34 int (*dev_dma_map)(struct linux_bus *lbus,
35 struct linux_device *ldev,
36 uint32_t dir,
37 struct metal_sg *sg_in,
38 int nents_in,
39 struct metal_sg *sg_out);
40 void (*dev_dma_unmap)(struct linux_bus *lbus,
41 struct linux_device *ldev,
42 uint32_t dir,
43 struct metal_sg *sg,
44 int nents);
45 };
46
47 struct linux_bus {
48 struct metal_bus bus;
49 const char *bus_name;
50 struct linux_driver drivers[MAX_DRIVERS];
51 struct sysfs_bus *sbus;
52 };
53
54 struct linux_device {
55 struct metal_device device;
56 char dev_name[PATH_MAX];
57 char dev_path[PATH_MAX];
58 char cls_path[PATH_MAX];
59 metal_phys_addr_t region_phys[METAL_MAX_DEVICE_REGIONS];
60 struct linux_driver *ldrv;
61 struct sysfs_device *sdev;
62 struct sysfs_attribute *override;
63 int fd;
64 };
65
to_linux_bus(struct metal_bus * bus)66 static struct linux_bus *to_linux_bus(struct metal_bus *bus)
67 {
68 return metal_container_of(bus, struct linux_bus, bus);
69 }
70
to_linux_device(struct metal_device * device)71 static struct linux_device *to_linux_device(struct metal_device *device)
72 {
73 return metal_container_of(device, struct linux_device, device);
74 }
75
metal_uio_read_map_attr(struct linux_device * ldev,unsigned int index,const char * name,unsigned long * value)76 static int metal_uio_read_map_attr(struct linux_device *ldev,
77 unsigned int index,
78 const char *name,
79 unsigned long *value)
80 {
81 const char *cls = ldev->cls_path;
82 struct sysfs_attribute *attr;
83 char path[SYSFS_PATH_MAX];
84 int result;
85
86 result = snprintf(path, sizeof(path), "%s/maps/map%u/%s", cls, index, name);
87 if (result >= (int)sizeof(path))
88 return -EOVERFLOW;
89 attr = sysfs_open_attribute(path);
90 if (!attr || sysfs_read_attribute(attr) != 0) {
91 sysfs_close_attribute(attr);
92 return -errno;
93 }
94
95 *value = strtoul(attr->value, NULL, 0);
96
97 sysfs_close_attribute(attr);
98 return 0;
99 }
100
metal_uio_dev_bind(struct linux_device * ldev,struct linux_driver * ldrv)101 static int metal_uio_dev_bind(struct linux_device *ldev,
102 struct linux_driver *ldrv)
103 {
104 struct sysfs_attribute *attr;
105 int result;
106
107 if (strcmp(ldev->sdev->driver_name, ldrv->drv_name) == 0)
108 return 0;
109
110 if (strcmp(ldev->sdev->driver_name, SYSFS_UNKNOWN) != 0) {
111 metal_log(METAL_LOG_INFO, "device %s in use by driver %s\n",
112 ldev->dev_name, ldev->sdev->driver_name);
113 return -EBUSY;
114 }
115
116 attr = sysfs_get_device_attr(ldev->sdev, "driver_override");
117 if (!attr) {
118 metal_log(METAL_LOG_ERROR, "device %s has no override\n",
119 ldev->dev_name);
120 return -errno;
121 }
122
123 result = sysfs_write_attribute(attr, ldrv->drv_name,
124 strlen(ldrv->drv_name));
125 if (result) {
126 metal_log(METAL_LOG_ERROR, "failed to set override on %s\n",
127 ldev->dev_name);
128 return -errno;
129 }
130 ldev->override = attr;
131
132 attr = sysfs_get_driver_attr(ldrv->sdrv, "bind");
133 if (!attr) {
134 metal_log(METAL_LOG_ERROR, "driver %s has no bind\n", ldrv->drv_name);
135 return -ENOTSUP;
136 }
137
138 result = sysfs_write_attribute(attr, ldev->dev_name,
139 strlen(ldev->dev_name));
140 if (result) {
141 metal_log(METAL_LOG_ERROR, "failed to bind %s to %s\n",
142 ldev->dev_name, ldrv->drv_name);
143 return -errno;
144 }
145
146 metal_log(METAL_LOG_DEBUG, "bound device %s to driver %s\n",
147 ldev->dev_name, ldrv->drv_name);
148
149 return 0;
150 }
151
metal_uio_dev_open(struct linux_bus * lbus,struct linux_device * ldev)152 static int metal_uio_dev_open(struct linux_bus *lbus, struct linux_device *ldev)
153 {
154 char *instance, path[SYSFS_PATH_MAX];
155 struct linux_driver *ldrv = ldev->ldrv;
156 unsigned long *phys, offset = 0, size = 0;
157 struct metal_io_region *io;
158 struct dlist *dlist;
159 int result, i;
160 void *virt;
161 int irq_info;
162
163
164 ldev->fd = -1;
165 ldev->device.irq_info = (void *)-1;
166
167 ldev->sdev = sysfs_open_device(lbus->bus_name, ldev->dev_name);
168 if (!ldev->sdev) {
169 metal_log(METAL_LOG_ERROR, "device %s:%s not found\n",
170 lbus->bus_name, ldev->dev_name);
171 return -ENODEV;
172 }
173 metal_log(METAL_LOG_DEBUG, "opened sysfs device %s:%s\n",
174 lbus->bus_name, ldev->dev_name);
175
176 result = metal_uio_dev_bind(ldev, ldrv);
177 if (result)
178 return result;
179
180 result = snprintf(path, sizeof(path), "%s/uio", ldev->sdev->path);
181 if (result >= (int)sizeof(path))
182 return -EOVERFLOW;
183 dlist = sysfs_open_directory_list(path);
184 if (!dlist) {
185 metal_log(METAL_LOG_ERROR, "failed to scan class path %s\n",
186 path);
187 return -errno;
188 }
189
190 dlist_for_each_data(dlist, instance, char) {
191 result = snprintf(ldev->cls_path, sizeof(ldev->cls_path),
192 "%s/%s", path, instance);
193 if (result >= (int)sizeof(ldev->cls_path))
194 return -EOVERFLOW;
195 result = snprintf(ldev->dev_path, sizeof(ldev->dev_path),
196 "/dev/%s", instance);
197 if (result >= (int)sizeof(ldev->dev_path))
198 return -EOVERFLOW;
199 break;
200 }
201 sysfs_close_list(dlist);
202
203 if (sysfs_path_is_dir(ldev->cls_path) != 0) {
204 metal_log(METAL_LOG_ERROR, "invalid device class path %s\n",
205 ldev->cls_path);
206 return -ENODEV;
207 }
208
209 i = 0;
210 do {
211 if (!access(ldev->dev_path, F_OK))
212 break;
213 usleep(10);
214 i++;
215 } while (i < 1000);
216 if (i >= 1000) {
217 metal_log(METAL_LOG_ERROR, "failed to open file %s, timeout.\n",
218 ldev->dev_path);
219 return -ENODEV;
220 }
221 result = metal_open(ldev->dev_path, 0);
222 if (result < 0) {
223 metal_log(METAL_LOG_ERROR, "failed to open device %s\n",
224 ldev->dev_path, strerror(-result));
225 return result;
226 }
227 ldev->fd = result;
228
229 metal_log(METAL_LOG_DEBUG, "opened %s:%s as %s\n",
230 lbus->bus_name, ldev->dev_name, ldev->dev_path);
231
232 for (i = 0, result = 0; !result && i < METAL_MAX_DEVICE_REGIONS; i++) {
233 phys = &ldev->region_phys[ldev->device.num_regions];
234 result = (result ? result :
235 metal_uio_read_map_attr(ldev, i, "offset", &offset));
236 result = (result ? result :
237 metal_uio_read_map_attr(ldev, i, "addr", phys));
238 result = (result ? result :
239 metal_uio_read_map_attr(ldev, i, "size", &size));
240 result = (result ? result :
241 metal_map(ldev->fd, i * getpagesize(), size, 0, 0, &virt));
242 if (!result) {
243 io = &ldev->device.regions[ldev->device.num_regions];
244 metal_io_init(io, virt, phys, size, -1, 0, NULL);
245 ldev->device.num_regions++;
246 }
247 }
248
249 irq_info = 1;
250 if (write(ldev->fd, &irq_info, sizeof(irq_info)) <= 0) {
251 metal_log(METAL_LOG_INFO,
252 "%s: No IRQ for device %s.\n",
253 __func__, ldev->dev_name);
254 ldev->device.irq_num = 0;
255 ldev->device.irq_info = (void *)-1;
256 } else {
257 ldev->device.irq_num = 1;
258 ldev->device.irq_info = (void *)(intptr_t)ldev->fd;
259 metal_linux_irq_register_dev(&ldev->device, ldev->fd);
260 }
261
262 return 0;
263 }
264
metal_uio_dev_close(struct linux_bus * lbus,struct linux_device * ldev)265 static void metal_uio_dev_close(struct linux_bus *lbus,
266 struct linux_device *ldev)
267 {
268 (void)lbus;
269 unsigned int i;
270
271 for (i = 0; i < ldev->device.num_regions; i++) {
272 metal_unmap(ldev->device.regions[i].virt,
273 ldev->device.regions[i].size);
274 }
275 if (ldev->override) {
276 sysfs_write_attribute(ldev->override, "", 1);
277 ldev->override = NULL;
278 }
279 if (ldev->sdev) {
280 sysfs_close_device(ldev->sdev);
281 ldev->sdev = NULL;
282 }
283 if (ldev->fd >= 0) {
284 close(ldev->fd);
285 }
286 }
287
metal_uio_dev_irq_ack(struct linux_bus * lbus,struct linux_device * ldev,int irq)288 static void metal_uio_dev_irq_ack(struct linux_bus *lbus,
289 struct linux_device *ldev,
290 int irq)
291 {
292 (void)lbus;
293 (void)irq;
294 int irq_info = 1;
295 unsigned int val;
296 int ret;
297
298 ret = read(ldev->fd, (void *)&val, sizeof(val));
299 if (ret < 0) {
300 metal_log(METAL_LOG_ERROR, "%s, read uio irq fd %d failed: %d.\n",
301 __func__, ldev->fd, ret);
302 return;
303 }
304 ret = write(ldev->fd, &irq_info, sizeof(irq_info));
305 if (ret < 0) {
306 metal_log(METAL_LOG_ERROR, "%s, write uio irq fd %d failed: %d.\n",
307 __func__, ldev->fd, errno);
308 }
309 }
310
metal_uio_dev_dma_map(struct linux_bus * lbus,struct linux_device * ldev,uint32_t dir,struct metal_sg * sg_in,int nents_in,struct metal_sg * sg_out)311 static int metal_uio_dev_dma_map(struct linux_bus *lbus,
312 struct linux_device *ldev,
313 uint32_t dir,
314 struct metal_sg *sg_in,
315 int nents_in,
316 struct metal_sg *sg_out)
317 {
318 int i, j;
319 void *vaddr_sg_lo, *vaddr_sg_hi, *vaddr_lo, *vaddr_hi;
320 struct metal_io_region *io;
321
322 (void)lbus;
323 (void)dir;
324
325 /* Check if the the input virt address is MMIO address */
326 for (i = 0; i < nents_in; i++) {
327 vaddr_sg_lo = sg_in[i].virt;
328 vaddr_sg_hi = vaddr_sg_lo + sg_in[i].len;
329 for (j = 0, io = ldev->device.regions;
330 j < (int)ldev->device.num_regions; j++, io++) {
331 vaddr_lo = io->virt;
332 vaddr_hi = vaddr_lo + io->size;
333 if (vaddr_sg_lo >= vaddr_lo &&
334 vaddr_sg_hi <= vaddr_hi) {
335 break;
336 }
337 }
338 if (j == (int)ldev->device.num_regions) {
339 metal_log(METAL_LOG_WARNING,
340 "%s,%s: input address isn't MMIO addr: 0x%x,%d.\n",
341 __func__, ldev->dev_name, vaddr_sg_lo, sg_in[i].len);
342 return -EINVAL;
343 }
344 }
345 if (sg_out != sg_in)
346 memcpy(sg_out, sg_in, nents_in*(sizeof(struct metal_sg)));
347 return nents_in;
348 }
349
metal_uio_dev_dma_unmap(struct linux_bus * lbus,struct linux_device * ldev,uint32_t dir,struct metal_sg * sg,int nents)350 static void metal_uio_dev_dma_unmap(struct linux_bus *lbus,
351 struct linux_device *ldev,
352 uint32_t dir,
353 struct metal_sg *sg,
354 int nents)
355 {
356 (void) lbus;
357 (void) ldev;
358 (void) dir;
359 (void) sg;
360 (void) nents;
361 }
362
363 static struct linux_bus linux_bus[] = {
364 {
365 .bus_name = "platform",
366 .drivers = {
367 {
368 .drv_name = "uio_pdrv_genirq",
369 .mod_name = "uio_pdrv_genirq",
370 .cls_name = "uio",
371 .dev_open = metal_uio_dev_open,
372 .dev_close = metal_uio_dev_close,
373 .dev_irq_ack = metal_uio_dev_irq_ack,
374 .dev_dma_map = metal_uio_dev_dma_map,
375 .dev_dma_unmap = metal_uio_dev_dma_unmap,
376 },
377 {
378 .drv_name = "uio_dmem_genirq",
379 .mod_name = "uio_dmem_genirq",
380 .cls_name = "uio",
381 .dev_open = metal_uio_dev_open,
382 .dev_close = metal_uio_dev_close,
383 .dev_irq_ack = metal_uio_dev_irq_ack,
384 .dev_dma_map = metal_uio_dev_dma_map,
385 .dev_dma_unmap = metal_uio_dev_dma_unmap,
386 },
387 { 0 /* sentinel */ }
388 }
389 },
390 {
391 .bus_name = "pci",
392 .drivers = {
393 {
394 .drv_name = "vfio-pci",
395 .mod_name = "vfio-pci",
396 },
397 {
398 .drv_name = "uio_pci_generic",
399 .mod_name = "uio_pci_generic",
400 .cls_name = "uio",
401 .dev_open = metal_uio_dev_open,
402 .dev_close = metal_uio_dev_close,
403 .dev_irq_ack = metal_uio_dev_irq_ack,
404 .dev_dma_map = metal_uio_dev_dma_map,
405 .dev_dma_unmap = metal_uio_dev_dma_unmap,
406 },
407 { 0 /* sentinel */ }
408 }
409 },
410 {
411 /* sentinel */
412 .bus_name = NULL,
413 },
414 };
415
416 #define for_each_linux_bus(lbus) \
417 for ((lbus) = linux_bus; (lbus)->bus_name; (lbus)++)
418 #define for_each_linux_driver(lbus, ldrv) \
419 for ((ldrv) = lbus->drivers; (ldrv)->drv_name; (ldrv)++)
420
421
metal_linux_dev_open(struct metal_bus * bus,const char * dev_name,struct metal_device ** device)422 static int metal_linux_dev_open(struct metal_bus *bus,
423 const char *dev_name,
424 struct metal_device **device)
425 {
426 struct linux_bus *lbus = to_linux_bus(bus);
427 struct linux_device *ldev = NULL;
428 struct linux_driver *ldrv;
429 int error;
430
431 ldev = malloc(sizeof(*ldev));
432 if (!ldev)
433 return -ENOMEM;
434
435 for_each_linux_driver(lbus, ldrv) {
436
437 /* Check if we have a viable driver. */
438 if (!ldrv->sdrv || !ldrv->dev_open)
439 continue;
440
441 /* Allocate a linux device if we haven't already. */
442 if (!ldev)
443 ldev = malloc(sizeof(*ldev));
444 if (!ldev)
445 return -ENOMEM;
446
447 /* Reset device data. */
448 memset(ldev, 0, sizeof(*ldev));
449 strncpy(ldev->dev_name, dev_name, sizeof(ldev->dev_name) - 1);
450 ldev->fd = -1;
451 ldev->ldrv = ldrv;
452 ldev->device.bus = bus;
453
454 /* Try and open the device. */
455 error = ldrv->dev_open(lbus, ldev);
456 if (error) {
457 ldrv->dev_close(lbus, ldev);
458 continue;
459 }
460
461 *device = &ldev->device;
462 (*device)->name = ldev->dev_name;
463
464 metal_list_add_tail(&bus->devices, &(*device)->node);
465 return 0;
466 }
467
468 if (ldev)
469 free(ldev);
470
471 return -ENODEV;
472 }
473
metal_linux_dev_close(struct metal_bus * bus,struct metal_device * device)474 static void metal_linux_dev_close(struct metal_bus *bus,
475 struct metal_device *device)
476 {
477 struct linux_device *ldev = to_linux_device(device);
478 struct linux_bus *lbus = to_linux_bus(bus);
479
480 ldev->ldrv->dev_close(lbus, ldev);
481 metal_list_del(&device->node);
482 free(ldev);
483 }
484
metal_linux_bus_close(struct metal_bus * bus)485 static void metal_linux_bus_close(struct metal_bus *bus)
486 {
487 struct linux_bus *lbus = to_linux_bus(bus);
488 struct linux_driver *ldrv;
489
490 for_each_linux_driver(lbus, ldrv) {
491 if (ldrv->sdrv)
492 sysfs_close_driver(ldrv->sdrv);
493 ldrv->sdrv = NULL;
494 }
495
496 sysfs_close_bus(lbus->sbus);
497 lbus->sbus = NULL;
498 }
499
metal_linux_dev_irq_ack(struct metal_bus * bus,struct metal_device * device,int irq)500 static void metal_linux_dev_irq_ack(struct metal_bus *bus,
501 struct metal_device *device,
502 int irq)
503 {
504 struct linux_device *ldev = to_linux_device(device);
505 struct linux_bus *lbus = to_linux_bus(bus);
506
507 return ldev->ldrv->dev_irq_ack(lbus, ldev, irq);
508 }
509
metal_linux_dev_dma_map(struct metal_bus * bus,struct metal_device * device,uint32_t dir,struct metal_sg * sg_in,int nents_in,struct metal_sg * sg_out)510 static int metal_linux_dev_dma_map(struct metal_bus *bus,
511 struct metal_device *device,
512 uint32_t dir,
513 struct metal_sg *sg_in,
514 int nents_in,
515 struct metal_sg *sg_out)
516 {
517 struct linux_device *ldev = to_linux_device(device);
518 struct linux_bus *lbus = to_linux_bus(bus);
519
520 return ldev->ldrv->dev_dma_map(lbus, ldev, dir, sg_in,
521 nents_in, sg_out);
522 }
523
metal_linux_dev_dma_unmap(struct metal_bus * bus,struct metal_device * device,uint32_t dir,struct metal_sg * sg,int nents)524 static void metal_linux_dev_dma_unmap(struct metal_bus *bus,
525 struct metal_device *device,
526 uint32_t dir,
527 struct metal_sg *sg,
528 int nents)
529 {
530 struct linux_device *ldev = to_linux_device(device);
531 struct linux_bus *lbus = to_linux_bus(bus);
532
533 ldev->ldrv->dev_dma_unmap(lbus, ldev, dir, sg,
534 nents);
535 }
536
537 static const struct metal_bus_ops metal_linux_bus_ops = {
538 .bus_close = metal_linux_bus_close,
539 .dev_open = metal_linux_dev_open,
540 .dev_close = metal_linux_dev_close,
541 .dev_irq_ack = metal_linux_dev_irq_ack,
542 .dev_dma_map = metal_linux_dev_dma_map,
543 .dev_dma_unmap = metal_linux_dev_dma_unmap,
544 };
545
metal_linux_register_bus(struct linux_bus * lbus)546 static int metal_linux_register_bus(struct linux_bus *lbus)
547 {
548 lbus->bus.name = lbus->bus_name;
549 lbus->bus.ops = metal_linux_bus_ops;
550 return metal_bus_register(&lbus->bus);
551 }
552
metal_linux_probe_driver(struct linux_bus * lbus,struct linux_driver * ldrv)553 static int metal_linux_probe_driver(struct linux_bus *lbus,
554 struct linux_driver *ldrv)
555 {
556 char command[256];
557 int ret;
558
559 ldrv->sdrv = sysfs_open_driver(lbus->bus_name, ldrv->drv_name);
560
561 /* Try probing the module and then open the driver. */
562 if (!ldrv->sdrv) {
563 ret = snprintf(command, sizeof(command),
564 "modprobe %s > /dev/null 2>&1", ldrv->mod_name);
565 if (ret >= (int)sizeof(command))
566 return -EOVERFLOW;
567 ret = system(command);
568 if (ret < 0) {
569 metal_log(METAL_LOG_WARNING,
570 "%s: executing system command '%s' failed.\n",
571 __func__, command);
572 }
573 ldrv->sdrv = sysfs_open_driver(lbus->bus_name, ldrv->drv_name);
574 }
575
576 /* Try sudo probing the module and then open the driver. */
577 if (!ldrv->sdrv) {
578 ret = snprintf(command, sizeof(command),
579 "sudo modprobe %s > /dev/null 2>&1", ldrv->mod_name);
580 if (ret >= (int)sizeof(command))
581 return -EOVERFLOW;
582 ret = system(command);
583 if (ret < 0) {
584 metal_log(METAL_LOG_WARNING,
585 "%s: executing system command '%s' failed.\n",
586 __func__, command);
587 }
588 ldrv->sdrv = sysfs_open_driver(lbus->bus_name, ldrv->drv_name);
589 }
590
591 /* If all else fails... */
592 return ldrv->sdrv ? 0 : -ENODEV;
593 }
594
metal_linux_probe_bus(struct linux_bus * lbus)595 static int metal_linux_probe_bus(struct linux_bus *lbus)
596 {
597 struct linux_driver *ldrv;
598 int ret, error = -ENODEV;
599
600 lbus->sbus = sysfs_open_bus(lbus->bus_name);
601 if (!lbus->sbus)
602 return -ENODEV;
603
604 for_each_linux_driver(lbus, ldrv) {
605 ret = metal_linux_probe_driver(lbus, ldrv);
606 /* Clear the error if any driver is available */
607 if (!ret)
608 error = ret;
609 }
610
611 if (error) {
612 metal_linux_bus_close(&lbus->bus);
613 return error;
614 }
615
616 error = metal_linux_register_bus(lbus);
617 if (error)
618 metal_linux_bus_close(&lbus->bus);
619
620 return error;
621 }
622
metal_linux_bus_init(void)623 int metal_linux_bus_init(void)
624 {
625 struct linux_bus *lbus;
626 int valid = 0;
627
628 for_each_linux_bus(lbus)
629 valid += metal_linux_probe_bus(lbus) ? 0 : 1;
630
631 return valid ? 0 : -ENODEV;
632 }
633
metal_linux_bus_finish(void)634 void metal_linux_bus_finish(void)
635 {
636 struct linux_bus *lbus;
637 struct metal_bus *bus;
638
639 for_each_linux_bus(lbus) {
640 if (metal_bus_find(lbus->bus_name, &bus) == 0)
641 metal_bus_unregister(bus);
642 }
643 }
644
metal_generic_dev_sys_open(struct metal_device * dev)645 int metal_generic_dev_sys_open(struct metal_device *dev)
646 {
647 (void)dev;
648 return 0;
649 }
650
metal_linux_get_device_property(struct metal_device * device,const char * property_name,void * output,int len)651 int metal_linux_get_device_property(struct metal_device *device,
652 const char *property_name,
653 void *output, int len)
654 {
655 int fd = 0;
656 int status = 0;
657 const int flags = O_RDONLY;
658 const int mode = S_IRUSR | S_IRGRP | S_IROTH;
659 struct linux_device *ldev = to_linux_device(device);
660 char path[PATH_MAX];
661
662 snprintf(path, sizeof(path), "%s/of_node/%s",
663 ldev->sdev->path, property_name);
664 fd = open(path, flags, mode);
665 if (fd < 0)
666 return -errno;
667 if (read(fd, output, len) < 0) {
668 status = -errno;
669 close(fd);
670 return status;
671 }
672
673 status = close(fd);
674 return status < 0 ? -errno : 0;
675 }
676
677