summaryrefslogtreecommitdiff
path: root/sys/arch/hppa/dev/astro.c
blob: 1aa2db44506f21549fcd0ece74a29b7e4c725744 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
/*	$OpenBSD: astro.c,v 1.17 2014/11/16 12:30:57 deraadt Exp $	*/

/*
 * Copyright (c) 2007 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/extent.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/reboot.h>
#include <sys/tree.h>

#include <uvm/uvm_extern.h>

#include <machine/iomod.h>
#include <machine/autoconf.h>

#include <hppa/dev/cpudevs.h>

struct astro_regs {
	u_int32_t	rid;
	u_int32_t	pad0000;
	u_int32_t	ioc_ctrl;
	u_int32_t	pad0008;
	u_int8_t	resv1[0x0300 - 0x0010];
	u_int64_t	lmmio_direct0_base;
	u_int64_t	lmmio_direct0_mask;
	u_int64_t	lmmio_direct0_route;
	u_int64_t	lmmio_direct1_base;
	u_int64_t	lmmio_direct1_mask;
	u_int64_t	lmmio_direct1_route;
	u_int64_t	lmmio_direct2_base;
	u_int64_t	lmmio_direct2_mask;
	u_int64_t	lmmio_direct2_route;
	u_int64_t	lmmio_direct3_base;
	u_int64_t	lmmio_direct3_mask;
	u_int64_t	lmmio_direct3_route;
	u_int64_t	lmmio_dist_base;
	u_int64_t	lmmio_dist_mask;
	u_int64_t	lmmio_dist_route;
	u_int64_t	gmmio_dist_base;
	u_int64_t	gmmio_dist_mask;
	u_int64_t	gmmio_dist_route;
	u_int64_t	ios_dist_base;
	u_int64_t	ios_dist_mask;
	u_int64_t	ios_dist_route;
	u_int8_t	resv2[0x03c0 - 0x03a8];
	u_int64_t	ios_direct_base;
	u_int64_t	ios_direct_mask;
	u_int64_t	ios_direct_route;
	u_int8_t	resv3[0x22000 - 0x03d8];
	u_int64_t	func_id;
	u_int64_t	func_class;
	u_int8_t	resv4[0x22040 - 0x22010];
	u_int64_t	rope_config;
	u_int8_t	resv5[0x22050 - 0x22048];
	u_int64_t	rope_debug;
	u_int8_t	resv6[0x22200 - 0x22058];
	u_int64_t	rope0_control;
	u_int64_t	rope1_control;
	u_int64_t	rope2_control;
	u_int64_t	rope3_control;
	u_int64_t	rope4_control;
	u_int64_t	rope5_control;
	u_int64_t	rope6_control;
	u_int64_t	rope7_control;
	u_int8_t	resv7[0x22300 - 0x22240];
	u_int32_t	tlb_ibase;
	u_int32_t	pad22300;
	u_int32_t	tlb_imask;
	u_int32_t	pad22308;
	u_int32_t	tlb_pcom;
	u_int32_t	pad22310;
	u_int32_t	tlb_tcnfg;
	u_int32_t	pad22318;
	u_int64_t	tlb_pdir_base;
};

#define ASTRO_IOC_CTRL_TE	0x0001	/* TOC Enable */
#define ASTRO_IOC_CTRL_CE	0x0002	/* Coalesce Enable */
#define ASTRO_IOC_CTRL_DE	0x0004	/* Dillon Enable */
#define ASTRO_IOC_CTRL_IE	0x0008	/* IOS Enable */
#define ASTRO_IOC_CTRL_OS	0x0010	/* Outbound Synchronous */
#define ASTRO_IOC_CTRL_IS	0x0020	/* Inbound Synchronous */
#define ASTRO_IOC_CTRL_RC	0x0040	/* Read Current Enable */
#define ASTRO_IOC_CTRL_L0	0x0080	/* 0-length Read Enable */
#define ASTRO_IOC_CTRL_RM	0x0100	/* Real Mode */
#define ASTRO_IOC_CTRL_NC	0x0200	/* Non-coherent Mode */
#define ASTRO_IOC_CTRL_ID	0x0400	/* Interrupt Disable */
#define ASTRO_IOC_CTRL_D4	0x0800	/* Disable 4-byte Coalescing */
#define ASTRO_IOC_CTRL_CC	0x1000	/* Increase Coalescing counter value */
#define ASTRO_IOC_CTRL_DD	0x2000	/* Disable distr. range coalescing */
#define ASTRO_IOC_CTRL_DC	0x4000	/* Disable the coalescing counter */

