//
// Copyright (c) 1997 Jean-Marc Zucconi
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer
//    in this position and unchanged.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products
//    derived from this software without specific prior written permission
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#include <string.h>
#include <stdio.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>

#include "cd-write.h"

#define TIMEOUT 20000

static char msg[500];

static int worm_read_session_info (FD, int *, int *), worm_write_session (FD);
extern void dump_info (union ccb *);
static  union ccb *ccb = 0;
static union ccb ccb_err;
int ccb_h_path_id, ccb_h_target_id, ccb_h_target_lun;
#define CCB  bzero (&ccb->ccb_h, sizeof(struct ccb_hdr));\
           ccb->ccb_h.path_id =  ccb_h_path_id;\
           ccb->ccb_h.target_id =  ccb_h_target_id;\
           ccb->ccb_h.target_lun = ccb_h_target_lun
#define SAVE    ccb_h_path_id = ccb->ccb_h.path_id;\
           ccb_h_target_id = ccb->ccb_h.target_id;\
           ccb_h_target_lun = ccb->ccb_h.target_lun

static int block_size;
static int h_err = 0;

static int w_speeds[4], default_speed;

static int default_1 () {return 1;}
static int default_2 () {return 2;}
static int *no_speeds () {static int s[] = {0}; return (int *) &s;
}

