Support multiple instances of jabra_vold.

BUG=chromium:365746
TEST=verify that multiple jabra_vold processes are launched with multi-jabra setup,
and they are destroyed when the jabra units are removed.
CQ-DEPEND=203511
Change-Id: Iec6bcfe365ea952c69b59af763f15356e09dd5d6
Reviewed-on: https://chromium-review.googlesource.com/201109
Reviewed-by: Haixia Shi <hshi@chromium.org>
Tested-by: Haixia Shi <hshi@chromium.org>
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
Commit-Queue: Haixia Shi <hshi@chromium.org>
diff --git a/99-jabra.rules b/99-jabra.rules
index 4ad4869..151d351 100644
--- a/99-jabra.rules
+++ b/99-jabra.rules
@@ -6,17 +6,17 @@
 ENV{DEVTYPE}!="usb_device", GOTO="jabra_end"
 
 # Jabra Speakerphone 410
-ATTRS{idVendor}=="0b0e", ATTRS{idProduct}=="0412", GOTO="jabra_action"
+ENV{ID_VENDOR_ID}=="0b0e", ENV{ID_MODEL_ID}=="0412", GOTO="jabra_action"
 # Jabra Speakerphone 510
-ATTRS{idVendor}=="0b0e", ATTRS{idProduct}=="0420", GOTO="jabra_action"
+ENV{ID_VENDOR_ID}=="0b0e", ENV{ID_MODEL_ID}=="0420", GOTO="jabra_action"
 
 GOTO="jabra_end"
 
 LABEL="jabra_action"
 
-# start or restart the jabra_vold daemon
-ACTION=="add|change", RUN+="/sbin/initctl emit jabra_vold DEVNUM=%s{devnum} BUSNUM=%s{busnum}"
+# start the jabra_vold daemon
+ACTION=="add", RUN+="/usr/sbin/jabra_vold -a start -n $env{DEVNUM} -b $env{BUSNUM}"
 # stop the daemon
-ACTION=="remove", RUN+="/sbin/initctl stop jabra-vold"
+ACTION=="remove", RUN+="/usr/sbin/jabra_vold -a stop -n $env{DEVNUM} -b $env{BUSNUM}"
 
 LABEL="jabra_end"
diff --git a/jabra-vold.conf b/jabra-vold.conf
deleted file mode 100644
index fb8fe6a..0000000
--- a/jabra-vold.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-description     "Jabra speakerphone volume control daemon"
-author          "chromium-os-dev@chromium.org"
-
-# when "jabra_vold" is emitted, we force re-start (stop then start)
-start on jabra_vold
-stop on jabra_vold or starting pre-shutdown
-
-exec /usr/sbin/jabra_vold
diff --git a/jabra.c b/jabra.c
index f1a67fd..639936b 100644
--- a/jabra.c
+++ b/jabra.c
@@ -47,6 +47,9 @@
 #define KEY_CROS_VOLDOWN KEY_VOLUMEDOWN
 #define KEY_CROS_VOLUP KEY_VOLUMEUP
 
+/* Maximum number of supported Alsa devices for Jabra */
+#define ALSA_DEV_COUNT_MAX 4
+
 /*
  * Linux kernel usbmon binary interface format
  * from Documentation/usb/usbmon.txt
@@ -83,8 +86,9 @@
 /* device context */
 struct jabra_dev {
 	int uinput_fd;
-	snd_hctl_t *hctl;
-	snd_hctl_elem_t *playback_vol;
+	int alsa_dev_count;
+	snd_hctl_t *hctl[ALSA_DEV_COUNT_MAX];
+	snd_hctl_elem_t *playback_vol[ALSA_DEV_COUNT_MAX];
 	int mon_fd;
 	int devnum;
 };
@@ -103,19 +107,22 @@
 static int alsa_touch_volume(struct jabra_dev *dev)
 {
 	snd_ctl_elem_value_t *elem;
-	int err;
+	int err = 0, idx;
 
 	snd_ctl_elem_value_alloca(&elem);
-	err = snd_hctl_elem_read(dev->playback_vol, elem);
-	if (err < 0)
-		return err;
-	err = snd_hctl_elem_write(dev->playback_vol, elem);
-	if (err < 0)
-		return err;
-	logd(LOG_INFO, "Touch volume: %ld\n",
-		snd_ctl_elem_value_get_integer(elem, 0));
+	for (idx = 0; idx < dev->alsa_dev_count; ++idx) {
+		err = snd_hctl_elem_read(dev->playback_vol[idx], elem);
+		if (err < 0)
+			goto return_err;
+		err = snd_hctl_elem_write(dev->playback_vol[idx], elem);
+		if (err < 0)
+			goto return_err;
+		logd(LOG_INFO, "Touch volume: %ld\n",
+			snd_ctl_elem_value_get_integer(elem, 0));
+	}
 
-	return 0;
+return_err:
+	return (err < 0) ? err : 0;
 }
 
 static int alsa_init(struct jabra_dev *dev)