#define IOTTE_V		0x8000000000000000LL	/* Entry valid */
#define IOTTE_PAMASK	0x000000fffffff000LL
#define IOTTE_CI	0x00000000000000ffLL	/* Coherent index */

struct astro_softc {
	struct device sc_dv;

	bus_dma_tag_t sc_dmat;
	struct astro_regs volatile *sc_regs;
	u_int64_t *sc_pdir;

	char sc_dvmamapname[20];
	struct extent *sc_dvmamap;
	struct mutex sc_dvmamtx;

	struct hppa_bus_dma_tag sc_dmatag;
};

/*
 * per-map DVMA page table
 */
struct iommu_page_entry {
	SPLAY_ENTRY(iommu_page_entry) ipe_node;
	paddr_t	ipe_pa;
	vaddr_t	ipe_va;
	bus_addr_t ipe_dva;
};

struct iommu_page_map {
	SPLAY_HEAD(iommu_page_tree, iommu_page_entry) ipm_tree;
	int ipm_maxpage;	/* Size of allocated page map */
	int ipm_pagecnt;	/* Number of entries in use */
	struct iommu_page_entry	ipm_map[1];
};

/*
 * per-map IOMMU state
 */
struct iommu_map_state {
	struct astro_softc *ims_sc;
	bus_addr_t ims_dvmastart;
	bus_size_t ims_dvmasize;
	struct extent_region ims_er;
	struct iommu_page_map ims_map;	/* map must be last (array at end) */
};

int	astro_match(struct device *, void *, void *);
void	astro_attach(struct device *, struct device *, void *);

struct cfattach astro_ca = {
	sizeof(struct astro_softc), astro_match, astro_attach
};

struct cfdriver astro_cd = {
	NULL, "astro", DV_DULL
};

int	iommu_dvmamap_create(void *, bus_size_t, int, bus_size_t, bus_size_t,
	    int, bus_dmamap_t *);
void	iommu_dvmamap_destroy(void *, bus_dmamap_t);
int	iommu_dvmamap_load(void *, bus_dmamap_t, void *, bus_size_t,
	    struct proc *, int);
int	iommu_iomap_load_map(struct astro_softc *, bus_dmamap_t, int);
int	iommu_dvmamap_load_mbuf(void *, bus_dmamap_t, struct mbuf *, int);
int	iommu_dvmamap_load_uio(void *, bus_dmamap_t, struct uio *, int);
int	iommu_dvmamap_load_raw(void *, bus_dmamap_t, bus_dma_segment_t *,
	    int, bus_size_t, int);
void	iommu_dvmamap_unload(void *, bus_dmamap_t);
void	iommu_dvmamap_sync(void *, bus_dmamap_t, bus_addr_t, bus_size_t, int);
int	iommu_dvmamem_alloc(void *, bus_size_t, bus_size_t, bus_size_t,
	    bus_dma_segment_t *, int, int *, int);
void	iommu_dvmamem_free(void *, bus_dma_segment_t *, int);
int	iommu_dvmamem_map(void *, bus_dma_segment_t *, int, size_t,
	    caddr_t *, int);
void	iommu_dvmamem_unmap(void *, caddr_t, size_t);
paddr_t	iommu_dvmamem_mmap(void *, bus_dma_segment_t *, int, off_t, int, int);

void	iommu_enter(struct astro_softc *, bus_addr_t, paddr_t, vaddr_t, int);
void	iommu_remove(struct astro_softc *, bus_addr_t);