static int
generic_read_blocks (cam_device *fd, char *data, int size, int lba, int bsize)
{
    unsigned char cmd[10] = {0x28, 0, 0,0,0,0, 0, 0,0, 0};
    union ccb *ccb = cam_getccb (fd);

    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 0;
    }

    cmd[2] = (lba >> 24) & 0xff;
    cmd[3] = (lba >> 16) & 0xff;
    cmd[4] = (lba >> 8) & 0xff;
    cmd[5] = lba & 0xff;

    cmd[7] = (size/bsize) >> 8;
    cmd[8] = (size/bsize) & 0xff;

    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_IN, (unsigned char *)data, size, cmd, TIMEOUT);
    
    if (cam_send_ccb(fd, ccb) < 0 || ccb->csio.sense_data.add_sense_code) {
	if (h_err) {
	    bcopy (ccb, &ccb_err, sizeof (ccb_err));
	    cam_freeccb (ccb);
	    return 0;
	}
	Info ("READ command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    return 1;
}
static int 
generic_read_audio_blocks (FD fd, char *data, int size, int lba)
{
    return generic_read_blocks (fd, data, size, lba, 2352);
}
static int 
generic_read_data_blocks (FD fd, char *data, int size, int lba)
{
    return generic_read_blocks (fd, data, size, lba, 2048);
}
//Philips WORM
static int
philips_read_audio_blocks (FD fd, char *data, int size, int lba)
{
    int c;
    h_err = 1;
    c = generic_read_blocks (fd, data, size, lba, 2352);
    h_err = 0;
    if (c) {
	swab (data, data, size);
	return 1;
    }
    if ((ccb_err.csio.sense_data.flags & 0x0f) == 4 && 
	ccb_err.csio.sense_data.add_sense_code == 0x15) {
	sprintf (msg, "Positioning error.");
	Info (msg);
	return 0;
    }
    if ((ccb_err.csio.sense_data.flags & 0x0f) == 1 && 
	ccb_err.csio.sense_data.add_sense_code == 0x17) {
	sprintf (msg, "Read error (recovered).");
	Info (msg);
	swab (data, data, size);
	return 1;
    }
    Info ("READ command failed");
    dump_info (&ccb_err);
    return 0;
}
// SONY cd-roms
static int
no_set (FD i, int j) 
{
    return 1;
}
static int *
sony_speeds ()
{
    static int s[] = {1, 0};
    return (int *) &s;
}
static int
sony_read_audio_blocks (cam_device *fd, char *data, int size, int lba)
{
    unsigned char cmd[12] = {0xd8, 0, 0,0,0,0, 0,0,0,0, 0, 0};

    union ccb *ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 0;
    }
    cmd[2] = (lba >> 24) & 0xff;
    cmd[3] = (lba >> 16) & 0xff;
    cmd[4] = (lba >> 8) & 0xff;
    cmd[5] = lba & 0xff;

    cmd[6] = ((size/2352) >> 24) & 0xff;
    cmd[7] = ((size/2352) >> 16) & 0xff;
    cmd[8] = ((size/2352) >> 8) & 0xff;
    cmd[9] = (size/2352) & 0xff;

    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_IN, (unsigned char *)data, size, cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0 || ((unsigned char *)&ccb->csio.sense_data)[12]) {
	Info ("READ command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    return 1;
}
struct cd_op sony_cd_op = {no_set, no_set,
			   sony_read_audio_blocks, 
			   generic_read_data_blocks, 
			   sony_speeds, default_1, 0}; 

// PHILIPS WORM
static int
philips_setmode (FD fd, int i)
{
    union ccb *ccb;
    unsigned char cmd[6] = {0x15, 0, 0, 0, 0xc, 0};
    unsigned char data[12] = {0, 0, 0, 8,  0, 0, 0, 0, 0, 0, 0,0};
    int size;

    if (i == T_DATA)
	size = 2048;
    else
	size = 2352;
    data[10] = size >> 8;
    data[11] = size & 0xff;

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_OUT, data, sizeof(data), cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    return 1;
}
static int
philips2600_setspeed (FD fd, int speed)
{
    union ccb *ccb;
    unsigned char cmd[6] = {0x15, 0x10, 0, 0, 0xc, 0};
    unsigned char data[12] = {0, 0, 0, 0,  0x23, 6, 0, 0, 0, 0, 0, 0};

    data[8] = speed;
    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 0;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_OUT, data, sizeof(data), cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    return 1;
}
static int
philips_setspeed (FD fd, int speed)
{
    union ccb *ccb;
    unsigned char cmd[6] = {0x15, 0x10, 0, 0, 0xc, 0};
    unsigned char data[12] = {0, 0, 0, 0,  0x23, 6, 0, 0, 0, 0, 0, 0};
    
    data[6] = speed;
    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 0;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_OUT, data, sizeof(data), cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    return 1;
}
static int *
philips_cdd2600_speeds ()
{
    static int s[] = {1, 2, 6, 0};
    return (int *)&s;
}
static int *
philips_cdd2000_speeds ()
{
    static int s[] = {1, 2, 0};
    return (int *)&s;
}

static int *
philips_worm_speeds ()
{
    static int s[] = {1, 2, 0};
    return (int *)&s;
}
static int
philips_cdd2000_disk_init (FD fd, int speed, int dummy)
{
    unsigned char cmd[6] = {0x15, 0x10, 0, 0, 0xc, 0};
    unsigned char cmd2[6] = {0x1b, 0, 0, 0, 1, 0};
    unsigned char data[12] = {0, 0, 0, 0,  0x23, 6, 0, 0, 0, 0, 0, 0};

    data[6] = speed;
    data[7] = dummy;

    if (ccb)
	cam_freeccb (ccb);
    ccb = cam_getccb (fd);
    if (!ccb) {
	Info ("CAM: error allocating ccb");
	return 1;
    }
    SAVE;

    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_OUT, data, sizeof(data), cmd, TIMEOUT);
    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	return 1;
    }
    CCB;
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd2, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("START UNIT command failed");
	dump_info (ccb);
	return 1;
    }
    return 0;
}
static int
philips_cdd2000_track_start (FD fd, int type, int len)
{
    unsigned char cmd[6] = {0x15, 0x10, 0, 0, 0x1c, 0};
    unsigned char data[28] = {0, 0, 0, 8,  0, 0, 0, 0, 0, 0, 0,0,  0x21, 0xE, 0,
		     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0};
    int flags, bsize;

    bsize = 2352;
    block_size = bsize;
    if (type == T_DAO)
	return 0;
    if (type == T_DATA) {
	flags = 1;
	bsize = 2048;
    } else if (type == T_AUDIO)
	flags = 4;
    else
	flags = 5;

    block_size = bsize;
    data[10] = bsize << 8;
    data[11] = bsize & 0xff;
    data[15] = flags;
    CCB;
    CFILL (ccb, CAM_DIR_OUT, data, sizeof(data), cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	return 1;
    }

    return 0;
}
static int
philips_cdd2000_track_end (FD fd)
{
    unsigned char cmd[10] = {0x35, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    int c;

    CCB;
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	if ((ccb_err.csio.sense_data.flags & 0xf) == 5 && 
	    (ccb_err.csio.sense_data.add_sense_code == 0xb5)) {
	    Info ("Warning: dummy blocks added");
	    return 0;
	}
	if ((ccb_err.csio.sense_data.flags & 0xf) == 3 && 
	    (ccb_err.csio.sense_data.add_sense_code == 0x34)) {
	    Info ("Absorption control error (not fatal)");
	    unsigned char data[132], *p;
	    unsigned char cmd[10] = {0xEB, 0, 0, 0, 0, 0, 0, 0, 1, 0};
	    cmd[8] = sizeof(data);
	    CCB;
	    CFILL (ccb, CAM_DIR_IN, data, sizeof(data), cmd, TIMEOUT);

	    if (cam_send_ccb(fd, ccb) < 0) {
		Info ("SEND ABSORPTION CONTROL ERRORS command failed");
		dump_info (ccb);
		return 0; // !!
	    }
	    sprintf (msg, "%d errors", data[3]);
	    p = data + 4;
	    c = data[1]/16;
	    while (c--) {
		sprintf (msg, "error # %d: addr= %d\n", 
			p[2], (((p[3]*256+p[5])*256+p[6])*256)+p[7]);
		p += 8;
	    }
	    return 0;
	}
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	return 1;
    }
    return 0;
}
static int
philips_cdd2000_disk_end (FD fd, int type)
{
    unsigned char cmd[10] = {0xE9, 0, 0, 0, 0, 0, 0, 0, 1, 0};

    CCB;
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 1000*60*15);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	return 1;
    }
    return 0;
}
static int
philips_cdd2000_write_recover (FD fd)
{
    // the write command failed for some reason.
    int sense_key = ccb_err.csio.sense_data.flags & 0xf;
    int asc = ccb_err.csio.sense_data.add_sense_code;

    if (sense_key == 0xd && asc == 0xaa) {
	Info ("WRITE ERROR: end of medium reached or power calibration area full");
	philips_cdd2000_track_end (fd);
	return WERR_UNRECOV;
    }
    if (sense_key == 0xb && asc == 0xad) {
	Info ("WRITE ERROR: buffer underrun. Closing track and continuing");
	philips_cdd2000_track_end (fd);
	return WERR_TRACK;
    }
    if (sense_key == 0x3 && asc == 0x34) {
	Info ("WRITE ERROR: absorption control error");
	return WERR_TRACK;
    }
    if (sense_key == 0x3 && asc == 0xaf) {
	Info ("WRITE ERROR: optimum power calibration error");
	return WERR_UNRECOV;
    }
    if (sense_key == 0xd && asc == 0xb4) {
	Info ("WRITE ERROR: calibration area full");
	return WERR_UNRECOV;
    }
    if (sense_key == 1)
	return WERR_RECOVERED;
     
    Info ("WRITE command failed");
    dump_info (ccb);
    return WERR_UNRECOV;    	     
}
static int
philips_cdd2000_write (FD fd, char *buf, int size)
{
    unsigned char cmd[10] = {0x2A, 0,  0,0,0,0,  0,  0,1, 0};
    int n;

    n = size/block_size;
    cmd[7] = n >> 8;
    cmd[8] = n & 0xff;

    CCB;
    CFILL (ccb, CAM_DIR_OUT, (unsigned char *)buf, size, cmd, TIMEOUT);

    if (cam_send_ccb (fd, ccb) < 0) {
	n = philips_cdd2000_write_recover (fd);
	if (n)
	    return n;
    }
}
struct cd_op philips_cdd2000_cd_op = {philips_setmode, philips_setspeed, 
				      philips_read_audio_blocks,
				      generic_read_data_blocks,
				      philips_cdd2000_speeds,
				      default_2, 1}; 
