Quellcode durchsuchen

[vmware] Add GuestRPC mechanism

Use the VMware backdoor I/O port to access the GuestRPC mechanism.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown vor 13 Jahren
Ursprung
Commit
3a5823a126

+ 1
- 0
src/arch/i386/Makefile Datei anzeigen

@@ -85,6 +85,7 @@ SRCDIRS		+= arch/i386/interface/pcbios
85 85
 SRCDIRS		+= arch/i386/interface/pxe
86 86
 SRCDIRS		+= arch/i386/interface/pxeparent
87 87
 SRCDIRS 	+= arch/i386/interface/syslinux
88
+SRCDIRS		+= arch/i386/interface/vmware
88 89
 SRCDIRS		+= arch/i386/hci/commands
89 90
 
90 91
 # The various xxx_loader.c files are #included into core/loader.c and

+ 2
- 0
src/arch/i386/include/bits/errfile.h Datei anzeigen

@@ -16,6 +16,8 @@ FILE_LICENCE ( GPL2_OR_LATER );
16 16
 #define ERRFILE_int13		( ERRFILE_ARCH | ERRFILE_CORE | 0x00050000 )
17 17
 #define ERRFILE_pxeparent	( ERRFILE_ARCH | ERRFILE_CORE | 0x00060000 )
18 18
 #define ERRFILE_runtime		( ERRFILE_ARCH | ERRFILE_CORE | 0x00070000 )
19
+#define ERRFILE_vmware		( ERRFILE_ARCH | ERRFILE_CORE | 0x00080000 )
20
+#define ERRFILE_guestrpc	( ERRFILE_ARCH | ERRFILE_CORE | 0x00090000 )
19 21
 
20 22
 #define ERRFILE_bootsector     ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 )
21 23
 #define ERRFILE_bzimage	       ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 )

+ 68
- 0
src/arch/i386/include/ipxe/guestrpc.h Datei anzeigen

@@ -0,0 +1,68 @@
1
+#ifndef _IPXE_GUESTRPC_H
2
+#define _IPXE_GUESTRPC_H
3
+
4
+/** @file
5
+ *
6
+ * VMware GuestRPC mechanism
7
+ *
8
+ */
9
+
10
+FILE_LICENCE ( GPL2_OR_LATER );
11
+
12
+#include <stdint.h>
13
+#include <ipxe/vmware.h>
14
+
15
+/** GuestRPC magic number */
16
+#define GUESTRPC_MAGIC 0x49435052 /* "RPCI" */
17
+
18
+/** Open RPC channel */
19
+#define GUESTRPC_OPEN 0x00
20
+
21
+/** Open RPC channel success status */
22
+#define GUESTRPC_OPEN_SUCCESS 0x00010000
23
+
24
+/** Send RPC command length */
25
+#define GUESTRPC_COMMAND_LEN 0x01
26
+
27
+/** Send RPC command length success status */
28
+#define GUESTRPC_COMMAND_LEN_SUCCESS 0x00810000
29
+
30
+/** Send RPC command data */
31
+#define GUESTRPC_COMMAND_DATA 0x02
32
+
33
+/** Send RPC command data success status */
34
+#define GUESTRPC_COMMAND_DATA_SUCCESS 0x00010000
35
+
36
+/** Receive RPC reply length */
37
+#define GUESTRPC_REPLY_LEN 0x03
38
+
39
+/** Receive RPC reply length success status */
40
+#define GUESTRPC_REPLY_LEN_SUCCESS 0x00830000
41
+
42
+/** Receive RPC reply data */
43
+#define GUESTRPC_REPLY_DATA 0x04
44
+
45
+/** Receive RPC reply data success status */
46
+#define GUESTRPC_REPLY_DATA_SUCCESS 0x00010000
47
+
48
+/** Finish receiving RPC reply */
49
+#define GUESTRPC_REPLY_FINISH 0x05
50
+
51
+/** Finish receiving RPC reply success status */
52
+#define GUESTRPC_REPLY_FINISH_SUCCESS 0x00010000
53
+
54
+/** Close RPC channel */
55
+#define GUESTRPC_CLOSE 0x06
56
+
57
+/** Close RPC channel success status */
58
+#define GUESTRPC_CLOSE_SUCCESS 0x00010000
59
+
60
+/** RPC command success status */
61
+#define GUESTRPC_SUCCESS 0x2031 /* "1 " */
62
+
63
+extern int guestrpc_open ( void );
64
+extern void guestrpc_close ( int channel );
65
+extern int guestrpc_command ( int channel, const char *command, char *reply,
66
+			      size_t reply_len );
67
+
68
+#endif /* _IPXE_GUESTRPC_H */