struct iommu_map_state *iommu_iomap_create(int);
void	iommu_iomap_destroy(struct iommu_map_state *);
int	iommu_iomap_insert_page(struct iommu_map_state *, vaddr_t, paddr_t);
bus_addr_t iommu_iomap_translate(struct iommu_map_state *, paddr_t);
void	iommu_iomap_clear_pages(struct iommu_map_state *);

const struct hppa_bus_dma_tag astro_dmat = {
	NULL,
	iommu_dvmamap_create, iommu_dvmamap_destroy,
	iommu_dvmamap_load, iommu_dvmamap_load_mbuf,
	iommu_dvmamap_load_uio, iommu_dvmamap_load_raw,
	iommu_dvmamap_unload, iommu_dvmamap_sync,

	iommu_dvmamem_alloc, iommu_dvmamem_free, iommu_dvmamem_map,
	iommu_dvmamem_unmap, iommu_dvmamem_mmap
};

int
astro_match(struct device *parent, void *cfdata, void *aux)   
{
	struct confargs *ca = aux;

	/* Astro is a U-Turn variant. */
	if (ca->ca_type.iodc_type != HPPA_TYPE_IOA ||
	    ca->ca_type.iodc_sv_model != HPPA_IOA_UTURN)
		return 0;

	if (ca->ca_type.iodc_model == 0x58 &&
	    ca->ca_type.iodc_revision >= 0x20)
		return 1;

	return 0;
}

void
astro_attach(struct device *parent, struct device *self, void *aux)
{
	struct confargs *ca = aux, nca;
	struct astro_softc *sc = (struct astro_softc *)self;
	volatile struct astro_regs *r;
	bus_space_handle_t ioh;
	u_int32_t rid, ioc_ctrl;
	psize_t size;
	vaddr_t va;
	paddr_t pa;
	struct vm_page *m;
	struct pglist mlist;
	int iova_bits;

	sc->sc_dmat = ca->ca_dmatag;
	if (bus_space_map(ca->ca_iot, ca->ca_hpa, sizeof(struct astro_regs),
	    0, &ioh)) {
		printf(": can't map IO space\n");
		return;
	}
	sc->sc_regs = r = (struct astro_regs *)ca->ca_hpa;

	rid = letoh32(r->rid);
	printf(": Astro rev %d.%d\n", (rid & 7) + 1, (rid >> 3) & 3);

	ioc_ctrl = letoh32(r->ioc_ctrl);
	ioc_ctrl &= ~ASTRO_IOC_CTRL_CE;
	ioc_ctrl &= ~ASTRO_IOC_CTRL_RM;
	ioc_ctrl &= ~ASTRO_IOC_CTRL_NC;
	r->ioc_ctrl = htole32(ioc_ctrl);

	/*
	 * Setup the iommu.
	 */

	/* XXX This gives us 256MB of iova space. */
	iova_bits = 28;

	r->tlb_ibase = htole32(0);
	r->tlb_imask = htole32(0xffffffff << iova_bits);

	/* Page size is 4K. */
	r->tlb_tcnfg = htole32(0);

	/* Flush TLB. */
	r->tlb_pcom = htole32(31);

	/*
	 * Allocate memory for I/O pagetables.  They need to be physically
	 * contiguous.
	 */

	size = (1 << (iova_bits - PAGE_SHIFT)) * sizeof(u_int64_t);
	TAILQ_INIT(&mlist);
	if (uvm_pglistalloc(size, 0, -1, PAGE_SIZE, 0, &mlist,
	    1, UVM_PLA_NOWAIT) != 0)
		panic("astrottach: no memory");

	va = (vaddr_t)km_alloc(size, &kv_any, &kp_none, &kd_nowait);
	if (va == 0)
		panic("astroattach: no memory");
	sc->sc_pdir = (u_int64_t *)va;

	m = TAILQ_FIRST(&mlist);
	r->tlb_pdir_base = htole64(VM_PAGE_TO_PHYS(m));

	/* Map the pages. */
	for (; m != NULL; m = TAILQ_NEXT(m, pageq)) {
		pa = VM_PAGE_TO_PHYS(m);
		pmap_enter(pmap_kernel(), va, pa,
		    PROT_READ | PROT_WRITE, PMAP_WIRED);
		va += PAGE_SIZE;
	}
	pmap_update(pmap_kernel());
	memset(sc->sc_pdir, 0, size);

	/*
	 * The PDC might have set up some devices to do DMA.  It will do
	 * this for the onboard USB controller if an USB keyboard is used
	 * for console input.  In that case, bad things will happen if we
	 * enable iova space.  So reset the PDC devices before we do that.
	 * Don't do this if we're using a serial console though, since it
	 * will stop working if we do.  This is fine since the serial port
	 * doesn't do DMA.
	 */
	if (PAGE0->mem_cons.pz_class != PCL_DUPLEX)
		pdc_call((iodcio_t)pdc, 0, PDC_IO, PDC_IO_RESET_DEVICES);

	/* Enable iova space. */
	r->tlb_ibase = htole32(1);

        /*
         * Now all the hardware's working we need to allocate a dvma map.
         */
	snprintf(sc->sc_dvmamapname, sizeof(sc->sc_dvmamapname),
	    "%s_dvma", sc->sc_dv.dv_xname);
	sc->sc_dvmamap = extent_create(sc->sc_dvmamapname, 0, (1 << iova_bits),
	    M_DEVBUF, NULL, 0, EX_NOWAIT | EX_NOCOALESCE);
	KASSERT(sc->sc_dvmamap);
	mtx_init(&sc->sc_dvmamtx, IPL_HIGH);

	sc->sc_dmatag = astro_dmat;
	sc->sc_dmatag._cookie = sc;

	nca = *ca;	/* clone from us */
	nca.ca_hpamask = HPPA_IOBEGIN;
	nca.ca_dmatag = &sc->sc_dmatag;
	pdc_scanbus(self, &nca, MAXMODBUS, 0, 0);
}

