1 /*
2 * Copyright (c) 2019-2025 Allwinner Technology Co., Ltd. ALL rights reserved.
3 *
4 * Allwinner is a trademark of Allwinner Technology Co.,Ltd., registered in
5 * the the people's Republic of China and other countries.
6 * All Allwinner Technology Co.,Ltd. trademarks are used with permission.
7 *
8 * DISCLAIMER
9 * THIRD PARTY LICENCES MAY BE REQUIRED TO IMPLEMENT THE SOLUTION/PRODUCT.
10 * IF YOU NEED TO INTEGRATE THIRD PARTY’S TECHNOLOGY (SONY, DTS, DOLBY, AVS OR MPEGLA, ETC.)
11 * IN ALLWINNERS’SDK OR PRODUCTS, YOU SHALL BE SOLELY RESPONSIBLE TO OBTAIN
12 * ALL APPROPRIATELY REQUIRED THIRD PARTY LICENCES.
13 * ALLWINNER SHALL HAVE NO WARRANTY, INDEMNITY OR OTHER OBLIGATIONS WITH RESPECT TO MATTERS
14 * COVERED UNDER ANY REQUIRED THIRD PARTY LICENSE.
15 * YOU ARE SOLELY RESPONSIBLE FOR YOUR USAGE OF THIRD PARTY’S TECHNOLOGY.
16 *
17 *
18 * THIS SOFTWARE IS PROVIDED BY ALLWINNER"AS IS" AND TO THE MAXIMUM EXTENT
19 * PERMITTED BY LAW, ALLWINNER EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY KIND,
20 * WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION REGARDING
21 * THE TITLE, NON-INFRINGEMENT, ACCURACY, CONDITION, COMPLETENESS, PERFORMANCE
22 * OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 * IN NO EVENT SHALL ALLWINNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <errno.h>
37 #include "pcm_local.h"
38 
snd_pcm_mmap_appl_backward(snd_pcm_t * pcm,snd_pcm_uframes_t frames)39 void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
40 {
41     snd_pcm_sframes_t appl_ptr = *pcm->appl.ptr;
42     appl_ptr -= frames;
43     if (appl_ptr < 0)
44         appl_ptr += pcm->boundary;
45     *pcm->appl.ptr = appl_ptr;
46 }
47 
snd_pcm_mmap_appl_forward(snd_pcm_t * pcm,snd_pcm_uframes_t frames)48 void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
49 {
50     snd_pcm_uframes_t appl_ptr = *pcm->appl.ptr;
51     appl_ptr += frames;
52     if (appl_ptr >= pcm->boundary)
53         appl_ptr -= pcm->boundary;
54     *pcm->appl.ptr = appl_ptr;
55 }
56 
snd_pcm_mmap_hw_backward(snd_pcm_t * pcm,snd_pcm_uframes_t frames)57 void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
58 {
59     snd_pcm_sframes_t hw_ptr = *pcm->hw.ptr;
60     hw_ptr -= frames;
61     if (hw_ptr < 0)
62         hw_ptr += pcm->boundary;
63     *pcm->hw.ptr = hw_ptr;
64 }
65 
snd_pcm_mmap_hw_forward(snd_pcm_t * pcm,snd_pcm_uframes_t frames)66 void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
67 {
68     snd_pcm_uframes_t hw_ptr = *pcm->hw.ptr;
69     hw_ptr += frames;
70     if (hw_ptr >= pcm->boundary)
71         hw_ptr -= pcm->boundary;
72     *pcm->hw.ptr = hw_ptr;
73 }
74 
snd_pcm_mmap_write_areas(snd_pcm_t * pcm,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)75 static snd_pcm_sframes_t snd_pcm_mmap_write_areas(snd_pcm_t *pcm,
76                           const snd_pcm_channel_area_t *areas,
77                           snd_pcm_uframes_t offset,
78                           snd_pcm_uframes_t size)
79 {
80     snd_pcm_uframes_t xfer = 0;
81 
82     if (snd_pcm_mmap_playback_avail(pcm) < size) {
83         awalsa_err("too short avail %ld to size %ld\n", snd_pcm_mmap_playback_avail(pcm), size);
84         return -EPIPE;
85     }
86     awalsa_debug("size: %lu\n", size);
87     while (size > 0) {
88         const snd_pcm_channel_area_t *pcm_areas;
89         snd_pcm_uframes_t pcm_offset;
90         snd_pcm_uframes_t frames = size;
91         snd_pcm_sframes_t result;
92 
93         __snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
94         snd_pcm_areas_copy(pcm_areas, pcm_offset,
95                    areas, offset,
96                    pcm->channels,
97                    frames, pcm->format);
98         result = __snd_pcm_mmap_commit(pcm, pcm_offset, frames);
99         if (result < 0)
100             return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
101         offset += result;
102         xfer += result;
103         size -= result;
104     }
105     return (snd_pcm_sframes_t)xfer;
106 }
107 
snd_pcm_mmap_writei(snd_pcm_t * pcm,const void * buffer,snd_pcm_uframes_t size)108 snd_pcm_sframes_t snd_pcm_mmap_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
109 {
110     snd_pcm_channel_area_t areas[pcm->channels];
111     snd_pcm_areas_from_buf(pcm, areas, (void*)buffer);
112     return snd_pcm_write_areas(pcm, areas, 0, size,
113                    snd_pcm_mmap_write_areas);
114 }
115 
snd_pcm_mmap_read_areas(snd_pcm_t * pcm,const snd_pcm_channel_area_t * areas,snd_pcm_uframes_t offset,snd_pcm_uframes_t size)116 static snd_pcm_sframes_t snd_pcm_mmap_read_areas(snd_pcm_t *pcm,
117                          const snd_pcm_channel_area_t *areas,
118                          snd_pcm_uframes_t offset,
119                          snd_pcm_uframes_t size)
120 {
121     snd_pcm_uframes_t xfer = 0;
122 
123     if (snd_pcm_mmap_capture_avail(pcm) < size) {
124         awalsa_err("too short avail %ld to size %ld\n", snd_pcm_mmap_capture_avail(pcm), size);
125         return -EPIPE;
126     }
127     while (size > 0) {
128         const snd_pcm_channel_area_t *pcm_areas;
129         snd_pcm_uframes_t pcm_offset;
130         snd_pcm_uframes_t frames = size;
131         snd_pcm_sframes_t result;
132 
133         __snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
134         snd_pcm_areas_copy(areas, offset,
135                    pcm_areas, pcm_offset,
136                    pcm->channels,
137                    frames, pcm->format);
138         result = __snd_pcm_mmap_commit(pcm, pcm_offset, frames);
139         if (result < 0)
140             return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
141         offset += result;
142         xfer += result;
143         size -= result;
144     }
145     return (snd_pcm_sframes_t)xfer;
146 }
147 
snd_pcm_mmap_readi(snd_pcm_t * pcm,void * buffer,snd_pcm_uframes_t size)148 snd_pcm_sframes_t snd_pcm_mmap_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
149 {
150     snd_pcm_channel_area_t areas[pcm->channels];
151     snd_pcm_areas_from_buf(pcm, areas, buffer);
152     return snd_pcm_read_areas(pcm, areas, 0, size,
153                   snd_pcm_mmap_read_areas);
154 }
155 
snd_pcm_channel_info_shm(snd_pcm_t * pcm,snd_pcm_channel_info_t * info,int shmid)156 int snd_pcm_channel_info_shm(snd_pcm_t *pcm, snd_pcm_channel_info_t *info, int shmid)
157 {
158     switch (pcm->access) {
159     case SND_PCM_ACCESS_MMAP_INTERLEAVED:
160     case SND_PCM_ACCESS_RW_INTERLEAVED:
161         info->first = info->channel * pcm->sample_bits;
162         info->step = pcm->frame_bits;
163         break;
164     case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
165     case SND_PCM_ACCESS_RW_NONINTERLEAVED:
166         info->first = 0;
167         info->step = pcm->sample_bits;
168         break;
169     default:
170         awalsa_err("invalid access type %d\n", pcm->access);
171         return -EINVAL;
172     }
173     info->addr = 0;
174     info->type = SND_PCM_AREA_LOCAL;
175     return 0;
176 }
177 
snd_pcm_mmap(snd_pcm_t * pcm)178 int snd_pcm_mmap(snd_pcm_t *pcm)
179 {
180     int err;
181     unsigned int c;
182     assert(pcm);
183     assert(pcm->ops->mmap);
184 
185     if (!pcm->setup) {
186         awalsa_err("PCM not set up\n");
187         return -EIO;
188     }
189     if (pcm->mmap_channels || pcm->running_areas) {
190         awalsa_err("Already mmapped\n");
191         return -EBUSY;
192     }
193     err = pcm->ops->mmap(pcm);
194     if (err < 0)
195         return err;
196     if (pcm->mmap_shadow)
197         return 0;
198     pcm->mmap_channels = calloc(pcm->channels, sizeof(pcm->mmap_channels[0]));
199     if (!pcm->mmap_channels)
200         return -ENOMEM;
201     pcm->running_areas = calloc(pcm->channels, sizeof(pcm->running_areas[0]));
202     if (!pcm->running_areas) {
203         free(pcm->mmap_channels);
204         pcm->mmap_channels = NULL;
205         return -ENOMEM;
206     }
207     for (c = 0; c < pcm->channels; ++c) {
208         snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
209         i->channel = c;
210         err = snd_pcm_channel_info(pcm, i);
211         if (err < 0) {
212             free(pcm->mmap_channels);
213             free(pcm->running_areas);
214             pcm->mmap_channels = NULL;
215             pcm->running_areas = NULL;
216             return err;
217         }
218     }
219 
220     for (c = 0; c < pcm->channels; ++c) {
221         snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
222         snd_pcm_channel_area_t *a = &pcm->running_areas[c];
223         char *ptr;
224         size_t size;
225         unsigned int c1;
226         if (i->addr) {
227             a->addr = i->addr;
228             a->first = i->first;
229             a->step = i->step;
230             continue;
231         }
232 
233         size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits;
234         for (c1 = c + 1; c1 < pcm->channels; ++c1) {
235             snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
236             size_t s;
237             if (i1->type != i->type)
238                 continue;
239             switch (i1->type) {
240             case SND_PCM_AREA_MMAP:
241             case SND_PCM_AREA_LOCAL:
242                 break;
243             default:
244                 assert(0);
245             }
246             s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits;
247             if (s > size)
248                 size = s;
249         }
250         size = (size + 7) / 8;
251         switch (i->type) {
252         case SND_PCM_AREA_MMAP:
253             /* i->addr has got from snd_pcm_channel_info() */
254             break;
255         case SND_PCM_AREA_LOCAL:
256             awalsa_debug("pcm_type:%s, size=%d, buffer_size=%d, period_size=%d\n",
257                 snd_pcm_type_name(snd_pcm_type(pcm)),
258                 size, pcm->buffer_size, pcm->period_size);
259             ptr = malloc(size);
260             if (ptr == NULL) {
261                 awalsa_err("malloc failed\n");
262                 return -ENOMEM;
263             }
264             i->addr = ptr;
265             break;
266         default:
267             assert(0);
268         }
269 
270         for (c1 = c + 1; c1 < pcm->channels; ++c1) {
271             snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
272             if (i1->type != i->type)
273                 continue;
274             switch (i1->type) {
275             case SND_PCM_AREA_MMAP:
276                 break;
277             case SND_PCM_AREA_LOCAL:
278                 if (pcm->access != SND_PCM_ACCESS_MMAP_INTERLEAVED &&
279                         pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED)
280                     continue;
281                 break;
282             default:
283                 assert(0);
284             }
285             i1->addr = i->addr;
286         }
287 
288         a->addr = i->addr;
289         a->first = i->first;
290         a->step = i->step;
291     }
292     return 0;
293 }
294 
snd_pcm_munmap(snd_pcm_t * pcm)295 int snd_pcm_munmap(snd_pcm_t *pcm)
296 {
297     int err;
298     unsigned int c;
299     assert(pcm);
300     assert(pcm->ops->mmap);
301 
302     if (!pcm->mmap_channels) {
303         awalsa_err("Not mmapped\n");
304         return -ENXIO;
305     }
306     if (pcm->mmap_shadow)
307         return pcm->ops->munmap(pcm);
308 
309     for (c = 0; c < pcm->channels; ++c) {
310         snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
311         unsigned int c1;
312         if (!i->addr)
313             continue;
314         for (c1 = c + 1; c1 < pcm->channels; ++c1) {
315             snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
316             if (i1->addr != i->addr)
317                 continue;
318             i1->addr = NULL;
319         }
320         switch (i->type) {
321         case SND_PCM_AREA_MMAP:
322             /* do nothing, the spaces are managed by driver */
323             break;
324         case SND_PCM_AREA_LOCAL:
325             free(i->addr);
326             break;
327         default:
328             assert(0);
329         }
330         i->addr = NULL;
331     }
332 
333     err = pcm->ops->munmap(pcm);
334     if (err < 0)
335         return err;
336     free(pcm->mmap_channels);
337     free(pcm->running_areas);
338     pcm->mmap_channels = NULL;
339     pcm->running_areas = NULL;
340     return 0;
341 }
342