📄 whiteheat.c
字号:
/* * USB ConnectTech WhiteHEAT driver * * Copyright (C) 2002 * Connect Tech Inc. * * Copyright (C) 1999 - 2001 * Greg Kroah-Hartman (greg@kroah.com) * * 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. * * See Documentation/usb/usb-serial.txt for more information on using this driver * * (10/09/2002) Stuart MacDonald (stuartm@connecttech.com) * Upgrade to full working driver * * (05/30/2001) gkh * switched from using spinlock to a semaphore, which fixes lots of problems. * * (04/08/2001) gb * Identify version on module load. * * 2001_Mar_19 gkh * Fixed MOD_INC and MOD_DEC logic, the ability to open a port more * than once, and the got the proper usb_device_id table entries so * the driver works again. * * (11/01/2000) Adam J. Richter * usb_device_id table support * * (10/05/2000) gkh * Fixed bug with urb->dev not being set properly, now that the usb * core needs it. * * (10/03/2000) smd * firmware is improved to guard against crap sent to device * firmware now replies CMD_FAILURE on bad things * read_callback fix you provided for private info struct * command_finished now indicates success or fail * setup_port struct now packed to avoid gcc padding * firmware uses 1 based port numbering, driver now handles that * * (09/11/2000) gkh * Removed DEBUG #ifdefs with call to usb_serial_debug_data * * (07/19/2000) gkh * Added module_init and module_exit functions to handle the fact that this * driver is a loadable module now. * Fixed bug with port->minor that was found by Al Borchers * * (07/04/2000) gkh * Added support for port settings. Baud rate can now be changed. Line signals * are not transferred to and from the tty layer yet, but things seem to be * working well now. * * (05/04/2000) gkh * First cut at open and close commands. Data can flow through the ports at * default speeds now. * * (03/26/2000) gkh * Split driver up into device specific pieces. * */#include <linux/config.h>#include <linux/kernel.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/tty.h>#include <linux/tty_driver.h>#include <linux/tty_flip.h>#include <linux/module.h>#include <linux/spinlock.h>#include <asm/uaccess.h>#include <asm/termbits.h>#include <linux/usb.h>#include <linux/serial_reg.h>#include <linux/serial.h>#include "usb-serial.h"#include "whiteheat_fw.h" /* firmware for the ConnectTech WhiteHEAT device */#include "whiteheat.h" /* WhiteHEAT specific commands */static int debug;#ifndef CMSPAR#define CMSPAR 0#endif/* * Version Information */#define DRIVER_VERSION "v2.0"#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Stuart MacDonald <stuartm@connecttech.com>"#define DRIVER_DESC "USB ConnectTech WhiteHEAT driver"#define CONNECT_TECH_VENDOR_ID 0x0710#define CONNECT_TECH_FAKE_WHITE_HEAT_ID 0x0001#define CONNECT_TECH_WHITE_HEAT_ID 0x8001/* ID tables for whiteheat are unusual, because we want to different things for different versions of the device. Eventually, this will be doable from a single table. But, for now, we define two separate ID tables, and then a third table that combines them just for the purpose of exporting the autoloading information.*/static struct usb_device_id id_table_std [] = { { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) }, { } /* Terminating entry */};static struct usb_device_id id_table_prerenumeration [] = { { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) }, { } /* Terminating entry */};static struct usb_device_id id_table_combined [] = { { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) }, { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) }, { } /* Terminating entry */};MODULE_DEVICE_TABLE (usb, id_table_combined);static struct usb_driver whiteheat_driver = { .owner = THIS_MODULE, .name = "whiteheat", .probe = usb_serial_probe, .disconnect = usb_serial_disconnect, .id_table = id_table_combined,};/* function prototypes for the Connect Tech WhiteHEAT prerenumeration device */static int whiteheat_firmware_download (struct usb_serial *serial, const struct usb_device_id *id);static int whiteheat_firmware_attach (struct usb_serial *serial);/* function prototypes for the Connect Tech WhiteHEAT serial converter */static int whiteheat_attach (struct usb_serial *serial);static void whiteheat_shutdown (struct usb_serial *serial);static int whiteheat_open (struct usb_serial_port *port, struct file *filp);static void whiteheat_close (struct usb_serial_port *port, struct file *filp);static int whiteheat_write (struct usb_serial_port *port, const unsigned char *buf, int count);static int whiteheat_write_room (struct usb_serial_port *port);static int whiteheat_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg);static void whiteheat_set_termios (struct usb_serial_port *port, struct termios * old);static int whiteheat_tiocmget (struct usb_serial_port *port, struct file *file);static int whiteheat_tiocmset (struct usb_serial_port *port, struct file *file, unsigned int set, unsigned int clear);static void whiteheat_break_ctl (struct usb_serial_port *port, int break_state);static int whiteheat_chars_in_buffer (struct usb_serial_port *port);static void whiteheat_throttle (struct usb_serial_port *port);static void whiteheat_unthrottle (struct usb_serial_port *port);static void whiteheat_read_callback (struct urb *urb, struct pt_regs *regs);static void whiteheat_write_callback (struct urb *urb, struct pt_regs *regs);static struct usb_serial_driver whiteheat_fake_device = { .driver = { .owner = THIS_MODULE, .name = "whiteheatnofirm", }, .description = "Connect Tech - WhiteHEAT - (prerenumeration)", .id_table = id_table_prerenumeration, .num_interrupt_in = NUM_DONT_CARE, .num_bulk_in = NUM_DONT_CARE, .num_bulk_out = NUM_DONT_CARE, .num_ports = 1, .probe = whiteheat_firmware_download, .attach = whiteheat_firmware_attach,};static struct usb_serial_driver whiteheat_device = { .driver = { .owner = THIS_MODULE, .name = "whiteheat", }, .description = "Connect Tech - WhiteHEAT", .id_table = id_table_std, .num_interrupt_in = NUM_DONT_CARE, .num_bulk_in = NUM_DONT_CARE, .num_bulk_out = NUM_DONT_CARE, .num_ports = 4, .attach = whiteheat_attach, .shutdown = whiteheat_shutdown, .open = whiteheat_open, .close = whiteheat_close, .write = whiteheat_write, .write_room = whiteheat_write_room, .ioctl = whiteheat_ioctl, .set_termios = whiteheat_set_termios, .break_ctl = whiteheat_break_ctl, .tiocmget = whiteheat_tiocmget, .tiocmset = whiteheat_tiocmset, .chars_in_buffer = whiteheat_chars_in_buffer, .throttle = whiteheat_throttle, .unthrottle = whiteheat_unthrottle, .read_bulk_callback = whiteheat_read_callback, .write_bulk_callback = whiteheat_write_callback,};struct whiteheat_command_private { spinlock_t lock; __u8 port_running; __u8 command_finished; wait_queue_head_t wait_command; /* for handling sleeping while waiting for a command to finish */ __u8 result_buffer[64];};#define THROTTLED 0x01#define ACTUALLY_THROTTLED 0x02static int urb_pool_size = 8;struct whiteheat_urb_wrap { struct list_head list; struct urb *urb;};struct whiteheat_private { spinlock_t lock; __u8 flags; __u8 mcr; struct list_head rx_urbs_free; struct list_head rx_urbs_submitted; struct list_head rx_urb_q; struct work_struct rx_work; struct list_head tx_urbs_free; struct list_head tx_urbs_submitted;};/* local function prototypes */static int start_command_port(struct usb_serial *serial);static void stop_command_port(struct usb_serial *serial);static void command_port_write_callback(struct urb *urb, struct pt_regs *regs);static void command_port_read_callback(struct urb *urb, struct pt_regs *regs);static int start_port_read(struct usb_serial_port *port);static struct whiteheat_urb_wrap *urb_to_wrap(struct urb *urb, struct list_head *head);static struct list_head *list_first(struct list_head *head);static void rx_data_softint(void *private);static int firm_send_command(struct usb_serial_port *port, __u8 command, __u8 *data, __u8 datasize);static int firm_open(struct usb_serial_port *port);static int firm_close(struct usb_serial_port *port);static int firm_setup_port(struct usb_serial_port *port);static int firm_set_rts(struct usb_serial_port *port, __u8 onoff);static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff);static int firm_set_break(struct usb_serial_port *port, __u8 onoff);static int firm_purge(struct usb_serial_port *port, __u8 rxtx);static int firm_get_dtr_rts(struct usb_serial_port *port);static int firm_report_tx_done(struct usb_serial_port *port);#define COMMAND_PORT 4#define COMMAND_TIMEOUT (2*HZ) /* 2 second timeout for a command */#define COMMAND_TIMEOUT_MS 2000#define CLOSING_DELAY (30 * HZ)/***************************************************************************** * Connect Tech's White Heat prerenumeration driver functions *****************************************************************************//* steps to download the firmware to the WhiteHEAT device: - hold the reset (by writing to the reset bit of the CPUCS register) - download the VEND_AX.HEX file to the chip using VENDOR_REQUEST-ANCHOR_LOAD - release the reset (by writing to the CPUCS register) - download the WH.HEX file for all addresses greater than 0x1b3f using VENDOR_REQUEST-ANCHOR_EXTERNAL_RAM_LOAD - hold the reset - download the WH.HEX file for all addresses less than 0x1b40 using VENDOR_REQUEST_ANCHOR_LOAD - release the reset - device renumerated itself and comes up as new device id with all firmware download completed.*/static int whiteheat_firmware_download (struct usb_serial *serial, const struct usb_device_id *id){ int response; const struct whiteheat_hex_record *record; dbg("%s", __FUNCTION__); response = ezusb_set_reset (serial, 1); record = &whiteheat_loader[0]; while (record->address != 0xffff) { response = ezusb_writememory (serial, record->address, (unsigned char *)record->data, record->data_size, 0xa0); if (response < 0) { err("%s - ezusb_writememory failed for loader (%d %04X %p %d)", __FUNCTION__, response, record->address, record->data, record->data_size); break; } ++record; } response = ezusb_set_reset (serial, 0); record = &whiteheat_firmware[0]; while (record->address < 0x1b40) { ++record; } while (record->address != 0xffff) { response = ezusb_writememory (serial, record->address, (unsigned char *)record->data, record->data_size, 0xa3); if (response < 0) { err("%s - ezusb_writememory failed for first firmware step (%d %04X %p %d)", __FUNCTION__, response, record->address, record->data, record->data_size); break; } ++record; } response = ezusb_set_reset (serial, 1); record = &whiteheat_firmware[0]; while (record->address < 0x1b40) { response = ezusb_writememory (serial, record->address, (unsigned char *)record->data, record->data_size, 0xa0); if (response < 0) { err("%s - ezusb_writememory failed for second firmware step (%d %04X %p %d)", __FUNCTION__, response, record->address, record->data, record->data_size); break; } ++record; } response = ezusb_set_reset (serial, 0); return 0;}static int whiteheat_firmware_attach (struct usb_serial *serial){ /* We want this device to fail to have a driver assigned to it */ return 1;}/***************************************************************************** * Connect Tech's White Heat serial driver functions *****************************************************************************/static int whiteheat_attach (struct usb_serial *serial){ struct usb_serial_port *command_port; struct whiteheat_command_private *command_info; struct usb_serial_port *port; struct whiteheat_private *info; struct whiteheat_hw_info *hw_info; int pipe; int ret; int alen; __u8 *command; __u8 *result; int i; int j; struct urb *urb; int buf_size; struct whiteheat_urb_wrap *wrap; struct list_head *tmp; command_port = serial->port[COMMAND_PORT]; pipe = usb_sndbulkpipe (serial->dev, command_port->bulk_out_endpointAddress); command = kmalloc(2, GFP_KERNEL); if (!command) goto no_command_buffer; command[0] = WHITEHEAT_GET_HW_INFO; command[1] = 0; result = kmalloc(sizeof(*hw_info) + 1, GFP_KERNEL); if (!result) goto no_result_buffer; /* * When the module is reloaded the firmware is still there and * the endpoints are still in the usb core unchanged. This is the * unlinking bug in disguise. Same for the call below. */ usb_clear_halt(serial->dev, pipe); ret = usb_bulk_msg (serial->dev, pipe, command, 2, &alen, COMMAND_TIMEOUT_MS); if (ret) { err("%s: Couldn't send command [%d]", serial->type->description, ret); goto no_firmware; } else if (alen != sizeof(command)) { err("%s: Send command incomplete [%d]", serial->type->description, alen); goto no_firmware; } pipe = usb_rcvbulkpipe (serial->dev, command_port->bulk_in_endpointAddress); /* See the comment on the usb_clear_halt() above */ usb_clear_halt(serial->dev, pipe); ret = usb_bulk_msg (serial->dev, pipe, result, sizeof(*hw_info) + 1, &alen, COMMAND_TIMEOUT_MS); if (ret) { err("%s: Couldn't get results [%d]", serial->type->description, ret); goto no_firmware; } else if (alen != sizeof(result)) { err("%s: Get results incomplete [%d]", serial->type->description, alen); goto no_firmware; } else if (result[0] != command[0]) { err("%s: Command failed [%d]", serial->type->description, result[0]); goto no_firmware; } hw_info = (struct whiteheat_hw_info *)&result[1]; info("%s: Driver %s: Firmware v%d.%02d", serial->type->description, DRIVER_VERSION, hw_info->sw_major_rev, hw_info->sw_minor_rev); for (i = 0; i < serial->num_ports; i++) { port = serial->port[i]; info = (struct whiteheat_private *)kmalloc(sizeof(struct whiteheat_private), GFP_KERNEL); if (info == NULL) { err("%s: Out of memory for port structures\n", serial->type->description); goto no_private; } spin_lock_init(&info->lock); info->flags = 0; info->mcr = 0; INIT_WORK(&info->rx_work, rx_data_softint, port); INIT_LIST_HEAD(&info->rx_urbs_free); INIT_LIST_HEAD(&info->rx_urbs_submitted); INIT_LIST_HEAD(&info->rx_urb_q); INIT_LIST_HEAD(&info->tx_urbs_free); INIT_LIST_HEAD(&info->tx_urbs_submitted); for (j = 0; j < urb_pool_size; j++) { urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) { err("No free urbs available"); goto no_rx_urb; } buf_size = port->read_urb->transfer_buffer_length; urb->transfer_buffer = kmalloc(buf_size, GFP_KERNEL); if (!urb->transfer_buffer) { err("Couldn't allocate urb buffer"); goto no_rx_buf; } wrap = kmalloc(sizeof(*wrap), GFP_KERNEL); if (!wrap) { err("Couldn't allocate urb wrapper"); goto no_rx_wrap; } usb_fill_bulk_urb(urb, serial->dev, usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), urb->transfer_buffer, buf_size, whiteheat_read_callback, port); wrap->urb = urb; list_add(&wrap->list, &info->rx_urbs_free); urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) { err("No free urbs available"); goto no_tx_urb; } buf_size = port->write_urb->transfer_buffer_length; urb->transfer_buffer = kmalloc(buf_size, GFP_KERNEL); if (!urb->transfer_buffer) { err("Couldn't allocate urb buffer"); goto no_tx_buf; } wrap = kmalloc(sizeof(*wrap), GFP_KERNEL); if (!wrap) { err("Couldn't allocate urb wrapper"); goto no_tx_wrap; } usb_fill_bulk_urb(urb, serial->dev, usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress), urb->transfer_buffer, buf_size, whiteheat_write_callback, port); wrap->urb = urb; list_add(&wrap->list, &info->tx_urbs_free); } usb_set_serial_port_data(port, info); } command_info = (struct whiteheat_command_private *)kmalloc(sizeof(struct whiteheat_command_private), GFP_KERNEL); if (command_info == NULL) { err("%s: Out of memory for port structures\n", serial->type->description); goto no_command_private; } spin_lock_init(&command_info->lock); command_info->port_running = 0; init_waitqueue_head(&command_info->wait_command); usb_set_serial_port_data(command_port, command_info); command_port->write_urb->complete = command_port_write_callback; command_port->read_urb->complete = command_port_read_callback; kfree(result); kfree(command); return 0;no_firmware:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -