All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
@ 2011-01-27 13:37 Jessica Nilsson
  0 siblings, 0 replies; 8+ messages in thread
From: Jessica Nilsson @ 2011-01-27 13:37 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 19652 bytes --]

---
This is needed in order for isimodem2.5 to work.

Best Regards,
Jessica

 gisi/common.h  |    3 +
 gisi/message.c |    8 +
 gisi/message.h |    1 +
 gisi/modem.c   |  105 ++++++++++++--
 gisi/modem.h   |    4 +
 gisi/pipe.c    |  433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 531 insertions(+), 23 deletions(-)

diff --git a/gisi/common.h b/gisi/common.h
index 83a8cf5..c78f893 100644
--- a/gisi/common.h
+++ b/gisi/common.h
@@ -26,6 +26,8 @@
 extern "C" {
 #endif
 
+#define PN_HOST					0x00
+#define PN_MODEM				0x60
 #define PN_COMMGR				0x10
 #define PN_NAMESERVICE				0xDB
 #define PN_FIREWALL				0x43
@@ -35,6 +37,7 @@ enum message_id {
 	PNS_NAME_ADD_REQ =			0x05,
 	PNS_NAME_REMOVE_REQ =			0x07,
 	PNS_SUBSCRIBED_RESOURCES_IND =		0x10,
+	PNS_SUBSCRIBED_RESOURCES_EXTEND_IND =	0x12,
 	COMM_ISI_VERSION_GET_REQ =		0x12,
 	COMM_ISI_VERSION_GET_RESP =		0x13,
 	COMM_ISA_ENTITY_NOT_REACHABLE_RESP =	0x14,
diff --git a/gisi/message.c b/gisi/message.c
index 8f4fe5a..9d00108 100644
--- a/gisi/message.c
+++ b/gisi/message.c
@@ -65,6 +65,14 @@ uint8_t g_isi_msg_resource(const GIsiMessage *msg)
 	return msg->addr->spn_resource;
 }
 
+uint8_t g_isi_msg_device(const GIsiMessage *msg)
+{
+	if (msg == NULL || msg->addr == NULL)
+		return 0;
+
+	return msg->addr->spn_dev;
+}
+
 uint16_t g_isi_msg_object(const GIsiMessage *msg)
 {
 	if (msg == NULL || msg->addr == NULL)
diff --git a/gisi/message.h b/gisi/message.h
index 95348f8..f34ce57 100644
--- a/gisi/message.h
+++ b/gisi/message.h
@@ -52,6 +52,7 @@ int g_isi_msg_version_minor(const GIsiMessage *msg);
 int g_isi_msg_error(const GIsiMessage *msg);
 const char *g_isi_msg_strerror(const GIsiMessage *msg);
 uint8_t g_isi_msg_resource(const GIsiMessage *msg);
+uint8_t g_isi_msg_device(const GIsiMessage *msg);
 uint16_t g_isi_msg_object(const GIsiMessage *msg);
 
 uint8_t g_isi_msg_id(const GIsiMessage *msg);
diff --git a/gisi/modem.c b/gisi/modem.c
index 8750367..7201235 100644
--- a/gisi/modem.c
+++ b/gisi/modem.c
@@ -61,6 +61,7 @@ struct _GIsiModem {
 	unsigned index;
 	GHashTable *services;
 	gboolean subs_source;
+	GIsiVersion version;
 	int req_fd;
 	int ind_fd;
 	guint req_watch;
@@ -87,11 +88,6 @@ static const struct sockaddr_pn namesrv = {
 	.spn_resource = PN_NAMESERVICE,
 };
 
-static const struct sockaddr_pn commgr = {
-	.spn_family = AF_PHONET,
-	.spn_resource = PN_COMMGR,
-};
-
 static GIsiServiceMux *service_get(GIsiModem *modem, uint8_t resource)
 {
 	GIsiServiceMux *mux;
@@ -349,11 +345,34 @@ static gboolean modem_subs_update(gpointer data)
 	gpointer keyptr, value;
 
 	GIsiModem *modem = data;
-	uint8_t msg[3 + 256] = {
-		0, PNS_SUBSCRIBED_RESOURCES_IND,
-		0,
-	};
+	GIsiMessage dmsg;
+	uint8_t msg[3 + 256] = { 0 };
 	uint8_t count = 0;
+	int msg_size = 0;
+	struct sockaddr_pn commgr = {
+		.spn_family = AF_PHONET,
+		.spn_resource = PN_COMMGR,
+	};
+
+	if (g_isi_modem_version_major(modem) == 2 &&
+		g_isi_modem_version_minor(modem) == 5) {
+		uint8_t s_msg[4] = {
+			0, PNS_SUBSCRIBED_RESOURCES_EXTEND_IND,
+			0,
+			0, /* filler */
+		};
+		g_memmove(msg, s_msg, 4);
+		msg_size = 4;
+
+		commgr.spn_dev = PN_MODEM;
+	} else {
+		uint8_t s_msg[3] = {
+			0, PNS_SUBSCRIBED_RESOURCES_IND,
+			0,
+		};
+		g_memmove(msg, s_msg, 3);
+		msg_size = 3;
+	}
 
 	modem->subs_source = 0;
 
@@ -363,13 +382,31 @@ static gboolean modem_subs_update(gpointer data)
 		GIsiServiceMux *mux = value;
 
 		if (mux->subscriptions > 0) {
-			msg[3 + count] = mux->resource;
+
 			count++;
+
+			if (g_isi_modem_version_major(modem) == 2 &&
+				g_isi_modem_version_minor(modem) == 5) {
+				msg[3 + (count * 4)] = mux->resource;
+				msg_size += 4;
+			} else {
+				msg[2 + count] = mux->resource;
+				msg_size += count;
+			}
+
 		}
 	}
 	msg[2] = count;
 
-	sendto(modem->ind_fd, msg, 3 + msg[2], MSG_NOSIGNAL, (void *)&commgr,
+	dmsg.addr = &commgr;
+	dmsg.error = 0;
+	dmsg.data = msg;
+	dmsg.len = msg_size;
+
+	if (modem->trace != NULL)
+		modem->trace(&dmsg, NULL);
+
+	sendto(modem->ind_fd, msg, msg_size, MSG_NOSIGNAL, (void *)&commgr,
 		sizeof(commgr));
 
 	return FALSE;
@@ -499,6 +536,52 @@ GIsiModem *g_isi_modem_create_by_name(const char *name)
 	return g_isi_modem_create(if_nametoindex(name));
 }
 
+guint g_isi_modem_add_to_watch(GIsiModem *modem, int fd)
+{
+	GIOChannel *channel;
+	guint watch;
+
+	if (modem == NULL || fd < 0)
+		return 0;
+
+	channel = g_io_channel_unix_new(fd);
+	g_io_channel_set_close_on_unref(channel, TRUE);
+	g_io_channel_set_encoding(channel, NULL, NULL);
+	g_io_channel_set_buffered(channel, FALSE);
+	watch = g_io_add_watch(channel,
+				G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
+				isi_callback, modem);
+	g_io_channel_unref(channel);
+
+	return watch;
+}
+
+gboolean g_isi_modem_set_version(GIsiModem *modem, const GIsiVersion version)
+{
+	if (modem == NULL)
+		return FALSE;
+
+	modem->version = version;
+
+	return TRUE;
+}
+
+int g_isi_modem_version_major(const GIsiModem *modem)
+{
+	if (modem == NULL)
+		return -1;
+
+	return modem->version.major;
+}
+
+int g_isi_modem_version_minor(const GIsiModem *modem)
+{
+	if (modem == NULL)
+		return -1;
+
+	return modem->version.minor;
+}
+
 void *g_isi_modem_set_userdata(GIsiModem *modem, void *data)
 {
 	void *old = modem->opaque;
diff --git a/gisi/modem.h b/gisi/modem.h
index f0a1617..361164c 100644
--- a/gisi/modem.h
+++ b/gisi/modem.h
@@ -44,9 +44,13 @@ typedef void (*GIsiDebugFunc)(const char *fmt, ...);
 GIsiModem *g_isi_modem_create(unsigned index);
 GIsiModem *g_isi_modem_create_by_name(const char *name);
 void g_isi_modem_destroy(GIsiModem *modem);
+guint g_isi_modem_add_to_watch(GIsiModem *modem, int fd);
 unsigned g_isi_modem_index(GIsiModem *modem);
 void g_isi_modem_set_trace(GIsiModem *modem, GIsiNotifyFunc notify);
 void g_isi_modem_set_debug(GIsiModem *modem, GIsiDebugFunc debug);
+gboolean g_isi_modem_set_version(GIsiModem *modem, const GIsiVersion version);
+int g_isi_modem_version_major(const GIsiModem *modem);
+int g_isi_modem_version_minor(const GIsiModem *modem);
 void *g_isi_modem_set_userdata(GIsiModem *modem, void *data);
 void *g_isi_modem_get_userdata(GIsiModem *modem);
 
diff --git a/gisi/pipe.c b/gisi/pipe.c
index 1bd5140..14d8b1c 100644
--- a/gisi/pipe.c
+++ b/gisi/pipe.c
@@ -24,15 +24,22 @@
 #endif
 
 #include <stdint.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <unistd.h>
 #include <errno.h>
 #include <glib.h>
 
 #include "client.h"
 #include "pipe.h"
+#include "common.h"
 
 #define PN_PIPE			0xD9
 #define PN_PIPE_INVALID_HANDLE	0xFF
 
+#define PN_OBJ_PEP_GPRS 0x30
+
 struct isi_pipe_create_req {
 	uint8_t cmd;
 	uint8_t state_after;
@@ -73,6 +80,53 @@ struct isi_pipe_resp {
 	uint8_t error2;
 };
 
+struct isi_pep_connect_req{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t state_after;
+	uint8_t other_pep_type;
+	uint8_t filler1;
+	uint8_t filler2;
+	uint8_t n_sb;
+	uint8_t data_sb;
+	uint8_t sb_len;
+	uint8_t alignment;
+	uint8_t filler;
+};
+
+struct isi_pep_enable_req{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t filler;
+};
+
+struct isi_pep_disconnect_req{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t filler;
+};
+
+struct isi_pep_resp {
+	uint8_t pipe_handle;
+	uint8_t error_code;
+};
+
+struct isi_pipe_created_ind{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t n_sb;
+	uint8_t sb_neg_fc;
+	uint8_t sb_len;
+	uint8_t tx_fc;
+	uint8_t rx_fc;
+};
+
+struct isi_pipe_enabled_ind{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t filler;
+};
+
 enum isi_pipe_message_id {
 	PNS_PIPE_CREATE_REQ,
 	PNS_PIPE_CREATE_RESP,
@@ -86,6 +140,8 @@ enum isi_pipe_message_id {
 	PNS_PIPE_REDIRECT_RESP,
 	PNS_PIPE_DISABLE_REQ,
 	PNS_PIPE_DISABLE_RESP,
+	PNS_PIPE_CREATED_IND =		0x61,
+	PNS_PIPE_ENABLED_IND =		0x64
 };
 
 enum pn_pipe_error {	/* error codes */
@@ -103,16 +159,47 @@ enum pn_pipe_error {	/* error codes */
 	PN_PIPE_ERR_NOT_SUPPORTED,
 };
 
+enum isi_pep_type {
+	PN_PEP_TYPE_COMMON
+};
+
+enum isi_pep_message_id {
+	PNS_PEP_CONNECT_REQ =		0x40,
+	PNS_PEP_CONNECT_RESP =		0x41,
+	PNS_PEP_DISCONNECT_REQ =	0x42,
+	PNS_PEP_DISCONNECT_RESP =	0x43,
+	PNS_PEP_ENABLE_REQ =		0x46,
+	PNS_PEP_ENABLE_RESP =		0x47,
+};
+
+enum isi_pep_sb_id {
+	PN_PIPE_SB_NEGOTIATED_FC =	0x03,
+	PN_PIPE_SB_ALIGNED_DATA =	0x06,
+};
+
 enum pn_pipe_state {	/* initial pipe state */
 	PN_PIPE_DISABLE,
 	PN_PIPE_ENABLE,
 };
 
 enum pn_msg_priority {
-	PN_MSG_PRIORITY_LOW = 1,
+	PN_MSG_PRIORITY_LOW =	1,
 	PN_MSG_PRIORITY_HIGH,
 };
 
+enum pn_flow_control {
+	PN_NO_FLOW_CONTROL =		0x00,
+	PN_LEGACY_FLOW_CONTROL =	0x01,
+	PN_ONE_CREDIT_FLOW_CONTROL =	0x02,
+	PN_MULTI_CREDIT_FLOW_CONTROL =	0x03,
+};
+
+struct _GIsiPipeServer {
+	int fd;
+	guint watch;
+};
+typedef struct _GIsiPipeServer GIsiPipeServer;
+
 struct _GIsiPipe {
 	GIsiClient *client;
 	GIsiPipeHandler handler;
@@ -122,8 +209,13 @@ struct _GIsiPipe {
 	uint8_t handle;
 	gboolean enabled;
 	gboolean enabling;
+	GIsiPipeServer *server;
 };
 
+static gboolean g_isi_pep_connect(GIsiPipe *pipe, uint8_t obj, uint8_t dev);
+static void g_isi_pep_enable(GIsiPipe *pipe, uint8_t dev);
+static void g_isi_pep_disconnect(GIsiPipe *pipe, uint8_t dev);
+
 static int g_isi_pipe_error(enum pn_pipe_error code)
 {
 	switch (code) {
@@ -168,6 +260,147 @@ static void g_isi_pipe_handle_error(GIsiPipe *pipe, uint8_t code)
 		pipe->error_handler(pipe);
 }
 
+static GIsiPipeServer *g_isi_pipe_server_create(GIsiModem *modem)
+{
+	GIsiPipeServer *server;
+	unsigned ifindex = g_isi_modem_index(modem);
+	struct sockaddr_pn addr = {
+		.spn_family = AF_PHONET,
+		.spn_resource = PN_PIPE,
+		.spn_obj = PN_OBJ_PEP_GPRS,
+	};
+	char buf[IF_NAMESIZE];
+	int fd = socket(PF_PHONET, SOCK_DGRAM, 0);
+
+	if (fd == -1)
+		return NULL;
+
+	server = g_try_new0(GIsiPipeServer, 1);
+	if (server == NULL)
+		goto error;
+
+	fcntl(fd, F_SETFD, FD_CLOEXEC);
+	/* Use blocking mode on purpose. */
+
+	if (ifindex == 0)
+		g_warning("Unspecified modem interface index");
+	else if (if_indextoname(ifindex, buf) == NULL ||
+		setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, IF_NAMESIZE))
+		goto error;
+
+	if (bind(fd, (void *)&addr, sizeof(addr)))
+		goto error;
+
+	server->fd = fd;
+	server->watch = g_isi_modem_add_to_watch(modem, fd);
+	if (!server->watch)
+		goto error;
+
+	return server;
+
+error:
+	close(fd);
+	return NULL;
+}
+
+static void g_isi_pipe_server_destroy(GIsiPipeServer *server)
+{
+
+	if (server->watch > 0)
+		g_source_remove(server->watch);
+
+	g_free(server);
+}
+
+static void g_isi_pep_connected(const GIsiMessage *msg, void *data)
+{
+	struct isi_pep_resp *resp;
+	size_t len = sizeof(struct isi_pipe_resp);
+	GIsiPipe *pipe = data;
+	GIsiClient *client = pipe->client;
+	struct isi_pipe_created_ind ind = {
+		.cmd = PNS_PIPE_CREATED_IND,
+		.pipe_handle = 0x00,
+		.n_sb = 0x01,
+		.sb_neg_fc = PN_PIPE_SB_NEGOTIATED_FC,
+		.sb_len = 0x04,
+		.tx_fc = PN_LEGACY_FLOW_CONTROL,
+		.rx_fc = PN_LEGACY_FLOW_CONTROL,
+	};
+
+	if (g_isi_msg_error(msg) < 0) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
+		return;
+	}
+
+	if (g_isi_msg_id(msg) != PNS_PEP_CONNECT_RESP)
+		return;
+
+	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
+		return;
+
+	if (resp->pipe_handle == PN_PIPE_INVALID_HANDLE) {
+		g_isi_pipe_handle_error(pipe, resp->error_code);
+		return;
+	}
+
+	pipe->handle = resp->pipe_handle;
+	ind.pipe_handle = resp->pipe_handle;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), msg->addr, &ind,
+			sizeof(ind), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			NULL, NULL, NULL) == NULL) {
+			g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_GENERAL);
+			return;
+	}
+
+	if (g_isi_msg_device(msg) == PN_HOST) {
+		g_isi_pep_connect(pipe, pipe->handle, PN_MODEM);
+		return;
+	}
+
+	if (pipe->enabling)
+		g_isi_pipe_start(pipe);
+
+	if (pipe->handler)
+		pipe->handler(pipe);
+}
+
+static gboolean g_isi_pep_connect(GIsiPipe *pipe, uint8_t obj, uint8_t dev)
+{
+	GIsiClient *client = pipe->client;
+	struct sockaddr_pn dst = {
+		.spn_family = AF_PHONET,
+	};
+	struct isi_pep_connect_req msg = {
+		.cmd = PNS_PEP_CONNECT_REQ,
+		.pipe_handle = obj,
+		.state_after = PN_PIPE_DISABLE,
+		.other_pep_type = PN_PEP_TYPE_COMMON,
+		.filler1 = 0x00,
+		.filler2 = 0x00,
+		.n_sb = 0x00,
+	};
+
+	if (dev == PN_HOST) {
+		dst.spn_dev = PN_HOST;
+		dst.spn_resource = PN_PIPE;
+		dst.spn_obj = obj;
+	} else if (dev == PN_MODEM) {
+		dst.spn_dev = PN_MODEM;
+		dst.spn_resource = g_isi_client_resource(client);
+		dst.spn_obj = PN_OBJ_PEP_GPRS;
+	} else
+		return FALSE;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), &dst, &msg,
+			sizeof(msg), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			g_isi_pep_connected, pipe, NULL) == NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
 static void g_isi_pipe_created(const GIsiMessage *msg, void *data)
 {
 	struct isi_pipe_resp *resp;
@@ -247,16 +480,115 @@ GIsiPipe *g_isi_pipe_create(GIsiModem *modem, GIsiPipeHandler cb, uint16_t obj1,
 	pipe->enabled = FALSE;
 	pipe->handle = PN_PIPE_INVALID_HANDLE;
 
-	if (g_isi_client_send(pipe->client, &msg, len,
+	if (g_isi_modem_version_major(modem) == 2 &&
+		g_isi_modem_version_minor(modem) == 5) {
+
+		pipe->server = g_isi_pipe_server_create(modem);
+		if (pipe->server == NULL)
+			goto error;
+
+		if (!g_isi_pep_connect(pipe, obj1 & 0xFF, PN_HOST)) {
+			g_isi_pipe_server_destroy(pipe->server);
+			goto error;
+		}
+	} else {
+
+		if (!g_isi_client_send(pipe->client, &msg, len,
 					g_isi_pipe_created, pipe, NULL))
-		return pipe;
+			goto error;
 
+	}
+
+	return pipe;
+
+error:
 	g_isi_client_destroy(pipe->client);
 	g_free(pipe);
 
 	return NULL;
 }
 
+static void g_isi_pep_enabled(const GIsiMessage *msg, void *data)
+{
+	struct isi_pep_resp *resp;
+	size_t len = sizeof(struct isi_pep_resp);
+	GIsiPipe *pipe = data;
+	GIsiClient *client = pipe->client;
+	struct isi_pipe_enabled_ind ind = {
+		.cmd = PNS_PIPE_ENABLED_IND,
+		.pipe_handle = pipe->handle,
+		.filler = 0x00,
+	};
+
+	if (g_isi_msg_error(msg) < 0) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
+		return;
+	}
+
+	if (g_isi_msg_id(msg) != PNS_PEP_ENABLE_RESP)
+		return;
+
+	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
+		return;
+
+	if (pipe->handle != resp->pipe_handle) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_INVALID_HANDLE);
+		return;
+	}
+
+	g_isi_pipe_handle_error(pipe, resp->error_code);
+	if (pipe->error)
+		return;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), msg->addr, &ind,
+			sizeof(ind), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			NULL, NULL, NULL) == NULL) {
+			g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_GENERAL);
+			return;
+	}
+
+	if (g_isi_msg_device(msg) == PN_MODEM) {
+		g_isi_pep_enable(pipe, PN_HOST);
+		return;
+	}
+
+	pipe->enabling = FALSE;
+
+	if (!pipe->error)
+		pipe->enabled = TRUE;
+}
+
+static void g_isi_pep_enable(GIsiPipe *pipe, uint8_t dev)
+{
+	GIsiClient *client = pipe->client;
+	struct sockaddr_pn dst = {
+		.spn_family = AF_PHONET,
+	};
+	struct isi_pep_enable_req msg = {
+		.cmd = PNS_PEP_ENABLE_REQ,
+		.pipe_handle = pipe->handle,
+		.filler = 0x00,
+	};
+
+	if (dev == PN_HOST) {
+		dst.spn_dev = PN_HOST;
+		dst.spn_resource = PN_PIPE;
+		dst.spn_obj = pipe->handle;
+	} else if (dev == PN_MODEM) {
+		dst.spn_dev = PN_MODEM;
+		dst.spn_resource = g_isi_client_resource(client);
+		dst.spn_obj = PN_OBJ_PEP_GPRS;
+	} else
+		return;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), &dst, &msg,
+			sizeof(msg), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			g_isi_pep_enabled, pipe, NULL) == NULL)
+		return;
+
+	return;
+}
+
 static void g_isi_pipe_enabled(const GIsiMessage *msg, void *data)
 {
 	GIsiPipe *pipe = data;
@@ -287,14 +619,19 @@ static void g_isi_pipe_enabled(const GIsiMessage *msg, void *data)
 
 static void g_isi_pipe_enable(GIsiPipe *pipe)
 {
+	GIsiModem *modem = g_isi_client_modem(pipe->client);
 	struct isi_pipe_enable_req msg = {
 		.cmd = PNS_PIPE_ENABLE_REQ,
 		.pipe_handle = pipe->handle,
 	};
 	size_t len = sizeof(msg);
 
-	g_isi_client_send(pipe->client, &msg, len,
-				g_isi_pipe_enabled, pipe, NULL);
+	if (g_isi_modem_version_major(modem) == 2 &&
+		g_isi_modem_version_minor(modem) == 5)
+		g_isi_pep_enable(pipe, PN_MODEM);
+	else
+		g_isi_client_send(pipe->client, &msg, len,
+					g_isi_pipe_enabled, pipe, NULL);
 }
 
 /**
@@ -339,8 +676,8 @@ static void g_isi_pipe_removed(const GIsiMessage *msg, void *data)
 	if (pipe->handle != resp->pipe_handle)
 		return;
 
-	pipe->handle = PN_PIPE_INVALID_HANDLE;
-	pipe->error = -EPIPE;
+	g_isi_client_destroy(pipe->client);
+	g_free(pipe);
 }
 
 
@@ -356,17 +693,89 @@ static void g_isi_pipe_remove(GIsiPipe *pipe)
 				g_isi_pipe_removed, pipe, NULL);
 }
 
+static void g_isi_pep_disconnected(const GIsiMessage *msg, void *data)
+{
+	struct isi_pep_resp *resp;
+	size_t len = sizeof(struct isi_pep_resp);
+	GIsiPipe *pipe = data;
+
+	if (g_isi_msg_error(msg) < 0) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
+		return;
+	}
+
+	if (g_isi_msg_id(msg) != PNS_PEP_DISCONNECT_RESP)
+		return;
+
+	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
+		return;
+
+	if (resp->pipe_handle == PN_PIPE_INVALID_HANDLE) {
+		g_isi_pipe_handle_error(pipe, resp->error_code);
+		return;
+	}
+
+	if (pipe->handle != resp->pipe_handle) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_INVALID_HANDLE);
+		return;
+	}
+
+	if (g_isi_msg_device(msg) == PN_HOST) {
+		g_isi_pep_disconnect(pipe, PN_MODEM);
+		return;
+	}
+
+	g_isi_pipe_server_destroy(pipe->server);
+	g_isi_client_destroy(pipe->client);
+	g_free(pipe);
+}
+
+static void g_isi_pep_disconnect(GIsiPipe *pipe, uint8_t dev)
+{
+	GIsiClient *client = pipe->client;
+	struct sockaddr_pn dst = {
+		.spn_family = AF_PHONET,
+	};
+	struct isi_pep_disconnect_req msg = {
+		.cmd = PNS_PEP_DISCONNECT_REQ,
+		.pipe_handle = pipe->handle,
+		.filler = 0x00,
+	};
+
+	if (dev == PN_HOST) {
+		dst.spn_dev = PN_HOST;
+		dst.spn_resource = PN_PIPE;
+		dst.spn_obj = pipe->handle;
+	} else if (dev == PN_MODEM) {
+		dst.spn_dev = PN_MODEM;
+		dst.spn_resource = g_isi_client_resource(client);
+		dst.spn_obj = PN_OBJ_PEP_GPRS;
+	} else
+		return;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), &dst, &msg,
+			sizeof(msg), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			g_isi_pep_disconnected, pipe, NULL) == NULL)
+		return;
+
+	return;
+}
+
 /**
  * Destroy a pipe. If it was connected, it is removed.
  * @param pipe pipe as returned from g_isi_pipe_create()
  */
 void g_isi_pipe_destroy(GIsiPipe *pipe)
 {
-	if (!pipe->error)
-		g_isi_pipe_remove(pipe);
-
-	g_isi_client_destroy(pipe->client);
-	g_free(pipe);
+	GIsiModem *modem = g_isi_client_modem(pipe->client);
+
+	if (!pipe->error) {
+		if (g_isi_modem_version_major(modem) == 2 &&
+			g_isi_modem_version_minor(modem) == 5)
+			g_isi_pep_disconnect(pipe, PN_HOST);
+		else
+			g_isi_pipe_remove(pipe);
+	}
 }
 
 void g_isi_pipe_set_error_handler(GIsiPipe *pipe, GIsiPipeErrorHandler cb)
-- 
1.7.3.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
  2011-02-04  8:41   ` Aki Niemi
@ 2011-02-07 10:01     ` Aki Niemi
  0 siblings, 0 replies; 8+ messages in thread
From: Aki Niemi @ 2011-02-07 10:01 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 661 bytes --]

