1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * SE/HMC Drive (Read) Cache Functions
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright IBM Corp. 2013
6*4882a593Smuzhiyun * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
7*4882a593Smuzhiyun *
8*4882a593Smuzhiyun */
9*4882a593Smuzhiyun
10*4882a593Smuzhiyun #define KMSG_COMPONENT "hmcdrv"
11*4882a593Smuzhiyun #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
12*4882a593Smuzhiyun
13*4882a593Smuzhiyun #include <linux/kernel.h>
14*4882a593Smuzhiyun #include <linux/mm.h>
15*4882a593Smuzhiyun #include <linux/jiffies.h>
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun #include "hmcdrv_ftp.h"
18*4882a593Smuzhiyun #include "hmcdrv_cache.h"
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #define HMCDRV_CACHE_TIMEOUT 30 /* aging timeout in seconds */
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun /**
23*4882a593Smuzhiyun * struct hmcdrv_cache_entry - file cache (only used on read/dir)
24*4882a593Smuzhiyun * @id: FTP command ID
25*4882a593Smuzhiyun * @content: kernel-space buffer, 4k aligned
26*4882a593Smuzhiyun * @len: size of @content cache (0 if caching disabled)
27*4882a593Smuzhiyun * @ofs: start of content within file (-1 if no cached content)
28*4882a593Smuzhiyun * @fname: file name
29*4882a593Smuzhiyun * @fsize: file size
30*4882a593Smuzhiyun * @timeout: cache timeout in jiffies
31*4882a593Smuzhiyun *
32*4882a593Smuzhiyun * Notice that the first three members (id, fname, fsize) are cached on all
33*4882a593Smuzhiyun * read/dir requests. But content is cached only under some preconditions.
34*4882a593Smuzhiyun * Uncached content is signalled by a negative value of @ofs.
35*4882a593Smuzhiyun */
36*4882a593Smuzhiyun struct hmcdrv_cache_entry {
37*4882a593Smuzhiyun enum hmcdrv_ftp_cmdid id;
38*4882a593Smuzhiyun char fname[HMCDRV_FTP_FIDENT_MAX];
39*4882a593Smuzhiyun size_t fsize;
40*4882a593Smuzhiyun loff_t ofs;
41*4882a593Smuzhiyun unsigned long timeout;
42*4882a593Smuzhiyun void *content;
43*4882a593Smuzhiyun size_t len;
44*4882a593Smuzhiyun };
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun static int hmcdrv_cache_order; /* cache allocated page order */
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun static struct hmcdrv_cache_entry hmcdrv_cache_file = {
49*4882a593Smuzhiyun .fsize = SIZE_MAX,
50*4882a593Smuzhiyun .ofs = -1,
51*4882a593Smuzhiyun .len = 0,
52*4882a593Smuzhiyun .fname = {'\0'}
53*4882a593Smuzhiyun };
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun /**
56*4882a593Smuzhiyun * hmcdrv_cache_get() - looks for file data/content in read cache
57*4882a593Smuzhiyun * @ftp: pointer to FTP command specification
58*4882a593Smuzhiyun *
59*4882a593Smuzhiyun * Return: number of bytes read from cache or a negative number if nothing
60*4882a593Smuzhiyun * in content cache (for the file/cmd specified in @ftp)
61*4882a593Smuzhiyun */
hmcdrv_cache_get(const struct hmcdrv_ftp_cmdspec * ftp)62*4882a593Smuzhiyun static ssize_t hmcdrv_cache_get(const struct hmcdrv_ftp_cmdspec *ftp)
63*4882a593Smuzhiyun {
64*4882a593Smuzhiyun loff_t pos; /* position in cache (signed) */
65*4882a593Smuzhiyun ssize_t len;
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun if ((ftp->id != hmcdrv_cache_file.id) ||
68*4882a593Smuzhiyun strcmp(hmcdrv_cache_file.fname, ftp->fname))
69*4882a593Smuzhiyun return -1;
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun if (ftp->ofs >= hmcdrv_cache_file.fsize) /* EOF ? */
72*4882a593Smuzhiyun return 0;
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun if ((hmcdrv_cache_file.ofs < 0) || /* has content? */
75*4882a593Smuzhiyun time_after(jiffies, hmcdrv_cache_file.timeout))
76*4882a593Smuzhiyun return -1;
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun /* there seems to be cached content - calculate the maximum number
79*4882a593Smuzhiyun * of bytes that can be returned (regarding file size and offset)
80*4882a593Smuzhiyun */
81*4882a593Smuzhiyun len = hmcdrv_cache_file.fsize - ftp->ofs;
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun if (len > ftp->len)
84*4882a593Smuzhiyun len = ftp->len;
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun /* check if the requested chunk falls into our cache (which starts
87*4882a593Smuzhiyun * at offset 'hmcdrv_cache_file.ofs' in the file of interest)
88*4882a593Smuzhiyun */
89*4882a593Smuzhiyun pos = ftp->ofs - hmcdrv_cache_file.ofs;
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun if ((pos >= 0) &&
92*4882a593Smuzhiyun ((pos + len) <= hmcdrv_cache_file.len)) {
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun memcpy(ftp->buf,
95*4882a593Smuzhiyun hmcdrv_cache_file.content + pos,
96*4882a593Smuzhiyun len);
97*4882a593Smuzhiyun pr_debug("using cached content of '%s', returning %zd/%zd bytes\n",
98*4882a593Smuzhiyun hmcdrv_cache_file.fname, len,
99*4882a593Smuzhiyun hmcdrv_cache_file.fsize);
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun return len;
102*4882a593Smuzhiyun }
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun return -1;
105*4882a593Smuzhiyun }
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun /**
108*4882a593Smuzhiyun * hmcdrv_cache_do() - do a HMC drive CD/DVD transfer with cache update
109*4882a593Smuzhiyun * @ftp: pointer to FTP command specification
110*4882a593Smuzhiyun * @func: FTP transfer function to be used
111*4882a593Smuzhiyun *
112*4882a593Smuzhiyun * Return: number of bytes read/written or a (negative) error code
113*4882a593Smuzhiyun */
hmcdrv_cache_do(const struct hmcdrv_ftp_cmdspec * ftp,hmcdrv_cache_ftpfunc func)114*4882a593Smuzhiyun static ssize_t hmcdrv_cache_do(const struct hmcdrv_ftp_cmdspec *ftp,
115*4882a593Smuzhiyun hmcdrv_cache_ftpfunc func)
116*4882a593Smuzhiyun {
117*4882a593Smuzhiyun ssize_t len;
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun /* only cache content if the read/dir cache really exists
120*4882a593Smuzhiyun * (hmcdrv_cache_file.len > 0), is large enough to handle the
121*4882a593Smuzhiyun * request (hmcdrv_cache_file.len >= ftp->len) and there is a need
122*4882a593Smuzhiyun * to do so (ftp->len > 0)
123*4882a593Smuzhiyun */
124*4882a593Smuzhiyun if ((ftp->len > 0) && (hmcdrv_cache_file.len >= ftp->len)) {
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun /* because the cache is not located at ftp->buf, we have to
127*4882a593Smuzhiyun * assemble a new HMC drive FTP cmd specification (pointing
128*4882a593Smuzhiyun * to our cache, and using the increased size)
129*4882a593Smuzhiyun */
130*4882a593Smuzhiyun struct hmcdrv_ftp_cmdspec cftp = *ftp; /* make a copy */
131*4882a593Smuzhiyun cftp.buf = hmcdrv_cache_file.content; /* and update */
132*4882a593Smuzhiyun cftp.len = hmcdrv_cache_file.len; /* buffer data */
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun len = func(&cftp, &hmcdrv_cache_file.fsize); /* now do */
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun if (len > 0) {
137*4882a593Smuzhiyun pr_debug("caching %zd bytes content for '%s'\n",
138*4882a593Smuzhiyun len, ftp->fname);
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun if (len > ftp->len)
141*4882a593Smuzhiyun len = ftp->len;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun hmcdrv_cache_file.ofs = ftp->ofs;
144*4882a593Smuzhiyun hmcdrv_cache_file.timeout = jiffies +
145*4882a593Smuzhiyun HMCDRV_CACHE_TIMEOUT * HZ;
146*4882a593Smuzhiyun memcpy(ftp->buf, hmcdrv_cache_file.content, len);
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun } else {
149*4882a593Smuzhiyun len = func(ftp, &hmcdrv_cache_file.fsize);
150*4882a593Smuzhiyun hmcdrv_cache_file.ofs = -1; /* invalidate content */
151*4882a593Smuzhiyun }
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun if (len > 0) {
154*4882a593Smuzhiyun /* cache some file info (FTP command, file name and file
155*4882a593Smuzhiyun * size) unconditionally
156*4882a593Smuzhiyun */
157*4882a593Smuzhiyun strlcpy(hmcdrv_cache_file.fname, ftp->fname,
158*4882a593Smuzhiyun HMCDRV_FTP_FIDENT_MAX);
159*4882a593Smuzhiyun hmcdrv_cache_file.id = ftp->id;
160*4882a593Smuzhiyun pr_debug("caching cmd %d, file size %zu for '%s'\n",
161*4882a593Smuzhiyun ftp->id, hmcdrv_cache_file.fsize, ftp->fname);
162*4882a593Smuzhiyun }
163*4882a593Smuzhiyun
164*4882a593Smuzhiyun return len;
165*4882a593Smuzhiyun }
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun /**
168*4882a593Smuzhiyun * hmcdrv_cache_cmd() - perform a cached HMC drive CD/DVD transfer
169*4882a593Smuzhiyun * @ftp: pointer to FTP command specification
170*4882a593Smuzhiyun * @func: FTP transfer function to be used
171*4882a593Smuzhiyun *
172*4882a593Smuzhiyun * Attention: Notice that this function is not reentrant - so the caller
173*4882a593Smuzhiyun * must ensure exclusive execution.
174*4882a593Smuzhiyun *
175*4882a593Smuzhiyun * Return: number of bytes read/written or a (negative) error code
176*4882a593Smuzhiyun */
hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec * ftp,hmcdrv_cache_ftpfunc func)177*4882a593Smuzhiyun ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp,
178*4882a593Smuzhiyun hmcdrv_cache_ftpfunc func)
179*4882a593Smuzhiyun {
180*4882a593Smuzhiyun ssize_t len;
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun if ((ftp->id == HMCDRV_FTP_DIR) || /* read cache */
183*4882a593Smuzhiyun (ftp->id == HMCDRV_FTP_NLIST) ||
184*4882a593Smuzhiyun (ftp->id == HMCDRV_FTP_GET)) {
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun len = hmcdrv_cache_get(ftp);
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun if (len >= 0) /* got it from cache ? */
189*4882a593Smuzhiyun return len; /* yes */
190*4882a593Smuzhiyun
191*4882a593Smuzhiyun len = hmcdrv_cache_do(ftp, func);
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun if (len >= 0)
194*4882a593Smuzhiyun return len;
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun } else {
197*4882a593Smuzhiyun len = func(ftp, NULL); /* simply do original command */
198*4882a593Smuzhiyun }
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun /* invalidate the (read) cache in case there was a write operation
201*4882a593Smuzhiyun * or an error on read/dir
202*4882a593Smuzhiyun */
203*4882a593Smuzhiyun hmcdrv_cache_file.id = HMCDRV_FTP_NOOP;
204*4882a593Smuzhiyun hmcdrv_cache_file.fsize = LLONG_MAX;
205*4882a593Smuzhiyun hmcdrv_cache_file.ofs = -1;
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun return len;
208*4882a593Smuzhiyun }
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun /**
211*4882a593Smuzhiyun * hmcdrv_cache_startup() - startup of HMC drive cache
212*4882a593Smuzhiyun * @cachesize: cache size
213*4882a593Smuzhiyun *
214*4882a593Smuzhiyun * Return: 0 on success, else a (negative) error code
215*4882a593Smuzhiyun */
hmcdrv_cache_startup(size_t cachesize)216*4882a593Smuzhiyun int hmcdrv_cache_startup(size_t cachesize)
217*4882a593Smuzhiyun {
218*4882a593Smuzhiyun if (cachesize > 0) { /* perform caching ? */
219*4882a593Smuzhiyun hmcdrv_cache_order = get_order(cachesize);
220*4882a593Smuzhiyun hmcdrv_cache_file.content =
221*4882a593Smuzhiyun (void *) __get_free_pages(GFP_KERNEL | GFP_DMA,
222*4882a593Smuzhiyun hmcdrv_cache_order);
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun if (!hmcdrv_cache_file.content) {
225*4882a593Smuzhiyun pr_err("Allocating the requested cache size of %zu bytes failed\n",
226*4882a593Smuzhiyun cachesize);
227*4882a593Smuzhiyun return -ENOMEM;
228*4882a593Smuzhiyun }
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun pr_debug("content cache enabled, size is %zu bytes\n",
231*4882a593Smuzhiyun cachesize);
232*4882a593Smuzhiyun }
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun hmcdrv_cache_file.len = cachesize;
235*4882a593Smuzhiyun return 0;
236*4882a593Smuzhiyun }
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun /**
239*4882a593Smuzhiyun * hmcdrv_cache_shutdown() - shutdown of HMC drive cache
240*4882a593Smuzhiyun */
hmcdrv_cache_shutdown(void)241*4882a593Smuzhiyun void hmcdrv_cache_shutdown(void)
242*4882a593Smuzhiyun {
243*4882a593Smuzhiyun if (hmcdrv_cache_file.content) {
244*4882a593Smuzhiyun free_pages((unsigned long) hmcdrv_cache_file.content,
245*4882a593Smuzhiyun hmcdrv_cache_order);
246*4882a593Smuzhiyun hmcdrv_cache_file.content = NULL;
247*4882a593Smuzhiyun }
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun hmcdrv_cache_file.id = HMCDRV_FTP_NOOP;
250*4882a593Smuzhiyun hmcdrv_cache_file.fsize = LLONG_MAX;
251*4882a593Smuzhiyun hmcdrv_cache_file.ofs = -1;
252*4882a593Smuzhiyun hmcdrv_cache_file.len = 0; /* no cache */
253*4882a593Smuzhiyun }
254