aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/kernel/pci_iommu.c
blob: c1d95e14bbed21e8f979367baa5ee1a2b71c9a69 (plain) (blame)
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
/*
 * Copyright (C) 2001 Mike Corrigan & Dave Engebretsen, IBM Corporation
 *
 * Rewrite, cleanup, new allocation schemes:
 * Copyright (C) 2004 Olof Johansson, IBM Corporation
 *
 * Dynamic DMA mapping support, platform-independent parts.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */


#include <linux/init.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/iommu.h>
#include <asm/pci-bridge.h>
#include <asm/machdep.h>
#include <asm/ppc-pci.h>

/*
 * We can use ->sysdata directly and avoid the extra work in
 * pci_device_to_OF_node since ->sysdata will have been initialised
 * in the iommu init code for all devices.
 */
#define PCI_GET_DN(dev) ((struct device_node *)((dev)->sysdata))

static inline struct iommu_table *devnode_table(struct device *dev)
{
	struct pci_dev *pdev;

	if (!dev) {
		pdev = ppc64_isabridge_dev;
		if (!pdev)
			return NULL;
	} else
		pdev = to_pci_dev(dev);

	return PCI_DN(PCI_GET_DN(pdev))->iommu_table;
}


static inline unsigned long device_to_mask(struct device *hwdev)
{
	struct pci_dev *pdev;

	if (!hwdev) {
		pdev = ppc64_isabridge_dev;
		if (!pdev) /* This is the best guess we can do */
			return 0xfffffffful;
	} else
		pdev = to_pci_dev(hwdev);

	if (pdev->dma_mask)
		return pdev->dma_mask;

	/* Assume devices without mask can take 32 bit addresses */
	return 0xfffffffful;
}


/* Allocates a contiguous real buffer and creates mappings over it.
 * Returns the virtual address of the buffer and sets dma_handle
 * to the dma address (mapping) of the first page.
 */
static void *pci_iommu_alloc_coherent(struct device *hwdev, size_t size,
			   dma_addr_t *dma_handle, gfp_t flag)
{
	return iommu_alloc_coherent(devnode_table(hwdev), size, dma_handle,
			device_to_mask(hwdev), flag);
}

static void pci_iommu_free_coherent(struct device *hwdev, size_t size,
			 void *vaddr, dma_addr_t dma_handle)
{
	iommu_free_coherent(devnode_table(hwdev), size, vaddr, dma_handle);
}

/* Creates TCEs for a user provided buffer.  The user buffer must be 
 * contiguous real kernel storage (not vmalloc).  The address of the buffer
 * passed here is the kernel (virtual) address of the buffer.  The buffer
 * need not be page aligned, the dma_addr_t returned will point to the same
 * byte within the page as vaddr.
 */
static dma_addr_t pci_iommu_map_single(struct device *hwdev, void *vaddr,
		size_t size, enum dma_data_direction direction)
{
	return iommu_map_single(devnode_table(hwdev), vaddr, size,
			        device_to_mask(hwdev), direction);
}


static void pci_iommu_unmap_single(struct device *hwdev, dma_addr_t dma_handle,
		size_t size, enum dma_data_direction direction)
{
	iommu_unmap_single(devnode_table(hwdev), dma_handle, size, direction);
}


static int pci_iommu_map_sg(struct device *pdev, struct scatterlist *sglist,
		int nelems, enum dma_data_direction direction)
{
	return iommu_map_sg(pdev, devnode_table(pdev), sglist,
			nelems, device_to_mask(pdev), direction);
}

static void pci_iommu_unmap_sg(struct device *pdev, struct scatterlist *sglist,
		int nelems, enum dma_data_direction direction)
{
	iommu_unmap_sg(devnode_table(pdev), sglist, nelems, direction);
}

/* We support DMA to/from any memory page via the iommu */
static int pci_iommu_dma_supported(struct device *dev, u64 mask)
{
	struct iommu_table *tbl = devnode_table(dev);

	if (!tbl || tbl->it_offset > mask) {
		printk(KERN_INFO "Warning: IOMMU table offset too big for device mask\n");
		if (tbl)
			printk(KERN_INFO "mask: 0x%08lx, table offset: 0x%08lx\n",
				mask, tbl->it_offset);
		else
			printk(KERN_INFO "mask: 0x%08lx, table unavailable\n",
				mask);
		return 0;
	} else
		return 1;
}

void pci_iommu_init(void)
{
	pci_dma_ops.alloc_coherent = pci_iommu_alloc_coherent;
	pci_dma_ops.free_coherent = pci_iommu_free_coherent;
	pci_dma_ops.map_single = pci_iommu_map_single;
	pci_dma_ops.unmap_single = pci_iommu_unmap_single;
	pci_dma_ops.map_sg = pci_iommu_map_sg;
	pci_dma_ops.unmap_sg = pci_iommu_unmap_sg;
	pci_dma_ops.dma_supported = pci_iommu_dma_supported;
}