int
iommu_dvmamap_create(void *v, bus_size_t size, int nsegments,
    bus_size_t maxsegsz, bus_size_t boundary, int flags, bus_dmamap_t *dmamap)
{
	struct astro_softc *sc = v;
	bus_dmamap_t map;
	struct iommu_map_state *ims;
	int error;

	error = bus_dmamap_create(sc->sc_dmat, size, nsegments, maxsegsz,
	    boundary, flags, &map);
	if (error)
		return (error);

	ims = iommu_iomap_create(atop(round_page(size)));
	if (ims == NULL) {
		bus_dmamap_destroy(sc->sc_dmat, map);
		return (ENOMEM);
	}

	ims->ims_sc = sc;
	map->_dm_cookie = ims;
	*dmamap = map;

	return (0);
}

void
iommu_dvmamap_destroy(void *v, bus_dmamap_t map)
{
	struct astro_softc *sc = v;

	/*
	 * The specification (man page) requires a loaded
	 * map to be unloaded before it is destroyed.
	 */
	if (map->dm_nsegs)
		iommu_dvmamap_unload(sc, map);

        if (map->_dm_cookie)
                iommu_iomap_destroy(map->_dm_cookie);
	map->_dm_cookie = NULL;

	bus_dmamap_destroy(sc->sc_dmat, map);
}