+ 81
- 0
src/arch/i386/include/ipxe/vmware.h Datei anzeigen

@@ -0,0 +1,81 @@
1
+#ifndef _IPXE_VMWARE_H
2
+#define _IPXE_VMWARE_H
3
+
4
+/** @file
5
+ *
6
+ * VMware backdoor mechanism
7
+ *
8
+ */
9
+
10
+FILE_LICENCE ( GPL2_OR_LATER );
11
+
12
+#include <stdint.h>
13
+
14
+/** VMware backdoor I/O port */
15
+#define VMW_PORT 0x5658
16
+
17
+/** VMware backdoor magic value */
18
+#define VMW_MAGIC 0x564d5868 /* "VMXh" */
19
+
20
+/** VMware backdoor magic instruction */
21
+#define VMW_BACKDOOR "inl %%dx, %%eax"
22
+
23
+/** Get VMware version */
24
+#define VMW_CMD_GET_VERSION 0x0a
25
+
26
+/** Issue GuestRPC command */
27
+#define VMW_CMD_GUESTRPC 0x1e
28
+
29
+/**
30
+ * Get VMware version
31
+ *
32
+ * @ret version		VMware version(?)
33
+ * @ret magic		VMware magic number, if present
34
+ * @ret product_type	VMware product type
35
+ */
36
+static inline __attribute__ (( always_inline )) void
37
+vmware_cmd_get_version ( uint32_t *version, uint32_t *magic,
38
+			 uint32_t *product_type ) {
39
+	uint32_t discard_d;
40
+
41
+	/* Perform backdoor call */
42
+	__asm__ __volatile__ ( VMW_BACKDOOR
43
+			       : "=a" ( *version ), "=b" ( *magic ),
44
+				 "=c" ( *product_type ), "=d" ( discard_d )
45
+			       : "0" ( VMW_MAGIC ), "1" ( 0 ),
46
+				 "2" ( VMW_CMD_GET_VERSION ),
47
+				 "3" ( VMW_PORT ) );
48
+}
49
+
50
+/**
51
+ * Issue GuestRPC command
52
+ *
53
+ * @v channel		Channel number
54
+ * @v subcommand	GuestRPC subcommand
55
+ * @v parameter		Subcommand-specific parameter
56
+ * @ret edxhi		Subcommand-specific result
57
+ * @ret ebx		Subcommand-specific result
58
+ * @ret status		Command status
59
+ */
60
+static inline __attribute__ (( always_inline )) uint32_t
61
+vmware_cmd_guestrpc ( int channel, uint16_t subcommand, uint32_t parameter,
62
+		      uint16_t *edxhi, uint32_t *ebx ) {
63
+	uint32_t discard_a;
64
+	uint32_t status;
65
+	uint32_t edx;
66
+
67
+	/* Perform backdoor call */
68
+	__asm__ __volatile__ ( VMW_BACKDOOR
69
+			       : "=a" ( discard_a ), "=b" ( *ebx ),
70
+				 "=c" ( status ), "=d" ( edx )
71
+			       : "0" ( VMW_MAGIC ), "1" ( parameter ),
72
+				 "2" ( VMW_CMD_GUESTRPC | ( subcommand << 16 )),
73
+				 "3" ( VMW_PORT | ( channel << 16 ) ) );
74
+	*edxhi = ( edx >> 16 );
75
+
76
+	return status;
77
+}
78
+
79
+extern int vmware_present ( void );
80
+
81
+#endif /* _IPXE_VMWARE_H */

