diff options
author | Ralf Hoppe <rhoppe@de.ibm.com> | 2013-04-08 03:52:57 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2014-09-25 04:52:02 -0400 |
commit | 8f933b1043e1e51f4776fc1ffe86752c7785fd4e (patch) | |
tree | 9549e4659cd027279b213bab4e735e1ccd5b15d7 | |
parent | ea61a579ab87f1620b14777afc32cf3827f07bc8 (diff) |
s390/hmcdrv: HMC drive CD/DVD access
This device driver allows accessing a HMC drive CD/DVD-ROM.
It can be used in a LPAR and z/VM environment.
Reviewed-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Ralf Hoppe <rhoppe@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
-rw-r--r-- | arch/s390/include/asm/irq.h | 1 | ||||
-rw-r--r-- | arch/s390/kernel/irq.c | 1 | ||||
-rw-r--r-- | drivers/s390/char/Kconfig | 13 | ||||
-rw-r--r-- | drivers/s390/char/Makefile | 3 | ||||
-rw-r--r-- | drivers/s390/char/diag_ftp.c | 237 | ||||
-rw-r--r-- | drivers/s390/char/diag_ftp.h | 21 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_cache.c | 252 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_cache.h | 24 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_dev.c | 370 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_dev.h | 14 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_ftp.c | 343 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_ftp.h | 63 | ||||
-rw-r--r-- | drivers/s390/char/hmcdrv_mod.c | 64 | ||||
-rw-r--r-- | drivers/s390/char/sclp.h | 2 | ||||
-rw-r--r-- | drivers/s390/char/sclp_diag.h | 89 | ||||
-rw-r--r-- | drivers/s390/char/sclp_ftp.c | 275 | ||||
-rw-r--r-- | drivers/s390/char/sclp_ftp.h | 21 |
17 files changed, 1793 insertions, 0 deletions
diff --git a/arch/s390/include/asm/irq.h b/arch/s390/include/asm/irq.h index c4dd400a2791..e787cc1bff8f 100644 --- a/arch/s390/include/asm/irq.h +++ b/arch/s390/include/asm/irq.h | |||
@@ -51,6 +51,7 @@ enum interruption_class { | |||
51 | IRQEXT_CMS, | 51 | IRQEXT_CMS, |
52 | IRQEXT_CMC, | 52 | IRQEXT_CMC, |
53 | IRQEXT_CMR, | 53 | IRQEXT_CMR, |
54 | IRQEXT_FTP, | ||
54 | IRQIO_CIO, | 55 | IRQIO_CIO, |
55 | IRQIO_QAI, | 56 | IRQIO_QAI, |
56 | IRQIO_DAS, | 57 | IRQIO_DAS, |
diff --git a/arch/s390/kernel/irq.c b/arch/s390/kernel/irq.c index 8eb82443cfbd..051574ee5366 100644 --- a/arch/s390/kernel/irq.c +++ b/arch/s390/kernel/irq.c | |||
@@ -70,6 +70,7 @@ static const struct irq_class irqclass_sub_desc[NR_ARCH_IRQS] = { | |||
70 | {.irq = IRQEXT_CMS, .name = "CMS", .desc = "[EXT] CPU-Measurement: Sampling"}, | 70 | {.irq = IRQEXT_CMS, .name = "CMS", .desc = "[EXT] CPU-Measurement: Sampling"}, |
71 | {.irq = IRQEXT_CMC, .name = "CMC", .desc = "[EXT] CPU-Measurement: Counter"}, | 71 | {.irq = IRQEXT_CMC, .name = "CMC", .desc = "[EXT] CPU-Measurement: Counter"}, |
72 | {.irq = IRQEXT_CMR, .name = "CMR", .desc = "[EXT] CPU-Measurement: RI"}, | 72 | {.irq = IRQEXT_CMR, .name = "CMR", .desc = "[EXT] CPU-Measurement: RI"}, |
73 | {.irq = IRQEXT_FTP, .name = "FTP", .desc = "[EXT] HMC FTP Service"}, | ||
73 | {.irq = IRQIO_CIO, .name = "CIO", .desc = "[I/O] Common I/O Layer Interrupt"}, | 74 | {.irq = IRQIO_CIO, .name = "CIO", .desc = "[I/O] Common I/O Layer Interrupt"}, |
74 | {.irq = IRQIO_QAI, .name = "QAI", .desc = "[I/O] QDIO Adapter Interrupt"}, | 75 | {.irq = IRQIO_QAI, .name = "QAI", .desc = "[I/O] QDIO Adapter Interrupt"}, |
75 | {.irq = IRQIO_DAS, .name = "DAS", .desc = "[I/O] DASD"}, | 76 | {.irq = IRQIO_DAS, .name = "DAS", .desc = "[I/O] DASD"}, |
diff --git a/drivers/s390/char/Kconfig b/drivers/s390/char/Kconfig index 71bf959732fe..dc24ecfac2d1 100644 --- a/drivers/s390/char/Kconfig +++ b/drivers/s390/char/Kconfig | |||
@@ -102,6 +102,19 @@ config SCLP_ASYNC | |||
102 | want for inform other people about your kernel panics, | 102 | want for inform other people about your kernel panics, |
103 | need this feature and intend to run your kernel in LPAR. | 103 | need this feature and intend to run your kernel in LPAR. |
104 | 104 | ||
105 | config HMC_DRV | ||
106 | def_tristate m | ||
107 | prompt "Support for file transfers from HMC drive CD/DVD-ROM" | ||
108 | depends on 64BIT | ||
109 | select CRC16 | ||
110 | help | ||
111 | This option enables support for file transfers from a Hardware | ||
112 | Management Console (HMC) drive CD/DVD-ROM. It is available as a | ||
113 | module, called 'hmcdrv', and also as kernel built-in. There is one | ||
114 | optional parameter for this module: cachesize=N, which modifies the | ||
115 | transfer cache size from it's default value 0.5MB to N bytes. If N | ||
116 | is zero, then no caching is performed. | ||
117 | |||
105 | config S390_TAPE | 118 | config S390_TAPE |
106 | def_tristate m | 119 | def_tristate m |
107 | prompt "S/390 tape device support" | 120 | prompt "S/390 tape device support" |
diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile index 78b6ace7edcb..6fa9364d1c07 100644 --- a/drivers/s390/char/Makefile +++ b/drivers/s390/char/Makefile | |||
@@ -33,3 +33,6 @@ obj-$(CONFIG_S390_VMUR) += vmur.o | |||
33 | 33 | ||
34 | zcore_mod-objs := sclp_sdias.o zcore.o | 34 | zcore_mod-objs := sclp_sdias.o zcore.o |
35 | obj-$(CONFIG_CRASH_DUMP) += zcore_mod.o | 35 | obj-$(CONFIG_CRASH_DUMP) += zcore_mod.o |
36 | |||
37 | hmcdrv-objs := hmcdrv_mod.o hmcdrv_dev.o hmcdrv_ftp.o hmcdrv_cache.o diag_ftp.o sclp_ftp.o | ||
38 | obj-$(CONFIG_HMC_DRV) += hmcdrv.o | ||
diff --git a/drivers/s390/char/diag_ftp.c b/drivers/s390/char/diag_ftp.c new file mode 100644 index 000000000000..93889632fdf9 --- /dev/null +++ b/drivers/s390/char/diag_ftp.c | |||
@@ -0,0 +1,237 @@ | |||
1 | /* | ||
2 | * DIAGNOSE X'2C4' instruction based HMC FTP services, useable on z/VM | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | #define KMSG_COMPONENT "hmcdrv" | ||
10 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/mm.h> | ||
14 | #include <linux/irq.h> | ||
15 | #include <linux/wait.h> | ||
16 | #include <linux/string.h> | ||
17 | #include <asm/ctl_reg.h> | ||
18 | |||
19 | #include "hmcdrv_ftp.h" | ||
20 | #include "diag_ftp.h" | ||
21 | |||
22 | /* DIAGNOSE X'2C4' return codes in Ry */ | ||
23 | #define DIAG_FTP_RET_OK 0 /* HMC FTP started successfully */ | ||
24 | #define DIAG_FTP_RET_EBUSY 4 /* HMC FTP service currently busy */ | ||
25 | #define DIAG_FTP_RET_EIO 8 /* HMC FTP service I/O error */ | ||
26 | /* and an artificial extension */ | ||
27 | #define DIAG_FTP_RET_EPERM 2 /* HMC FTP service privilege error */ | ||
28 | |||
29 | /* FTP service status codes (after INTR at guest real location 133) */ | ||
30 | #define DIAG_FTP_STAT_OK 0U /* request completed successfully */ | ||
31 | #define DIAG_FTP_STAT_PGCC 4U /* program check condition */ | ||
32 | #define DIAG_FTP_STAT_PGIOE 8U /* paging I/O error */ | ||
33 | #define DIAG_FTP_STAT_TIMEOUT 12U /* timeout */ | ||
34 | #define DIAG_FTP_STAT_EBASE 16U /* base of error codes from SCLP */ | ||
35 | #define DIAG_FTP_STAT_LDFAIL (DIAG_FTP_STAT_EBASE + 1U) /* failed */ | ||
36 | #define DIAG_FTP_STAT_LDNPERM (DIAG_FTP_STAT_EBASE + 2U) /* not allowed */ | ||
37 | #define DIAG_FTP_STAT_LDRUNS (DIAG_FTP_STAT_EBASE + 3U) /* runs */ | ||
38 | #define DIAG_FTP_STAT_LDNRUNS (DIAG_FTP_STAT_EBASE + 4U) /* not runs */ | ||
39 | |||
40 | /** | ||
41 | * struct diag_ftp_ldfpl - load file FTP parameter list (LDFPL) | ||
42 | * @bufaddr: real buffer address (at 4k boundary) | ||
43 | * @buflen: length of buffer | ||
44 | * @offset: dir/file offset | ||
45 | * @intparm: interruption parameter (unused) | ||
46 | * @transferred: bytes transferred | ||
47 | * @fsize: file size, filled on GET | ||
48 | * @failaddr: failing address | ||
49 | * @spare: padding | ||
50 | * @fident: file name - ASCII | ||
51 | */ | ||
52 | struct diag_ftp_ldfpl { | ||
53 | u64 bufaddr; | ||
54 | u64 buflen; | ||
55 | u64 offset; | ||
56 | u64 intparm; | ||
57 | u64 transferred; | ||
58 | u64 fsize; | ||
59 | u64 failaddr; | ||
60 | u64 spare; | ||
61 | u8 fident[HMCDRV_FTP_FIDENT_MAX]; | ||
62 | } __packed; | ||
63 | |||
64 | static DECLARE_COMPLETION(diag_ftp_rx_complete); | ||
65 | static int diag_ftp_subcode; | ||
66 | |||
67 | /** | ||
68 | * diag_ftp_handler() - FTP services IRQ handler | ||
69 | * @extirq: external interrupt (sub-) code | ||
70 | * @param32: 32-bit interruption parameter from &struct diag_ftp_ldfpl | ||
71 | * @param64: unused (for 64-bit interrupt parameters) | ||
72 | */ | ||
73 | static void diag_ftp_handler(struct ext_code extirq, | ||
74 | unsigned int param32, | ||
75 | unsigned long param64) | ||
76 | { | ||
77 | if ((extirq.subcode >> 8) != 8) | ||
78 | return; /* not a FTP services sub-code */ | ||
79 | |||
80 | inc_irq_stat(IRQEXT_FTP); | ||
81 | diag_ftp_subcode = extirq.subcode & 0xffU; | ||
82 | complete(&diag_ftp_rx_complete); | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * diag_ftp_2c4() - DIAGNOSE X'2C4' service call | ||
87 | * @fpl: pointer to prepared LDFPL | ||
88 | * @cmd: FTP command to be executed | ||
89 | * | ||
90 | * Performs a DIAGNOSE X'2C4' call with (input/output) FTP parameter list | ||
91 | * @fpl and FTP function code @cmd. In case of an error the function does | ||
92 | * nothing and returns an (negative) error code. | ||
93 | * | ||
94 | * Notes: | ||
95 | * 1. This function only initiates a transfer, so the caller must wait | ||
96 | * for completion (asynchronous execution). | ||
97 | * 2. The FTP parameter list @fpl must be aligned to a double-word boundary. | ||
98 | * 3. fpl->bufaddr must be a real address, 4k aligned | ||
99 | */ | ||
100 | static int diag_ftp_2c4(struct diag_ftp_ldfpl *fpl, | ||
101 | enum hmcdrv_ftp_cmdid cmd) | ||
102 | { | ||
103 | int rc; | ||
104 | |||
105 | asm volatile( | ||
106 | " diag %[addr],%[cmd],0x2c4\n" | ||
107 | "0: j 2f\n" | ||
108 | "1: la %[rc],%[err]\n" | ||
109 | "2:\n" | ||
110 | EX_TABLE(0b, 1b) | ||
111 | : [rc] "=d" (rc), "+m" (*fpl) | ||
112 | : [cmd] "0" (cmd), [addr] "d" (virt_to_phys(fpl)), | ||
113 | [err] "i" (DIAG_FTP_RET_EPERM) | ||
114 | : "cc"); | ||
115 | |||
116 | switch (rc) { | ||
117 | case DIAG_FTP_RET_OK: | ||
118 | return 0; | ||
119 | case DIAG_FTP_RET_EBUSY: | ||
120 | return -EBUSY; | ||
121 | case DIAG_FTP_RET_EPERM: | ||
122 | return -EPERM; | ||
123 | case DIAG_FTP_RET_EIO: | ||
124 | default: | ||
125 | return -EIO; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * diag_ftp_cmd() - executes a DIAG X'2C4' FTP command, targeting a HMC | ||
131 | * @ftp: pointer to FTP command specification | ||
132 | * @fsize: return of file size (or NULL if undesirable) | ||
133 | * | ||
134 | * Attention: Notice that this function is not reentrant - so the caller | ||
135 | * must ensure locking. | ||
136 | * | ||
137 | * Return: number of bytes read/written or a (negative) error code | ||
138 | */ | ||
139 | ssize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize) | ||
140 | { | ||
141 | struct diag_ftp_ldfpl *ldfpl; | ||
142 | ssize_t len; | ||
143 | #ifdef DEBUG | ||
144 | unsigned long start_jiffies; | ||
145 | |||
146 | pr_debug("starting DIAG X'2C4' on '%s', requesting %zd bytes\n", | ||
147 | ftp->fname, ftp->len); | ||
148 | start_jiffies = jiffies; | ||
149 | #endif | ||
150 | init_completion(&diag_ftp_rx_complete); | ||
151 | |||
152 | ldfpl = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); | ||
153 | if (!ldfpl) { | ||
154 | len = -ENOMEM; | ||
155 | goto out; | ||
156 | } | ||
157 | |||
158 | len = strlcpy(ldfpl->fident, ftp->fname, sizeof(ldfpl->fident)); | ||
159 | if (len >= HMCDRV_FTP_FIDENT_MAX) { | ||
160 | len = -EINVAL; | ||
161 | goto out_free; | ||
162 | } | ||
163 | |||
164 | ldfpl->transferred = 0; | ||
165 | ldfpl->fsize = 0; | ||
166 | ldfpl->offset = ftp->ofs; | ||
167 | ldfpl->buflen = ftp->len; | ||
168 | ldfpl->bufaddr = virt_to_phys(ftp->buf); | ||
169 | |||
170 | len = diag_ftp_2c4(ldfpl, ftp->id); | ||
171 | if (len) | ||
172 | goto out_free; | ||
173 | |||
174 | /* | ||
175 | * There is no way to cancel the running diag X'2C4', the code | ||
176 | * needs to wait unconditionally until the transfer is complete. | ||
177 | */ | ||
178 | wait_for_completion(&diag_ftp_rx_complete); | ||
179 | |||
180 | #ifdef DEBUG | ||
181 | pr_debug("completed DIAG X'2C4' after %lu ms\n", | ||
182 | (jiffies - start_jiffies) * 1000 / HZ); | ||
183 | pr_debug("status of DIAG X'2C4' is %u, with %lld/%lld bytes\n", | ||
184 | diag_ftp_subcode, ldfpl->transferred, ldfpl->fsize); | ||
185 | #endif | ||
186 | |||
187 | switch (diag_ftp_subcode) { | ||
188 | case DIAG_FTP_STAT_OK: /* success */ | ||
189 | len = ldfpl->transferred; | ||
190 | if (fsize) | ||
191 | *fsize = ldfpl->fsize; | ||
192 | break; | ||
193 | case DIAG_FTP_STAT_LDNPERM: | ||
194 | len = -EPERM; | ||
195 | break; | ||
196 | case DIAG_FTP_STAT_LDRUNS: | ||
197 | len = -EBUSY; | ||
198 | break; | ||
199 | case DIAG_FTP_STAT_LDFAIL: | ||
200 | len = -ENOENT; /* no such file or media */ | ||
201 | break; | ||
202 | default: | ||
203 | len = -EIO; | ||
204 | break; | ||
205 | } | ||
206 | |||
207 | out_free: | ||
208 | free_page((unsigned long) ldfpl); | ||
209 | out: | ||
210 | return len; | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * diag_ftp_startup() - startup of FTP services, when running on z/VM | ||
215 | * | ||
216 | * Return: 0 on success, else an (negative) error code | ||
217 | */ | ||
218 | int diag_ftp_startup(void) | ||
219 | { | ||
220 | int rc; | ||
221 | |||
222 | rc = register_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler); | ||
223 | if (rc) | ||
224 | return rc; | ||
225 | |||
226 | ctl_set_bit(0, 63 - 22); | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | /** | ||
231 | * diag_ftp_shutdown() - shutdown of FTP services, when running on z/VM | ||
232 | */ | ||
233 | void diag_ftp_shutdown(void) | ||
234 | { | ||
235 | ctl_clear_bit(0, 63 - 22); | ||
236 | unregister_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler); | ||
237 | } | ||
diff --git a/drivers/s390/char/diag_ftp.h b/drivers/s390/char/diag_ftp.h new file mode 100644 index 000000000000..3abd2614053a --- /dev/null +++ b/drivers/s390/char/diag_ftp.h | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * DIAGNOSE X'2C4' instruction based SE/HMC FTP Services, useable on z/VM | ||
3 | * | ||
4 | * Notice that all functions exported here are not reentrant. | ||
5 | * So usage should be exclusive, ensured by the caller (e.g. using a | ||
6 | * mutex). | ||
7 | * | ||
8 | * Copyright IBM Corp. 2013 | ||
9 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
10 | */ | ||
11 | |||
12 | #ifndef __DIAG_FTP_H__ | ||
13 | #define __DIAG_FTP_H__ | ||
14 | |||
15 | #include "hmcdrv_ftp.h" | ||
16 | |||
17 | int diag_ftp_startup(void); | ||
18 | void diag_ftp_shutdown(void); | ||
19 | ssize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize); | ||
20 | |||
21 | #endif /* __DIAG_FTP_H__ */ | ||
diff --git a/drivers/s390/char/hmcdrv_cache.c b/drivers/s390/char/hmcdrv_cache.c new file mode 100644 index 000000000000..4cda5ada143a --- /dev/null +++ b/drivers/s390/char/hmcdrv_cache.c | |||
@@ -0,0 +1,252 @@ | |||
1 | /* | ||
2 | * SE/HMC Drive (Read) Cache Functions | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | #define KMSG_COMPONENT "hmcdrv" | ||
10 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/mm.h> | ||
14 | #include <linux/jiffies.h> | ||
15 | |||
16 | #include "hmcdrv_ftp.h" | ||
17 | #include "hmcdrv_cache.h" | ||
18 | |||
19 | #define HMCDRV_CACHE_TIMEOUT 30 /* aging timeout in seconds */ | ||
20 | |||
21 | /** | ||
22 | * struct hmcdrv_cache_entry - file cache (only used on read/dir) | ||
23 | * @id: FTP command ID | ||
24 | * @content: kernel-space buffer, 4k aligned | ||
25 | * @len: size of @content cache (0 if caching disabled) | ||
26 | * @ofs: start of content within file (-1 if no cached content) | ||
27 | * @fname: file name | ||
28 | * @fsize: file size | ||
29 | * @timeout: cache timeout in jiffies | ||
30 | * | ||
31 | * Notice that the first three members (id, fname, fsize) are cached on all | ||
32 | * read/dir requests. But content is cached only under some preconditions. | ||
33 | * Uncached content is signalled by a negative value of @ofs. | ||
34 | */ | ||
35 | struct hmcdrv_cache_entry { | ||
36 | enum hmcdrv_ftp_cmdid id; | ||
37 | char fname[HMCDRV_FTP_FIDENT_MAX]; | ||
38 | size_t fsize; | ||
39 | loff_t ofs; | ||
40 | unsigned long timeout; | ||
41 | void *content; | ||
42 | size_t len; | ||
43 | }; | ||
44 | |||
45 | static int hmcdrv_cache_order; /* cache allocated page order */ | ||
46 | |||
47 | static struct hmcdrv_cache_entry hmcdrv_cache_file = { | ||
48 | .fsize = SIZE_MAX, | ||
49 | .ofs = -1, | ||
50 | .len = 0, | ||
51 | .fname = {'\0'} | ||
52 | }; | ||
53 | |||
54 | /** | ||
55 | * hmcdrv_cache_get() - looks for file data/content in read cache | ||
56 | * @ftp: pointer to FTP command specification | ||
57 | * | ||
58 | * Return: number of bytes read from cache or a negative number if nothing | ||
59 | * in content cache (for the file/cmd specified in @ftp) | ||
60 | */ | ||
61 | static ssize_t hmcdrv_cache_get(const struct hmcdrv_ftp_cmdspec *ftp) | ||
62 | { | ||
63 | loff_t pos; /* position in cache (signed) */ | ||
64 | ssize_t len; | ||
65 | |||
66 | if ((ftp->id != hmcdrv_cache_file.id) || | ||
67 | strcmp(hmcdrv_cache_file.fname, ftp->fname)) | ||
68 | return -1; | ||
69 | |||
70 | if (ftp->ofs >= hmcdrv_cache_file.fsize) /* EOF ? */ | ||
71 | return 0; | ||
72 | |||
73 | if ((hmcdrv_cache_file.ofs < 0) || /* has content? */ | ||
74 | time_after(jiffies, hmcdrv_cache_file.timeout)) | ||
75 | return -1; | ||
76 | |||
77 | /* there seems to be cached content - calculate the maximum number | ||
78 | * of bytes that can be returned (regarding file size and offset) | ||
79 | */ | ||
80 | len = hmcdrv_cache_file.fsize - ftp->ofs; | ||
81 | |||
82 | if (len > ftp->len) | ||
83 | len = ftp->len; | ||
84 | |||
85 | /* check if the requested chunk falls into our cache (which starts | ||
86 | * at offset 'hmcdrv_cache_file.ofs' in the file of interest) | ||
87 | */ | ||
88 | pos = ftp->ofs - hmcdrv_cache_file.ofs; | ||
89 | |||
90 | if ((pos >= 0) && | ||
91 | ((pos + len) <= hmcdrv_cache_file.len)) { | ||
92 | |||
93 | memcpy(ftp->buf, | ||
94 | hmcdrv_cache_file.content + pos, | ||
95 | len); | ||
96 | pr_debug("using cached content of '%s', returning %zd/%zd bytes\n", | ||
97 | hmcdrv_cache_file.fname, len, | ||
98 | hmcdrv_cache_file.fsize); | ||
99 | |||
100 | return len; | ||
101 | } | ||
102 | |||
103 | return -1; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * hmcdrv_cache_do() - do a HMC drive CD/DVD transfer with cache update | ||
108 | * @ftp: pointer to FTP command specification | ||
109 | * @func: FTP transfer function to be used | ||
110 | * | ||
111 | * Return: number of bytes read/written or a (negative) error code | ||
112 | */ | ||
113 | static ssize_t hmcdrv_cache_do(const struct hmcdrv_ftp_cmdspec *ftp, | ||
114 | hmcdrv_cache_ftpfunc func) | ||
115 | { | ||
116 | ssize_t len; | ||
117 | |||
118 | /* only cache content if the read/dir cache really exists | ||
119 | * (hmcdrv_cache_file.len > 0), is large enough to handle the | ||
120 | * request (hmcdrv_cache_file.len >= ftp->len) and there is a need | ||
121 | * to do so (ftp->len > 0) | ||
122 | */ | ||
123 | if ((ftp->len > 0) && (hmcdrv_cache_file.len >= ftp->len)) { | ||
124 | |||
125 | /* because the cache is not located at ftp->buf, we have to | ||
126 | * assemble a new HMC drive FTP cmd specification (pointing | ||
127 | * to our cache, and using the increased size) | ||
128 | */ | ||
129 | struct hmcdrv_ftp_cmdspec cftp = *ftp; /* make a copy */ | ||
130 | cftp.buf = hmcdrv_cache_file.content; /* and update */ | ||
131 | cftp.len = hmcdrv_cache_file.len; /* buffer data */ | ||
132 | |||
133 | len = func(&cftp, &hmcdrv_cache_file.fsize); /* now do */ | ||
134 | |||
135 | if (len > 0) { | ||
136 | pr_debug("caching %zd bytes content for '%s'\n", | ||
137 | len, ftp->fname); | ||
138 | |||
139 | if (len > ftp->len) | ||
140 | len = ftp->len; | ||
141 | |||
142 | hmcdrv_cache_file.ofs = ftp->ofs; | ||
143 | hmcdrv_cache_file.timeout = jiffies + | ||
144 | HMCDRV_CACHE_TIMEOUT * HZ; | ||
145 | memcpy(ftp->buf, hmcdrv_cache_file.content, len); | ||
146 | } | ||
147 | } else { | ||
148 | len = func(ftp, &hmcdrv_cache_file.fsize); | ||
149 | hmcdrv_cache_file.ofs = -1; /* invalidate content */ | ||
150 | } | ||
151 | |||
152 | if (len > 0) { | ||
153 | /* cache some file info (FTP command, file name and file | ||
154 | * size) unconditionally | ||
155 | */ | ||
156 | strlcpy(hmcdrv_cache_file.fname, ftp->fname, | ||
157 | HMCDRV_FTP_FIDENT_MAX); | ||
158 | hmcdrv_cache_file.id = ftp->id; | ||
159 | pr_debug("caching cmd %d, file size %zu for '%s'\n", | ||
160 | ftp->id, hmcdrv_cache_file.fsize, ftp->fname); | ||
161 | } | ||
162 | |||
163 | return len; | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * hmcdrv_cache_cmd() - perform a cached HMC drive CD/DVD transfer | ||
168 | * @ftp: pointer to FTP command specification | ||
169 | * @func: FTP transfer function to be used | ||
170 | * | ||
171 | * Attention: Notice that this function is not reentrant - so the caller | ||
172 | * must ensure exclusive execution. | ||
173 | * | ||
174 | * Return: number of bytes read/written or a (negative) error code | ||
175 | */ | ||
176 | ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp, | ||
177 | hmcdrv_cache_ftpfunc func) | ||
178 | { | ||
179 | ssize_t len; | ||
180 | |||
181 | if ((ftp->id == HMCDRV_FTP_DIR) || /* read cache */ | ||
182 | (ftp->id == HMCDRV_FTP_NLIST) || | ||
183 | (ftp->id == HMCDRV_FTP_GET)) { | ||
184 | |||
185 | len = hmcdrv_cache_get(ftp); | ||
186 | |||
187 | if (len >= 0) /* got it from cache ? */ | ||
188 | return len; /* yes */ | ||
189 | |||
190 | len = hmcdrv_cache_do(ftp, func); | ||
191 | |||
192 | if (len >= 0) | ||
193 | return len; | ||
194 | |||
195 | } else { | ||
196 | len = func(ftp, NULL); /* simply do original command */ | ||
197 | } | ||
198 | |||
199 | /* invalidate the (read) cache in case there was a write operation | ||
200 | * or an error on read/dir | ||
201 | */ | ||
202 | hmcdrv_cache_file.id = HMCDRV_FTP_NOOP; | ||
203 | hmcdrv_cache_file.fsize = LLONG_MAX; | ||
204 | hmcdrv_cache_file.ofs = -1; | ||
205 | |||
206 | return len; | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * hmcdrv_cache_startup() - startup of HMC drive cache | ||
211 | * @cachesize: cache size | ||
212 | * | ||
213 | * Return: 0 on success, else a (negative) error code | ||
214 | */ | ||
215 | int hmcdrv_cache_startup(size_t cachesize) | ||
216 | { | ||
217 | if (cachesize > 0) { /* perform caching ? */ | ||
218 | hmcdrv_cache_order = get_order(cachesize); | ||
219 | hmcdrv_cache_file.content = | ||
220 | (void *) __get_free_pages(GFP_KERNEL | GFP_DMA, | ||
221 | hmcdrv_cache_order); | ||
222 | |||
223 | if (!hmcdrv_cache_file.content) { | ||
224 | pr_err("Allocating the requested cache size of %zu bytes failed\n", | ||
225 | cachesize); | ||
226 | return -ENOMEM; | ||
227 | } | ||
228 | |||
229 | pr_debug("content cache enabled, size is %zu bytes\n", | ||
230 | cachesize); | ||
231 | } | ||
232 | |||
233 | hmcdrv_cache_file.len = cachesize; | ||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * hmcdrv_cache_shutdown() - shutdown of HMC drive cache | ||
239 | */ | ||
240 | void hmcdrv_cache_shutdown(void) | ||
241 | { | ||
242 | if (hmcdrv_cache_file.content) { | ||
243 | free_pages((unsigned long) hmcdrv_cache_file.content, | ||
244 | hmcdrv_cache_order); | ||
245 | hmcdrv_cache_file.content = NULL; | ||
246 | } | ||
247 | |||
248 | hmcdrv_cache_file.id = HMCDRV_FTP_NOOP; | ||
249 | hmcdrv_cache_file.fsize = LLONG_MAX; | ||
250 | hmcdrv_cache_file.ofs = -1; | ||
251 | hmcdrv_cache_file.len = 0; /* no cache */ | ||
252 | } | ||
diff --git a/drivers/s390/char/hmcdrv_cache.h b/drivers/s390/char/hmcdrv_cache.h new file mode 100644 index 000000000000..a14b57526781 --- /dev/null +++ b/drivers/s390/char/hmcdrv_cache.h | |||
@@ -0,0 +1,24 @@ | |||
1 | /* | ||
2 | * SE/HMC Drive (Read) Cache Functions | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | */ | ||
7 | |||
8 | #ifndef __HMCDRV_CACHE_H__ | ||
9 | #define __HMCDRV_CACHE_H__ | ||
10 | |||
11 | #include <linux/mmzone.h> | ||
12 | #include "hmcdrv_ftp.h" | ||
13 | |||
14 | #define HMCDRV_CACHE_SIZE_DFLT (MAX_ORDER_NR_PAGES * PAGE_SIZE / 2UL) | ||
15 | |||
16 | typedef ssize_t (*hmcdrv_cache_ftpfunc)(const struct hmcdrv_ftp_cmdspec *ftp, | ||
17 | size_t *fsize); | ||
18 | |||
19 | ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp, | ||
20 | hmcdrv_cache_ftpfunc func); | ||
21 | int hmcdrv_cache_startup(size_t cachesize); | ||
22 | void hmcdrv_cache_shutdown(void); | ||
23 | |||
24 | #endif /* __HMCDRV_CACHE_H__ */ | ||
diff --git a/drivers/s390/char/hmcdrv_dev.c b/drivers/s390/char/hmcdrv_dev.c new file mode 100644 index 000000000000..0c5176179c17 --- /dev/null +++ b/drivers/s390/char/hmcdrv_dev.c | |||
@@ -0,0 +1,370 @@ | |||
1 | /* | ||
2 | * HMC Drive CD/DVD Device | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | * | ||
7 | * This file provides a Linux "misc" character device for access to an | ||
8 | * assigned HMC drive CD/DVD-ROM. It works as follows: First create the | ||
9 | * device by calling hmcdrv_dev_init(). After open() a lseek(fd, 0, | ||
10 | * SEEK_END) indicates that a new FTP command follows (not needed on the | ||
11 | * first command after open). Then write() the FTP command ASCII string | ||
12 | * to it, e.g. "dir /" or "nls <directory>" or "get <filename>". At the | ||
13 | * end read() the response. | ||
14 | */ | ||
15 | |||
16 | #define KMSG_COMPONENT "hmcdrv" | ||
17 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
18 | |||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <linux/fs.h> | ||
23 | #include <linux/cdev.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/device.h> | ||
26 | #include <linux/capability.h> | ||
27 | #include <linux/delay.h> | ||
28 | #include <linux/uaccess.h> | ||
29 | |||
30 | #include "hmcdrv_dev.h" | ||
31 | #include "hmcdrv_ftp.h" | ||
32 | |||
33 | /* If the following macro is defined, then the HMC device creates it's own | ||
34 | * separated device class (and dynamically assigns a major number). If not | ||
35 | * defined then the HMC device is assigned to the "misc" class devices. | ||
36 | * | ||
37 | #define HMCDRV_DEV_CLASS "hmcftp" | ||
38 | */ | ||
39 | |||
40 | #define HMCDRV_DEV_NAME "hmcdrv" | ||
41 | #define HMCDRV_DEV_BUSY_DELAY 500 /* delay between -EBUSY trials in ms */ | ||
42 | #define HMCDRV_DEV_BUSY_RETRIES 3 /* number of retries on -EBUSY */ | ||
43 | |||
44 | struct hmcdrv_dev_node { | ||
45 | |||
46 | #ifdef HMCDRV_DEV_CLASS | ||
47 | struct cdev dev; /* character device structure */ | ||
48 | umode_t mode; /* mode of device node (unused, zero) */ | ||
49 | #else | ||
50 | struct miscdevice dev; /* "misc" device structure */ | ||
51 | #endif | ||
52 | |||
53 | }; | ||
54 | |||
55 | static int hmcdrv_dev_open(struct inode *inode, struct file *fp); | ||
56 | static int hmcdrv_dev_release(struct inode *inode, struct file *fp); | ||
57 | static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence); | ||
58 | static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, | ||
59 | size_t len, loff_t *pos); | ||
60 | static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, | ||
61 | size_t len, loff_t *pos); | ||
62 | static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, | ||
63 | char __user *buf, size_t len); | ||
64 | |||
65 | /* | ||
66 | * device operations | ||
67 | */ | ||
68 | static const struct file_operations hmcdrv_dev_fops = { | ||
69 | .open = hmcdrv_dev_open, | ||
70 | .llseek = hmcdrv_dev_seek, | ||
71 | .release = hmcdrv_dev_release, | ||
72 | .read = hmcdrv_dev_read, | ||
73 | .write = hmcdrv_dev_write, | ||
74 | }; | ||
75 | |||
76 | static struct hmcdrv_dev_node hmcdrv_dev; /* HMC device struct (static) */ | ||
77 | |||
78 | #ifdef HMCDRV_DEV_CLASS | ||
79 | |||
80 | static struct class *hmcdrv_dev_class; /* device class pointer */ | ||
81 | static dev_t hmcdrv_dev_no; /* device number (major/minor) */ | ||
82 | |||
83 | /** | ||
84 | * hmcdrv_dev_name() - provides a naming hint for a device node in /dev | ||
85 | * @dev: device for which the naming/mode hint is | ||
86 | * @mode: file mode for device node created in /dev | ||
87 | * | ||
88 | * See: devtmpfs.c, function devtmpfs_create_node() | ||
89 | * | ||
90 | * Return: recommended device file name in /dev | ||
91 | */ | ||
92 | static char *hmcdrv_dev_name(struct device *dev, umode_t *mode) | ||
93 | { | ||
94 | char *nodename = NULL; | ||
95 | const char *devname = dev_name(dev); /* kernel device name */ | ||
96 | |||
97 | if (devname) | ||
98 | nodename = kasprintf(GFP_KERNEL, "%s", devname); | ||
99 | |||
100 | /* on device destroy (rmmod) the mode pointer may be NULL | ||
101 | */ | ||
102 | if (mode) | ||
103 | *mode = hmcdrv_dev.mode; | ||
104 | |||
105 | return nodename; | ||
106 | } | ||
107 | |||
108 | #endif /* HMCDRV_DEV_CLASS */ | ||
109 | |||
110 | /* | ||
111 | * open() | ||
112 | */ | ||
113 | static int hmcdrv_dev_open(struct inode *inode, struct file *fp) | ||
114 | { | ||
115 | int rc; | ||
116 | |||
117 | /* check for non-blocking access, which is really unsupported | ||
118 | */ | ||
119 | if (fp->f_flags & O_NONBLOCK) | ||
120 | return -EINVAL; | ||
121 | |||
122 | /* Because it makes no sense to open this device read-only (then a | ||
123 | * FTP command cannot be emitted), we respond with an error. | ||
124 | */ | ||
125 | if ((fp->f_flags & O_ACCMODE) == O_RDONLY) | ||
126 | return -EINVAL; | ||
127 | |||
128 | /* prevent unloading this module as long as anyone holds the | ||
129 | * device file open - so increment the reference count here | ||
130 | */ | ||
131 | if (!try_module_get(THIS_MODULE)) | ||
132 | return -ENODEV; | ||
133 | |||
134 | fp->private_data = NULL; /* no command yet */ | ||
135 | rc = hmcdrv_ftp_startup(); | ||
136 | if (rc) | ||
137 | module_put(THIS_MODULE); | ||
138 | |||
139 | pr_debug("open file '/dev/%s' with return code %d\n", | ||
140 | fp->f_dentry->d_name.name, rc); | ||
141 | return rc; | ||
142 | } | ||
143 | |||
144 | /* | ||
145 | * release() | ||
146 | */ | ||
147 | static int hmcdrv_dev_release(struct inode *inode, struct file *fp) | ||
148 | { | ||
149 | pr_debug("closing file '/dev/%s'\n", fp->f_dentry->d_name.name); | ||
150 | kfree(fp->private_data); | ||
151 | fp->private_data = NULL; | ||
152 | hmcdrv_ftp_shutdown(); | ||
153 | module_put(THIS_MODULE); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | /* | ||
158 | * lseek() | ||
159 | */ | ||
160 | static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence) | ||
161 | { | ||
162 | switch (whence) { | ||
163 | case SEEK_CUR: /* relative to current file position */ | ||
164 | pos += fp->f_pos; /* new position stored in 'pos' */ | ||
165 | break; | ||
166 | |||
167 | case SEEK_SET: /* absolute (relative to beginning of file) */ | ||
168 | break; /* SEEK_SET */ | ||
169 | |||
170 | /* We use SEEK_END as a special indicator for a SEEK_SET | ||
171 | * (set absolute position), combined with a FTP command | ||
172 | * clear. | ||
173 | */ | ||
174 | case SEEK_END: | ||
175 | if (fp->private_data) { | ||
176 | kfree(fp->private_data); | ||
177 | fp->private_data = NULL; | ||
178 | } | ||
179 | |||
180 | break; /* SEEK_END */ | ||
181 | |||
182 | default: /* SEEK_DATA, SEEK_HOLE: unsupported */ | ||
183 | return -EINVAL; | ||
184 | } | ||
185 | |||
186 | if (pos < 0) | ||
187 | return -EINVAL; | ||
188 | |||
189 | if (fp->f_pos != pos) | ||
190 | ++fp->f_version; | ||
191 | |||
192 | fp->f_pos = pos; | ||
193 | return pos; | ||
194 | } | ||
195 | |||
196 | /* | ||
197 | * transfer (helper function) | ||
198 | */ | ||
199 | static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, | ||
200 | char __user *buf, size_t len) | ||
201 | { | ||
202 | ssize_t retlen; | ||
203 | unsigned trials = HMCDRV_DEV_BUSY_RETRIES; | ||
204 | |||
205 | do { | ||
206 | retlen = hmcdrv_ftp_cmd(cmd, offset, buf, len); | ||
207 | |||
208 | if (retlen != -EBUSY) | ||
209 | break; | ||
210 | |||
211 | msleep(HMCDRV_DEV_BUSY_DELAY); | ||
212 | |||
213 | } while (--trials > 0); | ||
214 | |||
215 | return retlen; | ||
216 | } | ||
217 | |||
218 | /* | ||
219 | * read() | ||
220 | */ | ||
221 | static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, | ||
222 | size_t len, loff_t *pos) | ||
223 | { | ||
224 | ssize_t retlen; | ||
225 | |||
226 | if (((fp->f_flags & O_ACCMODE) == O_WRONLY) || | ||
227 | (fp->private_data == NULL)) { /* no FTP cmd defined ? */ | ||
228 | return -EBADF; | ||
229 | } | ||
230 | |||
231 | retlen = hmcdrv_dev_transfer((char *) fp->private_data, | ||
232 | *pos, ubuf, len); | ||
233 | |||
234 | pr_debug("read from file '/dev/%s' at %lld returns %zd/%zu\n", | ||
235 | fp->f_dentry->d_name.name, (long long) *pos, retlen, len); | ||
236 | |||
237 | if (retlen > 0) | ||
238 | *pos += retlen; | ||
239 | |||
240 | return retlen; | ||
241 | } | ||
242 | |||
243 | /* | ||
244 | * write() | ||
245 | */ | ||
246 | static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, | ||
247 | size_t len, loff_t *pos) | ||
248 | { | ||
249 | ssize_t retlen; | ||
250 | |||
251 | pr_debug("writing file '/dev/%s' at pos. %lld with length %zd\n", | ||
252 | fp->f_dentry->d_name.name, (long long) *pos, len); | ||
253 | |||
254 | if (!fp->private_data) { /* first expect a cmd write */ | ||
255 | fp->private_data = kmalloc(len + 1, GFP_KERNEL); | ||
256 | |||
257 | if (!fp->private_data) | ||
258 | return -ENOMEM; | ||
259 | |||
260 | if (!copy_from_user(fp->private_data, ubuf, len)) { | ||
261 | ((char *)fp->private_data)[len] = '\0'; | ||
262 | return len; | ||
263 | } | ||
264 | |||
265 | kfree(fp->private_data); | ||
266 | fp->private_data = NULL; | ||
267 | return -EFAULT; | ||
268 | } | ||
269 | |||
270 | retlen = hmcdrv_dev_transfer((char *) fp->private_data, | ||
271 | *pos, (char __user *) ubuf, len); | ||
272 | if (retlen > 0) | ||
273 | *pos += retlen; | ||
274 | |||
275 | pr_debug("write to file '/dev/%s' returned %zd\n", | ||
276 | fp->f_dentry->d_name.name, retlen); | ||
277 | |||
278 | return retlen; | ||
279 | } | ||
280 | |||
281 | /** | ||
282 | * hmcdrv_dev_init() - creates a HMC drive CD/DVD device | ||
283 | * | ||
284 | * This function creates a HMC drive CD/DVD kernel device and an associated | ||
285 | * device under /dev, using a dynamically allocated major number. | ||
286 | * | ||
287 | * Return: 0 on success, else an error code. | ||
288 | */ | ||
289 | int hmcdrv_dev_init(void) | ||
290 | { | ||
291 | int rc; | ||
292 | |||
293 | #ifdef HMCDRV_DEV_CLASS | ||
294 | struct device *dev; | ||
295 | |||
296 | rc = alloc_chrdev_region(&hmcdrv_dev_no, 0, 1, HMCDRV_DEV_NAME); | ||
297 | |||
298 | if (rc) | ||
299 | goto out_err; | ||
300 | |||
301 | cdev_init(&hmcdrv_dev.dev, &hmcdrv_dev_fops); | ||
302 | hmcdrv_dev.dev.owner = THIS_MODULE; | ||
303 | rc = cdev_add(&hmcdrv_dev.dev, hmcdrv_dev_no, 1); | ||
304 | |||
305 | if (rc) | ||
306 | goto out_unreg; | ||
307 | |||
308 | /* At this point the character device exists in the kernel (see | ||
309 | * /proc/devices), but not under /dev nor /sys/devices/virtual. So | ||
310 | * we have to create an associated class (see /sys/class). | ||
311 | */ | ||
312 | hmcdrv_dev_class = class_create(THIS_MODULE, HMCDRV_DEV_CLASS); | ||
313 | |||
314 | if (IS_ERR(hmcdrv_dev_class)) { | ||
315 | rc = PTR_ERR(hmcdrv_dev_class); | ||
316 | goto out_devdel; | ||
317 | } | ||
318 | |||
319 | /* Finally a device node in /dev has to be established (as 'mkdev' | ||
320 | * does from the command line). Notice that assignment of a device | ||
321 | * node name/mode function is optional (only for mode != 0600). | ||
322 | */ | ||
323 | hmcdrv_dev.mode = 0; /* "unset" */ | ||
324 | hmcdrv_dev_class->devnode = hmcdrv_dev_name; | ||
325 | |||
326 | dev = device_create(hmcdrv_dev_class, NULL, hmcdrv_dev_no, NULL, | ||
327 | "%s", HMCDRV_DEV_NAME); | ||
328 | if (!IS_ERR(dev)) | ||
329 | return 0; | ||
330 | |||
331 | rc = PTR_ERR(dev); | ||
332 | class_destroy(hmcdrv_dev_class); | ||
333 | hmcdrv_dev_class = NULL; | ||
334 | |||
335 | out_devdel: | ||
336 | cdev_del(&hmcdrv_dev.dev); | ||
337 | |||
338 | out_unreg: | ||
339 | unregister_chrdev_region(hmcdrv_dev_no, 1); | ||
340 | |||
341 | out_err: | ||
342 | |||
343 | #else /* !HMCDRV_DEV_CLASS */ | ||
344 | hmcdrv_dev.dev.minor = MISC_DYNAMIC_MINOR; | ||
345 | hmcdrv_dev.dev.name = HMCDRV_DEV_NAME; | ||
346 | hmcdrv_dev.dev.fops = &hmcdrv_dev_fops; | ||
347 | hmcdrv_dev.dev.mode = 0; /* finally produces 0600 */ | ||
348 | rc = misc_register(&hmcdrv_dev.dev); | ||
349 | #endif /* HMCDRV_DEV_CLASS */ | ||
350 | |||
351 | return rc; | ||
352 | } | ||
353 | |||
354 | /** | ||
355 | * hmcdrv_dev_exit() - destroys a HMC drive CD/DVD device | ||
356 | */ | ||
357 | void hmcdrv_dev_exit(void) | ||
358 | { | ||
359 | #ifdef HMCDRV_DEV_CLASS | ||
360 | if (!IS_ERR_OR_NULL(hmcdrv_dev_class)) { | ||
361 | device_destroy(hmcdrv_dev_class, hmcdrv_dev_no); | ||
362 | class_destroy(hmcdrv_dev_class); | ||
363 | } | ||
364 | |||
365 | cdev_del(&hmcdrv_dev.dev); | ||
366 | unregister_chrdev_region(hmcdrv_dev_no, 1); | ||
367 | #else /* !HMCDRV_DEV_CLASS */ | ||
368 | misc_deregister(&hmcdrv_dev.dev); | ||
369 | #endif /* HMCDRV_DEV_CLASS */ | ||
370 | } | ||
diff --git a/drivers/s390/char/hmcdrv_dev.h b/drivers/s390/char/hmcdrv_dev.h new file mode 100644 index 000000000000..cb17f07e02de --- /dev/null +++ b/drivers/s390/char/hmcdrv_dev.h | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SE/HMC Drive FTP Device | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | */ | ||
7 | |||
8 | #ifndef __HMCDRV_DEV_H__ | ||
9 | #define __HMCDRV_DEV_H__ | ||
10 | |||
11 | int hmcdrv_dev_init(void); | ||
12 | void hmcdrv_dev_exit(void); | ||
13 | |||
14 | #endif /* __HMCDRV_DEV_H__ */ | ||
diff --git a/drivers/s390/char/hmcdrv_ftp.c b/drivers/s390/char/hmcdrv_ftp.c new file mode 100644 index 000000000000..4bd63322fc29 --- /dev/null +++ b/drivers/s390/char/hmcdrv_ftp.c | |||
@@ -0,0 +1,343 @@ | |||
1 | /* | ||
2 | * HMC Drive FTP Services | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | */ | ||
7 | |||
8 | #define KMSG_COMPONENT "hmcdrv" | ||
9 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
10 | |||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <linux/uaccess.h> | ||
14 | #include <linux/export.h> | ||
15 | |||
16 | #include <linux/ctype.h> | ||
17 | #include <linux/crc16.h> | ||
18 | |||
19 | #include "hmcdrv_ftp.h" | ||
20 | #include "hmcdrv_cache.h" | ||
21 | #include "sclp_ftp.h" | ||
22 | #include "diag_ftp.h" | ||
23 | |||
24 | /** | ||
25 | * struct hmcdrv_ftp_ops - HMC drive FTP operations | ||
26 | * @startup: startup function | ||
27 | * @shutdown: shutdown function | ||
28 | * @cmd: FTP transfer function | ||
29 | */ | ||
30 | struct hmcdrv_ftp_ops { | ||
31 | int (*startup)(void); | ||
32 | void (*shutdown)(void); | ||
33 | ssize_t (*transfer)(const struct hmcdrv_ftp_cmdspec *ftp, | ||
34 | size_t *fsize); | ||
35 | }; | ||
36 | |||
37 | static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len); | ||
38 | static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp); | ||
39 | |||
40 | static struct hmcdrv_ftp_ops *hmcdrv_ftp_funcs; /* current operations */ | ||
41 | static DEFINE_MUTEX(hmcdrv_ftp_mutex); /* mutex for hmcdrv_ftp_funcs */ | ||
42 | static unsigned hmcdrv_ftp_refcnt; /* start/shutdown reference counter */ | ||
43 | |||
44 | /** | ||
45 | * hmcdrv_ftp_cmd_getid() - determine FTP command ID from a command string | ||
46 | * @cmd: FTP command string (NOT zero-terminated) | ||
47 | * @len: length of FTP command string in @cmd | ||
48 | */ | ||
49 | static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len) | ||
50 | { | ||
51 | /* HMC FTP command descriptor */ | ||
52 | struct hmcdrv_ftp_cmd_desc { | ||
53 | const char *str; /* command string */ | ||
54 | enum hmcdrv_ftp_cmdid cmd; /* associated command as enum */ | ||
55 | }; | ||
56 | |||
57 | /* Description of all HMC drive FTP commands | ||
58 | * | ||
59 | * Notes: | ||
60 | * 1. Array size should be a prime number. | ||
61 | * 2. Do not change the order of commands in table (because the | ||
62 | * index is determined by CRC % ARRAY_SIZE). | ||
63 | * 3. Original command 'nlist' was renamed, else the CRC would | ||
64 | * collide with 'append' (see point 2). | ||
65 | */ | ||
66 | static const struct hmcdrv_ftp_cmd_desc ftpcmds[7] = { | ||
67 | |||
68 | {.str = "get", /* [0] get (CRC = 0x68eb) */ | ||
69 | .cmd = HMCDRV_FTP_GET}, | ||
70 | {.str = "dir", /* [1] dir (CRC = 0x6a9e) */ | ||
71 | .cmd = HMCDRV_FTP_DIR}, | ||
72 | {.str = "delete", /* [2] delete (CRC = 0x53ae) */ | ||
73 | .cmd = HMCDRV_FTP_DELETE}, | ||
74 | {.str = "nls", /* [3] nls (CRC = 0xf87c) */ | ||
75 | .cmd = HMCDRV_FTP_NLIST}, | ||
76 | {.str = "put", /* [4] put (CRC = 0xac56) */ | ||
77 | .cmd = HMCDRV_FTP_PUT}, | ||
78 | {.str = "append", /* [5] append (CRC = 0xf56e) */ | ||
79 | .cmd = HMCDRV_FTP_APPEND}, | ||
80 | {.str = NULL} /* [6] unused */ | ||
81 | }; | ||
82 | |||
83 | const struct hmcdrv_ftp_cmd_desc *pdesc; | ||
84 | |||
85 | u16 crc = 0xffffU; | ||
86 | |||
87 | if (len == 0) | ||
88 | return HMCDRV_FTP_NOOP; /* error indiactor */ | ||
89 | |||
90 | crc = crc16(crc, cmd, len); | ||
91 | pdesc = ftpcmds + (crc % ARRAY_SIZE(ftpcmds)); | ||
92 | pr_debug("FTP command '%s' has CRC 0x%04x, at table pos. %lu\n", | ||
93 | cmd, crc, (crc % ARRAY_SIZE(ftpcmds))); | ||
94 | |||
95 | if (!pdesc->str || strncmp(pdesc->str, cmd, len)) | ||
96 | return HMCDRV_FTP_NOOP; | ||
97 | |||
98 | pr_debug("FTP command '%s' found, with ID %d\n", | ||
99 | pdesc->str, pdesc->cmd); | ||
100 | |||
101 | return pdesc->cmd; | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * hmcdrv_ftp_parse() - HMC drive FTP command parser | ||
106 | * @cmd: FTP command string "<cmd> <filename>" | ||
107 | * @ftp: Pointer to FTP command specification buffer (output) | ||
108 | * | ||
109 | * Return: 0 on success, else a (negative) error code | ||
110 | */ | ||
111 | static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp) | ||
112 | { | ||
113 | char *start; | ||
114 | int argc = 0; | ||
115 | |||
116 | ftp->id = HMCDRV_FTP_NOOP; | ||
117 | ftp->fname = NULL; | ||
118 | |||
119 | while (*cmd != '\0') { | ||
120 | |||
121 | while (isspace(*cmd)) | ||
122 | ++cmd; | ||
123 | |||
124 | if (*cmd == '\0') | ||
125 | break; | ||
126 | |||
127 | start = cmd; | ||
128 | |||
129 | switch (argc) { | ||
130 | case 0: /* 1st argument (FTP command) */ | ||
131 | while ((*cmd != '\0') && !isspace(*cmd)) | ||
132 | ++cmd; | ||
133 | ftp->id = hmcdrv_ftp_cmd_getid(start, cmd - start); | ||
134 | break; | ||
135 | case 1: /* 2nd / last argument (rest of line) */ | ||
136 | while ((*cmd != '\0') && !iscntrl(*cmd)) | ||
137 | ++cmd; | ||
138 | ftp->fname = start; | ||
139 | /* fall through */ | ||
140 | default: | ||
141 | *cmd = '\0'; | ||
142 | break; | ||
143 | } /* switch */ | ||
144 | |||
145 | ++argc; | ||
146 | } /* while */ | ||
147 | |||
148 | if (!ftp->fname || (ftp->id == HMCDRV_FTP_NOOP)) | ||
149 | return -EINVAL; | ||
150 | |||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | /** | ||
155 | * hmcdrv_ftp_do() - perform a HMC drive FTP, with data from kernel-space | ||
156 | * @ftp: pointer to FTP command specification | ||
157 | * | ||
158 | * Return: number of bytes read/written or a negative error code | ||
159 | */ | ||
160 | ssize_t hmcdrv_ftp_do(const struct hmcdrv_ftp_cmdspec *ftp) | ||
161 | { | ||
162 | ssize_t len; | ||
163 | |||
164 | mutex_lock(&hmcdrv_ftp_mutex); | ||
165 | |||
166 | if (hmcdrv_ftp_funcs && hmcdrv_ftp_refcnt) { | ||
167 | pr_debug("starting transfer, cmd %d for '%s' at %lld with %zd bytes\n", | ||
168 | ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len); | ||
169 | len = hmcdrv_cache_cmd(ftp, hmcdrv_ftp_funcs->transfer); | ||
170 | } else { | ||
171 | len = -ENXIO; | ||
172 | } | ||
173 | |||
174 | mutex_unlock(&hmcdrv_ftp_mutex); | ||
175 | return len; | ||
176 | } | ||
177 | EXPORT_SYMBOL(hmcdrv_ftp_do); | ||
178 | |||
179 | /** | ||
180 | * hmcdrv_ftp_probe() - probe for the HMC drive FTP service | ||
181 | * | ||
182 | * Return: 0 if service is available, else an (negative) error code | ||
183 | */ | ||
184 | int hmcdrv_ftp_probe(void) | ||
185 | { | ||
186 | int rc; | ||
187 | |||
188 | struct hmcdrv_ftp_cmdspec ftp = { | ||
189 | .id = HMCDRV_FTP_NOOP, | ||
190 | .ofs = 0, | ||
191 | .fname = "", | ||
192 | .len = PAGE_SIZE | ||
193 | }; | ||
194 | |||
195 | ftp.buf = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); | ||
196 | |||
197 | if (!ftp.buf) | ||
198 | return -ENOMEM; | ||
199 | |||
200 | rc = hmcdrv_ftp_startup(); | ||
201 | |||
202 | if (rc) | ||
203 | return rc; | ||
204 | |||
205 | rc = hmcdrv_ftp_do(&ftp); | ||
206 | free_page((unsigned long) ftp.buf); | ||
207 | hmcdrv_ftp_shutdown(); | ||
208 | |||
209 | switch (rc) { | ||
210 | case -ENOENT: /* no such file/media or currently busy, */ | ||
211 | case -EBUSY: /* but service seems to be available */ | ||
212 | rc = 0; | ||
213 | break; | ||
214 | default: /* leave 'rc' as it is for [0, -EPERM, -E...] */ | ||
215 | if (rc > 0) | ||
216 | rc = 0; /* clear length (success) */ | ||
217 | break; | ||
218 | } /* switch */ | ||
219 | |||
220 | return rc; | ||
221 | } | ||
222 | EXPORT_SYMBOL(hmcdrv_ftp_probe); | ||
223 | |||
224 | /** | ||
225 | * hmcdrv_ftp_cmd() - Perform a HMC drive FTP, with data from user-space | ||
226 | * | ||
227 | * @cmd: FTP command string "<cmd> <filename>" | ||
228 | * @offset: file position to read/write | ||
229 | * @buf: user-space buffer for read/written directory/file | ||
230 | * @len: size of @buf (read/dir) or number of bytes to write | ||
231 | * | ||
232 | * This function must not be called before hmcdrv_ftp_startup() was called. | ||
233 | * | ||
234 | * Return: number of bytes read/written or a negative error code | ||
235 | */ | ||
236 | ssize_t hmcdrv_ftp_cmd(char __kernel *cmd, loff_t offset, | ||
237 | char __user *buf, size_t len) | ||
238 | { | ||
239 | int order; | ||
240 | |||
241 | struct hmcdrv_ftp_cmdspec ftp = {.len = len, .ofs = offset}; | ||
242 | ssize_t retlen = hmcdrv_ftp_parse(cmd, &ftp); | ||
243 | |||
244 | if (retlen) | ||
245 | return retlen; | ||
246 | |||
247 | order = get_order(ftp.len); | ||
248 | ftp.buf = (void *) __get_free_pages(GFP_KERNEL | GFP_DMA, order); | ||
249 | |||
250 | if (!ftp.buf) | ||
251 | return -ENOMEM; | ||
252 | |||
253 | switch (ftp.id) { | ||
254 | case HMCDRV_FTP_DIR: | ||
255 | case HMCDRV_FTP_NLIST: | ||
256 | case HMCDRV_FTP_GET: | ||
257 | retlen = hmcdrv_ftp_do(&ftp); | ||
258 | |||
259 | if ((retlen >= 0) && | ||
260 | copy_to_user(buf, ftp.buf, retlen)) | ||
261 | retlen = -EFAULT; | ||
262 | break; | ||
263 | |||
264 | case HMCDRV_FTP_PUT: | ||
265 | case HMCDRV_FTP_APPEND: | ||
266 | if (!copy_from_user(ftp.buf, buf, ftp.len)) | ||
267 | retlen = hmcdrv_ftp_do(&ftp); | ||
268 | else | ||
269 | retlen = -EFAULT; | ||
270 | break; | ||
271 | |||
272 | case HMCDRV_FTP_DELETE: | ||
273 | retlen = hmcdrv_ftp_do(&ftp); | ||
274 | break; | ||
275 | |||
276 | default: | ||
277 | retlen = -EOPNOTSUPP; | ||
278 | break; | ||
279 | } | ||
280 | |||
281 | free_pages((unsigned long) ftp.buf, order); | ||
282 | return retlen; | ||
283 | } | ||
284 | |||
285 | /** | ||
286 | * hmcdrv_ftp_startup() - startup of HMC drive FTP functionality for a | ||
287 | * dedicated (owner) instance | ||
288 | * | ||
289 | * Return: 0 on success, else an (negative) error code | ||
290 | */ | ||
291 | int hmcdrv_ftp_startup(void) | ||
292 | { | ||
293 | static struct hmcdrv_ftp_ops hmcdrv_ftp_zvm = { | ||
294 | .startup = diag_ftp_startup, | ||
295 | .shutdown = diag_ftp_shutdown, | ||
296 | .transfer = diag_ftp_cmd | ||
297 | }; | ||
298 | |||
299 | static struct hmcdrv_ftp_ops hmcdrv_ftp_lpar = { | ||
300 | .startup = sclp_ftp_startup, | ||
301 | .shutdown = sclp_ftp_shutdown, | ||
302 | .transfer = sclp_ftp_cmd | ||
303 | }; | ||
304 | |||
305 | int rc = 0; | ||
306 | |||
307 | mutex_lock(&hmcdrv_ftp_mutex); /* block transfers while start-up */ | ||
308 | |||
309 | if (hmcdrv_ftp_refcnt == 0) { | ||
310 | if (MACHINE_IS_VM) | ||
311 | hmcdrv_ftp_funcs = &hmcdrv_ftp_zvm; | ||
312 | else if (MACHINE_IS_LPAR || MACHINE_IS_KVM) | ||
313 | hmcdrv_ftp_funcs = &hmcdrv_ftp_lpar; | ||
314 | else | ||
315 | rc = -EOPNOTSUPP; | ||
316 | |||
317 | if (hmcdrv_ftp_funcs) | ||
318 | rc = hmcdrv_ftp_funcs->startup(); | ||
319 | } | ||
320 | |||
321 | if (!rc) | ||
322 | ++hmcdrv_ftp_refcnt; | ||
323 | |||
324 | mutex_unlock(&hmcdrv_ftp_mutex); | ||
325 | return rc; | ||
326 | } | ||
327 | EXPORT_SYMBOL(hmcdrv_ftp_startup); | ||
328 | |||
329 | /** | ||
330 | * hmcdrv_ftp_shutdown() - shutdown of HMC drive FTP functionality for a | ||
331 | * dedicated (owner) instance | ||
332 | */ | ||
333 | void hmcdrv_ftp_shutdown(void) | ||
334 | { | ||
335 | mutex_lock(&hmcdrv_ftp_mutex); | ||
336 | --hmcdrv_ftp_refcnt; | ||
337 | |||
338 | if ((hmcdrv_ftp_refcnt == 0) && hmcdrv_ftp_funcs) | ||
339 | hmcdrv_ftp_funcs->shutdown(); | ||
340 | |||
341 | mutex_unlock(&hmcdrv_ftp_mutex); | ||
342 | } | ||
343 | EXPORT_SYMBOL(hmcdrv_ftp_shutdown); | ||
diff --git a/drivers/s390/char/hmcdrv_ftp.h b/drivers/s390/char/hmcdrv_ftp.h new file mode 100644 index 000000000000..f3643a7b3676 --- /dev/null +++ b/drivers/s390/char/hmcdrv_ftp.h | |||
@@ -0,0 +1,63 @@ | |||
1 | /* | ||
2 | * SE/HMC Drive FTP Services | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | */ | ||
7 | |||
8 | #ifndef __HMCDRV_FTP_H__ | ||
9 | #define __HMCDRV_FTP_H__ | ||
10 | |||
11 | #include <linux/types.h> /* size_t, loff_t */ | ||
12 | |||
13 | /* | ||
14 | * HMC drive FTP Service max. length of path (w/ EOS) | ||
15 | */ | ||
16 | #define HMCDRV_FTP_FIDENT_MAX 192 | ||
17 | |||
18 | /** | ||
19 | * enum hmcdrv_ftp_cmdid - HMC drive FTP commands | ||
20 | * @HMCDRV_FTP_NOOP: do nothing (only for probing) | ||
21 | * @HMCDRV_FTP_GET: read a file | ||
22 | * @HMCDRV_FTP_PUT: (over-) write a file | ||
23 | * @HMCDRV_FTP_APPEND: append to a file | ||
24 | * @HMCDRV_FTP_DIR: list directory long (ls -l) | ||
25 | * @HMCDRV_FTP_NLIST: list files, no directories (name list) | ||
26 | * @HMCDRV_FTP_DELETE: delete a file | ||
27 | * @HMCDRV_FTP_CANCEL: cancel operation (SCLP/LPAR only) | ||
28 | */ | ||
29 | enum hmcdrv_ftp_cmdid { | ||
30 | HMCDRV_FTP_NOOP = 0, | ||
31 | HMCDRV_FTP_GET = 1, | ||
32 | HMCDRV_FTP_PUT = 2, | ||
33 | HMCDRV_FTP_APPEND = 3, | ||
34 | HMCDRV_FTP_DIR = 4, | ||
35 | HMCDRV_FTP_NLIST = 5, | ||
36 | HMCDRV_FTP_DELETE = 6, | ||
37 | HMCDRV_FTP_CANCEL = 7 | ||
38 | }; | ||
39 | |||
40 | /** | ||
41 | * struct hmcdrv_ftp_cmdspec - FTP command specification | ||
42 | * @id: FTP command ID | ||
43 | * @ofs: offset in file | ||
44 | * @fname: filename (ASCII), null-terminated | ||
45 | * @buf: kernel-space transfer data buffer, 4k aligned | ||
46 | * @len: (max) number of bytes to transfer from/to @buf | ||
47 | */ | ||
48 | struct hmcdrv_ftp_cmdspec { | ||
49 | enum hmcdrv_ftp_cmdid id; | ||
50 | loff_t ofs; | ||
51 | const char *fname; | ||
52 | void __kernel *buf; | ||
53 | size_t len; | ||
54 | }; | ||
55 | |||
56 | int hmcdrv_ftp_startup(void); | ||
57 | void hmcdrv_ftp_shutdown(void); | ||
58 | int hmcdrv_ftp_probe(void); | ||
59 | ssize_t hmcdrv_ftp_do(const struct hmcdrv_ftp_cmdspec *ftp); | ||
60 | ssize_t hmcdrv_ftp_cmd(char __kernel *cmd, loff_t offset, | ||
61 | char __user *buf, size_t len); | ||
62 | |||
63 | #endif /* __HMCDRV_FTP_H__ */ | ||
diff --git a/drivers/s390/char/hmcdrv_mod.c b/drivers/s390/char/hmcdrv_mod.c new file mode 100644 index 000000000000..505c6a78ee1a --- /dev/null +++ b/drivers/s390/char/hmcdrv_mod.c | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * HMC Drive DVD Module | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | */ | ||
7 | |||
8 | #define KMSG_COMPONENT "hmcdrv" | ||
9 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
10 | |||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/moduleparam.h> | ||
14 | #include <linux/version.h> | ||
15 | #include <linux/stat.h> | ||
16 | |||
17 | #include "hmcdrv_ftp.h" | ||
18 | #include "hmcdrv_dev.h" | ||
19 | #include "hmcdrv_cache.h" | ||
20 | |||
21 | MODULE_LICENSE("GPL"); | ||
22 | MODULE_AUTHOR("Copyright 2013 IBM Corporation"); | ||
23 | MODULE_DESCRIPTION("HMC drive DVD access"); | ||
24 | |||
25 | /* | ||
26 | * module parameter 'cachesize' | ||
27 | */ | ||
28 | static size_t hmcdrv_mod_cachesize = HMCDRV_CACHE_SIZE_DFLT; | ||
29 | module_param_named(cachesize, hmcdrv_mod_cachesize, ulong, S_IRUGO); | ||
30 | |||
31 | /** | ||
32 | * hmcdrv_mod_init() - module init function | ||
33 | */ | ||
34 | static int __init hmcdrv_mod_init(void) | ||
35 | { | ||
36 | int rc = hmcdrv_ftp_probe(); /* perform w/o cache */ | ||
37 | |||
38 | if (rc) | ||
39 | return rc; | ||
40 | |||
41 | rc = hmcdrv_cache_startup(hmcdrv_mod_cachesize); | ||
42 | |||
43 | if (rc) | ||
44 | return rc; | ||
45 | |||
46 | rc = hmcdrv_dev_init(); | ||
47 | |||
48 | if (rc) | ||
49 | hmcdrv_cache_shutdown(); | ||
50 | |||
51 | return rc; | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * hmcdrv_mod_exit() - module exit function | ||
56 | */ | ||
57 | static void __exit hmcdrv_mod_exit(void) | ||
58 | { | ||
59 | hmcdrv_dev_exit(); | ||
60 | hmcdrv_cache_shutdown(); | ||
61 | } | ||
62 | |||
63 | module_init(hmcdrv_mod_init); | ||
64 | module_exit(hmcdrv_mod_exit); | ||
diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index a68b5ec7d042..a88069f8c677 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h | |||
@@ -19,6 +19,7 @@ | |||
19 | 19 | ||
20 | #define EVTYP_OPCMD 0x01 | 20 | #define EVTYP_OPCMD 0x01 |
21 | #define EVTYP_MSG 0x02 | 21 | #define EVTYP_MSG 0x02 |
22 | #define EVTYP_DIAG_TEST 0x07 | ||
22 | #define EVTYP_STATECHANGE 0x08 | 23 | #define EVTYP_STATECHANGE 0x08 |
23 | #define EVTYP_PMSGCMD 0x09 | 24 | #define EVTYP_PMSGCMD 0x09 |
24 | #define EVTYP_CNTLPROGOPCMD 0x20 | 25 | #define EVTYP_CNTLPROGOPCMD 0x20 |
@@ -32,6 +33,7 @@ | |||
32 | 33 | ||
33 | #define EVTYP_OPCMD_MASK 0x80000000 | 34 | #define EVTYP_OPCMD_MASK 0x80000000 |
34 | #define EVTYP_MSG_MASK 0x40000000 | 35 | #define EVTYP_MSG_MASK 0x40000000 |
36 | #define EVTYP_DIAG_TEST_MASK 0x02000000 | ||
35 | #define EVTYP_STATECHANGE_MASK 0x01000000 | 37 | #define EVTYP_STATECHANGE_MASK 0x01000000 |
36 | #define EVTYP_PMSGCMD_MASK 0x00800000 | 38 | #define EVTYP_PMSGCMD_MASK 0x00800000 |
37 | #define EVTYP_CTLPROGOPCMD_MASK 0x00000001 | 39 | #define EVTYP_CTLPROGOPCMD_MASK 0x00000001 |
diff --git a/drivers/s390/char/sclp_diag.h b/drivers/s390/char/sclp_diag.h new file mode 100644 index 000000000000..59c4afa5e670 --- /dev/null +++ b/drivers/s390/char/sclp_diag.h | |||
@@ -0,0 +1,89 @@ | |||
1 | /* | ||
2 | * Copyright IBM Corp. 2013 | ||
3 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
4 | */ | ||
5 | |||
6 | #ifndef _SCLP_DIAG_H | ||
7 | #define _SCLP_DIAG_H | ||
8 | |||
9 | #include <linux/types.h> | ||
10 | |||
11 | /* return codes for Diagnostic Test FTP Service, as indicated in member | ||
12 | * sclp_diag_ftp::ldflg | ||
13 | */ | ||
14 | #define SCLP_DIAG_FTP_OK 0x80U /* success */ | ||
15 | #define SCLP_DIAG_FTP_LDFAIL 0x01U /* load failed */ | ||
16 | #define SCLP_DIAG_FTP_LDNPERM 0x02U /* not allowed */ | ||
17 | #define SCLP_DIAG_FTP_LDRUNS 0x03U /* LD runs */ | ||
18 | #define SCLP_DIAG_FTP_LDNRUNS 0x04U /* LD does not run */ | ||
19 | |||
20 | #define SCLP_DIAG_FTP_XPCX 0x80 /* PCX communication code */ | ||
21 | #define SCLP_DIAG_FTP_ROUTE 4 /* routing code for new FTP service */ | ||
22 | |||
23 | /* | ||
24 | * length of Diagnostic Test FTP Service event buffer | ||
25 | */ | ||
26 | #define SCLP_DIAG_FTP_EVBUF_LEN \ | ||
27 | (offsetof(struct sclp_diag_evbuf, mdd) + \ | ||
28 | sizeof(struct sclp_diag_ftp)) | ||
29 | |||
30 | /** | ||
31 | * struct sclp_diag_ftp - Diagnostic Test FTP Service model-dependent data | ||
32 | * @pcx: code for PCX communication (should be 0x80) | ||
33 | * @ldflg: load flag (see defines above) | ||
34 | * @cmd: FTP command | ||
35 | * @pgsize: page size (0 = 4kB, 1 = large page size) | ||
36 | * @srcflg: source flag | ||
37 | * @spare: reserved (zeroes) | ||
38 | * @offset: file offset | ||
39 | * @fsize: file size | ||
40 | * @length: buffer size resp. bytes transferred | ||
41 | * @failaddr: failing address | ||
42 | * @bufaddr: buffer address, virtual | ||
43 | * @asce: region or segment table designation | ||
44 | * @fident: file name (ASCII, zero-terminated) | ||
45 | */ | ||
46 | struct sclp_diag_ftp { | ||
47 | u8 pcx; | ||
48 | u8 ldflg; | ||
49 | u8 cmd; | ||
50 | u8 pgsize; | ||
51 | u8 srcflg; | ||
52 | u8 spare; | ||
53 | u64 offset; | ||
54 | u64 fsize; | ||
55 | u64 length; | ||
56 | u64 failaddr; | ||
57 | u64 bufaddr; | ||
58 | u64 asce; | ||
59 | |||
60 | u8 fident[256]; | ||
61 | } __packed; | ||
62 | |||
63 | /** | ||
64 | * struct sclp_diag_evbuf - Diagnostic Test (ET7) Event Buffer | ||
65 | * @hdr: event buffer header | ||
66 | * @route: diagnostic route | ||
67 | * @mdd: model-dependent data (@route dependent) | ||
68 | */ | ||
69 | struct sclp_diag_evbuf { | ||
70 | struct evbuf_header hdr; | ||
71 | u16 route; | ||
72 | |||
73 | union { | ||
74 | struct sclp_diag_ftp ftp; | ||
75 | } mdd; | ||
76 | } __packed; | ||
77 | |||
78 | /** | ||
79 | * struct sclp_diag_sccb - Diagnostic Test (ET7) SCCB | ||
80 | * @hdr: SCCB header | ||
81 | * @evbuf: event buffer | ||
82 | */ | ||
83 | struct sclp_diag_sccb { | ||
84 | |||
85 | struct sccb_header hdr; | ||
86 | struct sclp_diag_evbuf evbuf; | ||
87 | } __packed; | ||
88 | |||
89 | #endif /* _SCLP_DIAG_H */ | ||
diff --git a/drivers/s390/char/sclp_ftp.c b/drivers/s390/char/sclp_ftp.c new file mode 100644 index 000000000000..6561cc5b2d5d --- /dev/null +++ b/drivers/s390/char/sclp_ftp.c | |||
@@ -0,0 +1,275 @@ | |||
1 | /* | ||
2 | * SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | #define KMSG_COMPONENT "hmcdrv" | ||
10 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/mm.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/wait.h> | ||
17 | #include <linux/string.h> | ||
18 | #include <linux/jiffies.h> | ||
19 | #include <asm/sysinfo.h> | ||
20 | #include <asm/ebcdic.h> | ||
21 | |||
22 | #include "sclp.h" | ||
23 | #include "sclp_diag.h" | ||
24 | #include "sclp_ftp.h" | ||
25 | |||
26 | static DECLARE_COMPLETION(sclp_ftp_rx_complete); | ||
27 | static u8 sclp_ftp_ldflg; | ||
28 | static u64 sclp_ftp_fsize; | ||
29 | static u64 sclp_ftp_length; | ||
30 | |||
31 | /** | ||
32 | * sclp_ftp_txcb() - Diagnostic Test FTP services SCLP command callback | ||
33 | */ | ||
34 | static void sclp_ftp_txcb(struct sclp_req *req, void *data) | ||
35 | { | ||
36 | struct completion *completion = data; | ||
37 | |||
38 | #ifdef DEBUG | ||
39 | pr_debug("SCLP (ET7) TX-IRQ, SCCB @ 0x%p: %*phN\n", | ||
40 | req->sccb, 24, req->sccb); | ||
41 | #endif | ||
42 | complete(completion); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * sclp_ftp_rxcb() - Diagnostic Test FTP services receiver event callback | ||
47 | */ | ||
48 | static void sclp_ftp_rxcb(struct evbuf_header *evbuf) | ||
49 | { | ||
50 | struct sclp_diag_evbuf *diag = (struct sclp_diag_evbuf *) evbuf; | ||
51 | |||
52 | /* | ||
53 | * Check for Diagnostic Test FTP Service | ||
54 | */ | ||
55 | if (evbuf->type != EVTYP_DIAG_TEST || | ||
56 | diag->route != SCLP_DIAG_FTP_ROUTE || | ||
57 | diag->mdd.ftp.pcx != SCLP_DIAG_FTP_XPCX || | ||
58 | evbuf->length < SCLP_DIAG_FTP_EVBUF_LEN) | ||
59 | return; | ||
60 | |||
61 | #ifdef DEBUG | ||
62 | pr_debug("SCLP (ET7) RX-IRQ, Event @ 0x%p: %*phN\n", | ||
63 | evbuf, 24, evbuf); | ||
64 | #endif | ||
65 | |||
66 | /* | ||
67 | * Because the event buffer is located in a page which is owned | ||
68 | * by the SCLP core, all data of interest must be copied. The | ||
69 | * error indication is in 'sclp_ftp_ldflg' | ||
70 | */ | ||
71 | sclp_ftp_ldflg = diag->mdd.ftp.ldflg; | ||
72 | sclp_ftp_fsize = diag->mdd.ftp.fsize; | ||
73 | sclp_ftp_length = diag->mdd.ftp.length; | ||
74 | |||
75 | complete(&sclp_ftp_rx_complete); | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * sclp_ftp_et7() - start a Diagnostic Test FTP Service SCLP request | ||
80 | * @ftp: pointer to FTP descriptor | ||
81 | * | ||
82 | * Return: 0 on success, else a (negative) error code | ||
83 | */ | ||
84 | static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp) | ||
85 | { | ||
86 | struct completion completion; | ||
87 | struct sclp_diag_sccb *sccb; | ||
88 | struct sclp_req *req; | ||
89 | size_t len; | ||
90 | int rc; | ||
91 | |||
92 | req = kzalloc(sizeof(*req), GFP_KERNEL); | ||
93 | sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); | ||
94 | if (!req || !sccb) { | ||
95 | rc = -ENOMEM; | ||
96 | goto out_free; | ||
97 | } | ||
98 | |||
99 | sccb->hdr.length = SCLP_DIAG_FTP_EVBUF_LEN + | ||
100 | sizeof(struct sccb_header); | ||
101 | sccb->evbuf.hdr.type = EVTYP_DIAG_TEST; | ||
102 | sccb->evbuf.hdr.length = SCLP_DIAG_FTP_EVBUF_LEN; | ||
103 | sccb->evbuf.hdr.flags = 0; /* clear processed-buffer */ | ||
104 | sccb->evbuf.route = SCLP_DIAG_FTP_ROUTE; | ||
105 | sccb->evbuf.mdd.ftp.pcx = SCLP_DIAG_FTP_XPCX; | ||
106 | sccb->evbuf.mdd.ftp.srcflg = 0; | ||
107 | sccb->evbuf.mdd.ftp.pgsize = 0; | ||
108 | sccb->evbuf.mdd.ftp.asce = _ASCE_REAL_SPACE; | ||
109 | sccb->evbuf.mdd.ftp.ldflg = SCLP_DIAG_FTP_LDFAIL; | ||
110 | sccb->evbuf.mdd.ftp.fsize = 0; | ||
111 | sccb->evbuf.mdd.ftp.cmd = ftp->id; | ||
112 | sccb->evbuf.mdd.ftp.offset = ftp->ofs; | ||
113 | sccb->evbuf.mdd.ftp.length = ftp->len; | ||
114 | sccb->evbuf.mdd.ftp.bufaddr = virt_to_phys(ftp->buf); | ||
115 | |||
116 | len = strlcpy(sccb->evbuf.mdd.ftp.fident, ftp->fname, | ||
117 | HMCDRV_FTP_FIDENT_MAX); | ||
118 | if (len >= HMCDRV_FTP_FIDENT_MAX) { | ||
119 | rc = -EINVAL; | ||
120 | goto out_free; | ||
121 | } | ||
122 | |||
123 | req->command = SCLP_CMDW_WRITE_EVENT_DATA; | ||
124 | req->sccb = sccb; | ||
125 | req->status = SCLP_REQ_FILLED; | ||
126 | req->callback = sclp_ftp_txcb; | ||
127 | req->callback_data = &completion; | ||
128 | |||
129 | init_completion(&completion); | ||
130 | |||
131 | rc = sclp_add_request(req); | ||
132 | if (rc) | ||
133 | goto out_free; | ||
134 | |||
135 | /* Wait for end of ftp sclp command. */ | ||
136 | wait_for_completion(&completion); | ||
137 | |||
138 | #ifdef DEBUG | ||
139 | pr_debug("status of SCLP (ET7) request is 0x%04x (0x%02x)\n", | ||
140 | sccb->hdr.response_code, sccb->evbuf.hdr.flags); | ||
141 | #endif | ||
142 | |||
143 | /* | ||
144 | * Check if sclp accepted the request. The data transfer runs | ||
145 | * asynchronously and the completion is indicated with an | ||
146 | * sclp ET7 event. | ||
147 | */ | ||
148 | if (req->status != SCLP_REQ_DONE || | ||
149 | (sccb->evbuf.hdr.flags & 0x80) == 0 || /* processed-buffer */ | ||
150 | (sccb->hdr.response_code & 0xffU) != 0x20U) { | ||
151 | rc = -EIO; | ||
152 | } | ||
153 | |||
154 | out_free: | ||
155 | free_page((unsigned long) sccb); | ||
156 | kfree(req); | ||
157 | return rc; | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * sclp_ftp_cmd() - executes a HMC related SCLP Diagnose (ET7) FTP command | ||
162 | * @ftp: pointer to FTP command specification | ||
163 | * @fsize: return of file size (or NULL if undesirable) | ||
164 | * | ||
165 | * Attention: Notice that this function is not reentrant - so the caller | ||
166 | * must ensure locking. | ||
167 | * | ||
168 | * Return: number of bytes read/written or a (negative) error code | ||
169 | */ | ||
170 | ssize_t sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize) | ||
171 | { | ||
172 | ssize_t len; | ||
173 | #ifdef DEBUG | ||
174 | unsigned long start_jiffies; | ||
175 | |||
176 | pr_debug("starting SCLP (ET7), cmd %d for '%s' at %lld with %zd bytes\n", | ||
177 | ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len); | ||
178 | start_jiffies = jiffies; | ||
179 | #endif | ||
180 | |||
181 | init_completion(&sclp_ftp_rx_complete); | ||
182 | |||
183 | /* Start ftp sclp command. */ | ||
184 | len = sclp_ftp_et7(ftp); | ||
185 | if (len) | ||
186 | goto out_unlock; | ||
187 | |||
188 | /* | ||
189 | * There is no way to cancel the sclp ET7 request, the code | ||
190 | * needs to wait unconditionally until the transfer is complete. | ||
191 | */ | ||
192 | wait_for_completion(&sclp_ftp_rx_complete); | ||
193 | |||
194 | #ifdef DEBUG | ||
195 | pr_debug("completed SCLP (ET7) request after %lu ms (all)\n", | ||
196 | (jiffies - start_jiffies) * 1000 / HZ); | ||
197 | pr_debug("return code of SCLP (ET7) FTP Service is 0x%02x, with %lld/%lld bytes\n", | ||
198 | sclp_ftp_ldflg, sclp_ftp_length, sclp_ftp_fsize); | ||
199 | #endif | ||
200 | |||
201 | switch (sclp_ftp_ldflg) { | ||
202 | case SCLP_DIAG_FTP_OK: | ||
203 | len = sclp_ftp_length; | ||
204 | if (fsize) | ||
205 | *fsize = sclp_ftp_fsize; | ||
206 | break; | ||
207 | case SCLP_DIAG_FTP_LDNPERM: | ||
208 | len = -EPERM; | ||
209 | break; | ||
210 | case SCLP_DIAG_FTP_LDRUNS: | ||
211 | len = -EBUSY; | ||
212 | break; | ||
213 | case SCLP_DIAG_FTP_LDFAIL: | ||
214 | len = -ENOENT; | ||
215 | break; | ||
216 | default: | ||
217 | len = -EIO; | ||
218 | break; | ||
219 | } | ||
220 | |||
221 | out_unlock: | ||
222 | return len; | ||
223 | } | ||
224 | |||
225 | /* | ||
226 | * ET7 event listener | ||
227 | */ | ||
228 | static struct sclp_register sclp_ftp_event = { | ||
229 | .send_mask = EVTYP_DIAG_TEST_MASK, /* want tx events */ | ||
230 | .receive_mask = EVTYP_DIAG_TEST_MASK, /* want rx events */ | ||
231 | .receiver_fn = sclp_ftp_rxcb, /* async callback (rx) */ | ||
232 | .state_change_fn = NULL, | ||
233 | .pm_event_fn = NULL, | ||
234 | }; | ||
235 | |||
236 | /** | ||
237 | * sclp_ftp_startup() - startup of FTP services, when running on LPAR | ||
238 | */ | ||
239 | int sclp_ftp_startup(void) | ||
240 | { | ||
241 | #ifdef DEBUG | ||
242 | unsigned long info; | ||
243 | #endif | ||
244 | int rc; | ||
245 | |||
246 | rc = sclp_register(&sclp_ftp_event); | ||
247 | if (rc) | ||
248 | return rc; | ||
249 | |||
250 | #ifdef DEBUG | ||
251 | info = get_zeroed_page(GFP_KERNEL); | ||
252 | |||
253 | if (info != 0) { | ||
254 | struct sysinfo_2_2_2 *info222 = (struct sysinfo_2_2_2 *)info; | ||
255 | |||
256 | if (!stsi(info222, 2, 2, 2)) { /* get SYSIB 2.2.2 */ | ||
257 | info222->name[sizeof(info222->name) - 1] = '\0'; | ||
258 | EBCASC_500(info222->name, sizeof(info222->name) - 1); | ||
259 | pr_debug("SCLP (ET7) FTP Service working on LPAR %u (%s)\n", | ||
260 | info222->lpar_number, info222->name); | ||
261 | } | ||
262 | |||
263 | free_page(info); | ||
264 | } | ||
265 | #endif /* DEBUG */ | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | /** | ||
270 | * sclp_ftp_shutdown() - shutdown of FTP services, when running on LPAR | ||
271 | */ | ||
272 | void sclp_ftp_shutdown(void) | ||
273 | { | ||
274 | sclp_unregister(&sclp_ftp_event); | ||
275 | } | ||
diff --git a/drivers/s390/char/sclp_ftp.h b/drivers/s390/char/sclp_ftp.h new file mode 100644 index 000000000000..98ba3183e7d9 --- /dev/null +++ b/drivers/s390/char/sclp_ftp.h | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR | ||
3 | * | ||
4 | * Notice that all functions exported here are not reentrant. | ||
5 | * So usage should be exclusive, ensured by the caller (e.g. using a | ||
6 | * mutex). | ||
7 | * | ||
8 | * Copyright IBM Corp. 2013 | ||
9 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
10 | */ | ||
11 | |||
12 | #ifndef __SCLP_FTP_H__ | ||
13 | #define __SCLP_FTP_H__ | ||
14 | |||
15 | #include "hmcdrv_ftp.h" | ||
16 | |||
17 | int sclp_ftp_startup(void); | ||
18 | void sclp_ftp_shutdown(void); | ||
19 | ssize_t sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize); | ||
20 | |||
21 | #endif /* __SCLP_FTP_H__ */ | ||