int
iommu_iomap_load_map(struct astro_softc *sc, bus_dmamap_t map, int flags)
{
	struct iommu_map_state *ims = map->_dm_cookie;
	struct iommu_page_map *ipm = &ims->ims_map;
	struct iommu_page_entry *e;
	int err, seg;
	paddr_t pa, paend;
	vaddr_t va;
	bus_size_t sgsize;
	bus_size_t align, boundary;
	u_long dvmaddr;
	bus_addr_t dva;
	int i;

	/* XXX */
	boundary = map->_dm_boundary;
	align = PAGE_SIZE;

	iommu_iomap_clear_pages(ims);

	for (seg = 0; seg < map->dm_nsegs; seg++) {
		struct hppa_bus_dma_segment *ds = &map->dm_segs[seg];

		paend = round_page(ds->ds_addr + ds->ds_len);
		for (pa = trunc_page(ds->ds_addr), va = trunc_page(ds->_ds_va);
		     pa < paend; pa += PAGE_SIZE, va += PAGE_SIZE) {
			err = iommu_iomap_insert_page(ims, va, pa);
			if (err) {
                               printf("iomap insert error: %d for "
                                    "va 0x%lx pa 0x%lx\n", err, va, pa);
				bus_dmamap_unload(sc->sc_dmat, map);
				iommu_iomap_clear_pages(ims);
			}
		}
	}

	sgsize = ims->ims_map.ipm_pagecnt * PAGE_SIZE;
	mtx_enter(&sc->sc_dvmamtx);
	err = extent_alloc_with_descr(sc->sc_dvmamap, sgsize, align, 0,
	    boundary, EX_NOWAIT | EX_BOUNDZERO, &ims->ims_er, &dvmaddr);
	mtx_leave(&sc->sc_dvmamtx);
	if (err)
		return (err);

	ims->ims_dvmastart = dvmaddr;
	ims->ims_dvmasize = sgsize;

	dva = dvmaddr;
	for (i = 0, e = ipm->ipm_map; i < ipm->ipm_pagecnt; ++i, ++e) {
		e->ipe_dva = dva;
		iommu_enter(sc, e->ipe_dva, e->ipe_pa, e->ipe_va, flags);
		dva += PAGE_SIZE;
	}

	for (seg = 0; seg < map->dm_nsegs; seg++) {
		struct hppa_bus_dma_segment *ds = &map->dm_segs[seg];
		ds->ds_addr = iommu_iomap_translate(ims, ds->ds_addr);
	}

	return (0);
}

int
iommu_dvmamap_load(void *v, bus_dmamap_t map, void *addr, bus_size_t size,
    struct proc *p, int flags)
{
	struct astro_softc *sc = v;
	int err;

	err = bus_dmamap_load(sc->sc_dmat, map, addr, size, p, flags);
	if (err)
		return (err);

	return iommu_iomap_load_map(sc, map, flags);
}

int
iommu_dvmamap_load_mbuf(void *v, bus_dmamap_t map, struct mbuf *m, int flags)
{
	struct astro_softc *sc = v;
	int err;

	err = bus_dmamap_load_mbuf(sc->sc_dmat, map, m, flags);
	if (err)
		return (err);

	return iommu_iomap_load_map(sc, map, flags);
}

int
iommu_dvmamap_load_uio(void *v, bus_dmamap_t map, struct uio *uio, int flags)
{
	struct astro_softc *sc = v;

	printf("load_uio\n");

	return (bus_dmamap_load_uio(sc->sc_dmat, map, uio, flags));
}

int
iommu_dvmamap_load_raw(void *v, bus_dmamap_t map, bus_dma_segment_t *segs,
    int nsegs, bus_size_t size, int flags)
{
	struct astro_softc *sc = v;

	printf("load_raw\n");

	return (bus_dmamap_load_raw(sc->sc_dmat, map, segs, nsegs, size, flags));
}

void
iommu_dvmamap_unload(void *v, bus_dmamap_t map)
{
	struct astro_softc *sc = v;
	struct iommu_map_state *ims = map->_dm_cookie;
	struct iommu_page_map *ipm = &ims->ims_map;
	struct iommu_page_entry *e;
	int err, i;

	/* Remove the IOMMU entries. */
	for (i = 0, e = ipm->ipm_map; i < ipm->ipm_pagecnt; ++i, ++e)
		iommu_remove(sc, e->ipe_dva);

	/* Clear the iomap. */
	iommu_iomap_clear_pages(ims);

	bus_dmamap_unload(sc->sc_dmat, map);

	mtx_enter(&sc->sc_dvmamtx);
	err = extent_free(sc->sc_dvmamap, ims->ims_dvmastart,
	    ims->ims_dvmasize, EX_NOWAIT);
	ims->ims_dvmastart = 0;
	ims->ims_dvmasize = 0;
	mtx_leave(&sc->sc_dvmamtx);
	if (err)
		printf("warning: %ld of DVMA space lost\n", ims->ims_dvmasize);
}