struct cd_op philips_cdd2600_cd_op = {philips_setmode, philips2600_setspeed, 
				      philips_read_audio_blocks,
				      generic_read_data_blocks,
				      philips_cdd2600_speeds,
				      default_2, 1}; 

static struct {
    int toc_type;
    int onp;
    int lofp;
    int length;
    char catalog[13];
    u_char *track_desc;
} session = {0, 0, 0, 0, "", 0};

static void
make_cue_sheet (cuesheet *c)
{
    int length, size;
    msf *index;
    cuesheet *a;
    u_char *t;
    // compute length
    for (length = 0, a = c; a; a = a->next) {
	length += 12;
	index = c->index;
	while (index) {
	    length += 4;
	    index = index->next;
	}
    }
    session.length = length;
    session.toc_type = 0; /* CD-DA */
    session.onp = 0;
    session.lofp = 0;
    bzero (session.catalog, 13);
    if (session.track_desc)
	delete [] session.track_desc;
    t = session.track_desc = new u_char[length];
    bzero (t, length);
    while (c) {
	index = c->index;
	length = 0;
	while (index) {
	    size = index->m*60*75 + index->s*75 + index->f;
	    t[12+length] = size >> 24;
	    t[13+length] = (size >> 16) & 0xff;
	    t[14+length] = (size >> 8) & 0xff;
	    t[15+length] = size & 0xff;
	    length += 4;
	    index = index->next;
	}
	length += 12;
	t[0] = length >> 8;
	t[1] = length & 0xff;
	t[2] = c->preemp;
	t += length;
	c = c->next;
    }
}

