1 /*
2  * Copyright (c) 2006-2021, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author            Notes
8  * 2017/12/30     Bernard           The first version.
9  * 2024/03/26     TroyMitchelle     Added some function comments
10  * 2024/03/27     TroyMitchelle     Fix the issue of incorrect return of invalid parameters in aio_write
11  */
12 
13 #include <rtthread.h>
14 #include <rthw.h>
15 #include <stdint.h>
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <sys/errno.h>
19 #include "aio.h"
20 
21 struct rt_workqueue* aio_queue = NULL;
22 
23 /**
24  * The aio_cancel() function shall attempt to cancel one or more asynchronous I/O
25  * requests currently outstanding against file descriptor fildes. The aiocbp
26  * argument points to the asynchronous I/O control block for a particular request
27  * to be canceled. If aiocbp is NULL, then all outstanding cancelable asynchronous
28  * I/O requests against fildes shall be canceled.
29  *
30  * Normal asynchronous notification shall occur for asynchronous I/O operations
31  * that are successfully canceled. If there are requests that cannot be canceled,
32  * then the normal asynchronous completion process shall take place for those
33  * requests when they are completed.
34  *
35  * For requested operations that are successfully canceled, the associated error
36  * status shall be set to [ECANCELED] and the return status shall be -1. For
37  * requested operations that are not successfully canceled, the aiocbp shall not
38  * be modified by aio_cancel().
39  *
40  * If aiocbp is not NULL, then if fildes does not have the same value as the file
41  * descriptor with which the asynchronous operation was initiated, unspecified results occur.
42  *
43  * Which operations are cancelable is implementation-defined.
44  */
aio_cancel(int fd,struct aiocb * cb)45 int aio_cancel(int fd, struct aiocb *cb)
46 {
47     rt_err_t ret;
48 
49     if (!cb) return -EINVAL;
50     if (cb->aio_fildes != fd) return -EINVAL;
51 
52     ret = rt_workqueue_cancel_work_sync(aio_queue, &(cb->aio_work));
53     if (ret == RT_EOK)
54     {
55         errno = -ECANCELED;
56         return -1;
57     }
58 
59     return 0;
60 }
61 
62 /**
63  * The aio_error() function shall return the error status associated with the
64  * aiocb structure referenced by the aiocbp argument. The error status for an
65  * asynchronous I/O operation is the errno value that would be set by the corresponding
66  * read(), write(),
67  */
aio_error(const struct aiocb * cb)68 int aio_error(const struct aiocb *cb)
69 {
70     if (cb)
71     {
72         return cb->aio_result;
73     }
74 
75     return -EINVAL;
76 }
77 
78 /**
79  * The aio_fsync() function shall asynchronously perform a file synchronization
80  * operation, as specified by the op argument, for I/O operations associated with
81  * the file indicated by the file descriptor aio_fildes member of the aiocb
82  * structure referenced by the aiocbp argument and queued at the time of the
83  * call to aio_fsync(). The function call shall return when the synchronization
84  * request has been initiated or queued to the file or device (even when the data
85  * cannot be synchronized immediately).
86  *
87  * option: If op is O_DSYNC, all currently queued I/O operations shall be completed
88  * as if by a call to fdatasync(); that is, as defined for synchronized I/O data
89  * integrity completion.
90  *
91  * option: If op is O_SYNC, all currently queued I/O operations shall be completed
92  * as if by a call to fsync(); that is, as defined for synchronized I/O file integrity
93  * completion. If the aio_fsync() function fails, or if the operation queued by
94  * aio_fsync() fails, then outstanding I/O operations are not guaranteed to have
95  * been completed.
96  *
97  * If aio_fsync() succeeds, then it is only the I/O that was queued at the time
98  * of the call to aio_fsync() that is guaranteed to be forced to the relevant
99  * completion state. The completion of subsequent I/O on the file descriptor is
100  * not guaranteed to be completed in a synchronized fashion.
101  *
102  * The aiocbp argument refers to an asynchronous I/O control block. The aiocbp
103  * value may be used as an argument to aio_error() and aio_return() in order to
104  * determine the error status and return status, respectively, of the asynchronous
105  * operation while it is proceeding. When the request is queued, the error status
106  * for the operation is [EINPROGRESS]. When all data has been successfully transferred,
107  * the error status shall be reset to reflect the success or failure of the operation.
108  * If the operation does not complete successfully, the error status for the
109  * operation shall be set to indicate the error. The aio_sigevent member determines
110  * the asynchronous notification to occur as specified in Signal Generation and
111  * Delivery when all operations have achieved synchronized I/O completion. All
112  * other members of the structure referenced by aiocbp are ignored. If the control
113  * block referenced by aiocbp becomes an illegal address prior to asynchronous
114  * I/O completion, then the behavior is undefined.
115  *
116  * If the aio_fsync() function fails or aiocbp indicates an error condition,
117  * data is not guaranteed to have been successfully transferred.
118  */
aio_fync_work(struct rt_work * work,void * work_data)119 static void aio_fync_work(struct rt_work* work, void* work_data)
120 {
121     int result;
122     rt_base_t level;
123     struct aiocb *cb = (struct aiocb*)work_data;
124 
125     RT_ASSERT(cb != RT_NULL);
126 
127     result = fsync(cb->aio_fildes);
128     /* modify result */
129     level = rt_hw_interrupt_disable();
130     if (result < 0)
131         cb->aio_result = errno;
132     else
133         cb->aio_result = 0;
134     rt_hw_interrupt_enable(level);
135 
136     return ;
137 }
138 
139 /**
140  * @brief   Initiates an asynchronous fsync operation.
141  *
142  * This function initiates an asynchronous fsync operation on the file associated
143  * with the specified aiocb structure. The operation is queued to the workqueue
144  * for execution.
145  *
146  * @param   op  The operation to be performed. This parameter is ignored.
147  * @param   cb  Pointer to the aiocb structure representing the asynchronous fsync operation.
148  *
149  * @return  Returns 0 on success.
150  */
aio_fsync(int op,struct aiocb * cb)151 int aio_fsync(int op, struct aiocb *cb)
152 {
153     rt_base_t level;
154     if (!cb) return -EINVAL;
155 
156     level = rt_hw_interrupt_disable();
157     cb->aio_result = -EINPROGRESS;
158     rt_hw_interrupt_enable(level);
159 
160     rt_work_init(&(cb->aio_work), aio_fync_work, cb);
161     rt_workqueue_dowork(aio_queue, &(cb->aio_work));
162 
163     return 0;
164 }
165 
166 /**
167  * @brief   Worker function for asynchronous read operation.
168  *
169  * This function performs the actual reading of data from the file associated with
170  * the specified aiocb structure. It sets the result of the operation in the
171  * aio_result field of the aiocb structure.
172  *
173  * @param   work       Pointer to the work item.
174  * @param   work_data  Pointer to the aiocb structure representing the asynchronous read operation.
175  */
aio_read_work(struct rt_work * work,void * work_data)176 static void aio_read_work(struct rt_work* work, void* work_data)
177 {
178     int len;
179     rt_base_t level;
180     uint8_t *buf_ptr;
181     struct aiocb *cb = (struct aiocb*)work_data;
182 
183     buf_ptr = (uint8_t*)cb->aio_buf;
184 
185     /* seek to offset */
186     lseek(cb->aio_fildes, cb->aio_offset, SEEK_SET);
187     len = read(cb->aio_fildes, &buf_ptr[cb->aio_offset], cb->aio_nbytes);
188 
189     /* modify result */
190     level = rt_hw_interrupt_disable();
191     if (len <= 0)
192         cb->aio_result = errno;
193     else
194         cb->aio_result = len;
195     rt_hw_interrupt_enable(level);
196 
197     return ;
198 }
199 
200 /**
201  * The aio_read() function shall read aiocbp->aio_nbytes from the file associated
202  * with aiocbp->aio_fildes into the buffer pointed to by aiocbp->aio_buf. The
203  * function call shall return when the read request has been initiated or queued
204  * to the file or device (even when the data cannot be delivered immediately).
205  *
206  * If prioritized I/O is supported for this file, then the asynchronous operation
207  * shall be submitted at a priority equal to a base scheduling priority minus
208  * aiocbp->aio_reqprio. If Thread Execution Scheduling is not supported, then
209  * the base scheduling priority is that of the calling process;
210  *
211  * otherwise, the base scheduling priority is that of the calling thread.
212  *
213  * The aiocbp value may be used as an argument to aio_error() and aio_return()
214  * in order to determine the error status and return status, respectively, of
215  * the asynchronous operation while it is proceeding. If an error condition is
216  * encountered during queuing, the function call shall return without having
217  * initiated or queued the request. The requested operation takes place at the
218  * absolute position in the file as given by aio_offset, as if lseek() were called
219  * immediately prior to the operation with an offset equal to aio_offset and a
220  * whence equal to SEEK_SET. After a successful call to enqueue an asynchronous
221  * I/O operation, the value of the file offset for the file is unspecified.
222  *
223  * The aio_sigevent member specifies the notification which occurs when the
224  * request is completed.
225  *
226  * The aiocbp->aio_lio_opcode field shall be ignored by aio_read().
227  *
228  * The aiocbp argument points to an aiocb structure. If the buffer pointed to by
229  * aiocbp->aio_buf or the control block pointed to by aiocbp becomes an illegal
230  * address prior to asynchronous I/O completion, then the behavior is undefined.
231  *
232  * Simultaneous asynchronous operations using the same aiocbp produce undefined
233  * results.
234  *
235  * If synchronized I/O is enabled on the file associated with aiocbp->aio_fildes,
236  * the behavior of this function shall be according to the definitions of synchronized
237  * I/O data integrity completion and synchronized I/O file integrity completion.
238  *
239  * For any system action that changes the process memory space while an asynchronous
240  * I/O is outstanding to the address range being changed, the result of that action
241  * is undefined.
242  *
243  * For regular files, no data transfer shall occur past the offset maximum
244  * established in the open file description associated with aiocbp->aio_fildes.
245  *
246  */
aio_read(struct aiocb * cb)247 int aio_read(struct aiocb *cb)
248 {
249     rt_base_t level;
250 
251     if (!cb) return -EINVAL;
252     if (cb->aio_offset < 0) return -EINVAL;
253 
254     level = rt_hw_interrupt_disable();
255     cb->aio_result = -EINPROGRESS;
256     rt_hw_interrupt_enable(level);
257 
258     /* en-queue read work */
259     rt_work_init(&(cb->aio_work), aio_read_work, cb);
260     rt_workqueue_dowork(aio_queue, &(cb->aio_work));
261 
262     return 0;
263 }
264 
265 /**
266  * The aio_return() function shall return the return status associated with the
267  * aiocb structure referenced by the aiocbp argument. The return status for an
268  * asynchronous I/O operation is the value that would be returned by the corresponding
269  * read(), write(), or fsync() function call. If the error status for the operation
270  * is equal to [EINPROGRESS], then the return status for the operation is undefined.
271  * The aio_return() function may be called exactly once to retrieve the return
272  * status of a given asynchronous operation; thereafter, if the same aiocb structure
273  * is used in a call to aio_return() or aio_error(), an error may be returned.
274  * When the aiocb structure referred to by aiocbp is used to submit another asynchronous
275  * operation, then aio_return() may be successfully used to retrieve the return
276  * status of that operation.
277  */
aio_return(struct aiocb * cb)278 ssize_t  aio_return(struct aiocb *cb)
279 {
280     if (cb)
281     {
282         if (cb->aio_result < 0)
283             rt_set_errno(cb->aio_result);
284 
285         return cb->aio_result;
286     }
287 
288     return -EINVAL;
289 }
290 
291 /**
292  * The aio_suspend() function shall suspend the calling thread until at least
293  * one of the asynchronous I/O operations referenced by the list argument has
294  * completed, until a signal interrupts the function, or, if timeout is not NULL,
295  * until the time interval specified by timeout has passed. If any of the aiocb
296  * structures in the list correspond to completed asynchronous I/O operations
297  * (that is, the error status for the operation is not equal to [EINPROGRESS])
298  * at the time of the call, the function shall return without suspending the
299  * calling thread. The list argument is an array of pointers to asynchronous I/O
300  * control blocks. The nent argument indicates the number of elements in the
301  * array. Each aiocb structure pointed to has been used in initiating an asynchronous
302  * I/O request via aio_read(), aio_write(), or lio_listio(). This array may
303  * contain null pointers, which are ignored. If this array contains pointers
304  * that refer to aiocb structures that have not been used in submitting asynchronous
305  * I/O, the effect is undefined.
306  *
307  * If the time interval indicated in the timespec structure pointed to by timeout
308  * passes before any of the I/O operations referenced by list are completed, then
309  * aio_suspend() shall return with an error.
310  */
aio_suspend(const struct aiocb * const list[],int nent,const struct timespec * timeout)311 int aio_suspend(const struct aiocb *const list[], int nent,
312              const struct timespec *timeout)
313 {
314     return -ENOSYS;
315 }
316 
317 /**
318  * @brief   Worker function for asynchronous write operation.
319  *
320  * This function performs the actual writing of data to the file associated with
321  * the specified aiocb structure. It sets the result of the operation in the
322  * aio_result field of the aiocb structure.
323  *
324  * @param   work       Pointer to the work item.
325  * @param   work_data  Pointer to the aiocb structure representing the asynchronous write operation.
326  */
aio_write_work(struct rt_work * work,void * work_data)327 static void aio_write_work(struct rt_work* work, void* work_data)
328 {
329     rt_base_t level;
330     int len, oflags;
331     uint8_t *buf_ptr;
332     struct aiocb *cb = (struct aiocb*)work_data;
333 
334     buf_ptr = (uint8_t*)cb->aio_buf;
335 
336     /* whether seek offset */
337     oflags = fcntl(cb->aio_fildes, F_GETFL, 0);
338     if ((oflags & O_APPEND) == 0)
339     {
340         lseek(cb->aio_fildes, SEEK_SET, cb->aio_offset);
341     }
342 
343     /* write data */
344     len = write(cb->aio_fildes, buf_ptr, cb->aio_nbytes);
345 
346     /* modify result */
347     level = rt_hw_interrupt_disable();
348     if (len <= 0)
349         cb->aio_result = errno;
350     else
351         cb->aio_result = len;
352     rt_hw_interrupt_enable(level);
353 
354     return;
355 }
356 
357 /**
358  * The aio_write() function shall write aiocbp->aio_nbytes to the file associated
359  * with aiocbp->aio_fildes from the buffer pointed to by aiocbp->aio_buf. The
360  * function shall return when the write request has been initiated or, at a minimum,
361  * queued to the file or device.
362  *
363  * The aiocbp argument may be used as an argument to aio_error() and aio_return()
364  * in order to determine the error status and return status, respectively, of the
365  * asynchronous operation while it is proceeding.
366  *
367  * The aiocbp argument points to an aiocb structure. If the buffer pointed to by
368  * aiocbp->aio_buf or the control block pointed to by aiocbp becomes an illegal
369  * address prior to asynchronous I/O completion, then the behavior is undefined.
370  *
371  * If O_APPEND is not set for the file descriptor aio_fildes, then the requested
372  * operation shall take place at the absolute position in the file as given by
373  * aio_offset, as if lseek() were called immediately prior to the operation with
374  * an offset equal to aio_offset and a whence equal to SEEK_SET. If O_APPEND is
375  * set for the file descriptor, or if aio_fildes is associated with a device that
376  * is incapable of seeking, write operations append to the file in the same order
377  * as the calls were made, except under circumstances described in Asynchronous
378  * I/O. After a successful call to enqueue an asynchronous I/O operation, the value
379  * of the file offset for the file is unspecified.
380  *
381  * The aio_sigevent member specifies the notification which occurs when the request
382  * is completed.
383  *
384  * The aiocbp->aio_lio_opcode field shall be ignored by aio_write().
385  *
386  * Simultaneous asynchronous operations using the same aiocbp produce undefined
387  * results.
388  *
389  * If synchronized I/O is enabled on the file associated with aiocbp->aio_fildes,
390  * the behavior of this function shall be according to the definitions of synchronized
391  * I/O data integrity completion, and synchronized I/O file integrity completion.
392  *
393  * For regular files, no data transfer shall occur past the offset maximum established
394  * in the open file description associated with aiocbp->aio_fildes.
395  */
aio_write(struct aiocb * cb)396 int aio_write(struct aiocb *cb)
397 {
398     int oflags;
399     rt_base_t level;
400 
401     if (!cb || (cb->aio_buf == NULL)) return -EINVAL;
402 
403     /* check access mode */
404     oflags = fcntl(cb->aio_fildes, F_GETFL, 0);
405     /* If the flag is not in write only or read-write mode, it cannot be written then an invalid parameter is returned  */
406     if ((oflags & O_ACCMODE) != O_WRONLY &&
407         (oflags & O_ACCMODE) != O_RDWR)
408         return -EINVAL;
409 
410     level = rt_hw_interrupt_disable();
411     cb->aio_result = -EINPROGRESS;
412     rt_hw_interrupt_enable(level);
413 
414     rt_work_init(&(cb->aio_work), aio_write_work, cb);
415     rt_workqueue_dowork(aio_queue, &(cb->aio_work));
416 
417     return 0;
418 }
419 
420 /**
421  * The lio_listio() function shall initiate a list of I/O requests with a single
422  * function call.
423  *
424  * The mode argument takes one of the values LIO_WAIT or LIO_NOWAIT declared in
425  * <aio.h> and determines whether the function returns when the I/O operations
426  * have been completed, or as soon as the operations have been queued. If the
427  * mode argument is LIO_WAIT, the function shall wait until all I/O is complete
428  * and the sig argument shall be ignored.
429  *
430  * If the mode argument is LIO_NOWAIT, the function shall return immediately, and
431  * asynchronous notification shall occur, according to the sig argument, when all
432  * the I/O operations complete. If sig is NULL, then no asynchronous notification
433  * shall occur. If sig is not NULL, asynchronous notification occurs as specified
434  * in Signal Generation and Delivery when all the requests in list have completed.
435  *
436  * The I/O requests enumerated by list are submitted in an unspecified order.
437  *
438  * The list argument is an array of pointers to aiocb structures. The array contains
439  * nent elements. The array may contain NULL elements, which shall be ignored.
440  *
441  * If the buffer pointed to by list or the aiocb structures pointed to by the
442  * elements of the array list become illegal addresses before all asynchronous I/O
443  * completed and, if necessary, the notification is sent, then the behavior is
444  * undefined. If the buffers pointed to by the aio_buf member of the aiocb structure
445  * pointed to by the elements of the array list become illegal addresses prior to
446  * the asynchronous I/O associated with that aiocb structure being completed, the
447  * behavior is undefined.
448  *
449  * The aio_lio_opcode field of each aiocb structure specifies the operation to be
450  * performed. The supported operations are LIO_READ, LIO_WRITE, and LIO_NOP; these
451  * symbols are defined in <aio.h>. The LIO_NOP operation causes the list entry to
452  * be ignored. If the aio_lio_opcode element is equal to LIO_READ, then an I/O operation
453  * is submitted as if by a call to aio_read() with the aiocbp equal to the address
454  * of the aiocb structure. If the aio_lio_opcode element is equal to LIO_WRITE, then
455  * an I/O operation is submitted as if by a call to aio_write() with the aiocbp equal
456  * to the address of the aiocb structure.
457  *
458  * The aio_fildes member specifies the file descriptor on which the operation is to
459  * be performed.
460  *
461  * The aio_buf member specifies the address of the buffer to or from which the data
462  * is transferred.
463  *
464  * The aio_nbytes member specifies the number of bytes of data to be transferred.
465  *
466  * The members of the aiocb structure further describe the I/O operation to be
467  * performed, in a manner identical to that of the corresponding aiocb structure
468  * when used by the aio_read() and aio_write() functions.
469  *
470  * The nent argument specifies how many elements are members of the list; that is,
471  * the length of the array.
472  *
473  * The behavior of this function is altered according to the definitions of synchronized
474  * I/O data integrity completion and synchronized I/O file integrity completion if
475  * synchronized I/O is enabled on the file associated with aio_fildes.
476  *
477  * For regular files, no data transfer shall occur past the offset maximum established
478  * in the open file description associated with aiocbp->aio_fildes.
479  *
480  * If sig->sigev_notify is SIGEV_THREAD and sig->sigev_notify_attributes is a
481  * non-null pointer and the block pointed to by this pointer becomes an illegal
482  * address prior to all asynchronous I/O being completed, then the behavior is
483  * undefined.
484  */
lio_listio(int mode,struct aiocb * const list[],int nent,struct sigevent * sig)485 int lio_listio(int mode, struct aiocb * const list[], int nent,
486             struct sigevent *sig)
487 {
488     return -ENOSYS;
489 }
490 
491 /**
492  * @brief   Initializes the asynchronous I/O system.
493  *
494  * This function initializes the asynchronous I/O system by creating a workqueue
495  * for asynchronous I/O operations.
496  *
497  * @return  Returns 0 on success.
498  */
aio_system_init(void)499 int aio_system_init(void)
500 {
501     aio_queue = rt_workqueue_create("aio", 2048, RT_THREAD_PRIORITY_MAX/2);
502     RT_ASSERT(aio_queue != NULL);
503 
504     return 0;
505 }
506 INIT_COMPONENT_EXPORT(aio_system_init);
507