@@ -124,7 +131,7 @@
 	snd_ctl_elem_id_t *id;
 	int idx;
 	char *card_name;
-	char *hwid = NULL;
+	char *hwid[ALSA_DEV_COUNT_MAX] = { NULL, };
 	time_t deadline = time(NULL) + 5 /* 5-second timeout */;
 
 	while (time(NULL) < deadline) {
@@ -132,17 +139,20 @@
 		for (idx = -1; (snd_card_next(&idx) == 0) && (idx >= 0); ) {
 			ret = snd_card_get_name(idx, &card_name);
 			if (!ret && (strncmp(card_name, ALSA_DEV_NAME,
-			     sizeof(ALSA_DEV_NAME) - 1) == 0)) {
-				ret = asprintf(&hwid, "hw:%d", idx);
+				sizeof(ALSA_DEV_NAME) - 1) == 0)) {
+				ret = asprintf(&hwid[dev->alsa_dev_count], "hw:%d", idx);
 				logd(LOG_NOTICE, "ALSA dev %s (%s)\n",
-							hwid, card_name);
+						hwid[dev->alsa_dev_count], card_name);
 				free(card_name);
-				break;
+				if (dev->alsa_dev_count++ >= ALSA_DEV_COUNT_MAX) {
+					logd(LOG_ERR, "Too many ALSA devices found for Jabra\n");
+					break;
+				}
 			}
 			if (!ret)
 				free(card_name);
 		}
-		if (hwid)
+		if (dev->alsa_dev_count)
 			break;
 		logd(LOG_INFO, "no ALSA device, retry later ...\n");
 		/*
@@ -151,33 +161,40 @@
 		 */
 		usleep(250000);
 	}
-	if (!hwid) {
+	if (!dev->alsa_dev_count) {
 		logd(LOG_ERR, "Cannot find ALSA device for Jabra\n");
 		return -ENODEV;
 	}
 
 	/* initialize the Jabra USB-audio mixer control */
-	ret = snd_hctl_open(&dev->hctl, hwid, 0);
-	if (ret < 0)
-		return -ENODEV;
-	ret = snd_hctl_load(dev->hctl);
-	if (ret < 0)
-		goto fail_after_open;
+	for (idx = 0; idx < dev->alsa_dev_count; ++idx) {
+		ret = snd_hctl_open(&dev->hctl[idx], hwid[idx], 0);
+		if (ret < 0)
+			return -ENODEV;
+		ret = snd_hctl_load(dev->hctl[idx]);
+		if (ret < 0)
+			goto fail_after_open;
 
-	snd_ctl_elem_id_alloca(&id);
-	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
-	snd_ctl_elem_id_set_name(id, ALSA_CONTROL_NAME);
-	dev->playback_vol = snd_hctl_find_elem(dev->hctl, id);
-	if (!dev->playback_vol)
-		goto fail_after_open;
+		snd_ctl_elem_id_alloca(&id);
+		snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+		snd_ctl_elem_id_set_name(id, ALSA_CONTROL_NAME);
+		dev->playback_vol[idx] = snd_hctl_find_elem(dev->hctl[idx], id);
+		if (!dev->playback_vol[idx])
+			goto fail_after_open;
 
-	free(hwid);
+		free(hwid[idx]);
+	}
 	return 0;
 
 fail_after_open:
 	logd(LOG_ERR, "alsa initialization failed (%d)\n", ret);
-	snd_hctl_close(dev->hctl);
-	free(hwid);
+	for (idx = 0; idx < dev->alsa_dev_count; ++idx) {
+		if (dev->hctl[idx]) {
+			snd_hctl_close(dev->hctl[idx]);
+			dev->hctl[idx] = NULL;
+		}
+		free(hwid[idx]);
+	}
 	return ret;
 }
 
@@ -186,7 +203,12 @@
 	int ret;
 	size_t count;
 	char* uidev_ptr;
-	struct uinput_user_dev uidev;
+	struct uinput_user_dev *uidev = calloc(1, sizeof(struct uinput_user_dev));
+
+	if (!uidev) {
+		logd(LOG_ERR, "Failed to allocate uinput_user_dev buffer");
+		return -ENOMEM;
+	}
 
 	dev->uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
 	if (dev->uinput_fd < 0) {
@@ -199,11 +221,11 @@
 	ioctl(dev->uinput_fd, UI_SET_KEYBIT, KEY_CROS_VOLDOWN);
 	ioctl(dev->uinput_fd, UI_SET_KEYBIT, KEY_CROS_VOLUP);
 
-	snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "jabra_vold input device");
-	uidev.id.bustype = BUS_USB;
-	uidev.id.version = 1;
+	snprintf(uidev->name, UINPUT_MAX_NAME_SIZE, "jabra_vold input device");
+	uidev->id.bustype = BUS_USB;
+	uidev->id.version = 1;
 