static int
worm_send_cuesheet (FD fd, cuesheet *c, int *leadin, int *leadout)
{
    int error;

    error = worm_read_session_info (fd, leadin, leadout);
    if (error)
	return error;
    
    make_cue_sheet (c);
    
    error = worm_write_session (fd);
    return error;
}
static int
worm_read_session_info (FD fd, int *leadin, int *leadout)
{
    unsigned char cmd[10] = {0xee, 0, 0, 0, 0, 0, 0, 0, 4, 0};
    unsigned char data[4];

    CCB;
    CFILL (ccb, CAM_DIR_IN, data, sizeof (data), cmd, TIMEOUT);
    
    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("READ SESSION INFO command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return -1;
    }
    *leadin = (data[0] << 8)| data[1];
    *leadout = (data[2] << 8)| data[3];
    return 0;
}
static int
worm_write_session (FD fd)
{
    unsigned char data[22] = {0, 0, 0, 8,  0, 0, 0, 0, 0, 0, 0,0, 
		     0x22, 8, 0, 0, 0, 0, 0, 0, 0, 0};
    unsigned char cmd[6] = {0x15, 0x10, 0, 0, 22, 0};
    unsigned char cmd2[10] = {0xED, 0, 0, 0, 0, 0, 1, 0,1, 0};

    data[10] = 2352 >> 8;
    data[11] = 2352 & 0xff;
    CCB;
    CFILL (ccb, CAM_DIR_OUT, data, sizeof (data), cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("WRITE SESSION command failed");
	dump_info (ccb);
	return -1;
    }
    cmd2[6] = (session.lofp << 4) | (session.onp? 8 : 0) + session.toc_type;
    cmd2[7] = session.length >> 8;
    cmd2[8] = session.length & 0xff;
    CCB;
    CFILL (ccb, CAM_DIR_OUT, session.track_desc, session.length, cmd2, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("WRITE SESSION command failed");
	dump_info (ccb);
	return -1;
    }
    return 0;
}
struct worm_op philips_cdd2000_worm_op = {philips_cdd2000_disk_init,
					  philips_cdd2000_track_start,
					  philips_cdd2000_write,
					  philips_cdd2000_track_end,
					  philips_cdd2000_disk_end,
					  worm_send_cuesheet,
					  philips_worm_speeds,
					  default_2, 
					  1};
