NetBSD/usr.sbin/eshconfig/eshconfig.c

767 lines
20 KiB
C
Raw Normal View History

/* $NetBSD: eshconfig.c,v 1.3 2000/04/14 06:26:53 simonb Exp $ */
/*
* Copyright (c) 1998 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code contributed to The NetBSD Foundation by Kevin M. Lahey
* of the Numerical Aerospace Simulation Facility, NASA Ames Research
* Center.
*
* 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.
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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 <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: eshconfig.c,v 1.3 2000/04/14 06:26:53 simonb Exp $");
#endif /* not lint */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dev/ic/rrunnerreg.h>
#include <dev/ic/rrunnervar.h>
/*
* Create a simple pair of tables to map possible burst DMA values
* to the values required by the RoadRunner.
*/
struct map_dma {
int value;
u_int32_t rr_value;
};
struct map_dma read_dma_map[] = {{0, RR_PS_READ_DISABLE},
{4, RR_PS_READ_4},
{16, RR_PS_READ_16},
{32, RR_PS_READ_32},
{64, RR_PS_READ_64},
{128, RR_PS_READ_128},
{256, RR_PS_READ_256},
{1024, RR_PS_READ_1024},
{-1, 0}};
struct map_dma write_dma_map[] = {{0, RR_PS_WRITE_DISABLE},
{4, RR_PS_WRITE_4},
{16, RR_PS_WRITE_16},
{32, RR_PS_WRITE_32},
{64, RR_PS_WRITE_64},
{128, RR_PS_WRITE_128},
{256, RR_PS_WRITE_256},
{1024, RR_PS_WRITE_1024},
{-1, 0}};
/*
* The RunCode is composed of separate segments, each of which has a
* starting address in SRAM memory (for running) and in EEPROM
* (for storage).
*/
struct rr_seg_descr {
u_int32_t start_addr;
u_int32_t length;
u_int32_t ee_addr;
};
static u_int32_t do_map __P((int, struct map_dma *));
static void eeprom_upload __P((const char *));
static void eeprom_download __P((const char *));
static u_int32_t rr_checksum __P((const u_int32_t *, int));
static void esh_tune __P((void));
static void esh_tune_eeprom __P((void));
static void esh_tuning_stats __P((void));
static void esh_stats __P((int));
static void esh_reset __P((void));
static int drvspec_ioctl __P((char *, int, int, int, caddr_t));
static void usage __P((void));
int main __P((int, char *[]));
struct ifreq ifr;
char name[30] = "esh0";
int s;
#define RR_EE_SIZE 8192
u_int32_t eeprom[RR_EE_SIZE];
u_int32_t runcode[RR_EE_SIZE];
struct ifdrv ifd;
/* drvspec_ioctl
*
* We defined a driver-specific socket ioctl to allow us to tweak
* the characteristics of network devices. This routine will
* provide a shortcut to calling this routine, which would otherwise
* require lots of costly and annoying setup.
*/
static int
drvspec_ioctl(char *name, int fd, int cmd, int len, caddr_t data)
{
strcpy(ifd.ifd_name, name);
ifd.ifd_cmd = cmd;
ifd.ifd_len = len;
ifd.ifd_data = data;
return ioctl(fd, SIOCSDRVSPEC, (caddr_t) &ifd);
}
static void
usage()
{
fprintf(stderr, "eshconfig -- configure Essential Communications "
"HIPPI driver\n");
fprintf(stderr, "-b burst size for read\n");
fprintf(stderr, "-c burst size for write:\n");
fprintf(stderr, "\t0 (no limit), 5, 16, 32, 64, 128, 256, 1024\n");
fprintf(stderr, "-d download filename\n");
fprintf(stderr, "-e write data to EEPROM\n");
fprintf(stderr, "-m minimum bytes DMA per direction\n");
fprintf(stderr, "-r bytes before DMA starts for read\n");
fprintf(stderr, "-s show statistics (-ss to display only non-zero)\n");
fprintf(stderr, "-t show tuning parameters\n");
fprintf(stderr, "-u upload filename [not working]\n");
fprintf(stderr, "-w bytes before DMA starts for write\n");
fprintf(stderr, "-i interrupt delay in usecs\n");
fprintf(stderr, "-x reset interface\n");
exit(1);
}
/* do_map
*
* Map between values for burst DMA sizes and the values expected by
* the RoadRunner chip.
*/
static u_int32_t
do_map(int value, struct map_dma *map)
{
int i;
for (i = 0; map[i].value != -1; i++)
if (value == map[i].value)
return map[i].rr_value;
return -1;
}
/* do_map_dma
*
* Reverse the mapping.
*/
static int
do_map_dma(int value, struct map_dma *map)
{
int i;
for (i = 0; map[i].value != -1; i++)
if (value == map[i].rr_value)
return map[i].value;
return 0;
}
int dma_thresh_read = -1;
int dma_thresh_write = -1;
int dma_min_grab = -1;
int dma_max_read = -1;
int dma_max_write = -1;
int interrupt_delay = -1;
int get_stats = 0;
int get_tuning_stats = 0;
int eeprom_write = 0;
char *eeprom_download_filename = NULL;
char *eeprom_upload_filename = NULL;
int reset = 0;
struct rr_tuning rr_tune;
struct rr_eeprom rr_eeprom;
struct rr_stats rr_stats;
int
main(argc, argv)
int argc;
char *argv[];
{
int ch;
/* Parse command-line options */
while ((ch = getopt(argc, argv, "b:c:d:ei:m:r:stu:w:x")) != -1) {
switch (ch) {
case 'b':
dma_max_read = atoi(optarg);
break;
case 'c':
dma_max_write = atoi(optarg);
break;
case 'd':
eeprom_download_filename = optarg;
break;
case 'e':
eeprom_write++;
break;
case 'i':
interrupt_delay = atoi(optarg);
break;
case 'm':
dma_min_grab = atoi(optarg);
break;
case 'r':
dma_thresh_read = atoi(optarg);
break;
case 's':
get_stats++;
break;
case 't':
get_tuning_stats++;
break;
case 'u':
eeprom_upload_filename = optarg;
break;
case 'w':
dma_thresh_write = atoi(optarg);
break;
case 'x':
reset = 1;
break;
default:
usage();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
if (argc > 1)
usage();
if (argc == 1) {
(void) strncpy(name, argv[0], sizeof(name));
argc--; argv++;
}
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
err(1, "socket");
if (eeprom_upload_filename)
eeprom_upload(eeprom_upload_filename);
if (eeprom_download_filename)
eeprom_download(eeprom_download_filename);
if (get_stats) {
esh_stats(get_stats);
}
if (drvspec_ioctl(name, s, EIOCGTUNE, sizeof(struct rr_tuning),
(caddr_t) &rr_tune) < 0) {
err(1, "ioctl(EIOCGTUNE)");
}
if (get_tuning_stats) {
if (get_stats)
printf("\n");
esh_tuning_stats();
}
if (eeprom_write || dma_thresh_read != -1 ||
dma_thresh_write != -1 ||
dma_min_grab != -1 ||
dma_max_read != -1 ||
dma_max_write != -1 ||
interrupt_delay != -1) {
esh_tune();
}
if (eeprom_write)
esh_tune_eeprom();
if (reset)
esh_reset();
exit(0);
}
static void
esh_tune()
{
dma_max_read = do_map(dma_max_read, read_dma_map);
if (dma_max_read != -1) {
rr_tune.rt_pci_state &= ~RR_PS_READ_MASK;
rr_tune.rt_pci_state |= dma_max_read;
}
dma_max_write = do_map(dma_max_write, write_dma_map);
if (dma_max_write != -1) {
rr_tune.rt_pci_state &= ~RR_PS_WRITE_MASK;
rr_tune.rt_pci_state |= dma_max_write;
}
if (dma_min_grab != -1) {
if ((dma_min_grab & (RR_PS_MIN_DMA_MASK >> RR_PS_MIN_DMA_SHIFT))
!= dma_min_grab)
usage();
rr_tune.rt_pci_state &= ~RR_PS_MIN_DMA_MASK;
rr_tune.rt_pci_state |=
(dma_min_grab << RR_PS_MIN_DMA_SHIFT);
}
if (dma_thresh_write != -1) {
if (dma_thresh_write < 1 || dma_thresh_write > RR_DW_THRESHOLD_MAX)
usage();
rr_tune.rt_dma_write_state &= ~RR_DW_THRESHOLD_MASK;
rr_tune.rt_dma_write_state |=
dma_thresh_write << RR_DW_THRESHOLD_SHIFT;
}
if (dma_thresh_read != -1) {
if (dma_thresh_read < 1 || dma_thresh_read > RR_DR_THRESHOLD_MAX)
usage();
rr_tune.rt_dma_read_state &= ~RR_DR_THRESHOLD_MASK;
rr_tune.rt_dma_read_state |=
dma_thresh_read << RR_DR_THRESHOLD_SHIFT;
}
rr_tune.rt_stats_timer = ESH_STATS_TIMER_DEFAULT;
if (interrupt_delay != -1)
rr_tune.rt_interrupt_timer = interrupt_delay;
if (drvspec_ioctl(name, s, EIOCSTUNE, sizeof(struct rr_tuning),
(caddr_t) &rr_tune) < 0)
err(1, "EIOCSTUNE");
}
/* esh_tune_eeprom
*
* Store the current tuning data into the eeprom.
*/
static void
esh_tune_eeprom()
{
#define LAST (RR_EE_HEADER_CHECKSUM / RR_EE_WORD_LEN)
#define FIRST (RR_EE_HEADER_CHECKSUM / RR_EE_WORD_LEN)
u_int32_t tuning_data[LAST + 1];
rr_eeprom.ifr_buffer = tuning_data;
rr_eeprom.ifr_length = sizeof(tuning_data);
rr_eeprom.ifr_offset = 0;
if (drvspec_ioctl(name, s, EIOCGEEPROM, sizeof(struct rr_eeprom),
(caddr_t) &rr_eeprom) == -1)
err(6, "ioctl to retrieve tuning information from EEPROM");
tuning_data[RR_EE_PCI_STATE / RR_EE_WORD_LEN] =
rr_tune.rt_pci_state;
tuning_data[RR_EE_DMA_WRITE_STATE / RR_EE_WORD_LEN] =
rr_tune.rt_dma_write_state;
tuning_data[RR_EE_DMA_READ_STATE / RR_EE_WORD_LEN] =
rr_tune.rt_dma_read_state;
tuning_data[RR_EE_INTERRUPT_TIMER / RR_EE_WORD_LEN] = rr_tune.rt_interrupt_timer;
tuning_data[RR_EE_STATS_TIMER / RR_EE_WORD_LEN] =
ESH_STATS_TIMER_DEFAULT;
tuning_data[RR_EE_HEADER_CHECKSUM / RR_EE_WORD_LEN] =
rr_checksum(&tuning_data[FIRST], LAST - FIRST);
rr_eeprom.ifr_buffer = tuning_data;
rr_eeprom.ifr_length = sizeof(tuning_data);
rr_eeprom.ifr_offset = 0;
if (drvspec_ioctl(name, s, EIOCSEEPROM, sizeof(struct rr_eeprom),
(caddr_t) &rr_eeprom) == -1)
err(7, "ioctl to set tuning information from EEPROM");
}
/* eeprom_upload
*
* Upload the EEPROM from the card and store in the data file.
*/
static void
eeprom_upload(const char *filename)
{
int fd;
bzero(eeprom, sizeof(eeprom));
if ((fd = open(filename, O_WRONLY | O_CREAT, 0644)) < 0)
err(4, "Couldn't open %s for output", filename);
rr_eeprom.ifr_buffer = eeprom;
rr_eeprom.ifr_length = sizeof(eeprom);
rr_eeprom.ifr_offset = 0;
if (drvspec_ioctl(name, s, EIOCGEEPROM, sizeof(struct rr_eeprom),
(caddr_t) &rr_eeprom) == -1)
err(5, "ioctl to retrieve all of EEPROM");
write(fd, eeprom, sizeof(eeprom));
close(fd);
}
/* eeprom_download
*
* Download into eeprom the contents of a file. The file is made up
* of ASCII text; the first three characters can be ignored, the next
* four hex characters define an address, the next two characters can
* be ignored, and the final eight hex characters are the data.
*/
static void
eeprom_download(const char *filename)
{
FILE *fp;
struct rr_seg_descr *segd = NULL;
char id[BUFSIZ];
char pad[BUFSIZ];
char buffer[BUFSIZ];
u_int32_t address = 0;
u_int32_t last_address = 0;
u_int32_t value;
u_int32_t length = 0;
int segment_start = 0;
int seg_table_start;
int seg_count_offset;
int phase2_start;
int phase2_checksum;
int in_segment = 0;
int segment = 0;
int eof = 0;
int line = 0;
int zero_count = 0;
int i;
/* Clear out eeprom storage space, then read in the value on the card */
bzero(eeprom, sizeof(eeprom));
bzero(runcode, sizeof(runcode));
rr_eeprom.ifr_buffer = eeprom;
rr_eeprom.ifr_length = sizeof(eeprom);
rr_eeprom.ifr_offset = 0;
if (drvspec_ioctl(name, s, EIOCGEEPROM, sizeof(struct rr_eeprom),
(caddr_t) &rr_eeprom) == -1)
err(5, "ioctl to retrieve EEPROM");
/*
* Open the input file and proceed to read the data file, storing
* the data and counting the number of segments.
*/
if ((fp = fopen(filename, "r")) == NULL)
err(2, "fopen");
do {
if (fgets(buffer, sizeof(buffer), fp) == NULL)
errx(3, "premature, unmarked end of file, line %d", line);
line++;
if (!strncmp(buffer + 7, "01", 2)) { /* check for EOF marker... */
eof = 1;
} else {
sscanf(buffer, "%3s%4x%2s%8x%2s",
id, &address, pad, &value, pad);
if (strcmp(id, ":04") != 0)
errx(3, "bad initial id on line %d", line);
}
/*
* Check to see if we terminated a segment; this happens
* when we are at end of file, or we hit a non-sequential
* address value, or we see three or more zeroes in a row.
*/
if ((length == RR_EE_SEG_SIZE || eof || zero_count >= 3 ||
(last_address && last_address != address - 1)) && in_segment) {
length -= zero_count;
segment_start += length;
segd[segment].length = length;
printf("segment %d, %d words\n", segment, length);
last_address = in_segment = zero_count = length = 0;
segment++;
}
if (eof)
break;
/* Skip zero values starting a segment */
if (!in_segment && value == 0)
continue;
last_address = address;
/*
* If we haven't started a segment yet, do so now.
* Store away the address at which this code should be placed
* in memory and the address of the code in the EEPROM.
*/
if (!in_segment) {
in_segment = 1;
segd = realloc(segd, sizeof(struct rr_seg_descr) * (segment + 1));
if (segd == NULL)
err(6, "couldn't realloc segment descriptor space");
segd[segment].start_addr = address * sizeof(u_int32_t);
segd[segment].ee_addr = segment_start;
}
/* Keep track of consecutive zeroes */
if (in_segment && value == 0)
zero_count++;
else
zero_count = 0;
/* Store away the actual data */
runcode[segment_start + length++] = value;
} while (!eof);
fclose(fp);
/* Now that we have a segment count, fill in the EEPROM image. */
seg_count_offset = eeprom[RR_EE_RUNCODE_SEGMENTS / RR_EE_WORD_LEN];
seg_count_offset = (seg_count_offset - RR_EE_OFFSET) / RR_EE_WORD_LEN;
seg_table_start = seg_count_offset + 1;
phase2_checksum = seg_table_start + 3 * segment;
phase2_start = eeprom[RR_EE_PHASE2_EE_START / RR_EE_WORD_LEN];
phase2_start = (phase2_start - RR_EE_OFFSET) / RR_EE_WORD_LEN;
printf("segment table start = %x, segments = %d\n",
seg_table_start, eeprom[seg_count_offset]);
/* We'll fill in anything after the segment count, so clear it */
bzero(eeprom + seg_count_offset,
sizeof(eeprom) - seg_count_offset * sizeof(eeprom[0]));
eeprom[seg_count_offset] = segment;
for (i = 0; i < segment; i++)
segd[i].ee_addr = RR_EE_OFFSET +
(segd[i].ee_addr + phase2_checksum + 1) * RR_EE_WORD_LEN;
bcopy(segd, &eeprom[seg_table_start],
sizeof(struct rr_seg_descr) * segment);
bcopy(runcode, &eeprom[phase2_checksum + 1],
segment_start * sizeof(u_int32_t));
eeprom[phase2_checksum] = rr_checksum(&eeprom[phase2_start],
phase2_checksum - phase2_start);
eeprom[segment_start + phase2_checksum + 1] =
rr_checksum(&eeprom[phase2_checksum + 1], segment_start);
printf("phase2 checksum %x, runcode checksum %x\n",
eeprom[phase2_checksum],
eeprom[segment_start + phase2_checksum + 1]);
rr_eeprom.ifr_buffer = eeprom;
rr_eeprom.ifr_length = sizeof(eeprom);
rr_eeprom.ifr_offset = 0;
if (drvspec_ioctl(name, s, EIOCSEEPROM, sizeof(struct rr_eeprom),
(caddr_t) &rr_eeprom) == -1)
err(5, "ioctl to retrieve EEPROM");
}
/* rr_checksum
*
* Perform checksum on RunCode. Length is in words. Ugh.
*/
static u_int32_t
rr_checksum(const u_int32_t *data, int length)
{
u_int32_t checksum = 0;
while (length--)
checksum += *data++;
checksum = 0 - checksum;
return checksum;
}
struct stats_values {
int offset;
char *name;
};
struct stats_values stats_values[] = {
{0x04, "receive rings created"},
{0x08, "receive rings deleted"},
{0x0c, "interrupts"},
{0x10, "event overflows"},
{0x14, "invalid commands"},
{0x18, "DMA read errors"},
{0x1c, "DMA write errors"},
{0x20, "stats updates per timer"},
{0x24, "stats updates per host"},
{0x28, "watchdog"},
{0x2c, "trace"},
{0x30, "link ready sync established"},
{0x34, "GLink errors"},
{0x38, "alternating flag errors"},
{0x3c, "overhead bit 8 synchronized"},
{0x40, "remote serial parity errors"},
{0x44, "remote parallel parity errors"},
{0x48, "remote loopback requested"},
{0x50, "transmit connections established"},
{0x54, "transmit connections rejected"},
{0x58, "transmit connections retried"},
{0x5c, "transmit connections timed out"},
{0x60, "transmit connections disconnected"},
{0x64, "transmit parity errors"},
{0x68, "packets sent"},
{0x74, "short first burst sent"},
{0x80, "transmit data not moving"},
{0x90, "receive connections accepted"},
{0x94, "receive connections rejected -- bad parity"},
{0x98, "receive connections rejected -- 64-bit width"},
{0x9c, "receive connections rejected -- buffers low"},
{0xa0, "receive connections disconnected"},
{0xa4, "receive connections with no data"},
{0xa8, "packets received"},
{0xb4, "short first burst received"},
{0xc0, "receive parity error"},
{0xc4, "receive LLRC error"},
{0xc8, "receive burst size error"},
{0xcc, "receive state error"},
{0xd0, "receive ready ULP"},
{0xd4, "receive invalid ULP"},
{0xd8, "receive packets flow control due to buffer space"},
{0xdc, "receive packets flow control due to descriptors"},
{0xe0, "receive ring fulls"},
{0xe4, "packet length errors"},
{0xe8, "packets with checksum error"},
{0xec, "packets dropped"},
{0xf0, "ring low on space"},
{0xf4, "data in ring at close"},
{0xf8, "receives to ring not moving data"},
{0xfc, "receiver idles"},
{0, 0},
};
static void
esh_reset()
{
if (drvspec_ioctl(name, s, EIOCRESET, 0, 0) < 0)
err(1, "ioctl(EIOCRESET)");
}
static void
esh_stats(int get_stats)
{
u_int32_t *stats;
long long value;
int offset;
if (drvspec_ioctl(name, s, EIOCGSTATS, sizeof(struct rr_stats),
(caddr_t) &rr_stats) < 0)
err(1, "ioctl(EIOCGTUNE)");
stats = rr_stats.rs_stats;
value = (((long long) stats[0x78 / 4]) << 32) | stats[0x7c / 4];
if (get_stats == 1 || value > 0)
printf("%12qd bytes sent\n", value);
value = ((long long) stats[0xb8 / 4] << 32) | stats[0xbc / 4];
if (get_stats == 1 || value > 0)
printf("%12qd bytes received\n", value);
for (offset = 0; stats_values[offset].offset != 0; offset++) {
if (get_stats == 1 || stats[stats_values[offset].offset / 4] > 0)
printf("%12d %s\n", stats[stats_values[offset].offset / 4],
stats_values[offset].name);
}
}
static void
esh_tuning_stats()
{
printf("rt_mode_and_status = %x\n",
rr_tune.rt_mode_and_status);
printf("rt_conn_retry_count = %x\n",
rr_tune.rt_conn_retry_count);
printf("rt_conn_retry_timer = %x\n",
rr_tune.rt_conn_retry_timer);
printf("rt_conn_timeout = %x\n", rr_tune.rt_conn_timeout);
printf("rt_stats_timer = %x\n", rr_tune.rt_stats_timer);
printf("rt_interrupt_timer = %x\n",
rr_tune.rt_interrupt_timer);
printf("rt_tx_timeout = %x\n", rr_tune.rt_tx_timeout);
printf("rt_rx_timeout = %x\n", rr_tune.rt_rx_timeout);
printf("rt_pci_state = %x"
" min dma %x read max %x write max %x\n",
rr_tune.rt_pci_state,
(rr_tune.rt_pci_state & RR_PS_MIN_DMA_MASK)
>> RR_PS_MIN_DMA_SHIFT,
do_map_dma(rr_tune.rt_pci_state & RR_PS_READ_MASK,
read_dma_map),
do_map_dma(rr_tune.rt_pci_state & RR_PS_WRITE_MASK,
write_dma_map));
printf("rt_dma_write_state = %x\n",
rr_tune.rt_dma_write_state);
printf("rt_dma_read_state = %x\n", rr_tune.rt_dma_read_state);
printf("rt_driver_param = %x\n", rr_tune.rt_driver_param);
}