diff options
-rw-r--r-- | drivers/char/mspec.c | 69 |
1 files changed, 48 insertions, 21 deletions
diff --git a/drivers/char/mspec.c b/drivers/char/mspec.c index c08a4152ee8f..049a46cc9f87 100644 --- a/drivers/char/mspec.c +++ b/drivers/char/mspec.c | |||
@@ -67,7 +67,7 @@ | |||
67 | /* | 67 | /* |
68 | * Page types allocated by the device. | 68 | * Page types allocated by the device. |
69 | */ | 69 | */ |
70 | enum { | 70 | enum mspec_page_type { |
71 | MSPEC_FETCHOP = 1, | 71 | MSPEC_FETCHOP = 1, |
72 | MSPEC_CACHED, | 72 | MSPEC_CACHED, |
73 | MSPEC_UNCACHED | 73 | MSPEC_UNCACHED |
@@ -83,15 +83,25 @@ static int is_sn2; | |||
83 | * One of these structures is allocated when an mspec region is mmaped. The | 83 | * One of these structures is allocated when an mspec region is mmaped. The |
84 | * structure is pointed to by the vma->vm_private_data field in the vma struct. | 84 | * structure is pointed to by the vma->vm_private_data field in the vma struct. |
85 | * This structure is used to record the addresses of the mspec pages. | 85 | * This structure is used to record the addresses of the mspec pages. |
86 | * This structure is shared by all vma's that are split off from the | ||
87 | * original vma when split_vma()'s are done. | ||
88 | * | ||
89 | * The refcnt is incremented atomically because mm->mmap_sem does not | ||
90 | * protect in fork case where multiple tasks share the vma_data. | ||
86 | */ | 91 | */ |
87 | struct vma_data { | 92 | struct vma_data { |
88 | atomic_t refcnt; /* Number of vmas sharing the data. */ | 93 | atomic_t refcnt; /* Number of vmas sharing the data. */ |
89 | spinlock_t lock; /* Serialize access to the vma. */ | 94 | spinlock_t lock; /* Serialize access to this structure. */ |
90 | int count; /* Number of pages allocated. */ | 95 | int count; /* Number of pages allocated. */ |
91 | int type; /* Type of pages allocated. */ | 96 | enum mspec_page_type type; /* Type of pages allocated. */ |
97 | int flags; /* See VMD_xxx below. */ | ||
98 | unsigned long vm_start; /* Original (unsplit) base. */ | ||
99 | unsigned long vm_end; /* Original (unsplit) end. */ | ||
92 | unsigned long maddr[0]; /* Array of MSPEC addresses. */ | 100 | unsigned long maddr[0]; /* Array of MSPEC addresses. */ |
93 | }; | 101 | }; |
94 | 102 | ||
103 | #define VMD_VMALLOCED 0x1 /* vmalloc'd rather than kmalloc'd */ | ||
104 | |||
95 | /* used on shub2 to clear FOP cache in the HUB */ | 105 | /* used on shub2 to clear FOP cache in the HUB */ |
96 | static unsigned long scratch_page[MAX_NUMNODES]; | 106 | static unsigned long scratch_page[MAX_NUMNODES]; |
97 | #define SH2_AMO_CACHE_ENTRIES 4 | 107 | #define SH2_AMO_CACHE_ENTRIES 4 |
@@ -129,8 +139,8 @@ mspec_zero_block(unsigned long addr, int len) | |||
129 | * mspec_open | 139 | * mspec_open |
130 | * | 140 | * |
131 | * Called when a device mapping is created by a means other than mmap | 141 | * Called when a device mapping is created by a means other than mmap |
132 | * (via fork, etc.). Increments the reference count on the underlying | 142 | * (via fork, munmap, etc.). Increments the reference count on the |
133 | * mspec data so it is not freed prematurely. | 143 | * underlying mspec data so it is not freed prematurely. |
134 | */ | 144 | */ |
135 | static void | 145 | static void |
136 | mspec_open(struct vm_area_struct *vma) | 146 | mspec_open(struct vm_area_struct *vma) |
@@ -151,34 +161,44 @@ static void | |||
151 | mspec_close(struct vm_area_struct *vma) | 161 | mspec_close(struct vm_area_struct *vma) |
152 | { | 162 | { |
153 | struct vma_data *vdata; | 163 | struct vma_data *vdata; |
154 | int i, pages, result, vdata_size; | 164 | int index, last_index, result; |
165 | unsigned long my_page; | ||
155 | 166 | ||
156 | vdata = vma->vm_private_data; | 167 | vdata = vma->vm_private_data; |
157 | if (!atomic_dec_and_test(&vdata->refcnt)) | ||
158 | return; | ||
159 | 168 | ||
160 | pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; | 169 | BUG_ON(vma->vm_start < vdata->vm_start || vma->vm_end > vdata->vm_end); |
161 | vdata_size = sizeof(struct vma_data) + pages * sizeof(long); | 170 | |
162 | for (i = 0; i < pages; i++) { | 171 | spin_lock(&vdata->lock); |
163 | if (vdata->maddr[i] == 0) | 172 | index = (vma->vm_start - vdata->vm_start) >> PAGE_SHIFT; |
173 | last_index = (vma->vm_end - vdata->vm_start) >> PAGE_SHIFT; | ||
174 | for (; index < last_index; index++) { | ||
175 | if (vdata->maddr[index] == 0) | ||
164 | continue; | 176 | continue; |
165 | /* | 177 | /* |
166 | * Clear the page before sticking it back | 178 | * Clear the page before sticking it back |
167 | * into the pool. | 179 | * into the pool. |
168 | */ | 180 | */ |
169 | result = mspec_zero_block(vdata->maddr[i], PAGE_SIZE); | 181 | my_page = vdata->maddr[index]; |
182 | vdata->maddr[index] = 0; | ||
183 | spin_unlock(&vdata->lock); | ||
184 | result = mspec_zero_block(my_page, PAGE_SIZE); | ||
170 | if (!result) | 185 | if (!result) |
171 | uncached_free_page(vdata->maddr[i]); | 186 | uncached_free_page(my_page); |
172 | else | 187 | else |
173 | printk(KERN_WARNING "mspec_close(): " | 188 | printk(KERN_WARNING "mspec_close(): " |
174 | "failed to zero page %i\n", | 189 | "failed to zero page %i\n", |
175 | result); | 190 | result); |
191 | spin_lock(&vdata->lock); | ||
176 | } | 192 | } |
193 | spin_unlock(&vdata->lock); | ||
177 | 194 | ||
178 | if (vdata_size <= PAGE_SIZE) | 195 | if (!atomic_dec_and_test(&vdata->refcnt)) |
179 | kfree(vdata); | 196 | return; |
180 | else | 197 | |
198 | if (vdata->flags & VMD_VMALLOCED) | ||
181 | vfree(vdata); | 199 | vfree(vdata); |
200 | else | ||
201 | kfree(vdata); | ||
182 | } | 202 | } |
183 | 203 | ||
184 | 204 | ||
@@ -195,7 +215,8 @@ mspec_nopfn(struct vm_area_struct *vma, unsigned long address) | |||
195 | int index; | 215 | int index; |
196 | struct vma_data *vdata = vma->vm_private_data; | 216 | struct vma_data *vdata = vma->vm_private_data; |
197 | 217 | ||
198 | index = (address - vma->vm_start) >> PAGE_SHIFT; | 218 | BUG_ON(address < vdata->vm_start || address >= vdata->vm_end); |
219 | index = (address - vdata->vm_start) >> PAGE_SHIFT; | ||
199 | maddr = (volatile unsigned long) vdata->maddr[index]; | 220 | maddr = (volatile unsigned long) vdata->maddr[index]; |
200 | if (maddr == 0) { | 221 | if (maddr == 0) { |
201 | maddr = uncached_alloc_page(numa_node_id()); | 222 | maddr = uncached_alloc_page(numa_node_id()); |
@@ -237,10 +258,11 @@ static struct vm_operations_struct mspec_vm_ops = { | |||
237 | * underlying pages. | 258 | * underlying pages. |
238 | */ | 259 | */ |
239 | static int | 260 | static int |
240 | mspec_mmap(struct file *file, struct vm_area_struct *vma, int type) | 261 | mspec_mmap(struct file *file, struct vm_area_struct *vma, |
262 | enum mspec_page_type type) | ||
241 | { | 263 | { |
242 | struct vma_data *vdata; | 264 | struct vma_data *vdata; |
243 | int pages, vdata_size; | 265 | int pages, vdata_size, flags = 0; |
244 | 266 | ||
245 | if (vma->vm_pgoff != 0) | 267 | if (vma->vm_pgoff != 0) |
246 | return -EINVAL; | 268 | return -EINVAL; |
@@ -255,12 +277,17 @@ mspec_mmap(struct file *file, struct vm_area_struct *vma, int type) | |||
255 | vdata_size = sizeof(struct vma_data) + pages * sizeof(long); | 277 | vdata_size = sizeof(struct vma_data) + pages * sizeof(long); |
256 | if (vdata_size <= PAGE_SIZE) | 278 | if (vdata_size <= PAGE_SIZE) |
257 | vdata = kmalloc(vdata_size, GFP_KERNEL); | 279 | vdata = kmalloc(vdata_size, GFP_KERNEL); |
258 | else | 280 | else { |
259 | vdata = vmalloc(vdata_size); | 281 | vdata = vmalloc(vdata_size); |
282 | flags = VMD_VMALLOCED; | ||
283 | } | ||
260 | if (!vdata) | 284 | if (!vdata) |
261 | return -ENOMEM; | 285 | return -ENOMEM; |
262 | memset(vdata, 0, vdata_size); | 286 | memset(vdata, 0, vdata_size); |
263 | 287 | ||
288 | vdata->vm_start = vma->vm_start; | ||
289 | vdata->vm_end = vma->vm_end; | ||
290 | vdata->flags = flags; | ||
264 | vdata->type = type; | 291 | vdata->type = type; |
265 | spin_lock_init(&vdata->lock); | 292 | spin_lock_init(&vdata->lock); |
266 | vdata->refcnt = ATOMIC_INIT(1); | 293 | vdata->refcnt = ATOMIC_INIT(1); |