struct worm_op philips_cdd2600_worm_op = {philips_cdd2000_disk_init,
					  philips_cdd2000_track_start,
					  philips_cdd2000_write,
					  philips_cdd2000_track_end,
					  philips_cdd2000_disk_end,
					  worm_send_cuesheet,
					  philips_worm_speeds,
					  default_2,
					  1};
struct worm_op plasmon_worm_op = {philips_cdd2000_disk_init,
				  philips_cdd2000_track_start,
				  philips_cdd2000_write,
				  philips_cdd2000_track_end,
				  philips_cdd2000_disk_end,
				  0,
				  philips_worm_speeds,
				  default_2, 
				  1};
// TOSHIBA CDroms
static int
toshiba_setmode (FD fd, int i) 
{
    union ccb *ccb;
    unsigned char cmd[6] = {0x15, 0, 0, 0, 0xc, 0};
    int size, density;
    unsigned char data[12] = {0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,0};

    if (i == T_DATA) {
	size = 2048;
	density = 0;
    } else {
	size = 2352;
	density = 0x82;
    }
    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    data[4] = density;
    data[10] = size >> 8;
    data[11] = size & 0xff;
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_OUT, data, sizeof (data), cmd, TIMEOUT);

    if (cam_send_ccb(fd, ccb) < 0) {
	Info ("MODE SELECT command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    return 1;
}
static int
toshiba_setspeed (int i, int s)
{
    return s == 1; // XXX
}
static int *
toshiba_speeds ()
{
    static int s[] = {1, 0}; // XXX
    return (int *) &s;
}
static int
toshiba_read_audio_blocks (FD fd, char *data, int size, int lba)
{
    return generic_read_blocks (fd, data, size, lba, 2352);
}
struct cd_op toshiba_cd_op = {toshiba_setmode, no_set,
			      0, 
			      generic_read_data_blocks, 
			      no_speeds, default_1, 0}; 

struct cd_op data_only_cd_op = {no_set, no_set, 
				0, 
				generic_read_data_blocks, 
				no_speeds, default_1, 0}; 
struct cd_op *
cd_cap (char *vendor, char *product, char *version)
{
    if (strcmp (vendor, "SONY") == 0)
	    return &sony_cd_op;
    if (strcmp (vendor, "PHILIPS") == 0) {
	if  (strstr (product, "CDD2600"))
	    return &philips_cdd2600_cd_op;
	if  (strstr (product, "CDD2000"))
	    return &philips_cdd2000_cd_op;
    }
    if (strcmp (vendor, "HP") == 0) {
	if  (strstr (product, "6020"))
	    return &philips_cdd2600_cd_op;
	if  (strstr (product, "4020"))
	    return &philips_cdd2000_cd_op;
	if  (strstr (product, "C4324/C4325"))
            return &philips_cdd2000_cd_op;
    }
    if (strcmp (vendor, "TOSHIBA") == 0) 
	return &toshiba_cd_op;

    return &data_only_cd_op;
}
struct worm_op *
worm_cap (char *vendor, char *product, char *version)
{
    if (strcmp (vendor, "PHILIPS") == 0) {
	if  (strstr (product, "CDD2600"))
	    return &philips_cdd2600_worm_op;
	if  (strstr (product, "CDD2000"))
	    return &philips_cdd2000_worm_op;
    }
    if (!strcmp (vendor, "HP")) {
	if (strstr (product, "6020"))
	    return &philips_cdd2600_worm_op;
	if  (strstr (product, "C4324/C4325"))
            return &philips_cdd2000_worm_op;
    }
    if (!strcmp (vendor, "IMS") && strstr (product, "CDD2000"))
	return &philips_cdd2000_worm_op;
    if (!strcmp (vendor, "PLASMON") && strstr (product, "RF"))
            return &plasmon_worm_op;
    return 0;
}
