/*
* EMU10k1 dump loader
*
* Copyright (c) 2003,2004 by Peter Zubaj
*
* Hwdep usage based on sb16_csp
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
/* TODO - kontrola dat, ktore nahravam */
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/ioctl.h>
#include <alsa/asoundlib.h>
#include <alsa/sound/emu10k1.h>
#include <getopt.h>
#include <bitops.h>
#include "ld10k1_dump_file.h"
#define DL10K1_SIGNATURE "DUMP Image (dl10k1)"
int card = 0;
snd_hwdep_t *handle;
const char *card_proc_id;
void error(const char *fmt,...)
{
va_list va;
va_start(va, fmt);
fprintf(stderr, "Error: ");
vfprintf(stderr, fmt, va);
fprintf(stderr, "\n");
va_end(va);
}
static void help(char *command)
{
fprintf(stderr,
"Usage: %s [-options]\n"
"\nAvailable options:\n"
" -h, --help this help\n"
" -c, --card select card number, default = 0\n"
" -d, --dump file with dump\n"
, command);
}
int driver_set_tram_size(int tram_size)
{
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_TRAM_SETUP, &tram_size) < 0) {
error("unable to setup tram");
return 1;
}
return 0;
}
void free_code_struct(emu10k1_fx8010_code_t *code)
{
if (code->gpr_map)
free(code->gpr_map);
if (code->tram_data_map)
free(code->tram_data_map);
if (code->tram_addr_map)
free(code->tram_addr_map);
if (code->code)
free(code->code);
}
int alloc_code_struct(emu10k1_fx8010_code_t *code)
{
/* alloc code structure */
code->gpr_map = NULL;
code->tram_data_map = NULL;
code->tram_addr_map = NULL;
code->code = NULL;
code->gpr_map = (uint32_t *)malloc(sizeof(uint32_t) * 0x200);
if (!code->gpr_map)
goto err;
memset(code->gpr_map, 0, sizeof(uint32_t) * 0x200);
code->tram_data_map = (uint32_t *)malloc(sizeof(uint32_t) * 0x100);
if (!code->tram_data_map)
goto err;
memset(code->tram_data_map, 0, sizeof(uint32_t) * 0x100);
code->tram_addr_map = (uint32_t *)malloc(sizeof(uint32_t) * 0x100);
if (!code->tram_addr_map)
goto err;
memset(code->tram_addr_map, 0, sizeof(uint32_t) * 0x100);
code->code = (uint32_t *)malloc(sizeof(uint32_t) * 1024 * 2);
if (!code->code)
goto err;
memset(code->code, 0, sizeof(uint32_t) * 1024 * 2);
return 0;
err:
free_code_struct(code);
return -1;
}
int driver_init_dsp(int audigy)
{
int i;
emu10k1_fx8010_code_t code;
emu10k1_fx8010_control_gpr_t *ctrl;
emu10k1_ctl_elem_id_t *ids;
emu10k1_fx8010_pcm_t ipcm;
unsigned int *iptr;
if (alloc_code_struct(&code) < 0) {
error("no mem");
return 1;
}
/* get count of controls */
code.gpr_list_control_count = 0;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_PEEK, &code) < 0) {
error("unable to peek code");
free_code_struct(&code);
return 1;
}
ctrl = (emu10k1_fx8010_control_gpr_t *)malloc(sizeof(emu10k1_fx8010_control_gpr_t) * code.gpr_list_control_total);
if (!ctrl) {
error("no mem");
free_code_struct(&code);
return 1;
}
code.gpr_list_control_count = code.gpr_list_control_total;
code.gpr_list_controls = ctrl;
for (i = 0; i < sizeof(code.gpr_valid) / sizeof(unsigned long); i++)
code.gpr_valid[i] = 0x0;
for (i = 0; i < sizeof(code.tram_valid) / sizeof(unsigned long); i++)
code.tram_valid[i] = 0x0;
for (i = 0; i < sizeof(code.code_valid) / sizeof(unsigned long); i++)
code.code_valid[i] = 0x0;;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_PEEK, &code) < 0) {
error("unable to peek code");
free_code_struct(&code);
free(ctrl);
return 1;
}
/* new name */
strcpy(code.name, DL10K1_SIGNATURE);
for (i = 0; i < sizeof(code.gpr_valid) / sizeof(unsigned long); i++)
code.gpr_valid[i] = ~0;
for (i = 0; i < sizeof(code.gpr_valid) * 8; i++)
code.gpr_map[i] = 0;
ids = (emu10k1_ctl_elem_id_t *)malloc(sizeof(emu10k1_ctl_elem_id_t) * code.gpr_list_control_total);
if (!ids) {
free_code_struct(&code);
free(ctrl);
error("no mem");
return 1;
}
code.gpr_del_control_count = code.gpr_list_control_total;
if (code.gpr_del_control_count) {
for (i = 0; i < code.gpr_del_control_count; i++) {
memcpy(&(ids[i]), &(ctrl[i].id), sizeof(emu10k1_ctl_elem_id_t));
}
}
free(ctrl);
code.gpr_del_controls = ids;
code.gpr_list_control_count = 0;
code.gpr_add_control_count = 0;
code.gpr_list_control_count = 0;
for (i = 0; i < sizeof(code.tram_valid) / sizeof(unsigned long); i++)
code.tram_valid[i] = ~0;
for (i = 0; i < sizeof(code.code_valid) / sizeof(unsigned long); i++)
code.code_valid[i] = ~0;
for (i = 0; i < sizeof(code.tram_valid) * 8; i++) {
code.tram_addr_map[i] = 0;
code.tram_data_map[i] = 0;
}
for (iptr = code.code, i = 0; i < sizeof(code.code_valid) * 8; i++, iptr += 2)
if (audigy) {
*iptr = ((0xcf & 0x7ff) << 12) | (0xc0 & 0x7ff);
*(iptr + 1) = ((0x0f & 0x0f) << 24) | ((0xc0 & 0x7ff) << 12) | (0xc0 & 0x7ff);
} else {
*iptr = ((0x40 & 0x3ff) << 10) | (0x40 & 0x3ff);
*(iptr + 1) = ((0x06 & 0x0f) << 20) | ((0x40 & 0x3ff) << 10) | (0x40 & 0x3ff);
}
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_POKE, &code) < 0) {
error("unable to poke code");
free_code_struct(&code);
free(ids);
return 1;
}
free(ids);
/* delete tram pcm dsp part */
if (!audigy) {
for (i = 0; i < EMU10K1_FX8010_PCM_COUNT; i++) {
ipcm.substream = i;
ipcm.channels = 0;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_PCM_POKE, &ipcm) < 0) {
error("unable to poke code");
free_code_struct(&code);
return 1;
}
}
}
return 0;
}
int dump_load(int audigy, char *file_name)
{
struct stat dump_stat;
void *dump_data, *ptr;
FILE *dump_file;
emu10k1_fx8010_control_gpr_t *ctrl = NULL;
ld10k1_ctl_dump_t *fctrl = NULL;
unsigned int *fgpr = NULL;
ld10k1_tram_dump_t *ftram = NULL;
ld10k1_instr_dump_t *finstr = NULL;
int i, j;
unsigned int vaddr, addr;
int op;
unsigned int *iptr;
emu10k1_fx8010_code_t code;
ld10k1_dump_t *header = NULL;
/* first load patch to mem */
if (stat(file_name, &dump_stat)) {
error("unable to load patch %s", file_name);
return 1;
}
/* minimal dump len is size of header */
if (dump_stat.st_size < sizeof(ld10k1_dump_t)) {
error("unable to load dump %s (wrong file size)", file_name);
return 1;
}
dump_data = malloc(dump_stat.st_size);
if (!dump_data) {
error("no mem");
return 1;
}
dump_file = fopen(file_name, "r");
if (!dump_file) {
error("unable to open file %s", file_name);
goto err;
}
if (fread(dump_data, dump_stat.st_size, 1, dump_file) != 1) {
error("unable to read data from file %s", file_name);
goto err;
} else
fclose(dump_file);
/* signature check */
header = (ld10k1_dump_t *)dump_data;
if (strncmp(header->signature, "LD10K1 DUMP 001", 16) != 0) {
error("wrong dump file %s (wrong signature)", file_name);
goto err;
}
/*printf("Size header%d\nctc %d %d\ngpr %d %d\ntram %d %d\ninstr %d %d\n", sizeof(ld10k1_dump_t),
header->ctl_count, sizeof(ld10k1_ctl_dump_t),
header->gpr_count, sizeof(unsigned int),
header->tram_count, sizeof(ld10k1_tram_dump_t),
header->instr_count, sizeof(ld10k1_instr_dump_t));*/
/*check size */
if (sizeof(ld10k1_dump_t) +
header->ctl_count * sizeof(ld10k1_ctl_dump_t) +
header->gpr_count * sizeof(unsigned int) +
header->tram_count * sizeof(ld10k1_tram_dump_t) +
header->instr_count * sizeof(ld10k1_instr_dump_t) != dump_stat.st_size)
goto err;
/* check dump type */
if (header->dump_type == DUMP_TYPE_LIVE && audigy) {
error("can't load dump from Live to Audigy");
goto err1;
} else if ((header->dump_type == DUMP_TYPE_AUDIGY_OLD ||
header->dump_type == DUMP_TYPE_AUDIGY) &&
!audigy) {
error("can't load dump from Audigy to Live");
goto err1;
} else if (header->dump_type == DUMP_TYPE_AUDIGY_OLD) {
error("can't load dump from Audigy (not patched drivers) to Audigy (current drivers)");
goto err1;
}
ptr = dump_data;
ptr += sizeof(ld10k1_dump_t);
ctrl = (emu10k1_fx8010_control_gpr_t *)malloc(sizeof(emu10k1_fx8010_control_gpr_t) * header->ctl_count);
if (!ctrl) {
error("no mem");
goto err1;
}
if (alloc_code_struct(&code) < 0) {
error("no mem");
return 1;
}
strcpy(code.name, DL10K1_SIGNATURE);
/* copy ctls */
fctrl = (ld10k1_ctl_dump_t *)ptr;
memset(ctrl, 0, sizeof(emu10k1_fx8010_control_gpr_t) * header->ctl_count);
for (i = 0; i < header->ctl_count; i++) {
strcpy(ctrl[i].id.name, fctrl[i].name);
ctrl[i].id.iface = EMU10K1_CTL_ELEM_IFACE_MIXER;
ctrl[i].id.index = fctrl[i].index;
ctrl[i].vcount = fctrl[i].vcount;
ctrl[i].count = fctrl[i].count;
for (j = 0; j < 32; j++) {
ctrl[i].gpr[j] = fctrl[i].gpr_idx[j];
ctrl[i].value[j] = fctrl[i].value[j];
}
ctrl[i].min = fctrl[i].min;
ctrl[i].max = fctrl[i].max;
ctrl[i].translation = fctrl[i].translation;
}
code.gpr_add_control_count = header->ctl_count;
code.gpr_add_controls = ctrl;
code.gpr_del_control_count = 0;
code.gpr_del_controls = NULL;
code.gpr_list_control_count = 0;
code.gpr_list_controls = NULL;
/* copy gprs */
ptr += sizeof(ld10k1_ctl_dump_t) * header->ctl_count;
fgpr = (unsigned int *)ptr;
for (i = 0; i < sizeof(code.gpr_valid) / sizeof(unsigned long); i++)
code.gpr_valid[i] = ~0;
for (i = 0; i < header->gpr_count; i++)
code.gpr_map[i] = fgpr[i];
ptr += sizeof(unsigned int) * header->gpr_count;
ftram = (ld10k1_tram_dump_t *)ptr;
/* tram addr + data */
for (i = 0; i < header->tram_count; i++) {
addr = ftram[i].addr;
vaddr = addr & 0xFFFFF;
op = ftram[i].type;
set_bit(i, code.tram_valid);
switch(op) {
case DUMP_TRAM_READ:
if (audigy)
vaddr = vaddr | 0x2 << 20;
else
vaddr = vaddr | TANKMEMADDRREG_READ | TANKMEMADDRREG_ALIGN;
break;
case DUMP_TRAM_WRITE:
if (audigy)
vaddr = vaddr | 0x6 << 20;
else
vaddr = vaddr | TANKMEMADDRREG_WRITE | TANKMEMADDRREG_ALIGN;
break;
case DUMP_TRAM_NULL:
default:
vaddr = 0;
break;
}
code.tram_addr_map[i] = vaddr;
code.tram_data_map[i] = ftram[i].data;
}
ptr += sizeof(ld10k1_tram_dump_t) * header->tram_count;
finstr = (ld10k1_instr_dump_t *)ptr;
for (iptr = code.code, i = 0; i < header->instr_count; i++, iptr += 2) {
set_bit(i, code.code_valid);
if (finstr[i].used) {
if (audigy) {
*iptr = ((finstr[i].arg[2] & 0x7ff) << 12) | (finstr[i].arg[3] & 0x7ff);
*(iptr + 1) = ((finstr[i].op & 0x0f) << 24) | ((finstr[i].arg[0] & 0x7ff) << 12) | (finstr[i].arg[1] & 0x7ff);
} else {
if (i < 0x200) {
*iptr = ((finstr[i].arg[2] & 0x3ff) << 10) | (finstr[i].arg[3] & 0x3ff);
*(iptr + 1) = ((finstr[i].op & 0x0f) << 20) | ((finstr[i].arg[0] & 0x3ff) << 10) | (finstr[i].arg[1] & 0x3ff);
}
}
} else {
if (audigy) {
*iptr = ((0xcf & 0x7ff) << 12) | (0xc0 & 0x7ff);
*(iptr + 1) = ((0x0f & 0x0f) << 24) | ((0xc0 & 0x7ff) << 12) | (0xc0 & 0x7ff);
} else {
if (i < 0x200) {
*iptr = ((0x40 & 0x3ff) << 10) | (0x40 & 0x3ff);
*(iptr + 1) = ((0x06 & 0x0f) << 20) | ((0x40 & 0x3ff) << 10) | (0x40 & 0x3ff);
}
}
}
}
if (header->dump_type != DUMP_TYPE_AUDIGY_OLD &&
driver_set_tram_size(header->tram_size))
goto err1;
if (driver_init_dsp(audigy))
goto err1;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_POKE, &code) < 0) {
error("unable to poke code");
goto err1;
}
if (dump_data)
free(dump_data);
if (ctrl)
free(ctrl);
return 0;
err:
error("wrong dump file format %s", file_name);
err1:
free_code_struct(&code);
if (dump_data)
free(dump_data);
if (ctrl)
free(ctrl);
return 1;
}
int main(int argc, char *argv[])
{
int dev;
int c;
int err;
int audigy;
int opt_help = 0;
char *opt_dump_file = NULL;
char card_id[32];
snd_ctl_t *ctl_handle;
snd_ctl_card_info_t *card_info;
snd_hwdep_info_t *hwdep_info;
char name[16];
snd_ctl_card_info_alloca(&card_info);
snd_hwdep_info_alloca(&hwdep_info);
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"card", 1, 0, 'c'},
{"dump", 1, 0, 'd'},
{0, 0, 0, 0}
};
int option_index = 0;
while ((c = getopt_long(argc, argv, "hc:d:",
long_options, &option_index)) != EOF) {
switch (c) {
/* case 0: */
/* break; */
case 'h':
opt_help = 1;
break;
case 'd':
opt_dump_file = optarg;
break;
case 'c':
card = snd_card_get_index(optarg);
if (card < 0 || card > 31) {
error("wrong -c argument '%s'\n", optarg);
return 1;
}
break;
default:
return 1;
}
}
if (opt_help) {
help(argv[0]);
return 0;
}
if (!opt_dump_file) {
error("dump file not specified");
return 1;
}
if (getuid() != 0 )
{
error("You are not running dl10k1 as root.");
return 1;
}
/* Get control handle for selected card */
sprintf(card_id, "hw:%i", card);
if ((err = snd_ctl_open(&ctl_handle, card_id, 0)) < 0) {
error("control open (%s): %s", card_id, snd_strerror(err));
return 1;
}
/* Read control hardware info from card */
if ((err = snd_ctl_card_info(ctl_handle, card_info)) < 0) {
error("control hardware info (%s): %s", card_id, snd_strerror(err));
exit(1);
}
if (!(card_proc_id = snd_ctl_card_info_get_id (card_info))) {
error("card id (%s): %s", card_id, snd_strerror(err));
exit(1);
}
/* EMU10k1/EMU10k2 chip is present only on SB Live, Audigy, Audigy 2, E-mu APS cards */
if (strcmp(snd_ctl_card_info_get_driver(card_info), "EMU10K1") != 0 &&
strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy") != 0 &&
strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy2") != 0 &&
strcmp(snd_ctl_card_info_get_driver(card_info), "E-mu APS") != 0) {
error("not a EMU10K1/EMU10K2 based card");
exit(1);
}
if (strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy") == 0 ||
strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy2") == 0)
audigy = 1;
else
audigy = 0;
/* find EMU10k1 hardware dependant device and execute command */
dev = -1;
err = 1;
while (1) {
if (snd_ctl_hwdep_next_device(ctl_handle, &dev) < 0)
error("hwdep next device (%s): %s", card_id, snd_strerror(err));
if (dev < 0)
break;
snd_hwdep_info_set_device(hwdep_info, dev);
if (snd_ctl_hwdep_info(ctl_handle, hwdep_info) < 0) {
if (err != -ENOENT)
error("control hwdep info (%s): %s", card_id, snd_strerror(err));
continue;
}
if (snd_hwdep_info_get_iface(hwdep_info) == SND_HWDEP_IFACE_EMU10K1) {
sprintf(name, "hw:%i,%i", card, dev);
/* open EMU10k1 hwdep device */
if ((err = snd_hwdep_open(&handle, name, O_WRONLY)) < 0) {
error("EMU10k1 open (%i-%i): %s", card, dev, snd_strerror(err));
exit(1);
}
err = dump_load(audigy, opt_dump_file);
snd_hwdep_close(handle);
break;
}
}
snd_ctl_close(ctl_handle);
return 0;
}