+ 326
- 0
src/arch/i386/interface/vmware/guestrpc.c Datei anzeigen

@@ -0,0 +1,326 @@
1
+/*
2
+ * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
3
+ *
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License as
6
+ * published by the Free Software Foundation; either version 2 of the
7
+ * License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful, but
10
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
+ * General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
+ */
18
+
19
+FILE_LICENCE ( GPL2_OR_LATER );
20
+
21
+/** @file
22
+ *
23
+ * VMware GuestRPC mechanism
24
+ *
25
+ */
26
+
27
+#include <stdint.h>
28
+#include <string.h>
29
+#include <errno.h>
30
+#include <assert.h>
31
+#include <ipxe/vmware.h>
32
+#include <ipxe/guestrpc.h>
33
+
34
+/* Disambiguate the various error causes */
35
+#define EPROTO_OPEN __einfo_error ( EINFO_EPROTO_OPEN )
36
+#define EINFO_EPROTO_OPEN \
37
+	__einfo_uniqify ( EINFO_EPROTO, 0x00, "GuestRPC open failed" )
38
+#define EPROTO_COMMAND_LEN __einfo_error ( EINFO_EPROTO_COMMAND_LEN )
39
+#define EINFO_EPROTO_COMMAND_LEN \
40
+	__einfo_uniqify ( EINFO_EPROTO, 0x01, "GuestRPC command length failed" )
41
+#define EPROTO_COMMAND_DATA __einfo_error ( EINFO_EPROTO_COMMAND_DATA )
42
+#define EINFO_EPROTO_COMMAND_DATA \
43
+	__einfo_uniqify ( EINFO_EPROTO, 0x02, "GuestRPC command data failed" )
44
+#define EPROTO_REPLY_LEN __einfo_error ( EINFO_EPROTO_REPLY_LEN )
45
+#define EINFO_EPROTO_REPLY_LEN \
46
+	__einfo_uniqify ( EINFO_EPROTO, 0x03, "GuestRPC reply length failed" )
47
+#define EPROTO_REPLY_DATA __einfo_error ( EINFO_EPROTO_REPLY_DATA )
48
+#define EINFO_EPROTO_REPLY_DATA \
49
+	__einfo_uniqify ( EINFO_EPROTO, 0x04, "GuestRPC reply data failed" )
50
+#define EPROTO_REPLY_FINISH __einfo_error ( EINFO_EPROTO_REPLY_FINISH )
51
+#define EINFO_EPROTO_REPLY_FINISH \
52
+	__einfo_uniqify ( EINFO_EPROTO, 0x05, "GuestRPC reply finish failed" )
53
+#define EPROTO_CLOSE __einfo_error ( EINFO_EPROTO_CLOSE )
54
+#define EINFO_EPROTO_CLOSE \
55
+	__einfo_uniqify ( EINFO_EPROTO, 0x06, "GuestRPC close failed" )
56
+
57
+/**
58
+ * Open GuestRPC channel
59
+ *
60
+ * @ret channel		Channel number, or negative error
61
+ */
62
+int guestrpc_open ( void ) {
63
+	uint16_t channel;
64
+	uint32_t discard_b;
65
+	uint32_t status;
66
+
67
+	/* Issue GuestRPC command */
68
+	status = vmware_cmd_guestrpc ( 0, GUESTRPC_OPEN, GUESTRPC_MAGIC,
69
+				       &channel, &discard_b );
70
+	if ( status != GUESTRPC_OPEN_SUCCESS ) {
71
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC open failed: status %08x\n",
72
+		       status );
73
+		return -EPROTO_OPEN;
74
+	}
75
+
76
+	DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d opened\n", channel );
77
+	return channel;
78
+}
79
+
80
+/**
81
+ * Send GuestRPC command length
82
+ *
83
+ * @v channel		Channel number
84
+ * @v len		Command length
85
+ * @ret rc		Return status code
86
+ */
87
+static int guestrpc_command_len ( int channel, size_t len ) {
88
+	uint16_t discard_d;
89
+	uint32_t discard_b;
90
+	uint32_t status;
91
+
92
+	/* Issue GuestRPC command */
93
+	status = vmware_cmd_guestrpc ( channel, GUESTRPC_COMMAND_LEN, len,
94
+				       &discard_d, &discard_b );
95
+	if ( status != GUESTRPC_COMMAND_LEN_SUCCESS ) {
96
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d send command "
97
+		       "length %zd failed: status %08x\n",
98
+		       channel, len, status );
99
+		return -EPROTO_COMMAND_LEN;
100
+	}
101
+
102
+	return 0;
103
+}
104
+
105
+/**
106
+ * Send GuestRPC command data
107
+ *
108
+ * @v channel		Channel number
109
+ * @v data		Command data
110
+ * @ret rc		Return status code
111
+ */
112
+static int guestrpc_command_data ( int channel, uint32_t data ) {
113
+	uint16_t discard_d;
114
+	uint32_t discard_b;
115
+	uint32_t status;
116
+
117
+	/* Issue GuestRPC command */
118
+	status = vmware_cmd_guestrpc ( channel, GUESTRPC_COMMAND_DATA, data,
119
+				       &discard_d, &discard_b );
120
+	if ( status != GUESTRPC_COMMAND_DATA_SUCCESS ) {
121
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d send command "
122
+		       "data %08x failed: status %08x\n",
123
+		       channel, data, status );
124
+		return -EPROTO_COMMAND_DATA;
125
+	}
126
+
127
+	return 0;
128
+}
129
+
130
+/**
131
+ * Receive GuestRPC reply length
132
+ *
133
+ * @v channel		Channel number
134
+ * @ret reply_id	Reply ID
135
+ * @ret len		Reply length, or negative error
136
+ */
137
+static int guestrpc_reply_len ( int channel, uint16_t *reply_id ) {
138
+	uint32_t len;
139
+	uint32_t status;
140
+
141
+	/* Issue GuestRPC command */
142
+	status = vmware_cmd_guestrpc ( channel, GUESTRPC_REPLY_LEN, 0,
143
+				       reply_id, &len );
144
+	if ( status != GUESTRPC_REPLY_LEN_SUCCESS ) {
145
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d receive reply "
146
+		       "length failed: status %08x\n", channel, status );
147
+		return -EPROTO_REPLY_LEN;
148
+	}
149
+
150
+	return len;
151
+}
152
+
153
+/**
154
+ * Receive GuestRPC reply data
155
+ *
156
+ * @v channel		Channel number
157
+ * @v reply_id		Reply ID
158
+ * @ret data		Reply data
159
+ * @ret rc		Return status code
160
+ */
161
+static int guestrpc_reply_data ( int channel, uint16_t reply_id,
162
+				 uint32_t *data ) {
163
+	uint16_t discard_d;
164
+	uint32_t status;
165
+
166
+	/* Issue GuestRPC command */
167
+	status = vmware_cmd_guestrpc ( channel, GUESTRPC_REPLY_DATA, reply_id,
168
+				       &discard_d, data );
169
+	if ( status != GUESTRPC_REPLY_DATA_SUCCESS ) {
170
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d receive reply "
171
+		       "%d data failed: status %08x\n",
172
+		       channel, reply_id, status );
173
+		return -EPROTO_REPLY_DATA;
174
+	}
175
+
176
+	return 0;
177
+}
178
+
179
+/**
180
+ * Finish receiving GuestRPC reply
181
+ *
182
+ * @v channel		Channel number
183
+ * @v reply_id		Reply ID
184
+ * @ret rc		Return status code
185
+ */
186
+static int guestrpc_reply_finish ( int channel, uint16_t reply_id ) {
187
+	uint16_t discard_d;
188
+	uint32_t discard_b;
189
+	uint32_t status;
190
+
191
+	/* Issue GuestRPC command */
192
+	status = vmware_cmd_guestrpc ( channel, GUESTRPC_REPLY_FINISH, reply_id,
193
+				       &discard_d, &discard_b );
194
+	if ( status != GUESTRPC_REPLY_FINISH_SUCCESS ) {
195
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d finish reply %d "
196
+		       "failed: status %08x\n", channel, reply_id, status );
197
+		return -EPROTO_REPLY_FINISH;
198
+	}
199
+
200
+	return 0;
201
+}
202
+
203
+/**
204
+ * Close GuestRPC channel
205
+ *
206
+ * @v channel		Channel number
207
+ */
208
+void guestrpc_close ( int channel ) {
209
+	uint16_t discard_d;
210
+	uint32_t discard_b;
211
+	uint32_t status;
212
+
213
+	/* Issue GuestRPC command */
214
+	status = vmware_cmd_guestrpc ( channel, GUESTRPC_CLOSE, 0,
215
+				       &discard_d, &discard_b );
216
+	if ( status != GUESTRPC_CLOSE_SUCCESS ) {
217
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d close failed: "
218
+		       "status %08x\n", channel, status );
219
+		return;
220
+	}
221
+
222
+	DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d closed\n", channel );
223
+}
224
+
225
+/**
226
+ * Issue GuestRPC command
227
+ *
228
+ * @v channel		Channel number
229
+ * @v command		Command
230
+ * @v reply		Reply buffer
231
+ * @v reply_len		Length of reply buffer
232
+ * @ret len		Length of reply, or negative error
233
+ *
234
+ * The actual length of the reply will be returned even if the buffer
235
+ * was too small.
236
+ */
237
+int guestrpc_command ( int channel, const char *command, char *reply,
238
+		       size_t reply_len ) {
239
+	const uint8_t *command_bytes = ( ( const void * ) command );
240
+	uint8_t *reply_bytes = ( ( void * ) reply );
241
+	size_t command_len = strlen ( command );
242
+	int orig_reply_len = reply_len;
243
+	uint16_t status;
244
+	uint8_t *status_bytes = ( ( void * ) &status );
245
+	size_t status_len = sizeof ( status );
246
+	uint32_t data;
247
+	uint16_t reply_id;
248
+	int len;
249
+	int remaining;
250
+	unsigned int i;
251
+	int rc;
252
+
253
+	DBGC2 ( GUESTRPC_MAGIC, "GuestRPC channel %d issuing command:\n",
254
+		channel );
255
+	DBGC2_HDA ( GUESTRPC_MAGIC, 0, command, command_len );
256
+
257
+	/* Sanity check */
258
+	assert ( ( reply != NULL ) || ( reply_len == 0 ) );
259
+
260
+	/* Send command length */
261
+	if ( ( rc = guestrpc_command_len ( channel, command_len ) ) < 0 )
262
+		return rc;
263
+
264
+	/* Send command data */
265
+	while ( command_len ) {
266
+		data = 0;
267
+		for ( i = sizeof ( data ) ; i ; i-- ) {
268
+			if ( command_len ) {
269
+				data = ( ( data & ~0xff ) |
270
+					 *(command_bytes++) );
271
+				command_len--;
272
+			}
273
+			data = ( ( data << 24 ) | ( data >> 8 ) );
274
+		}
275
+		if ( ( rc = guestrpc_command_data ( channel, data ) ) < 0 )
276
+			return rc;
277
+	}
278
+
279
+	/* Receive reply length */
280
+	if ( ( len = guestrpc_reply_len ( channel, &reply_id ) ) < 0 ) {
281
+		rc = len;
282
+		return rc;
283
+	}
284
+
285
+	/* Receive reply */
286
+	for ( remaining = len ; remaining > 0 ; remaining -= sizeof ( data ) ) {
287
+		if ( ( rc = guestrpc_reply_data ( channel, reply_id,
288
+						  &data ) ) < 0 ) {
289
+			return rc;
290
+		}
291
+		for ( i = sizeof ( data ) ; i ; i-- ) {
292
+			if ( status_len ) {
293
+				*(status_bytes++) = ( data & 0xff );
294
+				status_len--;
295
+			} else if ( reply_len ) {
296
+				*(reply_bytes++) = ( data & 0xff );
297
+				reply_len--;
298
+			}
299
+			data = ( ( data << 24 ) | ( data >> 8 ) );
300
+		}
301
+	}
302
+
303
+	/* Finish receiving RPC reply */
304
+	if ( ( rc = guestrpc_reply_finish ( channel, reply_id ) ) < 0 )
305
+		return rc;
306
+
307
+	DBGC2 ( GUESTRPC_MAGIC, "GuestRPC channel %d received reply (id %d, "
308
+		"length %d):\n", channel, reply_id, len );
309
+	DBGC2_HDA ( GUESTRPC_MAGIC, 0, &status, sizeof ( status ) );
310
+	DBGC2_HDA ( GUESTRPC_MAGIC, sizeof ( status ), reply,
311
+		    ( ( len < orig_reply_len ) ? len : orig_reply_len ) );
312
+
313
+	/* Check reply status */
314
+	if ( status != GUESTRPC_SUCCESS ) {
315
+		DBGC ( GUESTRPC_MAGIC, "GuestRPC channel %d command failed "
316
+		       "(status %04x, reply id %d, reply length %d):\n",
317
+		       channel, status, reply_id, len );
318
+		DBGC_HDA ( GUESTRPC_MAGIC, 0, command, command_len );
319
+		DBGC_HDA ( GUESTRPC_MAGIC, 0, &status, sizeof ( status ) );
320
+		DBGC_HDA ( GUESTRPC_MAGIC, sizeof ( status ), reply,
321
+			   ( ( len < orig_reply_len ) ? len : orig_reply_len ));
322
+		return -EIO;
323
+	}
324
+
325
+	return len;
326
+}

