aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/linux/kfifo.h47
-rw-r--r--kernel/kfifo.c139
2 files changed, 170 insertions, 16 deletions
diff --git a/include/linux/kfifo.h b/include/linux/kfifo.h
index dd53eed3e2af..d3230fb08bc7 100644
--- a/include/linux/kfifo.h
+++ b/include/linux/kfifo.h
@@ -125,6 +125,16 @@ static inline void kfifo_reset(struct kfifo *fifo)
125} 125}
126 126
127/** 127/**
128 * kfifo_reset_out - skip FIFO contents
129 * @fifo: the fifo to be emptied.
130 */
131static inline void kfifo_reset_out(struct kfifo *fifo)
132{
133 smp_mb();
134 fifo->out = fifo->in;
135}
136
137/**
128 * kfifo_size - returns the size of the fifo in bytes 138 * kfifo_size - returns the size of the fifo in bytes
129 * @fifo: the fifo to be used. 139 * @fifo: the fifo to be used.
130 */ 140 */
@@ -231,4 +241,41 @@ static inline __must_check unsigned int kfifo_out_locked(struct kfifo *fifo,
231 return ret; 241 return ret;
232} 242}
233 243
244extern void kfifo_skip(struct kfifo *fifo, unsigned int len);
245
246extern __must_check unsigned int kfifo_from_user(struct kfifo *fifo,
247 const void __user *from, unsigned int n);
248
249extern __must_check unsigned int kfifo_to_user(struct kfifo *fifo,
250 void __user *to, unsigned int n);
251
252/**
253 * __kfifo_add_out internal helper function for updating the out offset
254 */
255static inline void __kfifo_add_out(struct kfifo *fifo,
256 unsigned int off)
257{
258 smp_mb();
259 fifo->out += off;
260}
261
262/**
263 * __kfifo_add_in internal helper function for updating the in offset
264 */
265static inline void __kfifo_add_in(struct kfifo *fifo,
266 unsigned int off)
267{
268 smp_wmb();
269 fifo->in += off;
270}
271
272/**
273 * __kfifo_off internal helper function for calculating the index of a
274 * given offeset
275 */
276static inline unsigned int __kfifo_off(struct kfifo *fifo, unsigned int off)
277{
278 return off & (fifo->size - 1);
279}
280
234#endif 281#endif
diff --git a/kernel/kfifo.c b/kernel/kfifo.c
index d659442e73f2..2a78425ef67f 100644
--- a/kernel/kfifo.c
+++ b/kernel/kfifo.c
@@ -26,6 +26,7 @@
26#include <linux/err.h> 26#include <linux/err.h>
27#include <linux/kfifo.h> 27#include <linux/kfifo.h>
28#include <linux/log2.h> 28#include <linux/log2.h>
29#include <linux/uaccess.h>
29 30
30static void _kfifo_init(struct kfifo *fifo, unsigned char *buffer, 31static void _kfifo_init(struct kfifo *fifo, unsigned char *buffer,
31 unsigned int size) 32 unsigned int size)
@@ -100,6 +101,21 @@ void kfifo_free(struct kfifo *fifo)
100EXPORT_SYMBOL(kfifo_free); 101EXPORT_SYMBOL(kfifo_free);
101 102
102/** 103/**
104 * kfifo_skip - skip output data
105 * @fifo: the fifo to be used.
106 * @len: number of bytes to skip
107 */
108void kfifo_skip(struct kfifo *fifo, unsigned int len)
109{
110 if (len < kfifo_len(fifo)) {
111 __kfifo_add_out(fifo, len);
112 return;
113 }
114 kfifo_reset_out(fifo);
115}
116EXPORT_SYMBOL(kfifo_skip);
117
118/**
103 * kfifo_in - puts some data into the FIFO 119 * kfifo_in - puts some data into the FIFO
104 * @fifo: the fifo to be used. 120 * @fifo: the fifo to be used.
105 * @from: the data to be added. 121 * @from: the data to be added.
@@ -115,6 +131,7 @@ EXPORT_SYMBOL(kfifo_free);
115unsigned int kfifo_in(struct kfifo *fifo, 131unsigned int kfifo_in(struct kfifo *fifo,
116 const unsigned char *from, unsigned int len) 132 const unsigned char *from, unsigned int len)
117{ 133{
134 unsigned int off;
118 unsigned int l; 135 unsigned int l;
119 136
120 len = min(len, fifo->size - fifo->in + fifo->out); 137 len = min(len, fifo->size - fifo->in + fifo->out);
@@ -126,21 +143,16 @@ unsigned int kfifo_in(struct kfifo *fifo,
126 143
127 smp_mb(); 144 smp_mb();
128 145
146 off = __kfifo_off(fifo, fifo->in);
147
129 /* first put the data starting from fifo->in to buffer end */ 148 /* first put the data starting from fifo->in to buffer end */
130 l = min(len, fifo->size - (fifo->in & (fifo->size - 1))); 149 l = min(len, fifo->size - off);
131 memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), from, l); 150 memcpy(fifo->buffer + off, from, l);
132 151
133 /* then put the rest (if any) at the beginning of the buffer */ 152 /* then put the rest (if any) at the beginning of the buffer */
134 memcpy(fifo->buffer, from + l, len - l); 153 memcpy(fifo->buffer, from + l, len - l);
135 154
136 /* 155 __kfifo_add_in(fifo, len);
137 * Ensure that we add the bytes to the kfifo -before-
138 * we update the fifo->in index.
139 */
140
141 smp_wmb();
142
143 fifo->in += len;
144 156
145 return len; 157 return len;
146} 158}
@@ -161,6 +173,7 @@ EXPORT_SYMBOL(kfifo_in);
161unsigned int kfifo_out(struct kfifo *fifo, 173unsigned int kfifo_out(struct kfifo *fifo,
162 unsigned char *to, unsigned int len) 174 unsigned char *to, unsigned int len)
163{ 175{
176 unsigned int off;
164 unsigned int l; 177 unsigned int l;
165 178
166 len = min(len, fifo->in - fifo->out); 179 len = min(len, fifo->in - fifo->out);
@@ -172,22 +185,116 @@ unsigned int kfifo_out(struct kfifo *fifo,
172 185
173 smp_rmb(); 186 smp_rmb();
174 187
188 off = __kfifo_off(fifo, fifo->out);
189
175 /* first get the data from fifo->out until the end of the buffer */ 190 /* first get the data from fifo->out until the end of the buffer */
176 l = min(len, fifo->size - (fifo->out & (fifo->size - 1))); 191 l = min(len, fifo->size - off);
177 memcpy(to, fifo->buffer + (fifo->out & (fifo->size - 1)), l); 192 memcpy(to, fifo->buffer + off, l);
178 193
179 /* then get the rest (if any) from the beginning of the buffer */ 194 /* then get the rest (if any) from the beginning of the buffer */
180 memcpy(to + l, fifo->buffer, len - l); 195 memcpy(to + l, fifo->buffer, len - l);
181 196
197 __kfifo_add_out(fifo, len);
198
199 return len;
200}
201EXPORT_SYMBOL(kfifo_out);
202
203/**
204 * kfifo_from_user - puts some data from user space into the FIFO
205 * @fifo: the fifo to be used.
206 * @from: pointer to the data to be added.
207 * @len: the length of the data to be added.
208 *
209 * This function copies at most @len bytes from the @from into the
210 * FIFO depending and returns the number of copied bytes.
211 *
212 * Note that with only one concurrent reader and one concurrent
213 * writer, you don't need extra locking to use these functions.
214 */
215unsigned int kfifo_from_user(struct kfifo *fifo,
216 const void __user *from, unsigned int len)
217{
218 unsigned int off;
219 unsigned int l;
220 int ret;
221
222 len = min(len, fifo->size - fifo->in + fifo->out);
223
182 /* 224 /*
183 * Ensure that we remove the bytes from the kfifo -before- 225 * Ensure that we sample the fifo->out index -before- we
184 * we update the fifo->out index. 226 * start putting bytes into the kfifo.
185 */ 227 */
186 228
187 smp_mb(); 229 smp_mb();
188 230
189 fifo->out += len; 231 off = __kfifo_off(fifo, fifo->in);
232
233 /* first put the data starting from fifo->in to buffer end */
234 l = min(len, fifo->size - off);
235 ret = copy_from_user(fifo->buffer + off, from, l);
236
237 if (unlikely(ret))
238 return l - ret;
239
240 /* then put the rest (if any) at the beginning of the buffer */
241 ret = copy_from_user(fifo->buffer, from + l, len - l);
242
243 if (unlikely(ret))
244 return len - ret;
245
246 __kfifo_add_in(fifo, len);
190 247
191 return len; 248 return len;
192} 249}
193EXPORT_SYMBOL(kfifo_out); 250EXPORT_SYMBOL(kfifo_from_user);
251
252/**
253 * kfifo_to_user - gets data from the FIFO and write it to user space
254 * @fifo: the fifo to be used.
255 * @to: where the data must be copied.
256 * @len: the size of the destination buffer.
257 *
258 * This function copies at most @len bytes from the FIFO into the
259 * @to buffer and returns the number of copied bytes.
260 *
261 * Note that with only one concurrent reader and one concurrent
262 * writer, you don't need extra locking to use these functions.
263 */
264unsigned int kfifo_to_user(struct kfifo *fifo,
265 void __user *to, unsigned int len)
266{
267 unsigned int off;
268 unsigned int l;
269 int ret;
270
271 len = min(len, fifo->in - fifo->out);
272
273 /*
274 * Ensure that we sample the fifo->in index -before- we
275 * start removing bytes from the kfifo.
276 */
277
278 smp_rmb();
279
280 off = __kfifo_off(fifo, fifo->out);
281
282 /* first get the data from fifo->out until the end of the buffer */
283 l = min(len, fifo->size - off);
284 ret = copy_to_user(to, fifo->buffer + off, l);
285
286 if (unlikely(ret))
287 return l - ret;
288
289 /* then get the rest (if any) from the beginning of the buffer */
290 ret = copy_to_user(to + l, fifo->buffer, len - l);
291
292 if (unlikely(ret))
293 return len - ret;
294
295 __kfifo_add_out(fifo, len);
296
297 return len;
298}
299EXPORT_SYMBOL(kfifo_to_user);
300