1 /***************************************************************************
2  *
3  * sysevent.c : Solaris sysevents
4  *
5  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
6  * Use is subject to license terms.
7  *
8  * Licensed under the Academic Free License version 2.1
9  *
10  **************************************************************************/
11 
12 #pragma ident	"%Z%%M%	%I%	%E% SMI"
13 
14 #ifdef HAVE_CONFIG_H
15 #  include <config.h>
16 #endif
17 
18 #include <stdio.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <sys/dkio.h>
25 #include <sys/stat.h>
26 #include <libdevinfo.h>
27 #include <libsysevent.h>
28 #include <sys/sysevent/dev.h>
29 #include <sys/sysevent/pwrctl.h>
30 #include <glib.h>
31 
32 #include "../osspec.h"
33 #include "../logger.h"
34 #include "../hald.h"
35 #include "../hald_dbus.h"
36 #include "../device_info.h"
37 #include "../util.h"
38 #include "osspec_solaris.h"
39 #include "hotplug.h"
40 #include "devinfo.h"
41 #include "devinfo_storage.h"
42 #include "devinfo_acpi.h"
43 #include "devinfo_usb.h"
44 #include "sysevent.h"
45 
46 #ifndef ESC_LOFI
47 #define	ESC_LOFI "lofi"
48 #endif
49 
50 static void	sysevent_dev_handler(sysevent_t *);
51 static gboolean sysevent_iochannel_data(GIOChannel *, GIOCondition, gpointer);
52 static void	sysevent_dev_add(gchar *, gchar *);
53 static void	sysevent_dev_remove(gchar *, gchar *);
54 static void	sysevent_dev_branch(gchar *);
55 static void	sysevent_lofi_add(gchar *, gchar *);
56 static void	sysevent_lofi_remove(gchar *, gchar *);
57 static void	sysevent_devfs_add(gchar *);
58 static void	sysevent_pwrctl(gchar *, gchar *, gchar *, gchar *, gchar *,
59 		    gchar *, uint_t);
60 
61 static sysevent_handle_t	*shp;
62 
63 static int sysevent_pipe_fds[2];
64 static GIOChannel *sysevent_iochannel;
65 static guint sysevent_source_id;
66 
67 gboolean
68 sysevent_init(void)
69 {
70 	GError *err = NULL;
71 	const char	*subcl[6];
72 
73 	/*
74 	 * pipe used to serialize sysevents through the main loop
75 	 */
76 	if (pipe (sysevent_pipe_fds) != 0) {
77 		HAL_INFO (("pipe() failed errno=%d", errno));
78 		return (FALSE);
79 	}
80 	sysevent_iochannel = g_io_channel_unix_new (sysevent_pipe_fds[0]);
81 	if (sysevent_iochannel == NULL) {
82 		HAL_INFO (("g_io_channel_unix_new failed"));
83 		return (FALSE);
84 	}
85 	g_io_channel_set_flags (sysevent_iochannel, G_IO_FLAG_NONBLOCK, &err);
86 	sysevent_source_id = g_io_add_watch (
87 	    sysevent_iochannel, G_IO_IN, sysevent_iochannel_data, NULL);
88 
89 	shp = sysevent_bind_handle(sysevent_dev_handler);
90 	if (shp == NULL) {
91 		HAL_INFO (("sysevent_bind_handle failed %d", errno));
92 		return (FALSE);
93 	}
94 
95 	subcl[0] = ESC_DISK;
96 	subcl[1] = ESC_LOFI;
97 	subcl[2] = ESC_PRINTER;
98 	if (sysevent_subscribe_event(shp, EC_DEV_ADD, subcl, 3) != 0) {
99 		HAL_INFO (("subscribe(dev_add) failed %d", errno));
100 		sysevent_unbind_handle(shp);
101 		return (FALSE);
102 	}
103 	if (sysevent_subscribe_event(shp, EC_DEV_REMOVE, subcl, 3) != 0) {
104 		HAL_INFO (("subscribe(dev_remove) failed %d", errno));
105 		sysevent_unbind_handle(shp);
106 		return (FALSE);
107 	}
108 
109 	subcl[0] = ESC_DEV_BRANCH_REMOVE;
110 	if (sysevent_subscribe_event(shp, EC_DEV_BRANCH, subcl, 1) != 0) {
111 		HAL_INFO (("subscribe(dev_branch) failed %d", errno));
112 		sysevent_unbind_handle(shp);
113 		return (FALSE);
114 	}
115 
116 	subcl[0] = ESC_PWRCTL_ADD;
117 	subcl[1] = ESC_PWRCTL_REMOVE;
118 	subcl[2] = ESC_PWRCTL_STATE_CHANGE;
119 	subcl[3] = ESC_PWRCTL_BRIGHTNESS_UP;
120 	subcl[4] = ESC_PWRCTL_BRIGHTNESS_DOWN;
121 	subcl[5] = ESC_PWRCTL_POWER_BUTTON;
122 	if (sysevent_subscribe_event(shp, EC_PWRCTL, subcl, 6) != 0) {
123 		HAL_INFO(("subscribe(dev_add) failed %d", errno));
124 		sysevent_unbind_handle(shp);
125 		return (FALSE);
126 	}
127 
128 	subcl[0] = ESC_DEVFS_DEVI_ADD;
129 	if (sysevent_subscribe_event(shp, EC_DEVFS, subcl, 1) != 0) {
130 		HAL_INFO (("subscribe(EC_DEVFS) failed %d", errno));
131 		sysevent_unbind_handle(shp);
132 		return (FALSE);
133 	}
134 
135 	return (B_TRUE);
136 }
137 
138 void
139 sysevent_fini(void)
140 {
141 	sysevent_unbind_handle(shp);
142 	shp = NULL;
143 }
144 
145 static void
146 sysevent_dev_handler(sysevent_t *ev)
147 {
148 	char		*class;
149 	char		*subclass;
150 	nvlist_t	*attr_list;
151 	char		*phys_path;
152 	char		*dev_name;
153 	char		*dev_hid;
154 	char		*dev_uid;
155 	uint_t		dev_index;
156 	char		s[1024];
157 	ssize_t		nwritten;
158 
159 	if ((class = sysevent_get_class_name(ev)) == NULL)
160 		return;
161 
162 	if ((subclass = sysevent_get_subclass_name(ev)) == NULL)
163 		return;
164 
165 	if (sysevent_get_attr_list(ev, &attr_list) != 0)
166 		return;
167 
168 	if (strcmp(class, EC_DEVFS) == 0) {
169 		if (nvlist_lookup_string(attr_list, DEVFS_PATHNAME, &phys_path) != 0) {
170 			goto out;
171 		}
172 
173 		snprintf(s, sizeof (s), "%s %s %s\n",
174 		    class, subclass, phys_path);
175 		nwritten = write(sysevent_pipe_fds[1], s, strlen(s) + 1);
176 
177 		HAL_INFO (("sysevent_dev_handler: wrote %d bytes", nwritten));
178 		goto out;
179 	}
180 
181 	if (strcmp(class, EC_PWRCTL) == 0) {
182 		if (nvlist_lookup_string(attr_list, PWRCTL_DEV_PHYS_PATH,
183 		    &phys_path) != 0) {
184 			goto out;
185 		}
186 	} else if (nvlist_lookup_string(attr_list, DEV_PHYS_PATH, &phys_path)
187 	    != 0) {
188 		goto out;
189 	}
190 
191 	if (nvlist_lookup_string(attr_list, DEV_NAME, &dev_name) != 0) {
192 		if (strcmp(class, EC_PWRCTL) == 0) {
193 			dev_name = "noname";
194 		} else {
195 			dev_name = "";
196 		}
197 	}
198 
199 	if (nvlist_lookup_string(attr_list, PWRCTL_DEV_HID, &dev_hid) != 0) {
200 		dev_hid = "";
201 	}
202 	if (nvlist_lookup_string(attr_list, PWRCTL_DEV_UID, &dev_uid) != 0) {
203 		dev_uid = "";
204 	}
205 	if (nvlist_lookup_uint32(attr_list, PWRCTL_DEV_INDEX, &dev_index)
206 	    != 0) {
207 		dev_index = 0;
208 	}
209 
210 	snprintf(s, sizeof (s), "%s %s %s %s %s %s %d\n",
211 	    class, subclass, phys_path, dev_name, dev_hid, dev_uid, dev_index);
212 	nwritten = write(sysevent_pipe_fds[1], s, strlen(s) + 1);
213 
214 	HAL_INFO (("sysevent_dev_handler: wrote %d bytes", nwritten));
215 
216 out:
217 	nvlist_free(attr_list);
218 }
219 
220 static gboolean
221 sysevent_iochannel_data (GIOChannel *source,
222 		    GIOCondition condition,
223 		    gpointer user_data)
224 {
225 	GError *err = NULL;
226 	gchar *s = NULL;
227 	gsize len;
228 	int matches;
229 	gchar class[1024];
230 	gchar subclass[1024];
231 	gchar phys_path[1024];
232 	gchar dev_name[1024];
233 	gchar dev_uid[1024];
234 	gchar dev_hid[1024];
235 	uint_t dev_index;
236 
237 	HAL_INFO (("sysevent_iochannel_data"));
238 
239 	while (g_io_channel_read_line (sysevent_iochannel, &s, &len, NULL,
240 	    &err) == G_IO_STATUS_NORMAL) {
241 		if (len == 0) {
242 			break;
243 		}
244 
245 		class[0] = subclass[0] = phys_path[0] = dev_name[0] =
246 		    dev_hid[0] = dev_uid[0] = '\0';
247 		matches = sscanf(s, "%s %s %s %s %s %s %d", class, subclass,
248 		    phys_path, dev_name, dev_hid, dev_uid, &dev_index);
249 		g_free (s);
250 		s = NULL;
251 		if (matches < 3) {
252 			continue;
253 		}
254 		HAL_INFO (("sysevent: class=%s, sub=%s", class, subclass));
255 
256 		if (strcmp(class, EC_DEV_ADD) == 0) {
257 			if ((strcmp(subclass, ESC_DISK) == 0) ||
258 			    (strcmp(subclass, ESC_PRINTER) == 0)) {
259 				sysevent_dev_add(phys_path, dev_name);
260 			} else if (strcmp(subclass, ESC_LOFI) == 0) {
261 				sysevent_lofi_add(phys_path, dev_name);
262 			}
263 		} else if (strcmp(class, EC_DEV_REMOVE) == 0) {
264 			if ((strcmp(subclass, ESC_DISK) == 0) ||
265 			    (strcmp(subclass, ESC_PRINTER) == 0)) {
266 				sysevent_dev_remove(phys_path, dev_name);
267 			} else if (strcmp(subclass, ESC_LOFI) == 0) {
268 				sysevent_lofi_remove(phys_path, dev_name);
269 			}
270 		} else if (strcmp(class, EC_DEV_BRANCH) == 0) {
271 			sysevent_dev_branch(phys_path);
272 		} else if (strcmp(class, EC_PWRCTL) == 0) {
273 			sysevent_pwrctl(class, subclass, phys_path,
274 			    dev_name, dev_hid, dev_uid, dev_index);
275 		} else if (strcmp(class, EC_DEVFS) == 0) {
276 			if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
277 				sysevent_devfs_add(phys_path);
278 			}
279 		}
280 	}
281 
282 	if (err) {
283 		g_error_free (err);
284 	}
285 
286 	return (TRUE);
287 }
288 
289 static void
290 sysevent_dev_add(gchar *devfs_path, gchar *name)
291 {
292 	gchar	*parent_devfs_path, *hotplug_devfs_path;
293 	HalDevice *parent;
294 
295 	HAL_INFO (("dev_add: %s %s", name, devfs_path));
296 
297 	parent = hal_util_find_closest_ancestor (devfs_path, &parent_devfs_path, &hotplug_devfs_path);
298 	if (parent == NULL) {
299 		return;
300 	}
301 
302 	HAL_INFO (("dev_add: parent=%s", parent_devfs_path));
303 	HAL_INFO (("dev_add: real=%s", hotplug_devfs_path));
304 
305 	devinfo_add (parent, hotplug_devfs_path);
306 
307 	g_free (parent_devfs_path);
308 	g_free (hotplug_devfs_path);
309 
310 	hotplug_event_process_queue ();
311 }
312 
313 static void
314 sysevent_dev_remove(gchar *devfs_path, gchar *name)
315 {
316 	HAL_INFO (("dev_remove: %s %s", name, devfs_path));
317 
318 	devinfo_remove_branch (devfs_path, NULL);
319 	hotplug_event_process_queue ();
320 }
321 
322 static void
323 sysevent_dev_branch(gchar *devfs_path)
324 {
325 	HAL_INFO (("branch_remove: %s", devfs_path));
326 
327 	devinfo_remove_branch (devfs_path, NULL);
328 	hotplug_event_process_queue ();
329 }
330 
331 static void
332 sysevent_lofi_add(gchar *devfs_path, gchar *name)
333 {
334 	di_node_t node;
335 	const char *parent_udi;
336 	HalDevice *d, *parent;
337 
338 	HAL_INFO (("lofi_add: %s %s", name, devfs_path));
339 
340 	if ((d = hal_device_store_match_key_value_string (hald_get_gdl (),
341 	    "solaris.devfs_path", devfs_path)) == NULL) {
342 		HAL_INFO (("device not found in GDL %s", devfs_path));
343 		return;
344 	}
345 	parent_udi = hal_device_property_get_string (d, "info.parent");
346 	if ((parent_udi == NULL) || (strlen(parent_udi) == 0)) {
347 		HAL_INFO (("parent not found in GDL %s", parent_udi));
348 		return;
349 	}
350 	if ((parent = hal_device_store_match_key_value_string (hald_get_gdl (),
351 	    "info.udi", parent_udi)) == NULL) {
352 		HAL_INFO (("parent not found in GDL %s", parent_udi));
353 		return;
354 	}
355 
356 	if ((node = di_init (devfs_path, DINFOCPYALL)) == DI_NODE_NIL) {
357 		HAL_INFO (("device not found in devinfo %s", devfs_path));
358 		return;
359 	}
360 
361 	HAL_INFO (("device %s parent %s", hal_device_get_udi (d), parent_udi));
362 	devinfo_lofi_add_major (parent, node, devfs_path, NULL, TRUE, d);
363 
364 	di_fini (node);
365 
366 	hotplug_event_process_queue ();
367 }
368 
369 static void
370 sysevent_lofi_remove(gchar *parent_devfs_path, gchar *name)
371 {
372 	devinfo_lofi_remove_minor(parent_devfs_path, name);
373 	hotplug_event_process_queue ();
374 }
375 
376 static HalDevice *
377 lookup_parent(char *devfs_path)
378 {
379 	gchar		*path = NULL;
380 	HalDevice	*parent = NULL;
381 	char *p;
382 
383 	path = strdup (devfs_path);
384 	p = strrchr (path, '/');
385 	if (p == NULL) {
386 		free (path);
387 		return (NULL);
388 	}
389 	*p = '\0';
390 
391 	/* Look up the parent node in the gdl. */
392 	parent = hal_device_store_match_key_value_string (hald_get_gdl (),
393 	    "solaris.devfs_path", path);
394 
395 	if (parent == NULL) {
396 		/* Look up the parent node in the tdl. */
397 		parent = hal_device_store_match_key_value_string (hald_get_tdl (),
398 		    "solaris.devfs_path", path);
399 	}
400 
401 	free (path);
402 	return (parent);
403 }
404 
405 /*
406  * Handle the USB bus devices hot plugging events.
407  */
408 static void
409 sysevent_devfs_add(gchar *devfs_path)
410 {
411 	di_node_t node;
412 	HalDevice *parent;
413 	char *driver_name;
414 
415 	HAL_INFO (("devfs_handle: %s", devfs_path));
416 
417 	if ((node = di_init (devfs_path, DINFOCPYALL)) == DI_NODE_NIL) {
418 		HAL_INFO (("device not found in devinfo %s", devfs_path));
419 		return;
420 	}
421 
422 	if ((driver_name = di_driver_name (node)) == NULL)
423 		goto out;
424 
425 	/* The disk and printer devices are handled by EC_DEV_ADD class. */
426 	if ((strcmp (driver_name, "scsa2usb") == 0) ||
427 	    (strcmp (driver_name, "usbprn") == 0))
428 		goto out;
429 
430 	if ((parent = lookup_parent (devfs_path)) == NULL)
431 		goto out;
432 
433 	devinfo_usb_add (parent, node, devfs_path, NULL);
434 
435 	di_fini (node);
436 
437 	hotplug_event_process_queue ();
438 
439 	return;
440 
441  out:
442 	di_fini (node);
443 }
444 
445 static void
446 sysevent_pwrctl(gchar *class, gchar *subclass, gchar *phys_path,
447     gchar *dev_name, gchar *dev_hid, gchar *dev_uid, uint_t dev_index)
448 {
449 	const gchar prefix[] = "/org/freedesktop/Hal/devices/pseudo/acpi_drv_0";
450 	gchar udi[HAL_PATH_MAX];
451 
452 	if (strcmp(dev_hid, "PNP0C0A") == 0) {
453 		snprintf(udi, sizeof(udi), "%s_battery%d_0", prefix, dev_index);
454 		devinfo_battery_device_rescan(phys_path, udi);
455 	} else if (strcmp(dev_hid, "ACPI0003") == 0) {
456 		snprintf(udi, sizeof (udi), "%s_ac%d_0", prefix, dev_index);
457 		devinfo_battery_device_rescan(phys_path, udi);
458 	} else if (strcmp(dev_hid, "PNP0C0D") == 0) {
459 		snprintf(udi, sizeof (udi), "%s_lid_0", prefix);
460 		devinfo_lid_device_rescan(subclass, udi);
461 	} else if (strcmp(subclass, ESC_PWRCTL_POWER_BUTTON) == 0) {
462 		devinfo_power_button_rescan();
463 	} else if ((strcmp(subclass, ESC_PWRCTL_BRIGHTNESS_UP) == 0) ||
464 	    (strcmp(subclass, ESC_PWRCTL_BRIGHTNESS_DOWN) == 0)) {
465 		devinfo_brightness_hotkeys_rescan(subclass);
466 	} else {
467 		HAL_INFO(("Unmatched EC_PWRCTL"));
468 	}
469 }
470