Hi,

2011/2/4 Aki Niemi <aki@protocolpolice.com>:
> This is problematic, since there is no version query by default for
> the PN_COMMGR because creating the GIsiModem is not async. Also, even
> the N900 modem supports both of these INDs, but the
> PNS_SUBSCRIBED_RESOURCES_EXTEND_IND is blocked by a modem side
> firewall.
>
> So I think we need something different here, like flags that the
> plugin can set to control which IND type gets used.

I just pushed a new API to set flags on a modem instance, and also
refactored the indication subscription logic based on this patch.

Please check that it works like you want it to.

Cheers,
Aki

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
  2011-01-28 14:33 ` =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont
  2011-01-31 12:05   ` Jessica Nilsson
@ 2011-02-04  8:41   ` Aki Niemi
  2011-02-07 10:01     ` Aki Niemi
  1 sibling, 1 reply; 8+ messages in thread
From: Aki Niemi @ 2011-02-04  8:41 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 2582 bytes --]

Hi Rémi,

2011/1/28 Rémi Denis-Courmont <remi.denis-courmont@nokia.com>:
> On Friday 28 January 2011 13:37:10 ext Jessica Nilsson, you wrote:
>> diff --git a/gisi/common.h b/gisi/common.h
>> index 83a8cf5..c78f893 100644
>> --- a/gisi/common.h
>> +++ b/gisi/common.h
>> @@ -26,6 +26,8 @@
>>  extern "C" {
>>  #endif
>>
>> +#define PN_HOST                                        0x00
>> +#define PN_MODEM                               0x60
>
> It looks like you are mixing up services (PN_* from 0 to 255), with device
> addresses (PN_DEV_* multiple of four from 0 to 252).
>
>>  #define PN_COMMGR                              0x10
>>  #define PN_NAMESERVICE                         0xDB
>>  #define PN_FIREWALL                            0x43
>> diff --git a/gisi/modem.c b/gisi/modem.c
>> index 8750367..7201235 100644
>> --- a/gisi/modem.c
>> +++ b/gisi/modem.c
>> @@ -61,6 +61,7 @@ struct _GIsiModem {
>>         unsigned index;
>>         GHashTable *services;
>>         gboolean subs_source;
>> +       GIsiVersion version;
>
> I can't see where this gets set?!
>
>>         int req_fd;
>>         int ind_fd;
>>         guint req_watch;
>> @@ -349,11 +345,34 @@ static gboolean modem_subs_update(gpointer data)
>>         gpointer keyptr, value;
>>
>>         GIsiModem *modem = data;
>> -       uint8_t msg[3 + 256] = {
>> -               0, PNS_SUBSCRIBED_RESOURCES_IND,
>> -               0,
>> -       };
>> +       GIsiMessage dmsg;
>> +       uint8_t msg[3 + 256] = { 0 };
>>         uint8_t count = 0;
>> +       int msg_size = 0;
>> +       struct sockaddr_pn commgr = {
>> +               .spn_family = AF_PHONET,
>> +               .spn_resource = PN_COMMGR,
>> +       };
>> +
>> +       if (g_isi_modem_version_major(modem) == 2 &&
>> +               g_isi_modem_version_minor(modem) == 5) {
>
> You really need to factor this check out. It's going to be a disaster when
> there are more than two versions otherwise.

This is problematic, since there is no version query by default for
the PN_COMMGR because creating the GIsiModem is not async. Also, even
the N900 modem supports both of these INDs, but the
PNS_SUBSCRIBED_RESOURCES_EXTEND_IND is blocked by a modem side
firewall.

So I think we need something different here, like flags that the
plugin can set to control which IND type gets used.

Cheers,
Aki

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
  2011-01-28 14:33 ` =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont
@ 2011-01-31 12:05   ` Jessica Nilsson
  2011-02-04  8:41   ` Aki Niemi
  1 sibling, 0 replies; 8+ messages in thread
From: Jessica Nilsson @ 2011-01-31 12:05 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 1946 bytes --]

Hi Rémi,

2011/1/28 Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
>
> It looks like you are mixing up services (PN_* from 0 to 255), with device
> addresses (PN_DEV_* multiple of four from 0 to 252).

Yes, you are right. We will change it.



>
> > @@ -61,6 +61,7 @@ struct _GIsiModem {
> >         unsigned index;
> >         GHashTable *services;
> >         gboolean subs_source;
> > +       GIsiVersion version;
>
> I can't see where this gets set?!

In g_isi_modem_set_version() in plugins/u8500.c
New file, that will hopefully be sent in a patch to the mailing list today.



>
> > +
> > +       if (g_isi_modem_version_major(modem) == 2 &&
> > +               g_isi_modem_version_minor(modem) == 5) {
>
> You really need to factor this check out. It's going to be a disaster when
> there are more than two versions otherwise.

Well, we really need the PNS_SUBSCRIBED_RESOURCES_EXTEND_IND because
the other one doesn't work for us.

Would a lookup function do the trick, with filling in the necessary
values in msg depending on which version it is?
If that is not satisfactory, could you please be a bit more specific
in what you would like to see so we don't misunderstand?
>
> >
> > @@ -363,13 +382,31 @@ static gboolean modem_subs_update(gpointer data)
>
>
> That should probably be a separate patch.

Yes, you are right.
>
> > diff --git a/gisi/pipe.c b/gisi/pipe.c
>
>
> We already have this in kernel and I wonder why this needs to be duplicated in
> userspace now?! Furthermore pipe.c is about pipes. Pipe endpoints code belongs
> in pep.c. If you want to implement a pipe controller in gisi, then I think it
> really deserves a new file of its own.
>

isimodem2.5 needs this code, the kernel struct won't be applicable for
us in the modem case.

We will move the pep functions to the pep.c

Best Regards,
Jessica Nilsson
ST-Ericsson

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
  2011-01-28 11:37 Jessica Nilsson
  2011-01-28 11:42 ` Marcel Holtmann
@ 2011-01-28 14:33 ` =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont
  2011-01-31 12:05   ` Jessica Nilsson
  2011-02-04  8:41   ` Aki Niemi
  1 sibling, 2 replies; 8+ messages in thread
From: =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont @ 2011-01-28 14:33 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 5931 bytes --]

On Friday 28 January 2011 13:37:10 ext Jessica Nilsson, you wrote:
> diff --git a/gisi/common.h b/gisi/common.h
> index 83a8cf5..c78f893 100644
> --- a/gisi/common.h
> +++ b/gisi/common.h
> @@ -26,6 +26,8 @@
>  extern "C" {
>  #endif
> 
> +#define PN_HOST                                        0x00
> +#define PN_MODEM                               0x60

It looks like you are mixing up services (PN_* from 0 to 255), with device 
addresses (PN_DEV_* multiple of four from 0 to 252).

>  #define PN_COMMGR                              0x10
>  #define PN_NAMESERVICE                         0xDB
>  #define PN_FIREWALL                            0x43
> diff --git a/gisi/modem.c b/gisi/modem.c
> index 8750367..7201235 100644
> --- a/gisi/modem.c
> +++ b/gisi/modem.c
> @@ -61,6 +61,7 @@ struct _GIsiModem {
>         unsigned index;
>         GHashTable *services;
>         gboolean subs_source;
> +       GIsiVersion version;

I can't see where this gets set?!

>         int req_fd;
>         int ind_fd;
>         guint req_watch;
> @@ -349,11 +345,34 @@ static gboolean modem_subs_update(gpointer data)
>         gpointer keyptr, value;
> 
>         GIsiModem *modem = data;
> -       uint8_t msg[3 + 256] = {
> -               0, PNS_SUBSCRIBED_RESOURCES_IND,
> -               0,
> -       };
> +       GIsiMessage dmsg;
> +       uint8_t msg[3 + 256] = { 0 };
>         uint8_t count = 0;
> +       int msg_size = 0;
> +       struct sockaddr_pn commgr = {
> +               .spn_family = AF_PHONET,
> +               .spn_resource = PN_COMMGR,
> +       };
> +
> +       if (g_isi_modem_version_major(modem) == 2 &&
> +               g_isi_modem_version_minor(modem) == 5) {

You really need to factor this check out. It's going to be a disaster when 
there are more than two versions otherwise.

> +               uint8_t s_msg[4] = {
> +                       0, PNS_SUBSCRIBED_RESOURCES_EXTEND_IND,
> +                       0,
> +                       0, /* filler */
> +               };
> +               g_memmove(msg, s_msg, 4);
> +               msg_size = 4;
> +
> +               commgr.spn_dev = PN_MODEM;
> +       } else {
> +               uint8_t s_msg[3] = {
> +                       0, PNS_SUBSCRIBED_RESOURCES_IND,
> +                       0,
> +               };
> +               g_memmove(msg, s_msg, 3);
> +               msg_size = 3;
> +       }
> 
>         modem->subs_source = 0;
> 
> @@ -363,13 +382,31 @@ static gboolean modem_subs_update(gpointer data)
>                 GIsiServiceMux *mux = value;
> 
>                 if (mux->subscriptions > 0) {
> -                       msg[3 + count] = mux->resource;
> +
>                         count++;
> +
> +                       if (g_isi_modem_version_major(modem) == 2 &&
> +                               g_isi_modem_version_minor(modem) == 5) {
> +                               msg[3 + (count * 4)] = mux->resource;
> +                               msg_size += 4;
> +                       } else {
> +                               msg[2 + count] = mux->resource;
> +                               msg_size += count;
> +                       }
> +
>                 }
>         }
>         msg[2] = count;
> 
> -       sendto(modem->ind_fd, msg, 3 + msg[2], MSG_NOSIGNAL, (void
> *)&commgr, +       dmsg.addr = &commgr;
> +       dmsg.error = 0;
> +       dmsg.data = msg;
> +       dmsg.len = msg_size;
> +
> +       if (modem->trace != NULL)
> +               modem->trace(&dmsg, NULL);
> +
> +       sendto(modem->ind_fd, msg, msg_size, MSG_NOSIGNAL, (void *)&commgr,
>                 sizeof(commgr));

That should probably be a separate patch.

> 
>         return FALSE;
> diff --git a/gisi/pipe.c b/gisi/pipe.c
> index 1bd5140..14d8b1c 100644
> --- a/gisi/pipe.c
> +++ b/gisi/pipe.c
> @@ -24,15 +24,22 @@
>  #endif
> 
>  #include <stdint.h>
> +#include <sys/ioctl.h>
> +#include <net/if.h>
> +#include <fcntl.h>
> +#include <unistd.h>
>  #include <errno.h>
>  #include <glib.h>
> 
>  #include "client.h"
>  #include "pipe.h"
> +#include "common.h"
> 
>  #define PN_PIPE                        0xD9
>  #define PN_PIPE_INVALID_HANDLE 0xFF
> 
> +#define PN_OBJ_PEP_GPRS 0x30
> +
>  struct isi_pipe_create_req {
>         uint8_t cmd;
>         uint8_t state_after;
> @@ -73,6 +80,53 @@ struct isi_pipe_resp {
>         uint8_t error2;
>  };
> 
> +struct isi_pep_connect_req{
> +       uint8_t cmd;
> +       uint8_t pipe_handle;
> +       uint8_t state_after;
> +       uint8_t other_pep_type;
> +       uint8_t filler1;
> +       uint8_t filler2;
> +       uint8_t n_sb;
> +       uint8_t data_sb;
> +       uint8_t sb_len;
> +       uint8_t alignment;
> +       uint8_t filler;
> +};
> +
> +struct isi_pep_enable_req{
> +       uint8_t cmd;
> +       uint8_t pipe_handle;
> +       uint8_t filler;
> +};
> +
> +struct isi_pep_disconnect_req{
> +       uint8_t cmd;
> +       uint8_t pipe_handle;
> +       uint8_t filler;
> +};
> +
> +struct isi_pep_resp {
> +       uint8_t pipe_handle;
> +       uint8_t error_code;
> +};
> +
> +struct isi_pipe_created_ind{
> +       uint8_t cmd;
> +       uint8_t pipe_handle;
> +       uint8_t n_sb;
> +       uint8_t sb_neg_fc;
> +       uint8_t sb_len;
> +       uint8_t tx_fc;
> +       uint8_t rx_fc;
> +};
> +
> +struct isi_pipe_enabled_ind{
> +       uint8_t cmd;
> +       uint8_t pipe_handle;
> +       uint8_t filler;
> +};
> +

We already have this in kernel and I wonder why this needs to be duplicated in 
userspace now?! Furthermore pipe.c is about pipes. Pipe endpoints code belongs 
in pep.c. If you want to implement a pipe controller in gisi, then I think it 
really deserves a new file of its own.

-- 
Rémi Denis-Courmont
Nokia Devices R&D, Maemo Software, Helsinki

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
  2011-01-28 11:42 ` Marcel Holtmann
@ 2011-01-28 13:15   ` Jessica Nilsson
  0 siblings, 0 replies; 8+ messages in thread
From: Jessica Nilsson @ 2011-01-28 13:15 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 593 bytes --]

Hi Marcel,

On Fri, Jan 28, 2011 at 12:42 PM, Marcel Holtmann <marcel@holtmann.org> wrote:
> you might wanna keep the subject line under 50 characters and have a
> more detailed explanation in the commit body. See M5.

How did I miss that one?
I will fix it!

> Also you might need a From line at the top.

I think the mail server has been creative, because it was there when I
sent it. I'll see if I can change the settings somehow,

I'll wait for more comments on the actual code before sending an new
patch though, is that ok?

Best Regards,
Jessica Nilsson
ST-Ericsson

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
  2011-01-28 11:37 Jessica Nilsson
@ 2011-01-28 11:42 ` Marcel Holtmann
  2011-01-28 13:15   ` Jessica Nilsson
  2011-01-28 14:33 ` =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont
  1 sibling, 1 reply; 8+ messages in thread
From: Marcel Holtmann @ 2011-01-28 11:42 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 273 bytes --]

Hi Jessica,

> ---
> This is needed in order for isimodem2.5 to work.

you might wanna keep the subject line under 50 characters and have a
more detailed explanation in the commit body. See M5.

Also you might need a From line at the top.

Regards

Marcel



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions
@ 2011-01-28 11:37 Jessica Nilsson
  2011-01-28 11:42 ` Marcel Holtmann
  2011-01-28 14:33 ` =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont
  0 siblings, 2 replies; 8+ messages in thread
From: Jessica Nilsson @ 2011-01-28 11:37 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 19674 bytes --]

---
This is needed in order for isimodem2.5 to work.

Best Regards,
Jessica Nilsson 
ST-Ericsson

 gisi/common.h  |    3 +
 gisi/message.c |    8 +
 gisi/message.h |    1 +
 gisi/modem.c   |  105 ++++++++++++--
 gisi/modem.h   |    4 +
 gisi/pipe.c    |  433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 531 insertions(+), 23 deletions(-)

diff --git a/gisi/common.h b/gisi/common.h
index 83a8cf5..c78f893 100644
--- a/gisi/common.h
+++ b/gisi/common.h
@@ -26,6 +26,8 @@
 extern "C" {
 #endif
 
+#define PN_HOST					0x00
+#define PN_MODEM				0x60
 #define PN_COMMGR				0x10
 #define PN_NAMESERVICE				0xDB
 #define PN_FIREWALL				0x43
@@ -35,6 +37,7 @@ enum message_id {
 	PNS_NAME_ADD_REQ =			0x05,
 	PNS_NAME_REMOVE_REQ =			0x07,
 	PNS_SUBSCRIBED_RESOURCES_IND =		0x10,
+	PNS_SUBSCRIBED_RESOURCES_EXTEND_IND =	0x12,
 	COMM_ISI_VERSION_GET_REQ =		0x12,
 	COMM_ISI_VERSION_GET_RESP =		0x13,
 	COMM_ISA_ENTITY_NOT_REACHABLE_RESP =	0x14,
diff --git a/gisi/message.c b/gisi/message.c
index 8f4fe5a..9d00108 100644
--- a/gisi/message.c
+++ b/gisi/message.c
@@ -65,6 +65,14 @@ uint8_t g_isi_msg_resource(const GIsiMessage *msg)
 	return msg->addr->spn_resource;
 }
 
+uint8_t g_isi_msg_device(const GIsiMessage *msg)
+{
+	if (msg == NULL || msg->addr == NULL)
+		return 0;
+
+	return msg->addr->spn_dev;
+}
+
 uint16_t g_isi_msg_object(const GIsiMessage *msg)
 {
 	if (msg == NULL || msg->addr == NULL)
diff --git a/gisi/message.h b/gisi/message.h
index 95348f8..f34ce57 100644
--- a/gisi/message.h
+++ b/gisi/message.h
@@ -52,6 +52,7 @@ int g_isi_msg_version_minor(const GIsiMessage *msg);
 int g_isi_msg_error(const GIsiMessage *msg);
 const char *g_isi_msg_strerror(const GIsiMessage *msg);
 uint8_t g_isi_msg_resource(const GIsiMessage *msg);
+uint8_t g_isi_msg_device(const GIsiMessage *msg);
 uint16_t g_isi_msg_object(const GIsiMessage *msg);
 
 uint8_t g_isi_msg_id(const GIsiMessage *msg);
diff --git a/gisi/modem.c b/gisi/modem.c
index 8750367..7201235 100644
--- a/gisi/modem.c
+++ b/gisi/modem.c
@@ -61,6 +61,7 @@ struct _GIsiModem {
 	unsigned index;
 	GHashTable *services;
 	gboolean subs_source;
+	GIsiVersion version;
 	int req_fd;
 	int ind_fd;
 	guint req_watch;
@@ -87,11 +88,6 @@ static const struct sockaddr_pn namesrv = {
 	.spn_resource = PN_NAMESERVICE,
 };
 
-static const struct sockaddr_pn commgr = {
-	.spn_family = AF_PHONET,
-	.spn_resource = PN_COMMGR,
-};
-
 static GIsiServiceMux *service_get(GIsiModem *modem, uint8_t resource)
 {
 	GIsiServiceMux *mux;
@@ -349,11 +345,34 @@ static gboolean modem_subs_update(gpointer data)
 	gpointer keyptr, value;
 
 	GIsiModem *modem = data;
-	uint8_t msg[3 + 256] = {
-		0, PNS_SUBSCRIBED_RESOURCES_IND,
-		0,
-	};
+	GIsiMessage dmsg;
+	uint8_t msg[3 + 256] = { 0 };
 	uint8_t count = 0;
+	int msg_size = 0;
+	struct sockaddr_pn commgr = {
+		.spn_family = AF_PHONET,
+		.spn_resource = PN_COMMGR,
+	};
+
+	if (g_isi_modem_version_major(modem) == 2 &&
+		g_isi_modem_version_minor(modem) == 5) {
+		uint8_t s_msg[4] = {
+			0, PNS_SUBSCRIBED_RESOURCES_EXTEND_IND,
+			0,
+			0, /* filler */
+		};
+		g_memmove(msg, s_msg, 4);
+		msg_size = 4;
+
+		commgr.spn_dev = PN_MODEM;
+	} else {
+		uint8_t s_msg[3] = {
+			0, PNS_SUBSCRIBED_RESOURCES_IND,
+			0,
+		};
+		g_memmove(msg, s_msg, 3);
+		msg_size = 3;
+	}
 
 	modem->subs_source = 0;
 
@@ -363,13 +382,31 @@ static gboolean modem_subs_update(gpointer data)
 		GIsiServiceMux *mux = value;
 
 		if (mux->subscriptions > 0) {
-			msg[3 + count] = mux->resource;
+
 			count++;
+
+			if (g_isi_modem_version_major(modem) == 2 &&
+				g_isi_modem_version_minor(modem) == 5) {
+				msg[3 + (count * 4)] = mux->resource;
+				msg_size += 4;
+			} else {
+				msg[2 + count] = mux->resource;
+				msg_size += count;
+			}
+
 		}
 	}
 	msg[2] = count;
 
-	sendto(modem->ind_fd, msg, 3 + msg[2], MSG_NOSIGNAL, (void *)&commgr,
+	dmsg.addr = &commgr;
+	dmsg.error = 0;
+	dmsg.data = msg;
+	dmsg.len = msg_size;
+
+	if (modem->trace != NULL)
+		modem->trace(&dmsg, NULL);
+
+	sendto(modem->ind_fd, msg, msg_size, MSG_NOSIGNAL, (void *)&commgr,
 		sizeof(commgr));
 
 	return FALSE;
@@ -499,6 +536,52 @@ GIsiModem *g_isi_modem_create_by_name(const char *name)
 	return g_isi_modem_create(if_nametoindex(name));
 }
 
+guint g_isi_modem_add_to_watch(GIsiModem *modem, int fd)
+{
+	GIOChannel *channel;
+	guint watch;
+
+	if (modem == NULL || fd < 0)
+		return 0;
+
+	channel = g_io_channel_unix_new(fd);
+	g_io_channel_set_close_on_unref(channel, TRUE);
+	g_io_channel_set_encoding(channel, NULL, NULL);
+	g_io_channel_set_buffered(channel, FALSE);
+	watch = g_io_add_watch(channel,
+				G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
+				isi_callback, modem);
+	g_io_channel_unref(channel);
+
+	return watch;
+}
+
+gboolean g_isi_modem_set_version(GIsiModem *modem, const GIsiVersion version)
+{
+	if (modem == NULL)
+		return FALSE;
+
+	modem->version = version;
+
+	return TRUE;
+}
+
+int g_isi_modem_version_major(const GIsiModem *modem)
+{
+	if (modem == NULL)
+		return -1;
+
+	return modem->version.major;
+}
+
+int g_isi_modem_version_minor(const GIsiModem *modem)
+{
+	if (modem == NULL)
+		return -1;
+
+	return modem->version.minor;
+}
+
 void *g_isi_modem_set_userdata(GIsiModem *modem, void *data)
 {
 	void *old = modem->opaque;
diff --git a/gisi/modem.h b/gisi/modem.h
index f0a1617..361164c 100644
--- a/gisi/modem.h
+++ b/gisi/modem.h
@@ -44,9 +44,13 @@ typedef void (*GIsiDebugFunc)(const char *fmt, ...);
 GIsiModem *g_isi_modem_create(unsigned index);
 GIsiModem *g_isi_modem_create_by_name(const char *name);
 void g_isi_modem_destroy(GIsiModem *modem);
+guint g_isi_modem_add_to_watch(GIsiModem *modem, int fd);
 unsigned g_isi_modem_index(GIsiModem *modem);
 void g_isi_modem_set_trace(GIsiModem *modem, GIsiNotifyFunc notify);
 void g_isi_modem_set_debug(GIsiModem *modem, GIsiDebugFunc debug);
+gboolean g_isi_modem_set_version(GIsiModem *modem, const GIsiVersion version);
+int g_isi_modem_version_major(const GIsiModem *modem);
+int g_isi_modem_version_minor(const GIsiModem *modem);
 void *g_isi_modem_set_userdata(GIsiModem *modem, void *data);
 void *g_isi_modem_get_userdata(GIsiModem *modem);
 
diff --git a/gisi/pipe.c b/gisi/pipe.c
index 1bd5140..14d8b1c 100644
--- a/gisi/pipe.c
+++ b/gisi/pipe.c
@@ -24,15 +24,22 @@
 #endif
 
 #include <stdint.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <unistd.h>
 #include <errno.h>
 #include <glib.h>
 
 #include "client.h"
 #include "pipe.h"
+#include "common.h"
 
 #define PN_PIPE			0xD9
 #define PN_PIPE_INVALID_HANDLE	0xFF
 
+#define PN_OBJ_PEP_GPRS 0x30
+
 struct isi_pipe_create_req {
 	uint8_t cmd;
 	uint8_t state_after;
@@ -73,6 +80,53 @@ struct isi_pipe_resp {
 	uint8_t error2;
 };
 
+struct isi_pep_connect_req{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t state_after;
+	uint8_t other_pep_type;
+	uint8_t filler1;
+	uint8_t filler2;
+	uint8_t n_sb;
+	uint8_t data_sb;
+	uint8_t sb_len;
+	uint8_t alignment;
+	uint8_t filler;
+};
+
+struct isi_pep_enable_req{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t filler;
+};
+
+struct isi_pep_disconnect_req{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t filler;
+};
+
+struct isi_pep_resp {
+	uint8_t pipe_handle;
+	uint8_t error_code;
+};
+
+struct isi_pipe_created_ind{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t n_sb;
+	uint8_t sb_neg_fc;
+	uint8_t sb_len;
+	uint8_t tx_fc;
+	uint8_t rx_fc;
+};
+
+struct isi_pipe_enabled_ind{
+	uint8_t cmd;
+	uint8_t pipe_handle;
+	uint8_t filler;
+};
+
 enum isi_pipe_message_id {
 	PNS_PIPE_CREATE_REQ,
 	PNS_PIPE_CREATE_RESP,
@@ -86,6 +140,8 @@ enum isi_pipe_message_id {
 	PNS_PIPE_REDIRECT_RESP,
 	PNS_PIPE_DISABLE_REQ,
 	PNS_PIPE_DISABLE_RESP,
+	PNS_PIPE_CREATED_IND =		0x61,
+	PNS_PIPE_ENABLED_IND =		0x64
 };
 
 enum pn_pipe_error {	/* error codes */
@@ -103,16 +159,47 @@ enum pn_pipe_error {	/* error codes */
 	PN_PIPE_ERR_NOT_SUPPORTED,
 };
 
+enum isi_pep_type {
+	PN_PEP_TYPE_COMMON
+};
+
+enum isi_pep_message_id {
+	PNS_PEP_CONNECT_REQ =		0x40,
+	PNS_PEP_CONNECT_RESP =		0x41,
+	PNS_PEP_DISCONNECT_REQ =	0x42,
+	PNS_PEP_DISCONNECT_RESP =	0x43,
+	PNS_PEP_ENABLE_REQ =		0x46,
+	PNS_PEP_ENABLE_RESP =		0x47,
+};
+
+enum isi_pep_sb_id {
+	PN_PIPE_SB_NEGOTIATED_FC =	0x03,
+	PN_PIPE_SB_ALIGNED_DATA =	0x06,
+};
+
 enum pn_pipe_state {	/* initial pipe state */
 	PN_PIPE_DISABLE,
 	PN_PIPE_ENABLE,
 };
 
 enum pn_msg_priority {
-	PN_MSG_PRIORITY_LOW = 1,
+	PN_MSG_PRIORITY_LOW =	1,
 	PN_MSG_PRIORITY_HIGH,
 };
 
+enum pn_flow_control {
+	PN_NO_FLOW_CONTROL =		0x00,
+	PN_LEGACY_FLOW_CONTROL =	0x01,
+	PN_ONE_CREDIT_FLOW_CONTROL =	0x02,
+	PN_MULTI_CREDIT_FLOW_CONTROL =	0x03,
+};
+
+struct _GIsiPipeServer {
+	int fd;
+	guint watch;
+};
+typedef struct _GIsiPipeServer GIsiPipeServer;
+
 struct _GIsiPipe {
 	GIsiClient *client;
 	GIsiPipeHandler handler;
@@ -122,8 +209,13 @@ struct _GIsiPipe {
 	uint8_t handle;
 	gboolean enabled;
 	gboolean enabling;
+	GIsiPipeServer *server;
 };
 
+static gboolean g_isi_pep_connect(GIsiPipe *pipe, uint8_t obj, uint8_t dev);
+static void g_isi_pep_enable(GIsiPipe *pipe, uint8_t dev);
+static void g_isi_pep_disconnect(GIsiPipe *pipe, uint8_t dev);
+
 static int g_isi_pipe_error(enum pn_pipe_error code)
 {
 	switch (code) {
@@ -168,6 +260,147 @@ static void g_isi_pipe_handle_error(GIsiPipe *pipe, uint8_t code)
 		pipe->error_handler(pipe);
 }
 
+static GIsiPipeServer *g_isi_pipe_server_create(GIsiModem *modem)
+{
+	GIsiPipeServer *server;
+	unsigned ifindex = g_isi_modem_index(modem);
+	struct sockaddr_pn addr = {
+		.spn_family = AF_PHONET,
+		.spn_resource = PN_PIPE,
+		.spn_obj = PN_OBJ_PEP_GPRS,
+	};
+	char buf[IF_NAMESIZE];
+	int fd = socket(PF_PHONET, SOCK_DGRAM, 0);
+
+	if (fd == -1)
+		return NULL;
+
+	server = g_try_new0(GIsiPipeServer, 1);
+	if (server == NULL)
+		goto error;
+
+	fcntl(fd, F_SETFD, FD_CLOEXEC);
+	/* Use blocking mode on purpose. */
+
+	if (ifindex == 0)
+		g_warning("Unspecified modem interface index");
+	else if (if_indextoname(ifindex, buf) == NULL ||
+		setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, IF_NAMESIZE))
+		goto error;
+
+	if (bind(fd, (void *)&addr, sizeof(addr)))
+		goto error;
+
+	server->fd = fd;
+	server->watch = g_isi_modem_add_to_watch(modem, fd);
+	if (!server->watch)
+		goto error;
+
+	return server;
+
+error:
+	close(fd);
+	return NULL;
+}
+
+static void g_isi_pipe_server_destroy(GIsiPipeServer *server)
+{
+
+	if (server->watch > 0)
+		g_source_remove(server->watch);
+
+	g_free(server);
+}
+
+static void g_isi_pep_connected(const GIsiMessage *msg, void *data)
+{
+	struct isi_pep_resp *resp;
+	size_t len = sizeof(struct isi_pipe_resp);
+	GIsiPipe *pipe = data;
+	GIsiClient *client = pipe->client;
+	struct isi_pipe_created_ind ind = {
+		.cmd = PNS_PIPE_CREATED_IND,
+		.pipe_handle = 0x00,
+		.n_sb = 0x01,
+		.sb_neg_fc = PN_PIPE_SB_NEGOTIATED_FC,
+		.sb_len = 0x04,
+		.tx_fc = PN_LEGACY_FLOW_CONTROL,
+		.rx_fc = PN_LEGACY_FLOW_CONTROL,
+	};
+
+	if (g_isi_msg_error(msg) < 0) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
+		return;
+	}
+
+	if (g_isi_msg_id(msg) != PNS_PEP_CONNECT_RESP)
+		return;
+
+	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
+		return;
+
+	if (resp->pipe_handle == PN_PIPE_INVALID_HANDLE) {
+		g_isi_pipe_handle_error(pipe, resp->error_code);
+		return;
+	}
+
+	pipe->handle = resp->pipe_handle;
+	ind.pipe_handle = resp->pipe_handle;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), msg->addr, &ind,
+			sizeof(ind), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			NULL, NULL, NULL) == NULL) {
+			g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_GENERAL);
+			return;
+	}
+
+	if (g_isi_msg_device(msg) == PN_HOST) {
+		g_isi_pep_connect(pipe, pipe->handle, PN_MODEM);
+		return;
+	}
+
+	if (pipe->enabling)
+		g_isi_pipe_start(pipe);
+
+	if (pipe->handler)
+		pipe->handler(pipe);
+}
+
+static gboolean g_isi_pep_connect(GIsiPipe *pipe, uint8_t obj, uint8_t dev)
+{
+	GIsiClient *client = pipe->client;
+	struct sockaddr_pn dst = {
+		.spn_family = AF_PHONET,
+	};
+	struct isi_pep_connect_req msg = {
+		.cmd = PNS_PEP_CONNECT_REQ,
+		.pipe_handle = obj,
+		.state_after = PN_PIPE_DISABLE,
+		.other_pep_type = PN_PEP_TYPE_COMMON,
+		.filler1 = 0x00,
+		.filler2 = 0x00,
+		.n_sb = 0x00,
+	};
+
+	if (dev == PN_HOST) {
+		dst.spn_dev = PN_HOST;
+		dst.spn_resource = PN_PIPE;
+		dst.spn_obj = obj;
+	} else if (dev == PN_MODEM) {
+		dst.spn_dev = PN_MODEM;
+		dst.spn_resource = g_isi_client_resource(client);
+		dst.spn_obj = PN_OBJ_PEP_GPRS;
+	} else
+		return FALSE;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), &dst, &msg,
+			sizeof(msg), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			g_isi_pep_connected, pipe, NULL) == NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
 static void g_isi_pipe_created(const GIsiMessage *msg, void *data)
 {
 	struct isi_pipe_resp *resp;
@@ -247,16 +480,115 @@ GIsiPipe *g_isi_pipe_create(GIsiModem *modem, GIsiPipeHandler cb, uint16_t obj1,
 	pipe->enabled = FALSE;
 	pipe->handle = PN_PIPE_INVALID_HANDLE;
 
-	if (g_isi_client_send(pipe->client, &msg, len,
+	if (g_isi_modem_version_major(modem) == 2 &&
+		g_isi_modem_version_minor(modem) == 5) {
+
+		pipe->server = g_isi_pipe_server_create(modem);
+		if (pipe->server == NULL)
+			goto error;
+
+		if (!g_isi_pep_connect(pipe, obj1 & 0xFF, PN_HOST)) {
+			g_isi_pipe_server_destroy(pipe->server);
+			goto error;
+		}
+	} else {
+
+		if (!g_isi_client_send(pipe->client, &msg, len,
 					g_isi_pipe_created, pipe, NULL))
-		return pipe;
+			goto error;
 
+	}
+
+	return pipe;
+
+error:
 	g_isi_client_destroy(pipe->client);
 	g_free(pipe);
 
 	return NULL;
 }
 
+static void g_isi_pep_enabled(const GIsiMessage *msg, void *data)
+{
+	struct isi_pep_resp *resp;
+	size_t len = sizeof(struct isi_pep_resp);
+	GIsiPipe *pipe = data;
+	GIsiClient *client = pipe->client;
+	struct isi_pipe_enabled_ind ind = {
+		.cmd = PNS_PIPE_ENABLED_IND,
+		.pipe_handle = pipe->handle,
+		.filler = 0x00,
+	};
+
+	if (g_isi_msg_error(msg) < 0) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
+		return;
+	}
+
+	if (g_isi_msg_id(msg) != PNS_PEP_ENABLE_RESP)
+		return;
+
+	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
+		return;
+
+	if (pipe->handle != resp->pipe_handle) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_INVALID_HANDLE);
+		return;
+	}
+
+	g_isi_pipe_handle_error(pipe, resp->error_code);
+	if (pipe->error)
+		return;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), msg->addr, &ind,
+			sizeof(ind), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			NULL, NULL, NULL) == NULL) {
+			g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_GENERAL);
+			return;
+	}
+
+	if (g_isi_msg_device(msg) == PN_MODEM) {
+		g_isi_pep_enable(pipe, PN_HOST);
+		return;
+	}
+
+	pipe->enabling = FALSE;
+
+	if (!pipe->error)
+		pipe->enabled = TRUE;
+}
+
+static void g_isi_pep_enable(GIsiPipe *pipe, uint8_t dev)
+{
+	GIsiClient *client = pipe->client;
+	struct sockaddr_pn dst = {
+		.spn_family = AF_PHONET,
+	};
+	struct isi_pep_enable_req msg = {
+		.cmd = PNS_PEP_ENABLE_REQ,
+		.pipe_handle = pipe->handle,
+		.filler = 0x00,
+	};
+
+	if (dev == PN_HOST) {
+		dst.spn_dev = PN_HOST;
+		dst.spn_resource = PN_PIPE;
+		dst.spn_obj = pipe->handle;
+	} else if (dev == PN_MODEM) {
+		dst.spn_dev = PN_MODEM;
+		dst.spn_resource = g_isi_client_resource(client);
+		dst.spn_obj = PN_OBJ_PEP_GPRS;
+	} else
+		return;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), &dst, &msg,
+			sizeof(msg), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			g_isi_pep_enabled, pipe, NULL) == NULL)
+		return;
+
+	return;
+}
+
 static void g_isi_pipe_enabled(const GIsiMessage *msg, void *data)
 {
 	GIsiPipe *pipe = data;
@@ -287,14 +619,19 @@ static void g_isi_pipe_enabled(const GIsiMessage *msg, void *data)
 
 static void g_isi_pipe_enable(GIsiPipe *pipe)
 {
+	GIsiModem *modem = g_isi_client_modem(pipe->client);
 	struct isi_pipe_enable_req msg = {
 		.cmd = PNS_PIPE_ENABLE_REQ,
 		.pipe_handle = pipe->handle,
 	};
 	size_t len = sizeof(msg);
 
-	g_isi_client_send(pipe->client, &msg, len,
-				g_isi_pipe_enabled, pipe, NULL);
+	if (g_isi_modem_version_major(modem) == 2 &&
+		g_isi_modem_version_minor(modem) == 5)
+		g_isi_pep_enable(pipe, PN_MODEM);
+	else
+		g_isi_client_send(pipe->client, &msg, len,
+					g_isi_pipe_enabled, pipe, NULL);
 }
 
 /**
@@ -339,8 +676,8 @@ static void g_isi_pipe_removed(const GIsiMessage *msg, void *data)
 	if (pipe->handle != resp->pipe_handle)
 		return;
 
-	pipe->handle = PN_PIPE_INVALID_HANDLE;
-	pipe->error = -EPIPE;
+	g_isi_client_destroy(pipe->client);
+	g_free(pipe);
 }
 
 
@@ -356,17 +693,89 @@ static void g_isi_pipe_remove(GIsiPipe *pipe)
 				g_isi_pipe_removed, pipe, NULL);
 }
 
+static void g_isi_pep_disconnected(const GIsiMessage *msg, void *data)
+{
+	struct isi_pep_resp *resp;
+	size_t len = sizeof(struct isi_pep_resp);
+	GIsiPipe *pipe = data;
+
+	if (g_isi_msg_error(msg) < 0) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
+		return;
+	}
+
+	if (g_isi_msg_id(msg) != PNS_PEP_DISCONNECT_RESP)
+		return;
+
+	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
+		return;
+
+	if (resp->pipe_handle == PN_PIPE_INVALID_HANDLE) {
+		g_isi_pipe_handle_error(pipe, resp->error_code);
+		return;
+	}
+
+	if (pipe->handle != resp->pipe_handle) {
+		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_INVALID_HANDLE);
+		return;
+	}
+
+	if (g_isi_msg_device(msg) == PN_HOST) {
+		g_isi_pep_disconnect(pipe, PN_MODEM);
+		return;
+	}
+
+	g_isi_pipe_server_destroy(pipe->server);
+	g_isi_client_destroy(pipe->client);
+	g_free(pipe);
+}
+
+static void g_isi_pep_disconnect(GIsiPipe *pipe, uint8_t dev)
+{
+	GIsiClient *client = pipe->client;
+	struct sockaddr_pn dst = {
+		.spn_family = AF_PHONET,
+	};
+	struct isi_pep_disconnect_req msg = {
+		.cmd = PNS_PEP_DISCONNECT_REQ,
+		.pipe_handle = pipe->handle,
+		.filler = 0x00,
+	};
+
+	if (dev == PN_HOST) {
+		dst.spn_dev = PN_HOST;
+		dst.spn_resource = PN_PIPE;
+		dst.spn_obj = pipe->handle;
+	} else if (dev == PN_MODEM) {
+		dst.spn_dev = PN_MODEM;
+		dst.spn_resource = g_isi_client_resource(client);
+		dst.spn_obj = PN_OBJ_PEP_GPRS;
+	} else
+		return;
+
+	if (g_isi_request_sendto(g_isi_client_modem(client), &dst, &msg,
+			sizeof(msg), G_ISI_CLIENT_DEFAULT_TIMEOUT,
+			g_isi_pep_disconnected, pipe, NULL) == NULL)
+		return;
+
+	return;
+}
+
 /**
  * Destroy a pipe. If it was connected, it is removed.
  * @param pipe pipe as returned from g_isi_pipe_create()
  */
 void g_isi_pipe_destroy(GIsiPipe *pipe)
 {
-	if (!pipe->error)
-		g_isi_pipe_remove(pipe);
-
-	g_isi_client_destroy(pipe->client);
-	g_free(pipe);
+	GIsiModem *modem = g_isi_client_modem(pipe->client);
+
+	if (!pipe->error) {
+		if (g_isi_modem_version_major(modem) == 2 &&
+			g_isi_modem_version_minor(modem) == 5)
+			g_isi_pep_disconnect(pipe, PN_HOST);
+		else
+			g_isi_pipe_remove(pipe);
+	}
 }
 
 void g_isi_pipe_set_error_handler(GIsiPipe *pipe, GIsiPipeErrorHandler cb)
-- 
1.7.3.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2011-02-07 10:01 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-01-27 13:37 [PATCH 1/1] gisi: Updated subscriptions and pipe handling to accomodate additional isimodem versions Jessica Nilsson
2011-01-28 11:37 Jessica Nilsson
2011-01-28 11:42 ` Marcel Holtmann
2011-01-28 13:15   ` Jessica Nilsson
2011-01-28 14:33 ` =?unknown-8bit?q?R=C3=A9mi?= Denis-Courmont
2011-01-31 12:05   ` Jessica Nilsson
2011-02-04  8:41   ` Aki Niemi
2011-02-07 10:01     ` Aki Niemi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.