blob: 4492f8f80143af71bab852b3d47da20315d6afc4 [file]
/*
* hda-emu - simple HD-audio codec emulator for debugging snd-hda-intel driver
*
* Pretty simple command-line shell
*
* Copyright (c) Takashi Iwai <tiwai@suse.de>
*
* This driver 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 driver 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#ifdef HAVE_LIBREADLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include "hda-types.h"
#include "hda-log.h"
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
static void usage(char *);
struct delayed_work *__work_pending;
extern struct list_head snd_ctl_list_head;
static char *gettoken(char **bufp)
{
char *p;
char *token = *bufp;
char quote = 0;
while (*token && isspace(*token))
token++;
if (!*token || *token == '\n')
return NULL;
if (*token == '\'' || *token == '"') {
quote = *token;
token++;
}
p = token;
for (; *p && *p != '\n'; p++) {
if (*p == quote)
break;
if (!quote && isspace(*p))
break;
}
if (*p)
*p++ = 0;
*bufp = p;
return token;
}
static int getint(char **bufp, int *val)
{
char *token = gettoken(bufp);
if (!token)
return -ENOENT;
*val = strtoul(token, NULL, 0);
return 0;
}
static int getbool(char **bufp, int *val)
{
char *token = gettoken(bufp);
if (!token)
return -ENOENT;
if (*token == '1' || !strcmp(token, "on"))
*val = 1;
else
*val = 0;
return 0;
}
static void show_elements(char *line)
{
struct snd_kcontrol *kctl;
list_for_each_entry(kctl, &snd_ctl_list_head, list) {
hda_log(HDA_LOG_INFO, "%d: %s:%d (%d)\n",
kctl->id.numid, kctl->id.name, kctl->id.index,
kctl->count);
}
}
static char *get_enum_name(struct snd_kcontrol *kctl, int item)
{
static struct snd_ctl_elem_info uinfo;
memset(&uinfo, 0, sizeof(uinfo));
uinfo.id = kctl->id;
uinfo.value.enumerated.item = item;
kctl->info(kctl, &uinfo);
return uinfo.value.enumerated.name;
}
static void db_string(int db, char *str, int len)
{
const char *pfx;
if (db < 0) {
pfx = "-";
db = -db;
} else {
pfx = "";
}
snprintf(str, len, "%s%d.%02d", pfx, db / 100, db % 100);
}
static void show_db_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *uinfo,
struct snd_ctl_elem_value *uval)
{
unsigned int _tlv[64];
const unsigned int *tlv;
int i, err;
int mindb, maxdb, step;
char db1[8], db2[8];
if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
err = kctl->tlv.c(kctl, 0, sizeof(_tlv), _tlv);
if (err < 0)
return;
tlv = _tlv;
} else {
tlv = kctl->tlv.p;
}
if (!tlv)
return;
if (*tlv != SNDRV_CTL_TLVT_DB_SCALE)
return;
mindb = tlv[2];
step = tlv[3] & 0xffff;
maxdb = mindb +
(uinfo->value.integer.max - uinfo->value.integer.min) * step;
db_string(mindb, db1, sizeof(db1));
db_string(maxdb, db2, sizeof(db2));
hda_log(HDA_LOG_INFO, "\ndB min/max: %sdB%s,%sdB", db1,
(tlv[3] & 0x10000) ? "(mute)" : "", db2);
for (i = 0; i < uinfo->count; i++) {
int curv, curdb;
curv = uval->value.integer.value[i];
if (curv < uinfo->value.integer.min)
curv = uinfo->value.integer.min;
else if (curv > uinfo->value.integer.max)
curv = uinfo->value.integer.max;
curdb = mindb + step * (curv - uinfo->value.integer.min);
db_string(curdb, db1, sizeof(db1));
hda_log(HDA_LOG_INFO, " [%sdB]", db1);
}
}
/* name string may be modified */
static int get_ctl_numid(char *name)
{
struct snd_kcontrol *kctl = NULL;
struct snd_ctl_elem_id id;
char *p;
memset(&id, 0, sizeof(id));
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
p = strchr(name, ':');
if (p) {
id.index = atoi(p + 1);
if (id.index < 0)
goto error;
*p = 0;
}
strncpy(id.name, name, sizeof(id.name) - 1);
kctl = snd_ctl_find_id(NULL, &id);
error:
if (p)
*p = ':';
if (kctl)
return kctl->id.numid;
return 0;
}
static void get_element(char *line)
{
struct snd_kcontrol *kctl;
struct snd_ctl_elem_info uinfo;
struct snd_ctl_elem_value uval;
int numid;
int i, err;
char *token;
token = gettoken(&line);
if (!token) {
usage("get");
return;
}
if (isdigit(*token))
numid = strtoul(token, NULL, 0);
else {
numid = get_ctl_numid(token);
if (!numid) {
hda_log(HDA_LOG_INFO, "No element '%s'\n", token);
return;
}
}
kctl = snd_ctl_find_numid(NULL, numid);
if (!kctl) {
hda_log(HDA_LOG_INFO, "No element %d\n", numid);
return;
}
memset(&uinfo, 0, sizeof(uinfo));
uinfo.id = kctl->id;
uinfo.id.numid = numid;
uinfo.id.index = snd_ctl_get_ioffnum(kctl, &uinfo.id);
err = kctl->info(kctl, &uinfo);
if (err < 0) {
hda_log(HDA_LOG_INFO, "Error in info for %d\n", numid);
return;
}
memset(&uval, 0, sizeof(uval));
uval.id = uinfo.id;
err = kctl->get(kctl, &uval);
if (err < 0) {
hda_log(HDA_LOG_INFO, "Error in get for %d\n", numid);
return;
}
hda_log(HDA_LOG_INFO, "%d %s:%d\n",
kctl->id.numid, kctl->id.name, kctl->id.index);
switch (uinfo.type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER:
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
hda_log(HDA_LOG_INFO, "MIN/MAX: %ld/%ld, ",
uinfo.value.integer.min, uinfo.value.integer.max);
hda_log(HDA_LOG_INFO, " VAL:");
for (i = 0; i < uinfo.count; i++)
hda_log(HDA_LOG_INFO, " [%ld]",
uval.value.integer.value[i]);
if (uinfo.type == SNDRV_CTL_ELEM_TYPE_INTEGER &&
(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ))
show_db_info(kctl, &uinfo, &uval);
hda_log(HDA_LOG_INFO, "\n");
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
hda_log(HDA_LOG_INFO, "ITEM:");
for (i = 0; i < uinfo.value.enumerated.items; i++)
hda_log(HDA_LOG_INFO, " %d:%s,",
i, get_enum_name(kctl, i));
hda_log(HDA_LOG_INFO, " VAL:");
for (i = 0; i < uinfo.count; i++)
hda_log(HDA_LOG_INFO, " [%s]",
get_enum_name(kctl, uval.value.enumerated.item[i]));
hda_log(HDA_LOG_INFO, "\n");
break;
case SNDRV_CTL_ELEM_TYPE_IEC958:
hda_log(HDA_LOG_INFO, "Can't handle IEC958 yet\n");
break;
default:
hda_log(HDA_LOG_INFO, "Unsupported type %d\n", uinfo.type);
break;
}
}
#ifdef SNDRV_CTL_ELEM_ACCESS_SPK_LED
extern void __led_trigger(unsigned int flags, long *vals);
#endif
static void set_element(char *line)
{
struct snd_kcontrol *kctl;
struct snd_ctl_elem_info uinfo;
struct snd_ctl_elem_value uval;
unsigned int numid;
int i, err;
char *token;
token = gettoken(&line);
if (!token) {
usage("set");
return;
}
if (isdigit(*token))
numid = strtoul(token, NULL, 0);
else {
numid = get_ctl_numid(token);
if (!numid) {
hda_log(HDA_LOG_INFO, "No element '%s'\n", token);
return;
}
}
kctl = snd_ctl_find_numid(NULL, numid);
if (!kctl) {
hda_log(HDA_LOG_INFO, "No element %d\n", numid);
return;
}
if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE)) {
hda_log(HDA_LOG_INFO, "Have no write access\n");
return;
}
memset(&uinfo, 0, sizeof(uinfo));
uinfo.id = kctl->id;
uinfo.id.numid = numid;
uinfo.id.index = snd_ctl_get_ioffnum(kctl, &uinfo.id);
err = kctl->info(kctl, &uinfo);
if (err < 0) {
hda_log(HDA_LOG_INFO, "Error in info for %d\n", numid);
return;
}
switch (uinfo.type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER:
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
break;
default:
hda_log(HDA_LOG_INFO, "Can't set type %d\n", uinfo.type);
return;
}
memset(&uval, 0, sizeof(uval));
uval.id = uinfo.id;
for (i = 0; i < uinfo.count; i++) {
int val;
if (getint(&line, &val)) {
hda_log(HDA_LOG_INFO, "No value #%d is given\n", i);
return;
}
switch (uinfo.type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER:
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
uval.value.integer.value[i] = val;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
uval.value.enumerated.item[i] = val;
break;
}
}
err = kctl->put(kctl, &uval);
if (err < 0) {
hda_log(HDA_LOG_INFO, "Error in get for %d\n", numid);
return;
}
#ifdef SNDRV_CTL_ELEM_ACCESS_SPK_LED
if (kctl->vd[0].access & (SNDRV_CTL_ELEM_ACCESS_SPK_LED | SNDRV_CTL_ELEM_ACCESS_MIC_LED))
__led_trigger(kctl->vd[0].access & (SNDRV_CTL_ELEM_ACCESS_SPK_LED | SNDRV_CTL_ELEM_ACCESS_MIC_LED),
uval.value.integer.value);
#endif
}
static void dump_proc(char *line)
{
unsigned int nid = 0;
char *file = NULL;
if (!getint(&line, &nid))
file = gettoken(&line);
hda_log_dump_proc(nid, file);
}
static void handle_module_option(char *line)
{
char *opt;
opt = gettoken(&line);
if (!opt)
goto usage;
if (!strcmp(opt, "power_save")) {
char *p = gettoken(&line);
if (!p)
hda_log(HDA_LOG_INFO, "Power-save = %d\n",
hda_get_power_save());
else
hda_set_power_save(atoi(p));
return;
}
if (!strcmp(opt, "dump_coef")) {
char *p = gettoken(&line);
if (!p)
hda_log(HDA_LOG_INFO, "Dump-COEF = %d\n",
hda_get_dump_coef());
else
hda_set_dump_coef(atoi(p));
return;
}
usage:
hda_log(HDA_LOG_INFO, "Available options: power_save, dump_coef\n");
}
static void set_jack(char *line)
{
int nid, val;
char *token;
token = gettoken(&line);
if (!token) {
hda_log_list_jacks(0);
return;
}
if (*token == '-') {
/* option */
switch (token[1]) {
case 'r':
hda_log_list_jacks(1);
return;
default:
usage("jack");
return;
}
}
nid = strtoul(token, NULL, 0);
if (getbool(&line, &val)) {
hda_log_jack_state(nid);
return;
}
hda_log_set_jack(nid, val);
}
static void issue_unsol(char *line)
{
char *p;
int nid;
p = gettoken(&line);
if (!p) {
usage("unsol");
return;
}
nid = strtoul(p, NULL, 0);
hda_log_issue_unsol(nid);
}
static void show_routes(char *line)
{
int numid;
unsigned int flags = SHOW_DIR_IN | SHOW_DIR_OUT;
char *token;
for (;;) {
token = gettoken(&line);
if (!token) {
usage("route");
return;
}
if (*token == '-') {
/* option */
switch (token[1]) {
case 'a':
flags |= SHOW_ALL | SHOW_INACTIVE;
break;
case 'x':
flags |= SHOW_INACTIVE;
break;
case 'i':
flags &= ~SHOW_DIR_OUT;
flags |= SHOW_DIR_IN;
break;
case 'o':
flags &= ~SHOW_DIR_IN;
flags |= SHOW_DIR_OUT;
break;
case 'm':
flags |= SHOW_MUTE;
break;
default:
hda_log(HDA_LOG_ERR, "Invalid route option\n");
usage("route");
return;
}
} else
break;
}
numid = strtoul(token, NULL, 0);
hda_show_routes(numid, flags);
}
static void run_verb(char *line)
{
char *parm[3];
unsigned int verb, val;
int i;
for (i = 0; i < 3; i++) {
parm[i] = gettoken(&line);
if (!parm[i]) {
usage("verb");
return;
}
}
if (hda_encode_verb_parm(parm[1], parm[2], &verb, &val) < 0) {
hda_log(HDA_LOG_ERR, "Invalid verb/parameter %s/%s\n",
parm[1], parm[2]);
return;
}
hda_exec_verb(strtoul(parm[0], NULL, 0), verb, val);
}
static int get_pcm_substream(char *id)
{
id = strchr(id, ':');
if (!id)
return 0;
return strtoul(id + 1, NULL, 0);
}
enum {
TEST_PM_NORMAL,
TEST_PM_RANDOMIZE,
TEST_PM_REINIT,
};
static void test_pm(char *line)
{
char *token;
int mode = TEST_PM_NORMAL;
while ((token = gettoken(&line))) {
if (*token == '-') {
switch (token[1]) {
case 'r':
mode = TEST_PM_RANDOMIZE;
break;
case 'i':
mode = TEST_PM_REINIT;
break;
default:
usage("pm");
return;
}
} else {
usage("pm");
return;
}
}
hda_log(HDA_LOG_INFO, "** SUSPENDING **\n");
hda_test_suspend();
switch (mode) {
case TEST_PM_RANDOMIZE:
hda_log(HDA_LOG_INFO, "** randomize **\n");
hda_test_pm_randomize();
break;
case TEST_PM_REINIT:
hda_log(HDA_LOG_INFO, "** init **\n");
hda_test_pm_reinit();
break;
}
hda_log(HDA_LOG_INFO, "** RESUMING **\n");
hda_test_resume();
hda_log(HDA_LOG_INFO, "** TEST_PM DONE **\n");
}
static void test_pcm(char *line)
{
char *id;
int stream, dir, rate = 48000, channels = 2, format = 16;
int substream = 0;
int op = PCM_TEST_ALL;
char *stream_token;
char *token;
token = gettoken(&line);
if (!token) {
hda_list_pcms();
return;
}
if (*token == '-') {
/* option */
switch (token[1]) {
case 's':
op = PCM_TEST_START;
break;
case 'e':
op = PCM_TEST_END;
break;
default:
hda_log(HDA_LOG_ERR, "Invalid PCM option\n");
usage("PCM");
return;
}
token = gettoken(&line);
}
stream_token = token;
if (!stream_token) {
usage("PCM");
return;
}
id = gettoken(&line);
if (!id) {
hda_log(HDA_LOG_ERR, "No stream direction is given\n");
return;
}
switch (*id) {
case 'p':
case 'P':
dir = 0;
break;
case 'c':
case 'C':
dir = 1;
break;
default:
dir = strtoul(id, NULL, 0);
if (dir != 0 || dir != 1) {
hda_log(HDA_LOG_ERR, "Invalid direction %s\n", id);
return;
}
break;
}
stream = strtoul(stream_token, NULL, 0);
substream = get_pcm_substream(stream_token);
if (!getint(&line, &rate) &&
!getint(&line, &channels))
getint(&line, &format);
hda_test_pcm(stream, op, substream, dir, rate, channels, format);
}
static void help(char *buf)
{
usage(gettoken(&buf));
}
#ifdef CONFIG_SND_HDA_RECONFIG
#ifdef HAVE_USER_PINCFGS
static void get_drv_pincfgs(char *line)
{
hda_log_show_driver_pin_configs();
}
static void get_init_pincfgs(char *line)
{
hda_log_show_init_pin_configs();
}
static void get_user_pincfgs(char *line)
{
hda_log_show_user_pin_configs();
}
static void set_user_pincfgs(char *line)
{
unsigned int nid, val;
if (getint(&line, &nid) || getint(&line, &val))
goto error;
hda_log_set_user_pin_configs(nid, val);
return;
error:
hda_log(HDA_LOG_ERR, "Specify NID and PINCFG values\n");
return;
}
#endif /* HAVE_USER_PINCFGS */
static void get_hints(char *line)
{
hda_log_show_hints(*line ? line : NULL);
}
static void set_hints(char *line)
{
hda_log_set_hints(line);
}
static void get_not_yet(char *line)
{
hda_log(HDA_LOG_ERR, "Not implemented yet\n");
}
static void set_not_yet(char *line)
{
hda_log(HDA_LOG_ERR, "Not implemented yet\n");
}
static void clear_codec(char *line)
{
hda_codec_reset();
/* clear internal list */
}
static void reconfig_codec(char *line)
{
hda_codec_reconfig();
}
#define get_vendor_id get_not_yet
#define set_vendor_id set_not_yet
#define get_subsystem_id get_not_yet
#define set_subsystem_id set_not_yet
#define get_revision_id get_not_yet
#define set_revision_id set_not_yet
struct sysfs_entry {
const char *name;
void (*get)(char *line);
void (*set)(char *line);
};
static struct sysfs_entry sysfs_entries[] = {
#ifdef HAVE_USER_PINCFGS
{ "driver_pin_configs", get_drv_pincfgs, NULL },
{ "init_pin_configs", get_init_pincfgs, NULL },
{ "user_pin_configs", get_user_pincfgs, set_user_pincfgs },
#endif
{ "hints", get_hints, set_hints },
{ "vendor_id", get_vendor_id, set_vendor_id },
{ "subsystem_id", get_subsystem_id, set_subsystem_id },
{ "revision_id", get_revision_id, set_revision_id },
{ "clear", NULL, clear_codec },
{ "reconfig", NULL, reconfig_codec },
{ },
};
static struct sysfs_entry *find_sysfs_entry(char *file)
{
struct sysfs_entry *s;
struct sysfs_entry *matched = NULL;
int num_matches = 0;
for (s = sysfs_entries; s->name; s++) {
if (!strcmp(s->name, file))
return s;
if (!strncmp(s->name, file, strlen(file))) {
matched = s;
num_matches++;
}
}
if (num_matches == 1)
return matched;
hda_log(HDA_LOG_INFO, "No such a file: %s\n", file);
return NULL;
}
static void get_sysfs(char *file, char *line)
{
struct sysfs_entry *s;
s = find_sysfs_entry(file);
if (!s)
return;
if (!s->get) {
hda_log(HDA_LOG_INFO, "%s has no read premission\n", file);
return;
}
s->get(line);
}
static void set_sysfs(char *file, char *line)
{
struct sysfs_entry *s;
s = find_sysfs_entry(file);
if (!s)
return;
if (!s->set) {
hda_log(HDA_LOG_INFO, "%s has no write permission\n", file);
return;
}
s->set(line);
}
static void list_sysfs(void)
{
struct sysfs_entry *s;
hda_log(HDA_LOG_INFO, "Available sysfs entries:\n");
for (s = sysfs_entries; s->name; s++)
hda_log(HDA_LOG_INFO, " %s (%s%s)\n",
s->name, (s->get ? "R" : ""), (s->set ? "W" : ""));
}
static void handle_sysfs(char *line)
{
char *cmd;
cmd = gettoken(&line);
if (!cmd)
goto error;
if (*cmd == 'l')
list_sysfs();
else if (*cmd == 'g' || *cmd == 's') {
char *file;
file = gettoken(&line);
if (!file)
goto error;
if (*cmd == 'g')
get_sysfs(file, line);
else
set_sysfs(file, line);
}
return;
error:
usage("fs");
return;
}
#endif
/*
*/
struct usage_table {
const char *cmd;
const char *line;
const char *desc;
void (*handler)(char *);
};
static struct usage_table usage_str[] = {
{ "list", "list",
"Show all control elements",
show_elements },
{ "get", "get numid",
"Get the contents of the given control element",
get_element },
{ "set", "set numid val [val2]",
"Set the contents of the given control element",
set_element },
{ "dump", "dump [nid [filename]]",
"Dump codec contents in the proc file format\n"
"nid = 0 means all widgets",
dump_proc },
{ "jack", "jack [-r] numid [val]",
"Get jack state or set jack state\n"
"val = 0 or 1; -r = list from uncached pincfgs",
set_jack },
{ "unsol", "unsol numid",
"Issue an unsolicited event",
issue_unsol },
{ "route", "route [-opts] numid",
"Show routes via the given widget\n"
"-a = show all, -x = show inactive pins too, -i|-o = set direction,\n"
"-m = show mute flags",
show_routes },
{ "option", "option variable [val]",
"Get/set module option value",
handle_module_option },
{ "help", "help [command]",
"Show help texts",
help },
{ "verb", "verb nid cmd parameter",
"Execute a verb",
run_verb },
{ "PCM", "PCM [-s|-e] [pcm-id dir [rate [channels [format-bits]]]]",
"List PCM streams or test the given PCM stream",
test_pcm },
{ "pm", "pm [-i|-r]",
"Test suspend/resume cycle\n"
"-i = re-init pins/amps, -r = randomize pins/amps",
test_pm },
#ifdef CONFIG_SND_HDA_RECONFIG
{ "fs", "fs {get|set|list} file args...",
"Read or write sysfs files",
handle_sysfs },
#endif
{ "quit", "quit",
"Quit the program",
NULL },
{ }
};
static void usage(char *cmd)
{
struct usage_table *tbl;
for (tbl = usage_str; tbl->cmd; tbl++) {
if (cmd && !strcmp(cmd, tbl->cmd)) {
hda_log(HDA_LOG_INFO, "Usage: %s\n", tbl->line);
hda_log(HDA_LOG_INFO, "%s\n", tbl->desc);
return;
}
}
hda_log(HDA_LOG_INFO, "Available commands:");
for (tbl = usage_str; tbl->cmd; tbl++)
hda_log(HDA_LOG_INFO, " %s", tbl->cmd);
hda_log(HDA_LOG_INFO, "\n");
hda_log(HDA_LOG_INFO, "Run \"help CMD\" for details\n");
}
#ifdef HAVE_LIBREADLINE
/* Line completion functions */
static char *command_generator(const char *text, int state)
{
static int index, len;
const char *name;
if (!state) {
index = 0;
len = strlen(text);
}
/* Return the next name which partially matches from command list. */
while ((name = usage_str[index].cmd) != NULL) {
index++;
if (strncmp(name, text, len) == 0)
return strdup(name);
}
return NULL;
}
static char **ctlsh_completion(const char *text, int start, int end)
{
char **matches = NULL;
if (start == 0)
matches = rl_completion_matches(text, command_generator);
return matches;
}
static void init_completion(void)
{
rl_attempted_completion_function = ctlsh_completion;
}
#else
static FILE *rl_instream;
static FILE *rl_outstream;
static char *readline(const char *prompt)
{
char line[256], *buf;
fprintf(stderr, "%s", prompt);
buf = fgets(line, sizeof(line), rl_instream);
if (!buf)
return NULL;
return strdup(buf);
}
#define add_history(l)
#define init_completion()
#endif /* HAVE_LIBREADLINE */
static struct usage_table *cmd_match(char *p)
{
struct usage_table *tbl, *match = NULL;
int len, num_matches = 0;
len = strlen(p);
for (tbl = usage_str; tbl->cmd; tbl++) {
if (strncmp(p, tbl->cmd, len) == 0) {
num_matches++;
match = tbl;
}
}
if (num_matches == 1)
return match;
return NULL;
}
int cmd_loop(FILE *fp)
{
char *line;
char *buf, *p;
struct usage_table *tbl;
if (fp)
rl_instream = fp;
rl_outstream = stderr;
init_completion();
for (;;) {
hda_log_flush();
line = readline("> ");
if (!line)
break;
if (line && *line)
add_history(line);
buf = line;
hda_log_echo(HDA_LOG_INFO, "> %s", buf);
p = gettoken(&buf);
if (!p) {
usage(NULL);
goto next;
}
tbl = cmd_match(p);
if (tbl == NULL)
usage(NULL);
else if (!strcmp(tbl->cmd, "quit"))
return 0;
else
tbl->handler(buf);
next:
flush_scheduled_work();
free(line);
}
return 0;
}