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#ifdef HAVE_CONFIG_H
13#include <config.h>
14#endif
15
16#include <stdio.h>
17#include <unistd.h>
18#include <stdlib.h>
19#include <string.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <sys/dkio.h>
23#include <sys/stat.h>
24#include <libdevinfo.h>
25#include <libsysevent.h>
26#include <sys/sysevent/dev.h>
27#include <sys/sysevent/pwrctl.h>
28#include <sys/sysevent/dr.h>
29#include <glib.h>
30#include <config_admin.h>
31#include <kstat.h>
32
33#include "../osspec.h"
34#include "../logger.h"
35#include "../hald.h"
36#include "../hald_dbus.h"
37#include "../device_info.h"
38#include "../util.h"
39#include "osspec_solaris.h"
40#include "hotplug.h"
41#include "devinfo.h"
42#include "devinfo_storage.h"
43#include "devinfo_acpi.h"
44#include "devinfo_usb.h"
45#include "sysevent.h"
46#include "devinfo_misc.h"
47#include "devinfo_cpu.h"
48
49#ifndef ESC_LOFI
50#define	ESC_LOFI "lofi"
51#endif
52
53static void	sysevent_dev_handler(sysevent_t *);
54static gboolean sysevent_iochannel_data(GIOChannel *, GIOCondition, gpointer);
55static void	sysevent_dev_add(gchar *, gchar *);
56static void	sysevent_dev_remove(gchar *, gchar *);
57static void	sysevent_dev_branch(gchar *);
58static void	sysevent_lofi_add(gchar *, gchar *);
59static void	sysevent_lofi_remove(gchar *, gchar *);
60static void	sysevent_devfs_add(gchar *);
61static void	sysevent_pwrctl(gchar *, gchar *, gchar *, gchar *, gchar *,
62		    gchar *, uint_t);
63static void	sysevent_process_dr(gchar *, gchar *);
64
65static sysevent_handle_t	*shp;
66
67static int sysevent_pipe_fds[2];
68static GIOChannel *sysevent_iochannel;
69static guint sysevent_source_id;
70
71gboolean
72sysevent_init(void)
73{
74	GError *err = NULL;
75	const char	*subcl[6];
76
77	/*
78	 * pipe used to serialize sysevents through the main loop
79	 */
80	if (pipe (sysevent_pipe_fds) != 0) {
81		HAL_INFO (("pipe() failed errno=%d", errno));
82		return (FALSE);
83	}
84	sysevent_iochannel = g_io_channel_unix_new (sysevent_pipe_fds[0]);
85	if (sysevent_iochannel == NULL) {
86		HAL_INFO (("g_io_channel_unix_new failed"));
87		return (FALSE);
88	}
89	g_io_channel_set_flags (sysevent_iochannel, G_IO_FLAG_NONBLOCK, &err);
90	sysevent_source_id = g_io_add_watch (
91	    sysevent_iochannel, G_IO_IN, sysevent_iochannel_data, NULL);
92
93	shp = sysevent_bind_handle(sysevent_dev_handler);
94	if (shp == NULL) {
95		HAL_INFO (("sysevent_bind_handle failed %d", errno));
96		return (FALSE);
97	}
98
99	subcl[0] = ESC_DISK;
100	subcl[1] = ESC_LOFI;
101	subcl[2] = ESC_PRINTER;
102	if (sysevent_subscribe_event(shp, EC_DEV_ADD, subcl, 3) != 0) {
103		HAL_INFO (("subscribe(dev_add) failed %d", errno));
104		sysevent_unbind_handle(shp);
105		return (FALSE);
106	}
107	if (sysevent_subscribe_event(shp, EC_DEV_REMOVE, subcl, 3) != 0) {
108		HAL_INFO (("subscribe(dev_remove) failed %d", errno));
109		sysevent_unbind_handle(shp);
110		return (FALSE);
111	}
112
113	subcl[0] = ESC_DEV_BRANCH_REMOVE;
114	if (sysevent_subscribe_event(shp, EC_DEV_BRANCH, subcl, 1) != 0) {
115		HAL_INFO (("subscribe(dev_branch) failed %d", errno));
116		sysevent_unbind_handle(shp);
117		return (FALSE);
118	}
119
120	subcl[0] = ESC_PWRCTL_ADD;
121	subcl[1] = ESC_PWRCTL_REMOVE;
122	subcl[2] = ESC_PWRCTL_STATE_CHANGE;
123	subcl[3] = ESC_PWRCTL_BRIGHTNESS_UP;
124	subcl[4] = ESC_PWRCTL_BRIGHTNESS_DOWN;
125	subcl[5] = ESC_PWRCTL_POWER_BUTTON;
126	if (sysevent_subscribe_event(shp, EC_PWRCTL, subcl, 6) != 0) {
127		HAL_INFO(("subscribe(dev_add) failed %d", errno));
128		sysevent_unbind_handle(shp);
129		return (FALSE);
130	}
131
132	subcl[0] = ESC_DEVFS_DEVI_ADD;
133	if (sysevent_subscribe_event(shp, EC_DEVFS, subcl, 1) != 0) {
134		HAL_INFO (("subscribe(EC_DEVFS) failed %d", errno));
135		sysevent_unbind_handle(shp);
136		return (FALSE);
137	}
138
139	subcl[0] = ESC_DR_AP_STATE_CHANGE;
140	if (sysevent_subscribe_event(shp, EC_DR, subcl, 1) != 0) {
141		HAL_INFO (("subscribe(dynamic reconfiguration) failed %d",
142		    errno));
143		sysevent_unbind_handle(shp);
144		return (FALSE);
145	}
146
147	return (B_TRUE);
148}
149
150void
151sysevent_fini(void)
152{
153	sysevent_unbind_handle(shp);
154	shp = NULL;
155}
156
157static void
158sysevent_dev_handler(sysevent_t *ev)
159{
160	char		*class;
161	char		*subclass;
162	nvlist_t	*attr_list;
163	char		*phys_path;
164	char		*dev_name;
165	char		*dev_hid;
166	char		*dev_uid;
167	uint_t		dev_index;
168	char		s[1024];
169	ssize_t		nwritten;
170
171	if ((class = sysevent_get_class_name(ev)) == NULL)
172		return;
173
174	if ((subclass = sysevent_get_subclass_name(ev)) == NULL)
175		return;
176
177	if (sysevent_get_attr_list(ev, &attr_list) != 0)
178		return;
179
180	if (strcmp(class, EC_DEVFS) == 0) {
181		if (nvlist_lookup_string(attr_list, DEVFS_PATHNAME, &phys_path) != 0) {
182			goto out;
183		}
184
185		snprintf(s, sizeof (s), "%s %s %s\n",
186		    class, subclass, phys_path);
187		nwritten = write(sysevent_pipe_fds[1], s, strlen(s) + 1);
188
189		HAL_INFO (("sysevent_dev_handler: wrote %d bytes", nwritten));
190		goto out;
191	}
192
193	if (strcmp(class, EC_PWRCTL) == 0) {
194		if (nvlist_lookup_string(attr_list, PWRCTL_DEV_PHYS_PATH,
195		    &phys_path) != 0) {
196			goto out;
197		}
198	} else if (strcmp(class, EC_DR) == 0) {
199		if (nvlist_lookup_string(attr_list, DR_AP_ID,
200		    &phys_path) != 0) {
201			goto out;
202		}
203	} else if (nvlist_lookup_string(attr_list, DEV_PHYS_PATH, &phys_path)
204	    != 0) {
205		goto out;
206	}
207
208	/*
209	 * In case of EC_DR, use dev_name to store DR_HINT val
210	 */
211	if (strcmp(class, EC_DR) == 0) {
212		if (nvlist_lookup_string(attr_list, DR_HINT, &dev_name) != 0) {
213			goto out;
214		}
215	} else if (nvlist_lookup_string(attr_list, DEV_NAME, &dev_name) != 0) {
216		if (strcmp(class, EC_PWRCTL) == 0) {
217			dev_name = "noname";
218		} else {
219			dev_name = "";
220		}
221	}
222
223	if (nvlist_lookup_string(attr_list, PWRCTL_DEV_HID, &dev_hid) != 0) {
224		dev_hid = "";
225	}
226	if (nvlist_lookup_string(attr_list, PWRCTL_DEV_UID, &dev_uid) != 0) {
227		dev_uid = "";
228	}
229	if (nvlist_lookup_uint32(attr_list, PWRCTL_DEV_INDEX, &dev_index)
230	    != 0) {
231		dev_index = 0;
232	}
233
234	snprintf(s, sizeof (s), "%s %s %s %s %s %s %d\n",
235	    class, subclass, phys_path, dev_name, dev_hid, dev_uid, dev_index);
236	nwritten = write(sysevent_pipe_fds[1], s, strlen(s) + 1);
237
238	HAL_INFO (("sysevent_dev_handler: wrote %d bytes", nwritten));
239
240out:
241	nvlist_free(attr_list);
242}
243
244static gboolean
245sysevent_iochannel_data (GIOChannel *source,
246		    GIOCondition condition,
247		    gpointer user_data)
248{
249	GError *err = NULL;
250	gchar *s = NULL;
251	gsize len;
252	int matches;
253	gchar class[1024];
254	gchar subclass[1024];
255	gchar phys_path[1024];
256	gchar dev_name[1024];
257	gchar dev_uid[1024];
258	gchar dev_hid[1024];
259	uint_t dev_index;
260
261	HAL_INFO (("sysevent_iochannel_data"));
262
263	while (g_io_channel_read_line (sysevent_iochannel, &s, &len, NULL,
264	    &err) == G_IO_STATUS_NORMAL) {
265		if (len == 0) {
266			break;
267		}
268		HAL_INFO (("IOChannel val => %s", s));
269		class[0] = subclass[0] = phys_path[0] = dev_name[0] =
270		    dev_hid[0] = dev_uid[0] = '\0';
271		matches = sscanf(s, "%s %s %s %s %s %s %d", class, subclass,
272		    phys_path, dev_name, dev_hid, dev_uid, &dev_index);
273		g_free (s);
274		s = NULL;
275		if (matches < 3) {
276			continue;
277		}
278		HAL_INFO (("sysevent: class=%s, sub=%s", class, subclass));
279
280		if (strcmp(class, EC_DEV_ADD) == 0) {
281			if ((strcmp(subclass, ESC_DISK) == 0) ||
282			    (strcmp(subclass, ESC_PRINTER) == 0)) {
283				sysevent_dev_add(phys_path, dev_name);
284			} else if (strcmp(subclass, ESC_LOFI) == 0) {
285				sysevent_lofi_add(phys_path, dev_name);
286			}
287		} else if (strcmp(class, EC_DEV_REMOVE) == 0) {
288			if ((strcmp(subclass, ESC_DISK) == 0) ||
289			    (strcmp(subclass, ESC_PRINTER) == 0)) {
290				sysevent_dev_remove(phys_path, dev_name);
291			} else if (strcmp(subclass, ESC_LOFI) == 0) {
292				sysevent_lofi_remove(phys_path, dev_name);
293			}
294		} else if (strcmp(class, EC_DEV_BRANCH) == 0) {
295			sysevent_dev_branch(phys_path);
296		} else if (strcmp(class, EC_PWRCTL) == 0) {
297			sysevent_pwrctl(class, subclass, phys_path,
298			    dev_name, dev_hid, dev_uid, dev_index);
299		} else if (strcmp(class, EC_DEVFS) == 0) {
300			if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
301				sysevent_devfs_add(phys_path);
302			}
303		} else if (strcmp(class, EC_DR) == 0) {
304			/*
305			 * Note: AP_ID is stored in phys_path and HINT is
306			 * stored in dev_name, to avoid creating seperate
307			 * variables and multiple conditions checking
308			 */
309			HAL_DEBUG (("In %s, AP_ID-> %s, Hint-> %s", class,
310			    phys_path, dev_name));
311			if (strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
312				sysevent_process_dr(phys_path, dev_name);
313			}
314		}
315	}
316
317	if (err) {
318		g_error_free (err);
319	}
320
321	return (TRUE);
322}
323
324static void
325sysevent_dev_add(gchar *devfs_path, gchar *name)
326{
327	gchar	*parent_devfs_path, *hotplug_devfs_path;
328	HalDevice *parent;
329
330	HAL_INFO (("dev_add: %s %s", name, devfs_path));
331
332	parent = hal_util_find_closest_ancestor (devfs_path, &parent_devfs_path, &hotplug_devfs_path);
333	if (parent == NULL) {
334		return;
335	}
336
337	HAL_INFO (("dev_add: parent=%s", parent_devfs_path));
338	HAL_INFO (("dev_add: real=%s", hotplug_devfs_path));
339
340	devinfo_add (parent, hotplug_devfs_path);
341
342	g_free (parent_devfs_path);
343	g_free (hotplug_devfs_path);
344
345	hotplug_event_process_queue ();
346}
347
348static void
349sysevent_dev_remove(gchar *devfs_path, gchar *name)
350{
351	HAL_INFO (("dev_remove: %s %s", name, devfs_path));
352
353	devinfo_remove_branch (devfs_path, NULL);
354	hotplug_event_process_queue ();
355}
356
357static void
358sysevent_dev_branch(gchar *devfs_path)
359{
360	HAL_INFO (("branch_remove: %s", devfs_path));
361
362	devinfo_remove_branch (devfs_path, NULL);
363	hotplug_event_process_queue ();
364}
365
366static void
367sysevent_lofi_add(gchar *devfs_path, gchar *name)
368{
369	di_node_t node;
370	const char *parent_udi;
371	HalDevice *d, *parent;
372
373	HAL_INFO (("lofi_add: %s %s", name, devfs_path));
374
375	if ((d = hal_device_store_match_key_value_string (hald_get_gdl (),
376	    "solaris.devfs_path", devfs_path)) == NULL) {
377		HAL_INFO (("device not found in GDL %s", devfs_path));
378		return;
379	}
380	parent_udi = hal_device_property_get_string (d, "info.parent");
381	if ((parent_udi == NULL) || (strlen(parent_udi) == 0)) {
382		HAL_INFO (("parent not found in GDL %s", parent_udi));
383		return;
384	}
385	if ((parent = hal_device_store_match_key_value_string (hald_get_gdl (),
386	    "info.udi", parent_udi)) == NULL) {
387		HAL_INFO (("parent not found in GDL %s", parent_udi));
388		return;
389	}
390
391	if ((node = di_init (devfs_path, DINFOCPYALL)) == DI_NODE_NIL) {
392		HAL_INFO (("device not found in devinfo %s", devfs_path));
393		return;
394	}
395
396	HAL_INFO (("device %s parent %s", hal_device_get_udi (d), parent_udi));
397	devinfo_lofi_add_major (parent, node, devfs_path, NULL, TRUE, d);
398
399	di_fini (node);
400
401	hotplug_event_process_queue ();
402}
403
404static void
405sysevent_lofi_remove(gchar *parent_devfs_path, gchar *name)
406{
407	devinfo_lofi_remove_minor(parent_devfs_path, name);
408	hotplug_event_process_queue ();
409}
410
411static HalDevice *
412lookup_parent(char *devfs_path)
413{
414	gchar		*path = NULL;
415	HalDevice	*parent = NULL;
416	char *p;
417
418	path = strdup (devfs_path);
419	p = strrchr (path, '/');
420	if (p == NULL) {
421		free (path);
422		return (NULL);
423	}
424	*p = '\0';
425
426	/* Look up the parent node in the gdl. */
427	parent = hal_device_store_match_key_value_string (hald_get_gdl (),
428	    "solaris.devfs_path", path);
429
430	if (parent == NULL) {
431		/* Look up the parent node in the tdl. */
432		parent = hal_device_store_match_key_value_string (hald_get_tdl (),
433		    "solaris.devfs_path", path);
434	}
435
436	free (path);
437	return (parent);
438}
439
440/*
441 * Handle the USB bus devices hot plugging events.
442 */
443static void
444sysevent_devfs_add(gchar *devfs_path)
445{
446	di_node_t node;
447	HalDevice *parent;
448	char *driver_name;
449
450	HAL_INFO (("devfs_handle: %s", devfs_path));
451
452	if ((node = di_init (devfs_path, DINFOCPYALL)) == DI_NODE_NIL) {
453		HAL_INFO (("device not found in devinfo %s", devfs_path));
454		return;
455	}
456
457	if ((driver_name = di_driver_name (node)) == NULL)
458		goto out;
459
460	/* The disk and printer devices are handled by EC_DEV_ADD class. */
461	if ((strcmp (driver_name, "scsa2usb") == 0) ||
462	    (strcmp (driver_name, "usbprn") == 0))
463		goto out;
464
465	if ((parent = lookup_parent (devfs_path)) == NULL)
466		goto out;
467
468	devinfo_usb_add (parent, node, devfs_path, NULL);
469
470	di_fini (node);
471
472	hotplug_event_process_queue ();
473
474	return;
475
476 out:
477	di_fini (node);
478}
479
480static void
481sysevent_pwrctl(gchar *class, gchar *subclass, gchar *phys_path,
482    gchar *dev_name, gchar *dev_hid, gchar *dev_uid, uint_t dev_index)
483{
484	const gchar prefix[] = "/org/freedesktop/Hal/devices/pseudo/acpi_drv_0";
485	gchar udi[HAL_PATH_MAX];
486
487	if (strcmp(dev_hid, "PNP0C0A") == 0) {
488		snprintf(udi, sizeof(udi), "%s_battery%d_0", prefix, dev_index);
489		devinfo_battery_rescan(phys_path, udi);
490	} else if (strcmp(dev_hid, "ACPI0003") == 0) {
491		snprintf(udi, sizeof (udi), "%s_ac%d_0", prefix, dev_index);
492		devinfo_battery_rescan(phys_path, udi);
493	} else if (strcmp(dev_hid, "PNP0C0D") == 0) {
494		snprintf(udi, sizeof (udi), "%s_lid_0", prefix);
495		devinfo_lid_event(subclass, udi);
496	} else if (strcmp(subclass, ESC_PWRCTL_POWER_BUTTON) == 0) {
497		devinfo_power_button_event();
498	} else if ((strcmp(subclass, ESC_PWRCTL_BRIGHTNESS_UP) == 0) ||
499	    (strcmp(subclass, ESC_PWRCTL_BRIGHTNESS_DOWN) == 0)) {
500		devinfo_brightness_hotkeys_event(subclass);
501	} else {
502		HAL_INFO(("Unmatched EC_PWRCTL"));
503	}
504}
505
506static void
507sysevent_dr_remove_cpu()
508{
509
510	HalDeviceStore	*gdl;
511	GSList		*iter;
512	HalDevice	*d, *del_dev;
513	int		cpu_id, del_cpuid;
514	kstat_ctl_t	*kc;
515	kstat_t		*ksp;
516	kstat_named_t	*ksdata;
517	const char	*cpu_devfs_path;
518	/*
519	 * Find the CPU's that are DR removed. For each "processor" device in
520	 * HAL device tree, check if it has its corresponding kstat_info. If
521	 * not, then, that cpu has been removed and can remove the entry from
522	 * HAL entry
523	 */
524
525	HAL_DEBUG (("sysevent_dr_remove_cpu()"));
526	kc = kstat_open ();
527	if (kc == NULL) {
528		HAL_INFO (("Error in removing HAL cpu entry during DR. Could"
529		    " not open kstat to get cpu info: %s", strerror (errno)));
530		return;
531	}
532
533	/*
534	 * Iterate through the HAL device list to get the processor devices
535	 */
536	gdl = hald_get_gdl ();
537	iter = gdl->devices;
538
539	while (iter != NULL) {
540		d = HAL_DEVICE (iter->data);
541
542		if (!hal_device_has_property (d, "processor.number")) {
543			iter = iter->next;
544			continue;
545		}
546
547		cpu_id = hal_device_property_get_int (d, "processor.number");
548
549		/*
550		 * Check if the above cpu_id has its info in kstat
551		 */
552
553		ksp = kstat_lookup (kc, "cpu_info", cpu_id, NULL);
554		if (ksp != NULL) {
555			iter = iter->next;
556			continue;
557		}
558		/*
559		 *  kstat info not found. Delete the device entry
560		 */
561		HAL_INFO ((" Remove CPU entry: %d", cpu_id));
562		iter = iter->next;
563		cpu_devfs_path = hal_device_property_get_string (d,
564		    "solaris.devfs_path");
565		if (cpu_devfs_path == NULL) {
566			HAL_INFO (("Could not get cpu_devfs_path to "
567			    "remove for cpu_id %d", cpu_id));
568		} else {
569			/*
570			 * Remove the cpu device
571			 */
572			HAL_DEBUG (("Queue %s for removal", cpu_devfs_path));
573			devinfo_remove_enqueue ((char *)cpu_devfs_path, NULL);
574			hotplug_event_process_queue ();
575		}
576	}
577
578	if (kc) {
579		kstat_close (kc);
580	}
581}
582
583int
584sysevent_dr_insert_cpu(di_node_t node, void *arg)
585{
586	char	*devfs_path;
587	char	*device_type = NULL;
588	DevinfoDevHandler *dh;
589
590	dh = &devinfo_cpu_handler;
591	devfs_path = di_devfs_path (node);
592
593	(void) di_prop_lookup_strings (DDI_DEV_T_ANY, node, "device_type",
594	    &device_type);
595
596	dh->add (NULL, node, devfs_path, device_type);
597
598	di_devfs_path_free (devfs_path);
599	return (DI_WALK_CONTINUE);
600}
601
602/*
603 * Remove/Add the DR event device
604 * Note: Currently it supports only CPU DR events
605 */
606static void
607sysevent_process_dr(gchar *ap_id, gchar *hint_val)
608{
609	cfga_err_t 		cfgerr;
610	cfga_list_data_t 	*cfg_stat;
611	int			nlist;
612	char			*errstr;
613	di_node_t		root_node;
614
615	if ((ap_id == NULL) || (hint_val == NULL))
616		return;
617	HAL_DEBUG (("sysevent_process_dr: %s", ap_id));
618
619	cfgerr = config_list_ext (1, (char *const *)&ap_id, &cfg_stat, &nlist,
620	    NULL, NULL, &errstr, 0);
621
622	if (cfgerr != CFGA_OK) {
623		HAL_INFO (("DR sysevent process %d config_list_ext error: %s",
624		    ap_id, errstr));
625		goto out;
626	}
627	/*
628	 * Check if the device type is CPU
629	 */
630	HAL_DEBUG ((" Ap-Type: %s, State: %d", cfg_stat->ap_type,
631	    cfg_stat->ap_r_state));
632	if (strcmp (cfg_stat->ap_type, "CPU") == 0) {
633		if (strcmp (hint_val, DR_HINT_REMOVE) == 0) {
634			sysevent_dr_remove_cpu();
635		} else if (strcmp (hint_val, DR_HINT_INSERT) == 0) {
636			/*
637			 * Go through the device list and add the new cpu
638			 * entries into HAL
639			 */
640			if ((root_node =
641			    di_init ("/", DINFOCPYALL)) == DI_NODE_NIL) {
642				HAL_INFO (("di_init failed. "\
643				    "Cannot insert CPU"));
644				goto out;
645			}
646			di_walk_node (root_node, DI_WALK_CLDFIRST, NULL,
647			    sysevent_dr_insert_cpu);
648			di_fini (root_node);
649			hotplug_event_process_queue ();
650		}
651	} else {
652		HAL_INFO (("Not a CPU, so cannot DR"));
653	}
654
655out:
656	if (cfg_stat)
657		free (cfg_stat);
658	if (errstr)
659		free (errstr);
660}
661