kevent & USB serial ports

315 views Asked by At

I'm having trouble using kevent on mac with a USB serial console. I've narrowed it down to:

#include <errno.h>                                                                                                                                          
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/event.h>
#include <sys/ioctl.h>
#include <termios.h>

#define DEVICE "/dev/cu.usbserial-0011111D"

int main() {
  int kqueue_fd = kqueue();
  if (kqueue_fd < 0) {
    printf("Failed to open kqueue\n");
    return -1;
  }
  int device_fd = open(DEVICE, O_RDWR | O_NONBLOCK | O_NOCTTY);
  if (device_fd < 0) {
    printf("Failed to open device: %s\n", DEVICE);
    return -1;
  }
  printf("Opened %d\n", device_fd);

  enum { MAX_EVENTS = 1 };
  struct kevent events[MAX_EVENTS];

  EV_SET(&events[0], device_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);

  int r = kevent(kqueue_fd, events, 1, NULL, 0, NULL);
  if (r < 0) {
    printf("kevent failed: %s\n", strerror(errno));
    return -1;
  }

  struct timespec sleep_time;
  sleep_time.tv_sec = 5;
  sleep_time.tv_nsec = 0;

  int ready = kevent(kqueue_fd, NULL, 0, (struct kevent*) &events,
                     MAX_EVENTS, &sleep_time);
  if (ready == 0) {
    printf("No event\n");
    return 0;
  }
  for (int i = 0; i < ready; i++) {
    printf(".ident %ld, .filter %d, .flags 0x%x, .fflags 0x%x, "
           ".data: %ld, .udata %p\n",
           events[i].ident,
           events[i].filter,
           events[i].flags,
           events[i].fflags,
           events[i].data,
           events[i].udata);

    int unread = 0;
    r = ioctl(events[i].ident, FIONREAD, &unread);
    if (r < 0) {
      printf("ioctl failed: %d: %s\n", errno, strerror(errno));
    }
  }
}

When I run this and unplug the USB device in the middle of the call to kevent(), I get:

Opened 4
.ident 4, .filter -1, .flags 0x1, .fflags 0x0, .data: 6, .udata 0x0
ioctl failed: 6: Device not configured

My understanding is that the contents of the event translates to:

FD 4, EVFILT_READ, EV_ADD, 6 bytes remaining on fd. But the ioctl() fails (since the device was removed), and errno is also 6, so it seems as if event.data is returning the errno, not the bytes remaining.

How can I differentiate between the normal read case and the case where the device has been removed? The filter, flags & fflags appear the same in both cases.

Additional Information

If I switch from opening the serial console to a pipe, and write a single byte followed by closing the write end, I get:

pipe() fd: 5 -> 4
.ident 4, .filter -1, .flags 0x1, .fflags 0x0, .data: 1, .udata 0x0
.ident 4, .filter -1, .flags 0x8001, .fflags 0x0, .data: 0, .udata 0x0

This is what I expect, since 0x8000 is EV_EOF.

0

There are 0 answers