1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26
27#include <sys/types.h>
28#include <sys/time.h>
29#include <sys/errno.h>
30#include <sys/cmn_err.h>
31#include <sys/param.h>
32#include <sys/modctl.h>
33#include <sys/conf.h>
34#include <sys/open.h>
35#include <sys/stat.h>
36#include <sys/clock.h>
37#include <sys/tod.h>
38#include <sys/todio.h>
39#include <sys/ddi.h>
40#include <sys/sunddi.h>
41#include <sys/file.h>
42
43
44#define	getsoftc(minor)	\
45		((struct tod_softc *)ddi_get_soft_state(statep, (minor)))
46
47/* dev_ops and cb_ops entry point function declarations */
48
49static int	tod_attach(dev_info_t *, ddi_attach_cmd_t);
50static int	tod_detach(dev_info_t *, ddi_detach_cmd_t);
51static int	tod_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
52
53static int	tod_open(dev_t *, int, int, cred_t *);
54static int	tod_close(dev_t, int, int, cred_t *);
55static int	tod_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
56
57struct cb_ops tod_cb_ops = {
58	tod_open,
59	tod_close,
60	nodev,
61	nodev,
62	nodev,			/* dump */
63	nodev,
64	nodev,
65	tod_ioctl,
66	nodev,			/* devmap */
67	nodev,
68	ddi_segmap,		/* segmap */
69	nochpoll,
70	ddi_prop_op,
71	NULL,			/* for STREAMS drivers */
72	D_NEW | D_MP		/* driver compatibility flag */
73};
74
75static struct dev_ops tod_dev_ops = {
76	DEVO_REV,		/* driver build version */
77	0,			/* device reference count */
78	tod_getinfo,
79	nulldev,
80	nulldev,		/* probe */
81	tod_attach,
82	tod_detach,
83	nulldev,		/* reset */
84	&tod_cb_ops,
85	(struct bus_ops *)NULL,
86	nulldev,		/* power */
87	ddi_quiesce_not_supported,	/* devo_quiesce */
88};
89
90/* module configuration stuff */
91static void    *statep;
92extern struct mod_ops mod_driverops;
93
94static struct modldrv modldrv = {
95	&mod_driverops,
96	"tod driver",
97	&tod_dev_ops
98};
99
100static struct modlinkage modlinkage = {
101	MODREV_1,
102	&modldrv,
103	0
104};
105
106
107int
108_init(void)
109{
110	int    e;
111
112	if (e = ddi_soft_state_init(&statep, sizeof (struct tod_softc), 1)) {
113		return (e);
114	}
115
116	if ((e = mod_install(&modlinkage)) != 0) {
117		ddi_soft_state_fini(&statep);
118	}
119
120	return (e);
121}
122
123
124int
125_fini(void)
126{
127	int e;
128
129	if ((e = mod_remove(&modlinkage)) != 0) {
130		return (e);
131	}
132
133	ddi_soft_state_fini(&statep);
134
135	return (DDI_SUCCESS);
136}
137
138
139int
140_info(struct modinfo *modinfop)
141{
142	return (mod_info(&modlinkage, modinfop));
143}
144
145
146/* ARGSUSED */
147static int
148tod_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
149{
150	int	inst = getminor((dev_t)arg);
151	int	retval = DDI_SUCCESS;
152	struct tod_softc *softc;
153
154	switch (cmd) {
155
156	case DDI_INFO_DEVT2DEVINFO:
157		if ((softc = getsoftc(inst)) == NULL) {
158			*result = (void *)NULL;
159			retval = DDI_FAILURE;
160		} else {
161			*result = (void *)softc->dip;
162		}
163		break;
164
165	case DDI_INFO_DEVT2INSTANCE:
166		*result = (void *)(uintptr_t)inst;
167		break;
168
169	default:
170		retval = DDI_FAILURE;
171	}
172
173	return (retval);
174}
175
176static int
177tod_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
178{
179
180	int inst;
181	struct tod_softc *softc = NULL;
182	char name[80];
183
184	switch (cmd) {
185
186	case DDI_ATTACH:
187		inst = ddi_get_instance(dip);
188		/*
189		 * Create minor node.  The minor device number, inst, has no
190		 * meaning.  The model number above, which will be added to
191		 * the device's softc, is used to direct peculiar behavior.
192		 */
193		(void) sprintf(name, "tod%d", inst);
194		if (ddi_create_minor_node(dip, name, S_IFCHR, inst,
195		    DDI_PSEUDO, 0) == DDI_FAILURE)
196			goto attach_failed;
197
198		/*
199		 * Allocate a soft state structure for this instance.
200		 */
201		if (ddi_soft_state_zalloc(statep, inst) != DDI_SUCCESS)
202			goto attach_failed;
203
204		softc = getsoftc(inst);
205		softc->dip = dip;
206		softc->cpr_stage = ~TOD_SUSPENDED;
207		mutex_init(&softc->mutex, NULL, MUTEX_DRIVER, NULL);
208		ddi_report_dev(dip);
209		return (DDI_SUCCESS);
210
211	case DDI_RESUME:
212		inst = ddi_get_instance(dip);
213		softc = getsoftc(inst);
214		mutex_enter(&softc->mutex);
215		softc->cpr_stage = ~TOD_SUSPENDED;
216		mutex_exit(&softc->mutex);
217		return (DDI_SUCCESS);
218
219	default:
220		return (DDI_FAILURE);
221	}
222
223attach_failed:
224	/* Free soft state, if allocated. remove minor node if added earlier */
225	if (softc)
226		ddi_soft_state_free(statep, inst);
227
228	ddi_remove_minor_node(dip, NULL);
229
230	return (DDI_FAILURE);
231}
232
233static int
234tod_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
235{
236	int inst;
237	struct tod_softc *softc;
238
239	switch (cmd) {
240
241	case DDI_DETACH:
242		inst = ddi_get_instance(dip);
243		if ((softc = getsoftc(inst)) == NULL)
244			return (ENXIO);
245		/*
246		 * Free the soft state and remove minor node added earlier.
247		 */
248		mutex_destroy(&softc->mutex);
249		ddi_soft_state_free(statep, inst);
250		ddi_remove_minor_node(dip, NULL);
251		return (DDI_SUCCESS);
252
253	case DDI_SUSPEND:
254		inst = ddi_get_instance(dip);
255		softc = getsoftc(inst);
256		mutex_enter(&softc->mutex);
257		softc->cpr_stage = TOD_SUSPENDED;
258		mutex_exit(&softc->mutex);
259		return (DDI_SUCCESS);
260
261	default:
262		return (DDI_FAILURE);
263
264	}
265}
266
267/* ARGSUSED */
268static int
269tod_open(dev_t *devp, int flag, int otyp, cred_t *credp)
270{
271	int	inst = getminor(*devp);
272
273	return (getsoftc(inst) == NULL ? ENXIO : 0);
274}
275
276
277/* ARGSUSED */
278static int
279tod_close(dev_t dev, int flag, int otyp, cred_t *credp)
280{
281	int	inst = getminor(dev);
282
283	return (getsoftc(inst) == NULL ? ENXIO : 0);
284}
285
286
287/* ARGSUSED */
288static int
289tod_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp)
290{
291	int		inst = getminor(dev);
292	struct tod_softc *softc;
293	timestruc_t	ts;
294
295	if ((softc = getsoftc(inst)) == NULL)
296		return (ENXIO);
297
298	mutex_enter(&softc->mutex);
299	while (softc->cpr_stage == TOD_SUSPENDED) {
300		mutex_exit(&softc->mutex);
301		(void) ddi_dev_is_needed(softc->dip, 0, 1);
302		mutex_enter(&softc->mutex);
303	}
304
305	switch (cmd) {
306
307	case TOD_CLEAR_ALARM:
308		mutex_enter(&tod_lock);
309		tod_ops.tod_clear_power_alarm();
310		mutex_exit(&tod_lock);
311		break;
312
313	case TOD_SET_ALARM:
314		if ((mode & FMODELS) == FNATIVE) {
315			if (ddi_copyin((caddr_t)arg, (caddr_t)&ts.tv_sec,
316			    sizeof (ts.tv_sec), mode) != 0) {
317				mutex_exit(&softc->mutex);
318				return (EFAULT);
319			}
320		} else {
321			time32_t time32;
322
323			if (ddi_copyin((caddr_t)arg,
324			    &time32, sizeof (time32), mode) != 0) {
325				mutex_exit(&softc->mutex);
326				return (EFAULT);
327			}
328			ts.tv_sec = (time_t)time32;
329		}
330		ts.tv_nsec = 0;
331
332		mutex_enter(&tod_lock);
333		tod_ops.tod_set_power_alarm(ts);
334		mutex_exit(&tod_lock);
335		break;
336
337	case TOD_GET_DATE:
338		mutex_enter(&tod_lock);
339		ts = tod_ops.tod_get();
340		mutex_exit(&tod_lock);
341
342		if ((mode & FMODELS) == FNATIVE) {
343			if (ddi_copyout((caddr_t)&ts.tv_sec, (caddr_t)arg,
344			    sizeof (ts.tv_sec), mode) != 0) {
345				mutex_exit(&softc->mutex);
346				return (EFAULT);
347			}
348		} else {
349			time32_t time32;
350
351			if (TIMEVAL_OVERFLOW(&ts)) {
352				mutex_exit(&softc->mutex);
353				return (EOVERFLOW);
354			}
355
356			time32 = (time32_t)ts.tv_sec;
357			if (ddi_copyout(&time32,
358			    (caddr_t)arg, sizeof (time32), mode) != 0) {
359				mutex_exit(&softc->mutex);
360				return (EFAULT);
361			}
362		}
363		break;
364
365	default:
366		mutex_exit(&softc->mutex);
367		return (EINVAL);
368	}
369
370	mutex_exit(&softc->mutex);
371	return (0);
372}
373