void
iommu_dvmamap_sync(void *v, bus_dmamap_t map, bus_addr_t off,
    bus_size_t len, int ops)
{
	/* Nothing to do; DMA is cache-coherent. */
}

int
iommu_dvmamem_alloc(void *v, bus_size_t size, bus_size_t alignment,
    bus_size_t boundary, bus_dma_segment_t *segs,
    int nsegs, int *rsegs, int flags)
{
	struct astro_softc *sc = v;

	return (bus_dmamem_alloc(sc->sc_dmat, size, alignment, boundary,
	    segs, nsegs, rsegs, flags));
}

void
iommu_dvmamem_free(void *v, bus_dma_segment_t *segs, int nsegs)
{
	struct astro_softc *sc = v;

	bus_dmamem_free(sc->sc_dmat, segs, nsegs);
}

int
iommu_dvmamem_map(void *v, bus_dma_segment_t *segs, int nsegs, size_t size,
    caddr_t *kvap, int flags)
{
	struct astro_softc *sc = v;

	return (bus_dmamem_map(sc->sc_dmat, segs, nsegs, size, kvap, flags));
}

void
iommu_dvmamem_unmap(void *v, caddr_t kva, size_t size)
{
	struct astro_softc *sc = v;

	bus_dmamem_unmap(sc->sc_dmat, kva, size);
}

paddr_t
iommu_dvmamem_mmap(void *v, bus_dma_segment_t *segs, int nsegs, off_t off,
    int prot, int flags)
{
	struct astro_softc *sc = v;

	return (bus_dmamem_mmap(sc->sc_dmat, segs, nsegs, off, prot, flags));
}

/*
 * Utility function used by splay tree to order page entries by pa.
 */
static inline int
iomap_compare(struct iommu_page_entry *a, struct iommu_page_entry *b)
{
	return ((a->ipe_pa > b->ipe_pa) ? 1 :
		(a->ipe_pa < b->ipe_pa) ? -1 : 0);
}

SPLAY_PROTOTYPE(iommu_page_tree, iommu_page_entry, ipe_node, iomap_compare);

SPLAY_GENERATE(iommu_page_tree, iommu_page_entry, ipe_node, iomap_compare);

/*
 * Create a new iomap.
 */
struct iommu_map_state *
iommu_iomap_create(int n)
{
	struct iommu_map_state *ims;

	/* Safety for heavily fragmented data, such as mbufs */
	n += 4;
	if (n < 16)
		n = 16;

	ims = malloc(sizeof(*ims) + (n - 1) * sizeof(ims->ims_map.ipm_map[0]),
	    M_DEVBUF, M_NOWAIT | M_ZERO);
	if (ims == NULL)
		return (NULL);

	/* Initialize the map. */
	ims->ims_map.ipm_maxpage = n;
	SPLAY_INIT(&ims->ims_map.ipm_tree);

	return (ims);
}

/*
 * Destroy an iomap.
 */
void
iommu_iomap_destroy(struct iommu_map_state *ims)
{
#ifdef DIAGNOSTIC
	if (ims->ims_map.ipm_pagecnt > 0)
		printf("iommu_iomap_destroy: %d page entries in use\n",
		    ims->ims_map.ipm_pagecnt);
#endif

	free(ims, M_DEVBUF, 0);
}

/*
 * Insert a pa entry in the iomap.
 */
