1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * Common EVDMA functionality models
9  *
10  * Notes:
11  *   * Attributes with special meaning/signaling that data should be
12  *     processed by the DMA are not yet implemented.
13  */
14 
15 #include <stdlib.h>
16 #include <stdint.h>
17 #include <stdbool.h>
18 #include <string.h>
19 #include "NHW_EVDMA.h"
20 #include <bs_tracing.h>
21 
22 /**
23  * Read the next job from the joblist into the EVDMA status structure
24  * and move the joblist_ptr to point to the one after.
25  * returns
26  *    true if the joblist ended (no new job)
27  *    false otherwise
28  */
nhw_EVDMA_read_next_job(EVDMA_status_t * st)29 static bool nhw_EVDMA_read_next_job(EVDMA_status_t *st) {
30   st->job_buff = st->joblist_ptr->job_buff;
31 
32   if (st->job_buff == NULL) {
33     return true;
34   }
35 
36   uint32_t at_len = st->joblist_ptr->attr_len;
37 
38   st->job_attrib = at_len >> 24;
39   st->job_pend_length = at_len & (0xFFFFFF);
40 
41   st->joblist_ptr++;
42 
43   return false;
44 }
45 
46 /**
47  * Peek on the next job from the joblist
48  * and set *job_attrib with that next job attribute
49  * returns
50  *    true if the joblist ends in the next one (no new job)
51  *    false otherwise
52  */
nhw_EVDMA_peek_next_job_attrib(EVDMA_status_t * st,uint * job_attrib)53 static bool nhw_EVDMA_peek_next_job_attrib(EVDMA_status_t *st, uint *job_attrib) {
54   char *job_buff = st->joblist_ptr->job_buff;
55 
56   if (job_buff == NULL) {
57     return true;
58   }
59 
60   uint32_t at_len = st->joblist_ptr->attr_len;
61   *job_attrib = at_len >> 24;
62 
63   return false;
64 }
65 
66 /**
67  * Initialize the EVDMA for a new joblist
68  *
69  *   joblist_ptr: pointer to the job list
70  *   st : EVDMA internal status to be initialized
71  *
72  *   return: 0 on success
73  *           != 0 if joblist_ptr is NULL
74  */
nhw_EVDMA_start(EVDMA_status_t * st,void * joblist_ptr)75 int nhw_EVDMA_start(EVDMA_status_t *st, void *joblist_ptr) {
76   st->joblist_ptr = joblist_ptr;
77   st->mode = EVDMA_idle;
78 
79   st->job_buff = NULL;
80   st->job_pend_length = 0;
81   st->job_attrib = 0;
82 
83   if (joblist_ptr == NULL) {
84     bs_trace_warning_time_line("FW error: Job list pointer is NULL\n");
85     return -1;
86   } else {
87     return 0;
88   }
89 }
90 
91 /*
92  * Do an EVMA read/write of <nbytes>
93  *
94  * Returns the number of read/written bytes if all requested where read or a negative number otherwise
95  *
96  *  st             EVDMA internal status (will be updated)
97  *  read_not_write True for read, false for write access.
98  *  periph_buf     Buffer in the peripheral where the data will be written to/read from
99  *  nbytes         Number of bytes to read/write
100  *  n_actual       Number of bytes actually read/written
101  *  new_job        Skip to the next job even if the current one has pending data
102  *
103  *  returns:
104  *    >=0 (nbytes) read as many as requested
105  *    -1 partial read/write
106  *    -2 end of job list reached (n_actual may be != 0 on a partial read/write)
107  *    -3 error, joblist_ptr is invalid
108  */
nhw_EVDMA_access(EVDMA_status_t * st,bool read_not_write,uint8_t * periph_buf,size_t nbytes,size_t * n_actual,bool start_new_job)109 int nhw_EVDMA_access(EVDMA_status_t *st,
110                      bool read_not_write,
111                      uint8_t *periph_buf,
112                      size_t nbytes,
113                      size_t *n_actual,
114                      bool start_new_job) {
115 
116   *n_actual = 0;
117 
118   if (st->joblist_ptr == NULL) {
119     bs_trace_warning_time_line("FW error: Job list pointer is NULL\n");
120     return -3;
121   }
122 
123   if (   ( read_not_write && (st->mode == EVDMA_write))
124       || (!read_not_write && (st->mode == EVDMA_read)) ) {
125     bs_trace_error_time_line("Programming error\n");
126   } else {
127     if (read_not_write) {
128       st->mode = EVDMA_read;
129     } else {
130       st->mode = EVDMA_write;
131     }
132   }
133 
134   if ((st->job_pend_length == 0) || start_new_job) {
135     bool list_end = nhw_EVDMA_read_next_job(st);
136     if (list_end) {
137       return -2;
138     }
139   }
140 
141   do {
142     uint to_read = BS_MIN(nbytes - *n_actual, st->job_pend_length);
143 
144     if (read_not_write) {
145       memcpy(periph_buf, st->job_buff, to_read);
146     } else {
147       memcpy(st->job_buff, periph_buf, to_read);
148     }
149     st->job_pend_length -= to_read;
150     st->job_buff += to_read;
151     *n_actual += to_read;
152     periph_buf += to_read;
153 
154     if (*n_actual == nbytes) {
155       return nbytes;
156     }
157 
158     uint next_job_attrib;
159     bool list_end = nhw_EVDMA_peek_next_job_attrib(st, &next_job_attrib);
160     if (list_end) {
161       return -2;
162     } else if (next_job_attrib != st->job_attrib) {
163       return -1;
164     }
165 
166     nhw_EVDMA_read_next_job(st);
167 
168   } while (true);
169 }
170