serial: Make serial device event sources more robust

Disallow polling for input/error and output-ready events at the
same time, and ensure only a single FD event source is installed.
Also, do not leak if the FD event source is removed by means
other than calling serial_source_remove().
This commit is contained in:
Daniel Elstner 2015-09-11 23:08:10 +02:00 committed by Uwe Hermann
parent 0c536bcd00
commit cbc1413f31
3 changed files with 44 additions and 48 deletions

View File

@ -574,10 +574,6 @@ struct sr_serial_dev_inst {
char *serialcomm; char *serialcomm;
/** libserialport port handle */ /** libserialport port handle */
struct sp_port *data; struct sp_port *data;
/** libserialport event set */
struct sp_event_set *event_set;
/** GPollFDs for event polling */
GPollFD *pollfds;
}; };
#endif #endif
@ -732,6 +728,9 @@ SR_PRIV int sr_session_source_remove_internal(struct sr_session *session,
void *key); void *key);
SR_PRIV int sr_session_source_destroyed(struct sr_session *session, SR_PRIV int sr_session_source_destroyed(struct sr_session *session,
void *key, GSource *source); void *key, GSource *source);
SR_PRIV int sr_session_fd_source_add(struct sr_session *session,
void *key, gintptr fd, int events, int timeout,
sr_receive_data_callback cb, void *cb_data);
SR_PRIV int sr_session_send(const struct sr_dev_inst *sdi, SR_PRIV int sr_session_send(const struct sr_dev_inst *sdi,
const struct sr_datafeed_packet *packet); const struct sr_datafeed_packet *packet);
SR_PRIV int sr_sessionfile_check(const char *filename); SR_PRIV int sr_sessionfile_check(const char *filename);

View File

@ -799,10 +799,17 @@ SR_PRIV int serial_source_add(struct sr_session *session,
struct sr_serial_dev_inst *serial, int events, int timeout, struct sr_serial_dev_inst *serial, int events, int timeout,
sr_receive_data_callback cb, void *cb_data) sr_receive_data_callback cb, void *cb_data)
{ {
struct sp_event_set *event_set;
gintptr poll_fd;
unsigned int poll_events;
enum sp_event mask = 0; enum sp_event mask = 0;
unsigned int i;
if (sp_new_event_set(&serial->event_set) != SP_OK) if ((events & (G_IO_IN|G_IO_ERR)) && (events & G_IO_OUT)) {
sr_err("Cannot poll input/error and output simultaneously.");
return SR_ERR_ARG;
}
if (sp_new_event_set(&event_set) != SP_OK)
return SR_ERR; return SR_ERR;
if (events & G_IO_IN) if (events & G_IO_IN)
@ -812,54 +819,44 @@ SR_PRIV int serial_source_add(struct sr_session *session,
if (events & G_IO_ERR) if (events & G_IO_ERR)
mask |= SP_EVENT_ERROR; mask |= SP_EVENT_ERROR;
if (sp_add_port_events(serial->event_set, serial->data, mask) != SP_OK) { if (sp_add_port_events(event_set, serial->data, mask) != SP_OK) {
sp_free_event_set(serial->event_set); sp_free_event_set(event_set);
return SR_ERR;
}
if (event_set->count != 1) {
sr_err("Unexpected number (%u) of event handles to poll.",
event_set->count);
sp_free_event_set(event_set);
return SR_ERR; return SR_ERR;
} }
serial->pollfds = g_new0(GPollFD, serial->event_set->count); poll_fd = (gintptr) ((event_handle *)event_set->handles)[0];
mask = event_set->masks[0];
for (i = 0; i < serial->event_set->count; i++) { sp_free_event_set(event_set);
serial->pollfds[i].fd = (gintptr) poll_events = 0;
((event_handle *)serial->event_set->handles)[i]; if (mask & SP_EVENT_RX_READY)
mask = serial->event_set->masks[i]; poll_events |= G_IO_IN;
if (mask & SP_EVENT_TX_READY)
if (mask & SP_EVENT_RX_READY) poll_events |= G_IO_OUT;
serial->pollfds[i].events |= G_IO_IN; if (mask & SP_EVENT_ERROR)
if (mask & SP_EVENT_TX_READY) poll_events |= G_IO_ERR;
serial->pollfds[i].events |= G_IO_OUT; /*
if (mask & SP_EVENT_ERROR) * Using serial->data as the key for the event source is not quite
serial->pollfds[i].events |= G_IO_ERR; * proper, as it makes it impossible to create another event source
* for the same serial port. However, these fixed keys will soon be
if (sr_session_source_add_pollfd(session, &serial->pollfds[i], * removed from the API anyway, so this is OK for now.
timeout, cb, cb_data) != SR_OK) */
return SR_ERR; return sr_session_fd_source_add(session, serial->data,
} poll_fd, poll_events, timeout, cb, cb_data);
return SR_OK;
} }
/** @private */ /** @private */
SR_PRIV int serial_source_remove(struct sr_session *session, SR_PRIV int serial_source_remove(struct sr_session *session,
struct sr_serial_dev_inst *serial) struct sr_serial_dev_inst *serial)
{ {
unsigned int i; return sr_session_source_remove_internal(session, serial->data);
if (!serial->event_set)
return SR_OK;
for (i = 0; i < serial->event_set->count; i++)
if (sr_session_source_remove_pollfd(session, &serial->pollfds[i]) != SR_OK)
return SR_ERR;
g_free(serial->pollfds);
sp_free_event_set(serial->event_set);
serial->pollfds = NULL;
serial->event_set = NULL;
return SR_OK;
} }
/** /**

View File

@ -985,8 +985,8 @@ SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
return ret; return ret;
} }
static int attach_fd_source(struct sr_session *session, SR_PRIV int sr_session_fd_source_add(struct sr_session *session,
void *key, int fd, int events, int timeout, void *key, gintptr fd, int events, int timeout,
sr_receive_data_callback cb, void *cb_data) sr_receive_data_callback cb, void *cb_data)
{ {
GSource *source; GSource *source;
@ -1027,7 +1027,7 @@ SR_API int sr_session_source_add(struct sr_session *session, int fd,
sr_err("Cannot create timer source without timeout."); sr_err("Cannot create timer source without timeout.");
return SR_ERR_ARG; return SR_ERR_ARG;
} }
return attach_fd_source(session, GINT_TO_POINTER(fd), return sr_session_fd_source_add(session, GINT_TO_POINTER(fd),
fd, events, timeout, cb, cb_data); fd, events, timeout, cb, cb_data);
} }
@ -1054,7 +1054,7 @@ SR_API int sr_session_source_add_pollfd(struct sr_session *session,
sr_err("%s: pollfd was NULL", __func__); sr_err("%s: pollfd was NULL", __func__);
return SR_ERR_ARG; return SR_ERR_ARG;
} }
return attach_fd_source(session, pollfd, pollfd->fd, return sr_session_fd_source_add(session, pollfd, pollfd->fd,
pollfd->events, timeout, cb, cb_data); pollfd->events, timeout, cb, cb_data);
} }
@ -1093,7 +1093,7 @@ SR_API int sr_session_source_add_channel(struct sr_session *session,
pollfd.fd = g_io_channel_unix_get_fd(channel); pollfd.fd = g_io_channel_unix_get_fd(channel);
pollfd.events = events; pollfd.events = events;
#endif #endif
return attach_fd_source(session, channel, pollfd.fd, return sr_session_fd_source_add(session, channel, pollfd.fd,
pollfd.events, timeout, cb, cb_data); pollfd.events, timeout, cb, cb_data);
} }