1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
#define AL_LOG_SECTION "cache_cdio"
//#define AL_LOG_ENABLE_TRACE
#include <al/log.h>
#include "../../codec/codec.h"
#include "../backings/file.h"
#include "../backings/memory.h"
#include "../threaded_waits.h"
#include "../range.h"
#include "cdio.h"
// 1 sector at a time to minimize skips.
#define SECTORS_PER_STEP 1
#define BYTES_PER_STEP (SECTORS_PER_STEP * CDIO_CD_FRAMESIZE_RAW)
static void cdio_log_messages(struct cch_handler_cdio *cdio)
{
char *error_messages = cdio_cddap_errors(cdio->drive);
if (error_messages) {
log_error("%s", error_messages);
cdio_cddap_free_messages(error_messages);
}
char *messages = cdio_cddap_messages(cdio->drive);
if (messages) {
log_info("%s", messages);
cdio_cddap_free_messages(messages);
}
}
static bool handler_cdio_can_seek(struct cch_handler *handler)
{
(void)handler;
return true;
}
static nn_thread_result NNWT_THREADCALL cd_read_thread(void *userdata)
{
struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)userdata;
nn_thread_set_name("cdio_read");
for (;;) {
if (!atomic_load(bool)(&cdio->running, AL_ATOMIC_RELAXED)) {
// We don't care about how complete the read was.
return 0;
}
// cdio_read() can block for a long time so we buffer each read before writing it to the backing.
long ret = cdio_cddap_read(cdio->drive, nn_buffer_get_ptr(&cdio->buffer, 0), cdio->sector, SECTORS_PER_STEP);
cdio_log_messages(cdio);
al_assert(ret <= SECTORS_PER_STEP);
off_t pointer = cdio->sector * CDIO_CD_FRAMESIZE_RAW;
size_t size;
u8 *ptr = cdio->backing->get_ptr(cdio->backing, pointer, &size);
al_assert(size >= BYTES_PER_STEP);
if (ret == SECTORS_PER_STEP) {
al_memcpy(ptr, nn_buffer_get_ptr(&cdio->buffer, 0), BYTES_PER_STEP);
} else if (ret == -10) {
log_error("Disc read error, skipping sector.");
al_memset(ptr, 0, BYTES_PER_STEP);
ret = SECTORS_PER_STEP;
} else if (ret < 0) {
log_error("Fatal error (%d).", ret);
cdio->backing->unlock(cdio->backing);
break;
}
cch_backing_fill_range(cdio->backing, pointer, BYTES_PER_STEP);
cdio->sector += ret;
cdio->backing->unlock(cdio->backing);
cch_threaded_waits_evaluate(&cdio->waits, cdio->backing);
if (ret < SECTORS_PER_STEP || cdio->sector > cdio->end) {
log_debug("Normal CD EOF.");
break;
}
}
if (cdio->sector <= cdio->end) {
log_warn("Disc read cut short (sectors: %zd, end: %zd).", cdio->sector, cdio->end);
// We didn't read the full disc, disable waiters.
cch_threaded_waits_disable_all(&cdio->waits);
}
return 0;
}
static void handler_cdio_maybe_spawn_worker(struct cch_handler *handler, size_t index)
{
struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler;
if (cdio->start == (lsn_t)index) return;
if (atomic_load(bool)(&cdio->running, AL_ATOMIC_ACQUIRE)) {
atomic_store(bool)(&cdio->running, false, AL_ATOMIC_RELEASE);
nn_thread_join(&cdio->thread);
}
cdio->start = index;
cdio->sector = cdio->start;
atomic_store(bool)(&cdio->running, true, AL_ATOMIC_RELEASE);
nn_thread_create(&cdio->thread, cd_read_thread, cdio);
}
static bool handler_cdio_wait_for_range(struct cch_handler *handler, struct cch_wait *wait)
{
struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler;
return cch_threaded_wait_for_range(&cdio->waits, cdio->backing, wait);
}
static s32 handler_cdio_guess_format(struct cch_handler *handler)
{
(void)handler;
return CAMU_CODEC_FALLBACK;
}
static void handler_cdio_free(struct cch_handler **handler)
{
struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)*handler;
cch_threaded_waits_disable_all(&cdio->waits);
if (atomic_load(bool)(&cdio->running, AL_ATOMIC_RELAXED)) {
atomic_store(bool)(&cdio->running, false, AL_ATOMIC_RELAXED);
nn_thread_join(&cdio->thread);
}
if (cdio->drive) {
cdio_log_messages(cdio);
cdio_cddap_close(cdio->drive);
}
cch_threaded_waits_close(&cdio->waits);
al_str_free(&cdio->handler.handler);
al_free(cdio);
*handler = NULL;
}
static bool open_cd_drive(struct cch_handler_cdio *cdio)
{
cdrom_drive_t *drive = NULL;
char **cd_drives;
CdIo_t *cdio_handle = NULL;
cd_drives = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false);
if (cd_drives) {
// Use first drive.
cdio_handle = cdio_open(*cd_drives, DRIVER_UNKNOWN);
drive = cdio_cddap_identify_cdio(cdio_handle, CDDA_MESSAGE_LOGIT, NULL);
} else {
log_error("No compatible disc found.");
return false;
}
cdio_free_device_list(cd_drives);
cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
s32 ret = cdio_cddap_open(drive);
if (ret != 0) {
log_error("Unable to open disc (%d).", ret);
return false;
}
ret = cdio_cddap_speed_set(drive, 4);
if (ret != 0) {
log_warn("Failed to set drive speed (%d).", ret);
}
track_t tracks = cdio_cddap_tracks(drive);
track_t first_track = cdio_get_first_track_num(drive->p_cdio);
track_t last_track = cdio_get_last_track_num(drive->p_cdio);
log_info("CD with %d tracks loaded (%d-%d).", tracks, first_track, last_track);
al_array_reserve(cdio->handler.entry->chapters, tracks);
lsn_t start = cdio_cddap_disc_firstsector(drive);
lsn_t end = cdio_cddap_disc_lastsector(drive);
lsn_t first_track_start = cdio_cddap_track_firstsector(drive, first_track);
lsn_t offset = first_track_start;
s32 track = CDIO_INVALID_TRACK;
while (offset < end) {
track = cdio_cddap_sector_gettrack(drive, offset);
al_assert(track != CDIO_INVALID_TRACK);
lsn_t track_start = cdio_cddap_track_firstsector(drive, track);
lsn_t track_end = cdio_cddap_track_lastsector(drive, track);
al_array_push(cdio->handler.entry->chapters, ((struct cch_chapter){ track_start, track_end }));
log_trace("Track #%u: %zd-%zd.", track, track_start, track_end);
offset = track_end + 1;
}
// end is a valid index so add one framesize.
cdio->backing->set_size(cdio->backing, ((end - start) + 1) * CDIO_CD_FRAMESIZE_RAW, true);
cdio->start = -1;
cdio->end = end;
cdio->drive = drive;
cdio_log_messages(cdio);
return true;
}
struct cch_entry *cch_handler_cdio_create(void)
{
struct cch_backing *backing = cch_backing_file_create(&al_str_c("/tmp/camu_cd_data"), 4096);
if (!backing) return NULL;
struct cch_handler_cdio *cdio = al_alloc_object(struct cch_handler_cdio);
cdio->backing = backing;
struct cch_entry *entry = al_alloc_object(struct cch_entry);
entry->backing = backing;
entry->handler = (struct cch_handler *)cdio;
entry->handler->can_seek = handler_cdio_can_seek;
entry->handler->maybe_spawn_worker = handler_cdio_maybe_spawn_worker;
entry->handler->wait_for_range = handler_cdio_wait_for_range;
entry->handler->guess_format = handler_cdio_guess_format;
entry->handler->free = handler_cdio_free;
entry->handler->entry = entry;
al_str_from(&entry->handler->handler, "cdio");
if (!open_cd_drive(cdio)) {
return NULL;
}
atomic_store(bool)(&cdio->running, false, AL_ATOMIC_RELAXED);
nn_buffer_init(&cdio->buffer);
nn_buffer_ensure_space(&cdio->buffer, BYTES_PER_STEP);
cdio->buffer.size = BYTES_PER_STEP;
cch_threaded_waits_init(&cdio->waits);
return entry;
}
|