diff options
Diffstat (limited to 'serial/port.c')
-rw-r--r-- | serial/port.c | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/serial/port.c b/serial/port.c index 80e3c6df..7d3b95c8 100644 --- a/serial/port.c +++ b/serial/port.c @@ -25,4 +25,262 @@ #include <config.h> #endif +#include <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <glib.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> + +#include "dbus.h" +#include "dbus-helper.h" +#include "logging.h" + +#include "error.h" +#include "manager.h" #include "port.h" + +#define SERIAL_PORT_INTERFACE "org.bluez.serial.Port" + +/* Waiting for udev to create the device node */ +#define MAX_OPEN_TRIES 5 +#define OPEN_WAIT 300 /* ms */ + +struct rfcomm_node { + int16_t id; /* RFCOMM device id */ + char *name; /* RFCOMM device name */ + DBusConnection *conn; /* for name listener handling */ + char *owner; /* Bus name */ + GIOChannel *io; /* Connected node IO Channel */ + guint io_id; /* IO Channel ID */ +}; + +struct open_context { + char *dev; + open_notify_t notify; + udata_free_t ufree; + void *udata; + int ntries; +}; + +static GSList *connected_nodes = NULL; + +static DBusHandlerResult port_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + /* FIXME: call port_open() */ + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult port_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult port_get_address(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusMethodVTable port_methods[] = { + { "Connect", port_connect, "", "" }, + { "Disconnect", port_disconnect, "", "" }, + { "GetAddress", port_get_address, "", "s" }, + { NULL, NULL, NULL, NULL }, +}; + +static DBusSignalVTable port_signals[] = { + { NULL, NULL } +}; + +static void rfcomm_node_free(struct rfcomm_node *node) +{ + if (node->name) + g_free(node->name); + if (node->conn) + dbus_connection_unref(node->conn); + if (node->owner) + g_free(node->owner); + if (node->io) { + g_source_remove(node->io_id); + g_io_channel_unref(node->io); + } + g_free(node); +} + +static void connection_owner_exited(const char *name, struct rfcomm_node *node) +{ + char path[MAX_PATH_LENGTH]; + + debug("Connect requestor %s exited. Releasing %s node", + name, node->name); + + snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%d", SERIAL_MANAGER_PATH, node->id); + rfcomm_release(node->id); + dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH, + SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" , + DBUS_TYPE_STRING, &node->name, + DBUS_TYPE_INVALID); + + connected_nodes = g_slist_remove(connected_nodes, node); + dbus_connection_destroy_object_path(node->conn, path); +} + +static gboolean rfcomm_disconnect_cb(GIOChannel *io, + GIOCondition cond, struct rfcomm_node *node) +{ + debug("RFCOMM node %s was disconnected", node->name); + + if (cond & (G_IO_ERR | G_IO_HUP)) + g_io_channel_close(io); + + name_listener_remove(node->conn, node->owner, + (name_cb_t) connection_owner_exited, node); + + dbus_connection_emit_signal(node->conn, SERIAL_MANAGER_PATH, + SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" , + DBUS_TYPE_STRING, &node->name, + DBUS_TYPE_INVALID); + + connected_nodes = g_slist_remove(connected_nodes, node); + rfcomm_node_free(node); + + return FALSE; +} + +static void port_unregister(DBusConnection *conn, void *data) +{ + struct rfcomm_node *node = data; + + debug("Unregistered serial port: %s", node->name); + + rfcomm_node_free(node); +} + +int port_register(DBusConnection *conn, int id, int fd, + const char *name, const char *owner, char *ppath) +{ + char path[MAX_PATH_LENGTH]; + struct rfcomm_node *node; + + node = g_new0(struct rfcomm_node, 1); + node->id = id; + node->name = g_strdup(name); + node->conn = dbus_connection_ref(conn); + + snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%d", SERIAL_MANAGER_PATH, id); + + if (!dbus_connection_create_object_path(conn, path, node, + port_unregister)) { + error("D-Bus failed to register %s path", path); + rfcomm_node_free(node); + return -1; + } + + if (!dbus_connection_register_interface(conn, path, + SERIAL_PORT_INTERFACE, + port_methods, + port_signals, NULL)) { + error("D-Bus failed to register %s interface", + SERIAL_PORT_INTERFACE); + dbus_connection_destroy_object_path(conn, path); + return -1; + } + + info("Registered RFCOMM:%s, path:%s owner:%s", name, path, owner); + + if (ppath) + strcpy(ppath, path); + + if (fd < 0) + return 0; + + node->owner = g_strdup(owner); + node->io = g_io_channel_unix_new(fd); + node->io_id = g_io_add_watch(node->io, G_IO_ERR | G_IO_NVAL | G_IO_HUP, + (GIOFunc) rfcomm_disconnect_cb, node); + + connected_nodes = g_slist_append(connected_nodes, node); + + /* Serial port connection listener */ + return name_listener_add(node->conn, owner, + (name_cb_t) connection_owner_exited, node); +} + +const char *port_get_owner(DBusConnection *conn, int16_t id) +{ + struct rfcomm_node *node; + static char path[MAX_PATH_LENGTH]; + + snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%d", SERIAL_MANAGER_PATH, id); + + if (!dbus_connection_get_object_user_data(conn, path, (void *) &node) + || !node) { + errno = ENOENT; + return NULL; + } + + return node->owner; +} + +static gboolean open_continue(struct open_context *oc) +{ + int fd; + + fd = open(oc->dev, O_RDONLY | O_NOCTTY); + if (fd < 0) { + int err = errno; + error("Could not open %s: %s (%d)", + oc->dev, strerror(err), err); + if (++oc->ntries >= MAX_OPEN_TRIES) { + /* Reporting error */ + oc->notify(fd, err, oc->udata); + return FALSE; + } + return TRUE; + } + /* Connection succeeded */ + oc->notify(fd, 0, oc->udata); + return FALSE; +} + +static void open_context_free(void *data) +{ + struct open_context *oc = data; + + if (oc->ufree) + oc->ufree(oc->udata); + g_free(oc->dev); + g_free(oc); +} + +int port_open(const char *dev, open_notify_t notify, void *udata, udata_free_t ufree) +{ + int fd; + + fd = open(dev, O_RDONLY | O_NOCTTY); + if (fd < 0) { + struct open_context *oc; + oc = g_new0(struct open_context, 1); + oc->dev = g_strdup(dev); + oc->notify = notify; + oc->ufree = ufree; + oc->udata = udata; + g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, OPEN_WAIT, + (GSourceFunc) open_continue, oc, open_context_free); + return -EINPROGRESS; + } + + return fd; +} |