1 /*
2  * SPU core dump code
3  *
4  * (C) Copyright 2006 IBM Corp.
5  *
6  * Author: Dwayne Grant McConnell <decimal@us.ibm.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  */
22 
23 #include <linux/elf.h>
24 #include <linux/file.h>
25 #include <linux/fdtable.h>
26 #include <linux/fs.h>
27 #include <linux/gfp.h>
28 #include <linux/list.h>
29 #include <linux/syscalls.h>
30 
31 #include <asm/uaccess.h>
32 
33 #include "spufs.h"
34 
35 static ssize_t do_coredump_read(int num, struct spu_context *ctx, void *buffer,
36 				size_t size, loff_t *off)
37 {
38 	u64 data;
39 	int ret;
40 
41 	if (spufs_coredump_read[num].read)
42 		return spufs_coredump_read[num].read(ctx, buffer, size, off);
43 
44 	data = spufs_coredump_read[num].get(ctx);
45 	ret = snprintf(buffer, size, "0x%.16llx", data);
46 	if (ret >= size)
47 		return size;
48 	return ++ret; /* count trailing NULL */
49 }
50 
51 /*
52  * These are the only things you should do on a core-file: use only these
53  * functions to write out all the necessary info.
54  */
55 static int spufs_dump_write(struct file *file, const void *addr, int nr, loff_t *foffset)
56 {
57 	unsigned long limit = rlimit(RLIMIT_CORE);
58 	ssize_t written;
59 
60 	if (*foffset + nr > limit)
61 		return -EIO;
62 
63 	written = file->f_op->write(file, addr, nr, &file->f_pos);
64 	*foffset += written;
65 
66 	if (written != nr)
67 		return -EIO;
68 
69 	return 0;
70 }
71 
72 static int spufs_dump_align(struct file *file, char *buf, loff_t new_off,
73 			    loff_t *foffset)
74 {
75 	int rc, size;
76 
77 	size = min((loff_t)PAGE_SIZE, new_off - *foffset);
78 	memset(buf, 0, size);
79 
80 	rc = 0;
81 	while (rc == 0 && new_off > *foffset) {
82 		size = min((loff_t)PAGE_SIZE, new_off - *foffset);
83 		rc = spufs_dump_write(file, buf, size, foffset);
84 	}
85 
86 	return rc;
87 }
88 
89 static int spufs_ctx_note_size(struct spu_context *ctx, int dfd)
90 {
91 	int i, sz, total = 0;
92 	char *name;
93 	char fullname[80];
94 
95 	for (i = 0; spufs_coredump_read[i].name != NULL; i++) {
96 		name = spufs_coredump_read[i].name;
97 		sz = spufs_coredump_read[i].size;
98 
99 		sprintf(fullname, "SPU/%d/%s", dfd, name);
100 
101 		total += sizeof(struct elf_note);
102 		total += roundup(strlen(fullname) + 1, 4);
103 		total += roundup(sz, 4);
104 	}
105 
106 	return total;
107 }
108 
109 /*
110  * The additional architecture-specific notes for Cell are various
111  * context files in the spu context.
112  *
113  * This function iterates over all open file descriptors and sees
114  * if they are a directory in spufs.  In that case we use spufs
115  * internal functionality to dump them without needing to actually
116  * open the files.
117  */
118 static struct spu_context *coredump_next_context(int *fd)
119 {
120 	struct fdtable *fdt = files_fdtable(current->files);
121 	struct file *file;
122 	struct spu_context *ctx = NULL;
123 
124 	for (; *fd < fdt->max_fds; (*fd)++) {
125 		if (!FD_ISSET(*fd, fdt->open_fds))
126 			continue;
127 
128 		file = fcheck(*fd);
129 
130 		if (!file || file->f_op != &spufs_context_fops)
131 			continue;
132 
133 		ctx = SPUFS_I(file->f_dentry->d_inode)->i_ctx;
134 		if (ctx->flags & SPU_CREATE_NOSCHED)
135 			continue;
136 
137 		break;
138 	}
139 
140 	return ctx;
141 }
142 
143 int spufs_coredump_extra_notes_size(void)
144 {
145 	struct spu_context *ctx;
146 	int size = 0, rc, fd;
147 
148 	fd = 0;
149 	while ((ctx = coredump_next_context(&fd)) != NULL) {
150 		rc = spu_acquire_saved(ctx);
151 		if (rc)
152 			break;
153 		rc = spufs_ctx_note_size(ctx, fd);
154 		spu_release_saved(ctx);
155 		if (rc < 0)
156 			break;
157 
158 		size += rc;
159 
160 		/* start searching the next fd next time */
161 		fd++;
162 	}
163 
164 	return size;
165 }
166 
167 static int spufs_arch_write_note(struct spu_context *ctx, int i,
168 				  struct file *file, int dfd, loff_t *foffset)
169 {
170 	loff_t pos = 0;
171 	int sz, rc, nread, total = 0;
172 	const int bufsz = PAGE_SIZE;
173 	char *name;
174 	char fullname[80], *buf;
175 	struct elf_note en;
176 
177 	buf = (void *)get_zeroed_page(GFP_KERNEL);
178 	if (!buf)
179 		return -ENOMEM;
180 
181 	name = spufs_coredump_read[i].name;
182 	sz = spufs_coredump_read[i].size;
183 
184 	sprintf(fullname, "SPU/%d/%s", dfd, name);
185 	en.n_namesz = strlen(fullname) + 1;
186 	en.n_descsz = sz;
187 	en.n_type = NT_SPU;
188 
189 	rc = spufs_dump_write(file, &en, sizeof(en), foffset);
190 	if (rc)
191 		goto out;
192 
193 	rc = spufs_dump_write(file, fullname, en.n_namesz, foffset);
194 	if (rc)
195 		goto out;
196 
197 	rc = spufs_dump_align(file, buf, roundup(*foffset, 4), foffset);
198 	if (rc)
199 		goto out;
200 
201 	do {
202 		nread = do_coredump_read(i, ctx, buf, bufsz, &pos);
203 		if (nread > 0) {
204 			rc = spufs_dump_write(file, buf, nread, foffset);
205 			if (rc)
206 				goto out;
207 			total += nread;
208 		}
209 	} while (nread == bufsz && total < sz);
210 
211 	if (nread < 0) {
212 		rc = nread;
213 		goto out;
214 	}
215 
216 	rc = spufs_dump_align(file, buf, roundup(*foffset - total + sz, 4),
217 			      foffset);
218 
219 out:
220 	free_page((unsigned long)buf);
221 	return rc;
222 }
223 
224 int spufs_coredump_extra_notes_write(struct file *file, loff_t *foffset)
225 {
226 	struct spu_context *ctx;
227 	int fd, j, rc;
228 
229 	fd = 0;
230 	while ((ctx = coredump_next_context(&fd)) != NULL) {
231 		rc = spu_acquire_saved(ctx);
232 		if (rc)
233 			return rc;
234 
235 		for (j = 0; spufs_coredump_read[j].name != NULL; j++) {
236 			rc = spufs_arch_write_note(ctx, j, file, fd, foffset);
237 			if (rc) {
238 				spu_release_saved(ctx);
239 				return rc;
240 			}
241 		}
242 
243 		spu_release_saved(ctx);
244 
245 		/* start searching the next fd next time */
246 		fd++;
247 	}
248 
249 	return 0;
250 }
251