int
iommu_iomap_insert_page(struct iommu_map_state *ims, vaddr_t va, paddr_t pa)
{
	struct iommu_page_map *ipm = &ims->ims_map;
	struct iommu_page_entry *e;

	if (ipm->ipm_pagecnt >= ipm->ipm_maxpage) {
		struct iommu_page_entry ipe;

		ipe.ipe_pa = pa;
		if (SPLAY_FIND(iommu_page_tree, &ipm->ipm_tree, &ipe))
			return (0);

		return (ENOMEM);
	}

	e = &ipm->ipm_map[ipm->ipm_pagecnt];

	e->ipe_pa = pa;
	e->ipe_va = va;
	e->ipe_dva = 0;

	e = SPLAY_INSERT(iommu_page_tree, &ipm->ipm_tree, e);

	/* Duplicates are okay, but only count them once. */
	if (e)
		return (0);

	++ipm->ipm_pagecnt;

	return (0);
}

/*
 * Translate a physical address (pa) into a DVMA address.
 */
bus_addr_t
iommu_iomap_translate(struct iommu_map_state *ims, paddr_t pa)
{
	struct iommu_page_map *ipm = &ims->ims_map;
	struct iommu_page_entry *e;
	struct iommu_page_entry pe;
	paddr_t offset = pa & PAGE_MASK;

	pe.ipe_pa = trunc_page(pa);

	e = SPLAY_FIND(iommu_page_tree, &ipm->ipm_tree, &pe);

	if (e == NULL) {
		panic("couldn't find pa %lx", pa);
		return 0;
	}

	return (e->ipe_dva | offset);
}

/*
 * Clear the iomap table and tree.
 */
void
iommu_iomap_clear_pages(struct iommu_map_state *ims)
{
        ims->ims_map.ipm_pagecnt = 0;
        SPLAY_INIT(&ims->ims_map.ipm_tree);
}

/*
 * Add an entry to the IOMMU table.
 */
void
iommu_enter(struct astro_softc *sc, bus_addr_t dva, paddr_t pa, vaddr_t va,
    int flags)
{
	volatile u_int64_t *tte_ptr = &sc->sc_pdir[dva >> PAGE_SHIFT];
	u_int64_t tte;
	u_int32_t ci;

#ifdef DEBUG
	printf("iommu_enter dva %lx, pa %lx, va %lx\n", dva, pa, va);
#endif

#ifdef DIAGNOSTIC
	tte = letoh64(*tte_ptr);

	if (tte & IOTTE_V) {
		printf("Overwriting valid tte entry (dva %lx pa %lx "
		    "&tte %p tte %llx)\n", dva, pa, tte_ptr, tte);
		extent_print(sc->sc_dvmamap);
		panic("IOMMU overwrite");
	}
#endif

	mtsp(HPPA_SID_KERNEL, 1);
	__asm volatile("lci 0(%%sr1, %1), %0" : "=r" (ci) : "r" (va));

	tte = (pa & IOTTE_PAMASK) | ((ci >> 12) & IOTTE_CI);
	tte |= IOTTE_V;

	*tte_ptr = htole64(tte);
	__asm volatile("fdc 0(%%sr1, %0)\n\tsync" : : "r" (tte_ptr));
}

/*
 * Remove an entry from the IOMMU table.
 */
void
iommu_remove(struct astro_softc *sc, bus_addr_t dva)
{
	volatile struct astro_regs *r = sc->sc_regs;
	u_int64_t *tte_ptr = &sc->sc_pdir[dva >> PAGE_SHIFT];
	u_int64_t tte;

#ifdef DIAGNOSTIC
	if (dva != trunc_page(dva)) {
		printf("iommu_remove: unaligned dva: %lx\n", dva);
		dva = trunc_page(dva);
	}
#endif

	tte = letoh64(*tte_ptr);

#ifdef DIAGNOSTIC
	if ((tte & IOTTE_V) == 0) {
		printf("Removing invalid tte entry (dva %lx &tte %p "
		    "tte %llx)\n", dva, tte_ptr, tte);
		extent_print(sc->sc_dvmamap);
		panic("IOMMU remove overwrite");
	}
#endif

	*tte_ptr = htole64(tte & ~IOTTE_V);

	/* Flush IOMMU. */
	r->tlb_pcom = htole32(dva | PAGE_SHIFT);
}