1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#ifdef SDL_CDROM_QNX
25
26/* Functions for system-level CD-ROM audio control */
27
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <sys/ioctl.h>
31#include <fcntl.h>
32#include <errno.h>
33#include <unistd.h>
34#include <sys/cdrom.h>
35#include <sys/dcmd_cam.h>
36
37#include "SDL_timer.h"
38#include "SDL_cdrom.h"
39#include "../SDL_syscdrom.h"
40
41/* The maximum number of CD-ROM drives we'll detect */
42#define MAX_DRIVES 16
43
44#define QNX_CD_OPENMODE O_RDONLY | O_EXCL
45
46/* A list of available CD-ROM drives */
47static char *SDL_cdlist[MAX_DRIVES];
48static dev_t SDL_cdmode[MAX_DRIVES];
49static int   SDL_cdopen[MAX_DRIVES];
50
51/* The system-dependent CD control functions */
52static const char *SDL_SYS_CDName(int drive);
53static int SDL_SYS_CDOpen(int drive);
54static int SDL_SYS_CDGetTOC(SDL_CD *cdrom);
55static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position);
56static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length);
57static int SDL_SYS_CDPause(SDL_CD *cdrom);
58static int SDL_SYS_CDResume(SDL_CD *cdrom);
59static int SDL_SYS_CDStop(SDL_CD *cdrom);
60static int SDL_SYS_CDEject(SDL_CD *cdrom);
61static void SDL_SYS_CDClose(SDL_CD *cdrom);
62
63/* Check a drive to see if it is a CD-ROM */
64static int CheckDrive(char *drive, struct stat *stbuf)
65{
66    int is_cd, cdfd;
67    cam_devinfo_t dinfo;
68    int devctlret=0;
69
70    int atapi;
71    int removable;
72    int cdb10;
73
74    /* If it doesn't exist, return -1 */
75    if (stat(drive, stbuf) < 0)
76    {
77        return(-1);
78    }
79
80    /* If it does exist, verify that it's an available CD-ROM */
81    is_cd = 0;
82
83    if (S_ISCHR(stbuf->st_mode) || S_ISBLK(stbuf->st_mode))
84    {
85        cdfd = open(drive, QNX_CD_OPENMODE);
86        if ( cdfd >= 0 )
87        {
88            devctlret=devctl(cdfd, DCMD_CAM_DEVINFO, &dinfo, sizeof(cam_devinfo_t), NULL);
89
90            if (devctlret==EOK)
91            {
92               atapi=dinfo.flags & DEV_ATAPI;
93               removable=dinfo.flags & DEV_REMOVABLE;
94               cdb10=dinfo.flags & DEV_CDB_10; /* I'm not sure about that flag */
95
96               /* in the near future need to add more checks for splitting cdroms from other devices */
97               if ((atapi)&&(removable))
98               {
99                   is_cd = 1;
100               }
101            }
102
103            close(cdfd);
104        }
105    }
106    return(is_cd);
107}
108
109/* Add a CD-ROM drive to our list of valid drives */
110static void AddDrive(char *drive, struct stat *stbuf)
111{
112    int i;
113
114    if (SDL_numcds < MAX_DRIVES)
115    {
116        /* Check to make sure it's not already in our list.
117        This can happen when we see a drive via symbolic link. */
118
119        for (i=0; i<SDL_numcds; ++i)
120        {
121            if (stbuf->st_rdev == SDL_cdmode[i])
122            {
123                return;
124            }
125        }
126
127        /* Add this drive to our list */
128
129        i = SDL_numcds;
130        SDL_cdlist[i] = SDL_strdup(drive);
131        if (SDL_cdlist[i] == NULL)
132        {
133            SDL_OutOfMemory();
134            return;
135        }
136        SDL_cdmode[i] = stbuf->st_rdev;
137        ++SDL_numcds;
138    }
139}
140
141int SDL_SYS_CDInit(void)
142{
143    /* checklist: /dev/cdrom, /dev/cd?, /dev/scd? */
144    static char *checklist[]={"cdrom", "?0 cd?", "?1 cd?", "?0 scd?", NULL};
145
146    char *SDLcdrom;
147    int i, j, exists;
148    char drive[32];
149    struct stat stbuf;
150
151    /* Fill in our driver capabilities */
152    SDL_CDcaps.Name = SDL_SYS_CDName;
153    SDL_CDcaps.Open = SDL_SYS_CDOpen;
154    SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC;
155    SDL_CDcaps.Status = SDL_SYS_CDStatus;
156    SDL_CDcaps.Play = SDL_SYS_CDPlay;
157    SDL_CDcaps.Pause = SDL_SYS_CDPause;
158    SDL_CDcaps.Resume = SDL_SYS_CDResume;
159    SDL_CDcaps.Stop = SDL_SYS_CDStop;
160    SDL_CDcaps.Eject = SDL_SYS_CDEject;
161    SDL_CDcaps.Close = SDL_SYS_CDClose;
162
163    /* clearing device open status */
164    for (i=0; i<MAX_DRIVES; i++)
165    {
166       SDL_cdopen[i]=0;
167    }
168
169    /* Look in the environment for our CD-ROM drive list */
170    SDLcdrom = SDL_getenv("SDL_CDROM");	/* ':' separated list of devices */
171    if ( SDLcdrom != NULL )
172    {
173        char *cdpath, *delim;
174	size_t len = SDL_strlen(SDLcdrom)+1;
175        cdpath = SDL_stack_alloc(char, len);
176        if (cdpath != NULL)
177        {
178            SDL_strlcpy(cdpath, SDLcdrom, len);
179            SDLcdrom = cdpath;
180            do {
181                delim = SDL_strchr(SDLcdrom, ':');
182                if (delim)
183                {
184                    *delim++ = '\0';
185                }
186                if (CheckDrive(SDLcdrom, &stbuf) > 0)
187                {
188                    AddDrive(SDLcdrom, &stbuf);
189                }
190                if (delim)
191                {
192                    SDLcdrom = delim;
193                }
194                else
195                {
196                    SDLcdrom = NULL;
197                }
198            } while (SDLcdrom);
199            SDL_stack_free(cdpath);
200        }
201
202        /* If we found our drives, there's nothing left to do */
203        if (SDL_numcds > 0)
204        {
205            return(0);
206        }
207    }
208
209    /* Scan the system for CD-ROM drives */
210    for ( i=0; checklist[i]; ++i )
211    {
212        if (checklist[i][0] == '?')
213        {
214            char* insert;
215            exists = 1;
216
217            for ( j=checklist[i][1]; exists; ++j )
218            {
219                SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s", &checklist[i][3]);
220                insert = SDL_strchr(drive, '?');
221                if (insert != NULL)
222                {
223                    *insert = j;
224                }
225                switch (CheckDrive(drive, &stbuf))
226                {
227                    /* Drive exists and is a CD-ROM */
228                    case 1:
229                             AddDrive(drive, &stbuf);
230                             break;
231                    /* Drive exists, but isn't a CD-ROM */
232                    case 0:
233                             break;
234                    /* Drive doesn't exist */
235                    case -1:
236                             exists = 0;
237                             break;
238                }
239            }
240        }
241        else
242        {
243            SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s", checklist[i]);
244            if (CheckDrive(drive, &stbuf) > 0)
245            {
246                AddDrive(drive, &stbuf);
247            }
248        }
249    }
250    return(0);
251}
252
253static const char *SDL_SYS_CDName(int drive)
254{
255    return(SDL_cdlist[drive]);
256}
257
258static int SDL_SYS_CDOpen(int drive)
259{
260    int handle;
261
262    handle=open(SDL_cdlist[drive], QNX_CD_OPENMODE);
263
264    if (handle>0)
265    {
266        SDL_cdopen[drive]=handle;
267    }
268
269    return (handle);
270}
271
272static int SDL_SYS_CDGetTOC(SDL_CD *cdrom)
273{
274    cdrom_read_toc_t toc;
275    int i, okay;
276
277    okay = 0;
278    if (devctl(cdrom->id, DCMD_CAM_CDROMREADTOC, &toc, sizeof(toc), NULL) == 0)
279    {
280        cdrom->numtracks = toc.last_track - toc.first_track + 1;
281        if (cdrom->numtracks > SDL_MAX_TRACKS)
282        {
283            cdrom->numtracks = SDL_MAX_TRACKS;
284        }
285        /* Read all the track TOC entries */
286        for (i=0; i<=cdrom->numtracks; ++i)
287        {
288            if (i == cdrom->numtracks)
289            {
290                cdrom->track[i].id = CDROM_LEADOUT;
291            }
292            else
293            {
294                cdrom->track[i].id = toc.first_track+i;
295            }
296
297            cdrom->track[i].type = toc.toc_entry[i].control_adr & 0x0F;
298            cdrom->track[i].offset = toc.toc_entry[i].addr.lba;
299            cdrom->track[i].length = 0;
300
301            if (i > 0)
302            {
303                 cdrom->track[i-1].length = cdrom->track[i].offset-cdrom->track[i-1].offset;
304            }
305        }
306        if (i == (cdrom->numtracks+1))
307        {
308            okay = 1;
309        }
310    }
311    return (okay ? 0 : -1);
312}
313
314/* Get CD-ROM status */
315static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position)
316{
317    CDstatus status;
318
319    cdrom_read_toc_t toc;
320    cdrom_subch_data_t info;
321    cam_devinfo_t dinfo;
322
323    int devctlret=0;
324    int drive=-1;
325    int i;
326    int eagaincnt=0;
327
328    /* check media presence before read subchannel call, some cdroms can lockups */
329    /* if no media, while calling read subchannel functions.                     */
330    devctlret=devctl(cdrom->id, DCMD_CAM_DEVINFO, &dinfo, sizeof(cam_devinfo_t), NULL);
331
332    if (devctlret==EOK)
333    {
334        if ((dinfo.flags & DEV_NO_MEDIA)!=0)
335        {
336            status = CD_TRAYEMPTY;
337            if (position)
338            {
339                *position = 0;
340            }
341            return (status);
342        }
343    }
344
345    /* if media exists, then do other stuff */
346
347    SDL_memset(&info, 0x00, sizeof(info));
348    info.subch_command.data_format = CDROM_SUBCH_CURRENT_POSITION;
349
350    do {
351        devctlret=devctl(cdrom->id, DCMD_CAM_CDROMSUBCHNL, &info, sizeof(info), NULL);
352        if (devctlret==EIO)
353        {
354            /* big workaround for media change, handle is unusable after that,
355               that bug was found in QNX 6.2, 6.2.1 is not released yet.    */
356
357            for (i=0; i<MAX_DRIVES; i++)
358            {
359                if (SDL_cdopen[i]==cdrom->id)
360                {
361                    drive=i;
362                    break;
363                }
364            }
365            if (drive==-1)
366            {
367               /* that cannot happen, but ... */
368               break;
369            }
370            close(cdrom->id);
371            cdrom->id=open(SDL_cdlist[drive], QNX_CD_OPENMODE);
372            devctlret=EAGAIN;
373        }
374        if (devctlret==EAGAIN)
375        {
376            eagaincnt++;
377        }
378        if (eagaincnt==2)
379        {
380            /* workaround for broken cdroms, which can return always EAGAIN when its not ready, */
381            /* that mean errornous media or just no media avail                                 */
382            devctlret=ENXIO;
383            break;
384        }
385    } while ((devctlret==EAGAIN)||(devctlret==ESTALE));
386
387    if (devctlret != 0)
388    {
389        if (devctlret==ENXIO)
390        {
391            status = CD_TRAYEMPTY;
392        }
393        else
394        {
395            status = CD_ERROR;
396        }
397    }
398    else
399    {
400        switch (info.current_position.header.audio_status)
401        {
402            case CDROM_AUDIO_INVALID:
403            case CDROM_AUDIO_NO_STATUS:
404                 /* Try to determine if there's a CD available */
405                 if (devctl(cdrom->id, DCMD_CAM_CDROMREADTOC, &toc, sizeof(toc), NULL)==0)
406                     status = CD_STOPPED;
407                 else
408                     status = CD_TRAYEMPTY;
409                 break;
410            case CDROM_AUDIO_COMPLETED:
411                 status = CD_STOPPED;
412                 break;
413            case CDROM_AUDIO_PLAY:
414                 status = CD_PLAYING;
415                 break;
416            case CDROM_AUDIO_PAUSED:
417                 /* Workaround buggy CD-ROM drive */
418                 if (info.current_position.data_format == CDROM_LEADOUT)
419                 {
420                     status = CD_STOPPED;
421                 }
422                 else
423                 {
424                     status = CD_PAUSED;
425                 }
426                 break;
427            default:
428                 status = CD_ERROR;
429                 break;
430        }
431    }
432
433    if (position)
434    {
435       if (status==CD_PLAYING || (status==CD_PAUSED))
436       {
437           *position = MSF_TO_FRAMES(info.current_position.addr.msf.minute,
438                                     info.current_position.addr.msf.second,
439                                     info.current_position.addr.msf.frame);
440       }
441       else
442       {
443           *position = 0;
444       }
445    }
446
447    return (status);
448}
449
450/* Start play */
451static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length)
452{
453    cdrom_playmsf_t playtime;
454
455    FRAMES_TO_MSF(start, &playtime.start_minute, &playtime.start_second, &playtime.start_frame);
456    FRAMES_TO_MSF(start+length, &playtime.end_minute, &playtime.end_second, &playtime.end_frame);
457
458    if (devctl(cdrom->id, DCMD_CAM_CDROMPLAYMSF, &playtime, sizeof(playtime), NULL) != 0)
459    {
460       return -1;
461    }
462    else
463    {
464       return 0;
465    }
466}
467
468/* Pause play */
469static int SDL_SYS_CDPause(SDL_CD *cdrom)
470{
471    if (devctl(cdrom->id, DCMD_CAM_CDROMPAUSE, NULL, 0, NULL)!=0)
472    {
473       return -1;
474    }
475    else
476    {
477       return 0;
478    }
479}
480
481/* Resume play */
482static int SDL_SYS_CDResume(SDL_CD *cdrom)
483{
484    if (devctl(cdrom->id, DCMD_CAM_CDROMRESUME, NULL, 0, NULL)!=0)
485    {
486       return -1;
487    }
488    else
489    {
490       return 0;
491    }
492}
493
494/* Stop play */
495static int SDL_SYS_CDStop(SDL_CD *cdrom)
496{
497    if (devctl(cdrom->id, DCMD_CAM_CDROMSTOP, NULL, 0, NULL)!=0)
498    {
499       return -1;
500    }
501    else
502    {
503       return 0;
504    }
505}
506
507/* Eject the CD-ROM */
508static int SDL_SYS_CDEject(SDL_CD *cdrom)
509{
510    if (devctl(cdrom->id, DCMD_CAM_EJECT_MEDIA, NULL, 0, NULL)!=0)
511    {
512       return -1;
513    }
514    else
515    {
516       return 0;
517    }
518}
519
520/* Close the CD-ROM handle */
521static void SDL_SYS_CDClose(SDL_CD *cdrom)
522{
523    int i;
524
525    for (i=0; i<MAX_DRIVES; i++)
526    {
527       if (SDL_cdopen[i]==cdrom->id)
528       {
529           SDL_cdopen[i]=0;
530           break;
531       }
532    }
533
534    close(cdrom->id);
535}
536
537void SDL_SYS_CDQuit(void)
538{
539    int i;
540
541    if (SDL_numcds > 0)
542    {
543        for (i=0; i<SDL_numcds; ++i)
544        {
545            SDL_free(SDL_cdlist[i]);
546        }
547        SDL_numcds = 0;
548    }
549}
550
551#endif /* SDL_CDROM_QNX */
552