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/*
28 * The tphci driver can be used to exercise the mpxio framework together
29 * with tvhci/tclient.
30 */
31
32#include <sys/conf.h>
33#include <sys/file.h>
34#include <sys/open.h>
35#include <sys/stat.h>
36#include <sys/modctl.h>
37#include <sys/ddi.h>
38#include <sys/sunddi.h>
39#include <sys/sunndi.h>
40#include <sys/sunmdi.h>
41#include <sys/disp.h>
42
43/* cb_ops entry points */
44static int tphci_open(dev_t *, int, int, cred_t *);
45static int tphci_close(dev_t, int, int, cred_t *);
46static int tphci_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
47static int tphci_attach(dev_info_t *, ddi_attach_cmd_t);
48static int tphci_detach(dev_info_t *, ddi_detach_cmd_t);
49static int tphci_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
50
51/* bus_ops entry points */
52static int tphci_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *,
53    void *);
54static int tphci_initchild(dev_info_t *, dev_info_t *);
55static int tphci_uninitchild(dev_info_t *, dev_info_t *);
56static int tphci_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, void *,
57    dev_info_t **);
58static int tphci_bus_unconfig(dev_info_t *, uint_t, ddi_bus_config_op_t,
59    void *);
60static int tphci_intr_op(dev_info_t *dip, dev_info_t *rdip,
61    ddi_intr_op_t op, ddi_intr_handle_impl_t *hdlp, void *result);
62
63
64static void *tphci_state;
65struct tphci_state {
66	dev_info_t *dip;
67};
68
69static struct cb_ops tphci_cb_ops = {
70	tphci_open,			/* open */
71	tphci_close,			/* close */
72	nodev,				/* strategy */
73	nodev,				/* print */
74	nodev,				/* dump */
75	nodev,				/* read */
76	nodev,				/* write */
77	tphci_ioctl,			/* ioctl */
78	nodev,				/* devmap */
79	nodev,				/* mmap */
80	nodev,				/* segmap */
81	nochpoll,			/* chpoll */
82	ddi_prop_op,			/* cb_prop_op */
83	0,				/* streamtab */
84	D_NEW | D_MP,			/* cb_flag */
85	CB_REV,				/* rev */
86	nodev,				/* aread */
87	nodev				/* awrite */
88};
89
90static struct bus_ops tphci_bus_ops = {
91	BUSO_REV,			/* busops_rev */
92	nullbusmap,			/* bus_map */
93	NULL,				/* bus_get_intrspec */
94	NULL,				/* bus_add_interspec */
95	NULL,				/* bus_remove_interspec */
96	i_ddi_map_fault,		/* bus_map_fault */
97	ddi_no_dma_map,			/* bus_dma_map */
98	ddi_no_dma_allochdl,		/* bus_dma_allochdl */
99	NULL,				/* bus_dma_freehdl */
100	NULL,				/* bus_dma_bindhdl */
101	NULL,				/* bus_dma_unbindhdl */
102	NULL,				/* bus_dma_flush */
103	NULL,				/* bus_dma_win */
104	NULL,				/* bus_dma_ctl */
105	tphci_ctl,			/* bus_ctl */
106	ddi_bus_prop_op,		/* bus_prop_op */
107	NULL,				/* bus_get_eventcookie */
108	NULL,				/* bus_add_eventcall */
109	NULL,				/* bus_remove_event */
110	NULL,				/* bus_post_event */
111	NULL,				/* bus_intr_ctl */
112	tphci_bus_config,		/* bus_config */
113	tphci_bus_unconfig,		/* bus_unconfig */
114	NULL,				/* bus_fm_init */
115	NULL,				/* bus_fm_fini */
116	NULL,				/* bus_fm_access_enter */
117	NULL,				/* bus_fm_access_exit */
118	NULL,				/* bus_power */
119	tphci_intr_op			/* bus_intr_op */
120};
121
122static struct dev_ops tphci_ops = {
123	DEVO_REV,
124	0,
125	tphci_getinfo,
126	nulldev,		/* identify */
127	nulldev,		/* probe */
128	tphci_attach,		/* attach and detach are mandatory */
129	tphci_detach,
130	nodev,			/* reset */
131	&tphci_cb_ops,		/* cb_ops */
132	&tphci_bus_ops,		/* bus_ops */
133	NULL,			/* power */
134	ddi_quiesce_not_needed,		/* quiesce */
135};
136
137extern struct mod_ops mod_driverops;
138
139static struct modldrv modldrv = {
140	&mod_driverops,
141	"test phci driver",
142	&tphci_ops
143};
144
145static struct modlinkage modlinkage = {
146	MODREV_1,
147	&modldrv,
148	NULL
149};
150
151int
152_init(void)
153{
154	int rval;
155
156	if ((rval = ddi_soft_state_init(&tphci_state,
157	    sizeof (struct tphci_state), 2)) != 0) {
158		return (rval);
159	}
160
161	if ((rval = mod_install(&modlinkage)) != 0) {
162		ddi_soft_state_fini(&tphci_state);
163	}
164	return (rval);
165}
166
167
168int
169_fini(void)
170{
171	int rval;
172
173	/*
174	 * don't start cleaning up until we know that the module remove
175	 * has worked  -- if this works, then we know that each instance
176	 * has successfully been detached
177	 */
178	if ((rval = mod_remove(&modlinkage)) != 0) {
179		return (rval);
180	}
181
182	ddi_soft_state_fini(&tphci_state);
183
184	return (rval);
185}
186
187int
188_info(struct modinfo *modinfop)
189{
190	return (mod_info(&modlinkage, modinfop));
191}
192
193/* ARGSUSED */
194static int
195tphci_open(dev_t *devp, int flag, int otype, cred_t *credp)
196{
197	struct tphci_state *phci;
198
199	if (otype != OTYP_CHR) {
200		return (EINVAL);
201	}
202
203	phci = ddi_get_soft_state(tphci_state, getminor(*devp));
204	if (phci == NULL) {
205		return (ENXIO);
206	}
207
208	return (0);
209}
210
211
212/* ARGSUSED */
213static int
214tphci_close(dev_t dev, int flag, int otype, cred_t *credp)
215{
216	struct tphci_state *phci;
217	if (otype != OTYP_CHR) {
218		return (EINVAL);
219	}
220
221	phci = ddi_get_soft_state(tphci_state, getminor(dev));
222	if (phci == NULL) {
223		return (ENXIO);
224	}
225
226	return (0);
227}
228
229/* ARGSUSED */
230static int
231tphci_ioctl(dev_t dev, int cmd, intptr_t data, int mode,
232	cred_t *credp, int *rval)
233{
234	return (0);
235}
236
237/*
238 * attach the module
239 */
240static int
241tphci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
242{
243	char *vclass;
244	int instance, phci_regis = 0;
245	struct tphci_state *phci = NULL;
246
247	instance = ddi_get_instance(dip);
248
249	switch (cmd) {
250	case DDI_ATTACH:
251		break;
252
253	case DDI_RESUME:
254	case DDI_PM_RESUME:
255		return (0);	/* nothing to do */
256
257	default:
258		return (DDI_FAILURE);
259	}
260
261	/*
262	 * Allocate phci data structure.
263	 */
264	if (ddi_soft_state_zalloc(tphci_state, instance) != DDI_SUCCESS) {
265		return (DDI_FAILURE);
266	}
267
268	phci = ddi_get_soft_state(tphci_state, instance);
269	ASSERT(phci != NULL);
270	phci->dip = dip;
271
272	/* bus_addr has the form #,<vhci_class> */
273	vclass = strchr(ddi_get_name_addr(dip), ',');
274	if (vclass == NULL || vclass[1] == '\0') {
275		cmn_err(CE_NOTE, "tphci invalid bus_addr %s",
276		    ddi_get_name_addr(dip));
277		goto attach_fail;
278	}
279
280	/*
281	 * Attach this instance with the mpxio framework
282	 */
283	if (mdi_phci_register(vclass + 1, dip, 0) != MDI_SUCCESS) {
284		cmn_err(CE_WARN, "%s mdi_phci_register failed",
285		    ddi_node_name(dip));
286		goto attach_fail;
287	}
288	phci_regis++;
289
290	if (ddi_create_minor_node(dip, "devctl", S_IFCHR,
291	    instance, DDI_NT_SCSI_NEXUS, 0) != DDI_SUCCESS) {
292		cmn_err(CE_NOTE, "%s ddi_create_minor_node failed",
293		    ddi_node_name(dip));
294		goto attach_fail;
295	}
296
297	(void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, DDI_NO_AUTODETACH, 1);
298	ddi_report_dev(dip);
299	return (DDI_SUCCESS);
300
301attach_fail:
302	if (phci_regis)
303		(void) mdi_phci_unregister(dip, 0);
304
305	ddi_soft_state_free(tphci_state, instance);
306	return (DDI_FAILURE);
307}
308
309
310/*ARGSUSED*/
311static int
312tphci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
313{
314	int instance = ddi_get_instance(dip);
315
316	switch (cmd) {
317	case DDI_DETACH:
318		break;
319
320	case DDI_SUSPEND:
321	case DDI_PM_SUSPEND:
322		return (0);	/* nothing to do */
323
324	default:
325		return (DDI_FAILURE);
326	}
327
328	if (mdi_phci_unregister(dip, 0) != MDI_SUCCESS)
329		return (DDI_FAILURE);
330
331	ddi_remove_minor_node(dip, NULL);
332	ddi_soft_state_free(tphci_state, instance);
333
334	return (DDI_SUCCESS);
335}
336
337/*
338 * tphci_getinfo()
339 * Given the device number, return the devinfo pointer or the
340 * instance number.
341 * Note: always succeed DDI_INFO_DEVT2INSTANCE, even before attach.
342 */
343
344/*ARGSUSED*/
345static int
346tphci_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
347{
348	struct tphci_state *phci;
349	int instance = getminor((dev_t)arg);
350
351	switch (cmd) {
352	case DDI_INFO_DEVT2DEVINFO:
353		phci = ddi_get_soft_state(tphci_state, instance);
354		if (phci != NULL)
355			*result = phci->dip;
356		else {
357			*result = NULL;
358			return (DDI_FAILURE);
359		}
360		break;
361
362	case DDI_INFO_DEVT2INSTANCE:
363		*result = (void *)(uintptr_t)instance;
364		break;
365
366	default:
367		return (DDI_FAILURE);
368	}
369
370	return (DDI_SUCCESS);
371}
372
373/*
374 * Interrupt stuff. NO OP for pseudo drivers.
375 */
376/*ARGSUSED*/
377static int
378tphci_intr_op(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op,
379    ddi_intr_handle_impl_t *hdlp, void *result)
380{
381	return (DDI_FAILURE);
382}
383
384static int
385tphci_ctl(dev_info_t *dip, dev_info_t *rdip,
386	ddi_ctl_enum_t ctlop, void *arg, void *result)
387{
388	switch (ctlop) {
389	case DDI_CTLOPS_REPORTDEV:
390		if (rdip == (dev_info_t *)0)
391			return (DDI_FAILURE);
392		cmn_err(CE_CONT, "?tphci-device: %s%d\n",
393		    ddi_get_name(rdip), ddi_get_instance(rdip));
394		return (DDI_SUCCESS);
395
396	case DDI_CTLOPS_INITCHILD:
397	{
398		dev_info_t *child = (dev_info_t *)arg;
399		return (tphci_initchild(dip, child));
400	}
401
402	case DDI_CTLOPS_UNINITCHILD:
403	{
404		dev_info_t *child = (dev_info_t *)arg;
405		return (tphci_uninitchild(dip, child));
406	}
407
408	case DDI_CTLOPS_DMAPMAPC:
409	case DDI_CTLOPS_REPORTINT:
410	case DDI_CTLOPS_REGSIZE:
411	case DDI_CTLOPS_NREGS:
412	case DDI_CTLOPS_SIDDEV:
413	case DDI_CTLOPS_SLAVEONLY:
414	case DDI_CTLOPS_AFFINITY:
415	case DDI_CTLOPS_POKE:
416	case DDI_CTLOPS_PEEK:
417		/*
418		 * These ops correspond to functions that "shouldn't" be called
419		 * by a pseudo driver.  So we whine when we're called.
420		 */
421		cmn_err(CE_CONT, "%s%d: invalid op (%d) from %s%d\n",
422		    ddi_get_name(dip), ddi_get_instance(dip),
423		    ctlop, ddi_get_name(rdip), ddi_get_instance(rdip));
424		return (DDI_FAILURE);
425
426	case DDI_CTLOPS_ATTACH:
427	case DDI_CTLOPS_BTOP:
428	case DDI_CTLOPS_BTOPR:
429	case DDI_CTLOPS_DETACH:
430	case DDI_CTLOPS_DVMAPAGESIZE:
431	case DDI_CTLOPS_IOMIN:
432	case DDI_CTLOPS_POWER:
433	case DDI_CTLOPS_PTOB:
434	default:
435		/*
436		 * The ops that we pass up (default).  We pass up memory
437		 * allocation oriented ops that we receive - these may be
438		 * associated with pseudo HBA drivers below us with target
439		 * drivers below them that use ddi memory allocation
440		 * interfaces like scsi_alloc_consistent_buf.
441		 */
442		return (ddi_ctlops(dip, rdip, ctlop, arg, result));
443	}
444}
445
446static int
447tphci_initchild(dev_info_t *dip, dev_info_t *child)
448{
449	_NOTE(ARGUNUSED(dip))
450	ddi_set_name_addr(child, "0");
451	return (DDI_SUCCESS);
452}
453
454/*ARGSUSED*/
455static int
456tphci_uninitchild(dev_info_t *dip, dev_info_t *child)
457{
458	ddi_set_name_addr(child, NULL);
459	return (DDI_SUCCESS);
460}
461
462static int
463tp_decode_name(char *devnm, char **cname, char **paddr, char **guid)
464{
465	char *tmp;
466
467	i_ddi_parse_name(devnm, cname, paddr, NULL);
468	if ((strcmp(*cname, "tclient") != 0) &&
469	    (strcmp(*cname, "tphci") != 0) || *paddr == NULL)
470		return (-1);
471
472	tmp = strchr(*paddr, ',');
473	if (tmp == NULL || tmp[1] == '\0')
474		return (-1);
475
476	*guid = tmp + 1;
477	return (0);
478}
479
480static int
481tphci_bus_config(dev_info_t *parent, uint_t flags,
482    ddi_bus_config_op_t op, void *arg, dev_info_t **childp)
483{
484	_NOTE(ARGUNUSED(flags))
485	char		*cname, *paddr, *guid, *devnm;
486	mdi_pathinfo_t	*pip;
487	int		len, circ, rval;
488
489	switch (op) {
490	case BUS_CONFIG_ONE:
491		break;
492	case BUS_CONFIG_DRIVER:	/* no direct children to configure */
493	case BUS_CONFIG_ALL:
494		return (NDI_SUCCESS);
495	default:
496		return (NDI_FAILURE);
497	}
498
499	/* only implement BUS_CONFIG_ONE */
500	devnm = i_ddi_strdup((char *)arg, KM_SLEEP);
501	len = strlen(devnm) + 1;
502
503	/* caddr is hardcoded in the form *,<guid> */
504	if (tp_decode_name(devnm, &cname, &paddr, &guid) != 0) {
505		cmn_err(CE_NOTE, "tphci_bus_config -- invalid device %s",
506		    (char *)arg);
507		kmem_free(devnm, len);
508		return (NDI_FAILURE);
509	}
510
511	mdi_devi_enter(parent, &circ);
512	rval = mdi_pi_alloc(parent, cname, guid, paddr, 0, &pip);
513	kmem_free(devnm, len);
514	if (rval != MDI_SUCCESS) {
515		cmn_err(CE_NOTE, "tphci_bus_config -- mdi_pi_alloc failed");
516		mdi_devi_exit(parent, circ);
517		return (NDI_FAILURE);
518	}
519
520	/*
521	 * Hold the path and exit the pHCI while calling mdi_pi_online
522	 * to avoid deadlock with power management of pHCI.
523	 */
524	mdi_hold_path(pip);
525	mdi_devi_exit_phci(parent, circ);
526	rval = mdi_pi_online(pip, 0);
527	mdi_devi_enter_phci(parent, &circ);
528	mdi_rele_path(pip);
529
530	if (rval != MDI_SUCCESS) {
531		cmn_err(CE_NOTE, "tphci_bus_config -- mdi_pi_online failed");
532		(void) mdi_pi_free(pip, 0);
533		mdi_devi_exit(parent, circ);
534		return (NDI_FAILURE);
535	}
536
537	if (childp) {
538		*childp = mdi_pi_get_client(pip);
539		ndi_hold_devi(*childp);
540	}
541	mdi_devi_exit(parent, circ);
542
543	return (NDI_SUCCESS);
544}
545
546static int
547tphci_bus_unconfig(dev_info_t *parent, uint_t flags,
548    ddi_bus_config_op_t op, void *arg)
549{
550	int		rval = MDI_SUCCESS;
551	int		circ;
552	mdi_pathinfo_t	*pip, *next;
553	char		*devnm, *cname, *caddr;
554
555	switch (op) {
556	case BUS_UNCONFIG_ONE:
557		devnm = (char *)arg;
558		i_ddi_parse_name(devnm, &cname, &caddr, NULL);
559		if (strcmp(cname, "tclient") != 0)
560			return (NDI_SUCCESS);	/* no such device */
561
562		mdi_devi_enter(parent, &circ);
563		pip = mdi_pi_find(parent, NULL, caddr);
564		if (pip) {
565			mdi_hold_path(pip);
566			mdi_devi_exit_phci(parent, circ);
567			rval = mdi_pi_offline(pip, NDI_DEVI_REMOVE);
568			mdi_devi_enter_phci(parent, &circ);
569			mdi_rele_path(pip);
570
571			if (rval == MDI_SUCCESS)
572				(void) mdi_pi_free(pip, 0);
573		}
574		mdi_devi_exit(parent, circ);
575		return (rval == MDI_SUCCESS ? NDI_SUCCESS : NDI_FAILURE);
576
577	case BUS_UNCONFIG_ALL:
578		if (flags & NDI_AUTODETACH)
579			return (NDI_FAILURE);
580
581		mdi_devi_enter(parent, &circ);
582		next = mdi_get_next_client_path(parent, NULL);
583		while ((pip = next) != NULL) {
584			next = mdi_get_next_client_path(parent, pip);
585
586			mdi_hold_path(pip);
587			mdi_devi_exit_phci(parent, circ);
588			rval = mdi_pi_offline(pip, NDI_DEVI_REMOVE);
589			mdi_devi_enter_phci(parent, &circ);
590			mdi_rele_path(pip);
591
592			if (rval != MDI_SUCCESS)
593				break;
594			(void) mdi_pi_free(pip, 0);
595		}
596		mdi_devi_exit(parent, circ);
597		return (rval == MDI_SUCCESS ? NDI_SUCCESS : NDI_FAILURE);
598
599	case BUS_UNCONFIG_DRIVER:	/* nothing to do */
600		return (NDI_SUCCESS);
601
602	default:
603		return (NDI_FAILURE);
604	}
605	/*NOTREACHED*/
606}
607