1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation.
4 //
5 // Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6 //
7 // Licensed under the terms of the GNU General Public License, version 2.
8
9 #include "xfer-libasound.h"
10 #include "misc.h"
11
12 struct map_layout {
13 snd_pcm_status_t *status;
14
15 char **vector;
16 unsigned int samples_per_frame;
17 };
18
irq_mmap_pre_process(struct libasound_state * state)19 static int irq_mmap_pre_process(struct libasound_state *state)
20 {
21 struct map_layout *layout = state->private_data;
22 snd_pcm_access_t access;
23 snd_pcm_uframes_t frame_offset;
24 snd_pcm_uframes_t avail = 0;
25 int i;
26 int err;
27
28 err = snd_pcm_status_malloc(&layout->status);
29 if (err < 0)
30 return err;
31
32 err = snd_pcm_hw_params_get_access(state->hw_params, &access);
33 if (err < 0)
34 return err;
35
36 err = snd_pcm_hw_params_get_channels(state->hw_params,
37 &layout->samples_per_frame);
38 if (err < 0)
39 return err;
40
41 if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
42 layout->vector = calloc(layout->samples_per_frame,
43 sizeof(*layout->vector));
44 if (layout->vector == NULL)
45 return err;
46 }
47
48 if (state->verbose) {
49 const snd_pcm_channel_area_t *areas;
50 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
51 &avail);
52 if (err < 0)
53 return err;
54
55 logging(state, "attributes for mapped page frame:\n");
56 for (i = 0; i < layout->samples_per_frame; ++i) {
57 const snd_pcm_channel_area_t *area = areas + i;
58
59 logging(state, " sample number: %d\n", i);
60 logging(state, " address: %p\n", area->addr);
61 logging(state, " bits for offset: %u\n", area->first);
62 logging(state, " bits/frame: %u\n", area->step);
63 }
64 logging(state, "\n");
65 }
66
67 return 0;
68 }
69
irq_mmap_process_frames(struct libasound_state * state,unsigned int * frame_count,struct mapper_context * mapper,struct container_context * cntrs)70 static int irq_mmap_process_frames(struct libasound_state *state,
71 unsigned int *frame_count,
72 struct mapper_context *mapper,
73 struct container_context *cntrs)
74 {
75 struct map_layout *layout = state->private_data;
76 const snd_pcm_channel_area_t *areas;
77 snd_pcm_uframes_t frame_offset;
78 snd_pcm_uframes_t avail;
79 unsigned int avail_count;
80 void *frame_buf;
81 snd_pcm_sframes_t consumed_count;
82 int err;
83
84 if (state->use_waiter) {
85 unsigned int msec_per_buffer;
86 unsigned short revents;
87
88 // Wait during msec equivalent to all audio data frames in
89 // buffer instead of period, for safe.
90 err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
91 &msec_per_buffer, NULL);
92 if (err < 0)
93 return err;
94 msec_per_buffer /= 1000;
95
96 // Wait for hardware IRQ when no avail space in buffer.
97 err = xfer_libasound_wait_event(state, msec_per_buffer,
98 &revents);
99 if (err == -ETIMEDOUT) {
100 logging(state,
101 "No event occurs for PCM substream during %u "
102 "msec. The implementaion of kernel driver or "
103 "userland backend causes this issue.\n",
104 msec_per_buffer);
105 return err;
106 }
107 if (err < 0)
108 return err;
109 if (revents & POLLERR) {
110 // TODO: error reporting?
111 return -EIO;
112 }
113 if (!(revents & (POLLIN | POLLOUT)))
114 return -EAGAIN;
115
116 // When rescheduled, current position of data transmission was
117 // queried to actual hardware by a handler of IRQ. No need to
118 // perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC.
119 }
120
121 // Sync cache in user space to data in kernel space to calculate avail
122 // frames according to the latest positions on PCM buffer.
123 //
124 // This has an additional advantage to handle libasound PCM plugins.
125 // Most of libasound PCM plugins perform resampling in .avail_update()
126 // callback for capture PCM substream, then update positions on buffer.
127 //
128 // MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can
129 // return the same number of available frames.
130 avail = snd_pcm_avail_update(state->handle);
131 if ((snd_pcm_sframes_t)avail < 0)
132 return (int)avail;
133 if (*frame_count < avail)
134 avail = *frame_count;
135
136 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
137 if (err < 0)
138 return err;
139
140 // Trim according up to expected frame count.
141 if (*frame_count < avail)
142 avail_count = *frame_count;
143 else
144 avail_count = (unsigned int)avail;
145
146 // TODO: Perhaps, the complex layout can be supported as a variation of
147 // vector type. However, there's no driver with this layout.
148 if (layout->vector == NULL) {
149 char *buf;
150 buf = areas[0].addr;
151 buf += snd_pcm_frames_to_bytes(state->handle, frame_offset);
152 frame_buf = buf;
153 } else {
154 int i;
155 for (i = 0; i < layout->samples_per_frame; ++i) {
156 layout->vector[i] = areas[i].addr;
157 layout->vector[i] += snd_pcm_samples_to_bytes(
158 state->handle, frame_offset);
159 }
160 frame_buf = layout->vector;
161 }
162
163 err = mapper_context_process_frames(mapper, frame_buf, &avail_count,
164 cntrs);
165 if (err < 0)
166 return err;
167 if (avail_count == 0) {
168 *frame_count = 0;
169 return 0;
170 }
171
172 consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
173 avail_count);
174 if (consumed_count < 0)
175 return (int)consumed_count;
176 if (consumed_count != avail_count)
177 logging(state, "A bug of access plugin for this PCM node.\n");
178
179 *frame_count = consumed_count;
180
181 return 0;
182 }
183
irq_mmap_r_process_frames(struct libasound_state * state,unsigned * frame_count,struct mapper_context * mapper,struct container_context * cntrs)184 static int irq_mmap_r_process_frames(struct libasound_state *state,
185 unsigned *frame_count,
186 struct mapper_context *mapper,
187 struct container_context *cntrs)
188 {
189 struct map_layout *layout = state->private_data;
190 snd_pcm_state_t s;
191 int err;
192
193 // To querying current status of hardware, we need to care of
194 // synchronization between 3 levels:
195 // 1. status to actual hardware by driver.
196 // 2. status data in kernel space.
197 // 3. status data in user space.
198 //
199 // Kernel driver query 1 and sync 2, according to requests of some
200 // ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core
201 // supports mmap(2) operation on cache coherent architectures, some
202 // ioctl(2) commands on cache incoherent architecture. In usage of the
203 // former mechanism, we need to care of concurrent access by IRQ context
204 // and process context to the mapped page frame.
205 // In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and
206 // SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because
207 // mapped page frame is unused regardless of architectures in a point of
208 // cache coherency.
209 err = snd_pcm_status(state->handle, layout->status);
210 if (err < 0)
211 goto error;
212 s = snd_pcm_status_get_state(layout->status);
213
214 // TODO: if reporting something, do here with the status data.
215
216 // For capture direction, need to start stream explicitly.
217 if (s != SND_PCM_STATE_RUNNING) {
218 if (s != SND_PCM_STATE_PREPARED) {
219 err = -EPIPE;
220 goto error;
221 }
222
223 err = snd_pcm_start(state->handle);
224 if (err < 0)
225 goto error;
226 }
227
228 err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
229 if (err < 0)
230 goto error;
231
232 return 0;
233 error:
234 *frame_count = 0;
235 return err;
236 }
237
irq_mmap_w_process_frames(struct libasound_state * state,unsigned * frame_count,struct mapper_context * mapper,struct container_context * cntrs)238 static int irq_mmap_w_process_frames(struct libasound_state *state,
239 unsigned *frame_count,
240 struct mapper_context *mapper,
241 struct container_context *cntrs)
242 {
243 struct map_layout *layout = state->private_data;
244 snd_pcm_state_t s;
245 int err;
246
247 // Read my comment in 'irq_mmap_r_process_frames().
248 err = snd_pcm_status(state->handle, layout->status);
249 if (err < 0)
250 goto error;
251 s = snd_pcm_status_get_state(layout->status);
252
253 // TODO: if reporting something, do here with the status data.
254
255 err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
256 if (err < 0)
257 goto error;
258
259 // Need to start playback stream explicitly
260 if (s != SND_PCM_STATE_RUNNING) {
261 if (s != SND_PCM_STATE_PREPARED) {
262 err = -EPIPE;
263 goto error;
264 }
265
266 err = snd_pcm_start(state->handle);
267 if (err < 0)
268 goto error;
269 }
270
271 return 0;
272 error:
273 *frame_count = 0;
274 return err;
275 }
276
irq_mmap_post_process(struct libasound_state * state)277 static void irq_mmap_post_process(struct libasound_state *state)
278 {
279 struct map_layout *layout = state->private_data;
280
281 if (layout->status)
282 snd_pcm_status_free(layout->status);
283 layout->status = NULL;
284
285 free(layout->vector);
286 layout->vector = NULL;
287 }
288
289 const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = {
290 .pre_process = irq_mmap_pre_process,
291 .process_frames = irq_mmap_w_process_frames,
292 .post_process = irq_mmap_post_process,
293 .private_size = sizeof(struct map_layout),
294 };
295
296 const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = {
297 .pre_process = irq_mmap_pre_process,
298 .process_frames = irq_mmap_r_process_frames,
299 .post_process = irq_mmap_post_process,
300 .private_size = sizeof(struct map_layout),
301 };
302