Commit a121f24accac1600bf5b6fb1e12eeabdfed7cb1a
Committed by
Linus Torvalds
1 parent
37bdfbbfaa
Exists in
master
and in
4 other branches
kfifo: add kfifo_skip, kfifo_from_user and kfifo_to_user
Add kfifo_reset_out() for save lockless discard the fifo output Add kfifo_skip() to skip a number of output bytes Add kfifo_from_user() to copy user space data into the fifo Add kfifo_to_user() to copy fifo data to user space Signed-off-by: Stefani Seibold <stefani@seibold.net> Acked-by: Greg Kroah-Hartman <gregkh@suse.de> Acked-by: Mauro Carvalho Chehab <mchehab@redhat.com> Acked-by: Andi Kleen <ak@linux.intel.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Showing 2 changed files with 170 additions and 16 deletions Side-by-side Diff
include/linux/kfifo.h
... | ... | @@ -125,6 +125,16 @@ |
125 | 125 | } |
126 | 126 | |
127 | 127 | /** |
128 | + * kfifo_reset_out - skip FIFO contents | |
129 | + * @fifo: the fifo to be emptied. | |
130 | + */ | |
131 | +static inline void kfifo_reset_out(struct kfifo *fifo) | |
132 | +{ | |
133 | + smp_mb(); | |
134 | + fifo->out = fifo->in; | |
135 | +} | |
136 | + | |
137 | +/** | |
128 | 138 | * kfifo_size - returns the size of the fifo in bytes |
129 | 139 | * @fifo: the fifo to be used. |
130 | 140 | */ |
... | ... | @@ -229,6 +239,43 @@ |
229 | 239 | spin_unlock_irqrestore(lock, flags); |
230 | 240 | |
231 | 241 | return ret; |
242 | +} | |
243 | + | |
244 | +extern void kfifo_skip(struct kfifo *fifo, unsigned int len); | |
245 | + | |
246 | +extern __must_check unsigned int kfifo_from_user(struct kfifo *fifo, | |
247 | + const void __user *from, unsigned int n); | |
248 | + | |
249 | +extern __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 | + */ | |
255 | +static 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 | + */ | |
265 | +static 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 | + */ | |
276 | +static inline unsigned int __kfifo_off(struct kfifo *fifo, unsigned int off) | |
277 | +{ | |
278 | + return off & (fifo->size - 1); | |
232 | 279 | } |
233 | 280 | |
234 | 281 | #endif |
kernel/kfifo.c
... | ... | @@ -26,6 +26,7 @@ |
26 | 26 | #include <linux/err.h> |
27 | 27 | #include <linux/kfifo.h> |
28 | 28 | #include <linux/log2.h> |
29 | +#include <linux/uaccess.h> | |
29 | 30 | |
30 | 31 | static void _kfifo_init(struct kfifo *fifo, unsigned char *buffer, |
31 | 32 | unsigned int size) |
... | ... | @@ -100,6 +101,21 @@ |
100 | 101 | EXPORT_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 | + */ | |
108 | +void 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 | +} | |
116 | +EXPORT_SYMBOL(kfifo_skip); | |
117 | + | |
118 | +/** | |
103 | 119 | * kfifo_in - puts some data into the FIFO |
104 | 120 | * @fifo: the fifo to be used. |
105 | 121 | * @from: the data to be added. |
... | ... | @@ -115,6 +131,7 @@ |
115 | 131 | unsigned int kfifo_in(struct kfifo *fifo, |
116 | 132 | const unsigned char *from, unsigned int len) |
117 | 133 | { |
134 | + unsigned int off; | |
118 | 135 | unsigned int l; |
119 | 136 | |
120 | 137 | len = min(len, fifo->size - fifo->in + fifo->out); |
121 | 138 | |
122 | 139 | |
123 | 140 | |
... | ... | @@ -126,22 +143,17 @@ |
126 | 143 | |
127 | 144 | smp_mb(); |
128 | 145 | |
146 | + off = __kfifo_off(fifo, fifo->in); | |
147 | + | |
129 | 148 | /* first put the data starting from fifo->in to buffer end */ |
130 | - l = min(len, fifo->size - (fifo->in & (fifo->size - 1))); | |
131 | - memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), from, l); | |
149 | + l = min(len, fifo->size - off); | |
150 | + memcpy(fifo->buffer + off, from, l); | |
132 | 151 | |
133 | 152 | /* then put the rest (if any) at the beginning of the buffer */ |
134 | 153 | memcpy(fifo->buffer, from + l, len - l); |
135 | 154 | |
136 | - /* | |
137 | - * Ensure that we add the bytes to the kfifo -before- | |
138 | - * we update the fifo->in index. | |
139 | - */ | |
155 | + __kfifo_add_in(fifo, len); | |
140 | 156 | |
141 | - smp_wmb(); | |
142 | - | |
143 | - fifo->in += len; | |
144 | - | |
145 | 157 | return len; |
146 | 158 | } |
147 | 159 | EXPORT_SYMBOL(kfifo_in); |
... | ... | @@ -161,6 +173,7 @@ |
161 | 173 | unsigned int kfifo_out(struct kfifo *fifo, |
162 | 174 | unsigned char *to, unsigned int len) |
163 | 175 | { |
176 | + unsigned int off; | |
164 | 177 | unsigned int l; |
165 | 178 | |
166 | 179 | len = min(len, fifo->in - fifo->out); |
167 | 180 | |
168 | 181 | |
169 | 182 | |
170 | 183 | |
171 | 184 | |
172 | 185 | |
... | ... | @@ -172,23 +185,116 @@ |
172 | 185 | |
173 | 186 | smp_rmb(); |
174 | 187 | |
188 | + off = __kfifo_off(fifo, fifo->out); | |
189 | + | |
175 | 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))); | |
177 | - memcpy(to, fifo->buffer + (fifo->out & (fifo->size - 1)), l); | |
191 | + l = min(len, fifo->size - off); | |
192 | + memcpy(to, fifo->buffer + off, l); | |
178 | 193 | |
179 | 194 | /* then get the rest (if any) from the beginning of the buffer */ |
180 | 195 | memcpy(to + l, fifo->buffer, len - l); |
181 | 196 | |
197 | + __kfifo_add_out(fifo, len); | |
198 | + | |
199 | + return len; | |
200 | +} | |
201 | +EXPORT_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 | + */ | |
215 | +unsigned 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- | |
184 | - * we update the fifo->out index. | |
225 | + * Ensure that we sample the fifo->out index -before- we | |
226 | + * start putting bytes into the kfifo. | |
185 | 227 | */ |
186 | 228 | |
187 | 229 | smp_mb(); |
188 | 230 | |
189 | - fifo->out += len; | |
231 | + off = __kfifo_off(fifo, fifo->in); | |
190 | 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); | |
247 | + | |
191 | 248 | return len; |
192 | 249 | } |
193 | -EXPORT_SYMBOL(kfifo_out); | |
250 | +EXPORT_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 | + */ | |
264 | +unsigned 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 | +} | |
299 | +EXPORT_SYMBOL(kfifo_to_user); |