+ 57
- 0
src/arch/i386/interface/vmware/vmware.c Datei anzeigen

@@ -0,0 +1,57 @@
1
+/*
2
+ * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
3
+ *
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License as
6
+ * published by the Free Software Foundation; either version 2 of the
7
+ * License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful, but
10
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
+ * General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
+ */
18
+
19
+FILE_LICENCE ( GPL2_OR_LATER );
20
+
21
+/** @file
22
+ *
23
+ * VMware backdoor mechanism
24
+ *
25
+ * Based on the unofficial documentation at
26
+ *
27
+ *   http://sites.google.com/site/chitchatvmback/backdoor
28
+ *
29
+ */
30
+
31
+#include <stdint.h>
32
+#include <errno.h>
33
+#include <ipxe/vmware.h>
34
+
35
+/**
36
+ * Detect VMware presence
37
+ *
38
+ * @ret rc		Return status code
39
+ */
40
+int vmware_present ( void ) {
41
+	uint32_t version;
42
+	uint32_t magic;
43
+	uint32_t product_type;
44
+
45
+	/* Perform backdoor call */
46
+	vmware_cmd_get_version ( &version, &magic, &product_type );
47
+
48
+	/* Check for VMware presence */
49
+	if ( magic != VMW_MAGIC ) {
50
+		DBGC ( VMW_MAGIC, "VMware not present\n" );
51
+		return -ENOENT;
52
+	}
53
+
54
+	DBGC ( VMW_MAGIC, "VMware product type %04x version %08x detected\n",
55
+	       product_type, version );
56
+	return 0;
57
+}

Laden…
Abbrechen
Speichern