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 #include <sys/types.h>
27 #include <sys/debug.h>
28 #include <sys/kmem.h>
29 #include <sys/ksynch.h>
30 #ifndef DS_DDICT
31 #include <sys/vnode.h>
32 #endif
33 #include <sys/cmn_err.h>
34 #include <sys/open.h>
35 #include <sys/file.h>
36 #include <sys/cred.h>
37 #include <sys/conf.h>
38 #include <sys/errno.h>
39 #include <sys/uio.h>
40 #ifndef DS_DDICT
41 #include <sys/pathname.h> /* for lookupname */
42 #endif
43 #include <sys/ddi.h>
44 #include <sys/sunddi.h>
45 #include <sys/sunldi.h>
46
47 #include <ns/solaris/nsc_thread.h>
48 #ifdef DS_DDICT
49 #include "../contract.h"
50 #endif
51 #include "../nsctl.h"
52 #include "nskernd.h"
53
54
55 typedef struct raw_maj {
56 struct raw_maj *next;
57 major_t major;
58 struct dev_ops *devops;
59 strategy_fn_t strategy;
60 int (*open)(dev_t *, int, int, cred_t *);
61 int (*close)(dev_t, int, int, cred_t *);
62 int (*ioctl)(dev_t, int, intptr_t, int, cred_t *, int *);
63 } raw_maj_t;
64
65 typedef struct raw_dev {
66 ldi_handle_t lh; /* Solaris layered driver handle */
67 struct vnode *vp; /* vnode of device */
68 uint64_t size; /* size of device in blocks */
69 raw_maj_t *major; /* pointer to major structure */
70 char *path; /* pathname -- kmem_alloc'd */
71 int plen; /* length of kmem_alloc for pathname */
72 dev_t rdev; /* device number */
73 char in_use; /* flag */
74 int partition; /* partition number */
75 } raw_dev_t;
76
77 static int fd_hwm = 0; /* first never used entry in _nsc_raw_files */
78
79 static raw_dev_t *_nsc_raw_files;
80 static raw_maj_t *_nsc_raw_majors;
81
82 kmutex_t _nsc_raw_lock;
83
84 int _nsc_raw_flags = 0; /* required by nsctl */
85 static int _nsc_raw_maxdevs; /* local copy */
86
87 static int _raw_strategy(struct buf *); /* forward decl */
88
89 static dev_t
90 ldi_get_dev_t_from_path(char *path)
91 {
92 vnode_t *vp;
93 dev_t rdev;
94
95 /* Validate parameters */
96 if (path == NULL)
97 return (NULL);
98
99 /* Lookup path */
100 vp = NULL;
101 if (lookupname(path, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp))
102 return (NULL);
103
104 /* Validate resulting vnode */
105 if ((vp) && (vp->v_type == VCHR))
106 rdev = vp->v_rdev;
107 else
108 rdev = (dev_t)NULL;
109
110 /* Release vnode */
111 if (vp)
112 VN_RELE(vp);
113
114 return (rdev);
115 }
116
117 int
118 _nsc_init_raw(int maxdevs)
119 {
120 _nsc_raw_files =
121 kmem_zalloc(sizeof (*_nsc_raw_files) * maxdevs, KM_SLEEP);
122
123 _nsc_raw_maxdevs = maxdevs;
124 _nsc_raw_majors = NULL;
125
126 mutex_init(&_nsc_raw_lock, NULL, MUTEX_DRIVER, NULL);
127 return (0);
128 }
129
130
131 void
132 _nsc_deinit_raw(void)
133 {
134 raw_maj_t *maj = _nsc_raw_majors;
135 raw_maj_t *next;
136
137 /* Free the memory allocated for strategy pointers */
138 while (maj != NULL) {
139 next = maj->next;
140 kmem_free(maj, sizeof (*maj));
141 maj = next;
142 }
143
144 mutex_destroy(&_nsc_raw_lock);
145 kmem_free(_nsc_raw_files, sizeof (*_nsc_raw_files) * _nsc_raw_maxdevs);
146 _nsc_raw_files = NULL;
147 _nsc_raw_maxdevs = 0;
148 }
149
150
151 /* must be called with the _nsc_raw_lock held */
152 static raw_maj_t *
153 _raw_get_maj_info(major_t umaj)
154 {
155 raw_maj_t *maj = _nsc_raw_majors;
156
157 ASSERT(MUTEX_HELD(&_nsc_raw_lock));
158
159 /* Walk through the linked list */
160 while (maj != NULL) {
161 if (maj->major == umaj) {
162 /* Found major number */
163 break;
164 }
165 maj = maj->next;
166 }
167
168 if (maj == NULL) {
169 struct dev_ops *ops = NULL;
170 #ifdef DEBUG
171 const int maxtry = 5;
172 int try = maxtry;
173 #endif
174
175 /*
176 * The earlier ldi_open call has locked the driver
177 * for this major number into memory, so just index into
178 * the devopsp array to get the dev_ops pointer which
179 * must be valid.
180 */
181
182 ops = devopsp[umaj];
183
184 if (ops == NULL || ops->devo_cb_ops == NULL) {
185 cmn_err(CE_WARN,
186 "nskern: cannot find dev_ops for major %d", umaj);
187
188 return (NULL);
189 }
190
191 #ifdef DEBUG
192 cmn_err(CE_NOTE,
193 "nsc_raw: held driver (%d) after %d attempts",
194 umaj, (maxtry - try));
195 #endif /* DEBUG */
196
197 maj = kmem_zalloc(sizeof (raw_maj_t), KM_NOSLEEP);
198 if (!maj) {
199 return (NULL);
200 }
201
202 maj->strategy = ops->devo_cb_ops->cb_strategy;
203 maj->ioctl = ops->devo_cb_ops->cb_ioctl;
204 maj->close = ops->devo_cb_ops->cb_close;
205 maj->open = ops->devo_cb_ops->cb_open;
206 maj->major = umaj;
207 maj->devops = ops;
208
209 if (maj->strategy == NULL ||
210 maj->strategy == nodev ||
211 maj->strategy == nulldev) {
212 cmn_err(CE_WARN,
213 "nskern: no strategy function for "
214 "disk driver (major %d)",
215 umaj);
216 kmem_free(maj, sizeof (*maj));
217 return (NULL);
218 }
219
220 maj->next = _nsc_raw_majors;
221 _nsc_raw_majors = maj;
222 }
223
224 return (maj);
225 }
226
227
228 /*
229 * nsc_get_strategy returns the strategy function associated with
230 * the major number umaj. NULL is returned if no strategy is found.
231 */
232 strategy_fn_t
233 nsc_get_strategy(major_t umaj)
234 {
235 raw_maj_t *maj;
236 strategy_fn_t strategy = NULL;
237
238 mutex_enter(&_nsc_raw_lock);
239
240 for (maj = _nsc_raw_majors; maj != NULL; maj = maj->next) {
241 if (maj->major == umaj) {
242 /* Found major number */
243 strategy = maj->strategy;
244 break;
245 }
246 }
247
248 mutex_exit(&_nsc_raw_lock);
249
250 return (strategy);
251 }
252
253
254 void *
255 nsc_get_devops(major_t umaj)
256 {
257 raw_maj_t *maj;
258 void *devops = NULL;
259
260 mutex_enter(&_nsc_raw_lock);
261
262 for (maj = _nsc_raw_majors; maj != NULL; maj = maj->next) {
263 if (maj->major == umaj) {
264 devops = maj->devops;
265 break;
266 }
267 }
268
269 mutex_exit(&_nsc_raw_lock);
270
271 return (devops);
272 }
273
274
275 /*
276 * _raw_open
277 *
278 * Multiple opens, single close.
279 */
280
281 /* ARGSUSED */
282 static int
283 _raw_open(char *path, int flag, blind_t *cdp, void *iodev)
284 {
285 struct cred *cred;
286 raw_dev_t *cdi = NULL;
287 char *spath;
288 dev_t rdev;
289 int rc, cd, the_cd;
290 int plen;
291 ldi_ident_t li;
292
293 if (proc_nskernd == NULL) {
294 cmn_err(CE_WARN, "nskern: no nskernd daemon running!");
295 return (ENXIO);
296 }
297
298 if (_nsc_raw_maxdevs == 0) {
299 cmn_err(CE_WARN, "nskern: _raw_open() before _nsc_init_raw()!");
300 return (ENXIO);
301 }
302
303 plen = strlen(path) + 1;
304 spath = kmem_alloc(plen, KM_SLEEP);
305
306 (void) strcpy(spath, path);
307
308 /*
309 * Lookup the vnode to extract the dev_t info,
310 * then release the vnode.
311 */
312 if ((rdev = ldi_get_dev_t_from_path(path)) == 0) {
313 kmem_free(spath, plen);
314 return (ENXIO);
315 }
316
317 /*
318 * See if this device is already opened
319 */
320
321 the_cd = -1;
322
323 mutex_enter(&_nsc_raw_lock);
324
325 for (cd = 0, cdi = _nsc_raw_files; cd < fd_hwm; cd++, cdi++) {
326 if (rdev == cdi->rdev) {
327 the_cd = cd;
328 break;
329 } else if (the_cd == -1 && !cdi->in_use)
330 the_cd = cd;
331 }
332
333 if (the_cd == -1) {
334 if (fd_hwm < _nsc_raw_maxdevs)
335 the_cd = fd_hwm++;
336 else {
337 mutex_exit(&_nsc_raw_lock);
338 cmn_err(CE_WARN, "_raw_open: too many open devices");
339 kmem_free(spath, plen);
340 return (EIO);
341 }
342 }
343
344 cdi = &_nsc_raw_files[the_cd];
345 if (cdi->in_use) {
346 /* already set up - just return */
347 mutex_exit(&_nsc_raw_lock);
348 *cdp = (blind_t)cdi->rdev;
349 kmem_free(spath, plen);
350 return (0);
351 }
352
353 cdi->partition = -1;
354 cdi->size = (uint64_t)0;
355 cdi->rdev = rdev;
356 cdi->path = spath;
357 cdi->plen = plen;
358
359 cred = ddi_get_cred();
360
361 /*
362 * Layered driver
363 *
364 * We use xxx_open_by_dev() since this guarantees that a
365 * specfs vnode is created and used, not a standard filesystem
366 * vnode. This is necessary since in a cluster PXFS will block
367 * vnode operations during switchovers, so we have to use the
368 * underlying specfs vnode not the PXFS vnode.
369 *
370 */
371
372 if ((rc = ldi_ident_from_dev(cdi->rdev, &li)) == 0) {
373 rc = ldi_open_by_dev(&cdi->rdev,
374 OTYP_BLK, FREAD|FWRITE, cred, &cdi->lh, li);
375 }
376 if (rc != 0) {
377 cdi->lh = NULL;
378 goto failed;
379 }
380
381 /*
382 * grab the major_t related information
383 */
384
385 cdi->major = _raw_get_maj_info(getmajor(rdev));
386 if (cdi->major == NULL) {
387 /* Out of memory */
388 cmn_err(CE_WARN,
389 "_raw_open: cannot alloc major number structure");
390
391 rc = ENOMEM;
392 goto failed;
393 }
394
395 *cdp = (blind_t)cdi->rdev;
396 cdi->in_use++;
397
398 mutex_exit(&_nsc_raw_lock);
399
400 return (rc);
401
402 failed:
403
404 if (cdi->lh)
405 (void) ldi_close(cdi->lh, FWRITE|FREAD, cred);
406
407 bzero(cdi, sizeof (*cdi));
408
409 mutex_exit(&_nsc_raw_lock);
410
411 kmem_free(spath, plen);
412 return (rc);
413 }
414
415
416 static int
417 __raw_get_cd(dev_t fd)
418 {
419 int cd;
420
421 if (_nsc_raw_maxdevs != 0) {
422 for (cd = 0; cd < fd_hwm; cd++) {
423 if (fd == _nsc_raw_files[cd].rdev)
424 return (cd);
425 }
426 }
427
428 return (-1);
429 }
430
431
432 /*
433 * _raw_close
434 *
435 * Multiple opens, single close.
436 */
437
438 static int
439 _raw_close(dev_t fd)
440 {
441 struct cred *cred;
442 raw_dev_t *cdi;
443 int rc;
444 int cd;
445
446 mutex_enter(&_nsc_raw_lock);
447
448 if ((cd = __raw_get_cd(fd)) == -1 || !_nsc_raw_files[cd].in_use) {
449 mutex_exit(&_nsc_raw_lock);
450 return (EIO);
451 }
452
453 cdi = &_nsc_raw_files[cd];
454
455 cred = ddi_get_cred();
456
457 rc = ldi_close(cdi->lh, FREAD|FWRITE, cred);
458 if (rc != 0) {
459 mutex_exit(&_nsc_raw_lock);
460 return (rc);
461 }
462
463 kmem_free(cdi->path, cdi->plen);
464
465 bzero(cdi, sizeof (*cdi));
466
467 mutex_exit(&_nsc_raw_lock);
468
469 return (0);
470 }
471
472
473 /* ARGSUSED */
474 static int
475 _raw_uread(dev_t fd, uio_t *uiop, cred_t *crp)
476 {
477 return (physio(_raw_strategy, 0, fd, B_READ, minphys, uiop));
478 }
479
480
481 /* ARGSUSED */
482 static int
483 _raw_uwrite(dev_t fd, uio_t *uiop, cred_t *crp)
484 {
485 return (physio(_raw_strategy, 0, fd, B_WRITE, minphys, uiop));
486 }
487
488
489 static int
490 _raw_strategy(struct buf *bp)
491 {
492 int cd = __raw_get_cd(bp->b_edev);
493
494 if (cd == -1 || _nsc_raw_files[cd].major == NULL) {
495 bioerror(bp, ENXIO);
496 biodone(bp);
497 return (NULL);
498 }
499
500 return ((*_nsc_raw_files[cd].major->strategy)(bp));
501 }
502
503
504 static int
505 _raw_partsize(dev_t fd, nsc_size_t *rvalp)
506 {
507 int cd;
508
509 if ((cd = __raw_get_cd(fd)) == -1 || !_nsc_raw_files[cd].in_use)
510 return (EIO);
511
512 *rvalp = (nsc_size_t)_nsc_raw_files[cd].size;
513 return (0);
514 }
515
516
517 /*
518 * Return largest i/o size.
519 */
520
521 static nsc_size_t nsc_rawmaxfbas = 0;
522 /* ARGSUSED */
523 static int
524 _raw_maxfbas(dev_t dev, int flag, nsc_size_t *ptr)
525 {
526 struct buf *bp;
527 if (flag == NSC_CACHEBLK)
528 *ptr = 1;
529 else {
530 if (nsc_rawmaxfbas == 0) {
531 bp = getrbuf(KM_SLEEP);
532 bp->b_bcount = 4096 * 512;
533 minphys(bp);
534 nsc_rawmaxfbas = FBA_NUM(bp->b_bcount);
535 freerbuf(bp);
536 }
537 *ptr = nsc_rawmaxfbas;
538 }
539 return (0);
540 }
541
542
543 /*
544 * Control device or system.
545 */
546
547 /* ARGSUSED */
548 static int
549 _raw_control(dev_t dev, int cmd, int *ptr)
550 {
551 #ifdef DEBUG
552 cmn_err(CE_WARN, "unrecognised nsc_control: %x", cmd);
553 #endif
554 return (EINVAL); /* no control commands understood */
555 }
556
557
558 static int
559 _raw_get_bsize(dev_t dev, uint64_t *bsizep, int *partitionp)
560 {
561 #ifdef DKIOCPARTITION
562 struct partition64 *p64 = NULL;
563 #endif
564 struct dk_cinfo *dki_info = NULL;
565 struct dev_ops *ops;
566 struct cred *cred;
567 struct vtoc *vtoc = NULL;
568 dev_info_t *dip;
569 raw_dev_t *cdi;
570 int rc, cd;
571 int flags;
572 int rval;
573
574 *partitionp = -1;
575 *bsizep = 0;
576
577 if ((cd = __raw_get_cd(dev)) == -1 || !_nsc_raw_files[cd].in_use)
578 return (-1);
579
580 cdi = &_nsc_raw_files[cd];
581 ops = cdi->major->devops;
582
583 if (ops == NULL) {
584 return (-1);
585 }
586
587 rc = (*ops->devo_getinfo)(NULL, DDI_INFO_DEVT2DEVINFO,
588 (void *)dev, (void **)&dip);
589
590 if (rc != DDI_SUCCESS || dip == NULL) {
591 return (-1);
592 }
593
594 if (!ddi_prop_exists(DDI_DEV_T_ANY, dip,
595 DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL)) {
596 return (-1);
597 }
598
599 cred = ddi_get_cred();
600
601 flags = FKIOCTL | FREAD | FWRITE | DATAMODEL_NATIVE;
602
603 dki_info = kmem_alloc(sizeof (*dki_info), KM_SLEEP);
604
605 /* DKIOCINFO */
606 rc = (*cdi->major->ioctl)(dev, DKIOCINFO,
607 (intptr_t)dki_info, flags, cred, &rval);
608
609 if (rc != 0) {
610 goto out;
611 }
612
613 /* return partition number */
614 *partitionp = (int)dki_info->dki_partition;
615
616 vtoc = kmem_alloc(sizeof (*vtoc), KM_SLEEP);
617
618 /* DKIOCGVTOC */
619 rc = (*cdi->major->ioctl)(dev, DKIOCGVTOC,
620 (intptr_t)vtoc, flags, cred, &rval);
621
622 if (rc) {
623 /* DKIOCGVTOC failed, but there might be an EFI label */
624 rc = -1;
625
626 #ifdef DKIOCPARTITION
627 /* do we have an EFI partition table? */
628 p64 = kmem_alloc(sizeof (*p64), KM_SLEEP);
629 p64->p_partno = (uint_t)*partitionp;
630
631 /* DKIOCPARTITION */
632 rc = (*cdi->major->ioctl)(dev, DKIOCPARTITION,
633 (intptr_t)p64, flags, cred, &rval);
634
635 if (rc == 0) {
636 /* found EFI, return size */
637 *bsizep = (uint64_t)p64->p_size;
638 } else {
639 /* both DKIOCGVTOC and DKIOCPARTITION failed - error */
640 rc = -1;
641 }
642 #endif
643
644 goto out;
645 }
646
647 if ((vtoc->v_sanity != VTOC_SANE) ||
648 (vtoc->v_version != V_VERSION && vtoc->v_version != 0) ||
649 (dki_info->dki_partition > V_NUMPAR)) {
650 rc = -1;
651 goto out;
652 }
653
654 *bsizep = (uint64_t)vtoc->v_part[(int)dki_info->dki_partition].p_size;
655 rc = 0;
656
657 out:
658 if (dki_info) {
659 kmem_free(dki_info, sizeof (*dki_info));
660 }
661
662 if (vtoc) {
663 kmem_free(vtoc, sizeof (*vtoc));
664 }
665
666 #ifdef DKIOCPARTITION
667 if (p64) {
668 kmem_free(p64, sizeof (*p64));
669 }
670 #endif
671
672 return (rc);
673 }
674
675
676 /*
677 * Ugly, ugly, ugly.
678 *
679 * Some volume managers (Veritas) don't support layered ioctls
680 * (no FKIOCTL support, no DDI_KERNEL_IOCTL property defined) AND
681 * do not support the properties for bdev_Size()/bdev_size().
682 *
683 * If the underlying driver has specified DDI_KERNEL_IOCTL, then we use
684 * the FKIOCTL technique. Otherwise ...
685 *
686 * The only reliable way to get the partition size, is to bounce the
687 * command through user land (nskernd).
688 *
689 * Then, SunCluster PXFS blocks access at the vnode level to device
690 * nodes during failover / switchover, so a read_vtoc() function call
691 * from user land deadlocks. So, we end up coming back into the kernel
692 * to go directly to the underlying device driver - that's what
693 * nskern_bsize() is doing below.
694 *
695 * There has to be a better way ...
696 */
697
698 static int
699 _raw_init_dev(dev_t fd, uint64_t *sizep, int *partitionp)
700 {
701 struct nskernd *nsk;
702 int rc, cd;
703
704 if ((cd = __raw_get_cd(fd)) == -1 || !_nsc_raw_files[cd].in_use)
705 return (EIO);
706
707 /* try the in-kernel way */
708
709 rc = _raw_get_bsize(fd, sizep, partitionp);
710 if (rc == 0) {
711 return (0);
712 }
713
714 /* fallback to the the slow way */
715
716 nsk = kmem_zalloc(sizeof (*nsk), KM_SLEEP);
717 nsk->command = NSKERND_BSIZE;
718 nsk->data1 = (uint64_t)0;
719 nsk->data2 = (uint64_t)fd;
720 (void) strncpy(nsk->char1, _nsc_raw_files[cd].path, NSC_MAXPATH);
721
722 rc = nskernd_get(nsk);
723 if (rc == 0) {
724 *partitionp = (int)nsk->data2;
725 *sizep = nsk->data1;
726 }
727
728 kmem_free(nsk, sizeof (*nsk));
729 return (rc < 0 ? EIO : 0);
730 }
731
732
733 static int
734 _raw_attach_io(dev_t fd)
735 {
736 int cd;
737
738 if ((cd = __raw_get_cd(fd)) == -1 || !_nsc_raw_files[cd].in_use)
739 return (EIO);
740
741 return (_raw_init_dev(fd, &_nsc_raw_files[cd].size,
742 &_nsc_raw_files[cd].partition));
743 }
744
745
746 /*
747 * See the comment above _raw_init_dev().
748 */
749
750 int
751 nskern_bsize(struct nscioc_bsize *bsize, int *rvp)
752 {
753 struct cred *cred;
754 raw_dev_t *cdi;
755 int errno = 0;
756 int flag;
757 int cd;
758
759 *rvp = 0;
760
761 if (bsize == NULL || rvp == NULL)
762 return (EINVAL);
763
764 cd = __raw_get_cd(bsize->raw_fd);
765 if (cd == -1 || !_nsc_raw_files[cd].in_use)
766 return (EIO);
767
768 cdi = &_nsc_raw_files[cd];
769 cred = ddi_get_cred();
770
771 /*
772 * ddi_mmap_get_model() returns the model for this user thread
773 * which is what we want - get_udatamodel() is not public.
774 */
775
776 flag = FREAD | FWRITE | ddi_mmap_get_model();
777
778 if (bsize->efi == 0) {
779 /* DKIOCINFO */
780 errno = (*cdi->major->ioctl)(bsize->raw_fd,
781 DKIOCINFO, (intptr_t)bsize->dki_info, flag, cred, rvp);
782
783 if (errno) {
784 return (errno);
785 }
786
787 /* DKIOCGVTOC */
788 errno = (*cdi->major->ioctl)(bsize->raw_fd,
789 DKIOCGVTOC, (intptr_t)bsize->vtoc, flag, cred, rvp);
790
791 if (errno) {
792 return (errno);
793 }
794 } else {
795 #ifdef DKIOCPARTITION
796 /* do we have an EFI partition table? */
797 errno = (*cdi->major->ioctl)(bsize->raw_fd,
798 DKIOCPARTITION, (intptr_t)bsize->p64, flag, cred, rvp);
799
800 if (errno) {
801 return (errno);
802 }
803 #endif
804 }
805
806 return (0);
807 }
808
809
810 /*
811 * Private function for sv to use.
812 */
813 int
814 nskern_partition(dev_t fd, int *partitionp)
815 {
816 uint64_t size;
817 int cd, rc;
818
819 if ((cd = __raw_get_cd(fd)) == -1 || !_nsc_raw_files[cd].in_use)
820 return (EIO);
821
822 if ((*partitionp = _nsc_raw_files[cd].partition) != -1) {
823 return (0);
824 }
825
826 rc = _raw_init_dev(fd, &size, partitionp);
827 if (rc != 0 || *partitionp < 0) {
828 return (EIO);
829 }
830
831 return (0);
832 }
833
834
835 nsc_def_t _nsc_raw_def[] = {
836 "Open", (uintptr_t)_raw_open, 0,
837 "Close", (uintptr_t)_raw_close, 0,
838 "Attach", (uintptr_t)_raw_attach_io, 0,
839 "UserRead", (uintptr_t)_raw_uread, 0,
840 "UserWrite", (uintptr_t)_raw_uwrite, 0,
841 "PartSize", (uintptr_t)_raw_partsize, 0,
842 "MaxFbas", (uintptr_t)_raw_maxfbas, 0,
843 "Control", (uintptr_t)_raw_control, 0,
844 "Provide", NSC_DEVICE, 0,
845 0, 0, 0
846 };