summaryrefslogtreecommitdiff
path: root/arch/s390/hypfs/hypfs_diag.c
blob: 279b7bba4d430d8bfcd8e8f60a13f0c542521322 (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
// SPDX-License-Identifier: GPL-2.0
/*
 *    Hypervisor filesystem for Linux on s390. Diag 204 and 224
 *    implementation.
 *
 *    Copyright IBM Corp. 2006, 2008
 *    Author(s): Michael Holzheu <holzheu@de.ibm.com>
 */

#define KMSG_COMPONENT "hypfs"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/diag.h>
#include <asm/ebcdic.h>
#include "hypfs_diag.h"
#include "hypfs.h"

#define DBFS_D204_HDR_VERSION	0

static enum diag204_sc diag204_store_sc;	/* used subcode for store */
static enum diag204_format diag204_info_type;	/* used diag 204 data format */

static void *diag204_buf;		/* 4K aligned buffer for diag204 data */
static int diag204_buf_pages;		/* number of pages for diag204 data */

static struct dentry *dbfs_d204_file;

enum diag204_format diag204_get_info_type(void)
{
	return diag204_info_type;
}

static void diag204_set_info_type(enum diag204_format type)
{
	diag204_info_type = type;
}

/* Diagnose 204 functions */
/*
 * For the old diag subcode 4 with simple data format we have to use real
 * memory. If we use subcode 6 or 7 with extended data format, we can (and
 * should) use vmalloc, since we need a lot of memory in that case. Currently
 * up to 93 pages!
 */

static void diag204_free_buffer(void)
{
	vfree(diag204_buf);
	diag204_buf = NULL;
}

void *diag204_get_buffer(enum diag204_format fmt, int *pages)
{
	if (diag204_buf) {
		*pages = diag204_buf_pages;
		return diag204_buf;
	}
	if (fmt == DIAG204_INFO_SIMPLE) {
		*pages = 1;
	} else {/* DIAG204_INFO_EXT */
		*pages = diag204((unsigned long)DIAG204_SUBC_RSI |
				 (unsigned long)DIAG204_INFO_EXT, 0, NULL);
		if (*pages <= 0)
			return ERR_PTR(-EOPNOTSUPP);
	}
	diag204_buf = __vmalloc_node(array_size(*pages, PAGE_SIZE),
				     PAGE_SIZE, GFP_KERNEL, NUMA_NO_NODE,
				     __builtin_return_address(0));
	if (!diag204_buf)
		return ERR_PTR(-ENOMEM);
	diag204_buf_pages = *pages;
	return diag204_buf;
}

/*
 * diag204_probe() has to find out, which type of diagnose 204 implementation
 * we have on our machine. Currently there are three possible scanarios:
 *   - subcode 4   + simple data format (only one page)
 *   - subcode 4-6 + extended data format
 *   - subcode 4-7 + extended data format
 *
 * Subcode 5 is used to retrieve the size of the data, provided by subcodes
 * 6 and 7. Subcode 7 basically has the same function as subcode 6. In addition
 * to subcode 6 it provides also information about secondary cpus.
 * In order to get as much information as possible, we first try
 * subcode 7, then 6 and if both fail, we use subcode 4.
 */

static int diag204_probe(void)
{
	void *buf;
	int pages, rc;

	buf = diag204_get_buffer(DIAG204_INFO_EXT, &pages);
	if (!IS_ERR(buf)) {
		if (diag204((unsigned long)DIAG204_SUBC_STIB7 |
			    (unsigned long)DIAG204_INFO_EXT, pages, buf) >= 0) {
			diag204_store_sc = DIAG204_SUBC_STIB7;
			diag204_set_info_type(DIAG204_INFO_EXT);
			goto out;
		}
		if (diag204((unsigned long)DIAG204_SUBC_STIB6 |
			    (unsigned long)DIAG204_INFO_EXT, pages, buf) >= 0) {
			diag204_store_sc = DIAG204_SUBC_STIB6;
			diag204_set_info_type(DIAG204_INFO_EXT);
			goto out;
		}
		diag204_free_buffer();
	}

	/* subcodes 6 and 7 failed, now try subcode 4 */

	buf = diag204_get_buffer(DIAG204_INFO_SIMPLE, &pages);
	if (IS_ERR(buf)) {
		rc = PTR_ERR(buf);
		goto fail_alloc;
	}
	if (diag204((unsigned long)DIAG204_SUBC_STIB4 |
		    (unsigned long)DIAG204_INFO_SIMPLE, pages, buf) >= 0) {
		diag204_store_sc = DIAG204_SUBC_STIB4;
		diag204_set_info_type(DIAG204_INFO_SIMPLE);
		goto out;
	} else {
		rc = -EOPNOTSUPP;
		goto fail_store;
	}
out:
	rc = 0;
fail_store:
	diag204_free_buffer();
fail_alloc:
	return rc;
}

int diag204_store(void *buf, int pages)
{
	int rc;

	rc = diag204((unsigned long)diag204_store_sc |
		     (unsigned long)diag204_get_info_type(), pages, buf);
	return rc < 0 ? -EOPNOTSUPP : 0;
}

struct dbfs_d204_hdr {
	u64	len;		/* Length of d204 buffer without header */
	u16	version;	/* Version of header */
	u8	sc;		/* Used subcode */
	char	reserved[53];
} __attribute__ ((packed));

struct dbfs_d204 {
	struct dbfs_d204_hdr	hdr;	/* 64 byte header */
	char			buf[];	/* d204 buffer */
} __attribute__ ((packed));

static int dbfs_d204_create(void **data, void **data_free_ptr, size_t *size)
{
	struct dbfs_d204 *d204;
	int rc, buf_size;
	void *base;

	buf_size = PAGE_SIZE * (diag204_buf_pages + 1) + sizeof(d204->hdr);
	base = vzalloc(buf_size);
	if (!base)
		return -ENOMEM;
	d204 = PTR_ALIGN(base + sizeof(d204->hdr), PAGE_SIZE) - sizeof(d204->hdr);
	rc = diag204_store(d204->buf, diag204_buf_pages);
	if (rc) {
		vfree(base);
		return rc;
	}
	d204->hdr.version = DBFS_D204_HDR_VERSION;
	d204->hdr.len = PAGE_SIZE * diag204_buf_pages;
	d204->hdr.sc = diag204_store_sc;
	*data = d204;
	*data_free_ptr = base;
	*size = d204->hdr.len + sizeof(struct dbfs_d204_hdr);
	return 0;
}

static struct hypfs_dbfs_file dbfs_file_d204 = {
	.name		= "diag_204",
	.data_create	= dbfs_d204_create,
	.data_free	= vfree,
};

__init int hypfs_diag_init(void)
{
	int rc;

	if (diag204_probe()) {
		pr_info("The hardware system does not support hypfs\n");
		return -ENODATA;
	}

	if (diag204_get_info_type() == DIAG204_INFO_EXT)
		hypfs_dbfs_create_file(&dbfs_file_d204);

	rc = hypfs_diag_fs_init();
	if (rc) {
		pr_err("The hardware system does not provide all functions required by hypfs\n");
		debugfs_remove(dbfs_d204_file);
	}
	return rc;
}

void hypfs_diag_exit(void)
{
	debugfs_remove(dbfs_d204_file);
	hypfs_diag_fs_exit();
	diag204_free_buffer();
	hypfs_dbfs_remove_file(&dbfs_file_d204);
}