-	for (uidev_ptr = (char*)&uidev, count = sizeof(uidev); count; ) {
+	for (uidev_ptr = (char*)uidev, count = sizeof(*uidev); count; ) {
 		int written = write(dev->uinput_fd, uidev_ptr, count);
 		if (written < 0) {
 			if (errno == EINTR || errno == EAGAIN) continue;
@@ -221,11 +243,13 @@
 		goto fail_exit;
 	}
 
+	free(uidev);
 	return 0;
 
 fail_exit:
 	close(dev->uinput_fd);
 	dev->uinput_fd = 0;
+	free(uidev);
 	return ret;
 }
 
@@ -339,7 +363,7 @@
 static int use_jabra_device(int busnum, int devnum, int loglvl, int errlog)
 {
 	struct jabra_dev *dev;
-	int ret;
+	int idx, ret;
 
 	/* setup the syslog facility and honor UDEV log level if set */
 	openlog(DAEMON_NAME, errlog ? LOG_PERROR : 0, LOG_DAEMON);
@@ -366,8 +390,10 @@
 err:
 	if (dev->mon_fd)
 		close(dev->mon_fd);
-	if (dev->hctl)
-		snd_hctl_close(dev->hctl);
+	for (idx = 0; idx < dev->alsa_dev_count; ++idx) {
+		if (dev->hctl[idx])
+			snd_hctl_close(dev->hctl[idx]);
+	}
 	if (dev->uinput_fd) {
 		ioctl(dev->uinput_fd, UI_DEV_DESTROY);
 		close(dev->uinput_fd);
@@ -380,15 +406,21 @@
 int main(int argc, char *argv[])
 {
 	int opt;
+	char *action_str = "start";
 	char *busnum_str = getenv("BUSNUM");
 	char *devnum_str = getenv("DEVNUM");
 	char *loglevel_str = getenv("LOGLEVEL");
+	char pid_filename[128];
 	int use_stderr = 0;
 	int busnum, devnum;
 	int loglevel = LOG_NOTICE;
+	int pid;
 
-	while ((opt = getopt(argc, argv, "b:d:n:")) != -1) {
+	while ((opt = getopt(argc, argv, "a:b:d:n:")) != -1) {
 		switch (opt) {
+		case 'a':
+			action_str = optarg;
+			break;
 		case 'b':
 			busnum_str = optarg;
 			break;
@@ -410,6 +442,60 @@
 	devnum = atoi(devnum_str);
 	if (loglevel_str)
 		loglevel = atoi(loglevel_str);
+	snprintf(pid_filename, sizeof(pid_filename), "/var/run/jabra_vold.%d.%d.pid",
+			busnum, devnum);
+
+	if (strcmp(action_str, "start") == 0) {
+		pid = fork();
+		if (pid < 0) {
+			fprintf(stderr, "Failed to fork child process\n");
+			return -ENOMEM;
+		}
+		if (pid != 0) {
+			logd(LOG_INFO, "Started jabra_vold child process %d.\n", pid);
+			return 0;
+		}
+		pid = fork();
+		if (pid < 0) {
+			fprintf(stderr, "Failed to fork grandchild process\n");
+			return -ENOMEM;
+		}
+		if (pid != 0) {
+			FILE* pid_file = fopen(pid_filename, "w");
+			if (!pid_file) {
+				fprintf(stderr, "Failed to open PID file to write for jabra_vold %d:%d\n",
+						busnum, devnum);
+				return -ENOMEM;
+			}
+			logd(LOG_INFO, "Started jabra_vold grandchild process %d.\n", pid);
+			if (fprintf(pid_file, "%d\n", pid) <= 0) {
+				fprintf(stderr, "Failed to write PID to the PID file.\n");
+				return -ENOMEM;
+			}
+			fclose(pid_file);
+			return 0;
+		}
+	} else if (strcmp(action_str, "stop") == 0) {
+		FILE* pid_file = fopen(pid_filename, "r");
+		if (!pid_file) {
+			fprintf(stderr, "Failed to open PID file to read for jabra_vold %d:%d\n",
+					busnum, devnum);
+			return -ENOMEM;
+		}
+		if (fscanf(pid_file, "%d", &pid) <= 0) {
+			fprintf(stderr, "Failed to obtain PID for jabra_vold %d:%d\n",
+					busnum, devnum);
+			return -ENOMEM;
+		}
+		fclose(pid_file);
+		remove(pid_filename);
+		logd(LOG_INFO, "Stopping jabra_vold process %d\n", pid);
+		kill(pid, SIGKILL);
+		return 0;
+	} else {
+		fprintf(stderr, "Unrecognized action string '%s'\n", action_str);
+		return -EINVAL;
+	}
 
 	return use_jabra_device(busnum, devnum, loglevel, use_stderr);
 }