Index: head/sys/dev/sound/usb/uaudio.c =================================================================== --- head/sys/dev/sound/usb/uaudio.c (revision 357971) +++ head/sys/dev/sound/usb/uaudio.c (revision 357972) @@ -1,6288 +1,6292 @@ /* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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 __FBSDID("$FreeBSD$"); /* * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf * http://www.usb.org/developers/devclass_docs/frmts10.pdf * http://www.usb.org/developers/devclass_docs/termt10.pdf */ /* * Also merged: * $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $ * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #define USB_DEBUG_VAR uaudio_debug #include #include #include /* for bootverbose */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "feeder_if.h" static int uaudio_default_rate = 0; /* use rate list */ static int uaudio_default_bits = 32; static int uaudio_default_channels = 0; /* use default */ static int uaudio_buffer_ms = 8; #ifdef USB_DEBUG static int uaudio_debug; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uaudio"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RWTUN, &uaudio_debug, 0, "uaudio debug level"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RWTUN, &uaudio_default_rate, 0, "uaudio default sample rate"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RWTUN, &uaudio_default_bits, 0, "uaudio default sample bits"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RWTUN, &uaudio_default_channels, 0, "uaudio default sample channels"); static int uaudio_buffer_ms_sysctl(SYSCTL_HANDLER_ARGS) { int err, val; val = uaudio_buffer_ms; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == uaudio_buffer_ms) return (err); if (val > 8) val = 8; else if (val < 2) val = 2; uaudio_buffer_ms = val; return (0); } -SYSCTL_PROC(_hw_usb_uaudio, OID_AUTO, buffer_ms, CTLTYPE_INT | CTLFLAG_RWTUN, - 0, sizeof(int), uaudio_buffer_ms_sysctl, "I", +SYSCTL_PROC(_hw_usb_uaudio, OID_AUTO, buffer_ms, + CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), + uaudio_buffer_ms_sysctl, "I", "uaudio buffering delay from 2ms to 8ms"); #else #define uaudio_debug 0 #endif #define UAUDIO_NFRAMES 64 /* must be factor of 8 due HS-USB */ #define UAUDIO_NCHANBUFS 2 /* number of outstanding request */ #define UAUDIO_RECURSE_LIMIT 255 /* rounds */ #define UAUDIO_CHANNELS_MAX MIN(64, AFMT_CHANNEL_MAX) #define UAUDIO_MATRIX_MAX 8 /* channels */ #define MAKE_WORD(h,l) (((h) << 8) | (l)) #define BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1) #define UAUDIO_MAX_CHAN(x) (x) #define MIX(sc) ((sc)->sc_mixer_node) union uaudio_asid { const struct usb_audio_streaming_interface_descriptor *v1; const struct usb_audio20_streaming_interface_descriptor *v2; }; union uaudio_asf1d { const struct usb_audio_streaming_type1_descriptor *v1; const struct usb_audio20_streaming_type1_descriptor *v2; }; union uaudio_sed { const struct usb_audio_streaming_endpoint_descriptor *v1; const struct usb_audio20_streaming_endpoint_descriptor *v2; }; struct uaudio_mixer_node { const char *name; int32_t minval; int32_t maxval; #define MIX_MAX_CHAN 16 int32_t wValue[MIX_MAX_CHAN]; /* using nchan */ uint32_t mul; uint32_t ctl; int wData[MIX_MAX_CHAN]; /* using nchan */ uint16_t wIndex; uint8_t update[(MIX_MAX_CHAN + 7) / 8]; uint8_t nchan; uint8_t type; #define MIX_ON_OFF 1 #define MIX_SIGNED_16 2 #define MIX_UNSIGNED_16 3 #define MIX_SIGNED_8 4 #define MIX_SELECTOR 5 #define MIX_UNKNOWN 6 #define MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \ ((n) == MIX_UNSIGNED_16)) ? 2 : 1) #define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) #define MAX_SELECTOR_INPUT_PIN 256 uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; uint8_t class; uint8_t val_default; uint8_t desc[64]; struct uaudio_mixer_node *next; }; struct uaudio_configure_msg { struct usb_proc_msg hdr; struct uaudio_softc *sc; }; #define CHAN_MAX_ALT 24 struct uaudio_chan_alt { union uaudio_asf1d p_asf1d; union uaudio_sed p_sed; const usb_endpoint_descriptor_audio_t *p_ed1; const struct uaudio_format *p_fmt; const struct usb_config *usb_cfg; uint32_t sample_rate; /* in Hz */ uint16_t sample_size; uint8_t iface_index; uint8_t iface_alt_index; uint8_t channels; }; struct uaudio_chan { struct pcmchan_caps pcm_cap; /* capabilities */ struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT]; struct snd_dbuf *pcm_buf; struct mtx *pcm_mtx; /* lock protecting this structure */ struct uaudio_softc *priv_sc; struct pcm_channel *pcm_ch; struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1]; uint8_t *buf; /* pointer to buffer */ uint8_t *start; /* upper layer buffer start */ uint8_t *end; /* upper layer buffer end */ uint8_t *cur; /* current position in upper layer * buffer */ uint32_t intr_frames; /* in units */ uint32_t frames_per_second; uint32_t sample_rem; uint32_t sample_curr; uint32_t max_buf; int32_t jitter_rem; int32_t jitter_curr; int feedback_rate; uint32_t pcm_format[2]; uint16_t bytes_per_frame[2]; uint32_t intr_counter; uint32_t running; uint32_t num_alt; uint32_t cur_alt; uint32_t set_alt; uint32_t operation; #define CHAN_OP_NONE 0 #define CHAN_OP_START 1 #define CHAN_OP_STOP 2 #define CHAN_OP_DRAIN 3 }; #define UMIDI_EMB_JACK_MAX 16 /* units */ #define UMIDI_TX_FRAMES 256 /* units */ #define UMIDI_TX_BUFFER (UMIDI_TX_FRAMES * 4) /* bytes */ enum { UMIDI_TX_TRANSFER, UMIDI_RX_TRANSFER, UMIDI_N_TRANSFER, }; struct umidi_sub_chan { struct usb_fifo_sc fifo; uint8_t *temp_cmd; uint8_t temp_0[4]; uint8_t temp_1[4]; uint8_t state; #define UMIDI_ST_UNKNOWN 0 /* scan for command */ #define UMIDI_ST_1PARAM 1 #define UMIDI_ST_2PARAM_1 2 #define UMIDI_ST_2PARAM_2 3 #define UMIDI_ST_SYSEX_0 4 #define UMIDI_ST_SYSEX_1 5 #define UMIDI_ST_SYSEX_2 6 uint8_t read_open:1; uint8_t write_open:1; uint8_t unused:6; }; struct umidi_chan { struct umidi_sub_chan sub[UMIDI_EMB_JACK_MAX]; struct mtx mtx; struct usb_xfer *xfer[UMIDI_N_TRANSFER]; uint8_t iface_index; uint8_t iface_alt_index; uint8_t read_open_refcount; uint8_t write_open_refcount; uint8_t curr_cable; uint8_t max_emb_jack; uint8_t valid; uint8_t single_command; }; struct uaudio_search_result { uint8_t bit_input[(256 + 7) / 8]; uint8_t bit_output[(256 + 7) / 8]; uint8_t recurse_level; uint8_t id_max; uint8_t is_input; }; enum { UAUDIO_HID_RX_TRANSFER, UAUDIO_HID_N_TRANSFER, }; struct uaudio_hid { struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER]; struct hid_location volume_up_loc; struct hid_location volume_down_loc; struct hid_location mute_loc; uint32_t flags; #define UAUDIO_HID_VALID 0x0001 #define UAUDIO_HID_HAS_ID 0x0002 #define UAUDIO_HID_HAS_VOLUME_UP 0x0004 #define UAUDIO_HID_HAS_VOLUME_DOWN 0x0008 #define UAUDIO_HID_HAS_MUTE 0x0010 uint8_t iface_index; uint8_t volume_up_id; uint8_t volume_down_id; uint8_t mute_id; }; #define UAUDIO_SPDIF_OUT 0x01 /* Enable S/PDIF output */ #define UAUDIO_SPDIF_OUT_48K 0x02 /* Out sample rate = 48K */ #define UAUDIO_SPDIF_OUT_96K 0x04 /* Out sample rate = 96K */ #define UAUDIO_SPDIF_IN_MIX 0x10 /* Input mix enable */ struct uaudio_softc { struct sbuf sc_sndstat; struct sndcard_func sc_sndcard_func; struct uaudio_chan sc_rec_chan; struct uaudio_chan sc_play_chan; struct umidi_chan sc_midi_chan; struct uaudio_hid sc_hid; struct uaudio_search_result sc_mixer_clocks; struct uaudio_mixer_node sc_mixer_node; struct uaudio_configure_msg sc_config_msg[2]; struct mtx *sc_mixer_lock; struct snd_mixer *sc_mixer_dev; struct usb_device *sc_udev; struct usb_xfer *sc_mixer_xfer[1]; struct uaudio_mixer_node *sc_mixer_root; struct uaudio_mixer_node *sc_mixer_curr; int (*sc_set_spdif_fn) (struct uaudio_softc *, int); uint32_t sc_mix_info; uint32_t sc_recsrc_info; uint16_t sc_audio_rev; uint16_t sc_mixer_count; uint8_t sc_sndstat_valid; uint8_t sc_mixer_iface_index; uint8_t sc_mixer_iface_no; uint8_t sc_mixer_chan; uint8_t sc_pcm_registered:1; uint8_t sc_mixer_init:1; uint8_t sc_uq_audio_swap_lr:1; uint8_t sc_uq_au_inp_async:1; uint8_t sc_uq_au_no_xu:1; uint8_t sc_uq_bad_adc:1; uint8_t sc_uq_au_vendor_class:1; uint8_t sc_pcm_bitperfect:1; }; struct uaudio_terminal_node { union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it_v1; const struct usb_audio_output_terminal *ot_v1; const struct usb_audio_mixer_unit_0 *mu_v1; const struct usb_audio_selector_unit *su_v1; const struct usb_audio_feature_unit *fu_v1; const struct usb_audio_processing_unit_0 *pu_v1; const struct usb_audio_extension_unit_0 *eu_v1; const struct usb_audio20_clock_source_unit *csrc_v2; const struct usb_audio20_clock_selector_unit_0 *csel_v2; const struct usb_audio20_clock_multiplier_unit *cmul_v2; const struct usb_audio20_input_terminal *it_v2; const struct usb_audio20_output_terminal *ot_v2; const struct usb_audio20_mixer_unit_0 *mu_v2; const struct usb_audio20_selector_unit *su_v2; const struct usb_audio20_feature_unit *fu_v2; const struct usb_audio20_sample_rate_unit *ru_v2; const struct usb_audio20_processing_unit_0 *pu_v2; const struct usb_audio20_extension_unit_0 *eu_v2; const struct usb_audio20_effect_unit *ef_v2; } u; struct uaudio_search_result usr; struct uaudio_terminal_node *root; }; struct uaudio_format { uint16_t wFormat; uint8_t bPrecision; uint32_t freebsd_fmt; const char *description; }; static const struct uaudio_format uaudio10_formats[] = { {UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; static const struct uaudio_format uaudio20_formats[] = { {UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; #define UAC_OUTPUT 0 #define UAC_INPUT 1 #define UAC_EQUAL 2 #define UAC_RECORD 3 #define UAC_NCLASSES 4 #ifdef USB_DEBUG static const char *uac_names[] = { "outputs", "inputs", "equalization", "record" }; #endif /* prototypes */ static device_probe_t uaudio_probe; static device_attach_t uaudio_attach; static device_detach_t uaudio_detach; static usb_callback_t uaudio_chan_play_callback; static usb_callback_t uaudio_chan_play_sync_callback; static usb_callback_t uaudio_chan_record_callback; static usb_callback_t uaudio_chan_record_sync_callback; static usb_callback_t uaudio_mixer_write_cfg_callback; static usb_callback_t umidi_bulk_read_callback; static usb_callback_t umidi_bulk_write_callback; static usb_callback_t uaudio_hid_rx_callback; static usb_proc_callback_t uaudio_configure_msg; /* ==== USB mixer ==== */ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS); static void uaudio_mixer_ctl_free(struct uaudio_softc *); static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t); static void uaudio_mixer_reload_all(struct uaudio_softc *); static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *); /* ==== USB audio v1.0 ==== */ static void uaudio_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static uint32_t uaudio_mixer_feature_get_bmaControls( const struct usb_audio_feature_unit *, uint8_t); static void uaudio_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing_updown(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_extension(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static uint16_t uaudio_mixer_feature_name(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t); static int uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t, struct uaudio_mixer_node *); /* ==== USB audio v2.0 ==== */ static void uaudio20_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static uint16_t uaudio20_mixer_feature_name(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio20_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t, uint8_t, uint32_t); /* USB audio v1.0 and v2.0 */ static void uaudio_chan_fill_info_sub(struct uaudio_softc *, struct usb_device *, uint32_t, uint8_t, uint8_t); static void uaudio_chan_fill_info(struct uaudio_softc *, struct usb_device *); static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_fill_info(struct uaudio_softc *, struct usb_device *, void *); static void uaudio_mixer_ctl_set(struct uaudio_softc *, struct uaudio_mixer_node *, uint8_t, int32_t val); static int uaudio_mixer_signext(uint8_t, int); static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *, int32_t val); static void uaudio_mixer_init(struct uaudio_softc *); static const struct uaudio_terminal_node *uaudio_mixer_get_input( const struct uaudio_terminal_node *, uint8_t); static const struct uaudio_terminal_node *uaudio_mixer_get_output( const struct uaudio_terminal_node *, uint8_t); static void uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *, uint8_t, uint8_t, struct uaudio_search_result *); static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t); static struct umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *); static void umidi_start_read(struct usb_fifo *); static void umidi_stop_read(struct usb_fifo *); static void umidi_start_write(struct usb_fifo *); static void umidi_stop_write(struct usb_fifo *); static int umidi_open(struct usb_fifo *, int); static int umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int); static void umidi_close(struct usb_fifo *, int); static void umidi_init(device_t dev); static int umidi_probe(device_t dev); static int umidi_detach(device_t dev); static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa); static void uaudio_hid_detach(struct uaudio_softc *sc); #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc( const usb_endpoint_descriptor_audio_t *); #endif static const struct usb_config uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_sync_callback, }, }; static const struct usb_config uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_sync_callback, }, }; static const struct usb_config uaudio_mixer_config[1] = { [0] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + 4), .callback = &uaudio_mixer_write_cfg_callback, .timeout = 1000, /* 1 second */ }, }; static const uint8_t umidi_cmd_to_len[16] = { [0x0] = 0, /* reserved */ [0x1] = 0, /* reserved */ [0x2] = 2, /* bytes */ [0x3] = 3, /* bytes */ [0x4] = 3, /* bytes */ [0x5] = 1, /* bytes */ [0x6] = 2, /* bytes */ [0x7] = 3, /* bytes */ [0x8] = 3, /* bytes */ [0x9] = 3, /* bytes */ [0xA] = 3, /* bytes */ [0xB] = 3, /* bytes */ [0xC] = 2, /* bytes */ [0xD] = 2, /* bytes */ [0xE] = 3, /* bytes */ [0xF] = 1, /* bytes */ }; static const struct usb_config umidi_config[UMIDI_N_TRANSFER] = { [UMIDI_TX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMIDI_TX_BUFFER, .flags = {.no_pipe_ok = 1}, .callback = &umidi_bulk_write_callback, }, [UMIDI_RX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 4, /* bytes */ .flags = {.short_xfer_ok = 1,.proxy_buffer = 1,.no_pipe_ok = 1}, .callback = &umidi_bulk_read_callback, }, }; static const struct usb_config uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = { [UAUDIO_HID_RX_TRANSFER] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_hid_rx_callback, }, }; static devclass_t uaudio_devclass; static device_method_t uaudio_methods[] = { DEVMETHOD(device_probe, uaudio_probe), DEVMETHOD(device_attach, uaudio_attach), DEVMETHOD(device_detach, uaudio_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t uaudio_driver = { .name = "uaudio", .methods = uaudio_methods, .size = sizeof(struct uaudio_softc), }; /* The following table is derived from Linux's quirks-table.h */ static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = { { USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */ }; static const STRUCT_USB_HOST_ID __used uaudio_devs[] = { /* Generic USB audio class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),}, /* Generic USB MIDI class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),}, }; static int uaudio_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); /* lookup non-standard device(s) */ if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { return (BUS_PROBE_SPECIFIC); } if (uaa->info.bInterfaceClass != UICLASS_AUDIO) { if (uaa->info.bInterfaceClass != UICLASS_VENDOR || usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0) return (ENXIO); } /* check for AUDIO control interface */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) { if (usb_test_quirk(uaa, UQ_BAD_AUDIO)) return (ENXIO); else return (BUS_PROBE_GENERIC); } /* check for MIDI stream */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) { if (usb_test_quirk(uaa, UQ_BAD_MIDI)) return (ENXIO); else return (BUS_PROBE_GENERIC); } return (ENXIO); } /* * Set Cmedia CM6206 S/PDIF settings * Source: CM6206 Datasheet v2.3. */ static int uaudio_set_spdif_cm6206(struct uaudio_softc *sc, int flags) { uint8_t cmd[2][4] = { {0x20, 0x20, 0x00, 0}, {0x20, 0x30, 0x02, 1} }; int i; if (flags & UAUDIO_SPDIF_OUT) cmd[1][1] = 0x00; else cmd[1][1] = 0x02; if (flags & UAUDIO_SPDIF_OUT_96K) cmd[0][1] = 0x60; /* 96K: 3'b110 */ if (flags & UAUDIO_SPDIF_IN_MIX) cmd[1][1] = 0x03; /* SPDIFMIX */ for (i = 0; i < 2; i++) { if (usbd_req_set_report(sc->sc_udev, NULL, cmd[i], sizeof(cmd[0]), sc->sc_mixer_iface_index, UHID_OUTPUT_REPORT, 0) != 0) { return (ENXIO); } } return (0); } static int uaudio_set_spdif_dummy(struct uaudio_softc *sc, int flags) { return (0); } static int uaudio_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uaudio_softc *sc = device_get_softc(dev); struct usb_interface_descriptor *id; usb_error_t err; device_t child; sc->sc_play_chan.priv_sc = sc; sc->sc_rec_chan.priv_sc = sc; sc->sc_udev = uaa->device; sc->sc_mixer_iface_index = uaa->info.bIfaceIndex; sc->sc_mixer_iface_no = uaa->info.bIfaceNum; sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[0].sc = sc; sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[1].sc = sc; if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR)) sc->sc_uq_audio_swap_lr = 1; if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC)) sc->sc_uq_au_inp_async = 1; if (usb_test_quirk(uaa, UQ_AU_NO_XU)) sc->sc_uq_au_no_xu = 1; if (usb_test_quirk(uaa, UQ_BAD_ADC)) sc->sc_uq_bad_adc = 1; if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS)) sc->sc_uq_au_vendor_class = 1; /* set S/PDIF function */ if (usb_test_quirk(uaa, UQ_AU_SET_SPDIF_CM6206)) sc->sc_set_spdif_fn = uaudio_set_spdif_cm6206; else sc->sc_set_spdif_fn = uaudio_set_spdif_dummy; umidi_init(dev); device_set_usb_desc(dev); id = usbd_get_interface_descriptor(uaa->iface); /* must fill mixer info before channel info */ uaudio_mixer_fill_info(sc, uaa->device, id); /* fill channel info */ uaudio_chan_fill_info(sc, uaa->device); DPRINTF("audio rev %d.%02x\n", sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); if (sc->sc_mixer_count == 0) { if (uaa->info.idVendor == USB_VENDOR_MAUDIO && (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA || uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) { DPRINTF("Generating mixer descriptors\n"); uaudio_mixer_controls_create_ftu(sc); } } DPRINTF("%d mixer controls\n", sc->sc_mixer_count); if (sc->sc_play_chan.num_alt > 0) { uint8_t x; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinte * re-enumeration loop: */ err = usbd_set_alt_interface_index(sc->sc_udev, sc->sc_play_chan.usb_alt[0].iface_index, sc->sc_play_chan.usb_alt[0].iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_play_chan.num_alt; x++) { device_printf(dev, "Play: %d Hz, %d ch, %s format, " "2x8ms buffer.\n", sc->sc_play_chan.usb_alt[x].sample_rate, sc->sc_play_chan.usb_alt[x].channels, sc->sc_play_chan.usb_alt[x].p_fmt->description); } } else { device_printf(dev, "No playback.\n"); } if (sc->sc_rec_chan.num_alt > 0) { uint8_t x; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinte * re-enumeration loop: */ err = usbd_set_alt_interface_index(sc->sc_udev, sc->sc_rec_chan.usb_alt[0].iface_index, sc->sc_rec_chan.usb_alt[0].iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_rec_chan.num_alt; x++) { device_printf(dev, "Record: %d Hz, %d ch, %s format, " "2x8ms buffer.\n", sc->sc_rec_chan.usb_alt[x].sample_rate, sc->sc_rec_chan.usb_alt[x].channels, sc->sc_rec_chan.usb_alt[x].p_fmt->description); } } else { device_printf(dev, "No recording.\n"); } if (sc->sc_midi_chan.valid == 0) { if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { sc->sc_midi_chan.iface_index = (uint8_t)uaa->driver_info; sc->sc_midi_chan.iface_alt_index = 0; sc->sc_midi_chan.valid = 1; } } if (sc->sc_midi_chan.valid) { if (umidi_probe(dev)) { goto detach; } device_printf(dev, "MIDI sequencer.\n"); } else { device_printf(dev, "No MIDI sequencer.\n"); } DPRINTF("doing child attach\n"); /* attach the children */ sc->sc_sndcard_func.func = SCF_PCM; /* * Only attach a PCM device if we have a playback, recording * or mixer device present: */ if (sc->sc_play_chan.num_alt > 0 || sc->sc_rec_chan.num_alt > 0 || sc->sc_mix_info) { child = device_add_child(dev, "pcm", -1); if (child == NULL) { DPRINTF("out of memory\n"); goto detach; } device_set_ivars(child, &sc->sc_sndcard_func); } if (bus_generic_attach(dev)) { DPRINTF("child attach failed\n"); goto detach; } if (uaudio_hid_probe(sc, uaa) == 0) { device_printf(dev, "HID volume keys found.\n"); } else { device_printf(dev, "No HID volume keys found.\n"); } /* reload all mixer settings */ uaudio_mixer_reload_all(sc); /* enable S/PDIF output, if any */ if (sc->sc_set_spdif_fn(sc, UAUDIO_SPDIF_OUT | UAUDIO_SPDIF_OUT_48K) != 0) { device_printf(dev, "Failed to enable S/PDIF at 48K\n"); } return (0); /* success */ detach: uaudio_detach(dev); return (ENXIO); } static void uaudio_pcm_setflags(device_t dev, uint32_t flags) { pcm_setflags(dev, pcm_getflags(dev) | flags); } int uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); char status[SND_STATUSLEN]; uaudio_mixer_init(sc); if (sc->sc_uq_audio_swap_lr) { DPRINTF("hardware has swapped left and right\n"); /* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */ } if (!(sc->sc_mix_info & SOUND_MASK_PCM)) { DPRINTF("emulating master volume\n"); /* * Emulate missing pcm mixer controller * through FEEDER_VOLUME */ uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL); } if (sc->sc_pcm_bitperfect) { DPRINTF("device needs bitperfect by default\n"); uaudio_pcm_setflags(dev, SD_F_BITPERFECT); } if (mixer_init(dev, mixer_class, sc)) goto detach; sc->sc_mixer_init = 1; mixer_hwvol_init(dev); snprintf(status, sizeof(status), "at ? %s", PCM_KLDSTRING(snd_uaudio)); if (pcm_register(dev, sc, (sc->sc_play_chan.num_alt > 0) ? 1 : 0, (sc->sc_rec_chan.num_alt > 0) ? 1 : 0)) { goto detach; } uaudio_pcm_setflags(dev, SD_F_MPSAFE); sc->sc_pcm_registered = 1; if (sc->sc_play_chan.num_alt > 0) { pcm_addchan(dev, PCMDIR_PLAY, chan_class, sc); } if (sc->sc_rec_chan.num_alt > 0) { pcm_addchan(dev, PCMDIR_REC, chan_class, sc); } pcm_setstatus(dev, status); uaudio_mixer_register_sysctl(sc, dev); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan.feedback_rate, 0, "Feedback sample rate in Hz"); return (0); /* success */ detach: uaudio_detach_sub(dev); return (ENXIO); } int uaudio_detach_sub(device_t dev) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); int error = 0; /* disable S/PDIF output, if any */ (void) sc->sc_set_spdif_fn(sc, 0); repeat: if (sc->sc_pcm_registered) { error = pcm_unregister(dev); } else { if (sc->sc_mixer_init) { error = mixer_uninit(dev); } } if (error) { device_printf(dev, "Waiting for sound application to exit!\n"); usb_pause_mtx(NULL, 2 * hz); goto repeat; /* try again */ } return (0); /* success */ } static int uaudio_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); /* * Stop USB transfers early so that any audio applications * will time out and close opened /dev/dspX.Y device(s), if * any. */ usb_proc_explore_lock(sc->sc_udev); sc->sc_play_chan.operation = CHAN_OP_DRAIN; sc->sc_rec_chan.operation = CHAN_OP_DRAIN; usb_proc_explore_mwait(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); usb_proc_explore_unlock(sc->sc_udev); usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS + 1); usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS + 1); uaudio_hid_detach(sc); if (bus_generic_detach(dev) != 0) { DPRINTF("detach failed!\n"); } sbuf_delete(&sc->sc_sndstat); sc->sc_sndstat_valid = 0; umidi_detach(dev); /* free mixer data */ uaudio_mixer_ctl_free(sc); return (0); } static uint32_t uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt) { struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt]; /* We use 2 times 8ms of buffer */ uint32_t buf_size = chan_alt->sample_size * howmany(chan_alt->sample_rate * (UAUDIO_NFRAMES / 8), 1000); return (buf_size); } static void uaudio_configure_msg_sub(struct uaudio_softc *sc, struct uaudio_chan *chan, int dir) { struct uaudio_chan_alt *chan_alt; uint32_t frames; uint32_t buf_size; uint16_t fps; uint8_t set_alt; uint8_t fps_shift; uint8_t operation; usb_error_t err; if (chan->num_alt <= 0) return; DPRINTF("\n"); usb_proc_explore_lock(sc->sc_udev); operation = chan->operation; chan->operation = CHAN_OP_NONE; usb_proc_explore_unlock(sc->sc_udev); mtx_lock(chan->pcm_mtx); if (chan->cur_alt != chan->set_alt) set_alt = chan->set_alt; else set_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); if (set_alt >= chan->num_alt) goto done; chan_alt = chan->usb_alt + set_alt; usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); err = usbd_set_alt_interface_index(sc->sc_udev, chan_alt->iface_index, chan_alt->iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); goto error; } /* * Only set the sample rate if the channel reports that it * supports the frequency control. */ if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { unsigned int x; for (x = 0; x != 256; x++) { if (dir == PCMDIR_PLAY) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } if (uaudio20_set_speed(sc->sc_udev, sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting * the speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } } else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) { if (uaudio_set_speed(sc->sc_udev, chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting the * speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer, chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) { DPRINTF("could not allocate USB transfers!\n"); goto error; } fps = usbd_get_isoc_fps(sc->sc_udev); if (fps < 8000) { /* FULL speed USB */ frames = uaudio_buffer_ms; } else { /* HIGH speed USB */ frames = uaudio_buffer_ms * 8; } fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]); /* down shift number of frames per second, if any */ fps >>= fps_shift; frames >>= fps_shift; /* bytes per frame should not be zero */ chan->bytes_per_frame[0] = ((chan_alt->sample_rate / fps) * chan_alt->sample_size); chan->bytes_per_frame[1] = howmany(chan_alt->sample_rate, fps) * chan_alt->sample_size; /* setup data rate dithering, if any */ chan->frames_per_second = fps; chan->sample_rem = chan_alt->sample_rate % fps; chan->sample_curr = 0; /* compute required buffer size */ buf_size = (chan->bytes_per_frame[1] * frames); if (buf_size > (chan->end - chan->start)) { DPRINTF("buffer size is too big\n"); goto error; } chan->intr_frames = frames; DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem); if (chan->intr_frames == 0) { DPRINTF("frame shift is too high!\n"); goto error; } mtx_lock(chan->pcm_mtx); chan->cur_alt = set_alt; mtx_unlock(chan->pcm_mtx); done: #if (UAUDIO_NCHANBUFS != 2) #error "please update code" #endif switch (operation) { case CHAN_OP_START: mtx_lock(chan->pcm_mtx); usbd_transfer_start(chan->xfer[0]); usbd_transfer_start(chan->xfer[1]); mtx_unlock(chan->pcm_mtx); break; case CHAN_OP_STOP: mtx_lock(chan->pcm_mtx); usbd_transfer_stop(chan->xfer[0]); usbd_transfer_stop(chan->xfer[1]); mtx_unlock(chan->pcm_mtx); break; default: break; } return; error: usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); mtx_lock(chan->pcm_mtx); chan->cur_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); } static void uaudio_configure_msg(struct usb_proc_msg *pm) { struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc; usb_proc_explore_unlock(sc->sc_udev); uaudio_configure_msg_sub(sc, &sc->sc_play_chan, PCMDIR_PLAY); uaudio_configure_msg_sub(sc, &sc->sc_rec_chan, PCMDIR_REC); usb_proc_explore_lock(sc->sc_udev); } /*========================================================================* * AS - Audio Stream - routines *========================================================================*/ #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed) { if (ed) { DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n" "bEndpointAddress=%d bmAttributes=0x%x \n" "wMaxPacketSize=%d bInterval=%d \n" "bRefresh=%d bSynchAddress=%d\n", ed, ed->bLength, ed->bDescriptorType, ed->bEndpointAddress, ed->bmAttributes, UGETW(ed->wMaxPacketSize), ed->bInterval, UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0, UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0); } } #endif /* * The following is a workaround for broken no-name USB audio devices * sold by dealextreme called "3D sound". The problem is that the * manufacturer computed wMaxPacketSize is too small to hold the * actual data sent. In other words the device sometimes sends more * data than it actually reports it can send in a single isochronous * packet. */ static void uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep, uint32_t xps, uint32_t add) { uint32_t mps; mps = UGETW(ep->wMaxPacketSize); /* * If the device indicates it can send more data than what the * sample rate indicates, we apply the workaround. */ if (mps > xps) { /* allow additional data */ xps += add; /* check against the maximum USB 1.x length */ if (xps > 1023) xps = 1023; /* check if we should do an update */ if (mps < xps) { /* simply update the wMaxPacketSize field */ USETW(ep->wMaxPacketSize, xps); DPRINTF("Workaround: Updated wMaxPacketSize " "from %d to %d bytes.\n", (int)mps, (int)xps); } } } static usb_error_t uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t rate) { struct usb_device_request req; usb_error_t error; #define UAUDIO20_MAX_RATES 32 /* we support at maxium 32 rates */ uint8_t data[2 + UAUDIO20_MAX_RATES * 12]; uint16_t actlen; uint16_t rates; uint16_t x; DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n", iface_no, clockid, rate); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UA20_CS_RANGE; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); /* * Assume there is at least one rate to begin with, else some * devices might refuse to return the USB descriptor: */ USETW(req.wLength, (2 + 1 * 12)); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) { /* * Likely the descriptor doesn't fit into the supplied * buffer. Try using a larger buffer and see if that * helps: */ rates = MIN(UAUDIO20_MAX_RATES, (255 - 2) / 12); error = USB_ERR_INVAL; } else { rates = UGETW(data); if (rates > UAUDIO20_MAX_RATES) { DPRINTF("Too many rates truncating to %d\n", UAUDIO20_MAX_RATES); rates = UAUDIO20_MAX_RATES; error = USB_ERR_INVAL; } else if (rates > 1) { DPRINTF("Need to read full rate descriptor\n"); error = USB_ERR_INVAL; } } if (error != 0) { /* * Try to read full rate descriptor. */ actlen = (2 + rates * 12); USETW(req.wLength, actlen); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) return (USB_ERR_INVAL); rates = UGETW(data); } actlen = (actlen - 2) / 12; if (rates > actlen) { DPRINTF("Too many rates truncating to %d\n", actlen); rates = actlen; } for (x = 0; x != rates; x++) { uint32_t min = UGETDW(data + 2 + (12 * x)); uint32_t max = UGETDW(data + 6 + (12 * x)); uint32_t res = UGETDW(data + 10 + (12 * x)); if (res == 0) { DPRINTF("Zero residue\n"); res = 1; } if (min > max) { DPRINTF("Swapped max and min\n"); uint32_t temp; temp = min; min = max; max = temp; } if (rate >= min && rate <= max && (((rate - min) % res) == 0)) { return (0); } } return (USB_ERR_INVAL); } static void uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev, uint32_t rate, uint8_t channels, uint8_t bit_resolution) { struct usb_descriptor *desc = NULL; union uaudio_asid asid = { NULL }; union uaudio_asf1d asf1d = { NULL }; union uaudio_sed sed = { NULL }; struct usb_midi_streaming_endpoint_descriptor *msid = NULL; usb_endpoint_descriptor_audio_t *ed1 = NULL; const struct usb_audio_control_descriptor *acdp = NULL; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); struct usb_interface_descriptor *id; const struct uaudio_format *p_fmt = NULL; struct uaudio_chan *chan; struct uaudio_chan_alt *chan_alt; uint32_t format; uint16_t curidx = 0xFFFF; uint16_t lastidx = 0xFFFF; uint16_t alt_index = 0; uint16_t audio_rev = 0; uint16_t x; uint8_t ep_dir; uint8_t bChannels; uint8_t bBitResolution; uint8_t audio_if = 0; uint8_t midi_if = 0; uint8_t uma_if_class; while ((desc = usb_desc_foreach(cd, desc))) { if ((desc->bDescriptorType == UDESC_INTERFACE) && (desc->bLength >= sizeof(*id))) { id = (void *)desc; if (id->bInterfaceNumber != lastidx) { lastidx = id->bInterfaceNumber; curidx++; alt_index = 0; } else { alt_index++; } if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) && (id->bInterfaceClass == UICLASS_HID) && (id->bInterfaceSubClass == 0) && (id->bInterfaceProtocol == 0) && (alt_index == 0) && usbd_get_iface(udev, curidx) != NULL) { DPRINTF("Found HID interface at %d\n", curidx); sc->sc_hid.flags |= UAUDIO_HID_VALID; sc->sc_hid.iface_index = curidx; } uma_if_class = ((id->bInterfaceClass == UICLASS_AUDIO) || ((id->bInterfaceClass == UICLASS_VENDOR) && (sc->sc_uq_au_vendor_class != 0))); if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) { audio_if = 1; } else { audio_if = 0; } if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) { /* * XXX could allow multiple MIDI interfaces */ midi_if = 1; if ((sc->sc_midi_chan.valid == 0) && (usbd_get_iface(udev, curidx) != NULL)) { sc->sc_midi_chan.iface_index = curidx; sc->sc_midi_chan.iface_alt_index = alt_index; sc->sc_midi_chan.valid = 1; } } else { midi_if = 0; } asid.v1 = NULL; asf1d.v1 = NULL; ed1 = NULL; sed.v1 = NULL; /* * There can only be one USB audio instance * per USB device. Grab all USB audio * interfaces on this USB device so that we * don't attach USB audio twice: */ if (alt_index == 0 && curidx != sc->sc_mixer_iface_index && (id->bInterfaceClass == UICLASS_AUDIO || audio_if != 0 || midi_if != 0)) { usbd_set_parent_iface(sc->sc_udev, curidx, sc->sc_mixer_iface_index); } } if (audio_if == 0) { if (midi_if == 0) { if ((acdp == NULL) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) && (desc->bLength >= sizeof(*acdp))) { acdp = (void *)desc; audio_rev = UGETW(acdp->bcdADC); } } else { msid = (void *)desc; /* get the maximum number of embedded jacks in use, if any */ if (msid->bLength >= sizeof(*msid) && msid->bDescriptorType == UDESC_CS_ENDPOINT && msid->bDescriptorSubtype == MS_GENERAL && msid->bNumEmbMIDIJack > sc->sc_midi_chan.max_emb_jack) { sc->sc_midi_chan.max_emb_jack = msid->bNumEmbMIDIJack; } } /* * Don't collect any USB audio descriptors if * this is not an USB audio stream interface. */ continue; } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == AS_GENERAL) && (asid.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asid.v2)) { asid.v2 = (void *)desc; } } else { if (desc->bLength >= sizeof(*asid.v1)) { asid.v1 = (void *)desc; } } } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == FORMAT_TYPE) && (asf1d.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asf1d.v2)) asf1d.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*asf1d.v1)) { asf1d.v1 = (void *)desc; if (asf1d.v1->bFormatType != FORMAT_TYPE_I) { DPRINTFN(11, "ignored bFormatType = %d\n", asf1d.v1->bFormatType); asf1d.v1 = NULL; continue; } if (desc->bLength < (sizeof(*asf1d.v1) + ((asf1d.v1->bSamFreqType == 0) ? 6 : (asf1d.v1->bSamFreqType * 3)))) { DPRINTFN(11, "invalid descriptor, " "too short\n"); asf1d.v1 = NULL; continue; } } } } if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= UEP_MINSIZE) && (ed1 == NULL)) { ed1 = (void *)desc; if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) { ed1 = NULL; continue; } } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_ENDPOINT) && (desc->bDescriptorSubtype == AS_GENERAL) && (sed.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*sed.v2)) sed.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*sed.v1)) sed.v1 = (void *)desc; } } if (asid.v1 == NULL || asf1d.v1 == NULL || ed1 == NULL || sed.v1 == NULL) { /* need more descriptors */ continue; } ep_dir = UE_GET_DIR(ed1->bEndpointAddress); /* We ignore sync endpoint information until further. */ if (audio_rev >= UAUDIO_VERSION_30) { goto next_ep; } else if (audio_rev >= UAUDIO_VERSION_20) { uint32_t dwFormat; dwFormat = UGETDW(asid.v2->bmFormats); bChannels = asid.v2->bNrChannels; bBitResolution = asf1d.v2->bSubslotSize * 8; if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } for (p_fmt = uaudio20_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat & dwFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } for (x = 0; x != 256; x++) { if (ep_dir == UE_DIR_OUT) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } DPRINTF("Checking clock ID=%d\n", x); if (uaudio20_check_rate(udev, sc->sc_mixer_iface_no, x, rate)) { DPRINTF("Unsupported sampling " "rate, id=%d\n", x); goto next_ep; } } } else { uint16_t wFormat; wFormat = UGETW(asid.v1->wFormatTag); bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels); bBitResolution = asf1d.v1->bSubFrameSize * 8; if (asf1d.v1->bSamFreqType == 0) { DPRINTFN(16, "Sample rate: %d-%dHz\n", UA_SAMP_LO(asf1d.v1), UA_SAMP_HI(asf1d.v1)); if ((rate >= UA_SAMP_LO(asf1d.v1)) && (rate <= UA_SAMP_HI(asf1d.v1))) goto found_rate; } else { for (x = 0; x < asf1d.v1->bSamFreqType; x++) { DPRINTFN(16, "Sample rate = %dHz\n", UA_GETSAMP(asf1d.v1, x)); if (rate == UA_GETSAMP(asf1d.v1, x)) goto found_rate; } } goto next_ep; found_rate: for (p_fmt = uaudio10_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat == wFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } } chan = (ep_dir == UE_DIR_IN) ? &sc->sc_rec_chan : &sc->sc_play_chan; if (usbd_get_iface(udev, curidx) == NULL) { DPRINTF("Interface is not valid\n"); goto next_ep; } if (chan->num_alt == CHAN_MAX_ALT) { DPRINTF("Too many alternate settings\n"); goto next_ep; } chan->set_alt = 0; chan->cur_alt = CHAN_MAX_ALT; chan_alt = &chan->usb_alt[chan->num_alt++]; #ifdef USB_DEBUG uaudio_chan_dump_ep_desc(ed1); #endif DPRINTF("Sample rate = %dHz, channels = %d, " "bits = %d, format = %s\n", rate, channels, bit_resolution, p_fmt->description); chan_alt->sample_rate = rate; chan_alt->p_asf1d = asf1d; chan_alt->p_ed1 = ed1; chan_alt->p_fmt = p_fmt; chan_alt->p_sed = sed; chan_alt->iface_index = curidx; chan_alt->iface_alt_index = alt_index; if (ep_dir == UE_DIR_IN) chan_alt->usb_cfg = uaudio_cfg_record; else chan_alt->usb_cfg = uaudio_cfg_play; chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) * p_fmt->bPrecision) / 8; chan_alt->channels = channels; if (ep_dir == UE_DIR_IN && usbd_get_speed(udev) == USB_SPEED_FULL) { uaudio_record_fix_fs(ed1, chan_alt->sample_size * (rate / 1000), chan_alt->sample_size * (rate / 4000)); } /* setup play/record format */ format = chan_alt->p_fmt->freebsd_fmt; /* get default SND_FORMAT() */ format = SND_FORMAT(format, chan_alt->channels, 0); switch (chan_alt->channels) { uint32_t temp_fmt; case 1: case 2: /* mono and stereo */ break; default: /* surround and more */ temp_fmt = feeder_matrix_default_format(format); /* if multichannel, then format can be zero */ if (temp_fmt != 0) format = temp_fmt; break; } /* check if format is not supported */ if (format == 0) { DPRINTF("The selected audio format is not supported\n"); chan->num_alt--; goto next_ep; } if (chan->num_alt > 1) { /* we only accumulate one format at different sample rates */ if (chan->pcm_format[0] != format) { DPRINTF("Multiple formats is not supported\n"); chan->num_alt--; goto next_ep; } /* ignore if duplicate sample rate entry */ if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) { DPRINTF("Duplicate sample rate detected\n"); chan->num_alt--; goto next_ep; } } chan->pcm_cap.fmtlist = chan->pcm_format; chan->pcm_cap.fmtlist[0] = format; /* check if device needs bitperfect */ if (chan_alt->channels > UAUDIO_MATRIX_MAX) sc->sc_pcm_bitperfect = 1; if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0) chan->pcm_cap.minspeed = rate; if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0) chan->pcm_cap.maxspeed = rate; if (sc->sc_sndstat_valid != 0) { sbuf_printf(&sc->sc_sndstat, "\n\t" "mode %d.%d:(%s) %dch, %dbit, %s, %dHz", curidx, alt_index, (ep_dir == UE_DIR_IN) ? "input" : "output", channels, p_fmt->bPrecision, p_fmt->description, rate); } next_ep: sed.v1 = NULL; ed1 = NULL; } } /* This structure defines all the supported rates. */ static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = { 384000, 352800, 192000, 176400, 96000, 88200, 88000, 80000, 72000, 64000, 56000, 48000, 44100, 40000, 32000, 24000, 22050, 16000, 11025, 8000, 0 }; static void uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev) { uint32_t rate = uaudio_default_rate; uint8_t z; uint8_t bits = uaudio_default_bits; uint8_t y; uint8_t channels = uaudio_default_channels; uint8_t x; bits -= (bits % 8); if ((bits == 0) || (bits > 32)) { /* set a valid value */ bits = 32; } if (channels == 0) { switch (usbd_get_speed(udev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: /* * Due to high bandwidth usage and problems * with HIGH-speed split transactions we * disable surround setups on FULL-speed USB * by default */ channels = 4; break; default: channels = UAUDIO_CHANNELS_MAX; break; } } else if (channels > UAUDIO_CHANNELS_MAX) channels = UAUDIO_CHANNELS_MAX; if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) sc->sc_sndstat_valid = 1; /* try to search for a valid config */ for (x = channels; x; x--) { for (y = bits; y; y -= 8) { /* try user defined rate, if any */ if (rate != 0) uaudio_chan_fill_info_sub(sc, udev, rate, x, y); /* try find a matching rate, if any */ for (z = 0; uaudio_rate_list[z]; z++) uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y); } } if (sc->sc_sndstat_valid) sbuf_finish(&sc->sc_sndstat); } static void uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint64_t sample_rate; uint8_t buf[4]; uint64_t temp; int len; int actlen; int nframes; usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "transferred %d bytes\n", actlen); if (nframes == 0) break; len = usbd_xfer_frame_len(xfer, 0); if (len == 0) break; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); temp = UGETDW(buf); DPRINTF("Value = 0x%08x\n", (int)temp); /* auto-detect SYNC format */ if (len == 4) temp &= 0x0fffffff; /* check for no data */ if (temp == 0) break; temp *= 125ULL; sample_rate = ch->usb_alt[ch->cur_alt].sample_rate; /* auto adjust */ while (temp < (sample_rate - (sample_rate / 4))) temp *= 2; while (temp > (sample_rate + (sample_rate / 2))) temp /= 2; DPRINTF("Comparing %d Hz :: %d Hz\n", (int)temp, (int)sample_rate); /* * Use feedback value as fallback when there is no * recording channel: */ if (ch->priv_sc->sc_rec_chan.num_alt == 0) { int32_t jitter_max = howmany(sample_rate, 16000); /* * Range check the jitter values to avoid * bogus sample rate adjustments. The expected * deviation should not be more than 1Hz per * second. The USB v2.0 specification also * mandates this requirement. Refer to chapter * 5.12.4.2 about feedback. */ ch->jitter_curr = temp - sample_rate; if (ch->jitter_curr > jitter_max) ch->jitter_curr = jitter_max; else if (ch->jitter_curr < -jitter_max) ch->jitter_curr = -jitter_max; } ch->feedback_rate = temp; break; case USB_ST_SETUP: /* * Check if the recording stream can be used as a * source of jitter information to save some * isochronous bandwidth: */ if (ch->priv_sc->sc_rec_chan.num_alt != 0 && uaudio_debug == 0) break; usbd_xfer_set_frames(xfer, 1); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ break; } } static int uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt) { uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes; return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC); } static void uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct uaudio_chan *ch_rec; struct usb_page_cache *pc; uint32_t mfl; uint32_t total; uint32_t blockcount; uint32_t n; uint32_t offset; int sample_size; int actlen; int sumlen; if (ch->running == 0 || ch->start == ch->end) { DPRINTF("not running or no buffer!\n"); return; } /* check if there is a record channel */ if (ch->priv_sc->sc_rec_chan.num_alt > 0) ch_rec = &ch->priv_sc->sc_rec_chan; else ch_rec = NULL; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: tr_setup: if (ch_rec != NULL) { /* reset receive jitter counters */ mtx_lock(ch_rec->pcm_mtx); ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; mtx_unlock(ch_rec->pcm_mtx); } /* reset transmit jitter counters */ ch->jitter_curr = 0; ch->jitter_rem = 0; /* FALLTHROUGH */ case USB_ST_TRANSFERRED: if (actlen < sumlen) { DPRINTF("short transfer, " "%d of %d bytes\n", actlen, sumlen); } chn_intr(ch->pcm_ch); /* * Check for asynchronous playback endpoint and that * the playback endpoint is properly configured: */ if (ch_rec != NULL && uaudio_chan_is_async(ch, ch->cur_alt) != 0) { mtx_lock(ch_rec->pcm_mtx); if (ch_rec->cur_alt < ch_rec->num_alt) { int64_t tx_jitter; int64_t rx_rate; /* translate receive jitter into transmit jitter */ tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate; tx_jitter = (tx_jitter * ch_rec->jitter_curr) + ch->jitter_rem; /* reset receive jitter counters */ ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; /* compute exact number of transmit jitter samples */ rx_rate = ch_rec->usb_alt[ch_rec->cur_alt].sample_rate; ch->jitter_curr += tx_jitter / rx_rate; ch->jitter_rem = tx_jitter % rx_rate; } mtx_unlock(ch_rec->pcm_mtx); } /* start the SYNC transfer one time per second, if any */ ch->intr_counter += ch->intr_frames; if (ch->intr_counter >= ch->frames_per_second) { ch->intr_counter -= ch->frames_per_second; usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]); } mfl = usbd_xfer_max_framelen(xfer); if (ch->bytes_per_frame[1] > mfl) { DPRINTF("bytes per transfer, %d, " "exceeds maximum, %d!\n", ch->bytes_per_frame[1], mfl); break; } blockcount = ch->intr_frames; /* setup number of frames */ usbd_xfer_set_frames(xfer, blockcount); /* get sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; /* reset total length */ total = 0; /* setup frame lengths */ for (n = 0; n != blockcount; n++) { uint32_t frame_len; ch->sample_curr += ch->sample_rem; if (ch->sample_curr >= ch->frames_per_second) { ch->sample_curr -= ch->frames_per_second; frame_len = ch->bytes_per_frame[1]; } else { frame_len = ch->bytes_per_frame[0]; } /* handle free running clock case */ if (ch->jitter_curr > 0 && (frame_len + sample_size) <= mfl) { DPRINTFN(6, "sending one sample more\n"); ch->jitter_curr--; frame_len += sample_size; } else if (ch->jitter_curr < 0 && frame_len >= sample_size) { DPRINTFN(6, "sending one sample less\n"); ch->jitter_curr++; frame_len -= sample_size; } usbd_xfer_set_frame_len(xfer, n, frame_len); total += frame_len; } DPRINTFN(6, "transferring %d bytes\n", total); offset = 0; pc = usbd_xfer_get_frame(xfer, 0); while (total > 0) { n = (ch->end - ch->cur); if (n > total) n = total; usbd_copy_in(pc, offset, ch->cur, n); total -= n; ch->cur += n; offset += n; if (ch->cur >= ch->end) ch->cur = ch->start; } usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } static void uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error) { /* TODO */ } static void uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t offset0; uint32_t mfl; int m; int n; int len; int actlen; int nframes; int expected_bytes; int sample_size; if (ch->start == ch->end) { DPRINTF("no buffer!\n"); return; } usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); mfl = usbd_xfer_max_framelen(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: offset0 = 0; pc = usbd_xfer_get_frame(xfer, 0); /* try to compute the number of expected bytes */ ch->sample_curr += (ch->sample_rem * ch->intr_frames); /* compute number of expected bytes */ expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) + ((ch->sample_curr / ch->frames_per_second) * (ch->bytes_per_frame[1] - ch->bytes_per_frame[0])); /* keep remainder */ ch->sample_curr %= ch->frames_per_second; /* get current sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; for (n = 0; n != nframes; n++) { uint32_t offset1 = offset0; len = usbd_xfer_frame_len(xfer, n); /* make sure we only receive complete samples */ len = len - (len % sample_size); /* subtract bytes received from expected payload */ expected_bytes -= len; /* don't receive data when not ready */ if (ch->running == 0 || ch->cur_alt != ch->set_alt) continue; /* fill ring buffer with samples, if any */ while (len > 0) { m = (ch->end - ch->cur); if (m > len) m = len; usbd_copy_out(pc, offset1, ch->cur, m); len -= m; offset1 += m; ch->cur += m; if (ch->cur >= ch->end) ch->cur = ch->start; } offset0 += mfl; } /* update current jitter */ ch->jitter_curr -= (expected_bytes / sample_size); /* don't allow a huge amount of jitter to accumulate */ nframes = 2 * ch->intr_frames; /* range check current jitter */ if (ch->jitter_curr < -nframes) ch->jitter_curr = -nframes; else if (ch->jitter_curr > nframes) ch->jitter_curr = nframes; DPRINTFN(6, "transferred %d bytes, jitter %d samples\n", actlen, ch->jitter_curr); if (ch->running != 0) chn_intr(ch->pcm_ch); case USB_ST_SETUP: tr_setup: nframes = ch->intr_frames; usbd_xfer_set_frames(xfer, nframes); for (n = 0; n != nframes; n++) usbd_xfer_set_frame_len(xfer, n, mfl); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } void * uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct uaudio_chan *ch = ((dir == PCMDIR_PLAY) ? &sc->sc_play_chan : &sc->sc_rec_chan); uint32_t buf_size; uint8_t x; /* store mutex and PCM channel */ ch->pcm_ch = c; ch->pcm_mtx = c->lock; /* compute worst case buffer */ buf_size = 0; for (x = 0; x != ch->num_alt; x++) { uint32_t temp = uaudio_get_buffer_size(ch, x); if (temp > buf_size) buf_size = temp; } /* allow double buffering */ buf_size *= 2; DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size); ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO); if (ch->buf == NULL) goto error; if (sndbuf_setup(b, ch->buf, buf_size) != 0) goto error; ch->start = ch->buf; ch->end = ch->buf + buf_size; ch->cur = ch->buf; ch->pcm_buf = b; ch->max_buf = buf_size; if (ch->pcm_mtx == NULL) { DPRINTF("ERROR: PCM channels does not have a mutex!\n"); goto error; } return (ch); error: uaudio_chan_free(ch); return (NULL); } int uaudio_chan_free(struct uaudio_chan *ch) { if (ch->buf != NULL) { free(ch->buf, M_DEVBUF); ch->buf = NULL; } usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1); ch->num_alt = 0; return (0); } int uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize) { uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); sndbuf_setup(ch->pcm_buf, ch->buf, temp); return (temp / 2); } int uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, uint32_t blockcount) { return (1); } int uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) { struct uaudio_softc *sc; uint8_t x; sc = ch->priv_sc; for (x = 0; x < ch->num_alt; x++) { if (ch->usb_alt[x].sample_rate < speed) { /* sample rate is too low */ break; } } if (x != 0) x--; usb_proc_explore_lock(sc->sc_udev); ch->set_alt = x; usb_proc_explore_unlock(sc->sc_udev); DPRINTF("Selecting alt %d\n", (int)x); return (ch->usb_alt[x].sample_rate); } int uaudio_chan_getptr(struct uaudio_chan *ch) { return (ch->cur - ch->start); } struct pcmchan_caps * uaudio_chan_getcaps(struct uaudio_chan *ch) { return (&ch->pcm_cap); } static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = { .id = SND_CHN_MATRIX_DRV, .channels = 2, .ext = 0, .map = { /* Right */ [0] = { .type = SND_CHN_T_FR, .members = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR }, /* Left */ [1] = { .type = SND_CHN_T_FL, .members = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL }, [2] = { .type = SND_CHN_T_MAX, .members = 0 } }, .mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL, .offset = { 1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } }; struct pcmchan_matrix * uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format) { struct uaudio_softc *sc; sc = ch->priv_sc; if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 && AFMT_CHANNEL(format) == 2) return (&uaudio_chan_matrix_swap_2_0); return (feeder_matrix_format_map(format)); } int uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format) { DPRINTF("Selecting format 0x%08x\n", (unsigned int)format); return (0); } static void uaudio_chan_start_sub(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; int do_start = 0; if (ch->operation != CHAN_OP_DRAIN) { if (ch->cur_alt == ch->set_alt && ch->operation == CHAN_OP_NONE && mtx_owned(ch->pcm_mtx) != 0) { /* save doing the explore task */ do_start = 1; } else { ch->operation = CHAN_OP_START; (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } } if (do_start) { usbd_transfer_start(ch->xfer[0]); usbd_transfer_start(ch->xfer[1]); } } static int uaudio_chan_need_both(struct uaudio_softc *sc) { return (sc->sc_play_chan.num_alt > 0 && sc->sc_play_chan.running != 0 && uaudio_chan_is_async(&sc->sc_play_chan, sc->sc_play_chan.set_alt) != 0 && sc->sc_rec_chan.num_alt > 0 && sc->sc_rec_chan.running == 0); } static int uaudio_chan_need_none(struct uaudio_softc *sc) { return (sc->sc_play_chan.num_alt > 0 && sc->sc_play_chan.running == 0 && sc->sc_rec_chan.num_alt > 0 && sc->sc_rec_chan.running == 0); } void uaudio_chan_start(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if not running */ if (ch->running == 0) { uint32_t temp; /* get current buffer size */ temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); /* set running flag */ ch->running = 1; /* ensure the hardware buffer is reset */ ch->start = ch->buf; ch->end = ch->buf + temp; ch->cur = ch->buf; if (uaudio_chan_need_both(sc)) { /* * Start both endpoints because of need for * jitter information: */ uaudio_chan_start_sub(&sc->sc_rec_chan); uaudio_chan_start_sub(&sc->sc_play_chan); } else { uaudio_chan_start_sub(ch); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } static void uaudio_chan_stop_sub(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; int do_stop = 0; if (ch->operation != CHAN_OP_DRAIN) { if (ch->cur_alt == ch->set_alt && ch->operation == CHAN_OP_NONE && mtx_owned(ch->pcm_mtx) != 0) { /* save doing the explore task */ do_stop = 1; } else { ch->operation = CHAN_OP_STOP; (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } } if (do_stop) { usbd_transfer_stop(ch->xfer[0]); usbd_transfer_stop(ch->xfer[1]); } } void uaudio_chan_stop(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if running */ if (ch->running != 0) { /* clear running flag */ ch->running = 0; if (uaudio_chan_need_both(sc)) { /* * Leave the endpoints running because we need * information about jitter! */ } else if (uaudio_chan_need_none(sc)) { /* * Stop both endpoints in case the one was used for * jitter information: */ uaudio_chan_stop_sub(&sc->sc_rec_chan); uaudio_chan_stop_sub(&sc->sc_play_chan); } else { uaudio_chan_stop_sub(ch); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } /*========================================================================* * AC - Audio Controller - routines *========================================================================*/ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct uaudio_softc *sc; struct uaudio_mixer_node *pmc; int hint; int error; int temp = 0; int chan = 0; sc = (struct uaudio_softc *)oidp->oid_arg1; hint = oidp->oid_arg2; if (sc->sc_mixer_lock == NULL) return (ENXIO); /* lookup mixer node */ mtx_lock(sc->sc_mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { for (chan = 0; chan != (int)pmc->nchan; chan++) { if (pmc->wValue[chan] != -1 && pmc->wValue[chan] == hint) { temp = pmc->wData[chan]; goto found; } } } found: mtx_unlock(sc->sc_mixer_lock); error = sysctl_handle_int(oidp, &temp, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* update mixer value */ mtx_lock(sc->sc_mixer_lock); if (pmc != NULL && temp >= pmc->minval && temp <= pmc->maxval) { pmc->wData[chan] = temp; pmc->update[(chan / 8)] |= (1 << (chan % 8)); /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } mtx_unlock(sc->sc_mixer_lock); return (0); } static void uaudio_mixer_ctl_free(struct uaudio_softc *sc) { struct uaudio_mixer_node *p_mc; while ((p_mc = sc->sc_mixer_root) != NULL) { sc->sc_mixer_root = p_mc->next; free(p_mc, M_USBDEV); } } static void uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev) { struct uaudio_mixer_node *pmc; struct sysctl_oid *mixer_tree; struct sysctl_oid *control_tree; char buf[32]; int chan; int n; mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer", - CTLFLAG_RD, NULL, ""); + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); if (mixer_tree == NULL) return; for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next, n++) { for (chan = 0; chan < pmc->nchan; chan++) { if (pmc->nchan > 1) { snprintf(buf, sizeof(buf), "%s_%d_%d", pmc->name, n, chan); } else { snprintf(buf, sizeof(buf), "%s_%d", pmc->name, n); } control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf, - CTLFLAG_RD, NULL, "Mixer control nodes"); + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, + "Mixer control nodes"); if (control_tree == NULL) continue; SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), - OID_AUTO, "val", CTLTYPE_INT | CTLFLAG_RWTUN, sc, - pmc->wValue[chan], + OID_AUTO, "val", + CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, + sc, pmc->wValue[chan], uaudio_mixer_sysctl_handler, "I", "Current value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval, "Minimum value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval, "Maximum value"); SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0, "Description"); } } } /* M-Audio FastTrack Ultra Mixer Description */ /* Origin: Linux USB Audio driver */ static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc) { int chx; int chy; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(8, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect"; MIX(sc).minval = 0; MIX(sc).maxval = 7; MIX(sc).mul = 7; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chx = 0; chx != 8; chx++) { for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_rec"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "AIn%d - Out%d Record Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_play"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = (chx == chy) ? 2 : 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "DIn%d - Out%d Playback Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(2, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_vol"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(3, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_dur"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f00; MIX(sc).mul = 0x7f00; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(4, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_fb"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no); for (chy = 0; chy != 4; chy++) { MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_ret"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Return %d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send AIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send DIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_reload_all(struct uaudio_softc *sc) { struct uaudio_mixer_node *pmc; int chan; if (sc->sc_mixer_lock == NULL) return; mtx_lock(sc->sc_mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { /* use reset defaults for non-oss controlled settings */ if (pmc->ctl == SOUND_MIXER_NRDEVICES) continue; for (chan = 0; chan < pmc->nchan; chan++) pmc->update[chan / 8] |= (1 << (chan % 8)); } usbd_transfer_start(sc->sc_mixer_xfer[0]); /* start HID volume keys, if any */ usbd_transfer_start(sc->sc_hid.xfer[0]); mtx_unlock(sc->sc_mixer_lock); } static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { struct uaudio_mixer_node *p_mc_new = malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK); int ch; if (p_mc_new != NULL) { memcpy(p_mc_new, mc, sizeof(*p_mc_new)); p_mc_new->next = sc->sc_mixer_root; sc->sc_mixer_root = p_mc_new; sc->sc_mixer_count++; /* set default value for all channels */ for (ch = 0; ch < p_mc_new->nchan; ch++) { switch (p_mc_new->val_default) { case 1: /* 50% */ p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2; break; case 2: /* 100% */ p_mc_new->wData[ch] = p_mc_new->maxval; break; default: /* 0% */ p_mc_new->wData[ch] = p_mc_new->minval; break; } } } else { DPRINTF("out of memory\n"); } } static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { int32_t res; if (mc->class < UAC_NCLASSES) { DPRINTF("adding %s.%d\n", uac_names[mc->class], mc->ctl); } else { DPRINTF("adding %d\n", mc->ctl); } if (mc->type == MIX_ON_OFF) { mc->minval = 0; mc->maxval = 1; } else if (mc->type == MIX_SELECTOR) { } else { /* determine min and max values */ mc->minval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MIN, mc); mc->maxval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MAX, mc); /* check if max and min was swapped */ if (mc->maxval < mc->minval) { res = mc->maxval; mc->maxval = mc->minval; mc->minval = res; } /* compute value range */ mc->mul = mc->maxval - mc->minval; if (mc->mul == 0) mc->mul = 1; /* compute value alignment */ res = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_RES, mc); DPRINTF("Resolution = %d\n", (int)res); } uaudio_mixer_add_ctl_sub(sc, mc); #ifdef USB_DEBUG if (uaudio_debug > 2) { uint8_t i; for (i = 0; i < mc->nchan; i++) { DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]); } DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' " "min=%d max=%d\n", mc->wIndex, mc->type, mc->ctl, mc->minval, mc->maxval); } #endif } static void uaudio_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1; const struct usb_audio_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_SIGNED_16; if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio20_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2; const struct usb_audio20_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); uaudio20_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_SIGNED_16; if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_selector_unit *d = iot[id].u.su_v1; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) { MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; } MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval); for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) { MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; } for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio_mixer_feature_name( &iot[d->baSourceId[i]], &MIX(sc)); } MIX(sc).class = 0; /* not used */ uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio20_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_selector_unit *d = iot[id].u.su_v2; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); uaudio20_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval); for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio20_mixer_feature_name( &iot[d->baSourceId[i]], &MIX(sc)); } MIX(sc).class = 0; /* not used */ uaudio_mixer_add_ctl(sc, &MIX(sc)); } static uint32_t uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d, uint8_t i) { uint32_t temp = 0; uint32_t offset = (i * d->bControlSize); if (d->bControlSize > 0) { temp |= d->bmaControls[offset]; if (d->bControlSize > 1) { temp |= d->bmaControls[offset + 1] << 8; if (d->bControlSize > 2) { temp |= d->bmaControls[offset + 2] << 16; if (d->bControlSize > 3) { temp |= d->bmaControls[offset + 3] << 24; } } } } return (temp); } static void uaudio_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_feature_unit *d = iot[id].u.fu_v1; uint32_t fumask; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t ctl; uint8_t i; if (d->bControlSize == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 7) / d->bControlSize; mmask = uaudio_mixer_feature_get_bmaControls(d, 0); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) { DPRINTFN(10, "chan=%d mask=%x\n", chan, uaudio_mixer_feature_get_bmaControls(d, chan)); cmask |= uaudio_mixer_feature_get_bmaControls(d, chan); } if (nchan > MIX_MAX_CHAN) { nchan = MIX_MAX_CHAN; } MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[d->bControlSize]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) { fumask = FU_MASK(ctl); DPRINTFN(5, "ctl=%d fumask=0x%04x\n", ctl, fumask); if (mmask & fumask) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(ctl, 0); } else if (cmask & fumask) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask) MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } mixernumber = uaudio_mixer_feature_name(&iot[id], &MIX(sc)); switch (ctl) { case MUTE_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mute"; break; case VOLUME_CONTROL: MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; break; case BASS_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; break; case MID_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; break; case TREBLE_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; break; case GRAPHIC_EQUALIZER_CONTROL: continue; /* XXX don't add anything */ case AGC_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; break; case DELAY_CONTROL: MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; break; case BASS_BOOST_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; break; case LOUDNESS_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; break; default: MIX(sc).type = MIX_UNKNOWN; break; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio20_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2; uint32_t ctl; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t i; uint8_t what; if (UGETDW(d->bmaControls[0]) == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 6) / 4; mmask = UGETDW(d->bmaControls[0]); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) cmask |= UGETDW(d->bmaControls[chan]); if (nchan > MIX_MAX_CHAN) nchan = MIX_MAX_CHAN; MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[nchan][0]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } for (ctl = 3; ctl != 0; ctl <<= 2) { mixernumber = uaudio20_mixer_feature_name(&iot[id], &MIX(sc)); switch (ctl) { case (3 << 0): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mute"; what = MUTE_CONTROL; break; case (3 << 2): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; what = VOLUME_CONTROL; break; case (3 << 4): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; what = BASS_CONTROL; break; case (3 << 6): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; what = MID_CONTROL; break; case (3 << 8): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; what = TREBLE_CONTROL; break; case (3 << 12): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; what = AGC_CONTROL; break; case (3 << 14): MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; what = DELAY_CONTROL; break; case (3 << 16): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; what = BASS_BOOST_CONTROL; break; case (3 << 18): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; what = LOUDNESS_CONTROL; break; case (3 << 20): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igain"; what = INPUT_GAIN_CONTROL; break; case (3 << 22): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igainpad"; what = INPUT_GAIN_PAD_CONTROL; break; default: continue; } if ((mmask & ctl) == ctl) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(what, 0); } else if ((cmask & ctl) == ctl) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if ((UGETDW(d->bmaControls[i]) & ctl) == ctl) MIX(sc).wValue[i - 1] = MAKE_WORD(what, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_add_processing_updown(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); const struct usb_audio_processing_unit_updown *ud = (const void *)(d1->bmControls + d1->bControlSize); uint8_t i; if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) { return; } if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes)) == NULL) { return; } DPRINTFN(3, "bUnitId=%d bNrModes=%d\n", d0->bUnitId, ud->bNrModes); if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { DPRINTF("no mode select\n"); return; } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_ON_OFF; /* XXX */ for (i = 0; i < ud->bNrModes; i++) { DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i])); /* XXX */ } uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio_mixer_add_processing(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); uint16_t ptype; memset(&MIX(sc), 0, sizeof(MIX(sc))); ptype = UGETW(d0->wProcessType); DPRINTFN(3, "wProcessType=%d bUnitId=%d " "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins); if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } switch (ptype) { case UPDOWNMIX_PROCESS: uaudio_mixer_add_processing_updown(sc, iot, id); break; case DOLBY_PROLOGIC_PROCESS: case P3D_STEREO_EXTENDER_PROCESS: case REVERBATION_PROCESS: case CHORUS_PROCESS: case DYN_RANGE_COMP_PROCESS: default: DPRINTF("unit %d, type=%d is not implemented\n", d0->bUnitId, ptype); break; } } static void uaudio_mixer_add_extension(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1; const struct usb_audio_extension_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); if (sc->sc_uq_au_no_xu) { return; } if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static const void * uaudio_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio_mixer_unit_1 *d1; const struct usb_audio_extension_unit_1 *e1; const struct usb_audio_processing_unit_1 *u1; union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it; const struct usb_audio_output_terminal *ot; const struct usb_audio_mixer_unit_0 *mu; const struct usb_audio_selector_unit *su; const struct usb_audio_feature_unit *fu; const struct usb_audio_processing_unit_0 *pu; const struct usb_audio_extension_unit_0 *eu; } u; u.desc = arg; if (u.desc == NULL) { goto error; } if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) { goto error; } switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) { goto error; } len += u.mu->bNrInPins; if (u.desc->bLength < len) { goto error; } d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1); break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) { goto error; } len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; if (u.desc->bLength < len) goto error; len += u.fu->bControlSize; break; case UDESCSUB_AC_PROCESSING: len += sizeof(*u.pu); if (u.desc->bLength < len) { goto error; } len += u.pu->bNrInPins; if (u.desc->bLength < len) { goto error; } u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); if (u.desc->bLength < len) { goto error; } len += u1->bControlSize; break; case UDESCSUB_AC_EXTENSION: len += sizeof(*u.eu); if (u.desc->bLength < len) { goto error; } len += u.eu->bNrInPins; if (u.desc->bLength < len) { goto error; } e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); if (u.desc->bLength < len) { goto error; } len += e1->bControlSize; break; default: goto error; } if (u.desc->bLength < len) { goto error; } return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static const void * uaudio20_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio20_mixer_unit_1 *d1; const struct usb_audio20_extension_unit_1 *e1; const struct usb_audio20_processing_unit_1 *u1; const struct usb_audio20_clock_selector_unit_1 *c1; union { const struct usb_descriptor *desc; const struct usb_audio20_clock_source_unit *csrc; const struct usb_audio20_clock_selector_unit_0 *csel; const struct usb_audio20_clock_multiplier_unit *cmul; const struct usb_audio20_input_terminal *it; const struct usb_audio20_output_terminal *ot; const struct usb_audio20_mixer_unit_0 *mu; const struct usb_audio20_selector_unit *su; const struct usb_audio20_feature_unit *fu; const struct usb_audio20_sample_rate_unit *ru; const struct usb_audio20_processing_unit_0 *pu; const struct usb_audio20_extension_unit_0 *eu; const struct usb_audio20_effect_unit *ef; } u; u.desc = arg; if (u.desc == NULL) goto error; if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) goto error; switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) goto error; len += u.mu->bNrInPins; if (u.desc->bLength < len) goto error; d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1) + d1->bNrChannels; break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) goto error; len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; break; case UDESCSUB_AC_EFFECT: len += sizeof(*u.ef) + 4; break; case UDESCSUB_AC_PROCESSING_V2: len += sizeof(*u.pu); if (u.desc->bLength < len) goto error; len += u.pu->bNrInPins; if (u.desc->bLength < len) goto error; u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); break; case UDESCSUB_AC_EXTENSION_V2: len += sizeof(*u.eu); if (u.desc->bLength < len) goto error; len += u.eu->bNrInPins; if (u.desc->bLength < len) goto error; e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); break; case UDESCSUB_AC_CLOCK_SRC: len += sizeof(*u.csrc); break; case UDESCSUB_AC_CLOCK_SEL: len += sizeof(*u.csel); if (u.desc->bLength < len) goto error; len += u.csel->bNrInPins; if (u.desc->bLength < len) goto error; c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins); len += sizeof(*c1); break; case UDESCSUB_AC_CLOCK_MUL: len += sizeof(*u.cmul); break; case UDESCSUB_AC_SAMPLE_RT: len += sizeof(*u.ru); break; default: goto error; } if (u.desc->bLength < len) goto error; return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) { goto error; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v1->bNrChannels; r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0]; r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1]; r.iChannelNames = iot[id].u.it_v1->iChannelNames; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v1->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio_cluster *) &iot[id].u.mu_v1->baSourceId[ iot[id].u.mu_v1->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v1->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v1->baSourceId[0]; } break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v1->bSourceId; break; case UDESCSUB_AC_PROCESSING: r = *((const struct usb_audio_cluster *) &iot[id].u.pu_v1->baSourceId[ iot[id].u.pu_v1->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION: r = *((const struct usb_audio_cluster *) &iot[id].u.eu_v1->baSourceId[ iot[id].u.eu_v1->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("bad data\n"); memset(&r, 0, sizeof(r)); done: return (r); } static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio20_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) goto error; switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v2->bNrChannels; r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0]; r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1]; r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2]; r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3]; r.iChannelNames = iot[id].u.it_v2->iTerminal; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v2->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio20_cluster *) &iot[id].u.mu_v2->baSourceId[ iot[id].u.mu_v2->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v2->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v2->baSourceId[0]; } break; case UDESCSUB_AC_SAMPLE_RT: id = iot[id].u.ru_v2->bSourceId; break; case UDESCSUB_AC_EFFECT: id = iot[id].u.ef_v2->bSourceId; break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v2->bSourceId; break; case UDESCSUB_AC_PROCESSING_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.pu_v2->baSourceId[ iot[id].u.pu_v2->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.eu_v2->baSourceId[ iot[id].u.eu_v2->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("Bad data!\n"); memset(&r, 0, sizeof(r)); done: return (r); } static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { uint16_t terminal_type = 0x0000; const struct uaudio_terminal_node *input[2]; const struct uaudio_terminal_node *output[2]; input[0] = uaudio_mixer_get_input(iot, 0); input[1] = uaudio_mixer_get_input(iot, 1); output[0] = uaudio_mixer_get_output(iot, 0); output[1] = uaudio_mixer_get_output(iot, 1); /* * check if there is only * one output terminal: */ if (output[0] && (!output[1])) { terminal_type = UGETW(output[0]->u.ot_v1->wTerminalType); } /* * If the only output terminal is USB, * the class is UAC_RECORD. */ if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { mix->class = UAC_RECORD; if (input[0] && (!input[1])) { terminal_type = UGETW(input[0]->u.it_v1->wTerminalType); } else { terminal_type = 0; } goto done; } /* * if the unit is connected to just * one input terminal, the * class is UAC_INPUT: */ if (input[0] && (!input[1])) { mix->class = UAC_INPUT; terminal_type = UGETW(input[0]->u.it_v1->wTerminalType); goto done; } /* * Otherwise, the class is UAC_OUTPUT. */ mix->class = UAC_OUTPUT; done: return (terminal_type); } static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { uint16_t terminal_type = 0x0000; const struct uaudio_terminal_node *input[2]; const struct uaudio_terminal_node *output[2]; input[0] = uaudio_mixer_get_input(iot, 0); input[1] = uaudio_mixer_get_input(iot, 1); output[0] = uaudio_mixer_get_output(iot, 0); output[1] = uaudio_mixer_get_output(iot, 1); /* * check if there is only * one output terminal: */ if (output[0] && (!output[1])) terminal_type = UGETW(output[0]->u.ot_v2->wTerminalType); /* * If the only output terminal is USB, * the class is UAC_RECORD. */ if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { mix->class = UAC_RECORD; if (input[0] && (!input[1])) { terminal_type = UGETW(input[0]->u.it_v2->wTerminalType); } else { terminal_type = 0; } goto done; } /* * if the unit is connected to just * one input terminal, the * class is UAC_INPUT: */ if (input[0] && (!input[1])) { mix->class = UAC_INPUT; terminal_type = UGETW(input[0]->u.it_v2->wTerminalType); goto done; } /* * Otherwise, the class is UAC_OUTPUT. */ mix->class = UAC_OUTPUT; done: return (terminal_type); } struct uaudio_tt_to_feature { uint16_t terminal_type; uint16_t feature; }; static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = { {UAT_STREAM, SOUND_MIXER_PCM}, {UATI_MICROPHONE, SOUND_MIXER_MIC}, {UATI_DESKMICROPHONE, SOUND_MIXER_MIC}, {UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC}, {UATI_OMNIMICROPHONE, SOUND_MIXER_MIC}, {UATI_MICROPHONEARRAY, SOUND_MIXER_MIC}, {UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC}, {UATO_SPEAKER, SOUND_MIXER_SPEAKER}, {UATO_DESKTOPSPEAKER, SOUND_MIXER_SPEAKER}, {UATO_ROOMSPEAKER, SOUND_MIXER_SPEAKER}, {UATO_COMMSPEAKER, SOUND_MIXER_SPEAKER}, {UATE_ANALOGCONN, SOUND_MIXER_LINE}, {UATE_LINECONN, SOUND_MIXER_LINE}, {UATE_LEGACYCONN, SOUND_MIXER_LINE}, {UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM}, {UATE_SPDIF, SOUND_MIXER_ALTPCM}, {UATE_1394DA, SOUND_MIXER_ALTPCM}, {UATE_1394DV, SOUND_MIXER_ALTPCM}, {UATF_CDPLAYER, SOUND_MIXER_CD}, {UATF_SYNTHESIZER, SOUND_MIXER_SYNTH}, {UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO}, {UATF_DVDAUDIO, SOUND_MIXER_VIDEO}, {UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO}, /* telephony terminal types */ {UATT_UNDEFINED, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATT_PHONELINE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATT_TELEPHONE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATT_DOWNLINEPHONE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATF_RADIORECV, SOUND_MIXER_RADIO}, {UATF_RADIOXMIT, SOUND_MIXER_RADIO}, {UAT_UNDEFINED, SOUND_MIXER_VOLUME}, {UAT_VENDOR, SOUND_MIXER_VOLUME}, {UATI_UNDEFINED, SOUND_MIXER_VOLUME}, /* output terminal types */ {UATO_UNDEFINED, SOUND_MIXER_VOLUME}, {UATO_DISPLAYAUDIO, SOUND_MIXER_VOLUME}, {UATO_SUBWOOFER, SOUND_MIXER_VOLUME}, {UATO_HEADPHONES, SOUND_MIXER_VOLUME}, /* bidir terminal types */ {UATB_UNDEFINED, SOUND_MIXER_VOLUME}, {UATB_HANDSET, SOUND_MIXER_VOLUME}, {UATB_HEADSET, SOUND_MIXER_VOLUME}, {UATB_SPEAKERPHONE, SOUND_MIXER_VOLUME}, {UATB_SPEAKERPHONEESUP, SOUND_MIXER_VOLUME}, {UATB_SPEAKERPHONEECANC, SOUND_MIXER_VOLUME}, /* external terminal types */ {UATE_UNDEFINED, SOUND_MIXER_VOLUME}, /* embedded function terminal types */ {UATF_UNDEFINED, SOUND_MIXER_VOLUME}, {UATF_CALIBNOISE, SOUND_MIXER_VOLUME}, {UATF_EQUNOISE, SOUND_MIXER_VOLUME}, {UATF_DAT, SOUND_MIXER_VOLUME}, {UATF_DCC, SOUND_MIXER_VOLUME}, {UATF_MINIDISK, SOUND_MIXER_VOLUME}, {UATF_ANALOGTAPE, SOUND_MIXER_VOLUME}, {UATF_PHONOGRAPH, SOUND_MIXER_VOLUME}, {UATF_VCRAUDIO, SOUND_MIXER_VOLUME}, {UATF_SATELLITE, SOUND_MIXER_VOLUME}, {UATF_CABLETUNER, SOUND_MIXER_VOLUME}, {UATF_DSS, SOUND_MIXER_VOLUME}, {UATF_MULTITRACK, SOUND_MIXER_VOLUME}, {0xffff, SOUND_MIXER_VOLUME}, /* default */ {0x0000, SOUND_MIXER_VOLUME}, }; static uint16_t uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature; uint16_t terminal_type = uaudio_mixer_determine_class(iot, mix); if ((mix->class == UAC_RECORD) && (terminal_type == 0)) { return (SOUND_MIXER_IMIX); } while (uat->terminal_type) { if (uat->terminal_type == terminal_type) { break; } uat++; } DPRINTF("terminal_type=0x%04x -> %d\n", terminal_type, uat->feature); return (uat->feature); } static uint16_t uaudio20_mixer_feature_name(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { const struct uaudio_tt_to_feature *uat; uint16_t terminal_type = uaudio20_mixer_determine_class(iot, mix); if ((mix->class == UAC_RECORD) && (terminal_type == 0)) return (SOUND_MIXER_IMIX); for (uat = uaudio_tt_to_feature; uat->terminal_type != 0; uat++) { if (uat->terminal_type == terminal_type) break; } DPRINTF("terminal_type=0x%04x -> %d\n", terminal_type, uat->feature); return (uat->feature); } static const struct uaudio_terminal_node * uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t i) { struct uaudio_terminal_node *root = iot->root; uint8_t n; n = iot->usr.id_max; do { if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) { if (!i--) return (root + n); } } while (n--); return (NULL); } static const struct uaudio_terminal_node * uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t i) { struct uaudio_terminal_node *root = iot->root; uint8_t n; n = iot->usr.id_max; do { if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) { if (!i--) return (root + n); } } while (n--); return (NULL); } static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_FEATURE: if (is_last) { p_id = &iot->u.fu_v1->bSourceId; n_id = 1; goto top; } uaudio_mixer_find_inputs_sub( root, &iot->u.fu_v1->bSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: if (is_last) { p_id = &iot->u.ot_v1->bSourceId; n_id = 1; goto top; } uaudio_mixer_find_inputs_sub( root, &iot->u.ot_v1->bSourceId, 1, info); break; case UDESCSUB_AC_MIXER: if (is_last) { p_id = iot->u.mu_v1->baSourceId; n_id = iot->u.mu_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.mu_v1->baSourceId, iot->u.mu_v1->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: if (is_last) { p_id = iot->u.su_v1->baSourceId; n_id = iot->u.su_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.su_v1->baSourceId, iot->u.su_v1->bNrInPins, info); break; case UDESCSUB_AC_PROCESSING: if (is_last) { p_id = iot->u.pu_v1->baSourceId; n_id = iot->u.pu_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.pu_v1->baSourceId, iot->u.pu_v1->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION: if (is_last) { p_id = iot->u.eu_v1->baSourceId; n_id = iot->u.eu_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.eu_v1->baSourceId, iot->u.eu_v1->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_OUTPUT: if (is_last) { p_id = &iot->u.ot_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.ot_v2->bSourceId, 1, info); break; case UDESCSUB_AC_MIXER: if (is_last) { p_id = iot->u.mu_v2->baSourceId; n_id = iot->u.mu_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.mu_v2->baSourceId, iot->u.mu_v2->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: if (is_last) { p_id = iot->u.su_v2->baSourceId; n_id = iot->u.su_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.su_v2->baSourceId, iot->u.su_v2->bNrInPins, info); break; case UDESCSUB_AC_SAMPLE_RT: if (is_last) { p_id = &iot->u.ru_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.ru_v2->bSourceId, 1, info); break; case UDESCSUB_AC_EFFECT: if (is_last) { p_id = &iot->u.ef_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.ef_v2->bSourceId, 1, info); break; case UDESCSUB_AC_FEATURE: if (is_last) { p_id = &iot->u.fu_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.fu_v2->bSourceId, 1, info); break; case UDESCSUB_AC_PROCESSING_V2: if (is_last) { p_id = iot->u.pu_v2->baSourceId; n_id = iot->u.pu_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.pu_v2->baSourceId, iot->u.pu_v2->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION_V2: if (is_last) { p_id = iot->u.eu_v2->baSourceId; n_id = iot->u.eu_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.eu_v2->baSourceId, iot->u.eu_v2->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; uint8_t id; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->is_input = 1; if (is_last) { p_id = &iot->u.it_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.it_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: info->is_input = 0; if (is_last) { p_id = &iot->u.ot_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.ot_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SEL: if (is_last) { p_id = iot->u.csel_v2->baCSourceId; n_id = iot->u.csel_v2->bNrInPins; goto top; } uaudio20_mixer_find_clocks_sub(root, iot->u.csel_v2->baCSourceId, iot->u.csel_v2->bNrInPins, info); break; case UDESCSUB_AC_CLOCK_MUL: if (is_last) { p_id = &iot->u.cmul_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.cmul_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SRC: id = iot->u.csrc_v2->bClockId; switch (info->is_input) { case 0: info->bit_output[id / 8] |= (1 << (id % 8)); break; case 1: info->bit_input[id / 8] |= (1 << (id % 8)); break; default: break; } break; default: break; } } } static void uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot = (root + id); uint8_t j; j = n_id; do { if ((j != id) && ((root + j)->u.desc) && ((root + j)->u.desc->bDescriptorSubtype == UDESCSUB_AC_OUTPUT)) { /* * "j" (output) <--- virtual wire <--- "id" (input) * * if "j" has "id" on the input, then "id" have "j" on * the output, because they are connected: */ if ((root + j)->usr.bit_input[id / 8] & (1 << (id % 8))) { iot->usr.bit_output[j / 8] |= (1 << (j % 8)); } } } while (j--); } static void uaudio_mixer_fill_info(struct uaudio_softc *sc, struct usb_device *udev, void *desc) { const struct usb_audio_control_descriptor *acdp; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); const struct usb_descriptor *dp; const struct usb_audio_unit *au; struct uaudio_terminal_node *iot = NULL; uint16_t wTotalLen; uint8_t ID_max = 0; /* inclusive */ uint8_t i; desc = usb_desc_foreach(cd, desc); if (desc == NULL) { DPRINTF("no Audio Control header\n"); goto done; } acdp = desc; if ((acdp->bLength < sizeof(*acdp)) || (acdp->bDescriptorType != UDESC_CS_INTERFACE) || (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) { DPRINTF("invalid Audio Control header\n"); goto done; } /* "wTotalLen" is allowed to be corrupt */ wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength; /* get USB audio revision */ sc->sc_audio_rev = UGETW(acdp->bcdADC); DPRINTFN(3, "found AC header, vers=%03x, len=%d\n", sc->sc_audio_rev, wTotalLen); iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP, M_WAITOK | M_ZERO); if (iot == NULL) { DPRINTF("no memory!\n"); goto done; } while ((desc = usb_desc_foreach(cd, desc))) { dp = desc; if (dp->bLength > wTotalLen) { break; } else { wTotalLen -= dp->bLength; } if (sc->sc_audio_rev >= UAUDIO_VERSION_30) au = NULL; else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) au = uaudio20_mixer_verify_desc(dp, 0); else au = uaudio_mixer_verify_desc(dp, 0); if (au) { iot[au->bUnitId].u.desc = (const void *)au; if (au->bUnitId > ID_max) ID_max = au->bUnitId; } } DPRINTF("Maximum ID=%d\n", ID_max); /* * determine sourcing inputs for * all nodes in the tree: */ i = ID_max; do { if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { uaudio20_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); sc->sc_mixer_clocks.is_input = 255; sc->sc_mixer_clocks.recurse_level = 0; uaudio20_mixer_find_clocks_sub(iot, &i, 1, &sc->sc_mixer_clocks); } else { uaudio_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); } } while (i--); /* * determine outputs for * all nodes in the tree: */ i = ID_max; do { uaudio_mixer_find_outputs_sub(iot, i, ID_max, &((iot + i)->usr)); } while (i--); /* set "id_max" and "root" */ i = ID_max; do { (iot + i)->usr.id_max = ID_max; (iot + i)->root = iot; } while (i--); /* * Scan the config to create a linked list of "mixer" nodes: */ i = ID_max; do { dp = iot[i].u.desc; if (dp == NULL) continue; DPRINTFN(11, "id=%d subtype=%d\n", i, dp->bDescriptorSubtype); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { continue; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: case UDESCSUB_AC_PROCESSING_V2: case UDESCSUB_AC_EXTENSION_V2: case UDESCSUB_AC_EFFECT: case UDESCSUB_AC_CLOCK_SRC: case UDESCSUB_AC_CLOCK_SEL: case UDESCSUB_AC_CLOCK_MUL: case UDESCSUB_AC_SAMPLE_RT: break; case UDESCSUB_AC_MIXER: uaudio20_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio20_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio20_mixer_add_feature(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } continue; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: break; case UDESCSUB_AC_MIXER: uaudio_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_add_feature(sc, iot, i); break; case UDESCSUB_AC_PROCESSING: uaudio_mixer_add_processing(sc, iot, i); break; case UDESCSUB_AC_EXTENSION: uaudio_mixer_add_extension(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } } while (i--); done: free(iot, M_TEMP); } static int uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev, uint8_t what, struct uaudio_mixer_node *mc) { struct usb_device_request req; int val; uint8_t data[2 + (2 * 3)]; usb_error_t err; if (mc->wValue[0] == -1) return (0); if (audio_rev >= UAUDIO_VERSION_30) return (0); else if (audio_rev >= UAUDIO_VERSION_20) { if (what == GET_CUR) { req.bRequest = UA20_CS_CUR; USETW(req.wLength, 2); } else { req.bRequest = UA20_CS_RANGE; USETW(req.wLength, 8); } } else { uint16_t len = MIX_SIZE(mc->type); req.bRequest = what; USETW(req.wLength, len); } req.bmRequestType = UT_READ_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[0]); USETW(req.wIndex, mc->wIndex); memset(data, 0, sizeof(data)); err = usbd_do_request(udev, NULL, &req, data); if (err) { DPRINTF("err=%s\n", usbd_errstr(err)); return (0); } if (audio_rev >= UAUDIO_VERSION_30) { val = 0; } else if (audio_rev >= UAUDIO_VERSION_20) { switch (what) { case GET_CUR: val = (data[0] | (data[1] << 8)); break; case GET_MIN: val = (data[2] | (data[3] << 8)); break; case GET_MAX: val = (data[4] | (data[5] << 8)); break; case GET_RES: val = (data[6] | (data[7] << 8)); break; default: val = 0; break; } } else { val = (data[0] | (data[1] << 8)); } if (what == GET_CUR || what == GET_MIN || what == GET_MAX) val = uaudio_mixer_signext(mc->type, val); DPRINTFN(3, "val=%d\n", val); return (val); } static void uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_device_request req; struct uaudio_softc *sc = usbd_xfer_softc(xfer); struct uaudio_mixer_node *mc = sc->sc_mixer_curr; struct usb_page_cache *pc; uint16_t len; uint8_t repeat = 1; uint8_t update; uint8_t chan; uint8_t buf[2]; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: case USB_ST_SETUP: tr_setup: if (mc == NULL) { mc = sc->sc_mixer_root; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; repeat = 0; } while (mc) { while (sc->sc_mixer_chan < mc->nchan) { chan = sc->sc_mixer_chan; sc->sc_mixer_chan++; update = ((mc->update[chan / 8] & (1 << (chan % 8))) && (mc->wValue[chan] != -1)); mc->update[chan / 8] &= ~(1 << (chan % 8)); if (update) { req.bmRequestType = UT_WRITE_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[chan]); USETW(req.wIndex, mc->wIndex); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { return; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { len = 2; req.bRequest = UA20_CS_CUR; USETW(req.wLength, len); } else { len = MIX_SIZE(mc->type); req.bRequest = SET_CUR; USETW(req.wLength, len); } buf[0] = (mc->wData[chan] & 0xFF); buf[1] = (mc->wData[chan] >> 8) & 0xFF; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, buf, len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, len); usbd_xfer_set_frames(xfer, len ? 2 : 1); usbd_transfer_submit(xfer); return; } } mc = mc->next; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; } if (repeat) { goto tr_setup; } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) { /* do nothing - we are detaching */ break; } goto tr_transferred; } } static usb_error_t uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed) { struct usb_device_request req; uint8_t data[3]; DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed); req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; req.bRequest = SET_CUR; USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0); USETW(req.wIndex, endpt); USETW(req.wLength, 3); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; return (usbd_do_request(udev, NULL, &req, data)); } static usb_error_t uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t speed) { struct usb_device_request req; uint8_t data[4]; DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n", iface_no, clockid, speed); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UA20_CS_CUR; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); USETW(req.wLength, 4); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; data[3] = speed >> 24; return (usbd_do_request(udev, NULL, &req, data)); } static int uaudio_mixer_signext(uint8_t type, int val) { if (!MIX_UNSIGNED(type)) { if (MIX_SIZE(type) == 2) { val = (int16_t)val; } else { val = (int8_t)val; } } return (val); } static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val) { if (mc->type == MIX_ON_OFF) { val = (val != 0); } else if (mc->type == MIX_SELECTOR) { if ((val < mc->minval) || (val > mc->maxval)) { val = mc->minval; } } else { /* compute actual volume */ val = (val * mc->mul) / 255; /* add lower offset */ val = val + mc->minval; /* make sure we don't write a value out of range */ if (val > mc->maxval) val = mc->maxval; else if (val < mc->minval) val = mc->minval; } DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n", mc->type, val, mc->minval, mc->maxval, val); return (val); } static void uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc, uint8_t chan, int32_t val) { val = uaudio_mixer_bsd2value(mc, val); mc->update[chan / 8] |= (1 << (chan % 8)); mc->wData[chan] = val; /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } static void uaudio_mixer_init(struct uaudio_softc *sc) { struct uaudio_mixer_node *mc; int32_t i; for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if (mc->ctl != SOUND_MIXER_NRDEVICES) { /* * Set device mask bits. See * /usr/include/machine/soundcard.h */ sc->sc_mix_info |= (1 << mc->ctl); } if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) { continue; } sc->sc_recsrc_info |= 1 << mc->slctrtype[i - 1]; } } } } int uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m) { DPRINTF("\n"); sc->sc_mixer_lock = mixer_get_lock(m); sc->sc_mixer_dev = m; if (usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index, sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc, sc->sc_mixer_lock)) { DPRINTFN(0, "could not allocate USB " "transfer for audio mixer!\n"); return (ENOMEM); } if (!(sc->sc_mix_info & SOUND_MASK_VOLUME)) { mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); } mix_setdevs(m, sc->sc_mix_info); mix_setrecdevs(m, sc->sc_recsrc_info); return (0); } int uaudio_mixer_uninit_sub(struct uaudio_softc *sc) { DPRINTF("\n"); usbd_transfer_unsetup(sc->sc_mixer_xfer, 1); sc->sc_mixer_lock = NULL; return (0); } void uaudio_mixer_set(struct uaudio_softc *sc, unsigned type, unsigned left, unsigned right) { struct uaudio_mixer_node *mc; int chan; for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) { if (mc->ctl == type) { for (chan = 0; chan < mc->nchan; chan++) { uaudio_mixer_ctl_set(sc, mc, chan, (int)((chan == 0 ? left : right) * 255) / 100); } } } } uint32_t uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src) { struct uaudio_mixer_node *mc; uint32_t mask; uint32_t temp; int32_t i; for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { /* compute selector mask */ mask = 0; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { mask |= (1 << mc->slctrtype[i - 1]); } temp = mask & src; if (temp == 0) { continue; } /* find the first set bit */ temp = (-temp) & temp; /* update "src" */ src &= ~mask; src |= temp; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (temp != (1 << mc->slctrtype[i - 1])) { continue; } uaudio_mixer_ctl_set(sc, mc, 0, i); break; } } } return (src); } /*========================================================================* * MIDI support routines *========================================================================*/ static void umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint8_t buf[4]; uint8_t cmd_len; uint8_t cn; uint16_t pos; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", actlen); pos = 0; pc = usbd_xfer_get_frame(xfer, 0); while (actlen >= 4) { /* copy out the MIDI data */ usbd_copy_out(pc, pos, buf, 4); /* command length */ cmd_len = umidi_cmd_to_len[buf[0] & 0xF]; /* cable number */ cn = buf[0] >> 4; /* * Lookup sub-channel. The index is range * checked below. */ sub = &chan->sub[cn]; if ((cmd_len != 0) && (cn < chan->max_emb_jack) && (sub->read_open != 0)) { /* Send data to the application */ usb_fifo_put_data_linear( sub->fifo.fp[USB_FIFO_RX], buf + 1, cmd_len, 1); } actlen -= 4; pos += 4; } case USB_ST_SETUP: DPRINTF("start\n"); tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } /* * The following statemachine, that converts MIDI commands to * USB MIDI packets, derives from Linux's usbmidi.c, which * was written by "Clemens Ladisch": * * Returns: * 0: No command * Else: Command is complete */ static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b) { uint8_t p0 = (cn << 4); if (b >= 0xf8) { sub->temp_0[0] = p0 | 0x0f; sub->temp_0[1] = b; sub->temp_0[2] = 0; sub->temp_0[3] = 0; sub->temp_cmd = sub->temp_0; return (1); } else if (b >= 0xf0) { switch (b) { case 0xf0: /* system exclusive begin */ sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case 0xf1: /* MIDI time code */ case 0xf3: /* song select */ sub->temp_1[1] = b; sub->state = UMIDI_ST_1PARAM; break; case 0xf2: /* song position pointer */ sub->temp_1[1] = b; sub->state = UMIDI_ST_2PARAM_1; break; case 0xf4: /* unknown */ case 0xf5: /* unknown */ sub->state = UMIDI_ST_UNKNOWN; break; case 0xf6: /* tune request */ sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf6; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case 0xf7: /* system exclusive end */ switch (sub->state) { case UMIDI_ST_SYSEX_0: sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf7; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_1: sub->temp_1[0] = p0 | 0x06; sub->temp_1[2] = 0xf7; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x07; sub->temp_1[3] = 0xf7; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); } sub->state = UMIDI_ST_UNKNOWN; break; } } else if (b >= 0x80) { sub->temp_1[1] = b; if ((b >= 0xc0) && (b <= 0xdf)) { sub->state = UMIDI_ST_1PARAM; } else { sub->state = UMIDI_ST_2PARAM_1; } } else { /* b < 0x80 */ switch (sub->state) { case UMIDI_ST_1PARAM: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; } else { p0 |= 0x02; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[2] = b; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_2PARAM_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_2PARAM_2; break; case UMIDI_ST_2PARAM_2: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; sub->state = UMIDI_ST_2PARAM_1; } else { p0 |= 0x03; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_SYSEX_0: sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case UMIDI_ST_SYSEX_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_SYSEX_2; break; case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x04; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_SYSEX_0; return (1); default: break; } } return (0); } static void umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint32_t actlen; uint16_t nframes; uint8_t buf; uint8_t start_cable; uint8_t tr_any; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); /* * NOTE: Some MIDI devices only accept 4 bytes of data per * short terminated USB transfer. */ switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", len); case USB_ST_SETUP: tr_setup: DPRINTF("start\n"); nframes = 0; /* reset */ start_cable = chan->curr_cable; tr_any = 0; pc = usbd_xfer_get_frame(xfer, 0); while (1) { /* round robin de-queueing */ sub = &chan->sub[chan->curr_cable]; if (sub->write_open) { usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX], &buf, 1, &actlen, 0); } else { actlen = 0; } if (actlen) { tr_any = 1; DPRINTF("byte=0x%02x from FIFO %u\n", buf, (unsigned int)chan->curr_cable); if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) { DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n", sub->temp_cmd[0], sub->temp_cmd[1], sub->temp_cmd[2], sub->temp_cmd[3]); usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4); nframes++; if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0)) break; } else { continue; } } chan->curr_cable++; if (chan->curr_cable >= chan->max_emb_jack) chan->curr_cable = 0; if (chan->curr_cable == start_cable) { if (tr_any == 0) break; tr_any = 0; } } if (nframes != 0) { DPRINTF("Transferring %d frames\n", (int)nframes); usbd_xfer_set_frame_len(xfer, 0, 4 * nframes); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static struct umidi_sub_chan * umidi_sub_by_fifo(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) { sub = &chan->sub[n]; if ((sub->fifo.fp[USB_FIFO_RX] == fifo) || (sub->fifo.fp[USB_FIFO_TX] == fifo)) { return (sub); } } panic("%s:%d cannot find usb_fifo!\n", __FILE__, __LINE__); return (NULL); } static void umidi_start_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); } static void umidi_stop_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->read_open = 0; if (--(chan->read_open_refcount) == 0) { /* * XXX don't stop the read transfer here, hence that causes * problems with some MIDI adapters */ DPRINTF("(stopping read transfer)\n"); } } static void umidi_start_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); if (chan->xfer[UMIDI_TX_TRANSFER] == NULL) { uint8_t buf[1]; int actlen; do { /* dump data */ usb_fifo_get_data_linear(fifo, buf, 1, &actlen, 0); } while (actlen > 0); } else { usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]); } } static void umidi_stop_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->write_open = 0; if (--(chan->write_open_refcount) == 0) { DPRINTF("(stopping write transfer)\n"); usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]); } } static int umidi_open(struct usb_fifo *fifo, int fflags) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); if (fflags & FREAD) { if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) { return (ENOMEM); } mtx_lock(&chan->mtx); chan->read_open_refcount++; sub->read_open = 1; mtx_unlock(&chan->mtx); } if (fflags & FWRITE) { if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) { return (ENOMEM); } /* clear stall first */ mtx_lock(&chan->mtx); chan->write_open_refcount++; sub->write_open = 1; /* reset */ sub->state = UMIDI_ST_UNKNOWN; mtx_unlock(&chan->mtx); } return (0); /* success */ } static void umidi_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { usb_fifo_free_buffer(fifo); } if (fflags & FWRITE) { usb_fifo_free_buffer(fifo); } } static int umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags) { return (ENODEV); } static void umidi_init(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE); } static struct usb_fifo_methods umidi_fifo_methods = { .f_start_read = &umidi_start_read, .f_start_write = &umidi_start_write, .f_stop_read = &umidi_stop_read, .f_stop_write = &umidi_stop_write, .f_open = &umidi_open, .f_close = &umidi_close, .f_ioctl = &umidi_ioctl, .basename[0] = "umidi", }; static int umidi_probe(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct umidi_chan *chan = &sc->sc_midi_chan; struct umidi_sub_chan *sub; int unit = device_get_unit(dev); int error; uint32_t n; if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI)) chan->single_command = 1; if (usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, chan->iface_alt_index)) { DPRINTF("setting of alternate index failed!\n"); goto detach; } usbd_set_parent_iface(sc->sc_udev, chan->iface_index, sc->sc_mixer_iface_index); error = usbd_transfer_setup(uaa->device, &chan->iface_index, chan->xfer, umidi_config, UMIDI_N_TRANSFER, chan, &chan->mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } if (chan->xfer[UMIDI_TX_TRANSFER] == NULL && chan->xfer[UMIDI_RX_TRANSFER] == NULL) { DPRINTF("no BULK or INTERRUPT MIDI endpoint(s) found\n"); goto detach; } /* * Some USB MIDI device makers couldn't resist using * wMaxPacketSize = 4 for RX and TX BULK endpoints, although * that size is an unsupported value for FULL speed BULK * endpoints. The same applies to some HIGH speed MIDI devices * which are using a wMaxPacketSize different from 512 bytes. * * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host * Controllers are required to have support for 8-, 16-, 32-, * and 64-byte maximum packet sizes for full-speed bulk * endpoints and 512 bytes for high-speed bulk endpoints." */ if (chan->xfer[UMIDI_TX_TRANSFER] != NULL && usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER])) chan->single_command = 1; if (chan->single_command != 0) device_printf(dev, "Single command MIDI quirk enabled\n"); if ((chan->max_emb_jack == 0) || (chan->max_emb_jack > UMIDI_EMB_JACK_MAX)) { chan->max_emb_jack = UMIDI_EMB_JACK_MAX; } for (n = 0; n < chan->max_emb_jack; n++) { sub = &chan->sub[n]; error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx, &umidi_fifo_methods, &sub->fifo, unit, n, chan->iface_index, UID_ROOT, GID_OPERATOR, 0644); if (error) { goto detach; } } mtx_lock(&chan->mtx); /* * NOTE: At least one device will not work properly unless the * BULK IN pipe is open all the time. This might have to do * about that the internal queues of the device overflow if we * don't read them regularly. */ usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); return (0); /* success */ detach: return (ENXIO); /* failure */ } static int umidi_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) usb_fifo_detach(&chan->sub[n].fifo); mtx_lock(&chan->mtx); usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER); mtx_destroy(&chan->mtx); return (0); } static void uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_softc *sc = usbd_xfer_softc(xfer); const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0); struct snd_mixer *m; uint8_t id; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d\n", actlen); if (actlen != 0 && (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) { id = *buffer; buffer++; actlen--; } else { id = 0; } m = sc->sc_mixer_dev; if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) && (sc->sc_hid.mute_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.mute_loc)) { DPRINTF("Mute toggle\n"); mixer_hwvol_mute_locked(m); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) && (sc->sc_hid.volume_up_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_up_loc)) { DPRINTF("Volume Up\n"); mixer_hwvol_step_locked(m, 1, 1); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) && (sc->sc_hid.volume_down_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_down_loc)) { DPRINTF("Volume Down\n"); mixer_hwvol_step_locked(m, -1, -1); } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa) { void *d_ptr; uint32_t flags; uint16_t d_len; uint8_t id; int error; if (!(sc->sc_hid.flags & UAUDIO_HID_VALID)) return (-1); if (sc->sc_mixer_lock == NULL) return (-1); /* Get HID descriptor */ error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, sc->sc_hid.iface_index); if (error) { DPRINTF("error reading report description\n"); return (-1); } /* check if there is an ID byte */ hid_report_size(d_ptr, d_len, hid_input, &id); if (id != 0) sc->sc_hid.flags |= UAUDIO_HID_HAS_ID; if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */), hid_input, 0, &sc->sc_hid.volume_up_loc, &flags, &sc->sc_hid.volume_up_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP; DPRINTFN(1, "Found Volume Up key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */), hid_input, 0, &sc->sc_hid.volume_down_loc, &flags, &sc->sc_hid.volume_down_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN; DPRINTFN(1, "Found Volume Down key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */), hid_input, 0, &sc->sc_hid.mute_loc, &flags, &sc->sc_hid.mute_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE; DPRINTFN(1, "Found Mute key\n"); } free(d_ptr, M_TEMP); if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP | UAUDIO_HID_HAS_VOLUME_DOWN | UAUDIO_HID_HAS_MUTE))) { DPRINTFN(1, "Did not find any volume related keys\n"); return (-1); } /* prevent the uhid driver from attaching */ usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index, sc->sc_mixer_iface_index); /* allocate USB transfers */ error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index, sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER, sc, sc->sc_mixer_lock); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); return (-1); } return (0); } static void uaudio_hid_detach(struct uaudio_softc *sc) { usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER); } DRIVER_MODULE_ORDERED(uaudio, uhub, uaudio_driver, uaudio_devclass, NULL, 0, SI_ORDER_ANY); MODULE_DEPEND(uaudio, usb, 1, 1, 1); MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(uaudio, 1); USB_PNP_HOST_INFO(uaudio_devs); USB_PNP_HOST_INFO(uaudio_vendor_midi); Index: head/sys/dev/usb/controller/atmegadci.c =================================================================== --- head/sys/dev/usb/controller/atmegadci.c (revision 357971) +++ head/sys/dev/usb/controller/atmegadci.c (revision 357972) @@ -1,2159 +1,2159 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the driver for the ATMEGA series USB OTG Controller. This * driver currently only supports the DCI mode of the USB hardware. */ /* * NOTE: When the chip detects BUS-reset it will also reset the * endpoints, Function-address and more. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR atmegadci_debug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #define ATMEGA_BUS2SC(bus) \ ((struct atmegadci_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct atmegadci_softc *)0)->sc_bus)))) #define ATMEGA_PC2SC(pc) \ ATMEGA_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) #ifdef USB_DEBUG static int atmegadci_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, atmegadci, CTLFLAG_RW, 0, +static SYSCTL_NODE(_hw_usb, OID_AUTO, atmegadci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB ATMEGA DCI"); SYSCTL_INT(_hw_usb_atmegadci, OID_AUTO, debug, CTLFLAG_RWTUN, &atmegadci_debug, 0, "ATMEGA DCI debug level"); #endif #define ATMEGA_INTR_ENDPT 1 /* prototypes */ static const struct usb_bus_methods atmegadci_bus_methods; static const struct usb_pipe_methods atmegadci_device_non_isoc_methods; static const struct usb_pipe_methods atmegadci_device_isoc_fs_methods; static atmegadci_cmd_t atmegadci_setup_rx; static atmegadci_cmd_t atmegadci_data_rx; static atmegadci_cmd_t atmegadci_data_tx; static atmegadci_cmd_t atmegadci_data_tx_sync; static void atmegadci_device_done(struct usb_xfer *, usb_error_t); static void atmegadci_do_poll(struct usb_bus *); static void atmegadci_standard_done(struct usb_xfer *); static void atmegadci_root_intr(struct atmegadci_softc *sc); /* * Here is a list of what the chip supports: */ static const struct usb_hw_ep_profile atmegadci_ep_profile[2] = { [0] = { .max_in_frame_size = 64, .max_out_frame_size = 64, .is_simplex = 1, .support_control = 1, }, [1] = { .max_in_frame_size = 64, .max_out_frame_size = 64, .is_simplex = 1, .support_bulk = 1, .support_interrupt = 1, .support_isochronous = 1, .support_in = 1, .support_out = 1, }, }; static void atmegadci_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { if (ep_addr == 0) *ppf = atmegadci_ep_profile; else if (ep_addr < ATMEGA_EP_MAX) *ppf = atmegadci_ep_profile + 1; else *ppf = NULL; } static void atmegadci_clocks_on(struct atmegadci_softc *sc) { if (sc->sc_flags.clocks_off && sc->sc_flags.port_powered) { DPRINTFN(5, "\n"); /* turn on clocks */ (sc->sc_clocks_on) (&sc->sc_bus); ATMEGA_WRITE_1(sc, ATMEGA_USBCON, ATMEGA_USBCON_USBE | ATMEGA_USBCON_OTGPADE | ATMEGA_USBCON_VBUSTE); sc->sc_flags.clocks_off = 0; /* enable transceiver ? */ } } static void atmegadci_clocks_off(struct atmegadci_softc *sc) { if (!sc->sc_flags.clocks_off) { DPRINTFN(5, "\n"); /* disable Transceiver ? */ ATMEGA_WRITE_1(sc, ATMEGA_USBCON, ATMEGA_USBCON_USBE | ATMEGA_USBCON_OTGPADE | ATMEGA_USBCON_FRZCLK | ATMEGA_USBCON_VBUSTE); /* turn clocks off */ (sc->sc_clocks_off) (&sc->sc_bus); sc->sc_flags.clocks_off = 1; } } static void atmegadci_pull_up(struct atmegadci_softc *sc) { /* pullup D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { sc->sc_flags.d_pulled_up = 1; ATMEGA_WRITE_1(sc, ATMEGA_UDCON, 0); } } static void atmegadci_pull_down(struct atmegadci_softc *sc) { /* pulldown D+, if possible */ if (sc->sc_flags.d_pulled_up) { sc->sc_flags.d_pulled_up = 0; ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH); } } static void atmegadci_wakeup_peer(struct atmegadci_softc *sc) { uint8_t temp; if (!sc->sc_flags.status_suspend) { return; } temp = ATMEGA_READ_1(sc, ATMEGA_UDCON); ATMEGA_WRITE_1(sc, ATMEGA_UDCON, temp | ATMEGA_UDCON_RMWKUP); /* wait 8 milliseconds */ /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); /* hardware should have cleared RMWKUP bit */ } static void atmegadci_set_address(struct atmegadci_softc *sc, uint8_t addr) { DPRINTFN(5, "addr=%d\n", addr); addr |= ATMEGA_UDADDR_ADDEN; ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, addr); } static uint8_t atmegadci_setup_rx(struct atmegadci_td *td) { struct atmegadci_softc *sc; struct usb_device_request req; uint16_t count; uint8_t temp; /* get pointer to softc */ sc = ATMEGA_PC2SC(td->pc); /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); /* check endpoint status */ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); DPRINTFN(5, "UEINTX=0x%02x\n", temp); if (!(temp & ATMEGA_UEINTX_RXSTPI)) { goto not_complete; } /* clear did stall */ td->did_stall = 0; /* get the packet byte count */ count = (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) | (ATMEGA_READ_1(sc, ATMEGA_UEBCLX)); /* mask away undefined bits */ count &= 0x7FF; /* verify data length */ if (count != td->remainder) { DPRINTFN(0, "Invalid SETUP packet " "length, %d bytes\n", count); goto not_complete; } if (count != sizeof(req)) { DPRINTFN(0, "Unsupported SETUP packet " "length, %d bytes\n", count); goto not_complete; } /* receive data */ ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX, (void *)&req, sizeof(req)); /* copy data into real buffer */ usbd_copy_in(td->pc, 0, &req, sizeof(req)); td->offset = sizeof(req); td->remainder = 0; /* sneak peek the set address */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { sc->sc_dv_addr = req.wValue[0] & 0x7F; /* must write address before ZLP */ ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, sc->sc_dv_addr); } else { sc->sc_dv_addr = 0xFF; } /* Clear SETUP packet interrupt and all other previous interrupts */ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0); return (0); /* complete */ not_complete: /* abort any ongoing transfer */ if (!td->did_stall) { DPRINTFN(5, "stalling\n"); ATMEGA_WRITE_1(sc, ATMEGA_UECONX, ATMEGA_UECONX_EPEN | ATMEGA_UECONX_STALLRQ); td->did_stall = 1; } if (temp & ATMEGA_UEINTX_RXSTPI) { /* clear SETUP packet interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ~ATMEGA_UEINTX_RXSTPI); } /* we only want to know if there is a SETUP packet */ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE); return (1); /* not complete */ } static uint8_t atmegadci_data_rx(struct atmegadci_td *td) { struct atmegadci_softc *sc; struct usb_page_search buf_res; uint16_t count; uint8_t temp; uint8_t to; uint8_t got_short; to = 3; /* don't loop forever! */ got_short = 0; /* get pointer to softc */ sc = ATMEGA_PC2SC(td->pc); /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); repeat: /* check if any of the FIFO banks have data */ /* check endpoint status */ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder); if (temp & ATMEGA_UEINTX_RXSTPI) { if (td->remainder == 0) { /* * We are actually complete and have * received the next SETUP */ DPRINTFN(5, "faking complete\n"); return (0); /* complete */ } /* * USB Host Aborted the transfer. */ td->error = 1; return (0); /* complete */ } /* check status */ if (!(temp & (ATMEGA_UEINTX_FIFOCON | ATMEGA_UEINTX_RXOUTI))) { /* no data */ goto not_complete; } /* get the packet byte count */ count = (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) | (ATMEGA_READ_1(sc, ATMEGA_UEBCLX)); /* mask away undefined bits */ count &= 0x7FF; /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } while (count > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* receive data */ ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX, buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear OUT packet interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_RXOUTI ^ 0xFF); /* release FIFO bank */ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_FIFOCON ^ 0xFF); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ return (0); } /* else need to receive a zero length packet */ } if (--to) { goto repeat; } not_complete: /* we only want to know if there is a SETUP packet or OUT packet */ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_RXOUTE); return (1); /* not complete */ } static uint8_t atmegadci_data_tx(struct atmegadci_td *td) { struct atmegadci_softc *sc; struct usb_page_search buf_res; uint16_t count; uint8_t to; uint8_t temp; to = 3; /* don't loop forever! */ /* get pointer to softc */ sc = ATMEGA_PC2SC(td->pc); /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); repeat: /* check endpoint status */ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder); if (temp & ATMEGA_UEINTX_RXSTPI) { /* * The current transfer was aborted * by the USB Host */ td->error = 1; return (0); /* complete */ } temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); if (temp & 3) { /* cannot write any data - a bank is busy */ goto not_complete; } count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* transmit data */ ATMEGA_WRITE_MULTI_1(sc, ATMEGA_UEDATX, buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear IN packet interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_TXINI); /* allocate FIFO bank */ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_FIFOCON); /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { return (0); /* complete */ } /* else we need to transmit a short packet */ } if (--to) { goto repeat; } not_complete: /* we only want to know if there is a SETUP packet or free IN packet */ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE); return (1); /* not complete */ } static uint8_t atmegadci_data_tx_sync(struct atmegadci_td *td) { struct atmegadci_softc *sc; uint8_t temp; /* get pointer to softc */ sc = ATMEGA_PC2SC(td->pc); /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); /* check endpoint status */ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); DPRINTFN(5, "temp=0x%02x\n", temp); if (temp & ATMEGA_UEINTX_RXSTPI) { DPRINTFN(5, "faking complete\n"); /* Race condition */ return (0); /* complete */ } /* * The control endpoint has only got one bank, so if that bank * is free the packet has been transferred! */ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); if (temp & 3) { /* cannot write any data - a bank is busy */ goto not_complete; } if (sc->sc_dv_addr != 0xFF) { /* set new address */ atmegadci_set_address(sc, sc->sc_dv_addr); } return (0); /* complete */ not_complete: /* we only want to know if there is a SETUP packet or free IN packet */ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE); return (1); /* not complete */ } static uint8_t atmegadci_xfer_do_fifo(struct usb_xfer *xfer) { struct atmegadci_td *td; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; while (1) { if ((td->func) (td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) { goto done; } } /* * Fetch the next transfer descriptor and transfer * some flags to the next transfer descriptor */ td = td->obj_next; xfer->td_transfer_cache = td; } return (1); /* not complete */ done: /* compute all actual lengths */ atmegadci_standard_done(xfer); return (0); /* complete */ } static void atmegadci_interrupt_poll(struct atmegadci_softc *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (!atmegadci_xfer_do_fifo(xfer)) { /* queue has been modified */ goto repeat; } } } static void atmegadci_vbus_interrupt(struct atmegadci_softc *sc, uint8_t is_on) { DPRINTFN(5, "vbus = %u\n", is_on); if (is_on) { if (!sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 1; /* complete root HUB interrupt endpoint */ atmegadci_root_intr(sc); } } else { if (sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ atmegadci_root_intr(sc); } } } void atmegadci_interrupt(struct atmegadci_softc *sc) { uint8_t status; USB_BUS_LOCK(&sc->sc_bus); /* read interrupt status */ status = ATMEGA_READ_1(sc, ATMEGA_UDINT); /* clear all set interrupts */ ATMEGA_WRITE_1(sc, ATMEGA_UDINT, (~status) & 0x7D); DPRINTFN(14, "UDINT=0x%02x\n", status); /* check for any bus state change interrupts */ if (status & ATMEGA_UDINT_EORSTI) { DPRINTFN(5, "end of reset\n"); /* set correct state */ sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* disable resume interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, ATMEGA_UDINT_SUSPE | ATMEGA_UDINT_EORSTE); /* complete root HUB interrupt endpoint */ atmegadci_root_intr(sc); } /* * If resume and suspend is set at the same time we interpret * that like RESUME. Resume is set when there is at least 3 * milliseconds of inactivity on the USB BUS. */ if (status & ATMEGA_UDINT_WAKEUPI) { DPRINTFN(5, "resume interrupt\n"); if (sc->sc_flags.status_suspend) { /* update status bits */ sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; /* disable resume interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, ATMEGA_UDINT_SUSPE | ATMEGA_UDINT_EORSTE); /* complete root HUB interrupt endpoint */ atmegadci_root_intr(sc); } } else if (status & ATMEGA_UDINT_SUSPI) { DPRINTFN(5, "suspend interrupt\n"); if (!sc->sc_flags.status_suspend) { /* update status bits */ sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; /* disable suspend interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, ATMEGA_UDINT_WAKEUPE | ATMEGA_UDINT_EORSTE); /* complete root HUB interrupt endpoint */ atmegadci_root_intr(sc); } } /* check VBUS */ status = ATMEGA_READ_1(sc, ATMEGA_USBINT); /* clear all set interrupts */ ATMEGA_WRITE_1(sc, ATMEGA_USBINT, (~status) & 0x03); if (status & ATMEGA_USBINT_VBUSTI) { uint8_t temp; DPRINTFN(5, "USBINT=0x%02x\n", status); temp = ATMEGA_READ_1(sc, ATMEGA_USBSTA); atmegadci_vbus_interrupt(sc, temp & ATMEGA_USBSTA_VBUS); } /* check for any endpoint interrupts */ status = ATMEGA_READ_1(sc, ATMEGA_UEINT); /* the hardware will clear the UEINT bits automatically */ if (status) { DPRINTFN(5, "real endpoint interrupt UEINT=0x%02x\n", status); atmegadci_interrupt_poll(sc); } USB_BUS_UNLOCK(&sc->sc_bus); } static void atmegadci_setup_standard_chain_sub(struct atmegadci_std_temp *temp) { struct atmegadci_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->error = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; } static void atmegadci_setup_standard_chain(struct usb_xfer *xfer) { struct atmegadci_std_temp temp; struct atmegadci_softc *sc; struct atmegadci_td *td; uint32_t x; uint8_t ep_no; uint8_t need_sync; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = ATMEGA_BUS2SC(xfer->xroot->bus); ep_no = (xfer->endpointno & UE_ADDR); /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { temp.func = &atmegadci_setup_rx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) temp.setup_alt_next = 0; } atmegadci_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) { temp.func = &atmegadci_data_tx; need_sync = 1; } else { temp.func = &atmegadci_data_rx; need_sync = 0; } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } else { need_sync = 0; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; } atmegadci_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } if (xfer->flags_int.control_xfr) { /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we need to sync */ if (need_sync) { /* we need a SYNC point after TX */ temp.func = &atmegadci_data_tx_sync; atmegadci_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (xfer->endpointno & UE_DIR_IN) { temp.func = &atmegadci_data_rx; need_sync = 0; } else { temp.func = &atmegadci_data_tx; need_sync = 1; } atmegadci_setup_standard_chain_sub(&temp); if (need_sync) { /* we need a SYNC point after TX */ temp.func = &atmegadci_data_tx_sync; atmegadci_setup_standard_chain_sub(&temp); } } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; } static void atmegadci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ atmegadci_device_done(xfer, USB_ERR_TIMEOUT); } static void atmegadci_start_standard_chain(struct usb_xfer *xfer) { DPRINTFN(9, "\n"); /* poll one time - will turn on interrupts */ if (atmegadci_xfer_do_fifo(xfer)) { /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &atmegadci_timeout, xfer->timeout); } } } static void atmegadci_root_intr(struct atmegadci_softc *sc) { DPRINTFN(9, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit */ sc->sc_hub_idata[0] = 0x02; /* we only have one port */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t atmegadci_standard_done_sub(struct usb_xfer *xfer) { struct atmegadci_td *td; uint32_t len; uint8_t error; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error) { /* the transfer is finished */ error = 1; td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error ? USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); } static void atmegadci_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = atmegadci_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = atmegadci_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = atmegadci_standard_done_sub(xfer); } done: atmegadci_device_done(xfer, err); } /*------------------------------------------------------------------------* * atmegadci_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void atmegadci_device_done(struct usb_xfer *xfer, usb_error_t error) { struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus); uint8_t ep_no; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { ep_no = (xfer->endpointno & UE_ADDR); /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no); /* disable endpoint interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0); DPRINTFN(15, "disabled interrupts!\n"); } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } static void atmegadci_xfer_stall(struct usb_xfer *xfer) { atmegadci_device_done(xfer, USB_ERR_STALLED); } static void atmegadci_set_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall) { struct atmegadci_softc *sc; uint8_t ep_no; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(5, "endpoint=%p\n", ep); sc = ATMEGA_BUS2SC(udev->bus); /* get endpoint number */ ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no); /* set stall */ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, ATMEGA_UECONX_EPEN | ATMEGA_UECONX_STALLRQ); } static void atmegadci_clear_stall_sub(struct atmegadci_softc *sc, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { uint8_t temp; if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no); /* set endpoint reset */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(ep_no)); /* clear endpoint reset */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); /* set stall */ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, ATMEGA_UECONX_EPEN | ATMEGA_UECONX_STALLRQ); /* reset data toggle */ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, ATMEGA_UECONX_EPEN | ATMEGA_UECONX_RSTDT); /* clear stall */ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, ATMEGA_UECONX_EPEN | ATMEGA_UECONX_STALLRQC); do { if (ep_type == UE_BULK) { temp = ATMEGA_UECFG0X_EPTYPE2; } else if (ep_type == UE_INTERRUPT) { temp = ATMEGA_UECFG0X_EPTYPE3; } else { temp = ATMEGA_UECFG0X_EPTYPE1; } if (ep_dir & UE_DIR_IN) { temp |= ATMEGA_UECFG0X_EPDIR; } /* two banks, 64-bytes wMaxPacket */ ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, temp); ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X, ATMEGA_UECFG1X_ALLOC | ATMEGA_UECFG1X_EPBK0 | /* one bank */ ATMEGA_UECFG1X_EPSIZE(3)); temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); if (!(temp & ATMEGA_UESTA0X_CFGOK)) { device_printf(sc->sc_bus.bdev, "Chip rejected configuration\n"); } } while (0); } static void atmegadci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct atmegadci_softc *sc; struct usb_endpoint_descriptor *ed; DPRINTFN(5, "endpoint=%p\n", ep); USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = ATMEGA_BUS2SC(udev->bus); /* get endpoint descriptor */ ed = ep->edesc; /* reset endpoint */ atmegadci_clear_stall_sub(sc, (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); } usb_error_t atmegadci_init(struct atmegadci_softc *sc) { uint8_t n; DPRINTF("start\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_1_1; sc->sc_bus.methods = &atmegadci_bus_methods; USB_BUS_LOCK(&sc->sc_bus); /* make sure USB is enabled */ ATMEGA_WRITE_1(sc, ATMEGA_USBCON, ATMEGA_USBCON_USBE | ATMEGA_USBCON_FRZCLK); /* enable USB PAD regulator */ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, ATMEGA_UHWCON_UVREGE | ATMEGA_UHWCON_UIMOD); /* the following register sets up the USB PLL, assuming 16MHz X-tal */ ATMEGA_WRITE_1(sc, 0x49 /* PLLCSR */, 0x14 | 0x02); /* wait for PLL to lock */ for (n = 0; n != 20; n++) { if (ATMEGA_READ_1(sc, 0x49) & 0x01) break; /* wait a little bit for PLL to start */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); } /* make sure USB is enabled */ ATMEGA_WRITE_1(sc, ATMEGA_USBCON, ATMEGA_USBCON_USBE | ATMEGA_USBCON_OTGPADE | ATMEGA_USBCON_VBUSTE); /* turn on clocks */ (sc->sc_clocks_on) (&sc->sc_bus); /* make sure device is re-enumerated */ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH); /* wait a little for things to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20); /* enable interrupts */ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, ATMEGA_UDINT_SUSPE | ATMEGA_UDINT_EORSTE); /* reset all endpoints */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, (1 << ATMEGA_EP_MAX) - 1); /* disable reset */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); /* disable all endpoints */ for (n = 0; n != ATMEGA_EP_MAX; n++) { /* select endpoint */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, n); /* disable endpoint interrupt */ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0); /* disable endpoint */ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, 0); } /* turn off clocks */ atmegadci_clocks_off(sc); /* read initial VBUS state */ n = ATMEGA_READ_1(sc, ATMEGA_USBSTA); atmegadci_vbus_interrupt(sc, n & ATMEGA_USBSTA_VBUS); USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ atmegadci_do_poll(&sc->sc_bus); return (0); /* success */ } void atmegadci_uninit(struct atmegadci_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); /* turn on clocks */ (sc->sc_clocks_on) (&sc->sc_bus); /* disable interrupts */ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, 0); /* reset all endpoints */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, (1 << ATMEGA_EP_MAX) - 1); /* disable reset */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; atmegadci_pull_down(sc); atmegadci_clocks_off(sc); /* disable USB PAD regulator */ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, 0); USB_BUS_UNLOCK(&sc->sc_bus); } static void atmegadci_suspend(struct atmegadci_softc *sc) { /* TODO */ } static void atmegadci_resume(struct atmegadci_softc *sc) { /* TODO */ } static void atmegadci_do_poll(struct usb_bus *bus) { struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); atmegadci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * atmegadci bulk support * atmegadci control support * atmegadci interrupt support *------------------------------------------------------------------------*/ static void atmegadci_device_non_isoc_open(struct usb_xfer *xfer) { return; } static void atmegadci_device_non_isoc_close(struct usb_xfer *xfer) { atmegadci_device_done(xfer, USB_ERR_CANCELLED); } static void atmegadci_device_non_isoc_enter(struct usb_xfer *xfer) { return; } static void atmegadci_device_non_isoc_start(struct usb_xfer *xfer) { /* setup TDs */ atmegadci_setup_standard_chain(xfer); atmegadci_start_standard_chain(xfer); } static const struct usb_pipe_methods atmegadci_device_non_isoc_methods = { .open = atmegadci_device_non_isoc_open, .close = atmegadci_device_non_isoc_close, .enter = atmegadci_device_non_isoc_enter, .start = atmegadci_device_non_isoc_start, }; /*------------------------------------------------------------------------* * atmegadci full speed isochronous support *------------------------------------------------------------------------*/ static void atmegadci_device_isoc_fs_open(struct usb_xfer *xfer) { return; } static void atmegadci_device_isoc_fs_close(struct usb_xfer *xfer) { atmegadci_device_done(xfer, USB_ERR_CANCELLED); } static void atmegadci_device_isoc_fs_enter(struct usb_xfer *xfer) { struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index */ nframes = (ATMEGA_READ_1(sc, ATMEGA_UDFNUMH) << 8) | (ATMEGA_READ_1(sc, ATMEGA_UDFNUML)); nframes &= ATMEGA_FRAME_MASK; /* * check if the frame index is within the window where the frames * will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & ATMEGA_FRAME_MASK; if ((xfer->endpoint->is_synced == 0) || (temp < xfer->nframes)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & ATMEGA_FRAME_MASK; xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & ATMEGA_FRAME_MASK; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + xfer->nframes; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += xfer->nframes; /* setup TDs */ atmegadci_setup_standard_chain(xfer); } static void atmegadci_device_isoc_fs_start(struct usb_xfer *xfer) { /* start TD chain */ atmegadci_start_standard_chain(xfer); } static const struct usb_pipe_methods atmegadci_device_isoc_fs_methods = { .open = atmegadci_device_isoc_fs_open, .close = atmegadci_device_isoc_fs_close, .enter = atmegadci_device_isoc_fs_enter, .start = atmegadci_device_isoc_fs_start, }; /*------------------------------------------------------------------------* * atmegadci root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor atmegadci_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct atmegadci_config_desc atmegadci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(atmegadci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | ATMEGA_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_hub_descriptor_min atmegadci_hubd = { .bDescLength = sizeof(atmegadci_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "A\0T\0M\0E\0G\0A" #define STRING_PRODUCT \ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, atmegadci_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, atmegadci_product); static usb_error_t atmegadci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; uint8_t temp; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_handle_clear_port_feature; case UR_SET_FEATURE: goto tr_handle_set_port_feature; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: goto tr_handle_get_port_status; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) { goto tr_stalled; } len = sizeof(atmegadci_devd); ptr = (const void *)&atmegadci_devd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) { goto tr_stalled; } len = sizeof(atmegadci_confd); ptr = (const void *)&atmegadci_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(atmegadci_vendor); ptr = (const void *)&atmegadci_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(atmegadci_product); ptr = (const void *)&atmegadci_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) { goto tr_stalled; } sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) { goto tr_stalled; } sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: atmegadci_wakeup_peer(sc); break; case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; atmegadci_pull_down(sc); atmegadci_clocks_off(sc); break; case UHF_C_PORT_CONNECTION: /* clear connect change flag */ sc->sc_flags.change_connect = 0; if (!sc->sc_flags.status_bus_reset) { /* we are not connected */ break; } /* configure the control endpoint */ /* select endpoint number */ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, 0); /* set endpoint reset */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(0)); /* clear endpoint reset */ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); /* enable and stall endpoint */ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, ATMEGA_UECONX_EPEN | ATMEGA_UECONX_STALLRQ); /* one bank, 64-bytes wMaxPacket */ ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, ATMEGA_UECFG0X_EPTYPE0); ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X, ATMEGA_UECFG1X_ALLOC | ATMEGA_UECFG1X_EPBK0 | ATMEGA_UECFG1X_EPSIZE(3)); /* check valid config */ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); if (!(temp & ATMEGA_UESTA0X_CFGOK)) { device_printf(sc->sc_bus.bdev, "Chip rejected EP0 configuration\n"); } break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_set_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); switch (value) { case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 1; break; case UHF_PORT_SUSPEND: case UHF_PORT_RESET: case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_get_port_status: DPRINTFN(9, "UR_GET_PORT_STATUS\n"); if (index != 1) { goto tr_stalled; } if (sc->sc_flags.status_vbus) { atmegadci_clocks_on(sc); atmegadci_pull_up(sc); } else { atmegadci_pull_down(sc); atmegadci_clocks_off(sc); } /* Select FULL-speed and Device Side Mode */ value = UPS_PORT_MODE_DEVICE; if (sc->sc_flags.port_powered) { value |= UPS_PORT_POWER; } if (sc->sc_flags.port_enabled) { value |= UPS_PORT_ENABLED; } if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { value |= UPS_CURRENT_CONNECT_STATUS; } if (sc->sc_flags.status_suspend) { value |= UPS_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_connect) { value |= UPS_C_CONNECT_STATUS; } if (sc->sc_flags.change_suspend) { value |= UPS_C_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) { goto tr_stalled; } ptr = (const void *)&atmegadci_hubd; len = sizeof(atmegadci_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: done: *plength = len; *pptr = ptr; return (err); } static void atmegadci_xfer_setup(struct usb_setup_params *parm) { const struct usb_hw_ep_profile *pf; struct atmegadci_softc *sc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; uint8_t ep_no; sc = ATMEGA_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x500; usbd_transfer_setup_sub(parm); /* * compute maximum number of TDs */ if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + 1 /* SYNC 2 */ ; } else { ntd = xfer->nframes + 1 /* SYNC */ ; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) return; /* * allocate transfer descriptors */ last_obj = NULL; /* * get profile stuff */ ep_no = xfer->endpointno & UE_ADDR; atmegadci_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct atmegadci_td *td; if (parm->buf) { td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* init TD */ td->max_packet_size = xfer->max_packet_size; td->ep_no = ep_no; if (pf->support_multi_buffer) { td->support_multi_buffer = 1; } td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void atmegadci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void atmegadci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_rt_addr, udev->device_index); if (udev->device_index != sc->sc_rt_addr) { if (udev->speed != USB_SPEED_FULL) { /* not supported */ return; } if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) ep->methods = &atmegadci_device_isoc_fs_methods; else ep->methods = &atmegadci_device_non_isoc_methods; } } static void atmegadci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: atmegadci_suspend(sc); break; case USB_HW_POWER_SHUTDOWN: atmegadci_uninit(sc); break; case USB_HW_POWER_RESUME: atmegadci_resume(sc); break; default: break; } } static const struct usb_bus_methods atmegadci_bus_methods = { .endpoint_init = &atmegadci_ep_init, .xfer_setup = &atmegadci_xfer_setup, .xfer_unsetup = &atmegadci_xfer_unsetup, .get_hw_ep_profile = &atmegadci_get_hw_ep_profile, .xfer_stall = &atmegadci_xfer_stall, .set_stall = &atmegadci_set_stall, .clear_stall = &atmegadci_clear_stall, .roothub_exec = &atmegadci_roothub_exec, .xfer_poll = &atmegadci_do_poll, .set_hw_power_sleep = &atmegadci_set_hw_power_sleep, }; Index: head/sys/dev/usb/controller/avr32dci.c =================================================================== --- head/sys/dev/usb/controller/avr32dci.c (revision 357971) +++ head/sys/dev/usb/controller/avr32dci.c (revision 357972) @@ -1,2103 +1,2105 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the driver for the AVR32 series USB Device * Controller */ /* * NOTE: When the chip detects BUS-reset it will also reset the * endpoints, Function-address and more. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR avr32dci_debug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #define AVR32_BUS2SC(bus) \ ((struct avr32dci_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct avr32dci_softc *)0)->sc_bus)))) #define AVR32_PC2SC(pc) \ AVR32_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) #ifdef USB_DEBUG static int avr32dci_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, avr32dci, CTLFLAG_RW, 0, "USB AVR32 DCI"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, avr32dci, + CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB AVR32 DCI"); SYSCTL_INT(_hw_usb_avr32dci, OID_AUTO, debug, CTLFLAG_RWTUN, &avr32dci_debug, 0, "AVR32 DCI debug level"); #endif #define AVR32_INTR_ENDPT 1 /* prototypes */ static const struct usb_bus_methods avr32dci_bus_methods; static const struct usb_pipe_methods avr32dci_device_non_isoc_methods; static const struct usb_pipe_methods avr32dci_device_isoc_fs_methods; static avr32dci_cmd_t avr32dci_setup_rx; static avr32dci_cmd_t avr32dci_data_rx; static avr32dci_cmd_t avr32dci_data_tx; static avr32dci_cmd_t avr32dci_data_tx_sync; static void avr32dci_device_done(struct usb_xfer *, usb_error_t); static void avr32dci_do_poll(struct usb_bus *); static void avr32dci_standard_done(struct usb_xfer *); static void avr32dci_root_intr(struct avr32dci_softc *sc); /* * Here is a list of what the chip supports: */ static const struct usb_hw_ep_profile avr32dci_ep_profile[4] = { [0] = { .max_in_frame_size = 64, .max_out_frame_size = 64, .is_simplex = 1, .support_control = 1, }, [1] = { .max_in_frame_size = 512, .max_out_frame_size = 512, .is_simplex = 1, .support_bulk = 1, .support_interrupt = 1, .support_isochronous = 1, .support_in = 1, .support_out = 1, }, [2] = { .max_in_frame_size = 64, .max_out_frame_size = 64, .is_simplex = 1, .support_bulk = 1, .support_interrupt = 1, .support_in = 1, .support_out = 1, }, [3] = { .max_in_frame_size = 1024, .max_out_frame_size = 1024, .is_simplex = 1, .support_bulk = 1, .support_interrupt = 1, .support_isochronous = 1, .support_in = 1, .support_out = 1, }, }; static void avr32dci_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { if (ep_addr == 0) *ppf = avr32dci_ep_profile; else if (ep_addr < 3) *ppf = avr32dci_ep_profile + 1; else if (ep_addr < 5) *ppf = avr32dci_ep_profile + 2; else if (ep_addr < 7) *ppf = avr32dci_ep_profile + 3; else *ppf = NULL; } static void avr32dci_mod_ctrl(struct avr32dci_softc *sc, uint32_t set, uint32_t clear) { uint32_t temp; temp = AVR32_READ_4(sc, AVR32_CTRL); temp |= set; temp &= ~clear; AVR32_WRITE_4(sc, AVR32_CTRL, temp); } static void avr32dci_mod_ien(struct avr32dci_softc *sc, uint32_t set, uint32_t clear) { uint32_t temp; temp = AVR32_READ_4(sc, AVR32_IEN); temp |= set; temp &= ~clear; AVR32_WRITE_4(sc, AVR32_IEN, temp); } static void avr32dci_clocks_on(struct avr32dci_softc *sc) { if (sc->sc_flags.clocks_off && sc->sc_flags.port_powered) { DPRINTFN(5, "\n"); /* turn on clocks */ (sc->sc_clocks_on) (&sc->sc_bus); avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0); sc->sc_flags.clocks_off = 0; } } static void avr32dci_clocks_off(struct avr32dci_softc *sc) { if (!sc->sc_flags.clocks_off) { DPRINTFN(5, "\n"); avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_EN_USBA); /* turn clocks off */ (sc->sc_clocks_off) (&sc->sc_bus); sc->sc_flags.clocks_off = 1; } } static void avr32dci_pull_up(struct avr32dci_softc *sc) { /* pullup D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { sc->sc_flags.d_pulled_up = 1; avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_DETACH); } } static void avr32dci_pull_down(struct avr32dci_softc *sc) { /* pulldown D+, if possible */ if (sc->sc_flags.d_pulled_up) { sc->sc_flags.d_pulled_up = 0; avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0); } } static void avr32dci_wakeup_peer(struct avr32dci_softc *sc) { if (!sc->sc_flags.status_suspend) { return; } avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_REWAKEUP, 0); /* wait 8 milliseconds */ /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); /* hardware should have cleared RMWKUP bit */ } static void avr32dci_set_address(struct avr32dci_softc *sc, uint8_t addr) { DPRINTFN(5, "addr=%d\n", addr); avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_FADDR_EN | addr, 0); } static uint8_t avr32dci_setup_rx(struct avr32dci_td *td) { struct avr32dci_softc *sc; struct usb_device_request req; uint16_t count; uint32_t temp; /* get pointer to softc */ sc = AVR32_PC2SC(td->pc); /* check endpoint status */ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); if (!(temp & AVR32_EPTSTA_RX_SETUP)) { goto not_complete; } /* clear did stall */ td->did_stall = 0; /* get the packet byte count */ count = AVR32_EPTSTA_BYTE_COUNT(temp); /* verify data length */ if (count != td->remainder) { DPRINTFN(0, "Invalid SETUP packet " "length, %d bytes\n", count); goto not_complete; } if (count != sizeof(req)) { DPRINTFN(0, "Unsupported SETUP packet " "length, %d bytes\n", count); goto not_complete; } /* receive data */ memcpy(&req, sc->physdata, sizeof(req)); /* copy data into real buffer */ usbd_copy_in(td->pc, 0, &req, sizeof(req)); td->offset = sizeof(req); td->remainder = 0; /* sneak peek the set address */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { sc->sc_dv_addr = req.wValue[0] & 0x7F; /* must write address before ZLP */ avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_FADDR_EN | AVR32_CTRL_DEV_ADDR); avr32dci_mod_ctrl(sc, sc->sc_dv_addr, 0); } else { sc->sc_dv_addr = 0xFF; } /* clear SETUP packet interrupt */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP); return (0); /* complete */ not_complete: if (temp & AVR32_EPTSTA_RX_SETUP) { /* clear SETUP packet interrupt */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP); } /* abort any ongoing transfer */ if (!td->did_stall) { DPRINTFN(5, "stalling\n"); AVR32_WRITE_4(sc, AVR32_EPTSETSTA(td->ep_no), AVR32_EPTSTA_FRCESTALL); td->did_stall = 1; } return (1); /* not complete */ } static uint8_t avr32dci_data_rx(struct avr32dci_td *td) { struct avr32dci_softc *sc; struct usb_page_search buf_res; uint16_t count; uint32_t temp; uint8_t to; uint8_t got_short; to = 4; /* don't loop forever! */ got_short = 0; /* get pointer to softc */ sc = AVR32_PC2SC(td->pc); repeat: /* check if any of the FIFO banks have data */ /* check endpoint status */ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); if (temp & AVR32_EPTSTA_RX_SETUP) { if (td->remainder == 0) { /* * We are actually complete and have * received the next SETUP */ DPRINTFN(5, "faking complete\n"); return (0); /* complete */ } /* * USB Host Aborted the transfer. */ td->error = 1; return (0); /* complete */ } /* check status */ if (!(temp & AVR32_EPTSTA_RX_BK_RDY)) { /* no data */ goto not_complete; } /* get the packet byte count */ count = AVR32_EPTSTA_BYTE_COUNT(temp); /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } while (count > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* receive data */ memcpy(buf_res.buffer, sc->physdata + (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) + (td->ep_no << 16) + (td->offset % td->max_packet_size), buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear OUT packet interrupt */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_BK_RDY); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ return (0); } /* else need to receive a zero length packet */ } if (--to) { goto repeat; } not_complete: return (1); /* not complete */ } static uint8_t avr32dci_data_tx(struct avr32dci_td *td) { struct avr32dci_softc *sc; struct usb_page_search buf_res; uint16_t count; uint8_t to; uint32_t temp; to = 4; /* don't loop forever! */ /* get pointer to softc */ sc = AVR32_PC2SC(td->pc); repeat: /* check endpoint status */ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); if (temp & AVR32_EPTSTA_RX_SETUP) { /* * The current transfer was aborted * by the USB Host */ td->error = 1; return (0); /* complete */ } if (temp & AVR32_EPTSTA_TX_PK_RDY) { /* cannot write any data - all banks are busy */ goto not_complete; } count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* transmit data */ memcpy(sc->physdata + (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) + (td->ep_no << 16) + (td->offset % td->max_packet_size), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* allocate FIFO bank */ AVR32_WRITE_4(sc, AVR32_EPTCTL(td->ep_no), AVR32_EPTCTL_TX_PK_RDY); /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { return (0); /* complete */ } /* else we need to transmit a short packet */ } if (--to) { goto repeat; } not_complete: return (1); /* not complete */ } static uint8_t avr32dci_data_tx_sync(struct avr32dci_td *td) { struct avr32dci_softc *sc; uint32_t temp; /* get pointer to softc */ sc = AVR32_PC2SC(td->pc); /* check endpoint status */ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); if (temp & AVR32_EPTSTA_RX_SETUP) { DPRINTFN(5, "faking complete\n"); /* Race condition */ return (0); /* complete */ } /* * The control endpoint has only got one bank, so if that bank * is free the packet has been transferred! */ if (AVR32_EPTSTA_BUSY_BANK_STA(temp) != 0) { /* cannot write any data - a bank is busy */ goto not_complete; } if (sc->sc_dv_addr != 0xFF) { /* set new address */ avr32dci_set_address(sc, sc->sc_dv_addr); } return (0); /* complete */ not_complete: return (1); /* not complete */ } static uint8_t avr32dci_xfer_do_fifo(struct usb_xfer *xfer) { struct avr32dci_td *td; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; while (1) { if ((td->func) (td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) { goto done; } } /* * Fetch the next transfer descriptor and transfer * some flags to the next transfer descriptor */ td = td->obj_next; xfer->td_transfer_cache = td; } return (1); /* not complete */ done: /* compute all actual lengths */ avr32dci_standard_done(xfer); return (0); /* complete */ } static void avr32dci_interrupt_poll(struct avr32dci_softc *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (!avr32dci_xfer_do_fifo(xfer)) { /* queue has been modified */ goto repeat; } } } void avr32dci_vbus_interrupt(struct avr32dci_softc *sc, uint8_t is_on) { DPRINTFN(5, "vbus = %u\n", is_on); if (is_on) { if (!sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 1; /* complete root HUB interrupt endpoint */ avr32dci_root_intr(sc); } } else { if (sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ avr32dci_root_intr(sc); } } } void avr32dci_interrupt(struct avr32dci_softc *sc) { uint32_t status; USB_BUS_LOCK(&sc->sc_bus); /* read interrupt status */ status = AVR32_READ_4(sc, AVR32_INTSTA); /* clear all set interrupts */ AVR32_WRITE_4(sc, AVR32_CLRINT, status); DPRINTFN(14, "INTSTA=0x%08x\n", status); /* check for any bus state change interrupts */ if (status & AVR32_INT_ENDRESET) { DPRINTFN(5, "end of reset\n"); /* set correct state */ sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* disable resume interrupt */ avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD | AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP); /* complete root HUB interrupt endpoint */ avr32dci_root_intr(sc); } /* * If resume and suspend is set at the same time we interpret * that like RESUME. Resume is set when there is at least 3 * milliseconds of inactivity on the USB BUS. */ if (status & AVR32_INT_WAKE_UP) { DPRINTFN(5, "resume interrupt\n"); if (sc->sc_flags.status_suspend) { /* update status bits */ sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; /* disable resume interrupt */ avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD | AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP); /* complete root HUB interrupt endpoint */ avr32dci_root_intr(sc); } } else if (status & AVR32_INT_DET_SUSPD) { DPRINTFN(5, "suspend interrupt\n"); if (!sc->sc_flags.status_suspend) { /* update status bits */ sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; /* disable suspend interrupt */ avr32dci_mod_ien(sc, AVR32_INT_WAKE_UP | AVR32_INT_ENDRESET, AVR32_INT_DET_SUSPD); /* complete root HUB interrupt endpoint */ avr32dci_root_intr(sc); } } /* check for any endpoint interrupts */ if (status & -AVR32_INT_EPT_INT(0)) { DPRINTFN(5, "real endpoint interrupt\n"); avr32dci_interrupt_poll(sc); } USB_BUS_UNLOCK(&sc->sc_bus); } static void avr32dci_setup_standard_chain_sub(struct avr32dci_std_temp *temp) { struct avr32dci_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->error = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; } static void avr32dci_setup_standard_chain(struct usb_xfer *xfer) { struct avr32dci_std_temp temp; struct avr32dci_softc *sc; struct avr32dci_td *td; uint32_t x; uint8_t ep_no; uint8_t need_sync; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = AVR32_BUS2SC(xfer->xroot->bus); ep_no = (xfer->endpointno & UE_ADDR); /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { temp.func = &avr32dci_setup_rx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) temp.setup_alt_next = 0; } avr32dci_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) { temp.func = &avr32dci_data_tx; need_sync = 1; } else { temp.func = &avr32dci_data_rx; need_sync = 0; } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } else { need_sync = 0; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; } avr32dci_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } if (xfer->flags_int.control_xfr) { /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we need to sync */ if (need_sync) { /* we need a SYNC point after TX */ temp.func = &avr32dci_data_tx_sync; avr32dci_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (xfer->endpointno & UE_DIR_IN) { temp.func = &avr32dci_data_rx; need_sync = 0; } else { temp.func = &avr32dci_data_tx; need_sync = 1; } avr32dci_setup_standard_chain_sub(&temp); if (need_sync) { /* we need a SYNC point after TX */ temp.func = &avr32dci_data_tx_sync; avr32dci_setup_standard_chain_sub(&temp); } } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; } static void avr32dci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ avr32dci_device_done(xfer, USB_ERR_TIMEOUT); } static void avr32dci_start_standard_chain(struct usb_xfer *xfer) { DPRINTFN(9, "\n"); /* poll one time - will turn on interrupts */ if (avr32dci_xfer_do_fifo(xfer)) { uint8_t ep_no = xfer->endpointno & UE_ADDR; struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus); avr32dci_mod_ien(sc, AVR32_INT_EPT_INT(ep_no), 0); /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &avr32dci_timeout, xfer->timeout); } } } static void avr32dci_root_intr(struct avr32dci_softc *sc) { DPRINTFN(9, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit */ sc->sc_hub_idata[0] = 0x02; /* we only have one port */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t avr32dci_standard_done_sub(struct usb_xfer *xfer) { struct avr32dci_td *td; uint32_t len; uint8_t error; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error) { /* the transfer is finished */ error = 1; td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error ? USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); } static void avr32dci_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = avr32dci_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = avr32dci_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = avr32dci_standard_done_sub(xfer); } done: avr32dci_device_done(xfer, err); } /*------------------------------------------------------------------------* * avr32dci_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void avr32dci_device_done(struct usb_xfer *xfer, usb_error_t error) { struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus); uint8_t ep_no; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(9, "xfer=%p, pipe=%p, error=%d\n", xfer, xfer->endpoint, error); if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { ep_no = (xfer->endpointno & UE_ADDR); /* disable endpoint interrupt */ avr32dci_mod_ien(sc, 0, AVR32_INT_EPT_INT(ep_no)); DPRINTFN(15, "disabled interrupts!\n"); } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } static void avr32dci_xfer_stall(struct usb_xfer *xfer) { avr32dci_device_done(xfer, USB_ERR_STALLED); } static void avr32dci_set_stall(struct usb_device *udev, struct usb_endpoint *pipe, uint8_t *did_stall) { struct avr32dci_softc *sc; uint8_t ep_no; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(5, "pipe=%p\n", pipe); sc = AVR32_BUS2SC(udev->bus); /* get endpoint number */ ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR); /* set stall */ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(ep_no), AVR32_EPTSTA_FRCESTALL); } static void avr32dci_clear_stall_sub(struct avr32dci_softc *sc, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { const struct usb_hw_ep_profile *pf; uint32_t temp; uint32_t epsize; uint8_t n; if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } /* set endpoint reset */ AVR32_WRITE_4(sc, AVR32_EPTRST, AVR32_EPTRST_MASK(ep_no)); /* set stall */ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(ep_no), AVR32_EPTSTA_FRCESTALL); /* reset data toggle */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(ep_no), AVR32_EPTSTA_TOGGLESQ); /* clear stall */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(ep_no), AVR32_EPTSTA_FRCESTALL); if (ep_type == UE_BULK) { temp = AVR32_EPTCFG_TYPE_BULK; } else if (ep_type == UE_INTERRUPT) { temp = AVR32_EPTCFG_TYPE_INTR; } else { temp = AVR32_EPTCFG_TYPE_ISOC | AVR32_EPTCFG_NB_TRANS(1); } if (ep_dir & UE_DIR_IN) { temp |= AVR32_EPTCFG_EPDIR_IN; } avr32dci_get_hw_ep_profile(NULL, &pf, ep_no); /* compute endpoint size (use maximum) */ epsize = pf->max_in_frame_size | pf->max_out_frame_size; n = 0; while ((epsize /= 2)) n++; temp |= AVR32_EPTCFG_EPSIZE(n); /* use the maximum number of banks supported */ if (ep_no < 1) temp |= AVR32_EPTCFG_NBANK(1); else if (ep_no < 3) temp |= AVR32_EPTCFG_NBANK(2); else temp |= AVR32_EPTCFG_NBANK(3); AVR32_WRITE_4(sc, AVR32_EPTCFG(ep_no), temp); temp = AVR32_READ_4(sc, AVR32_EPTCFG(ep_no)); if (!(temp & AVR32_EPTCFG_EPT_MAPD)) { device_printf(sc->sc_bus.bdev, "Chip rejected configuration\n"); } else { AVR32_WRITE_4(sc, AVR32_EPTCTLENB(ep_no), AVR32_EPTCTL_EPT_ENABL); } } static void avr32dci_clear_stall(struct usb_device *udev, struct usb_endpoint *pipe) { struct avr32dci_softc *sc; struct usb_endpoint_descriptor *ed; DPRINTFN(5, "pipe=%p\n", pipe); USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = AVR32_BUS2SC(udev->bus); /* get endpoint descriptor */ ed = pipe->edesc; /* reset endpoint */ avr32dci_clear_stall_sub(sc, (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); } usb_error_t avr32dci_init(struct avr32dci_softc *sc) { uint8_t n; DPRINTF("start\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_1_1; sc->sc_bus.methods = &avr32dci_bus_methods; USB_BUS_LOCK(&sc->sc_bus); /* make sure USB is enabled */ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0); /* turn on clocks */ (sc->sc_clocks_on) (&sc->sc_bus); /* make sure device is re-enumerated */ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0); /* wait a little for things to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20); /* disable interrupts */ avr32dci_mod_ien(sc, 0, 0xFFFFFFFF); /* enable interrupts */ avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD | AVR32_INT_ENDRESET, 0); /* reset all endpoints */ AVR32_WRITE_4(sc, AVR32_EPTRST, (1 << AVR32_EP_MAX) - 1); /* disable all endpoints */ for (n = 0; n != AVR32_EP_MAX; n++) { /* disable endpoint */ AVR32_WRITE_4(sc, AVR32_EPTCTLDIS(n), AVR32_EPTCTL_EPT_ENABL); } /* turn off clocks */ avr32dci_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ avr32dci_do_poll(&sc->sc_bus); return (0); /* success */ } void avr32dci_uninit(struct avr32dci_softc *sc) { uint8_t n; USB_BUS_LOCK(&sc->sc_bus); /* turn on clocks */ (sc->sc_clocks_on) (&sc->sc_bus); /* disable interrupts */ avr32dci_mod_ien(sc, 0, 0xFFFFFFFF); /* reset all endpoints */ AVR32_WRITE_4(sc, AVR32_EPTRST, (1 << AVR32_EP_MAX) - 1); /* disable all endpoints */ for (n = 0; n != AVR32_EP_MAX; n++) { /* disable endpoint */ AVR32_WRITE_4(sc, AVR32_EPTCTLDIS(n), AVR32_EPTCTL_EPT_ENABL); } sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; avr32dci_pull_down(sc); avr32dci_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void avr32dci_suspend(struct avr32dci_softc *sc) { /* TODO */ } static void avr32dci_resume(struct avr32dci_softc *sc) { /* TODO */ } static void avr32dci_do_poll(struct usb_bus *bus) { struct avr32dci_softc *sc = AVR32_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); avr32dci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * avr32dci bulk support * avr32dci control support * avr32dci interrupt support *------------------------------------------------------------------------*/ static void avr32dci_device_non_isoc_open(struct usb_xfer *xfer) { return; } static void avr32dci_device_non_isoc_close(struct usb_xfer *xfer) { avr32dci_device_done(xfer, USB_ERR_CANCELLED); } static void avr32dci_device_non_isoc_enter(struct usb_xfer *xfer) { return; } static void avr32dci_device_non_isoc_start(struct usb_xfer *xfer) { /* setup TDs */ avr32dci_setup_standard_chain(xfer); avr32dci_start_standard_chain(xfer); } static const struct usb_pipe_methods avr32dci_device_non_isoc_methods = { .open = avr32dci_device_non_isoc_open, .close = avr32dci_device_non_isoc_close, .enter = avr32dci_device_non_isoc_enter, .start = avr32dci_device_non_isoc_start, }; /*------------------------------------------------------------------------* * avr32dci full speed isochronous support *------------------------------------------------------------------------*/ static void avr32dci_device_isoc_fs_open(struct usb_xfer *xfer) { return; } static void avr32dci_device_isoc_fs_close(struct usb_xfer *xfer) { avr32dci_device_done(xfer, USB_ERR_CANCELLED); } static void avr32dci_device_isoc_fs_enter(struct usb_xfer *xfer) { struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; uint8_t ep_no; DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index */ ep_no = xfer->endpointno & UE_ADDR; nframes = (AVR32_READ_4(sc, AVR32_FNUM) / 8); nframes &= AVR32_FRAME_MASK; /* * check if the frame index is within the window where the frames * will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & AVR32_FRAME_MASK; if ((xfer->endpoint->is_synced == 0) || (temp < xfer->nframes)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & AVR32_FRAME_MASK; xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & AVR32_FRAME_MASK; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + xfer->nframes; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += xfer->nframes; /* setup TDs */ avr32dci_setup_standard_chain(xfer); } static void avr32dci_device_isoc_fs_start(struct usb_xfer *xfer) { /* start TD chain */ avr32dci_start_standard_chain(xfer); } static const struct usb_pipe_methods avr32dci_device_isoc_fs_methods = { .open = avr32dci_device_isoc_fs_open, .close = avr32dci_device_isoc_fs_close, .enter = avr32dci_device_isoc_fs_enter, .start = avr32dci_device_isoc_fs_start, }; /*------------------------------------------------------------------------* * avr32dci root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor avr32dci_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_HSHUBSTT, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct usb_device_qualifier avr32dci_odevd = { .bLength = sizeof(struct usb_device_qualifier), .bDescriptorType = UDESC_DEVICE_QUALIFIER, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize0 = 0, .bNumConfigurations = 0, }; static const struct avr32dci_config_desc avr32dci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(avr32dci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | AVR32_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_hub_descriptor_min avr32dci_hubd = { .bDescLength = sizeof(avr32dci_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "A\0V\0R\0003\0002" #define STRING_PRODUCT \ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, avr32dci_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, avr32dci_product); static usb_error_t avr32dci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct avr32dci_softc *sc = AVR32_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; uint32_t temp; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_handle_clear_port_feature; case UR_SET_FEATURE: goto tr_handle_set_port_feature; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: goto tr_handle_get_port_status; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) { goto tr_stalled; } len = sizeof(avr32dci_devd); ptr = (const void *)&avr32dci_devd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) { goto tr_stalled; } len = sizeof(avr32dci_confd); ptr = (const void *)&avr32dci_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(avr32dci_vendor); ptr = (const void *)&avr32dci_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(avr32dci_product); ptr = (const void *)&avr32dci_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) { goto tr_stalled; } sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) { goto tr_stalled; } sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: avr32dci_wakeup_peer(sc); break; case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; avr32dci_pull_down(sc); avr32dci_clocks_off(sc); break; case UHF_C_PORT_CONNECTION: /* clear connect change flag */ sc->sc_flags.change_connect = 0; if (!sc->sc_flags.status_bus_reset) { /* we are not connected */ break; } /* configure the control endpoint */ /* set endpoint reset */ AVR32_WRITE_4(sc, AVR32_EPTRST, AVR32_EPTRST_MASK(0)); /* set stall */ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(0), AVR32_EPTSTA_FRCESTALL); /* reset data toggle */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(0), AVR32_EPTSTA_TOGGLESQ); /* clear stall */ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(0), AVR32_EPTSTA_FRCESTALL); /* configure */ AVR32_WRITE_4(sc, AVR32_EPTCFG(0), AVR32_EPTCFG_TYPE_CTRL | AVR32_EPTCFG_NBANK(1) | AVR32_EPTCFG_EPSIZE(6)); temp = AVR32_READ_4(sc, AVR32_EPTCFG(0)); if (!(temp & AVR32_EPTCFG_EPT_MAPD)) { device_printf(sc->sc_bus.bdev, "Chip rejected configuration\n"); } else { AVR32_WRITE_4(sc, AVR32_EPTCTLENB(0), AVR32_EPTCTL_EPT_ENABL); } break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_set_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); switch (value) { case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 1; break; case UHF_PORT_SUSPEND: case UHF_PORT_RESET: case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_get_port_status: DPRINTFN(9, "UR_GET_PORT_STATUS\n"); if (index != 1) { goto tr_stalled; } if (sc->sc_flags.status_vbus) { avr32dci_clocks_on(sc); avr32dci_pull_up(sc); } else { avr32dci_pull_down(sc); avr32dci_clocks_off(sc); } /* Select Device Side Mode */ value = UPS_PORT_MODE_DEVICE; /* Check for High Speed */ if (AVR32_READ_4(sc, AVR32_INTSTA) & AVR32_INT_SPEED) value |= UPS_HIGH_SPEED; if (sc->sc_flags.port_powered) { value |= UPS_PORT_POWER; } if (sc->sc_flags.port_enabled) { value |= UPS_PORT_ENABLED; } if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { value |= UPS_CURRENT_CONNECT_STATUS; } if (sc->sc_flags.status_suspend) { value |= UPS_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_connect) { value |= UPS_C_CONNECT_STATUS; } if (sc->sc_flags.change_suspend) { value |= UPS_C_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) { goto tr_stalled; } ptr = (const void *)&avr32dci_hubd; len = sizeof(avr32dci_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: done: *plength = len; *pptr = ptr; return (err); } static void avr32dci_xfer_setup(struct usb_setup_params *parm) { const struct usb_hw_ep_profile *pf; struct avr32dci_softc *sc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; uint8_t ep_no; sc = AVR32_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x400; usbd_transfer_setup_sub(parm); /* * compute maximum number of TDs */ if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + 1 /* SYNC 2 */ ; } else { ntd = xfer->nframes + 1 /* SYNC */ ; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) return; /* * allocate transfer descriptors */ last_obj = NULL; /* * get profile stuff */ ep_no = xfer->endpointno & UE_ADDR; avr32dci_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct avr32dci_td *td; if (parm->buf) { uint32_t temp; td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* init TD */ td->max_packet_size = xfer->max_packet_size; td->ep_no = ep_no; temp = pf->max_in_frame_size | pf->max_out_frame_size; td->bank_shift = 0; while ((temp /= 2)) td->bank_shift++; if (pf->support_multi_buffer) { td->support_multi_buffer = 1; } td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void avr32dci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void avr32dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *pipe) { struct avr32dci_softc *sc = AVR32_BUS2SC(udev->bus); DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", pipe, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_rt_addr, udev->device_index); if (udev->device_index != sc->sc_rt_addr) { if ((udev->speed != USB_SPEED_FULL) && (udev->speed != USB_SPEED_HIGH)) { /* not supported */ return; } if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) pipe->methods = &avr32dci_device_isoc_fs_methods; else pipe->methods = &avr32dci_device_non_isoc_methods; } } static void avr32dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct avr32dci_softc *sc = AVR32_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: avr32dci_suspend(sc); break; case USB_HW_POWER_SHUTDOWN: avr32dci_uninit(sc); break; case USB_HW_POWER_RESUME: avr32dci_resume(sc); break; default: break; } } static const struct usb_bus_methods avr32dci_bus_methods = { .endpoint_init = &avr32dci_ep_init, .xfer_setup = &avr32dci_xfer_setup, .xfer_unsetup = &avr32dci_xfer_unsetup, .get_hw_ep_profile = &avr32dci_get_hw_ep_profile, .xfer_stall = &avr32dci_xfer_stall, .set_stall = &avr32dci_set_stall, .clear_stall = &avr32dci_clear_stall, .roothub_exec = &avr32dci_roothub_exec, .xfer_poll = &avr32dci_do_poll, .set_hw_power_sleep = &avr32dci_set_hw_power_sleep, }; Index: head/sys/dev/usb/controller/dwc_otg.c =================================================================== --- head/sys/dev/usb/controller/dwc_otg.c (revision 357971) +++ head/sys/dev/usb/controller/dwc_otg.c (revision 357972) @@ -1,5028 +1,5029 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2015 Daisuke Aoyama. All rights reserved. * Copyright (c) 2012-2015 Hans Petter Selasky. All rights reserved. * Copyright (c) 2010-2011 Aleksandr Rybalko. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the driver for the DesignWare series USB 2.0 OTG * Controller. */ /* * LIMITATION: Drivers must be bound to all OUT endpoints in the * active configuration for this driver to work properly. Blocking any * OUT endpoint will block all OUT endpoints including the control * endpoint. Usually this is not a problem. */ /* * NOTE: Writing to non-existing registers appears to cause an * internal reset. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR dwc_otg_debug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define DWC_OTG_BUS2SC(bus) \ ((struct dwc_otg_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct dwc_otg_softc *)0)->sc_bus)))) #define DWC_OTG_PC2UDEV(pc) \ (USB_DMATAG_TO_XROOT((pc)->tag_parent)->udev) #define DWC_OTG_MSK_GINT_THREAD_IRQ \ (GINTSTS_USBRST | GINTSTS_ENUMDONE | GINTSTS_PRTINT | \ GINTSTS_WKUPINT | GINTSTS_USBSUSP | GINTMSK_OTGINTMSK | \ GINTSTS_SESSREQINT) #ifndef DWC_OTG_PHY_DEFAULT #define DWC_OTG_PHY_DEFAULT DWC_OTG_PHY_ULPI #endif static int dwc_otg_phy_type = DWC_OTG_PHY_DEFAULT; -static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB DWC OTG"); SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, phy_type, CTLFLAG_RDTUN, &dwc_otg_phy_type, 0, "DWC OTG PHY TYPE - 0/1/2/3 - ULPI/HSIC/INTERNAL/UTMI+"); #ifdef USB_DEBUG static int dwc_otg_debug = 0; SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RWTUN, &dwc_otg_debug, 0, "DWC OTG debug level"); #endif #define DWC_OTG_INTR_ENDPT 1 /* prototypes */ static const struct usb_bus_methods dwc_otg_bus_methods; static const struct usb_pipe_methods dwc_otg_device_non_isoc_methods; static const struct usb_pipe_methods dwc_otg_device_isoc_methods; static dwc_otg_cmd_t dwc_otg_setup_rx; static dwc_otg_cmd_t dwc_otg_data_rx; static dwc_otg_cmd_t dwc_otg_data_tx; static dwc_otg_cmd_t dwc_otg_data_tx_sync; static dwc_otg_cmd_t dwc_otg_host_setup_tx; static dwc_otg_cmd_t dwc_otg_host_data_tx; static dwc_otg_cmd_t dwc_otg_host_data_rx; static void dwc_otg_device_done(struct usb_xfer *, usb_error_t); static void dwc_otg_do_poll(struct usb_bus *); static void dwc_otg_standard_done(struct usb_xfer *); static void dwc_otg_root_intr(struct dwc_otg_softc *); static void dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *); /* * Here is a configuration that the chip supports. */ static const struct usb_hw_ep_profile dwc_otg_ep_profile[1] = { [0] = { .max_in_frame_size = 64,/* fixed */ .max_out_frame_size = 64, /* fixed */ .is_simplex = 1, .support_control = 1, } }; static void dwc_otg_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { struct dwc_otg_softc *sc; sc = DWC_OTG_BUS2SC(udev->bus); if (ep_addr < sc->sc_dev_ep_max) *ppf = &sc->sc_hw_ep_profile[ep_addr].usb; else *ppf = NULL; } static void dwc_otg_write_fifo(struct dwc_otg_softc *sc, struct usb_page_cache *pc, uint32_t offset, uint32_t fifo, uint32_t count) { uint32_t temp; /* round down length to nearest 4-bytes */ temp = count & ~3; /* check if we can write the data directly */ if (temp != 0 && usb_pc_buffer_is_aligned(pc, offset, temp, 3)) { struct usb_page_search buf_res; /* pre-subtract length */ count -= temp; /* iterate buffer list */ do { /* get current buffer pointer */ usbd_get_page(pc, offset, &buf_res); if (buf_res.length > temp) buf_res.length = temp; /* transfer data into FIFO */ bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, fifo, buf_res.buffer, buf_res.length / 4); offset += buf_res.length; fifo += buf_res.length; temp -= buf_res.length; } while (temp != 0); } /* check for remainder */ if (count != 0) { /* clear topmost word before copy */ sc->sc_bounce_buffer[(count - 1) / 4] = 0; /* copy out data */ usbd_copy_out(pc, offset, sc->sc_bounce_buffer, count); /* transfer data into FIFO */ bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, fifo, sc->sc_bounce_buffer, (count + 3) / 4); } } static void dwc_otg_read_fifo(struct dwc_otg_softc *sc, struct usb_page_cache *pc, uint32_t offset, uint32_t count) { uint32_t temp; /* round down length to nearest 4-bytes */ temp = count & ~3; /* check if we can read the data directly */ if (temp != 0 && usb_pc_buffer_is_aligned(pc, offset, temp, 3)) { struct usb_page_search buf_res; /* pre-subtract length */ count -= temp; /* iterate buffer list */ do { /* get current buffer pointer */ usbd_get_page(pc, offset, &buf_res); if (buf_res.length > temp) buf_res.length = temp; /* transfer data from FIFO */ bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, sc->sc_current_rx_fifo, buf_res.buffer, buf_res.length / 4); offset += buf_res.length; sc->sc_current_rx_fifo += buf_res.length; sc->sc_current_rx_bytes -= buf_res.length; temp -= buf_res.length; } while (temp != 0); } /* check for remainder */ if (count != 0) { /* read data into bounce buffer */ bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, sc->sc_current_rx_fifo, sc->sc_bounce_buffer, (count + 3) / 4); /* store data into proper buffer */ usbd_copy_in(pc, offset, sc->sc_bounce_buffer, count); /* round length up to nearest 4 bytes */ count = (count + 3) & ~3; /* update counters */ sc->sc_current_rx_bytes -= count; sc->sc_current_rx_fifo += count; } } static void dwc_otg_tx_fifo_reset(struct dwc_otg_softc *sc, uint32_t value) { uint32_t temp; /* reset FIFO */ DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL, value); /* wait for reset to complete */ for (temp = 0; temp != 16; temp++) { value = DWC_OTG_READ_4(sc, DOTG_GRSTCTL); if (!(value & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH))) break; } } static int dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode) { struct dwc_otg_profile *pf; uint32_t fifo_size; uint32_t fifo_regs; uint32_t tx_start; uint8_t x; fifo_size = sc->sc_fifo_size; /* * NOTE: Reserved fixed size area at end of RAM, which must * not be allocated to the FIFOs: */ fifo_regs = 4 * 16; if (fifo_size < fifo_regs) { DPRINTF("Too little FIFO\n"); return (EINVAL); } /* subtract FIFO regs from total once */ fifo_size -= fifo_regs; /* split equally for IN and OUT */ fifo_size /= 2; /* Align to 4 bytes boundary (refer to PGM) */ fifo_size &= ~3; /* set global receive FIFO size */ DWC_OTG_WRITE_4(sc, DOTG_GRXFSIZ, fifo_size / 4); tx_start = fifo_size; if (fifo_size < 64) { DPRINTFN(-1, "Not enough data space for EP0 FIFO.\n"); return (EINVAL); } if (mode == DWC_MODE_HOST) { /* reset active endpoints */ sc->sc_active_rx_ep = 0; /* split equally for periodic and non-periodic */ fifo_size /= 2; DPRINTF("PTX/NPTX FIFO=%u\n", fifo_size); /* align to 4 bytes boundary */ fifo_size &= ~3; DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ, ((fifo_size / 4) << 16) | (tx_start / 4)); tx_start += fifo_size; for (x = 0; x != sc->sc_host_ch_max; x++) { /* enable all host interrupts */ DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(x), HCINT_DEFAULT_MASK); } DWC_OTG_WRITE_4(sc, DOTG_HPTXFSIZ, ((fifo_size / 4) << 16) | (tx_start / 4)); /* reset host channel state */ memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state)); /* enable all host channel interrupts */ DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK, (1U << sc->sc_host_ch_max) - 1U); /* enable proper host channel interrupts */ sc->sc_irq_mask |= GINTMSK_HCHINTMSK; sc->sc_irq_mask &= ~GINTMSK_IEPINTMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } if (mode == DWC_MODE_DEVICE) { DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ, (0x10 << 16) | (tx_start / 4)); fifo_size -= 0x40; tx_start += 0x40; /* setup control endpoint profile */ sc->sc_hw_ep_profile[0].usb = dwc_otg_ep_profile[0]; /* reset active endpoints */ sc->sc_active_rx_ep = 1; for (x = 1; x != sc->sc_dev_ep_max; x++) { pf = sc->sc_hw_ep_profile + x; pf->usb.max_out_frame_size = 1024 * 3; pf->usb.is_simplex = 0; /* assume duplex */ pf->usb.support_bulk = 1; pf->usb.support_interrupt = 1; pf->usb.support_isochronous = 1; pf->usb.support_out = 1; if (x < sc->sc_dev_in_ep_max) { uint32_t limit; limit = (x == 1) ? MIN(DWC_OTG_TX_MAX_FIFO_SIZE, DWC_OTG_MAX_TXN) : MIN(DWC_OTG_MAX_TXN / 2, DWC_OTG_TX_MAX_FIFO_SIZE); /* see if there is enough FIFO space */ if (limit <= fifo_size) { pf->max_buffer = limit; pf->usb.support_in = 1; } else { limit = MIN(DWC_OTG_TX_MAX_FIFO_SIZE, 0x40); if (limit <= fifo_size) { pf->usb.support_in = 1; } else { pf->usb.is_simplex = 1; limit = 0; } } /* set FIFO size */ DWC_OTG_WRITE_4(sc, DOTG_DIEPTXF(x), ((limit / 4) << 16) | (tx_start / 4)); tx_start += limit; fifo_size -= limit; pf->usb.max_in_frame_size = limit; } else { pf->usb.is_simplex = 1; } DPRINTF("FIFO%d = IN:%d / OUT:%d\n", x, pf->usb.max_in_frame_size, pf->usb.max_out_frame_size); } /* enable proper device channel interrupts */ sc->sc_irq_mask &= ~GINTMSK_HCHINTMSK; sc->sc_irq_mask |= GINTMSK_IEPINTMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } /* reset RX FIFO */ dwc_otg_tx_fifo_reset(sc, GRSTCTL_RXFFLSH); if (mode != DWC_MODE_OTG) { /* reset all TX FIFOs */ dwc_otg_tx_fifo_reset(sc, GRSTCTL_TXFIFO(0x10) | GRSTCTL_TXFFLSH); } else { /* reset active endpoints */ sc->sc_active_rx_ep = 0; /* reset host channel state */ memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state)); } return (0); } static uint8_t dwc_otg_uses_split(struct usb_device *udev) { /* * When a LOW or FULL speed device is connected directly to * the USB port we don't use split transactions: */ return (udev->speed != USB_SPEED_HIGH && udev->parent_hs_hub != NULL && udev->parent_hs_hub->parent_hub != NULL); } static void dwc_otg_update_host_frame_interval(struct dwc_otg_softc *sc) { /* * Disabled until further. Assuming that the register is already * programmed correctly by the boot loader. */ #if 0 uint32_t temp; /* setup HOST frame interval register, based on existing value */ temp = DWC_OTG_READ_4(sc, DOTG_HFIR) & HFIR_FRINT_MASK; if (temp >= 10000) temp /= 1000; else temp /= 125; /* figure out nearest X-tal value */ if (temp >= 54) temp = 60; /* MHz */ else if (temp >= 39) temp = 48; /* MHz */ else temp = 30; /* MHz */ if (sc->sc_flags.status_high_speed) temp *= 125; else temp *= 1000; DPRINTF("HFIR=0x%08x\n", temp); DWC_OTG_WRITE_4(sc, DOTG_HFIR, temp); #endif } static void dwc_otg_clocks_on(struct dwc_otg_softc *sc) { if (sc->sc_flags.clocks_off && sc->sc_flags.port_powered) { DPRINTFN(5, "\n"); /* TODO - platform specific */ sc->sc_flags.clocks_off = 0; } } static void dwc_otg_clocks_off(struct dwc_otg_softc *sc) { if (!sc->sc_flags.clocks_off) { DPRINTFN(5, "\n"); /* TODO - platform specific */ sc->sc_flags.clocks_off = 1; } } static void dwc_otg_pull_up(struct dwc_otg_softc *sc) { uint32_t temp; /* pullup D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { sc->sc_flags.d_pulled_up = 1; temp = DWC_OTG_READ_4(sc, DOTG_DCTL); temp &= ~DCTL_SFTDISCON; DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp); } } static void dwc_otg_pull_down(struct dwc_otg_softc *sc) { uint32_t temp; /* pulldown D+, if possible */ if (sc->sc_flags.d_pulled_up) { sc->sc_flags.d_pulled_up = 0; temp = DWC_OTG_READ_4(sc, DOTG_DCTL); temp |= DCTL_SFTDISCON; DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp); } } static void dwc_otg_enable_sof_irq(struct dwc_otg_softc *sc) { /* In device mode we don't use the SOF interrupt */ if (sc->sc_flags.status_device_mode != 0) return; /* Ensure the SOF interrupt is not disabled */ sc->sc_needsof = 1; /* Check if the SOF interrupt is already enabled */ if ((sc->sc_irq_mask & GINTMSK_SOFMSK) != 0) return; sc->sc_irq_mask |= GINTMSK_SOFMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } static void dwc_otg_resume_irq(struct dwc_otg_softc *sc) { if (sc->sc_flags.status_suspend) { /* update status bits */ sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; if (sc->sc_flags.status_device_mode) { /* * Disable resume interrupt and enable suspend * interrupt: */ sc->sc_irq_mask &= ~GINTMSK_WKUPINTMSK; sc->sc_irq_mask |= GINTMSK_USBSUSPMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); } } static void dwc_otg_suspend_irq(struct dwc_otg_softc *sc) { if (!sc->sc_flags.status_suspend) { /* update status bits */ sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; if (sc->sc_flags.status_device_mode) { /* * Disable suspend interrupt and enable resume * interrupt: */ sc->sc_irq_mask &= ~GINTMSK_USBSUSPMSK; sc->sc_irq_mask |= GINTMSK_WKUPINTMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); } } static void dwc_otg_wakeup_peer(struct dwc_otg_softc *sc) { if (!sc->sc_flags.status_suspend) return; DPRINTFN(5, "Remote wakeup\n"); if (sc->sc_flags.status_device_mode) { uint32_t temp; /* enable remote wakeup signalling */ temp = DWC_OTG_READ_4(sc, DOTG_DCTL); temp |= DCTL_RMTWKUPSIG; DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp); /* Wait 8ms for remote wakeup to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); temp &= ~DCTL_RMTWKUPSIG; DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp); } else { /* enable USB port */ DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0); /* wait 10ms */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* resume port */ sc->sc_hprt_val |= HPRT_PRTRES; DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val); /* Wait 100ms for resume signalling to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10); /* clear suspend and resume */ sc->sc_hprt_val &= ~(HPRT_PRTSUSP | HPRT_PRTRES); DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val); /* Wait 4ms */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); } /* need to fake resume IRQ */ dwc_otg_resume_irq(sc); } static void dwc_otg_set_address(struct dwc_otg_softc *sc, uint8_t addr) { uint32_t temp; DPRINTFN(5, "addr=%d\n", addr); temp = DWC_OTG_READ_4(sc, DOTG_DCFG); temp &= ~DCFG_DEVADDR_SET(0x7F); temp |= DCFG_DEVADDR_SET(addr); DWC_OTG_WRITE_4(sc, DOTG_DCFG, temp); } static void dwc_otg_common_rx_ack(struct dwc_otg_softc *sc) { DPRINTFN(5, "RX status clear\n"); /* enable RX FIFO level interrupt */ sc->sc_irq_mask |= GINTMSK_RXFLVLMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); if (sc->sc_current_rx_bytes != 0) { /* need to dump remaining data */ bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, sc->sc_current_rx_fifo, sc->sc_bounce_buffer, sc->sc_current_rx_bytes / 4); /* clear number of active bytes to receive */ sc->sc_current_rx_bytes = 0; } /* clear cached status */ sc->sc_last_rx_status = 0; } static void dwc_otg_clear_hcint(struct dwc_otg_softc *sc, uint8_t x) { uint32_t hcint; /* clear all pending interrupts */ hcint = DWC_OTG_READ_4(sc, DOTG_HCINT(x)); DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), hcint); /* clear buffered interrupts */ sc->sc_chan_state[x].hcint = 0; } static uint8_t dwc_otg_host_check_tx_fifo_empty(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint32_t temp; temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS); if (td->ep_type == UE_ISOCHRONOUS) { /* * NOTE: USB INTERRUPT transactions are executed like * USB CONTROL transactions! See the setup standard * chain function for more information. */ if (!(temp & GINTSTS_PTXFEMP)) { DPRINTF("Periodic TX FIFO is not empty\n"); if (!(sc->sc_irq_mask & GINTMSK_PTXFEMPMSK)) { sc->sc_irq_mask |= GINTMSK_PTXFEMPMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } return (1); /* busy */ } } else { if (!(temp & GINTSTS_NPTXFEMP)) { DPRINTF("Non-periodic TX FIFO is not empty\n"); if (!(sc->sc_irq_mask & GINTMSK_NPTXFEMPMSK)) { sc->sc_irq_mask |= GINTMSK_NPTXFEMPMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } return (1); /* busy */ } } return (0); /* ready for transmit */ } static uint8_t dwc_otg_host_channel_alloc(struct dwc_otg_softc *sc, struct dwc_otg_td *td, uint8_t is_out) { uint8_t x; uint8_t y; uint8_t z; if (td->channel[0] < DWC_OTG_MAX_CHANNELS) return (0); /* already allocated */ /* check if device is suspended */ if (DWC_OTG_PC2UDEV(td->pc)->flags.self_suspended != 0) return (1); /* busy - cannot transfer data */ /* compute needed TX FIFO size */ if (is_out != 0) { if (dwc_otg_host_check_tx_fifo_empty(sc, td) != 0) return (1); /* busy - cannot transfer data */ } z = td->max_packet_count; for (x = y = 0; x != sc->sc_host_ch_max; x++) { /* check if channel is allocated */ if (sc->sc_chan_state[x].allocated != 0) continue; /* check if channel is still enabled */ if (sc->sc_chan_state[x].wait_halted != 0) continue; /* store channel number */ td->channel[y++] = x; /* check if we got all channels */ if (y == z) break; } if (y != z) { /* reset channel variable */ td->channel[0] = DWC_OTG_MAX_CHANNELS; td->channel[1] = DWC_OTG_MAX_CHANNELS; td->channel[2] = DWC_OTG_MAX_CHANNELS; /* wait a bit */ dwc_otg_enable_sof_irq(sc); return (1); /* busy - not enough channels */ } for (y = 0; y != z; y++) { x = td->channel[y]; /* set allocated */ sc->sc_chan_state[x].allocated = 1; /* set wait halted */ sc->sc_chan_state[x].wait_halted = 1; /* clear interrupts */ dwc_otg_clear_hcint(sc, x); DPRINTF("CH=%d HCCHAR=0x%08x " "HCSPLT=0x%08x\n", x, td->hcchar, td->hcsplt); /* set active channel */ sc->sc_active_rx_ep |= (1 << x); } return (0); /* allocated */ } static void dwc_otg_host_channel_free_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td, uint8_t index) { uint32_t hcchar; uint8_t x; if (td->channel[index] >= DWC_OTG_MAX_CHANNELS) return; /* already freed */ /* free channel */ x = td->channel[index]; td->channel[index] = DWC_OTG_MAX_CHANNELS; DPRINTF("CH=%d\n", x); /* * We need to let programmed host channels run till complete * else the host channel will stop functioning. */ sc->sc_chan_state[x].allocated = 0; /* ack any pending messages */ if (sc->sc_last_rx_status != 0 && GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) == x) { dwc_otg_common_rx_ack(sc); } /* clear active channel */ sc->sc_active_rx_ep &= ~(1 << x); /* check if already halted */ if (sc->sc_chan_state[x].wait_halted == 0) return; /* disable host channel */ hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x)); if (hcchar & HCCHAR_CHENA) { DPRINTF("Halting channel %d\n", x); DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x), hcchar | HCCHAR_CHDIS); /* don't write HCCHAR until the channel is halted */ } else { sc->sc_chan_state[x].wait_halted = 0; } } static void dwc_otg_host_channel_free(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint8_t x; for (x = 0; x != td->max_packet_count; x++) dwc_otg_host_channel_free_sub(sc, td, x); } static void dwc_otg_host_dump_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint8_t x; /* dump any pending messages */ if (sc->sc_last_rx_status == 0) return; for (x = 0; x != td->max_packet_count; x++) { if (td->channel[x] >= DWC_OTG_MAX_CHANNELS || td->channel[x] != GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status)) continue; dwc_otg_common_rx_ack(sc); break; } } static uint8_t dwc_otg_host_setup_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { struct usb_device_request req __aligned(4); uint32_t hcint; uint32_t hcchar; uint8_t delta; dwc_otg_host_dump_rx(sc, td); if (td->channel[0] < DWC_OTG_MAX_CHANNELS) { hcint = sc->sc_chan_state[td->channel[0]].hcint; DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", td->channel[0], td->state, hcint, DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel[0])), DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel[0]))); } else { hcint = 0; goto check_state; } if (hcint & (HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { /* give success bits priority over failure bits */ } else if (hcint & HCINT_STALL) { DPRINTF("CH=%d STALL\n", td->channel[0]); td->error_stall = 1; td->error_any = 1; goto complete; } else if (hcint & HCINT_ERRORS) { DPRINTF("CH=%d ERROR\n", td->channel[0]); td->errcnt++; if (td->hcsplt != 0 || td->errcnt >= 3) { td->error_any = 1; goto complete; } } if (hcint & (HCINT_ERRORS | HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { if (!(hcint & HCINT_ERRORS)) td->errcnt = 0; } check_state: switch (td->state) { case DWC_CHAN_ST_START: goto send_pkt; case DWC_CHAN_ST_WAIT_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto send_pkt; } else if (hcint & (HCINT_ACK | HCINT_NYET)) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle = 1; td->tt_scheduled = 0; goto complete; } break; case DWC_CHAN_ST_WAIT_S_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto send_pkt; } else if (hcint & (HCINT_ACK | HCINT_NYET)) { goto send_cpkt; } break; case DWC_CHAN_ST_WAIT_C_ANE: if (hcint & HCINT_NYET) { goto send_cpkt; } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto send_pkt; } else if (hcint & HCINT_ACK) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle = 1; goto complete; } break; case DWC_CHAN_ST_WAIT_C_PKT: goto send_cpkt; default: break; } goto busy; send_pkt: /* free existing channel, if any */ dwc_otg_host_channel_free(sc, td); if (sizeof(req) != td->remainder) { td->error_any = 1; goto complete; } if (td->hcsplt != 0) { delta = td->tt_start_slot - sc->sc_last_frame_num - 1; if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { td->state = DWC_CHAN_ST_START; goto busy; } delta = sc->sc_last_frame_num - td->tt_start_slot; if (delta > 5) { /* missed it */ td->tt_scheduled = 0; td->state = DWC_CHAN_ST_START; goto busy; } } /* allocate a new channel */ if (dwc_otg_host_channel_alloc(sc, td, 1)) { td->state = DWC_CHAN_ST_START; goto busy; } if (td->hcsplt != 0) { td->hcsplt &= ~HCSPLT_COMPSPLT; td->state = DWC_CHAN_ST_WAIT_S_ANE; } else { td->state = DWC_CHAN_ST_WAIT_ANE; } /* copy out control request */ usbd_copy_out(td->pc, 0, &req, sizeof(req)); DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel[0]), (sizeof(req) << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT)); DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel[0]), td->hcsplt); hcchar = td->hcchar; hcchar &= ~(HCCHAR_EPDIR_IN | HCCHAR_EPTYPE_MASK); hcchar |= UE_CONTROL << HCCHAR_EPTYPE_SHIFT; /* must enable channel before writing data to FIFO */ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel[0]), hcchar); /* transfer data into FIFO */ bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, DOTG_DFIFO(td->channel[0]), (uint32_t *)&req, sizeof(req) / 4); /* wait until next slot before trying complete split */ td->tt_complete_slot = sc->sc_last_frame_num + 1; /* store number of bytes transmitted */ td->tx_bytes = sizeof(req); goto busy; send_cpkt: /* free existing channel, if any */ dwc_otg_host_channel_free(sc, td); delta = td->tt_complete_slot - sc->sc_last_frame_num - 1; if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } delta = sc->sc_last_frame_num - td->tt_start_slot; if (delta > DWC_OTG_TT_SLOT_MAX) { /* we missed the service interval */ if (td->ep_type != UE_ISOCHRONOUS) td->error_any = 1; goto complete; } /* allocate a new channel */ if (dwc_otg_host_channel_alloc(sc, td, 0)) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } /* wait until next slot before trying complete split */ td->tt_complete_slot = sc->sc_last_frame_num + 1; td->hcsplt |= HCSPLT_COMPSPLT; td->state = DWC_CHAN_ST_WAIT_C_ANE; DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel[0]), (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT)); DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel[0]), td->hcsplt); hcchar = td->hcchar; hcchar &= ~(HCCHAR_EPDIR_IN | HCCHAR_EPTYPE_MASK); hcchar |= UE_CONTROL << HCCHAR_EPTYPE_SHIFT; /* must enable channel before writing data to FIFO */ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel[0]), hcchar); busy: return (1); /* busy */ complete: dwc_otg_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t dwc_otg_setup_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { struct usb_device_request req __aligned(4); uint32_t temp; uint16_t count; /* check endpoint status */ if (sc->sc_last_rx_status == 0) goto not_complete; if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != 0) goto not_complete; if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) != GRXSTSRD_STP_DATA) { if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) != GRXSTSRD_STP_COMPLETE || td->remainder != 0) { /* release FIFO */ dwc_otg_common_rx_ack(sc); goto not_complete; } /* release FIFO */ dwc_otg_common_rx_ack(sc); return (0); /* complete */ } if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) != GRXSTSRD_DPID_DATA0) { /* release FIFO */ dwc_otg_common_rx_ack(sc); goto not_complete; } DPRINTFN(5, "GRXSTSR=0x%08x\n", sc->sc_last_rx_status); /* clear did stall */ td->did_stall = 0; /* get the packet byte count */ count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status); if (count != sizeof(req)) { DPRINTFN(0, "Unsupported SETUP packet " "length, %d bytes\n", count); /* release FIFO */ dwc_otg_common_rx_ack(sc); goto not_complete; } /* read FIFO */ dwc_otg_read_fifo(sc, td->pc, 0, sizeof(req)); /* copy out control request */ usbd_copy_out(td->pc, 0, &req, sizeof(req)); td->offset = sizeof(req); td->remainder = 0; /* sneak peek the set address */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { /* must write address before ZLP */ dwc_otg_set_address(sc, req.wValue[0] & 0x7F); } /* don't send any data by default */ DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(0), DIEPCTL_EPDIS); DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(0), DOEPCTL_EPDIS); /* reset IN endpoint buffer */ dwc_otg_tx_fifo_reset(sc, GRSTCTL_TXFIFO(0) | GRSTCTL_TXFFLSH); /* acknowledge RX status */ dwc_otg_common_rx_ack(sc); td->did_stall = 1; not_complete: /* abort any ongoing transfer, before enabling again */ if (!td->did_stall) { td->did_stall = 1; DPRINTFN(5, "stalling IN and OUT direction\n"); temp = sc->sc_out_ctl[0]; /* set stall after enabling endpoint */ DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(0), temp | DOEPCTL_STALL); temp = sc->sc_in_ctl[0]; /* set stall assuming endpoint is enabled */ DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(0), temp | DIEPCTL_STALL); } return (1); /* not complete */ } static uint8_t dwc_otg_host_rate_check_interrupt(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint8_t delta; delta = sc->sc_tmr_val - td->tmr_val; if (delta >= 128) return (1); /* busy */ td->tmr_val = sc->sc_tmr_val + td->tmr_res; /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } return (0); } static uint8_t dwc_otg_host_rate_check(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint8_t frame_num = (uint8_t)sc->sc_last_frame_num; if (td->ep_type == UE_ISOCHRONOUS) { /* non TT isochronous traffic */ if (frame_num & (td->tmr_res - 1)) goto busy; if ((frame_num ^ td->tmr_val) & td->tmr_res) goto busy; td->tmr_val = td->tmr_res + sc->sc_last_frame_num; td->toggle = 0; return (0); } else if (td->ep_type == UE_INTERRUPT) { if (!td->tt_scheduled) goto busy; td->tt_scheduled = 0; return (0); } else if (td->did_nak != 0) { /* check if we should pause sending queries for 125us */ if (td->tmr_res == frame_num) { /* wait a bit */ dwc_otg_enable_sof_irq(sc); goto busy; } } else if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } /* query for data one more time */ td->tmr_res = frame_num; td->did_nak = 0; return (0); busy: return (1); } static uint8_t dwc_otg_host_data_rx_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td, uint8_t channel) { uint32_t count; /* check endpoint status */ if (sc->sc_last_rx_status == 0) goto busy; if (channel >= DWC_OTG_MAX_CHANNELS) goto busy; if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != channel) goto busy; switch (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) { case GRXSTSRH_IN_DATA: DPRINTF("DATA ST=%d STATUS=0x%08x\n", (int)td->state, (int)sc->sc_last_rx_status); if (sc->sc_chan_state[channel].hcint & HCINT_SOFTWARE_ONLY) { /* * When using SPLIT transactions on interrupt * endpoints, sometimes data occurs twice. */ DPRINTF("Data already received\n"); break; } /* get the packet byte count */ count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status); /* check for ISOCHRONOUS endpoint */ if (td->ep_type == UE_ISOCHRONOUS) { if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) != GRXSTSRD_DPID_DATA0) { /* more data to be received */ td->tt_xactpos = HCSPLT_XACTPOS_MIDDLE; } else { /* all data received */ td->tt_xactpos = HCSPLT_XACTPOS_BEGIN; /* verify the packet byte count */ if (count != td->remainder) { /* we have a short packet */ td->short_pkt = 1; td->got_short = 1; } } } else { /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; td->got_short = 1; } else { /* invalid USB packet */ td->error_any = 1; /* release FIFO */ dwc_otg_common_rx_ack(sc); goto complete; } } td->toggle ^= 1; td->tt_scheduled = 0; } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error_any = 1; /* release FIFO */ dwc_otg_common_rx_ack(sc); goto complete; } /* read data from FIFO */ dwc_otg_read_fifo(sc, td->pc, td->offset, count); td->remainder -= count; td->offset += count; sc->sc_chan_state[channel].hcint |= HCINT_SOFTWARE_ONLY; break; default: break; } /* release FIFO */ dwc_otg_common_rx_ack(sc); busy: return (0); complete: return (1); } static uint8_t dwc_otg_host_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint32_t hcint = 0; uint32_t hcchar; uint8_t delta; uint8_t channel; uint8_t x; for (x = 0; x != td->max_packet_count; x++) { channel = td->channel[x]; if (channel >= DWC_OTG_MAX_CHANNELS) continue; hcint |= sc->sc_chan_state[channel].hcint; DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", channel, td->state, hcint, DWC_OTG_READ_4(sc, DOTG_HCCHAR(channel)), DWC_OTG_READ_4(sc, DOTG_HCTSIZ(channel))); /* check interrupt bits */ if (hcint & (HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { /* give success bits priority over failure bits */ } else if (hcint & HCINT_STALL) { DPRINTF("CH=%d STALL\n", channel); td->error_stall = 1; td->error_any = 1; goto complete; } else if (hcint & HCINT_ERRORS) { DPRINTF("CH=%d ERROR\n", channel); td->errcnt++; if (td->hcsplt != 0 || td->errcnt >= 3) { if (td->ep_type != UE_ISOCHRONOUS) { td->error_any = 1; goto complete; } } } /* check channels for data, if any */ if (dwc_otg_host_data_rx_sub(sc, td, channel)) goto complete; /* refresh interrupt status */ hcint |= sc->sc_chan_state[channel].hcint; if (hcint & (HCINT_ERRORS | HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { if (!(hcint & HCINT_ERRORS)) td->errcnt = 0; } } switch (td->state) { case DWC_CHAN_ST_START: if (td->hcsplt != 0) goto receive_spkt; else goto receive_pkt; case DWC_CHAN_ST_WAIT_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { if (td->ep_type == UE_INTERRUPT) { /* * The USB specification does not * mandate a particular data toggle * value for USB INTERRUPT * transfers. Switch the data toggle * value to receive the packet * correctly: */ if (hcint & HCINT_DATATGLERR) { DPRINTF("Retrying packet due to " "data toggle error\n"); td->toggle ^= 1; goto receive_pkt; } } else if (td->ep_type == UE_ISOCHRONOUS) { if (td->hcsplt != 0) { /* * Sometimes the complete * split packet may be queued * too early and the * transaction translator will * return a NAK. Ignore * this message and retry the * complete split instead. */ DPRINTF("Retrying complete split\n"); goto receive_pkt; } goto complete; } td->did_nak = 1; td->tt_scheduled = 0; if (td->hcsplt != 0) goto receive_spkt; else goto receive_pkt; } else if (hcint & HCINT_NYET) { if (td->hcsplt != 0) { /* try again */ goto receive_pkt; } else { /* not a valid token for IN endpoints */ td->error_any = 1; goto complete; } } else if (hcint & HCINT_ACK) { /* wait for data - ACK arrived first */ if (!(hcint & HCINT_SOFTWARE_ONLY)) goto busy; if (td->ep_type == UE_ISOCHRONOUS) { /* check if we are complete */ if (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN) { goto complete; } else if (td->hcsplt != 0) { goto receive_pkt; } else { /* get more packets */ goto busy; } } else { /* check if we are complete */ if ((td->remainder == 0) || (td->got_short != 0)) { if (td->short_pkt) goto complete; /* * Else need to receive a zero length * packet. */ } td->tt_scheduled = 0; td->did_nak = 0; if (td->hcsplt != 0) goto receive_spkt; else goto receive_pkt; } } break; case DWC_CHAN_ST_WAIT_S_ANE: /* * NOTE: The DWC OTG hardware provides a fake ACK in * case of interrupt and isochronous transfers: */ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto receive_spkt; } else if (hcint & HCINT_NYET) { td->tt_scheduled = 0; goto receive_spkt; } else if (hcint & HCINT_ACK) { td->did_nak = 0; goto receive_pkt; } break; case DWC_CHAN_ST_WAIT_C_PKT: goto receive_pkt; default: break; } goto busy; receive_pkt: /* free existing channel, if any */ dwc_otg_host_channel_free(sc, td); if (td->hcsplt != 0) { delta = td->tt_complete_slot - sc->sc_last_frame_num - 1; if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { if (td->ep_type != UE_ISOCHRONOUS) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } } delta = sc->sc_last_frame_num - td->tt_start_slot; if (delta > DWC_OTG_TT_SLOT_MAX) { if (td->ep_type != UE_ISOCHRONOUS) { /* we missed the service interval */ td->error_any = 1; } goto complete; } /* complete split */ td->hcsplt |= HCSPLT_COMPSPLT; } else if (dwc_otg_host_rate_check(sc, td)) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } /* allocate a new channel */ if (dwc_otg_host_channel_alloc(sc, td, 0)) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } td->state = DWC_CHAN_ST_WAIT_ANE; for (x = 0; x != td->max_packet_count; x++) { channel = td->channel[x]; /* receive one packet */ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (td->max_packet_size << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) : (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT))); DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); hcchar = td->hcchar; hcchar |= HCCHAR_EPDIR_IN; if (td->ep_type == UE_ISOCHRONOUS) { if (td->hcsplt != 0) { /* continously buffer */ if (sc->sc_last_frame_num & 1) hcchar &= ~HCCHAR_ODDFRM; else hcchar |= HCCHAR_ODDFRM; } else { /* multi buffer, if any */ if (sc->sc_last_frame_num & 1) hcchar |= HCCHAR_ODDFRM; else hcchar &= ~HCCHAR_ODDFRM; } } else { hcchar &= ~HCCHAR_ODDFRM; } /* must enable channel before data can be received */ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); } /* wait until next slot before trying complete split */ td->tt_complete_slot = sc->sc_last_frame_num + 1; goto busy; receive_spkt: /* free existing channel(s), if any */ dwc_otg_host_channel_free(sc, td); delta = td->tt_start_slot - sc->sc_last_frame_num - 1; if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { td->state = DWC_CHAN_ST_START; goto busy; } delta = sc->sc_last_frame_num - td->tt_start_slot; if (delta > 5) { /* missed it */ td->tt_scheduled = 0; td->state = DWC_CHAN_ST_START; goto busy; } /* allocate a new channel */ if (dwc_otg_host_channel_alloc(sc, td, 0)) { td->state = DWC_CHAN_ST_START; goto busy; } channel = td->channel[0]; td->hcsplt &= ~HCSPLT_COMPSPLT; td->state = DWC_CHAN_ST_WAIT_S_ANE; /* receive one packet */ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); /* send after next SOF event */ if ((sc->sc_last_frame_num & 1) == 0 && td->ep_type == UE_ISOCHRONOUS) td->hcchar |= HCCHAR_ODDFRM; else td->hcchar &= ~HCCHAR_ODDFRM; hcchar = td->hcchar; hcchar |= HCCHAR_EPDIR_IN; /* wait until next slot before trying complete split */ td->tt_complete_slot = sc->sc_last_frame_num + 1; /* must enable channel before data can be received */ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); busy: return (1); /* busy */ complete: dwc_otg_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t dwc_otg_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint32_t temp; uint16_t count; uint8_t got_short; got_short = 0; /* check endpoint status */ if (sc->sc_last_rx_status == 0) goto not_complete; if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != td->ep_no) goto not_complete; /* check for SETUP packet */ if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) == GRXSTSRD_STP_DATA || (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) == GRXSTSRD_STP_COMPLETE) { if (td->remainder == 0) { /* * We are actually complete and have * received the next SETUP */ DPRINTFN(5, "faking complete\n"); return (0); /* complete */ } /* * USB Host Aborted the transfer. */ td->error_any = 1; return (0); /* complete */ } if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) != GRXSTSRD_OUT_DATA) { /* release FIFO */ dwc_otg_common_rx_ack(sc); goto not_complete; } /* get the packet byte count */ count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status); /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error_any = 1; /* release FIFO */ dwc_otg_common_rx_ack(sc); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error_any = 1; /* release FIFO */ dwc_otg_common_rx_ack(sc); return (0); /* we are complete */ } /* read data from FIFO */ dwc_otg_read_fifo(sc, td->pc, td->offset, count); td->remainder -= count; td->offset += count; /* release FIFO */ dwc_otg_common_rx_ack(sc); temp = sc->sc_out_ctl[td->ep_no]; /* check for isochronous mode */ if ((temp & DIEPCTL_EPTYPE_MASK) == (DIEPCTL_EPTYPE_ISOC << DIEPCTL_EPTYPE_SHIFT)) { /* toggle odd or even frame bit */ if (temp & DIEPCTL_SETD1PID) { temp &= ~DIEPCTL_SETD1PID; temp |= DIEPCTL_SETD0PID; } else { temp &= ~DIEPCTL_SETD0PID; temp |= DIEPCTL_SETD1PID; } sc->sc_out_ctl[td->ep_no] = temp; } /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ return (0); } /* else need to receive a zero length packet */ } not_complete: /* enable SETUP and transfer complete interrupt */ if (td->ep_no == 0) { DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(0), DXEPTSIZ_SET_MULTI(3) | DXEPTSIZ_SET_NPKT(1) | DXEPTSIZ_SET_NBYTES(td->max_packet_size)); } else { /* allow reception of multiple packets */ DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(td->ep_no), DXEPTSIZ_SET_MULTI(1) | DXEPTSIZ_SET_NPKT(4) | DXEPTSIZ_SET_NBYTES(4 * ((td->max_packet_size + 3) & ~3))); } temp = sc->sc_out_ctl[td->ep_no]; DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(td->ep_no), temp | DOEPCTL_EPENA | DOEPCTL_CNAK); return (1); /* not complete */ } static uint8_t dwc_otg_host_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint32_t count; uint32_t hcint; uint32_t hcchar; uint8_t delta; uint8_t channel; uint8_t x; dwc_otg_host_dump_rx(sc, td); /* check that last channel is complete */ channel = td->channel[td->npkt]; if (channel < DWC_OTG_MAX_CHANNELS) { hcint = sc->sc_chan_state[channel].hcint; DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", channel, td->state, hcint, DWC_OTG_READ_4(sc, DOTG_HCCHAR(channel)), DWC_OTG_READ_4(sc, DOTG_HCTSIZ(channel))); if (hcint & (HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { /* give success bits priority over failure bits */ } else if (hcint & HCINT_STALL) { DPRINTF("CH=%d STALL\n", channel); td->error_stall = 1; td->error_any = 1; goto complete; } else if (hcint & HCINT_ERRORS) { DPRINTF("CH=%d ERROR\n", channel); td->errcnt++; if (td->hcsplt != 0 || td->errcnt >= 3) { td->error_any = 1; goto complete; } } if (hcint & (HCINT_ERRORS | HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { if (!(hcint & HCINT_ERRORS)) td->errcnt = 0; } } else { hcint = 0; } switch (td->state) { case DWC_CHAN_ST_START: goto send_pkt; case DWC_CHAN_ST_WAIT_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto send_pkt; } else if (hcint & (HCINT_ACK | HCINT_NYET)) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle ^= 1; /* check if next response will be a NAK */ if (hcint & HCINT_NYET) td->did_nak = 1; else td->did_nak = 0; td->tt_scheduled = 0; /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) goto complete; /* * Else we need to transmit a short * packet: */ } goto send_pkt; } break; case DWC_CHAN_ST_WAIT_S_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto send_pkt; } else if (hcint & (HCINT_ACK | HCINT_NYET)) { td->did_nak = 0; goto send_cpkt; } break; case DWC_CHAN_ST_WAIT_C_ANE: if (hcint & HCINT_NYET) { goto send_cpkt; } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { td->did_nak = 1; td->tt_scheduled = 0; goto send_pkt; } else if (hcint & HCINT_ACK) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle ^= 1; td->did_nak = 0; td->tt_scheduled = 0; /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) goto complete; /* else we need to transmit a short packet */ } goto send_pkt; } break; case DWC_CHAN_ST_WAIT_C_PKT: goto send_cpkt; case DWC_CHAN_ST_TX_WAIT_ISOC: /* Check if ISOCHRONOUS OUT traffic is complete */ if ((hcint & HCINT_HCH_DONE_MASK) == 0) break; td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; goto complete; default: break; } goto busy; send_pkt: /* free existing channel(s), if any */ dwc_otg_host_channel_free(sc, td); if (td->hcsplt != 0) { delta = td->tt_start_slot - sc->sc_last_frame_num - 1; if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { td->state = DWC_CHAN_ST_START; goto busy; } delta = sc->sc_last_frame_num - td->tt_start_slot; if (delta > 5) { /* missed it */ td->tt_scheduled = 0; td->state = DWC_CHAN_ST_START; goto busy; } } else if (dwc_otg_host_rate_check(sc, td)) { td->state = DWC_CHAN_ST_START; goto busy; } /* allocate a new channel */ if (dwc_otg_host_channel_alloc(sc, td, 1)) { td->state = DWC_CHAN_ST_START; goto busy; } /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } if (td->ep_type == UE_ISOCHRONOUS) { /* ISOCHRONOUS OUT transfers don't have any ACKs */ td->state = DWC_CHAN_ST_TX_WAIT_ISOC; td->hcsplt &= ~HCSPLT_COMPSPLT; if (td->hcsplt != 0) { /* get maximum transfer length */ count = td->remainder; if (count > HCSPLT_XACTLEN_BURST) { DPRINTF("TT overflow\n"); td->error_any = 1; goto complete; } /* Update transaction position */ td->hcsplt &= ~HCSPLT_XACTPOS_MASK; td->hcsplt |= (HCSPLT_XACTPOS_ALL << HCSPLT_XACTPOS_SHIFT); } } else if (td->hcsplt != 0) { td->hcsplt &= ~HCSPLT_COMPSPLT; /* Wait for ACK/NAK/ERR from TT */ td->state = DWC_CHAN_ST_WAIT_S_ANE; } else { /* Wait for ACK/NAK/STALL from device */ td->state = DWC_CHAN_ST_WAIT_ANE; } td->tx_bytes = 0; for (x = 0; x != td->max_packet_count; x++) { uint32_t rem_bytes; channel = td->channel[x]; /* send one packet at a time */ count = td->max_packet_size; rem_bytes = td->remainder - td->tx_bytes; if (rem_bytes < count) { /* we have a short packet */ td->short_pkt = 1; count = rem_bytes; } if (count == rem_bytes) { /* last packet */ switch (x) { case 0: DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) : (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT))); break; case 1: DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT)); break; default: DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_DATA2 << HCTSIZ_PID_SHIFT)); break; } } else if (td->ep_type == UE_ISOCHRONOUS && td->max_packet_count > 1) { /* ISOCHRONOUS multi packet */ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_MDATA << HCTSIZ_PID_SHIFT)); } else { /* TODO: HCTSIZ_DOPNG */ /* standard BULK/INTERRUPT/CONTROL packet */ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) : (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT))); } DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); hcchar = td->hcchar; hcchar &= ~HCCHAR_EPDIR_IN; /* send after next SOF event */ if ((sc->sc_last_frame_num & 1) == 0 && td->ep_type == UE_ISOCHRONOUS) hcchar |= HCCHAR_ODDFRM; else hcchar &= ~HCCHAR_ODDFRM; /* must enable before writing data to FIFO */ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); if (count != 0) { /* write data into FIFO */ dwc_otg_write_fifo(sc, td->pc, td->offset + td->tx_bytes, DOTG_DFIFO(channel), count); } /* store number of bytes transmitted */ td->tx_bytes += count; /* store last packet index */ td->npkt = x; /* check for last packet */ if (count == rem_bytes) break; } goto busy; send_cpkt: /* free existing channel, if any */ dwc_otg_host_channel_free(sc, td); delta = td->tt_complete_slot - sc->sc_last_frame_num - 1; if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } delta = sc->sc_last_frame_num - td->tt_start_slot; if (delta > DWC_OTG_TT_SLOT_MAX) { /* we missed the service interval */ if (td->ep_type != UE_ISOCHRONOUS) td->error_any = 1; goto complete; } /* allocate a new channel */ if (dwc_otg_host_channel_alloc(sc, td, 0)) { td->state = DWC_CHAN_ST_WAIT_C_PKT; goto busy; } channel = td->channel[0]; td->hcsplt |= HCSPLT_COMPSPLT; td->state = DWC_CHAN_ST_WAIT_C_ANE; DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); hcchar = td->hcchar; hcchar &= ~HCCHAR_EPDIR_IN; /* receive complete split ASAP */ if ((sc->sc_last_frame_num & 1) != 0 && td->ep_type == UE_ISOCHRONOUS) hcchar |= HCCHAR_ODDFRM; else hcchar &= ~HCCHAR_ODDFRM; /* must enable channel before data can be received */ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); /* wait until next slot before trying complete split */ td->tt_complete_slot = sc->sc_last_frame_num + 1; busy: return (1); /* busy */ complete: dwc_otg_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t dwc_otg_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint32_t max_buffer; uint32_t count; uint32_t fifo_left; uint32_t mpkt; uint32_t temp; uint8_t to; to = 3; /* don't loop forever! */ max_buffer = sc->sc_hw_ep_profile[td->ep_no].max_buffer; repeat: /* check for for endpoint 0 data */ temp = sc->sc_last_rx_status; if ((td->ep_no == 0) && (temp != 0) && (GRXSTSRD_CHNUM_GET(temp) == 0)) { if ((temp & GRXSTSRD_PKTSTS_MASK) != GRXSTSRD_STP_DATA && (temp & GRXSTSRD_PKTSTS_MASK) != GRXSTSRD_STP_COMPLETE) { /* dump data - wrong direction */ dwc_otg_common_rx_ack(sc); } else { /* * The current transfer was cancelled * by the USB Host: */ td->error_any = 1; return (0); /* complete */ } } /* fill in more TX data, if possible */ if (td->tx_bytes != 0) { uint16_t cpkt; /* check if packets have been transferred */ temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no)); /* get current packet number */ cpkt = DXEPTSIZ_GET_NPKT(temp); if (cpkt >= td->npkt) { fifo_left = 0; } else { if (max_buffer != 0) { fifo_left = (td->npkt - cpkt) * td->max_packet_size; if (fifo_left > max_buffer) fifo_left = max_buffer; } else { fifo_left = td->max_packet_size; } } count = td->tx_bytes; if (count > fifo_left) count = fifo_left; if (count != 0) { /* write data into FIFO */ dwc_otg_write_fifo(sc, td->pc, td->offset, DOTG_DFIFO(td->ep_no), count); td->tx_bytes -= count; td->remainder -= count; td->offset += count; td->npkt = cpkt; } if (td->tx_bytes != 0) goto not_complete; /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) return (0); /* complete */ /* else we need to transmit a short packet */ } } if (!to--) goto not_complete; /* check if not all packets have been transferred */ temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no)); if (DXEPTSIZ_GET_NPKT(temp) != 0) { DPRINTFN(5, "busy ep=%d npkt=%d DIEPTSIZ=0x%08x " "DIEPCTL=0x%08x\n", td->ep_no, DXEPTSIZ_GET_NPKT(temp), temp, DWC_OTG_READ_4(sc, DOTG_DIEPCTL(td->ep_no))); goto not_complete; } DPRINTFN(5, "rem=%u ep=%d\n", td->remainder, td->ep_no); /* try to optimise by sending more data */ if ((max_buffer != 0) && ((td->max_packet_size & 3) == 0)) { /* send multiple packets at the same time */ mpkt = max_buffer / td->max_packet_size; if (mpkt > 0x3FE) mpkt = 0x3FE; count = td->remainder; if (count > 0x7FFFFF) count = 0x7FFFFF - (0x7FFFFF % td->max_packet_size); td->npkt = count / td->max_packet_size; /* * NOTE: We could use 0x3FE instead of "mpkt" in the * check below to get more throughput, but then we * have a dependency towards non-generic chip features * to disable the TX-FIFO-EMPTY interrupts on a per * endpoint basis. Increase the maximum buffer size of * the IN endpoint to increase the performance. */ if (td->npkt > mpkt) { td->npkt = mpkt; count = td->max_packet_size * mpkt; } else if ((count == 0) || (count % td->max_packet_size)) { /* we are transmitting a short packet */ td->npkt++; td->short_pkt = 1; } } else { /* send one packet at a time */ mpkt = 1; count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } td->npkt = 1; } DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(td->ep_no), DXEPTSIZ_SET_MULTI(1) | DXEPTSIZ_SET_NPKT(td->npkt) | DXEPTSIZ_SET_NBYTES(count)); /* make room for buffering */ td->npkt += mpkt; temp = sc->sc_in_ctl[td->ep_no]; /* check for isochronous mode */ if ((temp & DIEPCTL_EPTYPE_MASK) == (DIEPCTL_EPTYPE_ISOC << DIEPCTL_EPTYPE_SHIFT)) { /* toggle odd or even frame bit */ if (temp & DIEPCTL_SETD1PID) { temp &= ~DIEPCTL_SETD1PID; temp |= DIEPCTL_SETD0PID; } else { temp &= ~DIEPCTL_SETD0PID; temp |= DIEPCTL_SETD1PID; } sc->sc_in_ctl[td->ep_no] = temp; } /* must enable before writing data to FIFO */ DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(td->ep_no), temp | DIEPCTL_EPENA | DIEPCTL_CNAK); td->tx_bytes = count; /* check remainder */ if (td->tx_bytes == 0 && td->remainder == 0) { if (td->short_pkt) return (0); /* complete */ /* else we need to transmit a short packet */ } goto repeat; not_complete: return (1); /* not complete */ } static uint8_t dwc_otg_data_tx_sync(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { uint32_t temp; /* * If all packets are transferred we are complete: */ temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no)); /* check that all packets have been transferred */ if (DXEPTSIZ_GET_NPKT(temp) != 0) { DPRINTFN(5, "busy ep=%d\n", td->ep_no); goto not_complete; } return (0); not_complete: /* we only want to know if there is a SETUP packet or free IN packet */ temp = sc->sc_last_rx_status; if ((td->ep_no == 0) && (temp != 0) && (GRXSTSRD_CHNUM_GET(temp) == 0)) { if ((temp & GRXSTSRD_PKTSTS_MASK) == GRXSTSRD_STP_DATA || (temp & GRXSTSRD_PKTSTS_MASK) == GRXSTSRD_STP_COMPLETE) { DPRINTFN(5, "faking complete\n"); /* * Race condition: We are complete! */ return (0); } else { /* dump data - wrong direction */ dwc_otg_common_rx_ack(sc); } } return (1); /* not complete */ } static void dwc_otg_xfer_do_fifo(struct dwc_otg_softc *sc, struct usb_xfer *xfer) { struct dwc_otg_td *td; uint8_t toggle; uint8_t tmr_val; uint8_t tmr_res; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; if (td == NULL) return; while (1) { if ((td->func) (sc, td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error_any) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) goto done; } /* * Fetch the next transfer descriptor and transfer * some flags to the next transfer descriptor */ tmr_res = td->tmr_res; tmr_val = td->tmr_val; toggle = td->toggle; td = td->obj_next; xfer->td_transfer_cache = td; td->toggle = toggle; /* transfer toggle */ td->tmr_res = tmr_res; td->tmr_val = tmr_val; } return; done: xfer->td_transfer_cache = NULL; sc->sc_xfer_complete = 1; } static uint8_t dwc_otg_xfer_do_complete_locked(struct dwc_otg_softc *sc, struct usb_xfer *xfer) { struct dwc_otg_td *td; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; if (td == NULL) { /* compute all actual lengths */ dwc_otg_standard_done(xfer); return (1); } return (0); } static void dwc_otg_timer(void *_sc) { struct dwc_otg_softc *sc = _sc; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTF("\n"); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* increment timer value */ sc->sc_tmr_val++; /* enable SOF interrupt, which will poll jobs */ dwc_otg_enable_sof_irq(sc); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); if (sc->sc_timer_active) { /* restart timer */ usb_callout_reset(&sc->sc_timer, hz / (1000 / DWC_OTG_HOST_TIMER_RATE), &dwc_otg_timer, sc); } } static void dwc_otg_timer_start(struct dwc_otg_softc *sc) { if (sc->sc_timer_active != 0) return; sc->sc_timer_active = 1; /* restart timer */ usb_callout_reset(&sc->sc_timer, hz / (1000 / DWC_OTG_HOST_TIMER_RATE), &dwc_otg_timer, sc); } static void dwc_otg_timer_stop(struct dwc_otg_softc *sc) { if (sc->sc_timer_active == 0) return; sc->sc_timer_active = 0; /* stop timer */ usb_callout_stop(&sc->sc_timer); } static uint16_t dwc_otg_compute_isoc_rx_tt_slot(struct dwc_otg_tt_info *pinfo) { if (pinfo->slot_index < DWC_OTG_TT_SLOT_MAX) pinfo->slot_index++; return (pinfo->slot_index); } static uint8_t dwc_otg_update_host_transfer_schedule_locked(struct dwc_otg_softc *sc) { TAILQ_HEAD(, usb_xfer) head; struct usb_xfer *xfer; struct usb_xfer *xfer_next; struct dwc_otg_td *td; uint16_t temp; uint16_t slot; temp = DWC_OTG_READ_4(sc, DOTG_HFNUM) & DWC_OTG_FRAME_MASK; if (sc->sc_last_frame_num == temp) return (0); sc->sc_last_frame_num = temp; TAILQ_INIT(&head); if ((temp & 7) == 0) { /* reset the schedule */ memset(sc->sc_tt_info, 0, sizeof(sc->sc_tt_info)); TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->ep_type != UE_ISOCHRONOUS) continue; /* check for IN direction */ if ((td->hcchar & HCCHAR_EPDIR_IN) != 0) continue; sc->sc_needsof = 1; if (td->hcsplt == 0 || td->tt_scheduled != 0) continue; /* compute slot */ slot = dwc_otg_compute_isoc_rx_tt_slot( sc->sc_tt_info + td->tt_index); if (slot > 3) { /* * Not enough time to get complete * split executed. */ continue; } /* Delayed start */ td->tt_start_slot = temp + slot; td->tt_scheduled = 1; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->ep_type != UE_ISOCHRONOUS) continue; /* check for OUT direction */ if ((td->hcchar & HCCHAR_EPDIR_IN) == 0) continue; sc->sc_needsof = 1; if (td->hcsplt == 0 || td->tt_scheduled != 0) continue; /* Start ASAP */ td->tt_start_slot = temp; td->tt_scheduled = 1; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->ep_type != UE_INTERRUPT) continue; if (td->tt_scheduled != 0) { sc->sc_needsof = 1; continue; } if (dwc_otg_host_rate_check_interrupt(sc, td)) continue; if (td->hcsplt == 0) { sc->sc_needsof = 1; td->tt_scheduled = 1; continue; } /* start ASAP */ td->tt_start_slot = temp; sc->sc_needsof = 1; td->tt_scheduled = 1; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->ep_type != UE_CONTROL) { continue; } sc->sc_needsof = 1; if (td->hcsplt == 0 || td->tt_scheduled != 0) continue; /* start ASAP */ td->tt_start_slot = temp; td->tt_scheduled = 1; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } } if ((temp & 7) < 6) { TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->ep_type != UE_BULK) { continue; } sc->sc_needsof = 1; if (td->hcsplt == 0 || td->tt_scheduled != 0) continue; /* start ASAP */ td->tt_start_slot = temp; td->tt_scheduled = 1; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } } /* Put TT transfers in execution order at the end */ TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry); /* move all TT transfers in front, keeping the current order */ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->hcsplt == 0) continue; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } TAILQ_CONCAT(&head, &sc->sc_bus.intr_q.head, wait_entry); TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry); /* put non-TT non-ISOCHRONOUS transfers last */ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { td = xfer->td_transfer_cache; if (td == NULL || td->hcsplt != 0 || td->ep_type == UE_ISOCHRONOUS) continue; TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); TAILQ_INSERT_TAIL(&head, xfer, wait_entry); } TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry); if ((temp & 7) == 0) { DPRINTFN(12, "SOF interrupt #%d, needsof=%d\n", (int)temp, (int)sc->sc_needsof); /* update SOF IRQ mask */ if (sc->sc_irq_mask & GINTMSK_SOFMSK) { if (sc->sc_needsof == 0) { sc->sc_irq_mask &= ~GINTMSK_SOFMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } } else { if (sc->sc_needsof != 0) { sc->sc_irq_mask |= GINTMSK_SOFMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } } /* clear need SOF flag */ sc->sc_needsof = 0; } return (1); } static void dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *sc) { struct usb_xfer *xfer; uint32_t count; uint32_t temp; uint32_t haint; uint8_t got_rx_status; uint8_t x; if (sc->sc_flags.status_device_mode == 0) { /* * Update host transfer schedule, so that new * transfers can be issued: */ dwc_otg_update_host_transfer_schedule_locked(sc); } count = 0; repeat: if (++count == 16) { /* give other interrupts a chance */ DPRINTF("Yield\n"); return; } /* get all host channel interrupts */ haint = DWC_OTG_READ_4(sc, DOTG_HAINT); while (1) { x = ffs(haint) - 1; if (x >= sc->sc_host_ch_max) break; temp = DWC_OTG_READ_4(sc, DOTG_HCINT(x)); DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), temp); temp &= ~HCINT_SOFTWARE_ONLY; sc->sc_chan_state[x].hcint |= temp; haint &= ~(1U << x); } if (sc->sc_last_rx_status == 0) { temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS); if (temp & GINTSTS_RXFLVL) { /* pop current status */ sc->sc_last_rx_status = DWC_OTG_READ_4(sc, DOTG_GRXSTSPD); } if (sc->sc_last_rx_status != 0) { uint8_t ep_no; temp = sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK; /* non-data messages we simply skip */ if (temp != GRXSTSRD_STP_DATA && temp != GRXSTSRD_STP_COMPLETE && temp != GRXSTSRD_OUT_DATA) { /* check for halted channel */ if (temp == GRXSTSRH_HALTED) { ep_no = GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status); sc->sc_chan_state[ep_no].wait_halted = 0; DPRINTFN(5, "channel halt complete ch=%u\n", ep_no); } /* store bytes and FIFO offset */ sc->sc_current_rx_bytes = 0; sc->sc_current_rx_fifo = 0; /* acknowledge status */ dwc_otg_common_rx_ack(sc); goto repeat; } temp = GRXSTSRD_BCNT_GET( sc->sc_last_rx_status); ep_no = GRXSTSRD_CHNUM_GET( sc->sc_last_rx_status); /* store bytes and FIFO offset */ sc->sc_current_rx_bytes = (temp + 3) & ~3; sc->sc_current_rx_fifo = DOTG_DFIFO(ep_no); DPRINTF("Reading %d bytes from ep %d\n", temp, ep_no); /* check if we should dump the data */ if (!(sc->sc_active_rx_ep & (1U << ep_no))) { dwc_otg_common_rx_ack(sc); goto repeat; } got_rx_status = 1; DPRINTFN(5, "RX status = 0x%08x: ch=%d pid=%d bytes=%d sts=%d\n", sc->sc_last_rx_status, ep_no, (sc->sc_last_rx_status >> 15) & 3, GRXSTSRD_BCNT_GET(sc->sc_last_rx_status), (sc->sc_last_rx_status >> 17) & 15); } else { got_rx_status = 0; } } else { uint8_t ep_no; ep_no = GRXSTSRD_CHNUM_GET( sc->sc_last_rx_status); /* check if we should dump the data */ if (!(sc->sc_active_rx_ep & (1U << ep_no))) { dwc_otg_common_rx_ack(sc); goto repeat; } got_rx_status = 1; } /* execute FIFOs */ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) dwc_otg_xfer_do_fifo(sc, xfer); if (got_rx_status) { /* check if data was consumed */ if (sc->sc_last_rx_status == 0) goto repeat; /* disable RX FIFO level interrupt */ sc->sc_irq_mask &= ~GINTMSK_RXFLVLMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } } static void dwc_otg_interrupt_complete_locked(struct dwc_otg_softc *sc) { struct usb_xfer *xfer; repeat: /* scan for completion events */ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (dwc_otg_xfer_do_complete_locked(sc, xfer)) goto repeat; } } static void dwc_otg_vbus_interrupt(struct dwc_otg_softc *sc, uint8_t is_on) { DPRINTFN(5, "vbus = %u\n", is_on); /* * If the USB host mode is forced, then assume VBUS is always * present else rely on the input to this function: */ if ((is_on != 0) || (sc->sc_mode == DWC_MODE_HOST)) { if (!sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 1; /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); } } else { if (sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); } } } int dwc_otg_filter_interrupt(void *arg) { struct dwc_otg_softc *sc = arg; int retval = FILTER_HANDLED; uint32_t status; USB_BUS_SPIN_LOCK(&sc->sc_bus); /* read and clear interrupt status */ status = DWC_OTG_READ_4(sc, DOTG_GINTSTS); /* clear interrupts we are handling here */ DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status & ~DWC_OTG_MSK_GINT_THREAD_IRQ); /* check for USB state change interrupts */ if ((status & DWC_OTG_MSK_GINT_THREAD_IRQ) != 0) retval = FILTER_SCHEDULE_THREAD; /* clear FIFO empty interrupts */ if (status & sc->sc_irq_mask & (GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP)) { sc->sc_irq_mask &= ~(GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP); DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } /* clear all IN endpoint interrupts */ if (status & GINTSTS_IEPINT) { uint32_t temp; uint8_t x; for (x = 0; x != sc->sc_dev_in_ep_max; x++) { temp = DWC_OTG_READ_4(sc, DOTG_DIEPINT(x)); /* * NOTE: Need to clear all interrupt bits, * because some appears to be unmaskable and * can cause an interrupt loop: */ if (temp != 0) DWC_OTG_WRITE_4(sc, DOTG_DIEPINT(x), temp); } } /* poll FIFOs, if any */ dwc_otg_interrupt_poll_locked(sc); if (sc->sc_xfer_complete != 0) retval = FILTER_SCHEDULE_THREAD; USB_BUS_SPIN_UNLOCK(&sc->sc_bus); return (retval); } void dwc_otg_interrupt(void *arg) { struct dwc_otg_softc *sc = arg; uint32_t status; USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* read and clear interrupt status */ status = DWC_OTG_READ_4(sc, DOTG_GINTSTS); /* clear interrupts we are handling here */ DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status & DWC_OTG_MSK_GINT_THREAD_IRQ); DPRINTFN(14, "GINTSTS=0x%08x HAINT=0x%08x HFNUM=0x%08x\n", status, DWC_OTG_READ_4(sc, DOTG_HAINT), DWC_OTG_READ_4(sc, DOTG_HFNUM)); if (status & GINTSTS_USBRST) { /* set correct state */ sc->sc_flags.status_device_mode = 1; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* Disable SOF interrupt */ sc->sc_irq_mask &= ~GINTMSK_SOFMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); } /* check for any bus state change interrupts */ if (status & GINTSTS_ENUMDONE) { uint32_t temp; DPRINTFN(5, "end of reset\n"); /* set correct state */ sc->sc_flags.status_device_mode = 1; sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; sc->sc_flags.status_low_speed = 0; sc->sc_flags.port_enabled = 1; /* reset FIFOs */ (void) dwc_otg_init_fifo(sc, DWC_MODE_DEVICE); /* reset function address */ dwc_otg_set_address(sc, 0); /* figure out enumeration speed */ temp = DWC_OTG_READ_4(sc, DOTG_DSTS); if (DSTS_ENUMSPD_GET(temp) == DSTS_ENUMSPD_HI) sc->sc_flags.status_high_speed = 1; else sc->sc_flags.status_high_speed = 0; /* * Disable resume and SOF interrupt, and enable * suspend and RX frame interrupt: */ sc->sc_irq_mask &= ~(GINTMSK_WKUPINTMSK | GINTMSK_SOFMSK); sc->sc_irq_mask |= GINTMSK_USBSUSPMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); } if (status & GINTSTS_PRTINT) { uint32_t hprt; hprt = DWC_OTG_READ_4(sc, DOTG_HPRT); /* clear change bits */ DWC_OTG_WRITE_4(sc, DOTG_HPRT, (hprt & ( HPRT_PRTPWR | HPRT_PRTENCHNG | HPRT_PRTCONNDET | HPRT_PRTOVRCURRCHNG)) | sc->sc_hprt_val); DPRINTFN(12, "GINTSTS=0x%08x, HPRT=0x%08x\n", status, hprt); sc->sc_flags.status_device_mode = 0; if (hprt & HPRT_PRTCONNSTS) sc->sc_flags.status_bus_reset = 1; else sc->sc_flags.status_bus_reset = 0; if ((hprt & HPRT_PRTENCHNG) && (hprt & HPRT_PRTENA) == 0) sc->sc_flags.change_enabled = 1; if (hprt & HPRT_PRTENA) sc->sc_flags.port_enabled = 1; else sc->sc_flags.port_enabled = 0; if (hprt & HPRT_PRTOVRCURRCHNG) sc->sc_flags.change_over_current = 1; if (hprt & HPRT_PRTOVRCURRACT) sc->sc_flags.port_over_current = 1; else sc->sc_flags.port_over_current = 0; if (hprt & HPRT_PRTPWR) sc->sc_flags.port_powered = 1; else sc->sc_flags.port_powered = 0; if (((hprt & HPRT_PRTSPD_MASK) >> HPRT_PRTSPD_SHIFT) == HPRT_PRTSPD_LOW) sc->sc_flags.status_low_speed = 1; else sc->sc_flags.status_low_speed = 0; if (((hprt & HPRT_PRTSPD_MASK) >> HPRT_PRTSPD_SHIFT) == HPRT_PRTSPD_HIGH) sc->sc_flags.status_high_speed = 1; else sc->sc_flags.status_high_speed = 0; if (hprt & HPRT_PRTCONNDET) sc->sc_flags.change_connect = 1; if (hprt & HPRT_PRTSUSP) dwc_otg_suspend_irq(sc); else dwc_otg_resume_irq(sc); /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); /* update host frame interval */ dwc_otg_update_host_frame_interval(sc); } /* * If resume and suspend is set at the same time we interpret * that like RESUME. Resume is set when there is at least 3 * milliseconds of inactivity on the USB BUS. */ if (status & GINTSTS_WKUPINT) { DPRINTFN(5, "resume interrupt\n"); dwc_otg_resume_irq(sc); } else if (status & GINTSTS_USBSUSP) { DPRINTFN(5, "suspend interrupt\n"); dwc_otg_suspend_irq(sc); } /* check VBUS */ if (status & (GINTSTS_USBSUSP | GINTSTS_USBRST | GINTMSK_OTGINTMSK | GINTSTS_SESSREQINT)) { uint32_t temp; temp = DWC_OTG_READ_4(sc, DOTG_GOTGCTL); DPRINTFN(5, "GOTGCTL=0x%08x\n", temp); dwc_otg_vbus_interrupt(sc, (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0); } if (sc->sc_xfer_complete != 0) { sc->sc_xfer_complete = 0; /* complete FIFOs, if any */ dwc_otg_interrupt_complete_locked(sc); } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } static void dwc_otg_setup_standard_chain_sub(struct dwc_otg_std_temp *temp) { struct dwc_otg_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->tx_bytes = 0; td->error_any = 0; td->error_stall = 0; td->npkt = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; td->set_toggle = 0; td->got_short = 0; td->did_nak = 0; td->channel[0] = DWC_OTG_MAX_CHANNELS; td->channel[1] = DWC_OTG_MAX_CHANNELS; td->channel[2] = DWC_OTG_MAX_CHANNELS; td->state = 0; td->errcnt = 0; td->tt_scheduled = 0; td->tt_xactpos = HCSPLT_XACTPOS_BEGIN; } static void dwc_otg_setup_standard_chain(struct usb_xfer *xfer) { struct dwc_otg_std_temp temp; struct dwc_otg_td *td; uint32_t x; uint8_t need_sync; uint8_t is_host; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; is_host = (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST); /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { if (is_host) temp.func = &dwc_otg_host_setup_tx; else temp.func = &dwc_otg_setup_rx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) temp.setup_alt_next = 0; } dwc_otg_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) { if (is_host) { temp.func = &dwc_otg_host_data_rx; need_sync = 0; } else { temp.func = &dwc_otg_data_tx; need_sync = 1; } } else { if (is_host) { temp.func = &dwc_otg_host_data_tx; need_sync = 0; } else { temp.func = &dwc_otg_data_rx; need_sync = 0; } } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } else { need_sync = 0; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer ? 0 : 1); } dwc_otg_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } if (xfer->flags_int.control_xfr) { /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we need to sync */ if (need_sync) { /* we need a SYNC point after TX */ temp.func = &dwc_otg_data_tx_sync; dwc_otg_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (xfer->endpointno & UE_DIR_IN) { if (is_host) { temp.func = &dwc_otg_host_data_tx; need_sync = 0; } else { temp.func = &dwc_otg_data_rx; need_sync = 0; } } else { if (is_host) { temp.func = &dwc_otg_host_data_rx; need_sync = 0; } else { temp.func = &dwc_otg_data_tx; need_sync = 1; } } dwc_otg_setup_standard_chain_sub(&temp); /* data toggle should be DATA1 */ td = temp.td; td->set_toggle = 1; if (need_sync) { /* we need a SYNC point after TX */ temp.func = &dwc_otg_data_tx_sync; dwc_otg_setup_standard_chain_sub(&temp); } } } else { /* check if we need to sync */ if (need_sync) { temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* we need a SYNC point after TX */ temp.func = &dwc_otg_data_tx_sync; dwc_otg_setup_standard_chain_sub(&temp); } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; if (is_host) { struct dwc_otg_softc *sc; uint32_t hcchar; uint32_t hcsplt; sc = DWC_OTG_BUS2SC(xfer->xroot->bus); /* get first again */ td = xfer->td_transfer_first; td->toggle = (xfer->endpoint->toggle_next ? 1 : 0); hcchar = (xfer->address << HCCHAR_DEVADDR_SHIFT) | ((xfer->endpointno & UE_ADDR) << HCCHAR_EPNUM_SHIFT) | (xfer->max_packet_size << HCCHAR_MPS_SHIFT) | HCCHAR_CHENA; /* * We are not always able to meet the timing * requirements of the USB interrupt endpoint's * complete split token, when doing transfers going * via a transaction translator. Use the CONTROL * transfer type instead of the INTERRUPT transfer * type in general, as a means to workaround * that. This trick should work for both FULL and LOW * speed USB traffic going through a TT. For non-TT * traffic it works as well. The reason for using * CONTROL type instead of BULK is that some TTs might * reject LOW speed BULK traffic. */ if (td->ep_type == UE_INTERRUPT) hcchar |= (UE_CONTROL << HCCHAR_EPTYPE_SHIFT); else hcchar |= (td->ep_type << HCCHAR_EPTYPE_SHIFT); if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) hcchar |= HCCHAR_EPDIR_IN; switch (xfer->xroot->udev->speed) { case USB_SPEED_LOW: hcchar |= HCCHAR_LSPDDEV; /* FALLTHROUGH */ case USB_SPEED_FULL: /* check if root HUB port is running High Speed */ if (dwc_otg_uses_split(xfer->xroot->udev)) { hcsplt = HCSPLT_SPLTENA | (xfer->xroot->udev->hs_port_no << HCSPLT_PRTADDR_SHIFT) | (xfer->xroot->udev->hs_hub_addr << HCSPLT_HUBADDR_SHIFT); } else { hcsplt = 0; } if (td->ep_type == UE_INTERRUPT) { uint32_t ival; ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE; if (ival == 0) ival = 1; else if (ival > 127) ival = 127; td->tmr_val = sc->sc_tmr_val + ival; td->tmr_res = ival; } else if (td->ep_type == UE_ISOCHRONOUS) { td->tmr_res = 1; td->tmr_val = sc->sc_last_frame_num; if (td->hcchar & HCCHAR_EPDIR_IN) td->tmr_val++; } else { td->tmr_val = 0; td->tmr_res = (uint8_t)sc->sc_last_frame_num; } break; case USB_SPEED_HIGH: hcsplt = 0; if (td->ep_type == UE_INTERRUPT) { uint32_t ival; hcchar |= ((xfer->max_packet_count & 3) << HCCHAR_MC_SHIFT); ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE; if (ival == 0) ival = 1; else if (ival > 127) ival = 127; td->tmr_val = sc->sc_tmr_val + ival; td->tmr_res = ival; } else if (td->ep_type == UE_ISOCHRONOUS) { hcchar |= ((xfer->max_packet_count & 3) << HCCHAR_MC_SHIFT); td->tmr_res = 1 << usbd_xfer_get_fps_shift(xfer); td->tmr_val = sc->sc_last_frame_num; if (td->hcchar & HCCHAR_EPDIR_IN) td->tmr_val += td->tmr_res; } else { td->tmr_val = 0; td->tmr_res = (uint8_t)sc->sc_last_frame_num; } break; default: hcsplt = 0; td->tmr_val = 0; td->tmr_res = 0; break; } /* store configuration in all TD's */ while (1) { td->hcchar = hcchar; td->hcsplt = hcsplt; if (((void *)td) == xfer->td_transfer_last) break; td = td->obj_next; } } } static void dwc_otg_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ dwc_otg_device_done(xfer, USB_ERR_TIMEOUT); } static void dwc_otg_start_standard_chain(struct usb_xfer *xfer) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); DPRINTFN(9, "\n"); /* * Poll one time in device mode, which will turn on the * endpoint interrupts. Else wait for SOF interrupt in host * mode. */ USB_BUS_SPIN_LOCK(&sc->sc_bus); if (sc->sc_flags.status_device_mode != 0) { dwc_otg_xfer_do_fifo(sc, xfer); if (dwc_otg_xfer_do_complete_locked(sc, xfer)) goto done; } else { struct dwc_otg_td *td = xfer->td_transfer_cache; if (td->ep_type == UE_ISOCHRONOUS && (td->hcchar & HCCHAR_EPDIR_IN) == 0) { /* * Need to start ISOCHRONOUS OUT transfer ASAP * because execution is delayed by one 125us * microframe: */ dwc_otg_xfer_do_fifo(sc, xfer); if (dwc_otg_xfer_do_complete_locked(sc, xfer)) goto done; } } /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &dwc_otg_timeout, xfer->timeout); } if (sc->sc_flags.status_device_mode != 0) goto done; /* enable SOF interrupt, if any */ dwc_otg_enable_sof_irq(sc); done: USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void dwc_otg_root_intr(struct dwc_otg_softc *sc) { DPRINTFN(9, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit */ sc->sc_hub_idata[0] = 0x02; /* we only have one port */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t dwc_otg_standard_done_sub(struct usb_xfer *xfer) { struct dwc_otg_td *td; uint32_t len; usb_error_t error; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; /* store last data toggle */ xfer->endpoint->toggle_next = td->toggle; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error_any = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error_any) { /* the transfer is finished */ error = (td->error_stall ? USB_ERR_STALLED : USB_ERR_IOERROR); td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error); } static void dwc_otg_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = dwc_otg_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = dwc_otg_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = dwc_otg_standard_done_sub(xfer); } done: dwc_otg_device_done(xfer, err); } /*------------------------------------------------------------------------* * dwc_otg_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void dwc_otg_device_done(struct usb_xfer *xfer, usb_error_t error) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); USB_BUS_SPIN_LOCK(&sc->sc_bus); if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { /* Interrupts are cleared by the interrupt handler */ } else { struct dwc_otg_td *td; td = xfer->td_transfer_cache; if (td != NULL) dwc_otg_host_channel_free(sc, td); } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void dwc_otg_xfer_stall(struct usb_xfer *xfer) { dwc_otg_device_done(xfer, USB_ERR_STALLED); } static void dwc_otg_set_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall) { struct dwc_otg_softc *sc; uint32_t temp; uint32_t reg; uint8_t ep_no; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } sc = DWC_OTG_BUS2SC(udev->bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* get endpoint address */ ep_no = ep->edesc->bEndpointAddress; DPRINTFN(5, "endpoint=0x%x\n", ep_no); if (ep_no & UE_DIR_IN) { reg = DOTG_DIEPCTL(ep_no & UE_ADDR); temp = sc->sc_in_ctl[ep_no & UE_ADDR]; } else { reg = DOTG_DOEPCTL(ep_no & UE_ADDR); temp = sc->sc_out_ctl[ep_no & UE_ADDR]; } /* disable and stall endpoint */ DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_EPDIS); DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_STALL); /* clear active OUT ep */ if (!(ep_no & UE_DIR_IN)) { sc->sc_active_rx_ep &= ~(1U << (ep_no & UE_ADDR)); if (sc->sc_last_rx_status != 0 && (ep_no & UE_ADDR) == GRXSTSRD_CHNUM_GET( sc->sc_last_rx_status)) { /* dump data */ dwc_otg_common_rx_ack(sc); /* poll interrupt */ dwc_otg_interrupt_poll_locked(sc); dwc_otg_interrupt_complete_locked(sc); } } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void dwc_otg_clear_stall_sub_locked(struct dwc_otg_softc *sc, uint32_t mps, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { uint32_t reg; uint32_t temp; if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } if (ep_dir) { reg = DOTG_DIEPCTL(ep_no); } else { reg = DOTG_DOEPCTL(ep_no); sc->sc_active_rx_ep |= (1U << ep_no); } /* round up and mask away the multiplier count */ mps = (mps + 3) & 0x7FC; if (ep_type == UE_BULK) { temp = DIEPCTL_EPTYPE_SET( DIEPCTL_EPTYPE_BULK) | DIEPCTL_USBACTEP; } else if (ep_type == UE_INTERRUPT) { temp = DIEPCTL_EPTYPE_SET( DIEPCTL_EPTYPE_INTERRUPT) | DIEPCTL_USBACTEP; } else { temp = DIEPCTL_EPTYPE_SET( DIEPCTL_EPTYPE_ISOC) | DIEPCTL_USBACTEP; } temp |= DIEPCTL_MPS_SET(mps); temp |= DIEPCTL_TXFNUM_SET(ep_no); if (ep_dir) sc->sc_in_ctl[ep_no] = temp; else sc->sc_out_ctl[ep_no] = temp; DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_EPDIS); DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_SETD0PID); DWC_OTG_WRITE_4(sc, reg, temp | DIEPCTL_SNAK); /* we only reset the transmit FIFO */ if (ep_dir) { dwc_otg_tx_fifo_reset(sc, GRSTCTL_TXFIFO(ep_no) | GRSTCTL_TXFFLSH); DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(ep_no), 0); } /* poll interrupt */ dwc_otg_interrupt_poll_locked(sc); dwc_otg_interrupt_complete_locked(sc); } static void dwc_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct dwc_otg_softc *sc; struct usb_endpoint_descriptor *ed; DPRINTFN(5, "endpoint=%p\n", ep); USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = DWC_OTG_BUS2SC(udev->bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* get endpoint descriptor */ ed = ep->edesc; /* reset endpoint */ dwc_otg_clear_stall_sub_locked(sc, UGETW(ed->wMaxPacketSize), (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void dwc_otg_device_state_change(struct usb_device *udev) { struct dwc_otg_softc *sc; uint8_t x; /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = DWC_OTG_BUS2SC(udev->bus); /* deactivate all other endpoint but the control endpoint */ if (udev->state == USB_STATE_CONFIGURED || udev->state == USB_STATE_ADDRESSED) { USB_BUS_LOCK(&sc->sc_bus); for (x = 1; x != sc->sc_dev_ep_max; x++) { if (x < sc->sc_dev_in_ep_max) { DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(x), DIEPCTL_EPDIS); DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(x), 0); } DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(x), DOEPCTL_EPDIS); DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(x), 0); } USB_BUS_UNLOCK(&sc->sc_bus); } } int dwc_otg_init(struct dwc_otg_softc *sc) { uint32_t temp; DPRINTF("start\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_2_0; sc->sc_bus.methods = &dwc_otg_bus_methods; usb_callout_init_mtx(&sc->sc_timer, &sc->sc_bus.bus_mtx, 0); USB_BUS_LOCK(&sc->sc_bus); /* turn on clocks */ dwc_otg_clocks_on(sc); temp = DWC_OTG_READ_4(sc, DOTG_GSNPSID); DPRINTF("Version = 0x%08x\n", temp); /* disconnect */ DWC_OTG_WRITE_4(sc, DOTG_DCTL, DCTL_SFTDISCON); /* wait for host to detect disconnect */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 32); DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL, GRSTCTL_CSFTRST); /* wait a little bit for block to reset */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 128); switch (sc->sc_mode) { case DWC_MODE_DEVICE: temp = GUSBCFG_FORCEDEVMODE; break; case DWC_MODE_HOST: temp = GUSBCFG_FORCEHOSTMODE; break; default: temp = 0; break; } if (sc->sc_phy_type == 0) sc->sc_phy_type = dwc_otg_phy_type + 1; if (sc->sc_phy_bits == 0) sc->sc_phy_bits = 16; /* select HSIC, ULPI, UTMI+ or internal PHY mode */ switch (sc->sc_phy_type) { case DWC_OTG_PHY_HSIC: DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, GUSBCFG_PHYIF | GUSBCFG_TRD_TIM_SET(5) | temp); DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0x000000EC); temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, temp & ~GLPMCFG_HSIC_CONN); DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, temp | GLPMCFG_HSIC_CONN); break; case DWC_OTG_PHY_ULPI: DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, GUSBCFG_ULPI_UTMI_SEL | GUSBCFG_TRD_TIM_SET(5) | temp); DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0); temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, temp & ~GLPMCFG_HSIC_CONN); break; case DWC_OTG_PHY_UTMI: DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, (sc->sc_phy_bits == 16 ? GUSBCFG_PHYIF : 0) | GUSBCFG_TRD_TIM_SET(5) | temp); DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0); temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, temp & ~GLPMCFG_HSIC_CONN); break; case DWC_OTG_PHY_INTERNAL: DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, GUSBCFG_PHYSEL | GUSBCFG_TRD_TIM_SET(5) | temp); DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0); temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, temp & ~GLPMCFG_HSIC_CONN); temp = DWC_OTG_READ_4(sc, DOTG_GGPIO); temp &= ~(DOTG_GGPIO_NOVBUSSENS | DOTG_GGPIO_I2CPADEN); temp |= (DOTG_GGPIO_VBUSASEN | DOTG_GGPIO_VBUSBSEN | DOTG_GGPIO_PWRDWN); DWC_OTG_WRITE_4(sc, DOTG_GGPIO, temp); break; default: break; } /* clear global nak */ DWC_OTG_WRITE_4(sc, DOTG_DCTL, DCTL_CGOUTNAK | DCTL_CGNPINNAK); /* disable USB port */ DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0xFFFFFFFF); /* wait 10ms */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* enable USB port */ DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0); /* wait 10ms */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG3); sc->sc_fifo_size = 4 * GHWCFG3_DFIFODEPTH_GET(temp); temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2); sc->sc_dev_ep_max = GHWCFG2_NUMDEVEPS_GET(temp); if (sc->sc_dev_ep_max > DWC_OTG_MAX_ENDPOINTS) sc->sc_dev_ep_max = DWC_OTG_MAX_ENDPOINTS; sc->sc_host_ch_max = GHWCFG2_NUMHSTCHNL_GET(temp); if (sc->sc_host_ch_max > DWC_OTG_MAX_CHANNELS) sc->sc_host_ch_max = DWC_OTG_MAX_CHANNELS; temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG4); sc->sc_dev_in_ep_max = GHWCFG4_NUM_IN_EP_GET(temp); DPRINTF("Total FIFO size = %d bytes, Device EPs = %d/%d Host CHs = %d\n", sc->sc_fifo_size, sc->sc_dev_ep_max, sc->sc_dev_in_ep_max, sc->sc_host_ch_max); /* setup FIFO */ if (dwc_otg_init_fifo(sc, sc->sc_mode)) { USB_BUS_UNLOCK(&sc->sc_bus); return (EINVAL); } /* enable interrupts */ sc->sc_irq_mask |= DWC_OTG_MSK_GINT_THREAD_IRQ; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); if (sc->sc_mode == DWC_MODE_OTG || sc->sc_mode == DWC_MODE_DEVICE) { /* enable all endpoint interrupts */ temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2); if (temp & GHWCFG2_MPI) { uint8_t x; DPRINTF("Disable Multi Process Interrupts\n"); for (x = 0; x != sc->sc_dev_in_ep_max; x++) { DWC_OTG_WRITE_4(sc, DOTG_DIEPEACHINTMSK(x), 0); DWC_OTG_WRITE_4(sc, DOTG_DOEPEACHINTMSK(x), 0); } DWC_OTG_WRITE_4(sc, DOTG_DEACHINTMSK, 0); } DWC_OTG_WRITE_4(sc, DOTG_DIEPMSK, DIEPMSK_XFERCOMPLMSK); DWC_OTG_WRITE_4(sc, DOTG_DOEPMSK, 0); DWC_OTG_WRITE_4(sc, DOTG_DAINTMSK, 0xFFFF); } if (sc->sc_mode == DWC_MODE_OTG || sc->sc_mode == DWC_MODE_HOST) { /* setup clocks */ temp = DWC_OTG_READ_4(sc, DOTG_HCFG); temp &= ~(HCFG_FSLSSUPP | HCFG_FSLSPCLKSEL_MASK); temp |= (1 << HCFG_FSLSPCLKSEL_SHIFT); DWC_OTG_WRITE_4(sc, DOTG_HCFG, temp); } /* only enable global IRQ */ DWC_OTG_WRITE_4(sc, DOTG_GAHBCFG, GAHBCFG_GLBLINTRMSK); /* turn off clocks */ dwc_otg_clocks_off(sc); /* read initial VBUS state */ temp = DWC_OTG_READ_4(sc, DOTG_GOTGCTL); DPRINTFN(5, "GOTCTL=0x%08x\n", temp); dwc_otg_vbus_interrupt(sc, (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0); USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ dwc_otg_do_poll(&sc->sc_bus); return (0); /* success */ } void dwc_otg_uninit(struct dwc_otg_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); /* stop host timer */ dwc_otg_timer_stop(sc); /* set disconnect */ DWC_OTG_WRITE_4(sc, DOTG_DCTL, DCTL_SFTDISCON); /* turn off global IRQ */ DWC_OTG_WRITE_4(sc, DOTG_GAHBCFG, 0); sc->sc_flags.port_enabled = 0; sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; dwc_otg_pull_down(sc); dwc_otg_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); usb_callout_drain(&sc->sc_timer); } static void dwc_otg_suspend(struct dwc_otg_softc *sc) { return; } static void dwc_otg_resume(struct dwc_otg_softc *sc) { return; } static void dwc_otg_do_poll(struct usb_bus *bus) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); dwc_otg_interrupt_poll_locked(sc); dwc_otg_interrupt_complete_locked(sc); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * DWC OTG bulk support * DWC OTG control support * DWC OTG interrupt support *------------------------------------------------------------------------*/ static void dwc_otg_device_non_isoc_open(struct usb_xfer *xfer) { } static void dwc_otg_device_non_isoc_close(struct usb_xfer *xfer) { dwc_otg_device_done(xfer, USB_ERR_CANCELLED); } static void dwc_otg_device_non_isoc_enter(struct usb_xfer *xfer) { } static void dwc_otg_device_non_isoc_start(struct usb_xfer *xfer) { /* setup TDs */ dwc_otg_setup_standard_chain(xfer); dwc_otg_start_standard_chain(xfer); } static const struct usb_pipe_methods dwc_otg_device_non_isoc_methods = { .open = dwc_otg_device_non_isoc_open, .close = dwc_otg_device_non_isoc_close, .enter = dwc_otg_device_non_isoc_enter, .start = dwc_otg_device_non_isoc_start, }; /*------------------------------------------------------------------------* * DWC OTG full speed isochronous support *------------------------------------------------------------------------*/ static void dwc_otg_device_isoc_open(struct usb_xfer *xfer) { } static void dwc_otg_device_isoc_close(struct usb_xfer *xfer) { dwc_otg_device_done(xfer, USB_ERR_CANCELLED); } static void dwc_otg_device_isoc_enter(struct usb_xfer *xfer) { } static void dwc_otg_device_isoc_start(struct usb_xfer *xfer) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t msframes; uint32_t framenum; uint8_t shift = usbd_xfer_get_fps_shift(xfer); DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); if (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST) { temp = DWC_OTG_READ_4(sc, DOTG_HFNUM); /* get the current frame index */ framenum = (temp & HFNUM_FRNUM_MASK); } else { temp = DWC_OTG_READ_4(sc, DOTG_DSTS); /* get the current frame index */ framenum = DSTS_SOFFN_GET(temp); } /* * Check if port is doing 8000 or 1000 frames per second: */ if (sc->sc_flags.status_high_speed) framenum /= 8; framenum &= DWC_OTG_FRAME_MASK; /* * Compute number of milliseconds worth of data traffic for * this USB transfer: */ if (xfer->xroot->udev->speed == USB_SPEED_HIGH) msframes = ((xfer->nframes << shift) + 7) / 8; else msframes = xfer->nframes; /* * check if the frame index is within the window where the frames * will be inserted */ temp = (framenum - xfer->endpoint->isoc_next) & DWC_OTG_FRAME_MASK; if ((xfer->endpoint->is_synced == 0) || (temp < msframes)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (framenum + 3) & DWC_OTG_FRAME_MASK; xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - framenum) & DWC_OTG_FRAME_MASK; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, framenum) + temp + msframes; /* setup TDs */ dwc_otg_setup_standard_chain(xfer); /* compute frame number for next insertion */ xfer->endpoint->isoc_next += msframes; /* start TD chain */ dwc_otg_start_standard_chain(xfer); } static const struct usb_pipe_methods dwc_otg_device_isoc_methods = { .open = dwc_otg_device_isoc_open, .close = dwc_otg_device_isoc_close, .enter = dwc_otg_device_isoc_enter, .start = dwc_otg_device_isoc_start, }; /*------------------------------------------------------------------------* * DWC OTG root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor dwc_otg_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_HSHUBSTT, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct dwc_otg_config_desc dwc_otg_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(dwc_otg_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | DWC_OTG_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_hub_descriptor_min dwc_otg_hubd = { .bDescLength = sizeof(dwc_otg_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "D\0W\0C\0O\0T\0G" #define STRING_PRODUCT \ "O\0T\0G\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, dwc_otg_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, dwc_otg_product); static usb_error_t dwc_otg_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_handle_clear_port_feature; case UR_SET_FEATURE: goto tr_handle_set_port_feature; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: goto tr_handle_get_port_status; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) { goto tr_stalled; } len = sizeof(dwc_otg_devd); ptr = (const void *)&dwc_otg_devd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) { goto tr_stalled; } len = sizeof(dwc_otg_confd); ptr = (const void *)&dwc_otg_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(dwc_otg_vendor); ptr = (const void *)&dwc_otg_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(dwc_otg_product); ptr = (const void *)&dwc_otg_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) { goto tr_stalled; } sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) { goto tr_stalled; } sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature: if (index != 1) goto tr_stalled; DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: dwc_otg_wakeup_peer(sc); break; case UHF_PORT_ENABLE: if (sc->sc_flags.status_device_mode == 0) { DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val | HPRT_PRTENA); } sc->sc_flags.port_enabled = 0; break; case UHF_C_PORT_RESET: sc->sc_flags.change_reset = 0; break; case UHF_C_PORT_ENABLE: sc->sc_flags.change_enabled = 0; break; case UHF_C_PORT_OVER_CURRENT: sc->sc_flags.change_over_current = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; if (sc->sc_mode == DWC_MODE_HOST || sc->sc_mode == DWC_MODE_OTG) { sc->sc_hprt_val = 0; DWC_OTG_WRITE_4(sc, DOTG_HPRT, HPRT_PRTENA); } dwc_otg_pull_down(sc); dwc_otg_clocks_off(sc); break; case UHF_C_PORT_CONNECTION: /* clear connect change flag */ sc->sc_flags.change_connect = 0; break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_set_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); switch (value) { case UHF_PORT_ENABLE: break; case UHF_PORT_SUSPEND: if (sc->sc_flags.status_device_mode == 0) { /* set suspend BIT */ sc->sc_hprt_val |= HPRT_PRTSUSP; DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val); /* generate HUB suspend event */ dwc_otg_suspend_irq(sc); } break; case UHF_PORT_RESET: if (sc->sc_flags.status_device_mode == 0) { DPRINTF("PORT RESET\n"); /* enable PORT reset */ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val | HPRT_PRTRST); /* Wait 62.5ms for reset to complete */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16); DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val); /* Wait 62.5ms for reset to complete */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16); /* reset FIFOs */ (void) dwc_otg_init_fifo(sc, DWC_MODE_HOST); sc->sc_flags.change_reset = 1; } else { err = USB_ERR_IOERROR; } break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; if (sc->sc_mode == DWC_MODE_HOST || sc->sc_mode == DWC_MODE_OTG) { sc->sc_hprt_val |= HPRT_PRTPWR; DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val); } if (sc->sc_mode == DWC_MODE_DEVICE || sc->sc_mode == DWC_MODE_OTG) { /* pull up D+, if any */ dwc_otg_pull_up(sc); } break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_get_port_status: DPRINTFN(9, "UR_GET_PORT_STATUS\n"); if (index != 1) goto tr_stalled; if (sc->sc_flags.status_vbus) dwc_otg_clocks_on(sc); else dwc_otg_clocks_off(sc); /* Select Device Side Mode */ if (sc->sc_flags.status_device_mode) { value = UPS_PORT_MODE_DEVICE; dwc_otg_timer_stop(sc); } else { value = 0; dwc_otg_timer_start(sc); } if (sc->sc_flags.status_high_speed) value |= UPS_HIGH_SPEED; else if (sc->sc_flags.status_low_speed) value |= UPS_LOW_SPEED; if (sc->sc_flags.port_powered) value |= UPS_PORT_POWER; if (sc->sc_flags.port_enabled) value |= UPS_PORT_ENABLED; if (sc->sc_flags.port_over_current) value |= UPS_OVERCURRENT_INDICATOR; if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) value |= UPS_CURRENT_CONNECT_STATUS; if (sc->sc_flags.status_suspend) value |= UPS_SUSPEND; USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_enabled) value |= UPS_C_PORT_ENABLED; if (sc->sc_flags.change_connect) value |= UPS_C_CONNECT_STATUS; if (sc->sc_flags.change_suspend) value |= UPS_C_SUSPEND; if (sc->sc_flags.change_reset) value |= UPS_C_PORT_RESET; if (sc->sc_flags.change_over_current) value |= UPS_C_OVERCURRENT_INDICATOR; USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) { goto tr_stalled; } ptr = (const void *)&dwc_otg_hubd; len = sizeof(dwc_otg_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: done: *plength = len; *pptr = ptr; return (err); } static void dwc_otg_xfer_setup(struct usb_setup_params *parm) { struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; uint8_t ep_no; uint8_t ep_type; xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 3; parm->hc_max_frame_size = 3 * 0x500; usbd_transfer_setup_sub(parm); /* * compute maximum number of TDs */ ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE); if (ep_type == UE_CONTROL) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + 1 /* SYNC 2 */ + 1 /* SYNC 3 */; } else { ntd = xfer->nframes + 1 /* SYNC */ ; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) return; /* * allocate transfer descriptors */ last_obj = NULL; ep_no = xfer->endpointno & UE_ADDR; /* * Check for a valid endpoint profile in USB device mode: */ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { const struct usb_hw_ep_profile *pf; dwc_otg_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct dwc_otg_td *td; if (parm->buf) { td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* compute shared bandwidth resource index for TT */ if (dwc_otg_uses_split(parm->udev)) { if (parm->udev->parent_hs_hub->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) td->tt_index = parm->udev->device_index; else td->tt_index = parm->udev->parent_hs_hub->device_index; } else { td->tt_index = parm->udev->device_index; } /* init TD */ td->max_packet_size = xfer->max_packet_size; td->max_packet_count = xfer->max_packet_count; /* range check */ if (td->max_packet_count == 0 || td->max_packet_count > 3) td->max_packet_count = 1; td->ep_no = ep_no; td->ep_type = ep_type; td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void dwc_otg_xfer_unsetup(struct usb_xfer *xfer) { return; } static void dwc_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_rt_addr, udev->device_index); if (udev->device_index != sc->sc_rt_addr) { if (udev->flags.usb_mode == USB_MODE_DEVICE) { if (udev->speed != USB_SPEED_FULL && udev->speed != USB_SPEED_HIGH) { /* not supported */ return; } } else { if (udev->speed == USB_SPEED_HIGH && (edesc->wMaxPacketSize[1] & 0x18) != 0 && (edesc->bmAttributes & UE_XFERTYPE) != UE_ISOCHRONOUS) { /* not supported */ DPRINTFN(-1, "Non-isochronous high bandwidth " "endpoint not supported\n"); return; } } if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) ep->methods = &dwc_otg_device_isoc_methods; else ep->methods = &dwc_otg_device_non_isoc_methods; } } static void dwc_otg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: dwc_otg_suspend(sc); break; case USB_HW_POWER_SHUTDOWN: dwc_otg_uninit(sc); break; case USB_HW_POWER_RESUME: dwc_otg_resume(sc); break; default: break; } } static void dwc_otg_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* DMA delay - wait until any use of memory is finished */ *pus = (2125); /* microseconds */ } static void dwc_otg_device_resume(struct usb_device *udev) { DPRINTF("\n"); /* poll all transfers again to restart resumed ones */ dwc_otg_do_poll(udev->bus); } static void dwc_otg_device_suspend(struct usb_device *udev) { DPRINTF("\n"); } static const struct usb_bus_methods dwc_otg_bus_methods = { .endpoint_init = &dwc_otg_ep_init, .xfer_setup = &dwc_otg_xfer_setup, .xfer_unsetup = &dwc_otg_xfer_unsetup, .get_hw_ep_profile = &dwc_otg_get_hw_ep_profile, .xfer_stall = &dwc_otg_xfer_stall, .set_stall = &dwc_otg_set_stall, .clear_stall = &dwc_otg_clear_stall, .roothub_exec = &dwc_otg_roothub_exec, .xfer_poll = &dwc_otg_do_poll, .device_state_change = &dwc_otg_device_state_change, .set_hw_power_sleep = &dwc_otg_set_hw_power_sleep, .get_dma_delay = &dwc_otg_get_dma_delay, .device_resume = &dwc_otg_device_resume, .device_suspend = &dwc_otg_device_suspend, }; Index: head/sys/dev/usb/controller/ehci.c =================================================================== --- head/sys/dev/usb/controller/ehci.c (revision 357971) +++ head/sys/dev/usb/controller/ehci.c (revision 357972) @@ -1,3971 +1,3972 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 2004 Lennart Augustsson. All rights reserved. * Copyright (c) 2004 Charles M. Hannum. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. * * The EHCI 0.96 spec can be found at * http://developer.intel.com/technology/usb/download/ehci-r096.pdf * The EHCI 1.0 spec can be found at * http://developer.intel.com/technology/usb/download/ehci-r10.pdf * and the USB 2.0 spec at * http://www.usb.org/developers/docs/usb_20.zip * */ /* * TODO: * 1) command failures are not recovered correctly */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ehcidebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define EHCI_BUS2SC(bus) \ ((ehci_softc_t *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((ehci_softc_t *)0)->sc_bus)))) #ifdef USB_DEBUG static int ehcidebug = 0; static int ehcinohighspeed = 0; static int ehciiaadbug = 0; static int ehcilostintrbug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ehci"); SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RWTUN, &ehcidebug, 0, "Debug level"); SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RWTUN, &ehcinohighspeed, 0, "Disable High Speed USB"); SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RWTUN, &ehciiaadbug, 0, "Enable doorbell bug workaround"); SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RWTUN, &ehcilostintrbug, 0, "Enable lost interrupt bug workaround"); static void ehci_dump_regs(ehci_softc_t *sc); static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh); #endif #define EHCI_INTR_ENDPT 1 static const struct usb_bus_methods ehci_bus_methods; static const struct usb_pipe_methods ehci_device_bulk_methods; static const struct usb_pipe_methods ehci_device_ctrl_methods; static const struct usb_pipe_methods ehci_device_intr_methods; static const struct usb_pipe_methods ehci_device_isoc_fs_methods; static const struct usb_pipe_methods ehci_device_isoc_hs_methods; static void ehci_do_poll(struct usb_bus *); static void ehci_device_done(struct usb_xfer *, usb_error_t); static uint8_t ehci_check_transfer(struct usb_xfer *); static void ehci_timeout(void *); static void ehci_poll_timeout(void *); static void ehci_root_intr(ehci_softc_t *sc); struct ehci_std_temp { ehci_softc_t *sc; struct usb_page_cache *pc; ehci_qtd_t *td; ehci_qtd_t *td_next; uint32_t average; uint32_t qtd_status; uint32_t len; uint16_t max_frame_size; uint8_t shortpkt; uint8_t auto_data_toggle; uint8_t setup_alt_next; uint8_t last_frame; }; void ehci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) { ehci_softc_t *sc = EHCI_BUS2SC(bus); uint32_t i; cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN); cb(bus, &sc->sc_hw.terminate_pc, &sc->sc_hw.terminate_pg, sizeof(struct ehci_qh_sub), EHCI_QH_ALIGN); cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg, sizeof(ehci_qh_t), EHCI_QH_ALIGN); for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i, sizeof(ehci_qh_t), EHCI_QH_ALIGN); } for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { cb(bus, sc->sc_hw.isoc_hs_start_pc + i, sc->sc_hw.isoc_hs_start_pg + i, sizeof(ehci_itd_t), EHCI_ITD_ALIGN); } for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { cb(bus, sc->sc_hw.isoc_fs_start_pc + i, sc->sc_hw.isoc_fs_start_pg + i, sizeof(ehci_sitd_t), EHCI_SITD_ALIGN); } } usb_error_t ehci_reset(ehci_softc_t *sc) { uint32_t hcr; int i; EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); for (i = 0; i < 100; i++) { usb_pause_mtx(NULL, hz / 128); hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET; if (!hcr) { if (sc->sc_vendor_post_reset != NULL) sc->sc_vendor_post_reset(sc); return (0); } } device_printf(sc->sc_bus.bdev, "reset timeout\n"); return (USB_ERR_IOERROR); } static usb_error_t ehci_hcreset(ehci_softc_t *sc) { uint32_t hcr; int i; EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ for (i = 0; i < 100; i++) { usb_pause_mtx(NULL, hz / 128); hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; if (hcr) break; } if (!hcr) /* * Fall through and try reset anyway even though * Table 2-9 in the EHCI spec says this will result * in undefined behavior. */ device_printf(sc->sc_bus.bdev, "stop timeout\n"); return (ehci_reset(sc)); } static int ehci_init_sub(struct ehci_softc *sc) { struct usb_page_search buf_res; uint32_t cparams; uint32_t hcr; uint8_t i; cparams = EREAD4(sc, EHCI_HCCPARAMS); DPRINTF("cparams=0x%x\n", cparams); if (EHCI_HCC_64BIT(cparams)) { DPRINTF("HCC uses 64-bit structures\n"); /* MUST clear segment register if 64 bit capable */ EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); } usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr); usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH); /* enable interrupts */ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); /* turn on controller */ EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */ (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) | EHCI_CMD_ASE | EHCI_CMD_PSE | EHCI_CMD_RS); /* Take over port ownership */ EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF); for (i = 0; i < 100; i++) { usb_pause_mtx(NULL, hz / 128); hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; if (!hcr) { break; } } if (hcr) { device_printf(sc->sc_bus.bdev, "run timeout\n"); return (USB_ERR_IOERROR); } return (USB_ERR_NORMAL_COMPLETION); } usb_error_t ehci_init(ehci_softc_t *sc) { struct usb_page_search buf_res; uint32_t version; uint32_t sparams; uint16_t i; uint16_t x; uint16_t y; uint16_t bit; usb_error_t err = 0; DPRINTF("start\n"); usb_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0); usb_callout_init_mtx(&sc->sc_tmo_poll, &sc->sc_bus.bus_mtx, 0); sc->sc_offs = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); #ifdef USB_DEBUG if (ehciiaadbug) sc->sc_flags |= EHCI_SCFLG_IAADBUG; if (ehcilostintrbug) sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; if (ehcidebug > 2) { ehci_dump_regs(sc); } #endif version = EHCI_HCIVERSION(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n", version >> 8, version & 0xff); sparams = EREAD4(sc, EHCI_HCSPARAMS); DPRINTF("sparams=0x%x\n", sparams); sc->sc_noport = EHCI_HCS_N_PORTS(sparams); sc->sc_bus.usbrev = USB_REV_2_0; if (!(sc->sc_flags & EHCI_SCFLG_DONTRESET)) { /* Reset the controller */ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); err = ehci_hcreset(sc); if (err) { device_printf(sc->sc_bus.bdev, "reset timeout\n"); return (err); } } /* * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4 * bytes 2: 256*4 bytes 3: unknown */ if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) { device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n"); return (USB_ERR_IOERROR); } /* set up the bus struct */ sc->sc_bus.methods = &ehci_bus_methods; sc->sc_eintrs = EHCI_NORMAL_INTRS; if (1) { struct ehci_qh_sub *qh; usbd_get_page(&sc->sc_hw.terminate_pc, 0, &buf_res); qh = buf_res.buffer; sc->sc_terminate_self = htohc32(sc, buf_res.physaddr); /* init terminate TD */ qh->qtd_next = htohc32(sc, EHCI_LINK_TERMINATE); qh->qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE); qh->qtd_status = htohc32(sc, EHCI_QTD_HALTED); } for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { ehci_qh_t *qh; usbd_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res); qh = buf_res.buffer; /* initialize page cache pointer */ qh->page_cache = sc->sc_hw.intr_start_pc + i; /* store a pointer to queue head */ sc->sc_intr_p_last[i] = qh; qh->qh_self = htohc32(sc, buf_res.physaddr) | htohc32(sc, EHCI_LINK_QH); qh->qh_endp = htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH)); qh->qh_endphub = htohc32(sc, EHCI_QH_SET_MULT(1)); qh->qh_curqtd = 0; qh->qh_qtd.qtd_next = htohc32(sc, EHCI_LINK_TERMINATE); qh->qh_qtd.qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE); qh->qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED); } /* * the QHs are arranged to give poll intervals that are * powers of 2 times 1ms */ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; while (bit) { x = bit; while (x & bit) { ehci_qh_t *qh_x; ehci_qh_t *qh_y; y = (x ^ bit) | (bit / 2); qh_x = sc->sc_intr_p_last[x]; qh_y = sc->sc_intr_p_last[y]; /* * the next QH has half the poll interval */ qh_x->qh_link = qh_y->qh_self; x++; } bit >>= 1; } if (1) { ehci_qh_t *qh; qh = sc->sc_intr_p_last[0]; /* the last (1ms) QH terminates */ qh->qh_link = htohc32(sc, EHCI_LINK_TERMINATE); } for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { ehci_sitd_t *sitd; ehci_itd_t *itd; usbd_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res); sitd = buf_res.buffer; /* initialize page cache pointer */ sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i; /* store a pointer to the transfer descriptor */ sc->sc_isoc_fs_p_last[i] = sitd; /* initialize full speed isochronous */ sitd->sitd_self = htohc32(sc, buf_res.physaddr) | htohc32(sc, EHCI_LINK_SITD); sitd->sitd_back = htohc32(sc, EHCI_LINK_TERMINATE); sitd->sitd_next = sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self; usbd_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res); itd = buf_res.buffer; /* initialize page cache pointer */ itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i; /* store a pointer to the transfer descriptor */ sc->sc_isoc_hs_p_last[i] = itd; /* initialize high speed isochronous */ itd->itd_self = htohc32(sc, buf_res.physaddr) | htohc32(sc, EHCI_LINK_ITD); itd->itd_next = sitd->sitd_self; } usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); if (1) { uint32_t *pframes; pframes = buf_res.buffer; /* * execution order: * pframes -> high speed isochronous -> * full speed isochronous -> interrupt QH's */ for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) { pframes[i] = sc->sc_isoc_hs_p_last [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self; } } usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); if (1) { ehci_qh_t *qh; qh = buf_res.buffer; /* initialize page cache pointer */ qh->page_cache = &sc->sc_hw.async_start_pc; /* store a pointer to the queue head */ sc->sc_async_p_last = qh; /* init dummy QH that starts the async list */ qh->qh_self = htohc32(sc, buf_res.physaddr) | htohc32(sc, EHCI_LINK_QH); /* fill the QH */ qh->qh_endp = htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); qh->qh_endphub = htohc32(sc, EHCI_QH_SET_MULT(1)); qh->qh_link = qh->qh_self; qh->qh_curqtd = 0; /* fill the overlay qTD */ qh->qh_qtd.qtd_next = htohc32(sc, EHCI_LINK_TERMINATE); qh->qh_qtd.qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE); qh->qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED); } /* flush all cache into memory */ usb_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc); #ifdef USB_DEBUG if (ehcidebug) { ehci_dump_sqh(sc, sc->sc_async_p_last); } #endif /* finial setup */ err = ehci_init_sub(sc); if (!err) { /* catch any lost interrupts */ ehci_do_poll(&sc->sc_bus); } return (err); } /* * shut down the controller when the system is going down */ void ehci_detach(ehci_softc_t *sc) { USB_BUS_LOCK(&sc->sc_bus); usb_callout_stop(&sc->sc_tmo_pcd); usb_callout_stop(&sc->sc_tmo_poll); EOWRITE4(sc, EHCI_USBINTR, 0); USB_BUS_UNLOCK(&sc->sc_bus); if (ehci_hcreset(sc)) { DPRINTF("reset failed!\n"); } /* XXX let stray task complete */ usb_pause_mtx(NULL, hz / 20); usb_callout_drain(&sc->sc_tmo_pcd); usb_callout_drain(&sc->sc_tmo_poll); } static void ehci_suspend(ehci_softc_t *sc) { DPRINTF("stopping the HC\n"); /* reset HC */ ehci_hcreset(sc); } static void ehci_resume(ehci_softc_t *sc) { /* reset HC */ ehci_hcreset(sc); /* setup HC */ ehci_init_sub(sc); /* catch any lost interrupts */ ehci_do_poll(&sc->sc_bus); } #ifdef USB_DEBUG static void ehci_dump_regs(ehci_softc_t *sc) { uint32_t i; i = EOREAD4(sc, EHCI_USBCMD); printf("cmd=0x%08x\n", i); if (i & EHCI_CMD_ITC_1) printf(" EHCI_CMD_ITC_1\n"); if (i & EHCI_CMD_ITC_2) printf(" EHCI_CMD_ITC_2\n"); if (i & EHCI_CMD_ITC_4) printf(" EHCI_CMD_ITC_4\n"); if (i & EHCI_CMD_ITC_8) printf(" EHCI_CMD_ITC_8\n"); if (i & EHCI_CMD_ITC_16) printf(" EHCI_CMD_ITC_16\n"); if (i & EHCI_CMD_ITC_32) printf(" EHCI_CMD_ITC_32\n"); if (i & EHCI_CMD_ITC_64) printf(" EHCI_CMD_ITC_64\n"); if (i & EHCI_CMD_ASPME) printf(" EHCI_CMD_ASPME\n"); if (i & EHCI_CMD_ASPMC) printf(" EHCI_CMD_ASPMC\n"); if (i & EHCI_CMD_LHCR) printf(" EHCI_CMD_LHCR\n"); if (i & EHCI_CMD_IAAD) printf(" EHCI_CMD_IAAD\n"); if (i & EHCI_CMD_ASE) printf(" EHCI_CMD_ASE\n"); if (i & EHCI_CMD_PSE) printf(" EHCI_CMD_PSE\n"); if (i & EHCI_CMD_FLS_M) printf(" EHCI_CMD_FLS_M\n"); if (i & EHCI_CMD_HCRESET) printf(" EHCI_CMD_HCRESET\n"); if (i & EHCI_CMD_RS) printf(" EHCI_CMD_RS\n"); i = EOREAD4(sc, EHCI_USBSTS); printf("sts=0x%08x\n", i); if (i & EHCI_STS_ASS) printf(" EHCI_STS_ASS\n"); if (i & EHCI_STS_PSS) printf(" EHCI_STS_PSS\n"); if (i & EHCI_STS_REC) printf(" EHCI_STS_REC\n"); if (i & EHCI_STS_HCH) printf(" EHCI_STS_HCH\n"); if (i & EHCI_STS_IAA) printf(" EHCI_STS_IAA\n"); if (i & EHCI_STS_HSE) printf(" EHCI_STS_HSE\n"); if (i & EHCI_STS_FLR) printf(" EHCI_STS_FLR\n"); if (i & EHCI_STS_PCD) printf(" EHCI_STS_PCD\n"); if (i & EHCI_STS_ERRINT) printf(" EHCI_STS_ERRINT\n"); if (i & EHCI_STS_INT) printf(" EHCI_STS_INT\n"); printf("ien=0x%08x\n", EOREAD4(sc, EHCI_USBINTR)); printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n", EOREAD4(sc, EHCI_FRINDEX), EOREAD4(sc, EHCI_CTRLDSSEGMENT), EOREAD4(sc, EHCI_PERIODICLISTBASE), EOREAD4(sc, EHCI_ASYNCLISTADDR)); for (i = 1; i <= sc->sc_noport; i++) { printf("port %d status=0x%08x\n", i, EOREAD4(sc, EHCI_PORTSC(i))); } } static void ehci_dump_link(ehci_softc_t *sc, uint32_t link, int type) { link = hc32toh(sc, link); printf("0x%08x", link); if (link & EHCI_LINK_TERMINATE) printf(""); else { printf("<"); if (type) { switch (EHCI_LINK_TYPE(link)) { case EHCI_LINK_ITD: printf("ITD"); break; case EHCI_LINK_QH: printf("QH"); break; case EHCI_LINK_SITD: printf("SITD"); break; case EHCI_LINK_FSTN: printf("FSTN"); break; } } printf(">"); } } static void ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd) { uint32_t s; printf(" next="); ehci_dump_link(sc, qtd->qtd_next, 0); printf(" altnext="); ehci_dump_link(sc, qtd->qtd_altnext, 0); printf("\n"); s = hc32toh(sc, qtd->qtd_status); printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n", s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s), EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s)); printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n", EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s), (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE", (s & EHCI_QTD_HALTED) ? "-HALTED" : "", (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "", (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "", (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "", (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "", (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "", (s & EHCI_QTD_PINGSTATE) ? "-PING" : ""); for (s = 0; s < 5; s++) { printf(" buffer[%d]=0x%08x\n", s, hc32toh(sc, qtd->qtd_buffer[s])); } for (s = 0; s < 5; s++) { printf(" buffer_hi[%d]=0x%08x\n", s, hc32toh(sc, qtd->qtd_buffer_hi[s])); } } static uint8_t ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd) { uint8_t temp; usb_pc_cpu_invalidate(sqtd->page_cache); printf("QTD(%p) at 0x%08x:\n", sqtd, hc32toh(sc, sqtd->qtd_self)); ehci_dump_qtd(sc, sqtd); temp = (sqtd->qtd_next & htohc32(sc, EHCI_LINK_TERMINATE)) ? 1 : 0; return (temp); } static void ehci_dump_sqtds(ehci_softc_t *sc, ehci_qtd_t *sqtd) { uint16_t i; uint8_t stop; stop = 0; for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) { stop = ehci_dump_sqtd(sc, sqtd); } if (sqtd) { printf("dump aborted, too many TDs\n"); } } static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *qh) { uint32_t endp; uint32_t endphub; usb_pc_cpu_invalidate(qh->page_cache); printf("QH(%p) at 0x%08x:\n", qh, hc32toh(sc, qh->qh_self) & ~0x1F); printf(" link="); ehci_dump_link(sc, qh->qh_link, 1); printf("\n"); endp = hc32toh(sc, qh->qh_endp); printf(" endp=0x%08x\n", endp); printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n", EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp), EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp), EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp)); printf(" mpl=0x%x ctl=%d nrl=%d\n", EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp), EHCI_QH_GET_NRL(endp)); endphub = hc32toh(sc, qh->qh_endphub); printf(" endphub=0x%08x\n", endphub); printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n", EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub), EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub), EHCI_QH_GET_MULT(endphub)); printf(" curqtd="); ehci_dump_link(sc, qh->qh_curqtd, 0); printf("\n"); printf("Overlay qTD:\n"); ehci_dump_qtd(sc, (void *)&qh->qh_qtd); } static void ehci_dump_sitd(ehci_softc_t *sc, ehci_sitd_t *sitd) { usb_pc_cpu_invalidate(sitd->page_cache); printf("SITD(%p) at 0x%08x\n", sitd, hc32toh(sc, sitd->sitd_self) & ~0x1F); printf(" next=0x%08x\n", hc32toh(sc, sitd->sitd_next)); printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n", hc32toh(sc, sitd->sitd_portaddr), (sitd->sitd_portaddr & htohc32(sc, EHCI_SITD_SET_DIR_IN)) ? "in" : "out", EHCI_SITD_GET_ADDR(hc32toh(sc, sitd->sitd_portaddr)), EHCI_SITD_GET_ENDPT(hc32toh(sc, sitd->sitd_portaddr)), EHCI_SITD_GET_PORT(hc32toh(sc, sitd->sitd_portaddr)), EHCI_SITD_GET_HUBA(hc32toh(sc, sitd->sitd_portaddr))); printf(" mask=0x%08x\n", hc32toh(sc, sitd->sitd_mask)); printf(" status=0x%08x <%s> len=0x%x\n", hc32toh(sc, sitd->sitd_status), (sitd->sitd_status & htohc32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "", EHCI_SITD_GET_LEN(hc32toh(sc, sitd->sitd_status))); printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n", hc32toh(sc, sitd->sitd_back), hc32toh(sc, sitd->sitd_bp[0]), hc32toh(sc, sitd->sitd_bp[1]), hc32toh(sc, sitd->sitd_bp_hi[0]), hc32toh(sc, sitd->sitd_bp_hi[1])); } static void ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd) { usb_pc_cpu_invalidate(itd->page_cache); printf("ITD(%p) at 0x%08x\n", itd, hc32toh(sc, itd->itd_self) & ~0x1F); printf(" next=0x%08x\n", hc32toh(sc, itd->itd_next)); printf(" status[0]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[0]), (itd->itd_status[0] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[1]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[1]), (itd->itd_status[1] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[2]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[2]), (itd->itd_status[2] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[3]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[3]), (itd->itd_status[3] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[4]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[4]), (itd->itd_status[4] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[5]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[5]), (itd->itd_status[5] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[6]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[6]), (itd->itd_status[6] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" status[7]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[7]), (itd->itd_status[7] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); printf(" bp[0]=0x%08x\n", hc32toh(sc, itd->itd_bp[0])); printf(" addr=0x%02x; endpt=0x%01x\n", EHCI_ITD_GET_ADDR(hc32toh(sc, itd->itd_bp[0])), EHCI_ITD_GET_ENDPT(hc32toh(sc, itd->itd_bp[0]))); printf(" bp[1]=0x%08x\n", hc32toh(sc, itd->itd_bp[1])); printf(" dir=%s; mpl=0x%02x\n", (hc32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out", EHCI_ITD_GET_MPL(hc32toh(sc, itd->itd_bp[1]))); printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n", hc32toh(sc, itd->itd_bp[2]), hc32toh(sc, itd->itd_bp[3]), hc32toh(sc, itd->itd_bp[4]), hc32toh(sc, itd->itd_bp[5]), hc32toh(sc, itd->itd_bp[6])); printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n" " 0x%08x,0x%08x,0x%08x\n", hc32toh(sc, itd->itd_bp_hi[0]), hc32toh(sc, itd->itd_bp_hi[1]), hc32toh(sc, itd->itd_bp_hi[2]), hc32toh(sc, itd->itd_bp_hi[3]), hc32toh(sc, itd->itd_bp_hi[4]), hc32toh(sc, itd->itd_bp_hi[5]), hc32toh(sc, itd->itd_bp_hi[6])); } static void ehci_dump_isoc(ehci_softc_t *sc) { ehci_itd_t *itd; ehci_sitd_t *sitd; uint16_t max = 1000; uint16_t pos; pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); printf("%s: isochronous dump from frame 0x%03x:\n", __FUNCTION__, pos); itd = sc->sc_isoc_hs_p_last[pos]; sitd = sc->sc_isoc_fs_p_last[pos]; while (itd && max && max--) { ehci_dump_itd(sc, itd); itd = itd->prev; } while (sitd && max && max--) { ehci_dump_sitd(sc, sitd); sitd = sitd->prev; } } #endif static void ehci_transfer_intr_enqueue(struct usb_xfer *xfer) { /* check for early completion */ if (ehci_check_transfer(xfer)) { return; } /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout); } } #define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last) static ehci_sitd_t * _ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) { DPRINTFN(11, "%p to %p\n", std, last); /* (sc->sc_bus.mtx) must be locked */ std->next = last->next; std->sitd_next = last->sitd_next; std->prev = last; usb_pc_cpu_flush(std->page_cache); /* * the last->next->prev is never followed: std->next->prev = std; */ last->next = std; last->sitd_next = std->sitd_self; usb_pc_cpu_flush(last->page_cache); return (std); } #define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last) static ehci_itd_t * _ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last) { DPRINTFN(11, "%p to %p\n", std, last); /* (sc->sc_bus.mtx) must be locked */ std->next = last->next; std->itd_next = last->itd_next; std->prev = last; usb_pc_cpu_flush(std->page_cache); /* * the last->next->prev is never followed: std->next->prev = std; */ last->next = std; last->itd_next = std->itd_self; usb_pc_cpu_flush(last->page_cache); return (std); } #define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last) static ehci_qh_t * _ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last) { DPRINTFN(11, "%p to %p\n", sqh, last); if (sqh->prev != NULL) { /* should not happen */ DPRINTFN(0, "QH already linked!\n"); return (last); } /* (sc->sc_bus.mtx) must be locked */ sqh->next = last->next; sqh->qh_link = last->qh_link; sqh->prev = last; usb_pc_cpu_flush(sqh->page_cache); /* * the last->next->prev is never followed: sqh->next->prev = sqh; */ last->next = sqh; last->qh_link = sqh->qh_self; usb_pc_cpu_flush(last->page_cache); return (sqh); } #define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last) static ehci_sitd_t * _ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) { DPRINTFN(11, "%p from %p\n", std, last); /* (sc->sc_bus.mtx) must be locked */ std->prev->next = std->next; std->prev->sitd_next = std->sitd_next; usb_pc_cpu_flush(std->prev->page_cache); if (std->next) { std->next->prev = std->prev; usb_pc_cpu_flush(std->next->page_cache); } return ((last == std) ? std->prev : last); } #define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last) static ehci_itd_t * _ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last) { DPRINTFN(11, "%p from %p\n", std, last); /* (sc->sc_bus.mtx) must be locked */ std->prev->next = std->next; std->prev->itd_next = std->itd_next; usb_pc_cpu_flush(std->prev->page_cache); if (std->next) { std->next->prev = std->prev; usb_pc_cpu_flush(std->next->page_cache); } return ((last == std) ? std->prev : last); } #define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last) static ehci_qh_t * _ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last) { DPRINTFN(11, "%p from %p\n", sqh, last); /* (sc->sc_bus.mtx) must be locked */ /* only remove if not removed from a queue */ if (sqh->prev) { sqh->prev->next = sqh->next; sqh->prev->qh_link = sqh->qh_link; usb_pc_cpu_flush(sqh->prev->page_cache); if (sqh->next) { sqh->next->prev = sqh->prev; usb_pc_cpu_flush(sqh->next->page_cache); } last = ((last == sqh) ? sqh->prev : last); sqh->prev = 0; usb_pc_cpu_flush(sqh->page_cache); } return (last); } static void ehci_data_toggle_update(struct usb_xfer *xfer, uint16_t actlen, uint16_t xlen) { uint16_t rem; uint8_t dt; /* count number of full packets */ dt = (actlen / xfer->max_packet_size) & 1; /* compute remainder */ rem = actlen % xfer->max_packet_size; if (rem > 0) dt ^= 1; /* short packet at the end */ else if (actlen != xlen) dt ^= 1; /* zero length packet at the end */ else if (xlen == 0) dt ^= 1; /* zero length transfer */ xfer->endpoint->toggle_next ^= dt; } static usb_error_t ehci_non_isoc_done_sub(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); ehci_qtd_t *td; ehci_qtd_t *td_alt_next; uint32_t status; uint16_t len; td = xfer->td_transfer_cache; td_alt_next = td->alt_next; if (xfer->aframes != xfer->nframes) { usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); } while (1) { usb_pc_cpu_invalidate(td->page_cache); status = hc32toh(sc, td->qtd_status); len = EHCI_QTD_GET_BYTES(status); /* * Verify the status length and * add the length to "frlengths[]": */ if (len > td->len) { /* should not happen */ DPRINTF("Invalid status length, " "0x%04x/0x%04x bytes\n", len, td->len); status |= EHCI_QTD_HALTED; } else if (xfer->aframes != xfer->nframes) { xfer->frlengths[xfer->aframes] += td->len - len; /* manually update data toggle */ ehci_data_toggle_update(xfer, td->len - len, td->len); } /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { td = NULL; break; } /* Check for transfer error */ if (status & EHCI_QTD_HALTED) { /* the transfer is finished */ td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok) { /* follow alt next */ td = td->alt_next; } else { /* the transfer is finished */ td = NULL; } break; } td = td->obj_next; if (td->alt_next != td_alt_next) { /* this USB frame is complete */ break; } } /* update transfer cache */ xfer->td_transfer_cache = td; #ifdef USB_DEBUG if (status & EHCI_QTD_STATERRS) { DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x" "status=%s%s%s%s%s%s%s%s\n", xfer->address, xfer->endpointno, xfer->aframes, (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", (status & EHCI_QTD_HALTED) ? "[HALTED]" : "", (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "", (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "", (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "", (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "", (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "", (status & EHCI_QTD_PINGSTATE) ? "[PING]" : ""); } #endif if (status & EHCI_QTD_HALTED) { if ((xfer->xroot->udev->parent_hs_hub != NULL) || (xfer->xroot->udev->address != 0)) { /* try to separate I/O errors from STALL */ if (EHCI_QTD_GET_CERR(status) == 0) return (USB_ERR_IOERROR); } return (USB_ERR_STALLED); } return (USB_ERR_NORMAL_COMPLETION); } static void ehci_non_isoc_done(struct usb_xfer *xfer) { ehci_qh_t *qh; usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); #ifdef USB_DEBUG if (ehcidebug > 10) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); ehci_dump_sqtds(sc, xfer->td_transfer_first); } #endif /* extract data toggle directly from the QH's overlay area */ qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; usb_pc_cpu_invalidate(qh->page_cache); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = ehci_non_isoc_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = ehci_non_isoc_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = ehci_non_isoc_done_sub(xfer); } done: ehci_device_done(xfer, err); } /*------------------------------------------------------------------------* * ehci_check_transfer * * Return values: * 0: USB transfer is not finished * Else: USB transfer is finished *------------------------------------------------------------------------*/ static uint8_t ehci_check_transfer(struct usb_xfer *xfer) { const struct usb_pipe_methods *methods = xfer->endpoint->methods; ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); uint32_t status; DPRINTFN(13, "xfer=%p checking transfer\n", xfer); if (methods == &ehci_device_isoc_fs_methods) { ehci_sitd_t *td; /* isochronous full speed transfer */ td = xfer->td_transfer_last; usb_pc_cpu_invalidate(td->page_cache); status = hc32toh(sc, td->sitd_status); /* also check if first is complete */ td = xfer->td_transfer_first; usb_pc_cpu_invalidate(td->page_cache); status |= hc32toh(sc, td->sitd_status); if (!(status & EHCI_SITD_ACTIVE)) { ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); goto transferred; } } else if (methods == &ehci_device_isoc_hs_methods) { ehci_itd_t *td; /* isochronous high speed transfer */ /* check last transfer */ td = xfer->td_transfer_last; usb_pc_cpu_invalidate(td->page_cache); status = td->itd_status[0]; status |= td->itd_status[1]; status |= td->itd_status[2]; status |= td->itd_status[3]; status |= td->itd_status[4]; status |= td->itd_status[5]; status |= td->itd_status[6]; status |= td->itd_status[7]; /* also check first transfer */ td = xfer->td_transfer_first; usb_pc_cpu_invalidate(td->page_cache); status |= td->itd_status[0]; status |= td->itd_status[1]; status |= td->itd_status[2]; status |= td->itd_status[3]; status |= td->itd_status[4]; status |= td->itd_status[5]; status |= td->itd_status[6]; status |= td->itd_status[7]; /* if no transactions are active we continue */ if (!(status & htohc32(sc, EHCI_ITD_ACTIVE))) { ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); goto transferred; } } else { ehci_qtd_t *td; ehci_qh_t *qh; /* non-isochronous transfer */ /* * check whether there is an error somewhere in the middle, * or whether there was a short packet (SPD and not ACTIVE) */ td = xfer->td_transfer_cache; qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; usb_pc_cpu_invalidate(qh->page_cache); status = hc32toh(sc, qh->qh_qtd.qtd_status); if (status & EHCI_QTD_ACTIVE) { /* transfer is pending */ goto done; } while (1) { usb_pc_cpu_invalidate(td->page_cache); status = hc32toh(sc, td->qtd_status); /* * Check if there is an active TD which * indicates that the transfer isn't done. */ if (status & EHCI_QTD_ACTIVE) { /* update cache */ xfer->td_transfer_cache = td; goto done; } /* * last transfer descriptor makes the transfer done */ if (((void *)td) == xfer->td_transfer_last) { break; } /* * any kind of error makes the transfer done */ if (status & EHCI_QTD_HALTED) { break; } /* * if there is no alternate next transfer, a short * packet also makes the transfer done */ if (EHCI_QTD_GET_BYTES(status)) { if (xfer->flags_int.short_frames_ok) { /* follow alt next */ if (td->alt_next) { td = td->alt_next; continue; } } /* transfer is done */ break; } td = td->obj_next; } ehci_non_isoc_done(xfer); goto transferred; } done: DPRINTFN(13, "xfer=%p is still active\n", xfer); return (0); transferred: return (1); } static void ehci_pcd_enable(ehci_softc_t *sc) { USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); sc->sc_eintrs |= EHCI_STS_PCD; EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); /* acknowledge any PCD interrupt */ EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD); ehci_root_intr(sc); } static void ehci_interrupt_poll(ehci_softc_t *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { /* * check if transfer is transferred */ if (ehci_check_transfer(xfer)) { /* queue has been modified */ goto repeat; } } } /* * Some EHCI chips from VIA / ATI seem to trigger interrupts before * writing back the qTD status, or miss signalling occasionally under * heavy load. If the host machine is too fast, we can miss * transaction completion - when we scan the active list the * transaction still seems to be active. This generally exhibits * itself as a umass stall that never recovers. * * We work around this behaviour by setting up this callback after any * softintr that completes with transactions still pending, giving us * another chance to check for completion after the writeback has * taken place. */ static void ehci_poll_timeout(void *arg) { ehci_softc_t *sc = arg; DPRINTFN(3, "\n"); ehci_interrupt_poll(sc); } /*------------------------------------------------------------------------* * ehci_interrupt - EHCI interrupt handler * * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, * hence the interrupt handler will be setup before "sc->sc_bus.bdev" * is present ! *------------------------------------------------------------------------*/ void ehci_interrupt(ehci_softc_t *sc) { uint32_t status; USB_BUS_LOCK(&sc->sc_bus); DPRINTFN(16, "real interrupt\n"); #ifdef USB_DEBUG if (ehcidebug > 15) { ehci_dump_regs(sc); } #endif status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); if (status == 0) { /* the interrupt was not for us */ goto done; } if (!(status & sc->sc_eintrs)) { goto done; } EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */ status &= sc->sc_eintrs; if (status & EHCI_STS_HSE) { printf("%s: unrecoverable error, " "controller halted\n", __FUNCTION__); #ifdef USB_DEBUG ehci_dump_regs(sc); ehci_dump_isoc(sc); #endif } if (status & EHCI_STS_PCD) { /* * Disable PCD interrupt for now, because it will be * on until the port has been reset. */ sc->sc_eintrs &= ~EHCI_STS_PCD; EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); ehci_root_intr(sc); /* do not allow RHSC interrupts > 1 per second */ usb_callout_reset(&sc->sc_tmo_pcd, hz, (void *)&ehci_pcd_enable, sc); } status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA); if (status != 0) { /* block unprocessed interrupts */ sc->sc_eintrs &= ~status; EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status); } /* poll all the USB transfers */ ehci_interrupt_poll(sc); if (sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) { usb_callout_reset(&sc->sc_tmo_poll, hz / 128, (void *)&ehci_poll_timeout, sc); } done: USB_BUS_UNLOCK(&sc->sc_bus); } /* * called when a request does not complete */ static void ehci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ ehci_device_done(xfer, USB_ERR_TIMEOUT); } static void ehci_do_poll(struct usb_bus *bus) { ehci_softc_t *sc = EHCI_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); ehci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void ehci_setup_standard_chain_sub(struct ehci_std_temp *temp) { struct usb_page_search buf_res; ehci_qtd_t *td; ehci_qtd_t *td_next; ehci_qtd_t *td_alt_next; uint32_t buf_offset; uint32_t average; uint32_t len_old; uint32_t terminate; uint32_t qtd_altnext; uint8_t shortpkt_old; uint8_t precompute; terminate = temp->sc->sc_terminate_self; qtd_altnext = temp->sc->sc_terminate_self; td_alt_next = NULL; buf_offset = 0; shortpkt_old = temp->shortpkt; len_old = temp->len; precompute = 1; restart: td = temp->td; td_next = temp->td_next; while (1) { if (temp->len == 0) { if (temp->shortpkt) { break; } /* send a Zero Length Packet, ZLP, last */ temp->shortpkt = 1; average = 0; } else { average = temp->average; if (temp->len < average) { if (temp->len % temp->max_frame_size) { temp->shortpkt = 1; } average = temp->len; } } if (td_next == NULL) { panic("%s: out of EHCI transfer descriptors!", __FUNCTION__); } /* get next TD */ td = td_next; td_next = td->obj_next; /* check if we are pre-computing */ if (precompute) { /* update remaining length */ temp->len -= average; continue; } /* fill out current TD */ td->qtd_status = temp->qtd_status | htohc32(temp->sc, EHCI_QTD_IOC | EHCI_QTD_SET_BYTES(average)); if (average == 0) { if (temp->auto_data_toggle == 0) { /* update data toggle, ZLP case */ temp->qtd_status ^= htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK); } td->len = 0; /* properly reset reserved fields */ td->qtd_buffer[0] = 0; td->qtd_buffer[1] = 0; td->qtd_buffer[2] = 0; td->qtd_buffer[3] = 0; td->qtd_buffer[4] = 0; td->qtd_buffer_hi[0] = 0; td->qtd_buffer_hi[1] = 0; td->qtd_buffer_hi[2] = 0; td->qtd_buffer_hi[3] = 0; td->qtd_buffer_hi[4] = 0; } else { uint8_t x; if (temp->auto_data_toggle == 0) { /* update data toggle */ if (howmany(average, temp->max_frame_size) & 1) { temp->qtd_status ^= htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK); } } td->len = average; /* update remaining length */ temp->len -= average; /* fill out buffer pointers */ usbd_get_page(temp->pc, buf_offset, &buf_res); td->qtd_buffer[0] = htohc32(temp->sc, buf_res.physaddr); td->qtd_buffer_hi[0] = 0; x = 1; while (average > EHCI_PAGE_SIZE) { average -= EHCI_PAGE_SIZE; buf_offset += EHCI_PAGE_SIZE; usbd_get_page(temp->pc, buf_offset, &buf_res); td->qtd_buffer[x] = htohc32(temp->sc, buf_res.physaddr & (~0xFFF)); td->qtd_buffer_hi[x] = 0; x++; } /* * NOTE: The "average" variable is never zero after * exiting the loop above ! * * NOTE: We have to subtract one from the offset to * ensure that we are computing the physical address * of a valid page ! */ buf_offset += average; usbd_get_page(temp->pc, buf_offset - 1, &buf_res); td->qtd_buffer[x] = htohc32(temp->sc, buf_res.physaddr & (~0xFFF)); td->qtd_buffer_hi[x] = 0; /* properly reset reserved fields */ while (++x < EHCI_QTD_NBUFFERS) { td->qtd_buffer[x] = 0; td->qtd_buffer_hi[x] = 0; } } if (td_next) { /* link the current TD with the next one */ td->qtd_next = td_next->qtd_self; } td->qtd_altnext = qtd_altnext; td->alt_next = td_alt_next; usb_pc_cpu_flush(td->page_cache); } if (precompute) { precompute = 0; /* setup alt next pointer, if any */ if (temp->last_frame) { td_alt_next = NULL; qtd_altnext = terminate; } else { /* we use this field internally */ td_alt_next = td_next; if (temp->setup_alt_next) { qtd_altnext = td_next->qtd_self; } else { qtd_altnext = terminate; } } /* restore */ temp->shortpkt = shortpkt_old; temp->len = len_old; goto restart; } temp->td = td; temp->td_next = td_next; } static void ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last) { struct ehci_std_temp temp; const struct usb_pipe_methods *methods; ehci_qh_t *qh; ehci_qtd_t *td; uint32_t qh_endp; uint32_t qh_endphub; uint32_t x; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.average = xfer->max_hc_frame_size; temp.max_frame_size = xfer->max_frame_size; temp.sc = EHCI_BUS2SC(xfer->xroot->bus); /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; temp.td = NULL; temp.td_next = td; temp.qtd_status = 0; temp.last_frame = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok; if (xfer->flags_int.control_xfr) { if (xfer->endpoint->toggle_next) { /* DATA1 is next */ temp.qtd_status |= htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1)); } temp.auto_data_toggle = 0; } else { temp.auto_data_toggle = 1; } if ((xfer->xroot->udev->parent_hs_hub != NULL) || (xfer->xroot->udev->address != 0)) { /* max 3 retries */ temp.qtd_status |= htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); } /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { xfer->endpoint->toggle_next = 0; temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); temp.qtd_status |= htohc32(temp.sc, EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) | EHCI_QTD_SET_TOGGLE(0)); temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.shortpkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } ehci_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; temp.pc = xfer->frbuffers + x; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { /* no STATUS stage yet, DATA is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } else { temp.last_frame = 1; temp.setup_alt_next = 0; } } /* keep previous data toggle and error count */ temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3) | EHCI_QTD_SET_TOGGLE(1)); if (temp.len == 0) { /* make sure that we send an USB packet */ temp.shortpkt = 0; } else { /* regular data transfer */ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; } /* set endpoint direction */ temp.qtd_status |= (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? htohc32(temp.sc, EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) : htohc32(temp.sc, EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT)); ehci_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current endpoint * direction. */ temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3) | EHCI_QTD_SET_TOGGLE(1)); temp.qtd_status |= (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ? htohc32(temp.sc, EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) | EHCI_QTD_SET_TOGGLE(1)) : htohc32(temp.sc, EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) | EHCI_QTD_SET_TOGGLE(1)); temp.len = 0; temp.pc = NULL; temp.shortpkt = 0; temp.last_frame = 1; temp.setup_alt_next = 0; ehci_setup_standard_chain_sub(&temp); } td = temp.td; /* the last TD terminates the transfer: */ td->qtd_next = htohc32(temp.sc, EHCI_LINK_TERMINATE); td->qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE); usb_pc_cpu_flush(td->page_cache); /* must have at least one frame! */ xfer->td_transfer_last = td; #ifdef USB_DEBUG if (ehcidebug > 8) { DPRINTF("nexttog=%d; data before transfer:\n", xfer->endpoint->toggle_next); ehci_dump_sqtds(temp.sc, xfer->td_transfer_first); } #endif methods = xfer->endpoint->methods; qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; /* the "qh_link" field is filled when the QH is added */ qh_endp = (EHCI_QH_SET_ADDR(xfer->address) | EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) | EHCI_QH_SET_MPL(xfer->max_packet_size)); if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH); if (methods != &ehci_device_intr_methods) qh_endp |= EHCI_QH_SET_NRL(8); } else { if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) { qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL); } else { qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW); } if (methods == &ehci_device_ctrl_methods) { qh_endp |= EHCI_QH_CTL; } if (methods != &ehci_device_intr_methods) { /* Only try one time per microframe! */ qh_endp |= EHCI_QH_SET_NRL(1); } } if (temp.auto_data_toggle == 0) { /* software computes the data toggle */ qh_endp |= EHCI_QH_DTC; } qh->qh_endp = htohc32(temp.sc, qh_endp); qh_endphub = (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) | EHCI_QH_SET_CMASK(xfer->endpoint->usb_cmask) | EHCI_QH_SET_SMASK(xfer->endpoint->usb_smask) | EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) | EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no)); qh->qh_endphub = htohc32(temp.sc, qh_endphub); qh->qh_curqtd = 0; /* fill the overlay qTD */ if (temp.auto_data_toggle && xfer->endpoint->toggle_next) { /* DATA1 is next */ qh->qh_qtd.qtd_status = htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1)); } else { qh->qh_qtd.qtd_status = 0; } td = xfer->td_transfer_first; qh->qh_qtd.qtd_next = td->qtd_self; qh->qh_qtd.qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE); /* properly reset reserved fields */ qh->qh_qtd.qtd_buffer[0] = 0; qh->qh_qtd.qtd_buffer[1] = 0; qh->qh_qtd.qtd_buffer[2] = 0; qh->qh_qtd.qtd_buffer[3] = 0; qh->qh_qtd.qtd_buffer[4] = 0; qh->qh_qtd.qtd_buffer_hi[0] = 0; qh->qh_qtd.qtd_buffer_hi[1] = 0; qh->qh_qtd.qtd_buffer_hi[2] = 0; qh->qh_qtd.qtd_buffer_hi[3] = 0; qh->qh_qtd.qtd_buffer_hi[4] = 0; usb_pc_cpu_flush(qh->page_cache); if (xfer->xroot->udev->flags.self_suspended == 0) { EHCI_APPEND_QH(qh, *qh_last); } } static void ehci_root_intr(ehci_softc_t *sc) { uint16_t i; uint16_t m; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* clear any old interrupt data */ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); /* set bits */ m = (sc->sc_noport + 1); if (m > (8 * sizeof(sc->sc_hub_idata))) { m = (8 * sizeof(sc->sc_hub_idata)); } for (i = 1; i < m; i++) { /* pick out CHANGE bits from the status register */ if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) { sc->sc_hub_idata[i / 8] |= 1 << (i % 8); DPRINTF("port %d changed\n", i); } } uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static void ehci_isoc_fs_done(ehci_softc_t *sc, struct usb_xfer *xfer) { uint32_t nframes = xfer->nframes; uint32_t status; uint32_t *plen = xfer->frlengths; uint16_t len = 0; ehci_sitd_t *td = xfer->td_transfer_first; ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos]; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); while (nframes--) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { pp_last = &sc->sc_isoc_fs_p_last[0]; } #ifdef USB_DEBUG if (ehcidebug > 15) { DPRINTF("isoc FS-TD\n"); ehci_dump_sitd(sc, td); } #endif usb_pc_cpu_invalidate(td->page_cache); status = hc32toh(sc, td->sitd_status); len = EHCI_SITD_GET_LEN(status); DPRINTFN(2, "status=0x%08x, rem=%u\n", status, len); if (*plen >= len) { len = *plen - len; } else { len = 0; } *plen = len; /* remove FS-TD from schedule */ EHCI_REMOVE_FS_TD(td, *pp_last); pp_last++; plen++; td = td->obj_next; } xfer->aframes = xfer->nframes; } static void ehci_isoc_hs_done(ehci_softc_t *sc, struct usb_xfer *xfer) { uint32_t nframes = xfer->nframes; uint32_t status; uint32_t *plen = xfer->frlengths; uint16_t len = 0; uint8_t td_no = 0; ehci_itd_t *td = xfer->td_transfer_first; ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos]; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); while (nframes) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { pp_last = &sc->sc_isoc_hs_p_last[0]; } #ifdef USB_DEBUG if (ehcidebug > 15) { DPRINTF("isoc HS-TD\n"); ehci_dump_itd(sc, td); } #endif usb_pc_cpu_invalidate(td->page_cache); status = hc32toh(sc, td->itd_status[td_no]); len = EHCI_ITD_GET_LEN(status); DPRINTFN(2, "status=0x%08x, len=%u\n", status, len); if (xfer->endpoint->usb_smask & (1 << td_no)) { if (*plen >= len) { /* * The length is valid. NOTE: The * complete length is written back * into the status field, and not the * remainder like with other transfer * descriptor types. */ } else { /* Invalid length - truncate */ len = 0; } *plen = len; plen++; nframes--; } td_no++; if ((td_no == 8) || (nframes == 0)) { /* remove HS-TD from schedule */ EHCI_REMOVE_HS_TD(td, *pp_last); pp_last++; td_no = 0; td = td->obj_next; } } xfer->aframes = xfer->nframes; } /* NOTE: "done" can be run two times in a row, * from close and from interrupt */ static void ehci_device_done(struct usb_xfer *xfer, usb_error_t error) { const struct usb_pipe_methods *methods = xfer->endpoint->methods; ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); if ((methods == &ehci_device_bulk_methods) || (methods == &ehci_device_ctrl_methods)) { #ifdef USB_DEBUG if (ehcidebug > 8) { DPRINTF("nexttog=%d; data after transfer:\n", xfer->endpoint->toggle_next); ehci_dump_sqtds(sc, xfer->td_transfer_first); } #endif EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], sc->sc_async_p_last); } if (methods == &ehci_device_intr_methods) { EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], sc->sc_intr_p_last[xfer->qh_pos]); } /* * Only finish isochronous transfers once which will update * "xfer->frlengths". */ if (xfer->td_transfer_first && xfer->td_transfer_last) { if (methods == &ehci_device_isoc_fs_methods) { ehci_isoc_fs_done(sc, xfer); } if (methods == &ehci_device_isoc_hs_methods) { ehci_isoc_hs_done(sc, xfer); } xfer->td_transfer_first = NULL; xfer->td_transfer_last = NULL; } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } /*------------------------------------------------------------------------* * ehci bulk support *------------------------------------------------------------------------*/ static void ehci_device_bulk_open(struct usb_xfer *xfer) { return; } static void ehci_device_bulk_close(struct usb_xfer *xfer) { ehci_device_done(xfer, USB_ERR_CANCELLED); } static void ehci_device_bulk_enter(struct usb_xfer *xfer) { return; } static void ehci_doorbell_async(struct ehci_softc *sc) { uint32_t temp; /* * XXX Performance quirk: Some Host Controllers have a too low * interrupt rate. Issue an IAAD to stimulate the Host * Controller after queueing the BULK transfer. * * XXX Force the host controller to refresh any QH caches. */ temp = EOREAD4(sc, EHCI_USBCMD); if (!(temp & EHCI_CMD_IAAD)) EOWRITE4(sc, EHCI_USBCMD, temp | EHCI_CMD_IAAD); } static void ehci_device_bulk_start(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); /* put transfer on interrupt queue */ ehci_transfer_intr_enqueue(xfer); /* * XXX Certain nVidia chipsets choke when using the IAAD * feature too frequently. */ if (sc->sc_flags & EHCI_SCFLG_IAADBUG) return; ehci_doorbell_async(sc); } static const struct usb_pipe_methods ehci_device_bulk_methods = { .open = ehci_device_bulk_open, .close = ehci_device_bulk_close, .enter = ehci_device_bulk_enter, .start = ehci_device_bulk_start, }; /*------------------------------------------------------------------------* * ehci control support *------------------------------------------------------------------------*/ static void ehci_device_ctrl_open(struct usb_xfer *xfer) { return; } static void ehci_device_ctrl_close(struct usb_xfer *xfer) { ehci_device_done(xfer, USB_ERR_CANCELLED); } static void ehci_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void ehci_device_ctrl_start(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); /* put transfer on interrupt queue */ ehci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ehci_device_ctrl_methods = { .open = ehci_device_ctrl_open, .close = ehci_device_ctrl_close, .enter = ehci_device_ctrl_enter, .start = ehci_device_ctrl_start, }; /*------------------------------------------------------------------------* * ehci interrupt support *------------------------------------------------------------------------*/ static void ehci_device_intr_open(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); uint16_t best; uint16_t bit; uint16_t x; usb_hs_bandwidth_alloc(xfer); /* * Find the best QH position corresponding to the given interval: */ best = 0; bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; while (bit) { if (xfer->interval >= bit) { x = bit; best = bit; while (x & bit) { if (sc->sc_intr_stat[x] < sc->sc_intr_stat[best]) { best = x; } x++; } break; } bit >>= 1; } sc->sc_intr_stat[best]++; xfer->qh_pos = best; DPRINTFN(3, "best=%d interval=%d\n", best, xfer->interval); } static void ehci_device_intr_close(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); sc->sc_intr_stat[xfer->qh_pos]--; ehci_device_done(xfer, USB_ERR_CANCELLED); /* bandwidth must be freed after device done */ usb_hs_bandwidth_free(xfer); } static void ehci_device_intr_enter(struct usb_xfer *xfer) { return; } static void ehci_device_intr_start(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); /* put transfer on interrupt queue */ ehci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ehci_device_intr_methods = { .open = ehci_device_intr_open, .close = ehci_device_intr_close, .enter = ehci_device_intr_enter, .start = ehci_device_intr_start, }; /*------------------------------------------------------------------------* * ehci full speed isochronous support *------------------------------------------------------------------------*/ static void ehci_device_isoc_fs_open(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); ehci_sitd_t *td; uint32_t sitd_portaddr; uint8_t ds; sitd_portaddr = EHCI_SITD_SET_ADDR(xfer->address) | EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) | EHCI_SITD_SET_HUBA(xfer->xroot->udev->hs_hub_addr) | EHCI_SITD_SET_PORT(xfer->xroot->udev->hs_port_no); if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) sitd_portaddr |= EHCI_SITD_SET_DIR_IN; sitd_portaddr = htohc32(sc, sitd_portaddr); /* initialize all TD's */ for (ds = 0; ds != 2; ds++) { for (td = xfer->td_start[ds]; td; td = td->obj_next) { td->sitd_portaddr = sitd_portaddr; /* * TODO: make some kind of automatic * SMASK/CMASK selection based on micro-frame * usage * * micro-frame usage (8 microframes per 1ms) */ td->sitd_back = htohc32(sc, EHCI_LINK_TERMINATE); usb_pc_cpu_flush(td->page_cache); } } } static void ehci_device_isoc_fs_close(struct usb_xfer *xfer) { ehci_device_done(xfer, USB_ERR_CANCELLED); } static void ehci_device_isoc_fs_enter(struct usb_xfer *xfer) { struct usb_page_search buf_res; ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); ehci_sitd_t *td; ehci_sitd_t *td_last = NULL; ehci_sitd_t **pp_last; uint32_t *plen; uint32_t buf_offset; uint32_t nframes; uint32_t temp; uint32_t sitd_mask; uint16_t tlen; uint8_t sa; uint8_t sb; #ifdef USB_DEBUG uint8_t once = 1; #endif DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index */ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; /* * check if the frame index is within the window where the frames * will be inserted */ buf_offset = (nframes - xfer->endpoint->isoc_next) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); if ((xfer->endpoint->is_synced == 0) || (buf_offset < xfer->nframes)) { /* * If there is data underflow or the pipe queue is empty we * schedule the transfer a few frames ahead of the current * frame position. Else two isochronous transfers might * overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ buf_offset = (xfer->endpoint->isoc_next - nframes) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + xfer->nframes; /* get the real number of frames */ nframes = xfer->nframes; buf_offset = 0; plen = xfer->frlengths; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; pp_last = &sc->sc_isoc_fs_p_last[xfer->endpoint->isoc_next]; /* store starting position */ xfer->qh_pos = xfer->endpoint->isoc_next; while (nframes--) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) pp_last = &sc->sc_isoc_fs_p_last[0]; /* reuse sitd_portaddr and sitd_back from last transfer */ if (*plen > xfer->max_frame_size) { #ifdef USB_DEBUG if (once) { once = 0; printf("%s: frame length(%d) exceeds %d " "bytes (frame truncated)\n", __FUNCTION__, *plen, xfer->max_frame_size); } #endif *plen = xfer->max_frame_size; } /* allocate a slot */ sa = usbd_fs_isoc_schedule_alloc_slot(xfer, xfer->isoc_time_complete - nframes - 1); if (sa == 255) { /* * Schedule is FULL, set length to zero: */ *plen = 0; sa = USB_FS_ISOC_UFRAME_MAX - 1; } if (*plen) { /* * only call "usbd_get_page()" when we have a * non-zero length */ usbd_get_page(xfer->frbuffers, buf_offset, &buf_res); td->sitd_bp[0] = htohc32(sc, buf_res.physaddr); buf_offset += *plen; /* * NOTE: We need to subtract one from the offset so * that we are on a valid page! */ usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); temp = buf_res.physaddr & ~0xFFF; } else { td->sitd_bp[0] = 0; temp = 0; } if (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) { tlen = *plen; if (tlen <= 188) { temp |= 1; /* T-count = 1, TP = ALL */ tlen = 1; } else { tlen += 187; tlen /= 188; temp |= tlen; /* T-count = [1..6] */ temp |= 8; /* TP = Begin */ } tlen += sa; if (tlen >= 8) { sb = 0; } else { sb = (1 << tlen); } sa = (1 << sa); sa = (sb - sa) & 0x3F; sb = 0; } else { sb = (-(4 << sa)) & 0xFE; sa = (1 << sa) & 0x3F; } sitd_mask = (EHCI_SITD_SET_SMASK(sa) | EHCI_SITD_SET_CMASK(sb)); td->sitd_bp[1] = htohc32(sc, temp); td->sitd_mask = htohc32(sc, sitd_mask); if (nframes == 0) { td->sitd_status = htohc32(sc, EHCI_SITD_IOC | EHCI_SITD_ACTIVE | EHCI_SITD_SET_LEN(*plen)); } else { td->sitd_status = htohc32(sc, EHCI_SITD_ACTIVE | EHCI_SITD_SET_LEN(*plen)); } usb_pc_cpu_flush(td->page_cache); #ifdef USB_DEBUG if (ehcidebug > 15) { DPRINTF("FS-TD %d\n", nframes); ehci_dump_sitd(sc, td); } #endif /* insert TD into schedule */ EHCI_APPEND_FS_TD(td, *pp_last); pp_last++; plen++; td_last = td; td = td->obj_next; } xfer->td_transfer_last = td_last; /* update isoc_next */ xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); /* * We don't allow cancelling of the SPLIT transaction USB FULL * speed transfer, because it disturbs the bandwidth * computation algorithm. */ xfer->flags_int.can_cancel_immed = 0; } static void ehci_device_isoc_fs_start(struct usb_xfer *xfer) { /* * We don't allow cancelling of the SPLIT transaction USB FULL * speed transfer, because it disturbs the bandwidth * computation algorithm. */ xfer->flags_int.can_cancel_immed = 0; /* set a default timeout */ if (xfer->timeout == 0) xfer->timeout = 500; /* ms */ /* put transfer on interrupt queue */ ehci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ehci_device_isoc_fs_methods = { .open = ehci_device_isoc_fs_open, .close = ehci_device_isoc_fs_close, .enter = ehci_device_isoc_fs_enter, .start = ehci_device_isoc_fs_start, }; /*------------------------------------------------------------------------* * ehci high speed isochronous support *------------------------------------------------------------------------*/ static void ehci_device_isoc_hs_open(struct usb_xfer *xfer) { ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); ehci_itd_t *td; uint32_t temp; uint8_t ds; usb_hs_bandwidth_alloc(xfer); /* initialize all TD's */ for (ds = 0; ds != 2; ds++) { for (td = xfer->td_start[ds]; td; td = td->obj_next) { /* set TD inactive */ td->itd_status[0] = 0; td->itd_status[1] = 0; td->itd_status[2] = 0; td->itd_status[3] = 0; td->itd_status[4] = 0; td->itd_status[5] = 0; td->itd_status[6] = 0; td->itd_status[7] = 0; /* set endpoint and address */ td->itd_bp[0] = htohc32(sc, EHCI_ITD_SET_ADDR(xfer->address) | EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno))); temp = EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF); /* set direction */ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { temp |= EHCI_ITD_SET_DIR_IN; } /* set maximum packet size */ td->itd_bp[1] = htohc32(sc, temp); /* set transfer multiplier */ td->itd_bp[2] = htohc32(sc, xfer->max_packet_count & 3); usb_pc_cpu_flush(td->page_cache); } } } static void ehci_device_isoc_hs_close(struct usb_xfer *xfer) { ehci_device_done(xfer, USB_ERR_CANCELLED); /* bandwidth must be freed after device done */ usb_hs_bandwidth_free(xfer); } static void ehci_device_isoc_hs_enter(struct usb_xfer *xfer) { struct usb_page_search buf_res; ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); ehci_itd_t *td; ehci_itd_t *td_last = NULL; ehci_itd_t **pp_last; bus_size_t page_addr; uint32_t *plen; uint32_t status; uint32_t buf_offset; uint32_t nframes; uint32_t itd_offset[8 + 1]; uint8_t x; uint8_t td_no; uint8_t page_no; uint8_t shift = usbd_xfer_get_fps_shift(xfer); #ifdef USB_DEBUG uint8_t once = 1; #endif DPRINTFN(6, "xfer=%p next=%d nframes=%d shift=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes, (int)shift); /* get the current frame index */ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; /* * check if the frame index is within the window where the frames * will be inserted */ buf_offset = (nframes - xfer->endpoint->isoc_next) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); if ((xfer->endpoint->is_synced == 0) || (buf_offset < (((xfer->nframes << shift) + 7) / 8))) { /* * If there is data underflow or the pipe queue is empty we * schedule the transfer a few frames ahead of the current * frame position. Else two isochronous transfers might * overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ buf_offset = (xfer->endpoint->isoc_next - nframes) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + (((xfer->nframes << shift) + 7) / 8); /* get the real number of frames */ nframes = xfer->nframes; buf_offset = 0; td_no = 0; plen = xfer->frlengths; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; pp_last = &sc->sc_isoc_hs_p_last[xfer->endpoint->isoc_next]; /* store starting position */ xfer->qh_pos = xfer->endpoint->isoc_next; while (nframes) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { pp_last = &sc->sc_isoc_hs_p_last[0]; } /* range check */ if (*plen > xfer->max_frame_size) { #ifdef USB_DEBUG if (once) { once = 0; printf("%s: frame length(%d) exceeds %d bytes " "(frame truncated)\n", __FUNCTION__, *plen, xfer->max_frame_size); } #endif *plen = xfer->max_frame_size; } if (xfer->endpoint->usb_smask & (1 << td_no)) { status = (EHCI_ITD_SET_LEN(*plen) | EHCI_ITD_ACTIVE | EHCI_ITD_SET_PG(0)); td->itd_status[td_no] = htohc32(sc, status); itd_offset[td_no] = buf_offset; buf_offset += *plen; plen++; nframes --; } else { td->itd_status[td_no] = 0; /* not active */ itd_offset[td_no] = buf_offset; } td_no++; if ((td_no == 8) || (nframes == 0)) { /* the rest of the transfers are not active, if any */ for (x = td_no; x != 8; x++) { td->itd_status[x] = 0; /* not active */ } /* check if there is any data to be transferred */ if (itd_offset[0] != buf_offset) { page_no = 0; itd_offset[td_no] = buf_offset; /* get first page offset */ usbd_get_page(xfer->frbuffers, itd_offset[0], &buf_res); /* get page address */ page_addr = buf_res.physaddr & ~0xFFF; /* update page address */ td->itd_bp[0] &= htohc32(sc, 0xFFF); td->itd_bp[0] |= htohc32(sc, page_addr); for (x = 0; x != td_no; x++) { /* set page number and page offset */ status = (EHCI_ITD_SET_PG(page_no) | (buf_res.physaddr & 0xFFF)); td->itd_status[x] |= htohc32(sc, status); /* get next page offset */ if (itd_offset[x + 1] == buf_offset) { /* * We subtract one so that * we don't go off the last * page! */ usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); } else { usbd_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res); } /* check if we need a new page */ if ((buf_res.physaddr ^ page_addr) & ~0xFFF) { /* new page needed */ page_addr = buf_res.physaddr & ~0xFFF; if (page_no == 6) { panic("%s: too many pages\n", __FUNCTION__); } page_no++; /* update page address */ td->itd_bp[page_no] &= htohc32(sc, 0xFFF); td->itd_bp[page_no] |= htohc32(sc, page_addr); } } } /* set IOC bit if we are complete */ if (nframes == 0) { td->itd_status[td_no - 1] |= htohc32(sc, EHCI_ITD_IOC); } usb_pc_cpu_flush(td->page_cache); #ifdef USB_DEBUG if (ehcidebug > 15) { DPRINTF("HS-TD %d\n", nframes); ehci_dump_itd(sc, td); } #endif /* insert TD into schedule */ EHCI_APPEND_HS_TD(td, *pp_last); pp_last++; td_no = 0; td_last = td; td = td->obj_next; } } xfer->td_transfer_last = td_last; /* update isoc_next */ xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); } static void ehci_device_isoc_hs_start(struct usb_xfer *xfer) { /* put transfer on interrupt queue */ ehci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ehci_device_isoc_hs_methods = { .open = ehci_device_isoc_hs_open, .close = ehci_device_isoc_hs_close, .enter = ehci_device_isoc_hs_enter, .start = ehci_device_isoc_hs_start, }; /*------------------------------------------------------------------------* * ehci root control support *------------------------------------------------------------------------* * Simulate a hardware hub by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor ehci_devd = { sizeof(struct usb_device_descriptor), UDESC_DEVICE, /* type */ {0x00, 0x02}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_HSHUBSTT, /* protocol */ 64, /* max packet */ {0}, {0}, {0x00, 0x01}, /* device id */ 1, 2, 0, /* string indexes */ 1 /* # of configurations */ }; static const struct usb_device_qualifier ehci_odevd = { sizeof(struct usb_device_qualifier), UDESC_DEVICE_QUALIFIER, /* type */ {0x00, 0x02}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_FSHUB, /* protocol */ 0, /* max packet */ 0, /* # of configurations */ 0 }; static const struct ehci_config_desc ehci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(ehci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0 /* max power */ }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | EHCI_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ .bInterval = 255, }, }; static const struct usb_hub_descriptor ehci_hubd = { .bDescLength = 0, /* dynamic length */ .bDescriptorType = UDESC_HUB, }; uint16_t ehci_get_port_speed_portsc(struct ehci_softc *sc, uint16_t index) { uint32_t v; v = EOREAD4(sc, EHCI_PORTSC(index)); v = (v >> EHCI_PORTSC_PSPD_SHIFT) & EHCI_PORTSC_PSPD_MASK; if (v == EHCI_PORT_SPEED_HIGH) return (UPS_HIGH_SPEED); if (v == EHCI_PORT_SPEED_LOW) return (UPS_LOW_SPEED); return (0); } uint16_t ehci_get_port_speed_hostc(struct ehci_softc *sc, uint16_t index) { uint32_t v; v = EOREAD4(sc, EHCI_HOSTC(index)); v = (v >> EHCI_HOSTC_PSPD_SHIFT) & EHCI_HOSTC_PSPD_MASK; if (v == EHCI_PORT_SPEED_HIGH) return (UPS_HIGH_SPEED); if (v == EHCI_PORT_SPEED_LOW) return (UPS_LOW_SPEED); return (0); } static void ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed) { uint32_t port; uint32_t v; DPRINTF("index=%d lowspeed=%d\n", index, lowspeed); port = EHCI_PORTSC(index); v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; EOWRITE4(sc, port, v | EHCI_PS_PO); } static usb_error_t ehci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); const char *str_ptr; const void *ptr; uint32_t port; uint32_t v; uint16_t len; uint16_t i; uint16_t value; uint16_t index; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_desc; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " "wValue=0x%04x wIndex=0x%04x\n", req->bmRequestType, req->bRequest, UGETW(req->wLength), value, index); #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): len = 1; sc->sc_hub_desc.temp[0] = sc->sc_conf; break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): switch (value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ehci_devd); ptr = (const void *)&ehci_devd; break; /* * We can't really operate at another speed, * but the specification says we need this * descriptor: */ case UDESC_DEVICE_QUALIFIER: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ehci_odevd); ptr = (const void *)&ehci_odevd; break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ehci_confd); ptr = (const void *)&ehci_confd; break; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ str_ptr = "\001"; break; case 1: /* Vendor */ str_ptr = sc->sc_vendor; break; case 2: /* Product */ str_ptr = "EHCI root HUB"; break; default: str_ptr = ""; break; } len = usb_make_str_desc( sc->sc_hub_desc.temp, sizeof(sc->sc_hub_desc.temp), str_ptr); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): len = 1; sc->sc_hub_desc.temp[0] = 0; break; case C(UR_GET_STATUS, UT_READ_DEVICE): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, 0); break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= EHCI_MAX_DEVICES) { err = USB_ERR_IOERROR; goto done; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if ((value != 0) && (value != 1)) { err = USB_ERR_IOERROR; goto done; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USB_ERR_IOERROR; goto done; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n"); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } port = EHCI_PORTSC(index); v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; switch (value) { case UHF_PORT_ENABLE: EOWRITE4(sc, port, v & ~EHCI_PS_PE); break; case UHF_PORT_SUSPEND: if ((v & EHCI_PS_SUSP) && (!(v & EHCI_PS_FPR))) { /* * waking up a High Speed device is rather * complicated if */ EOWRITE4(sc, port, v | EHCI_PS_FPR); } /* wait 20ms for resume sequence to complete */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP | EHCI_PS_FPR | (3 << 10) /* High Speed */ )); /* 4ms settle time */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); break; case UHF_PORT_POWER: EOWRITE4(sc, port, v & ~EHCI_PS_PP); break; case UHF_PORT_TEST: DPRINTFN(3, "clear port test " "%d\n", index); break; case UHF_PORT_INDICATOR: DPRINTFN(3, "clear port ind " "%d\n", index); EOWRITE4(sc, port, v & ~EHCI_PS_PIC); break; case UHF_C_PORT_CONNECTION: EOWRITE4(sc, port, v | EHCI_PS_CSC); break; case UHF_C_PORT_ENABLE: EOWRITE4(sc, port, v | EHCI_PS_PEC); break; case UHF_C_PORT_SUSPEND: EOWRITE4(sc, port, v | EHCI_PS_SUSP); break; case UHF_C_PORT_OVER_CURRENT: EOWRITE4(sc, port, v | EHCI_PS_OCC); break; case UHF_C_PORT_RESET: sc->sc_isreset = 0; break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } v = EREAD4(sc, EHCI_HCSPARAMS); sc->sc_hub_desc.hubd = ehci_hubd; sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; if (EHCI_HCS_PPC(v)) i = UHD_PWR_INDIVIDUAL; else i = UHD_PWR_NO_SWITCH; if (EHCI_HCS_P_INDICATOR(v)) i |= UHD_PORT_IND; USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i); /* XXX can't find out? */ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200; /* XXX don't know if ports are removable or not */ sc->sc_hub_desc.hubd.bDescLength = 8 + ((sc->sc_noport + 7) / 8); len = sc->sc_hub_desc.hubd.bDescLength; break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): len = 16; memset(sc->sc_hub_desc.temp, 0, 16); break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): DPRINTFN(9, "get port status i=%d\n", index); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } v = EOREAD4(sc, EHCI_PORTSC(index)); DPRINTFN(9, "port status=0x%04x\n", v); if (sc->sc_flags & EHCI_SCFLG_TT) { if (sc->sc_vendor_get_port_speed != NULL) { i = sc->sc_vendor_get_port_speed(sc, index); } else { device_printf(sc->sc_bus.bdev, "EHCI_SCFLG_TT quirk is set but " "sc_vendor_get_hub_speed() is NULL\n"); i = UPS_HIGH_SPEED; } } else { i = UPS_HIGH_SPEED; } if (v & EHCI_PS_CS) i |= UPS_CURRENT_CONNECT_STATUS; if (v & EHCI_PS_PE) i |= UPS_PORT_ENABLED; if ((v & EHCI_PS_SUSP) && !(v & EHCI_PS_FPR)) i |= UPS_SUSPEND; if (v & EHCI_PS_OCA) i |= UPS_OVERCURRENT_INDICATOR; if (v & EHCI_PS_PR) i |= UPS_RESET; if (v & EHCI_PS_PP) i |= UPS_PORT_POWER; USETW(sc->sc_hub_desc.ps.wPortStatus, i); i = 0; if (v & EHCI_PS_CSC) i |= UPS_C_CONNECT_STATUS; if (v & EHCI_PS_PEC) i |= UPS_C_PORT_ENABLED; if (v & EHCI_PS_OCC) i |= UPS_C_OVERCURRENT_INDICATOR; if (v & EHCI_PS_FPR) i |= UPS_C_SUSPEND; if (sc->sc_isreset) i |= UPS_C_PORT_RESET; USETW(sc->sc_hub_desc.ps.wPortChange, i); len = sizeof(sc->sc_hub_desc.ps); break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USB_ERR_IOERROR; goto done; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } port = EHCI_PORTSC(index); v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; switch (value) { case UHF_PORT_ENABLE: EOWRITE4(sc, port, v | EHCI_PS_PE); break; case UHF_PORT_SUSPEND: EOWRITE4(sc, port, v | EHCI_PS_SUSP); break; case UHF_PORT_RESET: DPRINTFN(6, "reset port %d\n", index); #ifdef USB_DEBUG if (ehcinohighspeed) { /* * Connect USB device to companion * controller. */ ehci_disown(sc, index, 1); break; } #endif if (EHCI_PS_IS_LOWSPEED(v) && (sc->sc_flags & EHCI_SCFLG_TT) == 0) { /* Low speed device, give up ownership. */ ehci_disown(sc, index, 1); break; } /* Start reset sequence. */ v &= ~(EHCI_PS_PE | EHCI_PS_PR); EOWRITE4(sc, port, v | EHCI_PS_PR); /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(usb_port_root_reset_delay)); /* Terminate reset sequence. */ if (!(sc->sc_flags & EHCI_SCFLG_NORESTERM)) EOWRITE4(sc, port, v); /* Wait for HC to complete reset. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(EHCI_PORT_RESET_COMPLETE)); v = EOREAD4(sc, port); DPRINTF("ehci after reset, status=0x%08x\n", v); if (v & EHCI_PS_PR) { device_printf(sc->sc_bus.bdev, "port reset timeout\n"); err = USB_ERR_TIMEOUT; goto done; } if (!(v & EHCI_PS_PE) && (sc->sc_flags & EHCI_SCFLG_TT) == 0) { /* Not a high speed device, give up ownership.*/ ehci_disown(sc, index, 0); break; } sc->sc_isreset = 1; DPRINTF("ehci port %d reset, status = 0x%08x\n", index, v); break; case UHF_PORT_POWER: DPRINTFN(3, "set port power %d\n", index); EOWRITE4(sc, port, v | EHCI_PS_PP); break; case UHF_PORT_TEST: DPRINTFN(3, "set port test %d\n", index); break; case UHF_PORT_INDICATOR: DPRINTFN(3, "set port ind %d\n", index); EOWRITE4(sc, port, v | EHCI_PS_PIC); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): break; default: err = USB_ERR_IOERROR; goto done; } done: *plength = len; *pptr = ptr; return (err); } static void ehci_xfer_setup(struct usb_setup_params *parm) { struct usb_page_search page_info; struct usb_page_cache *pc; ehci_softc_t *sc; struct usb_xfer *xfer; void *last_obj; uint32_t nqtd; uint32_t nqh; uint32_t nsitd; uint32_t nitd; uint32_t n; sc = EHCI_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; nqtd = 0; nqh = 0; nsitd = 0; nitd = 0; /* * compute maximum number of some structures */ if (parm->methods == &ehci_device_ctrl_methods) { /* * The proof for the "nqtd" formula is illustrated like * this: * * +------------------------------------+ * | | * | |remainder -> | * | +-----+---+ | * | | xxx | x | frm 0 | * | +-----+---++ | * | | xxx | xx | frm 1 | * | +-----+----+ | * | ... | * +------------------------------------+ * * "xxx" means a completely full USB transfer descriptor * * "x" and "xx" means a short USB packet * * For the remainder of an USB transfer modulo * "max_data_length" we need two USB transfer descriptors. * One to transfer the remaining data and one to finalise * with a zero length packet in case the "force_short_xfer" * flag is set. We only need two USB transfer descriptors in * the case where the transfer length of the first one is a * factor of "max_frame_size". The rest of the needed USB * transfer descriptors is given by the buffer size divided * by the maximum data payload. */ parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nqh = 1; nqtd = ((2 * xfer->nframes) + 1 /* STATUS */ + (xfer->max_data_length / xfer->max_hc_frame_size)); } else if (parm->methods == &ehci_device_bulk_methods) { parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nqh = 1; nqtd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); } else if (parm->methods == &ehci_device_intr_methods) { if (parm->speed == USB_SPEED_HIGH) { parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 3; } else if (parm->speed == USB_SPEED_FULL) { parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME; parm->hc_max_packet_count = 1; } else { parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8; parm->hc_max_packet_count = 1; } parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nqh = 1; nqtd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); } else if (parm->methods == &ehci_device_isoc_fs_methods) { parm->hc_max_packet_size = 0x3FF; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x3FF; xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nsitd = xfer->nframes; } else if (parm->methods == &ehci_device_isoc_hs_methods) { parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 3; parm->hc_max_frame_size = 0xC00; xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = ((xfer->nframes + 7) / 8) << usbd_xfer_get_fps_shift(xfer); } else { parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x400; usbd_transfer_setup_sub(parm); } alloc_dma_set: if (parm->err) { return; } /* * Allocate queue heads and transfer descriptors */ last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ehci_itd_t), EHCI_ITD_ALIGN, nitd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nitd; n++) { ehci_itd_t *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; /* init TD */ td->itd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_ITD); td->obj_next = last_obj; td->page_cache = pc + n; last_obj = td; usb_pc_cpu_flush(pc + n); } } if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ehci_sitd_t), EHCI_SITD_ALIGN, nsitd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nsitd; n++) { ehci_sitd_t *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; /* init TD */ td->sitd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_SITD); td->obj_next = last_obj; td->page_cache = pc + n; last_obj = td; usb_pc_cpu_flush(pc + n); } } if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ehci_qtd_t), EHCI_QTD_ALIGN, nqtd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nqtd; n++) { ehci_qtd_t *qtd; usbd_get_page(pc + n, 0, &page_info); qtd = page_info.buffer; /* init TD */ qtd->qtd_self = htohc32(sc, page_info.physaddr); qtd->obj_next = last_obj; qtd->page_cache = pc + n; last_obj = qtd; usb_pc_cpu_flush(pc + n); } } xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ehci_qh_t), EHCI_QH_ALIGN, nqh)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nqh; n++) { ehci_qh_t *qh; usbd_get_page(pc + n, 0, &page_info); qh = page_info.buffer; /* init QH */ qh->qh_self = htohc32(sc, page_info.physaddr | EHCI_LINK_QH); qh->obj_next = last_obj; qh->page_cache = pc + n; last_obj = qh; usb_pc_cpu_flush(pc + n); } } xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; if (!xfer->flags_int.curr_dma_set) { xfer->flags_int.curr_dma_set = 1; goto alloc_dma_set; } } static void ehci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void ehci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_addr); if (udev->device_index != sc->sc_addr) { if ((udev->speed != USB_SPEED_HIGH) && ((udev->hs_hub_addr == 0) || (udev->hs_port_no == 0) || (udev->parent_hs_hub == NULL) || (udev->parent_hs_hub->hub == NULL))) { /* We need a transaction translator */ goto done; } switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &ehci_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &ehci_device_intr_methods; break; case UE_ISOCHRONOUS: if (udev->speed == USB_SPEED_HIGH) { ep->methods = &ehci_device_isoc_hs_methods; } else if (udev->speed == USB_SPEED_FULL) { ep->methods = &ehci_device_isoc_fs_methods; } break; case UE_BULK: ep->methods = &ehci_device_bulk_methods; break; default: /* do nothing */ break; } } done: return; } static void ehci_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* * Wait until the hardware has finished any possible use of * the transfer descriptor(s) and QH */ *pus = (1125); /* microseconds */ } static void ehci_device_resume(struct usb_device *udev) { ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; if ((methods == &ehci_device_bulk_methods) || (methods == &ehci_device_ctrl_methods)) { EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], sc->sc_async_p_last); } if (methods == &ehci_device_intr_methods) { EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void ehci_device_suspend(struct usb_device *udev) { ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; if ((methods == &ehci_device_bulk_methods) || (methods == &ehci_device_ctrl_methods)) { EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], sc->sc_async_p_last); } if (methods == &ehci_device_intr_methods) { EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); } static void ehci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct ehci_softc *sc = EHCI_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: case USB_HW_POWER_SHUTDOWN: ehci_suspend(sc); break; case USB_HW_POWER_RESUME: ehci_resume(sc); break; default: break; } } static void ehci_set_hw_power(struct usb_bus *bus) { ehci_softc_t *sc = EHCI_BUS2SC(bus); uint32_t temp; uint32_t flags; DPRINTF("\n"); USB_BUS_LOCK(bus); flags = bus->hw_power_state; temp = EOREAD4(sc, EHCI_USBCMD); temp &= ~(EHCI_CMD_ASE | EHCI_CMD_PSE); if (flags & (USB_HW_POWER_CONTROL | USB_HW_POWER_BULK)) { DPRINTF("Async is active\n"); temp |= EHCI_CMD_ASE; } if (flags & (USB_HW_POWER_INTERRUPT | USB_HW_POWER_ISOC)) { DPRINTF("Periodic is active\n"); temp |= EHCI_CMD_PSE; } EOWRITE4(sc, EHCI_USBCMD, temp); USB_BUS_UNLOCK(bus); return; } static void ehci_start_dma_delay_second(struct usb_xfer *xfer) { struct ehci_softc *sc = EHCI_BUS2SC(xfer->xroot->bus); DPRINTF("\n"); /* trigger doorbell */ ehci_doorbell_async(sc); /* give the doorbell 4ms */ usbd_transfer_timeout_ms(xfer, (void (*)(void *))&usb_dma_delay_done_cb, 4); } /* * Ring the doorbell twice before freeing any DMA descriptors. Some host * controllers apparently cache the QH descriptors and need a message * that the cache needs to be discarded. */ static void ehci_start_dma_delay(struct usb_xfer *xfer) { struct ehci_softc *sc = EHCI_BUS2SC(xfer->xroot->bus); DPRINTF("\n"); /* trigger doorbell */ ehci_doorbell_async(sc); /* give the doorbell 4ms */ usbd_transfer_timeout_ms(xfer, (void (*)(void *))&ehci_start_dma_delay_second, 4); } static const struct usb_bus_methods ehci_bus_methods = { .endpoint_init = ehci_ep_init, .xfer_setup = ehci_xfer_setup, .xfer_unsetup = ehci_xfer_unsetup, .get_dma_delay = ehci_get_dma_delay, .device_resume = ehci_device_resume, .device_suspend = ehci_device_suspend, .set_hw_power = ehci_set_hw_power, .set_hw_power_sleep = ehci_set_hw_power_sleep, .roothub_exec = ehci_roothub_exec, .xfer_poll = ehci_do_poll, .start_dma_delay = ehci_start_dma_delay, }; Index: head/sys/dev/usb/controller/musb_otg.c =================================================================== --- head/sys/dev/usb/controller/musb_otg.c (revision 357971) +++ head/sys/dev/usb/controller/musb_otg.c (revision 357972) @@ -1,4279 +1,4280 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Thanks to Mentor Graphics for providing a reference driver for this USB chip * at their homepage. */ /* * This file contains the driver for the Mentor Graphics Inventra USB * 2.0 High Speed Dual-Role controller. * */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR musbotgdebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #define MUSBOTG_INTR_ENDPT 1 #define MUSBOTG_BUS2SC(bus) \ ((struct musbotg_softc *)(((uint8_t *)(bus)) - \ USB_P2U(&(((struct musbotg_softc *)0)->sc_bus)))) #define MUSBOTG_PC2SC(pc) \ MUSBOTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) #ifdef USB_DEBUG static int musbotgdebug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, musbotg, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB musbotg"); SYSCTL_INT(_hw_usb_musbotg, OID_AUTO, debug, CTLFLAG_RWTUN, &musbotgdebug, 0, "Debug level"); #endif #define MAX_NAK_TO 16 /* prototypes */ static const struct usb_bus_methods musbotg_bus_methods; static const struct usb_pipe_methods musbotg_device_bulk_methods; static const struct usb_pipe_methods musbotg_device_ctrl_methods; static const struct usb_pipe_methods musbotg_device_intr_methods; static const struct usb_pipe_methods musbotg_device_isoc_methods; /* Control transfers: Device mode */ static musbotg_cmd_t musbotg_dev_ctrl_setup_rx; static musbotg_cmd_t musbotg_dev_ctrl_data_rx; static musbotg_cmd_t musbotg_dev_ctrl_data_tx; static musbotg_cmd_t musbotg_dev_ctrl_status; /* Control transfers: Host mode */ static musbotg_cmd_t musbotg_host_ctrl_setup_tx; static musbotg_cmd_t musbotg_host_ctrl_data_rx; static musbotg_cmd_t musbotg_host_ctrl_data_tx; static musbotg_cmd_t musbotg_host_ctrl_status_rx; static musbotg_cmd_t musbotg_host_ctrl_status_tx; /* Bulk, Interrupt, Isochronous: Device mode */ static musbotg_cmd_t musbotg_dev_data_rx; static musbotg_cmd_t musbotg_dev_data_tx; /* Bulk, Interrupt, Isochronous: Host mode */ static musbotg_cmd_t musbotg_host_data_rx; static musbotg_cmd_t musbotg_host_data_tx; static void musbotg_device_done(struct usb_xfer *, usb_error_t); static void musbotg_do_poll(struct usb_bus *); static void musbotg_standard_done(struct usb_xfer *); static void musbotg_interrupt_poll(struct musbotg_softc *); static void musbotg_root_intr(struct musbotg_softc *); static int musbotg_channel_alloc(struct musbotg_softc *, struct musbotg_td *td, uint8_t); static void musbotg_channel_free(struct musbotg_softc *, struct musbotg_td *td); static void musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on); /* * Here is a configuration that the chip supports. */ static const struct usb_hw_ep_profile musbotg_ep_profile[1] = { [0] = { .max_in_frame_size = 64,/* fixed */ .max_out_frame_size = 64, /* fixed */ .is_simplex = 1, .support_control = 1, } }; static const struct musb_otg_ep_cfg musbotg_ep_default[] = { { .ep_end = 1, .ep_fifosz_shift = 12, .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_4096 | MUSB2_MASK_FIFODB, }, { .ep_end = 7, .ep_fifosz_shift = 10, .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_512 | MUSB2_MASK_FIFODB, }, { .ep_end = 15, .ep_fifosz_shift = 7, .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_128, }, { .ep_end = -1, }, }; static int musbotg_channel_alloc(struct musbotg_softc *sc, struct musbotg_td *td, uint8_t is_tx) { int ch; int ep; ep = td->ep_no; /* In device mode each EP got its own channel */ if (sc->sc_mode == MUSB2_DEVICE_MODE) { musbotg_ep_int_set(sc, ep, 1); return (ep); } /* * All control transactions go through EP0 */ if (ep == 0) { if (sc->sc_channel_mask & (1 << 0)) return (-1); sc->sc_channel_mask |= (1 << 0); musbotg_ep_int_set(sc, ep, 1); return (0); } for (ch = sc->sc_ep_max; ch != 0; ch--) { if (sc->sc_channel_mask & (1 << ch)) continue; /* check FIFO size requirement */ if (is_tx) { if (td->max_frame_size > sc->sc_hw_ep_profile[ch].max_in_frame_size) continue; } else { if (td->max_frame_size > sc->sc_hw_ep_profile[ch].max_out_frame_size) continue; } sc->sc_channel_mask |= (1 << ch); musbotg_ep_int_set(sc, ch, 1); return (ch); } DPRINTFN(-1, "No available channels. Mask: %04x\n", sc->sc_channel_mask); return (-1); } static void musbotg_channel_free(struct musbotg_softc *sc, struct musbotg_td *td) { DPRINTFN(1, "ep_no=%d\n", td->channel); if (sc->sc_mode == MUSB2_DEVICE_MODE) return; if (td == NULL) return; if (td->channel == -1) return; musbotg_ep_int_set(sc, td->channel, 0); sc->sc_channel_mask &= ~(1 << td->channel); td->channel = -1; } static void musbotg_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { struct musbotg_softc *sc; sc = MUSBOTG_BUS2SC(udev->bus); if (ep_addr == 0) { /* control endpoint */ *ppf = musbotg_ep_profile; } else if (ep_addr <= sc->sc_ep_max) { /* other endpoints */ *ppf = sc->sc_hw_ep_profile + ep_addr; } else { *ppf = NULL; } } static void musbotg_clocks_on(struct musbotg_softc *sc) { if (sc->sc_flags.clocks_off && sc->sc_flags.port_powered) { DPRINTFN(4, "\n"); if (sc->sc_clocks_on) { (sc->sc_clocks_on) (sc->sc_clocks_arg); } sc->sc_flags.clocks_off = 0; /* XXX enable Transceiver */ } } static void musbotg_clocks_off(struct musbotg_softc *sc) { if (!sc->sc_flags.clocks_off) { DPRINTFN(4, "\n"); /* XXX disable Transceiver */ if (sc->sc_clocks_off) { (sc->sc_clocks_off) (sc->sc_clocks_arg); } sc->sc_flags.clocks_off = 1; } } static void musbotg_pull_common(struct musbotg_softc *sc, uint8_t on) { uint8_t temp; temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); if (on) temp |= MUSB2_MASK_SOFTC; else temp &= ~MUSB2_MASK_SOFTC; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); } static void musbotg_pull_up(struct musbotg_softc *sc) { /* pullup D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { sc->sc_flags.d_pulled_up = 1; musbotg_pull_common(sc, 1); } } static void musbotg_pull_down(struct musbotg_softc *sc) { /* pulldown D+, if possible */ if (sc->sc_flags.d_pulled_up) { sc->sc_flags.d_pulled_up = 0; musbotg_pull_common(sc, 0); } } static void musbotg_suspend_host(struct musbotg_softc *sc) { uint8_t temp; if (sc->sc_flags.status_suspend) { return; } temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp |= MUSB2_MASK_SUSPMODE; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); sc->sc_flags.status_suspend = 1; } static void musbotg_wakeup_host(struct musbotg_softc *sc) { uint8_t temp; if (!(sc->sc_flags.status_suspend)) { return; } temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp &= ~MUSB2_MASK_SUSPMODE; temp |= MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); /* wait 20 milliseconds */ /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp &= ~MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); sc->sc_flags.status_suspend = 0; } static void musbotg_wakeup_peer(struct musbotg_softc *sc) { uint8_t temp; if (!(sc->sc_flags.status_suspend)) { return; } temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp |= MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); /* wait 8 milliseconds */ /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp &= ~MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); } static void musbotg_set_address(struct musbotg_softc *sc, uint8_t addr) { DPRINTFN(4, "addr=%d\n", addr); addr &= 0x7F; MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr); } static uint8_t musbotg_dev_ctrl_setup_rx(struct musbotg_td *td) { struct musbotg_softc *sc; struct usb_device_request req; uint16_t count; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* * NOTE: If DATAEND is set we should not call the * callback, hence the status stage is not complete. */ if (csr & MUSB2_MASK_CSR0L_DATAEND) { /* do not stall at this point */ td->did_stall = 1; /* wait for interrupt */ DPRINTFN(1, "CSR0 DATAEND\n"); goto not_complete; } if (csr & MUSB2_MASK_CSR0L_SENTSTALL) { /* clear SENTSTALL */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); /* get latest status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); /* update EP0 state */ sc->sc_ep0_busy = 0; } if (csr & MUSB2_MASK_CSR0L_SETUPEND) { /* clear SETUPEND */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_SETUPEND_CLR); /* get latest status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); /* update EP0 state */ sc->sc_ep0_busy = 0; } if (sc->sc_ep0_busy) { DPRINTFN(1, "EP0 BUSY\n"); goto not_complete; } if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { goto not_complete; } /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); /* verify data length */ if (count != td->remainder) { DPRINTFN(1, "Invalid SETUP packet " "length, %d bytes\n", count); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); /* don't clear stall */ td->did_stall = 1; goto not_complete; } if (count != sizeof(req)) { DPRINTFN(1, "Unsupported SETUP packet " "length, %d bytes\n", count); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); /* don't clear stall */ td->did_stall = 1; goto not_complete; } /* clear did stall flag */ td->did_stall = 0; /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req)); /* copy data into real buffer */ usbd_copy_in(td->pc, 0, &req, sizeof(req)); td->offset = sizeof(req); td->remainder = 0; /* set pending command */ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; /* we need set stall or dataend after this */ sc->sc_ep0_busy = 1; /* sneak peek the set address */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { sc->sc_dv_addr = req.wValue[0] & 0x7F; } else { sc->sc_dv_addr = 0xFF; } musbotg_channel_free(sc, td); return (0); /* complete */ not_complete: /* abort any ongoing transfer */ if (!td->did_stall) { DPRINTFN(4, "stalling\n"); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_SENDSTALL); td->did_stall = 1; } return (1); /* not complete */ } static uint8_t musbotg_host_ctrl_setup_tx(struct musbotg_td *td) { struct musbotg_softc *sc; struct usb_device_request req; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* Not ready yet yet */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; } if (csr & MUSB2_MASK_CSR0L_NAKTIMO) { DPRINTFN(1, "NAK timeout\n"); if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); } /* Fifo is not empty and there is no NAK timeout */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); /* check if we are complete */ if (td->remainder == 0) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* copy data into real buffer */ usbd_copy_out(td->pc, 0, &req, sizeof(req)); /* send data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req)); /* update offset and remainder */ td->offset += sizeof(req); td->remainder -= sizeof(req); MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_TXPKTRDY | MUSB2_MASK_CSR0L_SETUPPKT); /* Just to be consistent, not used above */ td->transaction_started = 1; return (1); /* in progress */ } /* Control endpoint only data handling functions (RX/TX/SYNC) */ static uint8_t musbotg_dev_ctrl_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t got_short; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* check if a command is pending */ if (sc->sc_ep0_cmd) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); sc->sc_ep0_cmd = 0; } /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); got_short = 0; if (csr & (MUSB2_MASK_CSR0L_SETUPEND | MUSB2_MASK_CSR0L_SENTSTALL)) { if (td->remainder == 0) { /* * We are actually complete and have * received the next SETUP */ DPRINTFN(4, "faking complete\n"); return (0); /* complete */ } /* * USB Host Aborted the transfer. */ td->error = 1; return (0); /* complete */ } if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { return (1); /* not complete */ } /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); /* verify the packet byte count */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)(&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; return (0); } /* else need to receive a zero length packet */ } /* write command - need more data */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); return (1); /* not complete */ } static uint8_t musbotg_dev_ctrl_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* check if a command is pending */ if (sc->sc_ep0_cmd) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); sc->sc_ep0_cmd = 0; } /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSR0L_SETUPEND | MUSB2_MASK_CSR0L_SENTSTALL)) { /* * The current transfer was aborted * by the USB Host */ td->error = 1; return (0); /* complete */ } if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) { return (1); /* not complete */ } count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY; return (0); /* complete */ } /* else we need to transmit a short packet */ } /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_TXPKTRDY); return (1); /* not complete */ } static uint8_t musbotg_host_ctrl_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t got_short; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); got_short = 0; if (!td->transaction_started) { td->transaction_started = 1; MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO); MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_REQPKT); return (1); } if (csr & MUSB2_MASK_CSR0L_NAKTIMO) { csr &= ~MUSB2_MASK_CSR0L_REQPKT; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); /* we are complete */ } if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) return (1); /* not yet */ /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); /* verify the packet byte count */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)(&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } csr &= ~MUSB2_MASK_CSR0L_RXPKTRDY; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* else need to receive a zero length packet */ } td->transaction_started = 1; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_REQPKT); return (1); /* not complete */ } static uint8_t musbotg_host_ctrl_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* No free EPs */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); td->error = 1; } if (csr & MUSB2_MASK_CSR0L_NAKTIMO ) { if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* * Wait while FIFO is empty. * Do not flush it because it will cause transactions * with size more then packet size. It might upset * some devices */ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) return (1); /* Packet still being processed */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); if (td->transaction_started) { /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* else we need to transmit a short packet */ } /* We're not complete - more transactions required */ td->transaction_started = 0; } /* check for short packet */ count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* Function address */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* TX NAK timeout */ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_TXPKTRDY); td->transaction_started = 1; return (1); /* not complete */ } static uint8_t musbotg_dev_ctrl_status(struct musbotg_td *td) { struct musbotg_softc *sc; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); if (sc->sc_ep0_busy) { sc->sc_ep0_busy = 0; sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); sc->sc_ep0_cmd = 0; } /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & MUSB2_MASK_CSR0L_DATAEND) { /* wait for interrupt */ return (1); /* not complete */ } if (sc->sc_dv_addr != 0xFF) { /* write function address */ musbotg_set_address(sc, sc->sc_dv_addr); } musbotg_channel_free(sc, td); return (0); /* complete */ } static uint8_t musbotg_host_ctrl_status_rx(struct musbotg_td *td) { struct musbotg_softc *sc; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); if (!td->transaction_started) { MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type); /* RX NAK timeout */ MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO); td->transaction_started = 1; /* Disable PING */ csrh = MUSB2_READ_1(sc, MUSB2_REG_RXCSRH); csrh |= MUSB2_MASK_CSR0H_PING_DIS; MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, csrh); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_STATUSPKT | MUSB2_MASK_CSR0L_REQPKT); return (1); /* Just started */ } csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "IN STATUS csr=0x%02x\n", csr); if (csr & MUSB2_MASK_CSR0L_RXPKTRDY) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); musbotg_channel_free(sc, td); return (0); /* complete */ } if (csr & MUSB2_MASK_CSR0L_NAKTIMO) { csr &= ~ (MUSB2_MASK_CSR0L_STATUSPKT | MUSB2_MASK_CSR0L_REQPKT); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); } return (1); /* Not ready yet */ } static uint8_t musbotg_host_ctrl_status_tx(struct musbotg_td *td) { struct musbotg_softc *sc; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d/%d [%d@%d.%d/%02x]\n", td->channel, td->transaction_started, td->dev_addr,td->haddr,td->hport, td->transfer_type); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* Not yet */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; musbotg_channel_free(sc, td); return (0); /* complete */ } if (td->transaction_started) { musbotg_channel_free(sc, td); return (0); /* complete */ } MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSR0H_PING_DIS); MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* TX NAK timeout */ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); td->transaction_started = 1; /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_STATUSPKT | MUSB2_MASK_CSR0L_TXPKTRDY); return (1); /* wait for interrupt */ } static uint8_t musbotg_dev_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t to; uint8_t got_short; to = 8; /* don't loop forever! */ got_short = 0; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); repeat: /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* clear overrun */ if (csr & MUSB2_MASK_CSRL_RXOVERRUN) { /* make sure we don't clear "RXPKTRDY" */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXPKTRDY); } /* check status */ if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) return (1); /* not complete */ /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); DPRINTFN(4, "count=0x%04x\n", count); /* * Check for short or invalid packet: */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* else need to receive a zero length packet */ } if (--to) { goto repeat; } return (1); /* not complete */ } static uint8_t musbotg_dev_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t to; to = 8; /* don't loop forever! */ /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) return (1); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); repeat: /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSRL_TXINCOMP | MUSB2_MASK_CSRL_TXUNDERRUN)) { /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); } if (csr & MUSB2_MASK_CSRL_TXPKTRDY) { return (1); /* not complete */ } /* check for short packet */ count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* Max packet size */ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, td->reg_max_packet); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXPKTRDY); /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* else we need to transmit a short packet */ } if (--to) { goto repeat; } return (1); /* not complete */ } static uint8_t musbotg_host_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr, csrh; uint8_t to; uint8_t got_short; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* No free EPs */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); to = 8; /* don't loop forever! */ got_short = 0; /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); repeat: /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (!td->transaction_started) { /* Function address */ MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(td->channel), td->dev_addr); /* SPLIT transaction */ MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(td->channel), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(td->channel), td->hport); /* RX NAK timeout */ if (td->transfer_type & MUSB2_MASK_TI_PROTO_ISOC) MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, 0); else MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO); /* Protocol, speed, device endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type); /* Max packet size */ MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, td->reg_max_packet); /* Data Toggle */ csrh = MUSB2_READ_1(sc, MUSB2_REG_RXCSRH); DPRINTFN(4, "csrh=0x%02x\n", csrh); csrh |= MUSB2_MASK_CSRH_RXDT_WREN; if (td->toggle) csrh |= MUSB2_MASK_CSRH_RXDT_VAL; else csrh &= ~MUSB2_MASK_CSRH_RXDT_VAL; /* Set data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, csrh); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXREQPKT); td->transaction_started = 1; return (1); } /* clear NAK timeout */ if (csr & MUSB2_MASK_CSRL_RXNAKTO) { DPRINTFN(4, "NAK Timeout\n"); if (csr & MUSB2_MASK_CSRL_RXREQPKT) { csr &= ~MUSB2_MASK_CSRL_RXREQPKT; MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, csr); csr &= ~MUSB2_MASK_CSRL_RXNAKTO; MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, csr); } td->error = 1; } if (csr & MUSB2_MASK_CSRL_RXERROR) { DPRINTFN(4, "RXERROR\n"); td->error = 1; } if (csr & MUSB2_MASK_CSRL_RXSTALL) { DPRINTFN(4, "RXSTALL\n"); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); /* we are complete */ } if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) { /* No data available yet */ return (1); } td->toggle ^= 1; /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); DPRINTFN(4, "count=0x%04x\n", count); /* * Check for short or invalid packet: */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* else need to receive a zero length packet */ } /* Reset transaction state and restart */ td->transaction_started = 0; if (--to) goto repeat; return (1); /* not complete */ } static uint8_t musbotg_host_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* No free EPs */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSRL_TXSTALLED | MUSB2_MASK_CSRL_TXERROR)) { /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); td->error = 1; musbotg_channel_free(sc, td); return (0); /* complete */ } if (csr & MUSB2_MASK_CSRL_TXNAKTO) { /* * Flush TX FIFO before clearing NAK TO */ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { csr |= MUSB2_MASK_CSRL_TXFFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { csr |= MUSB2_MASK_CSRL_TXFFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } csr &= ~MUSB2_MASK_CSRL_TXNAKTO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; musbotg_channel_free(sc, td); return (0); /* complete */ } /* * Wait while FIFO is empty. * Do not flush it because it will cause transactions * with size more then packet size. It might upset * some devices */ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) return (1); /* Packet still being processed */ if (csr & MUSB2_MASK_CSRL_TXPKTRDY) return (1); if (td->transaction_started) { /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* else we need to transmit a short packet */ } /* We're not complete - more transactions required */ td->transaction_started = 0; } /* check for short packet */ count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* Function address */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(td->channel), td->dev_addr); /* SPLIT transaction */ MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(td->channel), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(td->channel), td->hport); /* TX NAK timeout */ if (td->transfer_type & MUSB2_MASK_TI_PROTO_ISOC) MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, 0); else MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); /* Protocol, speed, device endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* Max packet size */ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, td->reg_max_packet); if (!td->transaction_started) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); DPRINTFN(4, "csrh=0x%02x\n", csrh); csrh |= MUSB2_MASK_CSRH_TXDT_WREN; if (td->toggle) csrh |= MUSB2_MASK_CSRH_TXDT_VAL; else csrh &= ~MUSB2_MASK_CSRH_TXDT_VAL; /* Set data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); } /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXPKTRDY); /* Update Data Toggle */ td->toggle ^= 1; td->transaction_started = 1; return (1); /* not complete */ } static uint8_t musbotg_xfer_do_fifo(struct usb_xfer *xfer) { struct musbotg_softc *sc; struct musbotg_td *td; DPRINTFN(8, "\n"); sc = MUSBOTG_BUS2SC(xfer->xroot->bus); td = xfer->td_transfer_cache; while (1) { if ((td->func) (td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) { goto done; } } /* * Fetch the next transfer descriptor and transfer * some flags to the next transfer descriptor */ td = td->obj_next; xfer->td_transfer_cache = td; } return (1); /* not complete */ done: /* compute all actual lengths */ musbotg_standard_done(xfer); return (0); /* complete */ } static void musbotg_interrupt_poll(struct musbotg_softc *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (!musbotg_xfer_do_fifo(xfer)) { /* queue has been modified */ goto repeat; } } } void musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on) { DPRINTFN(4, "vbus = %u\n", is_on); USB_BUS_LOCK(&sc->sc_bus); if (is_on) { if (!sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 1; /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); } } else { if (sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); } } USB_BUS_UNLOCK(&sc->sc_bus); } void musbotg_connect_interrupt(struct musbotg_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); USB_BUS_UNLOCK(&sc->sc_bus); } void musbotg_interrupt(struct musbotg_softc *sc, uint16_t rxstat, uint16_t txstat, uint8_t stat) { uint16_t rx_status; uint16_t tx_status; uint8_t usb_status; uint8_t temp; uint8_t to = 2; USB_BUS_LOCK(&sc->sc_bus); repeat: /* read all interrupt registers */ usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB); /* read all FIFO interrupts */ rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX); tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX); rx_status |= rxstat; tx_status |= txstat; usb_status |= stat; /* Clear platform flags after first time */ rxstat = 0; txstat = 0; stat = 0; /* check for any bus state change interrupts */ if (usb_status & (MUSB2_MASK_IRESET | MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP | MUSB2_MASK_ICONN | MUSB2_MASK_IDISC | MUSB2_MASK_IVBUSERR)) { DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status); if (usb_status & MUSB2_MASK_IRESET) { /* set correct state */ sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* determine line speed */ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); if (temp & MUSB2_MASK_HSMODE) sc->sc_flags.status_high_speed = 1; else sc->sc_flags.status_high_speed = 0; /* * After reset all interrupts are on and we need to * turn them off! */ temp = MUSB2_MASK_IRESET; /* disable resume interrupt */ temp &= ~MUSB2_MASK_IRESUME; /* enable suspend interrupt */ temp |= MUSB2_MASK_ISUSP; MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); /* disable TX and RX interrupts */ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); } /* * If RXRSM and RXSUSP is set at the same time we interpret * that like RESUME. Resume is set when there is at least 3 * milliseconds of inactivity on the USB BUS. */ if (usb_status & MUSB2_MASK_IRESUME) { if (sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); /* disable resume interrupt */ temp &= ~MUSB2_MASK_IRESUME; /* enable suspend interrupt */ temp |= MUSB2_MASK_ISUSP; MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); } } else if (usb_status & MUSB2_MASK_ISUSP) { if (!sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); /* disable suspend interrupt */ temp &= ~MUSB2_MASK_ISUSP; /* enable resume interrupt */ temp |= MUSB2_MASK_IRESUME; MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); } } if (usb_status & (MUSB2_MASK_ICONN | MUSB2_MASK_IDISC)) sc->sc_flags.change_connect = 1; /* * Host Mode: There is no IRESET so assume bus is * always in reset state once device is connected. */ if (sc->sc_mode == MUSB2_HOST_MODE) { /* check for VBUS error in USB host mode */ if (usb_status & MUSB2_MASK_IVBUSERR) { temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); temp |= MUSB2_MASK_SESS; MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); } if (usb_status & MUSB2_MASK_ICONN) sc->sc_flags.status_bus_reset = 1; if (usb_status & MUSB2_MASK_IDISC) sc->sc_flags.status_bus_reset = 0; } /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); } /* check for any endpoint interrupts */ if (rx_status || tx_status) { DPRINTFN(4, "real endpoint interrupt " "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status); } /* poll one time regardless of FIFO status */ musbotg_interrupt_poll(sc); if (--to) goto repeat; USB_BUS_UNLOCK(&sc->sc_bus); } static void musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp) { struct musbotg_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->error = 0; td->transaction_started = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; td->channel = temp->channel; td->dev_addr = temp->dev_addr; td->haddr = temp->haddr; td->hport = temp->hport; td->transfer_type = temp->transfer_type; } static void musbotg_setup_standard_chain(struct usb_xfer *xfer) { struct musbotg_std_temp temp; struct musbotg_softc *sc; struct musbotg_td *td; uint32_t x; uint8_t ep_no; uint8_t xfer_type; enum usb_dev_speed speed; int tx; int dev_addr; DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); sc = MUSBOTG_BUS2SC(xfer->xroot->bus); ep_no = (xfer->endpointno & UE_ADDR); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ dev_addr = xfer->address; xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; temp.channel = -1; temp.dev_addr = dev_addr; temp.haddr = xfer->xroot->udev->hs_hub_addr; temp.hport = xfer->xroot->udev->hs_port_no; if (xfer->flags_int.usb_mode == USB_MODE_HOST) { speed = usbd_get_speed(xfer->xroot->udev); switch (speed) { case USB_SPEED_LOW: temp.transfer_type = MUSB2_MASK_TI_SPEED_LO; break; case USB_SPEED_FULL: temp.transfer_type = MUSB2_MASK_TI_SPEED_FS; break; case USB_SPEED_HIGH: temp.transfer_type = MUSB2_MASK_TI_SPEED_HS; break; default: temp.transfer_type = 0; DPRINTFN(-1, "Invalid USB speed: %d\n", speed); break; } switch (xfer_type) { case UE_CONTROL: temp.transfer_type |= MUSB2_MASK_TI_PROTO_CTRL; break; case UE_ISOCHRONOUS: temp.transfer_type |= MUSB2_MASK_TI_PROTO_ISOC; break; case UE_BULK: temp.transfer_type |= MUSB2_MASK_TI_PROTO_BULK; break; case UE_INTERRUPT: temp.transfer_type |= MUSB2_MASK_TI_PROTO_INTR; break; default: DPRINTFN(-1, "Invalid USB transfer type: %d\n", xfer_type); break; } temp.transfer_type |= ep_no; td->toggle = xfer->endpoint->toggle_next; } /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) temp.func = &musbotg_dev_ctrl_setup_rx; else temp.func = &musbotg_host_ctrl_setup_tx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; musbotg_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } tx = 0; if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) tx = 1; if (xfer->flags_int.usb_mode == USB_MODE_HOST) { tx = !tx; if (tx) { if (xfer->flags_int.control_xfr) temp.func = &musbotg_host_ctrl_data_tx; else temp.func = &musbotg_host_data_tx; } else { if (xfer->flags_int.control_xfr) temp.func = &musbotg_host_ctrl_data_rx; else temp.func = &musbotg_host_data_rx; } } else { if (tx) { if (xfer->flags_int.control_xfr) temp.func = &musbotg_dev_ctrl_data_tx; else temp.func = &musbotg_dev_data_tx; } else { if (xfer->flags_int.control_xfr) temp.func = &musbotg_dev_ctrl_data_rx; else temp.func = &musbotg_dev_data_rx; } } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { if (xfer->flags_int.isochronous_xfr) { /* isochronous data transfer */ /* don't force short */ temp.short_pkt = 1; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer ? 0 : 1); } } musbotg_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } /* check for control transfer */ if (xfer->flags_int.control_xfr) { /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (sc->sc_mode == MUSB2_DEVICE_MODE) temp.func = &musbotg_dev_ctrl_status; else { if (xfer->endpointno & UE_DIR_IN) temp.func = musbotg_host_ctrl_status_tx; else temp.func = musbotg_host_ctrl_status_rx; } musbotg_setup_standard_chain_sub(&temp); } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; } static void musbotg_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTFN(1, "xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ musbotg_device_done(xfer, USB_ERR_TIMEOUT); } static void musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on) { uint16_t temp; /* * Only enable the endpoint interrupt when we are * actually waiting for data, hence we are dealing * with level triggered interrupts ! */ DPRINTFN(1, "ep_no=%d, on=%d\n", channel, on); if (channel == -1) return; if (channel == 0) { temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); if (on) temp |= MUSB2_MASK_EPINT(0); else temp &= ~MUSB2_MASK_EPINT(0); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); } else { temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE); if (on) temp |= MUSB2_MASK_EPINT(channel); else temp &= ~MUSB2_MASK_EPINT(channel); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp); temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); if (on) temp |= MUSB2_MASK_EPINT(channel); else temp &= ~MUSB2_MASK_EPINT(channel); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); } if (sc->sc_ep_int_set) sc->sc_ep_int_set(sc, channel, on); } static void musbotg_start_standard_chain(struct usb_xfer *xfer) { DPRINTFN(8, "\n"); /* poll one time */ if (musbotg_xfer_do_fifo(xfer)) { DPRINTFN(14, "enabled interrupts on endpoint\n"); /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &musbotg_timeout, xfer->timeout); } } } static void musbotg_root_intr(struct musbotg_softc *sc) { DPRINTFN(8, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit */ sc->sc_hub_idata[0] = 0x02; /* we only have one port */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t musbotg_standard_done_sub(struct usb_xfer *xfer) { struct musbotg_td *td; uint32_t len; uint8_t error; DPRINTFN(8, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; xfer->endpoint->toggle_next = td->toggle; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error) { /* the transfer is finished */ error = 1; td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error ? USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); } static void musbotg_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(12, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = musbotg_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = musbotg_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = musbotg_standard_done_sub(xfer); } done: musbotg_device_done(xfer, err); } /*------------------------------------------------------------------------* * musbotg_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void musbotg_device_done(struct usb_xfer *xfer, usb_error_t error) { struct musbotg_td *td; struct musbotg_softc *sc; USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); DPRINTFN(1, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); DPRINTFN(14, "disabled interrupts on endpoint\n"); sc = MUSBOTG_BUS2SC(xfer->xroot->bus); td = xfer->td_transfer_cache; if (td && (td->channel != -1)) musbotg_channel_free(sc, td); /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } static void musbotg_xfer_stall(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_STALLED); } static void musbotg_set_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall) { struct musbotg_softc *sc; uint8_t ep_no; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(4, "endpoint=%p\n", ep); /* set FORCESTALL */ sc = MUSBOTG_BUS2SC(udev->bus); ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); if (ep->edesc->bEndpointAddress & UE_DIR_IN) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXSENDSTALL); } else { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXSENDSTALL); } } static void musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { uint16_t mps; uint16_t temp; uint8_t csr; if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); /* compute max frame size */ mps = wMaxPacket & 0x7FF; switch ((wMaxPacket >> 11) & 3) { case 1: mps *= 2; break; case 2: mps *= 3; break; default: break; } if (ep_dir == UE_DIR_IN) { temp = 0; /* Configure endpoint */ switch (ep_type) { case UE_INTERRUPT: MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, MUSB2_MASK_CSRH_TXMODE | temp); break; case UE_ISOCHRONOUS: MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, MUSB2_MASK_CSRH_TXMODE | MUSB2_MASK_CSRH_TXISO | temp); break; case UE_BULK: MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, MUSB2_MASK_CSRH_TXMODE | temp); break; default: break; } /* Need to flush twice in case of double bufring */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } /* reset data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXDT_CLR); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); /* set double/single buffering */ temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS); if (mps <= (sc->sc_hw_ep_profile[ep_no]. max_in_frame_size / 2)) { /* double buffer */ temp &= ~(1 << ep_no); } else { /* single buffer */ temp |= (1 << ep_no); } MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp); /* clear sent stall */ if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } else { temp = 0; /* Configure endpoint */ switch (ep_type) { case UE_INTERRUPT: MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSRH_RXNYET | temp); break; case UE_ISOCHRONOUS: MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSRH_RXNYET | MUSB2_MASK_CSRH_RXISO | temp); break; case UE_BULK: MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp); break; default: break; } /* Need to flush twice in case of double bufring */ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); } } /* reset data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXDT_CLR); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); /* set double/single buffering */ temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS); if (mps <= (sc->sc_hw_ep_profile[ep_no]. max_out_frame_size / 2)) { /* double buffer */ temp &= ~(1 << ep_no); } else { /* single buffer */ temp |= (1 << ep_no); } MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp); /* clear sent stall */ if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); } } } static void musbotg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct musbotg_softc *sc; struct usb_endpoint_descriptor *ed; DPRINTFN(4, "endpoint=%p\n", ep); USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = MUSBOTG_BUS2SC(udev->bus); /* get endpoint descriptor */ ed = ep->edesc; /* reset endpoint */ musbotg_clear_stall_sub(sc, UGETW(ed->wMaxPacketSize), (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); } usb_error_t musbotg_init(struct musbotg_softc *sc) { const struct musb_otg_ep_cfg *cfg; struct usb_hw_ep_profile *pf; int i; uint16_t offset; uint8_t nrx; uint8_t ntx; uint8_t temp; uint8_t fsize; uint8_t frx; uint8_t ftx; uint8_t dynfifo; DPRINTFN(1, "start\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_2_0; sc->sc_bus.methods = &musbotg_bus_methods; /* Set a default endpoint configuration */ if (sc->sc_ep_cfg == NULL) sc->sc_ep_cfg = musbotg_ep_default; USB_BUS_LOCK(&sc->sc_bus); /* turn on clocks */ if (sc->sc_clocks_on) { (sc->sc_clocks_on) (sc->sc_clocks_arg); } /* wait a little for things to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); /* disable all interrupts */ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); DPRINTF("pre-DEVCTL=0x%02x\n", temp); MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); /* disable pullup */ musbotg_pull_common(sc, 0); /* wait a little bit (10ms) */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* disable double packet buffering */ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF); MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF); /* enable HighSpeed and ISO Update flags */ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD); if (sc->sc_mode == MUSB2_DEVICE_MODE) { /* clear Session bit, if set */ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); temp &= ~MUSB2_MASK_SESS; MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); } else { /* Enter session for Host mode */ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); temp |= MUSB2_MASK_SESS; MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); } /* wait a little for things to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10); DPRINTF("DEVCTL=0x%02x\n", temp); /* disable testmode */ MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0); /* set default value */ MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0); /* select endpoint index 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); if (sc->sc_ep_max == 0) { /* read out number of endpoints */ nrx = (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16); ntx = (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16); sc->sc_ep_max = (nrx > ntx) ? nrx : ntx; } else { nrx = ntx = sc->sc_ep_max; } /* these numbers exclude the control endpoint */ DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx); if (sc->sc_ep_max == 0) { DPRINTFN(2, "ERROR: Looks like the clocks are off!\n"); } /* read out configuration data */ sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA); DPRINTFN(2, "Config Data: 0x%02x\n", sc->sc_conf_data); dynfifo = (sc->sc_conf_data & MUSB2_MASK_CD_DYNFIFOSZ) ? 1 : 0; if (dynfifo) { device_printf(sc->sc_bus.bdev, "Dynamic FIFO sizing detected, " "assuming 16Kbytes of FIFO RAM\n"); } DPRINTFN(2, "HW version: 0x%04x\n", MUSB2_READ_1(sc, MUSB2_REG_HWVERS)); /* initialise endpoint profiles */ offset = 0; for (temp = 1; temp <= sc->sc_ep_max; temp++) { pf = sc->sc_hw_ep_profile + temp; /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp); fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE); frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16; ftx = (fsize & MUSB2_MASK_TX_FSIZE); DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u, DYN=%d\n", temp, ftx, frx, dynfifo); if (dynfifo) { if (frx && (temp <= nrx)) { for (i = 0; sc->sc_ep_cfg[i].ep_end >= 0; i++) { cfg = &sc->sc_ep_cfg[i]; if (temp <= cfg->ep_end) { frx = cfg->ep_fifosz_shift; MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ, cfg->ep_fifosz_reg); break; } } MUSB2_WRITE_2(sc, MUSB2_REG_RXFIFOADD, offset >> 3); offset += (1 << frx); } if (ftx && (temp <= ntx)) { for (i = 0; sc->sc_ep_cfg[i].ep_end >= 0; i++) { cfg = &sc->sc_ep_cfg[i]; if (temp <= cfg->ep_end) { ftx = cfg->ep_fifosz_shift; MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ, cfg->ep_fifosz_reg); break; } } MUSB2_WRITE_2(sc, MUSB2_REG_TXFIFOADD, offset >> 3); offset += (1 << ftx); } } if (frx && ftx && (temp <= nrx) && (temp <= ntx)) { pf->max_in_frame_size = 1 << ftx; pf->max_out_frame_size = 1 << frx; pf->is_simplex = 0; /* duplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; pf->support_interrupt = 1; pf->support_isochronous = 1; pf->support_in = 1; pf->support_out = 1; } else if (frx && (temp <= nrx)) { pf->max_out_frame_size = 1 << frx; pf->max_in_frame_size = 0; pf->is_simplex = 1; /* simplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; pf->support_interrupt = 1; pf->support_isochronous = 1; pf->support_out = 1; } else if (ftx && (temp <= ntx)) { pf->max_in_frame_size = 1 << ftx; pf->max_out_frame_size = 0; pf->is_simplex = 1; /* simplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; pf->support_interrupt = 1; pf->support_isochronous = 1; pf->support_in = 1; } } DPRINTFN(2, "Dynamic FIFO size = %d bytes\n", offset); /* turn on default interrupts */ if (sc->sc_mode == MUSB2_HOST_MODE) MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0xff); else MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, MUSB2_MASK_IRESET); musbotg_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ musbotg_do_poll(&sc->sc_bus); return (0); /* success */ } void musbotg_uninit(struct musbotg_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); /* disable all interrupts */ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; musbotg_pull_down(sc); musbotg_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void musbotg_do_poll(struct usb_bus *bus) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); musbotg_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * musbotg bulk support *------------------------------------------------------------------------*/ static void musbotg_device_bulk_open(struct usb_xfer *xfer) { return; } static void musbotg_device_bulk_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_bulk_enter(struct usb_xfer *xfer) { return; } static void musbotg_device_bulk_start(struct usb_xfer *xfer) { /* setup TDs */ musbotg_setup_standard_chain(xfer); musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_bulk_methods = { .open = musbotg_device_bulk_open, .close = musbotg_device_bulk_close, .enter = musbotg_device_bulk_enter, .start = musbotg_device_bulk_start, }; /*------------------------------------------------------------------------* * musbotg control support *------------------------------------------------------------------------*/ static void musbotg_device_ctrl_open(struct usb_xfer *xfer) { return; } static void musbotg_device_ctrl_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void musbotg_device_ctrl_start(struct usb_xfer *xfer) { /* setup TDs */ musbotg_setup_standard_chain(xfer); musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_ctrl_methods = { .open = musbotg_device_ctrl_open, .close = musbotg_device_ctrl_close, .enter = musbotg_device_ctrl_enter, .start = musbotg_device_ctrl_start, }; /*------------------------------------------------------------------------* * musbotg interrupt support *------------------------------------------------------------------------*/ static void musbotg_device_intr_open(struct usb_xfer *xfer) { return; } static void musbotg_device_intr_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_intr_enter(struct usb_xfer *xfer) { return; } static void musbotg_device_intr_start(struct usb_xfer *xfer) { /* setup TDs */ musbotg_setup_standard_chain(xfer); musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_intr_methods = { .open = musbotg_device_intr_open, .close = musbotg_device_intr_close, .enter = musbotg_device_intr_enter, .start = musbotg_device_intr_start, }; /*------------------------------------------------------------------------* * musbotg full speed isochronous support *------------------------------------------------------------------------*/ static void musbotg_device_isoc_open(struct usb_xfer *xfer) { return; } static void musbotg_device_isoc_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_isoc_enter(struct usb_xfer *xfer) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; uint32_t fs_frames; DPRINTFN(5, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index */ nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME); /* * check if the frame index is within the window where the frames * will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & MUSB2_MASK_FRAME; if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { fs_frames = (xfer->nframes + 7) / 8; } else { fs_frames = xfer->nframes; } if ((xfer->endpoint->is_synced == 0) || (temp < fs_frames)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME; xfer->endpoint->is_synced = 1; DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & MUSB2_MASK_FRAME; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + fs_frames; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += fs_frames; /* setup TDs */ musbotg_setup_standard_chain(xfer); } static void musbotg_device_isoc_start(struct usb_xfer *xfer) { /* start TD chain */ musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_isoc_methods = { .open = musbotg_device_isoc_open, .close = musbotg_device_isoc_close, .enter = musbotg_device_isoc_enter, .start = musbotg_device_isoc_start, }; /*------------------------------------------------------------------------* * musbotg root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor musbotg_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_HSHUBSTT, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct usb_device_qualifier musbotg_odevd = { .bLength = sizeof(struct usb_device_qualifier), .bDescriptorType = UDESC_DEVICE_QUALIFIER, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize0 = 0, .bNumConfigurations = 0, }; static const struct musbotg_config_desc musbotg_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(musbotg_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_hub_descriptor_min musbotg_hubd = { .bDescLength = sizeof(musbotg_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "M\0e\0n\0t\0o\0r\0 \0G\0r\0a\0p\0h\0i\0c\0s" #define STRING_PRODUCT \ "O\0T\0G\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product); static usb_error_t musbotg_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; uint8_t reg; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_handle_clear_port_feature; case UR_SET_FEATURE: goto tr_handle_set_port_feature; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: goto tr_handle_get_port_status; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) { goto tr_stalled; } len = sizeof(musbotg_devd); ptr = (const void *)&musbotg_devd; goto tr_valid; case UDESC_DEVICE_QUALIFIER: if (value & 0xff) { goto tr_stalled; } len = sizeof(musbotg_odevd); ptr = (const void *)&musbotg_odevd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) { goto tr_stalled; } len = sizeof(musbotg_confd); ptr = (const void *)&musbotg_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(musbotg_vendor); ptr = (const void *)&musbotg_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(musbotg_product); ptr = (const void *)&musbotg_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) { goto tr_stalled; } sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) { goto tr_stalled; } sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: if (sc->sc_mode == MUSB2_HOST_MODE) musbotg_wakeup_host(sc); else musbotg_wakeup_peer(sc); break; case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 0; break; case UHF_C_PORT_ENABLE: sc->sc_flags.change_enabled = 0; break; case UHF_C_PORT_OVER_CURRENT: sc->sc_flags.change_over_current = 0; break; case UHF_C_PORT_RESET: sc->sc_flags.change_reset = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; musbotg_pull_down(sc); musbotg_clocks_off(sc); break; case UHF_C_PORT_CONNECTION: sc->sc_flags.change_connect = 0; break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_set_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(8, "UR_SET_PORT_FEATURE\n"); switch (value) { case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 1; break; case UHF_PORT_SUSPEND: if (sc->sc_mode == MUSB2_HOST_MODE) musbotg_suspend_host(sc); break; case UHF_PORT_RESET: if (sc->sc_mode == MUSB2_HOST_MODE) { reg = MUSB2_READ_1(sc, MUSB2_REG_POWER); reg |= MUSB2_MASK_RESET; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, reg); /* Wait for 20 msec */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 5); reg = MUSB2_READ_1(sc, MUSB2_REG_POWER); reg &= ~MUSB2_MASK_RESET; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, reg); /* determine line speed */ reg = MUSB2_READ_1(sc, MUSB2_REG_POWER); if (reg & MUSB2_MASK_HSMODE) sc->sc_flags.status_high_speed = 1; else sc->sc_flags.status_high_speed = 0; sc->sc_flags.change_reset = 1; } else err = USB_ERR_IOERROR; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_get_port_status: DPRINTFN(8, "UR_GET_PORT_STATUS\n"); if (index != 1) { goto tr_stalled; } if (sc->sc_flags.status_vbus) { musbotg_clocks_on(sc); musbotg_pull_up(sc); } else { musbotg_pull_down(sc); musbotg_clocks_off(sc); } /* Select Device Side Mode */ if (sc->sc_mode == MUSB2_DEVICE_MODE) value = UPS_PORT_MODE_DEVICE; else value = 0; if (sc->sc_flags.status_high_speed) { value |= UPS_HIGH_SPEED; } if (sc->sc_flags.port_powered) { value |= UPS_PORT_POWER; } if (sc->sc_flags.port_enabled) { value |= UPS_PORT_ENABLED; } if (sc->sc_flags.port_over_current) value |= UPS_OVERCURRENT_INDICATOR; if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { value |= UPS_CURRENT_CONNECT_STATUS; } if (sc->sc_flags.status_suspend) { value |= UPS_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_connect) { value |= UPS_C_CONNECT_STATUS; if (sc->sc_mode == MUSB2_DEVICE_MODE) { if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { /* reset EP0 state */ sc->sc_ep0_busy = 0; sc->sc_ep0_cmd = 0; } } } if (sc->sc_flags.change_suspend) value |= UPS_C_SUSPEND; if (sc->sc_flags.change_reset) value |= UPS_C_PORT_RESET; if (sc->sc_flags.change_over_current) value |= UPS_C_OVERCURRENT_INDICATOR; USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) { goto tr_stalled; } ptr = (const void *)&musbotg_hubd; len = sizeof(musbotg_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: done: *plength = len; *pptr = ptr; return (err); } static void musbotg_xfer_setup(struct usb_setup_params *parm) { struct musbotg_softc *sc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; uint8_t ep_no; sc = MUSBOTG_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x400; parm->hc_max_frame_size = 0xc00; if ((parm->methods == &musbotg_device_isoc_methods) || (parm->methods == &musbotg_device_intr_methods)) parm->hc_max_packet_count = 3; else parm->hc_max_packet_count = 1; usbd_transfer_setup_sub(parm); /* * compute maximum number of TDs */ if (parm->methods == &musbotg_device_ctrl_methods) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; } else if (parm->methods == &musbotg_device_bulk_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else if (parm->methods == &musbotg_device_intr_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else if (parm->methods == &musbotg_device_isoc_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else { ntd = 0; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) { return; } /* * allocate transfer descriptors */ last_obj = NULL; ep_no = xfer->endpointno & UE_ADDR; /* * Check for a valid endpoint profile in USB device mode: */ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { const struct usb_hw_ep_profile *pf; musbotg_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct musbotg_td *td; if (parm->buf) { td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* init TD */ td->max_frame_size = xfer->max_frame_size; td->reg_max_packet = xfer->max_packet_size | ((xfer->max_packet_count - 1) << 11); td->ep_no = ep_no; td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void musbotg_xfer_unsetup(struct usb_xfer *xfer) { return; } static void musbotg_get_dma_delay(struct usb_device *udev, uint32_t *pus) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); if (sc->sc_mode == MUSB2_HOST_MODE) *pus = 2000; /* microseconds */ else *pus = 0; } static void musbotg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_rt_addr); if (udev->device_index != sc->sc_rt_addr) { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &musbotg_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &musbotg_device_intr_methods; break; case UE_ISOCHRONOUS: ep->methods = &musbotg_device_isoc_methods; break; case UE_BULK: ep->methods = &musbotg_device_bulk_methods; break; default: /* do nothing */ break; } } } static void musbotg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: musbotg_uninit(sc); break; case USB_HW_POWER_SHUTDOWN: musbotg_uninit(sc); break; case USB_HW_POWER_RESUME: musbotg_init(sc); break; default: break; } } static const struct usb_bus_methods musbotg_bus_methods = { .endpoint_init = &musbotg_ep_init, .get_dma_delay = &musbotg_get_dma_delay, .xfer_setup = &musbotg_xfer_setup, .xfer_unsetup = &musbotg_xfer_unsetup, .get_hw_ep_profile = &musbotg_get_hw_ep_profile, .xfer_stall = &musbotg_xfer_stall, .set_stall = &musbotg_set_stall, .clear_stall = &musbotg_clear_stall, .roothub_exec = &musbotg_roothub_exec, .xfer_poll = &musbotg_do_poll, .set_hw_power_sleep = &musbotg_set_hw_power_sleep, }; Index: head/sys/dev/usb/controller/ohci.c =================================================================== --- head/sys/dev/usb/controller/ohci.c (revision 357971) +++ head/sys/dev/usb/controller/ohci.c (revision 357972) @@ -1,2734 +1,2735 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB Open Host Controller driver. * * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html * USB spec: http://www.usb.org/developers/docs/usbspec.zip */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ohcidebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define OHCI_BUS2SC(bus) \ ((ohci_softc_t *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((ohci_softc_t *)0)->sc_bus)))) #ifdef USB_DEBUG static int ohcidebug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ohci"); SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RWTUN, &ohcidebug, 0, "ohci debug level"); static void ohci_dumpregs(ohci_softc_t *); static void ohci_dump_tds(ohci_td_t *); static uint8_t ohci_dump_td(ohci_td_t *); static void ohci_dump_ed(ohci_ed_t *); static uint8_t ohci_dump_itd(ohci_itd_t *); static void ohci_dump_itds(ohci_itd_t *); #endif #define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) #define OWRITE1(sc, r, x) \ do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) #define OWRITE2(sc, r, x) \ do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) #define OWRITE4(sc, r, x) \ do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) #define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define OHCI_INTR_ENDPT 1 static const struct usb_bus_methods ohci_bus_methods; static const struct usb_pipe_methods ohci_device_bulk_methods; static const struct usb_pipe_methods ohci_device_ctrl_methods; static const struct usb_pipe_methods ohci_device_intr_methods; static const struct usb_pipe_methods ohci_device_isoc_methods; static void ohci_do_poll(struct usb_bus *bus); static void ohci_device_done(struct usb_xfer *xfer, usb_error_t error); static void ohci_timeout(void *arg); static uint8_t ohci_check_transfer(struct usb_xfer *xfer); static void ohci_root_intr(ohci_softc_t *sc); struct ohci_std_temp { struct usb_page_cache *pc; ohci_td_t *td; ohci_td_t *td_next; uint32_t average; uint32_t td_flags; uint32_t len; uint16_t max_frame_size; uint8_t shortpkt; uint8_t setup_alt_next; uint8_t last_frame; }; static struct ohci_hcca * ohci_get_hcca(ohci_softc_t *sc) { usb_pc_cpu_invalidate(&sc->sc_hw.hcca_pc); return (sc->sc_hcca_p); } void ohci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) { struct ohci_softc *sc = OHCI_BUS2SC(bus); uint32_t i; cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg, sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN); cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg, sizeof(ohci_ed_t), OHCI_ED_ALIGN); cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, sizeof(ohci_ed_t), OHCI_ED_ALIGN); cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg, sizeof(ohci_ed_t), OHCI_ED_ALIGN); for (i = 0; i != OHCI_NO_EDS; i++) { cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i, sizeof(ohci_ed_t), OHCI_ED_ALIGN); } } static usb_error_t ohci_controller_init(ohci_softc_t *sc, int do_suspend) { struct usb_page_search buf_res; uint32_t i; uint32_t ctl; uint32_t ival; uint32_t hcr; uint32_t fm; uint32_t per; uint32_t desca; /* Determine in what context we are running. */ ctl = OREAD4(sc, OHCI_CONTROL); if (ctl & OHCI_IR) { /* SMM active, request change */ DPRINTF("SMM active, request owner change\n"); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR); for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) { usb_pause_mtx(NULL, hz / 1000); ctl = OREAD4(sc, OHCI_CONTROL); } if (ctl & OHCI_IR) { device_printf(sc->sc_bus.bdev, "SMM does not respond, resetting\n"); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); goto reset; } } else { DPRINTF("cold started\n"); reset: /* controller was cold started */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); } /* * This reset should not be necessary according to the OHCI spec, but * without it some controllers do not start. */ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); /* we now own the host controller and the bus has been reset */ ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL)); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */ /* nominal time for a reset is 10 us */ for (i = 0; i < 10; i++) { DELAY(10); hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR; if (!hcr) { break; } } if (hcr) { device_printf(sc->sc_bus.bdev, "reset timeout\n"); return (USB_ERR_IOERROR); } #ifdef USB_DEBUG if (ohcidebug > 15) { ohci_dumpregs(sc); } #endif if (do_suspend) { OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_SUSPEND); return (USB_ERR_NORMAL_COMPLETION); } /* The controller is now in SUSPEND state, we have 2ms to finish. */ /* set up HC registers */ usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); OWRITE4(sc, OHCI_HCCA, buf_res.physaddr); usbd_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res); OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr); usbd_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res); OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.physaddr); /* disable all interrupts and then switch on all desired interrupts */ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE); /* switch on desired functional features */ ctl = OREAD4(sc, OHCI_CONTROL); ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR); ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE | OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL; /* And finally start it! */ OWRITE4(sc, OHCI_CONTROL, ctl); /* * The controller is now OPERATIONAL. Set a some final * registers that should be set earlier, but that the * controller ignores when in the SUSPEND state. */ fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT; fm |= OHCI_FSMPS(ival) | ival; OWRITE4(sc, OHCI_FM_INTERVAL, fm); per = OHCI_PERIODIC(ival); /* 90% periodic */ OWRITE4(sc, OHCI_PERIODIC_START, per); /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */ desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP); OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(OHCI_ENABLE_POWER_DELAY)); OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca); /* * The AMD756 requires a delay before re-reading the register, * otherwise it will occasionally report 0 ports. */ sc->sc_noport = 0; for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) { usb_pause_mtx(NULL, USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY)); sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); } #ifdef USB_DEBUG if (ohcidebug > 5) { ohci_dumpregs(sc); } #endif return (USB_ERR_NORMAL_COMPLETION); } static struct ohci_ed * ohci_init_ed(struct usb_page_cache *pc) { struct usb_page_search buf_res; struct ohci_ed *ed; usbd_get_page(pc, 0, &buf_res); ed = buf_res.buffer; ed->ed_self = htole32(buf_res.physaddr); ed->ed_flags = htole32(OHCI_ED_SKIP); ed->page_cache = pc; return (ed); } usb_error_t ohci_init(ohci_softc_t *sc) { struct usb_page_search buf_res; uint16_t i; uint16_t bit; uint16_t x; uint16_t y; DPRINTF("start\n"); sc->sc_eintrs = OHCI_NORMAL_INTRS; /* * Setup all ED's */ sc->sc_ctrl_p_last = ohci_init_ed(&sc->sc_hw.ctrl_start_pc); sc->sc_bulk_p_last = ohci_init_ed(&sc->sc_hw.bulk_start_pc); sc->sc_isoc_p_last = ohci_init_ed(&sc->sc_hw.isoc_start_pc); for (i = 0; i != OHCI_NO_EDS; i++) { sc->sc_intr_p_last[i] = ohci_init_ed(sc->sc_hw.intr_start_pc + i); } /* * the QHs are arranged to give poll intervals that are * powers of 2 times 1ms */ bit = OHCI_NO_EDS / 2; while (bit) { x = bit; while (x & bit) { ohci_ed_t *ed_x; ohci_ed_t *ed_y; y = (x ^ bit) | (bit / 2); /* * the next QH has half the poll interval */ ed_x = sc->sc_intr_p_last[x]; ed_y = sc->sc_intr_p_last[y]; ed_x->next = NULL; ed_x->ed_next = ed_y->ed_self; x++; } bit >>= 1; } if (1) { ohci_ed_t *ed_int; ohci_ed_t *ed_isc; ed_int = sc->sc_intr_p_last[0]; ed_isc = sc->sc_isoc_p_last; /* the last (1ms) QH */ ed_int->next = ed_isc; ed_int->ed_next = ed_isc->ed_self; } usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); sc->sc_hcca_p = buf_res.buffer; /* * Fill HCCA interrupt table. The bit reversal is to get * the tree set up properly to spread the interrupts. */ for (i = 0; i != OHCI_NO_INTRS; i++) { sc->sc_hcca_p->hcca_interrupt_table[i] = sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self; } /* flush all cache into memory */ usb_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc); /* set up the bus struct */ sc->sc_bus.methods = &ohci_bus_methods; usb_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.bus_mtx, 0); #ifdef USB_DEBUG if (ohcidebug > 15) { for (i = 0; i != OHCI_NO_EDS; i++) { printf("ed#%d ", i); ohci_dump_ed(sc->sc_intr_p_last[i]); } printf("iso "); ohci_dump_ed(sc->sc_isoc_p_last); } #endif sc->sc_bus.usbrev = USB_REV_1_0; if (ohci_controller_init(sc, 0) != 0) return (USB_ERR_INVAL); /* catch any lost interrupts */ ohci_do_poll(&sc->sc_bus); return (USB_ERR_NORMAL_COMPLETION); } /* * shut down the controller when the system is going down */ void ohci_detach(struct ohci_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); usb_callout_stop(&sc->sc_tmo_rhsc); OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); USB_BUS_UNLOCK(&sc->sc_bus); /* XXX let stray task complete */ usb_pause_mtx(NULL, hz / 20); usb_callout_drain(&sc->sc_tmo_rhsc); } static void ohci_suspend(ohci_softc_t *sc) { DPRINTF("\n"); #ifdef USB_DEBUG if (ohcidebug > 2) ohci_dumpregs(sc); #endif /* reset HC and leave it suspended */ ohci_controller_init(sc, 1); } static void ohci_resume(ohci_softc_t *sc) { DPRINTF("\n"); #ifdef USB_DEBUG if (ohcidebug > 2) ohci_dumpregs(sc); #endif /* some broken BIOSes never initialize the Controller chip */ ohci_controller_init(sc, 0); /* catch any lost interrupts */ ohci_do_poll(&sc->sc_bus); } #ifdef USB_DEBUG static void ohci_dumpregs(ohci_softc_t *sc) { struct ohci_hcca *hcca; DPRINTF("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n", OREAD4(sc, OHCI_REVISION), OREAD4(sc, OHCI_CONTROL), OREAD4(sc, OHCI_COMMAND_STATUS)); DPRINTF(" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n", OREAD4(sc, OHCI_INTERRUPT_STATUS), OREAD4(sc, OHCI_INTERRUPT_ENABLE), OREAD4(sc, OHCI_INTERRUPT_DISABLE)); DPRINTF(" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n", OREAD4(sc, OHCI_HCCA), OREAD4(sc, OHCI_PERIOD_CURRENT_ED), OREAD4(sc, OHCI_CONTROL_HEAD_ED)); DPRINTF(" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n", OREAD4(sc, OHCI_CONTROL_CURRENT_ED), OREAD4(sc, OHCI_BULK_HEAD_ED), OREAD4(sc, OHCI_BULK_CURRENT_ED)); DPRINTF(" done=0x%08x fmival=0x%08x fmrem=0x%08x\n", OREAD4(sc, OHCI_DONE_HEAD), OREAD4(sc, OHCI_FM_INTERVAL), OREAD4(sc, OHCI_FM_REMAINING)); DPRINTF(" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n", OREAD4(sc, OHCI_FM_NUMBER), OREAD4(sc, OHCI_PERIODIC_START), OREAD4(sc, OHCI_LS_THRESHOLD)); DPRINTF(" desca=0x%08x descb=0x%08x stat=0x%08x\n", OREAD4(sc, OHCI_RH_DESCRIPTOR_A), OREAD4(sc, OHCI_RH_DESCRIPTOR_B), OREAD4(sc, OHCI_RH_STATUS)); DPRINTF(" port1=0x%08x port2=0x%08x\n", OREAD4(sc, OHCI_RH_PORT_STATUS(1)), OREAD4(sc, OHCI_RH_PORT_STATUS(2))); hcca = ohci_get_hcca(sc); DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n", le32toh(hcca->hcca_frame_number), le32toh(hcca->hcca_done_head)); } static void ohci_dump_tds(ohci_td_t *std) { for (; std; std = std->obj_next) { if (ohci_dump_td(std)) { break; } } } static uint8_t ohci_dump_td(ohci_td_t *std) { uint32_t td_flags; uint8_t temp; usb_pc_cpu_invalidate(std->page_cache); td_flags = le32toh(std->td_flags); temp = (std->td_next == 0); printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d " "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n", std, le32toh(std->td_self), (td_flags & OHCI_TD_R) ? "-R" : "", (td_flags & OHCI_TD_OUT) ? "-OUT" : "", (td_flags & OHCI_TD_IN) ? "-IN" : "", ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "", ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "", OHCI_TD_GET_DI(td_flags), OHCI_TD_GET_EC(td_flags), OHCI_TD_GET_CC(td_flags), le32toh(std->td_cbp), le32toh(std->td_next), le32toh(std->td_be)); return (temp); } static uint8_t ohci_dump_itd(ohci_itd_t *sitd) { uint32_t itd_flags; uint16_t i; uint8_t temp; usb_pc_cpu_invalidate(sitd->page_cache); itd_flags = le32toh(sitd->itd_flags); temp = (sitd->itd_next == 0); printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n" "bp0=0x%08x next=0x%08x be=0x%08x\n", sitd, le32toh(sitd->itd_self), OHCI_ITD_GET_SF(itd_flags), OHCI_ITD_GET_DI(itd_flags), OHCI_ITD_GET_FC(itd_flags), OHCI_ITD_GET_CC(itd_flags), le32toh(sitd->itd_bp0), le32toh(sitd->itd_next), le32toh(sitd->itd_be)); for (i = 0; i < OHCI_ITD_NOFFSET; i++) { printf("offs[%d]=0x%04x ", i, (uint32_t)le16toh(sitd->itd_offset[i])); } printf("\n"); return (temp); } static void ohci_dump_itds(ohci_itd_t *sitd) { for (; sitd; sitd = sitd->obj_next) { if (ohci_dump_itd(sitd)) { break; } } } static void ohci_dump_ed(ohci_ed_t *sed) { uint32_t ed_flags; uint32_t ed_headp; usb_pc_cpu_invalidate(sed->page_cache); ed_flags = le32toh(sed->ed_flags); ed_headp = le32toh(sed->ed_headp); printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n" "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n", sed, le32toh(sed->ed_self), OHCI_ED_GET_FA(ed_flags), OHCI_ED_GET_EN(ed_flags), OHCI_ED_GET_MAXP(ed_flags), (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "", (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "", (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "", (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "", (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "", le32toh(sed->ed_tailp), (ed_headp & OHCI_HALTED) ? "-HALTED" : "", (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "", le32toh(sed->ed_headp), le32toh(sed->ed_next)); } #endif static void ohci_transfer_intr_enqueue(struct usb_xfer *xfer) { /* check for early completion */ if (ohci_check_transfer(xfer)) { return; } /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout); } } #define OHCI_APPEND_QH(sed,last) (last) = _ohci_append_qh(sed,last) static ohci_ed_t * _ohci_append_qh(ohci_ed_t *sed, ohci_ed_t *last) { DPRINTFN(11, "%p to %p\n", sed, last); if (sed->prev != NULL) { /* should not happen */ DPRINTFN(0, "ED already linked!\n"); return (last); } /* (sc->sc_bus.bus_mtx) must be locked */ sed->next = last->next; sed->ed_next = last->ed_next; sed->ed_tailp = 0; sed->prev = last; usb_pc_cpu_flush(sed->page_cache); /* * the last->next->prev is never followed: sed->next->prev = sed; */ last->next = sed; last->ed_next = sed->ed_self; usb_pc_cpu_flush(last->page_cache); return (sed); } #define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last) static ohci_ed_t * _ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last) { DPRINTFN(11, "%p from %p\n", sed, last); /* (sc->sc_bus.bus_mtx) must be locked */ /* only remove if not removed from a queue */ if (sed->prev) { sed->prev->next = sed->next; sed->prev->ed_next = sed->ed_next; usb_pc_cpu_flush(sed->prev->page_cache); if (sed->next) { sed->next->prev = sed->prev; usb_pc_cpu_flush(sed->next->page_cache); } last = ((last == sed) ? sed->prev : last); sed->prev = 0; usb_pc_cpu_flush(sed->page_cache); } return (last); } static void ohci_isoc_done(struct usb_xfer *xfer) { uint8_t nframes; uint32_t *plen = xfer->frlengths; volatile uint16_t *olen; uint16_t len = 0; ohci_itd_t *td = xfer->td_transfer_first; while (1) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF("isoc TD\n"); ohci_dump_itd(td); } #endif usb_pc_cpu_invalidate(td->page_cache); nframes = td->frames; olen = &td->itd_offset[0]; if (nframes > 8) { nframes = 8; } while (nframes--) { len = le16toh(*olen); if ((len >> 12) == OHCI_CC_NOT_ACCESSED) { len = 0; } else { len &= ((1 << 12) - 1); } if (len > *plen) { len = 0;/* invalid length */ } *plen = len; plen++; olen++; } if (((void *)td) == xfer->td_transfer_last) { break; } td = td->obj_next; } xfer->aframes = xfer->nframes; ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); } #ifdef USB_DEBUG static const char *const ohci_cc_strs[] = { "NO_ERROR", "CRC", "BIT_STUFFING", "DATA_TOGGLE_MISMATCH", "STALL", "DEVICE_NOT_RESPONDING", "PID_CHECK_FAILURE", "UNEXPECTED_PID", "DATA_OVERRUN", "DATA_UNDERRUN", "BUFFER_OVERRUN", "BUFFER_UNDERRUN", "reserved", "reserved", "NOT_ACCESSED", "NOT_ACCESSED" }; #endif static usb_error_t ohci_non_isoc_done_sub(struct usb_xfer *xfer) { ohci_td_t *td; ohci_td_t *td_alt_next; uint32_t temp; uint32_t phy_start; uint32_t phy_end; uint32_t td_flags; uint16_t cc; td = xfer->td_transfer_cache; td_alt_next = td->alt_next; td_flags = 0; if (xfer->aframes != xfer->nframes) { usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); } while (1) { usb_pc_cpu_invalidate(td->page_cache); phy_start = le32toh(td->td_cbp); td_flags = le32toh(td->td_flags); cc = OHCI_TD_GET_CC(td_flags); if (phy_start) { /* * short transfer - compute the number of remaining * bytes in the hardware buffer: */ phy_end = le32toh(td->td_be); temp = (OHCI_PAGE(phy_start ^ phy_end) ? (OHCI_PAGE_SIZE + 1) : 0x0001); temp += OHCI_PAGE_OFFSET(phy_end); temp -= OHCI_PAGE_OFFSET(phy_start); if (temp > td->len) { /* guard against corruption */ cc = OHCI_CC_STALL; } else if (xfer->aframes != xfer->nframes) { /* * Sum up total transfer length * in "frlengths[]": */ xfer->frlengths[xfer->aframes] += td->len - temp; } } else { if (xfer->aframes != xfer->nframes) { /* transfer was complete */ xfer->frlengths[xfer->aframes] += td->len; } } /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { td = NULL; break; } /* Check transfer status */ if (cc) { /* the transfer is finished */ td = NULL; break; } /* Check for short transfer */ if (phy_start) { if (xfer->flags_int.short_frames_ok) { /* follow alt next */ td = td->alt_next; } else { /* the transfer is finished */ td = NULL; } break; } td = td->obj_next; if (td->alt_next != td_alt_next) { /* this USB frame is complete */ break; } } /* update transfer cache */ xfer->td_transfer_cache = td; DPRINTFN(16, "error cc=%d (%s)\n", cc, ohci_cc_strs[cc]); return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION : (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR); } static void ohci_non_isoc_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); #ifdef USB_DEBUG if (ohcidebug > 10) { ohci_dump_tds(xfer->td_transfer_first); } #endif /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = ohci_non_isoc_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = ohci_non_isoc_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = ohci_non_isoc_done_sub(xfer); } done: ohci_device_done(xfer, err); } /*------------------------------------------------------------------------* * ohci_check_transfer_sub *------------------------------------------------------------------------*/ static void ohci_check_transfer_sub(struct usb_xfer *xfer) { ohci_td_t *td; ohci_ed_t *ed; uint32_t phy_start; uint32_t td_flags; uint32_t td_next; uint16_t cc; td = xfer->td_transfer_cache; while (1) { usb_pc_cpu_invalidate(td->page_cache); phy_start = le32toh(td->td_cbp); td_flags = le32toh(td->td_flags); td_next = le32toh(td->td_next); /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { /* the transfer is finished */ td = NULL; break; } /* Check transfer status */ cc = OHCI_TD_GET_CC(td_flags); if (cc) { /* the transfer is finished */ td = NULL; break; } /* * Check if we reached the last packet * or if there is a short packet: */ if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) { /* follow alt next */ td = td->alt_next; break; } td = td->obj_next; } /* update transfer cache */ xfer->td_transfer_cache = td; if (td) { ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; ed->ed_headp = td->td_self; usb_pc_cpu_flush(ed->page_cache); DPRINTFN(13, "xfer=%p following alt next\n", xfer); /* * Make sure that the OHCI re-scans the schedule by * writing the BLF and CLF bits: */ if (xfer->xroot->udev->flags.self_suspended) { /* nothing to do */ } else if (xfer->endpoint->methods == &ohci_device_bulk_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); } else if (xfer->endpoint->methods == &ohci_device_ctrl_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); } } } /*------------------------------------------------------------------------* * ohci_check_transfer * * Return values: * 0: USB transfer is not finished * Else: USB transfer is finished *------------------------------------------------------------------------*/ static uint8_t ohci_check_transfer(struct usb_xfer *xfer) { ohci_ed_t *ed; uint32_t ed_headp; uint32_t ed_tailp; DPRINTFN(13, "xfer=%p checking transfer\n", xfer); ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; usb_pc_cpu_invalidate(ed->page_cache); ed_headp = le32toh(ed->ed_headp); ed_tailp = le32toh(ed->ed_tailp); if ((ed_headp & OHCI_HALTED) || (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) { if (xfer->endpoint->methods == &ohci_device_isoc_methods) { /* isochronous transfer */ ohci_isoc_done(xfer); } else { if (xfer->flags_int.short_frames_ok) { ohci_check_transfer_sub(xfer); if (xfer->td_transfer_cache) { /* not finished yet */ return (0); } } /* store data-toggle */ if (ed_headp & OHCI_TOGGLECARRY) { xfer->endpoint->toggle_next = 1; } else { xfer->endpoint->toggle_next = 0; } /* non-isochronous transfer */ ohci_non_isoc_done(xfer); } return (1); } DPRINTFN(13, "xfer=%p is still active\n", xfer); return (0); } static void ohci_rhsc_enable(ohci_softc_t *sc) { DPRINTFN(5, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); sc->sc_eintrs |= OHCI_RHSC; OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC); /* acknowledge any RHSC interrupt */ OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC); ohci_root_intr(sc); } static void ohci_interrupt_poll(ohci_softc_t *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { /* * check if transfer is transferred */ if (ohci_check_transfer(xfer)) { /* queue has been modified */ goto repeat; } } } /*------------------------------------------------------------------------* * ohci_interrupt - OHCI interrupt handler * * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, * hence the interrupt handler will be setup before "sc->sc_bus.bdev" * is present ! *------------------------------------------------------------------------*/ void ohci_interrupt(ohci_softc_t *sc) { struct ohci_hcca *hcca; uint32_t status; uint32_t done; USB_BUS_LOCK(&sc->sc_bus); hcca = ohci_get_hcca(sc); DPRINTFN(16, "real interrupt\n"); #ifdef USB_DEBUG if (ohcidebug > 15) { ohci_dumpregs(sc); } #endif done = le32toh(hcca->hcca_done_head); /* * The LSb of done is used to inform the HC Driver that an interrupt * condition exists for both the Done list and for another event * recorded in HcInterruptStatus. On an interrupt from the HC, the * HC Driver checks the HccaDoneHead Value. If this value is 0, then * the interrupt was caused by other than the HccaDoneHead update * and the HcInterruptStatus register needs to be accessed to * determine that exact interrupt cause. If HccaDoneHead is nonzero, * then a Done list update interrupt is indicated and if the LSb of * done is nonzero, then an additional interrupt event is indicated * and HcInterruptStatus should be checked to determine its cause. */ if (done != 0) { status = 0; if (done & ~OHCI_DONE_INTRS) { status |= OHCI_WDH; } if (done & OHCI_DONE_INTRS) { status |= OREAD4(sc, OHCI_INTERRUPT_STATUS); } hcca->hcca_done_head = 0; usb_pc_cpu_flush(&sc->sc_hw.hcca_pc); } else { status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH; } status &= ~OHCI_MIE; if (status == 0) { /* * nothing to be done (PCI shared * interrupt) */ goto done; } OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */ status &= sc->sc_eintrs; if (status == 0) { goto done; } if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) { #if 0 if (status & OHCI_SO) { /* XXX do what */ } #endif if (status & OHCI_RD) { printf("%s: resume detect\n", __FUNCTION__); /* XXX process resume detect */ } if (status & OHCI_UE) { printf("%s: unrecoverable error, " "controller halted\n", __FUNCTION__); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); /* XXX what else */ } if (status & OHCI_RHSC) { /* * Disable RHSC interrupt for now, because it will be * on until the port has been reset. */ sc->sc_eintrs &= ~OHCI_RHSC; OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC); ohci_root_intr(sc); /* do not allow RHSC interrupts > 1 per second */ usb_callout_reset(&sc->sc_tmo_rhsc, hz, (void *)&ohci_rhsc_enable, sc); } } status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO); if (status != 0) { /* Block unprocessed interrupts. XXX */ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status); sc->sc_eintrs &= ~status; printf("%s: blocking intrs 0x%x\n", __FUNCTION__, status); } /* poll all the USB transfers */ ohci_interrupt_poll(sc); done: USB_BUS_UNLOCK(&sc->sc_bus); } /* * called when a request does not complete */ static void ohci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ ohci_device_done(xfer, USB_ERR_TIMEOUT); } static void ohci_do_poll(struct usb_bus *bus) { struct ohci_softc *sc = OHCI_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); ohci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void ohci_setup_standard_chain_sub(struct ohci_std_temp *temp) { struct usb_page_search buf_res; ohci_td_t *td; ohci_td_t *td_next; ohci_td_t *td_alt_next; uint32_t buf_offset; uint32_t average; uint32_t len_old; uint8_t shortpkt_old; uint8_t precompute; td_alt_next = NULL; buf_offset = 0; shortpkt_old = temp->shortpkt; len_old = temp->len; precompute = 1; /* software is used to detect short incoming transfers */ if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) { temp->td_flags |= htole32(OHCI_TD_R); } else { temp->td_flags &= ~htole32(OHCI_TD_R); } restart: td = temp->td; td_next = temp->td_next; while (1) { if (temp->len == 0) { if (temp->shortpkt) { break; } /* send a Zero Length Packet, ZLP, last */ temp->shortpkt = 1; average = 0; } else { average = temp->average; if (temp->len < average) { if (temp->len % temp->max_frame_size) { temp->shortpkt = 1; } average = temp->len; } } if (td_next == NULL) { panic("%s: out of OHCI transfer descriptors!", __FUNCTION__); } /* get next TD */ td = td_next; td_next = td->obj_next; /* check if we are pre-computing */ if (precompute) { /* update remaining length */ temp->len -= average; continue; } /* fill out current TD */ td->td_flags = temp->td_flags; /* the next TD uses TOGGLE_CARRY */ temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK); if (average == 0) { /* * The buffer start and end phys addresses should be * 0x0 for a zero length packet. */ td->td_cbp = 0; td->td_be = 0; td->len = 0; } else { usbd_get_page(temp->pc, buf_offset, &buf_res); td->td_cbp = htole32(buf_res.physaddr); buf_offset += (average - 1); usbd_get_page(temp->pc, buf_offset, &buf_res); td->td_be = htole32(buf_res.physaddr); buf_offset++; td->len = average; /* update remaining length */ temp->len -= average; } if ((td_next == td_alt_next) && temp->setup_alt_next) { /* we need to receive these frames one by one ! */ td->td_flags &= htole32(~OHCI_TD_INTR_MASK); td->td_flags |= htole32(OHCI_TD_SET_DI(1)); td->td_next = htole32(OHCI_TD_NEXT_END); } else { if (td_next) { /* link the current TD with the next one */ td->td_next = td_next->td_self; } } td->alt_next = td_alt_next; usb_pc_cpu_flush(td->page_cache); } if (precompute) { precompute = 0; /* setup alt next pointer, if any */ if (temp->last_frame) { /* no alternate next */ td_alt_next = NULL; } else { /* we use this field internally */ td_alt_next = td_next; } /* restore */ temp->shortpkt = shortpkt_old; temp->len = len_old; goto restart; } temp->td = td; temp->td_next = td_next; } static void ohci_setup_standard_chain(struct usb_xfer *xfer, ohci_ed_t **ed_last) { struct ohci_std_temp temp; const struct usb_pipe_methods *methods; ohci_ed_t *ed; ohci_td_t *td; uint32_t ed_flags; uint32_t x; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.average = xfer->max_hc_frame_size; temp.max_frame_size = xfer->max_frame_size; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; temp.td = NULL; temp.td_next = td; temp.last_frame = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok; methods = xfer->endpoint->methods; /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC | OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR); temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.shortpkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } ohci_setup_standard_chain_sub(&temp); /* * XXX assume that the setup message is * contained within one USB packet: */ xfer->endpoint->toggle_next = 1; } x = 1; } else { x = 0; } temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR); /* set data toggle */ if (xfer->endpoint->toggle_next) { temp.td_flags |= htole32(OHCI_TD_TOGGLE_1); } else { temp.td_flags |= htole32(OHCI_TD_TOGGLE_0); } /* set endpoint direction */ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { temp.td_flags |= htole32(OHCI_TD_IN); } else { temp.td_flags |= htole32(OHCI_TD_OUT); } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; temp.pc = xfer->frbuffers + x; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { /* no STATUS stage yet, DATA is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } else { temp.last_frame = 1; temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.shortpkt = 0; } else { /* regular data transfer */ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; } ohci_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current endpoint * direction. */ /* set endpoint direction and data toggle */ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { temp.td_flags = htole32(OHCI_TD_OUT | OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); } else { temp.td_flags = htole32(OHCI_TD_IN | OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); } temp.len = 0; temp.pc = NULL; temp.shortpkt = 0; temp.last_frame = 1; temp.setup_alt_next = 0; ohci_setup_standard_chain_sub(&temp); } td = temp.td; /* Ensure that last TD is terminating: */ td->td_next = htole32(OHCI_TD_NEXT_END); td->td_flags &= ~htole32(OHCI_TD_INTR_MASK); td->td_flags |= htole32(OHCI_TD_SET_DI(1)); usb_pc_cpu_flush(td->page_cache); /* must have at least one frame! */ xfer->td_transfer_last = td; #ifdef USB_DEBUG if (ohcidebug > 8) { DPRINTF("nexttog=%d; data before transfer:\n", xfer->endpoint->toggle_next); ohci_dump_tds(xfer->td_transfer_first); } #endif ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; ed_flags = (OHCI_ED_SET_FA(xfer->address) | OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) | OHCI_ED_SET_MAXP(xfer->max_frame_size)); ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD); if (xfer->xroot->udev->speed == USB_SPEED_LOW) { ed_flags |= OHCI_ED_SPEED; } ed->ed_flags = htole32(ed_flags); td = xfer->td_transfer_first; ed->ed_headp = td->td_self; if (xfer->xroot->udev->flags.self_suspended == 0) { /* the append function will flush the endpoint descriptor */ OHCI_APPEND_QH(ed, *ed_last); if (methods == &ohci_device_bulk_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); } if (methods == &ohci_device_ctrl_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); } } else { usb_pc_cpu_flush(ed->page_cache); } } static void ohci_root_intr(ohci_softc_t *sc) { uint32_t hstatus; uint16_t i; uint16_t m; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* clear any old interrupt data */ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); hstatus = OREAD4(sc, OHCI_RH_STATUS); DPRINTF("sc=%p hstatus=0x%08x\n", sc, hstatus); /* set bits */ m = (sc->sc_noport + 1); if (m > (8 * sizeof(sc->sc_hub_idata))) { m = (8 * sizeof(sc->sc_hub_idata)); } for (i = 1; i < m; i++) { /* pick out CHANGE bits from the status register */ if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) { sc->sc_hub_idata[i / 8] |= 1 << (i % 8); DPRINTF("port %d changed\n", i); } } uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } /* NOTE: "done" can be run two times in a row, * from close and from interrupt */ static void ohci_device_done(struct usb_xfer *xfer, usb_error_t error) { const struct usb_pipe_methods *methods = xfer->endpoint->methods; ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); ohci_ed_t *ed; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (ed) { usb_pc_cpu_invalidate(ed->page_cache); } if (methods == &ohci_device_bulk_methods) { OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); } if (methods == &ohci_device_ctrl_methods) { OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); } if (methods == &ohci_device_intr_methods) { OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); } if (methods == &ohci_device_isoc_methods) { OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last); } xfer->td_transfer_first = NULL; xfer->td_transfer_last = NULL; /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } /*------------------------------------------------------------------------* * ohci bulk support *------------------------------------------------------------------------*/ static void ohci_device_bulk_open(struct usb_xfer *xfer) { return; } static void ohci_device_bulk_close(struct usb_xfer *xfer) { ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_bulk_enter(struct usb_xfer *xfer) { return; } static void ohci_device_bulk_start(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last); /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_bulk_methods = { .open = ohci_device_bulk_open, .close = ohci_device_bulk_close, .enter = ohci_device_bulk_enter, .start = ohci_device_bulk_start, }; /*------------------------------------------------------------------------* * ohci control support *------------------------------------------------------------------------*/ static void ohci_device_ctrl_open(struct usb_xfer *xfer) { return; } static void ohci_device_ctrl_close(struct usb_xfer *xfer) { ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void ohci_device_ctrl_start(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last); /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_ctrl_methods = { .open = ohci_device_ctrl_open, .close = ohci_device_ctrl_close, .enter = ohci_device_ctrl_enter, .start = ohci_device_ctrl_start, }; /*------------------------------------------------------------------------* * ohci interrupt support *------------------------------------------------------------------------*/ static void ohci_device_intr_open(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); uint16_t best; uint16_t bit; uint16_t x; best = 0; bit = OHCI_NO_EDS / 2; while (bit) { if (xfer->interval >= bit) { x = bit; best = bit; while (x & bit) { if (sc->sc_intr_stat[x] < sc->sc_intr_stat[best]) { best = x; } x++; } break; } bit >>= 1; } sc->sc_intr_stat[best]++; xfer->qh_pos = best; DPRINTFN(3, "best=%d interval=%d\n", best, xfer->interval); } static void ohci_device_intr_close(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); sc->sc_intr_stat[xfer->qh_pos]--; ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_intr_enter(struct usb_xfer *xfer) { return; } static void ohci_device_intr_start(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_intr_methods = { .open = ohci_device_intr_open, .close = ohci_device_intr_close, .enter = ohci_device_intr_enter, .start = ohci_device_intr_start, }; /*------------------------------------------------------------------------* * ohci isochronous support *------------------------------------------------------------------------*/ static void ohci_device_isoc_open(struct usb_xfer *xfer) { return; } static void ohci_device_isoc_close(struct usb_xfer *xfer) { /**/ ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_isoc_enter(struct usb_xfer *xfer) { struct usb_page_search buf_res; ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); struct ohci_hcca *hcca; uint32_t buf_offset; uint32_t nframes; uint32_t ed_flags; uint32_t *plen; uint16_t itd_offset[OHCI_ITD_NOFFSET]; uint16_t length; uint8_t ncur; ohci_itd_t *td; ohci_itd_t *td_last = NULL; ohci_ed_t *ed; hcca = ohci_get_hcca(sc); nframes = le32toh(hcca->hcca_frame_number); DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n", xfer, xfer->endpoint->isoc_next, xfer->nframes, nframes); if ((xfer->endpoint->is_synced == 0) || (((nframes - xfer->endpoint->isoc_next) & 0xFFFF) < xfer->nframes) || (((xfer->endpoint->isoc_next - nframes) & 0xFFFF) >= 128)) { /* * If there is data underflow or the pipe queue is empty we * schedule the transfer a few frames ahead of the current * frame position. Else two isochronous transfers might * overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & 0xFFFF; xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ buf_offset = ((xfer->endpoint->isoc_next - nframes) & 0xFFFF); /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = (usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + xfer->nframes); /* get the real number of frames */ nframes = xfer->nframes; buf_offset = 0; plen = xfer->frlengths; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; ncur = 0; length = 0; while (nframes--) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } itd_offset[ncur] = length; buf_offset += *plen; length += *plen; plen++; ncur++; if ( /* check if the ITD is full */ (ncur == OHCI_ITD_NOFFSET) || /* check if we have put more than 4K into the ITD */ (length & 0xF000) || /* check if it is the last frame */ (nframes == 0)) { /* fill current ITD */ td->itd_flags = htole32( OHCI_ITD_NOCC | OHCI_ITD_SET_SF(xfer->endpoint->isoc_next) | OHCI_ITD_NOINTR | OHCI_ITD_SET_FC(ncur)); td->frames = ncur; xfer->endpoint->isoc_next += ncur; if (length == 0) { /* all zero */ td->itd_bp0 = 0; td->itd_be = ~0; while (ncur--) { td->itd_offset[ncur] = htole16(OHCI_ITD_MK_OFFS(0)); } } else { usbd_get_page(xfer->frbuffers, buf_offset - length, &buf_res); length = OHCI_PAGE_MASK(buf_res.physaddr); buf_res.physaddr = OHCI_PAGE(buf_res.physaddr); td->itd_bp0 = htole32(buf_res.physaddr); usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); td->itd_be = htole32(buf_res.physaddr); while (ncur--) { itd_offset[ncur] += length; itd_offset[ncur] = OHCI_ITD_MK_OFFS(itd_offset[ncur]); td->itd_offset[ncur] = htole16(itd_offset[ncur]); } } ncur = 0; length = 0; td_last = td; td = td->obj_next; if (td) { /* link the last TD with the next one */ td_last->itd_next = td->itd_self; } usb_pc_cpu_flush(td_last->page_cache); } } /* update the last TD */ td_last->itd_flags &= ~htole32(OHCI_ITD_NOINTR); td_last->itd_flags |= htole32(OHCI_ITD_SET_DI(0)); td_last->itd_next = 0; usb_pc_cpu_flush(td_last->page_cache); xfer->td_transfer_last = td_last; #ifdef USB_DEBUG if (ohcidebug > 8) { DPRINTF("data before transfer:\n"); ohci_dump_itds(xfer->td_transfer_first); } #endif ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ed_flags = (OHCI_ED_DIR_IN | OHCI_ED_FORMAT_ISO); else ed_flags = (OHCI_ED_DIR_OUT | OHCI_ED_FORMAT_ISO); ed_flags |= (OHCI_ED_SET_FA(xfer->address) | OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) | OHCI_ED_SET_MAXP(xfer->max_frame_size)); if (xfer->xroot->udev->speed == USB_SPEED_LOW) { ed_flags |= OHCI_ED_SPEED; } ed->ed_flags = htole32(ed_flags); td = xfer->td_transfer_first; ed->ed_headp = td->itd_self; /* isochronous transfers are not affected by suspend / resume */ /* the append function will flush the endpoint descriptor */ OHCI_APPEND_QH(ed, sc->sc_isoc_p_last); } static void ohci_device_isoc_start(struct usb_xfer *xfer) { /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_isoc_methods = { .open = ohci_device_isoc_open, .close = ohci_device_isoc_close, .enter = ohci_device_isoc_enter, .start = ohci_device_isoc_start, }; /*------------------------------------------------------------------------* * ohci root control support *------------------------------------------------------------------------* * Simulate a hardware hub by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor ohci_devd = { sizeof(struct usb_device_descriptor), UDESC_DEVICE, /* type */ {0x00, 0x01}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_FSHUB, /* protocol */ 64, /* max packet */ {0}, {0}, {0x00, 0x01}, /* device id */ 1, 2, 0, /* string indexes */ 1 /* # of configurations */ }; static const struct ohci_config_desc ohci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(ohci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, /* max power */ }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | OHCI_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 32,/* max packet (255 ports) */ .bInterval = 255, }, }; static const struct usb_hub_descriptor ohci_hubd = { .bDescLength = 0, /* dynamic length */ .bDescriptorType = UDESC_HUB, }; static usb_error_t ohci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); const void *ptr; const char *str_ptr; uint32_t port; uint32_t v; uint16_t len; uint16_t value; uint16_t index; uint8_t l; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_desc.temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " "wValue=0x%04x wIndex=0x%04x\n", req->bmRequestType, req->bRequest, UGETW(req->wLength), value, index); #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): len = 1; sc->sc_hub_desc.temp[0] = sc->sc_conf; break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): switch (value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ohci_devd); ptr = (const void *)&ohci_devd; break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ohci_confd); ptr = (const void *)&ohci_confd; break; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ str_ptr = "\001"; break; case 1: /* Vendor */ str_ptr = sc->sc_vendor; break; case 2: /* Product */ str_ptr = "OHCI root HUB"; break; default: str_ptr = ""; break; } len = usb_make_str_desc( sc->sc_hub_desc.temp, sizeof(sc->sc_hub_desc.temp), str_ptr); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): len = 1; sc->sc_hub_desc.temp[0] = 0; break; case C(UR_GET_STATUS, UT_READ_DEVICE): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, 0); break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= OHCI_MAX_DEVICES) { err = USB_ERR_IOERROR; goto done; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if ((value != 0) && (value != 1)) { err = USB_ERR_IOERROR; goto done; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USB_ERR_IOERROR; goto done; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(9, "UR_CLEAR_PORT_FEATURE " "port=%d feature=%d\n", index, value); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } port = OHCI_RH_PORT_STATUS(index); switch (value) { case UHF_PORT_ENABLE: OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS); break; case UHF_PORT_SUSPEND: OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR); break; case UHF_PORT_POWER: /* Yes, writing to the LOW_SPEED bit clears power. */ OWRITE4(sc, port, UPS_LOW_SPEED); break; case UHF_C_PORT_CONNECTION: OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16); break; case UHF_C_PORT_ENABLE: OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16); break; case UHF_C_PORT_SUSPEND: OWRITE4(sc, port, UPS_C_SUSPEND << 16); break; case UHF_C_PORT_OVER_CURRENT: OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16); break; case UHF_C_PORT_RESET: OWRITE4(sc, port, UPS_C_PORT_RESET << 16); break; default: err = USB_ERR_IOERROR; goto done; } switch (value) { case UHF_C_PORT_CONNECTION: case UHF_C_PORT_ENABLE: case UHF_C_PORT_SUSPEND: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* enable RHSC interrupt if condition is cleared. */ if ((OREAD4(sc, port) >> 16) == 0) ohci_rhsc_enable(sc); break; default: break; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); sc->sc_hub_desc.hubd = ohci_hubd; sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, (v & OHCI_NPS ? UHD_PWR_NO_SWITCH : v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL) /* XXX overcurrent */ ); sc->sc_hub_desc.hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v); v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B); for (l = 0; l < sc->sc_noport; l++) { if (v & 1) { sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] |= (1 << (l % 8)); } v >>= 1; } sc->sc_hub_desc.hubd.bDescLength = 8 + ((sc->sc_noport + 7) / 8); len = sc->sc_hub_desc.hubd.bDescLength; break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): len = 16; memset(sc->sc_hub_desc.temp, 0, 16); break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): DPRINTFN(9, "get port status i=%d\n", index); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } v = OREAD4(sc, OHCI_RH_PORT_STATUS(index)); DPRINTFN(9, "port status=0x%04x\n", v); v &= ~UPS_PORT_MODE_DEVICE; /* force host mode */ USETW(sc->sc_hub_desc.ps.wPortStatus, v); USETW(sc->sc_hub_desc.ps.wPortChange, v >> 16); len = sizeof(sc->sc_hub_desc.ps); break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USB_ERR_IOERROR; goto done; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } port = OHCI_RH_PORT_STATUS(index); switch (value) { case UHF_PORT_ENABLE: OWRITE4(sc, port, UPS_PORT_ENABLED); break; case UHF_PORT_SUSPEND: OWRITE4(sc, port, UPS_SUSPEND); break; case UHF_PORT_RESET: DPRINTFN(6, "reset port %d\n", index); OWRITE4(sc, port, UPS_RESET); for (v = 0;; v++) { if (v < 12) { usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(usb_port_root_reset_delay)); if ((OREAD4(sc, port) & UPS_RESET) == 0) { break; } } else { err = USB_ERR_TIMEOUT; goto done; } } DPRINTFN(9, "ohci port %d reset, status = 0x%04x\n", index, OREAD4(sc, port)); break; case UHF_PORT_POWER: DPRINTFN(3, "set port power %d\n", index); OWRITE4(sc, port, UPS_PORT_POWER); break; default: err = USB_ERR_IOERROR; goto done; } break; default: err = USB_ERR_IOERROR; goto done; } done: *plength = len; *pptr = ptr; return (err); } static void ohci_xfer_setup(struct usb_setup_params *parm) { struct usb_page_search page_info; struct usb_page_cache *pc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t nitd; uint32_t nqh; uint32_t n; xfer = parm->curr_xfer; parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = OHCI_PAGE_SIZE; /* * calculate ntd and nqh */ if (parm->methods == &ohci_device_ctrl_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = 0; ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + (xfer->max_data_length / xfer->max_hc_frame_size)); nqh = 1; } else if (parm->methods == &ohci_device_bulk_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = 0; ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); nqh = 1; } else if (parm->methods == &ohci_device_intr_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = 0; ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); nqh = 1; } else if (parm->methods == &ohci_device_isoc_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = ((xfer->max_data_length / OHCI_PAGE_SIZE) + howmany(xfer->nframes, OHCI_ITD_NOFFSET) + 1 /* EXTRA */ ); ntd = 0; nqh = 1; } else { usbd_transfer_setup_sub(parm); nitd = 0; ntd = 0; nqh = 0; } alloc_dma_set: if (parm->err) { return; } last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ohci_td_t), OHCI_TD_ALIGN, ntd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != ntd; n++) { ohci_td_t *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; /* init TD */ td->td_self = htole32(page_info.physaddr); td->obj_next = last_obj; td->page_cache = pc + n; last_obj = td; usb_pc_cpu_flush(pc + n); } } if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ohci_itd_t), OHCI_ITD_ALIGN, nitd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nitd; n++) { ohci_itd_t *itd; usbd_get_page(pc + n, 0, &page_info); itd = page_info.buffer; /* init TD */ itd->itd_self = htole32(page_info.physaddr); itd->obj_next = last_obj; itd->page_cache = pc + n; last_obj = itd; usb_pc_cpu_flush(pc + n); } } xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ohci_ed_t), OHCI_ED_ALIGN, nqh)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nqh; n++) { ohci_ed_t *ed; usbd_get_page(pc + n, 0, &page_info); ed = page_info.buffer; /* init QH */ ed->ed_self = htole32(page_info.physaddr); ed->obj_next = last_obj; ed->page_cache = pc + n; last_obj = ed; usb_pc_cpu_flush(pc + n); } } xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; if (!xfer->flags_int.curr_dma_set) { xfer->flags_int.curr_dma_set = 1; goto alloc_dma_set; } } static void ohci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_addr); if (udev->device_index != sc->sc_addr) { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &ohci_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &ohci_device_intr_methods; break; case UE_ISOCHRONOUS: if (udev->speed == USB_SPEED_FULL) { ep->methods = &ohci_device_isoc_methods; } break; case UE_BULK: ep->methods = &ohci_device_bulk_methods; break; default: /* do nothing */ break; } } } static void ohci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void ohci_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* * Wait until hardware has finished any possible use of the * transfer descriptor(s) and QH */ *pus = (1125); /* microseconds */ } static void ohci_device_resume(struct usb_device *udev) { struct ohci_softc *sc = OHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; ohci_ed_t *ed; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (methods == &ohci_device_bulk_methods) { OHCI_APPEND_QH(ed, sc->sc_bulk_p_last); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); } if (methods == &ohci_device_ctrl_methods) { OHCI_APPEND_QH(ed, sc->sc_ctrl_p_last); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); } if (methods == &ohci_device_intr_methods) { OHCI_APPEND_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void ohci_device_suspend(struct usb_device *udev) { struct ohci_softc *sc = OHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; ohci_ed_t *ed; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (methods == &ohci_device_bulk_methods) { OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); } if (methods == &ohci_device_ctrl_methods) { OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); } if (methods == &ohci_device_intr_methods) { OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void ohci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct ohci_softc *sc = OHCI_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: case USB_HW_POWER_SHUTDOWN: ohci_suspend(sc); break; case USB_HW_POWER_RESUME: ohci_resume(sc); break; default: break; } } static void ohci_set_hw_power(struct usb_bus *bus) { struct ohci_softc *sc = OHCI_BUS2SC(bus); uint32_t temp; uint32_t flags; DPRINTF("\n"); USB_BUS_LOCK(bus); flags = bus->hw_power_state; temp = OREAD4(sc, OHCI_CONTROL); temp &= ~(OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE); if (flags & USB_HW_POWER_CONTROL) temp |= OHCI_CLE; if (flags & USB_HW_POWER_BULK) temp |= OHCI_BLE; if (flags & USB_HW_POWER_INTERRUPT) temp |= OHCI_PLE; if (flags & USB_HW_POWER_ISOC) temp |= OHCI_IE | OHCI_PLE; OWRITE4(sc, OHCI_CONTROL, temp); USB_BUS_UNLOCK(bus); return; } static const struct usb_bus_methods ohci_bus_methods = { .endpoint_init = ohci_ep_init, .xfer_setup = ohci_xfer_setup, .xfer_unsetup = ohci_xfer_unsetup, .get_dma_delay = ohci_get_dma_delay, .device_resume = ohci_device_resume, .device_suspend = ohci_device_suspend, .set_hw_power = ohci_set_hw_power, .set_hw_power_sleep = ohci_set_hw_power_sleep, .roothub_exec = ohci_roothub_exec, .xfer_poll = ohci_do_poll, }; Index: head/sys/dev/usb/controller/saf1761_otg.c =================================================================== --- head/sys/dev/usb/controller/saf1761_otg.c (revision 357971) +++ head/sys/dev/usb/controller/saf1761_otg.c (revision 357972) @@ -1,3738 +1,3738 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2014 Hans Petter Selasky * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the driver for the SAF1761 series USB OTG * controller. * * Datasheet is available from: * http://www.nxp.com/products/automotive/multimedia/usb/SAF1761BE.html */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR saf1761_otg_debug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define SAF1761_OTG_BUS2SC(bus) \ ((struct saf1761_otg_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct saf1761_otg_softc *)0)->sc_bus)))) #define SAF1761_OTG_PC2UDEV(pc) \ (USB_DMATAG_TO_XROOT((pc)->tag_parent)->udev) #define SAF1761_DCINTERRUPT_THREAD_IRQ \ (SOTG_DCINTERRUPT_IEVBUS | SOTG_DCINTERRUPT_IEBRST | \ SOTG_DCINTERRUPT_IERESM | SOTG_DCINTERRUPT_IESUSP) #ifdef USB_DEBUG static int saf1761_otg_debug = 0; static int saf1761_otg_forcefs = 0; static -SYSCTL_NODE(_hw_usb, OID_AUTO, saf1761_otg, CTLFLAG_RW, 0, +SYSCTL_NODE(_hw_usb, OID_AUTO, saf1761_otg, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB SAF1761 DCI"); SYSCTL_INT(_hw_usb_saf1761_otg, OID_AUTO, debug, CTLFLAG_RWTUN, &saf1761_otg_debug, 0, "SAF1761 DCI debug level"); SYSCTL_INT(_hw_usb_saf1761_otg, OID_AUTO, forcefs, CTLFLAG_RWTUN, &saf1761_otg_forcefs, 0, "SAF1761 DCI force FULL speed"); #endif #define SAF1761_OTG_INTR_ENDPT 1 /* prototypes */ static const struct usb_bus_methods saf1761_otg_bus_methods; static const struct usb_pipe_methods saf1761_otg_non_isoc_methods; static const struct usb_pipe_methods saf1761_otg_device_isoc_methods; static const struct usb_pipe_methods saf1761_otg_host_isoc_methods; static saf1761_otg_cmd_t saf1761_host_setup_tx; static saf1761_otg_cmd_t saf1761_host_bulk_data_rx; static saf1761_otg_cmd_t saf1761_host_bulk_data_tx; static saf1761_otg_cmd_t saf1761_host_intr_data_rx; static saf1761_otg_cmd_t saf1761_host_intr_data_tx; static saf1761_otg_cmd_t saf1761_host_isoc_data_rx; static saf1761_otg_cmd_t saf1761_host_isoc_data_tx; static saf1761_otg_cmd_t saf1761_device_setup_rx; static saf1761_otg_cmd_t saf1761_device_data_rx; static saf1761_otg_cmd_t saf1761_device_data_tx; static saf1761_otg_cmd_t saf1761_device_data_tx_sync; static void saf1761_otg_device_done(struct usb_xfer *, usb_error_t); static void saf1761_otg_do_poll(struct usb_bus *); static void saf1761_otg_standard_done(struct usb_xfer *); static void saf1761_otg_intr_set(struct usb_xfer *, uint8_t); static void saf1761_otg_root_intr(struct saf1761_otg_softc *); static void saf1761_otg_enable_psof(struct saf1761_otg_softc *, uint8_t); /* * Here is a list of what the SAF1761 chip can support. The main * limitation is that the sum of the buffer sizes must be less than * 8192 bytes. */ static const struct usb_hw_ep_profile saf1761_otg_ep_profile[] = { [0] = { .max_in_frame_size = 64, .max_out_frame_size = 64, .is_simplex = 0, .support_control = 1, }, [1] = { .max_in_frame_size = SOTG_HS_MAX_PACKET_SIZE, .max_out_frame_size = SOTG_HS_MAX_PACKET_SIZE, .is_simplex = 0, .support_interrupt = 1, .support_bulk = 1, .support_isochronous = 1, .support_in = 1, .support_out = 1, }, }; static void saf1761_otg_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { if (ep_addr == 0) { *ppf = saf1761_otg_ep_profile + 0; } else if (ep_addr < 8) { *ppf = saf1761_otg_ep_profile + 1; } else { *ppf = NULL; } } static void saf1761_otg_pull_up(struct saf1761_otg_softc *sc) { /* activate pullup on D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { DPRINTF("\n"); sc->sc_flags.d_pulled_up = 1; } } static void saf1761_otg_pull_down(struct saf1761_otg_softc *sc) { /* release pullup on D+, if possible */ if (sc->sc_flags.d_pulled_up) { DPRINTF("\n"); sc->sc_flags.d_pulled_up = 0; } } static void saf1761_otg_wakeup_peer(struct saf1761_otg_softc *sc) { uint16_t temp; if (!(sc->sc_flags.status_suspend)) return; DPRINTFN(5, "\n"); temp = SAF1761_READ_LE_4(sc, SOTG_MODE); SAF1761_WRITE_LE_4(sc, SOTG_MODE, temp | SOTG_MODE_SNDRSU); SAF1761_WRITE_LE_4(sc, SOTG_MODE, temp & ~SOTG_MODE_SNDRSU); /* Wait 8ms for remote wakeup to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); } static uint8_t saf1761_host_channel_alloc(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t map; int x; if (td->channel < SOTG_HOST_CHANNEL_MAX) return (0); /* check if device is suspended */ if (SAF1761_OTG_PC2UDEV(td->pc)->flags.self_suspended != 0) return (1); /* busy - cannot transfer data */ switch (td->ep_type) { case UE_INTERRUPT: map = ~(sc->sc_host_intr_map | sc->sc_host_intr_busy_map[0] | sc->sc_host_intr_busy_map[1]); /* find first set bit */ x = ffs(map) - 1; if (x < 0 || x > 31) break; sc->sc_host_intr_map |= (1U << x); td->channel = 32 + x; return (0); case UE_ISOCHRONOUS: map = ~(sc->sc_host_isoc_map | sc->sc_host_isoc_busy_map[0] | sc->sc_host_isoc_busy_map[1]); /* find first set bit */ x = ffs(map) - 1; if (x < 0 || x > 31) break; sc->sc_host_isoc_map |= (1U << x); td->channel = x; return (0); default: map = ~(sc->sc_host_async_map | sc->sc_host_async_busy_map[0] | sc->sc_host_async_busy_map[1]); /* find first set bit */ x = ffs(map) - 1; if (x < 0 || x > 31) break; sc->sc_host_async_map |= (1U << x); td->channel = 64 + x; return (0); } return (1); } static void saf1761_host_channel_free(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t x; if (td->channel >= SOTG_HOST_CHANNEL_MAX) return; switch (td->ep_type) { case UE_INTERRUPT: x = td->channel - 32; td->channel = SOTG_HOST_CHANNEL_MAX; sc->sc_host_intr_map &= ~(1U << x); sc->sc_host_intr_suspend_map &= ~(1U << x); sc->sc_host_intr_busy_map[0] |= (1U << x); SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); break; case UE_ISOCHRONOUS: x = td->channel; td->channel = SOTG_HOST_CHANNEL_MAX; sc->sc_host_isoc_map &= ~(1U << x); sc->sc_host_isoc_suspend_map &= ~(1U << x); sc->sc_host_isoc_busy_map[0] |= (1U << x); SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); break; default: x = td->channel - 64; td->channel = SOTG_HOST_CHANNEL_MAX; sc->sc_host_async_map &= ~(1U << x); sc->sc_host_async_suspend_map &= ~(1U << x); sc->sc_host_async_busy_map[0] |= (1U << x); SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); break; } saf1761_otg_enable_psof(sc, 1); } static uint32_t saf1761_peek_host_status_le_4(struct saf1761_otg_softc *sc, uint32_t offset) { uint32_t x = 0; while (1) { uint32_t retval; SAF1761_WRITE_LE_4(sc, SOTG_MEMORY_REG, offset); SAF1761_90NS_DELAY(sc); /* read prefetch time is 90ns */ retval = SAF1761_READ_LE_4(sc, offset); if (retval != 0) return (retval); if (++x == 8) { DPRINTF("STAUS is zero at offset 0x%x\n", offset); break; } } return (0); } static void saf1761_read_host_memory(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td, uint32_t len) { struct usb_page_search buf_res; uint32_t offset; uint32_t count; if (len == 0) return; offset = SOTG_DATA_ADDR(td->channel); SAF1761_WRITE_LE_4(sc, SOTG_MEMORY_REG, offset); SAF1761_90NS_DELAY(sc); /* read prefetch time is 90ns */ /* optimised read first */ while (len > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > len) buf_res.length = len; /* check buffer alignment */ if (((uintptr_t)buf_res.buffer) & 3) break; count = buf_res.length & ~3; if (count == 0) break; bus_space_read_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, offset, buf_res.buffer, count / 4); len -= count; offset += count; /* update remainder and offset */ td->remainder -= count; td->offset += count; } if (len > 0) { /* use bounce buffer */ bus_space_read_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, offset, sc->sc_bounce_buffer, (len + 3) / 4); usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buffer, len); /* update remainder and offset */ td->remainder -= len; td->offset += len; } } static void saf1761_write_host_memory(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td, uint32_t len) { struct usb_page_search buf_res; uint32_t offset; uint32_t count; if (len == 0) return; offset = SOTG_DATA_ADDR(td->channel); /* optimised write first */ while (len > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > len) buf_res.length = len; /* check buffer alignment */ if (((uintptr_t)buf_res.buffer) & 3) break; count = buf_res.length & ~3; if (count == 0) break; bus_space_write_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, offset, buf_res.buffer, count / 4); len -= count; offset += count; /* update remainder and offset */ td->remainder -= count; td->offset += count; } if (len > 0) { /* use bounce buffer */ usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buffer, len); bus_space_write_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, offset, sc->sc_bounce_buffer, (len + 3) / 4); /* update remainder and offset */ td->remainder -= len; td->offset += len; } } static uint8_t saf1761_host_setup_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t status; uint32_t count; uint32_t temp; if (td->channel < SOTG_HOST_CHANNEL_MAX) { pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { goto busy; } else if (status & SOTG_PTD_DW3_HALTED) { td->error_any = 1; } goto complete; } if (saf1761_host_channel_alloc(sc, td)) goto busy; count = 8; if (count != td->remainder) { td->error_any = 1; goto complete; } saf1761_write_host_memory(sc, td, count); pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, 0); temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_3; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (2 << 10) /* SETUP PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (td->max_packet_size << 18) /* wMaxPacketSize */ | (count << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); td->toggle = 1; busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t saf1761_host_bulk_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t temp; if (td->channel < SOTG_HOST_CHANNEL_MAX) { uint32_t status; uint32_t count; uint8_t got_short; pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { temp = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW0); if (temp & SOTG_PTD_DW0_VALID) { goto busy; } else { status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); /* check if still active */ if (status & SOTG_PTD_DW3_ACTIVE) { saf1761_host_channel_free(sc, td); goto retry; } else if (status & SOTG_PTD_DW3_HALTED) { if (!(status & SOTG_PTD_DW3_ERRORS)) td->error_stall = 1; td->error_any = 1; goto complete; } } } else if (status & SOTG_PTD_DW3_HALTED) { if (!(status & SOTG_PTD_DW3_ERRORS)) td->error_stall = 1; td->error_any = 1; goto complete; } if (td->dw1_value & SOTG_PTD_DW1_ENABLE_SPLIT) count = (status & SOTG_PTD_DW3_XFER_COUNT_SPLIT); else count = (status & SOTG_PTD_DW3_XFER_COUNT_HS); got_short = 0; /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error_any = 1; goto complete; } } td->toggle ^= 1; /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error_any = 1; goto complete; } saf1761_read_host_memory(sc, td, count); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) goto complete; /* else need to receive a zero length packet */ } saf1761_host_channel_free(sc, td); } retry: if (saf1761_host_channel_alloc(sc, td)) goto busy; /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } /* receive one more packet */ pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, 0); temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_2; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (1 << 10) /* IN-PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (td->max_packet_size << 18) /* wMaxPacketSize */ | (td->max_packet_size << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t saf1761_host_bulk_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t temp; uint32_t count; if (td->channel < SOTG_HOST_CHANNEL_MAX) { uint32_t status; pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { goto busy; } else if (status & SOTG_PTD_DW3_HALTED) { if (!(status & SOTG_PTD_DW3_ERRORS)) td->error_stall = 1; td->error_any = 1; goto complete; } /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) goto complete; /* else we need to transmit a short packet */ } saf1761_host_channel_free(sc, td); } if (saf1761_host_channel_alloc(sc, td)) goto busy; count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } saf1761_write_host_memory(sc, td, count); /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } /* send one more packet */ pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, 0); temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_2; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (0 << 10) /* OUT-PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (td->max_packet_size << 18) /* wMaxPacketSize */ | (count << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); td->toggle ^= 1; busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t saf1761_host_intr_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t temp; if (td->channel < SOTG_HOST_CHANNEL_MAX) { uint32_t status; uint32_t count; uint8_t got_short; pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { goto busy; } else if (status & SOTG_PTD_DW3_HALTED) { if (!(status & SOTG_PTD_DW3_ERRORS)) td->error_stall = 1; td->error_any = 1; goto complete; } if (td->dw1_value & SOTG_PTD_DW1_ENABLE_SPLIT) count = (status & SOTG_PTD_DW3_XFER_COUNT_SPLIT); else count = (status & SOTG_PTD_DW3_XFER_COUNT_HS); got_short = 0; /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error_any = 1; goto complete; } } td->toggle ^= 1; /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error_any = 1; goto complete; } saf1761_read_host_memory(sc, td, count); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) goto complete; /* else need to receive a zero length packet */ } saf1761_host_channel_free(sc, td); } if (saf1761_host_channel_alloc(sc, td)) goto busy; /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } /* receive one more packet */ pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); if (td->dw1_value & SOTG_PTD_DW1_ENABLE_SPLIT) { temp = (0xFC << td->uframe) & 0xFF; /* complete split */ } else { temp = 0; } SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, temp); temp = (1U << td->uframe); /* start mask or start split */ SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_3; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8) | (td->interval & 0xF8); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (1 << 10) /* IN-PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (td->max_packet_size << 18) /* wMaxPacketSize */ | (td->max_packet_size << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t saf1761_host_intr_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t temp; uint32_t count; if (td->channel < SOTG_HOST_CHANNEL_MAX) { uint32_t status; pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { goto busy; } else if (status & SOTG_PTD_DW3_HALTED) { if (!(status & SOTG_PTD_DW3_ERRORS)) td->error_stall = 1; td->error_any = 1; goto complete; } /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) goto complete; /* else we need to transmit a short packet */ } saf1761_host_channel_free(sc, td); } if (saf1761_host_channel_alloc(sc, td)) goto busy; count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } saf1761_write_host_memory(sc, td, count); /* set toggle, if any */ if (td->set_toggle) { td->set_toggle = 0; td->toggle = 1; } /* send one more packet */ pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); if (td->dw1_value & SOTG_PTD_DW1_ENABLE_SPLIT) { temp = (0xFC << td->uframe) & 0xFF; /* complete split */ } else { temp = 0; } SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, temp); temp = (1U << td->uframe); /* start mask or start split */ SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_3; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8) | (td->interval & 0xF8); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (0 << 10) /* OUT-PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (td->max_packet_size << 18) /* wMaxPacketSize */ | (count << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); td->toggle ^= 1; busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t saf1761_host_isoc_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t temp; if (td->channel < SOTG_HOST_CHANNEL_MAX) { uint32_t status; uint32_t count; pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { goto busy; } else if (status & SOTG_PTD_DW3_HALTED) { goto complete; } if (td->dw1_value & SOTG_PTD_DW1_ENABLE_SPLIT) count = (status & SOTG_PTD_DW3_XFER_COUNT_SPLIT); else count = (status & SOTG_PTD_DW3_XFER_COUNT_HS); /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; } else { /* invalid USB packet */ td->error_any = 1; goto complete; } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error_any = 1; goto complete; } saf1761_read_host_memory(sc, td, count); goto complete; } if (saf1761_host_channel_alloc(sc, td)) goto busy; /* receive one more packet */ pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); if (td->dw1_value & SOTG_PTD_DW1_ENABLE_SPLIT) { temp = (0xFC << (td->uframe & 7)) & 0xFF; /* complete split */ } else { temp = 0; } SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, temp); temp = (1U << (td->uframe & 7)); /* start mask or start split */ SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); temp = SOTG_PTD_DW3_ACTIVE | SOTG_PTD_DW3_CERR_3; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8) | (td->uframe & 0xF8); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (1 << 10) /* IN-PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (td->max_packet_size << 18) /* wMaxPacketSize */ | (td->max_packet_size << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static uint8_t saf1761_host_isoc_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t pdt_addr; uint32_t temp; uint32_t count; if (td->channel < SOTG_HOST_CHANNEL_MAX) { uint32_t status; pdt_addr = SOTG_PTD(td->channel); status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); DPRINTFN(5, "STATUS=0x%08x\n", status); if (status & SOTG_PTD_DW3_ACTIVE) { goto busy; } else if (status & SOTG_PTD_DW3_HALTED) { goto complete; } goto complete; } if (saf1761_host_channel_alloc(sc, td)) goto busy; count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } saf1761_write_host_memory(sc, td, count); /* send one more packet */ pdt_addr = SOTG_PTD(td->channel); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); temp = (1U << (td->uframe & 7)); /* start mask or start split */ SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); temp = SOTG_PTD_DW3_ACTIVE | SOTG_PTD_DW3_CERR_3; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8) | (td->uframe & 0xF8); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); temp = td->dw1_value | (0 << 10) /* OUT-PID */ | (td->ep_index >> 1); SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | (count << 18) /* wMaxPacketSize */ | (count << 3) /* transfer count */ | SOTG_PTD_DW0_VALID; SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); /* activate PTD */ SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); busy: return (1); /* busy */ complete: saf1761_host_channel_free(sc, td); return (0); /* complete */ } static void saf1761_otg_set_address(struct saf1761_otg_softc *sc, uint8_t addr) { DPRINTFN(5, "addr=%d\n", addr); SAF1761_WRITE_LE_4(sc, SOTG_ADDRESS, addr | SOTG_ADDRESS_ENABLE); } static void saf1761_read_device_fifo(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td, uint32_t len) { struct usb_page_search buf_res; uint32_t count; /* optimised read first */ while (len > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > len) buf_res.length = len; /* check buffer alignment */ if (((uintptr_t)buf_res.buffer) & 3) break; count = buf_res.length & ~3; if (count == 0) break; bus_space_read_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, buf_res.buffer, count / 4); len -= count; /* update remainder and offset */ td->remainder -= count; td->offset += count; } if (len > 0) { /* use bounce buffer */ bus_space_read_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, sc->sc_bounce_buffer, (len + 3) / 4); usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buffer, len); /* update remainder and offset */ td->remainder -= len; td->offset += len; } } static void saf1761_write_device_fifo(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td, uint32_t len) { struct usb_page_search buf_res; uint32_t count; /* optimised write first */ while (len > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > len) buf_res.length = len; /* check buffer alignment */ if (((uintptr_t)buf_res.buffer) & 3) break; count = buf_res.length & ~3; if (count == 0) break; bus_space_write_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, buf_res.buffer, count / 4); len -= count; /* update remainder and offset */ td->remainder -= count; td->offset += count; } if (len > 0) { /* use bounce buffer */ usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buffer, len); bus_space_write_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, sc->sc_bounce_buffer, (len + 3) / 4); /* update remainder and offset */ td->remainder -= len; td->offset += len; } } static uint8_t saf1761_device_setup_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { struct usb_device_request req; uint32_t count; /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) == 0) goto busy; /* get buffer length */ count &= SOTG_BUF_LENGTH_BUFLEN_MASK; DPRINTFN(5, "count=%u rem=%u\n", count, td->remainder); /* clear did stall */ td->did_stall = 0; /* clear stall */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, 0); /* verify data length */ if (count != td->remainder) { DPRINTFN(0, "Invalid SETUP packet " "length, %d bytes\n", count); goto busy; } if (count != sizeof(req)) { DPRINTFN(0, "Unsupported SETUP packet " "length, %d bytes\n", count); goto busy; } /* receive data */ saf1761_read_device_fifo(sc, td, sizeof(req)); /* extract SETUP packet again */ usbd_copy_out(td->pc, 0, &req, sizeof(req)); /* sneak peek the set address request */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { sc->sc_dv_addr = req.wValue[0] & 0x7F; DPRINTF("Set address %d\n", sc->sc_dv_addr); } else { sc->sc_dv_addr = 0xFF; } return (0); /* complete */ busy: /* abort any ongoing transfer */ if (!td->did_stall) { DPRINTFN(5, "stalling\n"); /* set stall */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_STALL); td->did_stall = 1; } return (1); /* not complete */ } static uint8_t saf1761_device_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t count; uint8_t got_short = 0; if (td->ep_index == 0) { /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) { if (td->remainder == 0) { /* * We are actually complete and have * received the next SETUP: */ DPRINTFN(5, "faking complete\n"); return (0); /* complete */ } DPRINTFN(5, "SETUP packet while receiving data\n"); /* * USB Host Aborted the transfer. */ td->error_any = 1; return (0); /* complete */ } } /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | SOTG_EP_INDEX_DIR_OUT); /* enable data stage */ if (td->set_toggle) { td->set_toggle = 0; SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_DSEN); } count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) == 0) return (1); /* not complete */ /* get buffer length */ count &= SOTG_BUF_LENGTH_BUFLEN_MASK; DPRINTFN(5, "rem=%u count=0x%04x\n", td->remainder, count); /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error_any = 1; return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error_any = 1; return (0); /* we are complete */ } /* receive data */ saf1761_read_device_fifo(sc, td, count); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ return (0); } /* else need to receive a zero length packet */ } return (1); /* not complete */ } static uint8_t saf1761_device_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t count; if (td->ep_index == 0) { /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) { DPRINTFN(5, "SETUP abort\n"); /* * USB Host Aborted the transfer. */ td->error_any = 1; return (0); /* complete */ } } /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | SOTG_EP_INDEX_DIR_IN); count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) return (1); /* not complete */ /* enable data stage */ if (td->set_toggle) { td->set_toggle = 0; SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_DSEN); } DPRINTFN(5, "rem=%u\n", td->remainder); count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } /* transmit data */ saf1761_write_device_fifo(sc, td, count); if (td->ep_index == 0) { if (count < SOTG_FS_MAX_PACKET_SIZE) { /* set end of packet */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP); } } else { if (count < SOTG_HS_MAX_PACKET_SIZE) { /* set end of packet */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP); } } /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { return (0); /* complete */ } /* else we need to transmit a short packet */ } return (1); /* not complete */ } static uint8_t saf1761_device_data_tx_sync(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) { uint32_t count; if (td->ep_index == 0) { /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) { DPRINTFN(5, "Faking complete\n"); return (0); /* complete */ } } /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | SOTG_EP_INDEX_DIR_IN); count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); /* check buffer status */ if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) return (1); /* busy */ if (sc->sc_dv_addr != 0xFF) { /* write function address */ saf1761_otg_set_address(sc, sc->sc_dv_addr); } return (0); /* complete */ } static void saf1761_otg_xfer_do_fifo(struct saf1761_otg_softc *sc, struct usb_xfer *xfer) { struct saf1761_otg_td *td; uint8_t toggle; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; if (td == NULL) return; while (1) { if ((td->func) (sc, td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error_any) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) { goto done; } } /* * Fetch the next transfer descriptor. */ toggle = td->toggle; td = td->obj_next; td->toggle = toggle; xfer->td_transfer_cache = td; } return; done: /* compute all actual lengths */ xfer->td_transfer_cache = NULL; sc->sc_xfer_complete = 1; } static uint8_t saf1761_otg_xfer_do_complete(struct saf1761_otg_softc *sc, struct usb_xfer *xfer) { struct saf1761_otg_td *td; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; if (td == NULL) { /* compute all actual lengths */ saf1761_otg_standard_done(xfer); return (1); } return (0); } static void saf1761_otg_interrupt_poll_locked(struct saf1761_otg_softc *sc) { struct usb_xfer *xfer; TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) saf1761_otg_xfer_do_fifo(sc, xfer); } static void saf1761_otg_enable_psof(struct saf1761_otg_softc *sc, uint8_t on) { if (on) { sc->sc_intr_enable |= SOTG_DCINTERRUPT_IEPSOF; } else { sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IEPSOF; } SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); } static void saf1761_otg_wait_suspend(struct saf1761_otg_softc *sc, uint8_t on) { if (on) { sc->sc_intr_enable |= SOTG_DCINTERRUPT_IESUSP; sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IERESM; } else { sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IESUSP; sc->sc_intr_enable |= SOTG_DCINTERRUPT_IERESM; } SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); } static void saf1761_otg_update_vbus(struct saf1761_otg_softc *sc) { uint16_t status; /* read fresh status */ status = SAF1761_READ_LE_4(sc, SOTG_STATUS); DPRINTFN(4, "STATUS=0x%04x\n", status); if ((status & SOTG_STATUS_VBUS_VLD) && (status & SOTG_STATUS_ID)) { /* VBUS present and device mode */ if (!sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 1; /* complete root HUB interrupt endpoint */ saf1761_otg_root_intr(sc); } } else { /* VBUS not-present or host mode */ if (sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ saf1761_otg_root_intr(sc); } } } static void saf1761_otg_interrupt_complete_locked(struct saf1761_otg_softc *sc) { struct usb_xfer *xfer; repeat: /* scan for completion events */ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (saf1761_otg_xfer_do_complete(sc, xfer)) goto repeat; } } int saf1761_otg_filter_interrupt(void *arg) { struct saf1761_otg_softc *sc = arg; int retval = FILTER_HANDLED; uint32_t hcstat; uint32_t status; USB_BUS_SPIN_LOCK(&sc->sc_bus); hcstat = SAF1761_READ_LE_4(sc, SOTG_HCINTERRUPT); /* acknowledge all host controller interrupts */ SAF1761_WRITE_LE_4(sc, SOTG_HCINTERRUPT, hcstat); status = SAF1761_READ_LE_4(sc, SOTG_DCINTERRUPT); /* acknowledge all device controller interrupts */ SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT, status & ~SAF1761_DCINTERRUPT_THREAD_IRQ); (void) SAF1761_READ_LE_4(sc, SOTG_ATL_PTD_DONE_PTD); (void) SAF1761_READ_LE_4(sc, SOTG_INT_PTD_DONE_PTD); (void) SAF1761_READ_LE_4(sc, SOTG_ISO_PTD_DONE_PTD); DPRINTFN(9, "HCINTERRUPT=0x%08x DCINTERRUPT=0x%08x\n", hcstat, status); if (status & SOTG_DCINTERRUPT_IEPSOF) { if ((sc->sc_host_async_busy_map[1] | sc->sc_host_async_busy_map[0] | sc->sc_host_intr_busy_map[1] | sc->sc_host_intr_busy_map[0] | sc->sc_host_isoc_busy_map[1] | sc->sc_host_isoc_busy_map[0]) != 0) { /* busy waiting is active */ retval = FILTER_SCHEDULE_THREAD; sc->sc_host_async_busy_map[1] = sc->sc_host_async_busy_map[0]; sc->sc_host_async_busy_map[0] = 0; sc->sc_host_intr_busy_map[1] = sc->sc_host_intr_busy_map[0]; sc->sc_host_intr_busy_map[0] = 0; sc->sc_host_isoc_busy_map[1] = sc->sc_host_isoc_busy_map[0]; sc->sc_host_isoc_busy_map[0] = 0; } else { /* busy waiting is not active */ saf1761_otg_enable_psof(sc, 0); } } if (status & SAF1761_DCINTERRUPT_THREAD_IRQ) retval = FILTER_SCHEDULE_THREAD; /* poll FIFOs, if any */ saf1761_otg_interrupt_poll_locked(sc); if (sc->sc_xfer_complete != 0) retval = FILTER_SCHEDULE_THREAD; USB_BUS_SPIN_UNLOCK(&sc->sc_bus); return (retval); } void saf1761_otg_interrupt(void *arg) { struct saf1761_otg_softc *sc = arg; uint32_t status; USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); status = SAF1761_READ_LE_4(sc, SOTG_DCINTERRUPT) & SAF1761_DCINTERRUPT_THREAD_IRQ; /* acknowledge all device controller interrupts */ SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT, status); DPRINTF("DCINTERRUPT=0x%08x SOF=0x%08x " "FRINDEX=0x%08x\n", status, SAF1761_READ_LE_4(sc, SOTG_FRAME_NUM), SAF1761_READ_LE_4(sc, SOTG_FRINDEX)); /* update VBUS and ID bits, if any */ if (status & SOTG_DCINTERRUPT_IEVBUS) saf1761_otg_update_vbus(sc); if (status & SOTG_DCINTERRUPT_IEBRST) { /* unlock device */ SAF1761_WRITE_LE_4(sc, SOTG_UNLOCK_DEVICE, SOTG_UNLOCK_DEVICE_CODE); /* Enable device address */ SAF1761_WRITE_LE_4(sc, SOTG_ADDRESS, SOTG_ADDRESS_ENABLE); sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* disable resume interrupt */ saf1761_otg_wait_suspend(sc, 1); /* complete root HUB interrupt endpoint */ saf1761_otg_root_intr(sc); } /* * If "RESUME" and "SUSPEND" is set at the same time we * interpret that like "RESUME". Resume is set when there is * at least 3 milliseconds of inactivity on the USB BUS: */ if (status & SOTG_DCINTERRUPT_IERESM) { /* unlock device */ SAF1761_WRITE_LE_4(sc, SOTG_UNLOCK_DEVICE, SOTG_UNLOCK_DEVICE_CODE); if (sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; /* disable resume interrupt */ saf1761_otg_wait_suspend(sc, 1); /* complete root HUB interrupt endpoint */ saf1761_otg_root_intr(sc); } } else if (status & SOTG_DCINTERRUPT_IESUSP) { if (!sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; /* enable resume interrupt */ saf1761_otg_wait_suspend(sc, 0); /* complete root HUB interrupt endpoint */ saf1761_otg_root_intr(sc); } } if (sc->sc_xfer_complete != 0) { sc->sc_xfer_complete = 0; /* complete FIFOs, if any */ saf1761_otg_interrupt_complete_locked(sc); } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } static void saf1761_otg_setup_standard_chain_sub(struct saf1761_otg_std_temp *temp) { struct saf1761_otg_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->error_any = 0; td->error_stall = 0; td->set_toggle = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; td->channel = SOTG_HOST_CHANNEL_MAX; } static void saf1761_otg_setup_standard_chain(struct usb_xfer *xfer) { struct saf1761_otg_std_temp temp; struct saf1761_otg_softc *sc; struct saf1761_otg_td *td; uint32_t x; uint8_t ep_no; uint8_t ep_type; uint8_t need_sync; uint8_t is_host; uint8_t uframe_start; uint8_t uframe_interval; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; is_host = (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST); sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); ep_no = (xfer->endpointno & UE_ADDR); ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE); /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { if (is_host) temp.func = &saf1761_host_setup_tx; else temp.func = &saf1761_device_setup_rx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) temp.setup_alt_next = 0; } saf1761_otg_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } uframe_start = 0; uframe_interval = 0; if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) { if (is_host) { if (ep_type == UE_INTERRUPT) { temp.func = &saf1761_host_intr_data_rx; } else if (ep_type == UE_ISOCHRONOUS) { temp.func = &saf1761_host_isoc_data_rx; uframe_start = (SAF1761_READ_LE_4(sc, SOTG_FRINDEX) + 8) & (SOTG_FRINDEX_MASK & ~7); if (xfer->xroot->udev->speed == USB_SPEED_HIGH) uframe_interval = 1U << xfer->fps_shift; else uframe_interval = 8U; } else { temp.func = &saf1761_host_bulk_data_rx; } need_sync = 0; } else { temp.func = &saf1761_device_data_tx; need_sync = 1; } } else { if (is_host) { if (ep_type == UE_INTERRUPT) { temp.func = &saf1761_host_intr_data_tx; } else if (ep_type == UE_ISOCHRONOUS) { temp.func = &saf1761_host_isoc_data_tx; uframe_start = (SAF1761_READ_LE_4(sc, SOTG_FRINDEX) + 8) & (SOTG_FRINDEX_MASK & ~7); if (xfer->xroot->udev->speed == USB_SPEED_HIGH) uframe_interval = 1U << xfer->fps_shift; else uframe_interval = 8U; } else { temp.func = &saf1761_host_bulk_data_tx; } need_sync = 0; } else { temp.func = &saf1761_device_data_rx; need_sync = 0; } } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } else { need_sync = 0; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; } saf1761_otg_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; /* stamp the starting point for this transaction */ temp.td->uframe = uframe_start; /* advance to next */ uframe_start += uframe_interval; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } /* check for control transfer */ if (xfer->flags_int.control_xfr) { /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (xfer->endpointno & UE_DIR_IN) { if (is_host) { temp.func = &saf1761_host_bulk_data_tx; need_sync = 0; } else { temp.func = &saf1761_device_data_rx; need_sync = 0; } } else { if (is_host) { temp.func = &saf1761_host_bulk_data_rx; need_sync = 0; } else { temp.func = &saf1761_device_data_tx; need_sync = 1; } } temp.len = 0; temp.short_pkt = 0; saf1761_otg_setup_standard_chain_sub(&temp); /* data toggle should be DATA1 */ td = temp.td; td->set_toggle = 1; if (need_sync) { /* we need a SYNC point after TX */ temp.func = &saf1761_device_data_tx_sync; saf1761_otg_setup_standard_chain_sub(&temp); } } } else { if (need_sync) { temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* we need a SYNC point after TX */ temp.func = &saf1761_device_data_tx_sync; saf1761_otg_setup_standard_chain_sub(&temp); } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; if (is_host) { /* get first again */ td = xfer->td_transfer_first; td->toggle = (xfer->endpoint->toggle_next ? 1 : 0); } } static void saf1761_otg_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ saf1761_otg_device_done(xfer, USB_ERR_TIMEOUT); } static void saf1761_otg_intr_set(struct usb_xfer *xfer, uint8_t set) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); uint8_t ep_no = (xfer->endpointno & UE_ADDR); uint32_t mask; DPRINTFN(15, "endpoint=%d set=%d\n", xfer->endpointno, set); if (ep_no == 0) { mask = SOTG_DCINTERRUPT_IEPRX(0) | SOTG_DCINTERRUPT_IEPTX(0) | SOTG_DCINTERRUPT_IEP0SETUP; } else if (xfer->endpointno & UE_DIR_IN) { mask = SOTG_DCINTERRUPT_IEPTX(ep_no); } else { mask = SOTG_DCINTERRUPT_IEPRX(ep_no); } if (set) sc->sc_intr_enable |= mask; else sc->sc_intr_enable &= ~mask; SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); } static void saf1761_otg_start_standard_chain(struct usb_xfer *xfer) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); DPRINTFN(9, "\n"); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* poll one time */ saf1761_otg_xfer_do_fifo(sc, xfer); if (saf1761_otg_xfer_do_complete(sc, xfer) == 0) { /* * Only enable the endpoint interrupt when we are * actually waiting for data, hence we are dealing * with level triggered interrupts ! */ saf1761_otg_intr_set(xfer, 1); /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &saf1761_otg_timeout, xfer->timeout); } } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void saf1761_otg_root_intr(struct saf1761_otg_softc *sc) { DPRINTFN(9, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit - we only have one port */ sc->sc_hub_idata[0] = 0x02; uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t saf1761_otg_standard_done_sub(struct usb_xfer *xfer) { struct saf1761_otg_td *td; uint32_t len; usb_error_t error; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; /* store last data toggle */ xfer->endpoint->toggle_next = td->toggle; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error_any = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error_any) { /* the transfer is finished */ error = (td->error_stall ? USB_ERR_STALLED : USB_ERR_IOERROR); td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error); } static void saf1761_otg_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = saf1761_otg_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = saf1761_otg_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = saf1761_otg_standard_done_sub(xfer); } done: saf1761_otg_device_done(xfer, err); } /*------------------------------------------------------------------------* * saf1761_otg_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void saf1761_otg_device_done(struct usb_xfer *xfer, usb_error_t error) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); USB_BUS_SPIN_LOCK(&sc->sc_bus); if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { saf1761_otg_intr_set(xfer, 0); } else { struct saf1761_otg_td *td; td = xfer->td_transfer_cache; if (td != NULL) saf1761_host_channel_free(sc, td); } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void saf1761_otg_xfer_stall(struct usb_xfer *xfer) { saf1761_otg_device_done(xfer, USB_ERR_STALLED); } static void saf1761_otg_set_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall) { struct saf1761_otg_softc *sc; uint8_t ep_no; uint8_t ep_type; uint8_t ep_dir; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } DPRINTFN(5, "endpoint=%p\n", ep); /* set STALL bit */ sc = SAF1761_OTG_BUS2SC(udev->bus); ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); ep_dir = (ep->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)); ep_type = (ep->edesc->bmAttributes & UE_XFERTYPE); if (ep_type == UE_CONTROL) { /* should not happen */ return; } USB_BUS_SPIN_LOCK(&sc->sc_bus); /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (ep_no << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | ((ep_dir == UE_DIR_IN) ? SOTG_EP_INDEX_DIR_IN : SOTG_EP_INDEX_DIR_OUT)); /* set stall */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_STALL); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void saf1761_otg_clear_stall_sub_locked(struct saf1761_otg_softc *sc, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (ep_no << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | ((ep_dir == UE_DIR_IN) ? SOTG_EP_INDEX_DIR_IN : SOTG_EP_INDEX_DIR_OUT)); /* disable endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_TYPE, 0); /* enable endpoint again - will clear data toggle */ SAF1761_WRITE_LE_4(sc, SOTG_EP_TYPE, ep_type | SOTG_EP_TYPE_ENABLE); /* clear buffer */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_CLBUF); /* clear stall */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, 0); } static void saf1761_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct saf1761_otg_softc *sc; struct usb_endpoint_descriptor *ed; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(5, "endpoint=%p\n", ep); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = SAF1761_OTG_BUS2SC(udev->bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* get endpoint descriptor */ ed = ep->edesc; /* reset endpoint */ saf1761_otg_clear_stall_sub_locked(sc, (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } usb_error_t saf1761_otg_init(struct saf1761_otg_softc *sc) { const struct usb_hw_ep_profile *pf; uint32_t x; DPRINTF("\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_2_0; sc->sc_bus.methods = &saf1761_otg_bus_methods; USB_BUS_LOCK(&sc->sc_bus); /* Reset Host controller, including HW mode */ SAF1761_WRITE_LE_4(sc, SOTG_SW_RESET, SOTG_SW_RESET_ALL); DELAY(1000); /* Reset Host controller, including HW mode */ SAF1761_WRITE_LE_4(sc, SOTG_SW_RESET, SOTG_SW_RESET_HC); /* wait a bit */ DELAY(1000); SAF1761_WRITE_LE_4(sc, SOTG_SW_RESET, 0); /* wait a bit */ DELAY(1000); /* Enable interrupts */ sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_GLOBAL_INTR_EN | SOTG_HW_MODE_CTRL_COMN_INT; /* unlock device */ SAF1761_WRITE_LE_4(sc, SOTG_UNLOCK_DEVICE, SOTG_UNLOCK_DEVICE_CODE); /* * Set correct hardware mode, must be written twice if bus * width is changed: */ SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode); SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode); SAF1761_WRITE_LE_4(sc, SOTG_DCSCRATCH, 0xdeadbeef); SAF1761_WRITE_LE_4(sc, SOTG_HCSCRATCH, 0xdeadbeef); DPRINTF("DCID=0x%08x VEND_PROD=0x%08x HWMODE=0x%08x SCRATCH=0x%08x,0x%08x\n", SAF1761_READ_LE_4(sc, SOTG_DCCHIP_ID), SAF1761_READ_LE_4(sc, SOTG_VEND_PROD_ID), SAF1761_READ_LE_4(sc, SOTG_HW_MODE_CTRL), SAF1761_READ_LE_4(sc, SOTG_DCSCRATCH), SAF1761_READ_LE_4(sc, SOTG_HCSCRATCH)); /* reset device controller */ SAF1761_WRITE_LE_4(sc, SOTG_MODE, SOTG_MODE_SFRESET); SAF1761_WRITE_LE_4(sc, SOTG_MODE, 0); /* wait a bit */ DELAY(1000); /* reset host controller */ SAF1761_WRITE_LE_4(sc, SOTG_USBCMD, SOTG_USBCMD_HCRESET); /* wait for reset to clear */ for (x = 0; x != 10; x++) { if ((SAF1761_READ_LE_4(sc, SOTG_USBCMD) & SOTG_USBCMD_HCRESET) == 0) break; usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10); } SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode | SOTG_HW_MODE_CTRL_ALL_ATX_RESET); /* wait a bit */ DELAY(1000); SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode); /* wait a bit */ DELAY(1000); /* do a pulldown */ saf1761_otg_pull_down(sc); /* wait 10ms for pulldown to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); for (x = 1;; x++) { saf1761_otg_get_hw_ep_profile(NULL, &pf, x); if (pf == NULL) break; /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (x << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | SOTG_EP_INDEX_DIR_IN); /* select the maximum packet size */ SAF1761_WRITE_LE_4(sc, SOTG_EP_MAXPACKET, pf->max_in_frame_size); /* select the correct endpoint */ SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, (x << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | SOTG_EP_INDEX_DIR_OUT); /* select the maximum packet size */ SAF1761_WRITE_LE_4(sc, SOTG_EP_MAXPACKET, pf->max_out_frame_size); } /* enable interrupts */ SAF1761_WRITE_LE_4(sc, SOTG_MODE, SOTG_MODE_GLINTENA | SOTG_MODE_CLKAON | SOTG_MODE_WKUPCS); sc->sc_interrupt_cfg |= SOTG_INTERRUPT_CFG_CDBGMOD | SOTG_INTERRUPT_CFG_DDBGMODIN | SOTG_INTERRUPT_CFG_DDBGMODOUT; /* set default values */ SAF1761_WRITE_LE_4(sc, SOTG_INTERRUPT_CFG, sc->sc_interrupt_cfg); /* enable VBUS and ID interrupt */ SAF1761_WRITE_LE_4(sc, SOTG_IRQ_ENABLE_SET_CLR, SOTG_IRQ_ENABLE_CLR(0xFFFF)); SAF1761_WRITE_LE_4(sc, SOTG_IRQ_ENABLE_SET_CLR, SOTG_IRQ_ENABLE_SET(SOTG_IRQ_ID | SOTG_IRQ_VBUS_VLD)); /* enable interrupts */ sc->sc_intr_enable = SOTG_DCINTERRUPT_IEVBUS | SOTG_DCINTERRUPT_IEBRST | SOTG_DCINTERRUPT_IESUSP; SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); /* * Connect ATX port 1 to device controller, select external * charge pump and driver VBUS to +5V: */ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_SET_CLR, SOTG_CTRL_CLR(0xFFFF)); #ifdef __rtems__ SAF1761_WRITE_LE_4(sc, SOTG_CTRL_SET_CLR, SOTG_CTRL_SET(SOTG_CTRL_SEL_CP_EXT | SOTG_CTRL_VBUS_DRV)); #else SAF1761_WRITE_LE_4(sc, SOTG_CTRL_SET_CLR, SOTG_CTRL_SET(SOTG_CTRL_SW_SEL_HC_DC | SOTG_CTRL_BDIS_ACON_EN | SOTG_CTRL_SEL_CP_EXT | SOTG_CTRL_VBUS_DRV)); #endif /* disable device address */ SAF1761_WRITE_LE_4(sc, SOTG_ADDRESS, 0); /* enable host controller clock and preserve reserved bits */ x = SAF1761_READ_LE_4(sc, SOTG_POWER_DOWN); SAF1761_WRITE_LE_4(sc, SOTG_POWER_DOWN, x | SOTG_POWER_DOWN_HC_CLK_EN); /* wait 10ms for clock */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* enable configuration flag */ SAF1761_WRITE_LE_4(sc, SOTG_CONFIGFLAG, SOTG_CONFIGFLAG_ENABLE); /* clear RAM block */ for (x = 0x400; x != 0x10000; x += 4) SAF1761_WRITE_LE_4(sc, x, 0); /* start the HC */ SAF1761_WRITE_LE_4(sc, SOTG_USBCMD, SOTG_USBCMD_RS); DPRINTF("USBCMD=0x%08x\n", SAF1761_READ_LE_4(sc, SOTG_USBCMD)); /* make HC scan all PTDs */ SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_LAST_PTD, (1 << 31)); SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_LAST_PTD, (1 << 31)); SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_LAST_PTD, (1 << 31)); /* skip all PTDs by default */ SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, -1U); SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, -1U); SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, -1U); /* activate all PTD types */ SAF1761_WRITE_LE_4(sc, SOTG_HCBUFFERSTATUS, SOTG_HCBUFFERSTATUS_ISO_BUF_FILL | SOTG_HCBUFFERSTATUS_INT_BUF_FILL | SOTG_HCBUFFERSTATUS_ATL_BUF_FILL); /* we don't use the AND mask */ SAF1761_WRITE_LE_4(sc, SOTG_ISO_IRQ_MASK_AND, 0); SAF1761_WRITE_LE_4(sc, SOTG_INT_IRQ_MASK_AND, 0); SAF1761_WRITE_LE_4(sc, SOTG_ATL_IRQ_MASK_AND, 0); /* enable all PTD OR interrupts by default */ SAF1761_WRITE_LE_4(sc, SOTG_ISO_IRQ_MASK_OR, -1U); SAF1761_WRITE_LE_4(sc, SOTG_INT_IRQ_MASK_OR, -1U); SAF1761_WRITE_LE_4(sc, SOTG_ATL_IRQ_MASK_OR, -1U); /* enable HC interrupts */ SAF1761_WRITE_LE_4(sc, SOTG_HCINTERRUPT_ENABLE, SOTG_HCINTERRUPT_OTG_IRQ | SOTG_HCINTERRUPT_ISO_IRQ | SOTG_HCINTERRUPT_ALT_IRQ | SOTG_HCINTERRUPT_INT_IRQ); /* poll initial VBUS status */ saf1761_otg_update_vbus(sc); USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ saf1761_otg_do_poll(&sc->sc_bus); return (0); /* success */ } void saf1761_otg_uninit(struct saf1761_otg_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); /* disable all interrupts */ SAF1761_WRITE_LE_4(sc, SOTG_MODE, 0); sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; saf1761_otg_pull_down(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void saf1761_otg_suspend(struct saf1761_otg_softc *sc) { /* TODO */ } static void saf1761_otg_resume(struct saf1761_otg_softc *sc) { /* TODO */ } static void saf1761_otg_do_poll(struct usb_bus *bus) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); saf1761_otg_interrupt_poll_locked(sc); saf1761_otg_interrupt_complete_locked(sc); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * saf1761_otg control support * saf1761_otg interrupt support * saf1761_otg bulk support *------------------------------------------------------------------------*/ static void saf1761_otg_device_non_isoc_open(struct usb_xfer *xfer) { return; } static void saf1761_otg_device_non_isoc_close(struct usb_xfer *xfer) { saf1761_otg_device_done(xfer, USB_ERR_CANCELLED); } static void saf1761_otg_device_non_isoc_enter(struct usb_xfer *xfer) { return; } static void saf1761_otg_device_non_isoc_start(struct usb_xfer *xfer) { /* setup TDs */ saf1761_otg_setup_standard_chain(xfer); saf1761_otg_start_standard_chain(xfer); } static const struct usb_pipe_methods saf1761_otg_non_isoc_methods = { .open = saf1761_otg_device_non_isoc_open, .close = saf1761_otg_device_non_isoc_close, .enter = saf1761_otg_device_non_isoc_enter, .start = saf1761_otg_device_non_isoc_start, }; /*------------------------------------------------------------------------* * saf1761_otg device side isochronous support *------------------------------------------------------------------------*/ static void saf1761_otg_device_isoc_open(struct usb_xfer *xfer) { return; } static void saf1761_otg_device_isoc_close(struct usb_xfer *xfer) { saf1761_otg_device_done(xfer, USB_ERR_CANCELLED); } static void saf1761_otg_device_isoc_enter(struct usb_xfer *xfer) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index - we don't need the high bits */ nframes = SAF1761_READ_LE_4(sc, SOTG_FRAME_NUM); /* * check if the frame index is within the window where the * frames will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & SOTG_FRAME_NUM_SOFR_MASK; if ((xfer->endpoint->is_synced == 0) || (temp < xfer->nframes)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & SOTG_FRAME_NUM_SOFR_MASK; xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & SOTG_FRAME_NUM_SOFR_MASK; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + xfer->nframes; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += xfer->nframes; /* setup TDs */ saf1761_otg_setup_standard_chain(xfer); } static void saf1761_otg_device_isoc_start(struct usb_xfer *xfer) { /* start TD chain */ saf1761_otg_start_standard_chain(xfer); } static const struct usb_pipe_methods saf1761_otg_device_isoc_methods = { .open = saf1761_otg_device_isoc_open, .close = saf1761_otg_device_isoc_close, .enter = saf1761_otg_device_isoc_enter, .start = saf1761_otg_device_isoc_start, }; /*------------------------------------------------------------------------* * saf1761_otg host side isochronous support *------------------------------------------------------------------------*/ static void saf1761_otg_host_isoc_open(struct usb_xfer *xfer) { return; } static void saf1761_otg_host_isoc_close(struct usb_xfer *xfer) { saf1761_otg_device_done(xfer, USB_ERR_CANCELLED); } static void saf1761_otg_host_isoc_enter(struct usb_xfer *xfer) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index - we don't need the high bits */ nframes = (SAF1761_READ_LE_4(sc, SOTG_FRINDEX) & SOTG_FRINDEX_MASK) >> 3; /* * check if the frame index is within the window where the * frames will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & (SOTG_FRINDEX_MASK >> 3); if ((xfer->endpoint->is_synced == 0) || (temp < xfer->nframes)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & (SOTG_FRINDEX_MASK >> 3); xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & (SOTG_FRINDEX_MASK >> 3); /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + xfer->nframes; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += xfer->nframes; /* setup TDs */ saf1761_otg_setup_standard_chain(xfer); } static void saf1761_otg_host_isoc_start(struct usb_xfer *xfer) { /* start TD chain */ saf1761_otg_start_standard_chain(xfer); } static const struct usb_pipe_methods saf1761_otg_host_isoc_methods = { .open = saf1761_otg_host_isoc_open, .close = saf1761_otg_host_isoc_close, .enter = saf1761_otg_host_isoc_enter, .start = saf1761_otg_host_isoc_start, }; /*------------------------------------------------------------------------* * saf1761_otg root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_device_descriptor saf1761_otg_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, HSETW(.idVendor, 0x04cc), HSETW(.idProduct, 0x1761), .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct usb_device_qualifier saf1761_otg_odevd = { .bLength = sizeof(struct usb_device_qualifier), .bDescriptorType = UDESC_DEVICE_QUALIFIER, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize0 = 0, .bNumConfigurations = 0, }; static const struct saf1761_otg_config_desc saf1761_otg_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(saf1761_otg_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | SAF1761_OTG_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; static const struct usb_hub_descriptor_min saf1761_otg_hubd = { .bDescLength = sizeof(saf1761_otg_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = SOTG_NUM_PORTS, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "N\0X\0P" #define STRING_PRODUCT \ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, saf1761_otg_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, saf1761_otg_product); static usb_error_t saf1761_otg_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; uint32_t temp; uint32_t i; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: if (index == SOTG_HOST_PORT_NUM) goto tr_handle_clear_port_feature_host; else if (index == SOTG_DEVICE_PORT_NUM) goto tr_handle_clear_port_feature_device; else goto tr_stalled; case UR_SET_FEATURE: if (index == SOTG_HOST_PORT_NUM) goto tr_handle_set_port_feature_host; else if (index == SOTG_DEVICE_PORT_NUM) goto tr_handle_set_port_feature_device; else goto tr_stalled; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: if (index == SOTG_HOST_PORT_NUM) goto tr_handle_get_port_status_host; else if (index == SOTG_DEVICE_PORT_NUM) goto tr_handle_get_port_status_device; else goto tr_stalled; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) goto tr_stalled; len = sizeof(saf1761_otg_devd); ptr = (const void *)&saf1761_otg_devd; goto tr_valid; case UDESC_DEVICE_QUALIFIER: if (value & 0xff) goto tr_stalled; len = sizeof(saf1761_otg_odevd); ptr = (const void *)&saf1761_otg_odevd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) goto tr_stalled; len = sizeof(saf1761_otg_confd); ptr = (const void *)&saf1761_otg_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(saf1761_otg_vendor); ptr = (const void *)&saf1761_otg_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(saf1761_otg_product); ptr = (const void *)&saf1761_otg_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) goto tr_stalled; sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) goto tr_stalled; sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature_device: DPRINTFN(9, "UR_CLEAR_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: saf1761_otg_wakeup_peer(sc); break; case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; saf1761_otg_pull_down(sc); break; case UHF_C_PORT_CONNECTION: sc->sc_flags.change_connect = 0; break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto tr_valid; } goto tr_valid; tr_handle_clear_port_feature_host: DPRINTFN(9, "UR_CLEAR_FEATURE on port %d\n", index); temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); switch (value) { case UHF_PORT_ENABLE: SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PED); break; case UHF_PORT_SUSPEND: if ((temp & SOTG_PORTSC1_SUSP) && (!(temp & SOTG_PORTSC1_FPR))) SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_FPR); /* wait 20ms for resume sequence to complete */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~(SOTG_PORTSC1_SUSP | SOTG_PORTSC1_FPR | SOTG_PORTSC1_LS /* High Speed */ )); /* 4ms settle time */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); break; case UHF_PORT_INDICATOR: SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PIC); break; case UHF_PORT_TEST: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: case UHF_C_PORT_SUSPEND: /* NOPs */ break; case UHF_PORT_POWER: SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PP); break; case UHF_C_PORT_CONNECTION: SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_ECSC); break; default: err = USB_ERR_IOERROR; goto tr_valid; } goto tr_valid; tr_handle_set_port_feature_device: DPRINTFN(9, "UR_SET_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 1; break; case UHF_PORT_SUSPEND: case UHF_PORT_RESET: case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; break; default: err = USB_ERR_IOERROR; goto tr_valid; } goto tr_valid; tr_handle_set_port_feature_host: DPRINTFN(9, "UR_SET_FEATURE on port %d\n", index); temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); switch (value) { case UHF_PORT_ENABLE: SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PED); break; case UHF_PORT_SUSPEND: SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_SUSP); break; case UHF_PORT_RESET: DPRINTFN(6, "reset port %d\n", index); /* Start reset sequence. */ temp &= ~(SOTG_PORTSC1_PED | SOTG_PORTSC1_PR); SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PR); /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(usb_port_root_reset_delay)); SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp); /* Wait for HC to complete reset. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(2)); temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); DPRINTF("After reset, status=0x%08x\n", temp); if (temp & SOTG_PORTSC1_PR) { device_printf(sc->sc_bus.bdev, "port reset timeout\n"); err = USB_ERR_TIMEOUT; goto tr_valid; } if (!(temp & SOTG_PORTSC1_PED)) { /* Not a high speed device, give up ownership.*/ SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PO); break; } sc->sc_isreset = 1; DPRINTF("port %d reset, status = 0x%08x\n", index, temp); break; case UHF_PORT_POWER: DPRINTFN(3, "set port power %d\n", index); SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PP); break; case UHF_PORT_TEST: DPRINTFN(3, "set port test %d\n", index); break; case UHF_PORT_INDICATOR: DPRINTFN(3, "set port ind %d\n", index); SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PIC); break; default: err = USB_ERR_IOERROR; goto tr_valid; } goto tr_valid; tr_handle_get_port_status_device: DPRINTFN(9, "UR_GET_PORT_STATUS on port %d\n", index); if (sc->sc_flags.status_vbus) { saf1761_otg_pull_up(sc); } else { saf1761_otg_pull_down(sc); } /* Select FULL-speed and Device Side Mode */ value = UPS_PORT_MODE_DEVICE; if (sc->sc_flags.port_powered) value |= UPS_PORT_POWER; if (sc->sc_flags.port_enabled) value |= UPS_PORT_ENABLED; if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) value |= UPS_CURRENT_CONNECT_STATUS; if (sc->sc_flags.status_suspend) value |= UPS_SUSPEND; USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_connect) value |= UPS_C_CONNECT_STATUS; if (sc->sc_flags.change_suspend) value |= UPS_C_SUSPEND; USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_port_status_host: temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); DPRINTFN(9, "UR_GET_PORT_STATUS on port %d = 0x%08x\n", index, temp); i = UPS_HIGH_SPEED; if (temp & SOTG_PORTSC1_ECCS) i |= UPS_CURRENT_CONNECT_STATUS; if (temp & SOTG_PORTSC1_PED) i |= UPS_PORT_ENABLED; if ((temp & SOTG_PORTSC1_SUSP) && !(temp & SOTG_PORTSC1_FPR)) i |= UPS_SUSPEND; if (temp & SOTG_PORTSC1_PR) i |= UPS_RESET; if (temp & SOTG_PORTSC1_PP) i |= UPS_PORT_POWER; USETW(sc->sc_hub_temp.ps.wPortStatus, i); i = 0; if (temp & SOTG_PORTSC1_ECSC) i |= UPS_C_CONNECT_STATUS; if (temp & SOTG_PORTSC1_FPR) i |= UPS_C_SUSPEND; if (sc->sc_isreset) i |= UPS_C_PORT_RESET; USETW(sc->sc_hub_temp.ps.wPortChange, i); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) goto tr_stalled; ptr = (const void *)&saf1761_otg_hubd; len = sizeof(saf1761_otg_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: *plength = len; *pptr = ptr; return (err); } static void saf1761_otg_xfer_setup(struct usb_setup_params *parm) { struct saf1761_otg_softc *sc; struct usb_xfer *xfer; void *last_obj; uint32_t dw1; uint32_t ntd; uint32_t n; uint8_t ep_no; uint8_t ep_type; sc = SAF1761_OTG_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x500; usbd_transfer_setup_sub(parm); /* * Compute maximum number of TDs: */ ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE); if (ep_type == UE_CONTROL) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; } else { ntd = xfer->nframes + 1 /* SYNC */ ; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) return; /* * allocate transfer descriptors */ last_obj = NULL; ep_no = xfer->endpointno & UE_ADDR; /* * Check profile stuff */ if (parm->udev->flags.usb_mode == USB_MODE_DEVICE) { const struct usb_hw_ep_profile *pf; saf1761_otg_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } } dw1 = (xfer->address << 3) | (ep_type << 12); switch (parm->udev->speed) { case USB_SPEED_FULL: case USB_SPEED_LOW: /* check if root HUB port is running High Speed */ if (parm->udev->parent_hs_hub != NULL) { dw1 |= SOTG_PTD_DW1_ENABLE_SPLIT; dw1 |= (parm->udev->hs_port_no << 18); dw1 |= (parm->udev->hs_hub_addr << 25); if (parm->udev->speed == USB_SPEED_LOW) dw1 |= (1 << 17); } break; default: break; } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct saf1761_otg_td *td; if (parm->buf) { td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* init TD */ td->max_packet_size = xfer->max_packet_size; td->ep_index = ep_no; td->ep_type = ep_type; td->dw1_value = dw1; td->uframe = 0; if (ep_type == UE_INTERRUPT) { if (xfer->interval > 32) td->interval = (32 / 2) << 3; else td->interval = (xfer->interval / 2) << 3; } else { td->interval = 0; } td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void saf1761_otg_xfer_unsetup(struct usb_xfer *xfer) { } static void saf1761_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { uint16_t mps; DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode); if (udev->parent_hub == NULL) { /* root HUB has special endpoint handling */ return; } /* Verify wMaxPacketSize */ mps = UGETW(edesc->wMaxPacketSize); if (udev->speed == USB_SPEED_HIGH) { if ((mps >> 11) & 3) { DPRINTF("A packet multiplier different from " "1 is not supported\n"); return; } } if (mps > SOTG_HS_MAX_PACKET_SIZE) { DPRINTF("Packet size %d bigger than %d\n", (int)mps, SOTG_HS_MAX_PACKET_SIZE); return; } if (udev->flags.usb_mode == USB_MODE_DEVICE) { if (udev->speed != USB_SPEED_FULL && udev->speed != USB_SPEED_HIGH) { /* not supported */ return; } switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_ISOCHRONOUS: ep->methods = &saf1761_otg_device_isoc_methods; break; default: ep->methods = &saf1761_otg_non_isoc_methods; break; } } else { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_ISOCHRONOUS: ep->methods = &saf1761_otg_host_isoc_methods; break; default: ep->methods = &saf1761_otg_non_isoc_methods; break; } } } static void saf1761_otg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: saf1761_otg_suspend(sc); break; case USB_HW_POWER_SHUTDOWN: saf1761_otg_uninit(sc); break; case USB_HW_POWER_RESUME: saf1761_otg_resume(sc); break; default: break; } } static void saf1761_otg_device_resume(struct usb_device *udev) { struct saf1761_otg_softc *sc; struct saf1761_otg_td *td; struct usb_xfer *xfer; uint8_t x; DPRINTF("\n"); if (udev->flags.usb_mode != USB_MODE_HOST) return; sc = SAF1761_OTG_BUS2SC(udev->bus); USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev != udev) continue; td = xfer->td_transfer_cache; if (td == NULL || td->channel >= SOTG_HOST_CHANNEL_MAX) continue; switch (td->ep_type) { case UE_INTERRUPT: x = td->channel - 32; sc->sc_host_intr_suspend_map &= ~(1U << x); SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); break; case UE_ISOCHRONOUS: x = td->channel; sc->sc_host_isoc_suspend_map &= ~(1U << x); SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); break; default: x = td->channel - 64; sc->sc_host_async_suspend_map &= ~(1U << x); SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); break; } } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); /* poll all transfers again to restart resumed ones */ saf1761_otg_do_poll(&sc->sc_bus); } static void saf1761_otg_device_suspend(struct usb_device *udev) { struct saf1761_otg_softc *sc; struct saf1761_otg_td *td; struct usb_xfer *xfer; uint8_t x; DPRINTF("\n"); if (udev->flags.usb_mode != USB_MODE_HOST) return; sc = SAF1761_OTG_BUS2SC(udev->bus); USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev != udev) continue; td = xfer->td_transfer_cache; if (td == NULL || td->channel >= SOTG_HOST_CHANNEL_MAX) continue; switch (td->ep_type) { case UE_INTERRUPT: x = td->channel - 32; sc->sc_host_intr_suspend_map |= (1U << x); SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); break; case UE_ISOCHRONOUS: x = td->channel; sc->sc_host_isoc_suspend_map |= (1U << x); SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); break; default: x = td->channel - 64; sc->sc_host_async_suspend_map |= (1U << x); SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); break; } } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } static const struct usb_bus_methods saf1761_otg_bus_methods = { .endpoint_init = &saf1761_otg_ep_init, .xfer_setup = &saf1761_otg_xfer_setup, .xfer_unsetup = &saf1761_otg_xfer_unsetup, .get_hw_ep_profile = &saf1761_otg_get_hw_ep_profile, .xfer_stall = &saf1761_otg_xfer_stall, .set_stall = &saf1761_otg_set_stall, .clear_stall = &saf1761_otg_clear_stall, .roothub_exec = &saf1761_otg_roothub_exec, .xfer_poll = &saf1761_otg_do_poll, .set_hw_power_sleep = saf1761_otg_set_hw_power_sleep, .device_resume = &saf1761_otg_device_resume, .device_suspend = &saf1761_otg_device_suspend, }; Index: head/sys/dev/usb/controller/uhci.c =================================================================== --- head/sys/dev/usb/controller/uhci.c (revision 357971) +++ head/sys/dev/usb/controller/uhci.c (revision 357972) @@ -1,3234 +1,3235 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB Universal Host Controller driver. * Handles e.g. PIIX3 and PIIX4. * * UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm * USB spec: http://www.usb.org/developers/docs/usbspec.zip * PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf * ftp://download.intel.com/design/intarch/datashts/29056201.pdf */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR uhcidebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define alt_next next #define UHCI_BUS2SC(bus) \ ((uhci_softc_t *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((uhci_softc_t *)0)->sc_bus)))) #ifdef USB_DEBUG static int uhcidebug = 0; static int uhcinoloop = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uhci"); SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RWTUN, &uhcidebug, 0, "uhci debug level"); SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RWTUN, &uhcinoloop, 0, "uhci noloop"); static void uhci_dumpregs(uhci_softc_t *sc); static void uhci_dump_tds(uhci_td_t *td); #endif #define UBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) #define UWRITE1(sc, r, x) \ do { UBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ } while (/*CONSTCOND*/0) #define UWRITE2(sc, r, x) \ do { UBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ } while (/*CONSTCOND*/0) #define UWRITE4(sc, r, x) \ do { UBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ } while (/*CONSTCOND*/0) #define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd) #define UHCISTS(sc) UREAD2(sc, UHCI_STS) #define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */ #define UHCI_INTR_ENDPT 1 struct uhci_mem_layout { struct usb_page_search buf_res; struct usb_page_search fix_res; struct usb_page_cache *buf_pc; struct usb_page_cache *fix_pc; uint32_t buf_offset; uint16_t max_frame_size; }; struct uhci_std_temp { struct uhci_mem_layout ml; uhci_td_t *td; uhci_td_t *td_next; uint32_t average; uint32_t td_status; uint32_t td_token; uint32_t len; uint16_t max_frame_size; uint8_t shortpkt; uint8_t setup_alt_next; uint8_t last_frame; }; static const struct usb_bus_methods uhci_bus_methods; static const struct usb_pipe_methods uhci_device_bulk_methods; static const struct usb_pipe_methods uhci_device_ctrl_methods; static const struct usb_pipe_methods uhci_device_intr_methods; static const struct usb_pipe_methods uhci_device_isoc_methods; static uint8_t uhci_restart(uhci_softc_t *sc); static void uhci_do_poll(struct usb_bus *); static void uhci_device_done(struct usb_xfer *, usb_error_t); static void uhci_transfer_intr_enqueue(struct usb_xfer *); static void uhci_timeout(void *); static uint8_t uhci_check_transfer(struct usb_xfer *); static void uhci_root_intr(uhci_softc_t *sc); void uhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) { struct uhci_softc *sc = UHCI_BUS2SC(bus); uint32_t i; cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, sizeof(uint32_t) * UHCI_FRAMELIST_COUNT, UHCI_FRAMELIST_ALIGN); cb(bus, &sc->sc_hw.ls_ctl_start_pc, &sc->sc_hw.ls_ctl_start_pg, sizeof(uhci_qh_t), UHCI_QH_ALIGN); cb(bus, &sc->sc_hw.fs_ctl_start_pc, &sc->sc_hw.fs_ctl_start_pg, sizeof(uhci_qh_t), UHCI_QH_ALIGN); cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, sizeof(uhci_qh_t), UHCI_QH_ALIGN); cb(bus, &sc->sc_hw.last_qh_pc, &sc->sc_hw.last_qh_pg, sizeof(uhci_qh_t), UHCI_QH_ALIGN); cb(bus, &sc->sc_hw.last_td_pc, &sc->sc_hw.last_td_pg, sizeof(uhci_td_t), UHCI_TD_ALIGN); for (i = 0; i != UHCI_VFRAMELIST_COUNT; i++) { cb(bus, sc->sc_hw.isoc_start_pc + i, sc->sc_hw.isoc_start_pg + i, sizeof(uhci_td_t), UHCI_TD_ALIGN); } for (i = 0; i != UHCI_IFRAMELIST_COUNT; i++) { cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i, sizeof(uhci_qh_t), UHCI_QH_ALIGN); } } static void uhci_mem_layout_init(struct uhci_mem_layout *ml, struct usb_xfer *xfer) { ml->buf_pc = xfer->frbuffers + 0; ml->fix_pc = xfer->buf_fixup; ml->buf_offset = 0; ml->max_frame_size = xfer->max_frame_size; } static void uhci_mem_layout_fixup(struct uhci_mem_layout *ml, struct uhci_td *td) { usbd_get_page(ml->buf_pc, ml->buf_offset, &ml->buf_res); if (ml->buf_res.length < td->len) { /* need to do a fixup */ usbd_get_page(ml->fix_pc, 0, &ml->fix_res); td->td_buffer = htole32(ml->fix_res.physaddr); /* * The UHCI driver cannot handle * page crossings, so a fixup is * needed: * * +----+----+ - - - * | YYY|Y | * +----+----+ - - - * \ \ * \ \ * +----+ * |YYYY| (fixup) * +----+ */ if ((td->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) { td->fix_pc = ml->fix_pc; usb_pc_cpu_invalidate(ml->fix_pc); } else { td->fix_pc = NULL; /* copy data to fixup location */ usbd_copy_out(ml->buf_pc, ml->buf_offset, ml->fix_res.buffer, td->len); usb_pc_cpu_flush(ml->fix_pc); } /* prepare next fixup */ ml->fix_pc++; } else { td->td_buffer = htole32(ml->buf_res.physaddr); td->fix_pc = NULL; } /* prepare next data location */ ml->buf_offset += td->len; } /* * Return values: * 0: Success * Else: Failure */ static uint8_t uhci_restart(uhci_softc_t *sc) { struct usb_page_search buf_res; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); if (UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS) { DPRINTFN(2, "Already started\n"); return (0); } DPRINTFN(2, "Restarting\n"); usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); /* Reload fresh base address */ UWRITE4(sc, UHCI_FLBASEADDR, buf_res.physaddr); /* * Assume 64 byte packets at frame end and start HC controller: */ UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS)); /* wait 10 milliseconds */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* check that controller has started */ if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) { DPRINTFN(2, "Failed\n"); return (1); } return (0); } void uhci_reset(uhci_softc_t *sc) { uint16_t n; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTF("resetting the HC\n"); /* disable interrupts */ UWRITE2(sc, UHCI_INTR, 0); /* global reset */ UHCICMD(sc, UHCI_CMD_GRESET); /* wait */ usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); /* terminate all transfers */ UHCICMD(sc, UHCI_CMD_HCRESET); /* the reset bit goes low when the controller is done */ n = UHCI_RESET_TIMEOUT; while (n--) { /* wait one millisecond */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET)) { goto done_1; } } device_printf(sc->sc_bus.bdev, "controller did not reset\n"); done_1: n = 10; while (n--) { /* wait one millisecond */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); /* check if HC is stopped */ if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) { goto done_2; } } device_printf(sc->sc_bus.bdev, "controller did not stop\n"); done_2: /* reset frame number */ UWRITE2(sc, UHCI_FRNUM, 0); /* set default SOF value */ UWRITE1(sc, UHCI_SOF, 0x40); USB_BUS_UNLOCK(&sc->sc_bus); /* stop root interrupt */ usb_callout_drain(&sc->sc_root_intr); USB_BUS_LOCK(&sc->sc_bus); } static void uhci_start(uhci_softc_t *sc) { USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(2, "enabling\n"); /* enable interrupts */ UWRITE2(sc, UHCI_INTR, (UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | UHCI_INTR_IOCE | UHCI_INTR_SPIE)); if (uhci_restart(sc)) { device_printf(sc->sc_bus.bdev, "cannot start HC controller\n"); } /* start root interrupt */ uhci_root_intr(sc); } static struct uhci_qh * uhci_init_qh(struct usb_page_cache *pc) { struct usb_page_search buf_res; struct uhci_qh *qh; usbd_get_page(pc, 0, &buf_res); qh = buf_res.buffer; qh->qh_self = htole32(buf_res.physaddr) | htole32(UHCI_PTR_QH); qh->page_cache = pc; return (qh); } static struct uhci_td * uhci_init_td(struct usb_page_cache *pc) { struct usb_page_search buf_res; struct uhci_td *td; usbd_get_page(pc, 0, &buf_res); td = buf_res.buffer; td->td_self = htole32(buf_res.physaddr) | htole32(UHCI_PTR_TD); td->page_cache = pc; return (td); } usb_error_t uhci_init(uhci_softc_t *sc) { uint16_t bit; uint16_t x; uint16_t y; DPRINTF("start\n"); usb_callout_init_mtx(&sc->sc_root_intr, &sc->sc_bus.bus_mtx, 0); #ifdef USB_DEBUG if (uhcidebug > 2) { uhci_dumpregs(sc); } #endif /* * Setup QH's */ sc->sc_ls_ctl_p_last = uhci_init_qh(&sc->sc_hw.ls_ctl_start_pc); sc->sc_fs_ctl_p_last = uhci_init_qh(&sc->sc_hw.fs_ctl_start_pc); sc->sc_bulk_p_last = uhci_init_qh(&sc->sc_hw.bulk_start_pc); #if 0 sc->sc_reclaim_qh_p = sc->sc_fs_ctl_p_last; #else /* setup reclaim looping point */ sc->sc_reclaim_qh_p = sc->sc_bulk_p_last; #endif sc->sc_last_qh_p = uhci_init_qh(&sc->sc_hw.last_qh_pc); sc->sc_last_td_p = uhci_init_td(&sc->sc_hw.last_td_pc); for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) { sc->sc_isoc_p_last[x] = uhci_init_td(sc->sc_hw.isoc_start_pc + x); } for (x = 0; x != UHCI_IFRAMELIST_COUNT; x++) { sc->sc_intr_p_last[x] = uhci_init_qh(sc->sc_hw.intr_start_pc + x); } /* * the QHs are arranged to give poll intervals that are * powers of 2 times 1ms */ bit = UHCI_IFRAMELIST_COUNT / 2; while (bit) { x = bit; while (x & bit) { uhci_qh_t *qh_x; uhci_qh_t *qh_y; y = (x ^ bit) | (bit / 2); /* * the next QH has half the poll interval */ qh_x = sc->sc_intr_p_last[x]; qh_y = sc->sc_intr_p_last[y]; qh_x->h_next = NULL; qh_x->qh_h_next = qh_y->qh_self; qh_x->e_next = NULL; qh_x->qh_e_next = htole32(UHCI_PTR_T); x++; } bit >>= 1; } if (1) { uhci_qh_t *qh_ls; uhci_qh_t *qh_intr; qh_ls = sc->sc_ls_ctl_p_last; qh_intr = sc->sc_intr_p_last[0]; /* start QH for interrupt traffic */ qh_intr->h_next = qh_ls; qh_intr->qh_h_next = qh_ls->qh_self; qh_intr->e_next = 0; qh_intr->qh_e_next = htole32(UHCI_PTR_T); } for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) { uhci_td_t *td_x; uhci_qh_t *qh_intr; td_x = sc->sc_isoc_p_last[x]; qh_intr = sc->sc_intr_p_last[x | (UHCI_IFRAMELIST_COUNT / 2)]; /* start TD for isochronous traffic */ td_x->next = NULL; td_x->td_next = qh_intr->qh_self; td_x->td_status = htole32(UHCI_TD_IOS); td_x->td_token = htole32(0); td_x->td_buffer = htole32(0); } if (1) { uhci_qh_t *qh_ls; uhci_qh_t *qh_fs; qh_ls = sc->sc_ls_ctl_p_last; qh_fs = sc->sc_fs_ctl_p_last; /* start QH where low speed control traffic will be queued */ qh_ls->h_next = qh_fs; qh_ls->qh_h_next = qh_fs->qh_self; qh_ls->e_next = 0; qh_ls->qh_e_next = htole32(UHCI_PTR_T); } if (1) { uhci_qh_t *qh_ctl; uhci_qh_t *qh_blk; uhci_qh_t *qh_lst; uhci_td_t *td_lst; qh_ctl = sc->sc_fs_ctl_p_last; qh_blk = sc->sc_bulk_p_last; /* start QH where full speed control traffic will be queued */ qh_ctl->h_next = qh_blk; qh_ctl->qh_h_next = qh_blk->qh_self; qh_ctl->e_next = 0; qh_ctl->qh_e_next = htole32(UHCI_PTR_T); qh_lst = sc->sc_last_qh_p; /* start QH where bulk traffic will be queued */ qh_blk->h_next = qh_lst; qh_blk->qh_h_next = qh_lst->qh_self; qh_blk->e_next = 0; qh_blk->qh_e_next = htole32(UHCI_PTR_T); td_lst = sc->sc_last_td_p; /* end QH which is used for looping the QHs */ qh_lst->h_next = 0; qh_lst->qh_h_next = htole32(UHCI_PTR_T); /* end of QH chain */ qh_lst->e_next = td_lst; qh_lst->qh_e_next = td_lst->td_self; /* * end TD which hangs from the last QH, to avoid a bug in the PIIX * that makes it run berserk otherwise */ td_lst->next = 0; td_lst->td_next = htole32(UHCI_PTR_T); td_lst->td_status = htole32(0); /* inactive */ td_lst->td_token = htole32(0); td_lst->td_buffer = htole32(0); } if (1) { struct usb_page_search buf_res; uint32_t *pframes; usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); pframes = buf_res.buffer; /* * Setup UHCI framelist * * Execution order: * * pframes -> full speed isochronous -> interrupt QH's -> low * speed control -> full speed control -> bulk transfers * */ for (x = 0; x != UHCI_FRAMELIST_COUNT; x++) { pframes[x] = sc->sc_isoc_p_last[x % UHCI_VFRAMELIST_COUNT]->td_self; } } /* flush all cache into memory */ usb_bus_mem_flush_all(&sc->sc_bus, &uhci_iterate_hw_softc); /* set up the bus struct */ sc->sc_bus.methods = &uhci_bus_methods; USB_BUS_LOCK(&sc->sc_bus); /* reset the controller */ uhci_reset(sc); /* start the controller */ uhci_start(sc); USB_BUS_UNLOCK(&sc->sc_bus); /* catch lost interrupts */ uhci_do_poll(&sc->sc_bus); return (0); } static void uhci_suspend(uhci_softc_t *sc) { #ifdef USB_DEBUG if (uhcidebug > 2) { uhci_dumpregs(sc); } #endif USB_BUS_LOCK(&sc->sc_bus); /* stop the controller */ uhci_reset(sc); /* enter global suspend */ UHCICMD(sc, UHCI_CMD_EGSM); USB_BUS_UNLOCK(&sc->sc_bus); } static void uhci_resume(uhci_softc_t *sc) { USB_BUS_LOCK(&sc->sc_bus); /* reset the controller */ uhci_reset(sc); /* force global resume */ UHCICMD(sc, UHCI_CMD_FGR); /* and start traffic again */ uhci_start(sc); USB_BUS_UNLOCK(&sc->sc_bus); #ifdef USB_DEBUG if (uhcidebug > 2) uhci_dumpregs(sc); #endif /* catch lost interrupts */ uhci_do_poll(&sc->sc_bus); } #ifdef USB_DEBUG static void uhci_dumpregs(uhci_softc_t *sc) { DPRINTFN(0, "%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, " "flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n", device_get_nameunit(sc->sc_bus.bdev), UREAD2(sc, UHCI_CMD), UREAD2(sc, UHCI_STS), UREAD2(sc, UHCI_INTR), UREAD2(sc, UHCI_FRNUM), UREAD4(sc, UHCI_FLBASEADDR), UREAD1(sc, UHCI_SOF), UREAD2(sc, UHCI_PORTSC1), UREAD2(sc, UHCI_PORTSC2)); } static uint8_t uhci_dump_td(uhci_td_t *p) { uint32_t td_next; uint32_t td_status; uint32_t td_token; uint8_t temp; usb_pc_cpu_invalidate(p->page_cache); td_next = le32toh(p->td_next); td_status = le32toh(p->td_status); td_token = le32toh(p->td_token); /* * Check whether the link pointer in this TD marks the link pointer * as end of queue: */ temp = ((td_next & UHCI_PTR_T) || (td_next == 0)); printf("TD(%p) at 0x%08x = link=0x%08x status=0x%08x " "token=0x%08x buffer=0x%08x\n", p, le32toh(p->td_self), td_next, td_status, td_token, le32toh(p->td_buffer)); printf("TD(%p) td_next=%s%s%s td_status=%s%s%s%s%s%s%s%s%s%s%s, errcnt=%d, actlen=%d pid=%02x," "addr=%d,endpt=%d,D=%d,maxlen=%d\n", p, (td_next & 1) ? "-T" : "", (td_next & 2) ? "-Q" : "", (td_next & 4) ? "-VF" : "", (td_status & UHCI_TD_BITSTUFF) ? "-BITSTUFF" : "", (td_status & UHCI_TD_CRCTO) ? "-CRCTO" : "", (td_status & UHCI_TD_NAK) ? "-NAK" : "", (td_status & UHCI_TD_BABBLE) ? "-BABBLE" : "", (td_status & UHCI_TD_DBUFFER) ? "-DBUFFER" : "", (td_status & UHCI_TD_STALLED) ? "-STALLED" : "", (td_status & UHCI_TD_ACTIVE) ? "-ACTIVE" : "", (td_status & UHCI_TD_IOC) ? "-IOC" : "", (td_status & UHCI_TD_IOS) ? "-IOS" : "", (td_status & UHCI_TD_LS) ? "-LS" : "", (td_status & UHCI_TD_SPD) ? "-SPD" : "", UHCI_TD_GET_ERRCNT(td_status), UHCI_TD_GET_ACTLEN(td_status), UHCI_TD_GET_PID(td_token), UHCI_TD_GET_DEVADDR(td_token), UHCI_TD_GET_ENDPT(td_token), UHCI_TD_GET_DT(td_token), UHCI_TD_GET_MAXLEN(td_token)); return (temp); } static uint8_t uhci_dump_qh(uhci_qh_t *sqh) { uint8_t temp; uint32_t qh_h_next; uint32_t qh_e_next; usb_pc_cpu_invalidate(sqh->page_cache); qh_h_next = le32toh(sqh->qh_h_next); qh_e_next = le32toh(sqh->qh_e_next); DPRINTFN(0, "QH(%p) at 0x%08x: h_next=0x%08x e_next=0x%08x\n", sqh, le32toh(sqh->qh_self), qh_h_next, qh_e_next); temp = ((((sqh->h_next != NULL) && !(qh_h_next & UHCI_PTR_T)) ? 1 : 0) | (((sqh->e_next != NULL) && !(qh_e_next & UHCI_PTR_T)) ? 2 : 0)); return (temp); } static void uhci_dump_all(uhci_softc_t *sc) { uhci_dumpregs(sc); uhci_dump_qh(sc->sc_ls_ctl_p_last); uhci_dump_qh(sc->sc_fs_ctl_p_last); uhci_dump_qh(sc->sc_bulk_p_last); uhci_dump_qh(sc->sc_last_qh_p); } static void uhci_dump_tds(uhci_td_t *td) { for (; td != NULL; td = td->obj_next) { if (uhci_dump_td(td)) { break; } } } #endif /* * Let the last QH loop back to the full speed control transfer QH. * This is what intel calls "bandwidth reclamation" and improves * USB performance a lot for some devices. * If we are already looping, just count it. */ static void uhci_add_loop(uhci_softc_t *sc) { struct uhci_qh *qh_lst; struct uhci_qh *qh_rec; #ifdef USB_DEBUG if (uhcinoloop) { return; } #endif if (++(sc->sc_loops) == 1) { DPRINTFN(6, "add\n"); qh_lst = sc->sc_last_qh_p; qh_rec = sc->sc_reclaim_qh_p; /* NOTE: we don't loop back the soft pointer */ qh_lst->qh_h_next = qh_rec->qh_self; usb_pc_cpu_flush(qh_lst->page_cache); } } static void uhci_rem_loop(uhci_softc_t *sc) { struct uhci_qh *qh_lst; #ifdef USB_DEBUG if (uhcinoloop) { return; } #endif if (--(sc->sc_loops) == 0) { DPRINTFN(6, "remove\n"); qh_lst = sc->sc_last_qh_p; qh_lst->qh_h_next = htole32(UHCI_PTR_T); usb_pc_cpu_flush(qh_lst->page_cache); } } static void uhci_transfer_intr_enqueue(struct usb_xfer *xfer) { /* check for early completion */ if (uhci_check_transfer(xfer)) { return; } /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout); } } #define UHCI_APPEND_TD(std,last) (last) = _uhci_append_td(std,last) static uhci_td_t * _uhci_append_td(uhci_td_t *std, uhci_td_t *last) { DPRINTFN(11, "%p to %p\n", std, last); /* (sc->sc_bus.mtx) must be locked */ std->next = last->next; std->td_next = last->td_next; std->prev = last; usb_pc_cpu_flush(std->page_cache); /* * the last->next->prev is never followed: std->next->prev = std; */ last->next = std; last->td_next = std->td_self; usb_pc_cpu_flush(last->page_cache); return (std); } #define UHCI_APPEND_QH(sqh,last) (last) = _uhci_append_qh(sqh,last) static uhci_qh_t * _uhci_append_qh(uhci_qh_t *sqh, uhci_qh_t *last) { DPRINTFN(11, "%p to %p\n", sqh, last); if (sqh->h_prev != NULL) { /* should not happen */ DPRINTFN(0, "QH already linked!\n"); return (last); } /* (sc->sc_bus.mtx) must be locked */ sqh->h_next = last->h_next; sqh->qh_h_next = last->qh_h_next; sqh->h_prev = last; usb_pc_cpu_flush(sqh->page_cache); /* * The "last->h_next->h_prev" is never followed: * * "sqh->h_next->h_prev" = sqh; */ last->h_next = sqh; last->qh_h_next = sqh->qh_self; usb_pc_cpu_flush(last->page_cache); return (sqh); } /**/ #define UHCI_REMOVE_TD(std,last) (last) = _uhci_remove_td(std,last) static uhci_td_t * _uhci_remove_td(uhci_td_t *std, uhci_td_t *last) { DPRINTFN(11, "%p from %p\n", std, last); /* (sc->sc_bus.mtx) must be locked */ std->prev->next = std->next; std->prev->td_next = std->td_next; usb_pc_cpu_flush(std->prev->page_cache); if (std->next) { std->next->prev = std->prev; usb_pc_cpu_flush(std->next->page_cache); } return ((last == std) ? std->prev : last); } #define UHCI_REMOVE_QH(sqh,last) (last) = _uhci_remove_qh(sqh,last) static uhci_qh_t * _uhci_remove_qh(uhci_qh_t *sqh, uhci_qh_t *last) { DPRINTFN(11, "%p from %p\n", sqh, last); /* (sc->sc_bus.mtx) must be locked */ /* only remove if not removed from a queue */ if (sqh->h_prev) { sqh->h_prev->h_next = sqh->h_next; sqh->h_prev->qh_h_next = sqh->qh_h_next; usb_pc_cpu_flush(sqh->h_prev->page_cache); if (sqh->h_next) { sqh->h_next->h_prev = sqh->h_prev; usb_pc_cpu_flush(sqh->h_next->page_cache); } last = ((last == sqh) ? sqh->h_prev : last); sqh->h_prev = 0; usb_pc_cpu_flush(sqh->page_cache); } return (last); } static void uhci_isoc_done(uhci_softc_t *sc, struct usb_xfer *xfer) { struct usb_page_search res; uint32_t nframes = xfer->nframes; uint32_t status; uint32_t offset = 0; uint32_t *plen = xfer->frlengths; uint16_t len = 0; uhci_td_t *td = xfer->td_transfer_first; uhci_td_t **pp_last = &sc->sc_isoc_p_last[xfer->qh_pos]; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* sync any DMA memory before doing fixups */ usb_bdma_post_sync(xfer); while (nframes--) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) { pp_last = &sc->sc_isoc_p_last[0]; } #ifdef USB_DEBUG if (uhcidebug > 5) { DPRINTF("isoc TD\n"); uhci_dump_td(td); } #endif usb_pc_cpu_invalidate(td->page_cache); status = le32toh(td->td_status); len = UHCI_TD_GET_ACTLEN(status); if (len > *plen) { len = *plen; } if (td->fix_pc) { usbd_get_page(td->fix_pc, 0, &res); /* copy data from fixup location to real location */ usb_pc_cpu_invalidate(td->fix_pc); usbd_copy_in(xfer->frbuffers, offset, res.buffer, len); } offset += *plen; *plen = len; /* remove TD from schedule */ UHCI_REMOVE_TD(td, *pp_last); pp_last++; plen++; td = td->obj_next; } xfer->aframes = xfer->nframes; } static usb_error_t uhci_non_isoc_done_sub(struct usb_xfer *xfer) { struct usb_page_search res; uhci_td_t *td; uhci_td_t *td_alt_next; uint32_t status; uint32_t token; uint16_t len; td = xfer->td_transfer_cache; td_alt_next = td->alt_next; if (xfer->aframes != xfer->nframes) { usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); } while (1) { usb_pc_cpu_invalidate(td->page_cache); status = le32toh(td->td_status); token = le32toh(td->td_token); /* * Verify the status and add * up the actual length: */ len = UHCI_TD_GET_ACTLEN(status); if (len > td->len) { /* should not happen */ DPRINTF("Invalid status length, " "0x%04x/0x%04x bytes\n", len, td->len); status |= UHCI_TD_STALLED; } else if ((xfer->aframes != xfer->nframes) && (len > 0)) { if (td->fix_pc) { usbd_get_page(td->fix_pc, 0, &res); /* * copy data from fixup location to real * location */ usb_pc_cpu_invalidate(td->fix_pc); usbd_copy_in(xfer->frbuffers + xfer->aframes, xfer->frlengths[xfer->aframes], res.buffer, len); } /* update actual length */ xfer->frlengths[xfer->aframes] += len; } /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { td = NULL; break; } if (status & UHCI_TD_STALLED) { /* the transfer is finished */ td = NULL; break; } /* Check for short transfer */ if (len != td->len) { if (xfer->flags_int.short_frames_ok) { /* follow alt next */ td = td->alt_next; } else { /* the transfer is finished */ td = NULL; } break; } td = td->obj_next; if (td->alt_next != td_alt_next) { /* this USB frame is complete */ break; } } /* update transfer cache */ xfer->td_transfer_cache = td; /* update data toggle */ xfer->endpoint->toggle_next = (token & UHCI_TD_SET_DT(1)) ? 0 : 1; #ifdef USB_DEBUG if (status & UHCI_TD_ERROR) { DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x " "status=%s%s%s%s%s%s%s%s%s%s%s\n", xfer->address, xfer->endpointno, xfer->aframes, (status & UHCI_TD_BITSTUFF) ? "[BITSTUFF]" : "", (status & UHCI_TD_CRCTO) ? "[CRCTO]" : "", (status & UHCI_TD_NAK) ? "[NAK]" : "", (status & UHCI_TD_BABBLE) ? "[BABBLE]" : "", (status & UHCI_TD_DBUFFER) ? "[DBUFFER]" : "", (status & UHCI_TD_STALLED) ? "[STALLED]" : "", (status & UHCI_TD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", (status & UHCI_TD_IOC) ? "[IOC]" : "", (status & UHCI_TD_IOS) ? "[IOS]" : "", (status & UHCI_TD_LS) ? "[LS]" : "", (status & UHCI_TD_SPD) ? "[SPD]" : ""); } #endif if (status & UHCI_TD_STALLED) { /* try to separate I/O errors from STALL */ if (UHCI_TD_GET_ERRCNT(status) == 0) return (USB_ERR_IOERROR); return (USB_ERR_STALLED); } return (USB_ERR_NORMAL_COMPLETION); } static void uhci_non_isoc_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); #ifdef USB_DEBUG if (uhcidebug > 10) { uhci_dump_tds(xfer->td_transfer_first); } #endif /* sync any DMA memory before doing fixups */ usb_bdma_post_sync(xfer); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = uhci_non_isoc_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = uhci_non_isoc_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = uhci_non_isoc_done_sub(xfer); } done: uhci_device_done(xfer, err); } /*------------------------------------------------------------------------* * uhci_check_transfer_sub * * The main purpose of this function is to update the data-toggle * in case it is wrong. *------------------------------------------------------------------------*/ static void uhci_check_transfer_sub(struct usb_xfer *xfer) { uhci_qh_t *qh; uhci_td_t *td; uhci_td_t *td_alt_next; uint32_t td_token; uint32_t td_self; td = xfer->td_transfer_cache; qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; td_token = td->obj_next->td_token; td = td->alt_next; xfer->td_transfer_cache = td; td_self = td->td_self; td_alt_next = td->alt_next; if (xfer->flags_int.control_xfr) goto skip; /* don't touch the DT value! */ if (!((td->td_token ^ td_token) & htole32(UHCI_TD_SET_DT(1)))) goto skip; /* data toggle has correct value */ /* * The data toggle is wrong and we need to toggle it ! */ while (1) { td->td_token ^= htole32(UHCI_TD_SET_DT(1)); usb_pc_cpu_flush(td->page_cache); if (td == xfer->td_transfer_last) { /* last transfer */ break; } td = td->obj_next; if (td->alt_next != td_alt_next) { /* next frame */ break; } } skip: /* update the QH */ qh->qh_e_next = td_self; usb_pc_cpu_flush(qh->page_cache); DPRINTFN(13, "xfer=%p following alt next\n", xfer); } /*------------------------------------------------------------------------* * uhci_check_transfer * * Return values: * 0: USB transfer is not finished * Else: USB transfer is finished *------------------------------------------------------------------------*/ static uint8_t uhci_check_transfer(struct usb_xfer *xfer) { uint32_t status; uint32_t token; uhci_td_t *td; DPRINTFN(16, "xfer=%p checking transfer\n", xfer); if (xfer->endpoint->methods == &uhci_device_isoc_methods) { /* isochronous transfer */ td = xfer->td_transfer_last; usb_pc_cpu_invalidate(td->page_cache); status = le32toh(td->td_status); /* check also if the first is complete */ td = xfer->td_transfer_first; usb_pc_cpu_invalidate(td->page_cache); status |= le32toh(td->td_status); if (!(status & UHCI_TD_ACTIVE)) { uhci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); goto transferred; } } else { /* non-isochronous transfer */ /* * check whether there is an error somewhere * in the middle, or whether there was a short * packet (SPD and not ACTIVE) */ td = xfer->td_transfer_cache; while (1) { usb_pc_cpu_invalidate(td->page_cache); status = le32toh(td->td_status); token = le32toh(td->td_token); /* * if there is an active TD the transfer isn't done */ if (status & UHCI_TD_ACTIVE) { /* update cache */ xfer->td_transfer_cache = td; goto done; } /* * last transfer descriptor makes the transfer done */ if (((void *)td) == xfer->td_transfer_last) { break; } /* * any kind of error makes the transfer done */ if (status & UHCI_TD_STALLED) { break; } /* * check if we reached the last packet * or if there is a short packet: */ if ((td->td_next == htole32(UHCI_PTR_T)) || (UHCI_TD_GET_ACTLEN(status) < td->len)) { if (xfer->flags_int.short_frames_ok) { /* follow alt next */ if (td->alt_next) { /* update cache */ xfer->td_transfer_cache = td; uhci_check_transfer_sub(xfer); goto done; } } /* transfer is done */ break; } td = td->obj_next; } uhci_non_isoc_done(xfer); goto transferred; } done: DPRINTFN(13, "xfer=%p is still active\n", xfer); return (0); transferred: return (1); } static void uhci_interrupt_poll(uhci_softc_t *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { /* * check if transfer is transferred */ if (uhci_check_transfer(xfer)) { /* queue has been modified */ goto repeat; } } } /*------------------------------------------------------------------------* * uhci_interrupt - UHCI interrupt handler * * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, * hence the interrupt handler will be setup before "sc->sc_bus.bdev" * is present ! *------------------------------------------------------------------------*/ void uhci_interrupt(uhci_softc_t *sc) { uint32_t status; USB_BUS_LOCK(&sc->sc_bus); DPRINTFN(16, "real interrupt\n"); #ifdef USB_DEBUG if (uhcidebug > 15) { uhci_dumpregs(sc); } #endif status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS; if (status == 0) { /* the interrupt was not for us */ goto done; } if (status & (UHCI_STS_RD | UHCI_STS_HSE | UHCI_STS_HCPE | UHCI_STS_HCH)) { if (status & UHCI_STS_RD) { #ifdef USB_DEBUG printf("%s: resume detect\n", __FUNCTION__); #endif } if (status & UHCI_STS_HSE) { printf("%s: host system error\n", __FUNCTION__); } if (status & UHCI_STS_HCPE) { printf("%s: host controller process error\n", __FUNCTION__); } if (status & UHCI_STS_HCH) { /* no acknowledge needed */ DPRINTF("%s: host controller halted\n", __FUNCTION__); #ifdef USB_DEBUG if (uhcidebug > 0) { uhci_dump_all(sc); } #endif } } /* get acknowledge bits */ status &= (UHCI_STS_USBINT | UHCI_STS_USBEI | UHCI_STS_RD | UHCI_STS_HSE | UHCI_STS_HCPE | UHCI_STS_HCH); if (status == 0) { /* nothing to acknowledge */ goto done; } /* acknowledge interrupts */ UWRITE2(sc, UHCI_STS, status); /* poll all the USB transfers */ uhci_interrupt_poll(sc); done: USB_BUS_UNLOCK(&sc->sc_bus); } /* * called when a request does not complete */ static void uhci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ uhci_device_done(xfer, USB_ERR_TIMEOUT); } static void uhci_do_poll(struct usb_bus *bus) { struct uhci_softc *sc = UHCI_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); uhci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void uhci_setup_standard_chain_sub(struct uhci_std_temp *temp) { uhci_td_t *td; uhci_td_t *td_next; uhci_td_t *td_alt_next; uint32_t average; uint32_t len_old; uint8_t shortpkt_old; uint8_t precompute; td_alt_next = NULL; shortpkt_old = temp->shortpkt; len_old = temp->len; precompute = 1; /* software is used to detect short incoming transfers */ if ((temp->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) { temp->td_status |= htole32(UHCI_TD_SPD); } else { temp->td_status &= ~htole32(UHCI_TD_SPD); } temp->ml.buf_offset = 0; restart: temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0)); temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->average)); td = temp->td; td_next = temp->td_next; while (1) { if (temp->len == 0) { if (temp->shortpkt) { break; } /* send a Zero Length Packet, ZLP, last */ temp->shortpkt = 1; temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(0)); average = 0; } else { average = temp->average; if (temp->len < average) { temp->shortpkt = 1; temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0)); temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->len)); average = temp->len; } } if (td_next == NULL) { panic("%s: out of UHCI transfer descriptors!", __FUNCTION__); } /* get next TD */ td = td_next; td_next = td->obj_next; /* check if we are pre-computing */ if (precompute) { /* update remaining length */ temp->len -= average; continue; } /* fill out current TD */ td->td_status = temp->td_status; td->td_token = temp->td_token; /* update data toggle */ temp->td_token ^= htole32(UHCI_TD_SET_DT(1)); if (average == 0) { td->len = 0; td->td_buffer = 0; td->fix_pc = NULL; } else { /* update remaining length */ temp->len -= average; td->len = average; /* fill out buffer pointer and do fixup, if any */ uhci_mem_layout_fixup(&temp->ml, td); } td->alt_next = td_alt_next; if ((td_next == td_alt_next) && temp->setup_alt_next) { /* we need to receive these frames one by one ! */ td->td_status |= htole32(UHCI_TD_IOC); td->td_next = htole32(UHCI_PTR_T); } else { if (td_next) { /* link the current TD with the next one */ td->td_next = td_next->td_self; } } usb_pc_cpu_flush(td->page_cache); } if (precompute) { precompute = 0; /* setup alt next pointer, if any */ if (temp->last_frame) { td_alt_next = NULL; } else { /* we use this field internally */ td_alt_next = td_next; } /* restore */ temp->shortpkt = shortpkt_old; temp->len = len_old; goto restart; } temp->td = td; temp->td_next = td_next; } static uhci_td_t * uhci_setup_standard_chain(struct usb_xfer *xfer) { struct uhci_std_temp temp; uhci_td_t *td; uint32_t x; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.average = xfer->max_frame_size; temp.max_frame_size = xfer->max_frame_size; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; temp.td = NULL; temp.td_next = td; temp.last_frame = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok; uhci_mem_layout_init(&temp.ml, xfer); temp.td_status = htole32(UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) | UHCI_TD_ACTIVE)); if (xfer->xroot->udev->speed == USB_SPEED_LOW) { temp.td_status |= htole32(UHCI_TD_LS); } temp.td_token = htole32(UHCI_TD_SET_ENDPT(xfer->endpointno) | UHCI_TD_SET_DEVADDR(xfer->address)); if (xfer->endpoint->toggle_next) { /* DATA1 is next */ temp.td_token |= htole32(UHCI_TD_SET_DT(1)); } /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | UHCI_TD_SET_ENDPT(0xF)); temp.td_token |= htole32(UHCI_TD_PID_SETUP | UHCI_TD_SET_DT(0)); temp.len = xfer->frlengths[0]; temp.ml.buf_pc = xfer->frbuffers + 0; temp.shortpkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } uhci_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; temp.ml.buf_pc = xfer->frbuffers + x; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { /* no STATUS stage yet, DATA is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } else { temp.last_frame = 1; temp.setup_alt_next = 0; } } /* * Keep previous data toggle, * device address and endpoint number: */ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | UHCI_TD_SET_ENDPT(0xF) | UHCI_TD_SET_DT(1)); if (temp.len == 0) { /* make sure that we send an USB packet */ temp.shortpkt = 0; } else { /* regular data transfer */ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; } /* set endpoint direction */ temp.td_token |= (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? htole32(UHCI_TD_PID_IN) : htole32(UHCI_TD_PID_OUT); uhci_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { /* * send a DATA1 message and reverse the current endpoint * direction */ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | UHCI_TD_SET_ENDPT(0xF) | UHCI_TD_SET_DT(1)); temp.td_token |= (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ? htole32(UHCI_TD_PID_IN | UHCI_TD_SET_DT(1)) : htole32(UHCI_TD_PID_OUT | UHCI_TD_SET_DT(1)); temp.len = 0; temp.ml.buf_pc = NULL; temp.shortpkt = 0; temp.last_frame = 1; temp.setup_alt_next = 0; uhci_setup_standard_chain_sub(&temp); } td = temp.td; /* Ensure that last TD is terminating: */ td->td_next = htole32(UHCI_PTR_T); /* set interrupt bit */ td->td_status |= htole32(UHCI_TD_IOC); usb_pc_cpu_flush(td->page_cache); /* must have at least one frame! */ xfer->td_transfer_last = td; #ifdef USB_DEBUG if (uhcidebug > 8) { DPRINTF("nexttog=%d; data before transfer:\n", xfer->endpoint->toggle_next); uhci_dump_tds(xfer->td_transfer_first); } #endif return (xfer->td_transfer_first); } /* NOTE: "done" can be run two times in a row, * from close and from interrupt */ static void uhci_device_done(struct usb_xfer *xfer, usb_error_t error) { const struct usb_pipe_methods *methods = xfer->endpoint->methods; uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); uhci_qh_t *qh; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (qh) { usb_pc_cpu_invalidate(qh->page_cache); } if (xfer->flags_int.bandwidth_reclaimed) { xfer->flags_int.bandwidth_reclaimed = 0; uhci_rem_loop(sc); } if (methods == &uhci_device_bulk_methods) { UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last); } if (methods == &uhci_device_ctrl_methods) { if (xfer->xroot->udev->speed == USB_SPEED_LOW) { UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last); } else { UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last); } } if (methods == &uhci_device_intr_methods) { UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); } /* * Only finish isochronous transfers once * which will update "xfer->frlengths". */ if (xfer->td_transfer_first && xfer->td_transfer_last) { if (methods == &uhci_device_isoc_methods) { uhci_isoc_done(sc, xfer); } xfer->td_transfer_first = NULL; xfer->td_transfer_last = NULL; } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } /*------------------------------------------------------------------------* * uhci bulk support *------------------------------------------------------------------------*/ static void uhci_device_bulk_open(struct usb_xfer *xfer) { return; } static void uhci_device_bulk_close(struct usb_xfer *xfer) { uhci_device_done(xfer, USB_ERR_CANCELLED); } static void uhci_device_bulk_enter(struct usb_xfer *xfer) { return; } static void uhci_device_bulk_start(struct usb_xfer *xfer) { uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); uhci_td_t *td; uhci_qh_t *qh; /* setup TD's */ td = uhci_setup_standard_chain(xfer); /* setup QH */ qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; qh->e_next = td; qh->qh_e_next = td->td_self; if (xfer->xroot->udev->flags.self_suspended == 0) { UHCI_APPEND_QH(qh, sc->sc_bulk_p_last); uhci_add_loop(sc); xfer->flags_int.bandwidth_reclaimed = 1; } else { usb_pc_cpu_flush(qh->page_cache); } /* put transfer on interrupt queue */ uhci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods uhci_device_bulk_methods = { .open = uhci_device_bulk_open, .close = uhci_device_bulk_close, .enter = uhci_device_bulk_enter, .start = uhci_device_bulk_start, }; /*------------------------------------------------------------------------* * uhci control support *------------------------------------------------------------------------*/ static void uhci_device_ctrl_open(struct usb_xfer *xfer) { return; } static void uhci_device_ctrl_close(struct usb_xfer *xfer) { uhci_device_done(xfer, USB_ERR_CANCELLED); } static void uhci_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void uhci_device_ctrl_start(struct usb_xfer *xfer) { uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); uhci_qh_t *qh; uhci_td_t *td; /* setup TD's */ td = uhci_setup_standard_chain(xfer); /* setup QH */ qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; qh->e_next = td; qh->qh_e_next = td->td_self; /* * NOTE: some devices choke on bandwidth- reclamation for control * transfers */ if (xfer->xroot->udev->flags.self_suspended == 0) { if (xfer->xroot->udev->speed == USB_SPEED_LOW) { UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last); } else { UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last); } } else { usb_pc_cpu_flush(qh->page_cache); } /* put transfer on interrupt queue */ uhci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods uhci_device_ctrl_methods = { .open = uhci_device_ctrl_open, .close = uhci_device_ctrl_close, .enter = uhci_device_ctrl_enter, .start = uhci_device_ctrl_start, }; /*------------------------------------------------------------------------* * uhci interrupt support *------------------------------------------------------------------------*/ static void uhci_device_intr_open(struct usb_xfer *xfer) { uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); uint16_t best; uint16_t bit; uint16_t x; best = 0; bit = UHCI_IFRAMELIST_COUNT / 2; while (bit) { if (xfer->interval >= bit) { x = bit; best = bit; while (x & bit) { if (sc->sc_intr_stat[x] < sc->sc_intr_stat[best]) { best = x; } x++; } break; } bit >>= 1; } sc->sc_intr_stat[best]++; xfer->qh_pos = best; DPRINTFN(3, "best=%d interval=%d\n", best, xfer->interval); } static void uhci_device_intr_close(struct usb_xfer *xfer) { uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); sc->sc_intr_stat[xfer->qh_pos]--; uhci_device_done(xfer, USB_ERR_CANCELLED); } static void uhci_device_intr_enter(struct usb_xfer *xfer) { return; } static void uhci_device_intr_start(struct usb_xfer *xfer) { uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); uhci_qh_t *qh; uhci_td_t *td; /* setup TD's */ td = uhci_setup_standard_chain(xfer); /* setup QH */ qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; qh->e_next = td; qh->qh_e_next = td->td_self; if (xfer->xroot->udev->flags.self_suspended == 0) { /* enter QHs into the controller data structures */ UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); } else { usb_pc_cpu_flush(qh->page_cache); } /* put transfer on interrupt queue */ uhci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods uhci_device_intr_methods = { .open = uhci_device_intr_open, .close = uhci_device_intr_close, .enter = uhci_device_intr_enter, .start = uhci_device_intr_start, }; /*------------------------------------------------------------------------* * uhci isochronous support *------------------------------------------------------------------------*/ static void uhci_device_isoc_open(struct usb_xfer *xfer) { uhci_td_t *td; uint32_t td_token; uint8_t ds; td_token = (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? UHCI_TD_IN(0, xfer->endpointno, xfer->address, 0) : UHCI_TD_OUT(0, xfer->endpointno, xfer->address, 0); td_token = htole32(td_token); /* initialize all TD's */ for (ds = 0; ds != 2; ds++) { for (td = xfer->td_start[ds]; td; td = td->obj_next) { /* mark TD as inactive */ td->td_status = htole32(UHCI_TD_IOS); td->td_token = td_token; usb_pc_cpu_flush(td->page_cache); } } } static void uhci_device_isoc_close(struct usb_xfer *xfer) { uhci_device_done(xfer, USB_ERR_CANCELLED); } static void uhci_device_isoc_enter(struct usb_xfer *xfer) { struct uhci_mem_layout ml; uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); uint32_t nframes; uint32_t temp; uint32_t *plen; #ifdef USB_DEBUG uint8_t once = 1; #endif uhci_td_t *td; uhci_td_t *td_last = NULL; uhci_td_t **pp_last; DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); nframes = UREAD2(sc, UHCI_FRNUM); temp = (nframes - xfer->endpoint->isoc_next) & (UHCI_VFRAMELIST_COUNT - 1); if ((xfer->endpoint->is_synced == 0) || (temp < xfer->nframes)) { /* * If there is data underflow or the pipe queue is empty we * schedule the transfer a few frames ahead of the current * frame position. Else two isochronous transfers might * overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & (UHCI_VFRAMELIST_COUNT - 1); xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & (UHCI_VFRAMELIST_COUNT - 1); /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + xfer->nframes; /* get the real number of frames */ nframes = xfer->nframes; uhci_mem_layout_init(&ml, xfer); plen = xfer->frlengths; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; pp_last = &sc->sc_isoc_p_last[xfer->endpoint->isoc_next]; /* store starting position */ xfer->qh_pos = xfer->endpoint->isoc_next; while (nframes--) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) { pp_last = &sc->sc_isoc_p_last[0]; } if (*plen > xfer->max_frame_size) { #ifdef USB_DEBUG if (once) { once = 0; printf("%s: frame length(%d) exceeds %d " "bytes (frame truncated)\n", __FUNCTION__, *plen, xfer->max_frame_size); } #endif *plen = xfer->max_frame_size; } /* reuse td_token from last transfer */ td->td_token &= htole32(~UHCI_TD_MAXLEN_MASK); td->td_token |= htole32(UHCI_TD_SET_MAXLEN(*plen)); td->len = *plen; if (td->len == 0) { /* * Do not call "uhci_mem_layout_fixup()" when the * length is zero! */ td->td_buffer = 0; td->fix_pc = NULL; } else { /* fill out buffer pointer and do fixup, if any */ uhci_mem_layout_fixup(&ml, td); } /* update status */ if (nframes == 0) { td->td_status = htole32 (UHCI_TD_ZERO_ACTLEN (UHCI_TD_SET_ERRCNT(0) | UHCI_TD_ACTIVE | UHCI_TD_IOS | UHCI_TD_IOC)); } else { td->td_status = htole32 (UHCI_TD_ZERO_ACTLEN (UHCI_TD_SET_ERRCNT(0) | UHCI_TD_ACTIVE | UHCI_TD_IOS)); } usb_pc_cpu_flush(td->page_cache); #ifdef USB_DEBUG if (uhcidebug > 5) { DPRINTF("TD %d\n", nframes); uhci_dump_td(td); } #endif /* insert TD into schedule */ UHCI_APPEND_TD(td, *pp_last); pp_last++; plen++; td_last = td; td = td->obj_next; } xfer->td_transfer_last = td_last; /* update isoc_next */ xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_p_last[0]) & (UHCI_VFRAMELIST_COUNT - 1); } static void uhci_device_isoc_start(struct usb_xfer *xfer) { /* put transfer on interrupt queue */ uhci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods uhci_device_isoc_methods = { .open = uhci_device_isoc_open, .close = uhci_device_isoc_close, .enter = uhci_device_isoc_enter, .start = uhci_device_isoc_start, }; /*------------------------------------------------------------------------* * uhci root control support *------------------------------------------------------------------------* * Simulate a hardware hub by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor uhci_devd = { sizeof(struct usb_device_descriptor), UDESC_DEVICE, /* type */ {0x00, 0x01}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_FSHUB, /* protocol */ 64, /* max packet */ {0}, {0}, {0x00, 0x01}, /* device id */ 1, 2, 0, /* string indexes */ 1 /* # of configurations */ }; static const struct uhci_config_desc uhci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(uhci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0 /* max power */ }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = UIPROTO_FSHUB, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | UHCI_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ .bInterval = 255, }, }; static const struct usb_hub_descriptor_min uhci_hubd_piix = { .bDescLength = sizeof(uhci_hubd_piix), .bDescriptorType = UDESC_HUB, .bNbrPorts = 2, .wHubCharacteristics = {UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0}, .bPwrOn2PwrGood = 50, }; /* * The USB hub protocol requires that SET_FEATURE(PORT_RESET) also * enables the port, and also states that SET_FEATURE(PORT_ENABLE) * should not be used by the USB subsystem. As we cannot issue a * SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port * will be enabled as part of the reset. * * On the VT83C572, the port cannot be successfully enabled until the * outstanding "port enable change" and "connection status change" * events have been reset. */ static usb_error_t uhci_portreset(uhci_softc_t *sc, uint16_t index) { uint16_t port; uint16_t x; uint8_t lim; if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else return (USB_ERR_IOERROR); /* * Before we do anything, turn on SOF messages on the USB * BUS. Some USB devices do not cope without them! */ uhci_restart(sc); x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_PR); usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(usb_port_root_reset_delay)); DPRINTFN(4, "uhci port %d reset, status0 = 0x%04x\n", index, UREAD2(sc, port)); x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); mtx_unlock(&sc->sc_bus.bus_mtx); /* * This delay needs to be exactly 100us, else some USB devices * fail to attach! */ DELAY(100); mtx_lock(&sc->sc_bus.bus_mtx); DPRINTFN(4, "uhci port %d reset, status1 = 0x%04x\n", index, UREAD2(sc, port)); x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_PE); for (lim = 0; lim < 12; lim++) { usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(usb_port_reset_delay)); x = UREAD2(sc, port); DPRINTFN(4, "uhci port %d iteration %u, status = 0x%04x\n", index, lim, x); if (!(x & UHCI_PORTSC_CCS)) { /* * No device is connected (or was disconnected * during reset). Consider the port reset. * The delay must be long enough to ensure on * the initial iteration that the device * connection will have been registered. 50ms * appears to be sufficient, but 20ms is not. */ DPRINTFN(4, "uhci port %d loop %u, device detached\n", index, lim); goto done; } if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) { /* * Port enabled changed and/or connection * status changed were set. Reset either or * both raised flags (by writing a 1 to that * bit), and wait again for state to settle. */ UWRITE2(sc, port, URWMASK(x) | (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC))); continue; } if (x & UHCI_PORTSC_PE) { /* port is enabled */ goto done; } UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE); } DPRINTFN(2, "uhci port %d reset timed out\n", index); return (USB_ERR_TIMEOUT); done: DPRINTFN(4, "uhci port %d reset, status2 = 0x%04x\n", index, UREAD2(sc, port)); sc->sc_isreset = 1; return (USB_ERR_NORMAL_COMPLETION); } static usb_error_t uhci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { uhci_softc_t *sc = UHCI_BUS2SC(udev->bus); const void *ptr; const char *str_ptr; uint16_t x; uint16_t port; uint16_t value; uint16_t index; uint16_t status; uint16_t change; uint16_t len; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_desc.temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " "wValue=0x%04x wIndex=0x%04x\n", req->bmRequestType, req->bRequest, UGETW(req->wLength), value, index); #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): len = 1; sc->sc_hub_desc.temp[0] = sc->sc_conf; break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): switch (value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(uhci_devd); ptr = (const void *)&uhci_devd; break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(uhci_confd); ptr = (const void *)&uhci_confd; break; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ str_ptr = "\001"; break; case 1: /* Vendor */ str_ptr = sc->sc_vendor; break; case 2: /* Product */ str_ptr = "UHCI root HUB"; break; default: str_ptr = ""; break; } len = usb_make_str_desc (sc->sc_hub_desc.temp, sizeof(sc->sc_hub_desc.temp), str_ptr); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): len = 1; sc->sc_hub_desc.temp[0] = 0; break; case C(UR_GET_STATUS, UT_READ_DEVICE): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, 0); break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= UHCI_MAX_DEVICES) { err = USB_ERR_IOERROR; goto done; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if ((value != 0) && (value != 1)) { err = USB_ERR_IOERROR; goto done; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USB_ERR_IOERROR; goto done; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(4, "UR_CLEAR_PORT_FEATURE " "port=%d feature=%d\n", index, value); if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USB_ERR_IOERROR; goto done; } switch (value) { case UHF_PORT_ENABLE: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_PE); break; case UHF_PORT_SUSPEND: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~(UHCI_PORTSC_SUSP)); break; case UHF_PORT_RESET: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); break; case UHF_C_PORT_CONNECTION: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_CSC); break; case UHF_C_PORT_ENABLE: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_POEDC); break; case UHF_C_PORT_OVER_CURRENT: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_OCIC); break; case UHF_C_PORT_RESET: sc->sc_isreset = 0; err = USB_ERR_NORMAL_COMPLETION; goto done; case UHF_C_PORT_SUSPEND: sc->sc_isresumed &= ~(1 << index); break; case UHF_PORT_CONNECTION: case UHF_PORT_OVER_CURRENT: case UHF_PORT_POWER: case UHF_PORT_LOW_SPEED: default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER): if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USB_ERR_IOERROR; goto done; } len = 1; sc->sc_hub_desc.temp[0] = ((UREAD2(sc, port) & UHCI_PORTSC_LS) >> UHCI_PORTSC_LS_SHIFT); break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(uhci_hubd_piix); ptr = (const void *)&uhci_hubd_piix; break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): len = 16; memset(sc->sc_hub_desc.temp, 0, 16); break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USB_ERR_IOERROR; goto done; } x = UREAD2(sc, port); status = change = 0; if (x & UHCI_PORTSC_CCS) status |= UPS_CURRENT_CONNECT_STATUS; if (x & UHCI_PORTSC_CSC) change |= UPS_C_CONNECT_STATUS; if (x & UHCI_PORTSC_PE) status |= UPS_PORT_ENABLED; if (x & UHCI_PORTSC_POEDC) change |= UPS_C_PORT_ENABLED; if (x & UHCI_PORTSC_OCI) status |= UPS_OVERCURRENT_INDICATOR; if (x & UHCI_PORTSC_OCIC) change |= UPS_C_OVERCURRENT_INDICATOR; if (x & UHCI_PORTSC_LSDA) status |= UPS_LOW_SPEED; if ((x & UHCI_PORTSC_PE) && (x & UHCI_PORTSC_RD)) { /* need to do a write back */ UWRITE2(sc, port, URWMASK(x)); /* wait 20ms for resume sequence to complete */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); /* clear suspend and resume detect */ UWRITE2(sc, port, URWMASK(x) & ~(UHCI_PORTSC_RD | UHCI_PORTSC_SUSP)); /* wait a little bit */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 500); sc->sc_isresumed |= (1 << index); } else if (x & UHCI_PORTSC_SUSP) { status |= UPS_SUSPEND; } status |= UPS_PORT_POWER; if (sc->sc_isresumed & (1 << index)) change |= UPS_C_SUSPEND; if (sc->sc_isreset) change |= UPS_C_PORT_RESET; USETW(sc->sc_hub_desc.ps.wPortStatus, status); USETW(sc->sc_hub_desc.ps.wPortChange, change); len = sizeof(sc->sc_hub_desc.ps); break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USB_ERR_IOERROR; goto done; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USB_ERR_IOERROR; goto done; } switch (value) { case UHF_PORT_ENABLE: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_PE); break; case UHF_PORT_SUSPEND: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_SUSP); break; case UHF_PORT_RESET: err = uhci_portreset(sc, index); goto done; case UHF_PORT_POWER: /* pretend we turned on power */ err = USB_ERR_NORMAL_COMPLETION; goto done; case UHF_C_PORT_CONNECTION: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_PORT_CONNECTION: case UHF_PORT_OVER_CURRENT: case UHF_PORT_LOW_SPEED: case UHF_C_PORT_SUSPEND: case UHF_C_PORT_RESET: default: err = USB_ERR_IOERROR; goto done; } break; default: err = USB_ERR_IOERROR; goto done; } done: *plength = len; *pptr = ptr; return (err); } /* * This routine is executed periodically and simulates interrupts from * the root controller interrupt pipe for port status change: */ static void uhci_root_intr(uhci_softc_t *sc) { DPRINTFN(21, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); sc->sc_hub_idata[0] = 0; if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC | UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) { sc->sc_hub_idata[0] |= 1 << 1; } if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC | UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) { sc->sc_hub_idata[0] |= 1 << 2; } /* restart timer */ usb_callout_reset(&sc->sc_root_intr, hz, (void *)&uhci_root_intr, sc); if (sc->sc_hub_idata[0] != 0) { uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } } static void uhci_xfer_setup(struct usb_setup_params *parm) { struct usb_page_search page_info; struct usb_page_cache *pc; uhci_softc_t *sc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t nqh; uint32_t nfixup; uint32_t n; uint16_t align; sc = UHCI_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x500; /* * compute ntd and nqh */ if (parm->methods == &uhci_device_ctrl_methods) { xfer->flags_int.bdma_enable = 1; xfer->flags_int.bdma_no_post_sync = 1; usbd_transfer_setup_sub(parm); /* see EHCI HC driver for proof of "ntd" formula */ nqh = 1; ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + (xfer->max_data_length / xfer->max_frame_size)); } else if (parm->methods == &uhci_device_bulk_methods) { xfer->flags_int.bdma_enable = 1; xfer->flags_int.bdma_no_post_sync = 1; usbd_transfer_setup_sub(parm); nqh = 1; ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_frame_size)); } else if (parm->methods == &uhci_device_intr_methods) { xfer->flags_int.bdma_enable = 1; xfer->flags_int.bdma_no_post_sync = 1; usbd_transfer_setup_sub(parm); nqh = 1; ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_frame_size)); } else if (parm->methods == &uhci_device_isoc_methods) { xfer->flags_int.bdma_enable = 1; xfer->flags_int.bdma_no_post_sync = 1; usbd_transfer_setup_sub(parm); nqh = 0; ntd = xfer->nframes; } else { usbd_transfer_setup_sub(parm); nqh = 0; ntd = 0; } if (parm->err) { return; } /* * NOTE: the UHCI controller requires that * every packet must be contiguous on * the same USB memory page ! */ nfixup = (parm->bufsize / USB_PAGE_SIZE) + 1; /* * Compute a suitable power of two alignment * for our "max_frame_size" fixup buffer(s): */ align = xfer->max_frame_size; n = 0; while (align) { align >>= 1; n++; } /* check for power of two */ if (!(xfer->max_frame_size & (xfer->max_frame_size - 1))) { n--; } /* * We don't allow alignments of * less than 8 bytes: * * NOTE: Allocating using an aligment * of 1 byte has special meaning! */ if (n < 3) { n = 3; } align = (1 << n); if (usbd_transfer_setup_sub_malloc( parm, &pc, xfer->max_frame_size, align, nfixup)) { parm->err = USB_ERR_NOMEM; return; } xfer->buf_fixup = pc; alloc_dma_set: if (parm->err) { return; } last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(uhci_td_t), UHCI_TD_ALIGN, ntd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != ntd; n++) { uhci_td_t *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; /* init TD */ if ((parm->methods == &uhci_device_bulk_methods) || (parm->methods == &uhci_device_ctrl_methods) || (parm->methods == &uhci_device_intr_methods)) { /* set depth first bit */ td->td_self = htole32(page_info.physaddr | UHCI_PTR_TD | UHCI_PTR_VF); } else { td->td_self = htole32(page_info.physaddr | UHCI_PTR_TD); } td->obj_next = last_obj; td->page_cache = pc + n; last_obj = td; usb_pc_cpu_flush(pc + n); } } xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(uhci_qh_t), UHCI_QH_ALIGN, nqh)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nqh; n++) { uhci_qh_t *qh; usbd_get_page(pc + n, 0, &page_info); qh = page_info.buffer; /* init QH */ qh->qh_self = htole32(page_info.physaddr | UHCI_PTR_QH); qh->obj_next = last_obj; qh->page_cache = pc + n; last_obj = qh; usb_pc_cpu_flush(pc + n); } } xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; if (!xfer->flags_int.curr_dma_set) { xfer->flags_int.curr_dma_set = 1; goto alloc_dma_set; } } static void uhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { uhci_softc_t *sc = UHCI_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_addr); if (udev->device_index != sc->sc_addr) { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &uhci_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &uhci_device_intr_methods; break; case UE_ISOCHRONOUS: if (udev->speed == USB_SPEED_FULL) { ep->methods = &uhci_device_isoc_methods; } break; case UE_BULK: ep->methods = &uhci_device_bulk_methods; break; default: /* do nothing */ break; } } } static void uhci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void uhci_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* * Wait until hardware has finished any possible use of the * transfer descriptor(s) and QH */ *pus = (1125); /* microseconds */ } static void uhci_device_resume(struct usb_device *udev) { struct uhci_softc *sc = UHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; uhci_qh_t *qh; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (methods == &uhci_device_bulk_methods) { UHCI_APPEND_QH(qh, sc->sc_bulk_p_last); uhci_add_loop(sc); xfer->flags_int.bandwidth_reclaimed = 1; } if (methods == &uhci_device_ctrl_methods) { if (xfer->xroot->udev->speed == USB_SPEED_LOW) { UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last); } else { UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last); } } if (methods == &uhci_device_intr_methods) { UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void uhci_device_suspend(struct usb_device *udev) { struct uhci_softc *sc = UHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; uhci_qh_t *qh; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (xfer->flags_int.bandwidth_reclaimed) { xfer->flags_int.bandwidth_reclaimed = 0; uhci_rem_loop(sc); } if (methods == &uhci_device_bulk_methods) { UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last); } if (methods == &uhci_device_ctrl_methods) { if (xfer->xroot->udev->speed == USB_SPEED_LOW) { UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last); } else { UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last); } } if (methods == &uhci_device_intr_methods) { UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void uhci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct uhci_softc *sc = UHCI_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: case USB_HW_POWER_SHUTDOWN: uhci_suspend(sc); break; case USB_HW_POWER_RESUME: uhci_resume(sc); break; default: break; } } static void uhci_set_hw_power(struct usb_bus *bus) { struct uhci_softc *sc = UHCI_BUS2SC(bus); uint32_t flags; DPRINTF("\n"); USB_BUS_LOCK(bus); flags = bus->hw_power_state; /* * WARNING: Some FULL speed USB devices require periodic SOF * messages! If any USB devices are connected through the * UHCI, power save will be disabled! */ if (flags & (USB_HW_POWER_CONTROL | USB_HW_POWER_NON_ROOT_HUB | USB_HW_POWER_BULK | USB_HW_POWER_INTERRUPT | USB_HW_POWER_ISOC)) { DPRINTF("Some USB transfer is " "active on unit %u.\n", device_get_unit(sc->sc_bus.bdev)); uhci_restart(sc); } else { DPRINTF("Power save on unit %u.\n", device_get_unit(sc->sc_bus.bdev)); UHCICMD(sc, UHCI_CMD_MAXP); } USB_BUS_UNLOCK(bus); return; } static const struct usb_bus_methods uhci_bus_methods = { .endpoint_init = uhci_ep_init, .xfer_setup = uhci_xfer_setup, .xfer_unsetup = uhci_xfer_unsetup, .get_dma_delay = uhci_get_dma_delay, .device_resume = uhci_device_resume, .device_suspend = uhci_device_suspend, .set_hw_power = uhci_set_hw_power, .set_hw_power_sleep = uhci_set_hw_power_sleep, .roothub_exec = uhci_roothub_exec, .xfer_poll = uhci_do_poll, }; Index: head/sys/dev/usb/controller/usb_controller.c =================================================================== --- head/sys/dev/usb/controller/usb_controller.c (revision 357971) +++ head/sys/dev/usb/controller/usb_controller.c (revision 357972) @@ -1,1037 +1,1038 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_ctrl_debug #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #endif /* USB_GLOBAL_INCLUDE_FILE */ /* function prototypes */ static device_probe_t usb_probe; static device_attach_t usb_attach; static device_detach_t usb_detach; static device_suspend_t usb_suspend; static device_resume_t usb_resume; static device_shutdown_t usb_shutdown; static void usb_attach_sub(device_t, struct usb_bus *); /* static variables */ #ifdef USB_DEBUG static int usb_ctrl_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ctrl, CTLFLAG_RW, 0, "USB controller"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ctrl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB controller"); SYSCTL_INT(_hw_usb_ctrl, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_ctrl_debug, 0, "Debug level"); #endif #if USB_HAVE_ROOT_MOUNT_HOLD static int usb_no_boot_wait = 0; SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RDTUN, &usb_no_boot_wait, 0, "No USB device enumerate waiting at boot."); #endif static int usb_no_suspend_wait = 0; SYSCTL_INT(_hw_usb, OID_AUTO, no_suspend_wait, CTLFLAG_RWTUN, &usb_no_suspend_wait, 0, "No USB device waiting at system suspend."); static int usb_no_shutdown_wait = 0; SYSCTL_INT(_hw_usb, OID_AUTO, no_shutdown_wait, CTLFLAG_RWTUN, &usb_no_shutdown_wait, 0, "No USB device waiting at system shutdown."); static devclass_t usb_devclass; static device_method_t usb_methods[] = { DEVMETHOD(device_probe, usb_probe), DEVMETHOD(device_attach, usb_attach), DEVMETHOD(device_detach, usb_detach), DEVMETHOD(device_suspend, usb_suspend), DEVMETHOD(device_resume, usb_resume), DEVMETHOD(device_shutdown, usb_shutdown), DEVMETHOD_END }; static driver_t usb_driver = { .name = "usbus", .methods = usb_methods, .size = 0, }; /* Host Only Drivers */ DRIVER_MODULE(usbus, ohci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, uhci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, ehci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, xhci, usb_driver, usb_devclass, 0, 0); /* Device Only Drivers */ DRIVER_MODULE(usbus, musbotg, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, uss820dci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, octusb, usb_driver, usb_devclass, 0, 0); /* Dual Mode Drivers */ DRIVER_MODULE(usbus, dwcotg, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, saf1761otg, usb_driver, usb_devclass, 0, 0); /*------------------------------------------------------------------------* * usb_probe * * This function is called from "{ehci,ohci,uhci}_pci_attach()". *------------------------------------------------------------------------*/ static int usb_probe(device_t dev) { DPRINTF("\n"); return (0); } #if USB_HAVE_ROOT_MOUNT_HOLD static void usb_root_mount_rel(struct usb_bus *bus) { if (bus->bus_roothold != NULL) { DPRINTF("Releasing root mount hold %p\n", bus->bus_roothold); root_mount_rel(bus->bus_roothold); bus->bus_roothold = NULL; } } #endif /*------------------------------------------------------------------------* * usb_attach *------------------------------------------------------------------------*/ static int usb_attach(device_t dev) { struct usb_bus *bus = device_get_ivars(dev); DPRINTF("\n"); if (bus == NULL) { device_printf(dev, "USB device has no ivars\n"); return (ENXIO); } #if USB_HAVE_ROOT_MOUNT_HOLD if (usb_no_boot_wait == 0) { /* delay vfs_mountroot until the bus is explored */ bus->bus_roothold = root_mount_hold(device_get_nameunit(dev)); } #endif usb_attach_sub(dev, bus); return (0); /* return success */ } /*------------------------------------------------------------------------* * usb_detach *------------------------------------------------------------------------*/ static int usb_detach(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } /* Stop power watchdog */ usb_callout_drain(&bus->power_wdog); #if USB_HAVE_ROOT_MOUNT_HOLD /* Let the USB explore process detach all devices. */ usb_root_mount_rel(bus); #endif USB_BUS_LOCK(bus); /* Queue detach job */ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->detach_msg[0], &bus->detach_msg[1]); /* Wait for detach to complete */ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus), &bus->detach_msg[0], &bus->detach_msg[1]); #if USB_HAVE_UGEN /* Wait for cleanup to complete */ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus), &bus->cleanup_msg[0], &bus->cleanup_msg[1]); #endif USB_BUS_UNLOCK(bus); #if USB_HAVE_PER_BUS_PROCESS /* Get rid of USB callback processes */ usb_proc_free(USB_BUS_GIANT_PROC(bus)); usb_proc_free(USB_BUS_NON_GIANT_ISOC_PROC(bus)); usb_proc_free(USB_BUS_NON_GIANT_BULK_PROC(bus)); /* Get rid of USB explore process */ usb_proc_free(USB_BUS_EXPLORE_PROC(bus)); /* Get rid of control transfer process */ usb_proc_free(USB_BUS_CONTROL_XFER_PROC(bus)); #endif #if USB_HAVE_PF usbpf_detach(bus); #endif return (0); } /*------------------------------------------------------------------------* * usb_suspend *------------------------------------------------------------------------*/ static int usb_suspend(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } USB_BUS_LOCK(bus); usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->suspend_msg[0], &bus->suspend_msg[1]); if (usb_no_suspend_wait == 0) { /* wait for suspend callback to be executed */ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus), &bus->suspend_msg[0], &bus->suspend_msg[1]); } USB_BUS_UNLOCK(bus); return (0); } /*------------------------------------------------------------------------* * usb_resume *------------------------------------------------------------------------*/ static int usb_resume(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } USB_BUS_LOCK(bus); usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->resume_msg[0], &bus->resume_msg[1]); USB_BUS_UNLOCK(bus); return (0); } /*------------------------------------------------------------------------* * usb_bus_reset_async_locked *------------------------------------------------------------------------*/ void usb_bus_reset_async_locked(struct usb_bus *bus) { USB_BUS_LOCK_ASSERT(bus, MA_OWNED); DPRINTF("\n"); if (bus->reset_msg[0].hdr.pm_qentry.tqe_prev != NULL || bus->reset_msg[1].hdr.pm_qentry.tqe_prev != NULL) { DPRINTF("Reset already pending\n"); return; } device_printf(bus->parent, "Resetting controller\n"); usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->reset_msg[0], &bus->reset_msg[1]); } /*------------------------------------------------------------------------* * usb_shutdown *------------------------------------------------------------------------*/ static int usb_shutdown(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } DPRINTF("%s: Controller shutdown\n", device_get_nameunit(bus->bdev)); USB_BUS_LOCK(bus); usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->shutdown_msg[0], &bus->shutdown_msg[1]); if (usb_no_shutdown_wait == 0) { /* wait for shutdown callback to be executed */ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus), &bus->shutdown_msg[0], &bus->shutdown_msg[1]); } USB_BUS_UNLOCK(bus); DPRINTF("%s: Controller shutdown complete\n", device_get_nameunit(bus->bdev)); return (0); } /*------------------------------------------------------------------------* * usb_bus_explore * * This function is used to explore the device tree from the root. *------------------------------------------------------------------------*/ static void usb_bus_explore(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (bus->no_explore != 0) return; if (udev != NULL) { USB_BUS_UNLOCK(bus); uhub_explore_handle_re_enumerate(udev); USB_BUS_LOCK(bus); } if (udev != NULL && udev->hub != NULL) { if (bus->do_probe) { bus->do_probe = 0; bus->driver_added_refcount++; } if (bus->driver_added_refcount == 0) { /* avoid zero, hence that is memory default */ bus->driver_added_refcount = 1; } #ifdef DDB /* * The following three lines of code are only here to * recover from DDB: */ usb_proc_rewakeup(USB_BUS_CONTROL_XFER_PROC(bus)); usb_proc_rewakeup(USB_BUS_GIANT_PROC(bus)); usb_proc_rewakeup(USB_BUS_NON_GIANT_ISOC_PROC(bus)); usb_proc_rewakeup(USB_BUS_NON_GIANT_BULK_PROC(bus)); #endif USB_BUS_UNLOCK(bus); #if USB_HAVE_POWERD /* * First update the USB power state! */ usb_bus_powerd(bus); #endif /* Explore the Root USB HUB. */ (udev->hub->explore) (udev); USB_BUS_LOCK(bus); } #if USB_HAVE_ROOT_MOUNT_HOLD usb_root_mount_rel(bus); #endif } /*------------------------------------------------------------------------* * usb_bus_detach * * This function is used to detach the device tree from the root. *------------------------------------------------------------------------*/ static void usb_bus_detach(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; device_t dev; bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; dev = bus->bdev; /* clear the softc */ device_set_softc(dev, NULL); USB_BUS_UNLOCK(bus); /* detach children first */ mtx_lock(&Giant); bus_generic_detach(dev); mtx_unlock(&Giant); /* * Free USB device and all subdevices, if any. */ usb_free_device(udev, 0); USB_BUS_LOCK(bus); /* clear bdev variable last */ bus->bdev = NULL; } /*------------------------------------------------------------------------* * usb_bus_suspend * * This function is used to suspend the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_suspend(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; usb_error_t err; uint8_t do_unlock; DPRINTF("\n"); bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev == NULL || bus->bdev == NULL) return; USB_BUS_UNLOCK(bus); /* * We use the shutdown event here because the suspend and * resume events are reserved for the USB port suspend and * resume. The USB system suspend is implemented like full * shutdown and all connected USB devices will be disconnected * subsequently. At resume all USB devices will be * re-connected again. */ bus_generic_shutdown(bus->bdev); do_unlock = usbd_enum_lock(udev); err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX); if (err) device_printf(bus->bdev, "Could not unconfigure root HUB\n"); USB_BUS_LOCK(bus); bus->hw_power_state = 0; bus->no_explore = 1; USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power != NULL) (bus->methods->set_hw_power) (bus); if (bus->methods->set_hw_power_sleep != NULL) (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SUSPEND); if (do_unlock) usbd_enum_unlock(udev); USB_BUS_LOCK(bus); } /*------------------------------------------------------------------------* * usb_bus_resume * * This function is used to resume the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_resume(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; usb_error_t err; uint8_t do_unlock; DPRINTF("\n"); bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev == NULL || bus->bdev == NULL) return; USB_BUS_UNLOCK(bus); do_unlock = usbd_enum_lock(udev); #if 0 DEVMETHOD(usb_take_controller, NULL); /* dummy */ #endif USB_TAKE_CONTROLLER(device_get_parent(bus->bdev)); USB_BUS_LOCK(bus); bus->hw_power_state = USB_HW_POWER_CONTROL | USB_HW_POWER_BULK | USB_HW_POWER_INTERRUPT | USB_HW_POWER_ISOC | USB_HW_POWER_NON_ROOT_HUB; bus->no_explore = 0; USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power_sleep != NULL) (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_RESUME); if (bus->methods->set_hw_power != NULL) (bus->methods->set_hw_power) (bus); /* restore USB configuration to index 0 */ err = usbd_set_config_index(udev, 0); if (err) device_printf(bus->bdev, "Could not configure root HUB\n"); /* probe and attach */ err = usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY); if (err) { device_printf(bus->bdev, "Could not probe and " "attach root HUB\n"); } if (do_unlock) usbd_enum_unlock(udev); USB_BUS_LOCK(bus); } /*------------------------------------------------------------------------* * usb_bus_reset * * This function is used to reset the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_reset(struct usb_proc_msg *pm) { struct usb_bus *bus; DPRINTF("\n"); bus = ((struct usb_bus_msg *)pm)->bus; if (bus->bdev == NULL || bus->no_explore != 0) return; /* a suspend and resume will reset the USB controller */ usb_bus_suspend(pm); usb_bus_resume(pm); } /*------------------------------------------------------------------------* * usb_bus_shutdown * * This function is used to shutdown the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_shutdown(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; usb_error_t err; uint8_t do_unlock; bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev == NULL || bus->bdev == NULL) return; USB_BUS_UNLOCK(bus); bus_generic_shutdown(bus->bdev); do_unlock = usbd_enum_lock(udev); err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX); if (err) device_printf(bus->bdev, "Could not unconfigure root HUB\n"); USB_BUS_LOCK(bus); bus->hw_power_state = 0; bus->no_explore = 1; USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power != NULL) (bus->methods->set_hw_power) (bus); if (bus->methods->set_hw_power_sleep != NULL) (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SHUTDOWN); if (do_unlock) usbd_enum_unlock(udev); USB_BUS_LOCK(bus); } /*------------------------------------------------------------------------* * usb_bus_cleanup * * This function is used to cleanup leftover USB character devices. *------------------------------------------------------------------------*/ #if USB_HAVE_UGEN static void usb_bus_cleanup(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_fs_privdata *pd; bus = ((struct usb_bus_msg *)pm)->bus; while ((pd = LIST_FIRST(&bus->pd_cleanup_list)) != NULL) { LIST_REMOVE(pd, pd_next); USB_BUS_UNLOCK(bus); usb_destroy_dev_sync(pd); USB_BUS_LOCK(bus); } } #endif static void usb_power_wdog(void *arg) { struct usb_bus *bus = arg; USB_BUS_LOCK_ASSERT(bus, MA_OWNED); usb_callout_reset(&bus->power_wdog, 4 * hz, usb_power_wdog, arg); #ifdef DDB /* * The following line of code is only here to recover from * DDB: */ usb_proc_rewakeup(USB_BUS_EXPLORE_PROC(bus)); /* recover from DDB */ #endif #if USB_HAVE_POWERD USB_BUS_UNLOCK(bus); usb_bus_power_update(bus); USB_BUS_LOCK(bus); #endif } /*------------------------------------------------------------------------* * usb_bus_attach * * This function attaches USB in context of the explore thread. *------------------------------------------------------------------------*/ static void usb_bus_attach(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *child; device_t dev; usb_error_t err; enum usb_dev_speed speed; bus = ((struct usb_bus_msg *)pm)->bus; dev = bus->bdev; DPRINTF("\n"); switch (bus->usbrev) { case USB_REV_1_0: speed = USB_SPEED_FULL; device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n"); break; case USB_REV_1_1: speed = USB_SPEED_FULL; device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n"); break; case USB_REV_2_0: speed = USB_SPEED_HIGH; device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n"); break; case USB_REV_2_5: speed = USB_SPEED_VARIABLE; device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n"); break; case USB_REV_3_0: speed = USB_SPEED_SUPER; device_printf(bus->bdev, "5.0Gbps Super Speed USB v3.0\n"); break; default: device_printf(bus->bdev, "Unsupported USB revision\n"); #if USB_HAVE_ROOT_MOUNT_HOLD usb_root_mount_rel(bus); #endif return; } /* default power_mask value */ bus->hw_power_state = USB_HW_POWER_CONTROL | USB_HW_POWER_BULK | USB_HW_POWER_INTERRUPT | USB_HW_POWER_ISOC | USB_HW_POWER_NON_ROOT_HUB; USB_BUS_UNLOCK(bus); /* make sure power is set at least once */ if (bus->methods->set_hw_power != NULL) { (bus->methods->set_hw_power) (bus); } /* allocate the Root USB device */ child = usb_alloc_device(bus->bdev, bus, NULL, 0, 0, 1, speed, USB_MODE_HOST); if (child) { err = usb_probe_and_attach(child, USB_IFACE_INDEX_ANY); if (!err) { if ((bus->devices[USB_ROOT_HUB_ADDR] == NULL) || (bus->devices[USB_ROOT_HUB_ADDR]->hub == NULL)) { err = USB_ERR_NO_ROOT_HUB; } } } else { err = USB_ERR_NOMEM; } USB_BUS_LOCK(bus); if (err) { device_printf(bus->bdev, "Root HUB problem, error=%s\n", usbd_errstr(err)); #if USB_HAVE_ROOT_MOUNT_HOLD usb_root_mount_rel(bus); #endif } /* set softc - we are ready */ device_set_softc(dev, bus); /* start watchdog */ usb_power_wdog(bus); } /*------------------------------------------------------------------------* * usb_attach_sub * * This function creates a thread which runs the USB attach code. *------------------------------------------------------------------------*/ static void usb_attach_sub(device_t dev, struct usb_bus *bus) { mtx_lock(&Giant); if (usb_devclass_ptr == NULL) usb_devclass_ptr = devclass_find("usbus"); mtx_unlock(&Giant); #if USB_HAVE_PF usbpf_attach(bus); #endif /* Initialise USB process messages */ bus->explore_msg[0].hdr.pm_callback = &usb_bus_explore; bus->explore_msg[0].bus = bus; bus->explore_msg[1].hdr.pm_callback = &usb_bus_explore; bus->explore_msg[1].bus = bus; bus->detach_msg[0].hdr.pm_callback = &usb_bus_detach; bus->detach_msg[0].bus = bus; bus->detach_msg[1].hdr.pm_callback = &usb_bus_detach; bus->detach_msg[1].bus = bus; bus->attach_msg[0].hdr.pm_callback = &usb_bus_attach; bus->attach_msg[0].bus = bus; bus->attach_msg[1].hdr.pm_callback = &usb_bus_attach; bus->attach_msg[1].bus = bus; bus->suspend_msg[0].hdr.pm_callback = &usb_bus_suspend; bus->suspend_msg[0].bus = bus; bus->suspend_msg[1].hdr.pm_callback = &usb_bus_suspend; bus->suspend_msg[1].bus = bus; bus->resume_msg[0].hdr.pm_callback = &usb_bus_resume; bus->resume_msg[0].bus = bus; bus->resume_msg[1].hdr.pm_callback = &usb_bus_resume; bus->resume_msg[1].bus = bus; bus->reset_msg[0].hdr.pm_callback = &usb_bus_reset; bus->reset_msg[0].bus = bus; bus->reset_msg[1].hdr.pm_callback = &usb_bus_reset; bus->reset_msg[1].bus = bus; bus->shutdown_msg[0].hdr.pm_callback = &usb_bus_shutdown; bus->shutdown_msg[0].bus = bus; bus->shutdown_msg[1].hdr.pm_callback = &usb_bus_shutdown; bus->shutdown_msg[1].bus = bus; #if USB_HAVE_UGEN LIST_INIT(&bus->pd_cleanup_list); bus->cleanup_msg[0].hdr.pm_callback = &usb_bus_cleanup; bus->cleanup_msg[0].bus = bus; bus->cleanup_msg[1].hdr.pm_callback = &usb_bus_cleanup; bus->cleanup_msg[1].bus = bus; #endif #if USB_HAVE_PER_BUS_PROCESS /* Create USB explore and callback processes */ if (usb_proc_create(USB_BUS_GIANT_PROC(bus), &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_MED)) { device_printf(dev, "WARNING: Creation of USB Giant " "callback process failed.\n"); } else if (usb_proc_create(USB_BUS_NON_GIANT_ISOC_PROC(bus), &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_HIGHEST)) { device_printf(dev, "WARNING: Creation of USB non-Giant ISOC " "callback process failed.\n"); } else if (usb_proc_create(USB_BUS_NON_GIANT_BULK_PROC(bus), &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_HIGH)) { device_printf(dev, "WARNING: Creation of USB non-Giant BULK " "callback process failed.\n"); } else if (usb_proc_create(USB_BUS_EXPLORE_PROC(bus), &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_MED)) { device_printf(dev, "WARNING: Creation of USB explore " "process failed.\n"); } else if (usb_proc_create(USB_BUS_CONTROL_XFER_PROC(bus), &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_MED)) { device_printf(dev, "WARNING: Creation of USB control transfer " "process failed.\n"); } else #endif { /* Get final attach going */ USB_BUS_LOCK(bus); usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->attach_msg[0], &bus->attach_msg[1]); USB_BUS_UNLOCK(bus); /* Do initial explore */ usb_needs_explore(bus, 1); } } SYSUNINIT(usb_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb_bus_unload, NULL); /*------------------------------------------------------------------------* * usb_bus_mem_flush_all_cb *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA static void usb_bus_mem_flush_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align) { usb_pc_cpu_flush(pc); } #endif /*------------------------------------------------------------------------* * usb_bus_mem_flush_all - factored out code *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA void usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) { if (cb) { cb(bus, &usb_bus_mem_flush_all_cb); } } #endif /*------------------------------------------------------------------------* * usb_bus_mem_alloc_all_cb *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA static void usb_bus_mem_alloc_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align) { /* need to initialize the page cache */ pc->tag_parent = bus->dma_parent_tag; if (usb_pc_alloc_mem(pc, pg, size, align)) { bus->alloc_failed = 1; } } #endif /*------------------------------------------------------------------------* * usb_bus_mem_alloc_all - factored out code * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ uint8_t usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, usb_bus_mem_cb_t *cb) { bus->alloc_failed = 0; mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent), "usb_def_mtx", MTX_DEF | MTX_RECURSE); mtx_init(&bus->bus_spin_lock, device_get_nameunit(bus->parent), "usb_spin_mtx", MTX_SPIN | MTX_RECURSE); usb_callout_init_mtx(&bus->power_wdog, &bus->bus_mtx, 0); TAILQ_INIT(&bus->intr_q.head); #if USB_HAVE_BUSDMA usb_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags, dmat, &bus->bus_mtx, NULL, bus->dma_bits, USB_BUS_DMA_TAG_MAX); #endif if ((bus->devices_max > USB_MAX_DEVICES) || (bus->devices_max < USB_MIN_DEVICES) || (bus->devices == NULL)) { DPRINTFN(0, "Devices field has not been " "initialised properly\n"); bus->alloc_failed = 1; /* failure */ } #if USB_HAVE_BUSDMA if (cb) { cb(bus, &usb_bus_mem_alloc_all_cb); } #endif if (bus->alloc_failed) { usb_bus_mem_free_all(bus, cb); } return (bus->alloc_failed); } /*------------------------------------------------------------------------* * usb_bus_mem_free_all_cb *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA static void usb_bus_mem_free_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align) { usb_pc_free_mem(pc); } #endif /*------------------------------------------------------------------------* * usb_bus_mem_free_all - factored out code *------------------------------------------------------------------------*/ void usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) { #if USB_HAVE_BUSDMA if (cb) { cb(bus, &usb_bus_mem_free_all_cb); } usb_dma_tag_unsetup(bus->dma_parent_tag); #endif mtx_destroy(&bus->bus_mtx); mtx_destroy(&bus->bus_spin_lock); } /* convenience wrappers */ void usb_proc_explore_mwait(struct usb_device *udev, void *pm1, void *pm2) { usb_proc_mwait(USB_BUS_EXPLORE_PROC(udev->bus), pm1, pm2); } void * usb_proc_explore_msignal(struct usb_device *udev, void *pm1, void *pm2) { return (usb_proc_msignal(USB_BUS_EXPLORE_PROC(udev->bus), pm1, pm2)); } void usb_proc_explore_lock(struct usb_device *udev) { USB_BUS_LOCK(udev->bus); } void usb_proc_explore_unlock(struct usb_device *udev) { USB_BUS_UNLOCK(udev->bus); } Index: head/sys/dev/usb/controller/uss820dci.c =================================================================== --- head/sys/dev/usb/controller/uss820dci.c (revision 357971) +++ head/sys/dev/usb/controller/uss820dci.c (revision 357972) @@ -1,2448 +1,2448 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the driver for the USS820 series USB Device * Controller * * NOTE: The datasheet does not document everything. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR uss820dcidebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #define USS820_DCI_BUS2SC(bus) \ ((struct uss820dci_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct uss820dci_softc *)0)->sc_bus)))) #define USS820_DCI_PC2SC(pc) \ USS820_DCI_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) #define USS820_DCI_THREAD_IRQ \ (USS820_SSR_SUSPEND | USS820_SSR_RESUME | USS820_SSR_RESET) #ifdef USB_DEBUG static int uss820dcidebug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uss820dci, CTLFLAG_RW, 0, +static SYSCTL_NODE(_hw_usb, OID_AUTO, uss820dci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB uss820dci"); SYSCTL_INT(_hw_usb_uss820dci, OID_AUTO, debug, CTLFLAG_RWTUN, &uss820dcidebug, 0, "uss820dci debug level"); #endif #define USS820_DCI_INTR_ENDPT 1 /* prototypes */ static const struct usb_bus_methods uss820dci_bus_methods; static const struct usb_pipe_methods uss820dci_device_bulk_methods; static const struct usb_pipe_methods uss820dci_device_ctrl_methods; static const struct usb_pipe_methods uss820dci_device_intr_methods; static const struct usb_pipe_methods uss820dci_device_isoc_fs_methods; static uss820dci_cmd_t uss820dci_setup_rx; static uss820dci_cmd_t uss820dci_data_rx; static uss820dci_cmd_t uss820dci_data_tx; static uss820dci_cmd_t uss820dci_data_tx_sync; static void uss820dci_device_done(struct usb_xfer *, usb_error_t); static void uss820dci_do_poll(struct usb_bus *); static void uss820dci_standard_done(struct usb_xfer *); static void uss820dci_intr_set(struct usb_xfer *, uint8_t); static void uss820dci_update_shared_1(struct uss820dci_softc *, uint8_t, uint8_t, uint8_t); static void uss820dci_root_intr(struct uss820dci_softc *); /* * Here is a list of what the USS820D chip can support. The main * limitation is that the sum of the buffer sizes must be less than * 1120 bytes. */ static const struct usb_hw_ep_profile uss820dci_ep_profile[] = { [0] = { .max_in_frame_size = 32, .max_out_frame_size = 32, .is_simplex = 0, .support_control = 1, }, [1] = { .max_in_frame_size = 64, .max_out_frame_size = 64, .is_simplex = 0, .support_multi_buffer = 1, .support_bulk = 1, .support_interrupt = 1, .support_in = 1, .support_out = 1, }, [2] = { .max_in_frame_size = 8, .max_out_frame_size = 8, .is_simplex = 0, .support_multi_buffer = 1, .support_bulk = 1, .support_interrupt = 1, .support_in = 1, .support_out = 1, }, [3] = { .max_in_frame_size = 256, .max_out_frame_size = 256, .is_simplex = 0, .support_multi_buffer = 1, .support_isochronous = 1, .support_in = 1, .support_out = 1, }, }; static void uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg, uint8_t keep_mask, uint8_t set_mask) { uint8_t temp; USS820_WRITE_1(sc, USS820_PEND, 1); temp = USS820_READ_1(sc, reg); temp &= (keep_mask); temp |= (set_mask); USS820_WRITE_1(sc, reg, temp); USS820_WRITE_1(sc, USS820_PEND, 0); } static void uss820dci_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { if (ep_addr == 0) { *ppf = uss820dci_ep_profile + 0; } else if (ep_addr < 5) { *ppf = uss820dci_ep_profile + 1; } else if (ep_addr < 7) { *ppf = uss820dci_ep_profile + 2; } else if (ep_addr == 7) { *ppf = uss820dci_ep_profile + 3; } else { *ppf = NULL; } } static void uss820dci_pull_up(struct uss820dci_softc *sc) { uint8_t temp; /* pullup D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { sc->sc_flags.d_pulled_up = 1; DPRINTF("\n"); temp = USS820_READ_1(sc, USS820_MCSR); temp |= USS820_MCSR_DPEN; USS820_WRITE_1(sc, USS820_MCSR, temp); } } static void uss820dci_pull_down(struct uss820dci_softc *sc) { uint8_t temp; /* pulldown D+, if possible */ if (sc->sc_flags.d_pulled_up) { sc->sc_flags.d_pulled_up = 0; DPRINTF("\n"); temp = USS820_READ_1(sc, USS820_MCSR); temp &= ~USS820_MCSR_DPEN; USS820_WRITE_1(sc, USS820_MCSR, temp); } } static void uss820dci_wakeup_peer(struct uss820dci_softc *sc) { if (!(sc->sc_flags.status_suspend)) { return; } DPRINTFN(0, "not supported\n"); } static void uss820dci_set_address(struct uss820dci_softc *sc, uint8_t addr) { DPRINTFN(5, "addr=%d\n", addr); USS820_WRITE_1(sc, USS820_FADDR, addr); } static uint8_t uss820dci_setup_rx(struct uss820dci_softc *sc, struct uss820dci_td *td) { struct usb_device_request req; uint16_t count; uint8_t rx_stat; uint8_t temp; /* select the correct endpoint */ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index); /* read out FIFO status */ rx_stat = USS820_READ_1(sc, USS820_RXSTAT); DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder); if (!(rx_stat & USS820_RXSTAT_RXSETUP)) { goto not_complete; } /* clear did stall */ td->did_stall = 0; /* clear stall and all I/O */ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF ^ (USS820_EPCON_TXSTL | USS820_EPCON_RXSTL | USS820_EPCON_RXIE | USS820_EPCON_TXOE), 0); /* clear end overwrite flag */ uss820dci_update_shared_1(sc, USS820_RXSTAT, 0xFF ^ USS820_RXSTAT_EDOVW, 0); /* get the packet byte count */ count = USS820_READ_1(sc, USS820_RXCNTL); count |= (USS820_READ_1(sc, USS820_RXCNTH) << 8); count &= 0x3FF; /* verify data length */ if (count != td->remainder) { DPRINTFN(0, "Invalid SETUP packet " "length, %d bytes\n", count); goto setup_not_complete; } if (count != sizeof(req)) { DPRINTFN(0, "Unsupported SETUP packet " "length, %d bytes\n", count); goto setup_not_complete; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, USS820_RXDAT * USS820_REG_STRIDE, (void *)&req, sizeof(req)); /* read out FIFO status */ rx_stat = USS820_READ_1(sc, USS820_RXSTAT); if (rx_stat & (USS820_RXSTAT_EDOVW | USS820_RXSTAT_STOVW)) { DPRINTF("new SETUP packet received\n"); return (1); /* not complete */ } /* clear receive setup bit */ uss820dci_update_shared_1(sc, USS820_RXSTAT, 0xFF ^ (USS820_RXSTAT_RXSETUP | USS820_RXSTAT_EDOVW | USS820_RXSTAT_STOVW), 0); /* set RXFFRC bit */ temp = USS820_READ_1(sc, USS820_RXCON); temp |= USS820_RXCON_RXFFRC; USS820_WRITE_1(sc, USS820_RXCON, temp); /* copy data into real buffer */ usbd_copy_in(td->pc, 0, &req, sizeof(req)); td->offset = sizeof(req); td->remainder = 0; /* sneak peek the set address */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { sc->sc_dv_addr = req.wValue[0] & 0x7F; } else { sc->sc_dv_addr = 0xFF; } /* reset TX FIFO */ temp = USS820_READ_1(sc, USS820_TXCON); temp |= USS820_TXCON_TXCLR; USS820_WRITE_1(sc, USS820_TXCON, temp); temp &= ~USS820_TXCON_TXCLR; USS820_WRITE_1(sc, USS820_TXCON, temp); return (0); /* complete */ setup_not_complete: /* set RXFFRC bit */ temp = USS820_READ_1(sc, USS820_RXCON); temp |= USS820_RXCON_RXFFRC; USS820_WRITE_1(sc, USS820_RXCON, temp); /* FALLTHROUGH */ not_complete: /* abort any ongoing transfer */ if (!td->did_stall) { DPRINTFN(5, "stalling\n"); /* set stall */ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, (USS820_EPCON_TXSTL | USS820_EPCON_RXSTL)); td->did_stall = 1; } /* clear end overwrite flag, if any */ if (rx_stat & USS820_RXSTAT_RXSETUP) { uss820dci_update_shared_1(sc, USS820_RXSTAT, 0xFF ^ (USS820_RXSTAT_EDOVW | USS820_RXSTAT_STOVW | USS820_RXSTAT_RXSETUP), 0); } return (1); /* not complete */ } static uint8_t uss820dci_data_rx(struct uss820dci_softc *sc, struct uss820dci_td *td) { struct usb_page_search buf_res; uint16_t count; uint8_t rx_flag; uint8_t rx_stat; uint8_t rx_cntl; uint8_t to; uint8_t got_short; to = 2; /* don't loop forever! */ got_short = 0; /* select the correct endpoint */ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index); /* check if any of the FIFO banks have data */ repeat: /* read out FIFO flag */ rx_flag = USS820_READ_1(sc, USS820_RXFLG); /* read out FIFO status */ rx_stat = USS820_READ_1(sc, USS820_RXSTAT); DPRINTFN(5, "rx_stat=0x%02x rx_flag=0x%02x rem=%u\n", rx_stat, rx_flag, td->remainder); if (rx_stat & (USS820_RXSTAT_RXSETUP | USS820_RXSTAT_RXSOVW | USS820_RXSTAT_EDOVW)) { if (td->remainder == 0 && td->ep_index == 0) { /* * We are actually complete and have * received the next SETUP */ DPRINTFN(5, "faking complete\n"); return (0); /* complete */ } /* * USB Host Aborted the transfer. */ td->error = 1; return (0); /* complete */ } /* check for errors */ if (rx_flag & (USS820_RXFLG_RXOVF | USS820_RXFLG_RXURF)) { DPRINTFN(5, "overflow or underflow\n"); /* should not happen */ td->error = 1; return (0); /* complete */ } /* check status */ if (!(rx_flag & (USS820_RXFLG_RXFIF0 | USS820_RXFLG_RXFIF1))) { /* read out EPCON register */ /* enable RX input */ if (!td->did_enable) { uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc), USS820_EPCON, 0xFF, USS820_EPCON_RXIE); td->did_enable = 1; } return (1); /* not complete */ } /* get the packet byte count */ count = USS820_READ_1(sc, USS820_RXCNTL); count |= (USS820_READ_1(sc, USS820_RXCNTH) << 8); count &= 0x3FF; DPRINTFN(5, "count=0x%04x\n", count); /* verify the packet byte count */ if (count != td->max_packet_size) { if (count < td->max_packet_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } while (count > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, USS820_RXDAT * USS820_REG_STRIDE, buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* set RXFFRC bit */ rx_cntl = USS820_READ_1(sc, USS820_RXCON); rx_cntl |= USS820_RXCON_RXFFRC; USS820_WRITE_1(sc, USS820_RXCON, rx_cntl); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ return (0); } /* else need to receive a zero length packet */ } if (--to) { goto repeat; } return (1); /* not complete */ } static uint8_t uss820dci_data_tx(struct uss820dci_softc *sc, struct uss820dci_td *td) { struct usb_page_search buf_res; uint16_t count; uint16_t count_copy; uint8_t rx_stat; uint8_t tx_flag; uint8_t to; /* select the correct endpoint */ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index); to = 2; /* don't loop forever! */ repeat: /* read out TX FIFO flags */ tx_flag = USS820_READ_1(sc, USS820_TXFLG); DPRINTFN(5, "tx_flag=0x%02x rem=%u\n", tx_flag, td->remainder); if (td->ep_index == 0) { /* read out RX FIFO status last */ rx_stat = USS820_READ_1(sc, USS820_RXSTAT); DPRINTFN(5, "rx_stat=0x%02x\n", rx_stat); if (rx_stat & (USS820_RXSTAT_RXSETUP | USS820_RXSTAT_RXSOVW | USS820_RXSTAT_EDOVW)) { /* * The current transfer was aborted by the USB * Host: */ td->error = 1; return (0); /* complete */ } } if (tx_flag & (USS820_TXFLG_TXOVF | USS820_TXFLG_TXURF)) { td->error = 1; return (0); /* complete */ } if (tx_flag & USS820_TXFLG_TXFIF0) { if (tx_flag & USS820_TXFLG_TXFIF1) { return (1); /* not complete */ } } if ((!td->support_multi_buffer) && (tx_flag & (USS820_TXFLG_TXFIF0 | USS820_TXFLG_TXFIF1))) { return (1); /* not complete */ } count = td->max_packet_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } count_copy = count; while (count > 0) { usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, USS820_TXDAT * USS820_REG_STRIDE, buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* post-write high packet byte count first */ USS820_WRITE_1(sc, USS820_TXCNTH, count_copy >> 8); /* post-write low packet byte count last */ USS820_WRITE_1(sc, USS820_TXCNTL, count_copy); /* * Enable TX output, which must happen after that we have written * data into the FIFO. This is undocumented. */ if (!td->did_enable) { uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc), USS820_EPCON, 0xFF, USS820_EPCON_TXOE); td->did_enable = 1; } /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { return (0); /* complete */ } /* else we need to transmit a short packet */ } if (--to) { goto repeat; } return (1); /* not complete */ } static uint8_t uss820dci_data_tx_sync(struct uss820dci_softc *sc, struct uss820dci_td *td) { uint8_t rx_stat; uint8_t tx_flag; /* select the correct endpoint */ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index); /* read out TX FIFO flag */ tx_flag = USS820_READ_1(sc, USS820_TXFLG); if (td->ep_index == 0) { /* read out RX FIFO status last */ rx_stat = USS820_READ_1(sc, USS820_RXSTAT); DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder); if (rx_stat & (USS820_RXSTAT_RXSETUP | USS820_RXSTAT_RXSOVW | USS820_RXSTAT_EDOVW)) { DPRINTFN(5, "faking complete\n"); /* Race condition */ return (0); /* complete */ } } DPRINTFN(5, "tx_flag=0x%02x rem=%u\n", tx_flag, td->remainder); if (tx_flag & (USS820_TXFLG_TXOVF | USS820_TXFLG_TXURF)) { td->error = 1; return (0); /* complete */ } if (tx_flag & (USS820_TXFLG_TXFIF0 | USS820_TXFLG_TXFIF1)) { return (1); /* not complete */ } if (td->ep_index == 0 && sc->sc_dv_addr != 0xFF) { /* write function address */ uss820dci_set_address(sc, sc->sc_dv_addr); } return (0); /* complete */ } static void uss820dci_xfer_do_fifo(struct usb_xfer *xfer) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); struct uss820dci_td *td; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; if (td == NULL) return; while (1) { if ((td->func) (sc, td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) { goto done; } } /* * Fetch the next transfer descriptor. */ td = td->obj_next; xfer->td_transfer_cache = td; } return; done: /* compute all actual lengths */ xfer->td_transfer_cache = NULL; sc->sc_xfer_complete = 1; } static uint8_t uss820dci_xfer_do_complete(struct usb_xfer *xfer) { struct uss820dci_td *td; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; if (td == NULL) { /* compute all actual lengths */ uss820dci_standard_done(xfer); return(1); } return (0); } static void uss820dci_interrupt_poll_locked(struct uss820dci_softc *sc) { struct usb_xfer *xfer; TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) uss820dci_xfer_do_fifo(xfer); } static void uss820dci_interrupt_complete_locked(struct uss820dci_softc *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (uss820dci_xfer_do_complete(xfer)) goto repeat; } } static void uss820dci_wait_suspend(struct uss820dci_softc *sc, uint8_t on) { uint8_t scr; uint8_t scratch; scr = USS820_READ_1(sc, USS820_SCR); scratch = USS820_READ_1(sc, USS820_SCRATCH); if (on) { scr |= USS820_SCR_IE_SUSP; scratch &= ~USS820_SCRATCH_IE_RESUME; } else { scr &= ~USS820_SCR_IE_SUSP; scratch |= USS820_SCRATCH_IE_RESUME; } USS820_WRITE_1(sc, USS820_SCR, scr); USS820_WRITE_1(sc, USS820_SCRATCH, scratch); } int uss820dci_filter_interrupt(void *arg) { struct uss820dci_softc *sc = arg; int retval = FILTER_HANDLED; uint8_t ssr; USB_BUS_SPIN_LOCK(&sc->sc_bus); ssr = USS820_READ_1(sc, USS820_SSR); uss820dci_update_shared_1(sc, USS820_SSR, USS820_DCI_THREAD_IRQ, 0); if (ssr & USS820_DCI_THREAD_IRQ) retval = FILTER_SCHEDULE_THREAD; /* poll FIFOs, if any */ uss820dci_interrupt_poll_locked(sc); if (sc->sc_xfer_complete != 0) retval = FILTER_SCHEDULE_THREAD; USB_BUS_SPIN_UNLOCK(&sc->sc_bus); return (retval); } void uss820dci_interrupt(void *arg) { struct uss820dci_softc *sc = arg; uint8_t ssr; uint8_t event; USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); ssr = USS820_READ_1(sc, USS820_SSR); /* acknowledge all interrupts */ uss820dci_update_shared_1(sc, USS820_SSR, ~USS820_DCI_THREAD_IRQ, 0); /* check for any bus state change interrupts */ if (ssr & USS820_DCI_THREAD_IRQ) { event = 0; if (ssr & USS820_SSR_RESET) { sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* disable resume interrupt */ uss820dci_wait_suspend(sc, 1); event = 1; } /* * If "RESUME" and "SUSPEND" is set at the same time * we interpret that like "RESUME". Resume is set when * there is at least 3 milliseconds of inactivity on * the USB BUS. */ if (ssr & USS820_SSR_RESUME) { if (sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; /* disable resume interrupt */ uss820dci_wait_suspend(sc, 1); event = 1; } } else if (ssr & USS820_SSR_SUSPEND) { if (!sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; /* enable resume interrupt */ uss820dci_wait_suspend(sc, 0); event = 1; } } if (event) { DPRINTF("real bus interrupt 0x%02x\n", ssr); /* complete root HUB interrupt endpoint */ uss820dci_root_intr(sc); } } /* acknowledge all SBI interrupts */ uss820dci_update_shared_1(sc, USS820_SBI, 0, 0); /* acknowledge all SBI1 interrupts */ uss820dci_update_shared_1(sc, USS820_SBI1, 0, 0); if (sc->sc_xfer_complete != 0) { sc->sc_xfer_complete = 0; /* complete FIFOs, if any */ uss820dci_interrupt_complete_locked(sc); } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } static void uss820dci_setup_standard_chain_sub(struct uss820_std_temp *temp) { struct uss820dci_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->error = 0; td->did_enable = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; } static void uss820dci_setup_standard_chain(struct usb_xfer *xfer) { struct uss820_std_temp temp; struct uss820dci_softc *sc; struct uss820dci_td *td; uint32_t x; uint8_t ep_no; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = USS820_DCI_BUS2SC(xfer->xroot->bus); ep_no = (xfer->endpointno & UE_ADDR); /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { temp.func = &uss820dci_setup_rx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) temp.setup_alt_next = 0; } uss820dci_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) { temp.func = &uss820dci_data_tx; } else { temp.func = &uss820dci_data_rx; } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; } uss820dci_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } /* check for control transfer */ if (xfer->flags_int.control_xfr) { uint8_t need_sync; /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (xfer->endpointno & UE_DIR_IN) { temp.func = &uss820dci_data_rx; need_sync = 0; } else { temp.func = &uss820dci_data_tx; need_sync = 1; } temp.len = 0; temp.short_pkt = 0; uss820dci_setup_standard_chain_sub(&temp); if (need_sync) { /* we need a SYNC point after TX */ temp.func = &uss820dci_data_tx_sync; uss820dci_setup_standard_chain_sub(&temp); } } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; } static void uss820dci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ uss820dci_device_done(xfer, USB_ERR_TIMEOUT); } static void uss820dci_intr_set(struct usb_xfer *xfer, uint8_t set) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); uint8_t ep_no = (xfer->endpointno & UE_ADDR); uint8_t ep_reg; uint8_t temp; DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpointno); if (ep_no > 3) { ep_reg = USS820_SBIE1; } else { ep_reg = USS820_SBIE; } ep_no &= 3; ep_no = 1 << (2 * ep_no); if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { ep_no <<= 1; /* RX interrupt only */ } else { ep_no |= (ep_no << 1); /* RX and TX interrupt */ } } else { if (!(xfer->endpointno & UE_DIR_IN)) { ep_no <<= 1; } } temp = USS820_READ_1(sc, ep_reg); if (set) { temp |= ep_no; } else { temp &= ~ep_no; } USS820_WRITE_1(sc, ep_reg, temp); } static void uss820dci_start_standard_chain(struct usb_xfer *xfer) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); DPRINTFN(9, "\n"); USB_BUS_SPIN_LOCK(&sc->sc_bus); /* poll one time */ uss820dci_xfer_do_fifo(xfer); if (uss820dci_xfer_do_complete(xfer) == 0) { /* * Only enable the endpoint interrupt when we are * actually waiting for data, hence we are dealing * with level triggered interrupts ! */ uss820dci_intr_set(xfer, 1); /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &uss820dci_timeout, xfer->timeout); } } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void uss820dci_root_intr(struct uss820dci_softc *sc) { DPRINTFN(9, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit */ sc->sc_hub_idata[0] = 0x02; /* we only have one port */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t uss820dci_standard_done_sub(struct usb_xfer *xfer) { struct uss820dci_td *td; uint32_t len; uint8_t error; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error) { /* the transfer is finished */ error = 1; td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error ? USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); } static void uss820dci_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = uss820dci_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = uss820dci_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = uss820dci_standard_done_sub(xfer); } done: uss820dci_device_done(xfer, err); } /*------------------------------------------------------------------------* * uss820dci_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void uss820dci_device_done(struct usb_xfer *xfer, usb_error_t error) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); USB_BUS_SPIN_LOCK(&sc->sc_bus); if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { uss820dci_intr_set(xfer, 0); } /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void uss820dci_xfer_stall(struct usb_xfer *xfer) { uss820dci_device_done(xfer, USB_ERR_STALLED); } static void uss820dci_set_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall) { struct uss820dci_softc *sc; uint8_t ep_no; uint8_t ep_type; uint8_t ep_dir; uint8_t temp; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(5, "endpoint=%p\n", ep); /* set FORCESTALL */ sc = USS820_DCI_BUS2SC(udev->bus); ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); ep_dir = (ep->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)); ep_type = (ep->edesc->bmAttributes & UE_XFERTYPE); if (ep_type == UE_CONTROL) { /* should not happen */ return; } USB_BUS_SPIN_LOCK(&sc->sc_bus); USS820_WRITE_1(sc, USS820_EPINDEX, ep_no); if (ep_dir == UE_DIR_IN) { temp = USS820_EPCON_TXSTL; } else { temp = USS820_EPCON_RXSTL; } uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void uss820dci_clear_stall_sub(struct uss820dci_softc *sc, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { uint8_t temp; if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } USB_BUS_SPIN_LOCK(&sc->sc_bus); /* select endpoint index */ USS820_WRITE_1(sc, USS820_EPINDEX, ep_no); /* clear stall and disable I/O transfers */ if (ep_dir == UE_DIR_IN) { temp = 0xFF ^ (USS820_EPCON_TXOE | USS820_EPCON_TXSTL); } else { temp = 0xFF ^ (USS820_EPCON_RXIE | USS820_EPCON_RXSTL); } uss820dci_update_shared_1(sc, USS820_EPCON, temp, 0); if (ep_dir == UE_DIR_IN) { /* reset data toggle */ USS820_WRITE_1(sc, USS820_TXSTAT, USS820_TXSTAT_TXSOVW); /* reset FIFO */ temp = USS820_READ_1(sc, USS820_TXCON); temp |= USS820_TXCON_TXCLR; USS820_WRITE_1(sc, USS820_TXCON, temp); temp &= ~USS820_TXCON_TXCLR; USS820_WRITE_1(sc, USS820_TXCON, temp); } else { /* reset data toggle */ uss820dci_update_shared_1(sc, USS820_RXSTAT, 0, USS820_RXSTAT_RXSOVW); /* reset FIFO */ temp = USS820_READ_1(sc, USS820_RXCON); temp |= USS820_RXCON_RXCLR; temp &= ~USS820_RXCON_RXFFRC; USS820_WRITE_1(sc, USS820_RXCON, temp); temp &= ~USS820_RXCON_RXCLR; USS820_WRITE_1(sc, USS820_RXCON, temp); } USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void uss820dci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct uss820dci_softc *sc; struct usb_endpoint_descriptor *ed; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(5, "endpoint=%p\n", ep); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = USS820_DCI_BUS2SC(udev->bus); /* get endpoint descriptor */ ed = ep->edesc; /* reset endpoint */ uss820dci_clear_stall_sub(sc, (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); } usb_error_t uss820dci_init(struct uss820dci_softc *sc) { const struct usb_hw_ep_profile *pf; uint8_t n; uint8_t temp; DPRINTF("start\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_1_1; sc->sc_bus.methods = &uss820dci_bus_methods; USB_BUS_LOCK(&sc->sc_bus); /* we always have VBUS */ sc->sc_flags.status_vbus = 1; /* reset the chip */ USS820_WRITE_1(sc, USS820_SCR, USS820_SCR_SRESET); DELAY(100); USS820_WRITE_1(sc, USS820_SCR, 0); /* wait for reset to complete */ for (n = 0;; n++) { temp = USS820_READ_1(sc, USS820_MCSR); if (temp & USS820_MCSR_INIT) { break; } if (n == 100) { USB_BUS_UNLOCK(&sc->sc_bus); return (USB_ERR_INVAL); } /* wait a little for things to stabilise */ DELAY(100); } /* do a pulldown */ uss820dci_pull_down(sc); /* wait 10ms for pulldown to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* check hardware revision */ temp = USS820_READ_1(sc, USS820_REV); if (temp < 0x13) { USB_BUS_UNLOCK(&sc->sc_bus); return (USB_ERR_INVAL); } /* enable interrupts */ USS820_WRITE_1(sc, USS820_SCR, USS820_SCR_T_IRQ | USS820_SCR_IE_RESET | /* USS820_SCR_RWUPE | */ USS820_SCR_IE_SUSP | USS820_SCR_IRQPOL); /* enable interrupts */ USS820_WRITE_1(sc, USS820_SCRATCH, USS820_SCRATCH_IE_RESUME); /* enable features */ USS820_WRITE_1(sc, USS820_MCSR, USS820_MCSR_BDFEAT | USS820_MCSR_FEAT); sc->sc_flags.mcsr_feat = 1; /* disable interrupts */ USS820_WRITE_1(sc, USS820_SBIE, 0); /* disable interrupts */ USS820_WRITE_1(sc, USS820_SBIE1, 0); /* disable all endpoints */ for (n = 0; n != USS820_EP_MAX; n++) { /* select endpoint */ USS820_WRITE_1(sc, USS820_EPINDEX, n); /* disable endpoint */ uss820dci_update_shared_1(sc, USS820_EPCON, 0, 0); } /* * Initialise default values for some registers that cannot be * changed during operation! */ for (n = 0; n != USS820_EP_MAX; n++) { uss820dci_get_hw_ep_profile(NULL, &pf, n); /* the maximum frame sizes should be the same */ if (pf->max_in_frame_size != pf->max_out_frame_size) { DPRINTF("Max frame size mismatch %u != %u\n", pf->max_in_frame_size, pf->max_out_frame_size); } if (pf->support_isochronous) { if (pf->max_in_frame_size <= 64) { temp = (USS820_TXCON_FFSZ_16_64 | USS820_TXCON_TXISO | USS820_TXCON_ATM); } else if (pf->max_in_frame_size <= 256) { temp = (USS820_TXCON_FFSZ_64_256 | USS820_TXCON_TXISO | USS820_TXCON_ATM); } else if (pf->max_in_frame_size <= 512) { temp = (USS820_TXCON_FFSZ_8_512 | USS820_TXCON_TXISO | USS820_TXCON_ATM); } else { /* 1024 bytes */ temp = (USS820_TXCON_FFSZ_32_1024 | USS820_TXCON_TXISO | USS820_TXCON_ATM); } } else { if ((pf->max_in_frame_size <= 8) && (sc->sc_flags.mcsr_feat)) { temp = (USS820_TXCON_FFSZ_8_512 | USS820_TXCON_ATM); } else if (pf->max_in_frame_size <= 16) { temp = (USS820_TXCON_FFSZ_16_64 | USS820_TXCON_ATM); } else if ((pf->max_in_frame_size <= 32) && (sc->sc_flags.mcsr_feat)) { temp = (USS820_TXCON_FFSZ_32_1024 | USS820_TXCON_ATM); } else { /* 64 bytes */ temp = (USS820_TXCON_FFSZ_64_256 | USS820_TXCON_ATM); } } /* need to configure the chip early */ USS820_WRITE_1(sc, USS820_EPINDEX, n); USS820_WRITE_1(sc, USS820_TXCON, temp); USS820_WRITE_1(sc, USS820_RXCON, temp); if (pf->support_control) { temp = USS820_EPCON_CTLEP | USS820_EPCON_RXSPM | USS820_EPCON_RXIE | USS820_EPCON_RXEPEN | USS820_EPCON_TXOE | USS820_EPCON_TXEPEN; } else { temp = USS820_EPCON_RXEPEN | USS820_EPCON_TXEPEN; } uss820dci_update_shared_1(sc, USS820_EPCON, 0, temp); } USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ uss820dci_do_poll(&sc->sc_bus); return (0); /* success */ } void uss820dci_uninit(struct uss820dci_softc *sc) { uint8_t temp; USB_BUS_LOCK(&sc->sc_bus); /* disable all interrupts */ temp = USS820_READ_1(sc, USS820_SCR); temp &= ~USS820_SCR_T_IRQ; USS820_WRITE_1(sc, USS820_SCR, temp); sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; uss820dci_pull_down(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void uss820dci_suspend(struct uss820dci_softc *sc) { /* TODO */ } static void uss820dci_resume(struct uss820dci_softc *sc) { /* TODO */ } static void uss820dci_do_poll(struct usb_bus *bus) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); USB_BUS_SPIN_LOCK(&sc->sc_bus); uss820dci_interrupt_poll_locked(sc); uss820dci_interrupt_complete_locked(sc); USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * uss820dci bulk support *------------------------------------------------------------------------*/ static void uss820dci_device_bulk_open(struct usb_xfer *xfer) { return; } static void uss820dci_device_bulk_close(struct usb_xfer *xfer) { uss820dci_device_done(xfer, USB_ERR_CANCELLED); } static void uss820dci_device_bulk_enter(struct usb_xfer *xfer) { return; } static void uss820dci_device_bulk_start(struct usb_xfer *xfer) { /* setup TDs */ uss820dci_setup_standard_chain(xfer); uss820dci_start_standard_chain(xfer); } static const struct usb_pipe_methods uss820dci_device_bulk_methods = { .open = uss820dci_device_bulk_open, .close = uss820dci_device_bulk_close, .enter = uss820dci_device_bulk_enter, .start = uss820dci_device_bulk_start, }; /*------------------------------------------------------------------------* * uss820dci control support *------------------------------------------------------------------------*/ static void uss820dci_device_ctrl_open(struct usb_xfer *xfer) { return; } static void uss820dci_device_ctrl_close(struct usb_xfer *xfer) { uss820dci_device_done(xfer, USB_ERR_CANCELLED); } static void uss820dci_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void uss820dci_device_ctrl_start(struct usb_xfer *xfer) { /* setup TDs */ uss820dci_setup_standard_chain(xfer); uss820dci_start_standard_chain(xfer); } static const struct usb_pipe_methods uss820dci_device_ctrl_methods = { .open = uss820dci_device_ctrl_open, .close = uss820dci_device_ctrl_close, .enter = uss820dci_device_ctrl_enter, .start = uss820dci_device_ctrl_start, }; /*------------------------------------------------------------------------* * uss820dci interrupt support *------------------------------------------------------------------------*/ static void uss820dci_device_intr_open(struct usb_xfer *xfer) { return; } static void uss820dci_device_intr_close(struct usb_xfer *xfer) { uss820dci_device_done(xfer, USB_ERR_CANCELLED); } static void uss820dci_device_intr_enter(struct usb_xfer *xfer) { return; } static void uss820dci_device_intr_start(struct usb_xfer *xfer) { /* setup TDs */ uss820dci_setup_standard_chain(xfer); uss820dci_start_standard_chain(xfer); } static const struct usb_pipe_methods uss820dci_device_intr_methods = { .open = uss820dci_device_intr_open, .close = uss820dci_device_intr_close, .enter = uss820dci_device_intr_enter, .start = uss820dci_device_intr_start, }; /*------------------------------------------------------------------------* * uss820dci full speed isochronous support *------------------------------------------------------------------------*/ static void uss820dci_device_isoc_fs_open(struct usb_xfer *xfer) { return; } static void uss820dci_device_isoc_fs_close(struct usb_xfer *xfer) { uss820dci_device_done(xfer, USB_ERR_CANCELLED); } static void uss820dci_device_isoc_fs_enter(struct usb_xfer *xfer) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index - we don't need the high bits */ nframes = USS820_READ_1(sc, USS820_SOFL); /* * check if the frame index is within the window where the * frames will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & USS820_SOFL_MASK; if ((xfer->endpoint->is_synced == 0) || (temp < xfer->nframes)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & USS820_SOFL_MASK; xfer->endpoint->is_synced = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & USS820_SOFL_MASK; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + xfer->nframes; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += xfer->nframes; /* setup TDs */ uss820dci_setup_standard_chain(xfer); } static void uss820dci_device_isoc_fs_start(struct usb_xfer *xfer) { /* start TD chain */ uss820dci_start_standard_chain(xfer); } static const struct usb_pipe_methods uss820dci_device_isoc_fs_methods = { .open = uss820dci_device_isoc_fs_open, .close = uss820dci_device_isoc_fs_close, .enter = uss820dci_device_isoc_fs_enter, .start = uss820dci_device_isoc_fs_start, }; /*------------------------------------------------------------------------* * uss820dci root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor uss820dci_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct usb_device_qualifier uss820dci_odevd = { .bLength = sizeof(struct usb_device_qualifier), .bDescriptorType = UDESC_DEVICE_QUALIFIER, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize0 = 0, .bNumConfigurations = 0, }; static const struct uss820dci_config_desc uss820dci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(uss820dci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | USS820_DCI_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_hub_descriptor_min uss820dci_hubd = { .bDescLength = sizeof(uss820dci_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "A\0G\0E\0R\0E" #define STRING_PRODUCT \ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, uss820dci_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, uss820dci_product); static usb_error_t uss820dci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_handle_clear_port_feature; case UR_SET_FEATURE: goto tr_handle_set_port_feature; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: goto tr_handle_get_port_status; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) { goto tr_stalled; } len = sizeof(uss820dci_devd); ptr = (const void *)&uss820dci_devd; goto tr_valid; case UDESC_DEVICE_QUALIFIER: if (value & 0xff) { goto tr_stalled; } len = sizeof(uss820dci_odevd); ptr = (const void *)&uss820dci_odevd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) { goto tr_stalled; } len = sizeof(uss820dci_confd); ptr = (const void *)&uss820dci_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(uss820dci_vendor); ptr = (const void *)&uss820dci_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(uss820dci_product); ptr = (const void *)&uss820dci_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) { goto tr_stalled; } sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) { goto tr_stalled; } sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: uss820dci_wakeup_peer(sc); break; case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; uss820dci_pull_down(sc); break; case UHF_C_PORT_CONNECTION: sc->sc_flags.change_connect = 0; break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_set_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); switch (value) { case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 1; break; case UHF_PORT_SUSPEND: case UHF_PORT_RESET: case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_get_port_status: DPRINTFN(9, "UR_GET_PORT_STATUS\n"); if (index != 1) { goto tr_stalled; } if (sc->sc_flags.status_vbus) { uss820dci_pull_up(sc); } else { uss820dci_pull_down(sc); } /* Select FULL-speed and Device Side Mode */ value = UPS_PORT_MODE_DEVICE; if (sc->sc_flags.port_powered) { value |= UPS_PORT_POWER; } if (sc->sc_flags.port_enabled) { value |= UPS_PORT_ENABLED; } if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { value |= UPS_CURRENT_CONNECT_STATUS; } if (sc->sc_flags.status_suspend) { value |= UPS_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_connect) { value |= UPS_C_CONNECT_STATUS; } if (sc->sc_flags.change_suspend) { value |= UPS_C_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) { goto tr_stalled; } ptr = (const void *)&uss820dci_hubd; len = sizeof(uss820dci_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: done: *plength = len; *pptr = ptr; return (err); } static void uss820dci_xfer_setup(struct usb_setup_params *parm) { const struct usb_hw_ep_profile *pf; struct uss820dci_softc *sc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; uint8_t ep_no; sc = USS820_DCI_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = 0x500; usbd_transfer_setup_sub(parm); /* * compute maximum number of TDs */ if (parm->methods == &uss820dci_device_ctrl_methods) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; } else if (parm->methods == &uss820dci_device_bulk_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else if (parm->methods == &uss820dci_device_intr_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else if (parm->methods == &uss820dci_device_isoc_fs_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else { ntd = 0; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) { return; } /* * allocate transfer descriptors */ last_obj = NULL; /* * get profile stuff */ if (ntd) { ep_no = xfer->endpointno & UE_ADDR; uss820dci_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } } else { ep_no = 0; pf = NULL; } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct uss820dci_td *td; if (parm->buf) { td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* init TD */ td->max_packet_size = xfer->max_packet_size; td->ep_index = ep_no; if (pf->support_multi_buffer && (parm->methods != &uss820dci_device_ctrl_methods)) { td->support_multi_buffer = 1; } td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void uss820dci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void uss820dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_rt_addr); if (udev->device_index != sc->sc_rt_addr) { if (udev->speed != USB_SPEED_FULL) { /* not supported */ return; } switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &uss820dci_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &uss820dci_device_intr_methods; break; case UE_ISOCHRONOUS: ep->methods = &uss820dci_device_isoc_fs_methods; break; case UE_BULK: ep->methods = &uss820dci_device_bulk_methods; break; default: /* do nothing */ break; } } } static void uss820dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: uss820dci_suspend(sc); break; case USB_HW_POWER_SHUTDOWN: uss820dci_uninit(sc); break; case USB_HW_POWER_RESUME: uss820dci_resume(sc); break; default: break; } } static const struct usb_bus_methods uss820dci_bus_methods = { .endpoint_init = &uss820dci_ep_init, .xfer_setup = &uss820dci_xfer_setup, .xfer_unsetup = &uss820dci_xfer_unsetup, .get_hw_ep_profile = &uss820dci_get_hw_ep_profile, .xfer_stall = &uss820dci_xfer_stall, .set_stall = &uss820dci_set_stall, .clear_stall = &uss820dci_clear_stall, .roothub_exec = &uss820dci_roothub_exec, .xfer_poll = &uss820dci_do_poll, .set_hw_power_sleep = uss820dci_set_hw_power_sleep, }; Index: head/sys/dev/usb/controller/xhci.c =================================================================== --- head/sys/dev/usb/controller/xhci.c (revision 357971) +++ head/sys/dev/usb/controller/xhci.c (revision 357972) @@ -1,4391 +1,4392 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB eXtensible Host Controller Interface, a.k.a. USB 3.0 controller. * * The XHCI 1.0 spec can be found at * http://www.intel.com/technology/usb/download/xHCI_Specification_for_USB.pdf * and the USB 3.0 spec at * http://www.usb.org/developers/docs/usb_30_spec_060910.zip */ /* * A few words about the design implementation: This driver emulates * the concept about TDs which is found in EHCI specification. This * way we achieve that the USB controller drivers look similar to * eachother which makes it easier to understand the code. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR xhcidebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define XHCI_BUS2SC(bus) \ ((struct xhci_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct xhci_softc *)0)->sc_bus)))) -static SYSCTL_NODE(_hw_usb, OID_AUTO, xhci, CTLFLAG_RW, 0, "USB XHCI"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, xhci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB XHCI"); static int xhcistreams; SYSCTL_INT(_hw_usb_xhci, OID_AUTO, streams, CTLFLAG_RWTUN, &xhcistreams, 0, "Set to enable streams mode support"); static int xhcictlquirk = 1; SYSCTL_INT(_hw_usb_xhci, OID_AUTO, ctlquirk, CTLFLAG_RWTUN, &xhcictlquirk, 0, "Set to enable control endpoint quirk"); #ifdef USB_DEBUG static int xhcidebug; static int xhciroute; static int xhcipolling; static int xhcidma32; static int xhcictlstep; SYSCTL_INT(_hw_usb_xhci, OID_AUTO, debug, CTLFLAG_RWTUN, &xhcidebug, 0, "Debug level"); SYSCTL_INT(_hw_usb_xhci, OID_AUTO, xhci_port_route, CTLFLAG_RWTUN, &xhciroute, 0, "Routing bitmap for switching EHCI ports to the XHCI controller"); SYSCTL_INT(_hw_usb_xhci, OID_AUTO, use_polling, CTLFLAG_RWTUN, &xhcipolling, 0, "Set to enable software interrupt polling for the XHCI controller"); SYSCTL_INT(_hw_usb_xhci, OID_AUTO, dma32, CTLFLAG_RWTUN, &xhcidma32, 0, "Set to only use 32-bit DMA for the XHCI controller"); SYSCTL_INT(_hw_usb_xhci, OID_AUTO, ctlstep, CTLFLAG_RWTUN, &xhcictlstep, 0, "Set to enable control endpoint status stage stepping"); #else #define xhciroute 0 #define xhcidma32 0 #define xhcictlstep 0 #endif #define XHCI_INTR_ENDPT 1 struct xhci_std_temp { struct xhci_softc *sc; struct usb_page_cache *pc; struct xhci_td *td; struct xhci_td *td_next; uint32_t len; uint32_t offset; uint32_t max_packet_size; uint32_t average; uint16_t isoc_delta; uint16_t isoc_frame; uint8_t shortpkt; uint8_t multishort; uint8_t last_frame; uint8_t trb_type; uint8_t direction; uint8_t tbc; uint8_t tlbpc; uint8_t step_td; uint8_t do_isoc_sync; }; static void xhci_do_poll(struct usb_bus *); static void xhci_device_done(struct usb_xfer *, usb_error_t); static void xhci_root_intr(struct xhci_softc *); static void xhci_free_device_ext(struct usb_device *); static struct xhci_endpoint_ext *xhci_get_endpoint_ext(struct usb_device *, struct usb_endpoint_descriptor *); static usb_proc_callback_t xhci_configure_msg; static usb_error_t xhci_configure_device(struct usb_device *); static usb_error_t xhci_configure_endpoint(struct usb_device *, struct usb_endpoint_descriptor *, struct xhci_endpoint_ext *, uint16_t, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t, uint8_t); static usb_error_t xhci_configure_mask(struct usb_device *, uint32_t, uint8_t); static usb_error_t xhci_cmd_evaluate_ctx(struct xhci_softc *, uint64_t, uint8_t); static void xhci_endpoint_doorbell(struct usb_xfer *); static void xhci_ctx_set_le32(struct xhci_softc *sc, volatile uint32_t *ptr, uint32_t val); static uint32_t xhci_ctx_get_le32(struct xhci_softc *sc, volatile uint32_t *ptr); static void xhci_ctx_set_le64(struct xhci_softc *sc, volatile uint64_t *ptr, uint64_t val); #ifdef USB_DEBUG static uint64_t xhci_ctx_get_le64(struct xhci_softc *sc, volatile uint64_t *ptr); #endif static const struct usb_bus_methods xhci_bus_methods; #ifdef USB_DEBUG static void xhci_dump_trb(struct xhci_trb *trb) { DPRINTFN(5, "trb = %p\n", trb); DPRINTFN(5, "qwTrb0 = 0x%016llx\n", (long long)le64toh(trb->qwTrb0)); DPRINTFN(5, "dwTrb2 = 0x%08x\n", le32toh(trb->dwTrb2)); DPRINTFN(5, "dwTrb3 = 0x%08x\n", le32toh(trb->dwTrb3)); } static void xhci_dump_endpoint(struct xhci_softc *sc, struct xhci_endp_ctx *pep) { DPRINTFN(5, "pep = %p\n", pep); DPRINTFN(5, "dwEpCtx0=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx0)); DPRINTFN(5, "dwEpCtx1=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx1)); DPRINTFN(5, "qwEpCtx2=0x%016llx\n", (long long)xhci_ctx_get_le64(sc, &pep->qwEpCtx2)); DPRINTFN(5, "dwEpCtx4=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx4)); DPRINTFN(5, "dwEpCtx5=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx5)); DPRINTFN(5, "dwEpCtx6=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx6)); DPRINTFN(5, "dwEpCtx7=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx7)); } static void xhci_dump_device(struct xhci_softc *sc, struct xhci_slot_ctx *psl) { DPRINTFN(5, "psl = %p\n", psl); DPRINTFN(5, "dwSctx0=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx0)); DPRINTFN(5, "dwSctx1=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx1)); DPRINTFN(5, "dwSctx2=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx2)); DPRINTFN(5, "dwSctx3=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx3)); } #endif uint8_t xhci_use_polling(void) { #ifdef USB_DEBUG return (xhcipolling != 0); #else return (0); #endif } static void xhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) { struct xhci_softc *sc = XHCI_BUS2SC(bus); uint16_t i; cb(bus, &sc->sc_hw.root_pc, &sc->sc_hw.root_pg, sizeof(struct xhci_hw_root), XHCI_PAGE_SIZE); cb(bus, &sc->sc_hw.ctx_pc, &sc->sc_hw.ctx_pg, sizeof(struct xhci_dev_ctx_addr), XHCI_PAGE_SIZE); for (i = 0; i != sc->sc_noscratch; i++) { cb(bus, &sc->sc_hw.scratch_pc[i], &sc->sc_hw.scratch_pg[i], XHCI_PAGE_SIZE, XHCI_PAGE_SIZE); } } static void xhci_ctx_set_le32(struct xhci_softc *sc, volatile uint32_t *ptr, uint32_t val) { if (sc->sc_ctx_is_64_byte) { uint32_t offset; /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ /* all contexts are initially 32-bytes */ offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); ptr = (volatile uint32_t *)(((volatile uint8_t *)ptr) + offset); } *ptr = htole32(val); } static uint32_t xhci_ctx_get_le32(struct xhci_softc *sc, volatile uint32_t *ptr) { if (sc->sc_ctx_is_64_byte) { uint32_t offset; /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ /* all contexts are initially 32-bytes */ offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); ptr = (volatile uint32_t *)(((volatile uint8_t *)ptr) + offset); } return (le32toh(*ptr)); } static void xhci_ctx_set_le64(struct xhci_softc *sc, volatile uint64_t *ptr, uint64_t val) { if (sc->sc_ctx_is_64_byte) { uint32_t offset; /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ /* all contexts are initially 32-bytes */ offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); ptr = (volatile uint64_t *)(((volatile uint8_t *)ptr) + offset); } *ptr = htole64(val); } #ifdef USB_DEBUG static uint64_t xhci_ctx_get_le64(struct xhci_softc *sc, volatile uint64_t *ptr) { if (sc->sc_ctx_is_64_byte) { uint32_t offset; /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ /* all contexts are initially 32-bytes */ offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); ptr = (volatile uint64_t *)(((volatile uint8_t *)ptr) + offset); } return (le64toh(*ptr)); } #endif static int xhci_reset_command_queue_locked(struct xhci_softc *sc) { struct usb_page_search buf_res; struct xhci_hw_root *phwr; uint64_t addr; uint32_t temp; DPRINTF("\n"); temp = XREAD4(sc, oper, XHCI_CRCR_LO); if (temp & XHCI_CRCR_LO_CRR) { DPRINTF("Command ring running\n"); temp &= ~(XHCI_CRCR_LO_CS | XHCI_CRCR_LO_CA); /* * Try to abort the last command as per section * 4.6.1.2 "Aborting a Command" of the XHCI * specification: */ /* stop and cancel */ XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CS); XWRITE4(sc, oper, XHCI_CRCR_HI, 0); XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CA); XWRITE4(sc, oper, XHCI_CRCR_HI, 0); /* wait 250ms */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 4); /* check if command ring is still running */ temp = XREAD4(sc, oper, XHCI_CRCR_LO); if (temp & XHCI_CRCR_LO_CRR) { DPRINTF("Comand ring still running\n"); return (USB_ERR_IOERROR); } } /* reset command ring */ sc->sc_command_ccs = 1; sc->sc_command_idx = 0; usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); /* set up command ring control base address */ addr = buf_res.physaddr; phwr = buf_res.buffer; addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_commands[0]; DPRINTF("CRCR=0x%016llx\n", (unsigned long long)addr); memset(phwr->hwr_commands, 0, sizeof(phwr->hwr_commands)); phwr->hwr_commands[XHCI_MAX_COMMANDS - 1].qwTrb0 = htole64(addr); usb_pc_cpu_flush(&sc->sc_hw.root_pc); XWRITE4(sc, oper, XHCI_CRCR_LO, ((uint32_t)addr) | XHCI_CRCR_LO_RCS); XWRITE4(sc, oper, XHCI_CRCR_HI, (uint32_t)(addr >> 32)); return (0); } usb_error_t xhci_start_controller(struct xhci_softc *sc) { struct usb_page_search buf_res; struct xhci_hw_root *phwr; struct xhci_dev_ctx_addr *pdctxa; usb_error_t err; uint64_t addr; uint32_t temp; uint16_t i; DPRINTF("\n"); sc->sc_event_ccs = 1; sc->sc_event_idx = 0; sc->sc_command_ccs = 1; sc->sc_command_idx = 0; err = xhci_reset_controller(sc); if (err) return (err); /* set up number of device slots */ DPRINTF("CONFIG=0x%08x -> 0x%08x\n", XREAD4(sc, oper, XHCI_CONFIG), sc->sc_noslot); XWRITE4(sc, oper, XHCI_CONFIG, sc->sc_noslot); temp = XREAD4(sc, oper, XHCI_USBSTS); /* clear interrupts */ XWRITE4(sc, oper, XHCI_USBSTS, temp); /* disable all device notifications */ XWRITE4(sc, oper, XHCI_DNCTRL, 0); /* set up device context base address */ usbd_get_page(&sc->sc_hw.ctx_pc, 0, &buf_res); pdctxa = buf_res.buffer; memset(pdctxa, 0, sizeof(*pdctxa)); addr = buf_res.physaddr; addr += (uintptr_t)&((struct xhci_dev_ctx_addr *)0)->qwSpBufPtr[0]; /* slot 0 points to the table of scratchpad pointers */ pdctxa->qwBaaDevCtxAddr[0] = htole64(addr); for (i = 0; i != sc->sc_noscratch; i++) { struct usb_page_search buf_scp; usbd_get_page(&sc->sc_hw.scratch_pc[i], 0, &buf_scp); pdctxa->qwSpBufPtr[i] = htole64((uint64_t)buf_scp.physaddr); } addr = buf_res.physaddr; XWRITE4(sc, oper, XHCI_DCBAAP_LO, (uint32_t)addr); XWRITE4(sc, oper, XHCI_DCBAAP_HI, (uint32_t)(addr >> 32)); XWRITE4(sc, oper, XHCI_DCBAAP_LO, (uint32_t)addr); XWRITE4(sc, oper, XHCI_DCBAAP_HI, (uint32_t)(addr >> 32)); /* set up event table size */ DPRINTF("ERSTSZ=0x%08x -> 0x%08x\n", XREAD4(sc, runt, XHCI_ERSTSZ(0)), sc->sc_erst_max); XWRITE4(sc, runt, XHCI_ERSTSZ(0), XHCI_ERSTS_SET(sc->sc_erst_max)); /* set up interrupt rate */ XWRITE4(sc, runt, XHCI_IMOD(0), sc->sc_imod_default); usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); phwr = buf_res.buffer; addr = buf_res.physaddr; addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_events[0]; /* reset hardware root structure */ memset(phwr, 0, sizeof(*phwr)); phwr->hwr_ring_seg[0].qwEvrsTablePtr = htole64(addr); phwr->hwr_ring_seg[0].dwEvrsTableSize = htole32(XHCI_MAX_EVENTS); DPRINTF("ERDP(0)=0x%016llx\n", (unsigned long long)addr); XWRITE4(sc, runt, XHCI_ERDP_LO(0), (uint32_t)addr); XWRITE4(sc, runt, XHCI_ERDP_HI(0), (uint32_t)(addr >> 32)); addr = buf_res.physaddr; DPRINTF("ERSTBA(0)=0x%016llx\n", (unsigned long long)addr); XWRITE4(sc, runt, XHCI_ERSTBA_LO(0), (uint32_t)addr); XWRITE4(sc, runt, XHCI_ERSTBA_HI(0), (uint32_t)(addr >> 32)); /* set up interrupter registers */ temp = XREAD4(sc, runt, XHCI_IMAN(0)); temp |= XHCI_IMAN_INTR_ENA; XWRITE4(sc, runt, XHCI_IMAN(0), temp); /* set up command ring control base address */ addr = buf_res.physaddr; addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_commands[0]; DPRINTF("CRCR=0x%016llx\n", (unsigned long long)addr); XWRITE4(sc, oper, XHCI_CRCR_LO, ((uint32_t)addr) | XHCI_CRCR_LO_RCS); XWRITE4(sc, oper, XHCI_CRCR_HI, (uint32_t)(addr >> 32)); phwr->hwr_commands[XHCI_MAX_COMMANDS - 1].qwTrb0 = htole64(addr); usb_bus_mem_flush_all(&sc->sc_bus, &xhci_iterate_hw_softc); /* Go! */ XWRITE4(sc, oper, XHCI_USBCMD, XHCI_CMD_RS | XHCI_CMD_INTE | XHCI_CMD_HSEE); for (i = 0; i != 100; i++) { usb_pause_mtx(NULL, hz / 100); temp = XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_HCH; if (!temp) break; } if (temp) { XWRITE4(sc, oper, XHCI_USBCMD, 0); device_printf(sc->sc_bus.parent, "Run timeout.\n"); return (USB_ERR_IOERROR); } /* catch any lost interrupts */ xhci_do_poll(&sc->sc_bus); if (sc->sc_port_route != NULL) { /* Route all ports to the XHCI by default */ sc->sc_port_route(sc->sc_bus.parent, ~xhciroute, xhciroute); } return (0); } usb_error_t xhci_halt_controller(struct xhci_softc *sc) { uint32_t temp; uint16_t i; DPRINTF("\n"); sc->sc_capa_off = 0; sc->sc_oper_off = XREAD1(sc, capa, XHCI_CAPLENGTH); sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0xF; sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3; /* Halt controller */ XWRITE4(sc, oper, XHCI_USBCMD, 0); for (i = 0; i != 100; i++) { usb_pause_mtx(NULL, hz / 100); temp = XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_HCH; if (temp) break; } if (!temp) { device_printf(sc->sc_bus.parent, "Controller halt timeout.\n"); return (USB_ERR_IOERROR); } return (0); } usb_error_t xhci_reset_controller(struct xhci_softc *sc) { uint32_t temp = 0; uint16_t i; DPRINTF("\n"); /* Reset controller */ XWRITE4(sc, oper, XHCI_USBCMD, XHCI_CMD_HCRST); for (i = 0; i != 100; i++) { usb_pause_mtx(NULL, hz / 100); temp = (XREAD4(sc, oper, XHCI_USBCMD) & XHCI_CMD_HCRST) | (XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_CNR); if (!temp) break; } if (temp) { device_printf(sc->sc_bus.parent, "Controller " "reset timeout.\n"); return (USB_ERR_IOERROR); } return (0); } usb_error_t xhci_init(struct xhci_softc *sc, device_t self, uint8_t dma32) { uint32_t temp; DPRINTF("\n"); /* initialize some bus fields */ sc->sc_bus.parent = self; /* set the bus revision */ sc->sc_bus.usbrev = USB_REV_3_0; /* set up the bus struct */ sc->sc_bus.methods = &xhci_bus_methods; /* set up devices array */ sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = XHCI_MAX_DEVICES; /* set default cycle state in case of early interrupts */ sc->sc_event_ccs = 1; sc->sc_command_ccs = 1; /* set up bus space offsets */ sc->sc_capa_off = 0; sc->sc_oper_off = XREAD1(sc, capa, XHCI_CAPLENGTH); sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0x1F; sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3; DPRINTF("CAPLENGTH=0x%x\n", sc->sc_oper_off); DPRINTF("RUNTIMEOFFSET=0x%x\n", sc->sc_runt_off); DPRINTF("DOOROFFSET=0x%x\n", sc->sc_door_off); DPRINTF("xHCI version = 0x%04x\n", XREAD2(sc, capa, XHCI_HCIVERSION)); if (!(XREAD4(sc, oper, XHCI_PAGESIZE) & XHCI_PAGESIZE_4K)) { device_printf(sc->sc_bus.parent, "Controller does " "not support 4K page size.\n"); return (ENXIO); } temp = XREAD4(sc, capa, XHCI_HCSPARAMS0); DPRINTF("HCS0 = 0x%08x\n", temp); /* set up context size */ if (XHCI_HCS0_CSZ(temp)) { sc->sc_ctx_is_64_byte = 1; } else { sc->sc_ctx_is_64_byte = 0; } /* get DMA bits */ sc->sc_bus.dma_bits = (XHCI_HCS0_AC64(temp) && xhcidma32 == 0 && dma32 == 0) ? 64 : 32; device_printf(self, "%d bytes context size, %d-bit DMA\n", sc->sc_ctx_is_64_byte ? 64 : 32, (int)sc->sc_bus.dma_bits); /* enable 64Kbyte control endpoint quirk */ sc->sc_bus.control_ep_quirk = (xhcictlquirk ? 1 : 0); temp = XREAD4(sc, capa, XHCI_HCSPARAMS1); /* get number of device slots */ sc->sc_noport = XHCI_HCS1_N_PORTS(temp); if (sc->sc_noport == 0) { device_printf(sc->sc_bus.parent, "Invalid number " "of ports: %u\n", sc->sc_noport); return (ENXIO); } sc->sc_noport = sc->sc_noport; sc->sc_noslot = XHCI_HCS1_DEVSLOT_MAX(temp); DPRINTF("Max slots: %u\n", sc->sc_noslot); if (sc->sc_noslot > XHCI_MAX_DEVICES) sc->sc_noslot = XHCI_MAX_DEVICES; temp = XREAD4(sc, capa, XHCI_HCSPARAMS2); DPRINTF("HCS2=0x%08x\n", temp); /* get number of scratchpads */ sc->sc_noscratch = XHCI_HCS2_SPB_MAX(temp); if (sc->sc_noscratch > XHCI_MAX_SCRATCHPADS) { device_printf(sc->sc_bus.parent, "XHCI request " "too many scratchpads\n"); return (ENOMEM); } DPRINTF("Max scratch: %u\n", sc->sc_noscratch); /* get event table size */ sc->sc_erst_max = 1U << XHCI_HCS2_ERST_MAX(temp); if (sc->sc_erst_max > XHCI_MAX_RSEG) sc->sc_erst_max = XHCI_MAX_RSEG; temp = XREAD4(sc, capa, XHCI_HCSPARAMS3); /* get maximum exit latency */ sc->sc_exit_lat_max = XHCI_HCS3_U1_DEL(temp) + XHCI_HCS3_U2_DEL(temp) + 250 /* us */; /* Check if we should use the default IMOD value. */ if (sc->sc_imod_default == 0) sc->sc_imod_default = XHCI_IMOD_DEFAULT; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &xhci_iterate_hw_softc)) { return (ENOMEM); } /* set up command queue mutex and condition varible */ cv_init(&sc->sc_cmd_cv, "CMDQ"); sx_init(&sc->sc_cmd_sx, "CMDQ lock"); sc->sc_config_msg[0].hdr.pm_callback = &xhci_configure_msg; sc->sc_config_msg[0].bus = &sc->sc_bus; sc->sc_config_msg[1].hdr.pm_callback = &xhci_configure_msg; sc->sc_config_msg[1].bus = &sc->sc_bus; return (0); } void xhci_uninit(struct xhci_softc *sc) { /* * NOTE: At this point the control transfer process is gone * and "xhci_configure_msg" is no longer called. Consequently * waiting for the configuration messages to complete is not * needed. */ usb_bus_mem_free_all(&sc->sc_bus, &xhci_iterate_hw_softc); cv_destroy(&sc->sc_cmd_cv); sx_destroy(&sc->sc_cmd_sx); } static void xhci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct xhci_softc *sc = XHCI_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: DPRINTF("Stopping the XHCI\n"); xhci_halt_controller(sc); xhci_reset_controller(sc); break; case USB_HW_POWER_SHUTDOWN: DPRINTF("Stopping the XHCI\n"); xhci_halt_controller(sc); xhci_reset_controller(sc); break; case USB_HW_POWER_RESUME: DPRINTF("Starting the XHCI\n"); xhci_start_controller(sc); break; default: break; } } static usb_error_t xhci_generic_done_sub(struct usb_xfer *xfer) { struct xhci_td *td; struct xhci_td *td_alt_next; uint32_t len; uint8_t status; td = xfer->td_transfer_cache; td_alt_next = td->alt_next; if (xfer->aframes != xfer->nframes) usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); while (1) { usb_pc_cpu_invalidate(td->page_cache); status = td->status; len = td->remainder; DPRINTFN(4, "xfer=%p[%u/%u] rem=%u/%u status=%u\n", xfer, (unsigned int)xfer->aframes, (unsigned int)xfer->nframes, (unsigned int)len, (unsigned int)td->len, (unsigned int)status); /* * Verify the status length and * add the length to "frlengths[]": */ if (len > td->len) { /* should not happen */ DPRINTF("Invalid status length, " "0x%04x/0x%04x bytes\n", len, td->len); status = XHCI_TRB_ERROR_LENGTH; } else if (xfer->aframes != xfer->nframes) { xfer->frlengths[xfer->aframes] += td->len - len; } /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { td = NULL; break; } /* Check for transfer error */ if (status != XHCI_TRB_ERROR_SHORT_PKT && status != XHCI_TRB_ERROR_SUCCESS) { /* the transfer is finished */ td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr || xfer->flags_int.control_xfr) { /* follow alt next */ td = td->alt_next; } else { /* the transfer is finished */ td = NULL; } break; } td = td->obj_next; if (td->alt_next != td_alt_next) { /* this USB frame is complete */ break; } } /* update transfer cache */ xfer->td_transfer_cache = td; return ((status == XHCI_TRB_ERROR_STALL) ? USB_ERR_STALLED : (status != XHCI_TRB_ERROR_SHORT_PKT && status != XHCI_TRB_ERROR_SUCCESS) ? USB_ERR_IOERROR : USB_ERR_NORMAL_COMPLETION); } static void xhci_generic_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) err = xhci_generic_done_sub(xfer); xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) goto done; } while (xfer->aframes != xfer->nframes) { err = xhci_generic_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) goto done; } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) err = xhci_generic_done_sub(xfer); done: /* transfer is complete */ xhci_device_done(xfer, err); } static void xhci_activate_transfer(struct usb_xfer *xfer) { struct xhci_td *td; td = xfer->td_transfer_cache; usb_pc_cpu_invalidate(td->page_cache); if (!(td->td_trb[0].dwTrb3 & htole32(XHCI_TRB_3_CYCLE_BIT))) { /* activate the transfer */ td->td_trb[0].dwTrb3 |= htole32(XHCI_TRB_3_CYCLE_BIT); usb_pc_cpu_flush(td->page_cache); xhci_endpoint_doorbell(xfer); } } static void xhci_skip_transfer(struct usb_xfer *xfer) { struct xhci_td *td; struct xhci_td *td_last; td = xfer->td_transfer_cache; td_last = xfer->td_transfer_last; td = td->alt_next; usb_pc_cpu_invalidate(td->page_cache); if (!(td->td_trb[0].dwTrb3 & htole32(XHCI_TRB_3_CYCLE_BIT))) { usb_pc_cpu_invalidate(td_last->page_cache); /* copy LINK TRB to current waiting location */ td->td_trb[0].qwTrb0 = td_last->td_trb[td_last->ntrb].qwTrb0; td->td_trb[0].dwTrb2 = td_last->td_trb[td_last->ntrb].dwTrb2; usb_pc_cpu_flush(td->page_cache); td->td_trb[0].dwTrb3 = td_last->td_trb[td_last->ntrb].dwTrb3; usb_pc_cpu_flush(td->page_cache); xhci_endpoint_doorbell(xfer); } } /*------------------------------------------------------------------------* * xhci_check_transfer *------------------------------------------------------------------------*/ static void xhci_check_transfer(struct xhci_softc *sc, struct xhci_trb *trb) { struct xhci_endpoint_ext *pepext; int64_t offset; uint64_t td_event; uint32_t temp; uint32_t remainder; uint16_t stream_id = 0; uint16_t i; uint8_t status; uint8_t halted; uint8_t epno; uint8_t index; /* decode TRB */ td_event = le64toh(trb->qwTrb0); temp = le32toh(trb->dwTrb2); remainder = XHCI_TRB_2_REM_GET(temp); status = XHCI_TRB_2_ERROR_GET(temp); temp = le32toh(trb->dwTrb3); epno = XHCI_TRB_3_EP_GET(temp); index = XHCI_TRB_3_SLOT_GET(temp); /* check if error means halted */ halted = (status != XHCI_TRB_ERROR_SHORT_PKT && status != XHCI_TRB_ERROR_SUCCESS); DPRINTF("slot=%u epno=%u remainder=%u status=%u\n", index, epno, remainder, status); if (index > sc->sc_noslot) { DPRINTF("Invalid slot.\n"); return; } if ((epno == 0) || (epno >= XHCI_MAX_ENDPOINTS)) { DPRINTF("Invalid endpoint.\n"); return; } pepext = &sc->sc_hw.devs[index].endp[epno]; /* try to find the USB transfer that generated the event */ for (i = 0;; i++) { struct usb_xfer *xfer; struct xhci_td *td; if (i == (XHCI_MAX_TRANSFERS - 1)) { if (pepext->trb_ep_mode != USB_EP_MODE_STREAMS || stream_id == (XHCI_MAX_STREAMS - 1)) break; stream_id++; i = 0; DPRINTFN(5, "stream_id=%u\n", stream_id); } xfer = pepext->xfer[i + (XHCI_MAX_TRANSFERS * stream_id)]; if (xfer == NULL) continue; td = xfer->td_transfer_cache; DPRINTFN(5, "Checking if 0x%016llx == (0x%016llx .. 0x%016llx)\n", (long long)td_event, (long long)td->td_self, (long long)td->td_self + sizeof(td->td_trb)); /* * NOTE: Some XHCI implementations might not trigger * an event on the last LINK TRB so we need to * consider both the last and second last event * address as conditions for a successful transfer. * * NOTE: We assume that the XHCI will only trigger one * event per chain of TRBs. */ offset = td_event - td->td_self; if (offset >= 0 && offset < (int64_t)sizeof(td->td_trb)) { usb_pc_cpu_invalidate(td->page_cache); /* compute rest of remainder, if any */ for (i = (offset / 16) + 1; i < td->ntrb; i++) { temp = le32toh(td->td_trb[i].dwTrb2); remainder += XHCI_TRB_2_BYTES_GET(temp); } DPRINTFN(5, "New remainder: %u\n", remainder); /* clear isochronous transfer errors */ if (xfer->flags_int.isochronous_xfr) { if (halted) { halted = 0; status = XHCI_TRB_ERROR_SUCCESS; remainder = td->len; } } /* "td->remainder" is verified later */ td->remainder = remainder; td->status = status; usb_pc_cpu_flush(td->page_cache); /* * 1) Last transfer descriptor makes the * transfer done */ if (((void *)td) == xfer->td_transfer_last) { DPRINTF("TD is last\n"); xhci_generic_done(xfer); break; } /* * 2) Any kind of error makes the transfer * done */ if (halted) { DPRINTF("TD has I/O error\n"); xhci_generic_done(xfer); break; } /* * 3) If there is no alternate next transfer, * a short packet also makes the transfer done */ if (td->remainder > 0) { if (td->alt_next == NULL) { DPRINTF( "short TD has no alternate next\n"); xhci_generic_done(xfer); break; } DPRINTF("TD has short pkt\n"); if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr || xfer->flags_int.control_xfr) { /* follow the alt next */ xfer->td_transfer_cache = td->alt_next; xhci_activate_transfer(xfer); break; } xhci_skip_transfer(xfer); xhci_generic_done(xfer); break; } /* * 4) Transfer complete - go to next TD */ DPRINTF("Following next TD\n"); xfer->td_transfer_cache = td->obj_next; xhci_activate_transfer(xfer); break; /* there should only be one match */ } } } static int xhci_check_command(struct xhci_softc *sc, struct xhci_trb *trb) { if (sc->sc_cmd_addr == trb->qwTrb0) { DPRINTF("Received command event\n"); sc->sc_cmd_result[0] = trb->dwTrb2; sc->sc_cmd_result[1] = trb->dwTrb3; cv_signal(&sc->sc_cmd_cv); return (1); /* command match */ } return (0); } static int xhci_interrupt_poll(struct xhci_softc *sc) { struct usb_page_search buf_res; struct xhci_hw_root *phwr; uint64_t addr; uint32_t temp; int retval = 0; uint16_t i; uint8_t event; uint8_t j; uint8_t k; uint8_t t; usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); phwr = buf_res.buffer; /* Receive any events */ usb_pc_cpu_invalidate(&sc->sc_hw.root_pc); i = sc->sc_event_idx; j = sc->sc_event_ccs; t = 2; while (1) { temp = le32toh(phwr->hwr_events[i].dwTrb3); k = (temp & XHCI_TRB_3_CYCLE_BIT) ? 1 : 0; if (j != k) break; event = XHCI_TRB_3_TYPE_GET(temp); DPRINTFN(10, "event[%u] = %u (0x%016llx 0x%08lx 0x%08lx)\n", i, event, (long long)le64toh(phwr->hwr_events[i].qwTrb0), (long)le32toh(phwr->hwr_events[i].dwTrb2), (long)le32toh(phwr->hwr_events[i].dwTrb3)); switch (event) { case XHCI_TRB_EVENT_TRANSFER: xhci_check_transfer(sc, &phwr->hwr_events[i]); break; case XHCI_TRB_EVENT_CMD_COMPLETE: retval |= xhci_check_command(sc, &phwr->hwr_events[i]); break; default: DPRINTF("Unhandled event = %u\n", event); break; } i++; if (i == XHCI_MAX_EVENTS) { i = 0; j ^= 1; /* check for timeout */ if (!--t) break; } } sc->sc_event_idx = i; sc->sc_event_ccs = j; /* * NOTE: The Event Ring Dequeue Pointer Register is 64-bit * latched. That means to activate the register we need to * write both the low and high double word of the 64-bit * register. */ addr = buf_res.physaddr; addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_events[i]; /* try to clear busy bit */ addr |= XHCI_ERDP_LO_BUSY; XWRITE4(sc, runt, XHCI_ERDP_LO(0), (uint32_t)addr); XWRITE4(sc, runt, XHCI_ERDP_HI(0), (uint32_t)(addr >> 32)); return (retval); } static usb_error_t xhci_do_command(struct xhci_softc *sc, struct xhci_trb *trb, uint16_t timeout_ms) { struct usb_page_search buf_res; struct xhci_hw_root *phwr; uint64_t addr; uint32_t temp; uint8_t i; uint8_t j; uint8_t timeout = 0; int err; XHCI_CMD_ASSERT_LOCKED(sc); /* get hardware root structure */ usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); phwr = buf_res.buffer; /* Queue command */ USB_BUS_LOCK(&sc->sc_bus); retry: i = sc->sc_command_idx; j = sc->sc_command_ccs; DPRINTFN(10, "command[%u] = %u (0x%016llx, 0x%08lx, 0x%08lx)\n", i, XHCI_TRB_3_TYPE_GET(le32toh(trb->dwTrb3)), (long long)le64toh(trb->qwTrb0), (long)le32toh(trb->dwTrb2), (long)le32toh(trb->dwTrb3)); phwr->hwr_commands[i].qwTrb0 = trb->qwTrb0; phwr->hwr_commands[i].dwTrb2 = trb->dwTrb2; usb_pc_cpu_flush(&sc->sc_hw.root_pc); temp = trb->dwTrb3; if (j) temp |= htole32(XHCI_TRB_3_CYCLE_BIT); else temp &= ~htole32(XHCI_TRB_3_CYCLE_BIT); temp &= ~htole32(XHCI_TRB_3_TC_BIT); phwr->hwr_commands[i].dwTrb3 = temp; usb_pc_cpu_flush(&sc->sc_hw.root_pc); addr = buf_res.physaddr; addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_commands[i]; sc->sc_cmd_addr = htole64(addr); i++; if (i == (XHCI_MAX_COMMANDS - 1)) { if (j) { temp = htole32(XHCI_TRB_3_TC_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) | XHCI_TRB_3_CYCLE_BIT); } else { temp = htole32(XHCI_TRB_3_TC_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); } phwr->hwr_commands[i].dwTrb3 = temp; usb_pc_cpu_flush(&sc->sc_hw.root_pc); i = 0; j ^= 1; } sc->sc_command_idx = i; sc->sc_command_ccs = j; XWRITE4(sc, door, XHCI_DOORBELL(0), 0); err = cv_timedwait(&sc->sc_cmd_cv, &sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(timeout_ms)); /* * In some error cases event interrupts are not generated. * Poll one time to see if the command has completed. */ if (err != 0 && xhci_interrupt_poll(sc) != 0) { DPRINTF("Command was completed when polling\n"); err = 0; } if (err != 0) { DPRINTF("Command timeout!\n"); /* * After some weeks of continuous operation, it has * been observed that the ASMedia Technology, ASM1042 * SuperSpeed USB Host Controller can suddenly stop * accepting commands via the command queue. Try to * first reset the command queue. If that fails do a * host controller reset. */ if (timeout == 0 && xhci_reset_command_queue_locked(sc) == 0) { temp = le32toh(trb->dwTrb3); /* * Avoid infinite XHCI reset loops if the set * address command fails to respond due to a * non-enumerating device: */ if (XHCI_TRB_3_TYPE_GET(temp) == XHCI_TRB_TYPE_ADDRESS_DEVICE && (temp & XHCI_TRB_3_BSR_BIT) == 0) { DPRINTF("Set address timeout\n"); } else { timeout = 1; goto retry; } } else { DPRINTF("Controller reset!\n"); usb_bus_reset_async_locked(&sc->sc_bus); } err = USB_ERR_TIMEOUT; trb->dwTrb2 = 0; trb->dwTrb3 = 0; } else { temp = le32toh(sc->sc_cmd_result[0]); if (XHCI_TRB_2_ERROR_GET(temp) != XHCI_TRB_ERROR_SUCCESS) err = USB_ERR_IOERROR; trb->dwTrb2 = sc->sc_cmd_result[0]; trb->dwTrb3 = sc->sc_cmd_result[1]; } USB_BUS_UNLOCK(&sc->sc_bus); return (err); } #if 0 static usb_error_t xhci_cmd_nop(struct xhci_softc *sc) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = 0; trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NOOP); trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } #endif static usb_error_t xhci_cmd_enable_slot(struct xhci_softc *sc, uint8_t *pslot) { struct xhci_trb trb; uint32_t temp; usb_error_t err; DPRINTF("\n"); trb.qwTrb0 = 0; trb.dwTrb2 = 0; trb.dwTrb3 = htole32(XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ENABLE_SLOT)); err = xhci_do_command(sc, &trb, 100 /* ms */); if (err) goto done; temp = le32toh(trb.dwTrb3); *pslot = XHCI_TRB_3_SLOT_GET(temp); done: return (err); } static usb_error_t xhci_cmd_disable_slot(struct xhci_softc *sc, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = 0; trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DISABLE_SLOT) | XHCI_TRB_3_SLOT_SET(slot_id); trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } static usb_error_t xhci_cmd_set_address(struct xhci_softc *sc, uint64_t input_ctx, uint8_t bsr, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = htole64(input_ctx); trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ADDRESS_DEVICE) | XHCI_TRB_3_SLOT_SET(slot_id); if (bsr) temp |= XHCI_TRB_3_BSR_BIT; trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 500 /* ms */)); } static usb_error_t xhci_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t address) { struct usb_page_search buf_inp; struct usb_page_search buf_dev; struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct xhci_hw_dev *hdev; struct xhci_dev_ctx *pdev; struct xhci_endpoint_ext *pepext; uint32_t temp; uint16_t mps; usb_error_t err; uint8_t index; /* the root HUB case is not handled here */ if (udev->parent_hub == NULL) return (USB_ERR_INVAL); index = udev->controller_slot_id; hdev = &sc->sc_hw.devs[index]; if (mtx != NULL) mtx_unlock(mtx); XHCI_CMD_LOCK(sc); switch (hdev->state) { case XHCI_ST_DEFAULT: case XHCI_ST_ENABLED: hdev->state = XHCI_ST_ENABLED; /* set configure mask to slot and EP0 */ xhci_configure_mask(udev, 3, 0); /* configure input slot context structure */ err = xhci_configure_device(udev); if (err != 0) { DPRINTF("Could not configure device\n"); break; } /* configure input endpoint context structure */ switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: mps = 8; break; case USB_SPEED_HIGH: mps = 64; break; default: mps = 512; break; } pepext = xhci_get_endpoint_ext(udev, &udev->ctrl_ep_desc); /* ensure the control endpoint is setup again */ USB_BUS_LOCK(udev->bus); pepext->trb_halted = 1; pepext->trb_running = 0; USB_BUS_UNLOCK(udev->bus); err = xhci_configure_endpoint(udev, &udev->ctrl_ep_desc, pepext, 0, 1, 1, 0, mps, mps, USB_EP_MODE_DEFAULT); if (err != 0) { DPRINTF("Could not configure default endpoint\n"); break; } /* execute set address command */ usbd_get_page(&hdev->input_pc, 0, &buf_inp); err = xhci_cmd_set_address(sc, buf_inp.physaddr, (address == 0), index); if (err != 0) { temp = le32toh(sc->sc_cmd_result[0]); if (address == 0 && sc->sc_port_route != NULL && XHCI_TRB_2_ERROR_GET(temp) == XHCI_TRB_ERROR_PARAMETER) { /* LynxPoint XHCI - ports are not switchable */ /* Un-route all ports from the XHCI */ sc->sc_port_route(sc->sc_bus.parent, 0, ~0); } DPRINTF("Could not set address " "for slot %u.\n", index); if (address != 0) break; } /* update device address to new value */ usbd_get_page(&hdev->device_pc, 0, &buf_dev); pdev = buf_dev.buffer; usb_pc_cpu_invalidate(&hdev->device_pc); temp = xhci_ctx_get_le32(sc, &pdev->ctx_slot.dwSctx3); udev->address = XHCI_SCTX_3_DEV_ADDR_GET(temp); /* update device state to new value */ if (address != 0) hdev->state = XHCI_ST_ADDRESSED; else hdev->state = XHCI_ST_DEFAULT; break; default: DPRINTF("Wrong state for set address.\n"); err = USB_ERR_IOERROR; break; } XHCI_CMD_UNLOCK(sc); if (mtx != NULL) mtx_lock(mtx); return (err); } static usb_error_t xhci_cmd_configure_ep(struct xhci_softc *sc, uint64_t input_ctx, uint8_t deconfigure, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = htole64(input_ctx); trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_CONFIGURE_EP) | XHCI_TRB_3_SLOT_SET(slot_id); if (deconfigure) temp |= XHCI_TRB_3_DCEP_BIT; trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } static usb_error_t xhci_cmd_evaluate_ctx(struct xhci_softc *sc, uint64_t input_ctx, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = htole64(input_ctx); trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_EVALUATE_CTX) | XHCI_TRB_3_SLOT_SET(slot_id); trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } static usb_error_t xhci_cmd_reset_ep(struct xhci_softc *sc, uint8_t preserve, uint8_t ep_id, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = 0; trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_RESET_EP) | XHCI_TRB_3_SLOT_SET(slot_id) | XHCI_TRB_3_EP_SET(ep_id); if (preserve) temp |= XHCI_TRB_3_PRSV_BIT; trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } static usb_error_t xhci_cmd_set_tr_dequeue_ptr(struct xhci_softc *sc, uint64_t dequeue_ptr, uint16_t stream_id, uint8_t ep_id, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = htole64(dequeue_ptr); temp = XHCI_TRB_2_STREAM_SET(stream_id); trb.dwTrb2 = htole32(temp); temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SET_TR_DEQUEUE) | XHCI_TRB_3_SLOT_SET(slot_id) | XHCI_TRB_3_EP_SET(ep_id); trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } static usb_error_t xhci_cmd_stop_ep(struct xhci_softc *sc, uint8_t suspend, uint8_t ep_id, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = 0; trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) | XHCI_TRB_3_SLOT_SET(slot_id) | XHCI_TRB_3_EP_SET(ep_id); if (suspend) temp |= XHCI_TRB_3_SUSP_EP_BIT; trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } static usb_error_t xhci_cmd_reset_dev(struct xhci_softc *sc, uint8_t slot_id) { struct xhci_trb trb; uint32_t temp; DPRINTF("\n"); trb.qwTrb0 = 0; trb.dwTrb2 = 0; temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_RESET_DEVICE) | XHCI_TRB_3_SLOT_SET(slot_id); trb.dwTrb3 = htole32(temp); return (xhci_do_command(sc, &trb, 100 /* ms */)); } /*------------------------------------------------------------------------* * xhci_interrupt - XHCI interrupt handler *------------------------------------------------------------------------*/ void xhci_interrupt(struct xhci_softc *sc) { uint32_t status; uint32_t temp; USB_BUS_LOCK(&sc->sc_bus); status = XREAD4(sc, oper, XHCI_USBSTS); /* acknowledge interrupts, if any */ if (status != 0) { XWRITE4(sc, oper, XHCI_USBSTS, status); DPRINTFN(16, "real interrupt (status=0x%08x)\n", status); } temp = XREAD4(sc, runt, XHCI_IMAN(0)); /* force clearing of pending interrupts */ if (temp & XHCI_IMAN_INTR_PEND) XWRITE4(sc, runt, XHCI_IMAN(0), temp); /* check for event(s) */ xhci_interrupt_poll(sc); if (status & (XHCI_STS_PCD | XHCI_STS_HCH | XHCI_STS_HSE | XHCI_STS_HCE)) { if (status & XHCI_STS_PCD) { xhci_root_intr(sc); } if (status & XHCI_STS_HCH) { printf("%s: host controller halted\n", __FUNCTION__); } if (status & XHCI_STS_HSE) { printf("%s: host system error\n", __FUNCTION__); } if (status & XHCI_STS_HCE) { printf("%s: host controller error\n", __FUNCTION__); } } USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * xhci_timeout - XHCI timeout handler *------------------------------------------------------------------------*/ static void xhci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ xhci_device_done(xfer, USB_ERR_TIMEOUT); } static void xhci_do_poll(struct usb_bus *bus) { struct xhci_softc *sc = XHCI_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); xhci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void xhci_setup_generic_chain_sub(struct xhci_std_temp *temp) { struct usb_page_search buf_res; struct xhci_td *td; struct xhci_td *td_next; struct xhci_td *td_alt_next; struct xhci_td *td_first; uint32_t buf_offset; uint32_t average; uint32_t len_old; uint32_t npkt_off; uint32_t dword; uint8_t shortpkt_old; uint8_t precompute; uint8_t x; td_alt_next = NULL; buf_offset = 0; shortpkt_old = temp->shortpkt; len_old = temp->len; npkt_off = 0; precompute = 1; restart: td = temp->td; td_next = td_first = temp->td_next; while (1) { if (temp->len == 0) { if (temp->shortpkt) break; /* send a Zero Length Packet, ZLP, last */ temp->shortpkt = 1; average = 0; } else { average = temp->average; if (temp->len < average) { if (temp->len % temp->max_packet_size) { temp->shortpkt = 1; } average = temp->len; } } if (td_next == NULL) panic("%s: out of XHCI transfer descriptors!", __FUNCTION__); /* get next TD */ td = td_next; td_next = td->obj_next; /* check if we are pre-computing */ if (precompute) { /* update remaining length */ temp->len -= average; continue; } /* fill out current TD */ td->len = average; td->remainder = 0; td->status = 0; /* update remaining length */ temp->len -= average; /* reset TRB index */ x = 0; if (temp->trb_type == XHCI_TRB_TYPE_SETUP_STAGE) { /* immediate data */ if (average > 8) average = 8; td->td_trb[0].qwTrb0 = 0; usbd_copy_out(temp->pc, temp->offset + buf_offset, (uint8_t *)(uintptr_t)&td->td_trb[0].qwTrb0, average); dword = XHCI_TRB_2_BYTES_SET(8) | XHCI_TRB_2_TDSZ_SET(0) | XHCI_TRB_2_IRQ_SET(0); td->td_trb[0].dwTrb2 = htole32(dword); dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | XHCI_TRB_3_IDT_BIT | XHCI_TRB_3_CYCLE_BIT; /* check wLength */ if (td->td_trb[0].qwTrb0 & htole64(XHCI_TRB_0_WLENGTH_MASK)) { if (td->td_trb[0].qwTrb0 & htole64(XHCI_TRB_0_DIR_IN_MASK)) dword |= XHCI_TRB_3_TRT_IN; else dword |= XHCI_TRB_3_TRT_OUT; } td->td_trb[0].dwTrb3 = htole32(dword); #ifdef USB_DEBUG xhci_dump_trb(&td->td_trb[x]); #endif x++; } else do { uint32_t npkt; /* fill out buffer pointers */ if (average == 0) { memset(&buf_res, 0, sizeof(buf_res)); } else { usbd_get_page(temp->pc, temp->offset + buf_offset, &buf_res); /* get length to end of page */ if (buf_res.length > average) buf_res.length = average; /* check for maximum length */ if (buf_res.length > XHCI_TD_PAGE_SIZE) buf_res.length = XHCI_TD_PAGE_SIZE; npkt_off += buf_res.length; } /* set up npkt */ npkt = howmany(len_old - npkt_off, temp->max_packet_size); if (npkt == 0) npkt = 1; else if (npkt > 31) npkt = 31; /* fill out TRB's */ td->td_trb[x].qwTrb0 = htole64((uint64_t)buf_res.physaddr); dword = XHCI_TRB_2_BYTES_SET(buf_res.length) | XHCI_TRB_2_TDSZ_SET(npkt) | XHCI_TRB_2_IRQ_SET(0); td->td_trb[x].dwTrb2 = htole32(dword); switch (temp->trb_type) { case XHCI_TRB_TYPE_ISOCH: dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TBC_SET(temp->tbc) | XHCI_TRB_3_TLBPC_SET(temp->tlbpc); if (td != td_first) { dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL); } else if (temp->do_isoc_sync != 0) { temp->do_isoc_sync = 0; /* wait until "isoc_frame" */ dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH) | XHCI_TRB_3_FRID_SET(temp->isoc_frame / 8); } else { /* start data transfer at next interval */ dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH) | XHCI_TRB_3_ISO_SIA_BIT; } if (temp->direction == UE_DIR_IN) dword |= XHCI_TRB_3_ISP_BIT; break; case XHCI_TRB_TYPE_DATA_STAGE: dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); if (temp->direction == UE_DIR_IN) dword |= XHCI_TRB_3_DIR_IN | XHCI_TRB_3_ISP_BIT; /* * Section 3.2.9 in the XHCI * specification about control * transfers says that we should use a * normal-TRB if there are more TRBs * extending the data-stage * TRB. Update the "trb_type". */ temp->trb_type = XHCI_TRB_TYPE_NORMAL; break; case XHCI_TRB_TYPE_STATUS_STAGE: dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STATUS_STAGE); if (temp->direction == UE_DIR_IN) dword |= XHCI_TRB_3_DIR_IN; break; default: /* XHCI_TRB_TYPE_NORMAL */ dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL); if (temp->direction == UE_DIR_IN) dword |= XHCI_TRB_3_ISP_BIT; break; } td->td_trb[x].dwTrb3 = htole32(dword); average -= buf_res.length; buf_offset += buf_res.length; #ifdef USB_DEBUG xhci_dump_trb(&td->td_trb[x]); #endif x++; } while (average != 0); td->td_trb[x-1].dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT); /* store number of data TRB's */ td->ntrb = x; DPRINTF("NTRB=%u\n", x); /* fill out link TRB */ if (td_next != NULL) { /* link the current TD with the next one */ td->td_trb[x].qwTrb0 = htole64((uint64_t)td_next->td_self); DPRINTF("LINK=0x%08llx\n", (long long)td_next->td_self); } else { /* this field will get updated later */ DPRINTF("NOLINK\n"); } dword = XHCI_TRB_2_IRQ_SET(0); td->td_trb[x].dwTrb2 = htole32(dword); dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) | XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_IOC_BIT | /* * CHAIN-BIT: Ensure that a multi-TRB IN-endpoint * frame only receives a single short packet event * by setting the CHAIN bit in the LINK field. In * addition some XHCI controllers have problems * sending a ZLP unless the CHAIN-BIT is set in * the LINK TRB. */ XHCI_TRB_3_CHAIN_BIT; td->td_trb[x].dwTrb3 = htole32(dword); td->alt_next = td_alt_next; #ifdef USB_DEBUG xhci_dump_trb(&td->td_trb[x]); #endif usb_pc_cpu_flush(td->page_cache); } if (precompute) { precompute = 0; /* set up alt next pointer, if any */ if (temp->last_frame) { td_alt_next = NULL; } else { /* we use this field internally */ td_alt_next = td_next; } /* restore */ temp->shortpkt = shortpkt_old; temp->len = len_old; goto restart; } /* * Remove cycle bit from the first TRB if we are * stepping them: */ if (temp->step_td != 0) { td_first->td_trb[0].dwTrb3 &= ~htole32(XHCI_TRB_3_CYCLE_BIT); usb_pc_cpu_flush(td_first->page_cache); } /* clear TD SIZE to zero, hence this is the last TRB */ /* remove chain bit because this is the last data TRB in the chain */ td->td_trb[td->ntrb - 1].dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(31)); td->td_trb[td->ntrb - 1].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); /* remove CHAIN-BIT from last LINK TRB */ td->td_trb[td->ntrb].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); usb_pc_cpu_flush(td->page_cache); temp->td = td; temp->td_next = td_next; } static void xhci_setup_generic_chain(struct usb_xfer *xfer) { struct xhci_std_temp temp; struct xhci_td *td; uint32_t x; uint32_t y; uint8_t mult; temp.do_isoc_sync = 0; temp.step_td = 0; temp.tbc = 0; temp.tlbpc = 0; temp.average = xfer->max_hc_frame_size; temp.max_packet_size = xfer->max_packet_size; temp.sc = XHCI_BUS2SC(xfer->xroot->bus); temp.pc = NULL; temp.last_frame = 0; temp.offset = 0; temp.multishort = xfer->flags_int.isochronous_xfr || xfer->flags_int.control_xfr || xfer->flags_int.short_frames_ok; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; temp.td = NULL; temp.td_next = td; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; if (xfer->flags_int.isochronous_xfr) { uint8_t shift; /* compute multiplier for ISOCHRONOUS transfers */ mult = xfer->endpoint->ecomp ? UE_GET_SS_ISO_MULT(xfer->endpoint->ecomp->bmAttributes) : 0; /* check for USB 2.0 multiplier */ if (mult == 0) { mult = (xfer->endpoint->edesc-> wMaxPacketSize[1] >> 3) & 3; } /* range check */ if (mult > 2) mult = 3; else mult++; x = XREAD4(temp.sc, runt, XHCI_MFINDEX); DPRINTF("MFINDEX=0x%08x\n", x); switch (usbd_get_speed(xfer->xroot->udev)) { case USB_SPEED_FULL: shift = 3; temp.isoc_delta = 8; /* 1ms */ x += temp.isoc_delta - 1; x &= ~(temp.isoc_delta - 1); break; default: shift = usbd_xfer_get_fps_shift(xfer); temp.isoc_delta = 1U << shift; x += temp.isoc_delta - 1; x &= ~(temp.isoc_delta - 1); /* simple frame load balancing */ x += xfer->endpoint->usb_uframe; break; } y = XHCI_MFINDEX_GET(x - xfer->endpoint->isoc_next); if ((xfer->endpoint->is_synced == 0) || (y < (xfer->nframes << shift)) || (XHCI_MFINDEX_GET(-y) >= (128 * 8))) { /* * If there is data underflow or the pipe * queue is empty we schedule the transfer a * few frames ahead of the current frame * position. Else two isochronous transfers * might overlap. */ xfer->endpoint->isoc_next = XHCI_MFINDEX_GET(x + (3 * 8)); xfer->endpoint->is_synced = 1; temp.do_isoc_sync = 1; DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); } /* compute isochronous completion time */ y = XHCI_MFINDEX_GET(xfer->endpoint->isoc_next - (x & ~7)); xfer->isoc_time_complete = usb_isoc_time_expand(&temp.sc->sc_bus, x / 8) + (y / 8) + (((xfer->nframes << shift) + 7) / 8); x = 0; temp.isoc_frame = xfer->endpoint->isoc_next; temp.trb_type = XHCI_TRB_TYPE_ISOCH; xfer->endpoint->isoc_next += xfer->nframes << shift; } else if (xfer->flags_int.control_xfr) { /* check if we should prepend a setup message */ if (xfer->flags_int.control_hdr) { temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.shortpkt = temp.len ? 1 : 0; temp.trb_type = XHCI_TRB_TYPE_SETUP_STAGE; temp.direction = 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) temp.last_frame = 1; } xhci_setup_generic_chain_sub(&temp); } x = 1; mult = 1; temp.isoc_delta = 0; temp.isoc_frame = 0; temp.trb_type = xfer->flags_int.control_did_data ? XHCI_TRB_TYPE_NORMAL : XHCI_TRB_TYPE_DATA_STAGE; } else { x = 0; mult = 1; temp.isoc_delta = 0; temp.isoc_frame = 0; temp.trb_type = XHCI_TRB_TYPE_NORMAL; } if (x != xfer->nframes) { /* set up page_cache pointer */ temp.pc = xfer->frbuffers + x; /* set endpoint direction */ temp.direction = UE_GET_DIR(xfer->endpointno); } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; temp.step_td = ((xfer->endpointno & UE_DIR_IN) && x != 0 && temp.multishort == 0); x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { /* no STATUS stage yet, DATA is last */ if (xfer->flags_int.control_act) temp.last_frame = 1; } else { temp.last_frame = 1; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.shortpkt = 0; temp.tbc = 0; temp.tlbpc = mult - 1; } else if (xfer->flags_int.isochronous_xfr) { uint8_t tdpc; /* * Isochronous transfers don't have short * packet termination: */ temp.shortpkt = 1; /* isochronous transfers have a transfer limit */ if (temp.len > xfer->max_frame_size) temp.len = xfer->max_frame_size; /* compute TD packet count */ tdpc = howmany(temp.len, xfer->max_packet_size); temp.tbc = howmany(tdpc, mult) - 1; temp.tlbpc = (tdpc % mult); if (temp.tlbpc == 0) temp.tlbpc = mult - 1; else temp.tlbpc--; } else { /* regular data transfer */ temp.shortpkt = xfer->flags.force_short_xfer ? 0 : 1; } xhci_setup_generic_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += xfer->frlengths[x - 1]; temp.isoc_frame += temp.isoc_delta; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } /* check if we should append a status stage */ if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (xhcictlstep || temp.sc->sc_ctlstep) { /* * Some XHCI controllers will not delay the * status stage until the next SOF. Force this * behaviour to avoid failed control * transfers. */ temp.step_td = (xfer->nframes != 0); } else { temp.step_td = 0; } temp.direction = UE_GET_DIR(xfer->endpointno) ^ UE_DIR_IN; temp.len = 0; temp.pc = NULL; temp.shortpkt = 0; temp.last_frame = 1; temp.trb_type = XHCI_TRB_TYPE_STATUS_STAGE; xhci_setup_generic_chain_sub(&temp); } td = temp.td; /* must have at least one frame! */ xfer->td_transfer_last = td; DPRINTF("first=%p last=%p\n", xfer->td_transfer_first, td); } static void xhci_set_slot_pointer(struct xhci_softc *sc, uint8_t index, uint64_t dev_addr) { struct usb_page_search buf_res; struct xhci_dev_ctx_addr *pdctxa; usbd_get_page(&sc->sc_hw.ctx_pc, 0, &buf_res); pdctxa = buf_res.buffer; DPRINTF("addr[%u]=0x%016llx\n", index, (long long)dev_addr); pdctxa->qwBaaDevCtxAddr[index] = htole64(dev_addr); usb_pc_cpu_flush(&sc->sc_hw.ctx_pc); } static usb_error_t xhci_configure_mask(struct usb_device *udev, uint32_t mask, uint8_t drop) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct usb_page_search buf_inp; struct xhci_input_dev_ctx *pinp; uint32_t temp; uint8_t index; uint8_t x; index = udev->controller_slot_id; usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp); pinp = buf_inp.buffer; if (drop) { mask &= XHCI_INCTX_NON_CTRL_MASK; xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx0, mask); xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx1, 0); } else { /* * Some hardware requires that we drop the endpoint * context before adding it again: */ xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx0, mask & XHCI_INCTX_NON_CTRL_MASK); /* Add new endpoint context */ xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx1, mask); /* find most significant set bit */ for (x = 31; x != 1; x--) { if (mask & (1 << x)) break; } /* adjust */ x--; /* figure out the maximum number of contexts */ if (x > sc->sc_hw.devs[index].context_num) sc->sc_hw.devs[index].context_num = x; else x = sc->sc_hw.devs[index].context_num; /* update number of contexts */ temp = xhci_ctx_get_le32(sc, &pinp->ctx_slot.dwSctx0); temp &= ~XHCI_SCTX_0_CTX_NUM_SET(31); temp |= XHCI_SCTX_0_CTX_NUM_SET(x + 1); xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx0, temp); } usb_pc_cpu_flush(&sc->sc_hw.devs[index].input_pc); return (0); } static usb_error_t xhci_configure_endpoint(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct xhci_endpoint_ext *pepext, uint16_t interval, uint8_t max_packet_count, uint8_t mult, uint8_t fps_shift, uint16_t max_packet_size, uint16_t max_frame_size, uint8_t ep_mode) { struct usb_page_search buf_inp; struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct xhci_input_dev_ctx *pinp; uint64_t ring_addr = pepext->physaddr; uint32_t temp; uint8_t index; uint8_t epno; uint8_t type; index = udev->controller_slot_id; usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp); pinp = buf_inp.buffer; epno = edesc->bEndpointAddress; type = edesc->bmAttributes & UE_XFERTYPE; if (type == UE_CONTROL) epno |= UE_DIR_IN; epno = XHCI_EPNO2EPID(epno); if (epno == 0) return (USB_ERR_NO_PIPE); /* invalid */ if (max_packet_count == 0) return (USB_ERR_BAD_BUFSIZE); max_packet_count--; if (mult == 0) return (USB_ERR_BAD_BUFSIZE); /* store endpoint mode */ pepext->trb_ep_mode = ep_mode; /* store bMaxPacketSize for control endpoints */ pepext->trb_ep_maxp = edesc->wMaxPacketSize[0]; usb_pc_cpu_flush(pepext->page_cache); if (ep_mode == USB_EP_MODE_STREAMS) { temp = XHCI_EPCTX_0_EPSTATE_SET(0) | XHCI_EPCTX_0_MAXP_STREAMS_SET(XHCI_MAX_STREAMS_LOG - 1) | XHCI_EPCTX_0_LSA_SET(1); ring_addr += sizeof(struct xhci_trb) * XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS; } else { temp = XHCI_EPCTX_0_EPSTATE_SET(0) | XHCI_EPCTX_0_MAXP_STREAMS_SET(0) | XHCI_EPCTX_0_LSA_SET(0); ring_addr |= XHCI_EPCTX_2_DCS_SET(1); } switch (udev->speed) { case USB_SPEED_FULL: case USB_SPEED_LOW: /* 1ms -> 125us */ fps_shift += 3; break; default: break; } switch (type) { case UE_INTERRUPT: if (fps_shift > 3) fps_shift--; temp |= XHCI_EPCTX_0_IVAL_SET(fps_shift); break; case UE_ISOCHRONOUS: temp |= XHCI_EPCTX_0_IVAL_SET(fps_shift); switch (udev->speed) { case USB_SPEED_SUPER: if (mult > 3) mult = 3; temp |= XHCI_EPCTX_0_MULT_SET(mult - 1); max_packet_count /= mult; break; default: break; } break; default: break; } xhci_ctx_set_le32(sc, &pinp->ctx_ep[epno - 1].dwEpCtx0, temp); temp = XHCI_EPCTX_1_HID_SET(0) | XHCI_EPCTX_1_MAXB_SET(max_packet_count) | XHCI_EPCTX_1_MAXP_SIZE_SET(max_packet_size); /* * Always enable the "three strikes and you are gone" feature * except for ISOCHRONOUS endpoints. This is suggested by * section 4.3.3 in the XHCI specification about device slot * initialisation. */ if (type != UE_ISOCHRONOUS) temp |= XHCI_EPCTX_1_CERR_SET(3); switch (type) { case UE_CONTROL: temp |= XHCI_EPCTX_1_EPTYPE_SET(4); break; case UE_ISOCHRONOUS: temp |= XHCI_EPCTX_1_EPTYPE_SET(1); break; case UE_BULK: temp |= XHCI_EPCTX_1_EPTYPE_SET(2); break; default: temp |= XHCI_EPCTX_1_EPTYPE_SET(3); break; } /* check for IN direction */ if (epno & 1) temp |= XHCI_EPCTX_1_EPTYPE_SET(4); xhci_ctx_set_le32(sc, &pinp->ctx_ep[epno - 1].dwEpCtx1, temp); xhci_ctx_set_le64(sc, &pinp->ctx_ep[epno - 1].qwEpCtx2, ring_addr); switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_ISOCHRONOUS: temp = XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_SET(max_frame_size) | XHCI_EPCTX_4_AVG_TRB_LEN_SET(MIN(XHCI_PAGE_SIZE, max_frame_size)); break; case UE_CONTROL: temp = XHCI_EPCTX_4_AVG_TRB_LEN_SET(8); break; default: temp = XHCI_EPCTX_4_AVG_TRB_LEN_SET(XHCI_PAGE_SIZE); break; } xhci_ctx_set_le32(sc, &pinp->ctx_ep[epno - 1].dwEpCtx4, temp); #ifdef USB_DEBUG xhci_dump_endpoint(sc, &pinp->ctx_ep[epno - 1]); #endif usb_pc_cpu_flush(&sc->sc_hw.devs[index].input_pc); return (0); /* success */ } static usb_error_t xhci_configure_endpoint_by_xfer(struct usb_xfer *xfer) { struct xhci_endpoint_ext *pepext; struct usb_endpoint_ss_comp_descriptor *ecomp; usb_stream_t x; pepext = xhci_get_endpoint_ext(xfer->xroot->udev, xfer->endpoint->edesc); ecomp = xfer->endpoint->ecomp; for (x = 0; x != XHCI_MAX_STREAMS; x++) { uint64_t temp; /* halt any transfers */ pepext->trb[x * XHCI_MAX_TRANSFERS].dwTrb3 = 0; /* compute start of TRB ring for stream "x" */ temp = pepext->physaddr + (x * XHCI_MAX_TRANSFERS * sizeof(struct xhci_trb)) + XHCI_SCTX_0_SCT_SEC_TR_RING; /* make tree structure */ pepext->trb[(XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS) + x].qwTrb0 = htole64(temp); /* reserved fields */ pepext->trb[(XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS) + x].dwTrb2 = 0; pepext->trb[(XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS) + x].dwTrb3 = 0; } usb_pc_cpu_flush(pepext->page_cache); return (xhci_configure_endpoint(xfer->xroot->udev, xfer->endpoint->edesc, pepext, xfer->interval, xfer->max_packet_count, (ecomp != NULL) ? UE_GET_SS_ISO_MULT(ecomp->bmAttributes) + 1 : 1, usbd_xfer_get_fps_shift(xfer), xfer->max_packet_size, xfer->max_frame_size, xfer->endpoint->ep_mode)); } static usb_error_t xhci_configure_device(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct usb_page_search buf_inp; struct usb_page_cache *pcinp; struct xhci_input_dev_ctx *pinp; struct usb_device *hubdev; uint32_t temp; uint32_t route; uint32_t rh_port; uint8_t is_hub; uint8_t index; uint8_t depth; index = udev->controller_slot_id; DPRINTF("index=%u\n", index); pcinp = &sc->sc_hw.devs[index].input_pc; usbd_get_page(pcinp, 0, &buf_inp); pinp = buf_inp.buffer; rh_port = 0; route = 0; /* figure out route string and root HUB port number */ for (hubdev = udev; hubdev != NULL; hubdev = hubdev->parent_hub) { if (hubdev->parent_hub == NULL) break; depth = hubdev->parent_hub->depth; /* * NOTE: HS/FS/LS devices and the SS root HUB can have * more than 15 ports */ rh_port = hubdev->port_no; if (depth == 0) break; if (rh_port > 15) rh_port = 15; if (depth < 6) route |= rh_port << (4 * (depth - 1)); } DPRINTF("Route=0x%08x\n", route); temp = XHCI_SCTX_0_ROUTE_SET(route) | XHCI_SCTX_0_CTX_NUM_SET( sc->sc_hw.devs[index].context_num + 1); switch (udev->speed) { case USB_SPEED_LOW: temp |= XHCI_SCTX_0_SPEED_SET(2); if (udev->parent_hs_hub != NULL && udev->parent_hs_hub->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) { DPRINTF("Device inherits MTT\n"); temp |= XHCI_SCTX_0_MTT_SET(1); } break; case USB_SPEED_HIGH: temp |= XHCI_SCTX_0_SPEED_SET(3); if (sc->sc_hw.devs[index].nports != 0 && udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) { DPRINTF("HUB supports MTT\n"); temp |= XHCI_SCTX_0_MTT_SET(1); } break; case USB_SPEED_FULL: temp |= XHCI_SCTX_0_SPEED_SET(1); if (udev->parent_hs_hub != NULL && udev->parent_hs_hub->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) { DPRINTF("Device inherits MTT\n"); temp |= XHCI_SCTX_0_MTT_SET(1); } break; default: temp |= XHCI_SCTX_0_SPEED_SET(4); break; } is_hub = sc->sc_hw.devs[index].nports != 0 && (udev->speed == USB_SPEED_SUPER || udev->speed == USB_SPEED_HIGH); if (is_hub) temp |= XHCI_SCTX_0_HUB_SET(1); xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx0, temp); temp = XHCI_SCTX_1_RH_PORT_SET(rh_port); if (is_hub) { temp |= XHCI_SCTX_1_NUM_PORTS_SET( sc->sc_hw.devs[index].nports); } switch (udev->speed) { case USB_SPEED_SUPER: switch (sc->sc_hw.devs[index].state) { case XHCI_ST_ADDRESSED: case XHCI_ST_CONFIGURED: /* enable power save */ temp |= XHCI_SCTX_1_MAX_EL_SET(sc->sc_exit_lat_max); break; default: /* disable power save */ break; } break; default: break; } xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx1, temp); temp = XHCI_SCTX_2_IRQ_TARGET_SET(0); if (is_hub) { temp |= XHCI_SCTX_2_TT_THINK_TIME_SET( sc->sc_hw.devs[index].tt); } hubdev = udev->parent_hs_hub; /* check if we should activate the transaction translator */ switch (udev->speed) { case USB_SPEED_FULL: case USB_SPEED_LOW: if (hubdev != NULL) { temp |= XHCI_SCTX_2_TT_HUB_SID_SET( hubdev->controller_slot_id); temp |= XHCI_SCTX_2_TT_PORT_NUM_SET( udev->hs_port_no); } break; default: break; } xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx2, temp); /* * These fields should be initialized to zero, according to * XHCI section 6.2.2 - slot context: */ temp = XHCI_SCTX_3_DEV_ADDR_SET(0) | XHCI_SCTX_3_SLOT_STATE_SET(0); xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx3, temp); #ifdef USB_DEBUG xhci_dump_device(sc, &pinp->ctx_slot); #endif usb_pc_cpu_flush(pcinp); return (0); /* success */ } static usb_error_t xhci_alloc_device_ext(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct usb_page_search buf_dev; struct usb_page_search buf_ep; struct xhci_trb *trb; struct usb_page_cache *pc; struct usb_page *pg; uint64_t addr; uint8_t index; uint8_t i; index = udev->controller_slot_id; pc = &sc->sc_hw.devs[index].device_pc; pg = &sc->sc_hw.devs[index].device_pg; /* need to initialize the page cache */ pc->tag_parent = sc->sc_bus.dma_parent_tag; if (usb_pc_alloc_mem(pc, pg, sc->sc_ctx_is_64_byte ? (2 * sizeof(struct xhci_dev_ctx)) : sizeof(struct xhci_dev_ctx), XHCI_PAGE_SIZE)) goto error; usbd_get_page(pc, 0, &buf_dev); pc = &sc->sc_hw.devs[index].input_pc; pg = &sc->sc_hw.devs[index].input_pg; /* need to initialize the page cache */ pc->tag_parent = sc->sc_bus.dma_parent_tag; if (usb_pc_alloc_mem(pc, pg, sc->sc_ctx_is_64_byte ? (2 * sizeof(struct xhci_input_dev_ctx)) : sizeof(struct xhci_input_dev_ctx), XHCI_PAGE_SIZE)) { goto error; } /* initialize all endpoint LINK TRBs */ for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) { pc = &sc->sc_hw.devs[index].endpoint_pc[i]; pg = &sc->sc_hw.devs[index].endpoint_pg[i]; /* need to initialize the page cache */ pc->tag_parent = sc->sc_bus.dma_parent_tag; if (usb_pc_alloc_mem(pc, pg, sizeof(struct xhci_dev_endpoint_trbs), XHCI_TRB_ALIGN)) { goto error; } /* lookup endpoint TRB ring */ usbd_get_page(pc, 0, &buf_ep); /* get TRB pointer */ trb = buf_ep.buffer; trb += XHCI_MAX_TRANSFERS - 1; /* get TRB start address */ addr = buf_ep.physaddr; /* create LINK TRB */ trb->qwTrb0 = htole64(addr); trb->dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); trb->dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); usb_pc_cpu_flush(pc); } xhci_set_slot_pointer(sc, index, buf_dev.physaddr); return (0); error: xhci_free_device_ext(udev); return (USB_ERR_NOMEM); } static void xhci_free_device_ext(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); uint8_t index; uint8_t i; index = udev->controller_slot_id; xhci_set_slot_pointer(sc, index, 0); usb_pc_free_mem(&sc->sc_hw.devs[index].device_pc); usb_pc_free_mem(&sc->sc_hw.devs[index].input_pc); for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) usb_pc_free_mem(&sc->sc_hw.devs[index].endpoint_pc[i]); } static struct xhci_endpoint_ext * xhci_get_endpoint_ext(struct usb_device *udev, struct usb_endpoint_descriptor *edesc) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct xhci_endpoint_ext *pepext; struct usb_page_cache *pc; struct usb_page_search buf_ep; uint8_t epno; uint8_t index; epno = edesc->bEndpointAddress; if ((edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) epno |= UE_DIR_IN; epno = XHCI_EPNO2EPID(epno); index = udev->controller_slot_id; pc = &sc->sc_hw.devs[index].endpoint_pc[epno]; usbd_get_page(pc, 0, &buf_ep); pepext = &sc->sc_hw.devs[index].endp[epno]; pepext->page_cache = pc; pepext->trb = buf_ep.buffer; pepext->physaddr = buf_ep.physaddr; return (pepext); } static void xhci_endpoint_doorbell(struct usb_xfer *xfer) { struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); uint8_t epno; uint8_t index; epno = xfer->endpointno; if (xfer->flags_int.control_xfr) epno |= UE_DIR_IN; epno = XHCI_EPNO2EPID(epno); index = xfer->xroot->udev->controller_slot_id; if (xfer->xroot->udev->flags.self_suspended == 0) { XWRITE4(sc, door, XHCI_DOORBELL(index), epno | XHCI_DB_SID_SET(xfer->stream_id)); } } static void xhci_transfer_remove(struct usb_xfer *xfer, usb_error_t error) { struct xhci_endpoint_ext *pepext; if (xfer->flags_int.bandwidth_reclaimed) { xfer->flags_int.bandwidth_reclaimed = 0; pepext = xhci_get_endpoint_ext(xfer->xroot->udev, xfer->endpoint->edesc); pepext->trb_used[xfer->stream_id]--; pepext->xfer[xfer->qh_pos] = NULL; if (error && pepext->trb_running != 0) { pepext->trb_halted = 1; pepext->trb_running = 0; } } } static usb_error_t xhci_transfer_insert(struct usb_xfer *xfer) { struct xhci_td *td_first; struct xhci_td *td_last; struct xhci_trb *trb_link; struct xhci_endpoint_ext *pepext; uint64_t addr; usb_stream_t id; uint8_t i; uint8_t inext; uint8_t trb_limit; DPRINTFN(8, "\n"); id = xfer->stream_id; /* check if already inserted */ if (xfer->flags_int.bandwidth_reclaimed) { DPRINTFN(8, "Already in schedule\n"); return (0); } pepext = xhci_get_endpoint_ext(xfer->xroot->udev, xfer->endpoint->edesc); td_first = xfer->td_transfer_first; td_last = xfer->td_transfer_last; addr = pepext->physaddr; switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: case UE_INTERRUPT: /* single buffered */ trb_limit = 1; break; default: /* multi buffered */ trb_limit = (XHCI_MAX_TRANSFERS - 2); break; } if (pepext->trb_used[id] >= trb_limit) { DPRINTFN(8, "Too many TDs queued.\n"); return (USB_ERR_NOMEM); } /* check if bMaxPacketSize changed */ if (xfer->flags_int.control_xfr != 0 && pepext->trb_ep_maxp != xfer->endpoint->edesc->wMaxPacketSize[0]) { DPRINTFN(8, "Reconfigure control endpoint\n"); /* force driver to reconfigure endpoint */ pepext->trb_halted = 1; pepext->trb_running = 0; } /* check for stopped condition, after putting transfer on interrupt queue */ if (pepext->trb_running == 0) { struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); DPRINTFN(8, "Not running\n"); /* start configuration */ (void)usb_proc_msignal(USB_BUS_CONTROL_XFER_PROC(&sc->sc_bus), &sc->sc_config_msg[0], &sc->sc_config_msg[1]); return (0); } pepext->trb_used[id]++; /* get current TRB index */ i = pepext->trb_index[id]; /* get next TRB index */ inext = (i + 1); /* the last entry of the ring is a hardcoded link TRB */ if (inext >= (XHCI_MAX_TRANSFERS - 1)) inext = 0; /* store next TRB index, before stream ID offset is added */ pepext->trb_index[id] = inext; /* offset for stream */ i += id * XHCI_MAX_TRANSFERS; inext += id * XHCI_MAX_TRANSFERS; /* compute terminating return address */ addr += (inext * sizeof(struct xhci_trb)); /* compute link TRB pointer */ trb_link = td_last->td_trb + td_last->ntrb; /* update next pointer of last link TRB */ trb_link->qwTrb0 = htole64(addr); trb_link->dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); trb_link->dwTrb3 = htole32(XHCI_TRB_3_IOC_BIT | XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); #ifdef USB_DEBUG xhci_dump_trb(&td_last->td_trb[td_last->ntrb]); #endif usb_pc_cpu_flush(td_last->page_cache); /* write ahead chain end marker */ pepext->trb[inext].qwTrb0 = 0; pepext->trb[inext].dwTrb2 = 0; pepext->trb[inext].dwTrb3 = 0; /* update next pointer of link TRB */ pepext->trb[i].qwTrb0 = htole64((uint64_t)td_first->td_self); pepext->trb[i].dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); #ifdef USB_DEBUG xhci_dump_trb(&pepext->trb[i]); #endif usb_pc_cpu_flush(pepext->page_cache); /* toggle cycle bit which activates the transfer chain */ pepext->trb[i].dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); usb_pc_cpu_flush(pepext->page_cache); DPRINTF("qh_pos = %u\n", i); pepext->xfer[i] = xfer; xfer->qh_pos = i; xfer->flags_int.bandwidth_reclaimed = 1; xhci_endpoint_doorbell(xfer); return (0); } static void xhci_root_intr(struct xhci_softc *sc) { uint16_t i; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* clear any old interrupt data */ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); for (i = 1; i <= sc->sc_noport; i++) { /* pick out CHANGE bits from the status register */ if (XREAD4(sc, oper, XHCI_PORTSC(i)) & ( XHCI_PS_CSC | XHCI_PS_PEC | XHCI_PS_OCC | XHCI_PS_WRC | XHCI_PS_PRC | XHCI_PS_PLC | XHCI_PS_CEC)) { sc->sc_hub_idata[i / 8] |= 1 << (i % 8); DPRINTF("port %d changed\n", i); } } uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } /*------------------------------------------------------------------------* * xhci_device_done - XHCI done handler * * NOTE: This function can be called two times in a row on * the same USB transfer. From close and from interrupt. *------------------------------------------------------------------------*/ static void xhci_device_done(struct usb_xfer *xfer, usb_error_t error) { DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); /* remove transfer from HW queue */ xhci_transfer_remove(xfer, error); /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } /*------------------------------------------------------------------------* * XHCI data transfer support (generic type) *------------------------------------------------------------------------*/ static void xhci_device_generic_open(struct usb_xfer *xfer) { if (xfer->flags_int.isochronous_xfr) { switch (xfer->xroot->udev->speed) { case USB_SPEED_FULL: break; default: usb_hs_bandwidth_alloc(xfer); break; } } } static void xhci_device_generic_close(struct usb_xfer *xfer) { DPRINTF("\n"); xhci_device_done(xfer, USB_ERR_CANCELLED); if (xfer->flags_int.isochronous_xfr) { switch (xfer->xroot->udev->speed) { case USB_SPEED_FULL: break; default: usb_hs_bandwidth_free(xfer); break; } } } static void xhci_device_generic_multi_enter(struct usb_endpoint *ep, usb_stream_t stream_id, struct usb_xfer *enter_xfer) { struct usb_xfer *xfer; /* check if there is a current transfer */ xfer = ep->endpoint_q[stream_id].curr; if (xfer == NULL) return; /* * Check if the current transfer is started and then pickup * the next one, if any. Else wait for next start event due to * block on failure feature. */ if (!xfer->flags_int.bandwidth_reclaimed) return; xfer = TAILQ_FIRST(&ep->endpoint_q[stream_id].head); if (xfer == NULL) { /* * In case of enter we have to consider that the * transfer is queued by the USB core after the enter * method is called. */ xfer = enter_xfer; if (xfer == NULL) return; } /* try to multi buffer */ xhci_transfer_insert(xfer); } static void xhci_device_generic_enter(struct usb_xfer *xfer) { DPRINTF("\n"); /* set up TD's and QH */ xhci_setup_generic_chain(xfer); xhci_device_generic_multi_enter(xfer->endpoint, xfer->stream_id, xfer); } static void xhci_device_generic_start(struct usb_xfer *xfer) { DPRINTF("\n"); /* try to insert xfer on HW queue */ xhci_transfer_insert(xfer); /* try to multi buffer */ xhci_device_generic_multi_enter(xfer->endpoint, xfer->stream_id, NULL); /* add transfer last on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) usbd_transfer_timeout_ms(xfer, &xhci_timeout, xfer->timeout); } static const struct usb_pipe_methods xhci_device_generic_methods = { .open = xhci_device_generic_open, .close = xhci_device_generic_close, .enter = xhci_device_generic_enter, .start = xhci_device_generic_start, }; /*------------------------------------------------------------------------* * xhci root HUB support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_device_descriptor xhci_devd = { .bLength = sizeof(xhci_devd), .bDescriptorType = UDESC_DEVICE, /* type */ HSETW(.bcdUSB, 0x0300), /* USB version */ .bDeviceClass = UDCLASS_HUB, /* class */ .bDeviceSubClass = UDSUBCLASS_HUB, /* subclass */ .bDeviceProtocol = UDPROTO_SSHUB, /* protocol */ .bMaxPacketSize = 9, /* max packet size */ HSETW(.idVendor, 0x0000), /* vendor */ HSETW(.idProduct, 0x0000), /* product */ HSETW(.bcdDevice, 0x0100), /* device version */ .iManufacturer = 1, .iProduct = 2, .iSerialNumber = 0, .bNumConfigurations = 1, /* # of configurations */ }; static const struct xhci_bos_desc xhci_bosd = { .bosd = { .bLength = sizeof(xhci_bosd.bosd), .bDescriptorType = UDESC_BOS, HSETW(.wTotalLength, sizeof(xhci_bosd)), .bNumDeviceCaps = 3, }, .usb2extd = { .bLength = sizeof(xhci_bosd.usb2extd), .bDescriptorType = 1, .bDevCapabilityType = 2, .bmAttributes[0] = 2, }, .usbdcd = { .bLength = sizeof(xhci_bosd.usbdcd), .bDescriptorType = UDESC_DEVICE_CAPABILITY, .bDevCapabilityType = 3, .bmAttributes = 0, /* XXX */ HSETW(.wSpeedsSupported, 0x000C), .bFunctionalitySupport = 8, .bU1DevExitLat = 255, /* dummy - not used */ .wU2DevExitLat = { 0x00, 0x08 }, }, .cidd = { .bLength = sizeof(xhci_bosd.cidd), .bDescriptorType = 1, .bDevCapabilityType = 4, .bReserved = 0, .bContainerID = 0, /* XXX */ }, }; static const struct xhci_config_desc xhci_confd = { .confd = { .bLength = sizeof(xhci_confd.confd), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(xhci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0 /* max power */ }, .ifcd = { .bLength = sizeof(xhci_confd.ifcd), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(xhci_confd.endpd), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | XHCI_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 2, /* max 15 ports */ .bInterval = 255, }, .endpcd = { .bLength = sizeof(xhci_confd.endpcd), .bDescriptorType = UDESC_ENDPOINT_SS_COMP, .bMaxBurst = 0, .bmAttributes = 0, }, }; static const struct usb_hub_ss_descriptor xhci_hubd = { .bLength = sizeof(xhci_hubd), .bDescriptorType = UDESC_SS_HUB, }; static usb_error_t xhci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); const char *str_ptr; const void *ptr; uint32_t port; uint32_t v; uint16_t len; uint16_t i; uint16_t value; uint16_t index; uint8_t j; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_desc; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " "wValue=0x%04x wIndex=0x%04x\n", req->bmRequestType, req->bRequest, UGETW(req->wLength), value, index); #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): len = 1; sc->sc_hub_desc.temp[0] = sc->sc_conf; break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): switch (value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(xhci_devd); ptr = (const void *)&xhci_devd; break; case UDESC_BOS: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(xhci_bosd); ptr = (const void *)&xhci_bosd; break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(xhci_confd); ptr = (const void *)&xhci_confd; break; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ str_ptr = "\001"; break; case 1: /* Vendor */ str_ptr = sc->sc_vendor; break; case 2: /* Product */ str_ptr = "XHCI root HUB"; break; default: str_ptr = ""; break; } len = usb_make_str_desc( sc->sc_hub_desc.temp, sizeof(sc->sc_hub_desc.temp), str_ptr); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): len = 1; sc->sc_hub_desc.temp[0] = 0; break; case C(UR_GET_STATUS, UT_READ_DEVICE): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, 0); break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= XHCI_MAX_DEVICES) { err = USB_ERR_IOERROR; goto done; } break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if (value != 0 && value != 1) { err = USB_ERR_IOERROR; goto done; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USB_ERR_IOERROR; goto done; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n"); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } port = XHCI_PORTSC(index); v = XREAD4(sc, oper, port); i = XHCI_PS_PLS_GET(v); v &= ~XHCI_PS_CLEAR; switch (value) { case UHF_C_BH_PORT_RESET: XWRITE4(sc, oper, port, v | XHCI_PS_WRC); break; case UHF_C_PORT_CONFIG_ERROR: XWRITE4(sc, oper, port, v | XHCI_PS_CEC); break; case UHF_C_PORT_SUSPEND: case UHF_C_PORT_LINK_STATE: XWRITE4(sc, oper, port, v | XHCI_PS_PLC); break; case UHF_C_PORT_CONNECTION: XWRITE4(sc, oper, port, v | XHCI_PS_CSC); break; case UHF_C_PORT_ENABLE: XWRITE4(sc, oper, port, v | XHCI_PS_PEC); break; case UHF_C_PORT_OVER_CURRENT: XWRITE4(sc, oper, port, v | XHCI_PS_OCC); break; case UHF_C_PORT_RESET: XWRITE4(sc, oper, port, v | XHCI_PS_PRC); break; case UHF_PORT_ENABLE: XWRITE4(sc, oper, port, v | XHCI_PS_PED); break; case UHF_PORT_POWER: XWRITE4(sc, oper, port, v & ~XHCI_PS_PP); break; case UHF_PORT_INDICATOR: XWRITE4(sc, oper, port, v & ~XHCI_PS_PIC_SET(3)); break; case UHF_PORT_SUSPEND: /* U3 -> U15 */ if (i == 3) { XWRITE4(sc, oper, port, v | XHCI_PS_PLS_SET(0xF) | XHCI_PS_LWS); } /* wait 20ms for resume sequence to complete */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); /* U0 */ XWRITE4(sc, oper, port, v | XHCI_PS_PLS_SET(0) | XHCI_PS_LWS); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } v = XREAD4(sc, capa, XHCI_HCSPARAMS0); sc->sc_hub_desc.hubd = xhci_hubd; sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; if (XHCI_HCS0_PPC(v)) i = UHD_PWR_INDIVIDUAL; else i = UHD_PWR_GANGED; if (XHCI_HCS0_PIND(v)) i |= UHD_PORT_IND; i |= UHD_OC_INDIVIDUAL; USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i); /* see XHCI section 5.4.9: */ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 10; for (j = 1; j <= sc->sc_noport; j++) { v = XREAD4(sc, oper, XHCI_PORTSC(j)); if (v & XHCI_PS_DR) { sc->sc_hub_desc.hubd. DeviceRemovable[j / 8] |= 1U << (j % 8); } } len = sc->sc_hub_desc.hubd.bLength; break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): len = 16; memset(sc->sc_hub_desc.temp, 0, 16); break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): DPRINTFN(9, "UR_GET_STATUS i=%d\n", index); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } v = XREAD4(sc, oper, XHCI_PORTSC(index)); DPRINTFN(9, "port status=0x%08x\n", v); i = UPS_PORT_LINK_STATE_SET(XHCI_PS_PLS_GET(v)); switch (XHCI_PS_SPEED_GET(v)) { case 3: i |= UPS_HIGH_SPEED; break; case 2: i |= UPS_LOW_SPEED; break; case 1: /* FULL speed */ break; default: i |= UPS_OTHER_SPEED; break; } if (v & XHCI_PS_CCS) i |= UPS_CURRENT_CONNECT_STATUS; if (v & XHCI_PS_PED) i |= UPS_PORT_ENABLED; if (v & XHCI_PS_OCA) i |= UPS_OVERCURRENT_INDICATOR; if (v & XHCI_PS_PR) i |= UPS_RESET; if (v & XHCI_PS_PP) { /* * The USB 3.0 RH is using the * USB 2.0's power bit */ i |= UPS_PORT_POWER; } USETW(sc->sc_hub_desc.ps.wPortStatus, i); i = 0; if (v & XHCI_PS_CSC) i |= UPS_C_CONNECT_STATUS; if (v & XHCI_PS_PEC) i |= UPS_C_PORT_ENABLED; if (v & XHCI_PS_OCC) i |= UPS_C_OVERCURRENT_INDICATOR; if (v & XHCI_PS_WRC) i |= UPS_C_BH_PORT_RESET; if (v & XHCI_PS_PRC) i |= UPS_C_PORT_RESET; if (v & XHCI_PS_PLC) i |= UPS_C_PORT_LINK_STATE; if (v & XHCI_PS_CEC) i |= UPS_C_PORT_CONFIG_ERROR; USETW(sc->sc_hub_desc.ps.wPortChange, i); len = sizeof(sc->sc_hub_desc.ps); break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USB_ERR_IOERROR; goto done; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): i = index >> 8; index &= 0x00FF; if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } port = XHCI_PORTSC(index); v = XREAD4(sc, oper, port) & ~XHCI_PS_CLEAR; switch (value) { case UHF_PORT_U1_TIMEOUT: if (XHCI_PS_SPEED_GET(v) != 4) { err = USB_ERR_IOERROR; goto done; } port = XHCI_PORTPMSC(index); v = XREAD4(sc, oper, port); v &= ~XHCI_PM3_U1TO_SET(0xFF); v |= XHCI_PM3_U1TO_SET(i); XWRITE4(sc, oper, port, v); break; case UHF_PORT_U2_TIMEOUT: if (XHCI_PS_SPEED_GET(v) != 4) { err = USB_ERR_IOERROR; goto done; } port = XHCI_PORTPMSC(index); v = XREAD4(sc, oper, port); v &= ~XHCI_PM3_U2TO_SET(0xFF); v |= XHCI_PM3_U2TO_SET(i); XWRITE4(sc, oper, port, v); break; case UHF_BH_PORT_RESET: XWRITE4(sc, oper, port, v | XHCI_PS_WPR); break; case UHF_PORT_LINK_STATE: XWRITE4(sc, oper, port, v | XHCI_PS_PLS_SET(i) | XHCI_PS_LWS); /* 4ms settle time */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); break; case UHF_PORT_ENABLE: DPRINTFN(3, "set port enable %d\n", index); break; case UHF_PORT_SUSPEND: DPRINTFN(6, "suspend port %u (LPM=%u)\n", index, i); j = XHCI_PS_SPEED_GET(v); if ((j < 1) || (j > 3)) { /* non-supported speed */ err = USB_ERR_IOERROR; goto done; } XWRITE4(sc, oper, port, v | XHCI_PS_PLS_SET(i ? 2 /* LPM */ : 3) | XHCI_PS_LWS); break; case UHF_PORT_RESET: DPRINTFN(6, "reset port %d\n", index); XWRITE4(sc, oper, port, v | XHCI_PS_PR); break; case UHF_PORT_POWER: DPRINTFN(3, "set port power %d\n", index); XWRITE4(sc, oper, port, v | XHCI_PS_PP); break; case UHF_PORT_TEST: DPRINTFN(3, "set port test %d\n", index); break; case UHF_PORT_INDICATOR: DPRINTFN(3, "set port indicator %d\n", index); v &= ~XHCI_PS_PIC_SET(3); v |= XHCI_PS_PIC_SET(1); XWRITE4(sc, oper, port, v); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): break; default: err = USB_ERR_IOERROR; goto done; } done: *plength = len; *pptr = ptr; return (err); } static void xhci_xfer_setup(struct usb_setup_params *parm) { struct usb_page_search page_info; struct usb_page_cache *pc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; xfer = parm->curr_xfer; /* * The proof for the "ntd" formula is illustrated like this: * * +------------------------------------+ * | | * | |remainder -> | * | +-----+---+ | * | | xxx | x | frm 0 | * | +-----+---++ | * | | xxx | xx | frm 1 | * | +-----+----+ | * | ... | * +------------------------------------+ * * "xxx" means a completely full USB transfer descriptor * * "x" and "xx" means a short USB packet * * For the remainder of an USB transfer modulo * "max_data_length" we need two USB transfer descriptors. * One to transfer the remaining data and one to finalise with * a zero length packet in case the "force_short_xfer" flag is * set. We only need two USB transfer descriptors in the case * where the transfer length of the first one is a factor of * "max_frame_size". The rest of the needed USB transfer * descriptors is given by the buffer size divided by the * maximum data payload. */ parm->hc_max_packet_size = 0x400; parm->hc_max_packet_count = 16 * 3; parm->hc_max_frame_size = XHCI_TD_PAYLOAD_MAX; xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); if (xfer->flags_int.isochronous_xfr) { ntd = ((1 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); } else if (xfer->flags_int.control_xfr) { ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + (xfer->max_data_length / xfer->max_hc_frame_size)); } else { ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); } alloc_dma_set: if (parm->err) return; /* * Allocate queue heads and transfer descriptors */ last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(struct xhci_td), XHCI_TD_ALIGN, ntd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != ntd; n++) { struct xhci_td *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; /* init TD */ td->td_self = page_info.physaddr; td->obj_next = last_obj; td->page_cache = pc + n; last_obj = td; usb_pc_cpu_flush(pc + n); } } xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; if (!xfer->flags_int.curr_dma_set) { xfer->flags_int.curr_dma_set = 1; goto alloc_dma_set; } } static usb_error_t xhci_configure_reset_endpoint(struct usb_xfer *xfer) { struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); struct usb_page_search buf_inp; struct usb_device *udev; struct xhci_endpoint_ext *pepext; struct usb_endpoint_descriptor *edesc; struct usb_page_cache *pcinp; usb_error_t err; usb_stream_t stream_id; uint32_t mask; uint8_t index; uint8_t epno; pepext = xhci_get_endpoint_ext(xfer->xroot->udev, xfer->endpoint->edesc); udev = xfer->xroot->udev; index = udev->controller_slot_id; pcinp = &sc->sc_hw.devs[index].input_pc; usbd_get_page(pcinp, 0, &buf_inp); edesc = xfer->endpoint->edesc; epno = edesc->bEndpointAddress; stream_id = xfer->stream_id; if ((edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) epno |= UE_DIR_IN; epno = XHCI_EPNO2EPID(epno); if (epno == 0) return (USB_ERR_NO_PIPE); /* invalid */ XHCI_CMD_LOCK(sc); /* configure endpoint */ err = xhci_configure_endpoint_by_xfer(xfer); if (err != 0) { XHCI_CMD_UNLOCK(sc); return (err); } /* * Get the endpoint into the stopped state according to the * endpoint context state diagram in the XHCI specification: */ err = xhci_cmd_stop_ep(sc, 0, epno, index); if (err != 0) DPRINTF("Could not stop endpoint %u\n", epno); err = xhci_cmd_reset_ep(sc, 0, epno, index); if (err != 0) DPRINTF("Could not reset endpoint %u\n", epno); err = xhci_cmd_set_tr_dequeue_ptr(sc, (pepext->physaddr + (stream_id * sizeof(struct xhci_trb) * XHCI_MAX_TRANSFERS)) | XHCI_EPCTX_2_DCS_SET(1), stream_id, epno, index); if (err != 0) DPRINTF("Could not set dequeue ptr for endpoint %u\n", epno); /* * Get the endpoint into the running state according to the * endpoint context state diagram in the XHCI specification: */ mask = (1U << epno); xhci_configure_mask(udev, mask | 1U, 0); if (!(sc->sc_hw.devs[index].ep_configured & mask)) { sc->sc_hw.devs[index].ep_configured |= mask; err = xhci_cmd_configure_ep(sc, buf_inp.physaddr, 0, index); } else { err = xhci_cmd_evaluate_ctx(sc, buf_inp.physaddr, index); } if (err != 0) { DPRINTF("Could not configure " "endpoint %u at slot %u.\n", epno, index); } XHCI_CMD_UNLOCK(sc); return (0); } static void xhci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void xhci_start_dma_delay(struct usb_xfer *xfer) { struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); /* put transfer on interrupt queue (again) */ usbd_transfer_enqueue(&sc->sc_bus.intr_q, xfer); (void)usb_proc_msignal(USB_BUS_CONTROL_XFER_PROC(&sc->sc_bus), &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } static void xhci_configure_msg(struct usb_proc_msg *pm) { struct xhci_softc *sc; struct xhci_endpoint_ext *pepext; struct usb_xfer *xfer; sc = XHCI_BUS2SC(((struct usb_bus_msg *)pm)->bus); restart: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { pepext = xhci_get_endpoint_ext(xfer->xroot->udev, xfer->endpoint->edesc); if ((pepext->trb_halted != 0) || (pepext->trb_running == 0)) { uint16_t i; /* clear halted and running */ pepext->trb_halted = 0; pepext->trb_running = 0; /* nuke remaining buffered transfers */ for (i = 0; i != (XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS); i++) { /* * NOTE: We need to use the timeout * error code here else existing * isochronous clients can get * confused: */ if (pepext->xfer[i] != NULL) { xhci_device_done(pepext->xfer[i], USB_ERR_TIMEOUT); } } /* * NOTE: The USB transfer cannot vanish in * this state! */ USB_BUS_UNLOCK(&sc->sc_bus); xhci_configure_reset_endpoint(xfer); USB_BUS_LOCK(&sc->sc_bus); /* check if halted is still cleared */ if (pepext->trb_halted == 0) { pepext->trb_running = 1; memset(pepext->trb_index, 0, sizeof(pepext->trb_index)); } goto restart; } if (xfer->flags_int.did_dma_delay) { /* remove transfer from interrupt queue (again) */ usbd_transfer_dequeue(xfer); /* we are finally done */ usb_dma_delay_done_cb(xfer); /* queue changed - restart */ goto restart; } } TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { /* try to insert xfer on HW queue */ xhci_transfer_insert(xfer); /* try to multi buffer */ xhci_device_generic_multi_enter(xfer->endpoint, xfer->stream_id, NULL); } } static void xhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct xhci_endpoint_ext *pepext; DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode); if (udev->parent_hub == NULL) { /* root HUB has special endpoint handling */ return; } ep->methods = &xhci_device_generic_methods; pepext = xhci_get_endpoint_ext(udev, edesc); USB_BUS_LOCK(udev->bus); pepext->trb_halted = 1; pepext->trb_running = 0; USB_BUS_UNLOCK(udev->bus); } static void xhci_ep_uninit(struct usb_device *udev, struct usb_endpoint *ep) { } static void xhci_ep_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct xhci_endpoint_ext *pepext; DPRINTF("\n"); if (udev->flags.usb_mode != USB_MODE_HOST) { /* not supported */ return; } if (udev->parent_hub == NULL) { /* root HUB has special endpoint handling */ return; } pepext = xhci_get_endpoint_ext(udev, ep->edesc); USB_BUS_LOCK(udev->bus); pepext->trb_halted = 1; pepext->trb_running = 0; USB_BUS_UNLOCK(udev->bus); } static usb_error_t xhci_device_init(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); usb_error_t err; uint8_t temp; /* no init for root HUB */ if (udev->parent_hub == NULL) return (0); XHCI_CMD_LOCK(sc); /* set invalid default */ udev->controller_slot_id = sc->sc_noslot + 1; /* try to get a new slot ID from the XHCI */ err = xhci_cmd_enable_slot(sc, &temp); if (err) { XHCI_CMD_UNLOCK(sc); return (err); } if (temp > sc->sc_noslot) { XHCI_CMD_UNLOCK(sc); return (USB_ERR_BAD_ADDRESS); } if (sc->sc_hw.devs[temp].state != XHCI_ST_DISABLED) { DPRINTF("slot %u already allocated.\n", temp); XHCI_CMD_UNLOCK(sc); return (USB_ERR_BAD_ADDRESS); } /* store slot ID for later reference */ udev->controller_slot_id = temp; /* reset data structure */ memset(&sc->sc_hw.devs[temp], 0, sizeof(sc->sc_hw.devs[0])); /* set mark slot allocated */ sc->sc_hw.devs[temp].state = XHCI_ST_ENABLED; err = xhci_alloc_device_ext(udev); XHCI_CMD_UNLOCK(sc); /* get device into default state */ if (err == 0) err = xhci_set_address(udev, NULL, 0); return (err); } static void xhci_device_uninit(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); uint8_t index; /* no init for root HUB */ if (udev->parent_hub == NULL) return; XHCI_CMD_LOCK(sc); index = udev->controller_slot_id; if (index <= sc->sc_noslot) { xhci_cmd_disable_slot(sc, index); sc->sc_hw.devs[index].state = XHCI_ST_DISABLED; /* free device extension */ xhci_free_device_ext(udev); } XHCI_CMD_UNLOCK(sc); } static void xhci_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* * Wait until the hardware has finished any possible use of * the transfer descriptor(s) */ *pus = 2048; /* microseconds */ } static void xhci_device_resume(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); uint8_t index; uint8_t n; uint8_t p; DPRINTF("\n"); /* check for root HUB */ if (udev->parent_hub == NULL) return; index = udev->controller_slot_id; XHCI_CMD_LOCK(sc); /* blindly resume all endpoints */ USB_BUS_LOCK(udev->bus); for (n = 1; n != XHCI_MAX_ENDPOINTS; n++) { for (p = 0; p != XHCI_MAX_STREAMS; p++) { XWRITE4(sc, door, XHCI_DOORBELL(index), n | XHCI_DB_SID_SET(p)); } } USB_BUS_UNLOCK(udev->bus); XHCI_CMD_UNLOCK(sc); } static void xhci_device_suspend(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); uint8_t index; uint8_t n; usb_error_t err; DPRINTF("\n"); /* check for root HUB */ if (udev->parent_hub == NULL) return; index = udev->controller_slot_id; XHCI_CMD_LOCK(sc); /* blindly suspend all endpoints */ for (n = 1; n != XHCI_MAX_ENDPOINTS; n++) { err = xhci_cmd_stop_ep(sc, 1, n, index); if (err != 0) { DPRINTF("Failed to suspend endpoint " "%u on slot %u (ignored).\n", n, index); } } XHCI_CMD_UNLOCK(sc); } static void xhci_set_hw_power(struct usb_bus *bus) { DPRINTF("\n"); } static void xhci_device_state_change(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); struct usb_page_search buf_inp; usb_error_t err; uint8_t index; /* check for root HUB */ if (udev->parent_hub == NULL) return; index = udev->controller_slot_id; DPRINTF("\n"); if (usb_get_device_state(udev) == USB_STATE_CONFIGURED) { err = uhub_query_info(udev, &sc->sc_hw.devs[index].nports, &sc->sc_hw.devs[index].tt); if (err != 0) sc->sc_hw.devs[index].nports = 0; } XHCI_CMD_LOCK(sc); switch (usb_get_device_state(udev)) { case USB_STATE_POWERED: if (sc->sc_hw.devs[index].state == XHCI_ST_DEFAULT) break; /* set default state */ sc->sc_hw.devs[index].state = XHCI_ST_DEFAULT; sc->sc_hw.devs[index].ep_configured = 3U; /* reset number of contexts */ sc->sc_hw.devs[index].context_num = 0; err = xhci_cmd_reset_dev(sc, index); if (err != 0) { DPRINTF("Device reset failed " "for slot %u.\n", index); } break; case USB_STATE_ADDRESSED: if (sc->sc_hw.devs[index].state == XHCI_ST_ADDRESSED) break; sc->sc_hw.devs[index].state = XHCI_ST_ADDRESSED; sc->sc_hw.devs[index].ep_configured = 3U; /* set configure mask to slot only */ xhci_configure_mask(udev, 1, 0); /* deconfigure all endpoints, except EP0 */ err = xhci_cmd_configure_ep(sc, 0, 1, index); if (err) { DPRINTF("Failed to deconfigure " "slot %u.\n", index); } break; case USB_STATE_CONFIGURED: if (sc->sc_hw.devs[index].state == XHCI_ST_CONFIGURED) { /* deconfigure all endpoints, except EP0 */ err = xhci_cmd_configure_ep(sc, 0, 1, index); if (err) { DPRINTF("Failed to deconfigure " "slot %u.\n", index); } } /* set configured state */ sc->sc_hw.devs[index].state = XHCI_ST_CONFIGURED; sc->sc_hw.devs[index].ep_configured = 3U; /* reset number of contexts */ sc->sc_hw.devs[index].context_num = 0; usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp); xhci_configure_mask(udev, 3, 0); err = xhci_configure_device(udev); if (err != 0) { DPRINTF("Could not configure device " "at slot %u.\n", index); } err = xhci_cmd_evaluate_ctx(sc, buf_inp.physaddr, index); if (err != 0) { DPRINTF("Could not evaluate device " "context at slot %u.\n", index); } break; default: break; } XHCI_CMD_UNLOCK(sc); } static usb_error_t xhci_set_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep, uint8_t ep_mode) { switch (ep_mode) { case USB_EP_MODE_DEFAULT: return (0); case USB_EP_MODE_STREAMS: if (xhcistreams == 0 || (ep->edesc->bmAttributes & UE_XFERTYPE) != UE_BULK || udev->speed != USB_SPEED_SUPER) return (USB_ERR_INVAL); return (0); default: return (USB_ERR_INVAL); } } static const struct usb_bus_methods xhci_bus_methods = { .endpoint_init = xhci_ep_init, .endpoint_uninit = xhci_ep_uninit, .xfer_setup = xhci_xfer_setup, .xfer_unsetup = xhci_xfer_unsetup, .get_dma_delay = xhci_get_dma_delay, .device_init = xhci_device_init, .device_uninit = xhci_device_uninit, .device_resume = xhci_device_resume, .device_suspend = xhci_device_suspend, .set_hw_power = xhci_set_hw_power, .roothub_exec = xhci_roothub_exec, .xfer_poll = xhci_do_poll, .start_dma_delay = xhci_start_dma_delay, .set_address = xhci_set_address, .clear_stall = xhci_ep_clear_stall, .device_state_change = xhci_device_state_change, .set_hw_power_sleep = xhci_set_hw_power_sleep, .set_endpoint_mode = xhci_set_endpoint_mode, }; Index: head/sys/dev/usb/gadget/g_audio.c =================================================================== --- head/sys/dev/usb/gadget/g_audio.c (revision 357971) +++ head/sys/dev/usb/gadget/g_audio.c (revision 357972) @@ -1,626 +1,627 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf * http://www.usb.org/developers/devclass_docs/frmts10.pdf * http://www.usb.org/developers/devclass_docs/termt10.pdf */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define USB_DEBUG_VAR g_audio_debug #include #include enum { G_AUDIO_ISOC0_RD, G_AUDIO_ISOC1_RD, G_AUDIO_ISOC0_WR, G_AUDIO_ISOC1_WR, G_AUDIO_N_TRANSFER, }; struct g_audio_softc { struct mtx sc_mtx; struct usb_callout sc_callout; struct usb_callout sc_watchdog; struct usb_xfer *sc_xfer[G_AUDIO_N_TRANSFER]; int sc_mode; int sc_pattern_len; int sc_throughput; int sc_tx_interval; int sc_state; int sc_noise_rem; int8_t sc_pattern[G_AUDIO_MAX_STRLEN]; uint16_t sc_data_len[2][G_AUDIO_FRAMES]; int16_t sc_data_buf[2][G_AUDIO_BUFSIZE / 2]; uint8_t sc_volume_setting[32]; uint8_t sc_volume_limit[32]; uint8_t sc_sample_rate[32]; }; -static SYSCTL_NODE(_hw_usb, OID_AUTO, g_audio, CTLFLAG_RW, 0, "USB audio gadget"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, g_audio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB audio gadget"); #ifdef USB_DEBUG static int g_audio_debug = 0; SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, debug, CTLFLAG_RWTUN, &g_audio_debug, 0, "Debug level"); #endif static int g_audio_mode = 0; SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, mode, CTLFLAG_RWTUN, &g_audio_mode, 0, "Mode selection"); static int g_audio_pattern_interval = 1000; SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, pattern_interval, CTLFLAG_RWTUN, &g_audio_pattern_interval, 0, "Pattern interval in milliseconds"); static char g_audio_pattern_data[G_AUDIO_MAX_STRLEN]; SYSCTL_STRING(_hw_usb_g_audio, OID_AUTO, pattern, CTLFLAG_RW, &g_audio_pattern_data, sizeof(g_audio_pattern_data), "Data pattern"); static int g_audio_throughput; SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, throughput, CTLFLAG_RD, &g_audio_throughput, sizeof(g_audio_throughput), "Throughput in bytes per second"); static device_probe_t g_audio_probe; static device_attach_t g_audio_attach; static device_detach_t g_audio_detach; static usb_handle_request_t g_audio_handle_request; static usb_callback_t g_audio_isoc_read_callback; static usb_callback_t g_audio_isoc_write_callback; static devclass_t g_audio_devclass; static void g_audio_watchdog(void *arg); static void g_audio_timeout(void *arg); static device_method_t g_audio_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, g_audio_handle_request), /* Device interface */ DEVMETHOD(device_probe, g_audio_probe), DEVMETHOD(device_attach, g_audio_attach), DEVMETHOD(device_detach, g_audio_detach), DEVMETHOD_END }; static driver_t g_audio_driver = { .name = "g_audio", .methods = g_audio_methods, .size = sizeof(struct g_audio_softc), }; DRIVER_MODULE(g_audio, uhub, g_audio_driver, g_audio_devclass, 0, 0); MODULE_DEPEND(g_audio, usb, 1, 1, 1); static const struct usb_config g_audio_config[G_AUDIO_N_TRANSFER] = { [G_AUDIO_ISOC0_RD] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .flags = {.ext_buffer = 1,.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = G_AUDIO_BUFSIZE, .callback = &g_audio_isoc_read_callback, .frames = G_AUDIO_FRAMES, .usb_mode = USB_MODE_DEVICE, .if_index = 1, }, [G_AUDIO_ISOC1_RD] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .flags = {.ext_buffer = 1,.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = G_AUDIO_BUFSIZE, .callback = &g_audio_isoc_read_callback, .frames = G_AUDIO_FRAMES, .usb_mode = USB_MODE_DEVICE, .if_index = 1, }, [G_AUDIO_ISOC0_WR] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .flags = {.ext_buffer = 1,.pipe_bof = 1,}, .bufsize = G_AUDIO_BUFSIZE, .callback = &g_audio_isoc_write_callback, .frames = G_AUDIO_FRAMES, .usb_mode = USB_MODE_DEVICE, .if_index = 2, }, [G_AUDIO_ISOC1_WR] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .flags = {.ext_buffer = 1,.pipe_bof = 1,}, .bufsize = G_AUDIO_BUFSIZE, .callback = &g_audio_isoc_write_callback, .frames = G_AUDIO_FRAMES, .usb_mode = USB_MODE_DEVICE, .if_index = 2, }, }; static void g_audio_timeout_reset(struct g_audio_softc *sc) { int i = g_audio_pattern_interval; sc->sc_tx_interval = i; if (i <= 0) i = 1; else if (i > 1023) i = 1023; i = USB_MS_TO_TICKS(i); usb_callout_reset(&sc->sc_callout, i, &g_audio_timeout, sc); } static void g_audio_timeout(void *arg) { struct g_audio_softc *sc = arg; sc->sc_mode = g_audio_mode; memcpy(sc->sc_pattern, g_audio_pattern_data, sizeof(sc->sc_pattern)); sc->sc_pattern[G_AUDIO_MAX_STRLEN - 1] = 0; sc->sc_pattern_len = strlen(sc->sc_pattern); if (sc->sc_mode != G_AUDIO_MODE_LOOP) { usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_WR]); usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_WR]); } g_audio_timeout_reset(sc); } static void g_audio_watchdog_reset(struct g_audio_softc *sc) { usb_callout_reset(&sc->sc_watchdog, hz, &g_audio_watchdog, sc); } static void g_audio_watchdog(void *arg) { struct g_audio_softc *sc = arg; int i; i = sc->sc_throughput; sc->sc_throughput = 0; g_audio_throughput = i; g_audio_watchdog_reset(sc); } static int g_audio_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_DEVICE) return (ENXIO); if ((uaa->info.bInterfaceClass == UICLASS_AUDIO) && (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL)) return (0); return (ENXIO); } static int g_audio_attach(device_t dev) { struct g_audio_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int error; int i; uint8_t iface_index[3]; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "g_audio", NULL, MTX_DEF); usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); sc->sc_mode = G_AUDIO_MODE_SILENT; sc->sc_noise_rem = 1; for (i = 0; i != G_AUDIO_FRAMES; i++) { sc->sc_data_len[0][i] = G_AUDIO_BUFSIZE / G_AUDIO_FRAMES; sc->sc_data_len[1][i] = G_AUDIO_BUFSIZE / G_AUDIO_FRAMES; } iface_index[0] = uaa->info.bIfaceIndex; iface_index[1] = uaa->info.bIfaceIndex + 1; iface_index[2] = uaa->info.bIfaceIndex + 2; error = usbd_set_alt_interface_index(uaa->device, iface_index[1], 1); if (error) { DPRINTF("alt iface setting error=%s\n", usbd_errstr(error)); goto detach; } error = usbd_set_alt_interface_index(uaa->device, iface_index[2], 1); if (error) { DPRINTF("alt iface setting error=%s\n", usbd_errstr(error)); goto detach; } error = usbd_transfer_setup(uaa->device, iface_index, sc->sc_xfer, g_audio_config, G_AUDIO_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } usbd_set_parent_iface(uaa->device, iface_index[1], iface_index[0]); usbd_set_parent_iface(uaa->device, iface_index[2], iface_index[0]); mtx_lock(&sc->sc_mtx); usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_RD]); usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_RD]); usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_WR]); usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_WR]); g_audio_timeout_reset(sc); g_audio_watchdog_reset(sc); mtx_unlock(&sc->sc_mtx); return (0); /* success */ detach: g_audio_detach(dev); return (ENXIO); /* error */ } static int g_audio_detach(device_t dev) { struct g_audio_softc *sc = device_get_softc(dev); DPRINTF("\n"); mtx_lock(&sc->sc_mtx); usb_callout_stop(&sc->sc_callout); usb_callout_stop(&sc->sc_watchdog); mtx_unlock(&sc->sc_mtx); usbd_transfer_unsetup(sc->sc_xfer, G_AUDIO_N_TRANSFER); usb_callout_drain(&sc->sc_callout); usb_callout_drain(&sc->sc_watchdog); mtx_destroy(&sc->sc_mtx); return (0); } static int32_t g_noise(struct g_audio_softc *sc) { uint32_t temp; const uint32_t prime = 0xFFFF1D; if (sc->sc_noise_rem & 1) { sc->sc_noise_rem += prime; } sc->sc_noise_rem /= 2; temp = sc->sc_noise_rem; /* unsigned to signed conversion */ temp ^= 0x800000; if (temp & 0x800000) { temp |= (-0x800000); } return temp; } static void g_audio_make_samples(struct g_audio_softc *sc, int16_t *ptr, int samples) { int i; int j; for (i = 0; i != samples; i++) { j = g_noise(sc); if ((sc->sc_state < 0) || (sc->sc_state >= sc->sc_pattern_len)) sc->sc_state = 0; if (sc->sc_pattern_len != 0) { j = (j * sc->sc_pattern[sc->sc_state]) >> 16; sc->sc_state++; } *ptr++ = j / 256; *ptr++ = j / 256; } } static void g_audio_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct g_audio_softc *sc = usbd_xfer_softc(xfer); int actlen; int aframes; int nr = (xfer == sc->sc_xfer[G_AUDIO_ISOC0_WR]) ? 0 : 1; int16_t *ptr; int i; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_throughput += actlen; if (sc->sc_mode == G_AUDIO_MODE_LOOP) break; /* sync with RX */ case USB_ST_SETUP: tr_setup: ptr = sc->sc_data_buf[nr]; if (sc->sc_mode == G_AUDIO_MODE_PATTERN) { for (i = 0; i != G_AUDIO_FRAMES; i++) { usbd_xfer_set_frame_data(xfer, i, ptr, sc->sc_data_len[nr][i]); g_audio_make_samples(sc, ptr, (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2); ptr += (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2; } } else if (sc->sc_mode == G_AUDIO_MODE_LOOP) { for (i = 0; i != G_AUDIO_FRAMES; i++) { usbd_xfer_set_frame_data(xfer, i, ptr, sc->sc_data_len[nr][i] & ~3); g_audio_make_samples(sc, ptr, sc->sc_data_len[nr][i] / 4); ptr += (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2; } } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void g_audio_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct g_audio_softc *sc = usbd_xfer_softc(xfer); int actlen; int aframes; int nr = (xfer == sc->sc_xfer[G_AUDIO_ISOC0_RD]) ? 0 : 1; int16_t *ptr; int i; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_throughput += actlen; for (i = 0; i != G_AUDIO_FRAMES; i++) { sc->sc_data_len[nr][i] = usbd_xfer_frame_len(xfer, i); } usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_WR]); usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_WR]); break; case USB_ST_SETUP: tr_setup: ptr = sc->sc_data_buf[nr]; for (i = 0; i != G_AUDIO_FRAMES; i++) { usbd_xfer_set_frame_data(xfer, i, ptr, G_AUDIO_BUFSIZE / G_AUDIO_FRAMES); ptr += (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2; } usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int g_audio_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct g_audio_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; if (!is_complete) { if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && (req->bRequest == 0x82 /* get min */ )) { if (offset == 0) { USETW(sc->sc_volume_limit, 0); *plen = 2; *pptr = &sc->sc_volume_limit; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && (req->bRequest == 0x83 /* get max */ )) { if (offset == 0) { USETW(sc->sc_volume_limit, 0x2000); *plen = 2; *pptr = &sc->sc_volume_limit; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && (req->bRequest == 0x84 /* get residue */ )) { if (offset == 0) { USETW(sc->sc_volume_limit, 1); *plen = 2; *pptr = &sc->sc_volume_limit; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && (req->bRequest == 0x81 /* get value */ )) { if (offset == 0) { USETW(sc->sc_volume_setting, 0x2000); *plen = sizeof(sc->sc_volume_setting); *pptr = &sc->sc_volume_setting; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == 0x01 /* set value */ )) { if (offset == 0) { *plen = sizeof(sc->sc_volume_setting); *pptr = &sc->sc_volume_setting; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_ENDPOINT) && (req->bRequest == 0x01 /* set value */ )) { if (offset == 0) { *plen = sizeof(sc->sc_sample_rate); *pptr = &sc->sc_sample_rate; } else { *plen = 0; } return (0); } } return (ENXIO); /* use builtin handler */ } Index: head/sys/dev/usb/gadget/g_keyboard.c =================================================================== --- head/sys/dev/usb/gadget/g_keyboard.c (revision 357971) +++ head/sys/dev/usb/gadget/g_keyboard.c (revision 357972) @@ -1,413 +1,415 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define USB_DEBUG_VAR g_keyboard_debug #include #include -static SYSCTL_NODE(_hw_usb, OID_AUTO, g_keyboard, CTLFLAG_RW, 0, "USB keyboard gadget"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, g_keyboard, + CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB keyboard gadget"); #ifdef USB_DEBUG static int g_keyboard_debug = 0; SYSCTL_INT(_hw_usb_g_keyboard, OID_AUTO, debug, CTLFLAG_RWTUN, &g_keyboard_debug, 0, "Debug level"); #endif static int g_keyboard_mode = 0; SYSCTL_INT(_hw_usb_g_keyboard, OID_AUTO, mode, CTLFLAG_RWTUN, &g_keyboard_mode, 0, "Mode selection"); static int g_keyboard_key_press_interval = 1000; SYSCTL_INT(_hw_usb_g_keyboard, OID_AUTO, key_press_interval, CTLFLAG_RWTUN, &g_keyboard_key_press_interval, 0, "Key Press Interval in milliseconds"); static char g_keyboard_key_press_pattern[G_KEYBOARD_MAX_STRLEN]; SYSCTL_STRING(_hw_usb_g_keyboard, OID_AUTO, key_press_pattern, CTLFLAG_RW, g_keyboard_key_press_pattern, sizeof(g_keyboard_key_press_pattern), "Key Press Patterns"); #define UPROTO_BOOT_KEYBOARD 1 #define G_KEYBOARD_NMOD 8 /* units */ #define G_KEYBOARD_NKEYCODE 6 /* units */ struct g_keyboard_data { uint8_t modifiers; #define MOD_CONTROL_L 0x01 #define MOD_CONTROL_R 0x10 #define MOD_SHIFT_L 0x02 #define MOD_SHIFT_R 0x20 #define MOD_ALT_L 0x04 #define MOD_ALT_R 0x40 #define MOD_WIN_L 0x08 #define MOD_WIN_R 0x80 uint8_t reserved; uint8_t keycode[G_KEYBOARD_NKEYCODE]; }; enum { G_KEYBOARD_INTR_DT, G_KEYBOARD_N_TRANSFER, }; struct g_keyboard_softc { struct mtx sc_mtx; struct usb_callout sc_callout; struct g_keyboard_data sc_data[2]; struct usb_xfer *sc_xfer[G_KEYBOARD_N_TRANSFER]; int sc_mode; int sc_state; int sc_pattern_len; char sc_pattern[G_KEYBOARD_MAX_STRLEN]; uint8_t sc_led_state[4]; }; static device_probe_t g_keyboard_probe; static device_attach_t g_keyboard_attach; static device_detach_t g_keyboard_detach; static usb_handle_request_t g_keyboard_handle_request; static usb_callback_t g_keyboard_intr_callback; static devclass_t g_keyboard_devclass; static device_method_t g_keyboard_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, g_keyboard_handle_request), /* Device interface */ DEVMETHOD(device_probe, g_keyboard_probe), DEVMETHOD(device_attach, g_keyboard_attach), DEVMETHOD(device_detach, g_keyboard_detach), DEVMETHOD_END }; static driver_t g_keyboard_driver = { .name = "g_keyboard", .methods = g_keyboard_methods, .size = sizeof(struct g_keyboard_softc), }; DRIVER_MODULE(g_keyboard, uhub, g_keyboard_driver, g_keyboard_devclass, 0, 0); MODULE_DEPEND(g_keyboard, usb, 1, 1, 1); static const struct usb_config g_keyboard_config[G_KEYBOARD_N_TRANSFER] = { [G_KEYBOARD_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.ext_buffer = 1,.pipe_bof = 1,}, .bufsize = sizeof(struct g_keyboard_data), .callback = &g_keyboard_intr_callback, .frames = 2, .usb_mode = USB_MODE_DEVICE, }, }; static void g_keyboard_timeout(void *arg); static void g_keyboard_timeout_reset(struct g_keyboard_softc *sc) { int i = g_keyboard_key_press_interval; if (i <= 0) i = 1; else if (i > 1023) i = 1023; i = USB_MS_TO_TICKS(i); usb_callout_reset(&sc->sc_callout, i, &g_keyboard_timeout, sc); } static void g_keyboard_timeout(void *arg) { struct g_keyboard_softc *sc = arg; sc->sc_mode = g_keyboard_mode; memcpy(sc->sc_pattern, g_keyboard_key_press_pattern, sizeof(sc->sc_pattern)); sc->sc_pattern[G_KEYBOARD_MAX_STRLEN - 1] = 0; sc->sc_pattern_len = strlen(sc->sc_pattern); DPRINTFN(11, "Timeout %p\n", sc->sc_xfer[G_KEYBOARD_INTR_DT]); usbd_transfer_start(sc->sc_xfer[G_KEYBOARD_INTR_DT]); g_keyboard_timeout_reset(sc); } static int g_keyboard_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_DEVICE) return (ENXIO); if ((uaa->info.bInterfaceClass == UICLASS_HID) && (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && (uaa->info.bInterfaceProtocol == UPROTO_BOOT_KEYBOARD)) return (0); return (ENXIO); } static int g_keyboard_attach(device_t dev) { struct g_keyboard_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int error; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "g_keyboard", NULL, MTX_DEF); usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); sc->sc_mode = G_KEYBOARD_MODE_SILENT; error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, g_keyboard_config, G_KEYBOARD_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } mtx_lock(&sc->sc_mtx); g_keyboard_timeout_reset(sc); mtx_unlock(&sc->sc_mtx); return (0); /* success */ detach: g_keyboard_detach(dev); return (ENXIO); /* error */ } static int g_keyboard_detach(device_t dev) { struct g_keyboard_softc *sc = device_get_softc(dev); DPRINTF("\n"); mtx_lock(&sc->sc_mtx); usb_callout_stop(&sc->sc_callout); mtx_unlock(&sc->sc_mtx); usbd_transfer_unsetup(sc->sc_xfer, G_KEYBOARD_N_TRANSFER); usb_callout_drain(&sc->sc_callout); mtx_destroy(&sc->sc_mtx); return (0); } static uint8_t g_keyboard_get_keycode(struct g_keyboard_softc *sc, int index) { int key; int mod = sc->sc_pattern_len; if (mod == 0) index = 0; else index %= mod; if ((index >= 0) && (index < sc->sc_pattern_len)) key = sc->sc_pattern[index]; else key = 'a'; if (key >= 'a' && key <= 'z') return (key - 'a' + 0x04); else return (0x04); } static void g_keyboard_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct g_keyboard_softc *sc = usbd_xfer_softc(xfer); int actlen; int aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: break; case USB_ST_SETUP: tr_setup: if (sc->sc_mode == G_KEYBOARD_MODE_SILENT) { memset(&sc->sc_data, 0, sizeof(sc->sc_data)); usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data[0], sizeof(sc->sc_data[0])); usbd_xfer_set_frame_data(xfer, 1, &sc->sc_data[1], sizeof(sc->sc_data[1])); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); } else if (sc->sc_mode == G_KEYBOARD_MODE_PATTERN) { memset(&sc->sc_data, 0, sizeof(sc->sc_data)); if ((sc->sc_state < 0) || (sc->sc_state >= G_KEYBOARD_MAX_STRLEN)) sc->sc_state = 0; switch (sc->sc_state % 6) { case 0: sc->sc_data[0].keycode[0] = g_keyboard_get_keycode(sc, sc->sc_state + 0); case 1: sc->sc_data[0].keycode[1] = g_keyboard_get_keycode(sc, sc->sc_state + 1); case 2: sc->sc_data[0].keycode[2] = g_keyboard_get_keycode(sc, sc->sc_state + 2); case 3: sc->sc_data[0].keycode[3] = g_keyboard_get_keycode(sc, sc->sc_state + 3); case 4: sc->sc_data[0].keycode[4] = g_keyboard_get_keycode(sc, sc->sc_state + 4); default: sc->sc_data[0].keycode[5] = g_keyboard_get_keycode(sc, sc->sc_state + 5); } sc->sc_state++; usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data[0], sizeof(sc->sc_data[0])); usbd_xfer_set_frame_data(xfer, 1, &sc->sc_data[1], sizeof(sc->sc_data[1])); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int g_keyboard_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct g_keyboard_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; if (!is_complete) { if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UR_SET_REPORT) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x02)) { if (offset == 0) { *plen = sizeof(sc->sc_led_state); *pptr = &sc->sc_led_state; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UR_SET_PROTOCOL) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x00)) { *plen = 0; return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UR_SET_IDLE)) { *plen = 0; return (0); } } return (ENXIO); /* use builtin handler */ } Index: head/sys/dev/usb/gadget/g_modem.c =================================================================== --- head/sys/dev/usb/gadget/g_modem.c (revision 357971) +++ head/sys/dev/usb/gadget/g_modem.c (revision 357972) @@ -1,546 +1,547 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define USB_DEBUG_VAR g_modem_debug #include #include enum { G_MODEM_INTR_DT, G_MODEM_BULK_RD, G_MODEM_BULK_WR, G_MODEM_N_TRANSFER, }; struct g_modem_softc { struct mtx sc_mtx; struct usb_callout sc_callout; struct usb_callout sc_watchdog; struct usb_xfer *sc_xfer[G_MODEM_N_TRANSFER]; int sc_mode; int sc_tx_busy; int sc_pattern_len; int sc_throughput; int sc_tx_interval; char sc_pattern[G_MODEM_MAX_STRLEN]; uint16_t sc_data_len; uint8_t sc_data_buf[G_MODEM_BUFSIZE]; uint8_t sc_line_coding[32]; uint8_t sc_abstract_state[32]; }; -static SYSCTL_NODE(_hw_usb, OID_AUTO, g_modem, CTLFLAG_RW, 0, "USB modem gadget"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, g_modem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB modem gadget"); #ifdef USB_DEBUG static int g_modem_debug = 0; SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, debug, CTLFLAG_RWTUN, &g_modem_debug, 0, "Debug level"); #endif static int g_modem_mode = 0; SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, mode, CTLFLAG_RWTUN, &g_modem_mode, 0, "Mode selection"); static int g_modem_pattern_interval = 1000; SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, pattern_interval, CTLFLAG_RWTUN, &g_modem_pattern_interval, 0, "Pattern interval in milliseconds"); static char g_modem_pattern_data[G_MODEM_MAX_STRLEN]; SYSCTL_STRING(_hw_usb_g_modem, OID_AUTO, pattern, CTLFLAG_RW, &g_modem_pattern_data, sizeof(g_modem_pattern_data), "Data pattern"); static int g_modem_throughput; SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, throughput, CTLFLAG_RD, &g_modem_throughput, sizeof(g_modem_throughput), "Throughput in bytes per second"); static device_probe_t g_modem_probe; static device_attach_t g_modem_attach; static device_detach_t g_modem_detach; static usb_handle_request_t g_modem_handle_request; static usb_callback_t g_modem_intr_callback; static usb_callback_t g_modem_bulk_read_callback; static usb_callback_t g_modem_bulk_write_callback; static void g_modem_timeout(void *arg); static devclass_t g_modem_devclass; static device_method_t g_modem_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, g_modem_handle_request), /* Device interface */ DEVMETHOD(device_probe, g_modem_probe), DEVMETHOD(device_attach, g_modem_attach), DEVMETHOD(device_detach, g_modem_detach), DEVMETHOD_END }; static driver_t g_modem_driver = { .name = "g_modem", .methods = g_modem_methods, .size = sizeof(struct g_modem_softc), }; DRIVER_MODULE(g_modem, uhub, g_modem_driver, g_modem_devclass, 0, 0); MODULE_DEPEND(g_modem, usb, 1, 1, 1); static const struct usb_config g_modem_config[G_MODEM_N_TRANSFER] = { [G_MODEM_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .flags = {.ext_buffer = 1,.pipe_bof = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &g_modem_intr_callback, .frames = 1, .usb_mode = USB_MODE_DEVICE, .if_index = 0, }, [G_MODEM_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .flags = {.ext_buffer = 1,.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = G_MODEM_BUFSIZE, .callback = &g_modem_bulk_read_callback, .frames = 1, .usb_mode = USB_MODE_DEVICE, .if_index = 1, }, [G_MODEM_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .flags = {.ext_buffer = 1,.pipe_bof = 1,}, .bufsize = G_MODEM_BUFSIZE, .callback = &g_modem_bulk_write_callback, .frames = 1, .usb_mode = USB_MODE_DEVICE, .if_index = 1, }, }; static void g_modem_timeout_reset(struct g_modem_softc *sc) { int i = g_modem_pattern_interval; sc->sc_tx_interval = i; if (i <= 0) i = 1; else if (i > 1023) i = 1023; i = USB_MS_TO_TICKS(i); usb_callout_reset(&sc->sc_callout, i, &g_modem_timeout, sc); } static void g_modem_timeout(void *arg) { struct g_modem_softc *sc = arg; sc->sc_mode = g_modem_mode; memcpy(sc->sc_pattern, g_modem_pattern_data, sizeof(sc->sc_pattern)); sc->sc_pattern[G_MODEM_MAX_STRLEN - 1] = 0; sc->sc_pattern_len = strlen(sc->sc_pattern); DPRINTFN(11, "Timeout %p\n", sc->sc_xfer[G_MODEM_INTR_DT]); usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_WR]); usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_RD]); g_modem_timeout_reset(sc); } static void g_modem_watchdog(void *arg); static void g_modem_watchdog_reset(struct g_modem_softc *sc) { usb_callout_reset(&sc->sc_watchdog, hz, &g_modem_watchdog, sc); } static void g_modem_watchdog(void *arg) { struct g_modem_softc *sc = arg; int i; i = sc->sc_throughput; sc->sc_throughput = 0; g_modem_throughput = i; g_modem_watchdog_reset(sc); } static int g_modem_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_DEVICE) return (ENXIO); if ((uaa->info.bInterfaceClass == UICLASS_CDC) && (uaa->info.bInterfaceSubClass == UISUBCLASS_ABSTRACT_CONTROL_MODEL) && (uaa->info.bInterfaceProtocol == UIPROTO_CDC_AT)) return (0); return (ENXIO); } static int g_modem_attach(device_t dev) { struct g_modem_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int error; uint8_t iface_index[2]; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "g_modem", NULL, MTX_DEF); usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); sc->sc_mode = G_MODEM_MODE_SILENT; iface_index[0] = uaa->info.bIfaceIndex; iface_index[1] = uaa->info.bIfaceIndex + 1; error = usbd_transfer_setup(uaa->device, iface_index, sc->sc_xfer, g_modem_config, G_MODEM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } usbd_set_parent_iface(uaa->device, iface_index[1], iface_index[0]); mtx_lock(&sc->sc_mtx); g_modem_timeout_reset(sc); g_modem_watchdog_reset(sc); mtx_unlock(&sc->sc_mtx); return (0); /* success */ detach: g_modem_detach(dev); return (ENXIO); /* error */ } static int g_modem_detach(device_t dev) { struct g_modem_softc *sc = device_get_softc(dev); DPRINTF("\n"); mtx_lock(&sc->sc_mtx); usb_callout_stop(&sc->sc_callout); usb_callout_stop(&sc->sc_watchdog); mtx_unlock(&sc->sc_mtx); usbd_transfer_unsetup(sc->sc_xfer, G_MODEM_N_TRANSFER); usb_callout_drain(&sc->sc_callout); usb_callout_drain(&sc->sc_watchdog); mtx_destroy(&sc->sc_mtx); return (0); } static void g_modem_intr_callback(struct usb_xfer *xfer, usb_error_t error) { int actlen; int aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: break; case USB_ST_SETUP: tr_setup: break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void g_modem_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct g_modem_softc *sc = usbd_xfer_softc(xfer); int actlen; int aframes; int mod; int x; int max; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_tx_busy = 0; sc->sc_throughput += actlen; if (sc->sc_mode == G_MODEM_MODE_LOOP) { /* start loop */ usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_RD]); break; } else if ((sc->sc_mode == G_MODEM_MODE_PATTERN) && (sc->sc_tx_interval != 0)) { /* wait for next timeout */ break; } case USB_ST_SETUP: tr_setup: if (sc->sc_mode == G_MODEM_MODE_PATTERN) { mod = sc->sc_pattern_len; max = sc->sc_tx_interval ? mod : G_MODEM_BUFSIZE; if (mod == 0) { for (x = 0; x != max; x++) sc->sc_data_buf[x] = x % 255; } else { for (x = 0; x != max; x++) sc->sc_data_buf[x] = sc->sc_pattern[x % mod]; } usbd_xfer_set_frame_data(xfer, 0, sc->sc_data_buf, max); usbd_xfer_set_interval(xfer, 0); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); } else if (sc->sc_mode == G_MODEM_MODE_LOOP) { if (sc->sc_tx_busy == 0) break; x = sc->sc_tx_interval; if (x < 0) x = 0; else if (x > 256) x = 256; usbd_xfer_set_frame_data(xfer, 0, sc->sc_data_buf, sc->sc_data_len); usbd_xfer_set_interval(xfer, x); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); } else { sc->sc_tx_busy = 0; } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void g_modem_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct g_modem_softc *sc = usbd_xfer_softc(xfer); int actlen; int aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_throughput += actlen; if (sc->sc_mode == G_MODEM_MODE_LOOP) { sc->sc_tx_busy = 1; sc->sc_data_len = actlen; usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_WR]); break; } case USB_ST_SETUP: tr_setup: if ((sc->sc_mode == G_MODEM_MODE_SILENT) || (sc->sc_tx_busy != 0)) break; usbd_xfer_set_frame_data(xfer, 0, sc->sc_data_buf, G_MODEM_BUFSIZE); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int g_modem_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct g_modem_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; if (!is_complete) { if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UCDC_SET_LINE_CODING) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x00)) { if (offset == 0) { *plen = sizeof(sc->sc_line_coding); *pptr = &sc->sc_line_coding; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UCDC_SET_COMM_FEATURE)) { if (offset == 0) { *plen = sizeof(sc->sc_abstract_state); *pptr = &sc->sc_abstract_state; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) { *plen = 0; return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UCDC_SEND_BREAK)) { *plen = 0; return (0); } } return (ENXIO); /* use builtin handler */ } Index: head/sys/dev/usb/gadget/g_mouse.c =================================================================== --- head/sys/dev/usb/gadget/g_mouse.c (revision 357971) +++ head/sys/dev/usb/gadget/g_mouse.c (revision 357972) @@ -1,458 +1,459 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define USB_DEBUG_VAR g_mouse_debug #include #include -static SYSCTL_NODE(_hw_usb, OID_AUTO, g_mouse, CTLFLAG_RW, 0, "USB mouse gadget"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, g_mouse, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB mouse gadget"); #ifdef USB_DEBUG static int g_mouse_debug = 0; SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, debug, CTLFLAG_RWTUN, &g_mouse_debug, 0, "Debug level"); #endif static int g_mouse_mode = 0; SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, mode, CTLFLAG_RWTUN, &g_mouse_mode, 0, "Mode selection"); static int g_mouse_button_press_interval = 0; SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, button_press_interval, CTLFLAG_RWTUN, &g_mouse_button_press_interval, 0, "Mouse button update interval in milliseconds"); static int g_mouse_cursor_update_interval = 1023; SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, cursor_update_interval, CTLFLAG_RWTUN, &g_mouse_cursor_update_interval, 0, "Mouse cursor update interval in milliseconds"); static int g_mouse_cursor_radius = 128; SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, cursor_radius, CTLFLAG_RWTUN, &g_mouse_cursor_radius, 0, "Mouse cursor radius in pixels"); struct g_mouse_data { uint8_t buttons; #define BUT_0 0x01 #define BUT_1 0x02 #define BUT_2 0x04 int8_t dx; int8_t dy; int8_t dz; }; enum { G_MOUSE_INTR_DT, G_MOUSE_N_TRANSFER, }; struct g_mouse_softc { struct mtx sc_mtx; struct usb_callout sc_button_press_callout; struct usb_callout sc_cursor_update_callout; struct g_mouse_data sc_data; struct usb_xfer *sc_xfer[G_MOUSE_N_TRANSFER]; int sc_mode; int sc_radius; int sc_last_x_state; int sc_last_y_state; int sc_curr_x_state; int sc_curr_y_state; int sc_tick; uint8_t sc_do_cursor_update; uint8_t sc_do_button_update; }; static device_probe_t g_mouse_probe; static device_attach_t g_mouse_attach; static device_detach_t g_mouse_detach; static usb_handle_request_t g_mouse_handle_request; static usb_callback_t g_mouse_intr_callback; static devclass_t g_mouse_devclass; static device_method_t g_mouse_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, g_mouse_handle_request), /* Device interface */ DEVMETHOD(device_probe, g_mouse_probe), DEVMETHOD(device_attach, g_mouse_attach), DEVMETHOD(device_detach, g_mouse_detach), DEVMETHOD_END }; static driver_t g_mouse_driver = { .name = "g_mouse", .methods = g_mouse_methods, .size = sizeof(struct g_mouse_softc), }; DRIVER_MODULE(g_mouse, uhub, g_mouse_driver, g_mouse_devclass, 0, 0); MODULE_DEPEND(g_mouse, usb, 1, 1, 1); static const struct usb_config g_mouse_config[G_MOUSE_N_TRANSFER] = { [G_MOUSE_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.ext_buffer = 1,.pipe_bof = 1,}, .bufsize = sizeof(struct g_mouse_data), .callback = &g_mouse_intr_callback, .frames = 1, .usb_mode = USB_MODE_DEVICE, }, }; static void g_mouse_button_press_timeout(void *arg); static void g_mouse_cursor_update_timeout(void *arg); static void g_mouse_button_press_timeout_reset(struct g_mouse_softc *sc) { int i = g_mouse_button_press_interval; if (i <= 0) { sc->sc_data.buttons = 0; sc->sc_do_button_update = 0; } else { sc->sc_do_button_update = 1; } if ((i <= 0) || (i > 1023)) i = 1023; i = USB_MS_TO_TICKS(i); usb_callout_reset(&sc->sc_button_press_callout, i, &g_mouse_button_press_timeout, sc); } static void g_mouse_cursor_update_timeout_reset(struct g_mouse_softc *sc) { int i = g_mouse_cursor_update_interval; if (i <= 0) { sc->sc_data.dx = 0; sc->sc_data.dy = 0; sc->sc_do_cursor_update = 0; sc->sc_tick = 0; } else { sc->sc_do_cursor_update = 1; } if ((i <= 0) || (i > 1023)) i = 1023; i = USB_MS_TO_TICKS(i); usb_callout_reset(&sc->sc_cursor_update_callout, i, &g_mouse_cursor_update_timeout, sc); } static void g_mouse_update_mode_radius(struct g_mouse_softc *sc) { sc->sc_mode = g_mouse_mode; sc->sc_radius = g_mouse_cursor_radius; if (sc->sc_radius < 0) sc->sc_radius = 0; else if (sc->sc_radius > 1023) sc->sc_radius = 1023; } static void g_mouse_button_press_timeout(void *arg) { struct g_mouse_softc *sc = arg; g_mouse_update_mode_radius(sc); DPRINTFN(11, "Timeout %p (button press)\n", sc->sc_xfer[G_MOUSE_INTR_DT]); g_mouse_button_press_timeout_reset(sc); usbd_transfer_start(sc->sc_xfer[G_MOUSE_INTR_DT]); } static void g_mouse_cursor_update_timeout(void *arg) { struct g_mouse_softc *sc = arg; g_mouse_update_mode_radius(sc); DPRINTFN(11, "Timeout %p (cursor update)\n", sc->sc_xfer[G_MOUSE_INTR_DT]); g_mouse_cursor_update_timeout_reset(sc); usbd_transfer_start(sc->sc_xfer[G_MOUSE_INTR_DT]); } static int g_mouse_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_DEVICE) return (ENXIO); if ((uaa->info.bInterfaceClass == UICLASS_HID) && (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE)) return (0); return (ENXIO); } static int g_mouse_attach(device_t dev) { struct g_mouse_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int error; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "g_mouse", NULL, MTX_DEF); usb_callout_init_mtx(&sc->sc_button_press_callout, &sc->sc_mtx, 0); usb_callout_init_mtx(&sc->sc_cursor_update_callout, &sc->sc_mtx, 0); sc->sc_mode = G_MOUSE_MODE_SILENT; error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, g_mouse_config, G_MOUSE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } mtx_lock(&sc->sc_mtx); g_mouse_button_press_timeout_reset(sc); g_mouse_cursor_update_timeout_reset(sc); mtx_unlock(&sc->sc_mtx); return (0); /* success */ detach: g_mouse_detach(dev); return (ENXIO); /* error */ } static int g_mouse_detach(device_t dev) { struct g_mouse_softc *sc = device_get_softc(dev); DPRINTF("\n"); mtx_lock(&sc->sc_mtx); usb_callout_stop(&sc->sc_button_press_callout); usb_callout_stop(&sc->sc_cursor_update_callout); mtx_unlock(&sc->sc_mtx); usbd_transfer_unsetup(sc->sc_xfer, G_MOUSE_N_TRANSFER); usb_callout_drain(&sc->sc_button_press_callout); usb_callout_drain(&sc->sc_cursor_update_callout); mtx_destroy(&sc->sc_mtx); return (0); } static void g_mouse_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct g_mouse_softc *sc = usbd_xfer_softc(xfer); int actlen; int aframes; int dx; int dy; int radius; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTF("st=%d aframes=%d actlen=%d bytes\n", USB_GET_STATE(xfer), aframes, actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (!(sc->sc_do_cursor_update || sc->sc_do_button_update)) break; case USB_ST_SETUP: tr_setup: if (sc->sc_do_cursor_update) { sc->sc_do_cursor_update = 0; sc->sc_tick += 80; if ((sc->sc_tick < 0) || (sc->sc_tick > 7999)) sc->sc_tick = 0; } if (sc->sc_do_button_update) { sc->sc_do_button_update = 0; sc->sc_data.buttons ^= BUT_0; } radius = sc->sc_radius; switch (sc->sc_mode) { case G_MOUSE_MODE_SILENT: sc->sc_data.buttons = 0; break; case G_MOUSE_MODE_SPIRAL: radius = (radius * (8000-sc->sc_tick)) / 8000; case G_MOUSE_MODE_CIRCLE: /* TODO */ sc->sc_curr_y_state = 0; sc->sc_curr_x_state = 0; break; case G_MOUSE_MODE_BOX: if (sc->sc_tick < 2000) { sc->sc_curr_x_state = (sc->sc_tick * radius) / 2000; sc->sc_curr_y_state = 0; } else if (sc->sc_tick < 4000) { sc->sc_curr_x_state = radius; sc->sc_curr_y_state = -(((sc->sc_tick - 2000) * radius) / 2000); } else if (sc->sc_tick < 6000) { sc->sc_curr_x_state = radius - (((sc->sc_tick - 4000) * radius) / 2000); sc->sc_curr_y_state = -radius; } else { sc->sc_curr_x_state = 0; sc->sc_curr_y_state = -radius + (((sc->sc_tick - 6000) * radius) / 2000); } break; default: break; } dx = sc->sc_curr_x_state - sc->sc_last_x_state; dy = sc->sc_curr_y_state - sc->sc_last_y_state; if (dx < -63) dx = -63; else if (dx > 63) dx = 63; if (dy < -63) dy = -63; else if (dy > 63) dy = 63; sc->sc_last_x_state += dx; sc->sc_last_y_state += dy; sc->sc_data.dx = dx; sc->sc_data.dy = dy; usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data, sizeof(sc->sc_data)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int g_mouse_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; if (!is_complete) { if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UR_SET_PROTOCOL) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x00)) { *plen = 0; return (0); } } return (ENXIO); /* use builtin handler */ } Index: head/sys/dev/usb/input/atp.c =================================================================== --- head/sys/dev/usb/input/atp.c (revision 357971) +++ head/sys/dev/usb/input/atp.c (revision 357972) @@ -1,2636 +1,2639 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 Rohit Grover * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Some tables, structures, definitions and constant values for the * touchpad protocol has been copied from Linux's * "drivers/input/mouse/bcm5974.c" which has the following copyright * holders under GPLv2. All device specific code in this driver has * been written from scratch. The decoding algorithm is based on * output from FreeBSD's usbdump. * * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com) * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) * Copyright (C) 2005 Stelian Pop (stelian@popies.net) * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) */ /* * Author's note: 'atp' supports two distinct families of Apple trackpad * products: the older Fountain/Geyser and the latest Wellspring trackpads. * The first version made its appearance with FreeBSD 8 and worked only with * the Fountain/Geyser hardware. A fork of this driver for Wellspring was * contributed by Huang Wen Hui. This driver unifies the Wellspring effort * and also improves upon the original work. * * I'm grateful to Stephan Scheunig, Angela Naegele, and Nokia IT-support * for helping me with access to hardware. Thanks also go to Nokia for * giving me an opportunity to do this work. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR atp_debug #include #include #define ATP_DRIVER_NAME "atp" /* * Driver specific options: the following options may be set by * `options' statements in the kernel configuration file. */ /* The divisor used to translate sensor reported positions to mickeys. */ #ifndef ATP_SCALE_FACTOR #define ATP_SCALE_FACTOR 16 #endif /* Threshold for small movement noise (in mickeys) */ #ifndef ATP_SMALL_MOVEMENT_THRESHOLD #define ATP_SMALL_MOVEMENT_THRESHOLD 30 #endif /* Threshold of instantaneous deltas beyond which movement is considered fast.*/ #ifndef ATP_FAST_MOVEMENT_TRESHOLD #define ATP_FAST_MOVEMENT_TRESHOLD 150 #endif /* * This is the age in microseconds beyond which a touch is considered * to be a slide; and therefore a tap event isn't registered. */ #ifndef ATP_TOUCH_TIMEOUT #define ATP_TOUCH_TIMEOUT 125000 #endif #ifndef ATP_IDLENESS_THRESHOLD #define ATP_IDLENESS_THRESHOLD 10 #endif #ifndef FG_SENSOR_NOISE_THRESHOLD #define FG_SENSOR_NOISE_THRESHOLD 2 #endif /* * A double-tap followed by a single-finger slide is treated as a * special gesture. The driver responds to this gesture by assuming a * virtual button-press for the lifetime of the slide. The following * threshold is the maximum time gap (in microseconds) between the two * tap events preceding the slide for such a gesture. */ #ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD #define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000 #endif /* * The wait duration in ticks after losing a touch contact before * zombied strokes are reaped and turned into button events. */ #define ATP_ZOMBIE_STROKE_REAP_INTERVAL (hz / 20) /* 50 ms */ /* The multiplier used to translate sensor reported positions to mickeys. */ #define FG_SCALE_FACTOR 380 /* * The movement threshold for a stroke; this is the maximum difference * in position which will be resolved as a continuation of a stroke * component. */ #define FG_MAX_DELTA_MICKEYS ((3 * (FG_SCALE_FACTOR)) >> 1) /* Distance-squared threshold for matching a finger with a known stroke */ #ifndef WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ #define WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ 1000000 #endif /* Ignore pressure spans with cumulative press. below this value. */ #define FG_PSPAN_MIN_CUM_PRESSURE 10 /* Maximum allowed width for pressure-spans.*/ #define FG_PSPAN_MAX_WIDTH 4 /* end of driver specific options */ /* Tunables */ -static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB ATP"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ATP"); #ifdef USB_DEBUG enum atp_log_level { ATP_LLEVEL_DISABLED = 0, ATP_LLEVEL_ERROR, ATP_LLEVEL_DEBUG, /* for troubleshooting */ ATP_LLEVEL_INFO, /* for diagnostics */ }; static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */ SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RWTUN, &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level"); #endif /* USB_DEBUG */ static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RWTUN, &atp_touch_timeout, 125000, "age threshold in microseconds for a touch"); static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RWTUN, &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD, "maximum time in microseconds to allow association between a double-tap and " "drag gesture"); static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR; static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS); -SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RWTUN, +SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor), - atp_sysctl_scale_factor_handler, "IU", "movement scale factor"); + atp_sysctl_scale_factor_handler, "IU", + "movement scale factor"); static u_int atp_small_movement_threshold = ATP_SMALL_MOVEMENT_THRESHOLD; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RWTUN, &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD, "the small movement black-hole for filtering noise"); static u_int atp_tap_minimum = 1; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RWTUN, &atp_tap_minimum, 1, "Minimum number of taps before detection"); /* * Strokes which accumulate at least this amount of absolute movement * from the aggregate of their components are considered as * slides. Unit: mickeys. */ static u_int atp_slide_min_movement = 2 * ATP_SMALL_MOVEMENT_THRESHOLD; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RWTUN, &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD, "strokes with at least this amt. of movement are considered slides"); /* * The minimum age of a stroke for it to be considered mature; this * helps filter movements (noise) from immature strokes. Units: interrupts. */ static u_int atp_stroke_maturity_threshold = 4; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RWTUN, &atp_stroke_maturity_threshold, 4, "the minimum age of a stroke for it to be considered mature"); typedef enum atp_trackpad_family { TRACKPAD_FAMILY_FOUNTAIN_GEYSER, TRACKPAD_FAMILY_WELLSPRING, TRACKPAD_FAMILY_MAX /* keep this at the tail end of the enumeration */ } trackpad_family_t; enum fountain_geyser_product { FOUNTAIN, GEYSER1, GEYSER1_17inch, GEYSER2, GEYSER3, GEYSER4, FOUNTAIN_GEYSER_PRODUCT_MAX /* keep this at the end */ }; enum wellspring_product { WELLSPRING1, WELLSPRING2, WELLSPRING3, WELLSPRING4, WELLSPRING4A, WELLSPRING5, WELLSPRING6A, WELLSPRING6, WELLSPRING5A, WELLSPRING7, WELLSPRING7A, WELLSPRING8, WELLSPRING_PRODUCT_MAX /* keep this at the end of the enumeration */ }; /* trackpad header types */ enum fountain_geyser_trackpad_type { FG_TRACKPAD_TYPE_GEYSER1, FG_TRACKPAD_TYPE_GEYSER2, FG_TRACKPAD_TYPE_GEYSER3, FG_TRACKPAD_TYPE_GEYSER4, }; enum wellspring_trackpad_type { WSP_TRACKPAD_TYPE1, /* plain trackpad */ WSP_TRACKPAD_TYPE2, /* button integrated in trackpad */ WSP_TRACKPAD_TYPE3 /* additional header fields since June 2013 */ }; /* * Trackpad family and product and family are encoded together in the * driver_info value associated with a trackpad product. */ #define N_PROD_BITS 8 /* Number of bits used to encode product */ #define ENCODE_DRIVER_INFO(FAMILY, PROD) \ (((FAMILY) << N_PROD_BITS) | (PROD)) #define DECODE_FAMILY_FROM_DRIVER_INFO(INFO) ((INFO) >> N_PROD_BITS) #define DECODE_PRODUCT_FROM_DRIVER_INFO(INFO) \ ((INFO) & ((1 << N_PROD_BITS) - 1)) #define FG_DRIVER_INFO(PRODUCT) \ ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_FOUNTAIN_GEYSER, PRODUCT) #define WELLSPRING_DRIVER_INFO(PRODUCT) \ ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_WELLSPRING, PRODUCT) /* * The following structure captures the state of a pressure span along * an axis. Each contact with the touchpad results in separate * pressure spans along the two axes. */ typedef struct fg_pspan { u_int width; /* in units of sensors */ u_int cum; /* cumulative compression (from all sensors) */ u_int cog; /* center of gravity */ u_int loc; /* location (scaled using the mickeys factor) */ boolean_t matched; /* to track pspans as they match against strokes. */ } fg_pspan; #define FG_MAX_PSPANS_PER_AXIS 3 #define FG_MAX_STROKES (2 * FG_MAX_PSPANS_PER_AXIS) #define WELLSPRING_INTERFACE_INDEX 1 /* trackpad finger data offsets, le16-aligned */ #define WSP_TYPE1_FINGER_DATA_OFFSET (13 * 2) #define WSP_TYPE2_FINGER_DATA_OFFSET (15 * 2) #define WSP_TYPE3_FINGER_DATA_OFFSET (19 * 2) /* trackpad button data offsets */ #define WSP_TYPE2_BUTTON_DATA_OFFSET 15 #define WSP_TYPE3_BUTTON_DATA_OFFSET 23 /* list of device capability bits */ #define HAS_INTEGRATED_BUTTON 1 /* trackpad finger structure - little endian */ struct wsp_finger_sensor_data { int16_t origin; /* zero when switching track finger */ int16_t abs_x; /* absolute x coordinate */ int16_t abs_y; /* absolute y coordinate */ int16_t rel_x; /* relative x coordinate */ int16_t rel_y; /* relative y coordinate */ int16_t tool_major; /* tool area, major axis */ int16_t tool_minor; /* tool area, minor axis */ int16_t orientation; /* 16384 when point, else 15 bit angle */ int16_t touch_major; /* touch area, major axis */ int16_t touch_minor; /* touch area, minor axis */ int16_t unused[3]; /* zeros */ int16_t multi; /* one finger: varies, more fingers: constant */ } __packed; typedef struct wsp_finger { /* to track fingers as they match against strokes. */ boolean_t matched; /* location (scaled using the mickeys factor) */ int x; int y; } wsp_finger_t; #define WSP_MAX_FINGERS 16 #define WSP_SIZEOF_FINGER_SENSOR_DATA sizeof(struct wsp_finger_sensor_data) #define WSP_SIZEOF_ALL_FINGER_DATA (WSP_MAX_FINGERS * \ WSP_SIZEOF_FINGER_SENSOR_DATA) #define WSP_MAX_FINGER_ORIENTATION 16384 #define ATP_SENSOR_DATA_BUF_MAX 1024 #if (ATP_SENSOR_DATA_BUF_MAX < ((WSP_MAX_FINGERS * 14 * 2) + \ WSP_TYPE3_FINGER_DATA_OFFSET)) /* note: 14 * 2 in the above is based on sizeof(struct wsp_finger_sensor_data)*/ #error "ATP_SENSOR_DATA_BUF_MAX is too small" #endif #define ATP_MAX_STROKES MAX(WSP_MAX_FINGERS, FG_MAX_STROKES) #define FG_MAX_XSENSORS 26 #define FG_MAX_YSENSORS 16 /* device-specific configuration */ struct fg_dev_params { u_int data_len; /* for sensor data */ u_int n_xsensors; u_int n_ysensors; enum fountain_geyser_trackpad_type prot; }; struct wsp_dev_params { uint8_t caps; /* device capability bitmask */ uint8_t tp_type; /* type of trackpad interface */ uint8_t finger_data_offset; /* offset to trackpad finger data */ }; static const struct fg_dev_params fg_dev_params[FOUNTAIN_GEYSER_PRODUCT_MAX] = { [FOUNTAIN] = { .data_len = 81, .n_xsensors = 16, .n_ysensors = 16, .prot = FG_TRACKPAD_TYPE_GEYSER1 }, [GEYSER1] = { .data_len = 81, .n_xsensors = 16, .n_ysensors = 16, .prot = FG_TRACKPAD_TYPE_GEYSER1 }, [GEYSER1_17inch] = { .data_len = 81, .n_xsensors = 26, .n_ysensors = 16, .prot = FG_TRACKPAD_TYPE_GEYSER1 }, [GEYSER2] = { .data_len = 64, .n_xsensors = 15, .n_ysensors = 9, .prot = FG_TRACKPAD_TYPE_GEYSER2 }, [GEYSER3] = { .data_len = 64, .n_xsensors = 20, .n_ysensors = 10, .prot = FG_TRACKPAD_TYPE_GEYSER3 }, [GEYSER4] = { .data_len = 64, .n_xsensors = 20, .n_ysensors = 10, .prot = FG_TRACKPAD_TYPE_GEYSER4 } }; static const STRUCT_USB_HOST_ID fg_devs[] = { /* PowerBooks Feb 2005, iBooks G4 */ { USB_VPI(USB_VENDOR_APPLE, 0x020e, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x020f, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0210, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x030a, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x030b, FG_DRIVER_INFO(GEYSER1)) }, /* PowerBooks Oct 2005 */ { USB_VPI(USB_VENDOR_APPLE, 0x0214, FG_DRIVER_INFO(GEYSER2)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0215, FG_DRIVER_INFO(GEYSER2)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0216, FG_DRIVER_INFO(GEYSER2)) }, /* Core Duo MacBook & MacBook Pro */ { USB_VPI(USB_VENDOR_APPLE, 0x0217, FG_DRIVER_INFO(GEYSER3)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0218, FG_DRIVER_INFO(GEYSER3)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0219, FG_DRIVER_INFO(GEYSER3)) }, /* Core2 Duo MacBook & MacBook Pro */ { USB_VPI(USB_VENDOR_APPLE, 0x021a, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x021b, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x021c, FG_DRIVER_INFO(GEYSER4)) }, /* Core2 Duo MacBook3,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x0229, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x022a, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x022b, FG_DRIVER_INFO(GEYSER4)) }, /* 17 inch PowerBook */ { USB_VPI(USB_VENDOR_APPLE, 0x020d, FG_DRIVER_INFO(GEYSER1_17inch)) }, }; static const struct wsp_dev_params wsp_dev_params[WELLSPRING_PRODUCT_MAX] = { [WELLSPRING1] = { .caps = 0, .tp_type = WSP_TRACKPAD_TYPE1, .finger_data_offset = WSP_TYPE1_FINGER_DATA_OFFSET, }, [WELLSPRING2] = { .caps = 0, .tp_type = WSP_TRACKPAD_TYPE1, .finger_data_offset = WSP_TYPE1_FINGER_DATA_OFFSET, }, [WELLSPRING3] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING4] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING4A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING5] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING6] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING5A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING6A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING7] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING7A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING8] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE3, .finger_data_offset = WSP_TYPE3_FINGER_DATA_OFFSET, }, }; #define ATP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } /* TODO: STRUCT_USB_HOST_ID */ static const struct usb_device_id wsp_devs[] = { /* MacbookAir1.1 */ ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)), ATP_DEV(APPLE, WELLSPRING_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING1)), ATP_DEV(APPLE, WELLSPRING_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING1)), /* MacbookProPenryn, aka wellspring2 */ ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)), ATP_DEV(APPLE, WELLSPRING2_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING2)), ATP_DEV(APPLE, WELLSPRING2_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING2)), /* Macbook5,1 (unibody), aka wellspring3 */ ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)), ATP_DEV(APPLE, WELLSPRING3_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING3)), ATP_DEV(APPLE, WELLSPRING3_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING3)), /* MacbookAir3,2 (unibody), aka wellspring4 */ ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)), ATP_DEV(APPLE, WELLSPRING4_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING4)), ATP_DEV(APPLE, WELLSPRING4_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING4)), /* MacbookAir3,1 (unibody), aka wellspring4 */ ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)), ATP_DEV(APPLE, WELLSPRING4A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING4A)), ATP_DEV(APPLE, WELLSPRING4A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING4A)), /* Macbook8 (unibody, March 2011) */ ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)), ATP_DEV(APPLE, WELLSPRING5_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING5)), ATP_DEV(APPLE, WELLSPRING5_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING5)), /* MacbookAir4,1 (unibody, July 2011) */ ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)), ATP_DEV(APPLE, WELLSPRING6A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING6A)), ATP_DEV(APPLE, WELLSPRING6A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING6A)), /* MacbookAir4,2 (unibody, July 2011) */ ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)), ATP_DEV(APPLE, WELLSPRING6_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING6)), ATP_DEV(APPLE, WELLSPRING6_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING6)), /* Macbook8,2 (unibody) */ ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)), ATP_DEV(APPLE, WELLSPRING5A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING5A)), ATP_DEV(APPLE, WELLSPRING5A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING5A)), /* MacbookPro10,1 (unibody, June 2012) */ /* MacbookPro11,? (unibody, June 2013) */ ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)), ATP_DEV(APPLE, WELLSPRING7_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING7)), ATP_DEV(APPLE, WELLSPRING7_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING7)), /* MacbookPro10,2 (unibody, October 2012) */ ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)), ATP_DEV(APPLE, WELLSPRING7A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING7A)), ATP_DEV(APPLE, WELLSPRING7A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING7A)), /* MacbookAir6,2 (unibody, June 2013) */ ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)), ATP_DEV(APPLE, WELLSPRING8_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING8)), ATP_DEV(APPLE, WELLSPRING8_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING8)), }; typedef enum atp_stroke_type { ATP_STROKE_TOUCH, ATP_STROKE_SLIDE, } atp_stroke_type; typedef enum atp_axis { X = 0, Y = 1, NUM_AXES } atp_axis; #define ATP_FIFO_BUF_SIZE 8 /* bytes */ #define ATP_FIFO_QUEUE_MAXLEN 50 /* units */ enum { ATP_INTR_DT, ATP_RESET, ATP_N_TRANSFER, }; typedef struct fg_stroke_component { /* Fields encapsulating the pressure-span. */ u_int loc; /* location (scaled) */ u_int cum_pressure; /* cumulative compression */ u_int max_cum_pressure; /* max cumulative compression */ boolean_t matched; /*to track components as they match against pspans.*/ int delta_mickeys; /* change in location (un-smoothened movement)*/ } fg_stroke_component_t; /* * The following structure captures a finger contact with the * touchpad. A stroke comprises two p-span components and some state. */ typedef struct atp_stroke { TAILQ_ENTRY(atp_stroke) entry; atp_stroke_type type; uint32_t flags; /* the state of this stroke */ #define ATSF_ZOMBIE 0x1 boolean_t matched; /* to track match against fingers.*/ struct timeval ctime; /* create time; for coincident siblings. */ /* * Unit: interrupts; we maintain this value in * addition to 'ctime' in order to avoid the * expensive call to microtime() at every * interrupt. */ uint32_t age; /* Location */ int x; int y; /* Fields containing information about movement. */ int instantaneous_dx; /* curr. change in X location (un-smoothened) */ int instantaneous_dy; /* curr. change in Y location (un-smoothened) */ int pending_dx; /* cum. of pending short movements */ int pending_dy; /* cum. of pending short movements */ int movement_dx; /* interpreted smoothened movement */ int movement_dy; /* interpreted smoothened movement */ int cum_movement_x; /* cum. horizontal movement */ int cum_movement_y; /* cum. vertical movement */ /* * The following member is relevant only for fountain-geyser trackpads. * For these, there is the need to track pressure-spans and cumulative * pressures for stroke components. */ fg_stroke_component_t components[NUM_AXES]; } atp_stroke_t; struct atp_softc; /* forward declaration */ typedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len); struct atp_softc { device_t sc_dev; struct usb_device *sc_usb_device; struct mtx sc_mutex; /* for synchronization */ struct usb_fifo_sc sc_fifo; #define MODE_LENGTH 8 char sc_mode_bytes[MODE_LENGTH]; /* device mode */ trackpad_family_t sc_family; const void *sc_params; /* device configuration */ sensor_data_interpreter_t sensor_data_interpreter; mousehw_t sc_hw; mousemode_t sc_mode; mousestatus_t sc_status; u_int sc_state; #define ATP_ENABLED 0x01 #define ATP_ZOMBIES_EXIST 0x02 #define ATP_DOUBLE_TAP_DRAG 0x04 #define ATP_VALID 0x08 struct usb_xfer *sc_xfer[ATP_N_TRANSFER]; u_int sc_pollrate; int sc_fflags; atp_stroke_t sc_strokes_data[ATP_MAX_STROKES]; TAILQ_HEAD(,atp_stroke) sc_stroke_free; TAILQ_HEAD(,atp_stroke) sc_stroke_used; u_int sc_n_strokes; struct callout sc_callout; /* * button status. Set to non-zero if the mouse-button is physically * pressed. This state variable is exposed through softc to allow * reap_sibling_zombies to avoid registering taps while the trackpad * button is pressed. */ uint8_t sc_ibtn; /* * Time when touch zombies were last reaped; useful for detecting * double-touch-n-drag. */ struct timeval sc_touch_reap_time; u_int sc_idlecount; /* Regarding the data transferred from t-pad in USB INTR packets. */ u_int sc_expected_sensor_data_len; uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4); int sc_cur_x[FG_MAX_XSENSORS]; /* current sensor readings */ int sc_cur_y[FG_MAX_YSENSORS]; int sc_base_x[FG_MAX_XSENSORS]; /* base sensor readings */ int sc_base_y[FG_MAX_YSENSORS]; int sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */ int sc_pressure_y[FG_MAX_YSENSORS]; fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS]; fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS]; }; /* * The last byte of the fountain-geyser sensor data contains status bits; the * following values define the meanings of these bits. * (only Geyser 3/4) */ enum geyser34_status_bits { FG_STATUS_BUTTON = (uint8_t)0x01, /* The button was pressed */ FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/ }; typedef enum interface_mode { RAW_SENSOR_MODE = (uint8_t)0x01, HID_MODE = (uint8_t)0x08 } interface_mode; /* * function prototypes */ static usb_fifo_cmd_t atp_start_read; static usb_fifo_cmd_t atp_stop_read; static usb_fifo_open_t atp_open; static usb_fifo_close_t atp_close; static usb_fifo_ioctl_t atp_ioctl; static struct usb_fifo_methods atp_fifo_methods = { .f_open = &atp_open, .f_close = &atp_close, .f_ioctl = &atp_ioctl, .f_start_read = &atp_start_read, .f_stop_read = &atp_stop_read, .basename[0] = ATP_DRIVER_NAME, }; /* device initialization and shutdown */ static usb_error_t atp_set_device_mode(struct atp_softc *, interface_mode); static void atp_reset_callback(struct usb_xfer *, usb_error_t); static int atp_enable(struct atp_softc *); static void atp_disable(struct atp_softc *); /* sensor interpretation */ static void fg_interpret_sensor_data(struct atp_softc *, u_int); static void fg_extract_sensor_data(const int8_t *, u_int, atp_axis, int *, enum fountain_geyser_trackpad_type); static void fg_get_pressures(int *, const int *, const int *, int); static void fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *); static void wsp_interpret_sensor_data(struct atp_softc *, u_int); /* movement detection */ static boolean_t fg_match_stroke_component(fg_stroke_component_t *, const fg_pspan *, atp_stroke_type); static void fg_match_strokes_against_pspans(struct atp_softc *, atp_axis, fg_pspan *, u_int, u_int); static boolean_t wsp_match_strokes_against_fingers(struct atp_softc *, wsp_finger_t *, u_int); static boolean_t fg_update_strokes(struct atp_softc *, fg_pspan *, u_int, fg_pspan *, u_int); static boolean_t wsp_update_strokes(struct atp_softc *, wsp_finger_t [WSP_MAX_FINGERS], u_int); static void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *); static void fg_add_new_strokes(struct atp_softc *, fg_pspan *, u_int, fg_pspan *, u_int); static void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *); static void atp_advance_stroke_state(struct atp_softc *, atp_stroke_t *, boolean_t *); static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *); static void atp_update_pending_mickeys(atp_stroke_t *); static boolean_t atp_compute_stroke_movement(atp_stroke_t *); static void atp_terminate_stroke(struct atp_softc *, atp_stroke_t *); /* tap detection */ static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *); static boolean_t atp_is_vertical_scroll(const atp_stroke_t *); static void atp_reap_sibling_zombies(void *); static void atp_convert_to_slide(struct atp_softc *, atp_stroke_t *); /* updating fifo */ static void atp_reset_buf(struct atp_softc *); static void atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t); /* Device methods. */ static device_probe_t atp_probe; static device_attach_t atp_attach; static device_detach_t atp_detach; static usb_callback_t atp_intr; static const struct usb_config atp_xfer_config[ATP_N_TRANSFER] = { [ATP_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, /* block pipe on failure */ .short_xfer_ok = 1, }, .bufsize = ATP_SENSOR_DATA_BUF_MAX, .callback = &atp_intr, }, [ATP_RESET] = { .type = UE_CONTROL, .endpoint = 0, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + MODE_LENGTH, .callback = &atp_reset_callback, .interval = 0, /* no pre-delay */ }, }; static atp_stroke_t * atp_alloc_stroke(struct atp_softc *sc) { atp_stroke_t *pstroke; pstroke = TAILQ_FIRST(&sc->sc_stroke_free); if (pstroke == NULL) goto done; TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry); memset(pstroke, 0, sizeof(*pstroke)); TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry); sc->sc_n_strokes++; done: return (pstroke); } static void atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke) { if (pstroke == NULL) return; sc->sc_n_strokes--; TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry); TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry); } static void atp_init_stroke_pool(struct atp_softc *sc) { u_int x; TAILQ_INIT(&sc->sc_stroke_free); TAILQ_INIT(&sc->sc_stroke_used); sc->sc_n_strokes = 0; memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data)); for (x = 0; x != ATP_MAX_STROKES; x++) { TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x], entry); } } static usb_error_t atp_set_device_mode(struct atp_softc *sc, interface_mode newMode) { uint8_t mode_value; usb_error_t err; if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE)) return (USB_ERR_INVAL); if ((newMode == RAW_SENSOR_MODE) && (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)) mode_value = (uint8_t)0x04; else mode_value = newMode; err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */, sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */, 0x03 /* type */, 0x00 /* id */); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("Failed to read device mode (%d)\n", err); return (err); } if (sc->sc_mode_bytes[0] == mode_value) return (err); /* * XXX Need to wait at least 250ms for hardware to get * ready. The device mode handling appears to be handled * asynchronously and we should not issue these commands too * quickly. */ pause("WHW", hz / 4); sc->sc_mode_bytes[0] = mode_value; return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */, sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */, 0x03 /* type */, 0x00 /* id */)); } static void atp_reset_callback(struct usb_xfer *xfer, usb_error_t error) { usb_device_request_t req; struct usb_page_cache *pc; struct atp_softc *sc = usbd_xfer_softc(xfer); uint8_t mode_value; if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER) mode_value = 0x04; else mode_value = RAW_SENSOR_MODE; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: sc->sc_mode_bytes[0] = mode_value; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_REPORT; USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); USETW(req.wIndex, 0); USETW(req.wLength, MODE_LENGTH); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; case USB_ST_TRANSFERRED: default: break; } } static int atp_enable(struct atp_softc *sc) { if (sc->sc_state & ATP_ENABLED) return (0); /* reset status */ memset(&sc->sc_status, 0, sizeof(sc->sc_status)); atp_init_stroke_pool(sc); sc->sc_state |= ATP_ENABLED; DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n"); return (0); } static void atp_disable(struct atp_softc *sc) { sc->sc_state &= ~(ATP_ENABLED | ATP_VALID); DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n"); } static void fg_interpret_sensor_data(struct atp_softc *sc, u_int data_len) { u_int n_xpspans = 0; u_int n_ypspans = 0; uint8_t status_bits; const struct fg_dev_params *params = (const struct fg_dev_params *)sc->sc_params; fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X, sc->sc_cur_x, params->prot); fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y, sc->sc_cur_y, params->prot); /* * If this is the initial update (from an untouched * pad), we should set the base values for the sensor * data; deltas with respect to these base values can * be used as pressure readings subsequently. */ status_bits = sc->sc_sensor_data[params->data_len - 1]; if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) || (params->prot == FG_TRACKPAD_TYPE_GEYSER4)) && ((sc->sc_state & ATP_VALID) == 0)) { if (status_bits & FG_STATUS_BASE_UPDATE) { memcpy(sc->sc_base_x, sc->sc_cur_x, params->n_xsensors * sizeof(*sc->sc_base_x)); memcpy(sc->sc_base_y, sc->sc_cur_y, params->n_ysensors * sizeof(*sc->sc_base_y)); sc->sc_state |= ATP_VALID; return; } } /* Get pressure readings and detect p-spans for both axes. */ fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x, params->n_xsensors); fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors, FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans); fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y, params->n_ysensors); fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors, FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans); /* Update strokes with new pspans to detect movements. */ if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans)) sc->sc_status.flags |= MOUSE_POSCHANGED; sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0; sc->sc_status.button = sc->sc_ibtn; /* * The Fountain/Geyser device continues to trigger interrupts * at a fast rate even after touchpad activity has * stopped. Upon detecting that the device has remained idle * beyond a threshold, we reinitialize it to silence the * interrupts. */ if ((sc->sc_status.flags == 0) && (sc->sc_n_strokes == 0)) { sc->sc_idlecount++; if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) { /* * Use the last frame before we go idle for * calibration on pads which do not send * calibration frames. */ const struct fg_dev_params *params = (const struct fg_dev_params *)sc->sc_params; DPRINTFN(ATP_LLEVEL_INFO, "idle\n"); if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) { memcpy(sc->sc_base_x, sc->sc_cur_x, params->n_xsensors * sizeof(*(sc->sc_base_x))); memcpy(sc->sc_base_y, sc->sc_cur_y, params->n_ysensors * sizeof(*(sc->sc_base_y))); } sc->sc_idlecount = 0; usbd_transfer_start(sc->sc_xfer[ATP_RESET]); } } else { sc->sc_idlecount = 0; } } /* * Interpret the data from the X and Y pressure sensors. This function * is called separately for the X and Y sensor arrays. The data in the * USB packet is laid out in the following manner: * * sensor_data: * --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4 * indices: 0 1 2 3 4 5 6 7 8 ... 15 ... 20 21 22 23 24 * * '--' (in the above) indicates that the value is unimportant. * * Information about the above layout was obtained from the * implementation of the AppleTouch driver in Linux. * * parameters: * sensor_data * raw sensor data from the USB packet. * num * The number of elements in the array 'arr'. * axis * Axis of data to fetch * arr * The array to be initialized with the readings. * prot * The protocol to use to interpret the data */ static void fg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis, int *arr, enum fountain_geyser_trackpad_type prot) { u_int i; u_int di; /* index into sensor data */ switch (prot) { case FG_TRACKPAD_TYPE_GEYSER1: /* * For Geyser 1, the sensors are laid out in pairs * every 5 bytes. */ for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) { arr[i] = sensor_data[di]; arr[i+8] = sensor_data[di+2]; if ((axis == X) && (num > 16)) arr[i+16] = sensor_data[di+40]; } break; case FG_TRACKPAD_TYPE_GEYSER2: for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) { arr[i++] = sensor_data[di++]; arr[i++] = sensor_data[di++]; di++; } break; case FG_TRACKPAD_TYPE_GEYSER3: case FG_TRACKPAD_TYPE_GEYSER4: for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) { arr[i++] = sensor_data[di++]; arr[i++] = sensor_data[di++]; di++; } break; default: break; } } static void fg_get_pressures(int *p, const int *cur, const int *base, int n) { int i; for (i = 0; i < n; i++) { p[i] = cur[i] - base[i]; if (p[i] > 127) p[i] -= 256; if (p[i] < -127) p[i] += 256; if (p[i] < 0) p[i] = 0; /* * Shave off pressures below the noise-pressure * threshold; this will reduce the contribution from * lower pressure readings. */ if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD) p[i] = 0; /* filter away noise */ else p[i] -= FG_SENSOR_NOISE_THRESHOLD; } } static void fg_detect_pspans(int *p, u_int num_sensors, u_int max_spans, /* max # of pspans permitted */ fg_pspan *spans, /* finger spans */ u_int *nspans_p) /* num spans detected */ { u_int i; int maxp; /* max pressure seen within a span */ u_int num_spans = 0; enum fg_pspan_state { ATP_PSPAN_INACTIVE, ATP_PSPAN_INCREASING, ATP_PSPAN_DECREASING, } state; /* state of the pressure span */ /* * The following is a simple state machine to track * the phase of the pressure span. */ memset(spans, 0, max_spans * sizeof(fg_pspan)); maxp = 0; state = ATP_PSPAN_INACTIVE; for (i = 0; i < num_sensors; i++) { if (num_spans >= max_spans) break; if (p[i] == 0) { if (state == ATP_PSPAN_INACTIVE) { /* * There is no pressure information for this * sensor, and we aren't tracking a finger. */ continue; } else { state = ATP_PSPAN_INACTIVE; maxp = 0; num_spans++; } } else { switch (state) { case ATP_PSPAN_INACTIVE: state = ATP_PSPAN_INCREASING; maxp = p[i]; break; case ATP_PSPAN_INCREASING: if (p[i] > maxp) maxp = p[i]; else if (p[i] <= (maxp >> 1)) state = ATP_PSPAN_DECREASING; break; case ATP_PSPAN_DECREASING: if (p[i] > p[i - 1]) { /* * This is the beginning of * another span; change state * to give the appearance that * we're starting from an * inactive span, and then * re-process this reading in * the next iteration. */ num_spans++; state = ATP_PSPAN_INACTIVE; maxp = 0; i--; continue; } break; } /* Update the finger span with this reading. */ spans[num_spans].width++; spans[num_spans].cum += p[i]; spans[num_spans].cog += p[i] * (i + 1); } } if (state != ATP_PSPAN_INACTIVE) num_spans++; /* close the last finger span */ /* post-process the spans */ for (i = 0; i < num_spans; i++) { /* filter away unwanted pressure spans */ if ((spans[i].cum < FG_PSPAN_MIN_CUM_PRESSURE) || (spans[i].width > FG_PSPAN_MAX_WIDTH)) { if ((i + 1) < num_spans) { memcpy(&spans[i], &spans[i + 1], (num_spans - i - 1) * sizeof(fg_pspan)); i--; } num_spans--; continue; } /* compute this span's representative location */ spans[i].loc = spans[i].cog * FG_SCALE_FACTOR / spans[i].cum; spans[i].matched = false; /* not yet matched against a stroke */ } *nspans_p = num_spans; } static void wsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len) { const struct wsp_dev_params *params = sc->sc_params; wsp_finger_t fingers[WSP_MAX_FINGERS]; struct wsp_finger_sensor_data *source_fingerp; u_int n_source_fingers; u_int n_fingers; u_int i; /* validate sensor data length */ if ((data_len < params->finger_data_offset) || ((data_len - params->finger_data_offset) % WSP_SIZEOF_FINGER_SENSOR_DATA) != 0) return; /* compute number of source fingers */ n_source_fingers = (data_len - params->finger_data_offset) / WSP_SIZEOF_FINGER_SENSOR_DATA; if (n_source_fingers > WSP_MAX_FINGERS) n_source_fingers = WSP_MAX_FINGERS; /* iterate over the source data collecting useful fingers */ n_fingers = 0; source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data + params->finger_data_offset); for (i = 0; i < n_source_fingers; i++, source_fingerp++) { /* swap endianness, if any */ if (le16toh(0x1234) != 0x1234) { source_fingerp->origin = le16toh((uint16_t)source_fingerp->origin); source_fingerp->abs_x = le16toh((uint16_t)source_fingerp->abs_x); source_fingerp->abs_y = le16toh((uint16_t)source_fingerp->abs_y); source_fingerp->rel_x = le16toh((uint16_t)source_fingerp->rel_x); source_fingerp->rel_y = le16toh((uint16_t)source_fingerp->rel_y); source_fingerp->tool_major = le16toh((uint16_t)source_fingerp->tool_major); source_fingerp->tool_minor = le16toh((uint16_t)source_fingerp->tool_minor); source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation); source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major); source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor); source_fingerp->multi = le16toh((uint16_t)source_fingerp->multi); } /* check for minium threshold */ if (source_fingerp->touch_major == 0) continue; fingers[n_fingers].matched = false; fingers[n_fingers].x = source_fingerp->abs_x; fingers[n_fingers].y = -source_fingerp->abs_y; n_fingers++; } if ((sc->sc_n_strokes == 0) && (n_fingers == 0)) return; if (wsp_update_strokes(sc, fingers, n_fingers)) sc->sc_status.flags |= MOUSE_POSCHANGED; switch(params->tp_type) { case WSP_TRACKPAD_TYPE2: sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET]; break; case WSP_TRACKPAD_TYPE3: sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET]; break; default: break; } sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0; } /* * Match a pressure-span against a stroke-component. If there is a * match, update the component's state and return true. */ static boolean_t fg_match_stroke_component(fg_stroke_component_t *component, const fg_pspan *pspan, atp_stroke_type stroke_type) { int delta_mickeys; u_int min_pressure; delta_mickeys = pspan->loc - component->loc; if (abs(delta_mickeys) > (int)FG_MAX_DELTA_MICKEYS) return (false); /* the finger span is too far out; no match */ component->loc = pspan->loc; /* * A sudden and significant increase in a pspan's cumulative * pressure indicates the incidence of a new finger * contact. This usually revises the pspan's * centre-of-gravity, and hence the location of any/all * matching stroke component(s). But such a change should * *not* be interpreted as a movement. */ if (pspan->cum > ((3 * component->cum_pressure) >> 1)) delta_mickeys = 0; component->cum_pressure = pspan->cum; if (pspan->cum > component->max_cum_pressure) component->max_cum_pressure = pspan->cum; /* * Disregard the component's movement if its cumulative * pressure drops below a fraction of the maximum; this * fraction is determined based on the stroke's type. */ if (stroke_type == ATP_STROKE_TOUCH) min_pressure = (3 * component->max_cum_pressure) >> 2; else min_pressure = component->max_cum_pressure >> 2; if (component->cum_pressure < min_pressure) delta_mickeys = 0; component->delta_mickeys = delta_mickeys; return (true); } static void fg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis, fg_pspan *pspans, u_int n_pspans, u_int repeat_count) { atp_stroke_t *strokep; u_int repeat_index = 0; u_int i; /* Determine the index of the multi-span. */ if (repeat_count) { for (i = 0; i < n_pspans; i++) { if (pspans[i].cum > pspans[repeat_index].cum) repeat_index = i; } } TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { if (strokep->components[axis].matched) continue; /* skip matched components */ for (i = 0; i < n_pspans; i++) { if (pspans[i].matched) continue; /* skip matched pspans */ if (fg_match_stroke_component( &strokep->components[axis], &pspans[i], strokep->type)) { /* There is a match. */ strokep->components[axis].matched = true; /* Take care to repeat at the multi-span. */ if ((repeat_count > 0) && (i == repeat_index)) repeat_count--; else pspans[i].matched = true; break; /* skip to the next strokep */ } } /* loop over pspans */ } /* loop over strokes */ } static boolean_t wsp_match_strokes_against_fingers(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers) { boolean_t movement = false; atp_stroke_t *strokep; u_int i; /* reset the matched status for all strokes */ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) strokep->matched = false; for (i = 0; i != n_fingers; i++) { u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ; atp_stroke_t *strokep_best = NULL; TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { int instantaneous_dx; int instantaneous_dy; u_int d_squared; if (strokep->matched) continue; instantaneous_dx = fingers[i].x - strokep->x; instantaneous_dy = fingers[i].y - strokep->y; /* skip strokes which are far away */ d_squared = (instantaneous_dx * instantaneous_dx) + (instantaneous_dy * instantaneous_dy); if (d_squared < least_distance_sq) { least_distance_sq = d_squared; strokep_best = strokep; } } strokep = strokep_best; if (strokep != NULL) { fingers[i].matched = true; strokep->matched = true; strokep->instantaneous_dx = fingers[i].x - strokep->x; strokep->instantaneous_dy = fingers[i].y - strokep->y; strokep->x = fingers[i].x; strokep->y = fingers[i].y; atp_advance_stroke_state(sc, strokep, &movement); } } return (movement); } /* * Update strokes by matching against current pressure-spans. * Return true if any movement is detected. */ static boolean_t fg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x, u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans) { atp_stroke_t *strokep; atp_stroke_t *strokep_next; boolean_t movement = false; u_int repeat_count = 0; u_int i; u_int j; /* Reset X and Y components of all strokes as unmatched. */ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { strokep->components[X].matched = false; strokep->components[Y].matched = false; } /* * Usually, the X and Y pspans come in pairs (the common case * being a single pair). It is possible, however, that * multiple contacts resolve to a single pspan along an * axis, as illustrated in the following: * * F = finger-contact * * pspan pspan * +-----------------------+ * | . . | * | . . | * | . . | * | . . | * pspan |.........F......F | * | | * | | * | | * +-----------------------+ * * * The above case can be detected by a difference in the * number of X and Y pspans. When this happens, X and Y pspans * aren't easy to pair or match against strokes. * * When X and Y pspans differ in number, the axis with the * smaller number of pspans is regarded as having a repeating * pspan (or a multi-pspan)--in the above illustration, the * Y-axis has a repeating pspan. Our approach is to try to * match the multi-pspan repeatedly against strokes. The * difference between the number of X and Y pspans gives us a * crude repeat_count for matching multi-pspans--i.e. the * multi-pspan along the Y axis (above) has a repeat_count of 1. */ repeat_count = abs(n_xpspans - n_ypspans); fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans, (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ? repeat_count : 0)); fg_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans, (((repeat_count != 0) && (n_ypspans < n_xpspans)) ? repeat_count : 0)); /* Update the state of strokes based on the above pspan matches. */ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) { if (strokep->components[X].matched && strokep->components[Y].matched) { strokep->matched = true; strokep->instantaneous_dx = strokep->components[X].delta_mickeys; strokep->instantaneous_dy = strokep->components[Y].delta_mickeys; atp_advance_stroke_state(sc, strokep, &movement); } else { /* * At least one component of this stroke * didn't match against current pspans; * terminate it. */ atp_terminate_stroke(sc, strokep); } } /* Add new strokes for pairs of unmatched pspans */ for (i = 0; i < n_xpspans; i++) { if (pspans_x[i].matched == false) break; } for (j = 0; j < n_ypspans; j++) { if (pspans_y[j].matched == false) break; } if ((i < n_xpspans) && (j < n_ypspans)) { #ifdef USB_DEBUG if (atp_debug >= ATP_LLEVEL_INFO) { printf("unmatched pspans:"); for (; i < n_xpspans; i++) { if (pspans_x[i].matched) continue; printf(" X:[loc:%u,cum:%u]", pspans_x[i].loc, pspans_x[i].cum); } for (; j < n_ypspans; j++) { if (pspans_y[j].matched) continue; printf(" Y:[loc:%u,cum:%u]", pspans_y[j].loc, pspans_y[j].cum); } printf("\n"); } #endif /* USB_DEBUG */ if ((n_xpspans == 1) && (n_ypspans == 1)) /* The common case of a single pair of new pspans. */ fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]); else fg_add_new_strokes(sc, pspans_x, n_xpspans, pspans_y, n_ypspans); } #ifdef USB_DEBUG if (atp_debug >= ATP_LLEVEL_INFO) { TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c" ",%clc:%u,dm:%d,cum:%d,max:%d,%c", (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "", (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<', strokep->components[X].loc, strokep->components[X].delta_mickeys, strokep->components[X].cum_pressure, strokep->components[X].max_cum_pressure, (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>', (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<', strokep->components[Y].loc, strokep->components[Y].delta_mickeys, strokep->components[Y].cum_pressure, strokep->components[Y].max_cum_pressure, (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>'); } if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL) printf("\n"); } #endif /* USB_DEBUG */ return (movement); } /* * Update strokes by matching against current pressure-spans. * Return true if any movement is detected. */ static boolean_t wsp_update_strokes(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers) { boolean_t movement = false; atp_stroke_t *strokep_next; atp_stroke_t *strokep; u_int i; if (sc->sc_n_strokes > 0) { movement = wsp_match_strokes_against_fingers( sc, fingers, n_fingers); /* handle zombie strokes */ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) { if (strokep->matched) continue; atp_terminate_stroke(sc, strokep); } } /* initialize unmatched fingers as strokes */ for (i = 0; i != n_fingers; i++) { if (fingers[i].matched) continue; wsp_add_stroke(sc, fingers + i); } return (movement); } /* Initialize a stroke using a pressure-span. */ static void fg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x, const fg_pspan *pspan_y) { atp_stroke_t *strokep; strokep = atp_alloc_stroke(sc); if (strokep == NULL) return; /* * Strokes begin as potential touches. If a stroke survives * longer than a threshold, or if it records significant * cumulative movement, then it is considered a 'slide'. */ strokep->type = ATP_STROKE_TOUCH; strokep->matched = false; microtime(&strokep->ctime); strokep->age = 1; /* number of interrupts */ strokep->x = pspan_x->loc; strokep->y = pspan_y->loc; strokep->components[X].loc = pspan_x->loc; strokep->components[X].cum_pressure = pspan_x->cum; strokep->components[X].max_cum_pressure = pspan_x->cum; strokep->components[X].matched = true; strokep->components[Y].loc = pspan_y->loc; strokep->components[Y].cum_pressure = pspan_y->cum; strokep->components[Y].max_cum_pressure = pspan_y->cum; strokep->components[Y].matched = true; if (sc->sc_n_strokes > 1) { /* Reset double-tap-n-drag if we have more than one strokes. */ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n", strokep->components[X].loc, strokep->components[Y].loc, (u_int)strokep->ctime.tv_sec, (unsigned long int)strokep->ctime.tv_usec); } static void fg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x, u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans) { fg_pspan spans[2][FG_MAX_PSPANS_PER_AXIS]; u_int nspans[2]; u_int i; u_int j; /* Copy unmatched pspans into the local arrays. */ for (i = 0, nspans[X] = 0; i < n_xpspans; i++) { if (pspans_x[i].matched == false) { spans[X][nspans[X]] = pspans_x[i]; nspans[X]++; } } for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) { if (pspans_y[j].matched == false) { spans[Y][nspans[Y]] = pspans_y[j]; nspans[Y]++; } } if (nspans[X] == nspans[Y]) { /* Create new strokes from pairs of unmatched pspans */ for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++) fg_add_stroke(sc, &spans[X][i], &spans[Y][j]); } else { u_int cum = 0; atp_axis repeat_axis; /* axis with multi-pspans */ u_int repeat_count; /* repeat count for the multi-pspan*/ u_int repeat_index = 0; /* index of the multi-span */ repeat_axis = (nspans[X] > nspans[Y]) ? Y : X; repeat_count = abs(nspans[X] - nspans[Y]); for (i = 0; i < nspans[repeat_axis]; i++) { if (spans[repeat_axis][i].cum > cum) { repeat_index = i; cum = spans[repeat_axis][i].cum; } } /* Create new strokes from pairs of unmatched pspans */ i = 0, j = 0; for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) { fg_add_stroke(sc, &spans[X][i], &spans[Y][j]); /* Take care to repeat at the multi-pspan. */ if (repeat_count > 0) { if ((repeat_axis == X) && (repeat_index == i)) { i--; /* counter loop increment */ repeat_count--; } else if ((repeat_axis == Y) && (repeat_index == j)) { j--; /* counter loop increment */ repeat_count--; } } } } } /* Initialize a stroke from an unmatched finger. */ static void wsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp) { atp_stroke_t *strokep; strokep = atp_alloc_stroke(sc); if (strokep == NULL) return; /* * Strokes begin as potential touches. If a stroke survives * longer than a threshold, or if it records significant * cumulative movement, then it is considered a 'slide'. */ strokep->type = ATP_STROKE_TOUCH; strokep->matched = true; microtime(&strokep->ctime); strokep->age = 1; /* number of interrupts */ strokep->x = fingerp->x; strokep->y = fingerp->y; /* Reset double-tap-n-drag if we have more than one strokes. */ if (sc->sc_n_strokes > 1) sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y); } static void atp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep, boolean_t *movementp) { /* Revitalize stroke if it had previously been marked as a zombie. */ if (strokep->flags & ATSF_ZOMBIE) strokep->flags &= ~ATSF_ZOMBIE; strokep->age++; if (strokep->age <= atp_stroke_maturity_threshold) { /* Avoid noise from immature strokes. */ strokep->instantaneous_dx = 0; strokep->instantaneous_dy = 0; } if (atp_compute_stroke_movement(strokep)) *movementp = true; if (strokep->type != ATP_STROKE_TOUCH) return; /* Convert touch strokes to slides upon detecting movement or age. */ if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) || (abs(strokep->cum_movement_y) > atp_slide_min_movement)) atp_convert_to_slide(sc, strokep); else { /* Compute the stroke's age. */ struct timeval tdiff; getmicrotime(&tdiff); if (timevalcmp(&tdiff, &strokep->ctime, >)) { timevalsub(&tdiff, &strokep->ctime); if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) || ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) && (tdiff.tv_usec >= (atp_touch_timeout % 1000000)))) atp_convert_to_slide(sc, strokep); } } } static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *strokep) { return (((u_int)abs(strokep->instantaneous_dx) <= atp_small_movement_threshold) && ((u_int)abs(strokep->instantaneous_dy) <= atp_small_movement_threshold)); } /* * Accumulate instantaneous changes into the stroke's 'pending' bucket; if * the aggregate exceeds the small_movement_threshold, then retain * instantaneous changes for later. */ static void atp_update_pending_mickeys(atp_stroke_t *strokep) { /* accumulate instantaneous movement */ strokep->pending_dx += strokep->instantaneous_dx; strokep->pending_dy += strokep->instantaneous_dy; #define UPDATE_INSTANTANEOUS_AND_PENDING(I, P) \ if (abs((P)) <= atp_small_movement_threshold) \ (I) = 0; /* clobber small movement */ \ else { \ if ((I) > 0) { \ /* \ * Round up instantaneous movement to the nearest \ * ceiling. This helps preserve small mickey \ * movements from being lost in following scaling \ * operation. \ */ \ (I) = (((I) + (atp_mickeys_scale_factor - 1)) / \ atp_mickeys_scale_factor) * \ atp_mickeys_scale_factor; \ \ /* \ * Deduct the rounded mickeys from pending mickeys. \ * Note: we multiply by 2 to offset the previous \ * accumulation of instantaneous movement into \ * pending. \ */ \ (P) -= ((I) << 1); \ \ /* truncate pending to 0 if it becomes negative. */ \ (P) = imax((P), 0); \ } else { \ /* \ * Round down instantaneous movement to the nearest \ * ceiling. This helps preserve small mickey \ * movements from being lost in following scaling \ * operation. \ */ \ (I) = (((I) - (atp_mickeys_scale_factor - 1)) / \ atp_mickeys_scale_factor) * \ atp_mickeys_scale_factor; \ \ /* \ * Deduct the rounded mickeys from pending mickeys. \ * Note: we multiply by 2 to offset the previous \ * accumulation of instantaneous movement into \ * pending. \ */ \ (P) -= ((I) << 1); \ \ /* truncate pending to 0 if it becomes positive. */ \ (P) = imin((P), 0); \ } \ } UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx, strokep->pending_dx); UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy, strokep->pending_dy); } /* * Compute a smoothened value for the stroke's movement from * instantaneous changes in the X and Y components. */ static boolean_t atp_compute_stroke_movement(atp_stroke_t *strokep) { /* * Short movements are added first to the 'pending' bucket, * and then acted upon only when their aggregate exceeds a * threshold. This has the effect of filtering away movement * noise. */ if (atp_stroke_has_small_movement(strokep)) atp_update_pending_mickeys(strokep); else { /* large movement */ /* clear away any pending mickeys if there are large movements*/ strokep->pending_dx = 0; strokep->pending_dy = 0; } /* scale movement */ strokep->movement_dx = (strokep->instantaneous_dx) / (int)atp_mickeys_scale_factor; strokep->movement_dy = (strokep->instantaneous_dy) / (int)atp_mickeys_scale_factor; if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) || (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) { strokep->movement_dx <<= 1; strokep->movement_dy <<= 1; } strokep->cum_movement_x += strokep->movement_dx; strokep->cum_movement_y += strokep->movement_dy; return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0)); } /* * Terminate a stroke. Aside from immature strokes, a slide or touch is * retained as a zombies so as to reap all their termination siblings * together; this helps establish the number of fingers involved at the * end of a multi-touch gesture. */ static void atp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep) { if (strokep->flags & ATSF_ZOMBIE) return; /* Drop immature strokes rightaway. */ if (strokep->age <= atp_stroke_maturity_threshold) { atp_free_stroke(sc, strokep); return; } strokep->flags |= ATSF_ZOMBIE; sc->sc_state |= ATP_ZOMBIES_EXIST; callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL, atp_reap_sibling_zombies, sc); /* * Reset the double-click-n-drag at the termination of any * slide stroke. */ if (strokep->type == ATP_STROKE_SLIDE) sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *strokep) { if (abs(strokep->cum_movement_x) < atp_slide_min_movement) return (false); if (strokep->cum_movement_y == 0) return (true); return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4); } static boolean_t atp_is_vertical_scroll(const atp_stroke_t *strokep) { if (abs(strokep->cum_movement_y) < atp_slide_min_movement) return (false); if (strokep->cum_movement_x == 0) return (true); return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4); } static void atp_reap_sibling_zombies(void *arg) { struct atp_softc *sc = (struct atp_softc *)arg; u_int8_t n_touches_reaped = 0; u_int8_t n_slides_reaped = 0; u_int8_t n_horizontal_scrolls = 0; u_int8_t n_vertical_scrolls = 0; int horizontal_scroll = 0; int vertical_scroll = 0; atp_stroke_t *strokep; atp_stroke_t *strokep_next; DPRINTFN(ATP_LLEVEL_INFO, "\n"); TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) { if ((strokep->flags & ATSF_ZOMBIE) == 0) continue; if (strokep->type == ATP_STROKE_TOUCH) { n_touches_reaped++; } else { n_slides_reaped++; if (atp_is_horizontal_scroll(strokep)) { n_horizontal_scrolls++; horizontal_scroll += strokep->cum_movement_x; } else if (atp_is_vertical_scroll(strokep)) { n_vertical_scrolls++; vertical_scroll += strokep->cum_movement_y; } } atp_free_stroke(sc, strokep); } DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", n_touches_reaped + n_slides_reaped); sc->sc_state &= ~ATP_ZOMBIES_EXIST; /* No further processing necessary if physical button is depressed. */ if (sc->sc_ibtn != 0) return; if ((n_touches_reaped == 0) && (n_slides_reaped == 0)) return; /* Add a pair of virtual button events (button-down and button-up) if * the physical button isn't pressed. */ if (n_touches_reaped != 0) { if (n_touches_reaped < atp_tap_minimum) return; switch (n_touches_reaped) { case 1: atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN); microtime(&sc->sc_touch_reap_time); /* remember this time */ break; case 2: atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN); break; case 3: atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN); break; default: /* we handle taps of only up to 3 fingers */ return; } atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */ } else if ((n_slides_reaped == 2) && (n_horizontal_scrolls == 2)) { if (horizontal_scroll < 0) atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN); else atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN); atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */ } } /* Switch a given touch stroke to being a slide. */ static void atp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep) { strokep->type = ATP_STROKE_SLIDE; /* Are we at the beginning of a double-click-n-drag? */ if ((sc->sc_n_strokes == 1) && ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) && timevalcmp(&strokep->ctime, &sc->sc_touch_reap_time, >)) { struct timeval delta; struct timeval window = { atp_double_tap_threshold / 1000000, atp_double_tap_threshold % 1000000 }; delta = strokep->ctime; timevalsub(&delta, &sc->sc_touch_reap_time); if (timevalcmp(&delta, &window, <=)) sc->sc_state |= ATP_DOUBLE_TAP_DRAG; } } static void atp_reset_buf(struct atp_softc *sc) { /* reset read queue */ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); } static void atp_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz, uint32_t buttons_in) { uint32_t buttons_out; uint8_t buf[8]; dx = imin(dx, 254); dx = imax(dx, -256); dy = imin(dy, 254); dy = imax(dy, -256); dz = imin(dz, 126); dz = imax(dz, -128); buttons_out = MOUSE_MSC_BUTTONS; if (buttons_in & MOUSE_BUTTON1DOWN) buttons_out &= ~MOUSE_MSC_BUTTON1UP; else if (buttons_in & MOUSE_BUTTON2DOWN) buttons_out &= ~MOUSE_MSC_BUTTON2UP; else if (buttons_in & MOUSE_BUTTON3DOWN) buttons_out &= ~MOUSE_MSC_BUTTON3UP; DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n", dx, dy, buttons_out); /* Encode the mouse data in standard format; refer to mouse(4) */ buf[0] = sc->sc_mode.syncmask[1]; buf[0] |= buttons_out; buf[1] = dx >> 1; buf[2] = dy >> 1; buf[3] = dx - (dx >> 1); buf[4] = dy - (dy >> 1); /* Encode extra bytes for level 1 */ if (sc->sc_mode.level == 1) { buf[5] = dz >> 1; buf[6] = dz - (dz >> 1); buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS); } usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, sc->sc_mode.packetsize, 1); } static int atp_probe(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bInterfaceClass != UICLASS_HID) return (ENXIO); /* * Note: for some reason, the check * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true * for wellspring trackpads, so we've removed it from the common path. */ if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0) return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ? 0 : ENXIO); if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0) if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX) return (0); return (ENXIO); } static int atp_attach(device_t dev) { struct atp_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); usb_error_t err; void *descriptor_ptr = NULL; uint16_t descriptor_len; unsigned long di; DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc); sc->sc_dev = dev; sc->sc_usb_device = uaa->device; /* Get HID descriptor */ if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr, &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) != USB_ERR_NORMAL_COMPLETION) return (ENXIO); /* Get HID report descriptor length */ sc->sc_expected_sensor_data_len = hid_report_size(descriptor_ptr, descriptor_len, hid_input, NULL); free(descriptor_ptr, M_TEMP); if ((sc->sc_expected_sensor_data_len <= 0) || (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) { DPRINTF("atp_attach: datalength invalid or too large: %d\n", sc->sc_expected_sensor_data_len); return (ENXIO); } /* * By default the touchpad behaves like an HID device, sending * packets with reportID = 2. Such reports contain only * limited information--they encode movement deltas and button * events,--but do not include data from the pressure * sensors. The device input mode can be switched from HID * reports to raw sensor data using vendor-specific USB * control commands. */ if ((err = atp_set_device_mode(sc, RAW_SENSOR_MODE)) != 0) { DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err); return (ENXIO); } mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE); di = USB_GET_DRIVER_INFO(uaa); sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di); switch(sc->sc_family) { case TRACKPAD_FAMILY_FOUNTAIN_GEYSER: sc->sc_params = &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)]; sc->sensor_data_interpreter = fg_interpret_sensor_data; break; case TRACKPAD_FAMILY_WELLSPRING: sc->sc_params = &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)]; sc->sensor_data_interpreter = wsp_interpret_sensor_data; break; default: goto detach; } err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_config, ATP_N_TRANSFER, sc, &sc->sc_mutex); if (err) { DPRINTF("error=%s\n", usbd_errstr(err)); goto detach; } if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex, &atp_fifo_methods, &sc->sc_fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644)) { goto detach; } device_set_usb_desc(dev); sc->sc_hw.buttons = 3; sc->sc_hw.iftype = MOUSE_IF_USB; sc->sc_hw.type = MOUSE_PAD; sc->sc_hw.model = MOUSE_MODEL_GENERIC; sc->sc_hw.hwid = 0; sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.rate = -1; sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; sc->sc_mode.accelfactor = 0; sc->sc_mode.level = 0; sc->sc_state = 0; sc->sc_ibtn = 0; callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0); return (0); detach: atp_detach(dev); return (ENOMEM); } static int atp_detach(device_t dev) { struct atp_softc *sc; sc = device_get_softc(dev); atp_set_device_mode(sc, HID_MODE); mtx_lock(&sc->sc_mutex); callout_drain(&sc->sc_callout); if (sc->sc_state & ATP_ENABLED) atp_disable(sc); mtx_unlock(&sc->sc_mutex); usb_fifo_detach(&sc->sc_fifo); usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER); mtx_destroy(&sc->sc_mutex); return (0); } static void atp_intr(struct usb_xfer *xfer, usb_error_t error) { struct atp_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, sc->sc_sensor_data, len); if (len < sc->sc_expected_sensor_data_len) { /* make sure we don't process old data */ memset(sc->sc_sensor_data + len, 0, sc->sc_expected_sensor_data_len - len); } sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED | MOUSE_POSCHANGED); sc->sc_status.obutton = sc->sc_status.button; (sc->sensor_data_interpreter)(sc, len); if (sc->sc_status.button != 0) { /* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) { /* Assume a button-press with DOUBLE_TAP_N_DRAG. */ sc->sc_status.button = MOUSE_BUTTON1DOWN; } sc->sc_status.flags |= sc->sc_status.button ^ sc->sc_status.obutton; if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) { DPRINTFN(ATP_LLEVEL_INFO, "button %s\n", ((sc->sc_status.button & MOUSE_BUTTON1DOWN) ? "pressed" : "released")); } if (sc->sc_status.flags & (MOUSE_POSCHANGED | MOUSE_STDBUTTONSCHANGED)) { atp_stroke_t *strokep; u_int8_t n_movements = 0; int dx = 0; int dy = 0; int dz = 0; TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { if (strokep->flags & ATSF_ZOMBIE) continue; dx += strokep->movement_dx; dy += strokep->movement_dy; if (strokep->movement_dx || strokep->movement_dy) n_movements++; } /* average movement if multiple strokes record motion.*/ if (n_movements > 1) { dx /= (int)n_movements; dy /= (int)n_movements; } /* detect multi-finger vertical scrolls */ if (n_movements >= 2) { boolean_t all_vertical_scrolls = true; TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { if (strokep->flags & ATSF_ZOMBIE) continue; if (!atp_is_vertical_scroll(strokep)) all_vertical_scrolls = false; } if (all_vertical_scrolls) { dz = dy; dy = dx = 0; } } sc->sc_status.dx += dx; sc->sc_status.dy += dy; sc->sc_status.dz += dz; atp_add_to_queue(sc, dx, -dy, -dz, sc->sc_status.button); } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { usbd_xfer_set_frame_len(xfer, 0, sc->sc_expected_sensor_data_len); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void atp_start_read(struct usb_fifo *fifo) { struct atp_softc *sc = usb_fifo_softc(fifo); int rate; /* Check if we should override the default polling interval */ rate = sc->sc_pollrate; /* Range check rate */ if (rate > 1000) rate = 1000; /* Check for set rate */ if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) { /* Stop current transfer, if any */ usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]); /* Set new interval */ usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate); /* Only set pollrate once */ sc->sc_pollrate = 0; } usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]); } static void atp_stop_read(struct usb_fifo *fifo) { struct atp_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]); } static int atp_open(struct usb_fifo *fifo, int fflags) { struct atp_softc *sc = usb_fifo_softc(fifo); /* check for duplicate open, should not happen */ if (sc->sc_fflags & fflags) return (EBUSY); /* check for first open */ if (sc->sc_fflags == 0) { int rc; if ((rc = atp_enable(sc)) != 0) return (rc); } if (fflags & FREAD) { if (usb_fifo_alloc_buffer(fifo, ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) { return (ENOMEM); } } sc->sc_fflags |= (fflags & (FREAD | FWRITE)); return (0); } static void atp_close(struct usb_fifo *fifo, int fflags) { struct atp_softc *sc = usb_fifo_softc(fifo); if (fflags & FREAD) usb_fifo_free_buffer(fifo); sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); if (sc->sc_fflags == 0) { atp_disable(sc); } } static int atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { struct atp_softc *sc = usb_fifo_softc(fifo); mousemode_t mode; int error = 0; mtx_lock(&sc->sc_mutex); switch(cmd) { case MOUSE_GETHWINFO: *(mousehw_t *)addr = sc->sc_hw; break; case MOUSE_GETMODE: *(mousemode_t *)addr = sc->sc_mode; break; case MOUSE_SETMODE: mode = *(mousemode_t *)addr; if (mode.level == -1) /* Don't change the current setting */ ; else if ((mode.level < 0) || (mode.level > 1)) { error = EINVAL; break; } sc->sc_mode.level = mode.level; sc->sc_pollrate = mode.rate; sc->sc_hw.buttons = 3; if (sc->sc_mode.level == 0) { sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->sc_mode.level == 1) { sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; } atp_reset_buf(sc); break; case MOUSE_GETLEVEL: *(int *)addr = sc->sc_mode.level; break; case MOUSE_SETLEVEL: if ((*(int *)addr < 0) || (*(int *)addr > 1)) { error = EINVAL; break; } sc->sc_mode.level = *(int *)addr; sc->sc_hw.buttons = 3; if (sc->sc_mode.level == 0) { sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->sc_mode.level == 1) { sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; } atp_reset_buf(sc); break; case MOUSE_GETSTATUS: { mousestatus_t *status = (mousestatus_t *)addr; *status = sc->sc_status; sc->sc_status.obutton = sc->sc_status.button; sc->sc_status.button = 0; sc->sc_status.dx = 0; sc->sc_status.dy = 0; sc->sc_status.dz = 0; if (status->dx || status->dy || status->dz) status->flags |= MOUSE_POSCHANGED; if (status->button != status->obutton) status->flags |= MOUSE_BUTTONSCHANGED; break; } default: error = ENOTTY; break; } mtx_unlock(&sc->sc_mutex); return (error); } static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS) { int error; u_int tmp; tmp = atp_mickeys_scale_factor; error = sysctl_handle_int(oidp, &tmp, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (tmp == atp_mickeys_scale_factor) return (0); /* no change */ if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR))) return (EINVAL); atp_mickeys_scale_factor = tmp; DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n", ATP_DRIVER_NAME, tmp); return (0); } static devclass_t atp_devclass; static device_method_t atp_methods[] = { DEVMETHOD(device_probe, atp_probe), DEVMETHOD(device_attach, atp_attach), DEVMETHOD(device_detach, atp_detach), DEVMETHOD_END }; static driver_t atp_driver = { .name = ATP_DRIVER_NAME, .methods = atp_methods, .size = sizeof(struct atp_softc) }; DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0); MODULE_DEPEND(atp, usb, 1, 1, 1); MODULE_VERSION(atp, 1); USB_PNP_HOST_INFO(fg_devs); USB_PNP_HOST_INFO(wsp_devs); Index: head/sys/dev/usb/input/uep.c =================================================================== --- head/sys/dev/usb/input/uep.c (revision 357971) +++ head/sys/dev/usb/input/uep.c (revision 357972) @@ -1,540 +1,541 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright 2010, Gleb Smirnoff * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ /* * http://www.eeti.com.tw/pdf/Software%20Programming%20Guide_v2.0.pdf */ #include "opt_evdev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #ifdef EVDEV_SUPPORT #include #include #else #include #include #endif #define USB_DEBUG_VAR uep_debug #include #ifdef USB_DEBUG static int uep_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW, 0, "USB uep"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uep"); SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RWTUN, &uep_debug, 0, "Debug level"); #endif #define UEP_MAX_X 2047 #define UEP_MAX_Y 2047 #define UEP_DOWN 0x01 #define UEP_PACKET_LEN_MAX 16 #define UEP_PACKET_LEN_REPORT 5 #define UEP_PACKET_LEN_REPORT2 6 #define UEP_PACKET_DIAG 0x0a #define UEP_PACKET_REPORT_MASK 0xe0 #define UEP_PACKET_REPORT 0x80 #define UEP_PACKET_REPORT_PRESSURE 0xc0 #define UEP_PACKET_REPORT_PLAYER 0xa0 #define UEP_PACKET_LEN_MASK #define UEP_FIFO_BUF_SIZE 8 /* bytes */ #define UEP_FIFO_QUEUE_MAXLEN 50 /* units */ enum { UEP_INTR_DT, UEP_N_TRANSFER, }; struct uep_softc { struct mtx mtx; struct usb_xfer *xfer[UEP_N_TRANSFER]; #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; #else struct usb_fifo_sc fifo; u_int pollrate; u_int state; #define UEP_ENABLED 0x01 #endif /* Reassembling buffer. */ u_char buf[UEP_PACKET_LEN_MAX]; uint8_t buf_len; }; static usb_callback_t uep_intr_callback; static device_probe_t uep_probe; static device_attach_t uep_attach; static device_detach_t uep_detach; #ifdef EVDEV_SUPPORT static evdev_open_t uep_ev_open; static evdev_close_t uep_ev_close; static const struct evdev_methods uep_evdev_methods = { .ev_open = &uep_ev_open, .ev_close = &uep_ev_close, }; #else /* !EVDEV_SUPPORT */ static usb_fifo_cmd_t uep_start_read; static usb_fifo_cmd_t uep_stop_read; static usb_fifo_open_t uep_open; static usb_fifo_close_t uep_close; static void uep_put_queue(struct uep_softc *, u_char *); static struct usb_fifo_methods uep_fifo_methods = { .f_open = &uep_open, .f_close = &uep_close, .f_start_read = &uep_start_read, .f_stop_read = &uep_stop_read, .basename[0] = "uep", }; #endif /* !EVDEV_SUPPORT */ static int get_pkt_len(u_char *buf) { if (buf[0] == UEP_PACKET_DIAG) { int len; len = buf[1] + 2; if (len > UEP_PACKET_LEN_MAX) { DPRINTF("bad packet len %u\n", len); return (UEP_PACKET_LEN_MAX); } return (len); } switch (buf[0] & UEP_PACKET_REPORT_MASK) { case UEP_PACKET_REPORT: return (UEP_PACKET_LEN_REPORT); case UEP_PACKET_REPORT_PRESSURE: case UEP_PACKET_REPORT_PLAYER: case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER: return (UEP_PACKET_LEN_REPORT2); default: DPRINTF("bad packet len 0\n"); return (0); } } static void uep_process_pkt(struct uep_softc *sc, u_char *buf) { int32_t x, y; #ifdef EVDEV_SUPPORT int touch; #endif if ((buf[0] & 0xFE) != 0x80) { DPRINTF("bad input packet format 0x%.2x\n", buf[0]); return; } /* * Packet format is 5 bytes: * * 1000000T * 0000AAAA * 0AAAAAAA * 0000BBBB * 0BBBBBBB * * T: 1=touched 0=not touched * A: bits of axis A position, MSB to LSB * B: bits of axis B position, MSB to LSB * * For the unit I have, which is CTF1020-S from CarTFT.com, * A = X and B = Y. But in NetBSD uep(4) it is other way round :) * * The controller sends a stream of T=1 events while the * panel is touched, followed by a single T=0 event. * */ x = (buf[1] << 7) | buf[2]; y = (buf[3] << 7) | buf[4]; DPRINTFN(2, "x %u y %u\n", x, y); #ifdef EVDEV_SUPPORT touch = buf[0] & (1 << 0); if (touch) { evdev_push_abs(sc->evdev, ABS_X, x); evdev_push_abs(sc->evdev, ABS_Y, y); } evdev_push_key(sc->evdev, BTN_TOUCH, touch); evdev_sync(sc->evdev); #else uep_put_queue(sc, buf); #endif } static void uep_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uep_softc *sc = usbd_xfer_softc(xfer); int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: { struct usb_page_cache *pc; u_char buf[17], *p; int pkt_len; if (len > (int)sizeof(buf)) { DPRINTF("bad input length %d\n", len); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); /* * The below code mimics Linux a lot. I don't know * why NetBSD reads complete packets, but we need * to reassamble 'em like Linux does (tries?). */ if (sc->buf_len > 0) { int res; if (sc->buf_len == 1) sc->buf[1] = buf[0]; if ((pkt_len = get_pkt_len(sc->buf)) == 0) goto tr_setup; res = pkt_len - sc->buf_len; memcpy(sc->buf + sc->buf_len, buf, res); uep_process_pkt(sc, sc->buf); sc->buf_len = 0; p = buf + res; len -= res; } else p = buf; if (len == 1) { sc->buf[0] = buf[0]; sc->buf_len = 1; goto tr_setup; } while (len > 0) { if ((pkt_len = get_pkt_len(p)) == 0) goto tr_setup; /* full packet: process */ if (pkt_len <= len) { uep_process_pkt(sc, p); } else { /* incomplete packet: save in buffer */ memcpy(sc->buf, p, len); sc->buf_len = len; } p += pkt_len; len -= pkt_len; } } case USB_ST_SETUP: tr_setup: #ifndef EVDEV_SUPPORT /* check if we can put more data into the FIFO */ if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) == 0) break; #endif usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static const struct usb_config uep_config[UEP_N_TRANSFER] = { [UEP_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &uep_intr_callback, }, }; static const STRUCT_USB_HOST_ID uep_devs[] = { {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)}, {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)}, {USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)}, }; static int uep_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa)); } static int uep_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uep_softc *sc = device_get_softc(dev); int error; device_set_usb_desc(dev); mtx_init(&sc->mtx, "uep lock", NULL, MTX_DEF); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->mtx); if (error) { DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); goto detach; } #ifdef EVDEV_SUPPORT sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(dev)); evdev_set_phys(sc->evdev, device_get_nameunit(dev)); evdev_set_id(sc->evdev, BUS_USB, uaa->info.idVendor, uaa->info.idProduct, 0); evdev_set_serial(sc->evdev, usb_get_serial(uaa->device)); evdev_set_methods(sc->evdev, sc, &uep_evdev_methods); evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT); evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); evdev_support_event(sc->evdev, EV_KEY); evdev_support_key(sc->evdev, BTN_TOUCH); evdev_support_abs(sc->evdev, ABS_X, 0, 0, UEP_MAX_X, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_Y, 0, 0, UEP_MAX_Y, 0, 0, 0); error = evdev_register_mtx(sc->evdev, &sc->mtx); if (error) { DPRINTF("evdev_register_mtx error=%s\n", usbd_errstr(error)); goto detach; } #else /* !EVDEV_SUPPORT */ error = usb_fifo_attach(uaa->device, sc, &sc->mtx, &uep_fifo_methods, &sc->fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (error) { DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error)); goto detach; } #endif /* !EVDEV_SUPPORT */ sc->buf_len = 0; return (0); detach: uep_detach(dev); return (ENOMEM); /* XXX */ } static int uep_detach(device_t dev) { struct uep_softc *sc = device_get_softc(dev); #ifdef EVDEV_SUPPORT evdev_free(sc->evdev); #else usb_fifo_detach(&sc->fifo); #endif usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER); mtx_destroy(&sc->mtx); return (0); } #ifdef EVDEV_SUPPORT static int uep_ev_close(struct evdev_dev *evdev) { struct uep_softc *sc = evdev_get_softc(evdev); mtx_assert(&sc->mtx, MA_OWNED); usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); return (0); } static int uep_ev_open(struct evdev_dev *evdev) { struct uep_softc *sc = evdev_get_softc(evdev); mtx_assert(&sc->mtx, MA_OWNED); usbd_transfer_start(sc->xfer[UEP_INTR_DT]); return (0); } #else /* !EVDEV_SUPPORT */ static void uep_start_read(struct usb_fifo *fifo) { struct uep_softc *sc = usb_fifo_softc(fifo); u_int rate; if ((rate = sc->pollrate) > 1000) rate = 1000; if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) { usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate); sc->pollrate = 0; } usbd_transfer_start(sc->xfer[UEP_INTR_DT]); } static void uep_stop_read(struct usb_fifo *fifo) { struct uep_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); } static void uep_put_queue(struct uep_softc *sc, u_char *buf) { usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf, UEP_PACKET_LEN_REPORT, 1); } static int uep_open(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { struct uep_softc *sc = usb_fifo_softc(fifo); if (sc->state & UEP_ENABLED) return (EBUSY); if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE, UEP_FIFO_QUEUE_MAXLEN)) return (ENOMEM); sc->state |= UEP_ENABLED; } return (0); } static void uep_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { struct uep_softc *sc = usb_fifo_softc(fifo); sc->state &= ~(UEP_ENABLED); usb_fifo_free_buffer(fifo); } } #endif /* !EVDEV_SUPPORT */ static devclass_t uep_devclass; static device_method_t uep_methods[] = { DEVMETHOD(device_probe, uep_probe), DEVMETHOD(device_attach, uep_attach), DEVMETHOD(device_detach, uep_detach), { 0, 0 }, }; static driver_t uep_driver = { .name = "uep", .methods = uep_methods, .size = sizeof(struct uep_softc), }; DRIVER_MODULE(uep, uhub, uep_driver, uep_devclass, NULL, NULL); MODULE_DEPEND(uep, usb, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(uep, evdev, 1, 1, 1); #endif MODULE_VERSION(uep, 1); USB_PNP_HOST_INFO(uep_devs); Index: head/sys/dev/usb/input/uhid.c =================================================================== --- head/sys/dev/usb/input/uhid.c (revision 357971) +++ head/sys/dev/usb/input/uhid.c (revision 357972) @@ -1,904 +1,905 @@ /* $NetBSD: uhid.c,v 1.46 2001/11/13 06:24:55 lukem Exp $ */ /* Also already merged from NetBSD: * $NetBSD: uhid.c,v 1.54 2002/09/23 05:51:21 simonb Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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. */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #define USB_DEBUG_VAR uhid_debug #include #include #include #ifdef USB_DEBUG static int uhid_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uhid, CTLFLAG_RW, 0, "USB uhid"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhid, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uhid"); SYSCTL_INT(_hw_usb_uhid, OID_AUTO, debug, CTLFLAG_RWTUN, &uhid_debug, 0, "Debug level"); #endif #define UHID_BSIZE 1024 /* bytes, buffer size */ #define UHID_FRAME_NUM 50 /* bytes, frame number */ enum { UHID_INTR_DT_WR, UHID_INTR_DT_RD, UHID_CTRL_DT_WR, UHID_CTRL_DT_RD, UHID_N_TRANSFER, }; struct uhid_softc { struct usb_fifo_sc sc_fifo; struct mtx sc_mtx; struct usb_xfer *sc_xfer[UHID_N_TRANSFER]; struct usb_device *sc_udev; void *sc_repdesc_ptr; uint32_t sc_isize; uint32_t sc_osize; uint32_t sc_fsize; uint16_t sc_repdesc_size; uint8_t sc_iface_no; uint8_t sc_iface_index; uint8_t sc_iid; uint8_t sc_oid; uint8_t sc_fid; uint8_t sc_flags; #define UHID_FLAG_IMMED 0x01 /* set if read should be immediate */ #define UHID_FLAG_STATIC_DESC 0x04 /* set if report descriptors are * static */ }; static const uint8_t uhid_xb360gp_report_descr[] = {UHID_XB360GP_REPORT_DESCR()}; static const uint8_t uhid_graphire_report_descr[] = {UHID_GRAPHIRE_REPORT_DESCR()}; static const uint8_t uhid_graphire3_4x5_report_descr[] = {UHID_GRAPHIRE3_4X5_REPORT_DESCR()}; /* prototypes */ static device_probe_t uhid_probe; static device_attach_t uhid_attach; static device_detach_t uhid_detach; static usb_callback_t uhid_intr_write_callback; static usb_callback_t uhid_intr_read_callback; static usb_callback_t uhid_write_callback; static usb_callback_t uhid_read_callback; static usb_fifo_cmd_t uhid_start_read; static usb_fifo_cmd_t uhid_stop_read; static usb_fifo_cmd_t uhid_start_write; static usb_fifo_cmd_t uhid_stop_write; static usb_fifo_open_t uhid_open; static usb_fifo_close_t uhid_close; static usb_fifo_ioctl_t uhid_ioctl; static struct usb_fifo_methods uhid_fifo_methods = { .f_open = &uhid_open, .f_close = &uhid_close, .f_ioctl = &uhid_ioctl, .f_start_read = &uhid_start_read, .f_stop_read = &uhid_stop_read, .f_start_write = &uhid_start_write, .f_stop_write = &uhid_stop_write, .basename[0] = "uhid", }; static void uhid_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhid_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc, 0, usbd_xfer_max_len(xfer), &actlen, 0)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uhid_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhid_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("transferred!\n"); pc = usbd_xfer_get_frame(xfer, 0); /* * If the ID byte is non zero we allow descriptors * having multiple sizes: */ if ((actlen >= (int)sc->sc_isize) || ((actlen > 0) && (sc->sc_iid != 0))) { /* limit report length to the maximum */ if (actlen > (int)sc->sc_isize) actlen = sc->sc_isize; usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc, 0, actlen, 1); } else { /* ignore it */ DPRINTF("ignored transfer, %d bytes\n", actlen); } case USB_ST_SETUP: re_submit: if (usb_fifo_put_bytes_max( sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { usbd_xfer_set_frame_len(xfer, 0, sc->sc_isize); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto re_submit; } return; } } static void uhid_fill_set_report(struct usb_device_request *req, uint8_t iface_no, uint8_t type, uint8_t id, uint16_t size) { req->bmRequestType = UT_WRITE_CLASS_INTERFACE; req->bRequest = UR_SET_REPORT; USETW2(req->wValue, type, id); req->wIndex[0] = iface_no; req->wIndex[1] = 0; USETW(req->wLength, size); } static void uhid_fill_get_report(struct usb_device_request *req, uint8_t iface_no, uint8_t type, uint8_t id, uint16_t size) { req->bmRequestType = UT_READ_CLASS_INTERFACE; req->bRequest = UR_GET_REPORT; USETW2(req->wValue, type, id); req->wIndex[0] = iface_no; req->wIndex[1] = 0; USETW(req->wLength, size); } static void uhid_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhid_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; uint32_t size = sc->sc_osize; uint32_t actlen; uint8_t id; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: /* try to extract the ID byte */ if (sc->sc_oid) { pc = usbd_xfer_get_frame(xfer, 0); if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc, 0, 1, &actlen, 0)) { if (actlen != 1) { goto tr_error; } usbd_copy_out(pc, 0, &id, 1); } else { return; } if (size) { size--; } } else { id = 0; } pc = usbd_xfer_get_frame(xfer, 1); if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc, 0, UHID_BSIZE, &actlen, 1)) { if (actlen != size) { goto tr_error; } uhid_fill_set_report (&req, sc->sc_iface_no, UHID_OUTPUT_REPORT, id, size); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, size); usbd_xfer_set_frames(xfer, size ? 2 : 1); usbd_transfer_submit(xfer); } return; default: tr_error: /* bomb out */ usb_fifo_get_data_error(sc->sc_fifo.fp[USB_FIFO_TX]); return; } } static void uhid_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhid_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; pc = usbd_xfer_get_frame(xfer, 0); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc, sizeof(req), sc->sc_isize, 1); return; case USB_ST_SETUP: if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) > 0) { uhid_fill_get_report (&req, sc->sc_iface_no, UHID_INPUT_REPORT, sc->sc_iid, sc->sc_isize); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, sc->sc_isize); usbd_xfer_set_frames(xfer, sc->sc_isize ? 2 : 1); usbd_transfer_submit(xfer); } return; default: /* Error */ /* bomb out */ usb_fifo_put_data_error(sc->sc_fifo.fp[USB_FIFO_RX]); return; } } static const struct usb_config uhid_config[UHID_N_TRANSFER] = { [UHID_INTR_DT_WR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .flags = {.pipe_bof = 1,.no_pipe_ok = 1, }, .bufsize = UHID_BSIZE, .callback = &uhid_intr_write_callback, }, [UHID_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = UHID_BSIZE, .callback = &uhid_intr_read_callback, }, [UHID_CTRL_DT_WR] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + UHID_BSIZE, .callback = &uhid_write_callback, .timeout = 1000, /* 1 second */ }, [UHID_CTRL_DT_RD] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + UHID_BSIZE, .callback = &uhid_read_callback, .timeout = 1000, /* 1 second */ }, }; static void uhid_start_read(struct usb_fifo *fifo) { struct uhid_softc *sc = usb_fifo_softc(fifo); if (sc->sc_flags & UHID_FLAG_IMMED) { usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_RD]); } else { usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_RD]); } } static void uhid_stop_read(struct usb_fifo *fifo) { struct uhid_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_RD]); usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_RD]); } static void uhid_start_write(struct usb_fifo *fifo) { struct uhid_softc *sc = usb_fifo_softc(fifo); if ((sc->sc_flags & UHID_FLAG_IMMED) || sc->sc_xfer[UHID_INTR_DT_WR] == NULL) { usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_WR]); } else { usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_WR]); } } static void uhid_stop_write(struct usb_fifo *fifo) { struct uhid_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_WR]); usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_WR]); } static int uhid_get_report(struct uhid_softc *sc, uint8_t type, uint8_t id, void *kern_data, void *user_data, uint16_t len) { int err; uint8_t free_data = 0; if (kern_data == NULL) { kern_data = malloc(len, M_USBDEV, M_WAITOK); if (kern_data == NULL) { err = ENOMEM; goto done; } free_data = 1; } err = usbd_req_get_report(sc->sc_udev, NULL, kern_data, len, sc->sc_iface_index, type, id); if (err) { err = ENXIO; goto done; } if (user_data) { /* dummy buffer */ err = copyout(kern_data, user_data, len); if (err) { goto done; } } done: if (free_data) { free(kern_data, M_USBDEV); } return (err); } static int uhid_set_report(struct uhid_softc *sc, uint8_t type, uint8_t id, void *kern_data, void *user_data, uint16_t len) { int err; uint8_t free_data = 0; if (kern_data == NULL) { kern_data = malloc(len, M_USBDEV, M_WAITOK); if (kern_data == NULL) { err = ENOMEM; goto done; } free_data = 1; err = copyin(user_data, kern_data, len); if (err) { goto done; } } err = usbd_req_set_report(sc->sc_udev, NULL, kern_data, len, sc->sc_iface_index, type, id); if (err) { err = ENXIO; goto done; } done: if (free_data) { free(kern_data, M_USBDEV); } return (err); } static int uhid_open(struct usb_fifo *fifo, int fflags) { struct uhid_softc *sc = usb_fifo_softc(fifo); /* * The buffers are one byte larger than maximum so that one * can detect too large read/writes and short transfers: */ if (fflags & FREAD) { /* reset flags */ mtx_lock(&sc->sc_mtx); sc->sc_flags &= ~UHID_FLAG_IMMED; mtx_unlock(&sc->sc_mtx); if (usb_fifo_alloc_buffer(fifo, sc->sc_isize + 1, UHID_FRAME_NUM)) { return (ENOMEM); } } if (fflags & FWRITE) { if (usb_fifo_alloc_buffer(fifo, sc->sc_osize + 1, UHID_FRAME_NUM)) { return (ENOMEM); } } return (0); } static void uhid_close(struct usb_fifo *fifo, int fflags) { if (fflags & (FREAD | FWRITE)) { usb_fifo_free_buffer(fifo); } } static int uhid_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { struct uhid_softc *sc = usb_fifo_softc(fifo); struct usb_gen_descriptor *ugd; uint32_t size; int error = 0; uint8_t id; switch (cmd) { case USB_GET_REPORT_DESC: ugd = addr; if (sc->sc_repdesc_size > ugd->ugd_maxlen) { size = ugd->ugd_maxlen; } else { size = sc->sc_repdesc_size; } ugd->ugd_actlen = size; if (ugd->ugd_data == NULL) break; /* descriptor length only */ error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size); break; case USB_SET_IMMED: if (!(fflags & FREAD)) { error = EPERM; break; } if (*(int *)addr) { /* do a test read */ error = uhid_get_report(sc, UHID_INPUT_REPORT, sc->sc_iid, NULL, NULL, sc->sc_isize); if (error) { break; } mtx_lock(&sc->sc_mtx); sc->sc_flags |= UHID_FLAG_IMMED; mtx_unlock(&sc->sc_mtx); } else { mtx_lock(&sc->sc_mtx); sc->sc_flags &= ~UHID_FLAG_IMMED; mtx_unlock(&sc->sc_mtx); } break; case USB_GET_REPORT: if (!(fflags & FREAD)) { error = EPERM; break; } ugd = addr; switch (ugd->ugd_report_type) { case UHID_INPUT_REPORT: size = sc->sc_isize; id = sc->sc_iid; break; case UHID_OUTPUT_REPORT: size = sc->sc_osize; id = sc->sc_oid; break; case UHID_FEATURE_REPORT: size = sc->sc_fsize; id = sc->sc_fid; break; default: return (EINVAL); } if (id != 0) copyin(ugd->ugd_data, &id, 1); error = uhid_get_report(sc, ugd->ugd_report_type, id, NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size)); break; case USB_SET_REPORT: if (!(fflags & FWRITE)) { error = EPERM; break; } ugd = addr; switch (ugd->ugd_report_type) { case UHID_INPUT_REPORT: size = sc->sc_isize; id = sc->sc_iid; break; case UHID_OUTPUT_REPORT: size = sc->sc_osize; id = sc->sc_oid; break; case UHID_FEATURE_REPORT: size = sc->sc_fsize; id = sc->sc_fid; break; default: return (EINVAL); } if (id != 0) copyin(ugd->ugd_data, &id, 1); error = uhid_set_report(sc, ugd->ugd_report_type, id, NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size)); break; case USB_GET_REPORT_ID: *(int *)addr = 0; /* XXX: we only support reportid 0? */ break; default: error = EINVAL; break; } return (error); } static const STRUCT_USB_HOST_ID uhid_devs[] = { /* generic HID class */ {USB_IFACE_CLASS(UICLASS_HID),}, /* the Xbox 360 gamepad doesn't use the HID class */ {USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),}, }; static int uhid_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; void *buf; uint16_t len; DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); error = usbd_lookup_id_by_uaa(uhid_devs, sizeof(uhid_devs), uaa); if (error) return (error); if (usb_test_quirk(uaa, UQ_HID_IGNORE)) return (ENXIO); /* * Don't attach to mouse and keyboard devices, hence then no * "nomatch" event is generated and then ums and ukbd won't * attach properly when loaded. */ if ((uaa->info.bInterfaceClass == UICLASS_HID) && (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && (((uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD) && !usb_test_quirk(uaa, UQ_KBD_IGNORE)) || ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) && !usb_test_quirk(uaa, UQ_UMS_IGNORE)))) return (ENXIO); /* Check for mandatory multitouch usages to give wmt(4) a chance */ if (!usb_test_quirk(uaa, UQ_WMT_IGNORE)) { error = usbd_req_get_hid_desc(uaa->device, NULL, &buf, &len, M_USBDEV, uaa->info.bIfaceIndex); /* Let HID decscriptor-less devices to be handled at attach */ if (!error) { if (hid_locate(buf, len, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), hid_feature, 0, NULL, NULL, NULL) && hid_locate(buf, len, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID), hid_input, 0, NULL, NULL, NULL)) { free(buf, M_USBDEV); return (ENXIO); } free(buf, M_USBDEV); } } return (BUS_PROBE_GENERIC); } static int uhid_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uhid_softc *sc = device_get_softc(dev); int unit = device_get_unit(dev); int error = 0; DPRINTFN(10, "sc=%p\n", sc); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uhid lock", NULL, MTX_DEF | MTX_RECURSE); sc->sc_udev = uaa->device; sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = uaa->info.bIfaceIndex; error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, uhid_config, UHID_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } if (uaa->info.idVendor == USB_VENDOR_WACOM) { /* the report descriptor for the Wacom Graphire is broken */ if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE) { sc->sc_repdesc_size = sizeof(uhid_graphire_report_descr); sc->sc_repdesc_ptr = __DECONST(void *, &uhid_graphire_report_descr); sc->sc_flags |= UHID_FLAG_STATIC_DESC; } else if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) { static uint8_t reportbuf[] = {2, 2, 2}; /* * The Graphire3 needs 0x0202 to be written to * feature report ID 2 before it'll start * returning digitizer data. */ error = usbd_req_set_report(uaa->device, NULL, reportbuf, sizeof(reportbuf), uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, 2); if (error) { DPRINTF("set report failed, error=%s (ignored)\n", usbd_errstr(error)); } sc->sc_repdesc_size = sizeof(uhid_graphire3_4x5_report_descr); sc->sc_repdesc_ptr = __DECONST(void *, &uhid_graphire3_4x5_report_descr); sc->sc_flags |= UHID_FLAG_STATIC_DESC; } } else if ((uaa->info.bInterfaceClass == UICLASS_VENDOR) && (uaa->info.bInterfaceSubClass == UISUBCLASS_XBOX360_CONTROLLER) && (uaa->info.bInterfaceProtocol == UIPROTO_XBOX360_GAMEPAD)) { static const uint8_t reportbuf[3] = {1, 3, 0}; /* * Turn off the four LEDs on the gamepad which * are blinking by default: */ error = usbd_req_set_report(uaa->device, NULL, __DECONST(void *, reportbuf), sizeof(reportbuf), uaa->info.bIfaceIndex, UHID_OUTPUT_REPORT, 0); if (error) { DPRINTF("set output report failed, error=%s (ignored)\n", usbd_errstr(error)); } /* the Xbox 360 gamepad has no report descriptor */ sc->sc_repdesc_size = sizeof(uhid_xb360gp_report_descr); sc->sc_repdesc_ptr = __DECONST(void *, &uhid_xb360gp_report_descr); sc->sc_flags |= UHID_FLAG_STATIC_DESC; } if (sc->sc_repdesc_ptr == NULL) { error = usbd_req_get_hid_desc(uaa->device, NULL, &sc->sc_repdesc_ptr, &sc->sc_repdesc_size, M_USBDEV, uaa->info.bIfaceIndex); if (error) { device_printf(dev, "no report descriptor\n"); goto detach; } } error = usbd_req_set_idle(uaa->device, NULL, uaa->info.bIfaceIndex, 0, 0); if (error) { DPRINTF("set idle failed, error=%s (ignored)\n", usbd_errstr(error)); } sc->sc_isize = hid_report_size (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_input, &sc->sc_iid); sc->sc_osize = hid_report_size (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_output, &sc->sc_oid); sc->sc_fsize = hid_report_size (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_feature, &sc->sc_fid); if (sc->sc_isize > UHID_BSIZE) { DPRINTF("input size is too large, " "%d bytes (truncating)\n", sc->sc_isize); sc->sc_isize = UHID_BSIZE; } if (sc->sc_osize > UHID_BSIZE) { DPRINTF("output size is too large, " "%d bytes (truncating)\n", sc->sc_osize); sc->sc_osize = UHID_BSIZE; } if (sc->sc_fsize > UHID_BSIZE) { DPRINTF("feature size is too large, " "%d bytes (truncating)\n", sc->sc_fsize); sc->sc_fsize = UHID_BSIZE; } error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, &uhid_fifo_methods, &sc->sc_fifo, unit, -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (error) { goto detach; } return (0); /* success */ detach: uhid_detach(dev); return (ENOMEM); } static int uhid_detach(device_t dev) { struct uhid_softc *sc = device_get_softc(dev); usb_fifo_detach(&sc->sc_fifo); usbd_transfer_unsetup(sc->sc_xfer, UHID_N_TRANSFER); if (sc->sc_repdesc_ptr) { if (!(sc->sc_flags & UHID_FLAG_STATIC_DESC)) { free(sc->sc_repdesc_ptr, M_USBDEV); } } mtx_destroy(&sc->sc_mtx); return (0); } static devclass_t uhid_devclass; static device_method_t uhid_methods[] = { DEVMETHOD(device_probe, uhid_probe), DEVMETHOD(device_attach, uhid_attach), DEVMETHOD(device_detach, uhid_detach), DEVMETHOD_END }; static driver_t uhid_driver = { .name = "uhid", .methods = uhid_methods, .size = sizeof(struct uhid_softc), }; DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, NULL, 0); MODULE_DEPEND(uhid, usb, 1, 1, 1); MODULE_VERSION(uhid, 1); USB_PNP_HOST_INFO(uhid_devs); Index: head/sys/dev/usb/input/ukbd.c =================================================================== --- head/sys/dev/usb/input/ukbd.c (revision 357971) +++ head/sys/dev/usb/input/ukbd.c (revision 357972) @@ -1,2184 +1,2185 @@ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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. * */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include "opt_kbd.h" #include "opt_ukbd.h" #include "opt_evdev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ukbd_debug #include #include #ifdef EVDEV_SUPPORT #include #include #endif #include #include #include #include /* the initial key map, accent map and fkey strings */ #if defined(UKBD_DFLT_KEYMAP) && !defined(KLD_MODULE) #define KBD_DFLT_KEYMAP #include "ukbdmap.h" #endif /* the following file must be included after "ukbdmap.h" */ #include #ifdef USB_DEBUG static int ukbd_debug = 0; static int ukbd_no_leds = 0; static int ukbd_pollrate = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW, 0, "USB keyboard"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB keyboard"); SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, debug, CTLFLAG_RWTUN, &ukbd_debug, 0, "Debug level"); SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, no_leds, CTLFLAG_RWTUN, &ukbd_no_leds, 0, "Disables setting of keyboard leds"); SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, pollrate, CTLFLAG_RWTUN, &ukbd_pollrate, 0, "Force this polling rate, 1-1000Hz"); #endif #define UKBD_EMULATE_ATSCANCODE 1 #define UKBD_DRIVER_NAME "ukbd" #define UKBD_NKEYCODE 256 /* units */ #define UKBD_IN_BUF_SIZE (4 * UKBD_NKEYCODE) /* scancodes */ #define UKBD_IN_BUF_FULL ((UKBD_IN_BUF_SIZE / 2) - 1) /* scancodes */ #define UKBD_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */ #define UKBD_BUFFER_SIZE 64 /* bytes */ #define UKBD_KEY_PRESSED(map, key) ({ \ CTASSERT((key) >= 0 && (key) < UKBD_NKEYCODE); \ ((map)[(key) / 64] & (1ULL << ((key) % 64))); \ }) #define MOD_EJECT 0x01 #define MOD_FN 0x02 struct ukbd_data { uint64_t bitmap[howmany(UKBD_NKEYCODE, 64)]; }; enum { UKBD_INTR_DT_0, UKBD_INTR_DT_1, UKBD_CTRL_LED, UKBD_N_TRANSFER, }; struct ukbd_softc { keyboard_t sc_kbd; keymap_t sc_keymap; accentmap_t sc_accmap; fkeytab_t sc_fkeymap[UKBD_NFKEY]; uint64_t sc_loc_key_valid[howmany(UKBD_NKEYCODE, 64)]; struct hid_location sc_loc_apple_eject; struct hid_location sc_loc_apple_fn; struct hid_location sc_loc_key[UKBD_NKEYCODE]; struct hid_location sc_loc_numlock; struct hid_location sc_loc_capslock; struct hid_location sc_loc_scrolllock; struct usb_callout sc_callout; struct ukbd_data sc_ndata; struct ukbd_data sc_odata; struct thread *sc_poll_thread; struct usb_device *sc_udev; struct usb_interface *sc_iface; struct usb_xfer *sc_xfer[UKBD_N_TRANSFER]; #ifdef EVDEV_SUPPORT struct evdev_dev *sc_evdev; #endif sbintime_t sc_co_basetime; int sc_delay; uint32_t sc_repeat_time; uint32_t sc_input[UKBD_IN_BUF_SIZE]; /* input buffer */ uint32_t sc_time_ms; uint32_t sc_composed_char; /* composed char code, if non-zero */ #ifdef UKBD_EMULATE_ATSCANCODE uint32_t sc_buffered_char[2]; #endif uint32_t sc_flags; /* flags */ #define UKBD_FLAG_COMPOSE 0x00000001 #define UKBD_FLAG_POLLING 0x00000002 #define UKBD_FLAG_SET_LEDS 0x00000004 #define UKBD_FLAG_ATTACHED 0x00000010 #define UKBD_FLAG_GONE 0x00000020 #define UKBD_FLAG_HID_MASK 0x003fffc0 #define UKBD_FLAG_APPLE_EJECT 0x00000040 #define UKBD_FLAG_APPLE_FN 0x00000080 #define UKBD_FLAG_APPLE_SWAP 0x00000100 #define UKBD_FLAG_NUMLOCK 0x00080000 #define UKBD_FLAG_CAPSLOCK 0x00100000 #define UKBD_FLAG_SCROLLLOCK 0x00200000 int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ int sc_state; /* shift/lock key state */ int sc_accents; /* accent key index (> 0) */ int sc_polling; /* polling recursion count */ int sc_led_size; int sc_kbd_size; uint16_t sc_inputs; uint16_t sc_inputhead; uint16_t sc_inputtail; uint8_t sc_leds; /* store for async led requests */ uint8_t sc_iface_index; uint8_t sc_iface_no; uint8_t sc_id_apple_eject; uint8_t sc_id_apple_fn; uint8_t sc_id_loc_key[UKBD_NKEYCODE]; uint8_t sc_id_numlock; uint8_t sc_id_capslock; uint8_t sc_id_scrolllock; uint8_t sc_kbd_id; uint8_t sc_repeat_key; uint8_t sc_buffer[UKBD_BUFFER_SIZE]; }; #define KEY_NONE 0x00 #define KEY_ERROR 0x01 #define KEY_PRESS 0 #define KEY_RELEASE 0x400 #define KEY_INDEX(c) ((c) & 0xFF) #define SCAN_PRESS 0 #define SCAN_RELEASE 0x80 #define SCAN_PREFIX_E0 0x100 #define SCAN_PREFIX_E1 0x200 #define SCAN_PREFIX_CTL 0x400 #define SCAN_PREFIX_SHIFT 0x800 #define SCAN_PREFIX (SCAN_PREFIX_E0 | SCAN_PREFIX_E1 | \ SCAN_PREFIX_CTL | SCAN_PREFIX_SHIFT) #define SCAN_CHAR(c) ((c) & 0x7f) #define UKBD_LOCK() USB_MTX_LOCK(&Giant) #define UKBD_UNLOCK() USB_MTX_UNLOCK(&Giant) #define UKBD_LOCK_ASSERT() USB_MTX_ASSERT(&Giant, MA_OWNED) #define NN 0 /* no translation */ /* * Translate USB keycodes to AT keyboard scancodes. */ /* * FIXME: Mac USB keyboard generates: * 0x53: keypad NumLock/Clear * 0x66: Power * 0x67: keypad = * 0x68: F13 * 0x69: F14 * 0x6a: F15 * * USB Apple Keyboard JIS generates: * 0x90: Kana * 0x91: Eisu */ static const uint8_t ukbd_trtab[256] = { 0, 0, 0, 0, 30, 48, 46, 32, /* 00 - 07 */ 18, 33, 34, 35, 23, 36, 37, 38, /* 08 - 0F */ 50, 49, 24, 25, 16, 19, 31, 20, /* 10 - 17 */ 22, 47, 17, 45, 21, 44, 2, 3, /* 18 - 1F */ 4, 5, 6, 7, 8, 9, 10, 11, /* 20 - 27 */ 28, 1, 14, 15, 57, 12, 13, 26, /* 28 - 2F */ 27, 43, 43, 39, 40, 41, 51, 52, /* 30 - 37 */ 53, 58, 59, 60, 61, 62, 63, 64, /* 38 - 3F */ 65, 66, 67, 68, 87, 88, 92, 70, /* 40 - 47 */ 104, 102, 94, 96, 103, 99, 101, 98, /* 48 - 4F */ 97, 100, 95, 69, 91, 55, 74, 78,/* 50 - 57 */ 89, 79, 80, 81, 75, 76, 77, 71, /* 58 - 5F */ 72, 73, 82, 83, 86, 107, 122, NN, /* 60 - 67 */ NN, NN, NN, NN, NN, NN, NN, NN, /* 68 - 6F */ NN, NN, NN, NN, 115, 108, 111, 113, /* 70 - 77 */ 109, 110, 112, 118, 114, 116, 117, 119, /* 78 - 7F */ 121, 120, NN, NN, NN, NN, NN, 123, /* 80 - 87 */ 124, 125, 126, 127, 128, NN, NN, NN, /* 88 - 8F */ 129, 130, NN, NN, NN, NN, NN, NN, /* 90 - 97 */ NN, NN, NN, NN, NN, NN, NN, NN, /* 98 - 9F */ NN, NN, NN, NN, NN, NN, NN, NN, /* A0 - A7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* A8 - AF */ NN, NN, NN, NN, NN, NN, NN, NN, /* B0 - B7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* B8 - BF */ NN, NN, NN, NN, NN, NN, NN, NN, /* C0 - C7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* C8 - CF */ NN, NN, NN, NN, NN, NN, NN, NN, /* D0 - D7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* D8 - DF */ 29, 42, 56, 105, 90, 54, 93, 106, /* E0 - E7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* E8 - EF */ NN, NN, NN, NN, NN, NN, NN, NN, /* F0 - F7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* F8 - FF */ }; static const uint8_t ukbd_boot_desc[] = { 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x03, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91, 0x02, 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0 }; /* prototypes */ static void ukbd_timeout(void *); static void ukbd_set_leds(struct ukbd_softc *, uint8_t); static int ukbd_set_typematic(keyboard_t *, int); #ifdef UKBD_EMULATE_ATSCANCODE static uint32_t ukbd_atkeycode(int, const uint64_t *); static int ukbd_key2scan(struct ukbd_softc *, int, const uint64_t *, int); #endif static uint32_t ukbd_read_char(keyboard_t *, int); static void ukbd_clear_state(keyboard_t *); static int ukbd_ioctl(keyboard_t *, u_long, caddr_t); static int ukbd_enable(keyboard_t *); static int ukbd_disable(keyboard_t *); static void ukbd_interrupt(struct ukbd_softc *); static void ukbd_event_keyinput(struct ukbd_softc *); static device_probe_t ukbd_probe; static device_attach_t ukbd_attach; static device_detach_t ukbd_detach; static device_resume_t ukbd_resume; #ifdef EVDEV_SUPPORT static evdev_event_t ukbd_ev_event; static const struct evdev_methods ukbd_evdev_methods = { .ev_event = ukbd_ev_event, }; #endif static bool ukbd_any_key_pressed(struct ukbd_softc *sc) { bool ret = false; unsigned i; for (i = 0; i != howmany(UKBD_NKEYCODE, 64); i++) ret |= (sc->sc_odata.bitmap[i] != 0); return (ret); } static bool ukbd_any_key_valid(struct ukbd_softc *sc) { bool ret = false; unsigned i; for (i = 0; i != howmany(UKBD_NKEYCODE, 64); i++) ret |= (sc->sc_loc_key_valid[i] != 0); return (ret); } static bool ukbd_is_modifier_key(uint32_t key) { return (key >= 0xe0 && key <= 0xe7); } static void ukbd_start_timer(struct ukbd_softc *sc) { sbintime_t delay, now, prec; now = sbinuptime(); /* check if initial delay passed and fallback to key repeat delay */ if (sc->sc_delay == 0) sc->sc_delay = sc->sc_kbd.kb_delay2; /* compute timeout */ delay = SBT_1MS * sc->sc_delay; sc->sc_co_basetime += delay; /* check if we are running behind */ if (sc->sc_co_basetime < now) sc->sc_co_basetime = now; /* This is rarely called, so prefer precision to efficiency. */ prec = qmin(delay >> 7, SBT_1MS * 10); usb_callout_reset_sbt(&sc->sc_callout, sc->sc_co_basetime, prec, ukbd_timeout, sc, C_ABSOLUTE); } static void ukbd_put_key(struct ukbd_softc *sc, uint32_t key) { UKBD_LOCK_ASSERT(); DPRINTF("0x%02x (%d) %s\n", key, key, (key & KEY_RELEASE) ? "released" : "pressed"); #ifdef EVDEV_SUPPORT if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && sc->sc_evdev != NULL) { evdev_push_event(sc->sc_evdev, EV_KEY, evdev_hid2key(KEY_INDEX(key)), !(key & KEY_RELEASE)); evdev_sync(sc->sc_evdev); } #endif if (sc->sc_inputs < UKBD_IN_BUF_SIZE) { sc->sc_input[sc->sc_inputtail] = key; ++(sc->sc_inputs); ++(sc->sc_inputtail); if (sc->sc_inputtail >= UKBD_IN_BUF_SIZE) { sc->sc_inputtail = 0; } } else { DPRINTF("input buffer is full\n"); } } static void ukbd_do_poll(struct ukbd_softc *sc, uint8_t wait) { UKBD_LOCK_ASSERT(); KASSERT((sc->sc_flags & UKBD_FLAG_POLLING) != 0, ("ukbd_do_poll called when not polling\n")); DPRINTFN(2, "polling\n"); if (USB_IN_POLLING_MODE_FUNC() == 0) { /* * In this context the kernel is polling for input, * but the USB subsystem works in normal interrupt-driven * mode, so we just wait on the USB threads to do the job. * Note that we currently hold the Giant, but it's also used * as the transfer mtx, so we must release it while waiting. */ while (sc->sc_inputs == 0) { /* * Give USB threads a chance to run. Note that * kern_yield performs DROP_GIANT + PICKUP_GIANT. */ kern_yield(PRI_UNCHANGED); if (!wait) break; } return; } while (sc->sc_inputs == 0) { usbd_transfer_poll(sc->sc_xfer, UKBD_N_TRANSFER); /* Delay-optimised support for repetition of keys */ if (ukbd_any_key_pressed(sc)) { /* a key is pressed - need timekeeping */ DELAY(1000); /* 1 millisecond has passed */ sc->sc_time_ms += 1; } ukbd_interrupt(sc); if (!wait) break; } } static int32_t ukbd_get_key(struct ukbd_softc *sc, uint8_t wait) { int32_t c; UKBD_LOCK_ASSERT(); KASSERT((USB_IN_POLLING_MODE_FUNC() == 0) || (sc->sc_flags & UKBD_FLAG_POLLING) != 0, ("not polling in kdb or panic\n")); if (sc->sc_inputs == 0 && (sc->sc_flags & UKBD_FLAG_GONE) == 0) { /* start transfer, if not already started */ usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_0]); usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_1]); } if (sc->sc_flags & UKBD_FLAG_POLLING) ukbd_do_poll(sc, wait); if (sc->sc_inputs == 0) { c = -1; } else { c = sc->sc_input[sc->sc_inputhead]; --(sc->sc_inputs); ++(sc->sc_inputhead); if (sc->sc_inputhead >= UKBD_IN_BUF_SIZE) { sc->sc_inputhead = 0; } } return (c); } static void ukbd_interrupt(struct ukbd_softc *sc) { const uint32_t now = sc->sc_time_ms; unsigned key; UKBD_LOCK_ASSERT(); /* Check for key changes */ for (key = 0; key != UKBD_NKEYCODE; key++) { const uint64_t mask = 1ULL << (key % 64); const uint64_t delta = sc->sc_odata.bitmap[key / 64] ^ sc->sc_ndata.bitmap[key / 64]; if (mask == 1 && delta == 0) { key += 63; continue; /* skip empty areas */ } else if (delta & mask) { if (sc->sc_odata.bitmap[key / 64] & mask) { ukbd_put_key(sc, key | KEY_RELEASE); /* clear repeating key, if any */ if (sc->sc_repeat_key == key) sc->sc_repeat_key = 0; } else { ukbd_put_key(sc, key | KEY_PRESS); if (ukbd_is_modifier_key(key)) continue; /* * Check for first new key and set * initial delay and [re]start timer: */ if (sc->sc_repeat_key == 0) { sc->sc_co_basetime = sbinuptime(); sc->sc_delay = sc->sc_kbd.kb_delay1; ukbd_start_timer(sc); } /* set repeat time for last key */ sc->sc_repeat_time = now + sc->sc_kbd.kb_delay1; sc->sc_repeat_key = key; } } } /* synchronize old data with new data */ sc->sc_odata = sc->sc_ndata; /* check if last key is still pressed */ if (sc->sc_repeat_key != 0) { const int32_t dtime = (sc->sc_repeat_time - now); /* check if time has elapsed */ if (dtime <= 0) { ukbd_put_key(sc, sc->sc_repeat_key | KEY_PRESS); sc->sc_repeat_time = now + sc->sc_kbd.kb_delay2; } } /* wakeup keyboard system */ ukbd_event_keyinput(sc); } static void ukbd_event_keyinput(struct ukbd_softc *sc) { int c; UKBD_LOCK_ASSERT(); if ((sc->sc_flags & UKBD_FLAG_POLLING) != 0) return; if (sc->sc_inputs == 0) return; if (KBD_IS_ACTIVE(&sc->sc_kbd) && KBD_IS_BUSY(&sc->sc_kbd)) { /* let the callback function process the input */ (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT, sc->sc_kbd.kb_callback.kc_arg); } else { /* read and discard the input, no one is waiting for it */ do { c = ukbd_read_char(&sc->sc_kbd, 0); } while (c != NOKEY); } } static void ukbd_timeout(void *arg) { struct ukbd_softc *sc = arg; UKBD_LOCK_ASSERT(); sc->sc_time_ms += sc->sc_delay; sc->sc_delay = 0; ukbd_interrupt(sc); /* Make sure any leftover key events gets read out */ ukbd_event_keyinput(sc); if (ukbd_any_key_pressed(sc) || (sc->sc_inputs != 0)) { ukbd_start_timer(sc); } } static uint32_t ukbd_apple_fn(uint32_t keycode) { switch (keycode) { case 0x28: return 0x49; /* RETURN -> INSERT */ case 0x2a: return 0x4c; /* BACKSPACE -> DEL */ case 0x50: return 0x4a; /* LEFT ARROW -> HOME */ case 0x4f: return 0x4d; /* RIGHT ARROW -> END */ case 0x52: return 0x4b; /* UP ARROW -> PGUP */ case 0x51: return 0x4e; /* DOWN ARROW -> PGDN */ default: return keycode; } } static uint32_t ukbd_apple_swap(uint32_t keycode) { switch (keycode) { case 0x35: return 0x64; case 0x64: return 0x35; default: return keycode; } } static void ukbd_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct ukbd_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t i; uint8_t id; uint8_t modifiers; int offset; int len; UKBD_LOCK_ASSERT(); usbd_xfer_status(xfer, &len, NULL, NULL, NULL); pc = usbd_xfer_get_frame(xfer, 0); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", len); if (len == 0) { DPRINTF("zero length data\n"); goto tr_setup; } if (sc->sc_kbd_id != 0) { /* check and remove HID ID byte */ usbd_copy_out(pc, 0, &id, 1); offset = 1; len--; if (len == 0) { DPRINTF("zero length data\n"); goto tr_setup; } } else { offset = 0; id = 0; } if (len > UKBD_BUFFER_SIZE) len = UKBD_BUFFER_SIZE; /* get data */ usbd_copy_out(pc, offset, sc->sc_buffer, len); /* clear temporary storage */ memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); /* clear modifiers */ modifiers = 0; /* scan through HID data */ if ((sc->sc_flags & UKBD_FLAG_APPLE_EJECT) && (id == sc->sc_id_apple_eject)) { if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_eject)) modifiers |= MOD_EJECT; } if ((sc->sc_flags & UKBD_FLAG_APPLE_FN) && (id == sc->sc_id_apple_fn)) { if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_fn)) modifiers |= MOD_FN; } for (i = 0; i != UKBD_NKEYCODE; i++) { const uint64_t valid = sc->sc_loc_key_valid[i / 64]; const uint64_t mask = 1ULL << (i % 64); if (mask == 1 && valid == 0) { i += 63; continue; /* skip empty areas */ } else if (~valid & mask) { continue; /* location is not valid */ } else if (id != sc->sc_id_loc_key[i]) { continue; /* invalid HID ID */ } else if (i == 0) { offset = sc->sc_loc_key[0].count; if (offset < 0 || offset > len) offset = len; while (offset--) { uint32_t key = hid_get_data(sc->sc_buffer + offset, len - offset, &sc->sc_loc_key[i]); if (modifiers & MOD_FN) key = ukbd_apple_fn(key); if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP) key = ukbd_apple_swap(key); if (key == KEY_NONE || key == KEY_ERROR || key >= UKBD_NKEYCODE) continue; /* set key in bitmap */ sc->sc_ndata.bitmap[key / 64] |= 1ULL << (key % 64); } } else if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_key[i])) { uint32_t key = i; if (modifiers & MOD_FN) key = ukbd_apple_fn(key); if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP) key = ukbd_apple_swap(key); if (key == KEY_NONE || key == KEY_ERROR || key >= UKBD_NKEYCODE) continue; /* set key in bitmap */ sc->sc_ndata.bitmap[key / 64] |= 1ULL << (key % 64); } } #ifdef USB_DEBUG DPRINTF("modifiers = 0x%04x\n", modifiers); for (i = 0; i != UKBD_NKEYCODE; i++) { const uint64_t valid = sc->sc_ndata.bitmap[i / 64]; const uint64_t mask = 1ULL << (i % 64); if (valid & mask) DPRINTF("Key 0x%02x pressed\n", i); } #endif ukbd_interrupt(sc); case USB_ST_SETUP: tr_setup: if (sc->sc_inputs < UKBD_IN_BUF_FULL) { usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); } else { DPRINTF("input queue is full!\n"); } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void ukbd_set_leds_callback(struct usb_xfer *xfer, usb_error_t error) { struct ukbd_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; uint8_t id; uint8_t any; int len; UKBD_LOCK_ASSERT(); #ifdef USB_DEBUG if (ukbd_no_leds) return; #endif switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: if (!(sc->sc_flags & UKBD_FLAG_SET_LEDS)) break; sc->sc_flags &= ~UKBD_FLAG_SET_LEDS; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_REPORT; USETW2(req.wValue, UHID_OUTPUT_REPORT, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; req.wLength[1] = 0; memset(sc->sc_buffer, 0, UKBD_BUFFER_SIZE); id = 0; any = 0; /* Assumption: All led bits must be in the same ID. */ if (sc->sc_flags & UKBD_FLAG_NUMLOCK) { if (sc->sc_leds & NLKED) { hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1, &sc->sc_loc_numlock, 1); } id = sc->sc_id_numlock; any = 1; } if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK) { if (sc->sc_leds & SLKED) { hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1, &sc->sc_loc_scrolllock, 1); } id = sc->sc_id_scrolllock; any = 1; } if (sc->sc_flags & UKBD_FLAG_CAPSLOCK) { if (sc->sc_leds & CLKED) { hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1, &sc->sc_loc_capslock, 1); } id = sc->sc_id_capslock; any = 1; } /* if no leds, nothing to do */ if (!any) break; #ifdef EVDEV_SUPPORT if (sc->sc_evdev != NULL) evdev_push_leds(sc->sc_evdev, sc->sc_leds); #endif /* range check output report length */ len = sc->sc_led_size; if (len > (UKBD_BUFFER_SIZE - 1)) len = (UKBD_BUFFER_SIZE - 1); /* check if we need to prefix an ID byte */ sc->sc_buffer[0] = id; pc = usbd_xfer_get_frame(xfer, 1); if (id != 0) { len++; usbd_copy_in(pc, 0, sc->sc_buffer, len); } else { usbd_copy_in(pc, 0, sc->sc_buffer + 1, len); } req.wLength[0] = len; usbd_xfer_set_frame_len(xfer, 1, len); DPRINTF("len=%d, id=%d\n", len, id); /* setup control request last */ pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); /* start data transfer */ usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTFN(1, "error=%s\n", usbd_errstr(error)); break; } } static const struct usb_config ukbd_config[UKBD_N_TRANSFER] = { [UKBD_INTR_DT_0] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &ukbd_intr_callback, }, [UKBD_INTR_DT_1] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &ukbd_intr_callback, }, [UKBD_CTRL_LED] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + UKBD_BUFFER_SIZE, .callback = &ukbd_set_leds_callback, .timeout = 1000, /* 1 second */ }, }; /* A match on these entries will load ukbd */ static const STRUCT_USB_HOST_ID __used ukbd_devs[] = { {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD),}, }; static int ukbd_probe(device_t dev) { keyboard_switch_t *sw = kbd_get_switch(UKBD_DRIVER_NAME); struct usb_attach_arg *uaa = device_get_ivars(dev); void *d_ptr; int error; uint16_t d_len; UKBD_LOCK_ASSERT(); DPRINTFN(11, "\n"); if (sw == NULL) { return (ENXIO); } if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bInterfaceClass != UICLASS_HID) return (ENXIO); if (usb_test_quirk(uaa, UQ_KBD_IGNORE)) return (ENXIO); if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && (uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD)) return (BUS_PROBE_DEFAULT); error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (error) return (ENXIO); if (hid_is_keyboard(d_ptr, d_len)) { if (hid_is_mouse(d_ptr, d_len)) { /* * NOTE: We currently don't support USB mouse * and USB keyboard on the same USB endpoint. * Let "ums" driver win. */ error = ENXIO; } else { error = BUS_PROBE_DEFAULT; } } else { error = ENXIO; } free(d_ptr, M_TEMP); return (error); } static void ukbd_parse_hid(struct ukbd_softc *sc, const uint8_t *ptr, uint32_t len) { uint32_t flags; uint32_t key; /* reset detected bits */ sc->sc_flags &= ~UKBD_FLAG_HID_MASK; /* reset detected keys */ memset(sc->sc_loc_key_valid, 0, sizeof(sc->sc_loc_key_valid)); /* check if there is an ID byte */ sc->sc_kbd_size = hid_report_size(ptr, len, hid_input, &sc->sc_kbd_id); /* investigate if this is an Apple Keyboard */ if (hid_locate(ptr, len, HID_USAGE2(HUP_CONSUMER, HUG_APPLE_EJECT), hid_input, 0, &sc->sc_loc_apple_eject, &flags, &sc->sc_id_apple_eject)) { if (flags & HIO_VARIABLE) sc->sc_flags |= UKBD_FLAG_APPLE_EJECT | UKBD_FLAG_APPLE_SWAP; DPRINTFN(1, "Found Apple eject-key\n"); } if (hid_locate(ptr, len, HID_USAGE2(0xFFFF, 0x0003), hid_input, 0, &sc->sc_loc_apple_fn, &flags, &sc->sc_id_apple_fn)) { if (flags & HIO_VARIABLE) sc->sc_flags |= UKBD_FLAG_APPLE_FN; DPRINTFN(1, "Found Apple FN-key\n"); } /* figure out event buffer */ if (hid_locate(ptr, len, HID_USAGE2(HUP_KEYBOARD, 0x00), hid_input, 0, &sc->sc_loc_key[0], &flags, &sc->sc_id_loc_key[0])) { if (flags & HIO_VARIABLE) { DPRINTFN(1, "Ignoring keyboard event control\n"); } else { sc->sc_loc_key_valid[0] |= 1; DPRINTFN(1, "Found keyboard event array\n"); } } /* figure out the keys */ for (key = 1; key != UKBD_NKEYCODE; key++) { if (hid_locate(ptr, len, HID_USAGE2(HUP_KEYBOARD, key), hid_input, 0, &sc->sc_loc_key[key], &flags, &sc->sc_id_loc_key[key])) { if (flags & HIO_VARIABLE) { sc->sc_loc_key_valid[key / 64] |= 1ULL << (key % 64); DPRINTFN(1, "Found key 0x%02x\n", key); } } } /* figure out leds on keyboard */ sc->sc_led_size = hid_report_size(ptr, len, hid_output, NULL); if (hid_locate(ptr, len, HID_USAGE2(HUP_LEDS, 0x01), hid_output, 0, &sc->sc_loc_numlock, &flags, &sc->sc_id_numlock)) { if (flags & HIO_VARIABLE) sc->sc_flags |= UKBD_FLAG_NUMLOCK; DPRINTFN(1, "Found keyboard numlock\n"); } if (hid_locate(ptr, len, HID_USAGE2(HUP_LEDS, 0x02), hid_output, 0, &sc->sc_loc_capslock, &flags, &sc->sc_id_capslock)) { if (flags & HIO_VARIABLE) sc->sc_flags |= UKBD_FLAG_CAPSLOCK; DPRINTFN(1, "Found keyboard capslock\n"); } if (hid_locate(ptr, len, HID_USAGE2(HUP_LEDS, 0x03), hid_output, 0, &sc->sc_loc_scrolllock, &flags, &sc->sc_id_scrolllock)) { if (flags & HIO_VARIABLE) sc->sc_flags |= UKBD_FLAG_SCROLLLOCK; DPRINTFN(1, "Found keyboard scrolllock\n"); } } static int ukbd_attach(device_t dev) { struct ukbd_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int unit = device_get_unit(dev); keyboard_t *kbd = &sc->sc_kbd; void *hid_ptr = NULL; usb_error_t err; uint16_t n; uint16_t hid_len; #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; int i; #endif #ifdef USB_DEBUG int rate; #endif UKBD_LOCK_ASSERT(); kbd_init_struct(kbd, UKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0); kbd->kb_data = (void *)sc; device_set_usb_desc(dev); sc->sc_udev = uaa->device; sc->sc_iface = uaa->iface; sc->sc_iface_index = uaa->info.bIfaceIndex; sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_mode = K_XLATE; usb_callout_init_mtx(&sc->sc_callout, &Giant, 0); #ifdef UKBD_NO_POLLING err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, ukbd_config, UKBD_N_TRANSFER, sc, &Giant); #else /* * Setup the UKBD USB transfers one by one, so they are memory * independent which allows for handling panics triggered by * the keyboard driver itself, typically via CTRL+ALT+ESC * sequences. Or if the USB keyboard driver was processing a * key at the moment of panic. */ for (n = 0; n != UKBD_N_TRANSFER; n++) { err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer + n, ukbd_config + n, 1, sc, &Giant); if (err) break; } #endif if (err) { DPRINTF("error=%s\n", usbd_errstr(err)); goto detach; } /* setup default keyboard maps */ sc->sc_keymap = key_map; sc->sc_accmap = accent_map; for (n = 0; n < UKBD_NFKEY; n++) { sc->sc_fkeymap[n] = fkey_tab[n]; } kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap, sc->sc_fkeymap, UKBD_NFKEY); KBD_FOUND_DEVICE(kbd); ukbd_clear_state(kbd); /* * FIXME: set the initial value for lock keys in "sc_state" * according to the BIOS data? */ KBD_PROBE_DONE(kbd); /* get HID descriptor */ err = usbd_req_get_hid_desc(uaa->device, NULL, &hid_ptr, &hid_len, M_TEMP, uaa->info.bIfaceIndex); if (err == 0) { DPRINTF("Parsing HID descriptor of %d bytes\n", (int)hid_len); ukbd_parse_hid(sc, hid_ptr, hid_len); free(hid_ptr, M_TEMP); } /* check if we should use the boot protocol */ if (usb_test_quirk(uaa, UQ_KBD_BOOTPROTO) || (err != 0) || ukbd_any_key_valid(sc) == false) { DPRINTF("Forcing boot protocol\n"); err = usbd_req_set_protocol(sc->sc_udev, NULL, sc->sc_iface_index, 0); if (err != 0) { DPRINTF("Set protocol error=%s (ignored)\n", usbd_errstr(err)); } ukbd_parse_hid(sc, ukbd_boot_desc, sizeof(ukbd_boot_desc)); } /* ignore if SETIDLE fails, hence it is not crucial */ usbd_req_set_idle(sc->sc_udev, NULL, sc->sc_iface_index, 0, 0); ukbd_ioctl(kbd, KDSETLED, (caddr_t)&sc->sc_state); KBD_INIT_DONE(kbd); if (kbd_register(kbd) < 0) { goto detach; } KBD_CONFIG_DONE(kbd); ukbd_enable(kbd); #ifdef KBD_INSTALL_CDEV if (kbd_attach(kbd)) { goto detach; } #endif #ifdef EVDEV_SUPPORT evdev = evdev_alloc(); evdev_set_name(evdev, device_get_desc(dev)); evdev_set_phys(evdev, device_get_nameunit(dev)); evdev_set_id(evdev, BUS_USB, uaa->info.idVendor, uaa->info.idProduct, 0); evdev_set_serial(evdev, usb_get_serial(uaa->device)); evdev_set_methods(evdev, kbd, &ukbd_evdev_methods); evdev_support_event(evdev, EV_SYN); evdev_support_event(evdev, EV_KEY); if (sc->sc_flags & (UKBD_FLAG_NUMLOCK | UKBD_FLAG_CAPSLOCK | UKBD_FLAG_SCROLLLOCK)) evdev_support_event(evdev, EV_LED); evdev_support_event(evdev, EV_REP); for (i = 0x00; i <= 0xFF; i++) evdev_support_key(evdev, evdev_hid2key(i)); if (sc->sc_flags & UKBD_FLAG_NUMLOCK) evdev_support_led(evdev, LED_NUML); if (sc->sc_flags & UKBD_FLAG_CAPSLOCK) evdev_support_led(evdev, LED_CAPSL); if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK) evdev_support_led(evdev, LED_SCROLLL); if (evdev_register_mtx(evdev, &Giant)) evdev_free(evdev); else sc->sc_evdev = evdev; #endif sc->sc_flags |= UKBD_FLAG_ATTACHED; if (bootverbose) { kbdd_diag(kbd, bootverbose); } #ifdef USB_DEBUG /* check for polling rate override */ rate = ukbd_pollrate; if (rate > 0) { if (rate > 1000) rate = 1; else rate = 1000 / rate; /* set new polling interval in ms */ usbd_xfer_set_interval(sc->sc_xfer[UKBD_INTR_DT_0], rate); usbd_xfer_set_interval(sc->sc_xfer[UKBD_INTR_DT_1], rate); } #endif /* start the keyboard */ usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_0]); usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_1]); return (0); /* success */ detach: ukbd_detach(dev); return (ENXIO); /* error */ } static int ukbd_detach(device_t dev) { struct ukbd_softc *sc = device_get_softc(dev); int error; UKBD_LOCK_ASSERT(); DPRINTF("\n"); sc->sc_flags |= UKBD_FLAG_GONE; usb_callout_stop(&sc->sc_callout); /* kill any stuck keys */ if (sc->sc_flags & UKBD_FLAG_ATTACHED) { /* stop receiving events from the USB keyboard */ usbd_transfer_stop(sc->sc_xfer[UKBD_INTR_DT_0]); usbd_transfer_stop(sc->sc_xfer[UKBD_INTR_DT_1]); /* release all leftover keys, if any */ memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); /* process releasing of all keys */ ukbd_interrupt(sc); } ukbd_disable(&sc->sc_kbd); #ifdef KBD_INSTALL_CDEV if (sc->sc_flags & UKBD_FLAG_ATTACHED) { error = kbd_detach(&sc->sc_kbd); if (error) { /* usb attach cannot return an error */ device_printf(dev, "WARNING: kbd_detach() " "returned non-zero! (ignored)\n"); } } #endif #ifdef EVDEV_SUPPORT evdev_free(sc->sc_evdev); #endif if (KBD_IS_CONFIGURED(&sc->sc_kbd)) { error = kbd_unregister(&sc->sc_kbd); if (error) { /* usb attach cannot return an error */ device_printf(dev, "WARNING: kbd_unregister() " "returned non-zero! (ignored)\n"); } } sc->sc_kbd.kb_flags = 0; usbd_transfer_unsetup(sc->sc_xfer, UKBD_N_TRANSFER); usb_callout_drain(&sc->sc_callout); DPRINTF("%s: disconnected\n", device_get_nameunit(dev)); return (0); } static int ukbd_resume(device_t dev) { struct ukbd_softc *sc = device_get_softc(dev); UKBD_LOCK_ASSERT(); ukbd_clear_state(&sc->sc_kbd); return (0); } #ifdef EVDEV_SUPPORT static void ukbd_ev_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, int32_t value) { keyboard_t *kbd = evdev_get_softc(evdev); if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && (type == EV_LED || type == EV_REP)) { mtx_lock(&Giant); kbd_ev_event(kbd, type, code, value); mtx_unlock(&Giant); } } #endif /* early keyboard probe, not supported */ static int ukbd_configure(int flags) { return (0); } /* detect a keyboard, not used */ static int ukbd__probe(int unit, void *arg, int flags) { return (ENXIO); } /* reset and initialize the device, not used */ static int ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) { return (ENXIO); } /* test the interface to the device, not used */ static int ukbd_test_if(keyboard_t *kbd) { return (0); } /* finish using this keyboard, not used */ static int ukbd_term(keyboard_t *kbd) { return (ENXIO); } /* keyboard interrupt routine, not used */ static int ukbd_intr(keyboard_t *kbd, void *arg) { return (0); } /* lock the access to the keyboard, not used */ static int ukbd_lock(keyboard_t *kbd, int lock) { return (1); } /* * Enable the access to the device; until this function is called, * the client cannot read from the keyboard. */ static int ukbd_enable(keyboard_t *kbd) { UKBD_LOCK(); KBD_ACTIVATE(kbd); UKBD_UNLOCK(); return (0); } /* disallow the access to the device */ static int ukbd_disable(keyboard_t *kbd) { UKBD_LOCK(); KBD_DEACTIVATE(kbd); UKBD_UNLOCK(); return (0); } /* check if data is waiting */ /* Currently unused. */ static int ukbd_check(keyboard_t *kbd) { struct ukbd_softc *sc = kbd->kb_data; UKBD_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (0); if (sc->sc_flags & UKBD_FLAG_POLLING) ukbd_do_poll(sc, 0); #ifdef UKBD_EMULATE_ATSCANCODE if (sc->sc_buffered_char[0]) { return (1); } #endif if (sc->sc_inputs > 0) { return (1); } return (0); } /* check if char is waiting */ static int ukbd_check_char_locked(keyboard_t *kbd) { struct ukbd_softc *sc = kbd->kb_data; UKBD_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (0); if ((sc->sc_composed_char > 0) && (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) { return (1); } return (ukbd_check(kbd)); } static int ukbd_check_char(keyboard_t *kbd) { int result; UKBD_LOCK(); result = ukbd_check_char_locked(kbd); UKBD_UNLOCK(); return (result); } /* read one byte from the keyboard if it's allowed */ /* Currently unused. */ static int ukbd_read(keyboard_t *kbd, int wait) { struct ukbd_softc *sc = kbd->kb_data; int32_t usbcode; #ifdef UKBD_EMULATE_ATSCANCODE uint32_t keycode; uint32_t scancode; #endif UKBD_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (-1); #ifdef UKBD_EMULATE_ATSCANCODE if (sc->sc_buffered_char[0]) { scancode = sc->sc_buffered_char[0]; if (scancode & SCAN_PREFIX) { sc->sc_buffered_char[0] &= ~SCAN_PREFIX; return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; sc->sc_buffered_char[1] = 0; return (scancode); } #endif /* UKBD_EMULATE_ATSCANCODE */ /* XXX */ usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1); if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1)) return (-1); ++(kbd->kb_count); #ifdef UKBD_EMULATE_ATSCANCODE keycode = ukbd_atkeycode(usbcode, sc->sc_ndata.bitmap); if (keycode == NN) { return -1; } return (ukbd_key2scan(sc, keycode, sc->sc_ndata.bitmap, (usbcode & KEY_RELEASE))); #else /* !UKBD_EMULATE_ATSCANCODE */ return (usbcode); #endif /* UKBD_EMULATE_ATSCANCODE */ } /* read char from the keyboard */ static uint32_t ukbd_read_char_locked(keyboard_t *kbd, int wait) { struct ukbd_softc *sc = kbd->kb_data; uint32_t action; uint32_t keycode; int32_t usbcode; #ifdef UKBD_EMULATE_ATSCANCODE uint32_t scancode; #endif UKBD_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (NOKEY); next_code: /* do we have a composed char to return ? */ if ((sc->sc_composed_char > 0) && (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) { action = sc->sc_composed_char; sc->sc_composed_char = 0; if (action > 0xFF) { goto errkey; } goto done; } #ifdef UKBD_EMULATE_ATSCANCODE /* do we have a pending raw scan code? */ if (sc->sc_mode == K_RAW) { scancode = sc->sc_buffered_char[0]; if (scancode) { if (scancode & SCAN_PREFIX) { sc->sc_buffered_char[0] = (scancode & ~SCAN_PREFIX); return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; sc->sc_buffered_char[1] = 0; return (scancode); } } #endif /* UKBD_EMULATE_ATSCANCODE */ /* see if there is something in the keyboard port */ /* XXX */ usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1); if (usbcode == -1) { return (NOKEY); } ++kbd->kb_count; #ifdef UKBD_EMULATE_ATSCANCODE /* USB key index -> key code -> AT scan code */ keycode = ukbd_atkeycode(usbcode, sc->sc_ndata.bitmap); if (keycode == NN) { return (NOKEY); } /* return an AT scan code for the K_RAW mode */ if (sc->sc_mode == K_RAW) { return (ukbd_key2scan(sc, keycode, sc->sc_ndata.bitmap, (usbcode & KEY_RELEASE))); } #else /* !UKBD_EMULATE_ATSCANCODE */ /* return the byte as is for the K_RAW mode */ if (sc->sc_mode == K_RAW) { return (usbcode); } /* USB key index -> key code */ keycode = ukbd_trtab[KEY_INDEX(usbcode)]; if (keycode == NN) { return (NOKEY); } #endif /* UKBD_EMULATE_ATSCANCODE */ switch (keycode) { case 0x38: /* left alt (compose key) */ if (usbcode & KEY_RELEASE) { if (sc->sc_flags & UKBD_FLAG_COMPOSE) { sc->sc_flags &= ~UKBD_FLAG_COMPOSE; if (sc->sc_composed_char > 0xFF) { sc->sc_composed_char = 0; } } } else { if (!(sc->sc_flags & UKBD_FLAG_COMPOSE)) { sc->sc_flags |= UKBD_FLAG_COMPOSE; sc->sc_composed_char = 0; } } break; } /* return the key code in the K_CODE mode */ if (usbcode & KEY_RELEASE) { keycode |= SCAN_RELEASE; } if (sc->sc_mode == K_CODE) { return (keycode); } /* compose a character code */ if (sc->sc_flags & UKBD_FLAG_COMPOSE) { switch (keycode) { /* key pressed, process it */ case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ sc->sc_composed_char *= 10; sc->sc_composed_char += keycode - 0x40; goto check_composed; case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ sc->sc_composed_char *= 10; sc->sc_composed_char += keycode - 0x47; goto check_composed; case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ sc->sc_composed_char *= 10; sc->sc_composed_char += keycode - 0x4E; goto check_composed; case 0x52: /* keypad 0 */ sc->sc_composed_char *= 10; goto check_composed; /* key released, no interest here */ case SCAN_RELEASE | 0x47: case SCAN_RELEASE | 0x48: case SCAN_RELEASE | 0x49: /* keypad 7,8,9 */ case SCAN_RELEASE | 0x4B: case SCAN_RELEASE | 0x4C: case SCAN_RELEASE | 0x4D: /* keypad 4,5,6 */ case SCAN_RELEASE | 0x4F: case SCAN_RELEASE | 0x50: case SCAN_RELEASE | 0x51: /* keypad 1,2,3 */ case SCAN_RELEASE | 0x52: /* keypad 0 */ goto next_code; case 0x38: /* left alt key */ break; default: if (sc->sc_composed_char > 0) { sc->sc_flags &= ~UKBD_FLAG_COMPOSE; sc->sc_composed_char = 0; goto errkey; } break; } } /* keycode to key action */ action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), (keycode & SCAN_RELEASE), &sc->sc_state, &sc->sc_accents); if (action == NOKEY) { goto next_code; } done: return (action); check_composed: if (sc->sc_composed_char <= 0xFF) { goto next_code; } errkey: return (ERRKEY); } /* Currently wait is always false. */ static uint32_t ukbd_read_char(keyboard_t *kbd, int wait) { uint32_t keycode; UKBD_LOCK(); keycode = ukbd_read_char_locked(kbd, wait); UKBD_UNLOCK(); return (keycode); } /* some useful control functions */ static int ukbd_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg) { struct ukbd_softc *sc = kbd->kb_data; int i; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; #endif UKBD_LOCK_ASSERT(); switch (cmd) { case KDGKBMODE: /* get keyboard mode */ *(int *)arg = sc->sc_mode; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 7): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBMODE: /* set keyboard mode */ switch (*(int *)arg) { case K_XLATE: if (sc->sc_mode != K_XLATE) { /* make lock key state and LED state match */ sc->sc_state &= ~LOCK_MASK; sc->sc_state |= KBD_LED_VAL(kbd); } /* FALLTHROUGH */ case K_RAW: case K_CODE: if (sc->sc_mode != *(int *)arg) { if ((sc->sc_flags & UKBD_FLAG_POLLING) == 0) ukbd_clear_state(kbd); sc->sc_mode = *(int *)arg; } break; default: return (EINVAL); } break; case KDGETLED: /* get keyboard LED */ *(int *)arg = KBD_LED_VAL(kbd); break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 66): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETLED: /* set keyboard LED */ /* NOTE: lock key state in "sc_state" won't be changed */ if (*(int *)arg & ~LOCK_MASK) return (EINVAL); i = *(int *)arg; /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ if (sc->sc_mode == K_XLATE && kbd->kb_keymap->n_keys > ALTGR_OFFSET) { if (i & ALKED) i |= CLKED; else i &= ~CLKED; } if (KBD_HAS_DEVICE(kbd)) ukbd_set_leds(sc, i); KBD_LED_VAL(kbd) = *(int *)arg; break; case KDGKBSTATE: /* get lock key state */ *(int *)arg = sc->sc_state & LOCK_MASK; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 20): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBSTATE: /* set lock key state */ if (*(int *)arg & ~LOCK_MASK) { return (EINVAL); } sc->sc_state &= ~LOCK_MASK; sc->sc_state |= *(int *)arg; /* set LEDs and quit */ return (ukbd_ioctl(kbd, KDSETLED, arg)); case KDSETREPEAT: /* set keyboard repeat rate (new * interface) */ if (!KBD_HAS_DEVICE(kbd)) { return (0); } /* * Convert negative, zero and tiny args to the same limits * as atkbd. We could support delays of 1 msec, but * anything much shorter than the shortest atkbd value * of 250.34 is almost unusable as well as incompatible. */ kbd->kb_delay1 = imax(((int *)arg)[0], 250); kbd->kb_delay2 = imax(((int *)arg)[1], 34); #ifdef EVDEV_SUPPORT if (sc->sc_evdev != NULL) evdev_push_repeats(sc->sc_evdev, kbd); #endif return (0); #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 67): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETRAD: /* set keyboard repeat rate (old * interface) */ return (ukbd_set_typematic(kbd, *(int *)arg)); case PIO_KEYMAP: /* set keyboard translation table */ case OPIO_KEYMAP: /* set keyboard translation table * (compat) */ case PIO_KEYMAPENT: /* set keyboard translation table * entry */ case PIO_DEADKEYMAP: /* set accent key translation table */ sc->sc_accents = 0; /* FALLTHROUGH */ default: return (genkbd_commonioctl(kbd, cmd, arg)); } return (0); } static int ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) { int result; /* * XXX Check if someone is calling us from a critical section: */ if (curthread->td_critnest != 0) return (EDEADLK); /* * XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any * context where printf(9) can be called, which among other things * includes interrupt filters and threads with any kinds of locks * already held. For this reason it would be dangerous to acquire * the Giant here unconditionally. On the other hand we have to * have it to handle the ioctl. * So we make our best effort to auto-detect whether we can grab * the Giant or not. Blame syscons(4) for this. */ switch (cmd) { case KDGKBSTATE: case KDSKBSTATE: case KDSETLED: if (!mtx_owned(&Giant) && !USB_IN_POLLING_MODE_FUNC()) return (EDEADLK); /* best I could come up with */ /* FALLTHROUGH */ default: UKBD_LOCK(); result = ukbd_ioctl_locked(kbd, cmd, arg); UKBD_UNLOCK(); return (result); } } /* clear the internal state of the keyboard */ static void ukbd_clear_state(keyboard_t *kbd) { struct ukbd_softc *sc = kbd->kb_data; UKBD_LOCK_ASSERT(); sc->sc_flags &= ~(UKBD_FLAG_COMPOSE | UKBD_FLAG_POLLING); sc->sc_state &= LOCK_MASK; /* preserve locking key state */ sc->sc_accents = 0; sc->sc_composed_char = 0; #ifdef UKBD_EMULATE_ATSCANCODE sc->sc_buffered_char[0] = 0; sc->sc_buffered_char[1] = 0; #endif memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); memset(&sc->sc_odata, 0, sizeof(sc->sc_odata)); sc->sc_repeat_time = 0; sc->sc_repeat_key = 0; } /* save the internal state, not used */ static int ukbd_get_state(keyboard_t *kbd, void *buf, size_t len) { return (len == 0) ? 1 : -1; } /* set the internal state, not used */ static int ukbd_set_state(keyboard_t *kbd, void *buf, size_t len) { return (EINVAL); } static int ukbd_poll(keyboard_t *kbd, int on) { struct ukbd_softc *sc = kbd->kb_data; UKBD_LOCK(); /* * Keep a reference count on polling to allow recursive * cngrab() during a panic for example. */ if (on) sc->sc_polling++; else if (sc->sc_polling > 0) sc->sc_polling--; if (sc->sc_polling != 0) { sc->sc_flags |= UKBD_FLAG_POLLING; sc->sc_poll_thread = curthread; } else { sc->sc_flags &= ~UKBD_FLAG_POLLING; sc->sc_delay = 0; } UKBD_UNLOCK(); return (0); } /* local functions */ static void ukbd_set_leds(struct ukbd_softc *sc, uint8_t leds) { UKBD_LOCK_ASSERT(); DPRINTF("leds=0x%02x\n", leds); sc->sc_leds = leds; sc->sc_flags |= UKBD_FLAG_SET_LEDS; /* start transfer, if not already started */ usbd_transfer_start(sc->sc_xfer[UKBD_CTRL_LED]); } static int ukbd_set_typematic(keyboard_t *kbd, int code) { #ifdef EVDEV_SUPPORT struct ukbd_softc *sc = kbd->kb_data; #endif static const int delays[] = {250, 500, 750, 1000}; static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63, 68, 76, 84, 92, 100, 110, 118, 126, 136, 152, 168, 184, 200, 220, 236, 252, 272, 304, 336, 368, 400, 440, 472, 504}; if (code & ~0x7f) { return (EINVAL); } kbd->kb_delay1 = delays[(code >> 5) & 3]; kbd->kb_delay2 = rates[code & 0x1f]; #ifdef EVDEV_SUPPORT if (sc->sc_evdev != NULL) evdev_push_repeats(sc->sc_evdev, kbd); #endif return (0); } #ifdef UKBD_EMULATE_ATSCANCODE static uint32_t ukbd_atkeycode(int usbcode, const uint64_t *bitmap) { uint32_t keycode; keycode = ukbd_trtab[KEY_INDEX(usbcode)]; /* * Translate Alt-PrintScreen to SysRq. * * Some or all AT keyboards connected through USB have already * mapped Alted PrintScreens to an unusual usbcode (0x8a). * ukbd_trtab translates this to 0x7e, and key2scan() would * translate that to 0x79 (Intl' 4). Assume that if we have * an Alted 0x7e here then it actually is an Alted PrintScreen. * * The usual usbcode for all PrintScreens is 0x46. ukbd_trtab * translates this to 0x5c, so the Alt check to classify 0x5c * is routine. */ if ((keycode == 0x5c || keycode == 0x7e) && (UKBD_KEY_PRESSED(bitmap, 0xe2 /* ALT-L */) || UKBD_KEY_PRESSED(bitmap, 0xe6 /* ALT-R */))) return (0x54); return (keycode); } static int ukbd_key2scan(struct ukbd_softc *sc, int code, const uint64_t *bitmap, int up) { static const int scan[] = { /* 89 */ 0x11c, /* Enter */ /* 90-99 */ 0x11d, /* Ctrl-R */ 0x135, /* Divide */ 0x137, /* PrintScreen */ 0x138, /* Alt-R */ 0x147, /* Home */ 0x148, /* Up */ 0x149, /* PageUp */ 0x14b, /* Left */ 0x14d, /* Right */ 0x14f, /* End */ /* 100-109 */ 0x150, /* Down */ 0x151, /* PageDown */ 0x152, /* Insert */ 0x153, /* Delete */ 0x146, /* Pause/Break */ 0x15b, /* Win_L(Super_L) */ 0x15c, /* Win_R(Super_R) */ 0x15d, /* Application(Menu) */ /* SUN TYPE 6 USB KEYBOARD */ 0x168, /* Sun Type 6 Help */ 0x15e, /* Sun Type 6 Stop */ /* 110 - 119 */ 0x15f, /* Sun Type 6 Again */ 0x160, /* Sun Type 6 Props */ 0x161, /* Sun Type 6 Undo */ 0x162, /* Sun Type 6 Front */ 0x163, /* Sun Type 6 Copy */ 0x164, /* Sun Type 6 Open */ 0x165, /* Sun Type 6 Paste */ 0x166, /* Sun Type 6 Find */ 0x167, /* Sun Type 6 Cut */ 0x125, /* Sun Type 6 Mute */ /* 120 - 130 */ 0x11f, /* Sun Type 6 VolumeDown */ 0x11e, /* Sun Type 6 VolumeUp */ 0x120, /* Sun Type 6 PowerDown */ /* Japanese 106/109 keyboard */ 0x73, /* Keyboard Intl' 1 (backslash / underscore) */ 0x70, /* Keyboard Intl' 2 (Katakana / Hiragana) */ 0x7d, /* Keyboard Intl' 3 (Yen sign) (Not using in jp106/109) */ 0x79, /* Keyboard Intl' 4 (Henkan) */ 0x7b, /* Keyboard Intl' 5 (Muhenkan) */ 0x5c, /* Keyboard Intl' 6 (Keypad ,) (For PC-9821 layout) */ 0x71, /* Apple Keyboard JIS (Kana) */ 0x72, /* Apple Keyboard JIS (Eisu) */ }; if ((code >= 89) && (code < (int)(89 + nitems(scan)))) { code = scan[code - 89]; } /* PrintScreen */ if (code == 0x137 && (!( UKBD_KEY_PRESSED(bitmap, 0xe0 /* CTRL-L */) || UKBD_KEY_PRESSED(bitmap, 0xe4 /* CTRL-R */) || UKBD_KEY_PRESSED(bitmap, 0xe1 /* SHIFT-L */) || UKBD_KEY_PRESSED(bitmap, 0xe5 /* SHIFT-R */)))) { code |= SCAN_PREFIX_SHIFT; } /* Pause/Break */ if ((code == 0x146) && (!( UKBD_KEY_PRESSED(bitmap, 0xe0 /* CTRL-L */) || UKBD_KEY_PRESSED(bitmap, 0xe4 /* CTRL-R */)))) { code = (0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL); } code |= (up ? SCAN_RELEASE : SCAN_PRESS); if (code & SCAN_PREFIX) { if (code & SCAN_PREFIX_CTL) { /* Ctrl */ sc->sc_buffered_char[0] = (0x1d | (code & SCAN_RELEASE)); sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX); } else if (code & SCAN_PREFIX_SHIFT) { /* Shift */ sc->sc_buffered_char[0] = (0x2a | (code & SCAN_RELEASE)); sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX_SHIFT); } else { sc->sc_buffered_char[0] = (code & ~SCAN_PREFIX); sc->sc_buffered_char[1] = 0; } return ((code & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } return (code); } #endif /* UKBD_EMULATE_ATSCANCODE */ static keyboard_switch_t ukbdsw = { .probe = &ukbd__probe, .init = &ukbd_init, .term = &ukbd_term, .intr = &ukbd_intr, .test_if = &ukbd_test_if, .enable = &ukbd_enable, .disable = &ukbd_disable, .read = &ukbd_read, .check = &ukbd_check, .read_char = &ukbd_read_char, .check_char = &ukbd_check_char, .ioctl = &ukbd_ioctl, .lock = &ukbd_lock, .clear_state = &ukbd_clear_state, .get_state = &ukbd_get_state, .set_state = &ukbd_set_state, .poll = &ukbd_poll, }; KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure); static int ukbd_driver_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kbd_add_driver(&ukbd_kbd_driver); break; case MOD_UNLOAD: kbd_delete_driver(&ukbd_kbd_driver); break; } return (0); } static devclass_t ukbd_devclass; static device_method_t ukbd_methods[] = { DEVMETHOD(device_probe, ukbd_probe), DEVMETHOD(device_attach, ukbd_attach), DEVMETHOD(device_detach, ukbd_detach), DEVMETHOD(device_resume, ukbd_resume), DEVMETHOD_END }; static driver_t ukbd_driver = { .name = "ukbd", .methods = ukbd_methods, .size = sizeof(struct ukbd_softc), }; DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0); MODULE_DEPEND(ukbd, usb, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(ukbd, evdev, 1, 1, 1); #endif MODULE_VERSION(ukbd, 1); USB_PNP_HOST_INFO(ukbd_devs); Index: head/sys/dev/usb/input/ums.c =================================================================== --- head/sys/dev/usb/input/ums.c (revision 357971) +++ head/sys/dev/usb/input/ums.c (revision 357972) @@ -1,1232 +1,1234 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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 __FBSDID("$FreeBSD$"); /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include "opt_evdev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ums_debug #include #include #ifdef EVDEV_SUPPORT #include #include #endif #include #include #include #ifdef USB_DEBUG static int ums_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ums"); SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RWTUN, &ums_debug, 0, "Debug level"); #endif #define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE) #define MOUSE_FLAGS (HIO_RELATIVE) #define UMS_BUF_SIZE 8 /* bytes */ #define UMS_IFQ_MAXLEN 50 /* units */ #define UMS_BUTTON_MAX 31 /* exclusive, must be less than 32 */ #define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i)) #define UMS_INFO_MAX 2 /* maximum number of HID sets */ enum { UMS_INTR_DT, UMS_N_TRANSFER, }; struct ums_info { struct hid_location sc_loc_w; struct hid_location sc_loc_x; struct hid_location sc_loc_y; struct hid_location sc_loc_z; struct hid_location sc_loc_t; struct hid_location sc_loc_btn[UMS_BUTTON_MAX]; uint32_t sc_flags; #define UMS_FLAG_X_AXIS 0x0001 #define UMS_FLAG_Y_AXIS 0x0002 #define UMS_FLAG_Z_AXIS 0x0004 #define UMS_FLAG_T_AXIS 0x0008 #define UMS_FLAG_SBU 0x0010 /* spurious button up events */ #define UMS_FLAG_REVZ 0x0020 /* Z-axis is reversed */ #define UMS_FLAG_W_AXIS 0x0040 uint8_t sc_iid_w; uint8_t sc_iid_x; uint8_t sc_iid_y; uint8_t sc_iid_z; uint8_t sc_iid_t; uint8_t sc_iid_btn[UMS_BUTTON_MAX]; uint8_t sc_buttons; }; struct ums_softc { struct usb_fifo_sc sc_fifo; struct mtx sc_mtx; struct usb_callout sc_callout; struct ums_info sc_info[UMS_INFO_MAX]; mousehw_t sc_hw; mousemode_t sc_mode; mousestatus_t sc_status; struct usb_xfer *sc_xfer[UMS_N_TRANSFER]; int sc_pollrate; int sc_fflags; #ifdef EVDEV_SUPPORT int sc_evflags; #define UMS_EVDEV_OPENED 1 #endif uint8_t sc_buttons; uint8_t sc_iid; uint8_t sc_temp[64]; #ifdef EVDEV_SUPPORT struct evdev_dev *sc_evdev; #endif }; static void ums_put_queue_timeout(void *__sc); static usb_callback_t ums_intr_callback; static device_probe_t ums_probe; static device_attach_t ums_attach; static device_detach_t ums_detach; static usb_fifo_cmd_t ums_fifo_start_read; static usb_fifo_cmd_t ums_fifo_stop_read; static usb_fifo_open_t ums_fifo_open; static usb_fifo_close_t ums_fifo_close; static usb_fifo_ioctl_t ums_fifo_ioctl; #ifdef EVDEV_SUPPORT static evdev_open_t ums_ev_open; static evdev_close_t ums_ev_close; static void ums_evdev_push(struct ums_softc *, int32_t, int32_t, int32_t, int32_t, int32_t); #endif static void ums_start_rx(struct ums_softc *); static void ums_stop_rx(struct ums_softc *); static void ums_put_queue(struct ums_softc *, int32_t, int32_t, int32_t, int32_t, int32_t); static int ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS); static struct usb_fifo_methods ums_fifo_methods = { .f_open = &ums_fifo_open, .f_close = &ums_fifo_close, .f_ioctl = &ums_fifo_ioctl, .f_start_read = &ums_fifo_start_read, .f_stop_read = &ums_fifo_stop_read, .basename[0] = "ums", }; #ifdef EVDEV_SUPPORT static const struct evdev_methods ums_evdev_methods = { .ev_open = &ums_ev_open, .ev_close = &ums_ev_close, }; #endif static void ums_put_queue_timeout(void *__sc) { struct ums_softc *sc = __sc; mtx_assert(&sc->sc_mtx, MA_OWNED); ums_put_queue(sc, 0, 0, 0, 0, 0); #ifdef EVDEV_SUPPORT ums_evdev_push(sc, 0, 0, 0, 0, 0); #endif } static void ums_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct ums_softc *sc = usbd_xfer_softc(xfer); struct ums_info *info = &sc->sc_info[0]; struct usb_page_cache *pc; uint8_t *buf = sc->sc_temp; int32_t buttons = 0; int32_t buttons_found = 0; #ifdef EVDEV_SUPPORT int32_t buttons_reported = 0; #endif int32_t dw = 0; int32_t dx = 0; int32_t dy = 0; int32_t dz = 0; int32_t dt = 0; uint8_t i; uint8_t id; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "sc=%p actlen=%d\n", sc, len); if (len > (int)sizeof(sc->sc_temp)) { DPRINTFN(6, "truncating large packet to %zu bytes\n", sizeof(sc->sc_temp)); len = sizeof(sc->sc_temp); } if (len == 0) goto tr_setup; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); DPRINTFN(6, "data = %02x %02x %02x %02x " "%02x %02x %02x %02x\n", (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0, (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0, (len > 4) ? buf[4] : 0, (len > 5) ? buf[5] : 0, (len > 6) ? buf[6] : 0, (len > 7) ? buf[7] : 0); if (sc->sc_iid) { id = *buf; len--; buf++; } else { id = 0; if (sc->sc_info[0].sc_flags & UMS_FLAG_SBU) { if ((*buf == 0x14) || (*buf == 0x15)) { goto tr_setup; } } } repeat: if ((info->sc_flags & UMS_FLAG_W_AXIS) && (id == info->sc_iid_w)) dw += hid_get_data(buf, len, &info->sc_loc_w); if ((info->sc_flags & UMS_FLAG_X_AXIS) && (id == info->sc_iid_x)) dx += hid_get_data(buf, len, &info->sc_loc_x); if ((info->sc_flags & UMS_FLAG_Y_AXIS) && (id == info->sc_iid_y)) dy -= hid_get_data(buf, len, &info->sc_loc_y); if ((info->sc_flags & UMS_FLAG_Z_AXIS) && (id == info->sc_iid_z)) { int32_t temp; temp = hid_get_data(buf, len, &info->sc_loc_z); if (info->sc_flags & UMS_FLAG_REVZ) temp = -temp; dz -= temp; } if ((info->sc_flags & UMS_FLAG_T_AXIS) && (id == info->sc_iid_t)) { dt += hid_get_data(buf, len, &info->sc_loc_t); /* T-axis is translated into button presses */ buttons_found |= (1UL << 5) | (1UL << 6); } for (i = 0; i < info->sc_buttons; i++) { uint32_t mask; mask = 1UL << UMS_BUT(i); /* check for correct button ID */ if (id != info->sc_iid_btn[i]) continue; /* check for button pressed */ if (hid_get_data(buf, len, &info->sc_loc_btn[i])) buttons |= mask; /* register button mask */ buttons_found |= mask; } if (++info != &sc->sc_info[UMS_INFO_MAX]) goto repeat; #ifdef EVDEV_SUPPORT buttons_reported = buttons; #endif /* keep old button value(s) for non-detected buttons */ buttons |= sc->sc_status.button & ~buttons_found; if (dx || dy || dz || dt || dw || (buttons != sc->sc_status.button)) { DPRINTFN(6, "x:%d y:%d z:%d t:%d w:%d buttons:0x%08x\n", dx, dy, dz, dt, dw, buttons); /* translate T-axis into button presses until further */ if (dt > 0) { ums_put_queue(sc, 0, 0, 0, 0, buttons); buttons |= 1UL << 6; } else if (dt < 0) { ums_put_queue(sc, 0, 0, 0, 0, buttons); buttons |= 1UL << 5; } sc->sc_status.button = buttons; sc->sc_status.dx += dx; sc->sc_status.dy += dy; sc->sc_status.dz += dz; /* * sc->sc_status.dt += dt; * no way to export this yet */ /* * The Qtronix keyboard has a built in PS/2 * port for a mouse. The firmware once in a * while posts a spurious button up * event. This event we ignore by doing a * timeout for 50 msecs. If we receive * dx=dy=dz=buttons=0 before we add the event * to the queue. In any other case we delete * the timeout event. */ if ((sc->sc_info[0].sc_flags & UMS_FLAG_SBU) && (dx == 0) && (dy == 0) && (dz == 0) && (dt == 0) && (dw == 0) && (buttons == 0)) { usb_callout_reset(&sc->sc_callout, hz / 20, &ums_put_queue_timeout, sc); } else { usb_callout_stop(&sc->sc_callout); ums_put_queue(sc, dx, dy, dz, dt, buttons); #ifdef EVDEV_SUPPORT ums_evdev_push(sc, dx, dy, dz, dt, buttons_reported); #endif } } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) == 0) { #ifdef EVDEV_SUPPORT if (sc->sc_evflags == 0) break; #else break; #endif } usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static const struct usb_config ums_config[UMS_N_TRANSFER] = { [UMS_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &ums_intr_callback, }, }; /* A match on these entries will load ums */ static const STRUCT_USB_HOST_ID __used ums_devs[] = { {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), USB_IFACE_PROTOCOL(UIPROTO_MOUSE),}, }; static int ums_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); void *d_ptr; int error; uint16_t d_len; DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bInterfaceClass != UICLASS_HID) return (ENXIO); if (usb_test_quirk(uaa, UQ_UMS_IGNORE)) return (ENXIO); if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE)) return (BUS_PROBE_DEFAULT); error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (error) return (ENXIO); if (hid_is_mouse(d_ptr, d_len)) error = BUS_PROBE_DEFAULT; else error = ENXIO; free(d_ptr, M_TEMP); return (error); } static void ums_hid_parse(struct ums_softc *sc, device_t dev, const uint8_t *buf, uint16_t len, uint8_t index) { struct ums_info *info = &sc->sc_info[index]; uint32_t flags; uint8_t i; uint8_t j; if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), hid_input, index, &info->sc_loc_x, &flags, &info->sc_iid_x)) { if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { info->sc_flags |= UMS_FLAG_X_AXIS; } } if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), hid_input, index, &info->sc_loc_y, &flags, &info->sc_iid_y)) { if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { info->sc_flags |= UMS_FLAG_Y_AXIS; } } /* Try the wheel first as the Z activator since it's tradition. */ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), hid_input, index, &info->sc_loc_z, &flags, &info->sc_iid_z) || hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), hid_input, index, &info->sc_loc_z, &flags, &info->sc_iid_z)) { if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { info->sc_flags |= UMS_FLAG_Z_AXIS; } /* * We might have both a wheel and Z direction, if so put * put the Z on the W coordinate. */ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), hid_input, index, &info->sc_loc_w, &flags, &info->sc_iid_w)) { if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { info->sc_flags |= UMS_FLAG_W_AXIS; } } } else if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), hid_input, index, &info->sc_loc_z, &flags, &info->sc_iid_z)) { if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { info->sc_flags |= UMS_FLAG_Z_AXIS; } } /* * The Microsoft Wireless Intellimouse 2.0 reports it's wheel * using 0x0048, which is HUG_TWHEEL, and seems to expect you * to know that the byte after the wheel is the tilt axis. * There are no other HID axis descriptors other than X,Y and * TWHEEL */ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), hid_input, index, &info->sc_loc_t, &flags, &info->sc_iid_t)) { info->sc_loc_t.pos += 8; if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { info->sc_flags |= UMS_FLAG_T_AXIS; } } else if (hid_locate(buf, len, HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN), hid_input, index, &info->sc_loc_t, &flags, &info->sc_iid_t)) { if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) info->sc_flags |= UMS_FLAG_T_AXIS; } /* figure out the number of buttons */ for (i = 0; i < UMS_BUTTON_MAX; i++) { if (!hid_locate(buf, len, HID_USAGE2(HUP_BUTTON, (i + 1)), hid_input, index, &info->sc_loc_btn[i], NULL, &info->sc_iid_btn[i])) { break; } } /* detect other buttons */ for (j = 0; (i < UMS_BUTTON_MAX) && (j < 2); i++, j++) { if (!hid_locate(buf, len, HID_USAGE2(HUP_MICROSOFT, (j + 1)), hid_input, index, &info->sc_loc_btn[i], NULL, &info->sc_iid_btn[i])) { break; } } info->sc_buttons = i; if (i > sc->sc_buttons) sc->sc_buttons = i; if (info->sc_flags == 0) return; /* announce information about the mouse */ device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n", (info->sc_buttons), (info->sc_flags & UMS_FLAG_X_AXIS) ? "X" : "", (info->sc_flags & UMS_FLAG_Y_AXIS) ? "Y" : "", (info->sc_flags & UMS_FLAG_Z_AXIS) ? "Z" : "", (info->sc_flags & UMS_FLAG_T_AXIS) ? "T" : "", (info->sc_flags & UMS_FLAG_W_AXIS) ? "W" : "", info->sc_iid_x); } static int ums_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ums_softc *sc = device_get_softc(dev); struct ums_info *info; void *d_ptr = NULL; int isize; int err; uint16_t d_len; uint8_t i; #ifdef USB_DEBUG uint8_t j; #endif DPRINTFN(11, "sc=%p\n", sc); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "ums lock", NULL, MTX_DEF | MTX_RECURSE); usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); /* * Force the report (non-boot) protocol. * * Mice without boot protocol support may choose not to implement * Set_Protocol at all; Ignore any error. */ err = usbd_req_set_protocol(uaa->device, NULL, uaa->info.bIfaceIndex, 1); err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, ums_config, UMS_N_TRANSFER, sc, &sc->sc_mtx); if (err) { DPRINTF("error=%s\n", usbd_errstr(err)); goto detach; } /* Get HID descriptor */ err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (err) { device_printf(dev, "error reading report description\n"); goto detach; } isize = hid_report_size(d_ptr, d_len, hid_input, &sc->sc_iid); /* * The Microsoft Wireless Notebook Optical Mouse seems to be in worse * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and * all of its other button positions are all off. It also reports that * it has two additional buttons and a tilt wheel. */ if (usb_test_quirk(uaa, UQ_MS_BAD_CLASS)) { sc->sc_iid = 0; info = &sc->sc_info[0]; info->sc_flags = (UMS_FLAG_X_AXIS | UMS_FLAG_Y_AXIS | UMS_FLAG_Z_AXIS | UMS_FLAG_SBU); info->sc_buttons = 3; isize = 5; /* 1st byte of descriptor report contains garbage */ info->sc_loc_x.pos = 16; info->sc_loc_x.size = 8; info->sc_loc_y.pos = 24; info->sc_loc_y.size = 8; info->sc_loc_z.pos = 32; info->sc_loc_z.size = 8; info->sc_loc_btn[0].pos = 8; info->sc_loc_btn[0].size = 1; info->sc_loc_btn[1].pos = 9; info->sc_loc_btn[1].size = 1; info->sc_loc_btn[2].pos = 10; info->sc_loc_btn[2].size = 1; /* Announce device */ device_printf(dev, "3 buttons and [XYZ] " "coordinates ID=0\n"); } else { /* Search the HID descriptor and announce device */ for (i = 0; i < UMS_INFO_MAX; i++) { ums_hid_parse(sc, dev, d_ptr, d_len, i); } } if (usb_test_quirk(uaa, UQ_MS_REVZ)) { info = &sc->sc_info[0]; /* Some wheels need the Z axis reversed. */ info->sc_flags |= UMS_FLAG_REVZ; } if (isize > (int)usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT])) { DPRINTF("WARNING: report size, %d bytes, is larger " "than interrupt size, %d bytes!\n", isize, usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT])); } free(d_ptr, M_TEMP); d_ptr = NULL; #ifdef USB_DEBUG for (j = 0; j < UMS_INFO_MAX; j++) { info = &sc->sc_info[j]; DPRINTF("sc=%p, index=%d\n", sc, j); DPRINTF("X\t%d/%d id=%d\n", info->sc_loc_x.pos, info->sc_loc_x.size, info->sc_iid_x); DPRINTF("Y\t%d/%d id=%d\n", info->sc_loc_y.pos, info->sc_loc_y.size, info->sc_iid_y); DPRINTF("Z\t%d/%d id=%d\n", info->sc_loc_z.pos, info->sc_loc_z.size, info->sc_iid_z); DPRINTF("T\t%d/%d id=%d\n", info->sc_loc_t.pos, info->sc_loc_t.size, info->sc_iid_t); DPRINTF("W\t%d/%d id=%d\n", info->sc_loc_w.pos, info->sc_loc_w.size, info->sc_iid_w); for (i = 0; i < info->sc_buttons; i++) { DPRINTF("B%d\t%d/%d id=%d\n", i + 1, info->sc_loc_btn[i].pos, info->sc_loc_btn[i].size, info->sc_iid_btn[i]); } } DPRINTF("size=%d, id=%d\n", isize, sc->sc_iid); #endif err = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, &ums_fifo_methods, &sc->sc_fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (err) goto detach; #ifdef EVDEV_SUPPORT sc->sc_evdev = evdev_alloc(); evdev_set_name(sc->sc_evdev, device_get_desc(dev)); evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev)); evdev_set_id(sc->sc_evdev, BUS_USB, uaa->info.idVendor, uaa->info.idProduct, 0); evdev_set_serial(sc->sc_evdev, usb_get_serial(uaa->device)); evdev_set_methods(sc->sc_evdev, sc, &ums_evdev_methods); evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER); evdev_support_event(sc->sc_evdev, EV_SYN); evdev_support_event(sc->sc_evdev, EV_REL); evdev_support_event(sc->sc_evdev, EV_KEY); info = &sc->sc_info[0]; if (info->sc_flags & UMS_FLAG_X_AXIS) evdev_support_rel(sc->sc_evdev, REL_X); if (info->sc_flags & UMS_FLAG_Y_AXIS) evdev_support_rel(sc->sc_evdev, REL_Y); if (info->sc_flags & UMS_FLAG_Z_AXIS) evdev_support_rel(sc->sc_evdev, REL_WHEEL); if (info->sc_flags & UMS_FLAG_T_AXIS) evdev_support_rel(sc->sc_evdev, REL_HWHEEL); for (i = 0; i < info->sc_buttons; i++) evdev_support_key(sc->sc_evdev, BTN_MOUSE + i); err = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx); if (err) goto detach; #endif SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), - OID_AUTO, "parseinfo", CTLTYPE_STRING|CTLFLAG_RD, - sc, 0, ums_sysctl_handler_parseinfo, - "", "Dump of parsed HID report descriptor"); + OID_AUTO, "parseinfo", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, ums_sysctl_handler_parseinfo, "", + "Dump of parsed HID report descriptor"); return (0); detach: if (d_ptr) { free(d_ptr, M_TEMP); } ums_detach(dev); return (ENOMEM); } static int ums_detach(device_t self) { struct ums_softc *sc = device_get_softc(self); DPRINTF("sc=%p\n", sc); usb_fifo_detach(&sc->sc_fifo); #ifdef EVDEV_SUPPORT evdev_free(sc->sc_evdev); #endif usbd_transfer_unsetup(sc->sc_xfer, UMS_N_TRANSFER); usb_callout_drain(&sc->sc_callout); mtx_destroy(&sc->sc_mtx); return (0); } static void ums_reset(struct ums_softc *sc) { /* reset all USB mouse parameters */ if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; else sc->sc_hw.buttons = sc->sc_buttons; sc->sc_hw.iftype = MOUSE_IF_USB; sc->sc_hw.type = MOUSE_MOUSE; sc->sc_hw.model = MOUSE_MODEL_GENERIC; sc->sc_hw.hwid = 0; sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.rate = -1; sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; sc->sc_mode.accelfactor = 0; sc->sc_mode.level = 0; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; /* reset status */ sc->sc_status.flags = 0; sc->sc_status.button = 0; sc->sc_status.obutton = 0; sc->sc_status.dx = 0; sc->sc_status.dy = 0; sc->sc_status.dz = 0; /* sc->sc_status.dt = 0; */ } static void ums_start_rx(struct ums_softc *sc) { int rate; /* Check if we should override the default polling interval */ rate = sc->sc_pollrate; /* Range check rate */ if (rate > 1000) rate = 1000; /* Check for set rate */ if ((rate > 0) && (sc->sc_xfer[UMS_INTR_DT] != NULL)) { DPRINTF("Setting pollrate = %d\n", rate); /* Stop current transfer, if any */ usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]); /* Set new interval */ usbd_xfer_set_interval(sc->sc_xfer[UMS_INTR_DT], 1000 / rate); /* Only set pollrate once */ sc->sc_pollrate = 0; } usbd_transfer_start(sc->sc_xfer[UMS_INTR_DT]); } static void ums_stop_rx(struct ums_softc *sc) { usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]); usb_callout_stop(&sc->sc_callout); } static void ums_fifo_start_read(struct usb_fifo *fifo) { struct ums_softc *sc = usb_fifo_softc(fifo); ums_start_rx(sc); } static void ums_fifo_stop_read(struct usb_fifo *fifo) { struct ums_softc *sc = usb_fifo_softc(fifo); ums_stop_rx(sc); } #if ((MOUSE_SYS_PACKETSIZE != 8) || \ (MOUSE_MSC_PACKETSIZE != 5)) #error "Software assumptions are not met. Please update code." #endif static void ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy, int32_t dz, int32_t dt, int32_t buttons) { uint8_t buf[8]; if (1) { if (dx > 254) dx = 254; if (dx < -256) dx = -256; if (dy > 254) dy = 254; if (dy < -256) dy = -256; if (dz > 126) dz = 126; if (dz < -128) dz = -128; if (dt > 126) dt = 126; if (dt < -128) dt = -128; buf[0] = sc->sc_mode.syncmask[1]; buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS; buf[1] = dx >> 1; buf[2] = dy >> 1; buf[3] = dx - (dx >> 1); buf[4] = dy - (dy >> 1); if (sc->sc_mode.level == 1) { buf[5] = dz >> 1; buf[6] = dz - (dz >> 1); buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS); } usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, sc->sc_mode.packetsize, 1); } else { DPRINTF("Buffer full, discarded packet\n"); } } #ifdef EVDEV_SUPPORT static void ums_evdev_push(struct ums_softc *sc, int32_t dx, int32_t dy, int32_t dz, int32_t dt, int32_t buttons) { if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) { /* Push evdev event */ evdev_push_rel(sc->sc_evdev, REL_X, dx); evdev_push_rel(sc->sc_evdev, REL_Y, -dy); evdev_push_rel(sc->sc_evdev, REL_WHEEL, -dz); evdev_push_rel(sc->sc_evdev, REL_HWHEEL, dt); evdev_push_mouse_btn(sc->sc_evdev, (buttons & ~MOUSE_STDBUTTONS) | (buttons & (1 << 2) ? MOUSE_BUTTON1DOWN : 0) | (buttons & (1 << 1) ? MOUSE_BUTTON2DOWN : 0) | (buttons & (1 << 0) ? MOUSE_BUTTON3DOWN : 0)); evdev_sync(sc->sc_evdev); } } #endif static void ums_reset_buf(struct ums_softc *sc) { /* reset read queue, must be called locked */ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); } #ifdef EVDEV_SUPPORT static int ums_ev_open(struct evdev_dev *evdev) { struct ums_softc *sc = evdev_get_softc(evdev); mtx_assert(&sc->sc_mtx, MA_OWNED); sc->sc_evflags = UMS_EVDEV_OPENED; if (sc->sc_fflags == 0) { ums_reset(sc); ums_start_rx(sc); } return (0); } static int ums_ev_close(struct evdev_dev *evdev) { struct ums_softc *sc = evdev_get_softc(evdev); mtx_assert(&sc->sc_mtx, MA_OWNED); sc->sc_evflags = 0; if (sc->sc_fflags == 0) ums_stop_rx(sc); return (0); } #endif static int ums_fifo_open(struct usb_fifo *fifo, int fflags) { struct ums_softc *sc = usb_fifo_softc(fifo); DPRINTFN(2, "\n"); /* check for duplicate open, should not happen */ if (sc->sc_fflags & fflags) return (EBUSY); /* check for first open */ #ifdef EVDEV_SUPPORT if (sc->sc_fflags == 0 && sc->sc_evflags == 0) ums_reset(sc); #else if (sc->sc_fflags == 0) ums_reset(sc); #endif if (fflags & FREAD) { /* allocate RX buffer */ if (usb_fifo_alloc_buffer(fifo, UMS_BUF_SIZE, UMS_IFQ_MAXLEN)) { return (ENOMEM); } } sc->sc_fflags |= fflags & (FREAD | FWRITE); return (0); } static void ums_fifo_close(struct usb_fifo *fifo, int fflags) { struct ums_softc *sc = usb_fifo_softc(fifo); DPRINTFN(2, "\n"); if (fflags & FREAD) usb_fifo_free_buffer(fifo); sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); } static int ums_fifo_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { struct ums_softc *sc = usb_fifo_softc(fifo); mousemode_t mode; int error = 0; DPRINTFN(2, "\n"); mtx_lock(&sc->sc_mtx); switch (cmd) { case MOUSE_GETHWINFO: *(mousehw_t *)addr = sc->sc_hw; break; case MOUSE_GETMODE: *(mousemode_t *)addr = sc->sc_mode; break; case MOUSE_SETMODE: mode = *(mousemode_t *)addr; if (mode.level == -1) { /* don't change the current setting */ } else if ((mode.level < 0) || (mode.level > 1)) { error = EINVAL; break; } else { sc->sc_mode.level = mode.level; } /* store polling rate */ sc->sc_pollrate = mode.rate; if (sc->sc_mode.level == 0) { if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; else sc->sc_hw.buttons = sc->sc_buttons; sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->sc_mode.level == 1) { if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON) sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON; else sc->sc_hw.buttons = sc->sc_buttons; sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; } ums_reset_buf(sc); break; case MOUSE_GETLEVEL: *(int *)addr = sc->sc_mode.level; break; case MOUSE_SETLEVEL: if (*(int *)addr < 0 || *(int *)addr > 1) { error = EINVAL; break; } sc->sc_mode.level = *(int *)addr; if (sc->sc_mode.level == 0) { if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; else sc->sc_hw.buttons = sc->sc_buttons; sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->sc_mode.level == 1) { if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON) sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON; else sc->sc_hw.buttons = sc->sc_buttons; sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; } ums_reset_buf(sc); break; case MOUSE_GETSTATUS:{ mousestatus_t *status = (mousestatus_t *)addr; *status = sc->sc_status; sc->sc_status.obutton = sc->sc_status.button; sc->sc_status.button = 0; sc->sc_status.dx = 0; sc->sc_status.dy = 0; sc->sc_status.dz = 0; /* sc->sc_status.dt = 0; */ if (status->dx || status->dy || status->dz /* || status->dt */ ) { status->flags |= MOUSE_POSCHANGED; } if (status->button != status->obutton) { status->flags |= MOUSE_BUTTONSCHANGED; } break; } default: error = ENOTTY; break; } mtx_unlock(&sc->sc_mtx); return (error); } static int ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS) { struct ums_softc *sc = arg1; struct ums_info *info; struct sbuf *sb; int i, j, err, had_output; sb = sbuf_new_auto(); for (i = 0, had_output = 0; i < UMS_INFO_MAX; i++) { info = &sc->sc_info[i]; /* Don't emit empty info */ if ((info->sc_flags & (UMS_FLAG_X_AXIS | UMS_FLAG_Y_AXIS | UMS_FLAG_Z_AXIS | UMS_FLAG_T_AXIS | UMS_FLAG_W_AXIS)) == 0 && info->sc_buttons == 0) continue; if (had_output) sbuf_printf(sb, "\n"); had_output = 1; sbuf_printf(sb, "i%d:", i + 1); if (info->sc_flags & UMS_FLAG_X_AXIS) sbuf_printf(sb, " X:r%d, p%d, s%d;", (int)info->sc_iid_x, (int)info->sc_loc_x.pos, (int)info->sc_loc_x.size); if (info->sc_flags & UMS_FLAG_Y_AXIS) sbuf_printf(sb, " Y:r%d, p%d, s%d;", (int)info->sc_iid_y, (int)info->sc_loc_y.pos, (int)info->sc_loc_y.size); if (info->sc_flags & UMS_FLAG_Z_AXIS) sbuf_printf(sb, " Z:r%d, p%d, s%d;", (int)info->sc_iid_z, (int)info->sc_loc_z.pos, (int)info->sc_loc_z.size); if (info->sc_flags & UMS_FLAG_T_AXIS) sbuf_printf(sb, " T:r%d, p%d, s%d;", (int)info->sc_iid_t, (int)info->sc_loc_t.pos, (int)info->sc_loc_t.size); if (info->sc_flags & UMS_FLAG_W_AXIS) sbuf_printf(sb, " W:r%d, p%d, s%d;", (int)info->sc_iid_w, (int)info->sc_loc_w.pos, (int)info->sc_loc_w.size); for (j = 0; j < info->sc_buttons; j++) { sbuf_printf(sb, " B%d:r%d, p%d, s%d;", j + 1, (int)info->sc_iid_btn[j], (int)info->sc_loc_btn[j].pos, (int)info->sc_loc_btn[j].size); } } sbuf_finish(sb); err = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb) + 1); sbuf_delete(sb); return (err); } static devclass_t ums_devclass; static device_method_t ums_methods[] = { DEVMETHOD(device_probe, ums_probe), DEVMETHOD(device_attach, ums_attach), DEVMETHOD(device_detach, ums_detach), DEVMETHOD_END }; static driver_t ums_driver = { .name = "ums", .methods = ums_methods, .size = sizeof(struct ums_softc), }; DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, NULL, 0); MODULE_DEPEND(ums, usb, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(ums, evdev, 1, 1, 1); #endif MODULE_VERSION(ums, 1); USB_PNP_HOST_INFO(ums_devs); Index: head/sys/dev/usb/input/wmt.c =================================================================== --- head/sys/dev/usb/input/wmt.c (revision 357971) +++ head/sys/dev/usb/input/wmt.c (revision 357972) @@ -1,885 +1,885 @@ /*- * Copyright (c) 2014-2017 Vladimir Kondratyev * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); /* * MS Windows 7/8/10 compatible USB HID Multi-touch Device driver. * https://msdn.microsoft.com/en-us/library/windows/hardware/jj151569(v=vs.85).aspx * https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt */ #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #include #define USB_DEBUG_VAR wmt_debug #include #ifdef USB_DEBUG static int wmt_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, wmt, CTLFLAG_RW, 0, +static SYSCTL_NODE(_hw_usb, OID_AUTO, wmt, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB MSWindows 7/8/10 compatible Multi-touch Device"); SYSCTL_INT(_hw_usb_wmt, OID_AUTO, debug, CTLFLAG_RWTUN, &wmt_debug, 1, "Debug level"); #endif #define WMT_BSIZE 1024 /* bytes, buffer size */ enum { WMT_INTR_DT, WMT_N_TRANSFER, }; enum { WMT_TIP_SWITCH, #define WMT_SLOT WMT_TIP_SWITCH WMT_WIDTH, #define WMT_MAJOR WMT_WIDTH WMT_HEIGHT, #define WMT_MINOR WMT_HEIGHT WMT_ORIENTATION, WMT_X, WMT_Y, WMT_CONTACTID, WMT_PRESSURE, WMT_IN_RANGE, WMT_CONFIDENCE, WMT_TOOL_X, WMT_TOOL_Y, WMT_N_USAGES, }; #define WMT_NO_CODE (ABS_MAX + 10) #define WMT_NO_USAGE -1 struct wmt_hid_map_item { char name[5]; int32_t usage; /* HID usage */ uint32_t code; /* Evdev event code */ bool required; /* Required for MT Digitizers */ }; static const struct wmt_hid_map_item wmt_hid_map[WMT_N_USAGES] = { [WMT_TIP_SWITCH] = { /* WMT_SLOT */ .name = "TIP", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH), .code = ABS_MT_SLOT, .required = true, }, [WMT_WIDTH] = { /* WMT_MAJOR */ .name = "WDTH", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH), .code = ABS_MT_TOUCH_MAJOR, .required = false, }, [WMT_HEIGHT] = { /* WMT_MINOR */ .name = "HGHT", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT), .code = ABS_MT_TOUCH_MINOR, .required = false, }, [WMT_ORIENTATION] = { .name = "ORIE", .usage = WMT_NO_USAGE, .code = ABS_MT_ORIENTATION, .required = false, }, [WMT_X] = { .name = "X", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), .code = ABS_MT_POSITION_X, .required = true, }, [WMT_Y] = { .name = "Y", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), .code = ABS_MT_POSITION_Y, .required = true, }, [WMT_CONTACTID] = { .name = "C_ID", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID), .code = ABS_MT_TRACKING_ID, .required = true, }, [WMT_PRESSURE] = { .name = "PRES", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_PRESSURE), .code = ABS_MT_PRESSURE, .required = false, }, [WMT_IN_RANGE] = { .name = "RANG", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE), .code = ABS_MT_DISTANCE, .required = false, }, [WMT_CONFIDENCE] = { .name = "CONF", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE), .code = WMT_NO_CODE, .required = false, }, [WMT_TOOL_X] = { /* Shares HID usage with WMT_X */ .name = "TL_X", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), .code = ABS_MT_TOOL_X, .required = false, }, [WMT_TOOL_Y] = { /* Shares HID usage with WMT_Y */ .name = "TL_Y", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), .code = ABS_MT_TOOL_Y, .required = false, }, }; struct wmt_absinfo { int32_t min; int32_t max; int32_t res; }; struct wmt_softc { device_t dev; struct mtx mtx; struct wmt_absinfo ai[WMT_N_USAGES]; struct hid_location locs[MAX_MT_SLOTS][WMT_N_USAGES]; struct hid_location nconts_loc; struct usb_xfer *xfer[WMT_N_TRANSFER]; struct evdev_dev *evdev; uint32_t slot_data[WMT_N_USAGES]; uint32_t caps; uint32_t isize; uint32_t nconts_max; uint8_t report_id; struct hid_location cont_max_loc; uint32_t cont_max_rlen; uint8_t cont_max_rid; uint32_t thqa_cert_rlen; uint8_t thqa_cert_rid; uint8_t buf[WMT_BSIZE] __aligned(4); }; #define USAGE_SUPPORTED(caps, usage) ((caps) & (1 << (usage))) #define WMT_FOREACH_USAGE(caps, usage) \ for ((usage) = 0; (usage) < WMT_N_USAGES; ++(usage)) \ if (USAGE_SUPPORTED((caps), (usage))) static bool wmt_hid_parse(struct wmt_softc *, const void *, uint16_t); static void wmt_cont_max_parse(struct wmt_softc *, const void *, uint16_t); static usb_callback_t wmt_intr_callback; static device_probe_t wmt_probe; static device_attach_t wmt_attach; static device_detach_t wmt_detach; #if __FreeBSD_version >= 1200077 static evdev_open_t wmt_ev_open; static evdev_close_t wmt_ev_close; #else static evdev_open_t wmt_ev_open_11; static evdev_close_t wmt_ev_close_11; #endif static const struct evdev_methods wmt_evdev_methods = { #if __FreeBSD_version >= 1200077 .ev_open = &wmt_ev_open, .ev_close = &wmt_ev_close, #else .ev_open = &wmt_ev_open_11, .ev_close = &wmt_ev_close_11, #endif }; static const struct usb_config wmt_config[WMT_N_TRANSFER] = { [WMT_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .bufsize = WMT_BSIZE, .callback = &wmt_intr_callback, }, }; static int wmt_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); void *d_ptr; uint16_t d_len; int err; if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bInterfaceClass != UICLASS_HID) return (ENXIO); if (usb_test_quirk(uaa, UQ_WMT_IGNORE)) return (ENXIO); err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (err) return (ENXIO); if (wmt_hid_parse(NULL, d_ptr, d_len)) err = BUS_PROBE_DEFAULT; else err = ENXIO; free(d_ptr, M_TEMP); return (err); } static int wmt_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct wmt_softc *sc = device_get_softc(dev); void *d_ptr; uint16_t d_len; size_t i; int err; bool hid_ok; device_set_usb_desc(dev); sc->dev = dev; /* Get HID descriptor */ err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (err) { DPRINTF("usbd_req_get_hid_desc error=%s\n", usbd_errstr(err)); return (ENXIO); } hid_ok = wmt_hid_parse(sc, d_ptr, d_len); free(d_ptr, M_TEMP); if (!hid_ok) { DPRINTF("multi-touch HID descriptor not found\n"); return (ENXIO); } /* Check HID report length */ if (sc->isize <= 0 || sc->isize > WMT_BSIZE) { DPRINTF("Input size invalid or too large: %d\n", sc->isize); return (ENXIO); } /* Fetch and parse "Contact count maximum" feature report */ if (sc->cont_max_rlen > 0 && sc->cont_max_rlen <= WMT_BSIZE) { err = usbd_req_get_report(uaa->device, NULL, sc->buf, sc->cont_max_rlen, uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, sc->cont_max_rid); if (err == USB_ERR_NORMAL_COMPLETION) wmt_cont_max_parse(sc, sc->buf, sc->cont_max_rlen); else DPRINTF("usbd_req_get_report error=(%s)\n", usbd_errstr(err)); } else DPRINTF("Feature report %hhu size invalid or too large: %u\n", sc->cont_max_rid, sc->cont_max_rlen); /* Fetch THQA certificate to enable some devices like WaveShare */ if (sc->thqa_cert_rlen > 0 && sc->thqa_cert_rlen <= WMT_BSIZE && sc->thqa_cert_rid != sc->cont_max_rid) (void)usbd_req_get_report(uaa->device, NULL, sc->buf, sc->thqa_cert_rlen, uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, sc->thqa_cert_rid); mtx_init(&sc->mtx, "wmt lock", NULL, MTX_DEF); err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->xfer, wmt_config, WMT_N_TRANSFER, sc, &sc->mtx); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err)); goto detach; } sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(dev)); evdev_set_phys(sc->evdev, device_get_nameunit(dev)); evdev_set_id(sc->evdev, BUS_USB, uaa->info.idVendor, uaa->info.idProduct, 0); evdev_set_serial(sc->evdev, usb_get_serial(uaa->device)); evdev_set_methods(sc->evdev, sc, &wmt_evdev_methods); evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT); evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT); evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); WMT_FOREACH_USAGE(sc->caps, i) { if (wmt_hid_map[i].code != WMT_NO_CODE) evdev_support_abs(sc->evdev, wmt_hid_map[i].code, 0, sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res); } err = evdev_register_mtx(sc->evdev, &sc->mtx); if (err) goto detach; return (0); detach: wmt_detach(dev); return (ENXIO); } static int wmt_detach(device_t dev) { struct wmt_softc *sc = device_get_softc(dev); evdev_free(sc->evdev); usbd_transfer_unsetup(sc->xfer, WMT_N_TRANSFER); mtx_destroy(&sc->mtx); return (0); } static void wmt_process_report(struct wmt_softc *sc, uint8_t *buf, int len) { size_t usage; uint32_t *slot_data = sc->slot_data; uint32_t cont; uint32_t nconts; uint32_t width; uint32_t height; int32_t slot; nconts = hid_get_data_unsigned(buf, len, &sc->nconts_loc); #ifdef USB_DEBUG DPRINTFN(6, "nconts = %u ", (unsigned)nconts); if (wmt_debug >= 6) { WMT_FOREACH_USAGE(sc->caps, usage) { if (wmt_hid_map[usage].usage != WMT_NO_USAGE) printf(" %-4s", wmt_hid_map[usage].name); } printf("\n"); } #endif if (nconts > sc->nconts_max) { DPRINTF("Contact count overflow %u\n", (unsigned)nconts); nconts = sc->nconts_max; } /* Use protocol Type B for reporting events */ for (cont = 0; cont < nconts; cont++) { bzero(slot_data, sizeof(sc->slot_data)); WMT_FOREACH_USAGE(sc->caps, usage) { if (sc->locs[cont][usage].size > 0) slot_data[usage] = hid_get_data_unsigned( buf, len, &sc->locs[cont][usage]); } slot = evdev_get_mt_slot_by_tracking_id(sc->evdev, slot_data[WMT_CONTACTID]); #ifdef USB_DEBUG DPRINTFN(6, "cont%01x: data = ", cont); if (wmt_debug >= 6) { WMT_FOREACH_USAGE(sc->caps, usage) { if (wmt_hid_map[usage].usage != WMT_NO_USAGE) printf("%04x ", slot_data[usage]); } printf("slot = %d\n", (int)slot); } #endif if (slot == -1) { DPRINTF("Slot overflow for contact_id %u\n", (unsigned)slot_data[WMT_CONTACTID]); continue; } if (slot_data[WMT_TIP_SWITCH] != 0 && !(USAGE_SUPPORTED(sc->caps, WMT_CONFIDENCE) && slot_data[WMT_CONFIDENCE] == 0)) { /* This finger is in proximity of the sensor */ slot_data[WMT_SLOT] = slot; slot_data[WMT_IN_RANGE] = !slot_data[WMT_IN_RANGE]; /* Divided by two to match visual scale of touch */ width = slot_data[WMT_WIDTH] >> 1; height = slot_data[WMT_HEIGHT] >> 1; slot_data[WMT_ORIENTATION] = width > height; slot_data[WMT_MAJOR] = MAX(width, height); slot_data[WMT_MINOR] = MIN(width, height); WMT_FOREACH_USAGE(sc->caps, usage) if (wmt_hid_map[usage].code != WMT_NO_CODE) evdev_push_abs(sc->evdev, wmt_hid_map[usage].code, slot_data[usage]); } else { evdev_push_abs(sc->evdev, ABS_MT_SLOT, slot); evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1); } } evdev_sync(sc->evdev); } static void wmt_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct wmt_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t *buf = sc->buf; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); DPRINTFN(6, "sc=%p actlen=%d\n", sc, len); if (len >= (int)sc->isize || (len > 0 && sc->report_id != 0)) { /* Limit report length to the maximum */ if (len > (int)sc->isize) len = sc->isize; usbd_copy_out(pc, 0, buf, len); /* Ignore irrelevant reports */ if (sc->report_id && *buf != sc->report_id) goto tr_ignore; /* Make sure we don't process old data */ if (len < sc->isize) bzero(buf + len, sc->isize - len); /* Strip leading "report ID" byte */ if (sc->report_id) { len--; buf++; } wmt_process_report(sc, buf, len); } else { tr_ignore: DPRINTF("Ignored transfer, %d bytes\n", len); } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: if (error != USB_ERR_CANCELLED) { /* Try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void wmt_ev_close_11(struct evdev_dev *evdev, void *ev_softc) { struct wmt_softc *sc = ev_softc; mtx_assert(&sc->mtx, MA_OWNED); usbd_transfer_stop(sc->xfer[WMT_INTR_DT]); } static int wmt_ev_open_11(struct evdev_dev *evdev, void *ev_softc) { struct wmt_softc *sc = ev_softc; mtx_assert(&sc->mtx, MA_OWNED); usbd_transfer_start(sc->xfer[WMT_INTR_DT]); return (0); } #if __FreeBSD_version >= 1200077 static int wmt_ev_close(struct evdev_dev *evdev) { struct wmt_softc *sc = evdev_get_softc(evdev); wmt_ev_close_11(evdev, sc); return (0); } static int wmt_ev_open(struct evdev_dev *evdev) { struct wmt_softc *sc = evdev_get_softc(evdev); return (wmt_ev_open_11(evdev, sc)); } #endif /* port of userland hid_report_size() from usbhid(3) to kernel */ static int wmt_hid_report_size(const void *buf, uint16_t len, enum hid_kind k, uint8_t id) { struct hid_data *d; struct hid_item h; uint32_t temp; uint32_t hpos; uint32_t lpos; int report_id = 0; hpos = 0; lpos = 0xFFFFFFFF; for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) { if (h.kind == k && h.report_ID == id) { /* compute minimum */ if (lpos > h.loc.pos) lpos = h.loc.pos; /* compute end position */ temp = h.loc.pos + (h.loc.size * h.loc.count); /* compute maximum */ if (hpos < temp) hpos = temp; if (h.report_ID != 0) report_id = 1; } } hid_end_parse(d); /* safety check - can happen in case of currupt descriptors */ if (lpos > hpos) temp = 0; else temp = hpos - lpos; /* return length in bytes rounded up */ return ((temp + 7) / 8 + report_id); } static bool wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len) { struct hid_item hi; struct hid_data *hd; size_t i; size_t cont = 0; uint32_t caps = 0; int32_t cont_count_max = 0; uint8_t report_id = 0; uint8_t cont_max_rid = 0; uint8_t thqa_cert_rid = 0; bool touch_coll = false; bool finger_coll = false; bool cont_count_found = false; bool scan_time_found = false; #define WMT_HI_ABSOLUTE(hi) \ (((hi).flags & (HIO_CONST|HIO_VARIABLE|HIO_RELATIVE)) == HIO_VARIABLE) #define HUMS_THQA_CERT 0xC5 /* Parse features for maximum contact count */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_feature); while (hid_get_item(hd, &hi)) { switch (hi.kind) { case hid_collection: if (hi.collevel == 1 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN)) touch_coll = true; break; case hid_endcollection: if (hi.collevel == 0 && touch_coll) touch_coll = false; break; case hid_feature: if (hi.collevel == 1 && touch_coll && hi.usage == HID_USAGE2(HUP_MICROSOFT, HUMS_THQA_CERT)) { thqa_cert_rid = hi.report_ID; break; } if (hi.collevel == 1 && touch_coll && WMT_HI_ABSOLUTE(hi) && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX)) { cont_count_max = hi.logical_maximum; cont_max_rid = hi.report_ID; if (sc != NULL) sc->cont_max_loc = hi.loc; } break; default: break; } } hid_end_parse(hd); /* Maximum contact count is required usage */ if (cont_max_rid == 0) return (false); touch_coll = false; /* Parse input for other parameters */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); while (hid_get_item(hd, &hi)) { switch (hi.kind) { case hid_collection: if (hi.collevel == 1 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN)) touch_coll = true; else if (touch_coll && hi.collevel == 2 && (report_id == 0 || report_id == hi.report_ID) && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER)) finger_coll = true; break; case hid_endcollection: if (hi.collevel == 1 && finger_coll) { finger_coll = false; cont++; } else if (hi.collevel == 0 && touch_coll) touch_coll = false; break; case hid_input: /* * Ensure that all usages are located within the same * report and proper collection. */ if (WMT_HI_ABSOLUTE(hi) && touch_coll && (report_id == 0 || report_id == hi.report_ID)) report_id = hi.report_ID; else break; if (hi.collevel == 1 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) { cont_count_found = true; if (sc != NULL) sc->nconts_loc = hi.loc; break; } /* Scan time is required but clobbered by evdev */ if (hi.collevel == 1 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_SCAN_TIME)) { scan_time_found = true; break; } if (!finger_coll || hi.collevel != 2) break; if (sc == NULL && cont > 0) break; if (cont >= MAX_MT_SLOTS) { DPRINTF("Finger %zu ignored\n", cont); break; } for (i = 0; i < WMT_N_USAGES; i++) { if (hi.usage == wmt_hid_map[i].usage) { if (sc == NULL) { if (USAGE_SUPPORTED(caps, i)) continue; caps |= 1 << i; break; } /* * HUG_X usage is an array mapped to * both ABS_MT_POSITION and ABS_MT_TOOL * events. So don`t stop search if we * already have HUG_X mapping done. */ if (sc->locs[cont][i].size) continue; sc->locs[cont][i] = hi.loc; /* * Hid parser returns valid logical and * physical sizes for first finger only * at least on ElanTS 0x04f3:0x0012. */ if (cont > 0) break; caps |= 1 << i; sc->ai[i] = (struct wmt_absinfo) { .max = hi.logical_maximum, .min = hi.logical_minimum, .res = hid_item_resolution(&hi), }; break; } } break; default: break; } } hid_end_parse(hd); /* Check for required HID Usages */ if (!cont_count_found || !scan_time_found || cont == 0) return (false); for (i = 0; i < WMT_N_USAGES; i++) { if (wmt_hid_map[i].required && !USAGE_SUPPORTED(caps, i)) return (false); } /* Stop probing here */ if (sc == NULL) return (true); /* * According to specifications 'Contact Count Maximum' should be read * from Feature Report rather than from HID descriptor. Set sane * default value now to handle the case of 'Get Report' request failure */ if (cont_count_max < 1) cont_count_max = cont; /* Cap contact count maximum to MAX_MT_SLOTS */ if (cont_count_max > MAX_MT_SLOTS) cont_count_max = MAX_MT_SLOTS; /* Set number of MT protocol type B slots */ sc->ai[WMT_SLOT] = (struct wmt_absinfo) { .min = 0, .max = cont_count_max - 1, .res = 0, }; /* Report touch orientation if both width and height are supported */ if (USAGE_SUPPORTED(caps, WMT_WIDTH) && USAGE_SUPPORTED(caps, WMT_HEIGHT)) { caps |= (1 << WMT_ORIENTATION); sc->ai[WMT_ORIENTATION].max = 1; } sc->isize = wmt_hid_report_size(d_ptr, d_len, hid_input, report_id); sc->cont_max_rlen = wmt_hid_report_size(d_ptr, d_len, hid_feature, cont_max_rid); if (thqa_cert_rid > 0) sc->thqa_cert_rlen = wmt_hid_report_size(d_ptr, d_len, hid_feature, thqa_cert_rid); sc->report_id = report_id; sc->caps = caps; sc->nconts_max = cont; sc->cont_max_rid = cont_max_rid; sc->thqa_cert_rid = thqa_cert_rid; /* Announce information about the touch device */ device_printf(sc->dev, "%d contacts and [%s%s%s%s%s]. Report range [%d:%d] - [%d:%d]\n", (int)cont_count_max, USAGE_SUPPORTED(sc->caps, WMT_IN_RANGE) ? "R" : "", USAGE_SUPPORTED(sc->caps, WMT_CONFIDENCE) ? "C" : "", USAGE_SUPPORTED(sc->caps, WMT_WIDTH) ? "W" : "", USAGE_SUPPORTED(sc->caps, WMT_HEIGHT) ? "H" : "", USAGE_SUPPORTED(sc->caps, WMT_PRESSURE) ? "P" : "", (int)sc->ai[WMT_X].min, (int)sc->ai[WMT_Y].min, (int)sc->ai[WMT_X].max, (int)sc->ai[WMT_Y].max); return (true); } static void wmt_cont_max_parse(struct wmt_softc *sc, const void *r_ptr, uint16_t r_len) { uint32_t cont_count_max; cont_count_max = hid_get_data_unsigned((const uint8_t *)r_ptr + 1, r_len - 1, &sc->cont_max_loc); if (cont_count_max > MAX_MT_SLOTS) { DPRINTF("Hardware reported %d contacts while only %d is " "supported\n", (int)cont_count_max, MAX_MT_SLOTS); cont_count_max = MAX_MT_SLOTS; } /* Feature report is a primary source of 'Contact Count Maximum' */ if (cont_count_max > 0 && cont_count_max != sc->ai[WMT_SLOT].max + 1) { sc->ai[WMT_SLOT].max = cont_count_max - 1; device_printf(sc->dev, "%d feature report contacts", cont_count_max); } } static const STRUCT_USB_HOST_ID wmt_devs[] = { /* generic HID class w/o boot interface */ {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(0),}, }; static devclass_t wmt_devclass; static device_method_t wmt_methods[] = { DEVMETHOD(device_probe, wmt_probe), DEVMETHOD(device_attach, wmt_attach), DEVMETHOD(device_detach, wmt_detach), DEVMETHOD_END }; static driver_t wmt_driver = { .name = "wmt", .methods = wmt_methods, .size = sizeof(struct wmt_softc), }; DRIVER_MODULE(wmt, uhub, wmt_driver, wmt_devclass, NULL, 0); MODULE_DEPEND(wmt, usb, 1, 1, 1); MODULE_DEPEND(wmt, evdev, 1, 1, 1); MODULE_VERSION(wmt, 1); USB_PNP_HOST_INFO(wmt_devs); Index: head/sys/dev/usb/input/wsp.c =================================================================== --- head/sys/dev/usb/input/wsp.c (revision 357971) +++ head/sys/dev/usb/input/wsp.c (revision 357972) @@ -1,1410 +1,1411 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 Huang Wen Hui * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR wsp_debug #include #include #define WSP_DRIVER_NAME "wsp" #define WSP_BUFFER_MAX 1024 #define WSP_CLAMP(x,low,high) do { \ if ((x) < (low)) \ (x) = (low); \ else if ((x) > (high)) \ (x) = (high); \ } while (0) /* Tunables */ -static SYSCTL_NODE(_hw_usb, OID_AUTO, wsp, CTLFLAG_RW, 0, "USB wsp"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, wsp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB wsp"); #ifdef USB_DEBUG enum wsp_log_level { WSP_LLEVEL_DISABLED = 0, WSP_LLEVEL_ERROR, WSP_LLEVEL_DEBUG, /* for troubleshooting */ WSP_LLEVEL_INFO, /* for diagnostics */ }; static int wsp_debug = WSP_LLEVEL_ERROR;/* the default is to only log errors */ SYSCTL_INT(_hw_usb_wsp, OID_AUTO, debug, CTLFLAG_RWTUN, &wsp_debug, WSP_LLEVEL_ERROR, "WSP debug level"); #endif /* USB_DEBUG */ static struct wsp_tuning { int scale_factor; int z_factor; int pressure_touch_threshold; int pressure_untouch_threshold; int pressure_tap_threshold; int scr_hor_threshold; int enable_single_tap_clicks; } wsp_tuning = { .scale_factor = 12, .z_factor = 5, .pressure_touch_threshold = 50, .pressure_untouch_threshold = 10, .pressure_tap_threshold = 120, .scr_hor_threshold = 20, .enable_single_tap_clicks = 1, }; static void wsp_runing_rangecheck(struct wsp_tuning *ptun) { WSP_CLAMP(ptun->scale_factor, 1, 63); WSP_CLAMP(ptun->z_factor, 1, 63); WSP_CLAMP(ptun->pressure_touch_threshold, 1, 255); WSP_CLAMP(ptun->pressure_untouch_threshold, 1, 255); WSP_CLAMP(ptun->pressure_tap_threshold, 1, 255); WSP_CLAMP(ptun->scr_hor_threshold, 1, 255); WSP_CLAMP(ptun->enable_single_tap_clicks, 0, 1); } SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scale_factor, CTLFLAG_RWTUN, &wsp_tuning.scale_factor, 0, "movement scale factor"); SYSCTL_INT(_hw_usb_wsp, OID_AUTO, z_factor, CTLFLAG_RWTUN, &wsp_tuning.z_factor, 0, "Z-axis scale factor"); SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_touch_threshold, CTLFLAG_RWTUN, &wsp_tuning.pressure_touch_threshold, 0, "touch pressure threshold"); SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_untouch_threshold, CTLFLAG_RWTUN, &wsp_tuning.pressure_untouch_threshold, 0, "untouch pressure threshold"); SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_tap_threshold, CTLFLAG_RWTUN, &wsp_tuning.pressure_tap_threshold, 0, "tap pressure threshold"); SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scr_hor_threshold, CTLFLAG_RWTUN, &wsp_tuning.scr_hor_threshold, 0, "horizontal scrolling threshold"); SYSCTL_INT(_hw_usb_wsp, OID_AUTO, enable_single_tap_clicks, CTLFLAG_RWTUN, &wsp_tuning.enable_single_tap_clicks, 0, "enable single tap clicks"); /* * Some tables, structures, definitions and constant values for the * touchpad protocol has been copied from Linux's * "drivers/input/mouse/bcm5974.c" which has the following copyright * holders under GPLv2. All device specific code in this driver has * been written from scratch. The decoding algorithm is based on * output from FreeBSD's usbdump. * * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com) * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) * Copyright (C) 2005 Stelian Pop (stelian@popies.net) * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) */ /* button data structure */ struct bt_data { uint8_t unknown1; /* constant */ uint8_t button; /* left button */ uint8_t rel_x; /* relative x coordinate */ uint8_t rel_y; /* relative y coordinate */ } __packed; /* trackpad header types */ enum tp_type { TYPE1, /* plain trackpad */ TYPE2, /* button integrated in trackpad */ TYPE3, /* additional header fields since June 2013 */ TYPE4 /* additional header field for pressure data */ }; /* trackpad finger data offsets, le16-aligned */ #define FINGER_TYPE1 (13 * 2) #define FINGER_TYPE2 (15 * 2) #define FINGER_TYPE3 (19 * 2) #define FINGER_TYPE4 (23 * 2) /* trackpad button data offsets */ #define BUTTON_TYPE2 15 #define BUTTON_TYPE3 23 #define BUTTON_TYPE4 31 /* list of device capability bits */ #define HAS_INTEGRATED_BUTTON 1 /* trackpad finger data block size */ #define FSIZE_TYPE1 (14 * 2) #define FSIZE_TYPE2 (14 * 2) #define FSIZE_TYPE3 (14 * 2) #define FSIZE_TYPE4 (15 * 2) /* trackpad finger header - little endian */ struct tp_header { uint8_t flag; uint8_t sn0; uint16_t wFixed0; uint32_t dwSn1; uint32_t dwFixed1; uint16_t wLength; uint8_t nfinger; uint8_t ibt; int16_t wUnknown[6]; uint8_t q1; uint8_t q2; } __packed; /* trackpad finger structure - little endian */ struct tp_finger { int16_t origin; /* zero when switching track finger */ int16_t abs_x; /* absolute x coodinate */ int16_t abs_y; /* absolute y coodinate */ int16_t rel_x; /* relative x coodinate */ int16_t rel_y; /* relative y coodinate */ int16_t tool_major; /* tool area, major axis */ int16_t tool_minor; /* tool area, minor axis */ int16_t orientation; /* 16384 when point, else 15 bit angle */ int16_t touch_major; /* touch area, major axis */ int16_t touch_minor; /* touch area, minor axis */ int16_t unused[2]; /* zeros */ int16_t pressure; /* pressure on forcetouch touchpad */ int16_t multi; /* one finger: varies, more fingers: * constant */ } __packed; /* trackpad finger data size, empirically at least ten fingers */ #define MAX_FINGERS 16 #define SIZEOF_FINGER sizeof(struct tp_finger) #define SIZEOF_ALL_FINGERS (MAX_FINGERS * SIZEOF_FINGER) #if (WSP_BUFFER_MAX < ((MAX_FINGERS * FSIZE_TYPE4) + FINGER_TYPE4)) #error "WSP_BUFFER_MAX is too small" #endif enum { WSP_FLAG_WELLSPRING1, WSP_FLAG_WELLSPRING2, WSP_FLAG_WELLSPRING3, WSP_FLAG_WELLSPRING4, WSP_FLAG_WELLSPRING4A, WSP_FLAG_WELLSPRING5, WSP_FLAG_WELLSPRING6A, WSP_FLAG_WELLSPRING6, WSP_FLAG_WELLSPRING5A, WSP_FLAG_WELLSPRING7, WSP_FLAG_WELLSPRING7A, WSP_FLAG_WELLSPRING8, WSP_FLAG_WELLSPRING9, WSP_FLAG_MAX, }; /* device-specific configuration */ struct wsp_dev_params { uint8_t caps; /* device capability bitmask */ uint8_t tp_type; /* type of trackpad interface */ uint8_t tp_button; /* offset to button data */ uint8_t tp_offset; /* offset to trackpad finger data */ uint8_t tp_fsize; /* bytes in single finger block */ uint8_t tp_delta; /* offset from header to finger struct */ uint8_t iface_index; uint8_t um_size; /* usb control message length */ uint8_t um_req_val; /* usb control message value */ uint8_t um_req_idx; /* usb control message index */ uint8_t um_switch_idx; /* usb control message mode switch index */ uint8_t um_switch_on; /* usb control message mode switch on */ uint8_t um_switch_off; /* usb control message mode switch off */ }; static const struct wsp_dev_params wsp_dev_params[WSP_FLAG_MAX] = { [WSP_FLAG_WELLSPRING1] = { .caps = 0, .tp_type = TYPE1, .tp_button = 0, .tp_offset = FINGER_TYPE1, .tp_fsize = FSIZE_TYPE1, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING2] = { .caps = 0, .tp_type = TYPE1, .tp_button = 0, .tp_offset = FINGER_TYPE1, .tp_fsize = FSIZE_TYPE1, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING3] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING4] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING4A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING5] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING6] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING5A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING6A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING7] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING7A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE2, .tp_button = BUTTON_TYPE2, .tp_offset = FINGER_TYPE2, .tp_fsize = FSIZE_TYPE2, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING8] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE3, .tp_button = BUTTON_TYPE3, .tp_offset = FINGER_TYPE3, .tp_fsize = FSIZE_TYPE3, .tp_delta = 0, .iface_index = 0, .um_size = 8, .um_req_val = 0x03, .um_req_idx = 0x00, .um_switch_idx = 0, .um_switch_on = 0x01, .um_switch_off = 0x08, }, [WSP_FLAG_WELLSPRING9] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = TYPE4, .tp_button = BUTTON_TYPE4, .tp_offset = FINGER_TYPE4, .tp_fsize = FSIZE_TYPE4, .tp_delta = 2, .iface_index = 2, .um_size = 2, .um_req_val = 0x03, .um_req_idx = 0x02, .um_switch_idx = 1, .um_switch_on = 0x01, .um_switch_off = 0x00, }, }; #define WSP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } static const STRUCT_USB_HOST_ID wsp_devs[] = { /* MacbookAir1.1 */ WSP_DEV(APPLE, WELLSPRING_ANSI, WSP_FLAG_WELLSPRING1), WSP_DEV(APPLE, WELLSPRING_ISO, WSP_FLAG_WELLSPRING1), WSP_DEV(APPLE, WELLSPRING_JIS, WSP_FLAG_WELLSPRING1), /* MacbookProPenryn, aka wellspring2 */ WSP_DEV(APPLE, WELLSPRING2_ANSI, WSP_FLAG_WELLSPRING2), WSP_DEV(APPLE, WELLSPRING2_ISO, WSP_FLAG_WELLSPRING2), WSP_DEV(APPLE, WELLSPRING2_JIS, WSP_FLAG_WELLSPRING2), /* Macbook5,1 (unibody), aka wellspring3 */ WSP_DEV(APPLE, WELLSPRING3_ANSI, WSP_FLAG_WELLSPRING3), WSP_DEV(APPLE, WELLSPRING3_ISO, WSP_FLAG_WELLSPRING3), WSP_DEV(APPLE, WELLSPRING3_JIS, WSP_FLAG_WELLSPRING3), /* MacbookAir3,2 (unibody), aka wellspring4 */ WSP_DEV(APPLE, WELLSPRING4_ANSI, WSP_FLAG_WELLSPRING4), WSP_DEV(APPLE, WELLSPRING4_ISO, WSP_FLAG_WELLSPRING4), WSP_DEV(APPLE, WELLSPRING4_JIS, WSP_FLAG_WELLSPRING4), /* MacbookAir3,1 (unibody), aka wellspring4 */ WSP_DEV(APPLE, WELLSPRING4A_ANSI, WSP_FLAG_WELLSPRING4A), WSP_DEV(APPLE, WELLSPRING4A_ISO, WSP_FLAG_WELLSPRING4A), WSP_DEV(APPLE, WELLSPRING4A_JIS, WSP_FLAG_WELLSPRING4A), /* Macbook8 (unibody, March 2011) */ WSP_DEV(APPLE, WELLSPRING5_ANSI, WSP_FLAG_WELLSPRING5), WSP_DEV(APPLE, WELLSPRING5_ISO, WSP_FLAG_WELLSPRING5), WSP_DEV(APPLE, WELLSPRING5_JIS, WSP_FLAG_WELLSPRING5), /* MacbookAir4,1 (unibody, July 2011) */ WSP_DEV(APPLE, WELLSPRING6A_ANSI, WSP_FLAG_WELLSPRING6A), WSP_DEV(APPLE, WELLSPRING6A_ISO, WSP_FLAG_WELLSPRING6A), WSP_DEV(APPLE, WELLSPRING6A_JIS, WSP_FLAG_WELLSPRING6A), /* MacbookAir4,2 (unibody, July 2011) */ WSP_DEV(APPLE, WELLSPRING6_ANSI, WSP_FLAG_WELLSPRING6), WSP_DEV(APPLE, WELLSPRING6_ISO, WSP_FLAG_WELLSPRING6), WSP_DEV(APPLE, WELLSPRING6_JIS, WSP_FLAG_WELLSPRING6), /* Macbook8,2 (unibody) */ WSP_DEV(APPLE, WELLSPRING5A_ANSI, WSP_FLAG_WELLSPRING5A), WSP_DEV(APPLE, WELLSPRING5A_ISO, WSP_FLAG_WELLSPRING5A), WSP_DEV(APPLE, WELLSPRING5A_JIS, WSP_FLAG_WELLSPRING5A), /* MacbookPro10,1 (unibody, June 2012) */ /* MacbookPro11,1-3 (unibody, June 2013) */ WSP_DEV(APPLE, WELLSPRING7_ANSI, WSP_FLAG_WELLSPRING7), WSP_DEV(APPLE, WELLSPRING7_ISO, WSP_FLAG_WELLSPRING7), WSP_DEV(APPLE, WELLSPRING7_JIS, WSP_FLAG_WELLSPRING7), /* MacbookPro10,2 (unibody, October 2012) */ WSP_DEV(APPLE, WELLSPRING7A_ANSI, WSP_FLAG_WELLSPRING7A), WSP_DEV(APPLE, WELLSPRING7A_ISO, WSP_FLAG_WELLSPRING7A), WSP_DEV(APPLE, WELLSPRING7A_JIS, WSP_FLAG_WELLSPRING7A), /* MacbookAir6,2 (unibody, June 2013) */ WSP_DEV(APPLE, WELLSPRING8_ANSI, WSP_FLAG_WELLSPRING8), WSP_DEV(APPLE, WELLSPRING8_ISO, WSP_FLAG_WELLSPRING8), WSP_DEV(APPLE, WELLSPRING8_JIS, WSP_FLAG_WELLSPRING8), /* MacbookPro12,1 MacbookPro11,4 */ WSP_DEV(APPLE, WELLSPRING9_ANSI, WSP_FLAG_WELLSPRING9), WSP_DEV(APPLE, WELLSPRING9_ISO, WSP_FLAG_WELLSPRING9), WSP_DEV(APPLE, WELLSPRING9_JIS, WSP_FLAG_WELLSPRING9), }; #define WSP_FIFO_BUF_SIZE 8 /* bytes */ #define WSP_FIFO_QUEUE_MAXLEN 50 /* units */ enum { WSP_INTR_DT, WSP_N_TRANSFER, }; struct wsp_softc { struct usb_device *sc_usb_device; struct mtx sc_mutex; /* for synchronization */ struct usb_xfer *sc_xfer[WSP_N_TRANSFER]; struct usb_fifo_sc sc_fifo; const struct wsp_dev_params *sc_params; /* device configuration */ mousehw_t sc_hw; mousemode_t sc_mode; u_int sc_pollrate; mousestatus_t sc_status; u_int sc_state; #define WSP_ENABLED 0x01 struct tp_finger *index[MAX_FINGERS]; /* finger index data */ int16_t pos_x[MAX_FINGERS]; /* position array */ int16_t pos_y[MAX_FINGERS]; /* position array */ u_int sc_touch; /* touch status */ #define WSP_UNTOUCH 0x00 #define WSP_FIRST_TOUCH 0x01 #define WSP_SECOND_TOUCH 0x02 #define WSP_TOUCHING 0x04 int16_t pre_pos_x; /* previous position array */ int16_t pre_pos_y; /* previous position array */ int dx_sum; /* x axis cumulative movement */ int dy_sum; /* y axis cumulative movement */ int dz_sum; /* z axis cumulative movement */ int dz_count; #define WSP_DZ_MAX_COUNT 32 int dt_sum; /* T-axis cumulative movement */ int rdx; /* x axis remainder of divide by scale_factor */ int rdy; /* y axis remainder of divide by scale_factor */ int rdz; /* z axis remainder of divide by scale_factor */ int tp_datalen; uint8_t o_ntouch; /* old touch finger status */ uint8_t finger; /* 0 or 1 *, check which finger moving */ uint16_t intr_count; #define WSP_TAP_THRESHOLD 3 #define WSP_TAP_MAX_COUNT 20 int distance; /* the distance of 2 fingers */ #define MAX_DISTANCE 2500 /* the max allowed distance */ uint8_t ibtn; /* button status in tapping */ uint8_t ntaps; /* finger status in tapping */ uint8_t scr_mode; /* scroll status in movement */ #define WSP_SCR_NONE 0 #define WSP_SCR_VER 1 #define WSP_SCR_HOR 2 uint8_t tp_data[WSP_BUFFER_MAX] __aligned(4); /* trackpad transferred data */ }; /* * function prototypes */ static usb_fifo_cmd_t wsp_start_read; static usb_fifo_cmd_t wsp_stop_read; static usb_fifo_open_t wsp_open; static usb_fifo_close_t wsp_close; static usb_fifo_ioctl_t wsp_ioctl; static struct usb_fifo_methods wsp_fifo_methods = { .f_open = &wsp_open, .f_close = &wsp_close, .f_ioctl = &wsp_ioctl, .f_start_read = &wsp_start_read, .f_stop_read = &wsp_stop_read, .basename[0] = WSP_DRIVER_NAME, }; /* device initialization and shutdown */ static int wsp_enable(struct wsp_softc *sc); static void wsp_disable(struct wsp_softc *sc); /* updating fifo */ static void wsp_reset_buf(struct wsp_softc *sc); static void wsp_add_to_queue(struct wsp_softc *, int, int, int, uint32_t); /* Device methods. */ static device_probe_t wsp_probe; static device_attach_t wsp_attach; static device_detach_t wsp_detach; static usb_callback_t wsp_intr_callback; static const struct usb_config wsp_config[WSP_N_TRANSFER] = { [WSP_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 0, .short_xfer_ok = 1, }, .bufsize = WSP_BUFFER_MAX, .callback = &wsp_intr_callback, }, }; static usb_error_t wsp_set_device_mode(struct wsp_softc *sc, uint8_t on) { const struct wsp_dev_params *params = sc->sc_params; uint8_t mode_bytes[8]; usb_error_t err; /* Type 3 does not require a mode switch */ if (params->tp_type == TYPE3) return 0; err = usbd_req_get_report(sc->sc_usb_device, NULL, mode_bytes, params->um_size, params->iface_index, params->um_req_val, params->um_req_idx); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("Failed to read device mode (%d)\n", err); return (err); } /* * XXX Need to wait at least 250ms for hardware to get * ready. The device mode handling appears to be handled * asynchronously and we should not issue these commands too * quickly. */ pause("WHW", hz / 4); mode_bytes[params->um_switch_idx] = on ? params->um_switch_on : params->um_switch_off; return (usbd_req_set_report(sc->sc_usb_device, NULL, mode_bytes, params->um_size, params->iface_index, params->um_req_val, params->um_req_idx)); } static int wsp_enable(struct wsp_softc *sc) { /* reset status */ memset(&sc->sc_status, 0, sizeof(sc->sc_status)); sc->sc_state |= WSP_ENABLED; DPRINTFN(WSP_LLEVEL_INFO, "enabled wsp\n"); return (0); } static void wsp_disable(struct wsp_softc *sc) { sc->sc_state &= ~WSP_ENABLED; DPRINTFN(WSP_LLEVEL_INFO, "disabled wsp\n"); } static int wsp_probe(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct usb_interface_descriptor *id; struct usb_interface *iface; uint8_t i; if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); /* figure out first interface matching */ for (i = 1;; i++) { iface = usbd_get_iface(uaa->device, i); if (iface == NULL || i == 3) return (ENXIO); id = iface->idesc; if ((id == NULL) || (id->bInterfaceClass != UICLASS_HID) || (id->bInterfaceProtocol != 0 && id->bInterfaceProtocol != UIPROTO_MOUSE)) continue; break; } /* check if we are attaching to the first match */ if (uaa->info.bIfaceIndex != i) return (ENXIO); return (usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)); } static int wsp_attach(device_t dev) { struct wsp_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); usb_error_t err; void *d_ptr = NULL; uint16_t d_len; DPRINTFN(WSP_LLEVEL_INFO, "sc=%p\n", sc); /* Get HID descriptor */ err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (err == USB_ERR_NORMAL_COMPLETION) { /* Get HID report descriptor length */ sc->tp_datalen = hid_report_size(d_ptr, d_len, hid_input, NULL); free(d_ptr, M_TEMP); if (sc->tp_datalen <= 0 || sc->tp_datalen > WSP_BUFFER_MAX) { DPRINTF("Invalid datalength or too big " "datalength: %d\n", sc->tp_datalen); return (ENXIO); } } else { return (ENXIO); } sc->sc_usb_device = uaa->device; /* get device specific configuration */ sc->sc_params = wsp_dev_params + USB_GET_DRIVER_INFO(uaa); /* * By default the touchpad behaves like a HID device, sending * packets with reportID = 8. Such reports contain only * limited information. They encode movement deltas and button * events, but do not include data from the pressure * sensors. The device input mode can be switched from HID * reports to raw sensor data using vendor-specific USB * control commands: */ /* * During re-enumeration of the device we need to force the * device back into HID mode before switching it to RAW * mode. Else the device does not work like expected. */ err = wsp_set_device_mode(sc, 0); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("Failed to set mode to HID MODE (%d)\n", err); return (ENXIO); } err = wsp_set_device_mode(sc, 1); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("failed to set mode to RAW MODE (%d)\n", err); return (ENXIO); } mtx_init(&sc->sc_mutex, "wspmtx", NULL, MTX_DEF | MTX_RECURSE); err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, wsp_config, WSP_N_TRANSFER, sc, &sc->sc_mutex); if (err) { DPRINTF("error=%s\n", usbd_errstr(err)); goto detach; } if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex, &wsp_fifo_methods, &sc->sc_fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644)) { goto detach; } device_set_usb_desc(dev); sc->sc_hw.buttons = 3; sc->sc_hw.iftype = MOUSE_IF_USB; sc->sc_hw.type = MOUSE_PAD; sc->sc_hw.model = MOUSE_MODEL_GENERIC; sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.rate = -1; sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; sc->sc_touch = WSP_UNTOUCH; sc->scr_mode = WSP_SCR_NONE; return (0); detach: wsp_detach(dev); return (ENOMEM); } static int wsp_detach(device_t dev) { struct wsp_softc *sc = device_get_softc(dev); (void) wsp_set_device_mode(sc, 0); mtx_lock(&sc->sc_mutex); if (sc->sc_state & WSP_ENABLED) wsp_disable(sc); mtx_unlock(&sc->sc_mutex); usb_fifo_detach(&sc->sc_fifo); usbd_transfer_unsetup(sc->sc_xfer, WSP_N_TRANSFER); mtx_destroy(&sc->sc_mutex); return (0); } static void wsp_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct wsp_softc *sc = usbd_xfer_softc(xfer); const struct wsp_dev_params *params = sc->sc_params; struct usb_page_cache *pc; struct tp_finger *f; struct tp_header *h; struct wsp_tuning tun = wsp_tuning; int ntouch = 0; /* the finger number in touch */ int ibt = 0; /* button status */ int dx = 0; int dy = 0; int dz = 0; int rdx = 0; int rdy = 0; int rdz = 0; int len; int i; wsp_runing_rangecheck(&tun); if (sc->dz_count == 0) sc->dz_count = WSP_DZ_MAX_COUNT; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* copy out received data */ pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, sc->tp_data, len); if ((len < params->tp_offset + params->tp_fsize) || ((len - params->tp_offset) % params->tp_fsize) != 0) { DPRINTFN(WSP_LLEVEL_INFO, "Invalid length: %d, %x, %x\n", len, sc->tp_data[0], sc->tp_data[1]); goto tr_setup; } if (len < sc->tp_datalen) { /* make sure we don't process old data */ memset(sc->tp_data + len, 0, sc->tp_datalen - len); } h = (struct tp_header *)(sc->tp_data); if (params->tp_type >= TYPE2) { ibt = sc->tp_data[params->tp_button]; ntouch = sc->tp_data[params->tp_button - 1]; } /* range check */ if (ntouch < 0) ntouch = 0; else if (ntouch > MAX_FINGERS) ntouch = MAX_FINGERS; for (i = 0; i != ntouch; i++) { f = (struct tp_finger *)(sc->tp_data + params->tp_offset + params->tp_delta + i * params->tp_fsize); /* swap endianness, if any */ if (le16toh(0x1234) != 0x1234) { f->origin = le16toh((uint16_t)f->origin); f->abs_x = le16toh((uint16_t)f->abs_x); f->abs_y = le16toh((uint16_t)f->abs_y); f->rel_x = le16toh((uint16_t)f->rel_x); f->rel_y = le16toh((uint16_t)f->rel_y); f->tool_major = le16toh((uint16_t)f->tool_major); f->tool_minor = le16toh((uint16_t)f->tool_minor); f->orientation = le16toh((uint16_t)f->orientation); f->touch_major = le16toh((uint16_t)f->touch_major); f->touch_minor = le16toh((uint16_t)f->touch_minor); f->pressure = le16toh((uint16_t)f->pressure); f->multi = le16toh((uint16_t)f->multi); } DPRINTFN(WSP_LLEVEL_INFO, "[%d]ibt=%d, taps=%d, o=%4d, ax=%5d, ay=%5d, " "rx=%5d, ry=%5d, tlmaj=%4d, tlmin=%4d, ot=%4x, " "tchmaj=%4d, tchmin=%4d, presure=%4d, m=%4x\n", i, ibt, ntouch, f->origin, f->abs_x, f->abs_y, f->rel_x, f->rel_y, f->tool_major, f->tool_minor, f->orientation, f->touch_major, f->touch_minor, f->pressure, f->multi); sc->pos_x[i] = f->abs_x; sc->pos_y[i] = -f->abs_y; sc->index[i] = f; } sc->sc_status.flags &= ~MOUSE_POSCHANGED; sc->sc_status.flags &= ~MOUSE_STDBUTTONSCHANGED; sc->sc_status.obutton = sc->sc_status.button; sc->sc_status.button = 0; if (ibt != 0) { if ((params->caps & HAS_INTEGRATED_BUTTON) && ntouch == 2) sc->sc_status.button |= MOUSE_BUTTON3DOWN; else if ((params->caps & HAS_INTEGRATED_BUTTON) && ntouch == 3) sc->sc_status.button |= MOUSE_BUTTON2DOWN; else sc->sc_status.button |= MOUSE_BUTTON1DOWN; sc->ibtn = 1; } sc->intr_count++; if (sc->ntaps < ntouch) { switch (ntouch) { case 1: if (sc->index[0]->touch_major > tun.pressure_tap_threshold && sc->index[0]->tool_major <= 1200) sc->ntaps = 1; break; case 2: if (sc->index[0]->touch_major > tun.pressure_tap_threshold-30 && sc->index[1]->touch_major > tun.pressure_tap_threshold-30) sc->ntaps = 2; break; case 3: if (sc->index[0]->touch_major > tun.pressure_tap_threshold-40 && sc->index[1]->touch_major > tun.pressure_tap_threshold-40 && sc->index[2]->touch_major > tun.pressure_tap_threshold-40) sc->ntaps = 3; break; default: break; } } if (ntouch == 2) { sc->distance = max(sc->distance, max( abs(sc->pos_x[0] - sc->pos_x[1]), abs(sc->pos_y[0] - sc->pos_y[1]))); } if (sc->index[0]->touch_major < tun.pressure_untouch_threshold && sc->sc_status.button == 0) { sc->sc_touch = WSP_UNTOUCH; if (sc->intr_count < WSP_TAP_MAX_COUNT && sc->intr_count > WSP_TAP_THRESHOLD && sc->ntaps && sc->ibtn == 0) { /* * Add a pair of events (button-down and * button-up). */ switch (sc->ntaps) { case 1: if (!(params->caps & HAS_INTEGRATED_BUTTON) || tun.enable_single_tap_clicks) { wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN); DPRINTFN(WSP_LLEVEL_INFO, "LEFT CLICK!\n"); } break; case 2: DPRINTFN(WSP_LLEVEL_INFO, "sum_x=%5d, sum_y=%5d\n", sc->dx_sum, sc->dy_sum); if (sc->distance < MAX_DISTANCE && abs(sc->dx_sum) < 5 && abs(sc->dy_sum) < 5) { wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN); DPRINTFN(WSP_LLEVEL_INFO, "RIGHT CLICK!\n"); } break; case 3: wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN); break; default: /* we don't handle taps of more than three fingers */ break; } wsp_add_to_queue(sc, 0, 0, 0, 0); /* button release */ } if ((sc->dt_sum / tun.scr_hor_threshold) != 0 && sc->ntaps == 2 && sc->scr_mode == WSP_SCR_HOR) { /* * translate T-axis into button presses * until further */ if (sc->dt_sum > 0) wsp_add_to_queue(sc, 0, 0, 0, 1UL << 3); else if (sc->dt_sum < 0) wsp_add_to_queue(sc, 0, 0, 0, 1UL << 4); } sc->dz_count = WSP_DZ_MAX_COUNT; sc->dz_sum = 0; sc->intr_count = 0; sc->ibtn = 0; sc->ntaps = 0; sc->finger = 0; sc->distance = 0; sc->dt_sum = 0; sc->dx_sum = 0; sc->dy_sum = 0; sc->rdx = 0; sc->rdy = 0; sc->rdz = 0; sc->scr_mode = WSP_SCR_NONE; } else if (sc->index[0]->touch_major >= tun.pressure_touch_threshold && sc->sc_touch == WSP_UNTOUCH) { /* ignore first touch */ sc->sc_touch = WSP_FIRST_TOUCH; } else if (sc->index[0]->touch_major >= tun.pressure_touch_threshold && sc->sc_touch == WSP_FIRST_TOUCH) { /* ignore second touch */ sc->sc_touch = WSP_SECOND_TOUCH; DPRINTFN(WSP_LLEVEL_INFO, "Fist pre_x=%5d, pre_y=%5d\n", sc->pre_pos_x, sc->pre_pos_y); } else { if (sc->sc_touch == WSP_SECOND_TOUCH) sc->sc_touch = WSP_TOUCHING; if (ntouch != 0 && sc->index[0]->touch_major >= tun.pressure_touch_threshold) { dx = sc->pos_x[0] - sc->pre_pos_x; dy = sc->pos_y[0] - sc->pre_pos_y; /* Ignore movement during button is releasing */ if (sc->ibtn != 0 && sc->sc_status.button == 0) dx = dy = 0; /* Ignore movement if ntouch changed */ if (sc->o_ntouch != ntouch) dx = dy = 0; /* Ignore unexpeted movement when typing */ if (ntouch == 1 && sc->index[0]->tool_major > 1200) dx = dy = 0; if (sc->ibtn != 0 && ntouch == 1 && sc->intr_count < WSP_TAP_MAX_COUNT && abs(sc->dx_sum) < 1 && abs(sc->dy_sum) < 1 ) dx = dy = 0; if (ntouch == 2 && sc->sc_status.button != 0) { dx = sc->pos_x[sc->finger] - sc->pre_pos_x; dy = sc->pos_y[sc->finger] - sc->pre_pos_y; /* * Ignore movement of switch finger or * movement from ibt=0 to ibt=1 */ if (sc->index[0]->origin == 0 || sc->index[1]->origin == 0 || sc->sc_status.obutton != sc->sc_status.button) { dx = dy = 0; sc->finger = 0; } if ((abs(sc->index[0]->rel_x) + abs(sc->index[0]->rel_y)) < (abs(sc->index[1]->rel_x) + abs(sc->index[1]->rel_y)) && sc->finger == 0) { sc->sc_touch = WSP_SECOND_TOUCH; dx = dy = 0; sc->finger = 1; } if ((abs(sc->index[0]->rel_x) + abs(sc->index[0]->rel_y)) >= (abs(sc->index[1]->rel_x) + abs(sc->index[1]->rel_y)) && sc->finger == 1) { sc->sc_touch = WSP_SECOND_TOUCH; dx = dy = 0; sc->finger = 0; } DPRINTFN(WSP_LLEVEL_INFO, "dx=%5d, dy=%5d, mov=%5d\n", dx, dy, sc->finger); } if (sc->dz_count--) { rdz = (dy + sc->rdz) % tun.scale_factor; sc->dz_sum -= (dy + sc->rdz) / tun.scale_factor; sc->rdz = rdz; } if ((sc->dz_sum / tun.z_factor) != 0) sc->dz_count = 0; } rdx = (dx + sc->rdx) % tun.scale_factor; dx = (dx + sc->rdx) / tun.scale_factor; sc->rdx = rdx; rdy = (dy + sc->rdy) % tun.scale_factor; dy = (dy + sc->rdy) / tun.scale_factor; sc->rdy = rdy; sc->dx_sum += dx; sc->dy_sum += dy; if (ntouch == 2 && sc->sc_status.button == 0) { if (sc->scr_mode == WSP_SCR_NONE && abs(sc->dx_sum) + abs(sc->dy_sum) > tun.scr_hor_threshold) sc->scr_mode = abs(sc->dx_sum) > abs(sc->dy_sum) * 2 ? WSP_SCR_HOR : WSP_SCR_VER; DPRINTFN(WSP_LLEVEL_INFO, "scr_mode=%5d, count=%d, dx_sum=%d, dy_sum=%d\n", sc->scr_mode, sc->intr_count, sc->dx_sum, sc->dy_sum); if (sc->scr_mode == WSP_SCR_HOR) sc->dt_sum += dx; else sc->dt_sum = 0; dx = dy = 0; if (sc->dz_count == 0) dz = sc->dz_sum / tun.z_factor; if (sc->scr_mode == WSP_SCR_HOR || abs(sc->pos_x[0] - sc->pos_x[1]) > MAX_DISTANCE || abs(sc->pos_y[0] - sc->pos_y[1]) > MAX_DISTANCE) dz = 0; } if (ntouch == 3) dx = dy = dz = 0; if (sc->intr_count < WSP_TAP_MAX_COUNT && abs(dx) < 3 && abs(dy) < 3 && abs(dz) < 3) dx = dy = dz = 0; else sc->intr_count = WSP_TAP_MAX_COUNT; if (dx || dy || dz) sc->sc_status.flags |= MOUSE_POSCHANGED; DPRINTFN(WSP_LLEVEL_INFO, "dx=%5d, dy=%5d, dz=%5d, sc_touch=%x, btn=%x\n", dx, dy, dz, sc->sc_touch, sc->sc_status.button); sc->sc_status.dx += dx; sc->sc_status.dy += dy; sc->sc_status.dz += dz; wsp_add_to_queue(sc, dx, -dy, dz, sc->sc_status.button); if (sc->dz_count == 0) { sc->dz_sum = 0; sc->rdz = 0; } } sc->pre_pos_x = sc->pos_x[0]; sc->pre_pos_y = sc->pos_y[0]; if (ntouch == 2 && sc->sc_status.button != 0) { sc->pre_pos_x = sc->pos_x[sc->finger]; sc->pre_pos_y = sc->pos_y[sc->finger]; } sc->o_ntouch = ntouch; case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ if (usb_fifo_put_bytes_max( sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { usbd_xfer_set_frame_len(xfer, 0, sc->tp_datalen); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void wsp_add_to_queue(struct wsp_softc *sc, int dx, int dy, int dz, uint32_t buttons_in) { uint32_t buttons_out; uint8_t buf[8]; dx = imin(dx, 254); dx = imax(dx, -256); dy = imin(dy, 254); dy = imax(dy, -256); dz = imin(dz, 126); dz = imax(dz, -128); buttons_out = MOUSE_MSC_BUTTONS; if (buttons_in & MOUSE_BUTTON1DOWN) buttons_out &= ~MOUSE_MSC_BUTTON1UP; else if (buttons_in & MOUSE_BUTTON2DOWN) buttons_out &= ~MOUSE_MSC_BUTTON2UP; else if (buttons_in & MOUSE_BUTTON3DOWN) buttons_out &= ~MOUSE_MSC_BUTTON3UP; /* Encode the mouse data in standard format; refer to mouse(4) */ buf[0] = sc->sc_mode.syncmask[1]; buf[0] |= buttons_out; buf[1] = dx >> 1; buf[2] = dy >> 1; buf[3] = dx - (dx >> 1); buf[4] = dy - (dy >> 1); /* Encode extra bytes for level 1 */ if (sc->sc_mode.level == 1) { buf[5] = dz >> 1; /* dz / 2 */ buf[6] = dz - (dz >> 1);/* dz - (dz / 2) */ buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS); } usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, sc->sc_mode.packetsize, 1); } static void wsp_reset_buf(struct wsp_softc *sc) { /* reset read queue */ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); } static void wsp_start_read(struct usb_fifo *fifo) { struct wsp_softc *sc = usb_fifo_softc(fifo); int rate; /* Check if we should override the default polling interval */ rate = sc->sc_pollrate; /* Range check rate */ if (rate > 1000) rate = 1000; /* Check for set rate */ if ((rate > 0) && (sc->sc_xfer[WSP_INTR_DT] != NULL)) { /* Stop current transfer, if any */ usbd_transfer_stop(sc->sc_xfer[WSP_INTR_DT]); /* Set new interval */ usbd_xfer_set_interval(sc->sc_xfer[WSP_INTR_DT], 1000 / rate); /* Only set pollrate once */ sc->sc_pollrate = 0; } usbd_transfer_start(sc->sc_xfer[WSP_INTR_DT]); } static void wsp_stop_read(struct usb_fifo *fifo) { struct wsp_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[WSP_INTR_DT]); } static int wsp_open(struct usb_fifo *fifo, int fflags) { DPRINTFN(WSP_LLEVEL_INFO, "\n"); if (fflags & FREAD) { struct wsp_softc *sc = usb_fifo_softc(fifo); int rc; if (sc->sc_state & WSP_ENABLED) return (EBUSY); if (usb_fifo_alloc_buffer(fifo, WSP_FIFO_BUF_SIZE, WSP_FIFO_QUEUE_MAXLEN)) { return (ENOMEM); } rc = wsp_enable(sc); if (rc != 0) { usb_fifo_free_buffer(fifo); return (rc); } } return (0); } static void wsp_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { struct wsp_softc *sc = usb_fifo_softc(fifo); wsp_disable(sc); usb_fifo_free_buffer(fifo); } } int wsp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { struct wsp_softc *sc = usb_fifo_softc(fifo); mousemode_t mode; int error = 0; mtx_lock(&sc->sc_mutex); switch (cmd) { case MOUSE_GETHWINFO: *(mousehw_t *)addr = sc->sc_hw; break; case MOUSE_GETMODE: *(mousemode_t *)addr = sc->sc_mode; break; case MOUSE_SETMODE: mode = *(mousemode_t *)addr; if (mode.level == -1) /* Don't change the current setting */ ; else if ((mode.level < 0) || (mode.level > 1)) { error = EINVAL; goto done; } sc->sc_mode.level = mode.level; sc->sc_pollrate = mode.rate; sc->sc_hw.buttons = 3; if (sc->sc_mode.level == 0) { sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->sc_mode.level == 1) { sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; } wsp_reset_buf(sc); break; case MOUSE_GETLEVEL: *(int *)addr = sc->sc_mode.level; break; case MOUSE_SETLEVEL: if (*(int *)addr < 0 || *(int *)addr > 1) { error = EINVAL; goto done; } sc->sc_mode.level = *(int *)addr; sc->sc_hw.buttons = 3; if (sc->sc_mode.level == 0) { sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->sc_mode.level == 1) { sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; } wsp_reset_buf(sc); break; case MOUSE_GETSTATUS:{ mousestatus_t *status = (mousestatus_t *)addr; *status = sc->sc_status; sc->sc_status.obutton = sc->sc_status.button; sc->sc_status.button = 0; sc->sc_status.dx = 0; sc->sc_status.dy = 0; sc->sc_status.dz = 0; if (status->dx || status->dy || status->dz) status->flags |= MOUSE_POSCHANGED; if (status->button != status->obutton) status->flags |= MOUSE_BUTTONSCHANGED; break; } default: error = ENOTTY; } done: mtx_unlock(&sc->sc_mutex); return (error); } static device_method_t wsp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, wsp_probe), DEVMETHOD(device_attach, wsp_attach), DEVMETHOD(device_detach, wsp_detach), DEVMETHOD_END }; static driver_t wsp_driver = { .name = WSP_DRIVER_NAME, .methods = wsp_methods, .size = sizeof(struct wsp_softc) }; static devclass_t wsp_devclass; DRIVER_MODULE(wsp, uhub, wsp_driver, wsp_devclass, NULL, 0); MODULE_DEPEND(wsp, usb, 1, 1, 1); MODULE_VERSION(wsp, 1); USB_PNP_HOST_INFO(wsp_devs); Index: head/sys/dev/usb/misc/udbp.c =================================================================== --- head/sys/dev/usb/misc/udbp.c (revision 357971) +++ head/sys/dev/usb/misc/udbp.c (revision 357972) @@ -1,863 +1,864 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1996-2000 Whistle Communications, Inc. * 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. * 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. Neither the name of author 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 NICK HIBMA 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 AUTHOR 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 __FBSDID("$FreeBSD$"); /* Driver for arbitrary double bulk pipe devices. * The driver assumes that there will be the same driver on the other side. * * XXX Some more information on what the framing of the IP packets looks like. * * To take full advantage of bulk transmission, packets should be chosen * between 1k and 5k in size (1k to make sure the sending side starts * streaming, and <5k to avoid overflowing the system with small TDs). */ /* probe/attach/detach: * Connect the driver to the hardware and netgraph * * The reason we submit a bulk in transfer is that USB does not know about * interrupts. The bulk transfer continuously polls the device for data. * While the device has no data available, the device NAKs the TDs. As soon * as there is data, the transfer happens and the data comes flowing in. * * In case you were wondering, interrupt transfers happen exactly that way. * It therefore doesn't make sense to use the interrupt pipe to signal * 'data ready' and then schedule a bulk transfer to fetch it. That would * incur a 2ms delay at least, without reducing bandwidth requirements. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR udbp_debug #include #include #include #include #include #include #include #ifdef USB_DEBUG static int udbp_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, udbp, CTLFLAG_RW, 0, "USB udbp"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, udbp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB udbp"); SYSCTL_INT(_hw_usb_udbp, OID_AUTO, debug, CTLFLAG_RWTUN, &udbp_debug, 0, "udbp debug level"); #endif #define UDBP_TIMEOUT 2000 /* timeout on outbound transfers, in * msecs */ #define UDBP_BUFFERSIZE MCLBYTES /* maximum number of bytes in one * transfer */ #define UDBP_T_WR 0 #define UDBP_T_RD 1 #define UDBP_T_WR_CS 2 #define UDBP_T_RD_CS 3 #define UDBP_T_MAX 4 #define UDBP_Q_MAXLEN 50 struct udbp_softc { struct mtx sc_mtx; struct ng_bt_mbufq sc_xmitq_hipri; /* hi-priority transmit queue */ struct ng_bt_mbufq sc_xmitq; /* low-priority transmit queue */ struct usb_xfer *sc_xfer[UDBP_T_MAX]; node_p sc_node; /* back pointer to node */ hook_p sc_hook; /* pointer to the hook */ struct mbuf *sc_bulk_in_buffer; uint32_t sc_packets_in; /* packets in from downstream */ uint32_t sc_packets_out; /* packets out towards downstream */ uint8_t sc_flags; #define UDBP_FLAG_READ_STALL 0x01 /* read transfer stalled */ #define UDBP_FLAG_WRITE_STALL 0x02 /* write transfer stalled */ uint8_t sc_name[16]; }; /* prototypes */ static int udbp_modload(module_t mod, int event, void *data); static device_probe_t udbp_probe; static device_attach_t udbp_attach; static device_detach_t udbp_detach; static usb_callback_t udbp_bulk_read_callback; static usb_callback_t udbp_bulk_read_clear_stall_callback; static usb_callback_t udbp_bulk_write_callback; static usb_callback_t udbp_bulk_write_clear_stall_callback; static void udbp_bulk_read_complete(node_p, hook_p, void *, int); static ng_constructor_t ng_udbp_constructor; static ng_rcvmsg_t ng_udbp_rcvmsg; static ng_shutdown_t ng_udbp_rmnode; static ng_newhook_t ng_udbp_newhook; static ng_connect_t ng_udbp_connect; static ng_rcvdata_t ng_udbp_rcvdata; static ng_disconnect_t ng_udbp_disconnect; /* Parse type for struct ngudbpstat */ static const struct ng_parse_struct_field ng_udbp_stat_type_fields[] = NG_UDBP_STATS_TYPE_INFO; static const struct ng_parse_type ng_udbp_stat_type = { &ng_parse_struct_type, &ng_udbp_stat_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_udbp_cmdlist[] = { { NGM_UDBP_COOKIE, NGM_UDBP_GET_STATUS, "getstatus", NULL, &ng_udbp_stat_type, }, { NGM_UDBP_COOKIE, NGM_UDBP_SET_FLAG, "setflag", &ng_parse_int32_type, NULL }, {0} }; /* Netgraph node type descriptor */ static struct ng_type ng_udbp_typestruct = { .version = NG_ABI_VERSION, .name = NG_UDBP_NODE_TYPE, .constructor = ng_udbp_constructor, .rcvmsg = ng_udbp_rcvmsg, .shutdown = ng_udbp_rmnode, .newhook = ng_udbp_newhook, .connect = ng_udbp_connect, .rcvdata = ng_udbp_rcvdata, .disconnect = ng_udbp_disconnect, .cmdlist = ng_udbp_cmdlist, }; /* USB config */ static const struct usb_config udbp_config[UDBP_T_MAX] = { [UDBP_T_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UDBP_BUFFERSIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &udbp_bulk_write_callback, .timeout = UDBP_TIMEOUT, }, [UDBP_T_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UDBP_BUFFERSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &udbp_bulk_read_callback, }, [UDBP_T_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &udbp_bulk_write_clear_stall_callback, .timeout = 1000, /* 1 second */ .interval = 50, /* 50ms */ }, [UDBP_T_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &udbp_bulk_read_clear_stall_callback, .timeout = 1000, /* 1 second */ .interval = 50, /* 50ms */ }, }; static devclass_t udbp_devclass; static device_method_t udbp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, udbp_probe), DEVMETHOD(device_attach, udbp_attach), DEVMETHOD(device_detach, udbp_detach), DEVMETHOD_END }; static driver_t udbp_driver = { .name = "udbp", .methods = udbp_methods, .size = sizeof(struct udbp_softc), }; static const STRUCT_USB_HOST_ID udbp_devs[] = { {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U258, 0)}, {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_TURBOCONNECT, 0)}, {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_GADGETZERO, 0)}, {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2301, 0)}, {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2302, 0)}, {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL27A1, 0)}, {USB_VPI(USB_VENDOR_ANCHOR, USB_PRODUCT_ANCHOR_EZLINK, 0)}, {USB_VPI(USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL620USB, 0)}, }; DRIVER_MODULE(udbp, uhub, udbp_driver, udbp_devclass, udbp_modload, 0); MODULE_DEPEND(udbp, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); MODULE_DEPEND(udbp, usb, 1, 1, 1); MODULE_VERSION(udbp, 1); USB_PNP_HOST_INFO(udbp_devs); static int udbp_modload(module_t mod, int event, void *data) { int error; switch (event) { case MOD_LOAD: error = ng_newtype(&ng_udbp_typestruct); if (error != 0) { printf("%s: Could not register " "Netgraph node type, error=%d\n", NG_UDBP_NODE_TYPE, error); } break; case MOD_UNLOAD: error = ng_rmtype(&ng_udbp_typestruct); break; default: error = EOPNOTSUPP; break; } return (error); } static int udbp_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(udbp_devs, sizeof(udbp_devs), uaa)); } static int udbp_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct udbp_softc *sc = device_get_softc(dev); int error; device_set_usb_desc(dev); snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", device_get_nameunit(dev)); mtx_init(&sc->sc_mtx, "udbp lock", NULL, MTX_DEF | MTX_RECURSE); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, udbp_config, UDBP_T_MAX, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } NG_BT_MBUFQ_INIT(&sc->sc_xmitq, UDBP_Q_MAXLEN); NG_BT_MBUFQ_INIT(&sc->sc_xmitq_hipri, UDBP_Q_MAXLEN); /* create Netgraph node */ if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) { printf("%s: Could not create Netgraph node\n", sc->sc_name); sc->sc_node = NULL; goto detach; } /* name node */ if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { printf("%s: Could not name node\n", sc->sc_name); NG_NODE_UNREF(sc->sc_node); sc->sc_node = NULL; goto detach; } NG_NODE_SET_PRIVATE(sc->sc_node, sc); /* the device is now operational */ return (0); /* success */ detach: udbp_detach(dev); return (ENOMEM); /* failure */ } static int udbp_detach(device_t dev) { struct udbp_softc *sc = device_get_softc(dev); /* destroy Netgraph node */ if (sc->sc_node != NULL) { NG_NODE_SET_PRIVATE(sc->sc_node, NULL); ng_rmnode_self(sc->sc_node); sc->sc_node = NULL; } /* free USB transfers, if any */ usbd_transfer_unsetup(sc->sc_xfer, UDBP_T_MAX); mtx_destroy(&sc->sc_mtx); /* destroy queues */ NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq); NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq_hipri); /* extra check */ if (sc->sc_bulk_in_buffer) { m_freem(sc->sc_bulk_in_buffer); sc->sc_bulk_in_buffer = NULL; } return (0); /* success */ } static void udbp_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct udbp_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct mbuf *m; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* allocate new mbuf */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { goto tr_setup; } if (!(MCLGET(m, M_NOWAIT))) { m_freem(m); goto tr_setup; } m->m_pkthdr.len = m->m_len = actlen; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, m->m_data, actlen); sc->sc_bulk_in_buffer = m; DPRINTF("received package %d bytes\n", actlen); case USB_ST_SETUP: tr_setup: if (sc->sc_bulk_in_buffer) { ng_send_fn(sc->sc_node, NULL, &udbp_bulk_read_complete, NULL, 0); return; } if (sc->sc_flags & UDBP_FLAG_READ_STALL) { usbd_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]); return; } usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ sc->sc_flags |= UDBP_FLAG_READ_STALL; usbd_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]); } return; } } static void udbp_bulk_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct udbp_softc *sc = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = sc->sc_xfer[UDBP_T_RD]; if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTF("stall cleared\n"); sc->sc_flags &= ~UDBP_FLAG_READ_STALL; usbd_transfer_start(xfer_other); } } static void udbp_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2) { struct udbp_softc *sc = NG_NODE_PRIVATE(node); struct mbuf *m; int error; if (sc == NULL) { return; } mtx_lock(&sc->sc_mtx); m = sc->sc_bulk_in_buffer; if (m) { sc->sc_bulk_in_buffer = NULL; if ((sc->sc_hook == NULL) || NG_HOOK_NOT_VALID(sc->sc_hook)) { DPRINTF("No upstream hook\n"); goto done; } sc->sc_packets_in++; NG_SEND_DATA_ONLY(error, sc->sc_hook, m); m = NULL; } done: if (m) { m_freem(m); } /* start USB bulk-in transfer, if not already started */ usbd_transfer_start(sc->sc_xfer[UDBP_T_RD]); mtx_unlock(&sc->sc_mtx); } static void udbp_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct udbp_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct mbuf *m; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_packets_out++; case USB_ST_SETUP: if (sc->sc_flags & UDBP_FLAG_WRITE_STALL) { usbd_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]); return; } /* get next mbuf, if any */ NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq_hipri, m); if (m == NULL) { NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq, m); if (m == NULL) { DPRINTF("Data queue is empty\n"); return; } } if (m->m_pkthdr.len > MCLBYTES) { DPRINTF("truncating large packet " "from %d to %d bytes\n", m->m_pkthdr.len, MCLBYTES); m->m_pkthdr.len = MCLBYTES; } pc = usbd_xfer_get_frame(xfer, 0); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); DPRINTF("packet out: %d bytes\n", m->m_pkthdr.len); m_freem(m); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ sc->sc_flags |= UDBP_FLAG_WRITE_STALL; usbd_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]); } return; } } static void udbp_bulk_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct udbp_softc *sc = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = sc->sc_xfer[UDBP_T_WR]; if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTF("stall cleared\n"); sc->sc_flags &= ~UDBP_FLAG_WRITE_STALL; usbd_transfer_start(xfer_other); } } /*********************************************************************** * Start of Netgraph methods **********************************************************************/ /* * If this is a device node so this work is done in the attach() * routine and the constructor will return EINVAL as you should not be able * to create nodes that depend on hardware (unless you can add the hardware :) */ static int ng_udbp_constructor(node_p node) { return (EINVAL); } /* * Give our ok for a hook to be added... * If we are not running this might kick a device into life. * Possibly decode information out of the hook name. * Add the hook's private info to the hook structure. * (if we had some). In this example, we assume that there is a * an array of structs, called 'channel' in the private info, * one for each active channel. The private * pointer of each hook points to the appropriate UDBP_hookinfo struct * so that the source of an input packet is easily identified. */ static int ng_udbp_newhook(node_p node, hook_p hook, const char *name) { struct udbp_softc *sc = NG_NODE_PRIVATE(node); int32_t error = 0; if (strcmp(name, NG_UDBP_HOOK_NAME)) { return (EINVAL); } mtx_lock(&sc->sc_mtx); if (sc->sc_hook != NULL) { error = EISCONN; } else { sc->sc_hook = hook; NG_HOOK_SET_PRIVATE(hook, NULL); } mtx_unlock(&sc->sc_mtx); return (error); } /* * Get a netgraph control message. * Check it is one we understand. If needed, send a response. * We could save the address for an async action later, but don't here. * Always free the message. * The response should be in a malloc'd region that the caller can 'free'. * A response is not required. * Theoretically you could respond defferently to old message types if * the cookie in the header didn't match what we consider to be current * (so that old userland programs could continue to work). */ static int ng_udbp_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct udbp_softc *sc = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_UDBP_COOKIE: switch (msg->header.cmd) { case NGM_UDBP_GET_STATUS: { struct ngudbpstat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) { error = ENOMEM; break; } stats = (struct ngudbpstat *)resp->data; mtx_lock(&sc->sc_mtx); stats->packets_in = sc->sc_packets_in; stats->packets_out = sc->sc_packets_out; mtx_unlock(&sc->sc_mtx); break; } case NGM_UDBP_SET_FLAG: if (msg->header.arglen != sizeof(uint32_t)) { error = EINVAL; break; } DPRINTF("flags = 0x%08x\n", *((uint32_t *)msg->data)); break; default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } /* Take care of synchronous response, if any */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Accept data from the hook and queue it for output. */ static int ng_udbp_rcvdata(hook_p hook, item_p item) { struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ng_bt_mbufq *queue_ptr; struct mbuf *m; struct ng_tag_prio *ptag; int error; if (sc == NULL) { NG_FREE_ITEM(item); return (EHOSTDOWN); } NGI_GET_M(item, m); NG_FREE_ITEM(item); /* * Now queue the data for when it can be sent */ ptag = (void *)m_tag_locate(m, NGM_GENERIC_COOKIE, NG_TAG_PRIO, NULL); if (ptag && (ptag->priority > NG_PRIO_CUTOFF)) queue_ptr = &sc->sc_xmitq_hipri; else queue_ptr = &sc->sc_xmitq; mtx_lock(&sc->sc_mtx); if (NG_BT_MBUFQ_FULL(queue_ptr)) { NG_BT_MBUFQ_DROP(queue_ptr); NG_FREE_M(m); error = ENOBUFS; } else { NG_BT_MBUFQ_ENQUEUE(queue_ptr, m); /* * start bulk-out transfer, if not already started: */ usbd_transfer_start(sc->sc_xfer[UDBP_T_WR]); error = 0; } mtx_unlock(&sc->sc_mtx); return (error); } /* * Do local shutdown processing.. * We are a persistent device, we refuse to go away, and * only remove our links and reset ourself. */ static int ng_udbp_rmnode(node_p node) { struct udbp_softc *sc = NG_NODE_PRIVATE(node); /* Let old node go */ NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); /* forget it ever existed */ if (sc == NULL) { goto done; } /* Create Netgraph node */ if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) { printf("%s: Could not create Netgraph node\n", sc->sc_name); sc->sc_node = NULL; goto done; } /* Name node */ if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { printf("%s: Could not name Netgraph node\n", sc->sc_name); NG_NODE_UNREF(sc->sc_node); sc->sc_node = NULL; goto done; } NG_NODE_SET_PRIVATE(sc->sc_node, sc); done: if (sc) { mtx_unlock(&sc->sc_mtx); } return (0); } /* * This is called once we've already connected a new hook to the other node. * It gives us a chance to balk at the last minute. */ static int ng_udbp_connect(hook_p hook) { struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); /* probably not at splnet, force outward queueing */ NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); mtx_lock(&sc->sc_mtx); sc->sc_flags |= (UDBP_FLAG_READ_STALL | UDBP_FLAG_WRITE_STALL); /* start bulk-in transfer */ usbd_transfer_start(sc->sc_xfer[UDBP_T_RD]); /* start bulk-out transfer */ usbd_transfer_start(sc->sc_xfer[UDBP_T_WR]); mtx_unlock(&sc->sc_mtx); return (0); } /* * Dook disconnection * * For this type, removal of the last link destroys the node */ static int ng_udbp_disconnect(hook_p hook) { struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int error = 0; if (sc != NULL) { mtx_lock(&sc->sc_mtx); if (hook != sc->sc_hook) { error = EINVAL; } else { /* stop bulk-in transfer */ usbd_transfer_stop(sc->sc_xfer[UDBP_T_RD_CS]); usbd_transfer_stop(sc->sc_xfer[UDBP_T_RD]); /* stop bulk-out transfer */ usbd_transfer_stop(sc->sc_xfer[UDBP_T_WR_CS]); usbd_transfer_stop(sc->sc_xfer[UDBP_T_WR]); /* cleanup queues */ NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq); NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq_hipri); if (sc->sc_bulk_in_buffer) { m_freem(sc->sc_bulk_in_buffer); sc->sc_bulk_in_buffer = NULL; } sc->sc_hook = NULL; } mtx_unlock(&sc->sc_mtx); } if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (error); } Index: head/sys/dev/usb/misc/ugold.c =================================================================== --- head/sys/dev/usb/misc/ugold.c (revision 357971) +++ head/sys/dev/usb/misc/ugold.c (revision 357972) @@ -1,406 +1,406 @@ /* $OpenBSD: ugold.c,v 1.7 2014/12/11 18:39:27 mpi Exp $ */ /* * Copyright (c) 2013 Takayoshi SASANO * Copyright (c) 2013 Martin Pieuchot * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Driver for Microdia's HID based TEMPer Temperature sensor */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR usb_debug #include #define UGOLD_INNER 0 #define UGOLD_OUTER 1 #define UGOLD_MAX_SENSORS 2 #define UGOLD_CMD_DATA 0x80 #define UGOLD_CMD_INIT 0x82 enum { UGOLD_INTR_DT, UGOLD_N_TRANSFER, }; /* * This driver only uses two of the three known commands for the * TEMPerV1.2 device. * * The first byte of the answer corresponds to the command and the * second one seems to be the size (in bytes) of the answer. * * The device always sends 8 bytes and if the length of the answer * is less than that, it just leaves the last bytes untouched. That * is why most of the time the last n bytes of the answers are the * same. * * The third command below seems to generate two answers with a * string corresponding to the device, for example: * 'TEMPer1F' and '1.1Per1F' (here Per1F is repeated). */ static uint8_t cmd_data[8] = {0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00}; static uint8_t cmd_init[8] = {0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00}; #if 0 static uint8_t cmd_type[8] = {0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00}; #endif struct ugold_softc; struct ugold_readout_msg { struct usb_proc_msg hdr; struct ugold_softc *sc; }; struct ugold_softc { struct usb_device *sc_udev; struct usb_xfer *sc_xfer[UGOLD_N_TRANSFER]; struct callout sc_callout; struct mtx sc_mtx; struct ugold_readout_msg sc_readout_msg[2]; int sc_num_sensors; int sc_sensor[UGOLD_MAX_SENSORS]; int sc_calib[UGOLD_MAX_SENSORS]; int sc_valid[UGOLD_MAX_SENSORS]; uint8_t sc_report_id; uint8_t sc_iface_index[2]; }; /* prototypes */ static device_probe_t ugold_probe; static device_attach_t ugold_attach; static device_detach_t ugold_detach; static usb_proc_callback_t ugold_readout_msg; static usb_callback_t ugold_intr_callback; static devclass_t ugold_devclass; static device_method_t ugold_methods[] = { DEVMETHOD(device_probe, ugold_probe), DEVMETHOD(device_attach, ugold_attach), DEVMETHOD(device_detach, ugold_detach), DEVMETHOD_END }; static driver_t ugold_driver = { .name = "ugold", .methods = ugold_methods, .size = sizeof(struct ugold_softc), }; static const STRUCT_USB_HOST_ID ugold_devs[] = { {USB_VPI(USB_VENDOR_CHICONY2, USB_PRODUCT_CHICONY2_TEMPER, 0)}, }; DRIVER_MODULE(ugold, uhub, ugold_driver, ugold_devclass, NULL, NULL); MODULE_DEPEND(ugold, usb, 1, 1, 1); MODULE_VERSION(ugold, 1); USB_PNP_HOST_INFO(ugold_devs); static const struct usb_config ugold_config[UGOLD_N_TRANSFER] = { [UGOLD_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &ugold_intr_callback, .if_index = 1, }, }; static void ugold_timeout(void *arg) { struct ugold_softc *sc = arg; usb_proc_explore_lock(sc->sc_udev); (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]); usb_proc_explore_unlock(sc->sc_udev); callout_reset(&sc->sc_callout, 6 * hz, &ugold_timeout, sc); } static int ugold_probe(device_t dev) { struct usb_attach_arg *uaa; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bInterfaceClass != UICLASS_HID) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(ugold_devs, sizeof(ugold_devs), uaa)); } static int ugold_attach(device_t dev) { struct ugold_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct sysctl_oid *sensor_tree; uint16_t d_len; void *d_ptr; int error; int i; sc->sc_udev = uaa->device; sc->sc_readout_msg[0].hdr.pm_callback = &ugold_readout_msg; sc->sc_readout_msg[0].sc = sc; sc->sc_readout_msg[1].hdr.pm_callback = &ugold_readout_msg; sc->sc_readout_msg[1].sc = sc; sc->sc_iface_index[0] = uaa->info.bIfaceIndex; sc->sc_iface_index[1] = uaa->info.bIfaceIndex + 1; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "ugold lock", NULL, MTX_DEF | MTX_RECURSE); callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); /* grab all interfaces from other drivers */ for (i = 0;; i++) { if (i == uaa->info.bIfaceIndex) continue; if (usbd_get_iface(uaa->device, i) == NULL) break; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); } /* figure out report ID */ error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); if (error) goto detach; (void)hid_report_size(d_ptr, d_len, hid_input, &sc->sc_report_id); free(d_ptr, M_TEMP); error = usbd_transfer_setup(uaa->device, sc->sc_iface_index, sc->sc_xfer, ugold_config, UGOLD_N_TRANSFER, sc, &sc->sc_mtx); if (error) goto detach; sensor_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors", - CTLFLAG_RD, NULL, ""); + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); if (sensor_tree == NULL) { error = ENOMEM; goto detach; } SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sensor_tree), OID_AUTO, "inner", CTLFLAG_RD, &sc->sc_sensor[UGOLD_INNER], 0, "Inner temperature in microCelcius"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sensor_tree), OID_AUTO, "inner_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_INNER], 0, "Inner temperature is valid"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sensor_tree), OID_AUTO, "inner_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_INNER], 0, "Inner calibration temperature in microCelcius"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sensor_tree), OID_AUTO, "outer", CTLFLAG_RD, &sc->sc_sensor[UGOLD_OUTER], 0, "Outer temperature in microCelcius"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sensor_tree), OID_AUTO, "outer_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_OUTER], 0, "Outer calibration temperature in microCelcius"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sensor_tree), OID_AUTO, "outer_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_OUTER], 0, "Outer temperature is valid"); mtx_lock(&sc->sc_mtx); usbd_transfer_start(sc->sc_xfer[UGOLD_INTR_DT]); ugold_timeout(sc); mtx_unlock(&sc->sc_mtx); return (0); detach: DPRINTF("error=%s\n", usbd_errstr(error)); ugold_detach(dev); return (error); } static int ugold_detach(device_t dev) { struct ugold_softc *sc = device_get_softc(dev); callout_drain(&sc->sc_callout); usb_proc_explore_lock(sc->sc_udev); usb_proc_explore_mwait(sc->sc_udev, &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]); usb_proc_explore_unlock(sc->sc_udev); usbd_transfer_unsetup(sc->sc_xfer, UGOLD_N_TRANSFER); mtx_destroy(&sc->sc_mtx); return (0); } static int ugold_ds75_temp(uint8_t msb, uint8_t lsb) { /* DS75: 12bit precision mode : 0.0625 degrees Celsius ticks */ /* NOTE: MSB has a sign bit for negative temperatures */ int32_t temp = (msb << 24) | ((lsb & 0xF0) << 16); return (((int64_t)temp * (int64_t)1000000LL) >> 24); } static void ugold_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct ugold_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[8]; int temp; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: memset(buf, 0, sizeof(buf)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, MIN(len, sizeof(buf))); switch (buf[0]) { case UGOLD_CMD_INIT: if (sc->sc_num_sensors) break; sc->sc_num_sensors = MIN(buf[1], UGOLD_MAX_SENSORS) /* XXX */ ; DPRINTF("%d sensor%s type ds75/12bit (temperature)\n", sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s"); break; case UGOLD_CMD_DATA: switch (buf[1]) { case 4: temp = ugold_ds75_temp(buf[4], buf[5]); sc->sc_sensor[UGOLD_OUTER] = temp + sc->sc_calib[UGOLD_OUTER]; sc->sc_valid[UGOLD_OUTER] = 1; /* FALLTHROUGH */ case 2: temp = ugold_ds75_temp(buf[2], buf[3]); sc->sc_sensor[UGOLD_INNER] = temp + sc->sc_calib[UGOLD_INNER]; sc->sc_valid[UGOLD_INNER] = 1; break; default: DPRINTF("invalid data length (%d bytes)\n", buf[1]); } break; default: DPRINTF("unknown command 0x%02x\n", buf[0]); break; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len) { return (usbd_req_set_report(sc->sc_udev, &sc->sc_mtx, cmd, len, sc->sc_iface_index[1], UHID_OUTPUT_REPORT, sc->sc_report_id)); } static void ugold_readout_msg(struct usb_proc_msg *pm) { struct ugold_softc *sc = ((struct ugold_readout_msg *)pm)->sc; usb_proc_explore_unlock(sc->sc_udev); mtx_lock(&sc->sc_mtx); if (sc->sc_num_sensors == 0) ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init)); ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data)); mtx_unlock(&sc->sc_mtx); usb_proc_explore_lock(sc->sc_udev); } Index: head/sys/dev/usb/net/if_aue.c =================================================================== --- head/sys/dev/usb/net/if_aue.c (revision 357971) +++ head/sys/dev/usb/net/if_aue.c (revision 357972) @@ -1,1076 +1,1077 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1997, 1998, 1999, 2000 * Bill Paul . All rights reserved. * * Copyright (c) 2006 * Alfred Perlstein . 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * ADMtek AN986 Pegasus and AN8511 Pegasus II USB to ethernet driver. * Datasheet is available from http://www.admtek.com.tw. * * Written by Bill Paul * Electrical Engineering Department * Columbia University, New York City * * SMP locking by Alfred Perlstein . * RED Inc. */ /* * The Pegasus chip uses four USB "endpoints" to provide 10/100 ethernet * support: the control endpoint for reading/writing registers, burst * read endpoint for packet reception, burst write for packet transmission * and one for "interrupts." The chip uses the same RX filter scheme * as the other ADMtek ethernet parts: one perfect filter entry for the * the station address and a 64-bit multicast hash table. The chip supports * both MII and HomePNA attachments. * * Since the maximum data transfer speed of USB is supposed to be 12Mbps, * you're never really going to get 100Mbps speeds from this device. I * think the idea is to allow the device to connect to 10 or 100Mbps * networks, not necessarily to provide 100Mbps performance. Also, since * the controller uses an external PHY chip, it's possible that board * designers might simply choose a 10Mbps PHY. * * Registers are accessed using uether_do_request(). Packet * transfers are done using usbd_transfer() and friends. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR aue_debug #include #include #include #include #include "miibus_if.h" #ifdef USB_DEBUG static int aue_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, aue, CTLFLAG_RW, 0, "USB aue"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, aue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB aue"); SYSCTL_INT(_hw_usb_aue, OID_AUTO, debug, CTLFLAG_RWTUN, &aue_debug, 0, "Debug level"); #endif /* * Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID aue_devs[] = { #define AUE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } AUE_DEV(3COM, 3C460B, AUE_FLAG_PII), AUE_DEV(ABOCOM, DSB650TX_PNA, 0), AUE_DEV(ABOCOM, UFE1000, AUE_FLAG_LSYS), AUE_DEV(ABOCOM, XX10, 0), AUE_DEV(ABOCOM, XX1, AUE_FLAG_PNA | AUE_FLAG_PII), AUE_DEV(ABOCOM, XX2, AUE_FLAG_PII), AUE_DEV(ABOCOM, XX4, AUE_FLAG_PNA), AUE_DEV(ABOCOM, XX5, AUE_FLAG_PNA), AUE_DEV(ABOCOM, XX6, AUE_FLAG_PII), AUE_DEV(ABOCOM, XX7, AUE_FLAG_PII), AUE_DEV(ABOCOM, XX8, AUE_FLAG_PII), AUE_DEV(ABOCOM, XX9, AUE_FLAG_PNA), AUE_DEV(ACCTON, SS1001, AUE_FLAG_PII), AUE_DEV(ACCTON, USB320_EC, 0), AUE_DEV(ADMTEK, PEGASUSII_2, AUE_FLAG_PII), AUE_DEV(ADMTEK, PEGASUSII_3, AUE_FLAG_PII), AUE_DEV(ADMTEK, PEGASUSII_4, AUE_FLAG_PII), AUE_DEV(ADMTEK, PEGASUSII, AUE_FLAG_PII), AUE_DEV(ADMTEK, PEGASUS, AUE_FLAG_PNA | AUE_FLAG_DUAL_PHY), AUE_DEV(AEI, FASTETHERNET, AUE_FLAG_PII), AUE_DEV(ALLIEDTELESYN, ATUSB100, AUE_FLAG_PII), AUE_DEV(ATEN, UC110T, AUE_FLAG_PII), AUE_DEV(BELKIN, USB2LAN, AUE_FLAG_PII), AUE_DEV(BILLIONTON, USB100, 0), AUE_DEV(BILLIONTON, USBE100, AUE_FLAG_PII), AUE_DEV(BILLIONTON, USBEL100, 0), AUE_DEV(BILLIONTON, USBLP100, AUE_FLAG_PNA), AUE_DEV(COREGA, FETHER_USB_TXS, AUE_FLAG_PII), AUE_DEV(COREGA, FETHER_USB_TX, 0), AUE_DEV(DLINK, DSB650TX1, AUE_FLAG_LSYS), AUE_DEV(DLINK, DSB650TX2, AUE_FLAG_LSYS | AUE_FLAG_PII), AUE_DEV(DLINK, DSB650TX3, AUE_FLAG_LSYS | AUE_FLAG_PII), AUE_DEV(DLINK, DSB650TX4, AUE_FLAG_LSYS | AUE_FLAG_PII), AUE_DEV(DLINK, DSB650TX_PNA, AUE_FLAG_PNA), AUE_DEV(DLINK, DSB650TX, AUE_FLAG_LSYS), AUE_DEV(DLINK, DSB650, AUE_FLAG_LSYS), AUE_DEV(ELCON, PLAN, AUE_FLAG_PNA | AUE_FLAG_PII), AUE_DEV(ELECOM, LDUSB20, AUE_FLAG_PII), AUE_DEV(ELECOM, LDUSBLTX, AUE_FLAG_PII), AUE_DEV(ELECOM, LDUSBTX0, 0), AUE_DEV(ELECOM, LDUSBTX1, AUE_FLAG_LSYS), AUE_DEV(ELECOM, LDUSBTX2, 0), AUE_DEV(ELECOM, LDUSBTX3, AUE_FLAG_LSYS), AUE_DEV(ELSA, USB2ETHERNET, 0), AUE_DEV(GIGABYTE, GNBR402W, 0), AUE_DEV(HAWKING, UF100, AUE_FLAG_PII), AUE_DEV(HP, HN210E, AUE_FLAG_PII), AUE_DEV(IODATA, USBETTXS, AUE_FLAG_PII), AUE_DEV(IODATA, USBETTX, 0), AUE_DEV(KINGSTON, KNU101TX, 0), AUE_DEV(LINKSYS, USB100H1, AUE_FLAG_LSYS | AUE_FLAG_PNA), AUE_DEV(LINKSYS, USB100TX, AUE_FLAG_LSYS), AUE_DEV(LINKSYS, USB10TA, AUE_FLAG_LSYS), AUE_DEV(LINKSYS, USB10TX1, AUE_FLAG_LSYS | AUE_FLAG_PII), AUE_DEV(LINKSYS, USB10TX2, AUE_FLAG_LSYS | AUE_FLAG_PII), AUE_DEV(LINKSYS, USB10T, AUE_FLAG_LSYS), AUE_DEV(MELCO, LUA2TX5, AUE_FLAG_PII), AUE_DEV(MELCO, LUATX1, 0), AUE_DEV(MELCO, LUATX5, 0), AUE_DEV(MICROSOFT, MN110, AUE_FLAG_PII), AUE_DEV(NETGEAR, FA101, AUE_FLAG_PII), AUE_DEV(SIEMENS, SPEEDSTREAM, AUE_FLAG_PII), AUE_DEV(SIIG2, USBTOETHER, AUE_FLAG_PII), AUE_DEV(SMARTBRIDGES, SMARTNIC, AUE_FLAG_PII), AUE_DEV(SMC, 2202USB, 0), AUE_DEV(SMC, 2206USB, AUE_FLAG_PII), AUE_DEV(SOHOWARE, NUB100, 0), AUE_DEV(SOHOWARE, NUB110, AUE_FLAG_PII), #undef AUE_DEV }; /* prototypes */ static device_probe_t aue_probe; static device_attach_t aue_attach; static device_detach_t aue_detach; static miibus_readreg_t aue_miibus_readreg; static miibus_writereg_t aue_miibus_writereg; static miibus_statchg_t aue_miibus_statchg; static usb_callback_t aue_intr_callback; static usb_callback_t aue_bulk_read_callback; static usb_callback_t aue_bulk_write_callback; static uether_fn_t aue_attach_post; static uether_fn_t aue_init; static uether_fn_t aue_stop; static uether_fn_t aue_start; static uether_fn_t aue_tick; static uether_fn_t aue_setmulti; static uether_fn_t aue_setpromisc; static uint8_t aue_csr_read_1(struct aue_softc *, uint16_t); static uint16_t aue_csr_read_2(struct aue_softc *, uint16_t); static void aue_csr_write_1(struct aue_softc *, uint16_t, uint8_t); static void aue_csr_write_2(struct aue_softc *, uint16_t, uint16_t); static uint16_t aue_eeprom_getword(struct aue_softc *, int); static void aue_reset(struct aue_softc *); static void aue_reset_pegasus_II(struct aue_softc *); static int aue_ifmedia_upd(struct ifnet *); static void aue_ifmedia_sts(struct ifnet *, struct ifmediareq *); static const struct usb_config aue_config[AUE_N_TRANSFER] = { [AUE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (MCLBYTES + 2), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = aue_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [AUE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = aue_bulk_read_callback, }, [AUE_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = aue_intr_callback, }, }; static device_method_t aue_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aue_probe), DEVMETHOD(device_attach, aue_attach), DEVMETHOD(device_detach, aue_detach), /* MII interface */ DEVMETHOD(miibus_readreg, aue_miibus_readreg), DEVMETHOD(miibus_writereg, aue_miibus_writereg), DEVMETHOD(miibus_statchg, aue_miibus_statchg), DEVMETHOD_END }; static driver_t aue_driver = { .name = "aue", .methods = aue_methods, .size = sizeof(struct aue_softc) }; static devclass_t aue_devclass; DRIVER_MODULE(aue, uhub, aue_driver, aue_devclass, NULL, 0); DRIVER_MODULE(miibus, aue, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(aue, uether, 1, 1, 1); MODULE_DEPEND(aue, usb, 1, 1, 1); MODULE_DEPEND(aue, ether, 1, 1, 1); MODULE_DEPEND(aue, miibus, 1, 1, 1); MODULE_VERSION(aue, 1); USB_PNP_HOST_INFO(aue_devs); static const struct usb_ether_methods aue_ue_methods = { .ue_attach_post = aue_attach_post, .ue_start = aue_start, .ue_init = aue_init, .ue_stop = aue_stop, .ue_tick = aue_tick, .ue_setmulti = aue_setmulti, .ue_setpromisc = aue_setpromisc, .ue_mii_upd = aue_ifmedia_upd, .ue_mii_sts = aue_ifmedia_sts, }; #define AUE_SETBIT(sc, reg, x) \ aue_csr_write_1(sc, reg, aue_csr_read_1(sc, reg) | (x)) #define AUE_CLRBIT(sc, reg, x) \ aue_csr_write_1(sc, reg, aue_csr_read_1(sc, reg) & ~(x)) static uint8_t aue_csr_read_1(struct aue_softc *sc, uint16_t reg) { struct usb_device_request req; usb_error_t err; uint8_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = AUE_UR_READREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 1); err = uether_do_request(&sc->sc_ue, &req, &val, 1000); if (err) return (0); return (val); } static uint16_t aue_csr_read_2(struct aue_softc *sc, uint16_t reg) { struct usb_device_request req; usb_error_t err; uint16_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = AUE_UR_READREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 2); err = uether_do_request(&sc->sc_ue, &req, &val, 1000); if (err) return (0); return (le16toh(val)); } static void aue_csr_write_1(struct aue_softc *sc, uint16_t reg, uint8_t val) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = AUE_UR_WRITEREG; req.wValue[0] = val; req.wValue[1] = 0; USETW(req.wIndex, reg); USETW(req.wLength, 1); if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) { /* error ignored */ } } static void aue_csr_write_2(struct aue_softc *sc, uint16_t reg, uint16_t val) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = AUE_UR_WRITEREG; USETW(req.wValue, val); USETW(req.wIndex, reg); USETW(req.wLength, 2); val = htole16(val); if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) { /* error ignored */ } } /* * Read a word of data stored in the EEPROM at address 'addr.' */ static uint16_t aue_eeprom_getword(struct aue_softc *sc, int addr) { int i; aue_csr_write_1(sc, AUE_EE_REG, addr); aue_csr_write_1(sc, AUE_EE_CTL, AUE_EECTL_READ); for (i = 0; i != AUE_TIMEOUT; i++) { if (aue_csr_read_1(sc, AUE_EE_CTL) & AUE_EECTL_DONE) break; if (uether_pause(&sc->sc_ue, hz / 100)) break; } if (i == AUE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "EEPROM read timed out\n"); return (aue_csr_read_2(sc, AUE_EE_DATA)); } /* * Read station address(offset 0) from the EEPROM. */ static void aue_read_mac(struct aue_softc *sc, uint8_t *eaddr) { int i, offset; uint16_t word; for (i = 0, offset = 0; i < ETHER_ADDR_LEN / 2; i++) { word = aue_eeprom_getword(sc, offset + i); eaddr[i * 2] = (uint8_t)word; eaddr[i * 2 + 1] = (uint8_t)(word >> 8); } } static int aue_miibus_readreg(device_t dev, int phy, int reg) { struct aue_softc *sc = device_get_softc(dev); int i, locked; uint16_t val = 0; locked = mtx_owned(&sc->sc_mtx); if (!locked) AUE_LOCK(sc); /* * The Am79C901 HomePNA PHY actually contains two transceivers: a 1Mbps * HomePNA PHY and a 10Mbps full/half duplex ethernet PHY with NWAY * autoneg. However in the ADMtek adapter, only the 1Mbps PHY is * actually connected to anything, so we ignore the 10Mbps one. It * happens to be configured for MII address 3, so we filter that out. */ if (sc->sc_flags & AUE_FLAG_DUAL_PHY) { if (phy == 3) goto done; #if 0 if (phy != 1) goto done; #endif } aue_csr_write_1(sc, AUE_PHY_ADDR, phy); aue_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_READ); for (i = 0; i != AUE_TIMEOUT; i++) { if (aue_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) break; if (uether_pause(&sc->sc_ue, hz / 100)) break; } if (i == AUE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "MII read timed out\n"); val = aue_csr_read_2(sc, AUE_PHY_DATA); done: if (!locked) AUE_UNLOCK(sc); return (val); } static int aue_miibus_writereg(device_t dev, int phy, int reg, int data) { struct aue_softc *sc = device_get_softc(dev); int i; int locked; if (phy == 3) return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) AUE_LOCK(sc); aue_csr_write_2(sc, AUE_PHY_DATA, data); aue_csr_write_1(sc, AUE_PHY_ADDR, phy); aue_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_WRITE); for (i = 0; i != AUE_TIMEOUT; i++) { if (aue_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) break; if (uether_pause(&sc->sc_ue, hz / 100)) break; } if (i == AUE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "MII write timed out\n"); if (!locked) AUE_UNLOCK(sc); return (0); } static void aue_miibus_statchg(device_t dev) { struct aue_softc *sc = device_get_softc(dev); struct mii_data *mii = GET_MII(sc); int locked; locked = mtx_owned(&sc->sc_mtx); if (!locked) AUE_LOCK(sc); AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); else AUE_CLRBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); else AUE_CLRBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); /* * Set the LED modes on the LinkSys adapter. * This turns on the 'dual link LED' bin in the auxmode * register of the Broadcom PHY. */ if (sc->sc_flags & AUE_FLAG_LSYS) { uint16_t auxmode; auxmode = aue_miibus_readreg(dev, 0, 0x1b); aue_miibus_writereg(dev, 0, 0x1b, auxmode | 0x04); } if (!locked) AUE_UNLOCK(sc); } #define AUE_BITS 6 static u_int aue_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint8_t *hashtbl = arg; uint32_t h; h = ether_crc32_le(LLADDR(sdl), ETHER_ADDR_LEN) & ((1 << AUE_BITS) - 1); hashtbl[(h >> 3)] |= 1 << (h & 0x7); return (1); } static void aue_setmulti(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint32_t i; uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; AUE_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); return; } AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); /* now program new ones */ if_foreach_llmaddr(ifp, aue_hash_maddr, hashtbl); /* write the hashtable */ for (i = 0; i != 8; i++) aue_csr_write_1(sc, AUE_MAR0 + i, hashtbl[i]); } static void aue_reset_pegasus_II(struct aue_softc *sc) { /* Magic constants taken from Linux driver. */ aue_csr_write_1(sc, AUE_REG_1D, 0); aue_csr_write_1(sc, AUE_REG_7B, 2); #if 0 if ((sc->sc_flags & HAS_HOME_PNA) && mii_mode) aue_csr_write_1(sc, AUE_REG_81, 6); else #endif aue_csr_write_1(sc, AUE_REG_81, 2); } static void aue_reset(struct aue_softc *sc) { int i; AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_RESETMAC); for (i = 0; i != AUE_TIMEOUT; i++) { if (!(aue_csr_read_1(sc, AUE_CTL1) & AUE_CTL1_RESETMAC)) break; if (uether_pause(&sc->sc_ue, hz / 100)) break; } if (i == AUE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "reset failed\n"); /* * The PHY(s) attached to the Pegasus chip may be held * in reset until we flip on the GPIO outputs. Make sure * to set the GPIO pins high so that the PHY(s) will * be enabled. * * NOTE: We used to force all of the GPIO pins low first and then * enable the ones we want. This has been changed to better * match the ADMtek's reference design to avoid setting the * power-down configuration line of the PHY at the same time * it is reset. */ aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1); aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1|AUE_GPIO_OUT0); if (sc->sc_flags & AUE_FLAG_LSYS) { /* Grrr. LinkSys has to be different from everyone else. */ aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1); aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1|AUE_GPIO_OUT0); } if (sc->sc_flags & AUE_FLAG_PII) aue_reset_pegasus_II(sc); /* Wait a little while for the chip to get its brains in order: */ uether_pause(&sc->sc_ue, hz / 100); } static void aue_attach_post(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); /* reset the adapter */ aue_reset(sc); /* get station address from the EEPROM */ aue_read_mac(sc, ue->ue_eaddr); } /* * Probe for a Pegasus chip. */ static int aue_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != AUE_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != AUE_IFACE_IDX) return (ENXIO); /* * Belkin USB Bluetooth dongles of the F8T012xx1 model series conflict * with older Belkin USB2LAN adapters. Skip if_aue if we detect one of * the devices that look like Bluetooth adapters. */ if (uaa->info.idVendor == USB_VENDOR_BELKIN && uaa->info.idProduct == USB_PRODUCT_BELKIN_F8T012 && uaa->info.bcdDevice == 0x0413) return (ENXIO); return (usbd_lookup_id_by_uaa(aue_devs, sizeof(aue_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int aue_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct aue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; sc->sc_flags = USB_GET_DRIVER_INFO(uaa); if (uaa->info.bcdDevice >= 0x0201) { /* XXX currently undocumented */ sc->sc_flags |= AUE_FLAG_VER_2; } device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = AUE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, aue_config, AUE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &aue_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: aue_detach(dev); return (ENXIO); /* failure */ } static int aue_detach(device_t dev) { struct aue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, AUE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void aue_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct aue_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct aue_intrpkt pkt; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if ((ifp->if_drv_flags & IFF_DRV_RUNNING) && actlen >= (int)sizeof(pkt)) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); if (pkt.aue_txstat0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (pkt.aue_txstat0 & (AUE_TXSTAT0_LATECOLL | AUE_TXSTAT0_EXCESSCOLL)) if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void aue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct aue_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct aue_rxpkt stat; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); pc = usbd_xfer_get_frame(xfer, 0); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "received %d bytes\n", actlen); if (sc->sc_flags & AUE_FLAG_VER_2) { if (actlen == 0) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } } else { if (actlen <= (int)(sizeof(stat) + ETHER_CRC_LEN)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } usbd_copy_out(pc, actlen - sizeof(stat), &stat, sizeof(stat)); /* * turn off all the non-error bits in the rx status * word: */ stat.aue_rxstat &= AUE_RXSTAT_MASK; if (stat.aue_rxstat) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } /* No errors; receive the packet. */ actlen -= (sizeof(stat) + ETHER_CRC_LEN); } uether_rxbuf(ue, pc, 0, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void aue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct aue_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; uint8_t buf[2]; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); pc = usbd_xfer_get_frame(xfer, 0); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer of %d bytes complete\n", actlen); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & AUE_FLAG_LINK) == 0) { /* * don't send anything if there is no link ! */ return; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; if (m->m_pkthdr.len > MCLBYTES) m->m_pkthdr.len = MCLBYTES; if (sc->sc_flags & AUE_FLAG_VER_2) { usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); } else { usbd_xfer_set_frame_len(xfer, 0, (m->m_pkthdr.len + 2)); /* * The ADMtek documentation says that the * packet length is supposed to be specified * in the first two bytes of the transfer, * however it actually seems to ignore this * info and base the frame size on the bulk * transfer length. */ buf[0] = (uint8_t)(m->m_pkthdr.len); buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); usbd_copy_in(pc, 0, buf, 2); usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); } /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); usbd_transfer_submit(xfer); return; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void aue_tick(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); struct mii_data *mii = GET_MII(sc); AUE_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & AUE_FLAG_LINK) == 0 && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->sc_flags |= AUE_FLAG_LINK; aue_start(ue); } } static void aue_start(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[AUE_INTR_DT_RD]); usbd_transfer_start(sc->sc_xfer[AUE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[AUE_BULK_DT_WR]); } static void aue_init(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); int i; AUE_LOCK_ASSERT(sc, MA_OWNED); /* * Cancel pending I/O */ aue_reset(sc); /* Set MAC address */ for (i = 0; i != ETHER_ADDR_LEN; i++) aue_csr_write_1(sc, AUE_PAR0 + i, IF_LLADDR(ifp)[i]); /* update promiscuous setting */ aue_setpromisc(ue); /* Load the multicast filter. */ aue_setmulti(ue); /* Enable RX and TX */ aue_csr_write_1(sc, AUE_CTL0, AUE_CTL0_RXSTAT_APPEND | AUE_CTL0_RX_ENB); AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_TX_ENB); AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_EP3_CLR); usbd_xfer_set_stall(sc->sc_xfer[AUE_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; aue_start(ue); } static void aue_setpromisc(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); AUE_LOCK_ASSERT(sc, MA_OWNED); /* if we want promiscuous mode, set the allframes bit: */ if (ifp->if_flags & IFF_PROMISC) AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); else AUE_CLRBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); } /* * Set media options. */ static int aue_ifmedia_upd(struct ifnet *ifp) { struct aue_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); struct mii_softc *miisc; int error; AUE_LOCK_ASSERT(sc, MA_OWNED); sc->sc_flags &= ~AUE_FLAG_LINK; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } /* * Report current media status. */ static void aue_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct aue_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); AUE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; AUE_UNLOCK(sc); } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void aue_stop(struct usb_ether *ue) { struct aue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); AUE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; sc->sc_flags &= ~AUE_FLAG_LINK; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[AUE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[AUE_BULK_DT_RD]); usbd_transfer_stop(sc->sc_xfer[AUE_INTR_DT_RD]); aue_csr_write_1(sc, AUE_CTL0, 0); aue_csr_write_1(sc, AUE_CTL1, 0); aue_reset(sc); } Index: head/sys/dev/usb/net/if_axe.c =================================================================== --- head/sys/dev/usb/net/if_axe.c (revision 357971) +++ head/sys/dev/usb/net/if_axe.c (revision 357972) @@ -1,1510 +1,1511 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1997, 1998, 1999, 2000-2003 * Bill Paul . 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * ASIX Electronics AX88172/AX88178/AX88778 USB 2.0 ethernet driver. * Used in the LinkSys USB200M and various other adapters. * * Manuals available from: * http://www.asix.com.tw/datasheet/mac/Ax88172.PDF * Note: you need the manual for the AX88170 chip (USB 1.x ethernet * controller) to find the definitions for the RX control register. * http://www.asix.com.tw/datasheet/mac/Ax88170.PDF * * Written by Bill Paul * Senior Engineer * Wind River Systems */ /* * The AX88172 provides USB ethernet supports at 10 and 100Mbps. * It uses an external PHY (reference designs use a RealTek chip), * and has a 64-bit multicast hash filter. There is some information * missing from the manual which one needs to know in order to make * the chip function: * * - You must set bit 7 in the RX control register, otherwise the * chip won't receive any packets. * - You must initialize all 3 IPG registers, or you won't be able * to send any packets. * * Note that this device appears to only support loading the station * address via autload from the EEPROM (i.e. there's no way to manually * set it). * * (Adam Weinberger wanted me to name this driver if_gir.c.) */ /* * Ax88178 and Ax88772 support backported from the OpenBSD driver. * 2007/02/12, J.R. Oldroyd, fbsd@opal.com * * Manual here: * http://www.asix.com.tw/FrootAttach/datasheet/AX88178_datasheet_Rev10.pdf * http://www.asix.com.tw/FrootAttach/datasheet/AX88772_datasheet_Rev10.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR axe_debug #include #include #include #include #include "miibus_if.h" /* * AXE_178_MAX_FRAME_BURST * max frame burst size for Ax88178 and Ax88772 * 0 2048 bytes * 1 4096 bytes * 2 8192 bytes * 3 16384 bytes * use the largest your system can handle without USB stalling. * * NB: 88772 parts appear to generate lots of input errors with * a 2K rx buffer and 8K is only slightly faster than 4K on an * EHCI port on a T42 so change at your own risk. */ #define AXE_178_MAX_FRAME_BURST 1 #define AXE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) #ifdef USB_DEBUG static int axe_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, axe, CTLFLAG_RW, 0, "USB axe"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, axe, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB axe"); SYSCTL_INT(_hw_usb_axe, OID_AUTO, debug, CTLFLAG_RWTUN, &axe_debug, 0, "Debug level"); #endif /* * Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID axe_devs[] = { #define AXE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } AXE_DEV(ABOCOM, UF200, 0), AXE_DEV(ACERCM, EP1427X2, 0), AXE_DEV(APPLE, ETHERNET, AXE_FLAG_772), AXE_DEV(ASIX, AX88172, 0), AXE_DEV(ASIX, AX88178, AXE_FLAG_178), AXE_DEV(ASIX, AX88772, AXE_FLAG_772), AXE_DEV(ASIX, AX88772A, AXE_FLAG_772A), AXE_DEV(ASIX, AX88772B, AXE_FLAG_772B), AXE_DEV(ASIX, AX88772B_1, AXE_FLAG_772B), AXE_DEV(ATEN, UC210T, 0), AXE_DEV(BELKIN, F5D5055, AXE_FLAG_178), AXE_DEV(BILLIONTON, USB2AR, 0), AXE_DEV(CISCOLINKSYS, USB200MV2, AXE_FLAG_772A), AXE_DEV(COREGA, FETHER_USB2_TX, 0), AXE_DEV(DLINK, DUBE100, 0), AXE_DEV(DLINK, DUBE100B1, AXE_FLAG_772), AXE_DEV(DLINK, DUBE100C1, AXE_FLAG_772B), AXE_DEV(GOODWAY, GWUSB2E, 0), AXE_DEV(IODATA, ETGUS2, AXE_FLAG_178), AXE_DEV(JVC, MP_PRX1, 0), AXE_DEV(LENOVO, ETHERNET, AXE_FLAG_772B), AXE_DEV(LINKSYS2, USB200M, 0), AXE_DEV(LINKSYS4, USB1000, AXE_FLAG_178), AXE_DEV(LOGITEC, LAN_GTJU2A, AXE_FLAG_178), AXE_DEV(MELCO, LUAU2KTX, 0), AXE_DEV(MELCO, LUA3U2AGT, AXE_FLAG_178), AXE_DEV(NETGEAR, FA120, 0), AXE_DEV(OQO, ETHER01PLUS, AXE_FLAG_772), AXE_DEV(PLANEX3, GU1000T, AXE_FLAG_178), AXE_DEV(SITECOM, LN029, 0), AXE_DEV(SITECOMEU, LN028, AXE_FLAG_178), AXE_DEV(SITECOMEU, LN031, AXE_FLAG_178), AXE_DEV(SYSTEMTALKS, SGCX2UL, 0), #undef AXE_DEV }; static device_probe_t axe_probe; static device_attach_t axe_attach; static device_detach_t axe_detach; static usb_callback_t axe_bulk_read_callback; static usb_callback_t axe_bulk_write_callback; static miibus_readreg_t axe_miibus_readreg; static miibus_writereg_t axe_miibus_writereg; static miibus_statchg_t axe_miibus_statchg; static uether_fn_t axe_attach_post; static uether_fn_t axe_init; static uether_fn_t axe_stop; static uether_fn_t axe_start; static uether_fn_t axe_tick; static uether_fn_t axe_setmulti; static uether_fn_t axe_setpromisc; static int axe_attach_post_sub(struct usb_ether *); static int axe_ifmedia_upd(struct ifnet *); static void axe_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int axe_cmd(struct axe_softc *, int, int, int, void *); static void axe_ax88178_init(struct axe_softc *); static void axe_ax88772_init(struct axe_softc *); static void axe_ax88772_phywake(struct axe_softc *); static void axe_ax88772a_init(struct axe_softc *); static void axe_ax88772b_init(struct axe_softc *); static int axe_get_phyno(struct axe_softc *, int); static int axe_ioctl(struct ifnet *, u_long, caddr_t); static int axe_rx_frame(struct usb_ether *, struct usb_page_cache *, int); static int axe_rxeof(struct usb_ether *, struct usb_page_cache *, unsigned int offset, unsigned int, struct axe_csum_hdr *); static void axe_csum_cfg(struct usb_ether *); static const struct usb_config axe_config[AXE_N_TRANSFER] = { [AXE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .frames = 16, .bufsize = 16 * MCLBYTES, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = axe_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [AXE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 16384, /* bytes */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = axe_bulk_read_callback, .timeout = 0, /* no timeout */ }, }; static const struct ax88772b_mfb ax88772b_mfb_table[] = { { 0x8000, 0x8001, 2048 }, { 0x8100, 0x8147, 4096}, { 0x8200, 0x81EB, 6144}, { 0x8300, 0x83D7, 8192}, { 0x8400, 0x851E, 16384}, { 0x8500, 0x8666, 20480}, { 0x8600, 0x87AE, 24576}, { 0x8700, 0x8A3D, 32768} }; static device_method_t axe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, axe_probe), DEVMETHOD(device_attach, axe_attach), DEVMETHOD(device_detach, axe_detach), /* MII interface */ DEVMETHOD(miibus_readreg, axe_miibus_readreg), DEVMETHOD(miibus_writereg, axe_miibus_writereg), DEVMETHOD(miibus_statchg, axe_miibus_statchg), DEVMETHOD_END }; static driver_t axe_driver = { .name = "axe", .methods = axe_methods, .size = sizeof(struct axe_softc), }; static devclass_t axe_devclass; DRIVER_MODULE(axe, uhub, axe_driver, axe_devclass, NULL, 0); DRIVER_MODULE(miibus, axe, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(axe, uether, 1, 1, 1); MODULE_DEPEND(axe, usb, 1, 1, 1); MODULE_DEPEND(axe, ether, 1, 1, 1); MODULE_DEPEND(axe, miibus, 1, 1, 1); MODULE_VERSION(axe, 1); USB_PNP_HOST_INFO(axe_devs); static const struct usb_ether_methods axe_ue_methods = { .ue_attach_post = axe_attach_post, .ue_attach_post_sub = axe_attach_post_sub, .ue_start = axe_start, .ue_init = axe_init, .ue_stop = axe_stop, .ue_tick = axe_tick, .ue_setmulti = axe_setmulti, .ue_setpromisc = axe_setpromisc, .ue_mii_upd = axe_ifmedia_upd, .ue_mii_sts = axe_ifmedia_sts, }; static int axe_cmd(struct axe_softc *sc, int cmd, int index, int val, void *buf) { struct usb_device_request req; usb_error_t err; AXE_LOCK_ASSERT(sc, MA_OWNED); req.bmRequestType = (AXE_CMD_IS_WRITE(cmd) ? UT_WRITE_VENDOR_DEVICE : UT_READ_VENDOR_DEVICE); req.bRequest = AXE_CMD_CMD(cmd); USETW(req.wValue, val); USETW(req.wIndex, index); USETW(req.wLength, AXE_CMD_LEN(cmd)); err = uether_do_request(&sc->sc_ue, &req, buf, 1000); return (err); } static int axe_miibus_readreg(device_t dev, int phy, int reg) { struct axe_softc *sc = device_get_softc(dev); uint16_t val; int locked; locked = mtx_owned(&sc->sc_mtx); if (!locked) AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); axe_cmd(sc, AXE_CMD_MII_READ_REG, reg, phy, &val); axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); val = le16toh(val); if (AXE_IS_772(sc) && reg == MII_BMSR) { /* * BMSR of AX88772 indicates that it supports extended * capability but the extended status register is * revered for embedded ethernet PHY. So clear the * extended capability bit of BMSR. */ val &= ~BMSR_EXTCAP; } if (!locked) AXE_UNLOCK(sc); return (val); } static int axe_miibus_writereg(device_t dev, int phy, int reg, int val) { struct axe_softc *sc = device_get_softc(dev); int locked; val = htole32(val); locked = mtx_owned(&sc->sc_mtx); if (!locked) AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); axe_cmd(sc, AXE_CMD_MII_WRITE_REG, reg, phy, &val); axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); if (!locked) AXE_UNLOCK(sc); return (0); } static void axe_miibus_statchg(device_t dev) { struct axe_softc *sc = device_get_softc(dev); struct mii_data *mii = GET_MII(sc); struct ifnet *ifp; uint16_t val; int err, locked; locked = mtx_owned(&sc->sc_mtx); if (!locked) AXE_LOCK(sc); ifp = uether_getifp(&sc->sc_ue); if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) goto done; sc->sc_flags &= ~AXE_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->sc_flags |= AXE_FLAG_LINK; break; case IFM_1000_T: if ((sc->sc_flags & AXE_FLAG_178) == 0) break; sc->sc_flags |= AXE_FLAG_LINK; break; default: break; } } /* Lost link, do nothing. */ if ((sc->sc_flags & AXE_FLAG_LINK) == 0) goto done; val = 0; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { val |= AXE_MEDIA_FULL_DUPLEX; if (AXE_IS_178_FAMILY(sc)) { if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) val |= AXE_178_MEDIA_TXFLOW_CONTROL_EN; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) val |= AXE_178_MEDIA_RXFLOW_CONTROL_EN; } } if (AXE_IS_178_FAMILY(sc)) { val |= AXE_178_MEDIA_RX_EN | AXE_178_MEDIA_MAGIC; if ((sc->sc_flags & AXE_FLAG_178) != 0) val |= AXE_178_MEDIA_ENCK; switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: val |= AXE_178_MEDIA_GMII | AXE_178_MEDIA_ENCK; break; case IFM_100_TX: val |= AXE_178_MEDIA_100TX; break; case IFM_10_T: /* doesn't need to be handled */ break; } } err = axe_cmd(sc, AXE_CMD_WRITE_MEDIA, 0, val, NULL); if (err) device_printf(dev, "media change failed, error %d\n", err); done: if (!locked) AXE_UNLOCK(sc); } /* * Set media options. */ static int axe_ifmedia_upd(struct ifnet *ifp) { struct axe_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); struct mii_softc *miisc; int error; AXE_LOCK_ASSERT(sc, MA_OWNED); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } /* * Report current media status. */ static void axe_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct axe_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); AXE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; AXE_UNLOCK(sc); } static u_int axe_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint8_t *hashtbl = arg; uint32_t h; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; hashtbl[h / 8] |= 1 << (h % 8); return (1); } static void axe_setmulti(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint16_t rxmode; uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; AXE_LOCK_ASSERT(sc, MA_OWNED); axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode); rxmode = le16toh(rxmode); if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { rxmode |= AXE_RXCMD_ALLMULTI; axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); return; } rxmode &= ~AXE_RXCMD_ALLMULTI; if_foreach_llmaddr(ifp, axe_hash_maddr, &hashtbl); axe_cmd(sc, AXE_CMD_WRITE_MCAST, 0, 0, (void *)&hashtbl); axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); } static int axe_get_phyno(struct axe_softc *sc, int sel) { int phyno; switch (AXE_PHY_TYPE(sc->sc_phyaddrs[sel])) { case PHY_TYPE_100_HOME: case PHY_TYPE_GIG: phyno = AXE_PHY_NO(sc->sc_phyaddrs[sel]); break; case PHY_TYPE_SPECIAL: /* FALLTHROUGH */ case PHY_TYPE_RSVD: /* FALLTHROUGH */ case PHY_TYPE_NON_SUP: /* FALLTHROUGH */ default: phyno = -1; break; } return (phyno); } #define AXE_GPIO_WRITE(x, y) do { \ axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, (x), NULL); \ uether_pause(ue, (y)); \ } while (0) static void axe_ax88178_init(struct axe_softc *sc) { struct usb_ether *ue; int gpio0, ledmode, phymode; uint16_t eeprom, val; ue = &sc->sc_ue; axe_cmd(sc, AXE_CMD_SROM_WR_ENABLE, 0, 0, NULL); /* XXX magic */ axe_cmd(sc, AXE_CMD_SROM_READ, 0, 0x0017, &eeprom); eeprom = le16toh(eeprom); axe_cmd(sc, AXE_CMD_SROM_WR_DISABLE, 0, 0, NULL); /* if EEPROM is invalid we have to use to GPIO0 */ if (eeprom == 0xffff) { phymode = AXE_PHY_MODE_MARVELL; gpio0 = 1; ledmode = 0; } else { phymode = eeprom & 0x7f; gpio0 = (eeprom & 0x80) ? 0 : 1; ledmode = eeprom >> 8; } if (bootverbose) device_printf(sc->sc_ue.ue_dev, "EEPROM data : 0x%04x, phymode : 0x%02x\n", eeprom, phymode); /* Program GPIOs depending on PHY hardware. */ switch (phymode) { case AXE_PHY_MODE_MARVELL: if (gpio0 == 1) { AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO0_EN, hz / 32); AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2_EN, hz / 4); AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); } else { AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 | AXE_GPIO1_EN, hz / 3); if (ledmode == 1) { AXE_GPIO_WRITE(AXE_GPIO1_EN, hz / 3); AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN, hz / 3); } else { AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2_EN, hz / 4); AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); } } break; case AXE_PHY_MODE_CICADA: case AXE_PHY_MODE_CICADA_V2: case AXE_PHY_MODE_CICADA_V2_ASIX: if (gpio0 == 1) AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO0 | AXE_GPIO0_EN, hz / 32); else AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 | AXE_GPIO1_EN, hz / 32); break; case AXE_PHY_MODE_AGERE: AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 | AXE_GPIO1_EN, hz / 32); AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2_EN, hz / 4); AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); break; case AXE_PHY_MODE_REALTEK_8211CL: case AXE_PHY_MODE_REALTEK_8211BN: case AXE_PHY_MODE_REALTEK_8251CL: val = gpio0 == 1 ? AXE_GPIO0 | AXE_GPIO0_EN : AXE_GPIO1 | AXE_GPIO1_EN; AXE_GPIO_WRITE(val, hz / 32); AXE_GPIO_WRITE(val | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); AXE_GPIO_WRITE(val | AXE_GPIO2_EN, hz / 4); AXE_GPIO_WRITE(val | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); if (phymode == AXE_PHY_MODE_REALTEK_8211CL) { axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, 0x1F, 0x0005); axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, 0x0C, 0x0000); val = axe_miibus_readreg(ue->ue_dev, sc->sc_phyno, 0x0001); axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, 0x01, val | 0x0080); axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, 0x1F, 0x0000); } break; default: /* Unknown PHY model or no need to program GPIOs. */ break; } /* soft reset */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); uether_pause(ue, hz / 4); axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_PRL | AXE_178_RESET_MAGIC, NULL); uether_pause(ue, hz / 4); /* Enable MII/GMII/RGMII interface to work with external PHY. */ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0, NULL); uether_pause(ue, hz / 4); axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); } static void axe_ax88772_init(struct axe_softc *sc) { axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x00b0, NULL); uether_pause(&sc->sc_ue, hz / 16); if (sc->sc_phyno == AXE_772_PHY_NO_EPHY) { /* ask for the embedded PHY */ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x01, NULL); uether_pause(&sc->sc_ue, hz / 64); /* power down and reset state, pin reset state */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); uether_pause(&sc->sc_ue, hz / 16); /* power down/reset state, pin operating state */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); uether_pause(&sc->sc_ue, hz / 4); /* power up, reset */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_PRL, NULL); /* power up, operating */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL | AXE_SW_RESET_PRL, NULL); } else { /* ask for external PHY */ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x00, NULL); uether_pause(&sc->sc_ue, hz / 64); /* power down internal PHY */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); } uether_pause(&sc->sc_ue, hz / 4); axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); } static void axe_ax88772_phywake(struct axe_softc *sc) { struct usb_ether *ue; ue = &sc->sc_ue; if (sc->sc_phyno == AXE_772_PHY_NO_EPHY) { /* Manually select internal(embedded) PHY - MAC mode. */ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, AXE_SW_PHY_SELECT_SS_ENB | AXE_SW_PHY_SELECT_EMBEDDED | AXE_SW_PHY_SELECT_SS_MII, NULL); uether_pause(&sc->sc_ue, hz / 32); } else { /* * Manually select external PHY - MAC mode. * Reverse MII/RMII is for AX88772A PHY mode. */ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, AXE_SW_PHY_SELECT_SS_ENB | AXE_SW_PHY_SELECT_EXT | AXE_SW_PHY_SELECT_SS_MII, NULL); uether_pause(&sc->sc_ue, hz / 32); } /* Take PHY out of power down. */ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPPD | AXE_SW_RESET_IPRL, NULL); uether_pause(&sc->sc_ue, hz / 4); axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL, NULL); uether_pause(&sc->sc_ue, hz); axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); uether_pause(&sc->sc_ue, hz / 32); axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL, NULL); uether_pause(&sc->sc_ue, hz / 32); } static void axe_ax88772a_init(struct axe_softc *sc) { struct usb_ether *ue; ue = &sc->sc_ue; /* Reload EEPROM. */ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM, hz / 32); axe_ax88772_phywake(sc); /* Stop MAC. */ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); } static void axe_ax88772b_init(struct axe_softc *sc) { struct usb_ether *ue; uint16_t eeprom; uint8_t *eaddr; int i; ue = &sc->sc_ue; /* Reload EEPROM. */ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM, hz / 32); /* * Save PHY power saving configuration(high byte) and * clear EEPROM checksum value(low byte). */ axe_cmd(sc, AXE_CMD_SROM_READ, 0, AXE_EEPROM_772B_PHY_PWRCFG, &eeprom); sc->sc_pwrcfg = le16toh(eeprom) & 0xFF00; /* * Auto-loaded default station address from internal ROM is * 00:00:00:00:00:00 such that an explicit access to EEPROM * is required to get real station address. */ eaddr = ue->ue_eaddr; for (i = 0; i < ETHER_ADDR_LEN / 2; i++) { axe_cmd(sc, AXE_CMD_SROM_READ, 0, AXE_EEPROM_772B_NODE_ID + i, &eeprom); eeprom = le16toh(eeprom); *eaddr++ = (uint8_t)(eeprom & 0xFF); *eaddr++ = (uint8_t)((eeprom >> 8) & 0xFF); } /* Wakeup PHY. */ axe_ax88772_phywake(sc); /* Stop MAC. */ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); } #undef AXE_GPIO_WRITE static void axe_reset(struct axe_softc *sc) { struct usb_config_descriptor *cd; usb_error_t err; cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, cd->bConfigurationValue); if (err) DPRINTF("reset failed (ignored)\n"); /* Wait a little while for the chip to get its brains in order. */ uether_pause(&sc->sc_ue, hz / 100); /* Reinitialize controller to achieve full reset. */ if (sc->sc_flags & AXE_FLAG_178) axe_ax88178_init(sc); else if (sc->sc_flags & AXE_FLAG_772) axe_ax88772_init(sc); else if (sc->sc_flags & AXE_FLAG_772A) axe_ax88772a_init(sc); else if (sc->sc_flags & AXE_FLAG_772B) axe_ax88772b_init(sc); } static void axe_attach_post(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); /* * Load PHY indexes first. Needed by axe_xxx_init(). */ axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, sc->sc_phyaddrs); if (bootverbose) device_printf(sc->sc_ue.ue_dev, "PHYADDR 0x%02x:0x%02x\n", sc->sc_phyaddrs[0], sc->sc_phyaddrs[1]); sc->sc_phyno = axe_get_phyno(sc, AXE_PHY_SEL_PRI); if (sc->sc_phyno == -1) sc->sc_phyno = axe_get_phyno(sc, AXE_PHY_SEL_SEC); if (sc->sc_phyno == -1) { device_printf(sc->sc_ue.ue_dev, "no valid PHY address found, assuming PHY address 0\n"); sc->sc_phyno = 0; } /* Initialize controller and get station address. */ if (sc->sc_flags & AXE_FLAG_178) { axe_ax88178_init(sc); axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); } else if (sc->sc_flags & AXE_FLAG_772) { axe_ax88772_init(sc); axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); } else if (sc->sc_flags & AXE_FLAG_772A) { axe_ax88772a_init(sc); axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); } else if (sc->sc_flags & AXE_FLAG_772B) { axe_ax88772b_init(sc); } else axe_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); /* * Fetch IPG values. */ if (sc->sc_flags & (AXE_FLAG_772A | AXE_FLAG_772B)) { /* Set IPG values. */ sc->sc_ipgs[0] = 0x15; sc->sc_ipgs[1] = 0x16; sc->sc_ipgs[2] = 0x1A; } else axe_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, sc->sc_ipgs); } static int axe_attach_post_sub(struct usb_ether *ue) { struct axe_softc *sc; struct ifnet *ifp; u_int adv_pause; int error; sc = uether_getsc(ue); ifp = ue->ue_ifp; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = uether_start; ifp->if_ioctl = axe_ioctl; ifp->if_init = uether_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); if (AXE_IS_178_FAMILY(sc)) ifp->if_capabilities |= IFCAP_VLAN_MTU; if (sc->sc_flags & AXE_FLAG_772B) { ifp->if_capabilities |= IFCAP_TXCSUM | IFCAP_RXCSUM; ifp->if_hwassist = AXE_CSUM_FEATURES; /* * Checksum offloading of AX88772B also works with VLAN * tagged frames but there is no way to take advantage * of the feature because vlan(4) assumes * IFCAP_VLAN_HWTAGGING is prerequisite condition to * support checksum offloading with VLAN. VLAN hardware * tagging support of AX88772B is very limited so it's * not possible to announce IFCAP_VLAN_HWTAGGING. */ } ifp->if_capenable = ifp->if_capabilities; if (sc->sc_flags & (AXE_FLAG_772A | AXE_FLAG_772B | AXE_FLAG_178)) adv_pause = MIIF_DOPAUSE; else adv_pause = 0; mtx_lock(&Giant); error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, uether_ifmedia_upd, ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, adv_pause); mtx_unlock(&Giant); return (error); } /* * Probe for a AX88172 chip. */ static int axe_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != AXE_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != AXE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(axe_devs, sizeof(axe_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int axe_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct axe_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; sc->sc_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = AXE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, axe_config, AXE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &axe_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: axe_detach(dev); return (ENXIO); /* failure */ } static int axe_detach(device_t dev) { struct axe_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, AXE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } #if (AXE_BULK_BUF_SIZE >= 0x10000) #error "Please update axe_bulk_read_callback()!" #endif static void axe_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct axe_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); axe_rx_frame(ue, pc, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static int axe_rx_frame(struct usb_ether *ue, struct usb_page_cache *pc, int actlen) { struct axe_softc *sc; struct axe_sframe_hdr hdr; struct axe_csum_hdr csum_hdr; int error, len, pos; sc = uether_getsc(ue); pos = 0; len = 0; error = 0; if ((sc->sc_flags & AXE_FLAG_STD_FRAME) != 0) { while (pos < actlen) { if ((int)(pos + sizeof(hdr)) > actlen) { /* too little data */ error = EINVAL; break; } usbd_copy_out(pc, pos, &hdr, sizeof(hdr)); if ((hdr.len ^ hdr.ilen) != sc->sc_lenmask) { /* we lost sync */ error = EINVAL; break; } pos += sizeof(hdr); len = le16toh(hdr.len); if (pos + len > actlen) { /* invalid length */ error = EINVAL; break; } axe_rxeof(ue, pc, pos, len, NULL); pos += len + (len % 2); } } else if ((sc->sc_flags & AXE_FLAG_CSUM_FRAME) != 0) { while (pos < actlen) { if ((int)(pos + sizeof(csum_hdr)) > actlen) { /* too little data */ error = EINVAL; break; } usbd_copy_out(pc, pos, &csum_hdr, sizeof(csum_hdr)); csum_hdr.len = le16toh(csum_hdr.len); csum_hdr.ilen = le16toh(csum_hdr.ilen); csum_hdr.cstatus = le16toh(csum_hdr.cstatus); if ((AXE_CSUM_RXBYTES(csum_hdr.len) ^ AXE_CSUM_RXBYTES(csum_hdr.ilen)) != sc->sc_lenmask) { /* we lost sync */ error = EINVAL; break; } /* * Get total transferred frame length including * checksum header. The length should be multiple * of 4. */ len = sizeof(csum_hdr) + AXE_CSUM_RXBYTES(csum_hdr.len); len = (len + 3) & ~3; if (pos + len > actlen) { /* invalid length */ error = EINVAL; break; } axe_rxeof(ue, pc, pos + sizeof(csum_hdr), AXE_CSUM_RXBYTES(csum_hdr.len), &csum_hdr); pos += len; } } else axe_rxeof(ue, pc, 0, actlen, NULL); if (error != 0) if_inc_counter(ue->ue_ifp, IFCOUNTER_IERRORS, 1); return (error); } static int axe_rxeof(struct usb_ether *ue, struct usb_page_cache *pc, unsigned int offset, unsigned int len, struct axe_csum_hdr *csum_hdr) { struct ifnet *ifp = ue->ue_ifp; struct mbuf *m; if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return (EINVAL); } m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); return (ENOMEM); } m->m_len = m->m_pkthdr.len = MCLBYTES; m_adj(m, ETHER_ALIGN); usbd_copy_out(pc, offset, mtod(m, uint8_t *), len); if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; if (csum_hdr != NULL && csum_hdr->cstatus & AXE_CSUM_HDR_L3_TYPE_IPV4) { if ((csum_hdr->cstatus & (AXE_CSUM_HDR_L4_CSUM_ERR | AXE_CSUM_HDR_L3_CSUM_ERR)) == 0) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED | CSUM_IP_VALID; if ((csum_hdr->cstatus & AXE_CSUM_HDR_L4_TYPE_MASK) == AXE_CSUM_HDR_L4_TYPE_TCP || (csum_hdr->cstatus & AXE_CSUM_HDR_L4_TYPE_MASK) == AXE_CSUM_HDR_L4_TYPE_UDP) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } } (void)mbufq_enqueue(&ue->ue_rxq, m); return (0); } #if ((AXE_BULK_BUF_SIZE >= 0x10000) || (AXE_BULK_BUF_SIZE < (MCLBYTES+4))) #error "Please update axe_bulk_write_callback()!" #endif static void axe_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct axe_softc *sc = usbd_xfer_softc(xfer); struct axe_sframe_hdr hdr; struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; int nframes, pos; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & AXE_FLAG_LINK) == 0 || (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { /* * Don't send anything if there is no link or * controller is busy. */ return; } for (nframes = 0; nframes < 16 && !IFQ_DRV_IS_EMPTY(&ifp->if_snd); nframes++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES, nframes); pos = 0; pc = usbd_xfer_get_frame(xfer, nframes); if (AXE_IS_178_FAMILY(sc)) { hdr.len = htole16(m->m_pkthdr.len); hdr.ilen = ~hdr.len; /* * If upper stack computed checksum, driver * should tell controller not to insert * computed checksum for checksum offloading * enabled controller. */ if (ifp->if_capabilities & IFCAP_TXCSUM) { if ((m->m_pkthdr.csum_flags & AXE_CSUM_FEATURES) != 0) hdr.len |= htole16( AXE_TX_CSUM_PSEUDO_HDR); else hdr.len |= htole16( AXE_TX_CSUM_DIS); } usbd_copy_in(pc, pos, &hdr, sizeof(hdr)); pos += sizeof(hdr); usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len); pos += m->m_pkthdr.len; if ((pos % 512) == 0) { hdr.len = 0; hdr.ilen = 0xffff; usbd_copy_in(pc, pos, &hdr, sizeof(hdr)); pos += sizeof(hdr); } } else { usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len); pos += m->m_pkthdr.len; } /* * XXX * Update TX packet counter here. This is not * correct way but it seems that there is no way * to know how many packets are sent at the end * of transfer because controller combines * multiple writes into single one if there is * room in TX buffer of controller. */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); /* Set frame length. */ usbd_xfer_set_frame_len(xfer, nframes, pos); } if (nframes != 0) { usbd_xfer_set_frames(xfer, nframes); usbd_transfer_submit(xfer); ifp->if_drv_flags |= IFF_DRV_OACTIVE; } return; /* NOTREACHED */ default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void axe_tick(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); struct mii_data *mii = GET_MII(sc); AXE_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & AXE_FLAG_LINK) == 0) { axe_miibus_statchg(ue->ue_dev); if ((sc->sc_flags & AXE_FLAG_LINK) != 0) axe_start(ue); } } static void axe_start(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[AXE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[AXE_BULK_DT_WR]); } static void axe_csum_cfg(struct usb_ether *ue) { struct axe_softc *sc; struct ifnet *ifp; uint16_t csum1, csum2; sc = uether_getsc(ue); AXE_LOCK_ASSERT(sc, MA_OWNED); if ((sc->sc_flags & AXE_FLAG_772B) != 0) { ifp = uether_getifp(ue); csum1 = 0; csum2 = 0; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) csum1 |= AXE_TXCSUM_IP | AXE_TXCSUM_TCP | AXE_TXCSUM_UDP; axe_cmd(sc, AXE_772B_CMD_WRITE_TXCSUM, csum2, csum1, NULL); csum1 = 0; csum2 = 0; if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) csum1 |= AXE_RXCSUM_IP | AXE_RXCSUM_IPVE | AXE_RXCSUM_TCP | AXE_RXCSUM_UDP | AXE_RXCSUM_ICMP | AXE_RXCSUM_IGMP; axe_cmd(sc, AXE_772B_CMD_WRITE_RXCSUM, csum2, csum1, NULL); } } static void axe_init(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint16_t rxmode; AXE_LOCK_ASSERT(sc, MA_OWNED); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* Cancel pending I/O */ axe_stop(ue); axe_reset(sc); /* Set MAC address and transmitter IPG values. */ if (AXE_IS_178_FAMILY(sc)) { axe_cmd(sc, AXE_178_CMD_WRITE_NODEID, 0, 0, IF_LLADDR(ifp)); axe_cmd(sc, AXE_178_CMD_WRITE_IPG012, sc->sc_ipgs[2], (sc->sc_ipgs[1] << 8) | (sc->sc_ipgs[0]), NULL); } else { axe_cmd(sc, AXE_172_CMD_WRITE_NODEID, 0, 0, IF_LLADDR(ifp)); axe_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->sc_ipgs[0], NULL); axe_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->sc_ipgs[1], NULL); axe_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->sc_ipgs[2], NULL); } if (AXE_IS_178_FAMILY(sc)) { sc->sc_flags &= ~(AXE_FLAG_STD_FRAME | AXE_FLAG_CSUM_FRAME); if ((sc->sc_flags & AXE_FLAG_772B) != 0 && (ifp->if_capenable & IFCAP_RXCSUM) != 0) { sc->sc_lenmask = AXE_CSUM_HDR_LEN_MASK; sc->sc_flags |= AXE_FLAG_CSUM_FRAME; } else { sc->sc_lenmask = AXE_HDR_LEN_MASK; sc->sc_flags |= AXE_FLAG_STD_FRAME; } } /* Configure TX/RX checksum offloading. */ axe_csum_cfg(ue); if (sc->sc_flags & AXE_FLAG_772B) { /* AX88772B uses different maximum frame burst configuration. */ axe_cmd(sc, AXE_772B_CMD_RXCTL_WRITE_CFG, ax88772b_mfb_table[AX88772B_MFB_16K].threshold, ax88772b_mfb_table[AX88772B_MFB_16K].byte_cnt, NULL); } /* Enable receiver, set RX mode. */ rxmode = (AXE_RXCMD_MULTICAST | AXE_RXCMD_ENABLE); if (AXE_IS_178_FAMILY(sc)) { if (sc->sc_flags & AXE_FLAG_772B) { /* * Select RX header format type 1. Aligning IP * header on 4 byte boundary is not needed when * checksum offloading feature is not used * because we always copy the received frame in * RX handler. When RX checksum offloading is * active, aligning IP header is required to * reflect actual frame length including RX * header size. */ rxmode |= AXE_772B_RXCMD_HDR_TYPE_1; if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) rxmode |= AXE_772B_RXCMD_IPHDR_ALIGN; } else { /* * Default Rx buffer size is too small to get * maximum performance. */ rxmode |= AXE_178_RXCMD_MFB_16384; } } else { rxmode |= AXE_172_RXCMD_UNICAST; } /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) rxmode |= AXE_RXCMD_PROMISC; if (ifp->if_flags & IFF_BROADCAST) rxmode |= AXE_RXCMD_BROADCAST; axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); /* Load the multicast filter. */ axe_setmulti(ue); usbd_xfer_set_stall(sc->sc_xfer[AXE_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; /* Switch to selected media. */ axe_ifmedia_upd(ifp); } static void axe_setpromisc(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint16_t rxmode; axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode); rxmode = le16toh(rxmode); if (ifp->if_flags & IFF_PROMISC) { rxmode |= AXE_RXCMD_PROMISC; } else { rxmode &= ~AXE_RXCMD_PROMISC; } axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); axe_setmulti(ue); } static void axe_stop(struct usb_ether *ue) { struct axe_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); AXE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->sc_flags &= ~AXE_FLAG_LINK; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[AXE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[AXE_BULK_DT_RD]); } static int axe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct usb_ether *ue = ifp->if_softc; struct axe_softc *sc; struct ifreq *ifr; int error, mask, reinit; sc = uether_getsc(ue); ifr = (struct ifreq *)data; error = 0; reinit = 0; if (cmd == SIOCSIFCAP) { AXE_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && (ifp->if_capabilities & IFCAP_TXCSUM) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) ifp->if_hwassist |= AXE_CSUM_FEATURES; else ifp->if_hwassist &= ~AXE_CSUM_FEATURES; reinit++; } if ((mask & IFCAP_RXCSUM) != 0 && (ifp->if_capabilities & IFCAP_RXCSUM) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; reinit++; } if (reinit > 0 && ifp->if_drv_flags & IFF_DRV_RUNNING) ifp->if_drv_flags &= ~IFF_DRV_RUNNING; else reinit = 0; AXE_UNLOCK(sc); if (reinit > 0) uether_init(ue); } else error = uether_ioctl(ifp, cmd, data); return (error); } Index: head/sys/dev/usb/net/if_axge.c =================================================================== --- head/sys/dev/usb/net/if_axge.c (revision 357971) +++ head/sys/dev/usb/net/if_axge.c (revision 357972) @@ -1,1068 +1,1069 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013-2014 Kevin Lo * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); /* * ASIX Electronics AX88178A/AX88179 USB 2.0/3.0 gigabit ethernet driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR axge_debug #include #include #include #include #include "miibus_if.h" /* * Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID axge_devs[] = { #define AXGE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } AXGE_DEV(ASIX, AX88178A), AXGE_DEV(ASIX, AX88179), AXGE_DEV(DLINK, DUB1312), AXGE_DEV(LENOVO, GIGALAN), AXGE_DEV(SITECOMEU, LN032), #undef AXGE_DEV }; static const struct { uint8_t ctrl; uint8_t timer_l; uint8_t timer_h; uint8_t size; uint8_t ifg; } __packed axge_bulk_size[] = { { 7, 0x4f, 0x00, 0x12, 0xff }, { 7, 0x20, 0x03, 0x16, 0xff }, { 7, 0xae, 0x07, 0x18, 0xff }, { 7, 0xcc, 0x4c, 0x18, 0x08 } }; /* prototypes */ static device_probe_t axge_probe; static device_attach_t axge_attach; static device_detach_t axge_detach; static usb_callback_t axge_bulk_read_callback; static usb_callback_t axge_bulk_write_callback; static miibus_readreg_t axge_miibus_readreg; static miibus_writereg_t axge_miibus_writereg; static miibus_statchg_t axge_miibus_statchg; static uether_fn_t axge_attach_post; static uether_fn_t axge_init; static uether_fn_t axge_stop; static uether_fn_t axge_start; static uether_fn_t axge_tick; static uether_fn_t axge_rxfilter; static int axge_read_mem(struct axge_softc *, uint8_t, uint16_t, uint16_t, void *, int); static void axge_write_mem(struct axge_softc *, uint8_t, uint16_t, uint16_t, void *, int); static uint8_t axge_read_cmd_1(struct axge_softc *, uint8_t, uint16_t); static uint16_t axge_read_cmd_2(struct axge_softc *, uint8_t, uint16_t, uint16_t); static void axge_write_cmd_1(struct axge_softc *, uint8_t, uint16_t, uint8_t); static void axge_write_cmd_2(struct axge_softc *, uint8_t, uint16_t, uint16_t, uint16_t); static void axge_chip_init(struct axge_softc *); static void axge_reset(struct axge_softc *); static int axge_attach_post_sub(struct usb_ether *); static int axge_ifmedia_upd(struct ifnet *); static void axge_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int axge_ioctl(struct ifnet *, u_long, caddr_t); static void axge_rx_frame(struct usb_ether *, struct usb_page_cache *, int); static void axge_rxeof(struct usb_ether *, struct usb_page_cache *, unsigned int, unsigned int, uint32_t); static void axge_csum_cfg(struct usb_ether *); #define AXGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) #ifdef USB_DEBUG static int axge_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, axge, CTLFLAG_RW, 0, "USB axge"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, axge, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB axge"); SYSCTL_INT(_hw_usb_axge, OID_AUTO, debug, CTLFLAG_RWTUN, &axge_debug, 0, "Debug level"); #endif static const struct usb_config axge_config[AXGE_N_TRANSFER] = { [AXGE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .frames = AXGE_N_FRAMES, .bufsize = AXGE_N_FRAMES * MCLBYTES, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = axge_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [AXGE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 65536, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = axge_bulk_read_callback, .timeout = 0, /* no timeout */ }, }; static device_method_t axge_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, axge_probe), DEVMETHOD(device_attach, axge_attach), DEVMETHOD(device_detach, axge_detach), /* MII interface. */ DEVMETHOD(miibus_readreg, axge_miibus_readreg), DEVMETHOD(miibus_writereg, axge_miibus_writereg), DEVMETHOD(miibus_statchg, axge_miibus_statchg), DEVMETHOD_END }; static driver_t axge_driver = { .name = "axge", .methods = axge_methods, .size = sizeof(struct axge_softc), }; static devclass_t axge_devclass; DRIVER_MODULE(axge, uhub, axge_driver, axge_devclass, NULL, NULL); DRIVER_MODULE(miibus, axge, miibus_driver, miibus_devclass, NULL, NULL); MODULE_DEPEND(axge, uether, 1, 1, 1); MODULE_DEPEND(axge, usb, 1, 1, 1); MODULE_DEPEND(axge, ether, 1, 1, 1); MODULE_DEPEND(axge, miibus, 1, 1, 1); MODULE_VERSION(axge, 1); USB_PNP_HOST_INFO(axge_devs); static const struct usb_ether_methods axge_ue_methods = { .ue_attach_post = axge_attach_post, .ue_attach_post_sub = axge_attach_post_sub, .ue_start = axge_start, .ue_init = axge_init, .ue_stop = axge_stop, .ue_tick = axge_tick, .ue_setmulti = axge_rxfilter, .ue_setpromisc = axge_rxfilter, .ue_mii_upd = axge_ifmedia_upd, .ue_mii_sts = axge_ifmedia_sts, }; static int axge_read_mem(struct axge_softc *sc, uint8_t cmd, uint16_t index, uint16_t val, void *buf, int len) { struct usb_device_request req; AXGE_LOCK_ASSERT(sc, MA_OWNED); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = cmd; USETW(req.wValue, val); USETW(req.wIndex, index); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static void axge_write_mem(struct axge_softc *sc, uint8_t cmd, uint16_t index, uint16_t val, void *buf, int len) { struct usb_device_request req; AXGE_LOCK_ASSERT(sc, MA_OWNED); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = cmd; USETW(req.wValue, val); USETW(req.wIndex, index); USETW(req.wLength, len); if (uether_do_request(&sc->sc_ue, &req, buf, 1000)) { /* Error ignored. */ } } static uint8_t axge_read_cmd_1(struct axge_softc *sc, uint8_t cmd, uint16_t reg) { uint8_t val; axge_read_mem(sc, cmd, 1, reg, &val, 1); return (val); } static uint16_t axge_read_cmd_2(struct axge_softc *sc, uint8_t cmd, uint16_t index, uint16_t reg) { uint8_t val[2]; axge_read_mem(sc, cmd, index, reg, &val, 2); return (UGETW(val)); } static void axge_write_cmd_1(struct axge_softc *sc, uint8_t cmd, uint16_t reg, uint8_t val) { axge_write_mem(sc, cmd, 1, reg, &val, 1); } static void axge_write_cmd_2(struct axge_softc *sc, uint8_t cmd, uint16_t index, uint16_t reg, uint16_t val) { uint8_t temp[2]; USETW(temp, val); axge_write_mem(sc, cmd, index, reg, &temp, 2); } static int axge_miibus_readreg(device_t dev, int phy, int reg) { struct axge_softc *sc; uint16_t val; int locked; sc = device_get_softc(dev); locked = mtx_owned(&sc->sc_mtx); if (!locked) AXGE_LOCK(sc); val = axge_read_cmd_2(sc, AXGE_ACCESS_PHY, reg, phy); if (!locked) AXGE_UNLOCK(sc); return (val); } static int axge_miibus_writereg(device_t dev, int phy, int reg, int val) { struct axge_softc *sc; int locked; sc = device_get_softc(dev); locked = mtx_owned(&sc->sc_mtx); if (!locked) AXGE_LOCK(sc); axge_write_cmd_2(sc, AXGE_ACCESS_PHY, reg, phy, val); if (!locked) AXGE_UNLOCK(sc); return (0); } static void axge_miibus_statchg(device_t dev) { struct axge_softc *sc; struct mii_data *mii; struct ifnet *ifp; uint8_t link_status, tmp[5]; uint16_t val; int locked; sc = device_get_softc(dev); mii = GET_MII(sc); locked = mtx_owned(&sc->sc_mtx); if (!locked) AXGE_LOCK(sc); ifp = uether_getifp(&sc->sc_ue); if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) goto done; sc->sc_flags &= ~AXGE_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: case IFM_1000_T: sc->sc_flags |= AXGE_FLAG_LINK; break; default: break; } } /* Lost link, do nothing. */ if ((sc->sc_flags & AXGE_FLAG_LINK) == 0) goto done; link_status = axge_read_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_PLSR); val = 0; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { val |= MSR_FD; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) val |= MSR_TFC; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) val |= MSR_RFC; } val |= MSR_RE; switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: val |= MSR_GM | MSR_EN_125MHZ; if (link_status & PLSR_USB_SS) memcpy(tmp, &axge_bulk_size[0], 5); else if (link_status & PLSR_USB_HS) memcpy(tmp, &axge_bulk_size[1], 5); else memcpy(tmp, &axge_bulk_size[3], 5); break; case IFM_100_TX: val |= MSR_PS; if (link_status & (PLSR_USB_SS | PLSR_USB_HS)) memcpy(tmp, &axge_bulk_size[2], 5); else memcpy(tmp, &axge_bulk_size[3], 5); break; case IFM_10_T: memcpy(tmp, &axge_bulk_size[3], 5); break; } /* Rx bulk configuration. */ axge_write_mem(sc, AXGE_ACCESS_MAC, 5, AXGE_RX_BULKIN_QCTRL, tmp, 5); axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR, val); done: if (!locked) AXGE_UNLOCK(sc); } static void axge_chip_init(struct axge_softc *sc) { /* Power up ethernet PHY. */ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR, 0); axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR, EPPRCR_IPRL); uether_pause(&sc->sc_ue, hz / 4); axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CLK_SELECT, AXGE_CLK_SELECT_ACS | AXGE_CLK_SELECT_BCS); uether_pause(&sc->sc_ue, hz / 10); } static void axge_reset(struct axge_softc *sc) { struct usb_config_descriptor *cd; usb_error_t err; cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, cd->bConfigurationValue); if (err) DPRINTF("reset failed (ignored)\n"); /* Wait a little while for the chip to get its brains in order. */ uether_pause(&sc->sc_ue, hz / 100); /* Reinitialize controller to achieve full reset. */ axge_chip_init(sc); } static void axge_attach_post(struct usb_ether *ue) { struct axge_softc *sc; sc = uether_getsc(ue); /* Initialize controller and get station address. */ axge_chip_init(sc); axge_read_mem(sc, AXGE_ACCESS_MAC, ETHER_ADDR_LEN, AXGE_NIDR, ue->ue_eaddr, ETHER_ADDR_LEN); } static int axge_attach_post_sub(struct usb_ether *ue) { struct axge_softc *sc; struct ifnet *ifp; int error; sc = uether_getsc(ue); ifp = ue->ue_ifp; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = uether_start; ifp->if_ioctl = axge_ioctl; ifp->if_init = uether_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_TXCSUM | IFCAP_RXCSUM; ifp->if_hwassist = AXGE_CSUM_FEATURES; ifp->if_capenable = ifp->if_capabilities; mtx_lock(&Giant); error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, uether_ifmedia_upd, ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, AXGE_PHY_ADDR, MII_OFFSET_ANY, MIIF_DOPAUSE); mtx_unlock(&Giant); return (error); } /* * Set media options. */ static int axge_ifmedia_upd(struct ifnet *ifp) { struct axge_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = ifp->if_softc; mii = GET_MII(sc); AXGE_LOCK_ASSERT(sc, MA_OWNED); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } /* * Report current media status. */ static void axge_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct axge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = GET_MII(sc); AXGE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; AXGE_UNLOCK(sc); } /* * Probe for a AX88179 chip. */ static int axge_probe(device_t dev) { struct usb_attach_arg *uaa; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != AXGE_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != AXGE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(axge_devs, sizeof(axge_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int axge_attach(device_t dev) { struct usb_attach_arg *uaa; struct axge_softc *sc; struct usb_ether *ue; uint8_t iface_index; int error; uaa = device_get_ivars(dev); sc = device_get_softc(dev); ue = &sc->sc_ue; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = AXGE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, axge_config, AXGE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); mtx_destroy(&sc->sc_mtx); return (ENXIO); } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &axge_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: axge_detach(dev); return (ENXIO); /* failure */ } static int axge_detach(device_t dev) { struct axge_softc *sc; struct usb_ether *ue; uint16_t val; sc = device_get_softc(dev); ue = &sc->sc_ue; if (device_is_attached(dev)) { /* wait for any post attach or other command to complete */ usb_proc_drain(&ue->ue_tq); AXGE_LOCK(sc); /* * XXX * ether_ifdetach(9) should be called first. */ axge_stop(ue); /* Force bulk-in to return a zero-length USB packet. */ val = axge_read_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR); val |= EPPRCR_BZ | EPPRCR_IPRL; axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR, val); /* Change clock. */ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CLK_SELECT, 0); /* Disable MAC. */ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_RCR, 0); AXGE_UNLOCK(sc); } usbd_transfer_unsetup(sc->sc_xfer, AXGE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void axge_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct axge_softc *sc; struct usb_ether *ue; struct usb_page_cache *pc; int actlen; sc = usbd_xfer_softc(xfer); ue = &sc->sc_ue; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); axge_rx_frame(ue, pc, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); break; default: if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void axge_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct axge_softc *sc; struct ifnet *ifp; struct usb_page_cache *pc; struct mbuf *m; struct axge_frame_txhdr txhdr; int nframes, pos; sc = usbd_xfer_softc(xfer); ifp = uether_getifp(&sc->sc_ue); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & AXGE_FLAG_LINK) == 0 || (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { /* * Don't send anything if there is no link or * controller is busy. */ return; } for (nframes = 0; nframes < AXGE_N_FRAMES && !IFQ_DRV_IS_EMPTY(&ifp->if_snd); nframes++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES, nframes); pc = usbd_xfer_get_frame(xfer, nframes); txhdr.mss = 0; txhdr.len = htole32(AXGE_TXBYTES(m->m_pkthdr.len)); if ((ifp->if_capenable & IFCAP_TXCSUM) != 0 && (m->m_pkthdr.csum_flags & AXGE_CSUM_FEATURES) == 0) txhdr.len |= htole32(AXGE_CSUM_DISABLE); pos = 0; usbd_copy_in(pc, pos, &txhdr, sizeof(txhdr)); pos += sizeof(txhdr); usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len); pos += m->m_pkthdr.len; /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); /* Set frame length. */ usbd_xfer_set_frame_len(xfer, nframes, pos); } if (nframes != 0) { /* * XXX * Update TX packet counter here. This is not * correct way but it seems that there is no way * to know how many packets are sent at the end * of transfer because controller combines * multiple writes into single one if there is * room in TX buffer of controller. */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, nframes); usbd_xfer_set_frames(xfer, nframes); usbd_transfer_submit(xfer); ifp->if_drv_flags |= IFF_DRV_OACTIVE; } return; /* NOTREACHED */ default: if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void axge_tick(struct usb_ether *ue) { struct axge_softc *sc; struct mii_data *mii; sc = uether_getsc(ue); mii = GET_MII(sc); AXGE_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); } static u_int axge_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint8_t *hashtbl = arg; uint32_t h; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; hashtbl[h / 8] |= 1 << (h % 8); return (1); } static void axge_rxfilter(struct usb_ether *ue) { struct axge_softc *sc; struct ifnet *ifp; uint16_t rxmode; uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; sc = uether_getsc(ue); ifp = uether_getifp(ue); AXGE_LOCK_ASSERT(sc, MA_OWNED); /* * Configure RX settings. * Don't set RCR_IPE(IP header alignment on 32bit boundary) to disable * inserting extra padding bytes. This wastes ethernet to USB host * bandwidth as well as complicating RX handling logic. Current USB * framework requires copying RX frames to mbufs so there is no need * to worry about alignment. */ rxmode = RCR_DROP_CRCERR | RCR_START; if (ifp->if_flags & IFF_BROADCAST) rxmode |= RCR_ACPT_BCAST; if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { if (ifp->if_flags & IFF_PROMISC) rxmode |= RCR_PROMISC; rxmode |= RCR_ACPT_ALL_MCAST; axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_RCR, rxmode); return; } rxmode |= RCR_ACPT_MCAST; if_foreach_llmaddr(ifp, axge_hash_maddr, &hashtbl); axge_write_mem(sc, AXGE_ACCESS_MAC, 8, AXGE_MFA, (void *)&hashtbl, 8); axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_RCR, rxmode); } static void axge_start(struct usb_ether *ue) { struct axge_softc *sc; sc = uether_getsc(ue); /* * Start the USB transfers, if not already started. */ usbd_transfer_start(sc->sc_xfer[AXGE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[AXGE_BULK_DT_WR]); } static void axge_init(struct usb_ether *ue) { struct axge_softc *sc; struct ifnet *ifp; sc = uether_getsc(ue); ifp = uether_getifp(ue); AXGE_LOCK_ASSERT(sc, MA_OWNED); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* * Cancel pending I/O and free all RX/TX buffers. */ axge_stop(ue); axge_reset(sc); /* Set MAC address. */ axge_write_mem(sc, AXGE_ACCESS_MAC, ETHER_ADDR_LEN, AXGE_NIDR, IF_LLADDR(ifp), ETHER_ADDR_LEN); axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_PWLLR, 0x34); axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_PWLHR, 0x52); /* Configure TX/RX checksum offloading. */ axge_csum_cfg(ue); /* Configure RX filters. */ axge_rxfilter(ue); /* * XXX * Controller supports wakeup on link change detection, * magic packet and wakeup frame recpetion. But it seems * there is no framework for USB ethernet suspend/wakeup. * Disable all wakeup functions. */ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_MMSR, 0); (void)axge_read_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_MMSR); /* Configure default medium type. */ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR, MSR_GM | MSR_FD | MSR_RFC | MSR_TFC | MSR_RE); usbd_xfer_set_stall(sc->sc_xfer[AXGE_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; /* Switch to selected media. */ axge_ifmedia_upd(ifp); } static void axge_stop(struct usb_ether *ue) { struct axge_softc *sc; struct ifnet *ifp; uint16_t val; sc = uether_getsc(ue); ifp = uether_getifp(ue); AXGE_LOCK_ASSERT(sc, MA_OWNED); val = axge_read_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR); val &= ~MSR_RE; axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR, val); if (ifp != NULL) ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->sc_flags &= ~AXGE_FLAG_LINK; /* * Stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[AXGE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[AXGE_BULK_DT_RD]); } static int axge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct usb_ether *ue; struct axge_softc *sc; struct ifreq *ifr; int error, mask, reinit; ue = ifp->if_softc; sc = uether_getsc(ue); ifr = (struct ifreq *)data; error = 0; reinit = 0; if (cmd == SIOCSIFCAP) { AXGE_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && (ifp->if_capabilities & IFCAP_TXCSUM) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) ifp->if_hwassist |= AXGE_CSUM_FEATURES; else ifp->if_hwassist &= ~AXGE_CSUM_FEATURES; reinit++; } if ((mask & IFCAP_RXCSUM) != 0 && (ifp->if_capabilities & IFCAP_RXCSUM) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; reinit++; } if (reinit > 0 && ifp->if_drv_flags & IFF_DRV_RUNNING) ifp->if_drv_flags &= ~IFF_DRV_RUNNING; else reinit = 0; AXGE_UNLOCK(sc); if (reinit > 0) uether_init(ue); } else error = uether_ioctl(ifp, cmd, data); return (error); } static void axge_rx_frame(struct usb_ether *ue, struct usb_page_cache *pc, int actlen) { struct axge_frame_rxhdr pkt_hdr; uint32_t rxhdr; uint32_t pos; uint32_t pkt_cnt, pkt_end; uint32_t hdr_off; uint32_t pktlen; /* verify we have enough data */ if (actlen < (int)sizeof(rxhdr)) return; pos = 0; usbd_copy_out(pc, actlen - sizeof(rxhdr), &rxhdr, sizeof(rxhdr)); rxhdr = le32toh(rxhdr); pkt_cnt = rxhdr & 0xFFFF; hdr_off = pkt_end = (rxhdr >> 16) & 0xFFFF; /* * <----------------------- actlen ------------------------> * [frame #0]...[frame #N][pkt_hdr #0]...[pkt_hdr #N][rxhdr] * Each RX frame would be aligned on 8 bytes boundary. If * RCR_IPE bit is set in AXGE_RCR register, there would be 2 * padding bytes and 6 dummy bytes(as the padding also should * be aligned on 8 bytes boundary) for each RX frame to align * IP header on 32bits boundary. Driver don't set RCR_IPE bit * of AXGE_RCR register, so there should be no padding bytes * which simplifies RX logic a lot. */ while (pkt_cnt--) { /* verify the header offset */ if ((int)(hdr_off + sizeof(pkt_hdr)) > actlen) { DPRINTF("End of packet headers\n"); break; } usbd_copy_out(pc, hdr_off, &pkt_hdr, sizeof(pkt_hdr)); pkt_hdr.status = le32toh(pkt_hdr.status); pktlen = AXGE_RXBYTES(pkt_hdr.status); if (pos + pktlen > pkt_end) { DPRINTF("Data position reached end\n"); break; } if (AXGE_RX_ERR(pkt_hdr.status) != 0) { DPRINTF("Dropped a packet\n"); if_inc_counter(ue->ue_ifp, IFCOUNTER_IERRORS, 1); } else axge_rxeof(ue, pc, pos, pktlen, pkt_hdr.status); pos += (pktlen + 7) & ~7; hdr_off += sizeof(pkt_hdr); } } static void axge_rxeof(struct usb_ether *ue, struct usb_page_cache *pc, unsigned int offset, unsigned int len, uint32_t status) { struct ifnet *ifp; struct mbuf *m; ifp = ue->ue_ifp; if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return; } if (len > MHLEN - ETHER_ALIGN) m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); else m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); return; } m->m_pkthdr.rcvif = ifp; m->m_len = m->m_pkthdr.len = len; m->m_data += ETHER_ALIGN; usbd_copy_out(pc, offset, mtod(m, uint8_t *), len); if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) { if ((status & AXGE_RX_L3_CSUM_ERR) == 0 && (status & AXGE_RX_L3_TYPE_MASK) == AXGE_RX_L3_TYPE_IPV4) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED | CSUM_IP_VALID; if ((status & AXGE_RX_L4_CSUM_ERR) == 0 && ((status & AXGE_RX_L4_TYPE_MASK) == AXGE_RX_L4_TYPE_UDP || (status & AXGE_RX_L4_TYPE_MASK) == AXGE_RX_L4_TYPE_TCP)) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); (void)mbufq_enqueue(&ue->ue_rxq, m); } static void axge_csum_cfg(struct usb_ether *ue) { struct axge_softc *sc; struct ifnet *ifp; uint8_t csum; sc = uether_getsc(ue); AXGE_LOCK_ASSERT(sc, MA_OWNED); ifp = uether_getifp(ue); csum = 0; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) csum |= CTCR_IP | CTCR_TCP | CTCR_UDP; axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CTCR, csum); csum = 0; if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) csum |= CRCR_IP | CRCR_TCP | CRCR_UDP; axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CRCR, csum); } Index: head/sys/dev/usb/net/if_cdce.c =================================================================== --- head/sys/dev/usb/net/if_cdce.c (revision 357971) +++ head/sys/dev/usb/net/if_cdce.c (revision 357972) @@ -1,1587 +1,1588 @@ /* $NetBSD: if_cdce.c,v 1.4 2004/10/24 12:50:54 augustss Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul * Copyright (c) 2003-2005 Craig Boston * Copyright (c) 2004 Daniel Hartmeier * Copyright (c) 2009 Hans Petter Selasky * 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul, THE VOICES IN HIS HEAD OR * THE 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. */ /* * USB Communication Device Class (Ethernet Networking Control Model) * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf */ /* * USB Network Control Model (NCM) * http://www.usb.org/developers/devclass_docs/NCM10.zip */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR cdce_debug #include #include #include #include "usb_if.h" #include #include static device_probe_t cdce_probe; static device_attach_t cdce_attach; static device_detach_t cdce_detach; static device_suspend_t cdce_suspend; static device_resume_t cdce_resume; static usb_handle_request_t cdce_handle_request; static usb_callback_t cdce_bulk_write_callback; static usb_callback_t cdce_bulk_read_callback; static usb_callback_t cdce_intr_read_callback; static usb_callback_t cdce_intr_write_callback; #if CDCE_HAVE_NCM static usb_callback_t cdce_ncm_bulk_write_callback; static usb_callback_t cdce_ncm_bulk_read_callback; #endif static uether_fn_t cdce_attach_post; static uether_fn_t cdce_init; static uether_fn_t cdce_stop; static uether_fn_t cdce_start; static uether_fn_t cdce_setmulti; static uether_fn_t cdce_setpromisc; static uint32_t cdce_m_crc32(struct mbuf *, uint32_t, uint32_t); #ifdef USB_DEBUG static int cdce_debug = 0; static int cdce_tx_interval = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, cdce, CTLFLAG_RW, 0, "USB CDC-Ethernet"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, cdce, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB CDC-Ethernet"); SYSCTL_INT(_hw_usb_cdce, OID_AUTO, debug, CTLFLAG_RWTUN, &cdce_debug, 0, "Debug level"); SYSCTL_INT(_hw_usb_cdce, OID_AUTO, interval, CTLFLAG_RWTUN, &cdce_tx_interval, 0, "NCM transmit interval in ms"); #endif static const struct usb_config cdce_config[CDCE_N_TRANSFER] = { [CDCE_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .frames = CDCE_FRAMES_MAX, .bufsize = (CDCE_FRAMES_MAX * MCLBYTES), .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, .callback = cdce_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .frames = CDCE_FRAMES_MAX, .bufsize = (CDCE_FRAMES_MAX * MCLBYTES), .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, .callback = cdce_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_INTR_RX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_read_callback, .timeout = 0, .usb_mode = USB_MODE_HOST, }, [CDCE_INTR_TX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DEVICE, }, }; #if CDCE_HAVE_NCM static const struct usb_config cdce_ncm_config[CDCE_N_TRANSFER] = { [CDCE_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .frames = CDCE_NCM_RX_FRAMES_MAX, .bufsize = (CDCE_NCM_RX_FRAMES_MAX * CDCE_NCM_RX_MAXLEN), .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,}, .callback = cdce_ncm_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .frames = CDCE_NCM_TX_FRAMES_MAX, .bufsize = (CDCE_NCM_TX_FRAMES_MAX * CDCE_NCM_TX_MAXLEN), .flags = {.pipe_bof = 1,}, .callback = cdce_ncm_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_INTR_RX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_read_callback, .timeout = 0, .usb_mode = USB_MODE_HOST, }, [CDCE_INTR_TX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DEVICE, }, }; #endif static device_method_t cdce_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, cdce_handle_request), /* Device interface */ DEVMETHOD(device_probe, cdce_probe), DEVMETHOD(device_attach, cdce_attach), DEVMETHOD(device_detach, cdce_detach), DEVMETHOD(device_suspend, cdce_suspend), DEVMETHOD(device_resume, cdce_resume), DEVMETHOD_END }; static driver_t cdce_driver = { .name = "cdce", .methods = cdce_methods, .size = sizeof(struct cdce_softc), }; static devclass_t cdce_devclass; static eventhandler_tag cdce_etag; static int cdce_driver_loaded(struct module *, int, void *); static const STRUCT_USB_HOST_ID cdce_switch_devs[] = { {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E3272_INIT, MSC_EJECT_HUAWEI2)}, }; static const STRUCT_USB_HOST_ID cdce_host_devs[] = { {USB_VPI(USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_ETHERNETGADGET, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500, CDCE_FLAG_ZAURUS)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_RTL8156, 0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x16), USB_DRIVER_INFO(0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x46), USB_DRIVER_INFO(0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x76), USB_DRIVER_INFO(0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x03), USB_IFACE_PROTOCOL(0x16), USB_DRIVER_INFO(0)}, }; static const STRUCT_USB_DUAL_ID cdce_dual_devs[] = { {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0)}, {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_MOBILE_DIRECT_LINE_MODEL, 0)}, {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_NETWORK_CONTROL_MODEL, 0)}, }; DRIVER_MODULE(cdce, uhub, cdce_driver, cdce_devclass, cdce_driver_loaded, 0); MODULE_VERSION(cdce, 1); MODULE_DEPEND(cdce, uether, 1, 1, 1); MODULE_DEPEND(cdce, usb, 1, 1, 1); MODULE_DEPEND(cdce, ether, 1, 1, 1); USB_PNP_DEVICE_INFO(cdce_switch_devs); USB_PNP_HOST_INFO(cdce_host_devs); USB_PNP_DUAL_INFO(cdce_dual_devs); static const struct usb_ether_methods cdce_ue_methods = { .ue_attach_post = cdce_attach_post, .ue_start = cdce_start, .ue_init = cdce_init, .ue_stop = cdce_stop, .ue_setmulti = cdce_setmulti, .ue_setpromisc = cdce_setpromisc, }; #if CDCE_HAVE_NCM /*------------------------------------------------------------------------* * cdce_ncm_init * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t cdce_ncm_init(struct cdce_softc *sc) { struct usb_ncm_parameters temp; struct usb_device_request req; struct usb_ncm_func_descriptor *ufd; uint8_t value[8]; int err; ufd = usbd_find_descriptor(sc->sc_ue.ue_udev, NULL, sc->sc_ifaces_index[1], UDESC_CS_INTERFACE, 0xFF, UCDC_NCM_FUNC_DESC_SUBTYPE, 0xFF); /* verify length of NCM functional descriptor */ if (ufd != NULL) { if (ufd->bLength < sizeof(*ufd)) ufd = NULL; else DPRINTFN(1, "Found NCM functional descriptor.\n"); } req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UCDC_NCM_GET_NTB_PARAMETERS; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, sizeof(temp)); err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, &temp, 0, NULL, 1000 /* ms */); if (err) return (1); /* Read correct set of parameters according to device mode */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { sc->sc_ncm.rx_max = UGETDW(temp.dwNtbInMaxSize); sc->sc_ncm.tx_max = UGETDW(temp.dwNtbOutMaxSize); sc->sc_ncm.tx_remainder = UGETW(temp.wNdpOutPayloadRemainder); sc->sc_ncm.tx_modulus = UGETW(temp.wNdpOutDivisor); sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpOutAlignment); sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams); } else { sc->sc_ncm.rx_max = UGETDW(temp.dwNtbOutMaxSize); sc->sc_ncm.tx_max = UGETDW(temp.dwNtbInMaxSize); sc->sc_ncm.tx_remainder = UGETW(temp.wNdpInPayloadRemainder); sc->sc_ncm.tx_modulus = UGETW(temp.wNdpInDivisor); sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpInAlignment); sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams); } /* Verify maximum receive length */ if ((sc->sc_ncm.rx_max < 32) || (sc->sc_ncm.rx_max > CDCE_NCM_RX_MAXLEN)) { DPRINTFN(1, "Using default maximum receive length\n"); sc->sc_ncm.rx_max = CDCE_NCM_RX_MAXLEN; } /* Verify maximum transmit length */ if ((sc->sc_ncm.tx_max < 32) || (sc->sc_ncm.tx_max > CDCE_NCM_TX_MAXLEN)) { DPRINTFN(1, "Using default maximum transmit length\n"); sc->sc_ncm.tx_max = CDCE_NCM_TX_MAXLEN; } /* * Verify that the structure alignment is: * - power of two * - not greater than the maximum transmit length * - not less than four bytes */ if ((sc->sc_ncm.tx_struct_align < 4) || (sc->sc_ncm.tx_struct_align != ((-sc->sc_ncm.tx_struct_align) & sc->sc_ncm.tx_struct_align)) || (sc->sc_ncm.tx_struct_align >= sc->sc_ncm.tx_max)) { DPRINTFN(1, "Using default other alignment: 4 bytes\n"); sc->sc_ncm.tx_struct_align = 4; } /* * Verify that the payload alignment is: * - power of two * - not greater than the maximum transmit length * - not less than four bytes */ if ((sc->sc_ncm.tx_modulus < 4) || (sc->sc_ncm.tx_modulus != ((-sc->sc_ncm.tx_modulus) & sc->sc_ncm.tx_modulus)) || (sc->sc_ncm.tx_modulus >= sc->sc_ncm.tx_max)) { DPRINTFN(1, "Using default transmit modulus: 4 bytes\n"); sc->sc_ncm.tx_modulus = 4; } /* Verify that the payload remainder */ if ((sc->sc_ncm.tx_remainder >= sc->sc_ncm.tx_modulus)) { DPRINTFN(1, "Using default transmit remainder: 0 bytes\n"); sc->sc_ncm.tx_remainder = 0; } /* * Offset the TX remainder so that IP packet payload starts at * the tx_modulus. This is not too clear in the specification. */ sc->sc_ncm.tx_remainder = (sc->sc_ncm.tx_remainder - ETHER_HDR_LEN) & (sc->sc_ncm.tx_modulus - 1); /* Verify max datagrams */ if (sc->sc_ncm.tx_nframe == 0 || sc->sc_ncm.tx_nframe > (CDCE_NCM_SUBFRAMES_MAX - 1)) { DPRINTFN(1, "Using default max " "subframes: %u units\n", CDCE_NCM_SUBFRAMES_MAX - 1); /* need to reserve one entry for zero padding */ sc->sc_ncm.tx_nframe = (CDCE_NCM_SUBFRAMES_MAX - 1); } /* Additional configuration, will fail in device side mode, which is OK. */ req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_NCM_SET_NTB_INPUT_SIZE; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; if (ufd != NULL && (ufd->bmNetworkCapabilities & UCDC_NCM_CAP_MAX_DGRAM)) { USETW(req.wLength, 8); USETDW(value, sc->sc_ncm.rx_max); USETW(value + 4, (CDCE_NCM_SUBFRAMES_MAX - 1)); USETW(value + 6, 0); } else { USETW(req.wLength, 4); USETDW(value, sc->sc_ncm.rx_max); } err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, &value, 0, NULL, 1000 /* ms */); if (err) { DPRINTFN(1, "Setting input size " "to %u failed.\n", sc->sc_ncm.rx_max); } req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_NCM_SET_CRC_MODE; USETW(req.wValue, 0); /* no CRC */ req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, 0); err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, NULL, 0, NULL, 1000 /* ms */); if (err) { DPRINTFN(1, "Setting CRC mode to off failed.\n"); } req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_NCM_SET_NTB_FORMAT; USETW(req.wValue, 0); /* NTB-16 */ req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, 0); err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, NULL, 0, NULL, 1000 /* ms */); if (err) { DPRINTFN(1, "Setting NTB format to 16-bit failed.\n"); } return (0); /* success */ } #endif static void cdce_test_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; struct usb_interface_descriptor *id; if (uaa->dev_state != UAA_DEV_READY) return; iface = usbd_get_iface(udev, 0); if (iface == NULL) return; id = iface->idesc; if (id == NULL || id->bInterfaceClass != UICLASS_MASS) return; if (usbd_lookup_id_by_uaa(cdce_switch_devs, sizeof(cdce_switch_devs), uaa)) return; /* no device match */ if (usb_msc_eject(udev, 0, USB_GET_DRIVER_INFO(uaa)) == 0) { /* success, mark the udev as disappearing */ uaa->dev_state = UAA_DEV_EJECTING; } } static int cdce_driver_loaded(struct module *mod, int what, void *arg) { switch (what) { case MOD_LOAD: /* register our autoinstall handler */ cdce_etag = EVENTHANDLER_REGISTER(usb_dev_configured, cdce_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); return (0); case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(usb_dev_configured, cdce_etag); return (0); default: return (EOPNOTSUPP); } } static int cdce_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; error = usbd_lookup_id_by_uaa(cdce_host_devs, sizeof(cdce_host_devs), uaa); if (error) error = usbd_lookup_id_by_uaa(cdce_dual_devs, sizeof(cdce_dual_devs), uaa); return (error); } static void cdce_attach_post(struct usb_ether *ue) { /* no-op */ return; } static int cdce_attach(device_t dev) { struct cdce_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_interface *iface; const struct usb_cdc_union_descriptor *ud; const struct usb_interface_descriptor *id; const struct usb_cdc_ethernet_descriptor *ued; const struct usb_config *pcfg; uint32_t seed; int error; uint8_t i; uint8_t data_iface_no; char eaddr_str[5 * ETHER_ADDR_LEN]; /* approx */ sc->sc_flags = USB_GET_DRIVER_INFO(uaa); sc->sc_ue.ue_udev = uaa->device; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); ud = usbd_find_descriptor (uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_UNION, 0xFF); if ((ud == NULL) || (ud->bLength < sizeof(*ud)) || (sc->sc_flags & CDCE_FLAG_NO_UNION)) { DPRINTFN(1, "No union descriptor!\n"); sc->sc_ifaces_index[0] = uaa->info.bIfaceIndex; sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; goto alloc_transfers; } data_iface_no = ud->bSlaveInterface[0]; for (i = 0;; i++) { iface = usbd_get_iface(uaa->device, i); if (iface) { id = usbd_get_interface_descriptor(iface); if (id && (id->bInterfaceNumber == data_iface_no)) { sc->sc_ifaces_index[0] = i; sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); break; } } else { device_printf(dev, "no data interface found\n"); goto detach; } } /* * * * The Data Class interface of a networking device shall have * a minimum of two interface settings. The first setting * (the default interface setting) includes no endpoints and * therefore no networking traffic is exchanged whenever the * default interface setting is selected. One or more * additional interface settings are used for normal * operation, and therefore each includes a pair of endpoints * (one IN, and one OUT) to exchange network traffic. Select * an alternate interface setting to initialize the network * aspects of the device and to enable the exchange of * network traffic. * * * * Some devices, most notably cable modems, include interface * settings that have no IN or OUT endpoint, therefore loop * through the list of all available interface settings * looking for one with both IN and OUT endpoints. */ alloc_transfers: pcfg = cdce_config; /* Default Configuration */ for (i = 0; i != 32; i++) { error = usbd_set_alt_interface_index(uaa->device, sc->sc_ifaces_index[0], i); if (error) break; #if CDCE_HAVE_NCM if ((i == 0) && (cdce_ncm_init(sc) == 0)) pcfg = cdce_ncm_config; #endif error = usbd_transfer_setup(uaa->device, sc->sc_ifaces_index, sc->sc_xfer, pcfg, CDCE_N_TRANSFER, sc, &sc->sc_mtx); if (error == 0) break; } if (error || (i == 32)) { device_printf(dev, "No valid alternate " "setting found\n"); goto detach; } ued = usbd_find_descriptor (uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_ENF, 0xFF); if ((ued == NULL) || (ued->bLength < sizeof(*ued))) { error = USB_ERR_INVAL; } else { error = usbd_req_get_string_any(uaa->device, NULL, eaddr_str, sizeof(eaddr_str), ued->iMacAddress); } if (error) { /* fake MAC address */ device_printf(dev, "faking MAC address\n"); seed = ticks; sc->sc_ue.ue_eaddr[0] = 0x2a; memcpy(&sc->sc_ue.ue_eaddr[1], &seed, sizeof(uint32_t)); sc->sc_ue.ue_eaddr[5] = device_get_unit(dev); } else { memset(sc->sc_ue.ue_eaddr, 0, sizeof(sc->sc_ue.ue_eaddr)); for (i = 0; i != (ETHER_ADDR_LEN * 2); i++) { char c = eaddr_str[i]; if ('0' <= c && c <= '9') c -= '0'; else if (c != 0) c -= 'A' - 10; else break; c &= 0xf; if ((i & 1) == 0) c <<= 4; sc->sc_ue.ue_eaddr[i / 2] |= c; } if (uaa->usb_mode == USB_MODE_DEVICE) { /* * Do not use the same MAC address like the peer ! */ sc->sc_ue.ue_eaddr[5] ^= 0xFF; } } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &cdce_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: cdce_detach(dev); return (ENXIO); /* failure */ } static int cdce_detach(device_t dev) { struct cdce_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; /* stop all USB transfers first */ usbd_transfer_unsetup(sc->sc_xfer, CDCE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void cdce_start(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); /* * Start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[CDCE_BULK_TX]); usbd_transfer_start(sc->sc_xfer[CDCE_BULK_RX]); } static void cdce_free_queue(struct mbuf **ppm, uint8_t n) { uint8_t x; for (x = 0; x != n; x++) { if (ppm[x] != NULL) { m_freem(ppm[x]); ppm[x] = NULL; } } } static void cdce_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct mbuf *m; struct mbuf *mt; uint32_t crc; uint8_t x; int actlen, aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete: %u bytes in %u frames\n", actlen, aframes); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* free all previous TX buffers */ cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: for (x = 0; x != CDCE_FRAMES_MAX; x++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; if (sc->sc_flags & CDCE_FLAG_ZAURUS) { /* * Zaurus wants a 32-bit CRC appended * to every frame */ crc = cdce_m_crc32(m, 0, m->m_pkthdr.len); crc = htole32(crc); if (!m_append(m, 4, (void *)&crc)) { m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } } if (m->m_len != m->m_pkthdr.len) { mt = m_defrag(m, M_NOWAIT); if (mt == NULL) { m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } m = mt; } if (m->m_pkthdr.len > MCLBYTES) { m->m_pkthdr.len = MCLBYTES; } sc->sc_tx_buf[x] = m; usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); /* * If there's a BPF listener, bounce a copy of * this frame to him: */ BPF_MTAP(ifp, m); } if (x != 0) { usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); /* free all previous TX buffers */ cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX); /* count output errors */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); } goto tr_setup; } break; } } static int32_t cdce_m_crc32_cb(void *arg, void *src, uint32_t count) { uint32_t *p_crc = arg; *p_crc = crc32_raw(src, count, *p_crc); return (0); } static uint32_t cdce_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len) { uint32_t crc = 0xFFFFFFFF; int error; error = m_apply(m, src_offset, src_len, cdce_m_crc32_cb, &crc); return (crc ^ 0xFFFFFFFF); } static void cdce_init(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CDCE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags |= IFF_DRV_RUNNING; /* start interrupt transfer */ usbd_transfer_start(sc->sc_xfer[CDCE_INTR_RX]); usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]); /* * Stall data write direction, which depends on USB mode. * * Some USB host stacks (e.g. Mac OS X) don't clears stall * bit as it should, so set it in our host mode only. */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) usbd_xfer_set_stall(sc->sc_xfer[CDCE_BULK_TX]); /* start data transfers */ cdce_start(ue); } static void cdce_stop(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CDCE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_RX]); usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_TX]); usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_RX]); usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_TX]); } static void cdce_setmulti(struct usb_ether *ue) { /* no-op */ return; } static void cdce_setpromisc(struct usb_ether *ue) { /* no-op */ return; } static int cdce_suspend(device_t dev) { device_printf(dev, "Suspending\n"); return (0); } static int cdce_resume(device_t dev) { device_printf(dev, "Resuming\n"); return (0); } static void cdce_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; uint8_t x; int actlen; int aframes; int len; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("received %u bytes in %u frames\n", actlen, aframes); for (x = 0; x != aframes; x++) { m = sc->sc_rx_buf[x]; sc->sc_rx_buf[x] = NULL; len = usbd_xfer_frame_len(xfer, x); /* Strip off CRC added by Zaurus, if any */ if ((sc->sc_flags & CDCE_FLAG_ZAURUS) && len >= 14) len -= 4; if (len < (int)sizeof(struct ether_header)) { m_freem(m); continue; } /* queue up mbuf */ uether_rxmbuf(&sc->sc_ue, m, len); } /* FALLTHROUGH */ case USB_ST_SETUP: /* * TODO: Implement support for multi frame transfers, * when the USB hardware supports it. */ for (x = 0; x != 1; x++) { if (sc->sc_rx_buf[x] == NULL) { m = uether_newbuf(); if (m == NULL) goto tr_stall; sc->sc_rx_buf[x] = m; } else { m = sc->sc_rx_buf[x]; } usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); } /* set number of frames and start hardware */ usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); /* flush any received frames */ uether_rxflush(&sc->sc_ue); break; default: /* Error */ DPRINTF("error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { tr_stall: if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } break; } /* need to free the RX-mbufs when we are cancelled */ cdce_free_queue(sc->sc_rx_buf, CDCE_FRAMES_MAX); break; } } static void cdce_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("Received %d bytes\n", actlen); /* TODO: decode some indications */ /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* start clear stall */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void cdce_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct usb_cdc_notification req; struct usb_page_cache *pc; uint32_t speed; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("Transferred %d bytes\n", actlen); switch (sc->sc_notify_state) { case CDCE_NOTIFY_NETWORK_CONNECTION: sc->sc_notify_state = CDCE_NOTIFY_SPEED_CHANGE; break; case CDCE_NOTIFY_SPEED_CHANGE: sc->sc_notify_state = CDCE_NOTIFY_DONE; break; default: break; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: /* * Inform host about connection. Required according to USB CDC * specification and communicating to Mac OS X USB host stack. * Some of the values seems ignored by Mac OS X though. */ if (sc->sc_notify_state == CDCE_NOTIFY_NETWORK_CONNECTION) { req.bmRequestType = UCDC_NOTIFICATION; req.bNotification = UCDC_N_NETWORK_CONNECTION; req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wValue, 1); /* Connected */ USETW(req.wLength, 0); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); } else if (sc->sc_notify_state == CDCE_NOTIFY_SPEED_CHANGE) { req.bmRequestType = UCDC_NOTIFICATION; req.bNotification = UCDC_N_CONNECTION_SPEED_CHANGE; req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wValue, 0); USETW(req.wLength, 8); /* Peak theoretical bulk trasfer rate in bits/s */ if (usbd_get_speed(sc->sc_ue.ue_udev) != USB_SPEED_FULL) speed = (13 * 512 * 8 * 1000 * 8); else speed = (19 * 64 * 1 * 1000 * 8); USETDW(req.data + 0, speed); /* Upstream bit rate */ USETDW(req.data + 4, speed); /* Downstream bit rate */ pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* start clear stall */ usbd_xfer_set_stall(xfer); } goto tr_setup; } break; } } static int cdce_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct cdce_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; /* * When Mac OS X resumes after suspending it expects * to be notified again after this request. */ if (req->bmRequestType == UT_WRITE_CLASS_INTERFACE && \ req->bRequest == UCDC_NCM_SET_ETHERNET_PACKET_FILTER) { if (is_complete == 1) { mtx_lock(&sc->sc_mtx); sc->sc_notify_state = CDCE_NOTIFY_SPEED_CHANGE; usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]); mtx_unlock(&sc->sc_mtx); } return (0); } return (ENXIO); /* use builtin handler */ } #if CDCE_HAVE_NCM static void cdce_ncm_tx_zero(struct usb_page_cache *pc, uint32_t start, uint32_t end) { if (start >= CDCE_NCM_TX_MAXLEN) return; if (end > CDCE_NCM_TX_MAXLEN) end = CDCE_NCM_TX_MAXLEN; usbd_frame_zero(pc, start, end - start); } static uint8_t cdce_ncm_fill_tx_frames(struct usb_xfer *xfer, uint8_t index) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, index); struct mbuf *m; uint32_t rem; uint32_t offset; uint32_t last_offset; uint16_t n; uint8_t retval; usbd_xfer_set_frame_offset(xfer, index * CDCE_NCM_TX_MAXLEN, index); offset = sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt) + sizeof(sc->sc_ncm.dp); /* Store last valid offset before alignment */ last_offset = offset; /* Align offset */ offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder, offset, sc->sc_ncm.tx_modulus); /* Zero pad */ cdce_ncm_tx_zero(pc, last_offset, offset); /* buffer full */ retval = 2; for (n = 0; n != sc->sc_ncm.tx_nframe; n++) { /* check if end of transmit buffer is reached */ if (offset >= sc->sc_ncm.tx_max) break; /* compute maximum buffer size */ rem = sc->sc_ncm.tx_max - offset; IFQ_DRV_DEQUEUE(&(ifp->if_snd), m); if (m == NULL) { /* buffer not full */ retval = 1; break; } if (m->m_pkthdr.len > (int)rem) { if (n == 0) { /* The frame won't fit in our buffer */ DPRINTFN(1, "Frame too big to be transmitted!\n"); m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); n--; continue; } /* Wait till next buffer becomes ready */ IFQ_DRV_PREPEND(&(ifp->if_snd), m); break; } usbd_m_copy_in(pc, offset, m, 0, m->m_pkthdr.len); USETW(sc->sc_ncm.dp[n].wFrameLength, m->m_pkthdr.len); USETW(sc->sc_ncm.dp[n].wFrameIndex, offset); /* Update offset */ offset += m->m_pkthdr.len; /* Store last valid offset before alignment */ last_offset = offset; /* Align offset */ offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder, offset, sc->sc_ncm.tx_modulus); /* Zero pad */ cdce_ncm_tx_zero(pc, last_offset, offset); /* * If there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); /* Free mbuf */ m_freem(m); /* Pre-increment interface counter */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } if (n == 0) return (0); rem = (sizeof(sc->sc_ncm.dpt) + (4 * n) + 4); USETW(sc->sc_ncm.dpt.wLength, rem); /* zero the rest of the data pointer entries */ for (; n != CDCE_NCM_SUBFRAMES_MAX; n++) { USETW(sc->sc_ncm.dp[n].wFrameLength, 0); USETW(sc->sc_ncm.dp[n].wFrameIndex, 0); } offset = last_offset; /* Align offset */ offset = CDCE_NCM_ALIGN(0, offset, CDCE_NCM_TX_MINLEN); /* Optimise, save bandwidth and force short termination */ if (offset >= sc->sc_ncm.tx_max) offset = sc->sc_ncm.tx_max; else offset ++; /* Zero pad */ cdce_ncm_tx_zero(pc, last_offset, offset); /* set frame length */ usbd_xfer_set_frame_len(xfer, index, offset); /* Fill out 16-bit header */ sc->sc_ncm.hdr.dwSignature[0] = 'N'; sc->sc_ncm.hdr.dwSignature[1] = 'C'; sc->sc_ncm.hdr.dwSignature[2] = 'M'; sc->sc_ncm.hdr.dwSignature[3] = 'H'; USETW(sc->sc_ncm.hdr.wHeaderLength, sizeof(sc->sc_ncm.hdr)); USETW(sc->sc_ncm.hdr.wBlockLength, offset); USETW(sc->sc_ncm.hdr.wSequence, sc->sc_ncm.tx_seq); USETW(sc->sc_ncm.hdr.wDptIndex, sizeof(sc->sc_ncm.hdr)); sc->sc_ncm.tx_seq++; /* Fill out 16-bit frame table header */ sc->sc_ncm.dpt.dwSignature[0] = 'N'; sc->sc_ncm.dpt.dwSignature[1] = 'C'; sc->sc_ncm.dpt.dwSignature[2] = 'M'; sc->sc_ncm.dpt.dwSignature[3] = '0'; USETW(sc->sc_ncm.dpt.wNextNdpIndex, 0); /* reserved */ usbd_copy_in(pc, 0, &(sc->sc_ncm.hdr), sizeof(sc->sc_ncm.hdr)); usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr), &(sc->sc_ncm.dpt), sizeof(sc->sc_ncm.dpt)); usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt), &(sc->sc_ncm.dp), sizeof(sc->sc_ncm.dp)); return (retval); } static void cdce_ncm_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); uint16_t x; uint8_t temp; int actlen; int aframes; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(10, "transfer complete: " "%u bytes in %u frames\n", actlen, aframes); case USB_ST_SETUP: for (x = 0; x != CDCE_NCM_TX_FRAMES_MAX; x++) { temp = cdce_ncm_fill_tx_frames(xfer, x); if (temp == 0) break; if (temp == 1) { x++; break; } } if (x != 0) { #ifdef USB_DEBUG usbd_xfer_set_interval(xfer, cdce_tx_interval); #endif usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(10, "Transfer error: %s\n", usbd_errstr(error)); /* update error counter */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } } break; } } static void cdce_ncm_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct mbuf *m; int sumdata; int sumlen; int actlen; int aframes; int temp; int nframes; int x; int offset; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, &sumlen, &aframes, NULL); DPRINTFN(1, "received %u bytes in %u frames\n", actlen, aframes); if (actlen < (int)(sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt))) { DPRINTFN(1, "frame too short\n"); goto tr_setup; } usbd_copy_out(pc, 0, &(sc->sc_ncm.hdr), sizeof(sc->sc_ncm.hdr)); if ((sc->sc_ncm.hdr.dwSignature[0] != 'N') || (sc->sc_ncm.hdr.dwSignature[1] != 'C') || (sc->sc_ncm.hdr.dwSignature[2] != 'M') || (sc->sc_ncm.hdr.dwSignature[3] != 'H')) { DPRINTFN(1, "invalid HDR signature: " "0x%02x:0x%02x:0x%02x:0x%02x\n", sc->sc_ncm.hdr.dwSignature[0], sc->sc_ncm.hdr.dwSignature[1], sc->sc_ncm.hdr.dwSignature[2], sc->sc_ncm.hdr.dwSignature[3]); goto tr_stall; } temp = UGETW(sc->sc_ncm.hdr.wBlockLength); if (temp > sumlen) { DPRINTFN(1, "unsupported block length %u/%u\n", temp, sumlen); goto tr_stall; } temp = UGETW(sc->sc_ncm.hdr.wDptIndex); if ((int)(temp + sizeof(sc->sc_ncm.dpt)) > actlen) { DPRINTFN(1, "invalid DPT index: 0x%04x\n", temp); goto tr_stall; } usbd_copy_out(pc, temp, &(sc->sc_ncm.dpt), sizeof(sc->sc_ncm.dpt)); if ((sc->sc_ncm.dpt.dwSignature[0] != 'N') || (sc->sc_ncm.dpt.dwSignature[1] != 'C') || (sc->sc_ncm.dpt.dwSignature[2] != 'M') || (sc->sc_ncm.dpt.dwSignature[3] != '0')) { DPRINTFN(1, "invalid DPT signature" "0x%02x:0x%02x:0x%02x:0x%02x\n", sc->sc_ncm.dpt.dwSignature[0], sc->sc_ncm.dpt.dwSignature[1], sc->sc_ncm.dpt.dwSignature[2], sc->sc_ncm.dpt.dwSignature[3]); goto tr_stall; } nframes = UGETW(sc->sc_ncm.dpt.wLength) / 4; /* Subtract size of header and last zero padded entry */ if (nframes >= (2 + 1)) nframes -= (2 + 1); else nframes = 0; DPRINTFN(1, "nframes = %u\n", nframes); temp += sizeof(sc->sc_ncm.dpt); if ((temp + (4 * nframes)) > actlen) goto tr_stall; if (nframes > CDCE_NCM_SUBFRAMES_MAX) { DPRINTFN(1, "Truncating number of frames from %u to %u\n", nframes, CDCE_NCM_SUBFRAMES_MAX); nframes = CDCE_NCM_SUBFRAMES_MAX; } usbd_copy_out(pc, temp, &(sc->sc_ncm.dp), (4 * nframes)); sumdata = 0; for (x = 0; x != nframes; x++) { offset = UGETW(sc->sc_ncm.dp[x].wFrameIndex); temp = UGETW(sc->sc_ncm.dp[x].wFrameLength); if ((offset == 0) || (temp < (int)sizeof(struct ether_header)) || (temp > (MCLBYTES - ETHER_ALIGN))) { DPRINTFN(1, "NULL frame detected at %d\n", x); m = NULL; /* silently ignore this frame */ continue; } else if ((offset + temp) > actlen) { DPRINTFN(1, "invalid frame " "detected at %d\n", x); m = NULL; /* silently ignore this frame */ continue; } else if (temp > (int)(MHLEN - ETHER_ALIGN)) { m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); } else { m = m_gethdr(M_NOWAIT, MT_DATA); } DPRINTFN(16, "frame %u, offset = %u, length = %u \n", x, offset, temp); /* check if we have a buffer */ if (m) { m->m_len = m->m_pkthdr.len = temp + ETHER_ALIGN; m_adj(m, ETHER_ALIGN); usbd_copy_out(pc, offset, m->m_data, temp); /* enqueue */ uether_rxmbuf(&sc->sc_ue, m, temp); sumdata += temp; } else { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); } } DPRINTFN(1, "Efficiency: %u/%u bytes\n", sumdata, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, sc->sc_ncm.rx_max); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); uether_rxflush(&sc->sc_ue); /* must be last */ break; default: /* Error */ DPRINTFN(1, "error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { tr_stall: if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } } break; } } #endif Index: head/sys/dev/usb/net/if_cdceem.c =================================================================== --- head/sys/dev/usb/net/if_cdceem.c (revision 357971) +++ head/sys/dev/usb/net/if_cdceem.c (revision 357972) @@ -1,870 +1,871 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012 Ben Gray . * Copyright (C) 2018 The FreeBSD Foundation. * Copyright (c) 2019 Edward Tomasz Napierala * * This software was developed by Arshan Khanifar * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Universal Serial Bus Communications Class Subclass Specification * for Ethernet Emulation Model Devices: * * https://usb.org/sites/default/files/CDC_EEM10.pdf */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR cdceem_debug #include #include #include #include "usb_if.h" #include #define CDCEEM_FRAMES_MAX 1 #define CDCEEM_ECHO_MAX 1024 #define CDCEEM_ECHO_PAYLOAD \ "ICH DALEKOPIS FALSZUJE GDY PROBY XQV NIE WYTRZYMUJE 1234567890" enum { CDCEEM_BULK_RX, CDCEEM_BULK_TX, CDCEEM_N_TRANSFER, }; struct cdceem_softc { struct usb_ether sc_ue; struct mtx sc_mtx; int sc_flags; struct usb_xfer *sc_xfer[CDCEEM_N_TRANSFER]; size_t sc_echo_len; char sc_echo_buffer[CDCEEM_ECHO_MAX]; }; #define CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING 0x1 #define CDCEEM_SC_FLAGS_ECHO_PENDING 0x2 -static SYSCTL_NODE(_hw_usb, OID_AUTO, cdceem, CTLFLAG_RW, 0, "USB CDC EEM"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, cdceem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB CDC EEM"); static int cdceem_debug = 1; SYSCTL_INT(_hw_usb_cdceem, OID_AUTO, debug, CTLFLAG_RWTUN, &cdceem_debug, 0, "Debug level"); static int cdceem_send_echoes = 0; SYSCTL_INT(_hw_usb_cdceem, OID_AUTO, send_echoes, CTLFLAG_RWTUN, &cdceem_send_echoes, 0, "Send an Echo command"); static int cdceem_send_fake_crc = 0; SYSCTL_INT(_hw_usb_cdceem, OID_AUTO, send_fake_crc, CTLFLAG_RWTUN, &cdceem_send_fake_crc, 0, "Use 0xdeadbeef instead of CRC"); #define CDCEEM_DEBUG(S, X, ...) \ do { \ if (cdceem_debug > 1) { \ device_printf(S->sc_ue.ue_dev, "%s: " X "\n", \ __func__, ## __VA_ARGS__); \ } \ } while (0) #define CDCEEM_WARN(S, X, ...) \ do { \ if (cdceem_debug > 0) { \ device_printf(S->sc_ue.ue_dev, \ "WARNING: %s: " X "\n", \ __func__, ## __VA_ARGS__); \ } \ } while (0) #define CDCEEM_LOCK(X) mtx_lock(&(X)->sc_mtx) #define CDCEEM_UNLOCK(X) mtx_unlock(&(X)->sc_mtx) #define CDCEEM_TYPE_CMD (0x1 << 15) #define CDCEEM_CMD_MASK (0x7 << 11) #define CDCEEM_CMD_ECHO (0x0 << 11) #define CDCEEM_CMD_ECHO_RESPONSE (0x1 << 11) #define CDCEEM_CMD_SUSPEND_HINT (0x2 << 11) #define CDCEEM_CMD_RESPONSE_HINT (0x3 << 11) #define CDCEEM_CMD_RESPONSE_COMPLETE_HINT (0x4 << 11) #define CDCEEM_CMD_TICKLE (0x5 << 11) #define CDCEEM_CMD_RESERVED (0x1 << 14) #define CDCEEM_ECHO_LEN_MASK 0x3ff #define CDCEEM_DATA_CRC (0x1 << 14) #define CDCEEM_DATA_LEN_MASK 0x3fff static device_probe_t cdceem_probe; static device_attach_t cdceem_attach; static device_detach_t cdceem_detach; static device_suspend_t cdceem_suspend; static device_resume_t cdceem_resume; static usb_callback_t cdceem_bulk_write_callback; static usb_callback_t cdceem_bulk_read_callback; static uether_fn_t cdceem_attach_post; static uether_fn_t cdceem_init; static uether_fn_t cdceem_stop; static uether_fn_t cdceem_start; static uether_fn_t cdceem_setmulti; static uether_fn_t cdceem_setpromisc; static uint32_t cdceem_m_crc32(struct mbuf *, uint32_t, uint32_t); static const struct usb_config cdceem_config[CDCEEM_N_TRANSFER] = { [CDCEEM_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .bufsize = 16 * (MCLBYTES + 16), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = cdceem_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DUAL, }, [CDCEEM_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .bufsize = 20480, /* bytes */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = cdceem_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_DUAL, }, }; static device_method_t cdceem_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cdceem_probe), DEVMETHOD(device_attach, cdceem_attach), DEVMETHOD(device_detach, cdceem_detach), DEVMETHOD(device_suspend, cdceem_suspend), DEVMETHOD(device_resume, cdceem_resume), DEVMETHOD_END }; static driver_t cdceem_driver = { .name = "cdceem", .methods = cdceem_methods, .size = sizeof(struct cdceem_softc), }; static devclass_t cdceem_devclass; static const STRUCT_USB_DUAL_ID cdceem_dual_devs[] = { {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ETHERNET_EMULATION_MODEL), 0}, }; DRIVER_MODULE(cdceem, uhub, cdceem_driver, cdceem_devclass, NULL, NULL); MODULE_VERSION(cdceem, 1); MODULE_DEPEND(cdceem, uether, 1, 1, 1); MODULE_DEPEND(cdceem, usb, 1, 1, 1); MODULE_DEPEND(cdceem, ether, 1, 1, 1); USB_PNP_DUAL_INFO(cdceem_dual_devs); static const struct usb_ether_methods cdceem_ue_methods = { .ue_attach_post = cdceem_attach_post, .ue_start = cdceem_start, .ue_init = cdceem_init, .ue_stop = cdceem_stop, .ue_setmulti = cdceem_setmulti, .ue_setpromisc = cdceem_setpromisc, }; static int cdceem_probe(device_t dev) { struct usb_attach_arg *uaa; int error; uaa = device_get_ivars(dev); error = usbd_lookup_id_by_uaa(cdceem_dual_devs, sizeof(cdceem_dual_devs), uaa); return (error); } static void cdceem_attach_post(struct usb_ether *ue) { return; } static int cdceem_attach(device_t dev) { struct cdceem_softc *sc; struct usb_ether *ue; struct usb_attach_arg *uaa; int error; uint8_t iface_index; sc = device_get_softc(dev); ue = &sc->sc_ue; uaa = device_get_ivars(dev); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Setup the endpoints. */ iface_index = 0; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, cdceem_config, CDCEEM_N_TRANSFER, sc, &sc->sc_mtx); if (error != 0) { CDCEEM_WARN(sc, "allocating USB transfers failed, error %d", error); mtx_destroy(&sc->sc_mtx); return (error); } /* Random MAC address. */ arc4rand(ue->ue_eaddr, ETHER_ADDR_LEN, 0); ue->ue_eaddr[0] &= ~0x01; /* unicast */ ue->ue_eaddr[0] |= 0x02; /* locally administered */ ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &cdceem_ue_methods; error = uether_ifattach(ue); if (error != 0) { CDCEEM_WARN(sc, "could not attach interface, error %d", error); usbd_transfer_unsetup(sc->sc_xfer, CDCEEM_N_TRANSFER); mtx_destroy(&sc->sc_mtx); return (error); } return (0); } static int cdceem_detach(device_t dev) { struct cdceem_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; /* Stop all USB transfers first. */ usbd_transfer_unsetup(sc->sc_xfer, CDCEEM_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void cdceem_handle_cmd(struct usb_xfer *xfer, uint16_t hdr, int *offp) { struct cdceem_softc *sc; struct usb_page_cache *pc; int actlen, off; uint16_t pktlen; off = *offp; sc = usbd_xfer_softc(xfer); pc = usbd_xfer_get_frame(xfer, 0); usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); if (hdr & CDCEEM_CMD_RESERVED) { CDCEEM_WARN(sc, "received command header %#x " "with Reserved bit set; ignoring", hdr); return; } switch (hdr & CDCEEM_CMD_MASK) { case CDCEEM_CMD_ECHO: pktlen = hdr & CDCEEM_ECHO_LEN_MASK; CDCEEM_DEBUG(sc, "received Echo, length %d", pktlen); if (pktlen > (actlen - off)) { CDCEEM_WARN(sc, "bad Echo length %d, should be at most %d", pktlen, actlen - off); break; } if (pktlen > sizeof(sc->sc_echo_buffer)) { CDCEEM_WARN(sc, "Echo length %u too big, must be less than %zd", pktlen, sizeof(sc->sc_echo_buffer)); break; } sc->sc_flags |= CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING; sc->sc_echo_len = pktlen; usbd_copy_out(pc, off, sc->sc_echo_buffer, pktlen); off += pktlen; break; case CDCEEM_CMD_ECHO_RESPONSE: pktlen = hdr & CDCEEM_ECHO_LEN_MASK; CDCEEM_DEBUG(sc, "received Echo Response, length %d", pktlen); if (pktlen > (actlen - off)) { CDCEEM_WARN(sc, "bad Echo Response length %d, " "should be at most %d", pktlen, actlen - off); break; } if (pktlen != sizeof(CDCEEM_ECHO_PAYLOAD)) { CDCEEM_WARN(sc, "received Echo Response with bad " "length %hu, should be %zd", pktlen, sizeof(CDCEEM_ECHO_PAYLOAD)); break; } usbd_copy_out(pc, off, sc->sc_echo_buffer, pktlen); off += pktlen; if (memcmp(sc->sc_echo_buffer, CDCEEM_ECHO_PAYLOAD, sizeof(CDCEEM_ECHO_PAYLOAD)) != 0) { CDCEEM_WARN(sc, "received Echo Response payload does not match"); } else { CDCEEM_DEBUG(sc, "received Echo Response is valid"); } break; case CDCEEM_CMD_SUSPEND_HINT: CDCEEM_DEBUG(sc, "received SuspendHint; ignoring"); break; case CDCEEM_CMD_RESPONSE_HINT: CDCEEM_DEBUG(sc, "received ResponseHint; ignoring"); break; case CDCEEM_CMD_RESPONSE_COMPLETE_HINT: CDCEEM_DEBUG(sc, "received ResponseCompleteHint; ignoring"); break; case CDCEEM_CMD_TICKLE: CDCEEM_DEBUG(sc, "received Tickle; ignoring"); break; default: CDCEEM_WARN(sc, "received unknown command %u, header %#x; ignoring", (hdr & CDCEEM_CMD_MASK >> 11), hdr); break; } *offp = off; } static void cdceem_handle_data(struct usb_xfer *xfer, uint16_t hdr, int *offp) { struct cdceem_softc *sc; struct usb_page_cache *pc; struct usb_ether *ue; struct ifnet *ifp; struct mbuf *m; int actlen, off; uint32_t computed_crc, received_crc; uint16_t pktlen; off = *offp; sc = usbd_xfer_softc(xfer); pc = usbd_xfer_get_frame(xfer, 0); ue = &sc->sc_ue; ifp = uether_getifp(ue); usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); pktlen = hdr & CDCEEM_DATA_LEN_MASK; CDCEEM_DEBUG(sc, "received Data, CRC %s, length %d", (hdr & CDCEEM_DATA_CRC) ? "valid" : "absent", pktlen); if (pktlen < ETHER_HDR_LEN) { CDCEEM_WARN(sc, "bad ethernet frame length %d, should be at least %d", pktlen, ETHER_HDR_LEN); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return; } if (pktlen > (actlen - off)) { CDCEEM_WARN(sc, "bad ethernet frame length %d, should be at most %d", pktlen, actlen - off); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return; } m = uether_newbuf(); if (m == NULL) { CDCEEM_WARN(sc, "uether_newbuf() failed"); if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); return; } pktlen -= 4; /* Subtract the CRC. */ usbd_copy_out(pc, off, mtod(m, uint8_t *), pktlen); off += pktlen; usbd_copy_out(pc, off, &received_crc, sizeof(received_crc)); off += sizeof(received_crc); if (hdr & CDCEEM_DATA_CRC) { computed_crc = cdceem_m_crc32(m, 0, pktlen); } else { computed_crc = be32toh(0xdeadbeef); } if (received_crc != computed_crc) { CDCEEM_WARN(sc, "received Data packet with wrong CRC %#x, expected %#x", received_crc, computed_crc); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); return; } else { CDCEEM_DEBUG(sc, "received correct CRC %#x", received_crc); } uether_rxmbuf(ue, m, pktlen); *offp = off; } static void cdceem_bulk_read_callback(struct usb_xfer *xfer, usb_error_t usb_error) { struct cdceem_softc *sc; struct usb_page_cache *pc; int actlen, aframes, off; uint16_t hdr; sc = usbd_xfer_softc(xfer); usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: CDCEEM_DEBUG(sc, "received %u bytes in %u frames", actlen, aframes); pc = usbd_xfer_get_frame(xfer, 0); off = 0; while (off < actlen) { usbd_copy_out(pc, off, &hdr, sizeof(hdr)); CDCEEM_DEBUG(sc, "hdr = %#x", hdr); off += sizeof(hdr); if (hdr == 0) { CDCEEM_DEBUG(sc, "received Zero Length EEM"); continue; } hdr = le16toh(hdr); if ((hdr & CDCEEM_TYPE_CMD) != 0) { cdceem_handle_cmd(xfer, hdr, &off); } else { cdceem_handle_data(xfer, hdr, &off); } KASSERT(off <= actlen, ("%s: went past the buffer, off %d, actlen %d", __func__, off, actlen)); } /* FALLTHROUGH */ case USB_ST_SETUP: CDCEEM_DEBUG(sc, "setup"); tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(&sc->sc_ue); break; default: CDCEEM_WARN(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); if (usb_error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void cdceem_send_echo(struct usb_xfer *xfer, int *offp) { struct cdceem_softc *sc; struct usb_page_cache *pc; int maxlen, off; uint16_t hdr; off = *offp; sc = usbd_xfer_softc(xfer); pc = usbd_xfer_get_frame(xfer, 0); maxlen = usbd_xfer_max_len(xfer); CDCEEM_DEBUG(sc, "sending Echo, length %zd", sizeof(CDCEEM_ECHO_PAYLOAD)); KASSERT(off + sizeof(hdr) + sizeof(CDCEEM_ECHO_PAYLOAD) < maxlen, ("%s: out of space; have %d, need %zd", __func__, maxlen, off + sizeof(hdr) + sizeof(CDCEEM_ECHO_PAYLOAD))); hdr = 0; hdr |= CDCEEM_TYPE_CMD; hdr |= CDCEEM_CMD_ECHO; hdr |= sizeof(CDCEEM_ECHO_PAYLOAD); CDCEEM_DEBUG(sc, "hdr = %#x", hdr); hdr = htole16(hdr); usbd_copy_in(pc, off, &hdr, sizeof(hdr)); off += sizeof(hdr); usbd_copy_in(pc, off, CDCEEM_ECHO_PAYLOAD, sizeof(CDCEEM_ECHO_PAYLOAD)); off += sizeof(CDCEEM_ECHO_PAYLOAD); sc->sc_flags &= ~CDCEEM_SC_FLAGS_ECHO_PENDING; *offp = off; } static void cdceem_send_echo_response(struct usb_xfer *xfer, int *offp) { struct cdceem_softc *sc; struct usb_page_cache *pc; int maxlen, off; uint16_t hdr; off = *offp; sc = usbd_xfer_softc(xfer); pc = usbd_xfer_get_frame(xfer, 0); maxlen = usbd_xfer_max_len(xfer); KASSERT(off + sizeof(hdr) + sc->sc_echo_len < maxlen, ("%s: out of space; have %d, need %zd", __func__, maxlen, off + sizeof(hdr) + sc->sc_echo_len)); CDCEEM_DEBUG(sc, "sending Echo Response, length %zd", sc->sc_echo_len); hdr = 0; hdr |= CDCEEM_TYPE_CMD; hdr |= CDCEEM_CMD_ECHO_RESPONSE; hdr |= sc->sc_echo_len; CDCEEM_DEBUG(sc, "hdr = %#x", hdr); hdr = htole16(hdr); usbd_copy_in(pc, off, &hdr, sizeof(hdr)); off += sizeof(hdr); usbd_copy_in(pc, off, sc->sc_echo_buffer, sc->sc_echo_len); off += sc->sc_echo_len; sc->sc_flags &= ~CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING; sc->sc_echo_len = 0; *offp = off; } static void cdceem_send_data(struct usb_xfer *xfer, int *offp) { struct cdceem_softc *sc; struct usb_page_cache *pc; struct ifnet *ifp; struct mbuf *m; int maxlen, off; uint32_t crc; uint16_t hdr; off = *offp; sc = usbd_xfer_softc(xfer); pc = usbd_xfer_get_frame(xfer, 0); ifp = uether_getifp(&sc->sc_ue); maxlen = usbd_xfer_max_len(xfer); IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) { CDCEEM_DEBUG(sc, "no Data packets to send"); return; } KASSERT((m->m_pkthdr.len & CDCEEM_DATA_LEN_MASK) == m->m_pkthdr.len, ("%s: packet too long: %d, should be %d\n", __func__, m->m_pkthdr.len, m->m_pkthdr.len & CDCEEM_DATA_LEN_MASK)); KASSERT(off + sizeof(hdr) + m->m_pkthdr.len + 4 < maxlen, ("%s: out of space; have %d, need %zd", __func__, maxlen, off + sizeof(hdr) + m->m_pkthdr.len + 4)); CDCEEM_DEBUG(sc, "sending Data, length %d + 4", m->m_pkthdr.len); hdr = 0; if (!cdceem_send_fake_crc) hdr |= CDCEEM_DATA_CRC; hdr |= (m->m_pkthdr.len + 4); /* +4 for CRC */ CDCEEM_DEBUG(sc, "hdr = %#x", hdr); hdr = htole16(hdr); usbd_copy_in(pc, off, &hdr, sizeof(hdr)); off += sizeof(hdr); usbd_m_copy_in(pc, off, m, 0, m->m_pkthdr.len); off += m->m_pkthdr.len; if (cdceem_send_fake_crc) { crc = htobe32(0xdeadbeef); } else { crc = cdceem_m_crc32(m, 0, m->m_pkthdr.len); } CDCEEM_DEBUG(sc, "CRC = %#x", crc); usbd_copy_in(pc, off, &crc, sizeof(crc)); off += sizeof(crc); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* * If there's a BPF listener, bounce a copy of this frame to it. */ BPF_MTAP(ifp, m); m_freem(m); *offp = off; } static void cdceem_bulk_write_callback(struct usb_xfer *xfer, usb_error_t usb_error) { struct cdceem_softc *sc; struct ifnet *ifp; int actlen, aframes, maxlen, off; sc = usbd_xfer_softc(xfer); maxlen = usbd_xfer_max_len(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); CDCEEM_DEBUG(sc, "transferred %u bytes in %u frames", actlen, aframes); /* FALLTHROUGH */ case USB_ST_SETUP: CDCEEM_DEBUG(sc, "setup"); tr_setup: off = 0; usbd_xfer_set_frame_offset(xfer, 0, 0); if (sc->sc_flags & CDCEEM_SC_FLAGS_ECHO_PENDING) { cdceem_send_echo(xfer, &off); } else if (sc->sc_flags & CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING) { cdceem_send_echo_response(xfer, &off); } else { cdceem_send_data(xfer, &off); } KASSERT(off <= maxlen, ("%s: went past the buffer, off %d, maxlen %d", __func__, off, maxlen)); if (off > 0) { CDCEEM_DEBUG(sc, "starting transfer, length %d", off); usbd_xfer_set_frame_len(xfer, 0, off); usbd_transfer_submit(xfer); } else { CDCEEM_DEBUG(sc, "nothing to transfer"); } break; default: CDCEEM_WARN(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); ifp = uether_getifp(&sc->sc_ue); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (usb_error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int32_t cdceem_m_crc32_cb(void *arg, void *src, uint32_t count) { uint32_t *p_crc = arg; *p_crc = crc32_raw(src, count, *p_crc); return (0); } static uint32_t cdceem_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len) { uint32_t crc = 0xFFFFFFFF; int error; error = m_apply(m, src_offset, src_len, cdceem_m_crc32_cb, &crc); return (crc ^ 0xFFFFFFFF); } static void cdceem_start(struct usb_ether *ue) { struct cdceem_softc *sc; sc = uether_getsc(ue); /* * Start the USB transfers, if not already started. */ usbd_transfer_start(sc->sc_xfer[CDCEEM_BULK_RX]); usbd_transfer_start(sc->sc_xfer[CDCEEM_BULK_TX]); } static void cdceem_init(struct usb_ether *ue) { struct cdceem_softc *sc; struct ifnet *ifp; sc = uether_getsc(ue); ifp = uether_getifp(ue); ifp->if_drv_flags |= IFF_DRV_RUNNING; if (cdceem_send_echoes) sc->sc_flags = CDCEEM_SC_FLAGS_ECHO_PENDING; else sc->sc_flags = 0; /* * Stall data write direction, which depends on USB mode. * * Some USB host stacks (e.g. Mac OS X) don't clears stall * bit as it should, so set it in our host mode only. */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) usbd_xfer_set_stall(sc->sc_xfer[CDCEEM_BULK_TX]); cdceem_start(ue); } static void cdceem_stop(struct usb_ether *ue) { struct cdceem_softc *sc; struct ifnet *ifp; sc = uether_getsc(ue); ifp = uether_getifp(ue); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; usbd_transfer_stop(sc->sc_xfer[CDCEEM_BULK_RX]); usbd_transfer_stop(sc->sc_xfer[CDCEEM_BULK_TX]); } static void cdceem_setmulti(struct usb_ether *ue) { /* no-op */ return; } static void cdceem_setpromisc(struct usb_ether *ue) { /* no-op */ return; } static int cdceem_suspend(device_t dev) { struct cdceem_softc *sc = device_get_softc(dev); CDCEEM_DEBUG(sc, "go"); return (0); } static int cdceem_resume(device_t dev) { struct cdceem_softc *sc = device_get_softc(dev); CDCEEM_DEBUG(sc, "go"); return (0); } Index: head/sys/dev/usb/net/if_cue.c =================================================================== --- head/sys/dev/usb/net/if_cue.c (revision 357971) +++ head/sys/dev/usb/net/if_cue.c (revision 357972) @@ -1,660 +1,661 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1997, 1998, 1999, 2000 * Bill Paul . 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * CATC USB-EL1210A USB to ethernet driver. Used in the CATC Netmate * adapters and others. * * Written by Bill Paul * Electrical Engineering Department * Columbia University, New York City */ /* * The CATC USB-EL1210A provides USB ethernet support at 10Mbps. The * RX filter uses a 512-bit multicast hash table, single perfect entry * for the station address, and promiscuous mode. Unlike the ADMtek * and KLSI chips, the CATC ASIC supports read and write combining * mode where multiple packets can be transferred using a single bulk * transaction, which helps performance a great deal. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR cue_debug #include #include #include #include /* * Various supported device vendors/products. */ /* Belkin F5U111 adapter covered by NETMATE entry */ static const STRUCT_USB_HOST_ID cue_devs[] = { #define CUE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } CUE_DEV(CATC, NETMATE), CUE_DEV(CATC, NETMATE2), CUE_DEV(SMARTBRIDGES, SMARTLINK), #undef CUE_DEV }; /* prototypes */ static device_probe_t cue_probe; static device_attach_t cue_attach; static device_detach_t cue_detach; static usb_callback_t cue_bulk_read_callback; static usb_callback_t cue_bulk_write_callback; static uether_fn_t cue_attach_post; static uether_fn_t cue_init; static uether_fn_t cue_stop; static uether_fn_t cue_start; static uether_fn_t cue_tick; static uether_fn_t cue_setmulti; static uether_fn_t cue_setpromisc; static uint8_t cue_csr_read_1(struct cue_softc *, uint16_t); static uint16_t cue_csr_read_2(struct cue_softc *, uint8_t); static int cue_csr_write_1(struct cue_softc *, uint16_t, uint16_t); static int cue_mem(struct cue_softc *, uint8_t, uint16_t, void *, int); static int cue_getmac(struct cue_softc *, void *); static uint32_t cue_mchash(const uint8_t *); static void cue_reset(struct cue_softc *); #ifdef USB_DEBUG static int cue_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, cue, CTLFLAG_RW, 0, "USB cue"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, cue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB cue"); SYSCTL_INT(_hw_usb_cue, OID_AUTO, debug, CTLFLAG_RWTUN, &cue_debug, 0, "Debug level"); #endif static const struct usb_config cue_config[CUE_N_TRANSFER] = { [CUE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (MCLBYTES + 2), .flags = {.pipe_bof = 1,}, .callback = cue_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [CUE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + 2), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = cue_bulk_read_callback, }, }; static device_method_t cue_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cue_probe), DEVMETHOD(device_attach, cue_attach), DEVMETHOD(device_detach, cue_detach), DEVMETHOD_END }; static driver_t cue_driver = { .name = "cue", .methods = cue_methods, .size = sizeof(struct cue_softc), }; static devclass_t cue_devclass; DRIVER_MODULE(cue, uhub, cue_driver, cue_devclass, NULL, 0); MODULE_DEPEND(cue, uether, 1, 1, 1); MODULE_DEPEND(cue, usb, 1, 1, 1); MODULE_DEPEND(cue, ether, 1, 1, 1); MODULE_VERSION(cue, 1); USB_PNP_HOST_INFO(cue_devs); static const struct usb_ether_methods cue_ue_methods = { .ue_attach_post = cue_attach_post, .ue_start = cue_start, .ue_init = cue_init, .ue_stop = cue_stop, .ue_tick = cue_tick, .ue_setmulti = cue_setmulti, .ue_setpromisc = cue_setpromisc, }; #define CUE_SETBIT(sc, reg, x) \ cue_csr_write_1(sc, reg, cue_csr_read_1(sc, reg) | (x)) #define CUE_CLRBIT(sc, reg, x) \ cue_csr_write_1(sc, reg, cue_csr_read_1(sc, reg) & ~(x)) static uint8_t cue_csr_read_1(struct cue_softc *sc, uint16_t reg) { struct usb_device_request req; uint8_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = CUE_CMD_READREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 1); if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) { /* ignore any errors */ } return (val); } static uint16_t cue_csr_read_2(struct cue_softc *sc, uint8_t reg) { struct usb_device_request req; uint16_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = CUE_CMD_READREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 2); (void)uether_do_request(&sc->sc_ue, &req, &val, 1000); return (le16toh(val)); } static int cue_csr_write_1(struct cue_softc *sc, uint16_t reg, uint16_t val) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = CUE_CMD_WRITEREG; USETW(req.wValue, val); USETW(req.wIndex, reg); USETW(req.wLength, 0); return (uether_do_request(&sc->sc_ue, &req, NULL, 1000)); } static int cue_mem(struct cue_softc *sc, uint8_t cmd, uint16_t addr, void *buf, int len) { struct usb_device_request req; if (cmd == CUE_CMD_READSRAM) req.bmRequestType = UT_READ_VENDOR_DEVICE; else req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = cmd; USETW(req.wValue, 0); USETW(req.wIndex, addr); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static int cue_getmac(struct cue_softc *sc, void *buf) { struct usb_device_request req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = CUE_CMD_GET_MACADDR; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, ETHER_ADDR_LEN); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } #define CUE_BITS 9 static uint32_t cue_mchash(const uint8_t *addr) { uint32_t crc; /* Compute CRC for the address value. */ crc = ether_crc32_le(addr, ETHER_ADDR_LEN); return (crc & ((1 << CUE_BITS) - 1)); } static void cue_setpromisc(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CUE_LOCK_ASSERT(sc, MA_OWNED); /* if we want promiscuous mode, set the allframes bit */ if (ifp->if_flags & IFF_PROMISC) CUE_SETBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); else CUE_CLRBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); /* write multicast hash-bits */ cue_setmulti(ue); } static u_int cue_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint8_t *hashtbl = arg; uint32_t h; h = cue_mchash(LLADDR(sdl)); hashtbl[h >> 3] |= 1 << (h & 0x7); return (1); } static void cue_setmulti(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint32_t h, i; uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; CUE_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { for (i = 0; i < 8; i++) hashtbl[i] = 0xff; cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, &hashtbl, 8); return; } /* now program new ones */ if_foreach_llmaddr(ifp, cue_hash_maddr, hashtbl); /* * Also include the broadcast address in the filter * so we can receive broadcast frames. */ if (ifp->if_flags & IFF_BROADCAST) { h = cue_mchash(ifp->if_broadcastaddr); hashtbl[h >> 3] |= 1 << (h & 0x7); } cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, &hashtbl, 8); } static void cue_reset(struct cue_softc *sc) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = CUE_CMD_RESET; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 0); if (uether_do_request(&sc->sc_ue, &req, NULL, 1000)) { /* ignore any errors */ } /* * wait a little while for the chip to get its brains in order: */ uether_pause(&sc->sc_ue, hz / 100); } static void cue_attach_post(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); cue_getmac(sc, ue->ue_eaddr); } static int cue_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != CUE_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != CUE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(cue_devs, sizeof(cue_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int cue_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct cue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = CUE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, cue_config, CUE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &cue_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: cue_detach(dev); return (ENXIO); /* failure */ } static int cue_detach(device_t dev) { struct cue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, CUE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void cue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cue_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct usb_page_cache *pc; uint8_t buf[2]; int len; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen <= (int)(2 + sizeof(struct ether_header))) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, 2); actlen -= 2; len = buf[0] | (buf[1] << 8); len = min(actlen, len); uether_rxbuf(ue, pc, 2, len); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void cue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cue_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; uint8_t buf[2]; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; if (m->m_pkthdr.len > MCLBYTES) m->m_pkthdr.len = MCLBYTES; usbd_xfer_set_frame_len(xfer, 0, (m->m_pkthdr.len + 2)); /* the first two bytes are the frame length */ buf[0] = (uint8_t)(m->m_pkthdr.len); buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, buf, 2); usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m); m_freem(m); usbd_transfer_submit(xfer); return; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void cue_tick(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CUE_LOCK_ASSERT(sc, MA_OWNED); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, cue_csr_read_2(sc, CUE_TX_SINGLECOLL)); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, cue_csr_read_2(sc, CUE_TX_MULTICOLL)); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, cue_csr_read_2(sc, CUE_TX_EXCESSCOLL)); if (cue_csr_read_2(sc, CUE_RX_FRAMEERR)) if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); } static void cue_start(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[CUE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[CUE_BULK_DT_WR]); } static void cue_init(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); int i; CUE_LOCK_ASSERT(sc, MA_OWNED); /* * Cancel pending I/O and free all RX/TX buffers. */ cue_stop(ue); #if 0 cue_reset(sc); #endif /* Set MAC address */ for (i = 0; i < ETHER_ADDR_LEN; i++) cue_csr_write_1(sc, CUE_PAR0 - i, IF_LLADDR(ifp)[i]); /* Enable RX logic. */ cue_csr_write_1(sc, CUE_ETHCTL, CUE_ETHCTL_RX_ON | CUE_ETHCTL_MCAST_ON); /* Load the multicast filter */ cue_setpromisc(ue); /* * Set the number of RX and TX buffers that we want * to reserve inside the ASIC. */ cue_csr_write_1(sc, CUE_RX_BUFPKTS, CUE_RX_FRAMES); cue_csr_write_1(sc, CUE_TX_BUFPKTS, CUE_TX_FRAMES); /* Set advanced operation modes. */ cue_csr_write_1(sc, CUE_ADVANCED_OPMODES, CUE_AOP_EMBED_RXLEN | 0x01);/* 1 wait state */ /* Program the LED operation. */ cue_csr_write_1(sc, CUE_LEDCTL, CUE_LEDCTL_FOLLOW_LINK); usbd_xfer_set_stall(sc->sc_xfer[CUE_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; cue_start(ue); } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void cue_stop(struct usb_ether *ue) { struct cue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CUE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[CUE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[CUE_BULK_DT_RD]); cue_csr_write_1(sc, CUE_ETHCTL, 0); cue_reset(sc); } Index: head/sys/dev/usb/net/if_ipheth.c =================================================================== --- head/sys/dev/usb/net/if_ipheth.c (revision 357971) +++ head/sys/dev/usb/net/if_ipheth.c (revision 357972) @@ -1,548 +1,549 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. * Copyright (c) 2009 Diego Giagio. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Thanks to Diego Giagio for figuring out the programming details for * the Apple iPhone Ethernet driver. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ipheth_debug #include #include #include #include static device_probe_t ipheth_probe; static device_attach_t ipheth_attach; static device_detach_t ipheth_detach; static usb_callback_t ipheth_bulk_write_callback; static usb_callback_t ipheth_bulk_read_callback; static uether_fn_t ipheth_attach_post; static uether_fn_t ipheth_tick; static uether_fn_t ipheth_init; static uether_fn_t ipheth_stop; static uether_fn_t ipheth_start; static uether_fn_t ipheth_setmulti; static uether_fn_t ipheth_setpromisc; #ifdef USB_DEBUG static int ipheth_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ipheth, CTLFLAG_RW, 0, "USB iPhone ethernet"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ipheth, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB iPhone ethernet"); SYSCTL_INT(_hw_usb_ipheth, OID_AUTO, debug, CTLFLAG_RWTUN, &ipheth_debug, 0, "Debug level"); #endif static const struct usb_config ipheth_config[IPHETH_N_TRANSFER] = { [IPHETH_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .frames = IPHETH_RX_FRAMES_MAX, .bufsize = (IPHETH_RX_FRAMES_MAX * MCLBYTES), .flags = {.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, .callback = ipheth_bulk_read_callback, .timeout = 0, /* no timeout */ }, [IPHETH_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .frames = IPHETH_TX_FRAMES_MAX, .bufsize = (IPHETH_TX_FRAMES_MAX * IPHETH_BUF_SIZE), .flags = {.force_short_xfer = 1,}, .callback = ipheth_bulk_write_callback, .timeout = IPHETH_TX_TIMEOUT, }, }; static device_method_t ipheth_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ipheth_probe), DEVMETHOD(device_attach, ipheth_attach), DEVMETHOD(device_detach, ipheth_detach), DEVMETHOD_END }; static driver_t ipheth_driver = { .name = "ipheth", .methods = ipheth_methods, .size = sizeof(struct ipheth_softc), }; static devclass_t ipheth_devclass; static const STRUCT_USB_HOST_ID ipheth_devs[] = { #if 0 {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE, IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, IPHETH_USBINTF_PROTO)}, {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G, IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, IPHETH_USBINTF_PROTO)}, {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3GS, IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, IPHETH_USBINTF_PROTO)}, {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4, IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, IPHETH_USBINTF_PROTO)}, {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4S, IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, IPHETH_USBINTF_PROTO)}, {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_5, IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, IPHETH_USBINTF_PROTO)}, #else /* product agnostic interface match */ {USB_VENDOR(USB_VENDOR_APPLE), USB_IFACE_CLASS(IPHETH_USBINTF_CLASS), USB_IFACE_SUBCLASS(IPHETH_USBINTF_SUBCLASS), USB_IFACE_PROTOCOL(IPHETH_USBINTF_PROTO)}, #endif }; DRIVER_MODULE(ipheth, uhub, ipheth_driver, ipheth_devclass, NULL, 0); MODULE_VERSION(ipheth, 1); MODULE_DEPEND(ipheth, uether, 1, 1, 1); MODULE_DEPEND(ipheth, usb, 1, 1, 1); MODULE_DEPEND(ipheth, ether, 1, 1, 1); USB_PNP_HOST_INFO(ipheth_devs); static const struct usb_ether_methods ipheth_ue_methods = { .ue_attach_post = ipheth_attach_post, .ue_start = ipheth_start, .ue_init = ipheth_init, .ue_tick = ipheth_tick, .ue_stop = ipheth_stop, .ue_setmulti = ipheth_setmulti, .ue_setpromisc = ipheth_setpromisc, }; #define IPHETH_ID(v,p,c,sc,pt) \ USB_VENDOR(v), USB_PRODUCT(p), \ USB_IFACE_CLASS(c), USB_IFACE_SUBCLASS(sc), \ USB_IFACE_PROTOCOL(pt) static int ipheth_get_mac_addr(struct ipheth_softc *sc) { struct usb_device_request req; int error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = IPHETH_CMD_GET_MACADDR; req.wValue[0] = 0; req.wValue[1] = 0; req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; req.wLength[0] = ETHER_ADDR_LEN; req.wLength[1] = 0; error = usbd_do_request(sc->sc_ue.ue_udev, NULL, &req, sc->sc_data); if (error) return (error); memcpy(sc->sc_ue.ue_eaddr, sc->sc_data, ETHER_ADDR_LEN); return (0); } static int ipheth_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); return (usbd_lookup_id_by_uaa(ipheth_devs, sizeof(ipheth_devs), uaa)); } static int ipheth_attach(device_t dev) { struct ipheth_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; struct usb_attach_arg *uaa = device_get_ivars(dev); int error; sc->sc_iface_no = uaa->info.bIfaceIndex; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); error = usbd_set_alt_interface_index(uaa->device, uaa->info.bIfaceIndex, IPHETH_ALT_INTFNUM); if (error) { device_printf(dev, "Cannot set alternate setting\n"); goto detach; } error = usbd_transfer_setup(uaa->device, &sc->sc_iface_no, sc->sc_xfer, ipheth_config, IPHETH_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "Cannot setup USB transfers\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &ipheth_ue_methods; error = ipheth_get_mac_addr(sc); if (error) { device_printf(dev, "Cannot get MAC address\n"); goto detach; } error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: ipheth_detach(dev); return (ENXIO); /* failure */ } static int ipheth_detach(device_t dev) { struct ipheth_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; /* stop all USB transfers first */ usbd_transfer_unsetup(sc->sc_xfer, IPHETH_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void ipheth_start(struct usb_ether *ue) { struct ipheth_softc *sc = uether_getsc(ue); /* * Start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[IPHETH_BULK_TX]); usbd_transfer_start(sc->sc_xfer[IPHETH_BULK_RX]); } static void ipheth_stop(struct usb_ether *ue) { struct ipheth_softc *sc = uether_getsc(ue); /* * Stop the USB transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[IPHETH_BULK_TX]); usbd_transfer_stop(sc->sc_xfer[IPHETH_BULK_RX]); } static void ipheth_tick(struct usb_ether *ue) { struct ipheth_softc *sc = uether_getsc(ue); struct usb_device_request req; int error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = IPHETH_CMD_CARRIER_CHECK; req.wValue[0] = 0; req.wValue[1] = 0; req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; req.wLength[0] = IPHETH_CTRL_BUF_SIZE; req.wLength[1] = 0; error = uether_do_request(ue, &req, sc->sc_data, IPHETH_CTRL_TIMEOUT); if (error) return; sc->sc_carrier_on = (sc->sc_data[0] == IPHETH_CARRIER_ON); } static void ipheth_attach_post(struct usb_ether *ue) { } static void ipheth_init(struct usb_ether *ue) { struct ipheth_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); IPHETH_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags |= IFF_DRV_RUNNING; /* stall data write direction, which depends on USB mode */ usbd_xfer_set_stall(sc->sc_xfer[IPHETH_BULK_TX]); /* start data transfers */ ipheth_start(ue); } static void ipheth_setmulti(struct usb_ether *ue) { } static void ipheth_setpromisc(struct usb_ether *ue) { } static void ipheth_free_queue(struct mbuf **ppm, uint8_t n) { uint8_t x; for (x = 0; x != n; x++) { if (ppm[x] != NULL) { m_freem(ppm[x]); ppm[x] = NULL; } } } static void ipheth_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ipheth_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; uint8_t x; int actlen; int aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete: %u bytes in %u frames\n", actlen, aframes); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* free all previous TX buffers */ ipheth_free_queue(sc->sc_tx_buf, IPHETH_TX_FRAMES_MAX); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: for (x = 0; x != IPHETH_TX_FRAMES_MAX; x++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; usbd_xfer_set_frame_offset(xfer, x * IPHETH_BUF_SIZE, x); pc = usbd_xfer_get_frame(xfer, x); sc->sc_tx_buf[x] = m; if (m->m_pkthdr.len > IPHETH_BUF_SIZE) m->m_pkthdr.len = IPHETH_BUF_SIZE; usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, x, IPHETH_BUF_SIZE); if (IPHETH_BUF_SIZE != m->m_pkthdr.len) { usbd_frame_zero(pc, m->m_pkthdr.len, IPHETH_BUF_SIZE - m->m_pkthdr.len); } /* * If there's a BPF listener, bounce a copy of * this frame to him: */ BPF_MTAP(ifp, m); } if (x != 0) { usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); /* free all previous TX buffers */ ipheth_free_queue(sc->sc_tx_buf, IPHETH_TX_FRAMES_MAX); /* count output errors */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void ipheth_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ipheth_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; uint8_t x; int actlen; int aframes; int len; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("received %u bytes in %u frames\n", actlen, aframes); for (x = 0; x != aframes; x++) { m = sc->sc_rx_buf[x]; sc->sc_rx_buf[x] = NULL; len = usbd_xfer_frame_len(xfer, x); if (len < (int)(sizeof(struct ether_header) + IPHETH_RX_ADJ)) { m_freem(m); continue; } m_adj(m, IPHETH_RX_ADJ); /* queue up mbuf */ uether_rxmbuf(&sc->sc_ue, m, len - IPHETH_RX_ADJ); } /* FALLTHROUGH */ case USB_ST_SETUP: for (x = 0; x != IPHETH_RX_FRAMES_MAX; x++) { if (sc->sc_rx_buf[x] == NULL) { m = uether_newbuf(); if (m == NULL) goto tr_stall; /* cancel alignment for ethernet */ m_adj(m, ETHER_ALIGN); sc->sc_rx_buf[x] = m; } else { m = sc->sc_rx_buf[x]; } usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); } /* set number of frames and start hardware */ usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); /* flush any received frames */ uether_rxflush(&sc->sc_ue); break; default: /* Error */ DPRINTF("error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { tr_stall: /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); break; } /* need to free the RX-mbufs when we are cancelled */ ipheth_free_queue(sc->sc_rx_buf, IPHETH_RX_FRAMES_MAX); break; } } Index: head/sys/dev/usb/net/if_kue.c =================================================================== --- head/sys/dev/usb/net/if_kue.c (revision 357971) +++ head/sys/dev/usb/net/if_kue.c (revision 357972) @@ -1,710 +1,711 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1997, 1998, 1999, 2000 * Bill Paul . 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * Kawasaki LSI KL5KUSB101B USB to ethernet adapter driver. * * Written by Bill Paul * Electrical Engineering Department * Columbia University, New York City */ /* * The KLSI USB to ethernet adapter chip contains an USB serial interface, * ethernet MAC and embedded microcontroller (called the QT Engine). * The chip must have firmware loaded into it before it will operate. * Packets are passed between the chip and host via bulk transfers. * There is an interrupt endpoint mentioned in the software spec, however * it's currently unused. This device is 10Mbps half-duplex only, hence * there is no media selection logic. The MAC supports a 128 entry * multicast filter, though the exact size of the filter can depend * on the firmware. Curiously, while the software spec describes various * ethernet statistics counters, my sample adapter and firmware combination * claims not to support any statistics counters at all. * * Note that once we load the firmware in the device, we have to be * careful not to load it again: if you restart your computer but * leave the adapter attached to the USB controller, it may remain * powered on and retain its firmware. In this case, we don't need * to load the firmware a second time. * * Special thanks to Rob Furr for providing an ADS Technologies * adapter for development and testing. No monkeys were harmed during * the development of this driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR kue_debug #include #include #include #include #include /* * Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID kue_devs[] = { #define KUE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } KUE_DEV(3COM, 3C19250), KUE_DEV(3COM, 3C460), KUE_DEV(ABOCOM, URE450), KUE_DEV(ADS, UBS10BT), KUE_DEV(ADS, UBS10BTX), KUE_DEV(AOX, USB101), KUE_DEV(ASANTE, EA), KUE_DEV(ATEN, DSB650C), KUE_DEV(ATEN, UC10T), KUE_DEV(COREGA, ETHER_USB_T), KUE_DEV(DLINK, DSB650C), KUE_DEV(ENTREGA, E45), KUE_DEV(ENTREGA, XX1), KUE_DEV(ENTREGA, XX2), KUE_DEV(IODATA, USBETT), KUE_DEV(JATON, EDA), KUE_DEV(KINGSTON, XX1), KUE_DEV(KLSI, DUH3E10BT), KUE_DEV(KLSI, DUH3E10BTN), KUE_DEV(LINKSYS, USB10T), KUE_DEV(MOBILITY, EA), KUE_DEV(NETGEAR, EA101), KUE_DEV(NETGEAR, EA101X), KUE_DEV(PERACOM, ENET), KUE_DEV(PERACOM, ENET2), KUE_DEV(PERACOM, ENET3), KUE_DEV(PORTGEAR, EA8), KUE_DEV(PORTGEAR, EA9), KUE_DEV(PORTSMITH, EEA), KUE_DEV(SHARK, PA), KUE_DEV(SILICOM, GPE), KUE_DEV(SILICOM, U2E), KUE_DEV(SMC, 2102USB), #undef KUE_DEV }; /* prototypes */ static device_probe_t kue_probe; static device_attach_t kue_attach; static device_detach_t kue_detach; static usb_callback_t kue_bulk_read_callback; static usb_callback_t kue_bulk_write_callback; static uether_fn_t kue_attach_post; static uether_fn_t kue_init; static uether_fn_t kue_stop; static uether_fn_t kue_start; static uether_fn_t kue_setmulti; static uether_fn_t kue_setpromisc; static int kue_do_request(struct kue_softc *, struct usb_device_request *, void *); static int kue_setword(struct kue_softc *, uint8_t, uint16_t); static int kue_ctl(struct kue_softc *, uint8_t, uint8_t, uint16_t, void *, int); static int kue_load_fw(struct kue_softc *); static void kue_reset(struct kue_softc *); #ifdef USB_DEBUG static int kue_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, kue, CTLFLAG_RW, 0, "USB kue"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, kue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB kue"); SYSCTL_INT(_hw_usb_kue, OID_AUTO, debug, CTLFLAG_RWTUN, &kue_debug, 0, "Debug level"); #endif static const struct usb_config kue_config[KUE_N_TRANSFER] = { [KUE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (MCLBYTES + 2 + 64), .flags = {.pipe_bof = 1,}, .callback = kue_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [KUE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + 2), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = kue_bulk_read_callback, .timeout = 0, /* no timeout */ }, }; static device_method_t kue_methods[] = { /* Device interface */ DEVMETHOD(device_probe, kue_probe), DEVMETHOD(device_attach, kue_attach), DEVMETHOD(device_detach, kue_detach), DEVMETHOD_END }; static driver_t kue_driver = { .name = "kue", .methods = kue_methods, .size = sizeof(struct kue_softc), }; static devclass_t kue_devclass; DRIVER_MODULE(kue, uhub, kue_driver, kue_devclass, NULL, 0); MODULE_DEPEND(kue, uether, 1, 1, 1); MODULE_DEPEND(kue, usb, 1, 1, 1); MODULE_DEPEND(kue, ether, 1, 1, 1); MODULE_VERSION(kue, 1); USB_PNP_HOST_INFO(kue_devs); static const struct usb_ether_methods kue_ue_methods = { .ue_attach_post = kue_attach_post, .ue_start = kue_start, .ue_init = kue_init, .ue_stop = kue_stop, .ue_setmulti = kue_setmulti, .ue_setpromisc = kue_setpromisc, }; /* * We have a custom do_request function which is almost like the * regular do_request function, except it has a much longer timeout. * Why? Because we need to make requests over the control endpoint * to download the firmware to the device, which can take longer * than the default timeout. */ static int kue_do_request(struct kue_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; err = uether_do_request(&sc->sc_ue, req, data, 60000); return (err); } static int kue_setword(struct kue_softc *sc, uint8_t breq, uint16_t word) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = breq; USETW(req.wValue, word); USETW(req.wIndex, 0); USETW(req.wLength, 0); return (kue_do_request(sc, &req, NULL)); } static int kue_ctl(struct kue_softc *sc, uint8_t rw, uint8_t breq, uint16_t val, void *data, int len) { struct usb_device_request req; if (rw == KUE_CTL_WRITE) req.bmRequestType = UT_WRITE_VENDOR_DEVICE; else req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = breq; USETW(req.wValue, val); USETW(req.wIndex, 0); USETW(req.wLength, len); return (kue_do_request(sc, &req, data)); } static int kue_load_fw(struct kue_softc *sc) { struct usb_device_descriptor *dd; uint16_t hwrev; usb_error_t err; dd = usbd_get_device_descriptor(sc->sc_ue.ue_udev); hwrev = UGETW(dd->bcdDevice); /* * First, check if we even need to load the firmware. * If the device was still attached when the system was * rebooted, it may already have firmware loaded in it. * If this is the case, we don't need to do it again. * And in fact, if we try to load it again, we'll hang, * so we have to avoid this condition if we don't want * to look stupid. * * We can test this quickly by checking the bcdRevision * code. The NIC will return a different revision code if * it's probed while the firmware is still loaded and * running. */ if (hwrev == 0x0202) return(0); /* Load code segment */ err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, 0, kue_code_seg, sizeof(kue_code_seg)); if (err) { device_printf(sc->sc_ue.ue_dev, "failed to load code segment: %s\n", usbd_errstr(err)); return(ENXIO); } /* Load fixup segment */ err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, 0, kue_fix_seg, sizeof(kue_fix_seg)); if (err) { device_printf(sc->sc_ue.ue_dev, "failed to load fixup segment: %s\n", usbd_errstr(err)); return(ENXIO); } /* Send trigger command. */ err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, 0, kue_trig_seg, sizeof(kue_trig_seg)); if (err) { device_printf(sc->sc_ue.ue_dev, "failed to load trigger segment: %s\n", usbd_errstr(err)); return(ENXIO); } return (0); } static void kue_setpromisc(struct usb_ether *ue) { struct kue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); KUE_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_PROMISC) sc->sc_rxfilt |= KUE_RXFILT_PROMISC; else sc->sc_rxfilt &= ~KUE_RXFILT_PROMISC; kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt); } static u_int kue_copy_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct kue_softc *sc = arg; if (cnt >= KUE_MCFILTCNT(sc)) return (1); memcpy(KUE_MCFILT(sc, cnt), LLADDR(sdl), ETHER_ADDR_LEN); return (1); } static void kue_setmulti(struct usb_ether *ue) { struct kue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); int i; KUE_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { sc->sc_rxfilt |= KUE_RXFILT_ALLMULTI; sc->sc_rxfilt &= ~KUE_RXFILT_MULTICAST; kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt); return; } sc->sc_rxfilt &= ~KUE_RXFILT_ALLMULTI; i = if_foreach_llmaddr(ifp, kue_copy_maddr, sc); if (i >= KUE_MCFILTCNT(sc)) sc->sc_rxfilt |= KUE_RXFILT_ALLMULTI; else { sc->sc_rxfilt |= KUE_RXFILT_MULTICAST; kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MCAST_FILTERS, i, sc->sc_mcfilters, i * ETHER_ADDR_LEN); } kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt); } /* * Issue a SET_CONFIGURATION command to reset the MAC. This should be * done after the firmware is loaded into the adapter in order to * bring it into proper operation. */ static void kue_reset(struct kue_softc *sc) { struct usb_config_descriptor *cd; usb_error_t err; cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, cd->bConfigurationValue); if (err) DPRINTF("reset failed (ignored)\n"); /* wait a little while for the chip to get its brains in order */ uether_pause(&sc->sc_ue, hz / 100); } static void kue_attach_post(struct usb_ether *ue) { struct kue_softc *sc = uether_getsc(ue); int error; /* load the firmware into the NIC */ error = kue_load_fw(sc); if (error) { device_printf(sc->sc_ue.ue_dev, "could not load firmware\n"); /* ignore the error */ } /* reset the adapter */ kue_reset(sc); /* read ethernet descriptor */ kue_ctl(sc, KUE_CTL_READ, KUE_CMD_GET_ETHER_DESCRIPTOR, 0, &sc->sc_desc, sizeof(sc->sc_desc)); /* copy in ethernet address */ memcpy(ue->ue_eaddr, sc->sc_desc.kue_macaddr, sizeof(ue->ue_eaddr)); } /* * Probe for a KLSI chip. */ static int kue_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != KUE_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != KUE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(kue_devs, sizeof(kue_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do * setup and ethernet/BPF attach. */ static int kue_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct kue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = KUE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, kue_config, KUE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } sc->sc_mcfilters = malloc(KUE_MCFILTCNT(sc) * ETHER_ADDR_LEN, M_USBDEV, M_WAITOK); if (sc->sc_mcfilters == NULL) { device_printf(dev, "failed allocating USB memory\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &kue_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: kue_detach(dev); return (ENXIO); /* failure */ } static int kue_detach(device_t dev) { struct kue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, KUE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); free(sc->sc_mcfilters, M_USBDEV); return (0); } /* * A frame has been uploaded: pass the resulting mbuf chain up to * the higher level protocols. */ static void kue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct kue_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct usb_page_cache *pc; uint8_t buf[2]; int len; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen <= (int)(2 + sizeof(struct ether_header))) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, 2); actlen -= 2; len = buf[0] | (buf[1] << 8); len = min(actlen, len); uether_rxbuf(ue, pc, 2, len); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void kue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct kue_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; int total_len; int temp_len; uint8_t buf[2]; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; if (m->m_pkthdr.len > MCLBYTES) m->m_pkthdr.len = MCLBYTES; temp_len = (m->m_pkthdr.len + 2); total_len = (temp_len + (64 - (temp_len % 64))); /* the first two bytes are the frame length */ buf[0] = (uint8_t)(m->m_pkthdr.len); buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, buf, 2); usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); usbd_frame_zero(pc, temp_len, total_len - temp_len); usbd_xfer_set_frame_len(xfer, 0, total_len); /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); usbd_transfer_submit(xfer); return; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void kue_start(struct usb_ether *ue) { struct kue_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[KUE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[KUE_BULK_DT_WR]); } static void kue_init(struct usb_ether *ue) { struct kue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); KUE_LOCK_ASSERT(sc, MA_OWNED); /* set MAC address */ kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MAC, 0, IF_LLADDR(ifp), ETHER_ADDR_LEN); /* I'm not sure how to tune these. */ #if 0 /* * Leave this one alone for now; setting it * wrong causes lockups on some machines/controllers. */ kue_setword(sc, KUE_CMD_SET_SOFS, 1); #endif kue_setword(sc, KUE_CMD_SET_URB_SIZE, 64); /* load the multicast filter */ kue_setpromisc(ue); usbd_xfer_set_stall(sc->sc_xfer[KUE_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; kue_start(ue); } static void kue_stop(struct usb_ether *ue) { struct kue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); KUE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[KUE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[KUE_BULK_DT_RD]); } Index: head/sys/dev/usb/net/if_mos.c =================================================================== --- head/sys/dev/usb/net/if_mos.c (revision 357971) +++ head/sys/dev/usb/net/if_mos.c (revision 357972) @@ -1,1035 +1,1036 @@ /*- * SPDX-License-Identifier: (BSD-1-Clause AND BSD-4-Clause) * * Copyright (c) 2011 Rick van der Zwet * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /*- * Copyright (c) 2008 Johann Christian Rode * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /*- * Copyright (c) 2005, 2006, 2007 Jonathan Gray * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /*- * Copyright (c) 1997, 1998, 1999, 2000-2003 * Bill Paul . 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * Moschip MCS7730/MCS7830/MCS7832 USB to Ethernet controller * The datasheet is available at the following URL: * http://www.moschip.com/data/products/MCS7830/Data%20Sheet_7830.pdf */ /* * The FreeBSD if_mos.c driver is based on various different sources: * The vendor provided driver at the following URL: * http://www.moschip.com/data/products/MCS7830/Driver_FreeBSD_7830.tar.gz * * Mixed together with the OpenBSD if_mos.c driver for validation and checking * and the FreeBSD if_reu.c as reference for the USB Ethernet framework. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR mos_debug #include #include #include #include "miibus_if.h" //#include #include "if_mosreg.h" #ifdef USB_DEBUG static int mos_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, mos, CTLFLAG_RW, 0, "USB mos"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, mos, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB mos"); SYSCTL_INT(_hw_usb_mos, OID_AUTO, debug, CTLFLAG_RWTUN, &mos_debug, 0, "Debug level"); #endif #define MOS_DPRINTFN(fmt,...) \ DPRINTF("mos: %s: " fmt "\n",__FUNCTION__,## __VA_ARGS__) #define USB_PRODUCT_MOSCHIP_MCS7730 0x7730 #define USB_PRODUCT_SITECOMEU_LN030 0x0021 /* Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID mos_devs[] = { {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7730, MCS7730)}, {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7830, MCS7830)}, {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7832, MCS7832)}, {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_LN030, MCS7830)}, }; static int mos_probe(device_t dev); static int mos_attach(device_t dev); static void mos_attach_post(struct usb_ether *ue); static int mos_detach(device_t dev); static void mos_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error); static void mos_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error); static void mos_intr_callback(struct usb_xfer *xfer, usb_error_t error); static void mos_tick(struct usb_ether *); static void mos_start(struct usb_ether *); static void mos_init(struct usb_ether *); static void mos_chip_init(struct mos_softc *); static void mos_stop(struct usb_ether *); static int mos_miibus_readreg(device_t, int, int); static int mos_miibus_writereg(device_t, int, int, int); static void mos_miibus_statchg(device_t); static int mos_ifmedia_upd(struct ifnet *); static void mos_ifmedia_sts(struct ifnet *, struct ifmediareq *); static void mos_reset(struct mos_softc *sc); static int mos_reg_read_1(struct mos_softc *, int); static int mos_reg_read_2(struct mos_softc *, int); static int mos_reg_write_1(struct mos_softc *, int, int); static int mos_reg_write_2(struct mos_softc *, int, int); static int mos_readmac(struct mos_softc *, uint8_t *); static int mos_writemac(struct mos_softc *, uint8_t *); static int mos_write_mcast(struct mos_softc *, u_char *); static void mos_setmulti(struct usb_ether *); static void mos_setpromisc(struct usb_ether *); static const struct usb_config mos_config[MOS_ENDPT_MAX] = { [MOS_ENDPT_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (MCLBYTES + 2), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = mos_bulk_write_callback, .timeout = 10000, }, [MOS_ENDPT_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = mos_bulk_read_callback, }, [MOS_ENDPT_INTR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, .callback = mos_intr_callback, }, }; static device_method_t mos_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mos_probe), DEVMETHOD(device_attach, mos_attach), DEVMETHOD(device_detach, mos_detach), /* MII interface */ DEVMETHOD(miibus_readreg, mos_miibus_readreg), DEVMETHOD(miibus_writereg, mos_miibus_writereg), DEVMETHOD(miibus_statchg, mos_miibus_statchg), DEVMETHOD_END }; static driver_t mos_driver = { .name = "mos", .methods = mos_methods, .size = sizeof(struct mos_softc) }; static devclass_t mos_devclass; DRIVER_MODULE(mos, uhub, mos_driver, mos_devclass, NULL, 0); DRIVER_MODULE(miibus, mos, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(mos, uether, 1, 1, 1); MODULE_DEPEND(mos, usb, 1, 1, 1); MODULE_DEPEND(mos, ether, 1, 1, 1); MODULE_DEPEND(mos, miibus, 1, 1, 1); USB_PNP_HOST_INFO(mos_devs); static const struct usb_ether_methods mos_ue_methods = { .ue_attach_post = mos_attach_post, .ue_start = mos_start, .ue_init = mos_init, .ue_stop = mos_stop, .ue_tick = mos_tick, .ue_setmulti = mos_setmulti, .ue_setpromisc = mos_setpromisc, .ue_mii_upd = mos_ifmedia_upd, .ue_mii_sts = mos_ifmedia_sts, }; static int mos_reg_read_1(struct mos_softc *sc, int reg) { struct usb_device_request req; usb_error_t err; uByte val = 0; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = MOS_UR_READREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 1); err = uether_do_request(&sc->sc_ue, &req, &val, 1000); if (err) { MOS_DPRINTFN("mos_reg_read_1 error, reg: %d\n", reg); return (-1); } return (val); } static int mos_reg_read_2(struct mos_softc *sc, int reg) { struct usb_device_request req; usb_error_t err; uWord val; USETW(val, 0); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = MOS_UR_READREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 2); err = uether_do_request(&sc->sc_ue, &req, &val, 1000); if (err) { MOS_DPRINTFN("mos_reg_read_2 error, reg: %d", reg); return (-1); } return (UGETW(val)); } static int mos_reg_write_1(struct mos_softc *sc, int reg, int aval) { struct usb_device_request req; usb_error_t err; uByte val; val = aval; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = MOS_UR_WRITEREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 1); err = uether_do_request(&sc->sc_ue, &req, &val, 1000); if (err) { MOS_DPRINTFN("mos_reg_write_1 error, reg: %d", reg); return (-1); } return (0); } static int mos_reg_write_2(struct mos_softc *sc, int reg, int aval) { struct usb_device_request req; usb_error_t err; uWord val; USETW(val, aval); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = MOS_UR_WRITEREG; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 2); err = uether_do_request(&sc->sc_ue, &req, &val, 1000); if (err) { MOS_DPRINTFN("mos_reg_write_2 error, reg: %d", reg); return (-1); } return (0); } static int mos_readmac(struct mos_softc *sc, u_char *mac) { struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = MOS_UR_READREG; USETW(req.wValue, 0); USETW(req.wIndex, MOS_MAC); USETW(req.wLength, ETHER_ADDR_LEN); err = uether_do_request(&sc->sc_ue, &req, mac, 1000); if (err) { return (-1); } return (0); } static int mos_writemac(struct mos_softc *sc, uint8_t *mac) { struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = MOS_UR_WRITEREG; USETW(req.wValue, 0); USETW(req.wIndex, MOS_MAC); USETW(req.wLength, ETHER_ADDR_LEN); err = uether_do_request(&sc->sc_ue, &req, mac, 1000); if (err) { MOS_DPRINTFN("mos_writemac error"); return (-1); } return (0); } static int mos_write_mcast(struct mos_softc *sc, u_char *hashtbl) { struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = MOS_UR_WRITEREG; USETW(req.wValue, 0); USETW(req.wIndex, MOS_MCAST_TABLE); USETW(req.wLength, 8); err = uether_do_request(&sc->sc_ue, &req, hashtbl, 1000); if (err) { MOS_DPRINTFN("mos_reg_mcast error"); return (-1); } return (0); } static int mos_miibus_readreg(device_t dev, int phy, int reg) { struct mos_softc *sc = device_get_softc(dev); uWord val; int i, res, locked; USETW(val, 0); locked = mtx_owned(&sc->sc_mtx); if (!locked) MOS_LOCK(sc); mos_reg_write_2(sc, MOS_PHY_DATA, 0); mos_reg_write_1(sc, MOS_PHY_CTL, (phy & MOS_PHYCTL_PHYADDR) | MOS_PHYCTL_READ); mos_reg_write_1(sc, MOS_PHY_STS, (reg & MOS_PHYSTS_PHYREG) | MOS_PHYSTS_PENDING); for (i = 0; i < MOS_TIMEOUT; i++) { if (mos_reg_read_1(sc, MOS_PHY_STS) & MOS_PHYSTS_READY) break; } if (i == MOS_TIMEOUT) { MOS_DPRINTFN("MII read timeout"); } res = mos_reg_read_2(sc, MOS_PHY_DATA); if (!locked) MOS_UNLOCK(sc); return (res); } static int mos_miibus_writereg(device_t dev, int phy, int reg, int val) { struct mos_softc *sc = device_get_softc(dev); int i, locked; locked = mtx_owned(&sc->sc_mtx); if (!locked) MOS_LOCK(sc); mos_reg_write_2(sc, MOS_PHY_DATA, val); mos_reg_write_1(sc, MOS_PHY_CTL, (phy & MOS_PHYCTL_PHYADDR) | MOS_PHYCTL_WRITE); mos_reg_write_1(sc, MOS_PHY_STS, (reg & MOS_PHYSTS_PHYREG) | MOS_PHYSTS_PENDING); for (i = 0; i < MOS_TIMEOUT; i++) { if (mos_reg_read_1(sc, MOS_PHY_STS) & MOS_PHYSTS_READY) break; } if (i == MOS_TIMEOUT) MOS_DPRINTFN("MII write timeout"); if (!locked) MOS_UNLOCK(sc); return 0; } static void mos_miibus_statchg(device_t dev) { struct mos_softc *sc = device_get_softc(dev); struct mii_data *mii = GET_MII(sc); int val, err, locked; locked = mtx_owned(&sc->sc_mtx); if (!locked) MOS_LOCK(sc); /* disable RX, TX prior to changing FDX, SPEEDSEL */ val = mos_reg_read_1(sc, MOS_CTL); val &= ~(MOS_CTL_TX_ENB | MOS_CTL_RX_ENB); mos_reg_write_1(sc, MOS_CTL, val); /* reset register which counts dropped frames */ mos_reg_write_1(sc, MOS_FRAME_DROP_CNT, 0); if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) val |= MOS_CTL_FDX_ENB; else val &= ~(MOS_CTL_FDX_ENB); switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_100_TX: val |= MOS_CTL_SPEEDSEL; break; case IFM_10_T: val &= ~(MOS_CTL_SPEEDSEL); break; } /* re-enable TX, RX */ val |= (MOS_CTL_TX_ENB | MOS_CTL_RX_ENB); err = mos_reg_write_1(sc, MOS_CTL, val); if (err) MOS_DPRINTFN("media change failed"); if (!locked) MOS_UNLOCK(sc); } /* * Set media options. */ static int mos_ifmedia_upd(struct ifnet *ifp) { struct mos_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); struct mii_softc *miisc; int error; MOS_LOCK_ASSERT(sc, MA_OWNED); sc->mos_link = 0; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } /* * Report current media status. */ static void mos_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct mos_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); MOS_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; MOS_UNLOCK(sc); } static void mos_setpromisc(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint8_t rxmode; MOS_LOCK_ASSERT(sc, MA_OWNED); rxmode = mos_reg_read_1(sc, MOS_CTL); /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) { rxmode |= MOS_CTL_RX_PROMISC; } else { rxmode &= ~MOS_CTL_RX_PROMISC; } mos_reg_write_1(sc, MOS_CTL, rxmode); } static u_int mos_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint8_t *hashtbl = arg; uint32_t h; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; hashtbl[h / 8] |= 1 << (h % 8); return (1); } static void mos_setmulti(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint8_t rxmode; uint8_t hashtbl[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int allmulti = 0; MOS_LOCK_ASSERT(sc, MA_OWNED); rxmode = mos_reg_read_1(sc, MOS_CTL); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) allmulti = 1; /* get all new ones */ if_foreach_llmaddr(ifp, mos_hash_maddr, &hashtbl); /* now program new ones */ if (allmulti == 1) { rxmode |= MOS_CTL_ALLMULTI; mos_reg_write_1(sc, MOS_CTL, rxmode); } else { rxmode &= ~MOS_CTL_ALLMULTI; mos_write_mcast(sc, (void *)&hashtbl); mos_reg_write_1(sc, MOS_CTL, rxmode); } } static void mos_reset(struct mos_softc *sc) { uint8_t ctl; ctl = mos_reg_read_1(sc, MOS_CTL); ctl &= ~(MOS_CTL_RX_PROMISC | MOS_CTL_ALLMULTI | MOS_CTL_TX_ENB | MOS_CTL_RX_ENB); /* Disable RX, TX, promiscuous and allmulticast mode */ mos_reg_write_1(sc, MOS_CTL, ctl); /* Reset frame drop counter register to zero */ mos_reg_write_1(sc, MOS_FRAME_DROP_CNT, 0); /* Wait a little while for the chip to get its brains in order. */ usb_pause_mtx(&sc->sc_mtx, hz / 128); return; } static void mos_chip_init(struct mos_softc *sc) { int i; /* * Rev.C devices have a pause threshold register which needs to be set * at startup. */ if (mos_reg_read_1(sc, MOS_PAUSE_TRHD) != -1) { for (i = 0; i < MOS_PAUSE_REWRITES; i++) mos_reg_write_1(sc, MOS_PAUSE_TRHD, 0); } sc->mos_phyaddrs[0] = 1; sc->mos_phyaddrs[1] = 0xFF; } /* * Probe for a MCS7x30 chip. */ static int mos_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int retval; if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != MOS_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != MOS_IFACE_IDX) return (ENXIO); retval = usbd_lookup_id_by_uaa(mos_devs, sizeof(mos_devs), uaa); return (retval); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int mos_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct mos_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; sc->mos_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = MOS_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, mos_config, MOS_ENDPT_MAX, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &mos_ue_methods; if (sc->mos_flags & MCS7730) { MOS_DPRINTFN("model: MCS7730"); } else if (sc->mos_flags & MCS7830) { MOS_DPRINTFN("model: MCS7830"); } else if (sc->mos_flags & MCS7832) { MOS_DPRINTFN("model: MCS7832"); } error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); detach: mos_detach(dev); return (ENXIO); } static void mos_attach_post(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); int err; /* Read MAC address, inform the world. */ err = mos_readmac(sc, ue->ue_eaddr); if (err) MOS_DPRINTFN("couldn't get MAC address"); MOS_DPRINTFN("address: %s", ether_sprintf(ue->ue_eaddr)); mos_chip_init(sc); } static int mos_detach(device_t dev) { struct mos_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, MOS_ENDPT_MAX); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } /* * A frame has been uploaded: pass the resulting mbuf chain up to * the higher level protocols. */ static void mos_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct mos_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); uint8_t rxstat = 0; uint32_t actlen; uint16_t pktlen = 0; struct usb_page_cache *pc; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); pc = usbd_xfer_get_frame(xfer, 0); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: MOS_DPRINTFN("actlen : %d", actlen); if (actlen <= 1) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } /* evaluate status byte at the end */ usbd_copy_out(pc, actlen - sizeof(rxstat), &rxstat, sizeof(rxstat)); if (rxstat != MOS_RXSTS_VALID) { MOS_DPRINTFN("erroneous frame received"); if (rxstat & MOS_RXSTS_SHORT_FRAME) MOS_DPRINTFN("frame size less than 64 bytes"); if (rxstat & MOS_RXSTS_LARGE_FRAME) { MOS_DPRINTFN("frame size larger than " "1532 bytes"); } if (rxstat & MOS_RXSTS_CRC_ERROR) MOS_DPRINTFN("CRC error"); if (rxstat & MOS_RXSTS_ALIGN_ERROR) MOS_DPRINTFN("alignment error"); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } /* Remember the last byte was used for the status fields */ pktlen = actlen - 1; if (pktlen < sizeof(struct ether_header)) { MOS_DPRINTFN("error: pktlen %d is smaller " "than ether_header %zd", pktlen, sizeof(struct ether_header)); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } uether_rxbuf(ue, pc, 0, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: MOS_DPRINTFN("bulk read error, %s", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } MOS_DPRINTFN("start rx %i", usbd_xfer_max_len(xfer)); return; } } /* * A frame was downloaded to the chip. It's safe for us to clean up * the list buffers. */ static void mos_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct mos_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: MOS_DPRINTFN("transfer of complete"); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: /* * XXX: don't send anything if there is no link? */ IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; pc = usbd_xfer_get_frame(xfer, 0); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); usbd_transfer_submit(xfer); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); return; default: MOS_DPRINTFN("usb error on tx: %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void mos_tick(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); struct mii_data *mii = GET_MII(sc); MOS_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if (!sc->mos_link && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { MOS_DPRINTFN("got link"); sc->mos_link++; mos_start(ue); } } static void mos_start(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_TX]); usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_RX]); usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_INTR]); } static void mos_init(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint8_t rxmode; MOS_LOCK_ASSERT(sc, MA_OWNED); /* Cancel pending I/O and free all RX/TX buffers. */ mos_reset(sc); /* Write MAC address */ mos_writemac(sc, IF_LLADDR(ifp)); /* Read and set transmitter IPG values */ sc->mos_ipgs[0] = mos_reg_read_1(sc, MOS_IPG0); sc->mos_ipgs[1] = mos_reg_read_1(sc, MOS_IPG1); mos_reg_write_1(sc, MOS_IPG0, sc->mos_ipgs[0]); mos_reg_write_1(sc, MOS_IPG1, sc->mos_ipgs[1]); /* * Enable receiver and transmitter, bridge controls speed/duplex * mode */ rxmode = mos_reg_read_1(sc, MOS_CTL); rxmode |= MOS_CTL_RX_ENB | MOS_CTL_TX_ENB | MOS_CTL_BS_ENB; rxmode &= ~(MOS_CTL_SLEEP); mos_setpromisc(ue); /* XXX: broadcast mode? */ mos_reg_write_1(sc, MOS_CTL, rxmode); /* Load the multicast filter. */ mos_setmulti(ue); ifp->if_drv_flags |= IFF_DRV_RUNNING; mos_start(ue); } static void mos_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct mos_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; uint32_t pkt; int actlen; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); MOS_DPRINTFN("actlen %i", actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: return; default: if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void mos_stop(struct usb_ether *ue) { struct mos_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); mos_reset(sc); MOS_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* stop all the transfers, if not already stopped */ usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_TX]); usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_RX]); usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_INTR]); sc->mos_link = 0; } Index: head/sys/dev/usb/net/if_muge.c =================================================================== --- head/sys/dev/usb/net/if_muge.c (revision 357971) +++ head/sys/dev/usb/net/if_muge.c (revision 357972) @@ -1,2281 +1,2281 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012 Ben Gray . * Copyright (C) 2018 The FreeBSD Foundation. * * This software was developed by Arshan Khanifar * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); /* * USB-To-Ethernet adapter driver for Microchip's LAN78XX and related families. * * USB 3.1 to 10/100/1000 Mbps Ethernet * LAN7800 http://www.microchip.com/wwwproducts/en/LAN7800 * * USB 2.0 to 10/100/1000 Mbps Ethernet * LAN7850 http://www.microchip.com/wwwproducts/en/LAN7850 * * USB 2 to 10/100/1000 Mbps Ethernet with built-in USB hub * LAN7515 (no datasheet available, but probes and functions as LAN7800) * * This driver is based on the if_smsc driver, with lan78xx-specific * functionality modelled on Microchip's Linux lan78xx driver. * * UNIMPLEMENTED FEATURES * ------------------ * A number of features supported by the lan78xx are not yet implemented in * this driver: * * - RX/TX checksum offloading: Nothing has been implemented yet for * TX checksumming. RX checksumming works with ICMP messages, but is broken * for TCP/UDP packets. * - Direct address translation filtering: Implemented but untested. * - VLAN tag removal. * - Support for USB interrupt endpoints. * - Latency Tolerance Messaging (LTM) support. * - TCP LSO support. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_platform.h" #ifdef FDT #include #include #include #include #endif #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR lan78xx_debug #include #include #include #include #include "miibus_if.h" #ifdef USB_DEBUG static int muge_debug = 0; -SYSCTL_NODE(_hw_usb, OID_AUTO, muge, CTLFLAG_RW, 0, +SYSCTL_NODE(_hw_usb, OID_AUTO, muge, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Microchip LAN78xx USB-GigE"); SYSCTL_INT(_hw_usb_muge, OID_AUTO, debug, CTLFLAG_RWTUN, &muge_debug, 0, "Debug level"); #endif #define MUGE_DEFAULT_RX_CSUM_ENABLE (false) #define MUGE_DEFAULT_TX_CSUM_ENABLE (false) #define MUGE_DEFAULT_TSO_CSUM_ENABLE (false) /* Supported Vendor and Product IDs. */ static const struct usb_device_id lan78xx_devs[] = { #define MUGE_DEV(p,i) { USB_VPI(USB_VENDOR_SMC2, USB_PRODUCT_SMC2_##p, i) } MUGE_DEV(LAN7800_ETH, 0), MUGE_DEV(LAN7801_ETH, 0), MUGE_DEV(LAN7850_ETH, 0), #undef MUGE_DEV }; #ifdef USB_DEBUG #define muge_dbg_printf(sc, fmt, args...) \ do { \ if (muge_debug > 0) \ device_printf((sc)->sc_ue.ue_dev, "debug: " fmt, ##args); \ } while(0) #else #define muge_dbg_printf(sc, fmt, args...) do { } while (0) #endif #define muge_warn_printf(sc, fmt, args...) \ device_printf((sc)->sc_ue.ue_dev, "warning: " fmt, ##args) #define muge_err_printf(sc, fmt, args...) \ device_printf((sc)->sc_ue.ue_dev, "error: " fmt, ##args) #define ETHER_IS_VALID(addr) \ (!ETHER_IS_MULTICAST(addr) && !ETHER_IS_ZERO(addr)) /* USB endpoints. */ enum { MUGE_BULK_DT_RD, MUGE_BULK_DT_WR, #if 0 /* Ignore interrupt endpoints for now as we poll on MII status. */ MUGE_INTR_DT_WR, MUGE_INTR_DT_RD, #endif MUGE_N_TRANSFER, }; struct muge_softc { struct usb_ether sc_ue; struct mtx sc_mtx; struct usb_xfer *sc_xfer[MUGE_N_TRANSFER]; int sc_phyno; uint32_t sc_leds; uint16_t sc_led_modes; uint16_t sc_led_modes_mask; /* Settings for the mac control (MAC_CSR) register. */ uint32_t sc_rfe_ctl; uint32_t sc_mdix_ctl; uint16_t chipid; uint16_t chiprev; uint32_t sc_mchash_table[ETH_DP_SEL_VHF_HASH_LEN]; uint32_t sc_pfilter_table[MUGE_NUM_PFILTER_ADDRS_][2]; uint32_t sc_flags; #define MUGE_FLAG_LINK 0x0001 #define MUGE_FLAG_INIT_DONE 0x0002 }; #define MUGE_IFACE_IDX 0 #define MUGE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define MUGE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define MUGE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) static device_probe_t muge_probe; static device_attach_t muge_attach; static device_detach_t muge_detach; static usb_callback_t muge_bulk_read_callback; static usb_callback_t muge_bulk_write_callback; static miibus_readreg_t lan78xx_miibus_readreg; static miibus_writereg_t lan78xx_miibus_writereg; static miibus_statchg_t lan78xx_miibus_statchg; static int muge_attach_post_sub(struct usb_ether *ue); static uether_fn_t muge_attach_post; static uether_fn_t muge_init; static uether_fn_t muge_stop; static uether_fn_t muge_start; static uether_fn_t muge_tick; static uether_fn_t muge_setmulti; static uether_fn_t muge_setpromisc; static int muge_ifmedia_upd(struct ifnet *); static void muge_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int lan78xx_chip_init(struct muge_softc *sc); static int muge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); static const struct usb_config muge_config[MUGE_N_TRANSFER] = { [MUGE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .frames = 16, .bufsize = 16 * (MCLBYTES + 16), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = muge_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [MUGE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 20480, /* bytes */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = muge_bulk_read_callback, .timeout = 0, /* no timeout */ }, /* * The chip supports interrupt endpoints, however they aren't * needed as we poll on the MII status. */ }; static const struct usb_ether_methods muge_ue_methods = { .ue_attach_post = muge_attach_post, .ue_attach_post_sub = muge_attach_post_sub, .ue_start = muge_start, .ue_ioctl = muge_ioctl, .ue_init = muge_init, .ue_stop = muge_stop, .ue_tick = muge_tick, .ue_setmulti = muge_setmulti, .ue_setpromisc = muge_setpromisc, .ue_mii_upd = muge_ifmedia_upd, .ue_mii_sts = muge_ifmedia_sts, }; /** * lan78xx_read_reg - Read a 32-bit register on the device * @sc: driver soft context * @off: offset of the register * @data: pointer a value that will be populated with the register value * * LOCKING: * The device lock must be held before calling this function. * * RETURNS: * 0 on success, a USB_ERR_?? error code on failure. */ static int lan78xx_read_reg(struct muge_softc *sc, uint32_t off, uint32_t *data) { struct usb_device_request req; uint32_t buf; usb_error_t err; MUGE_LOCK_ASSERT(sc, MA_OWNED); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UVR_READ_REG; USETW(req.wValue, 0); USETW(req.wIndex, off); USETW(req.wLength, 4); err = uether_do_request(&sc->sc_ue, &req, &buf, 1000); if (err != 0) muge_warn_printf(sc, "Failed to read register 0x%0x\n", off); *data = le32toh(buf); return (err); } /** * lan78xx_write_reg - Write a 32-bit register on the device * @sc: driver soft context * @off: offset of the register * @data: the 32-bit value to write into the register * * LOCKING: * The device lock must be held before calling this function. * * RETURNS: * 0 on success, a USB_ERR_?? error code on failure. */ static int lan78xx_write_reg(struct muge_softc *sc, uint32_t off, uint32_t data) { struct usb_device_request req; uint32_t buf; usb_error_t err; MUGE_LOCK_ASSERT(sc, MA_OWNED); buf = htole32(data); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UVR_WRITE_REG; USETW(req.wValue, 0); USETW(req.wIndex, off); USETW(req.wLength, 4); err = uether_do_request(&sc->sc_ue, &req, &buf, 1000); if (err != 0) muge_warn_printf(sc, "Failed to write register 0x%0x\n", off); return (err); } /** * lan78xx_wait_for_bits - Poll on a register value until bits are cleared * @sc: soft context * @reg: offset of the register * @bits: if the bits are clear the function returns * * LOCKING: * The device lock must be held before calling this function. * * RETURNS: * 0 on success, or a USB_ERR_?? error code on failure. */ static int lan78xx_wait_for_bits(struct muge_softc *sc, uint32_t reg, uint32_t bits) { usb_ticks_t start_ticks; const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000); uint32_t val; int err; MUGE_LOCK_ASSERT(sc, MA_OWNED); start_ticks = (usb_ticks_t)ticks; do { if ((err = lan78xx_read_reg(sc, reg, &val)) != 0) return (err); if (!(val & bits)) return (0); uether_pause(&sc->sc_ue, hz / 100); } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks); return (USB_ERR_TIMEOUT); } /** * lan78xx_eeprom_read_raw - Read the attached EEPROM * @sc: soft context * @off: the eeprom address offset * @buf: stores the bytes * @buflen: the number of bytes to read * * Simply reads bytes from an attached eeprom. * * LOCKING: * The function takes and releases the device lock if not already held. * * RETURNS: * 0 on success, or a USB_ERR_?? error code on failure. */ static int lan78xx_eeprom_read_raw(struct muge_softc *sc, uint16_t off, uint8_t *buf, uint16_t buflen) { usb_ticks_t start_ticks; const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000); int err, locked; uint32_t val, saved; uint16_t i; locked = mtx_owned(&sc->sc_mtx); /* XXX */ if (!locked) MUGE_LOCK(sc); if (sc->chipid == ETH_ID_REV_CHIP_ID_7800_) { /* EEDO/EECLK muxed with LED0/LED1 on LAN7800. */ err = lan78xx_read_reg(sc, ETH_HW_CFG, &val); saved = val; val &= ~(ETH_HW_CFG_LEDO_EN_ | ETH_HW_CFG_LED1_EN_); err = lan78xx_write_reg(sc, ETH_HW_CFG, val); } err = lan78xx_wait_for_bits(sc, ETH_E2P_CMD, ETH_E2P_CMD_BUSY_); if (err != 0) { muge_warn_printf(sc, "eeprom busy, failed to read data\n"); goto done; } /* Start reading the bytes, one at a time. */ for (i = 0; i < buflen; i++) { val = ETH_E2P_CMD_BUSY_ | ETH_E2P_CMD_READ_; val |= (ETH_E2P_CMD_ADDR_MASK_ & (off + i)); if ((err = lan78xx_write_reg(sc, ETH_E2P_CMD, val)) != 0) goto done; start_ticks = (usb_ticks_t)ticks; do { if ((err = lan78xx_read_reg(sc, ETH_E2P_CMD, &val)) != 0) goto done; if (!(val & ETH_E2P_CMD_BUSY_) || (val & ETH_E2P_CMD_TIMEOUT_)) break; uether_pause(&sc->sc_ue, hz / 100); } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks); if (val & (ETH_E2P_CMD_BUSY_ | ETH_E2P_CMD_TIMEOUT_)) { muge_warn_printf(sc, "eeprom command failed\n"); err = USB_ERR_IOERROR; break; } if ((err = lan78xx_read_reg(sc, ETH_E2P_DATA, &val)) != 0) goto done; buf[i] = (val & 0xff); } done: if (!locked) MUGE_UNLOCK(sc); if (sc->chipid == ETH_ID_REV_CHIP_ID_7800_) { /* Restore saved LED configuration. */ lan78xx_write_reg(sc, ETH_HW_CFG, saved); } return (err); } static bool lan78xx_eeprom_present(struct muge_softc *sc) { int ret; uint8_t sig; ret = lan78xx_eeprom_read_raw(sc, ETH_E2P_INDICATOR_OFFSET, &sig, 1); return (ret == 0 && sig == ETH_E2P_INDICATOR); } /** * lan78xx_otp_read_raw * @sc: soft context * @off: the otp address offset * @buf: stores the bytes * @buflen: the number of bytes to read * * Simply reads bytes from the OTP. * * LOCKING: * The function takes and releases the device lock if not already held. * * RETURNS: * 0 on success, or a USB_ERR_?? error code on failure. * */ static int lan78xx_otp_read_raw(struct muge_softc *sc, uint16_t off, uint8_t *buf, uint16_t buflen) { int locked, err; uint32_t val; uint16_t i; locked = mtx_owned(&sc->sc_mtx); if (!locked) MUGE_LOCK(sc); err = lan78xx_read_reg(sc, OTP_PWR_DN, &val); /* Checking if bit is set. */ if (val & OTP_PWR_DN_PWRDN_N) { /* Clear it, then wait for it to be cleared. */ lan78xx_write_reg(sc, OTP_PWR_DN, 0); err = lan78xx_wait_for_bits(sc, OTP_PWR_DN, OTP_PWR_DN_PWRDN_N); if (err != 0) { muge_warn_printf(sc, "OTP off? failed to read data\n"); goto done; } } /* Start reading the bytes, one at a time. */ for (i = 0; i < buflen; i++) { err = lan78xx_write_reg(sc, OTP_ADDR1, ((off + i) >> 8) & OTP_ADDR1_15_11); err = lan78xx_write_reg(sc, OTP_ADDR2, ((off + i) & OTP_ADDR2_10_3)); err = lan78xx_write_reg(sc, OTP_FUNC_CMD, OTP_FUNC_CMD_READ_); err = lan78xx_write_reg(sc, OTP_CMD_GO, OTP_CMD_GO_GO_); err = lan78xx_wait_for_bits(sc, OTP_STATUS, OTP_STATUS_BUSY_); if (err != 0) { muge_warn_printf(sc, "OTP busy failed to read data\n"); goto done; } if ((err = lan78xx_read_reg(sc, OTP_RD_DATA, &val)) != 0) goto done; buf[i] = (uint8_t)(val & 0xff); } done: if (!locked) MUGE_UNLOCK(sc); return (err); } /** * lan78xx_otp_read * @sc: soft context * @off: the otp address offset * @buf: stores the bytes * @buflen: the number of bytes to read * * Simply reads bytes from the otp. * * LOCKING: * The function takes and releases device lock if it is not already held. * * RETURNS: * 0 on success, or a USB_ERR_?? error code on failure. */ static int lan78xx_otp_read(struct muge_softc *sc, uint16_t off, uint8_t *buf, uint16_t buflen) { uint8_t sig; int err; err = lan78xx_otp_read_raw(sc, OTP_INDICATOR_OFFSET, &sig, 1); if (err == 0) { if (sig == OTP_INDICATOR_1) { } else if (sig == OTP_INDICATOR_2) { off += 0x100; /* XXX */ } else { err = -EINVAL; } if (!err) err = lan78xx_otp_read_raw(sc, off, buf, buflen); } return (err); } /** * lan78xx_setmacaddress - Set the mac address in the device * @sc: driver soft context * @addr: pointer to array contain at least 6 bytes of the mac * * LOCKING: * Should be called with the MUGE lock held. * * RETURNS: * Returns 0 on success or a negative error code. */ static int lan78xx_setmacaddress(struct muge_softc *sc, const uint8_t *addr) { int err; uint32_t val; muge_dbg_printf(sc, "setting mac address to %02x:%02x:%02x:%02x:%02x:%02x\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); MUGE_LOCK_ASSERT(sc, MA_OWNED); val = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; if ((err = lan78xx_write_reg(sc, ETH_RX_ADDRL, val)) != 0) goto done; val = (addr[5] << 8) | addr[4]; err = lan78xx_write_reg(sc, ETH_RX_ADDRH, val); done: return (err); } /** * lan78xx_set_rx_max_frame_length * @sc: driver soft context * @size: pointer to array contain at least 6 bytes of the mac * * Sets the maximum frame length to be received. Frames bigger than * this size are aborted. * * RETURNS: * Returns 0 on success or a negative error code. */ static int lan78xx_set_rx_max_frame_length(struct muge_softc *sc, int size) { int err = 0; uint32_t buf; bool rxenabled; /* First we have to disable rx before changing the length. */ err = lan78xx_read_reg(sc, ETH_MAC_RX, &buf); rxenabled = ((buf & ETH_MAC_RX_EN_) != 0); if (rxenabled) { buf &= ~ETH_MAC_RX_EN_; err = lan78xx_write_reg(sc, ETH_MAC_RX, buf); } /* Setting max frame length. */ buf &= ~ETH_MAC_RX_MAX_FR_SIZE_MASK_; buf |= (((size + 4) << ETH_MAC_RX_MAX_FR_SIZE_SHIFT_) & ETH_MAC_RX_MAX_FR_SIZE_MASK_); err = lan78xx_write_reg(sc, ETH_MAC_RX, buf); /* If it were enabled before, we enable it back. */ if (rxenabled) { buf |= ETH_MAC_RX_EN_; err = lan78xx_write_reg(sc, ETH_MAC_RX, buf); } return (0); } /** * lan78xx_miibus_readreg - Read a MII/MDIO register * @dev: usb ether device * @phy: the number of phy reading from * @reg: the register address * * LOCKING: * Takes and releases the device mutex lock if not already held. * * RETURNS: * Returns the 16-bits read from the MII register, if this function fails * 0 is returned. */ static int lan78xx_miibus_readreg(device_t dev, int phy, int reg) { struct muge_softc *sc = device_get_softc(dev); int locked; uint32_t addr, val; val = 0; locked = mtx_owned(&sc->sc_mtx); if (!locked) MUGE_LOCK(sc); if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) != 0) { muge_warn_printf(sc, "MII is busy\n"); goto done; } addr = (phy << 11) | (reg << 6) | ETH_MII_ACC_MII_READ_ | ETH_MII_ACC_MII_BUSY_; lan78xx_write_reg(sc, ETH_MII_ACC, addr); if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) != 0) { muge_warn_printf(sc, "MII read timeout\n"); goto done; } lan78xx_read_reg(sc, ETH_MII_DATA, &val); val = le32toh(val); done: if (!locked) MUGE_UNLOCK(sc); return (val & 0xFFFF); } /** * lan78xx_miibus_writereg - Writes a MII/MDIO register * @dev: usb ether device * @phy: the number of phy writing to * @reg: the register address * @val: the value to write * * Attempts to write a PHY register through the usb controller registers. * * LOCKING: * Takes and releases the device mutex lock if not already held. * * RETURNS: * Always returns 0 regardless of success or failure. */ static int lan78xx_miibus_writereg(device_t dev, int phy, int reg, int val) { struct muge_softc *sc = device_get_softc(dev); int locked; uint32_t addr; if (sc->sc_phyno != phy) return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) MUGE_LOCK(sc); if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) != 0) { muge_warn_printf(sc, "MII is busy\n"); goto done; } val = htole32(val); lan78xx_write_reg(sc, ETH_MII_DATA, val); addr = (phy << 11) | (reg << 6) | ETH_MII_ACC_MII_WRITE_ | ETH_MII_ACC_MII_BUSY_; lan78xx_write_reg(sc, ETH_MII_ACC, addr); if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) != 0) muge_warn_printf(sc, "MII write timeout\n"); done: if (!locked) MUGE_UNLOCK(sc); return (0); } /* * lan78xx_miibus_statchg - Called to detect phy status change * @dev: usb ether device * * This function is called periodically by the system to poll for status * changes of the link. * * LOCKING: * Takes and releases the device mutex lock if not already held. */ static void lan78xx_miibus_statchg(device_t dev) { struct muge_softc *sc = device_get_softc(dev); struct mii_data *mii = uether_getmii(&sc->sc_ue); struct ifnet *ifp; int locked; int err; uint32_t flow = 0; uint32_t fct_flow = 0; locked = mtx_owned(&sc->sc_mtx); if (!locked) MUGE_LOCK(sc); ifp = uether_getifp(&sc->sc_ue); if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) goto done; /* Use the MII status to determine link status */ sc->sc_flags &= ~MUGE_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { muge_dbg_printf(sc, "media is active\n"); switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->sc_flags |= MUGE_FLAG_LINK; muge_dbg_printf(sc, "10/100 ethernet\n"); break; case IFM_1000_T: sc->sc_flags |= MUGE_FLAG_LINK; muge_dbg_printf(sc, "Gigabit ethernet\n"); break; default: break; } } /* Lost link, do nothing. */ if ((sc->sc_flags & MUGE_FLAG_LINK) == 0) { muge_dbg_printf(sc, "link flag not set\n"); goto done; } err = lan78xx_read_reg(sc, ETH_FCT_FLOW, &fct_flow); if (err) { muge_warn_printf(sc, "failed to read initial flow control thresholds, error %d\n", err); goto done; } /* Enable/disable full duplex operation and TX/RX pause. */ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { muge_dbg_printf(sc, "full duplex operation\n"); /* Enable transmit MAC flow control function. */ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) flow |= ETH_FLOW_CR_TX_FCEN_ | 0xFFFF; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) flow |= ETH_FLOW_CR_RX_FCEN_; } /* XXX Flow control settings obtained from Microchip's driver. */ switch(usbd_get_speed(sc->sc_ue.ue_udev)) { case USB_SPEED_SUPER: fct_flow = 0x817; break; case USB_SPEED_HIGH: fct_flow = 0x211; break; default: break; } err += lan78xx_write_reg(sc, ETH_FLOW, flow); err += lan78xx_write_reg(sc, ETH_FCT_FLOW, fct_flow); if (err) muge_warn_printf(sc, "media change failed, error %d\n", err); done: if (!locked) MUGE_UNLOCK(sc); } /* * lan78xx_set_mdix_auto - Configure the device to enable automatic * crossover and polarity detection. LAN7800 provides HP Auto-MDIX * functionality for seamless crossover and polarity detection. * * @sc: driver soft context * * LOCKING: * Takes and releases the device mutex lock if not already held. */ static void lan78xx_set_mdix_auto(struct muge_softc *sc) { uint32_t buf, err; err = lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_EXT_PAGE_ACCESS, MUGE_EXT_PAGE_SPACE_1); buf = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_EXT_MODE_CTRL); buf &= ~MUGE_EXT_MODE_CTRL_MDIX_MASK_; buf |= MUGE_EXT_MODE_CTRL_AUTO_MDIX_; lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR); err += lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_EXT_MODE_CTRL, buf); err += lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_EXT_PAGE_ACCESS, MUGE_EXT_PAGE_SPACE_0); if (err != 0) muge_warn_printf(sc, "error setting PHY's MDIX status\n"); sc->sc_mdix_ctl = buf; } /** * lan78xx_phy_init - Initialises the in-built MUGE phy * @sc: driver soft context * * Resets the PHY part of the chip and then initialises it to default * values. The 'link down' and 'auto-negotiation complete' interrupts * from the PHY are also enabled, however we don't monitor the interrupt * endpoints for the moment. * * RETURNS: * Returns 0 on success or EIO if failed to reset the PHY. */ static int lan78xx_phy_init(struct muge_softc *sc) { muge_dbg_printf(sc, "Initializing PHY.\n"); uint16_t bmcr, lmsr; usb_ticks_t start_ticks; uint32_t hw_reg; const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000); MUGE_LOCK_ASSERT(sc, MA_OWNED); /* Reset phy and wait for reset to complete. */ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, BMCR_RESET); start_ticks = ticks; do { uether_pause(&sc->sc_ue, hz / 100); bmcr = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR); } while ((bmcr & BMCR_RESET) && ((ticks - start_ticks) < max_ticks)); if (((usb_ticks_t)(ticks - start_ticks)) >= max_ticks) { muge_err_printf(sc, "PHY reset timed-out\n"); return (EIO); } /* Setup phy to interrupt upon link down or autoneg completion. */ lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_PHY_INTR_STAT); lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_PHY_INTR_MASK, (MUGE_PHY_INTR_ANEG_COMP | MUGE_PHY_INTR_LINK_CHANGE)); /* Enable Auto-MDIX for crossover and polarity detection. */ lan78xx_set_mdix_auto(sc); /* Enable all modes. */ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_ANAR, ANAR_10 | ANAR_10_FD | ANAR_TX | ANAR_TX_FD | ANAR_CSMA | ANAR_FC | ANAR_PAUSE_ASYM); /* Restart auto-negotation. */ bmcr |= BMCR_STARTNEG; bmcr |= BMCR_AUTOEN; lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, bmcr); bmcr = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR); /* Configure LED Modes. */ if (sc->sc_led_modes_mask != 0) { lmsr = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_PHY_LED_MODE); lmsr &= ~sc->sc_led_modes_mask; lmsr |= sc->sc_led_modes; lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MUGE_PHY_LED_MODE, lmsr); } /* Enable appropriate LEDs. */ if (sc->sc_leds != 0 && lan78xx_read_reg(sc, ETH_HW_CFG, &hw_reg) == 0) { hw_reg &= ~(ETH_HW_CFG_LEDO_EN_ | ETH_HW_CFG_LED1_EN_ | ETH_HW_CFG_LED2_EN_ | ETH_HW_CFG_LED3_EN_ ); hw_reg |= sc->sc_leds; lan78xx_write_reg(sc, ETH_HW_CFG, hw_reg); } return (0); } /** * lan78xx_chip_init - Initialises the chip after power on * @sc: driver soft context * * This initialisation sequence is modelled on the procedure in the Linux * driver. * * RETURNS: * Returns 0 on success or an error code on failure. */ static int lan78xx_chip_init(struct muge_softc *sc) { int err; uint32_t buf; uint32_t burst_cap; MUGE_LOCK_ASSERT(sc, MA_OWNED); /* Enter H/W config mode. */ lan78xx_write_reg(sc, ETH_HW_CFG, ETH_HW_CFG_LRST_); if ((err = lan78xx_wait_for_bits(sc, ETH_HW_CFG, ETH_HW_CFG_LRST_)) != 0) { muge_warn_printf(sc, "timed-out waiting for lite reset to complete\n"); goto init_failed; } /* Set the mac address. */ if ((err = lan78xx_setmacaddress(sc, sc->sc_ue.ue_eaddr)) != 0) { muge_warn_printf(sc, "failed to set the MAC address\n"); goto init_failed; } /* Read and display the revision register. */ if ((err = lan78xx_read_reg(sc, ETH_ID_REV, &buf)) < 0) { muge_warn_printf(sc, "failed to read ETH_ID_REV (err = %d)\n", err); goto init_failed; } sc->chipid = (buf & ETH_ID_REV_CHIP_ID_MASK_) >> 16; sc->chiprev = buf & ETH_ID_REV_CHIP_REV_MASK_; switch (sc->chipid) { case ETH_ID_REV_CHIP_ID_7800_: case ETH_ID_REV_CHIP_ID_7850_: break; default: muge_warn_printf(sc, "Chip ID 0x%04x not yet supported\n", sc->chipid); goto init_failed; } device_printf(sc->sc_ue.ue_dev, "Chip ID 0x%04x rev %04x\n", sc->chipid, sc->chiprev); /* Respond to BULK-IN tokens with a NAK when RX FIFO is empty. */ if ((err = lan78xx_read_reg(sc, ETH_USB_CFG0, &buf)) != 0) { muge_warn_printf(sc, "failed to read ETH_USB_CFG0 (err=%d)\n", err); goto init_failed; } buf |= ETH_USB_CFG_BIR_; lan78xx_write_reg(sc, ETH_USB_CFG0, buf); /* * XXX LTM support will go here. */ /* Configuring the burst cap. */ switch (usbd_get_speed(sc->sc_ue.ue_udev)) { case USB_SPEED_SUPER: burst_cap = MUGE_DEFAULT_BURST_CAP_SIZE/MUGE_SS_USB_PKT_SIZE; break; case USB_SPEED_HIGH: burst_cap = MUGE_DEFAULT_BURST_CAP_SIZE/MUGE_HS_USB_PKT_SIZE; break; default: burst_cap = MUGE_DEFAULT_BURST_CAP_SIZE/MUGE_FS_USB_PKT_SIZE; } lan78xx_write_reg(sc, ETH_BURST_CAP, burst_cap); /* Set the default bulk in delay (same value from Linux driver). */ lan78xx_write_reg(sc, ETH_BULK_IN_DLY, MUGE_DEFAULT_BULK_IN_DELAY); /* Multiple ethernet frames per USB packets. */ err = lan78xx_read_reg(sc, ETH_HW_CFG, &buf); buf |= ETH_HW_CFG_MEF_; err = lan78xx_write_reg(sc, ETH_HW_CFG, buf); /* Enable burst cap. */ if ((err = lan78xx_read_reg(sc, ETH_USB_CFG0, &buf)) < 0) { muge_warn_printf(sc, "failed to read ETH_USB_CFG0 (err=%d)\n", err); goto init_failed; } buf |= ETH_USB_CFG_BCE_; err = lan78xx_write_reg(sc, ETH_USB_CFG0, buf); /* * Set FCL's RX and TX FIFO sizes: according to data sheet this is * already the default value. But we initialize it to the same value * anyways, as that's what the Linux driver does. * */ buf = (MUGE_MAX_RX_FIFO_SIZE - 512) / 512; err = lan78xx_write_reg(sc, ETH_FCT_RX_FIFO_END, buf); buf = (MUGE_MAX_TX_FIFO_SIZE - 512) / 512; err = lan78xx_write_reg(sc, ETH_FCT_TX_FIFO_END, buf); /* Enabling interrupts. (Not using them for now) */ err = lan78xx_write_reg(sc, ETH_INT_STS, ETH_INT_STS_CLEAR_ALL_); /* * Initializing flow control registers to 0. These registers are * properly set is handled in link-reset function in the Linux driver. */ err = lan78xx_write_reg(sc, ETH_FLOW, 0); err = lan78xx_write_reg(sc, ETH_FCT_FLOW, 0); /* * Settings for the RFE, we enable broadcast and destination address * perfect filtering. */ err = lan78xx_read_reg(sc, ETH_RFE_CTL, &buf); buf |= ETH_RFE_CTL_BCAST_EN_ | ETH_RFE_CTL_DA_PERFECT_; err = lan78xx_write_reg(sc, ETH_RFE_CTL, buf); /* * At this point the Linux driver writes multicast tables, and enables * checksum engines. But in FreeBSD that gets done in muge_init, * which gets called when the interface is brought up. */ /* Reset the PHY. */ lan78xx_write_reg(sc, ETH_PMT_CTL, ETH_PMT_CTL_PHY_RST_); if ((err = lan78xx_wait_for_bits(sc, ETH_PMT_CTL, ETH_PMT_CTL_PHY_RST_)) != 0) { muge_warn_printf(sc, "timed-out waiting for phy reset to complete\n"); goto init_failed; } err = lan78xx_read_reg(sc, ETH_MAC_CR, &buf); if (sc->chipid == ETH_ID_REV_CHIP_ID_7800_ && !lan78xx_eeprom_present(sc)) { /* Set automatic duplex and speed on LAN7800 without EEPROM. */ buf |= ETH_MAC_CR_AUTO_DUPLEX_ | ETH_MAC_CR_AUTO_SPEED_; } err = lan78xx_write_reg(sc, ETH_MAC_CR, buf); /* * Enable PHY interrupts (Not really getting used for now) * ETH_INT_EP_CTL: interrupt endpoint control register * phy events cause interrupts to be issued */ err = lan78xx_read_reg(sc, ETH_INT_EP_CTL, &buf); buf |= ETH_INT_ENP_PHY_INT; err = lan78xx_write_reg(sc, ETH_INT_EP_CTL, buf); /* * Enables mac's transmitter. It will transmit frames from the buffer * onto the cable. */ err = lan78xx_read_reg(sc, ETH_MAC_TX, &buf); buf |= ETH_MAC_TX_TXEN_; err = lan78xx_write_reg(sc, ETH_MAC_TX, buf); /* FIFO is capable of transmitting frames to MAC. */ err = lan78xx_read_reg(sc, ETH_FCT_TX_CTL, &buf); buf |= ETH_FCT_TX_CTL_EN_; err = lan78xx_write_reg(sc, ETH_FCT_TX_CTL, buf); /* * Set max frame length. In linux this is dev->mtu (which by default * is 1500) + VLAN_ETH_HLEN = 1518. */ err = lan78xx_set_rx_max_frame_length(sc, ETHER_MAX_LEN); /* Initialise the PHY. */ if ((err = lan78xx_phy_init(sc)) != 0) goto init_failed; /* Enable MAC RX. */ err = lan78xx_read_reg(sc, ETH_MAC_RX, &buf); buf |= ETH_MAC_RX_EN_; err = lan78xx_write_reg(sc, ETH_MAC_RX, buf); /* Enable FIFO controller RX. */ err = lan78xx_read_reg(sc, ETH_FCT_RX_CTL, &buf); buf |= ETH_FCT_TX_CTL_EN_; err = lan78xx_write_reg(sc, ETH_FCT_RX_CTL, buf); sc->sc_flags |= MUGE_FLAG_INIT_DONE; return (0); init_failed: muge_err_printf(sc, "lan78xx_chip_init failed (err=%d)\n", err); return (err); } static void muge_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct muge_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct mbuf *m; struct usb_page_cache *pc; uint16_t pktlen; uint32_t rx_cmd_a, rx_cmd_b; uint16_t rx_cmd_c; int off; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); muge_dbg_printf(sc, "rx : actlen %d\n", actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* * There is always a zero length frame after bringing the * interface up. */ if (actlen < (sizeof(rx_cmd_a) + ETHER_CRC_LEN)) goto tr_setup; /* * There may be multiple packets in the USB frame. Each will * have a header and each needs to have its own mbuf allocated * and populated for it. */ pc = usbd_xfer_get_frame(xfer, 0); off = 0; while (off < actlen) { /* The frame header is aligned on a 4 byte boundary. */ off = ((off + 0x3) & ~0x3); /* Extract RX CMD A. */ if (off + sizeof(rx_cmd_a) > actlen) goto tr_setup; usbd_copy_out(pc, off, &rx_cmd_a, sizeof(rx_cmd_a)); off += (sizeof(rx_cmd_a)); rx_cmd_a = le32toh(rx_cmd_a); /* Extract RX CMD B. */ if (off + sizeof(rx_cmd_b) > actlen) goto tr_setup; usbd_copy_out(pc, off, &rx_cmd_b, sizeof(rx_cmd_b)); off += (sizeof(rx_cmd_b)); rx_cmd_b = le32toh(rx_cmd_b); /* Extract RX CMD C. */ if (off + sizeof(rx_cmd_c) > actlen) goto tr_setup; usbd_copy_out(pc, off, &rx_cmd_c, sizeof(rx_cmd_c)); off += (sizeof(rx_cmd_c)); rx_cmd_c = le16toh(rx_cmd_c); if (off > actlen) goto tr_setup; pktlen = (rx_cmd_a & RX_CMD_A_LEN_MASK_); muge_dbg_printf(sc, "rx_cmd_a 0x%08x rx_cmd_b 0x%08x rx_cmd_c 0x%04x " " pktlen %d actlen %d off %d\n", rx_cmd_a, rx_cmd_b, rx_cmd_c, pktlen, actlen, off); if (rx_cmd_a & RX_CMD_A_RED_) { muge_dbg_printf(sc, "rx error (hdr 0x%08x)\n", rx_cmd_a); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); } else { /* Ethernet frame too big or too small? */ if ((pktlen < ETHER_HDR_LEN) || (pktlen > (actlen - off))) goto tr_setup; /* Create a new mbuf to store the packet. */ m = uether_newbuf(); if (m == NULL) { muge_warn_printf(sc, "failed to create new mbuf\n"); if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); goto tr_setup; } usbd_copy_out(pc, off, mtod(m, uint8_t *), pktlen); /* * Check if RX checksums are computed, and * offload them */ if ((ifp->if_capabilities & IFCAP_RXCSUM) && !(rx_cmd_a & RX_CMD_A_ICSM_)) { struct ether_header *eh; eh = mtod(m, struct ether_header *); /* * Remove the extra 2 bytes of the csum * * The checksum appears to be * simplistically calculated over the * protocol headers up to the end of the * eth frame. Which means if the eth * frame is padded the csum calculation * is incorrectly performed over the * padding bytes as well. Therefore to * be safe we ignore the H/W csum on * frames less than or equal to * 64 bytes. * * Protocols checksummed: * TCP, UDP, ICMP, IGMP, IP */ if (pktlen > ETHER_MIN_LEN) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; /* * Copy the checksum from the * last 2 bytes of the transfer * and put in the csum_data * field. */ usbd_copy_out(pc, (off + pktlen), &m->m_pkthdr.csum_data, 2); /* * The data is copied in network * order, but the csum algorithm * in the kernel expects it to * be in host network order. */ m->m_pkthdr.csum_data = ntohs(m->m_pkthdr.csum_data); muge_dbg_printf(sc, "RX checksum offloaded (0x%04x)\n", m->m_pkthdr.csum_data); } } /* Enqueue the mbuf on the receive queue. */ if (pktlen < (4 + ETHER_HDR_LEN)) { m_freem(m); goto tr_setup; } /* Remove 4 trailing bytes */ uether_rxmbuf(ue, m, pktlen - 4); } /* * Update the offset to move to the next potential * packet. */ off += pktlen; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: if (error != USB_ERR_CANCELLED) { muge_warn_printf(sc, "bulk read error, %s\n", usbd_errstr(error)); usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } /** * muge_bulk_write_callback - Write callback used to send ethernet frame(s) * @xfer: the USB transfer * @error: error code if the transfers is in an errored state * * The main write function that pulls ethernet frames off the queue and * sends them out. * */ static void muge_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct muge_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; int nframes; uint32_t frm_len = 0, tx_cmd_a = 0, tx_cmd_b = 0; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: muge_dbg_printf(sc, "USB TRANSFER status: USB_ST_TRANSFERRED\n"); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* FALLTHROUGH */ case USB_ST_SETUP: muge_dbg_printf(sc, "USB TRANSFER status: USB_ST_SETUP\n"); tr_setup: if ((sc->sc_flags & MUGE_FLAG_LINK) == 0 || (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { muge_dbg_printf(sc, "sc->sc_flags & MUGE_FLAG_LINK: %d\n", (sc->sc_flags & MUGE_FLAG_LINK)); muge_dbg_printf(sc, "ifp->if_drv_flags & IFF_DRV_OACTIVE: %d\n", (ifp->if_drv_flags & IFF_DRV_OACTIVE)); muge_dbg_printf(sc, "USB TRANSFER not sending: no link or controller is busy \n"); /* * Don't send anything if there is no link or * controller is busy. */ return; } for (nframes = 0; nframes < 16 && !IFQ_DRV_IS_EMPTY(&ifp->if_snd); nframes++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES, nframes); frm_len = 0; pc = usbd_xfer_get_frame(xfer, nframes); /* * Each frame is prefixed with two 32-bit values * describing the length of the packet and buffer. */ tx_cmd_a = (m->m_pkthdr.len & TX_CMD_A_LEN_MASK_) | TX_CMD_A_FCS_; tx_cmd_a = htole32(tx_cmd_a); usbd_copy_in(pc, 0, &tx_cmd_a, sizeof(tx_cmd_a)); tx_cmd_b = 0; /* TCP LSO Support will probably be implemented here. */ tx_cmd_b = htole32(tx_cmd_b); usbd_copy_in(pc, 4, &tx_cmd_b, sizeof(tx_cmd_b)); frm_len += 8; /* Next copy in the actual packet */ usbd_m_copy_in(pc, frm_len, m, 0, m->m_pkthdr.len); frm_len += m->m_pkthdr.len; if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* * If there's a BPF listener, bounce a copy of this * frame to it. */ BPF_MTAP(ifp, m); m_freem(m); /* Set frame length. */ usbd_xfer_set_frame_len(xfer, nframes, frm_len); } muge_dbg_printf(sc, "USB TRANSFER nframes: %d\n", nframes); if (nframes != 0) { muge_dbg_printf(sc, "USB TRANSFER submit attempt\n"); usbd_xfer_set_frames(xfer, nframes); usbd_transfer_submit(xfer); ifp->if_drv_flags |= IFF_DRV_OACTIVE; } return; default: if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (error != USB_ERR_CANCELLED) { muge_err_printf(sc, "usb error on tx: %s\n", usbd_errstr(error)); usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } /** * muge_set_mac_addr - Initiailizes NIC MAC address * @ue: the USB ethernet device * * Tries to obtain MAC address from number of sources: registers, * EEPROM, DTB blob. If all sources fail - generates random MAC. */ static void muge_set_mac_addr(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); uint32_t mac_h, mac_l; memset(ue->ue_eaddr, 0xff, ETHER_ADDR_LEN); uint32_t val; lan78xx_read_reg(sc, 0, &val); /* Read current MAC address from RX_ADDRx registers. */ if ((lan78xx_read_reg(sc, ETH_RX_ADDRL, &mac_l) == 0) && (lan78xx_read_reg(sc, ETH_RX_ADDRH, &mac_h) == 0)) { ue->ue_eaddr[5] = (uint8_t)((mac_h >> 8) & 0xff); ue->ue_eaddr[4] = (uint8_t)((mac_h) & 0xff); ue->ue_eaddr[3] = (uint8_t)((mac_l >> 24) & 0xff); ue->ue_eaddr[2] = (uint8_t)((mac_l >> 16) & 0xff); ue->ue_eaddr[1] = (uint8_t)((mac_l >> 8) & 0xff); ue->ue_eaddr[0] = (uint8_t)((mac_l) & 0xff); } /* * If RX_ADDRx did not provide a valid MAC address, try EEPROM. If that * doesn't work, try OTP. Whether any of these methods work or not, try * FDT data, because it is allowed to override the EEPROM/OTP values. */ if (ETHER_IS_VALID(ue->ue_eaddr)) { muge_dbg_printf(sc, "MAC assigned from registers\n"); } else if (lan78xx_eeprom_present(sc) && lan78xx_eeprom_read_raw(sc, ETH_E2P_MAC_OFFSET, ue->ue_eaddr, ETHER_ADDR_LEN) == 0 && ETHER_IS_VALID(ue->ue_eaddr)) { muge_dbg_printf(sc, "MAC assigned from EEPROM\n"); } else if (lan78xx_otp_read(sc, OTP_MAC_OFFSET, ue->ue_eaddr, ETHER_ADDR_LEN) == 0 && ETHER_IS_VALID(ue->ue_eaddr)) { muge_dbg_printf(sc, "MAC assigned from OTP\n"); } #ifdef FDT /* ue->ue_eaddr modified only if config exists for this dev instance. */ usb_fdt_get_mac_addr(ue->ue_dev, ue); if (ETHER_IS_VALID(ue->ue_eaddr)) { muge_dbg_printf(sc, "MAC assigned from FDT data\n"); } #endif if (!ETHER_IS_VALID(ue->ue_eaddr)) { muge_dbg_printf(sc, "MAC assigned randomly\n"); arc4rand(ue->ue_eaddr, ETHER_ADDR_LEN, 0); ue->ue_eaddr[0] &= ~0x01; /* unicast */ ue->ue_eaddr[0] |= 0x02; /* locally administered */ } } /** * muge_set_leds - Initializes NIC LEDs pattern * @ue: the USB ethernet device * * Tries to store the LED modes. * Supports only DTB blob like the Linux driver does. */ static void muge_set_leds(struct usb_ether *ue) { #ifdef FDT struct muge_softc *sc = uether_getsc(ue); phandle_t node; pcell_t modes[4]; /* 4 LEDs are possible */ ssize_t proplen; uint32_t count; if ((node = usb_fdt_get_node(ue->ue_dev, ue->ue_udev)) != -1 && (proplen = OF_getencprop(node, "microchip,led-modes", modes, sizeof(modes))) > 0) { count = proplen / sizeof( uint32_t ); sc->sc_leds = (count > 0) * ETH_HW_CFG_LEDO_EN_ | (count > 1) * ETH_HW_CFG_LED1_EN_ | (count > 2) * ETH_HW_CFG_LED2_EN_ | (count > 3) * ETH_HW_CFG_LED3_EN_; while (count-- > 0) { sc->sc_led_modes |= (modes[count] & 0xf) << (4 * count); sc->sc_led_modes_mask |= 0xf << (4 * count); } muge_dbg_printf(sc, "LED modes set from FDT data\n"); } #endif } /** * muge_attach_post - Called after the driver attached to the USB interface * @ue: the USB ethernet device * * This is where the chip is intialised for the first time. This is * different from the muge_init() function in that that one is designed to * setup the H/W to match the UE settings and can be called after a reset. * */ static void muge_attach_post(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); muge_dbg_printf(sc, "Calling muge_attach_post.\n"); /* Setup some of the basics */ sc->sc_phyno = 1; muge_set_mac_addr(ue); muge_set_leds(ue); /* Initialise the chip for the first time */ lan78xx_chip_init(sc); } /** * muge_attach_post_sub - Called after attach to the USB interface * @ue: the USB ethernet device * * Most of this is boilerplate code and copied from the base USB ethernet * driver. It has been overriden so that we can indicate to the system * that the chip supports H/W checksumming. * * RETURNS: * Returns 0 on success or a negative error code. */ static int muge_attach_post_sub(struct usb_ether *ue) { struct muge_softc *sc; struct ifnet *ifp; int error; sc = uether_getsc(ue); muge_dbg_printf(sc, "Calling muge_attach_post_sub.\n"); ifp = ue->ue_ifp; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = uether_start; ifp->if_ioctl = muge_ioctl; ifp->if_init = uether_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); /* * The chip supports TCP/UDP checksum offloading on TX and RX paths, * however currently only RX checksum is supported in the driver * (see top of file). */ ifp->if_capabilities |= IFCAP_VLAN_MTU; ifp->if_hwassist = 0; if (MUGE_DEFAULT_RX_CSUM_ENABLE) ifp->if_capabilities |= IFCAP_RXCSUM; if (MUGE_DEFAULT_TX_CSUM_ENABLE) ifp->if_capabilities |= IFCAP_TXCSUM; /* * In the Linux driver they also enable scatter/gather (NETIF_F_SG) * here, that's something related to socket buffers used in Linux. * FreeBSD doesn't have that as an interface feature. */ if (MUGE_DEFAULT_TSO_CSUM_ENABLE) ifp->if_capabilities |= IFCAP_TSO4 | IFCAP_TSO6; #if 0 /* TX checksuming is disabled since not yet implemented. */ ifp->if_capabilities |= IFCAP_TXCSUM; ifp->if_capenable |= IFCAP_TXCSUM; ifp->if_hwassist = CSUM_TCP | CSUM_UDP; #endif ifp->if_capenable = ifp->if_capabilities; mtx_lock(&Giant); error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, uether_ifmedia_upd, ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, 0); mtx_unlock(&Giant); return (0); } /** * muge_start - Starts communication with the LAN78xx chip * @ue: USB ether interface */ static void muge_start(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); /* * Start the USB transfers, if not already started. */ usbd_transfer_start(sc->sc_xfer[MUGE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[MUGE_BULK_DT_WR]); } /** * muge_ioctl - ioctl function for the device * @ifp: interface pointer * @cmd: the ioctl command * @data: data passed in the ioctl call, typically a pointer to struct * ifreq. * * The ioctl routine is overridden to detect change requests for the H/W * checksum capabilities. * * RETURNS: * 0 on success and an error code on failure. */ static int muge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct usb_ether *ue = ifp->if_softc; struct muge_softc *sc; struct ifreq *ifr; int rc; int mask; int reinit; if (cmd == SIOCSIFCAP) { sc = uether_getsc(ue); ifr = (struct ifreq *)data; MUGE_LOCK(sc); rc = 0; reinit = 0; mask = ifr->ifr_reqcap ^ ifp->if_capenable; /* Modify the RX CSUM enable bits. */ if ((mask & IFCAP_RXCSUM) != 0 && (ifp->if_capabilities & IFCAP_RXCSUM) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; if (ifp->if_drv_flags & IFF_DRV_RUNNING) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; reinit = 1; } } MUGE_UNLOCK(sc); if (reinit) uether_init(ue); } else { rc = uether_ioctl(ifp, cmd, data); } return (rc); } /** * muge_reset - Reset the SMSC chip * @sc: device soft context * * LOCKING: * Should be called with the SMSC lock held. */ static void muge_reset(struct muge_softc *sc) { struct usb_config_descriptor *cd; usb_error_t err; cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, cd->bConfigurationValue); if (err) muge_warn_printf(sc, "reset failed (ignored)\n"); /* Wait a little while for the chip to get its brains in order. */ uether_pause(&sc->sc_ue, hz / 100); /* Reinitialize controller to achieve full reset. */ lan78xx_chip_init(sc); } /** * muge_set_addr_filter * * @sc: device soft context * @index: index of the entry to the perfect address table * @addr: address to be written * */ static void muge_set_addr_filter(struct muge_softc *sc, int index, uint8_t addr[ETHER_ADDR_LEN]) { uint32_t tmp; if ((sc) && (index > 0) && (index < MUGE_NUM_PFILTER_ADDRS_)) { tmp = addr[3]; tmp |= addr[2] | (tmp << 8); tmp |= addr[1] | (tmp << 8); tmp |= addr[0] | (tmp << 8); sc->sc_pfilter_table[index][1] = tmp; tmp = addr[5]; tmp |= addr[4] | (tmp << 8); tmp |= ETH_MAF_HI_VALID_ | ETH_MAF_HI_TYPE_DST_; sc->sc_pfilter_table[index][0] = tmp; } } /** * lan78xx_dataport_write - write to the selected RAM * @sc: The device soft context. * @ram_select: Select which RAM to access. * @addr: Starting address to write to. * @buf: word-sized buffer to write to RAM, starting at @addr. * @length: length of @buf * * * RETURNS: * 0 if write successful. */ static int lan78xx_dataport_write(struct muge_softc *sc, uint32_t ram_select, uint32_t addr, uint32_t length, uint32_t *buf) { uint32_t dp_sel; int i, ret; MUGE_LOCK_ASSERT(sc, MA_OWNED); ret = lan78xx_wait_for_bits(sc, ETH_DP_SEL, ETH_DP_SEL_DPRDY_); if (ret < 0) goto done; ret = lan78xx_read_reg(sc, ETH_DP_SEL, &dp_sel); dp_sel &= ~ETH_DP_SEL_RSEL_MASK_; dp_sel |= ram_select; ret = lan78xx_write_reg(sc, ETH_DP_SEL, dp_sel); for (i = 0; i < length; i++) { ret = lan78xx_write_reg(sc, ETH_DP_ADDR, addr + i); ret = lan78xx_write_reg(sc, ETH_DP_DATA, buf[i]); ret = lan78xx_write_reg(sc, ETH_DP_CMD, ETH_DP_CMD_WRITE_); ret = lan78xx_wait_for_bits(sc, ETH_DP_SEL, ETH_DP_SEL_DPRDY_); if (ret != 0) goto done; } done: return (ret); } /** * muge_multicast_write * @sc: device's soft context * * Writes perfect addres filters and hash address filters to their * corresponding registers and RAMs. * */ static void muge_multicast_write(struct muge_softc *sc) { int i, ret; lan78xx_dataport_write(sc, ETH_DP_SEL_RSEL_VLAN_DA_, ETH_DP_SEL_VHF_VLAN_LEN, ETH_DP_SEL_VHF_HASH_LEN, sc->sc_mchash_table); for (i = 1; i < MUGE_NUM_PFILTER_ADDRS_; i++) { ret = lan78xx_write_reg(sc, PFILTER_HI(i), 0); ret = lan78xx_write_reg(sc, PFILTER_LO(i), sc->sc_pfilter_table[i][1]); ret = lan78xx_write_reg(sc, PFILTER_HI(i), sc->sc_pfilter_table[i][0]); } } /** * muge_hash - Calculate the hash of a mac address * @addr: The mac address to calculate the hash on * * This function is used when configuring a range of multicast mac * addresses to filter on. The hash of the mac address is put in the * device's mac hash table. * * RETURNS: * Returns a value from 0-63 value which is the hash of the mac address. */ static inline uint32_t muge_hash(uint8_t addr[ETHER_ADDR_LEN]) { return (ether_crc32_be(addr, ETHER_ADDR_LEN) >> 23) & 0x1ff; } static u_int muge_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct muge_softc *sc = arg; uint32_t bitnum; /* First fill up the perfect address table. */ if (cnt < 32 /* XXX */) muge_set_addr_filter(sc, cnt + 1, LLADDR(sdl)); else { bitnum = muge_hash(LLADDR(sdl)); sc->sc_mchash_table[bitnum / 32] |= (1 << (bitnum % 32)); sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_HASH_; } return (1); } /** * muge_setmulti - Setup multicast * @ue: usb ethernet device context * * Tells the device to either accept frames with a multicast mac address, * a select group of m'cast mac addresses or just the devices mac address. * * LOCKING: * Should be called with the MUGE lock held. */ static void muge_setmulti(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint8_t i; MUGE_LOCK_ASSERT(sc, MA_OWNED); sc->sc_rfe_ctl &= ~(ETH_RFE_CTL_UCAST_EN_ | ETH_RFE_CTL_MCAST_EN_ | ETH_RFE_CTL_DA_PERFECT_ | ETH_RFE_CTL_MCAST_HASH_); /* Initialize hash filter table. */ for (i = 0; i < ETH_DP_SEL_VHF_HASH_LEN; i++) sc->sc_mchash_table[i] = 0; /* Initialize perfect filter table. */ for (i = 1; i < MUGE_NUM_PFILTER_ADDRS_; i++) { sc->sc_pfilter_table[i][0] = sc->sc_pfilter_table[i][1] = 0; } sc->sc_rfe_ctl |= ETH_RFE_CTL_BCAST_EN_; if (ifp->if_flags & IFF_PROMISC) { muge_dbg_printf(sc, "promiscuous mode enabled\n"); sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_EN_ | ETH_RFE_CTL_UCAST_EN_; } else if (ifp->if_flags & IFF_ALLMULTI){ muge_dbg_printf(sc, "receive all multicast enabled\n"); sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_EN_; } else { if_foreach_llmaddr(ifp, muge_hash_maddr, sc); muge_multicast_write(sc); } lan78xx_write_reg(sc, ETH_RFE_CTL, sc->sc_rfe_ctl); } /** * muge_setpromisc - Enables/disables promiscuous mode * @ue: usb ethernet device context * * LOCKING: * Should be called with the MUGE lock held. */ static void muge_setpromisc(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); muge_dbg_printf(sc, "promiscuous mode %sabled\n", (ifp->if_flags & IFF_PROMISC) ? "en" : "dis"); MUGE_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_PROMISC) sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_EN_ | ETH_RFE_CTL_UCAST_EN_; else sc->sc_rfe_ctl &= ~(ETH_RFE_CTL_MCAST_EN_); lan78xx_write_reg(sc, ETH_RFE_CTL, sc->sc_rfe_ctl); } /** * muge_sethwcsum - Enable or disable H/W UDP and TCP checksumming * @sc: driver soft context * * LOCKING: * Should be called with the MUGE lock held. * * RETURNS: * Returns 0 on success or a negative error code. */ static int muge_sethwcsum(struct muge_softc *sc) { struct ifnet *ifp = uether_getifp(&sc->sc_ue); int err; if (!ifp) return (-EIO); MUGE_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_capabilities & IFCAP_RXCSUM) { sc->sc_rfe_ctl |= ETH_RFE_CTL_IGMP_COE_ | ETH_RFE_CTL_ICMP_COE_; sc->sc_rfe_ctl |= ETH_RFE_CTL_TCPUDP_COE_ | ETH_RFE_CTL_IP_COE_; } else { sc->sc_rfe_ctl &= ~(ETH_RFE_CTL_IGMP_COE_ | ETH_RFE_CTL_ICMP_COE_); sc->sc_rfe_ctl &= ~(ETH_RFE_CTL_TCPUDP_COE_ | ETH_RFE_CTL_IP_COE_); } sc->sc_rfe_ctl &= ~ETH_RFE_CTL_VLAN_FILTER_; err = lan78xx_write_reg(sc, ETH_RFE_CTL, sc->sc_rfe_ctl); if (err != 0) { muge_warn_printf(sc, "failed to write ETH_RFE_CTL (err=%d)\n", err); return (err); } return (0); } /** * muge_ifmedia_upd - Set media options * @ifp: interface pointer * * Basically boilerplate code that simply calls the mii functions to set * the media options. * * LOCKING: * The device lock must be held before this function is called. * * RETURNS: * Returns 0 on success or a negative error code. */ static int muge_ifmedia_upd(struct ifnet *ifp) { struct muge_softc *sc = ifp->if_softc; muge_dbg_printf(sc, "Calling muge_ifmedia_upd.\n"); struct mii_data *mii = uether_getmii(&sc->sc_ue); struct mii_softc *miisc; int err; MUGE_LOCK_ASSERT(sc, MA_OWNED); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); err = mii_mediachg(mii); return (err); } /** * muge_init - Initialises the LAN95xx chip * @ue: USB ether interface * * Called when the interface is brought up (i.e. ifconfig ue0 up), this * initialise the interface and the rx/tx pipes. * * LOCKING: * Should be called with the MUGE lock held. */ static void muge_init(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); muge_dbg_printf(sc, "Calling muge_init.\n"); struct ifnet *ifp = uether_getifp(ue); MUGE_LOCK_ASSERT(sc, MA_OWNED); if (lan78xx_setmacaddress(sc, IF_LLADDR(ifp))) muge_dbg_printf(sc, "setting MAC address failed\n"); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* Cancel pending I/O. */ muge_stop(ue); /* Reset the ethernet interface. */ muge_reset(sc); /* Load the multicast filter. */ muge_setmulti(ue); /* TCP/UDP checksum offload engines. */ muge_sethwcsum(sc); usbd_xfer_set_stall(sc->sc_xfer[MUGE_BULK_DT_WR]); /* Indicate we are up and running. */ ifp->if_drv_flags |= IFF_DRV_RUNNING; /* Switch to selected media. */ muge_ifmedia_upd(ifp); muge_start(ue); } /** * muge_stop - Stops communication with the LAN78xx chip * @ue: USB ether interface */ static void muge_stop(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); MUGE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->sc_flags &= ~MUGE_FLAG_LINK; /* * Stop all the transfers, if not already stopped. */ usbd_transfer_stop(sc->sc_xfer[MUGE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[MUGE_BULK_DT_RD]); } /** * muge_tick - Called periodically to monitor the state of the LAN95xx chip * @ue: USB ether interface * * Simply calls the mii status functions to check the state of the link. * * LOCKING: * Should be called with the MUGE lock held. */ static void muge_tick(struct usb_ether *ue) { struct muge_softc *sc = uether_getsc(ue); struct mii_data *mii = uether_getmii(&sc->sc_ue); MUGE_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & MUGE_FLAG_LINK) == 0) { lan78xx_miibus_statchg(ue->ue_dev); if ((sc->sc_flags & MUGE_FLAG_LINK) != 0) muge_start(ue); } } /** * muge_ifmedia_sts - Report current media status * @ifp: inet interface pointer * @ifmr: interface media request * * Call the mii functions to get the media status. * * LOCKING: * Internally takes and releases the device lock. */ static void muge_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct muge_softc *sc = ifp->if_softc; struct mii_data *mii = uether_getmii(&sc->sc_ue); MUGE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; MUGE_UNLOCK(sc); } /** * muge_probe - Probe the interface. * @dev: muge device handle * * Checks if the device is a match for this driver. * * RETURNS: * Returns 0 on success or an error code on failure. */ static int muge_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != MUGE_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != MUGE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(lan78xx_devs, sizeof(lan78xx_devs), uaa)); } /** * muge_attach - Attach the interface. * @dev: muge device handle * * Allocate softc structures, do ifmedia setup and ethernet/BPF attach. * * RETURNS: * Returns 0 on success or a negative error code. */ static int muge_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct muge_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int err; sc->sc_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Setup the endpoints for the Microchip LAN78xx device. */ iface_index = MUGE_IFACE_IDX; err = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, muge_config, MUGE_N_TRANSFER, sc, &sc->sc_mtx); if (err) { device_printf(dev, "error: allocating USB transfers failed\n"); goto err; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &muge_ue_methods; err = uether_ifattach(ue); if (err) { device_printf(dev, "error: could not attach interface\n"); goto err_usbd; } /* Wait for lan78xx_chip_init from post-attach callback to complete. */ uether_ifattach_wait(ue); if (!(sc->sc_flags & MUGE_FLAG_INIT_DONE)) goto err_attached; return (0); err_attached: uether_ifdetach(ue); err_usbd: usbd_transfer_unsetup(sc->sc_xfer, MUGE_N_TRANSFER); err: mtx_destroy(&sc->sc_mtx); return (ENXIO); } /** * muge_detach - Detach the interface. * @dev: muge device handle * * RETURNS: * Returns 0. */ static int muge_detach(device_t dev) { struct muge_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, MUGE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static device_method_t muge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, muge_probe), DEVMETHOD(device_attach, muge_attach), DEVMETHOD(device_detach, muge_detach), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, lan78xx_miibus_readreg), DEVMETHOD(miibus_writereg, lan78xx_miibus_writereg), DEVMETHOD(miibus_statchg, lan78xx_miibus_statchg), DEVMETHOD_END }; static driver_t muge_driver = { .name = "muge", .methods = muge_methods, .size = sizeof(struct muge_softc), }; static devclass_t muge_devclass; DRIVER_MODULE(muge, uhub, muge_driver, muge_devclass, NULL, NULL); DRIVER_MODULE(miibus, muge, miibus_driver, miibus_devclass, NULL, NULL); MODULE_DEPEND(muge, uether, 1, 1, 1); MODULE_DEPEND(muge, usb, 1, 1, 1); MODULE_DEPEND(muge, ether, 1, 1, 1); MODULE_DEPEND(muge, miibus, 1, 1, 1); MODULE_VERSION(muge, 1); USB_PNP_HOST_INFO(lan78xx_devs); Index: head/sys/dev/usb/net/if_rue.c =================================================================== --- head/sys/dev/usb/net/if_rue.c (revision 357971) +++ head/sys/dev/usb/net/if_rue.c (revision 357972) @@ -1,930 +1,931 @@ /*- * Copyright (c) 2001-2003, Shunsuke Akiyama . * Copyright (c) 1997, 1998, 1999, 2000 Bill Paul . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /*- * SPDX-License-Identifier: BSD-4-Clause AND BSD-2-Clause-FreeBSD * * Copyright (c) 1997, 1998, 1999, 2000 * Bill Paul . 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. * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * RealTek RTL8150 USB to fast ethernet controller driver. * Datasheet is available from * ftp://ftp.realtek.com.tw/lancard/data_sheet/8150/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR rue_debug #include #include #include #include #include "miibus_if.h" #ifdef USB_DEBUG static int rue_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, rue, CTLFLAG_RW, 0, "USB rue"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, rue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB rue"); SYSCTL_INT(_hw_usb_rue, OID_AUTO, debug, CTLFLAG_RWTUN, &rue_debug, 0, "Debug level"); #endif /* * Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID rue_devs[] = { {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAKTX, 0)}, {USB_VPI(USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_USBKR100, 0)}, {USB_VPI(USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01, 0)}, }; /* prototypes */ static device_probe_t rue_probe; static device_attach_t rue_attach; static device_detach_t rue_detach; static miibus_readreg_t rue_miibus_readreg; static miibus_writereg_t rue_miibus_writereg; static miibus_statchg_t rue_miibus_statchg; static usb_callback_t rue_intr_callback; static usb_callback_t rue_bulk_read_callback; static usb_callback_t rue_bulk_write_callback; static uether_fn_t rue_attach_post; static uether_fn_t rue_init; static uether_fn_t rue_stop; static uether_fn_t rue_start; static uether_fn_t rue_tick; static uether_fn_t rue_setmulti; static uether_fn_t rue_setpromisc; static int rue_read_mem(struct rue_softc *, uint16_t, void *, int); static int rue_write_mem(struct rue_softc *, uint16_t, void *, int); static uint8_t rue_csr_read_1(struct rue_softc *, uint16_t); static uint16_t rue_csr_read_2(struct rue_softc *, uint16_t); static int rue_csr_write_1(struct rue_softc *, uint16_t, uint8_t); static int rue_csr_write_2(struct rue_softc *, uint16_t, uint16_t); static int rue_csr_write_4(struct rue_softc *, int, uint32_t); static void rue_reset(struct rue_softc *); static int rue_ifmedia_upd(struct ifnet *); static void rue_ifmedia_sts(struct ifnet *, struct ifmediareq *); static const struct usb_config rue_config[RUE_N_TRANSFER] = { [RUE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = MCLBYTES, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = rue_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [RUE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + 4), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = rue_bulk_read_callback, .timeout = 0, /* no timeout */ }, [RUE_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = rue_intr_callback, }, }; static device_method_t rue_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rue_probe), DEVMETHOD(device_attach, rue_attach), DEVMETHOD(device_detach, rue_detach), /* MII interface */ DEVMETHOD(miibus_readreg, rue_miibus_readreg), DEVMETHOD(miibus_writereg, rue_miibus_writereg), DEVMETHOD(miibus_statchg, rue_miibus_statchg), DEVMETHOD_END }; static driver_t rue_driver = { .name = "rue", .methods = rue_methods, .size = sizeof(struct rue_softc), }; static devclass_t rue_devclass; DRIVER_MODULE_ORDERED(rue, uhub, rue_driver, rue_devclass, NULL, NULL, SI_ORDER_ANY); DRIVER_MODULE(miibus, rue, miibus_driver, miibus_devclass, NULL, NULL); MODULE_DEPEND(rue, uether, 1, 1, 1); MODULE_DEPEND(rue, usb, 1, 1, 1); MODULE_DEPEND(rue, ether, 1, 1, 1); MODULE_DEPEND(rue, miibus, 1, 1, 1); MODULE_VERSION(rue, 1); USB_PNP_HOST_INFO(rue_devs); static const struct usb_ether_methods rue_ue_methods = { .ue_attach_post = rue_attach_post, .ue_start = rue_start, .ue_init = rue_init, .ue_stop = rue_stop, .ue_tick = rue_tick, .ue_setmulti = rue_setmulti, .ue_setpromisc = rue_setpromisc, .ue_mii_upd = rue_ifmedia_upd, .ue_mii_sts = rue_ifmedia_sts, }; #define RUE_SETBIT(sc, reg, x) \ rue_csr_write_1(sc, reg, rue_csr_read_1(sc, reg) | (x)) #define RUE_CLRBIT(sc, reg, x) \ rue_csr_write_1(sc, reg, rue_csr_read_1(sc, reg) & ~(x)) static int rue_read_mem(struct rue_softc *sc, uint16_t addr, void *buf, int len) { struct usb_device_request req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UR_SET_ADDRESS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static int rue_write_mem(struct rue_softc *sc, uint16_t addr, void *buf, int len) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UR_SET_ADDRESS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static uint8_t rue_csr_read_1(struct rue_softc *sc, uint16_t reg) { uint8_t val; rue_read_mem(sc, reg, &val, 1); return (val); } static uint16_t rue_csr_read_2(struct rue_softc *sc, uint16_t reg) { uint8_t val[2]; rue_read_mem(sc, reg, &val, 2); return (UGETW(val)); } static int rue_csr_write_1(struct rue_softc *sc, uint16_t reg, uint8_t val) { return (rue_write_mem(sc, reg, &val, 1)); } static int rue_csr_write_2(struct rue_softc *sc, uint16_t reg, uint16_t val) { uint8_t temp[2]; USETW(temp, val); return (rue_write_mem(sc, reg, &temp, 2)); } static int rue_csr_write_4(struct rue_softc *sc, int reg, uint32_t val) { uint8_t temp[4]; USETDW(temp, val); return (rue_write_mem(sc, reg, &temp, 4)); } static int rue_miibus_readreg(device_t dev, int phy, int reg) { struct rue_softc *sc = device_get_softc(dev); uint16_t rval; uint16_t ruereg; int locked; if (phy != 0) /* RTL8150 supports PHY == 0, only */ return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) RUE_LOCK(sc); switch (reg) { case MII_BMCR: ruereg = RUE_BMCR; break; case MII_BMSR: ruereg = RUE_BMSR; break; case MII_ANAR: ruereg = RUE_ANAR; break; case MII_ANER: ruereg = RUE_AER; break; case MII_ANLPAR: ruereg = RUE_ANLP; break; case MII_PHYIDR1: case MII_PHYIDR2: rval = 0; goto done; default: if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) { rval = rue_csr_read_1(sc, reg); goto done; } device_printf(sc->sc_ue.ue_dev, "bad phy register\n"); rval = 0; goto done; } rval = rue_csr_read_2(sc, ruereg); done: if (!locked) RUE_UNLOCK(sc); return (rval); } static int rue_miibus_writereg(device_t dev, int phy, int reg, int data) { struct rue_softc *sc = device_get_softc(dev); uint16_t ruereg; int locked; if (phy != 0) /* RTL8150 supports PHY == 0, only */ return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) RUE_LOCK(sc); switch (reg) { case MII_BMCR: ruereg = RUE_BMCR; break; case MII_BMSR: ruereg = RUE_BMSR; break; case MII_ANAR: ruereg = RUE_ANAR; break; case MII_ANER: ruereg = RUE_AER; break; case MII_ANLPAR: ruereg = RUE_ANLP; break; case MII_PHYIDR1: case MII_PHYIDR2: goto done; default: if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) { rue_csr_write_1(sc, reg, data); goto done; } device_printf(sc->sc_ue.ue_dev, " bad phy register\n"); goto done; } rue_csr_write_2(sc, ruereg, data); done: if (!locked) RUE_UNLOCK(sc); return (0); } static void rue_miibus_statchg(device_t dev) { /* * When the code below is enabled the card starts doing weird * things after link going from UP to DOWN and back UP. * * Looks like some of register writes below messes up PHY * interface. * * No visible regressions were found after commenting this code * out, so that disable it for good. */ #if 0 struct rue_softc *sc = device_get_softc(dev); struct mii_data *mii = GET_MII(sc); uint16_t bmcr; int locked; locked = mtx_owned(&sc->sc_mtx); if (!locked) RUE_LOCK(sc); RUE_CLRBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); bmcr = rue_csr_read_2(sc, RUE_BMCR); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) bmcr |= RUE_BMCR_SPD_SET; else bmcr &= ~RUE_BMCR_SPD_SET; if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) bmcr |= RUE_BMCR_DUPLEX; else bmcr &= ~RUE_BMCR_DUPLEX; rue_csr_write_2(sc, RUE_BMCR, bmcr); RUE_SETBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); if (!locked) RUE_UNLOCK(sc); #endif } static void rue_setpromisc(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); RUE_LOCK_ASSERT(sc, MA_OWNED); /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) RUE_SETBIT(sc, RUE_RCR, RUE_RCR_AAP); else RUE_CLRBIT(sc, RUE_RCR, RUE_RCR_AAP); } static u_int rue_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t *hashes = arg; int h; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; if (h < 32) hashes[0] |= (1 << h); else hashes[1] |= (1 << (h - 32)); return (1); } /* * Program the 64-bit multicast hash filter. */ static void rue_setmulti(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint16_t rxcfg; uint32_t hashes[2] = { 0, 0 }; int mcnt; RUE_LOCK_ASSERT(sc, MA_OWNED); rxcfg = rue_csr_read_2(sc, RUE_RCR); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { rxcfg |= (RUE_RCR_AAM | RUE_RCR_AAP); rxcfg &= ~RUE_RCR_AM; rue_csr_write_2(sc, RUE_RCR, rxcfg); rue_csr_write_4(sc, RUE_MAR0, 0xFFFFFFFF); rue_csr_write_4(sc, RUE_MAR4, 0xFFFFFFFF); return; } /* first, zot all the existing hash bits */ rue_csr_write_4(sc, RUE_MAR0, 0); rue_csr_write_4(sc, RUE_MAR4, 0); /* now program new ones */ mcnt = if_foreach_llmaddr(ifp, rue_hash_maddr, &hashes); if (mcnt) rxcfg |= RUE_RCR_AM; else rxcfg &= ~RUE_RCR_AM; rxcfg &= ~(RUE_RCR_AAM | RUE_RCR_AAP); rue_csr_write_2(sc, RUE_RCR, rxcfg); rue_csr_write_4(sc, RUE_MAR0, hashes[0]); rue_csr_write_4(sc, RUE_MAR4, hashes[1]); } static void rue_reset(struct rue_softc *sc) { int i; rue_csr_write_1(sc, RUE_CR, RUE_CR_SOFT_RST); for (i = 0; i != RUE_TIMEOUT; i++) { if (uether_pause(&sc->sc_ue, hz / 1000)) break; if (!(rue_csr_read_1(sc, RUE_CR) & RUE_CR_SOFT_RST)) break; } if (i == RUE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "reset never completed\n"); uether_pause(&sc->sc_ue, hz / 100); } static void rue_attach_post(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); /* reset the adapter */ rue_reset(sc); /* get station address from the EEPROM */ rue_read_mem(sc, RUE_EEPROM_IDR0, ue->ue_eaddr, ETHER_ADDR_LEN); } /* * Probe for a RTL8150 chip. */ static int rue_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != RUE_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != RUE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(rue_devs, sizeof(rue_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int rue_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct rue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = RUE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, rue_config, RUE_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &rue_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: rue_detach(dev); return (ENXIO); /* failure */ } static int rue_detach(device_t dev) { struct rue_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, RUE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void rue_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct rue_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct rue_intrpkt pkt; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (ifp && (ifp->if_drv_flags & IFF_DRV_RUNNING) && actlen >= (int)sizeof(pkt)) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); if_inc_counter(ifp, IFCOUNTER_IERRORS, pkt.rue_rxlost_cnt); if_inc_counter(ifp, IFCOUNTER_IERRORS, pkt.rue_crcerr_cnt); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, pkt.rue_col_cnt); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void rue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct rue_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct usb_page_cache *pc; uint16_t status; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 4) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, actlen - 4, &status, sizeof(status)); actlen -= 4; /* check receive packet was valid or not */ status = le16toh(status); if ((status & RUE_RXSTAT_VALID) == 0) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } uether_rxbuf(ue, pc, 0, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void rue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct rue_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; int temp_len; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & RUE_FLAG_LINK) == 0) { /* * don't send anything if there is no link ! */ return; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; if (m->m_pkthdr.len > MCLBYTES) m->m_pkthdr.len = MCLBYTES; temp_len = m->m_pkthdr.len; pc = usbd_xfer_get_frame(xfer, 0); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); /* * This is an undocumented behavior. * RTL8150 chip doesn't send frame length smaller than * RUE_MIN_FRAMELEN (60) byte packet. */ if (temp_len < RUE_MIN_FRAMELEN) { usbd_frame_zero(pc, temp_len, RUE_MIN_FRAMELEN - temp_len); temp_len = RUE_MIN_FRAMELEN; } usbd_xfer_set_frame_len(xfer, 0, temp_len); /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); usbd_transfer_submit(xfer); return; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void rue_tick(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); struct mii_data *mii = GET_MII(sc); RUE_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & RUE_FLAG_LINK) == 0 && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->sc_flags |= RUE_FLAG_LINK; rue_start(ue); } } static void rue_start(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[RUE_INTR_DT_RD]); usbd_transfer_start(sc->sc_xfer[RUE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[RUE_BULK_DT_WR]); } static void rue_init(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); RUE_LOCK_ASSERT(sc, MA_OWNED); /* * Cancel pending I/O */ rue_reset(sc); /* Set MAC address */ rue_write_mem(sc, RUE_IDR0, IF_LLADDR(ifp), ETHER_ADDR_LEN); rue_stop(ue); /* * Set the initial TX and RX configuration. */ rue_csr_write_1(sc, RUE_TCR, RUE_TCR_CONFIG); rue_csr_write_2(sc, RUE_RCR, RUE_RCR_CONFIG|RUE_RCR_AB); /* Load the multicast filter */ rue_setpromisc(ue); /* Load the multicast filter. */ rue_setmulti(ue); /* Enable RX and TX */ rue_csr_write_1(sc, RUE_CR, (RUE_CR_TE | RUE_CR_RE | RUE_CR_EP3CLREN)); usbd_xfer_set_stall(sc->sc_xfer[RUE_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; rue_start(ue); } /* * Set media options. */ static int rue_ifmedia_upd(struct ifnet *ifp) { struct rue_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); struct mii_softc *miisc; int error; RUE_LOCK_ASSERT(sc, MA_OWNED); sc->sc_flags &= ~RUE_FLAG_LINK; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } /* * Report current media status. */ static void rue_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct rue_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); RUE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; RUE_UNLOCK(sc); } static void rue_stop(struct usb_ether *ue) { struct rue_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); RUE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; sc->sc_flags &= ~RUE_FLAG_LINK; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[RUE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[RUE_BULK_DT_RD]); usbd_transfer_stop(sc->sc_xfer[RUE_INTR_DT_RD]); rue_csr_write_1(sc, RUE_CR, 0x00); rue_reset(sc); } Index: head/sys/dev/usb/net/if_smsc.c =================================================================== --- head/sys/dev/usb/net/if_smsc.c (revision 357971) +++ head/sys/dev/usb/net/if_smsc.c (revision 357972) @@ -1,1811 +1,1812 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 * Ben Gray . * 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. * 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. * * 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 __FBSDID("$FreeBSD$"); /* * SMSC LAN9xxx devices (http://www.smsc.com/) * * The LAN9500 & LAN9500A devices are stand-alone USB to Ethernet chips that * support USB 2.0 and 10/100 Mbps Ethernet. * * The LAN951x devices are an integrated USB hub and USB to Ethernet adapter. * The driver only covers the Ethernet part, the standard USB hub driver * supports the hub part. * * This driver is closely modelled on the Linux driver written and copyrighted * by SMSC. * * * * * H/W TCP & UDP Checksum Offloading * --------------------------------- * The chip supports both tx and rx offloading of UDP & TCP checksums, this * feature can be dynamically enabled/disabled. * * RX checksuming is performed across bytes after the IPv4 header to the end of * the Ethernet frame, this means if the frame is padded with non-zero values * the H/W checksum will be incorrect, however the rx code compensates for this. * * TX checksuming is more complicated, the device requires a special header to * be prefixed onto the start of the frame which indicates the start and end * positions of the UDP or TCP frame. This requires the driver to manually * go through the packet data and decode the headers prior to sending. * On Linux they generally provide cues to the location of the csum and the * area to calculate it over, on FreeBSD we seem to have to do it all ourselves, * hence this is not as optimal and therefore h/w tX checksum is currently not * implemented. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_platform.h" #ifdef FDT #include #include #include #include #endif #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR smsc_debug #include #include #include #include #include "miibus_if.h" #ifdef USB_DEBUG static int smsc_debug = 0; -SYSCTL_NODE(_hw_usb, OID_AUTO, smsc, CTLFLAG_RW, 0, "USB smsc"); +SYSCTL_NODE(_hw_usb, OID_AUTO, smsc, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB smsc"); SYSCTL_INT(_hw_usb_smsc, OID_AUTO, debug, CTLFLAG_RWTUN, &smsc_debug, 0, "Debug level"); #endif /* * Various supported device vendors/products. */ static const struct usb_device_id smsc_devs[] = { #define SMSC_DEV(p,i) { USB_VPI(USB_VENDOR_SMC2, USB_PRODUCT_SMC2_##p, i) } SMSC_DEV(LAN89530_ETH, 0), SMSC_DEV(LAN9500_ETH, 0), SMSC_DEV(LAN9500_ETH_2, 0), SMSC_DEV(LAN9500A_ETH, 0), SMSC_DEV(LAN9500A_ETH_2, 0), SMSC_DEV(LAN9505_ETH, 0), SMSC_DEV(LAN9505A_ETH, 0), SMSC_DEV(LAN9514_ETH, 0), SMSC_DEV(LAN9514_ETH_2, 0), SMSC_DEV(LAN9530_ETH, 0), SMSC_DEV(LAN9730_ETH, 0), SMSC_DEV(LAN9500_SAL10, 0), SMSC_DEV(LAN9505_SAL10, 0), SMSC_DEV(LAN9500A_SAL10, 0), SMSC_DEV(LAN9505A_SAL10, 0), SMSC_DEV(LAN9514_SAL10, 0), SMSC_DEV(LAN9500A_HAL, 0), SMSC_DEV(LAN9505A_HAL, 0), #undef SMSC_DEV }; #ifdef USB_DEBUG #define smsc_dbg_printf(sc, fmt, args...) \ do { \ if (smsc_debug > 0) \ device_printf((sc)->sc_ue.ue_dev, "debug: " fmt, ##args); \ } while(0) #else #define smsc_dbg_printf(sc, fmt, args...) do { } while (0) #endif #define smsc_warn_printf(sc, fmt, args...) \ device_printf((sc)->sc_ue.ue_dev, "warning: " fmt, ##args) #define smsc_err_printf(sc, fmt, args...) \ device_printf((sc)->sc_ue.ue_dev, "error: " fmt, ##args) #define ETHER_IS_VALID(addr) \ (!ETHER_IS_MULTICAST(addr) && !ETHER_IS_ZERO(addr)) static device_probe_t smsc_probe; static device_attach_t smsc_attach; static device_detach_t smsc_detach; static usb_callback_t smsc_bulk_read_callback; static usb_callback_t smsc_bulk_write_callback; static miibus_readreg_t smsc_miibus_readreg; static miibus_writereg_t smsc_miibus_writereg; static miibus_statchg_t smsc_miibus_statchg; #if __FreeBSD_version > 1000000 static int smsc_attach_post_sub(struct usb_ether *ue); #endif static uether_fn_t smsc_attach_post; static uether_fn_t smsc_init; static uether_fn_t smsc_stop; static uether_fn_t smsc_start; static uether_fn_t smsc_tick; static uether_fn_t smsc_setmulti; static uether_fn_t smsc_setpromisc; static int smsc_ifmedia_upd(struct ifnet *); static void smsc_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int smsc_chip_init(struct smsc_softc *sc); static int smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); static const struct usb_config smsc_config[SMSC_N_TRANSFER] = { [SMSC_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .frames = 16, .bufsize = 16 * (MCLBYTES + 16), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = smsc_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [SMSC_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 20480, /* bytes */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = smsc_bulk_read_callback, .timeout = 0, /* no timeout */ }, /* The SMSC chip supports an interrupt endpoints, however they aren't * needed as we poll on the MII status. */ }; static const struct usb_ether_methods smsc_ue_methods = { .ue_attach_post = smsc_attach_post, #if __FreeBSD_version > 1000000 .ue_attach_post_sub = smsc_attach_post_sub, #endif .ue_start = smsc_start, .ue_ioctl = smsc_ioctl, .ue_init = smsc_init, .ue_stop = smsc_stop, .ue_tick = smsc_tick, .ue_setmulti = smsc_setmulti, .ue_setpromisc = smsc_setpromisc, .ue_mii_upd = smsc_ifmedia_upd, .ue_mii_sts = smsc_ifmedia_sts, }; /** * smsc_read_reg - Reads a 32-bit register on the device * @sc: driver soft context * @off: offset of the register * @data: pointer a value that will be populated with the register value * * LOCKING: * The device lock must be held before calling this function. * * RETURNS: * 0 on success, a USB_ERR_?? error code on failure. */ static int smsc_read_reg(struct smsc_softc *sc, uint32_t off, uint32_t *data) { struct usb_device_request req; uint32_t buf; usb_error_t err; SMSC_LOCK_ASSERT(sc, MA_OWNED); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = SMSC_UR_READ_REG; USETW(req.wValue, 0); USETW(req.wIndex, off); USETW(req.wLength, 4); err = uether_do_request(&sc->sc_ue, &req, &buf, 1000); if (err != 0) smsc_warn_printf(sc, "Failed to read register 0x%0x\n", off); *data = le32toh(buf); return (err); } /** * smsc_write_reg - Writes a 32-bit register on the device * @sc: driver soft context * @off: offset of the register * @data: the 32-bit value to write into the register * * LOCKING: * The device lock must be held before calling this function. * * RETURNS: * 0 on success, a USB_ERR_?? error code on failure. */ static int smsc_write_reg(struct smsc_softc *sc, uint32_t off, uint32_t data) { struct usb_device_request req; uint32_t buf; usb_error_t err; SMSC_LOCK_ASSERT(sc, MA_OWNED); buf = htole32(data); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = SMSC_UR_WRITE_REG; USETW(req.wValue, 0); USETW(req.wIndex, off); USETW(req.wLength, 4); err = uether_do_request(&sc->sc_ue, &req, &buf, 1000); if (err != 0) smsc_warn_printf(sc, "Failed to write register 0x%0x\n", off); return (err); } /** * smsc_wait_for_bits - Polls on a register value until bits are cleared * @sc: soft context * @reg: offset of the register * @bits: if the bits are clear the function returns * * LOCKING: * The device lock must be held before calling this function. * * RETURNS: * 0 on success, or a USB_ERR_?? error code on failure. */ static int smsc_wait_for_bits(struct smsc_softc *sc, uint32_t reg, uint32_t bits) { usb_ticks_t start_ticks; const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000); uint32_t val; int err; SMSC_LOCK_ASSERT(sc, MA_OWNED); start_ticks = (usb_ticks_t)ticks; do { if ((err = smsc_read_reg(sc, reg, &val)) != 0) return (err); if (!(val & bits)) return (0); uether_pause(&sc->sc_ue, hz / 100); } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks); return (USB_ERR_TIMEOUT); } /** * smsc_eeprom_read - Reads the attached EEPROM * @sc: soft context * @off: the eeprom address offset * @buf: stores the bytes * @buflen: the number of bytes to read * * Simply reads bytes from an attached eeprom. * * LOCKING: * The function takes and releases the device lock if it is not already held. * * RETURNS: * 0 on success, or a USB_ERR_?? error code on failure. */ static int smsc_eeprom_read(struct smsc_softc *sc, uint16_t off, uint8_t *buf, uint16_t buflen) { usb_ticks_t start_ticks; const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000); int err; int locked; uint32_t val; uint16_t i; locked = mtx_owned(&sc->sc_mtx); if (!locked) SMSC_LOCK(sc); err = smsc_wait_for_bits(sc, SMSC_EEPROM_CMD, SMSC_EEPROM_CMD_BUSY); if (err != 0) { smsc_warn_printf(sc, "eeprom busy, failed to read data\n"); goto done; } /* start reading the bytes, one at a time */ for (i = 0; i < buflen; i++) { val = SMSC_EEPROM_CMD_BUSY | (SMSC_EEPROM_CMD_ADDR_MASK & (off + i)); if ((err = smsc_write_reg(sc, SMSC_EEPROM_CMD, val)) != 0) goto done; start_ticks = (usb_ticks_t)ticks; do { if ((err = smsc_read_reg(sc, SMSC_EEPROM_CMD, &val)) != 0) goto done; if (!(val & SMSC_EEPROM_CMD_BUSY) || (val & SMSC_EEPROM_CMD_TIMEOUT)) break; uether_pause(&sc->sc_ue, hz / 100); } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks); if (val & (SMSC_EEPROM_CMD_BUSY | SMSC_EEPROM_CMD_TIMEOUT)) { smsc_warn_printf(sc, "eeprom command failed\n"); err = USB_ERR_IOERROR; break; } if ((err = smsc_read_reg(sc, SMSC_EEPROM_DATA, &val)) != 0) goto done; buf[i] = (val & 0xff); } done: if (!locked) SMSC_UNLOCK(sc); return (err); } /** * smsc_miibus_readreg - Reads a MII/MDIO register * @dev: usb ether device * @phy: the number of phy reading from * @reg: the register address * * Attempts to read a phy register over the MII bus. * * LOCKING: * Takes and releases the device mutex lock if not already held. * * RETURNS: * Returns the 16-bits read from the MII register, if this function fails 0 * is returned. */ static int smsc_miibus_readreg(device_t dev, int phy, int reg) { struct smsc_softc *sc = device_get_softc(dev); int locked; uint32_t addr; uint32_t val = 0; locked = mtx_owned(&sc->sc_mtx); if (!locked) SMSC_LOCK(sc); if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) { smsc_warn_printf(sc, "MII is busy\n"); goto done; } addr = (phy << 11) | (reg << 6) | SMSC_MII_READ | SMSC_MII_BUSY; smsc_write_reg(sc, SMSC_MII_ADDR, addr); if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) smsc_warn_printf(sc, "MII read timeout\n"); smsc_read_reg(sc, SMSC_MII_DATA, &val); val = le32toh(val); done: if (!locked) SMSC_UNLOCK(sc); return (val & 0xFFFF); } /** * smsc_miibus_writereg - Writes a MII/MDIO register * @dev: usb ether device * @phy: the number of phy writing to * @reg: the register address * @val: the value to write * * Attempts to write a phy register over the MII bus. * * LOCKING: * Takes and releases the device mutex lock if not already held. * * RETURNS: * Always returns 0 regardless of success or failure. */ static int smsc_miibus_writereg(device_t dev, int phy, int reg, int val) { struct smsc_softc *sc = device_get_softc(dev); int locked; uint32_t addr; if (sc->sc_phyno != phy) return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) SMSC_LOCK(sc); if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) { smsc_warn_printf(sc, "MII is busy\n"); goto done; } val = htole32(val); smsc_write_reg(sc, SMSC_MII_DATA, val); addr = (phy << 11) | (reg << 6) | SMSC_MII_WRITE | SMSC_MII_BUSY; smsc_write_reg(sc, SMSC_MII_ADDR, addr); if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) smsc_warn_printf(sc, "MII write timeout\n"); done: if (!locked) SMSC_UNLOCK(sc); return (0); } /** * smsc_miibus_statchg - Called to detect phy status change * @dev: usb ether device * * This function is called periodically by the system to poll for status * changes of the link. * * LOCKING: * Takes and releases the device mutex lock if not already held. */ static void smsc_miibus_statchg(device_t dev) { struct smsc_softc *sc = device_get_softc(dev); struct mii_data *mii = uether_getmii(&sc->sc_ue); struct ifnet *ifp; int locked; int err; uint32_t flow; uint32_t afc_cfg; locked = mtx_owned(&sc->sc_mtx); if (!locked) SMSC_LOCK(sc); ifp = uether_getifp(&sc->sc_ue); if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) goto done; /* Use the MII status to determine link status */ sc->sc_flags &= ~SMSC_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->sc_flags |= SMSC_FLAG_LINK; break; case IFM_1000_T: /* Gigabit ethernet not supported by chipset */ break; default: break; } } /* Lost link, do nothing. */ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0) { smsc_dbg_printf(sc, "link flag not set\n"); goto done; } err = smsc_read_reg(sc, SMSC_AFC_CFG, &afc_cfg); if (err) { smsc_warn_printf(sc, "failed to read initial AFC_CFG, error %d\n", err); goto done; } /* Enable/disable full duplex operation and TX/RX pause */ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { smsc_dbg_printf(sc, "full duplex operation\n"); sc->sc_mac_csr &= ~SMSC_MAC_CSR_RCVOWN; sc->sc_mac_csr |= SMSC_MAC_CSR_FDPX; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) flow = 0xffff0002; else flow = 0; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) afc_cfg |= 0xf; else afc_cfg &= ~0xf; } else { smsc_dbg_printf(sc, "half duplex operation\n"); sc->sc_mac_csr &= ~SMSC_MAC_CSR_FDPX; sc->sc_mac_csr |= SMSC_MAC_CSR_RCVOWN; flow = 0; afc_cfg |= 0xf; } err = smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr); err += smsc_write_reg(sc, SMSC_FLOW, flow); err += smsc_write_reg(sc, SMSC_AFC_CFG, afc_cfg); if (err) smsc_warn_printf(sc, "media change failed, error %d\n", err); done: if (!locked) SMSC_UNLOCK(sc); } /** * smsc_ifmedia_upd - Set media options * @ifp: interface pointer * * Basically boilerplate code that simply calls the mii functions to set the * media options. * * LOCKING: * The device lock must be held before this function is called. * * RETURNS: * Returns 0 on success or a negative error code. */ static int smsc_ifmedia_upd(struct ifnet *ifp) { struct smsc_softc *sc = ifp->if_softc; struct mii_data *mii = uether_getmii(&sc->sc_ue); struct mii_softc *miisc; int err; SMSC_LOCK_ASSERT(sc, MA_OWNED); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); err = mii_mediachg(mii); return (err); } /** * smsc_ifmedia_sts - Report current media status * @ifp: inet interface pointer * @ifmr: interface media request * * Basically boilerplate code that simply calls the mii functions to get the * media status. * * LOCKING: * Internally takes and releases the device lock. */ static void smsc_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct smsc_softc *sc = ifp->if_softc; struct mii_data *mii = uether_getmii(&sc->sc_ue); SMSC_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; SMSC_UNLOCK(sc); } /** * smsc_hash - Calculate the hash of a mac address * @addr: The mac address to calculate the hash on * * This function is used when configuring a range of m'cast mac addresses to * filter on. The hash of the mac address is put in the device's mac hash * table. * * RETURNS: * Returns a value from 0-63 value which is the hash of the mac address. */ static inline uint32_t smsc_hash(uint8_t addr[ETHER_ADDR_LEN]) { return (ether_crc32_be(addr, ETHER_ADDR_LEN) >> 26) & 0x3f; } static u_int smsc_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t hash, *hashtbl = arg; hash = smsc_hash(LLADDR(sdl)); hashtbl[hash >> 5] |= 1 << (hash & 0x1F); return (1); } /** * smsc_setmulti - Setup multicast * @ue: usb ethernet device context * * Tells the device to either accept frames with a multicast mac address, a * select group of m'cast mac addresses or just the devices mac address. * * LOCKING: * Should be called with the SMSC lock held. */ static void smsc_setmulti(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint32_t hashtbl[2] = { 0, 0 }; SMSC_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { smsc_dbg_printf(sc, "receive all multicast enabled\n"); sc->sc_mac_csr |= SMSC_MAC_CSR_MCPAS; sc->sc_mac_csr &= ~SMSC_MAC_CSR_HPFILT; } else { if (if_foreach_llmaddr(ifp, smsc_hash_maddr, &hashtbl) > 0) { /* We are filtering on a set of address so calculate * hashes of each of the address and set the * corresponding bits in the register. */ sc->sc_mac_csr |= SMSC_MAC_CSR_HPFILT; sc->sc_mac_csr &= ~(SMSC_MAC_CSR_PRMS | SMSC_MAC_CSR_MCPAS); } else { /* Only receive packets with destination set to * our mac address */ sc->sc_mac_csr &= ~(SMSC_MAC_CSR_MCPAS | SMSC_MAC_CSR_HPFILT); } /* Debug */ if (sc->sc_mac_csr & SMSC_MAC_CSR_HPFILT) smsc_dbg_printf(sc, "receive select group of macs\n"); else smsc_dbg_printf(sc, "receive own packets only\n"); } /* Write the hash table and mac control registers */ smsc_write_reg(sc, SMSC_HASHH, hashtbl[1]); smsc_write_reg(sc, SMSC_HASHL, hashtbl[0]); smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr); } /** * smsc_setpromisc - Enables/disables promiscuous mode * @ue: usb ethernet device context * * LOCKING: * Should be called with the SMSC lock held. */ static void smsc_setpromisc(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); smsc_dbg_printf(sc, "promiscuous mode %sabled\n", (ifp->if_flags & IFF_PROMISC) ? "en" : "dis"); SMSC_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_PROMISC) sc->sc_mac_csr |= SMSC_MAC_CSR_PRMS; else sc->sc_mac_csr &= ~SMSC_MAC_CSR_PRMS; smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr); } /** * smsc_sethwcsum - Enable or disable H/W UDP and TCP checksumming * @sc: driver soft context * * LOCKING: * Should be called with the SMSC lock held. * * RETURNS: * Returns 0 on success or a negative error code. */ static int smsc_sethwcsum(struct smsc_softc *sc) { struct ifnet *ifp = uether_getifp(&sc->sc_ue); uint32_t val; int err; if (!ifp) return (-EIO); SMSC_LOCK_ASSERT(sc, MA_OWNED); err = smsc_read_reg(sc, SMSC_COE_CTRL, &val); if (err != 0) { smsc_warn_printf(sc, "failed to read SMSC_COE_CTRL (err=%d)\n", err); return (err); } /* Enable/disable the Rx checksum */ if ((ifp->if_capabilities & ifp->if_capenable) & IFCAP_RXCSUM) val |= SMSC_COE_CTRL_RX_EN; else val &= ~SMSC_COE_CTRL_RX_EN; /* Enable/disable the Tx checksum (currently not supported) */ if ((ifp->if_capabilities & ifp->if_capenable) & IFCAP_TXCSUM) val |= SMSC_COE_CTRL_TX_EN; else val &= ~SMSC_COE_CTRL_TX_EN; err = smsc_write_reg(sc, SMSC_COE_CTRL, val); if (err != 0) { smsc_warn_printf(sc, "failed to write SMSC_COE_CTRL (err=%d)\n", err); return (err); } return (0); } /** * smsc_setmacaddress - Sets the mac address in the device * @sc: driver soft context * @addr: pointer to array contain at least 6 bytes of the mac * * Writes the MAC address into the device, usually the MAC is programmed with * values from the EEPROM. * * LOCKING: * Should be called with the SMSC lock held. * * RETURNS: * Returns 0 on success or a negative error code. */ static int smsc_setmacaddress(struct smsc_softc *sc, const uint8_t *addr) { int err; uint32_t val; smsc_dbg_printf(sc, "setting mac address to %02x:%02x:%02x:%02x:%02x:%02x\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); SMSC_LOCK_ASSERT(sc, MA_OWNED); val = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; if ((err = smsc_write_reg(sc, SMSC_MAC_ADDRL, val)) != 0) goto done; val = (addr[5] << 8) | addr[4]; err = smsc_write_reg(sc, SMSC_MAC_ADDRH, val); done: return (err); } /** * smsc_reset - Reset the SMSC chip * @sc: device soft context * * LOCKING: * Should be called with the SMSC lock held. */ static void smsc_reset(struct smsc_softc *sc) { struct usb_config_descriptor *cd; usb_error_t err; cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, cd->bConfigurationValue); if (err) smsc_warn_printf(sc, "reset failed (ignored)\n"); /* Wait a little while for the chip to get its brains in order. */ uether_pause(&sc->sc_ue, hz / 100); /* Reinitialize controller to achieve full reset. */ smsc_chip_init(sc); } /** * smsc_init - Initialises the LAN95xx chip * @ue: USB ether interface * * Called when the interface is brought up (i.e. ifconfig ue0 up), this * initialise the interface and the rx/tx pipes. * * LOCKING: * Should be called with the SMSC lock held. */ static void smsc_init(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); SMSC_LOCK_ASSERT(sc, MA_OWNED); if (smsc_setmacaddress(sc, IF_LLADDR(ifp))) smsc_dbg_printf(sc, "setting MAC address failed\n"); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* Cancel pending I/O */ smsc_stop(ue); #if __FreeBSD_version <= 1000000 /* On earlier versions this was the first place we could tell the system * that we supported h/w csuming, however this is only called after the * the interface has been brought up - not ideal. */ if (!(ifp->if_capabilities & IFCAP_RXCSUM)) { ifp->if_capabilities |= IFCAP_RXCSUM; ifp->if_capenable |= IFCAP_RXCSUM; ifp->if_hwassist = 0; } /* TX checksuming is disabled for now ifp->if_capabilities |= IFCAP_TXCSUM; ifp->if_capenable |= IFCAP_TXCSUM; ifp->if_hwassist = CSUM_TCP | CSUM_UDP; */ #endif /* Reset the ethernet interface. */ smsc_reset(sc); /* Load the multicast filter. */ smsc_setmulti(ue); /* TCP/UDP checksum offload engines. */ smsc_sethwcsum(sc); usbd_xfer_set_stall(sc->sc_xfer[SMSC_BULK_DT_WR]); /* Indicate we are up and running. */ ifp->if_drv_flags |= IFF_DRV_RUNNING; /* Switch to selected media. */ smsc_ifmedia_upd(ifp); smsc_start(ue); } /** * smsc_bulk_read_callback - Read callback used to process the USB URB * @xfer: the USB transfer * @error: * * Reads the URB data which can contain one or more ethernet frames, the * frames are copyed into a mbuf and given to the system. * * LOCKING: * No locking required, doesn't access internal driver settings. */ static void smsc_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct smsc_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct mbuf *m; struct usb_page_cache *pc; uint32_t rxhdr; uint16_t pktlen; int off; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); smsc_dbg_printf(sc, "rx : actlen %d\n", actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* There is always a zero length frame after bringing the IF up */ if (actlen < (sizeof(rxhdr) + ETHER_CRC_LEN)) goto tr_setup; /* There maybe multiple packets in the USB frame, each will have a * header and each needs to have it's own mbuf allocated and populated * for it. */ pc = usbd_xfer_get_frame(xfer, 0); off = 0; while (off < actlen) { /* The frame header is always aligned on a 4 byte boundary */ off = ((off + 0x3) & ~0x3); usbd_copy_out(pc, off, &rxhdr, sizeof(rxhdr)); off += (sizeof(rxhdr) + ETHER_ALIGN); rxhdr = le32toh(rxhdr); pktlen = (uint16_t)SMSC_RX_STAT_FRM_LENGTH(rxhdr); smsc_dbg_printf(sc, "rx : rxhdr 0x%08x : pktlen %d : actlen %d : " "off %d\n", rxhdr, pktlen, actlen, off); if (rxhdr & SMSC_RX_STAT_ERROR) { smsc_dbg_printf(sc, "rx error (hdr 0x%08x)\n", rxhdr); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); if (rxhdr & SMSC_RX_STAT_COLLISION) if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1); } else { /* Check if the ethernet frame is too big or too small */ if ((pktlen < ETHER_HDR_LEN) || (pktlen > (actlen - off))) goto tr_setup; /* Create a new mbuf to store the packet in */ m = uether_newbuf(); if (m == NULL) { smsc_warn_printf(sc, "failed to create new mbuf\n"); if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); goto tr_setup; } usbd_copy_out(pc, off, mtod(m, uint8_t *), pktlen); /* Check if RX TCP/UDP checksumming is being offloaded */ if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) { struct ether_header *eh; eh = mtod(m, struct ether_header *); /* Remove the extra 2 bytes of the csum */ pktlen -= 2; /* The checksum appears to be simplistically calculated * over the udp/tcp header and data up to the end of the * eth frame. Which means if the eth frame is padded * the csum calculation is incorrectly performed over * the padding bytes as well. Therefore to be safe we * ignore the H/W csum on frames less than or equal to * 64 bytes. * * Ignore H/W csum for non-IPv4 packets. */ if ((be16toh(eh->ether_type) == ETHERTYPE_IP) && (pktlen > ETHER_MIN_LEN)) { struct ip *ip; ip = (struct ip *)(eh + 1); if ((ip->ip_v == IPVERSION) && ((ip->ip_p == IPPROTO_TCP) || (ip->ip_p == IPPROTO_UDP))) { /* Indicate the UDP/TCP csum has been calculated */ m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; /* Copy the TCP/UDP checksum from the last 2 bytes * of the transfer and put in the csum_data field. */ usbd_copy_out(pc, (off + pktlen), &m->m_pkthdr.csum_data, 2); /* The data is copied in network order, but the * csum algorithm in the kernel expects it to be * in host network order. */ m->m_pkthdr.csum_data = ntohs(m->m_pkthdr.csum_data); smsc_dbg_printf(sc, "RX checksum offloaded (0x%04x)\n", m->m_pkthdr.csum_data); } } /* Need to adjust the offset as well or we'll be off * by 2 because the csum is removed from the packet * length. */ off += 2; } /* Finally enqueue the mbuf on the receive queue */ /* Remove 4 trailing bytes */ if (pktlen < (4 + ETHER_HDR_LEN)) { m_freem(m); goto tr_setup; } uether_rxmbuf(ue, m, pktlen - 4); } /* Update the offset to move to the next potential packet */ off += pktlen; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: if (error != USB_ERR_CANCELLED) { smsc_warn_printf(sc, "bulk read error, %s\n", usbd_errstr(error)); usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } /** * smsc_bulk_write_callback - Write callback used to send ethernet frame(s) * @xfer: the USB transfer * @error: error code if the transfers is in an errored state * * The main write function that pulls ethernet frames off the queue and sends * them out. * * LOCKING: * */ static void smsc_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct smsc_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; uint32_t txhdr; uint32_t frm_len = 0; int nframes; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & SMSC_FLAG_LINK) == 0 || (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { /* Don't send anything if there is no link or controller is busy. */ return; } for (nframes = 0; nframes < 16 && !IFQ_DRV_IS_EMPTY(&ifp->if_snd); nframes++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES, nframes); frm_len = 0; pc = usbd_xfer_get_frame(xfer, nframes); /* Each frame is prefixed with two 32-bit values describing the * length of the packet and buffer. */ txhdr = SMSC_TX_CTRL_0_BUF_SIZE(m->m_pkthdr.len) | SMSC_TX_CTRL_0_FIRST_SEG | SMSC_TX_CTRL_0_LAST_SEG; txhdr = htole32(txhdr); usbd_copy_in(pc, 0, &txhdr, sizeof(txhdr)); txhdr = SMSC_TX_CTRL_1_PKT_LENGTH(m->m_pkthdr.len); txhdr = htole32(txhdr); usbd_copy_in(pc, 4, &txhdr, sizeof(txhdr)); frm_len += 8; /* Next copy in the actual packet */ usbd_m_copy_in(pc, frm_len, m, 0, m->m_pkthdr.len); frm_len += m->m_pkthdr.len; if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* If there's a BPF listener, bounce a copy of this frame to him */ BPF_MTAP(ifp, m); m_freem(m); /* Set frame length. */ usbd_xfer_set_frame_len(xfer, nframes, frm_len); } if (nframes != 0) { usbd_xfer_set_frames(xfer, nframes); usbd_transfer_submit(xfer); ifp->if_drv_flags |= IFF_DRV_OACTIVE; } return; default: if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (error != USB_ERR_CANCELLED) { smsc_err_printf(sc, "usb error on tx: %s\n", usbd_errstr(error)); usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } /** * smsc_tick - Called periodically to monitor the state of the LAN95xx chip * @ue: USB ether interface * * Simply calls the mii status functions to check the state of the link. * * LOCKING: * Should be called with the SMSC lock held. */ static void smsc_tick(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); struct mii_data *mii = uether_getmii(&sc->sc_ue); SMSC_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & SMSC_FLAG_LINK) == 0) { smsc_miibus_statchg(ue->ue_dev); if ((sc->sc_flags & SMSC_FLAG_LINK) != 0) smsc_start(ue); } } /** * smsc_start - Starts communication with the LAN95xx chip * @ue: USB ether interface * * * */ static void smsc_start(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[SMSC_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[SMSC_BULK_DT_WR]); } /** * smsc_stop - Stops communication with the LAN95xx chip * @ue: USB ether interface * * * */ static void smsc_stop(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); SMSC_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->sc_flags &= ~SMSC_FLAG_LINK; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[SMSC_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[SMSC_BULK_DT_RD]); } /** * smsc_phy_init - Initialises the in-built SMSC phy * @sc: driver soft context * * Resets the PHY part of the chip and then initialises it to default * values. The 'link down' and 'auto-negotiation complete' interrupts * from the PHY are also enabled, however we don't monitor the interrupt * endpoints for the moment. * * RETURNS: * Returns 0 on success or EIO if failed to reset the PHY. */ static int smsc_phy_init(struct smsc_softc *sc) { int bmcr; usb_ticks_t start_ticks; const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000); SMSC_LOCK_ASSERT(sc, MA_OWNED); /* Reset phy and wait for reset to complete */ smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, BMCR_RESET); start_ticks = ticks; do { uether_pause(&sc->sc_ue, hz / 100); bmcr = smsc_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR); } while ((bmcr & BMCR_RESET) && ((ticks - start_ticks) < max_ticks)); if (((usb_ticks_t)(ticks - start_ticks)) >= max_ticks) { smsc_err_printf(sc, "PHY reset timed-out"); return (EIO); } smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_ANAR, ANAR_10 | ANAR_10_FD | ANAR_TX | ANAR_TX_FD | /* all modes */ ANAR_CSMA | ANAR_FC | ANAR_PAUSE_ASYM); /* Setup the phy to interrupt when the link goes down or autoneg completes */ smsc_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, SMSC_PHY_INTR_STAT); smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, SMSC_PHY_INTR_MASK, (SMSC_PHY_INTR_ANEG_COMP | SMSC_PHY_INTR_LINK_DOWN)); /* Restart auto-negotation */ bmcr = smsc_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR); bmcr |= BMCR_STARTNEG; smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, bmcr); return (0); } /** * smsc_chip_init - Initialises the chip after power on * @sc: driver soft context * * This initialisation sequence is modelled on the procedure in the Linux * driver. * * RETURNS: * Returns 0 on success or an error code on failure. */ static int smsc_chip_init(struct smsc_softc *sc) { int err; int locked; uint32_t reg_val; int burst_cap; locked = mtx_owned(&sc->sc_mtx); if (!locked) SMSC_LOCK(sc); /* Enter H/W config mode */ smsc_write_reg(sc, SMSC_HW_CFG, SMSC_HW_CFG_LRST); if ((err = smsc_wait_for_bits(sc, SMSC_HW_CFG, SMSC_HW_CFG_LRST)) != 0) { smsc_warn_printf(sc, "timed-out waiting for reset to complete\n"); goto init_failed; } /* Reset the PHY */ smsc_write_reg(sc, SMSC_PM_CTRL, SMSC_PM_CTRL_PHY_RST); if ((err = smsc_wait_for_bits(sc, SMSC_PM_CTRL, SMSC_PM_CTRL_PHY_RST)) != 0) { smsc_warn_printf(sc, "timed-out waiting for phy reset to complete\n"); goto init_failed; } /* Set the mac address */ if ((err = smsc_setmacaddress(sc, sc->sc_ue.ue_eaddr)) != 0) { smsc_warn_printf(sc, "failed to set the MAC address\n"); goto init_failed; } /* Don't know what the HW_CFG_BIR bit is, but following the reset sequence * as used in the Linux driver. */ if ((err = smsc_read_reg(sc, SMSC_HW_CFG, ®_val)) != 0) { smsc_warn_printf(sc, "failed to read HW_CFG: %d\n", err); goto init_failed; } reg_val |= SMSC_HW_CFG_BIR; smsc_write_reg(sc, SMSC_HW_CFG, reg_val); /* There is a so called 'turbo mode' that the linux driver supports, it * seems to allow you to jam multiple frames per Rx transaction. By default * this driver supports that and therefore allows multiple frames per URB. * * The xfer buffer size needs to reflect this as well, therefore based on * the calculations in the Linux driver the RX bufsize is set to 18944, * bufsz = (16 * 1024 + 5 * 512) * * Burst capability is the number of URBs that can be in a burst of data/ * ethernet frames. */ if (usbd_get_speed(sc->sc_ue.ue_udev) == USB_SPEED_HIGH) burst_cap = 37; else burst_cap = 128; smsc_write_reg(sc, SMSC_BURST_CAP, burst_cap); /* Set the default bulk in delay (magic value from Linux driver) */ smsc_write_reg(sc, SMSC_BULK_IN_DLY, 0x00002000); /* * Initialise the RX interface */ if ((err = smsc_read_reg(sc, SMSC_HW_CFG, ®_val)) < 0) { smsc_warn_printf(sc, "failed to read HW_CFG: (err = %d)\n", err); goto init_failed; } /* Adjust the packet offset in the buffer (designed to try and align IP * header on 4 byte boundary) */ reg_val &= ~SMSC_HW_CFG_RXDOFF; reg_val |= (ETHER_ALIGN << 9) & SMSC_HW_CFG_RXDOFF; /* The following setings are used for 'turbo mode', a.k.a multiple frames * per Rx transaction (again info taken form Linux driver). */ reg_val |= (SMSC_HW_CFG_MEF | SMSC_HW_CFG_BCE); smsc_write_reg(sc, SMSC_HW_CFG, reg_val); /* Clear the status register ? */ smsc_write_reg(sc, SMSC_INTR_STATUS, 0xffffffff); /* Read and display the revision register */ if ((err = smsc_read_reg(sc, SMSC_ID_REV, &sc->sc_rev_id)) < 0) { smsc_warn_printf(sc, "failed to read ID_REV (err = %d)\n", err); goto init_failed; } device_printf(sc->sc_ue.ue_dev, "chip 0x%04lx, rev. %04lx\n", (sc->sc_rev_id & SMSC_ID_REV_CHIP_ID_MASK) >> 16, (sc->sc_rev_id & SMSC_ID_REV_CHIP_REV_MASK)); /* GPIO/LED setup */ reg_val = SMSC_LED_GPIO_CFG_SPD_LED | SMSC_LED_GPIO_CFG_LNK_LED | SMSC_LED_GPIO_CFG_FDX_LED; smsc_write_reg(sc, SMSC_LED_GPIO_CFG, reg_val); /* * Initialise the TX interface */ smsc_write_reg(sc, SMSC_FLOW, 0); smsc_write_reg(sc, SMSC_AFC_CFG, AFC_CFG_DEFAULT); /* Read the current MAC configuration */ if ((err = smsc_read_reg(sc, SMSC_MAC_CSR, &sc->sc_mac_csr)) < 0) { smsc_warn_printf(sc, "failed to read MAC_CSR (err=%d)\n", err); goto init_failed; } /* Vlan */ smsc_write_reg(sc, SMSC_VLAN1, (uint32_t)ETHERTYPE_VLAN); /* * Initialise the PHY */ if ((err = smsc_phy_init(sc)) != 0) goto init_failed; /* * Start TX */ sc->sc_mac_csr |= SMSC_MAC_CSR_TXEN; smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr); smsc_write_reg(sc, SMSC_TX_CFG, SMSC_TX_CFG_ON); /* * Start RX */ sc->sc_mac_csr |= SMSC_MAC_CSR_RXEN; smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr); if (!locked) SMSC_UNLOCK(sc); return (0); init_failed: if (!locked) SMSC_UNLOCK(sc); smsc_err_printf(sc, "smsc_chip_init failed (err=%d)\n", err); return (err); } /** * smsc_ioctl - ioctl function for the device * @ifp: interface pointer * @cmd: the ioctl command * @data: data passed in the ioctl call, typically a pointer to struct ifreq. * * The ioctl routine is overridden to detect change requests for the H/W * checksum capabilities. * * RETURNS: * 0 on success and an error code on failure. */ static int smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct usb_ether *ue = ifp->if_softc; struct smsc_softc *sc; struct ifreq *ifr; int rc; int mask; int reinit; if (cmd == SIOCSIFCAP) { sc = uether_getsc(ue); ifr = (struct ifreq *)data; SMSC_LOCK(sc); rc = 0; reinit = 0; mask = ifr->ifr_reqcap ^ ifp->if_capenable; /* Modify the RX CSUM enable bits */ if ((mask & IFCAP_RXCSUM) != 0 && (ifp->if_capabilities & IFCAP_RXCSUM) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; if (ifp->if_drv_flags & IFF_DRV_RUNNING) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; reinit = 1; } } SMSC_UNLOCK(sc); if (reinit) #if __FreeBSD_version > 1000000 uether_init(ue); #else ifp->if_init(ue); #endif } else { rc = uether_ioctl(ifp, cmd, data); } return (rc); } /** * smsc_attach_post - Called after the driver attached to the USB interface * @ue: the USB ethernet device * * This is where the chip is intialised for the first time. This is different * from the smsc_init() function in that that one is designed to setup the * H/W to match the UE settings and can be called after a reset. * * */ static void smsc_attach_post(struct usb_ether *ue) { struct smsc_softc *sc = uether_getsc(ue); uint32_t mac_h, mac_l; int err; smsc_dbg_printf(sc, "smsc_attach_post\n"); /* Setup some of the basics */ sc->sc_phyno = 1; /* Attempt to get the mac address, if an EEPROM is not attached this * will just return FF:FF:FF:FF:FF:FF, so in such cases we invent a MAC * address based on urandom. */ memset(sc->sc_ue.ue_eaddr, 0xff, ETHER_ADDR_LEN); /* Check if there is already a MAC address in the register */ if ((smsc_read_reg(sc, SMSC_MAC_ADDRL, &mac_l) == 0) && (smsc_read_reg(sc, SMSC_MAC_ADDRH, &mac_h) == 0)) { sc->sc_ue.ue_eaddr[5] = (uint8_t)((mac_h >> 8) & 0xff); sc->sc_ue.ue_eaddr[4] = (uint8_t)((mac_h) & 0xff); sc->sc_ue.ue_eaddr[3] = (uint8_t)((mac_l >> 24) & 0xff); sc->sc_ue.ue_eaddr[2] = (uint8_t)((mac_l >> 16) & 0xff); sc->sc_ue.ue_eaddr[1] = (uint8_t)((mac_l >> 8) & 0xff); sc->sc_ue.ue_eaddr[0] = (uint8_t)((mac_l) & 0xff); } /* MAC address is not set so try to read from EEPROM, if that fails generate * a random MAC address. */ if (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr)) { err = smsc_eeprom_read(sc, 0x01, sc->sc_ue.ue_eaddr, ETHER_ADDR_LEN); #ifdef FDT if ((err != 0) || (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr))) err = usb_fdt_get_mac_addr(sc->sc_ue.ue_dev, &sc->sc_ue); #endif if ((err != 0) || (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr))) { read_random(sc->sc_ue.ue_eaddr, ETHER_ADDR_LEN); sc->sc_ue.ue_eaddr[0] &= ~0x01; /* unicast */ sc->sc_ue.ue_eaddr[0] |= 0x02; /* locally administered */ } } /* Initialise the chip for the first time */ smsc_chip_init(sc); } /** * smsc_attach_post_sub - Called after the driver attached to the USB interface * @ue: the USB ethernet device * * Most of this is boilerplate code and copied from the base USB ethernet * driver. It has been overriden so that we can indicate to the system that * the chip supports H/W checksumming. * * RETURNS: * Returns 0 on success or a negative error code. */ #if __FreeBSD_version > 1000000 static int smsc_attach_post_sub(struct usb_ether *ue) { struct smsc_softc *sc; struct ifnet *ifp; int error; sc = uether_getsc(ue); ifp = ue->ue_ifp; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = uether_start; ifp->if_ioctl = smsc_ioctl; ifp->if_init = uether_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); /* The chip supports TCP/UDP checksum offloading on TX and RX paths, however * currently only RX checksum is supported in the driver (see top of file). */ ifp->if_capabilities |= IFCAP_RXCSUM | IFCAP_VLAN_MTU; ifp->if_hwassist = 0; /* TX checksuming is disabled (for now?) ifp->if_capabilities |= IFCAP_TXCSUM; ifp->if_capenable |= IFCAP_TXCSUM; ifp->if_hwassist = CSUM_TCP | CSUM_UDP; */ ifp->if_capenable = ifp->if_capabilities; mtx_lock(&Giant); error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, uether_ifmedia_upd, ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, 0); mtx_unlock(&Giant); return (error); } #endif /* __FreeBSD_version > 1000000 */ /** * smsc_probe - Probe the interface. * @dev: smsc device handle * * Checks if the device is a match for this driver. * * RETURNS: * Returns 0 on success or an error code on failure. */ static int smsc_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != SMSC_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != SMSC_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(smsc_devs, sizeof(smsc_devs), uaa)); } /** * smsc_attach - Attach the interface. * @dev: smsc device handle * * Allocate softc structures, do ifmedia setup and ethernet/BPF attach. * * RETURNS: * Returns 0 on success or a negative error code. */ static int smsc_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct smsc_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int err; sc->sc_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Setup the endpoints for the SMSC LAN95xx device(s) */ iface_index = SMSC_IFACE_IDX; err = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, smsc_config, SMSC_N_TRANSFER, sc, &sc->sc_mtx); if (err) { device_printf(dev, "error: allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &smsc_ue_methods; err = uether_ifattach(ue); if (err) { device_printf(dev, "error: could not attach interface\n"); goto detach; } return (0); /* success */ detach: smsc_detach(dev); return (ENXIO); /* failure */ } /** * smsc_detach - Detach the interface. * @dev: smsc device handle * * RETURNS: * Returns 0. */ static int smsc_detach(device_t dev) { struct smsc_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, SMSC_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static device_method_t smsc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, smsc_probe), DEVMETHOD(device_attach, smsc_attach), DEVMETHOD(device_detach, smsc_detach), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, smsc_miibus_readreg), DEVMETHOD(miibus_writereg, smsc_miibus_writereg), DEVMETHOD(miibus_statchg, smsc_miibus_statchg), DEVMETHOD_END }; static driver_t smsc_driver = { .name = "smsc", .methods = smsc_methods, .size = sizeof(struct smsc_softc), }; static devclass_t smsc_devclass; DRIVER_MODULE(smsc, uhub, smsc_driver, smsc_devclass, NULL, 0); DRIVER_MODULE(miibus, smsc, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(smsc, uether, 1, 1, 1); MODULE_DEPEND(smsc, usb, 1, 1, 1); MODULE_DEPEND(smsc, ether, 1, 1, 1); MODULE_DEPEND(smsc, miibus, 1, 1, 1); MODULE_VERSION(smsc, 1); USB_PNP_HOST_INFO(smsc_devs); Index: head/sys/dev/usb/net/if_udav.c =================================================================== --- head/sys/dev/usb/net/if_udav.c (revision 357971) +++ head/sys/dev/usb/net/if_udav.c (revision 357972) @@ -1,889 +1,890 @@ /* $NetBSD: if_udav.c,v 1.2 2003/09/04 15:17:38 tsutsui Exp $ */ /* $nabe: if_udav.c,v 1.3 2003/08/21 16:57:19 nabe Exp $ */ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2003 * Shingo WATANABE . 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. * 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. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ /* * DM9601(DAVICOM USB to Ethernet MAC Controller with Integrated 10/100 PHY) * The spec can be found at the following url. * http://ptm2.cc.utu.fi/ftp/network/cards/DM9601/From_NET/DM9601-DS-P01-930914.pdf */ /* * TODO: * Interrupt Endpoint support * External PHYs */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include "miibus_if.h" #define USB_DEBUG_VAR udav_debug #include #include #include #include /* prototypes */ static device_probe_t udav_probe; static device_attach_t udav_attach; static device_detach_t udav_detach; static usb_callback_t udav_bulk_write_callback; static usb_callback_t udav_bulk_read_callback; static usb_callback_t udav_intr_callback; static uether_fn_t udav_attach_post; static uether_fn_t udav_init; static uether_fn_t udav_stop; static uether_fn_t udav_start; static uether_fn_t udav_tick; static uether_fn_t udav_setmulti; static uether_fn_t udav_setpromisc; static int udav_csr_read(struct udav_softc *, uint16_t, void *, int); static int udav_csr_write(struct udav_softc *, uint16_t, void *, int); static uint8_t udav_csr_read1(struct udav_softc *, uint16_t); static int udav_csr_write1(struct udav_softc *, uint16_t, uint8_t); static void udav_reset(struct udav_softc *); static int udav_ifmedia_upd(struct ifnet *); static void udav_ifmedia_status(struct ifnet *, struct ifmediareq *); static miibus_readreg_t udav_miibus_readreg; static miibus_writereg_t udav_miibus_writereg; static miibus_statchg_t udav_miibus_statchg; static const struct usb_config udav_config[UDAV_N_TRANSFER] = { [UDAV_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (MCLBYTES + 2), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = udav_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [UDAV_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + 3), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = udav_bulk_read_callback, .timeout = 0, /* no timeout */ }, [UDAV_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = udav_intr_callback, }, }; static device_method_t udav_methods[] = { /* Device interface */ DEVMETHOD(device_probe, udav_probe), DEVMETHOD(device_attach, udav_attach), DEVMETHOD(device_detach, udav_detach), /* MII interface */ DEVMETHOD(miibus_readreg, udav_miibus_readreg), DEVMETHOD(miibus_writereg, udav_miibus_writereg), DEVMETHOD(miibus_statchg, udav_miibus_statchg), DEVMETHOD_END }; static driver_t udav_driver = { .name = "udav", .methods = udav_methods, .size = sizeof(struct udav_softc), }; static devclass_t udav_devclass; static const STRUCT_USB_HOST_ID udav_devs[] = { /* ShanTou DM9601 USB NIC */ {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_DM9601, 0)}, /* ShanTou ST268 USB NIC */ {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ST268, 0)}, /* Corega USB-TXC */ {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC, 0)}, /* ShanTou AMD8515 USB NIC */ {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ADM8515, 0)}, /* Kontron AG USB Ethernet */ {USB_VPI(USB_VENDOR_KONTRON, USB_PRODUCT_KONTRON_DM9601, 0)}, {USB_VPI(USB_VENDOR_KONTRON, USB_PRODUCT_KONTRON_JP1082, UDAV_FLAG_NO_PHY)}, }; DRIVER_MODULE(udav, uhub, udav_driver, udav_devclass, NULL, 0); DRIVER_MODULE(miibus, udav, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(udav, uether, 1, 1, 1); MODULE_DEPEND(udav, usb, 1, 1, 1); MODULE_DEPEND(udav, ether, 1, 1, 1); MODULE_DEPEND(udav, miibus, 1, 1, 1); MODULE_VERSION(udav, 1); USB_PNP_HOST_INFO(udav_devs); static const struct usb_ether_methods udav_ue_methods = { .ue_attach_post = udav_attach_post, .ue_start = udav_start, .ue_init = udav_init, .ue_stop = udav_stop, .ue_tick = udav_tick, .ue_setmulti = udav_setmulti, .ue_setpromisc = udav_setpromisc, .ue_mii_upd = udav_ifmedia_upd, .ue_mii_sts = udav_ifmedia_status, }; static const struct usb_ether_methods udav_ue_methods_nophy = { .ue_attach_post = udav_attach_post, .ue_start = udav_start, .ue_init = udav_init, .ue_stop = udav_stop, .ue_setmulti = udav_setmulti, .ue_setpromisc = udav_setpromisc, }; #ifdef USB_DEBUG static int udav_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, udav, CTLFLAG_RW, 0, "USB udav"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, udav, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB udav"); SYSCTL_INT(_hw_usb_udav, OID_AUTO, debug, CTLFLAG_RWTUN, &udav_debug, 0, "Debug level"); #endif #define UDAV_SETBIT(sc, reg, x) \ udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) | (x)) #define UDAV_CLRBIT(sc, reg, x) \ udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) & ~(x)) static void udav_attach_post(struct usb_ether *ue) { struct udav_softc *sc = uether_getsc(ue); /* reset the adapter */ udav_reset(sc); /* Get Ethernet Address */ udav_csr_read(sc, UDAV_PAR, ue->ue_eaddr, ETHER_ADDR_LEN); } static int udav_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != UDAV_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != UDAV_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(udav_devs, sizeof(udav_devs), uaa)); } static int udav_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct udav_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; sc->sc_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = UDAV_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, udav_config, UDAV_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } /* * The JP1082 has an unusable PHY and provides no link information. */ if (sc->sc_flags & UDAV_FLAG_NO_PHY) { ue->ue_methods = &udav_ue_methods_nophy; sc->sc_flags |= UDAV_FLAG_LINK; } else { ue->ue_methods = &udav_ue_methods; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: udav_detach(dev); return (ENXIO); /* failure */ } static int udav_detach(device_t dev) { struct udav_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, UDAV_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } #if 0 static int udav_mem_read(struct udav_softc *sc, uint16_t offset, void *buf, int len) { struct usb_device_request req; len &= 0xff; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UDAV_REQ_MEM_READ; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static int udav_mem_write(struct udav_softc *sc, uint16_t offset, void *buf, int len) { struct usb_device_request req; len &= 0xff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_MEM_WRITE; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static int udav_mem_write1(struct udav_softc *sc, uint16_t offset, uint8_t ch) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_MEM_WRITE1; USETW(req.wValue, ch); USETW(req.wIndex, offset); USETW(req.wLength, 0x0000); return (uether_do_request(&sc->sc_ue, &req, NULL, 1000)); } #endif static int udav_csr_read(struct udav_softc *sc, uint16_t offset, void *buf, int len) { struct usb_device_request req; len &= 0xff; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UDAV_REQ_REG_READ; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static int udav_csr_write(struct udav_softc *sc, uint16_t offset, void *buf, int len) { struct usb_device_request req; offset &= 0xff; len &= 0xff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_REG_WRITE; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static uint8_t udav_csr_read1(struct udav_softc *sc, uint16_t offset) { uint8_t val; udav_csr_read(sc, offset, &val, 1); return (val); } static int udav_csr_write1(struct udav_softc *sc, uint16_t offset, uint8_t ch) { struct usb_device_request req; offset &= 0xff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_REG_WRITE1; USETW(req.wValue, ch); USETW(req.wIndex, offset); USETW(req.wLength, 0x0000); return (uether_do_request(&sc->sc_ue, &req, NULL, 1000)); } static void udav_init(struct usb_ether *ue) { struct udav_softc *sc = ue->ue_sc; struct ifnet *ifp = uether_getifp(&sc->sc_ue); UDAV_LOCK_ASSERT(sc, MA_OWNED); /* * Cancel pending I/O */ udav_stop(ue); /* set MAC address */ udav_csr_write(sc, UDAV_PAR, IF_LLADDR(ifp), ETHER_ADDR_LEN); /* initialize network control register */ /* disable loopback */ UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_LBK0 | UDAV_NCR_LBK1); /* Initialize RX control register */ UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_DIS_LONG | UDAV_RCR_DIS_CRC); /* load multicast filter and update promiscious mode bit */ udav_setpromisc(ue); /* enable RX */ UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_RXEN); /* clear POWER_DOWN state of internal PHY */ UDAV_SETBIT(sc, UDAV_GPCR, UDAV_GPCR_GEP_CNTL0); UDAV_CLRBIT(sc, UDAV_GPR, UDAV_GPR_GEPIO0); usbd_xfer_set_stall(sc->sc_xfer[UDAV_BULK_DT_WR]); ifp->if_drv_flags |= IFF_DRV_RUNNING; udav_start(ue); } static void udav_reset(struct udav_softc *sc) { int i; /* Select PHY */ #if 1 /* * XXX: force select internal phy. * external phy routines are not tested. */ UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); #else if (sc->sc_flags & UDAV_EXT_PHY) UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); else UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); #endif UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_RST); for (i = 0; i < UDAV_TX_TIMEOUT; i++) { if (!(udav_csr_read1(sc, UDAV_NCR) & UDAV_NCR_RST)) break; if (uether_pause(&sc->sc_ue, hz / 100)) break; } uether_pause(&sc->sc_ue, hz / 100); } static u_int udav_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint8_t *hashtbl = arg; int h; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; hashtbl[h / 8] |= 1 << (h % 8); return (1); } static void udav_setmulti(struct usb_ether *ue) { struct udav_softc *sc = ue->ue_sc; struct ifnet *ifp = uether_getifp(&sc->sc_ue); uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; UDAV_LOCK_ASSERT(sc, MA_OWNED); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); return; } /* first, zot all the existing hash bits */ memset(hashtbl, 0x00, sizeof(hashtbl)); hashtbl[7] |= 0x80; /* broadcast address */ udav_csr_write(sc, UDAV_MAR, hashtbl, sizeof(hashtbl)); /* now program new ones */ if_foreach_llmaddr(ifp, udav_hash_maddr, hashtbl); /* disable all multicast */ UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL); /* write hash value to the register */ udav_csr_write(sc, UDAV_MAR, hashtbl, sizeof(hashtbl)); } static void udav_setpromisc(struct usb_ether *ue) { struct udav_softc *sc = ue->ue_sc; struct ifnet *ifp = uether_getifp(&sc->sc_ue); uint8_t rxmode; rxmode = udav_csr_read1(sc, UDAV_RCR); rxmode &= ~(UDAV_RCR_ALL | UDAV_RCR_PRMSC); if (ifp->if_flags & IFF_PROMISC) rxmode |= UDAV_RCR_ALL | UDAV_RCR_PRMSC; else if (ifp->if_flags & IFF_ALLMULTI) rxmode |= UDAV_RCR_ALL; /* write new mode bits */ udav_csr_write1(sc, UDAV_RCR, rxmode); } static void udav_start(struct usb_ether *ue) { struct udav_softc *sc = ue->ue_sc; /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[UDAV_INTR_DT_RD]); usbd_transfer_start(sc->sc_xfer[UDAV_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[UDAV_BULK_DT_WR]); } static void udav_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct udav_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; int extra_len; int temp_len; uint8_t buf[2]; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & UDAV_FLAG_LINK) == 0) { /* * don't send anything if there is no link ! */ return; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; if (m->m_pkthdr.len > MCLBYTES) m->m_pkthdr.len = MCLBYTES; if (m->m_pkthdr.len < UDAV_MIN_FRAME_LEN) { extra_len = UDAV_MIN_FRAME_LEN - m->m_pkthdr.len; } else { extra_len = 0; } temp_len = (m->m_pkthdr.len + extra_len); /* * the frame length is specified in the first 2 bytes of the * buffer */ buf[0] = (uint8_t)(temp_len); buf[1] = (uint8_t)(temp_len >> 8); temp_len += 2; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, buf, 2); usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); if (extra_len) usbd_frame_zero(pc, temp_len - extra_len, extra_len); /* * if there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); m_freem(m); usbd_xfer_set_frame_len(xfer, 0, temp_len); usbd_transfer_submit(xfer); return; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void udav_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct udav_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct usb_page_cache *pc; struct udav_rxpkt stat; int len; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < (int)(sizeof(stat) + ETHER_CRC_LEN)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &stat, sizeof(stat)); actlen -= sizeof(stat); len = min(actlen, le16toh(stat.pktlen)); len -= ETHER_CRC_LEN; if (stat.rxstat & UDAV_RSR_LCS) { if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1); goto tr_setup; } if (stat.rxstat & UDAV_RSR_ERR) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } uether_rxbuf(ue, pc, sizeof(stat), len); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void udav_intr_callback(struct usb_xfer *xfer, usb_error_t error) { switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void udav_stop(struct usb_ether *ue) { struct udav_softc *sc = ue->ue_sc; struct ifnet *ifp = uether_getifp(&sc->sc_ue); UDAV_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; if (!(sc->sc_flags & UDAV_FLAG_NO_PHY)) sc->sc_flags &= ~UDAV_FLAG_LINK; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[UDAV_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[UDAV_BULK_DT_RD]); usbd_transfer_stop(sc->sc_xfer[UDAV_INTR_DT_RD]); udav_reset(sc); } static int udav_ifmedia_upd(struct ifnet *ifp) { struct udav_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); struct mii_softc *miisc; int error; UDAV_LOCK_ASSERT(sc, MA_OWNED); sc->sc_flags &= ~UDAV_FLAG_LINK; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } static void udav_ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr) { struct udav_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); UDAV_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; UDAV_UNLOCK(sc); } static void udav_tick(struct usb_ether *ue) { struct udav_softc *sc = ue->ue_sc; struct mii_data *mii = GET_MII(sc); UDAV_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & UDAV_FLAG_LINK) == 0 && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->sc_flags |= UDAV_FLAG_LINK; udav_start(ue); } } static int udav_miibus_readreg(device_t dev, int phy, int reg) { struct udav_softc *sc = device_get_softc(dev); uint16_t data16; uint8_t val[2]; int locked; /* XXX: one PHY only for the internal PHY */ if (phy != 0) return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) UDAV_LOCK(sc); /* select internal PHY and set PHY register address */ udav_csr_write1(sc, UDAV_EPAR, UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); /* select PHY operation and start read command */ udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRR); /* XXX: should we wait? */ /* end read command */ UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRR); /* retrieve the result from data registers */ udav_csr_read(sc, UDAV_EPDRL, val, 2); data16 = (val[0] | (val[1] << 8)); DPRINTFN(11, "phy=%d reg=0x%04x => 0x%04x\n", phy, reg, data16); if (!locked) UDAV_UNLOCK(sc); return (data16); } static int udav_miibus_writereg(device_t dev, int phy, int reg, int data) { struct udav_softc *sc = device_get_softc(dev); uint8_t val[2]; int locked; /* XXX: one PHY only for the internal PHY */ if (phy != 0) return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) UDAV_LOCK(sc); /* select internal PHY and set PHY register address */ udav_csr_write1(sc, UDAV_EPAR, UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); /* put the value to the data registers */ val[0] = (data & 0xff); val[1] = (data >> 8) & 0xff; udav_csr_write(sc, UDAV_EPDRL, val, 2); /* select PHY operation and start write command */ udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRW); /* XXX: should we wait? */ /* end write command */ UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRW); if (!locked) UDAV_UNLOCK(sc); return (0); } static void udav_miibus_statchg(device_t dev) { /* nothing to do */ } Index: head/sys/dev/usb/net/if_ure.c =================================================================== --- head/sys/dev/usb/net/if_ure.c (revision 357971) +++ head/sys/dev/usb/net/if_ure.c (revision 357972) @@ -1,1281 +1,1282 @@ /*- * Copyright (c) 2015-2016 Kevin Lo * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ure_debug #include #include #include #include #include "miibus_if.h" #ifdef USB_DEBUG static int ure_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ure, CTLFLAG_RW, 0, "USB ure"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ure, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ure"); SYSCTL_INT(_hw_usb_ure, OID_AUTO, debug, CTLFLAG_RWTUN, &ure_debug, 0, "Debug level"); #endif /* * Various supported device vendors/products. */ static const STRUCT_USB_HOST_ID ure_devs[] = { #define URE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } URE_DEV(LENOVO, RTL8153, 0), URE_DEV(LENOVO, TBT3LAN, 0), URE_DEV(LENOVO, ONELINK, 0), URE_DEV(LENOVO, USBCLAN, 0), URE_DEV(NVIDIA, RTL8153, 0), URE_DEV(REALTEK, RTL8152, URE_FLAG_8152), URE_DEV(REALTEK, RTL8153, 0), URE_DEV(TPLINK, RTL8153, 0), #undef URE_DEV }; static device_probe_t ure_probe; static device_attach_t ure_attach; static device_detach_t ure_detach; static usb_callback_t ure_bulk_read_callback; static usb_callback_t ure_bulk_write_callback; static miibus_readreg_t ure_miibus_readreg; static miibus_writereg_t ure_miibus_writereg; static miibus_statchg_t ure_miibus_statchg; static uether_fn_t ure_attach_post; static uether_fn_t ure_init; static uether_fn_t ure_stop; static uether_fn_t ure_start; static uether_fn_t ure_tick; static uether_fn_t ure_rxfilter; static int ure_ctl(struct ure_softc *, uint8_t, uint16_t, uint16_t, void *, int); static int ure_read_mem(struct ure_softc *, uint16_t, uint16_t, void *, int); static int ure_write_mem(struct ure_softc *, uint16_t, uint16_t, void *, int); static uint8_t ure_read_1(struct ure_softc *, uint16_t, uint16_t); static uint16_t ure_read_2(struct ure_softc *, uint16_t, uint16_t); static uint32_t ure_read_4(struct ure_softc *, uint16_t, uint16_t); static int ure_write_1(struct ure_softc *, uint16_t, uint16_t, uint32_t); static int ure_write_2(struct ure_softc *, uint16_t, uint16_t, uint32_t); static int ure_write_4(struct ure_softc *, uint16_t, uint16_t, uint32_t); static uint16_t ure_ocp_reg_read(struct ure_softc *, uint16_t); static void ure_ocp_reg_write(struct ure_softc *, uint16_t, uint16_t); static void ure_read_chipver(struct ure_softc *); static int ure_attach_post_sub(struct usb_ether *); static void ure_reset(struct ure_softc *); static int ure_ifmedia_upd(struct ifnet *); static void ure_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int ure_ioctl(struct ifnet *, u_long, caddr_t); static void ure_rtl8152_init(struct ure_softc *); static void ure_rtl8153_init(struct ure_softc *); static void ure_disable_teredo(struct ure_softc *); static void ure_init_fifo(struct ure_softc *); static const struct usb_config ure_config[URE_N_TRANSFER] = { [URE_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = MCLBYTES, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = ure_bulk_write_callback, .timeout = 10000, /* 10 seconds */ }, [URE_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 16384, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = ure_bulk_read_callback, .timeout = 0, /* no timeout */ }, }; static device_method_t ure_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, ure_probe), DEVMETHOD(device_attach, ure_attach), DEVMETHOD(device_detach, ure_detach), /* MII interface. */ DEVMETHOD(miibus_readreg, ure_miibus_readreg), DEVMETHOD(miibus_writereg, ure_miibus_writereg), DEVMETHOD(miibus_statchg, ure_miibus_statchg), DEVMETHOD_END }; static driver_t ure_driver = { .name = "ure", .methods = ure_methods, .size = sizeof(struct ure_softc), }; static devclass_t ure_devclass; DRIVER_MODULE(ure, uhub, ure_driver, ure_devclass, NULL, NULL); DRIVER_MODULE(miibus, ure, miibus_driver, miibus_devclass, NULL, NULL); MODULE_DEPEND(ure, uether, 1, 1, 1); MODULE_DEPEND(ure, usb, 1, 1, 1); MODULE_DEPEND(ure, ether, 1, 1, 1); MODULE_DEPEND(ure, miibus, 1, 1, 1); MODULE_VERSION(ure, 1); USB_PNP_HOST_INFO(ure_devs); static const struct usb_ether_methods ure_ue_methods = { .ue_attach_post = ure_attach_post, .ue_attach_post_sub = ure_attach_post_sub, .ue_start = ure_start, .ue_init = ure_init, .ue_stop = ure_stop, .ue_tick = ure_tick, .ue_setmulti = ure_rxfilter, .ue_setpromisc = ure_rxfilter, .ue_mii_upd = ure_ifmedia_upd, .ue_mii_sts = ure_ifmedia_sts, }; static int ure_ctl(struct ure_softc *sc, uint8_t rw, uint16_t val, uint16_t index, void *buf, int len) { struct usb_device_request req; URE_LOCK_ASSERT(sc, MA_OWNED); if (rw == URE_CTL_WRITE) req.bmRequestType = UT_WRITE_VENDOR_DEVICE; else req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UR_SET_ADDRESS; USETW(req.wValue, val); USETW(req.wIndex, index); USETW(req.wLength, len); return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); } static int ure_read_mem(struct ure_softc *sc, uint16_t addr, uint16_t index, void *buf, int len) { return (ure_ctl(sc, URE_CTL_READ, addr, index, buf, len)); } static int ure_write_mem(struct ure_softc *sc, uint16_t addr, uint16_t index, void *buf, int len) { return (ure_ctl(sc, URE_CTL_WRITE, addr, index, buf, len)); } static uint8_t ure_read_1(struct ure_softc *sc, uint16_t reg, uint16_t index) { uint32_t val; uint8_t temp[4]; uint8_t shift; shift = (reg & 3) << 3; reg &= ~3; ure_read_mem(sc, reg, index, &temp, 4); val = UGETDW(temp); val >>= shift; return (val & 0xff); } static uint16_t ure_read_2(struct ure_softc *sc, uint16_t reg, uint16_t index) { uint32_t val; uint8_t temp[4]; uint8_t shift; shift = (reg & 2) << 3; reg &= ~3; ure_read_mem(sc, reg, index, &temp, 4); val = UGETDW(temp); val >>= shift; return (val & 0xffff); } static uint32_t ure_read_4(struct ure_softc *sc, uint16_t reg, uint16_t index) { uint8_t temp[4]; ure_read_mem(sc, reg, index, &temp, 4); return (UGETDW(temp)); } static int ure_write_1(struct ure_softc *sc, uint16_t reg, uint16_t index, uint32_t val) { uint16_t byen; uint8_t temp[4]; uint8_t shift; byen = URE_BYTE_EN_BYTE; shift = reg & 3; val &= 0xff; if (reg & 3) { byen <<= shift; val <<= (shift << 3); reg &= ~3; } USETDW(temp, val); return (ure_write_mem(sc, reg, index | byen, &temp, 4)); } static int ure_write_2(struct ure_softc *sc, uint16_t reg, uint16_t index, uint32_t val) { uint16_t byen; uint8_t temp[4]; uint8_t shift; byen = URE_BYTE_EN_WORD; shift = reg & 2; val &= 0xffff; if (reg & 2) { byen <<= shift; val <<= (shift << 3); reg &= ~3; } USETDW(temp, val); return (ure_write_mem(sc, reg, index | byen, &temp, 4)); } static int ure_write_4(struct ure_softc *sc, uint16_t reg, uint16_t index, uint32_t val) { uint8_t temp[4]; USETDW(temp, val); return (ure_write_mem(sc, reg, index | URE_BYTE_EN_DWORD, &temp, 4)); } static uint16_t ure_ocp_reg_read(struct ure_softc *sc, uint16_t addr) { uint16_t reg; ure_write_2(sc, URE_PLA_OCP_GPHY_BASE, URE_MCU_TYPE_PLA, addr & 0xf000); reg = (addr & 0x0fff) | 0xb000; return (ure_read_2(sc, reg, URE_MCU_TYPE_PLA)); } static void ure_ocp_reg_write(struct ure_softc *sc, uint16_t addr, uint16_t data) { uint16_t reg; ure_write_2(sc, URE_PLA_OCP_GPHY_BASE, URE_MCU_TYPE_PLA, addr & 0xf000); reg = (addr & 0x0fff) | 0xb000; ure_write_2(sc, reg, URE_MCU_TYPE_PLA, data); } static int ure_miibus_readreg(device_t dev, int phy, int reg) { struct ure_softc *sc; uint16_t val; int locked; sc = device_get_softc(dev); locked = mtx_owned(&sc->sc_mtx); if (!locked) URE_LOCK(sc); /* Let the rgephy driver read the URE_GMEDIASTAT register. */ if (reg == URE_GMEDIASTAT) { if (!locked) URE_UNLOCK(sc); return (ure_read_1(sc, URE_GMEDIASTAT, URE_MCU_TYPE_PLA)); } val = ure_ocp_reg_read(sc, URE_OCP_BASE_MII + reg * 2); if (!locked) URE_UNLOCK(sc); return (val); } static int ure_miibus_writereg(device_t dev, int phy, int reg, int val) { struct ure_softc *sc; int locked; sc = device_get_softc(dev); if (sc->sc_phyno != phy) return (0); locked = mtx_owned(&sc->sc_mtx); if (!locked) URE_LOCK(sc); ure_ocp_reg_write(sc, URE_OCP_BASE_MII + reg * 2, val); if (!locked) URE_UNLOCK(sc); return (0); } static void ure_miibus_statchg(device_t dev) { struct ure_softc *sc; struct mii_data *mii; struct ifnet *ifp; int locked; sc = device_get_softc(dev); mii = GET_MII(sc); locked = mtx_owned(&sc->sc_mtx); if (!locked) URE_LOCK(sc); ifp = uether_getifp(&sc->sc_ue); if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) goto done; sc->sc_flags &= ~URE_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->sc_flags |= URE_FLAG_LINK; break; case IFM_1000_T: if ((sc->sc_flags & URE_FLAG_8152) != 0) break; sc->sc_flags |= URE_FLAG_LINK; break; default: break; } } /* Lost link, do nothing. */ if ((sc->sc_flags & URE_FLAG_LINK) == 0) goto done; done: if (!locked) URE_UNLOCK(sc); } /* * Probe for a RTL8152/RTL8153 chip. */ static int ure_probe(device_t dev) { struct usb_attach_arg *uaa; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != URE_CONFIG_IDX) return (ENXIO); if (uaa->info.bIfaceIndex != URE_IFACE_IDX) return (ENXIO); return (usbd_lookup_id_by_uaa(ure_devs, sizeof(ure_devs), uaa)); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int ure_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ure_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; uint8_t iface_index; int error; sc->sc_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); iface_index = URE_IFACE_IDX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, ure_config, URE_N_TRANSFER, sc, &sc->sc_mtx); if (error != 0) { device_printf(dev, "allocating USB transfers failed\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &ure_ue_methods; error = uether_ifattach(ue); if (error != 0) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: ure_detach(dev); return (ENXIO); /* failure */ } static int ure_detach(device_t dev) { struct ure_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; usbd_transfer_unsetup(sc->sc_xfer, URE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void ure_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ure_softc *sc = usbd_xfer_softc(xfer); struct usb_ether *ue = &sc->sc_ue; struct ifnet *ifp = uether_getifp(ue); struct usb_page_cache *pc; struct ure_rxpkt pkt; int actlen, len; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < (int)(sizeof(pkt))) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); len = le32toh(pkt.ure_pktlen) & URE_RXPKT_LEN_MASK; len -= ETHER_CRC_LEN; if (actlen < (int)(len + sizeof(pkt))) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } uether_rxbuf(ue, pc, sizeof(pkt), len); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); uether_rxflush(ue); return; default: /* Error */ DPRINTF("bulk read error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ure_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ure_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc; struct mbuf *m; struct ure_txpkt txpkt; int len, pos; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if ((sc->sc_flags & URE_FLAG_LINK) == 0 || (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { /* * don't send anything if there is no link ! */ return; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; pos = 0; len = m->m_pkthdr.len; pc = usbd_xfer_get_frame(xfer, 0); memset(&txpkt, 0, sizeof(txpkt)); txpkt.ure_pktlen = htole32((len & URE_TXPKT_LEN_MASK) | URE_TKPKT_TX_FS | URE_TKPKT_TX_LS); usbd_copy_in(pc, pos, &txpkt, sizeof(txpkt)); pos += sizeof(txpkt); usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len); pos += m->m_pkthdr.len; if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* * If there's a BPF listener, bounce a copy * of this frame to him. */ BPF_MTAP(ifp, m); m_freem(m); /* Set frame length. */ usbd_xfer_set_frame_len(xfer, 0, pos); usbd_transfer_submit(xfer); ifp->if_drv_flags |= IFF_DRV_OACTIVE; return; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ure_read_chipver(struct ure_softc *sc) { uint16_t ver; ver = ure_read_2(sc, URE_PLA_TCR1, URE_MCU_TYPE_PLA) & URE_VERSION_MASK; switch (ver) { case 0x4c00: sc->sc_chip |= URE_CHIP_VER_4C00; break; case 0x4c10: sc->sc_chip |= URE_CHIP_VER_4C10; break; case 0x5c00: sc->sc_chip |= URE_CHIP_VER_5C00; break; case 0x5c10: sc->sc_chip |= URE_CHIP_VER_5C10; break; case 0x5c20: sc->sc_chip |= URE_CHIP_VER_5C20; break; case 0x5c30: sc->sc_chip |= URE_CHIP_VER_5C30; break; default: device_printf(sc->sc_ue.ue_dev, "unknown version 0x%04x\n", ver); break; } } static void ure_attach_post(struct usb_ether *ue) { struct ure_softc *sc = uether_getsc(ue); sc->sc_phyno = 0; /* Determine the chip version. */ ure_read_chipver(sc); /* Initialize controller and get station address. */ if (sc->sc_flags & URE_FLAG_8152) ure_rtl8152_init(sc); else ure_rtl8153_init(sc); if ((sc->sc_chip & URE_CHIP_VER_4C00) || (sc->sc_chip & URE_CHIP_VER_4C10)) ure_read_mem(sc, URE_PLA_IDR, URE_MCU_TYPE_PLA, ue->ue_eaddr, 8); else ure_read_mem(sc, URE_PLA_BACKUP, URE_MCU_TYPE_PLA, ue->ue_eaddr, 8); if (ETHER_IS_ZERO(sc->sc_ue.ue_eaddr)) { device_printf(sc->sc_ue.ue_dev, "MAC assigned randomly\n"); arc4rand(sc->sc_ue.ue_eaddr, ETHER_ADDR_LEN, 0); sc->sc_ue.ue_eaddr[0] &= ~0x01; /* unicast */ sc->sc_ue.ue_eaddr[0] |= 0x02; /* locally administered */ } } static int ure_attach_post_sub(struct usb_ether *ue) { struct ure_softc *sc; struct ifnet *ifp; int error; sc = uether_getsc(ue); ifp = ue->ue_ifp; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = uether_start; ifp->if_ioctl = ure_ioctl; ifp->if_init = uether_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); mtx_lock(&Giant); error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, uether_ifmedia_upd, ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, 0); mtx_unlock(&Giant); return (error); } static void ure_init(struct usb_ether *ue) { struct ure_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); URE_LOCK_ASSERT(sc, MA_OWNED); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* Cancel pending I/O. */ ure_stop(ue); ure_reset(sc); /* Set MAC address. */ ure_write_1(sc, URE_PLA_CRWECR, URE_MCU_TYPE_PLA, URE_CRWECR_CONFIG); ure_write_mem(sc, URE_PLA_IDR, URE_MCU_TYPE_PLA | URE_BYTE_EN_SIX_BYTES, IF_LLADDR(ifp), 8); ure_write_1(sc, URE_PLA_CRWECR, URE_MCU_TYPE_PLA, URE_CRWECR_NORAML); /* Reset the packet filter. */ ure_write_2(sc, URE_PLA_FMC, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_FMC, URE_MCU_TYPE_PLA) & ~URE_FMC_FCR_MCU_EN); ure_write_2(sc, URE_PLA_FMC, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_FMC, URE_MCU_TYPE_PLA) | URE_FMC_FCR_MCU_EN); /* Enable transmit and receive. */ ure_write_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, ure_read_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA) | URE_CR_RE | URE_CR_TE); ure_write_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA) & ~URE_RXDY_GATED_EN); /* Configure RX filters. */ ure_rxfilter(ue); usbd_xfer_set_stall(sc->sc_xfer[URE_BULK_DT_WR]); /* Indicate we are up and running. */ ifp->if_drv_flags |= IFF_DRV_RUNNING; /* Switch to selected media. */ ure_ifmedia_upd(ifp); } static void ure_tick(struct usb_ether *ue) { struct ure_softc *sc = uether_getsc(ue); struct mii_data *mii = GET_MII(sc); URE_LOCK_ASSERT(sc, MA_OWNED); mii_tick(mii); if ((sc->sc_flags & URE_FLAG_LINK) == 0 && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->sc_flags |= URE_FLAG_LINK; ure_start(ue); } } static u_int ure_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t h, *hashes = arg; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; if (h < 32) hashes[0] |= (1 << h); else hashes[1] |= (1 << (h - 32)); return (1); } /* * Program the 64-bit multicast hash filter. */ static void ure_rxfilter(struct usb_ether *ue) { struct ure_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); uint32_t rxmode; uint32_t h, hashes[2] = { 0, 0 }; URE_LOCK_ASSERT(sc, MA_OWNED); rxmode = URE_RCR_APM; if (ifp->if_flags & IFF_BROADCAST) rxmode |= URE_RCR_AB; if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { if (ifp->if_flags & IFF_PROMISC) rxmode |= URE_RCR_AAP; rxmode |= URE_RCR_AM; hashes[0] = hashes[1] = 0xffffffff; goto done; } rxmode |= URE_RCR_AM; if_foreach_llmaddr(ifp, ure_hash_maddr, &hashes); h = bswap32(hashes[0]); hashes[0] = bswap32(hashes[1]); hashes[1] = h; rxmode |= URE_RCR_AM; done: ure_write_4(sc, URE_PLA_MAR0, URE_MCU_TYPE_PLA, hashes[0]); ure_write_4(sc, URE_PLA_MAR4, URE_MCU_TYPE_PLA, hashes[1]); ure_write_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA, rxmode); } static void ure_start(struct usb_ether *ue) { struct ure_softc *sc = uether_getsc(ue); /* * start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[URE_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[URE_BULK_DT_WR]); } static void ure_reset(struct ure_softc *sc) { int i; ure_write_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, URE_CR_RST); for (i = 0; i < URE_TIMEOUT; i++) { if (!(ure_read_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA) & URE_CR_RST)) break; uether_pause(&sc->sc_ue, hz / 100); } if (i == URE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "reset never completed\n"); } /* * Set media options. */ static int ure_ifmedia_upd(struct ifnet *ifp) { struct ure_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); struct mii_softc *miisc; int error; URE_LOCK_ASSERT(sc, MA_OWNED); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } /* * Report current media status. */ static void ure_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct ure_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = GET_MII(sc); URE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; URE_UNLOCK(sc); } static int ure_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct usb_ether *ue = ifp->if_softc; struct ure_softc *sc; struct ifreq *ifr; int error, mask, reinit; sc = uether_getsc(ue); ifr = (struct ifreq *)data; error = 0; reinit = 0; if (cmd == SIOCSIFCAP) { URE_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if (reinit > 0 && ifp->if_drv_flags & IFF_DRV_RUNNING) ifp->if_drv_flags &= ~IFF_DRV_RUNNING; else reinit = 0; URE_UNLOCK(sc); if (reinit > 0) uether_init(ue); } else error = uether_ioctl(ifp, cmd, data); return (error); } static void ure_rtl8152_init(struct ure_softc *sc) { uint32_t pwrctrl; /* Disable ALDPS. */ ure_ocp_reg_write(sc, URE_OCP_ALDPS_CONFIG, URE_ENPDNPS | URE_LINKENA | URE_DIS_SDSAVE); uether_pause(&sc->sc_ue, hz / 50); if (sc->sc_chip & URE_CHIP_VER_4C00) { ure_write_2(sc, URE_PLA_LED_FEATURE, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_LED_FEATURE, URE_MCU_TYPE_PLA) & ~URE_LED_MODE_MASK); } ure_write_2(sc, URE_USB_UPS_CTRL, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_UPS_CTRL, URE_MCU_TYPE_USB) & ~URE_POWER_CUT); ure_write_2(sc, URE_USB_PM_CTRL_STATUS, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_PM_CTRL_STATUS, URE_MCU_TYPE_USB) & ~URE_RESUME_INDICATE); ure_write_2(sc, URE_PLA_PHY_PWR, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_PHY_PWR, URE_MCU_TYPE_PLA) | URE_TX_10M_IDLE_EN | URE_PFM_PWM_SWITCH); pwrctrl = ure_read_4(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA); pwrctrl &= ~URE_MCU_CLK_RATIO_MASK; pwrctrl |= URE_MCU_CLK_RATIO | URE_D3_CLK_GATED_EN; ure_write_4(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA, pwrctrl); ure_write_2(sc, URE_PLA_GPHY_INTR_IMR, URE_MCU_TYPE_PLA, URE_GPHY_STS_MSK | URE_SPEED_DOWN_MSK | URE_SPDWN_RXDV_MSK | URE_SPDWN_LINKCHG_MSK); /* Disable Rx aggregation. */ ure_write_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB) | URE_RX_AGG_DISABLE); /* Disable ALDPS. */ ure_ocp_reg_write(sc, URE_OCP_ALDPS_CONFIG, URE_ENPDNPS | URE_LINKENA | URE_DIS_SDSAVE); uether_pause(&sc->sc_ue, hz / 50); ure_init_fifo(sc); ure_write_1(sc, URE_USB_TX_AGG, URE_MCU_TYPE_USB, URE_TX_AGG_MAX_THRESHOLD); ure_write_4(sc, URE_USB_RX_BUF_TH, URE_MCU_TYPE_USB, URE_RX_THR_HIGH); ure_write_4(sc, URE_USB_TX_DMA, URE_MCU_TYPE_USB, URE_TEST_MODE_DISABLE | URE_TX_SIZE_ADJUST1); } static void ure_rtl8153_init(struct ure_softc *sc) { uint16_t val; uint8_t u1u2[8]; int i; /* Disable ALDPS. */ ure_ocp_reg_write(sc, URE_OCP_POWER_CFG, ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) & ~URE_EN_ALDPS); uether_pause(&sc->sc_ue, hz / 50); memset(u1u2, 0x00, sizeof(u1u2)); ure_write_mem(sc, URE_USB_TOLERANCE, URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2)); for (i = 0; i < URE_TIMEOUT; i++) { if (ure_read_2(sc, URE_PLA_BOOT_CTRL, URE_MCU_TYPE_PLA) & URE_AUTOLOAD_DONE) break; uether_pause(&sc->sc_ue, hz / 100); } if (i == URE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "timeout waiting for chip autoload\n"); for (i = 0; i < URE_TIMEOUT; i++) { val = ure_ocp_reg_read(sc, URE_OCP_PHY_STATUS) & URE_PHY_STAT_MASK; if (val == URE_PHY_STAT_LAN_ON || val == URE_PHY_STAT_PWRDN) break; uether_pause(&sc->sc_ue, hz / 100); } if (i == URE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "timeout waiting for phy to stabilize\n"); ure_write_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB) & ~URE_U2P3_ENABLE); if (sc->sc_chip & URE_CHIP_VER_5C10) { val = ure_read_2(sc, URE_USB_SSPHYLINK2, URE_MCU_TYPE_USB); val &= ~URE_PWD_DN_SCALE_MASK; val |= URE_PWD_DN_SCALE(96); ure_write_2(sc, URE_USB_SSPHYLINK2, URE_MCU_TYPE_USB, val); ure_write_1(sc, URE_USB_USB2PHY, URE_MCU_TYPE_USB, ure_read_1(sc, URE_USB_USB2PHY, URE_MCU_TYPE_USB) | URE_USB2PHY_L1 | URE_USB2PHY_SUSPEND); } else if (sc->sc_chip & URE_CHIP_VER_5C20) { ure_write_1(sc, URE_PLA_DMY_REG0, URE_MCU_TYPE_PLA, ure_read_1(sc, URE_PLA_DMY_REG0, URE_MCU_TYPE_PLA) & ~URE_ECM_ALDPS); } if (sc->sc_chip & (URE_CHIP_VER_5C20 | URE_CHIP_VER_5C30)) { val = ure_read_1(sc, URE_USB_CSR_DUMMY1, URE_MCU_TYPE_USB); if (ure_read_2(sc, URE_USB_BURST_SIZE, URE_MCU_TYPE_USB) == 0) val &= ~URE_DYNAMIC_BURST; else val |= URE_DYNAMIC_BURST; ure_write_1(sc, URE_USB_CSR_DUMMY1, URE_MCU_TYPE_USB, val); } ure_write_1(sc, URE_USB_CSR_DUMMY2, URE_MCU_TYPE_USB, ure_read_1(sc, URE_USB_CSR_DUMMY2, URE_MCU_TYPE_USB) | URE_EP4_FULL_FC); ure_write_2(sc, URE_USB_WDT11_CTRL, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_WDT11_CTRL, URE_MCU_TYPE_USB) & ~URE_TIMER11_EN); ure_write_2(sc, URE_PLA_LED_FEATURE, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_LED_FEATURE, URE_MCU_TYPE_PLA) & ~URE_LED_MODE_MASK); if ((sc->sc_chip & URE_CHIP_VER_5C10) && usbd_get_speed(sc->sc_ue.ue_udev) != USB_SPEED_SUPER) val = URE_LPM_TIMER_500MS; else val = URE_LPM_TIMER_500US; ure_write_1(sc, URE_USB_LPM_CTRL, URE_MCU_TYPE_USB, val | URE_FIFO_EMPTY_1FB | URE_ROK_EXIT_LPM); val = ure_read_2(sc, URE_USB_AFE_CTRL2, URE_MCU_TYPE_USB); val &= ~URE_SEN_VAL_MASK; val |= URE_SEN_VAL_NORMAL | URE_SEL_RXIDLE; ure_write_2(sc, URE_USB_AFE_CTRL2, URE_MCU_TYPE_USB, val); ure_write_2(sc, URE_USB_CONNECT_TIMER, URE_MCU_TYPE_USB, 0x0001); ure_write_2(sc, URE_USB_POWER_CUT, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_POWER_CUT, URE_MCU_TYPE_USB) & ~(URE_PWR_EN | URE_PHASE2_EN)); ure_write_2(sc, URE_USB_MISC_0, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_MISC_0, URE_MCU_TYPE_USB) & ~URE_PCUT_STATUS); memset(u1u2, 0xff, sizeof(u1u2)); ure_write_mem(sc, URE_USB_TOLERANCE, URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2)); ure_write_2(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA, URE_ALDPS_SPDWN_RATIO); ure_write_2(sc, URE_PLA_MAC_PWR_CTRL2, URE_MCU_TYPE_PLA, URE_EEE_SPDWN_RATIO); ure_write_2(sc, URE_PLA_MAC_PWR_CTRL3, URE_MCU_TYPE_PLA, URE_PKT_AVAIL_SPDWN_EN | URE_SUSPEND_SPDWN_EN | URE_U1U2_SPDWN_EN | URE_L1_SPDWN_EN); ure_write_2(sc, URE_PLA_MAC_PWR_CTRL4, URE_MCU_TYPE_PLA, URE_PWRSAVE_SPDWN_EN | URE_RXDV_SPDWN_EN | URE_TX10MIDLE_EN | URE_TP100_SPDWN_EN | URE_TP500_SPDWN_EN | URE_TP1000_SPDWN_EN | URE_EEE_SPDWN_EN); val = ure_read_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB); if (!(sc->sc_chip & (URE_CHIP_VER_5C00 | URE_CHIP_VER_5C10))) val |= URE_U2P3_ENABLE; else val &= ~URE_U2P3_ENABLE; ure_write_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, val); memset(u1u2, 0x00, sizeof(u1u2)); ure_write_mem(sc, URE_USB_TOLERANCE, URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2)); /* Disable ALDPS. */ ure_ocp_reg_write(sc, URE_OCP_POWER_CFG, ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) & ~URE_EN_ALDPS); uether_pause(&sc->sc_ue, hz / 50); ure_init_fifo(sc); /* Disable Rx aggregation. */ ure_write_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB, ure_read_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB) | URE_RX_AGG_DISABLE); val = ure_read_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB); if (!(sc->sc_chip & (URE_CHIP_VER_5C00 | URE_CHIP_VER_5C10))) val |= URE_U2P3_ENABLE; else val &= ~URE_U2P3_ENABLE; ure_write_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, val); memset(u1u2, 0xff, sizeof(u1u2)); ure_write_mem(sc, URE_USB_TOLERANCE, URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2)); } static void ure_stop(struct usb_ether *ue) { struct ure_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); URE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->sc_flags &= ~URE_FLAG_LINK; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[URE_BULK_DT_WR]); usbd_transfer_stop(sc->sc_xfer[URE_BULK_DT_RD]); } static void ure_disable_teredo(struct ure_softc *sc) { ure_write_4(sc, URE_PLA_TEREDO_CFG, URE_MCU_TYPE_PLA, ure_read_4(sc, URE_PLA_TEREDO_CFG, URE_MCU_TYPE_PLA) & ~(URE_TEREDO_SEL | URE_TEREDO_RS_EVENT_MASK | URE_OOB_TEREDO_EN)); ure_write_2(sc, URE_PLA_WDT6_CTRL, URE_MCU_TYPE_PLA, URE_WDT6_SET_MODE); ure_write_2(sc, URE_PLA_REALWOW_TIMER, URE_MCU_TYPE_PLA, 0); ure_write_4(sc, URE_PLA_TEREDO_TIMER, URE_MCU_TYPE_PLA, 0); } static void ure_init_fifo(struct ure_softc *sc) { uint32_t rx_fifo1, rx_fifo2; int i; ure_write_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA) | URE_RXDY_GATED_EN); ure_disable_teredo(sc); ure_write_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA, ure_read_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA) & ~URE_RCR_ACPT_ALL); if (!(sc->sc_flags & URE_FLAG_8152)) { if (sc->sc_chip & (URE_CHIP_VER_5C00 | URE_CHIP_VER_5C10 | URE_CHIP_VER_5C20)) { ure_ocp_reg_write(sc, URE_OCP_ADC_CFG, URE_CKADSEL_L | URE_ADC_EN | URE_EN_EMI_L); } if (sc->sc_chip & URE_CHIP_VER_5C00) { ure_ocp_reg_write(sc, URE_OCP_EEE_CFG, ure_ocp_reg_read(sc, URE_OCP_EEE_CFG) & ~URE_CTAP_SHORT_EN); } ure_ocp_reg_write(sc, URE_OCP_POWER_CFG, ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) | URE_EEE_CLKDIV_EN); ure_ocp_reg_write(sc, URE_OCP_DOWN_SPEED, ure_ocp_reg_read(sc, URE_OCP_DOWN_SPEED) | URE_EN_10M_BGOFF); ure_ocp_reg_write(sc, URE_OCP_POWER_CFG, ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) | URE_EN_10M_PLLOFF); ure_ocp_reg_write(sc, URE_OCP_SRAM_ADDR, URE_SRAM_IMPEDANCE); ure_ocp_reg_write(sc, URE_OCP_SRAM_DATA, 0x0b13); ure_write_2(sc, URE_PLA_PHY_PWR, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_PHY_PWR, URE_MCU_TYPE_PLA) | URE_PFM_PWM_SWITCH); /* Enable LPF corner auto tune. */ ure_ocp_reg_write(sc, URE_OCP_SRAM_ADDR, URE_SRAM_LPF_CFG); ure_ocp_reg_write(sc, URE_OCP_SRAM_DATA, 0xf70f); /* Adjust 10M amplitude. */ ure_ocp_reg_write(sc, URE_OCP_SRAM_ADDR, URE_SRAM_10M_AMP1); ure_ocp_reg_write(sc, URE_OCP_SRAM_DATA, 0x00af); ure_ocp_reg_write(sc, URE_OCP_SRAM_ADDR, URE_SRAM_10M_AMP2); ure_ocp_reg_write(sc, URE_OCP_SRAM_DATA, 0x0208); } ure_reset(sc); ure_write_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, 0); ure_write_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA, ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) & ~URE_NOW_IS_OOB); ure_write_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA) & ~URE_MCU_BORW_EN); for (i = 0; i < URE_TIMEOUT; i++) { if (ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) & URE_LINK_LIST_READY) break; uether_pause(&sc->sc_ue, hz / 100); } if (i == URE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "timeout waiting for OOB control\n"); ure_write_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA) | URE_RE_INIT_LL); for (i = 0; i < URE_TIMEOUT; i++) { if (ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) & URE_LINK_LIST_READY) break; uether_pause(&sc->sc_ue, hz / 100); } if (i == URE_TIMEOUT) device_printf(sc->sc_ue.ue_dev, "timeout waiting for OOB control\n"); ure_write_2(sc, URE_PLA_CPCR, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_CPCR, URE_MCU_TYPE_PLA) & ~URE_CPCR_RX_VLAN); ure_write_2(sc, URE_PLA_TCR0, URE_MCU_TYPE_PLA, ure_read_2(sc, URE_PLA_TCR0, URE_MCU_TYPE_PLA) | URE_TCR0_AUTO_FIFO); /* Configure Rx FIFO threshold. */ ure_write_4(sc, URE_PLA_RXFIFO_CTRL0, URE_MCU_TYPE_PLA, URE_RXFIFO_THR1_NORMAL); if (usbd_get_speed(sc->sc_ue.ue_udev) == USB_SPEED_FULL) { rx_fifo1 = URE_RXFIFO_THR2_FULL; rx_fifo2 = URE_RXFIFO_THR3_FULL; } else { rx_fifo1 = URE_RXFIFO_THR2_HIGH; rx_fifo2 = URE_RXFIFO_THR3_HIGH; } ure_write_4(sc, URE_PLA_RXFIFO_CTRL1, URE_MCU_TYPE_PLA, rx_fifo1); ure_write_4(sc, URE_PLA_RXFIFO_CTRL2, URE_MCU_TYPE_PLA, rx_fifo2); /* Configure Tx FIFO threshold. */ ure_write_4(sc, URE_PLA_TXFIFO_CTRL, URE_MCU_TYPE_PLA, URE_TXFIFO_THR_NORMAL); } Index: head/sys/dev/usb/net/if_urndis.c =================================================================== --- head/sys/dev/usb/net/if_urndis.c (revision 357971) +++ head/sys/dev/usb/net/if_urndis.c (revision 357972) @@ -1,1058 +1,1059 @@ /* $OpenBSD: if_urndis.c,v 1.46 2013/12/09 15:45:29 pirofti Exp $ */ /* * Copyright (c) 2010 Jonathan Armani * Copyright (c) 2010 Fabien Romano * Copyright (c) 2010 Michael Knudsen * Copyright (c) 2014 Hans Petter Selasky * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR urndis_debug #include #include #include "usb_if.h" #include #include #include static device_probe_t urndis_probe; static device_attach_t urndis_attach; static device_detach_t urndis_detach; static device_suspend_t urndis_suspend; static device_resume_t urndis_resume; static usb_callback_t urndis_bulk_write_callback; static usb_callback_t urndis_bulk_read_callback; static usb_callback_t urndis_intr_read_callback; static uether_fn_t urndis_attach_post; static uether_fn_t urndis_init; static uether_fn_t urndis_stop; static uether_fn_t urndis_start; static uether_fn_t urndis_setmulti; static uether_fn_t urndis_setpromisc; static uint32_t urndis_ctrl_query(struct urndis_softc *sc, uint32_t oid, struct rndis_query_req *msg, uint16_t len, const void **rbuf, uint16_t *rbufsz); static uint32_t urndis_ctrl_set(struct urndis_softc *sc, uint32_t oid, struct rndis_set_req *msg, uint16_t len); static uint32_t urndis_ctrl_handle_init(struct urndis_softc *sc, const struct rndis_comp_hdr *hdr); static uint32_t urndis_ctrl_handle_query(struct urndis_softc *sc, const struct rndis_comp_hdr *hdr, const void **buf, uint16_t *bufsz); static uint32_t urndis_ctrl_handle_reset(struct urndis_softc *sc, const struct rndis_comp_hdr *hdr); static uint32_t urndis_ctrl_init(struct urndis_softc *sc); static uint32_t urndis_ctrl_halt(struct urndis_softc *sc); #ifdef USB_DEBUG static int urndis_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, urndis, CTLFLAG_RW, 0, "USB RNDIS-Ethernet"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, urndis, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB RNDIS-Ethernet"); SYSCTL_INT(_hw_usb_urndis, OID_AUTO, debug, CTLFLAG_RWTUN, &urndis_debug, 0, "Debug level"); #endif static const struct usb_config urndis_config[URNDIS_N_TRANSFER] = { [URNDIS_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .frames = 1, .bufsize = RNDIS_RX_MAXLEN, .flags = {.short_xfer_ok = 1,}, .callback = urndis_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_HOST, }, [URNDIS_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .frames = RNDIS_TX_FRAMES_MAX, .bufsize = (RNDIS_TX_FRAMES_MAX * RNDIS_TX_MAXLEN), .flags = { .force_short_xfer = 1, }, .callback = urndis_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_HOST, }, [URNDIS_INTR_RX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,}, .callback = urndis_intr_read_callback, .timeout = 0, .usb_mode = USB_MODE_HOST, }, }; static device_method_t urndis_methods[] = { /* Device interface */ DEVMETHOD(device_probe, urndis_probe), DEVMETHOD(device_attach, urndis_attach), DEVMETHOD(device_detach, urndis_detach), DEVMETHOD(device_suspend, urndis_suspend), DEVMETHOD(device_resume, urndis_resume), DEVMETHOD_END }; static driver_t urndis_driver = { .name = "urndis", .methods = urndis_methods, .size = sizeof(struct urndis_softc), }; static devclass_t urndis_devclass; static const STRUCT_USB_HOST_ID urndis_host_devs[] = { /* Generic RNDIS class match */ {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(0xff)}, {USB_IFACE_CLASS(UICLASS_WIRELESS), USB_IFACE_SUBCLASS(UISUBCLASS_RF), USB_IFACE_PROTOCOL(UIPROTO_RNDIS)}, {USB_IFACE_CLASS(UICLASS_IAD), USB_IFACE_SUBCLASS(UISUBCLASS_SYNC), USB_IFACE_PROTOCOL(UIPROTO_ACTIVESYNC)}, /* HP-WebOS */ {USB_VENDOR(USB_VENDOR_PALM), USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(0xff)}, /* Nokia 7 plus */ {USB_IFACE_CLASS(UICLASS_IAD), USB_IFACE_SUBCLASS(0x4), USB_IFACE_PROTOCOL(UIPROTO_ACTIVESYNC)}, }; DRIVER_MODULE(urndis, uhub, urndis_driver, urndis_devclass, NULL, NULL); MODULE_VERSION(urndis, 1); MODULE_DEPEND(urndis, uether, 1, 1, 1); MODULE_DEPEND(urndis, usb, 1, 1, 1); MODULE_DEPEND(urndis, ether, 1, 1, 1); USB_PNP_HOST_INFO(urndis_host_devs); static const struct usb_ether_methods urndis_ue_methods = { .ue_attach_post = urndis_attach_post, .ue_start = urndis_start, .ue_init = urndis_init, .ue_stop = urndis_stop, .ue_setmulti = urndis_setmulti, .ue_setpromisc = urndis_setpromisc, }; static int urndis_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); return (usbd_lookup_id_by_uaa(urndis_host_devs, sizeof(urndis_host_devs), uaa)); } static void urndis_attach_post(struct usb_ether *ue) { /* no-op */ } static int urndis_attach(device_t dev) { static struct { union { struct rndis_query_req query; struct rndis_set_req set; } hdr; union { uint8_t eaddr[ETHER_ADDR_LEN]; uint32_t filter; } ibuf; } msg; struct urndis_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_cdc_cm_descriptor *cmd; const void *buf; uint16_t bufsz; uint8_t iface_index[2] = { uaa->info.bIfaceIndex + 1, uaa->info.bIfaceIndex }; int error; uint8_t i; sc->sc_ue.ue_udev = uaa->device; sc->sc_ifaceno_ctl = uaa->info.bIfaceNum; cmd = usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_CM, 0xFF); if (cmd != NULL) { DPRINTF("Call Mode Descriptor found, dataif=%d\n", cmd->bDataInterface); iface_index[0] = cmd->bDataInterface; } device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* scan the alternate settings looking for a valid one */ for (i = 0; i != 32; i++) { error = usbd_set_alt_interface_index(uaa->device, iface_index[0], i); if (error != 0) break; error = usbd_transfer_setup(uaa->device, iface_index, sc->sc_xfer, urndis_config, URNDIS_N_TRANSFER, sc, &sc->sc_mtx); if (error == 0) break; } if ((error != 0) || (i == 32)) { device_printf(dev, "No valid alternate setting found\n"); goto detach; } /* Initialize device - must be done before even querying it */ URNDIS_LOCK(sc); error = urndis_ctrl_init(sc); URNDIS_UNLOCK(sc); if (error != (int)RNDIS_STATUS_SUCCESS) { device_printf(dev, "Unable to initialize hardware\n"); goto detach; } /* Determine MAC address */ memset(msg.ibuf.eaddr, 0, sizeof(msg.ibuf.eaddr)); URNDIS_LOCK(sc); error = urndis_ctrl_query(sc, OID_802_3_PERMANENT_ADDRESS, &msg.hdr.query, sizeof(msg.hdr.query) + sizeof(msg.ibuf.eaddr), &buf, &bufsz); URNDIS_UNLOCK(sc); if (error != (int)RNDIS_STATUS_SUCCESS) { device_printf(dev, "Unable to get hardware address\n"); goto detach; } if (bufsz != ETHER_ADDR_LEN) { device_printf(dev, "Invalid address length: %d bytes\n", bufsz); goto detach; } memcpy(&sc->sc_ue.ue_eaddr, buf, ETHER_ADDR_LEN); /* Initialize packet filter */ sc->sc_filter = NDIS_PACKET_TYPE_BROADCAST | NDIS_PACKET_TYPE_ALL_MULTICAST; msg.ibuf.filter = htole32(sc->sc_filter); URNDIS_LOCK(sc); error = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER, &msg.hdr.set, sizeof(msg.hdr.set) + sizeof(msg.ibuf.filter)); URNDIS_UNLOCK(sc); if (error != (int)RNDIS_STATUS_SUCCESS) { device_printf(dev, "Unable to set data filters\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &urndis_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "Could not attach interface\n"); goto detach; } URNDIS_LOCK(sc); /* start interrupt endpoint, if any */ usbd_transfer_start(sc->sc_xfer[URNDIS_INTR_RX]); URNDIS_UNLOCK(sc); return (0); /* success */ detach: (void)urndis_detach(dev); return (ENXIO); /* failure */ } static int urndis_detach(device_t dev) { struct urndis_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; /* stop all USB transfers first */ usbd_transfer_unsetup(sc->sc_xfer, URNDIS_N_TRANSFER); uether_ifdetach(ue); URNDIS_LOCK(sc); (void)urndis_ctrl_halt(sc); URNDIS_UNLOCK(sc); mtx_destroy(&sc->sc_mtx); return (0); } static void urndis_start(struct usb_ether *ue) { struct urndis_softc *sc = uether_getsc(ue); /* * Start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[URNDIS_BULK_TX]); usbd_transfer_start(sc->sc_xfer[URNDIS_BULK_RX]); } static void urndis_init(struct usb_ether *ue) { struct urndis_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); URNDIS_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags |= IFF_DRV_RUNNING; /* stall data write direction, which depends on USB mode */ usbd_xfer_set_stall(sc->sc_xfer[URNDIS_BULK_TX]); /* start data transfers */ urndis_start(ue); } static void urndis_stop(struct usb_ether *ue) { struct urndis_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); URNDIS_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[URNDIS_BULK_RX]); usbd_transfer_stop(sc->sc_xfer[URNDIS_BULK_TX]); } static void urndis_setmulti(struct usb_ether *ue) { /* no-op */ } static void urndis_setpromisc(struct usb_ether *ue) { /* no-op */ } static int urndis_suspend(device_t dev) { device_printf(dev, "Suspending\n"); return (0); } static int urndis_resume(device_t dev) { device_printf(dev, "Resuming\n"); return (0); } static usb_error_t urndis_ctrl_msg(struct urndis_softc *sc, uint8_t rt, uint8_t r, uint16_t index, uint16_t value, void *buf, uint16_t buflen) { usb_device_request_t req; req.bmRequestType = rt; req.bRequest = r; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, buflen); return (usbd_do_request_flags(sc->sc_ue.ue_udev, &sc->sc_mtx, &req, buf, (rt & UT_READ) ? USB_SHORT_XFER_OK : 0, NULL, 2000 /* ms */ )); } static usb_error_t urndis_ctrl_send(struct urndis_softc *sc, void *buf, uint16_t len) { usb_error_t err; err = urndis_ctrl_msg(sc, UT_WRITE_CLASS_INTERFACE, UCDC_SEND_ENCAPSULATED_COMMAND, sc->sc_ifaceno_ctl, 0, buf, len); DPRINTF("%s\n", usbd_errstr(err)); return (err); } static struct rndis_comp_hdr * urndis_ctrl_recv(struct urndis_softc *sc) { struct rndis_comp_hdr *hdr; usb_error_t err; err = urndis_ctrl_msg(sc, UT_READ_CLASS_INTERFACE, UCDC_GET_ENCAPSULATED_RESPONSE, sc->sc_ifaceno_ctl, 0, sc->sc_response_buf, RNDIS_RESPONSE_LEN); if (err != USB_ERR_NORMAL_COMPLETION) return (NULL); hdr = (struct rndis_comp_hdr *)sc->sc_response_buf; DPRINTF("type 0x%x len %u\n", le32toh(hdr->rm_type), le32toh(hdr->rm_len)); if (le32toh(hdr->rm_len) > RNDIS_RESPONSE_LEN) { DPRINTF("ctrl message error: wrong size %u > %u\n", le32toh(hdr->rm_len), RNDIS_RESPONSE_LEN); return (NULL); } return (hdr); } static uint32_t urndis_ctrl_handle(struct urndis_softc *sc, struct rndis_comp_hdr *hdr, const void **buf, uint16_t *bufsz) { uint32_t rval; DPRINTF("\n"); if (buf != NULL && bufsz != NULL) { *buf = NULL; *bufsz = 0; } switch (le32toh(hdr->rm_type)) { case REMOTE_NDIS_INITIALIZE_CMPLT: rval = urndis_ctrl_handle_init(sc, hdr); break; case REMOTE_NDIS_QUERY_CMPLT: rval = urndis_ctrl_handle_query(sc, hdr, buf, bufsz); break; case REMOTE_NDIS_RESET_CMPLT: rval = urndis_ctrl_handle_reset(sc, hdr); break; case REMOTE_NDIS_KEEPALIVE_CMPLT: case REMOTE_NDIS_SET_CMPLT: rval = le32toh(hdr->rm_status); break; default: device_printf(sc->sc_ue.ue_dev, "ctrl message error: unknown event 0x%x\n", le32toh(hdr->rm_type)); rval = RNDIS_STATUS_FAILURE; break; } return (rval); } static uint32_t urndis_ctrl_handle_init(struct urndis_softc *sc, const struct rndis_comp_hdr *hdr) { const struct rndis_init_comp *msg; msg = (const struct rndis_init_comp *)hdr; DPRINTF("len %u rid %u status 0x%x " "ver_major %u ver_minor %u devflags 0x%x medium 0x%x pktmaxcnt %u " "pktmaxsz %u align %u aflistoffset %u aflistsz %u\n", le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_status), le32toh(msg->rm_ver_major), le32toh(msg->rm_ver_minor), le32toh(msg->rm_devflags), le32toh(msg->rm_medium), le32toh(msg->rm_pktmaxcnt), le32toh(msg->rm_pktmaxsz), le32toh(msg->rm_align), le32toh(msg->rm_aflistoffset), le32toh(msg->rm_aflistsz)); if (le32toh(msg->rm_status) != RNDIS_STATUS_SUCCESS) { DPRINTF("init failed 0x%x\n", le32toh(msg->rm_status)); return (le32toh(msg->rm_status)); } if (le32toh(msg->rm_devflags) != RNDIS_DF_CONNECTIONLESS) { DPRINTF("wrong device type (current type: 0x%x)\n", le32toh(msg->rm_devflags)); return (RNDIS_STATUS_FAILURE); } if (le32toh(msg->rm_medium) != RNDIS_MEDIUM_802_3) { DPRINTF("medium not 802.3 (current medium: 0x%x)\n", le32toh(msg->rm_medium)); return (RNDIS_STATUS_FAILURE); } sc->sc_lim_pktsz = le32toh(msg->rm_pktmaxsz); return (le32toh(msg->rm_status)); } static uint32_t urndis_ctrl_handle_query(struct urndis_softc *sc, const struct rndis_comp_hdr *hdr, const void **buf, uint16_t *bufsz) { const struct rndis_query_comp *msg; uint64_t limit; msg = (const struct rndis_query_comp *)hdr; DPRINTF("len %u rid %u status 0x%x " "buflen %u bufoff %u\n", le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_status), le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset)); *buf = NULL; *bufsz = 0; if (le32toh(msg->rm_status) != RNDIS_STATUS_SUCCESS) { DPRINTF("query failed 0x%x\n", le32toh(msg->rm_status)); return (le32toh(msg->rm_status)); } limit = le32toh(msg->rm_infobuflen); limit += le32toh(msg->rm_infobufoffset); limit += RNDIS_HEADER_OFFSET; if (limit > (uint64_t)le32toh(msg->rm_len)) { DPRINTF("ctrl message error: invalid query info " "len/offset/end_position(%u/%u/%u) -> " "go out of buffer limit %u\n", le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset), le32toh(msg->rm_infobuflen) + le32toh(msg->rm_infobufoffset) + RNDIS_HEADER_OFFSET, le32toh(msg->rm_len)); return (RNDIS_STATUS_FAILURE); } *buf = ((const uint8_t *)msg) + RNDIS_HEADER_OFFSET + le32toh(msg->rm_infobufoffset); *bufsz = le32toh(msg->rm_infobuflen); return (le32toh(msg->rm_status)); } static uint32_t urndis_ctrl_handle_reset(struct urndis_softc *sc, const struct rndis_comp_hdr *hdr) { const struct rndis_reset_comp *msg; uint32_t rval; msg = (const struct rndis_reset_comp *)hdr; rval = le32toh(msg->rm_status); DPRINTF("len %u status 0x%x " "adrreset %u\n", le32toh(msg->rm_len), rval, le32toh(msg->rm_adrreset)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("reset failed 0x%x\n", rval); return (rval); } if (msg->rm_adrreset != 0) { struct { struct rndis_set_req hdr; uint32_t filter; } msg_filter; msg_filter.filter = htole32(sc->sc_filter); rval = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER, &msg_filter.hdr, sizeof(msg_filter)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("unable to reset data filters\n"); return (rval); } } return (rval); } static uint32_t urndis_ctrl_init(struct urndis_softc *sc) { struct rndis_init_req msg; struct rndis_comp_hdr *hdr; uint32_t rval; msg.rm_type = htole32(REMOTE_NDIS_INITIALIZE_MSG); msg.rm_len = htole32(sizeof(msg)); msg.rm_rid = 0; msg.rm_ver_major = htole32(RNDIS_VERSION_MAJOR); msg.rm_ver_minor = htole32(1); msg.rm_max_xfersz = htole32(RNDIS_RX_MAXLEN); DPRINTF("type %u len %u rid %u ver_major %u " "ver_minor %u max_xfersz %u\n", le32toh(msg.rm_type), le32toh(msg.rm_len), le32toh(msg.rm_rid), le32toh(msg.rm_ver_major), le32toh(msg.rm_ver_minor), le32toh(msg.rm_max_xfersz)); rval = urndis_ctrl_send(sc, &msg, sizeof(msg)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("init failed\n"); return (rval); } if ((hdr = urndis_ctrl_recv(sc)) == NULL) { DPRINTF("unable to get init response\n"); return (RNDIS_STATUS_FAILURE); } rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); return (rval); } static uint32_t urndis_ctrl_halt(struct urndis_softc *sc) { struct rndis_halt_req msg; uint32_t rval; msg.rm_type = htole32(REMOTE_NDIS_HALT_MSG); msg.rm_len = htole32(sizeof(msg)); msg.rm_rid = 0; DPRINTF("type %u len %u rid %u\n", le32toh(msg.rm_type), le32toh(msg.rm_len), le32toh(msg.rm_rid)); rval = urndis_ctrl_send(sc, &msg, sizeof(msg)); if (rval != RNDIS_STATUS_SUCCESS) DPRINTF("halt failed\n"); return (rval); } /* * NB: Querying a device has the requirement of using an input buffer the size * of the expected reply or larger, except for variably sized replies. */ static uint32_t urndis_ctrl_query(struct urndis_softc *sc, uint32_t oid, struct rndis_query_req *msg, uint16_t len, const void **rbuf, uint16_t *rbufsz) { struct rndis_comp_hdr *hdr; uint32_t datalen, rval; msg->rm_type = htole32(REMOTE_NDIS_QUERY_MSG); msg->rm_len = htole32(len); msg->rm_rid = 0; /* XXX */ msg->rm_oid = htole32(oid); datalen = len - sizeof(*msg); msg->rm_infobuflen = htole32(datalen); if (datalen != 0) { msg->rm_infobufoffset = htole32(sizeof(*msg) - RNDIS_HEADER_OFFSET); } else { msg->rm_infobufoffset = 0; } msg->rm_devicevchdl = 0; DPRINTF("type %u len %u rid %u oid 0x%x " "infobuflen %u infobufoffset %u devicevchdl %u\n", le32toh(msg->rm_type), le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_oid), le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset), le32toh(msg->rm_devicevchdl)); rval = urndis_ctrl_send(sc, msg, len); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("query failed\n"); return (rval); } if ((hdr = urndis_ctrl_recv(sc)) == NULL) { DPRINTF("unable to get query response\n"); return (RNDIS_STATUS_FAILURE); } rval = urndis_ctrl_handle(sc, hdr, rbuf, rbufsz); return (rval); } static uint32_t urndis_ctrl_set(struct urndis_softc *sc, uint32_t oid, struct rndis_set_req *msg, uint16_t len) { struct rndis_comp_hdr *hdr; uint32_t datalen, rval; msg->rm_type = htole32(REMOTE_NDIS_SET_MSG); msg->rm_len = htole32(len); msg->rm_rid = 0; /* XXX */ msg->rm_oid = htole32(oid); datalen = len - sizeof(*msg); msg->rm_infobuflen = htole32(datalen); if (datalen != 0) { msg->rm_infobufoffset = htole32(sizeof(*msg) - RNDIS_HEADER_OFFSET); } else { msg->rm_infobufoffset = 0; } msg->rm_devicevchdl = 0; DPRINTF("type %u len %u rid %u oid 0x%x " "infobuflen %u infobufoffset %u devicevchdl %u\n", le32toh(msg->rm_type), le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_oid), le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset), le32toh(msg->rm_devicevchdl)); rval = urndis_ctrl_send(sc, msg, len); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("set failed\n"); return (rval); } if ((hdr = urndis_ctrl_recv(sc)) == NULL) { DPRINTF("unable to get set response\n"); return (RNDIS_STATUS_FAILURE); } rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); if (rval != RNDIS_STATUS_SUCCESS) DPRINTF("set failed 0x%x\n", rval); return (rval); } static void urndis_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct urndis_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct rndis_packet_msg msg; struct mbuf *m; int actlen; int aframes; int offset; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "received %u bytes in %u frames\n", actlen, aframes); for (offset = 0; actlen >= (uint32_t)sizeof(msg);) { /* copy out header */ usbd_copy_out(pc, offset, &msg, sizeof(msg)); if (le32toh(0x1234567U) != 0x1234567U) { /* swap endianness */ msg.rm_type = le32toh(msg.rm_type); msg.rm_len = le32toh(msg.rm_len); msg.rm_dataoffset = le32toh(msg.rm_dataoffset); msg.rm_datalen = le32toh(msg.rm_datalen); msg.rm_oobdataoffset = le32toh(msg.rm_oobdataoffset); msg.rm_oobdatalen = le32toh(msg.rm_oobdatalen); msg.rm_oobdataelements = le32toh(msg.rm_oobdataelements); msg.rm_pktinfooffset = le32toh(msg.rm_pktinfooffset); msg.rm_pktinfolen = le32toh(msg.rm_pktinfolen); msg.rm_vchandle = le32toh(msg.rm_vchandle); msg.rm_reserved = le32toh(msg.rm_reserved); } DPRINTF("len %u data(off:%u len:%u) " "oobdata(off:%u len:%u nb:%u) perpacket(off:%u len:%u)\n", msg.rm_len, msg.rm_dataoffset, msg.rm_datalen, msg.rm_oobdataoffset, msg.rm_oobdatalen, msg.rm_oobdataelements, msg.rm_pktinfooffset, msg.rm_pktinfooffset); /* sanity check the RNDIS header */ if (msg.rm_type != REMOTE_NDIS_PACKET_MSG) { DPRINTF("invalid type 0x%x != 0x%x\n", msg.rm_type, REMOTE_NDIS_PACKET_MSG); goto tr_setup; } else if (msg.rm_len < (uint32_t)sizeof(msg)) { DPRINTF("invalid msg len %u < %u\n", msg.rm_len, (unsigned)sizeof(msg)); goto tr_setup; } else if (msg.rm_len > (uint32_t)actlen) { DPRINTF("invalid msg len %u > buffer " "len %u\n", msg.rm_len, actlen); goto tr_setup; } else if (msg.rm_dataoffset >= (uint32_t)actlen) { DPRINTF("invalid msg dataoffset %u > buffer " "dataoffset %u\n", msg.rm_dataoffset, actlen); goto tr_setup; } else if (msg.rm_datalen > (uint32_t)actlen) { DPRINTF("invalid msg datalen %u > buffer " "datalen %u\n", msg.rm_datalen, actlen); goto tr_setup; } else if ((msg.rm_dataoffset + msg.rm_datalen + (uint32_t)__offsetof(struct rndis_packet_msg, rm_dataoffset)) > (uint32_t)actlen) { DPRINTF("invalid dataoffset %u larger than %u\n", msg.rm_dataoffset + msg.rm_datalen + (uint32_t)__offsetof(struct rndis_packet_msg, rm_dataoffset), actlen); goto tr_setup; } else if (msg.rm_datalen < (uint32_t)sizeof(struct ether_header)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); DPRINTF("invalid ethernet size " "%u < %u\n", msg.rm_datalen, (unsigned)sizeof(struct ether_header)); goto tr_setup; } else if (msg.rm_datalen > (uint32_t)(MCLBYTES - ETHER_ALIGN)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); DPRINTF("invalid ethernet size " "%u > %u\n", msg.rm_datalen, (unsigned)MCLBYTES); goto tr_setup; } else if (msg.rm_datalen > (uint32_t)(MHLEN - ETHER_ALIGN)) { m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); } else { m = m_gethdr(M_NOWAIT, MT_DATA); } /* check if we have a buffer */ if (m != NULL) { m->m_len = m->m_pkthdr.len = msg.rm_datalen + ETHER_ALIGN; m_adj(m, ETHER_ALIGN); usbd_copy_out(pc, offset + msg.rm_dataoffset + __offsetof(struct rndis_packet_msg, rm_dataoffset), m->m_data, msg.rm_datalen); /* enqueue */ uether_rxmbuf(&sc->sc_ue, m, msg.rm_datalen); } else { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); } offset += msg.rm_len; actlen -= msg.rm_len; } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, RNDIS_RX_MAXLEN); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); uether_rxflush(&sc->sc_ue); /* must be last */ break; default: /* Error */ DPRINTFN(1, "error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } break; } } static void urndis_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct rndis_packet_msg msg; struct urndis_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct mbuf *m; unsigned x; int actlen; int aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "%u bytes in %u frames\n", actlen, aframes); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: memset(&msg, 0, sizeof(msg)); for (x = 0; x != RNDIS_TX_FRAMES_MAX; x++) { struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, x); usbd_xfer_set_frame_offset(xfer, x * RNDIS_TX_MAXLEN, x); next_pkt: IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; if ((m->m_pkthdr.len + sizeof(msg)) > RNDIS_TX_MAXLEN) { DPRINTF("Too big packet\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* Free buffer */ m_freem(m); goto next_pkt; } msg.rm_type = htole32(REMOTE_NDIS_PACKET_MSG); msg.rm_len = htole32(sizeof(msg) + m->m_pkthdr.len); msg.rm_dataoffset = htole32(RNDIS_DATA_OFFSET); msg.rm_datalen = htole32(m->m_pkthdr.len); /* copy in all data */ usbd_copy_in(pc, 0, &msg, sizeof(msg)); usbd_m_copy_in(pc, sizeof(msg), m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, x, sizeof(msg) + m->m_pkthdr.len); /* * If there's a BPF listener, bounce a copy of * this frame to him: */ BPF_MTAP(ifp, m); /* Free buffer */ m_freem(m); } if (x != 0) { usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); /* count output errors */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void urndis_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("Received %d bytes\n", actlen); /* TODO: decode some indications */ /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* start clear stall */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } Index: head/sys/dev/usb/net/if_usie.c =================================================================== --- head/sys/dev/usb/net/if_usie.c (revision 357971) +++ head/sys/dev/usb/net/if_usie.c (revision 357972) @@ -1,1621 +1,1622 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2011 Anybots Inc * written by Akinori Furukoshi * - ucom part is based on u3g.c * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR usie_debug #include #include #include #include #include #ifdef USB_DEBUG static int usie_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, usie, CTLFLAG_RW, 0, "sierra USB modem"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, usie, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "sierra USB modem"); SYSCTL_INT(_hw_usb_usie, OID_AUTO, debug, CTLFLAG_RWTUN, &usie_debug, 0, "usie debug level"); #endif /* Sierra Wireless Direct IP modems */ static const STRUCT_USB_HOST_ID usie_devs[] = { #define USIE_DEV(v, d) { \ USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##d) } USIE_DEV(SIERRA, MC8700), USIE_DEV(SIERRA, TRUINSTALL), USIE_DEV(AIRPRIME, USB308), #undef USIE_DEV }; static device_probe_t usie_probe; static device_attach_t usie_attach; static device_detach_t usie_detach; static void usie_free_softc(struct usie_softc *); static void usie_free(struct ucom_softc *); static void usie_uc_update_line_state(struct ucom_softc *, uint8_t); static void usie_uc_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void usie_uc_cfg_set_dtr(struct ucom_softc *, uint8_t); static void usie_uc_cfg_set_rts(struct ucom_softc *, uint8_t); static void usie_uc_cfg_open(struct ucom_softc *); static void usie_uc_cfg_close(struct ucom_softc *); static void usie_uc_start_read(struct ucom_softc *); static void usie_uc_stop_read(struct ucom_softc *); static void usie_uc_start_write(struct ucom_softc *); static void usie_uc_stop_write(struct ucom_softc *); static usb_callback_t usie_uc_tx_callback; static usb_callback_t usie_uc_rx_callback; static usb_callback_t usie_uc_status_callback; static usb_callback_t usie_if_tx_callback; static usb_callback_t usie_if_rx_callback; static usb_callback_t usie_if_status_callback; static void usie_if_sync_to(void *); static void usie_if_sync_cb(void *, int); static void usie_if_status_cb(void *, int); static void usie_if_start(struct ifnet *); static int usie_if_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *); static void usie_if_init(void *); static void usie_if_stop(struct usie_softc *); static int usie_if_ioctl(struct ifnet *, u_long, caddr_t); static int usie_do_request(struct usie_softc *, struct usb_device_request *, void *); static int usie_if_cmd(struct usie_softc *, uint8_t); static void usie_cns_req(struct usie_softc *, uint32_t, uint16_t); static void usie_cns_rsp(struct usie_softc *, struct usie_cns *); static void usie_hip_rsp(struct usie_softc *, uint8_t *, uint32_t); static int usie_driver_loaded(struct module *, int, void *); static const struct usb_config usie_uc_config[USIE_UC_N_XFER] = { [USIE_UC_STATUS] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &usie_uc_status_callback, }, [USIE_UC_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = USIE_BUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, .callback = &usie_uc_rx_callback, }, [USIE_UC_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = USIE_BUFSIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &usie_uc_tx_callback, } }; static const struct usb_config usie_if_config[USIE_IF_N_XFER] = { [USIE_IF_STATUS] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &usie_if_status_callback, }, [USIE_IF_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = USIE_BUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &usie_if_rx_callback, }, [USIE_IF_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = MAX(USIE_BUFSIZE, MCLBYTES), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &usie_if_tx_callback, } }; static device_method_t usie_methods[] = { DEVMETHOD(device_probe, usie_probe), DEVMETHOD(device_attach, usie_attach), DEVMETHOD(device_detach, usie_detach), DEVMETHOD_END }; static driver_t usie_driver = { .name = "usie", .methods = usie_methods, .size = sizeof(struct usie_softc), }; static devclass_t usie_devclass; static eventhandler_tag usie_etag; DRIVER_MODULE(usie, uhub, usie_driver, usie_devclass, usie_driver_loaded, 0); MODULE_DEPEND(usie, ucom, 1, 1, 1); MODULE_DEPEND(usie, usb, 1, 1, 1); MODULE_VERSION(usie, 1); USB_PNP_HOST_INFO(usie_devs); static const struct ucom_callback usie_uc_callback = { .ucom_cfg_get_status = &usie_uc_cfg_get_status, .ucom_cfg_set_dtr = &usie_uc_cfg_set_dtr, .ucom_cfg_set_rts = &usie_uc_cfg_set_rts, .ucom_cfg_open = &usie_uc_cfg_open, .ucom_cfg_close = &usie_uc_cfg_close, .ucom_start_read = &usie_uc_start_read, .ucom_stop_read = &usie_uc_stop_read, .ucom_start_write = &usie_uc_start_write, .ucom_stop_write = &usie_uc_stop_write, .ucom_free = &usie_free, }; static void usie_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; struct usb_interface_descriptor *id; struct usb_device_request req; int err; if (uaa->dev_state != UAA_DEV_READY) return; iface = usbd_get_iface(udev, 0); if (iface == NULL) return; id = iface->idesc; if (id == NULL || id->bInterfaceClass != UICLASS_MASS) return; if (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa) != 0) return; /* no device match */ if (bootverbose) { DPRINTF("Ejecting %s %s\n", usb_get_manufacturer(udev), usb_get_product(udev)); } req.bmRequestType = UT_VENDOR; req.bRequest = UR_SET_INTERFACE; USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); USETW(req.wIndex, UHF_PORT_CONNECTION); USETW(req.wLength, 0); /* at this moment there is no mutex */ err = usbd_do_request_flags(udev, NULL, &req, NULL, 0, NULL, 250 /* ms */ ); /* success, mark the udev as disappearing */ if (err == 0) uaa->dev_state = UAA_DEV_EJECTING; } static int usie_probe(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != USIE_CNFG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != USIE_IFACE_INDEX) return (ENXIO); if (uaa->info.bInterfaceClass != UICLASS_VENDOR) return (ENXIO); return (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa)); } static int usie_attach(device_t self) { struct usie_softc *sc = device_get_softc(self); struct usb_attach_arg *uaa = device_get_ivars(self); struct ifnet *ifp; struct usb_interface *iface; struct usb_interface_descriptor *id; struct usb_device_request req; int err; uint16_t fwattr; uint8_t iface_index; uint8_t ifidx; uint8_t start; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; mtx_init(&sc->sc_mtx, "usie", MTX_NETWORK_LOCK, MTX_DEF); ucom_ref(&sc->sc_super_ucom); TASK_INIT(&sc->sc_if_status_task, 0, usie_if_status_cb, sc); TASK_INIT(&sc->sc_if_sync_task, 0, usie_if_sync_cb, sc); usb_callout_init_mtx(&sc->sc_if_sync_ch, &sc->sc_mtx, 0); mtx_lock(&sc->sc_mtx); /* set power mode to D0 */ req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = USIE_POWER; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 0); if (usie_do_request(sc, &req, NULL)) { mtx_unlock(&sc->sc_mtx); goto detach; } /* read fw attr */ fwattr = 0; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = USIE_FW_ATTR; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, sizeof(fwattr)); if (usie_do_request(sc, &req, &fwattr)) { mtx_unlock(&sc->sc_mtx); goto detach; } mtx_unlock(&sc->sc_mtx); /* check DHCP supports */ DPRINTF("fwattr=%x\n", fwattr); if (!(fwattr & USIE_FW_DHCP)) { device_printf(self, "DHCP is not supported. A firmware upgrade might be needed.\n"); } /* find available interfaces */ sc->sc_nucom = 0; for (ifidx = 0; ifidx < USIE_IFACE_MAX; ifidx++) { iface = usbd_get_iface(uaa->device, ifidx); if (iface == NULL) break; id = usbd_get_interface_descriptor(iface); if ((id == NULL) || (id->bInterfaceClass != UICLASS_VENDOR)) continue; /* setup Direct IP transfer */ if (id->bInterfaceNumber >= 7 && id->bNumEndpoints == 3) { sc->sc_if_ifnum = id->bInterfaceNumber; iface_index = ifidx; DPRINTF("ifnum=%d, ifidx=%d\n", sc->sc_if_ifnum, ifidx); err = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_if_xfer, usie_if_config, USIE_IF_N_XFER, sc, &sc->sc_mtx); if (err == 0) continue; device_printf(self, "could not allocate USB transfers on " "iface_index=%d, err=%s\n", iface_index, usbd_errstr(err)); goto detach; } /* setup ucom */ if (sc->sc_nucom >= USIE_UCOM_MAX) continue; usbd_set_parent_iface(uaa->device, ifidx, uaa->info.bIfaceIndex); DPRINTF("NumEndpoints=%d bInterfaceNumber=%d\n", id->bNumEndpoints, id->bInterfaceNumber); if (id->bNumEndpoints == 2) { sc->sc_uc_xfer[sc->sc_nucom][0] = NULL; start = 1; } else start = 0; err = usbd_transfer_setup(uaa->device, &ifidx, sc->sc_uc_xfer[sc->sc_nucom] + start, usie_uc_config + start, USIE_UC_N_XFER - start, &sc->sc_ucom[sc->sc_nucom], &sc->sc_mtx); if (err != 0) { DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err)); continue; } mtx_lock(&sc->sc_mtx); for (; start < USIE_UC_N_XFER; start++) usbd_xfer_set_stall(sc->sc_uc_xfer[sc->sc_nucom][start]); mtx_unlock(&sc->sc_mtx); sc->sc_uc_ifnum[sc->sc_nucom] = id->bInterfaceNumber; sc->sc_nucom++; /* found a port */ } if (sc->sc_nucom == 0) { device_printf(self, "no comports found\n"); goto detach; } err = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_nucom, sc, &usie_uc_callback, &sc->sc_mtx); if (err != 0) { DPRINTF("ucom_attach failed\n"); goto detach; } DPRINTF("Found %d interfaces.\n", sc->sc_nucom); /* setup ifnet (Direct IP) */ sc->sc_ifp = ifp = if_alloc(IFT_OTHER); if (ifp == NULL) { device_printf(self, "Could not allocate a network interface\n"); goto detach; } if_initname(ifp, "usie", device_get_unit(self)); ifp->if_softc = sc; ifp->if_mtu = USIE_MTU_MAX; ifp->if_flags |= IFF_NOARP; ifp->if_init = usie_if_init; ifp->if_ioctl = usie_if_ioctl; ifp->if_start = usie_if_start; ifp->if_output = usie_if_output; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); if_attach(ifp); bpfattach(ifp, DLT_RAW, 0); if (fwattr & USIE_PM_AUTO) { usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); DPRINTF("enabling automatic suspend and resume\n"); } else { usbd_set_power_mode(uaa->device, USB_POWER_MODE_ON); DPRINTF("USB power is always ON\n"); } DPRINTF("device attached\n"); return (0); detach: usie_detach(self); return (ENOMEM); } static int usie_detach(device_t self) { struct usie_softc *sc = device_get_softc(self); uint8_t x; /* detach ifnet */ if (sc->sc_ifp != NULL) { usie_if_stop(sc); usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); bpfdetach(sc->sc_ifp); if_detach(sc->sc_ifp); if_free(sc->sc_ifp); sc->sc_ifp = NULL; } /* detach ucom */ if (sc->sc_nucom > 0) ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); /* stop all USB transfers */ usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); for (x = 0; x != USIE_UCOM_MAX; x++) usbd_transfer_unsetup(sc->sc_uc_xfer[x], USIE_UC_N_XFER); device_claim_softc(self); usie_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(usie); static void usie_free_softc(struct usie_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void usie_free(struct ucom_softc *ucom) { usie_free_softc(ucom->sc_parent); } static void usie_uc_update_line_state(struct ucom_softc *ucom, uint8_t ls) { struct usie_softc *sc = ucom->sc_parent; struct usb_device_request req; if (sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS] == NULL) return; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = USIE_LINK_STATE; USETW(req.wValue, ls); USETW(req.wIndex, sc->sc_uc_ifnum[ucom->sc_subunit]); USETW(req.wLength, 0); DPRINTF("sc_uc_ifnum=%d\n", sc->sc_uc_ifnum[ucom->sc_subunit]); usie_do_request(sc, &req, NULL); } static void usie_uc_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct usie_softc *sc = ucom->sc_parent; *msr = sc->sc_msr; *lsr = sc->sc_lsr; } static void usie_uc_cfg_set_dtr(struct ucom_softc *ucom, uint8_t flag) { uint8_t dtr; dtr = flag ? USIE_LS_DTR : 0; usie_uc_update_line_state(ucom, dtr); } static void usie_uc_cfg_set_rts(struct ucom_softc *ucom, uint8_t flag) { uint8_t rts; rts = flag ? USIE_LS_RTS : 0; usie_uc_update_line_state(ucom, rts); } static void usie_uc_cfg_open(struct ucom_softc *ucom) { struct usie_softc *sc = ucom->sc_parent; /* usbd_transfer_start() is NULL safe */ usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]); } static void usie_uc_cfg_close(struct ucom_softc *ucom) { struct usie_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]); } static void usie_uc_start_read(struct ucom_softc *ucom) { struct usie_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]); } static void usie_uc_stop_read(struct ucom_softc *ucom) { struct usie_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]); } static void usie_uc_start_write(struct ucom_softc *ucom) { struct usie_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]); } static void usie_uc_stop_write(struct ucom_softc *ucom) { struct usie_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]); } static void usie_uc_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct ucom_softc *ucom = usbd_xfer_softc(xfer); struct usie_softc *sc = ucom->sc_parent; struct usb_page_cache *pc; uint32_t actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); /* handle CnS response */ if (ucom == sc->sc_ucom && actlen >= USIE_HIPCNS_MIN) { DPRINTF("transferred=%u\n", actlen); /* check if it is really CnS reply */ usbd_copy_out(pc, 0, sc->sc_resp_temp, 1); if (sc->sc_resp_temp[0] == USIE_HIP_FRM_CHR) { /* verify actlen */ if (actlen > USIE_BUFSIZE) actlen = USIE_BUFSIZE; /* get complete message */ usbd_copy_out(pc, 0, sc->sc_resp_temp, actlen); usie_hip_rsp(sc, sc->sc_resp_temp, actlen); /* need to fall though */ goto tr_setup; } /* else call ucom_put_data() */ } /* standard ucom transfer */ ucom_put_data(ucom, pc, 0, actlen); /* fall though */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void usie_uc_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct ucom_softc *ucom = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); /* handle CnS request */ struct mbuf *m = usbd_xfer_get_priv(xfer); if (m != NULL) { usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); usbd_xfer_set_priv(xfer, NULL); usbd_transfer_submit(xfer); m_freem(m); break; } /* standard ucom transfer */ if (ucom_get_data(ucom, pc, 0, USIE_BUFSIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void usie_uc_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_page_cache *pc; struct { struct usb_device_request req; uint16_t param; } st; uint32_t actlen; uint16_t param; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(4, "info received, actlen=%u\n", actlen); if (actlen < sizeof(st)) { DPRINTF("data too short actlen=%u\n", actlen); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &st, sizeof(st)); if (st.req.bmRequestType == 0xa1 && st.req.bRequest == 0x20) { struct ucom_softc *ucom = usbd_xfer_softc(xfer); struct usie_softc *sc = ucom->sc_parent; param = le16toh(st.param); DPRINTF("param=%x\n", param); sc->sc_msr = sc->sc_lsr = 0; sc->sc_msr |= (param & USIE_DCD) ? SER_DCD : 0; sc->sc_msr |= (param & USIE_DSR) ? SER_DSR : 0; sc->sc_msr |= (param & USIE_RI) ? SER_RI : 0; sc->sc_msr |= (param & USIE_CTS) ? 0 : SER_CTS; sc->sc_msr |= (param & USIE_RTS) ? SER_RTS : 0; sc->sc_msr |= (param & USIE_DTR) ? SER_DTR : 0; } /* fall though */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("USB transfer error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void usie_if_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct epoch_tracker et; struct usie_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = sc->sc_ifp; struct mbuf *m0; struct mbuf *m = NULL; struct usie_desc *rxd; uint32_t actlen; uint16_t err; uint16_t pkt; uint16_t ipl; uint16_t len; uint16_t diff; uint8_t pad; uint8_t ipv; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(15, "rx done, actlen=%u\n", actlen); if (actlen < sizeof(struct usie_hip)) { DPRINTF("data too short %u\n", actlen); goto tr_setup; } m = sc->sc_rxm; sc->sc_rxm = NULL; /* fall though */ case USB_ST_SETUP: tr_setup: if (sc->sc_rxm == NULL) { sc->sc_rxm = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE /* could be bigger than MCLBYTES */ ); } if (sc->sc_rxm == NULL) { DPRINTF("could not allocate Rx mbuf\n"); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); } else { /* * Directly loading a mbuf cluster into DMA to * save some data copying. This works because * there is only one cluster. */ usbd_xfer_set_frame_data(xfer, 0, mtod(sc->sc_rxm, caddr_t), MIN(MJUMPAGESIZE, USIE_RXSZ_MAX)); usbd_xfer_set_frames(xfer, 1); } usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("USB transfer error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } if (sc->sc_rxm != NULL) { m_freem(sc->sc_rxm); sc->sc_rxm = NULL; } break; } if (m == NULL) return; mtx_unlock(&sc->sc_mtx); m->m_pkthdr.len = m->m_len = actlen; err = pkt = 0; /* HW can aggregate multiple frames in a single USB xfer */ NET_EPOCH_ENTER(et); for (;;) { rxd = mtod(m, struct usie_desc *); len = be16toh(rxd->hip.len) & USIE_HIP_IP_LEN_MASK; pad = (rxd->hip.id & USIE_HIP_PAD) ? 1 : 0; ipl = (len - pad - ETHER_HDR_LEN); if (ipl >= len) { DPRINTF("Corrupt frame\n"); m_freem(m); break; } diff = sizeof(struct usie_desc) + ipl + pad; if (((rxd->hip.id & USIE_HIP_MASK) != USIE_HIP_IP) || (be16toh(rxd->desc_type) & USIE_TYPE_MASK) != USIE_IP_RX) { DPRINTF("received wrong type of packet\n"); m->m_data += diff; m->m_pkthdr.len = (m->m_len -= diff); err++; if (m->m_pkthdr.len > 0) continue; m_freem(m); break; } switch (be16toh(rxd->ethhdr.ether_type)) { case ETHERTYPE_IP: ipv = NETISR_IP; break; #ifdef INET6 case ETHERTYPE_IPV6: ipv = NETISR_IPV6; break; #endif default: DPRINTF("unsupported ether type\n"); err++; break; } /* the last packet */ if (m->m_pkthdr.len <= diff) { m->m_data += (sizeof(struct usie_desc) + pad); m->m_pkthdr.len = m->m_len = ipl; m->m_pkthdr.rcvif = ifp; BPF_MTAP(sc->sc_ifp, m); netisr_dispatch(ipv, m); break; } /* copy aggregated frames to another mbuf */ m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m0 == NULL)) { DPRINTF("could not allocate mbuf\n"); err++; m_freem(m); break; } m_copydata(m, sizeof(struct usie_desc) + pad, ipl, mtod(m0, caddr_t)); m0->m_pkthdr.rcvif = ifp; m0->m_pkthdr.len = m0->m_len = ipl; BPF_MTAP(sc->sc_ifp, m0); netisr_dispatch(ipv, m0); m->m_data += diff; m->m_pkthdr.len = (m->m_len -= diff); } NET_EPOCH_EXIT(et); mtx_lock(&sc->sc_mtx); if_inc_counter(ifp, IFCOUNTER_IERRORS, err); if_inc_counter(ifp, IFCOUNTER_IPACKETS, pkt); } static void usie_if_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct usie_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct ifnet *ifp = sc->sc_ifp; struct mbuf *m; uint16_t size; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete\n"); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* fall though */ case USB_ST_SETUP: tr_setup: if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) break; IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; if (m->m_pkthdr.len > (int)(MCLBYTES - ETHER_HDR_LEN + ETHER_CRC_LEN - sizeof(sc->sc_txd))) { DPRINTF("packet len is too big: %d\n", m->m_pkthdr.len); break; } pc = usbd_xfer_get_frame(xfer, 0); sc->sc_txd.hip.len = htobe16(m->m_pkthdr.len + ETHER_HDR_LEN + ETHER_CRC_LEN); size = sizeof(sc->sc_txd); usbd_copy_in(pc, 0, &sc->sc_txd, size); usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len + size + ETHER_CRC_LEN); BPF_MTAP(ifp, m); m_freem(m); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("USB transfer error, %s\n", usbd_errstr(error)); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto tr_setup; } break; } } static void usie_if_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct usie_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct usb_cdc_notification cdc; uint32_t actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(4, "info received, actlen=%d\n", actlen); /* usb_cdc_notification - .data[16] */ if (actlen < (sizeof(cdc) - 16)) { DPRINTF("data too short %d\n", actlen); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &cdc, (sizeof(cdc) - 16)); DPRINTFN(4, "bNotification=%x\n", cdc.bNotification); if (cdc.bNotification & UCDC_N_RESPONSE_AVAILABLE) { taskqueue_enqueue(taskqueue_thread, &sc->sc_if_status_task); } /* fall though */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("USB transfer error, %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void usie_if_sync_to(void *arg) { struct usie_softc *sc = arg; taskqueue_enqueue(taskqueue_thread, &sc->sc_if_sync_task); } static void usie_if_sync_cb(void *arg, int pending) { struct usie_softc *sc = arg; mtx_lock(&sc->sc_mtx); /* call twice */ usie_if_cmd(sc, USIE_HIP_SYNC2M); usie_if_cmd(sc, USIE_HIP_SYNC2M); usb_callout_reset(&sc->sc_if_sync_ch, 2 * hz, usie_if_sync_to, sc); mtx_unlock(&sc->sc_mtx); } static void usie_if_status_cb(void *arg, int pending) { struct usie_softc *sc = arg; struct ifnet *ifp = sc->sc_ifp; struct usb_device_request req; struct usie_hip *hip; struct usie_lsi *lsi; uint16_t actlen; uint8_t ntries; uint8_t pad; mtx_lock(&sc->sc_mtx); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_if_ifnum); USETW(req.wLength, sizeof(sc->sc_status_temp)); for (ntries = 0; ntries != 10; ntries++) { int err; err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, sc->sc_status_temp, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (err == 0) break; DPRINTF("Control request failed: %s %d/10\n", usbd_errstr(err), ntries); usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); } if (ntries == 10) { mtx_unlock(&sc->sc_mtx); DPRINTF("Timeout\n"); return; } hip = (struct usie_hip *)sc->sc_status_temp; pad = (hip->id & USIE_HIP_PAD) ? 1 : 0; DPRINTF("hip.id=%x hip.len=%d actlen=%u pad=%d\n", hip->id, be16toh(hip->len), actlen, pad); switch (hip->id & USIE_HIP_MASK) { case USIE_HIP_SYNC2H: usie_if_cmd(sc, USIE_HIP_SYNC2M); break; case USIE_HIP_RESTR: usb_callout_stop(&sc->sc_if_sync_ch); break; case USIE_HIP_UMTS: lsi = (struct usie_lsi *)( sc->sc_status_temp + sizeof(struct usie_hip) + pad); DPRINTF("lsi.proto=%x lsi.len=%d\n", lsi->proto, be16toh(lsi->len)); if (lsi->proto != USIE_LSI_UMTS) break; if (lsi->area == USIE_LSI_AREA_NO || lsi->area == USIE_LSI_AREA_NODATA) { device_printf(sc->sc_dev, "no service available\n"); break; } if (lsi->state == USIE_LSI_STATE_IDLE) { DPRINTF("lsi.state=%x\n", lsi->state); break; } DPRINTF("ctx=%x\n", hip->param); sc->sc_txd.hip.param = hip->param; sc->sc_net.addr_len = lsi->pdp_addr_len; memcpy(&sc->sc_net.dns1_addr, &lsi->dns1_addr, 16); memcpy(&sc->sc_net.dns2_addr, &lsi->dns2_addr, 16); memcpy(sc->sc_net.pdp_addr, lsi->pdp_addr, 16); memcpy(sc->sc_net.gw_addr, lsi->gw_addr, 16); ifp->if_flags |= IFF_UP; ifp->if_drv_flags |= IFF_DRV_RUNNING; device_printf(sc->sc_dev, "IP Addr=%d.%d.%d.%d\n", *lsi->pdp_addr, *(lsi->pdp_addr + 1), *(lsi->pdp_addr + 2), *(lsi->pdp_addr + 3)); device_printf(sc->sc_dev, "Gateway Addr=%d.%d.%d.%d\n", *lsi->gw_addr, *(lsi->gw_addr + 1), *(lsi->gw_addr + 2), *(lsi->gw_addr + 3)); device_printf(sc->sc_dev, "Prim NS Addr=%d.%d.%d.%d\n", *lsi->dns1_addr, *(lsi->dns1_addr + 1), *(lsi->dns1_addr + 2), *(lsi->dns1_addr + 3)); device_printf(sc->sc_dev, "Scnd NS Addr=%d.%d.%d.%d\n", *lsi->dns2_addr, *(lsi->dns2_addr + 1), *(lsi->dns2_addr + 2), *(lsi->dns2_addr + 3)); usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI); break; case USIE_HIP_RCGI: /* ignore, workaround for sloppy windows */ break; default: DPRINTF("undefined msgid: %x\n", hip->id); break; } mtx_unlock(&sc->sc_mtx); } static void usie_if_start(struct ifnet *ifp) { struct usie_softc *sc = ifp->if_softc; if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { DPRINTF("Not running\n"); return; } mtx_lock(&sc->sc_mtx); usbd_transfer_start(sc->sc_if_xfer[USIE_IF_TX]); mtx_unlock(&sc->sc_mtx); DPRINTFN(3, "interface started\n"); } static int usie_if_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro) { int err; DPRINTF("proto=%x\n", dst->sa_family); switch (dst->sa_family) { #ifdef INET6 case AF_INET6; /* fall though */ #endif case AF_INET: break; /* silently drop dhclient packets */ case AF_UNSPEC: m_freem(m); return (0); /* drop other packet types */ default: m_freem(m); return (EAFNOSUPPORT); } err = (ifp->if_transmit)(ifp, m); if (err) { if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); return (0); } static void usie_if_init(void *arg) { struct usie_softc *sc = arg; struct ifnet *ifp = sc->sc_ifp; uint8_t i; mtx_lock(&sc->sc_mtx); /* write tx descriptor */ sc->sc_txd.hip.id = USIE_HIP_CTX; sc->sc_txd.hip.param = 0; /* init value */ sc->sc_txd.desc_type = htobe16(USIE_IP_TX); for (i = 0; i != USIE_IF_N_XFER; i++) usbd_xfer_set_stall(sc->sc_if_xfer[i]); usbd_transfer_start(sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_RX]); usbd_transfer_start(sc->sc_if_xfer[USIE_IF_STATUS]); usbd_transfer_start(sc->sc_if_xfer[USIE_IF_RX]); /* if not running, initiate the modem */ if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) usie_cns_req(sc, USIE_CNS_ID_INIT, USIE_CNS_OB_LINK_UPDATE); mtx_unlock(&sc->sc_mtx); DPRINTF("ifnet initialized\n"); } static void usie_if_stop(struct usie_softc *sc) { usb_callout_drain(&sc->sc_if_sync_ch); mtx_lock(&sc->sc_mtx); /* usie_cns_req() clears IFF_* flags */ usie_cns_req(sc, USIE_CNS_ID_STOP, USIE_CNS_OB_LINK_UPDATE); usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_TX]); usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_RX]); usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_STATUS]); /* shutdown device */ usie_if_cmd(sc, USIE_HIP_DOWN); mtx_unlock(&sc->sc_mtx); } static int usie_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct usie_softc *sc = ifp->if_softc; struct ieee80211req *ireq; struct ieee80211req_sta_info si; struct ifmediareq *ifmr; switch (cmd) { case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) usie_if_init(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) usie_if_stop(sc); } break; case SIOCSIFCAP: if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { device_printf(sc->sc_dev, "Connect to the network first.\n"); break; } mtx_lock(&sc->sc_mtx); usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI); mtx_unlock(&sc->sc_mtx); break; case SIOCG80211: ireq = (struct ieee80211req *)data; if (ireq->i_type != IEEE80211_IOC_STA_INFO) break; memset(&si, 0, sizeof(si)); si.isi_len = sizeof(si); /* * ifconfig expects RSSI in 0.5dBm units * relative to the noise floor. */ si.isi_rssi = 2 * sc->sc_rssi; if (copyout(&si, (uint8_t *)ireq->i_data + 8, sizeof(struct ieee80211req_sta_info))) DPRINTF("copyout failed\n"); DPRINTF("80211\n"); break; case SIOCGIFMEDIA: /* to fool ifconfig */ ifmr = (struct ifmediareq *)data; ifmr->ifm_count = 1; DPRINTF("media\n"); break; case SIOCSIFADDR: break; default: return (EINVAL); } return (0); } static int usie_do_request(struct usie_softc *sc, struct usb_device_request *req, void *data) { int err = 0; int ntries; mtx_assert(&sc->sc_mtx, MA_OWNED); for (ntries = 0; ntries != 10; ntries++) { err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, req, data); if (err == 0) break; DPRINTF("Control request failed: %s %d/10\n", usbd_errstr(err), ntries); usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); } return (err); } static int usie_if_cmd(struct usie_softc *sc, uint8_t cmd) { struct usb_device_request req; struct usie_hip msg; msg.len = 0; msg.id = cmd; msg.param = 0; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_if_ifnum); USETW(req.wLength, sizeof(msg)); DPRINTF("cmd=%x\n", cmd); return (usie_do_request(sc, &req, &msg)); } static void usie_cns_req(struct usie_softc *sc, uint32_t id, uint16_t obj) { struct ifnet *ifp = sc->sc_ifp; struct mbuf *m; struct usb_xfer *xfer; struct usie_hip *hip; struct usie_cns *cns; uint8_t *param; uint8_t *tmp; uint8_t cns_len; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) { DPRINTF("could not allocate mbuf\n"); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return; } /* to align usie_hip{} on 32 bit */ m->m_data += 3; param = mtod(m, uint8_t *); *param++ = USIE_HIP_FRM_CHR; hip = (struct usie_hip *)param; cns = (struct usie_cns *)(hip + 1); tmp = param + USIE_HIPCNS_MIN - 2; switch (obj) { case USIE_CNS_OB_LINK_UPDATE: cns_len = 2; cns->op = USIE_CNS_OP_SET; *tmp++ = 1; /* profile ID, always use 1 for now */ *tmp++ = id == USIE_CNS_ID_INIT ? 1 : 0; break; case USIE_CNS_OB_PROF_WRITE: cns_len = 245; cns->op = USIE_CNS_OP_SET; *tmp++ = 1; /* profile ID, always use 1 for now */ *tmp++ = 2; memcpy(tmp, &sc->sc_net, 34); memset(tmp + 35, 0, 245 - 36); tmp += 243; break; case USIE_CNS_OB_RSSI: cns_len = 0; cns->op = USIE_CNS_OP_REQ; break; default: DPRINTF("unsupported CnS object type\n"); return; } *tmp = USIE_HIP_FRM_CHR; hip->len = htobe16(sizeof(struct usie_cns) + cns_len); hip->id = USIE_HIP_CNS2M; hip->param = 0; /* none for CnS */ cns->obj = htobe16(obj); cns->id = htobe32(id); cns->len = cns_len; cns->rsv0 = cns->rsv1 = 0; /* always '0' */ param = (uint8_t *)(cns + 1); DPRINTF("param: %16D\n", param, ":"); m->m_pkthdr.len = m->m_len = USIE_HIPCNS_MIN + cns_len + 2; xfer = sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_TX]; if (usbd_xfer_get_priv(xfer) == NULL) { usbd_xfer_set_priv(xfer, m); usbd_transfer_start(xfer); } else { DPRINTF("Dropped CNS event\n"); m_freem(m); } } static void usie_cns_rsp(struct usie_softc *sc, struct usie_cns *cns) { struct ifnet *ifp = sc->sc_ifp; DPRINTF("received CnS\n"); switch (be16toh(cns->obj)) { case USIE_CNS_OB_LINK_UPDATE: if (be32toh(cns->id) & USIE_CNS_ID_INIT) usie_if_sync_to(sc); else if (be32toh(cns->id) & USIE_CNS_ID_STOP) { ifp->if_flags &= ~IFF_UP; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } else DPRINTF("undefined link update\n"); break; case USIE_CNS_OB_RSSI: sc->sc_rssi = be16toh(*(int16_t *)(cns + 1)); if (sc->sc_rssi <= 0) device_printf(sc->sc_dev, "No signal\n"); else { device_printf(sc->sc_dev, "RSSI=%ddBm\n", sc->sc_rssi - 110); } break; case USIE_CNS_OB_PROF_WRITE: break; case USIE_CNS_OB_PDP_READ: break; default: DPRINTF("undefined CnS\n"); break; } } static void usie_hip_rsp(struct usie_softc *sc, uint8_t *rsp, uint32_t len) { struct usie_hip *hip; struct usie_cns *cns; uint32_t i; uint32_t j; uint32_t off; uint8_t tmp[USIE_HIPCNS_MAX] __aligned(4); for (off = 0; (off + USIE_HIPCNS_MIN) <= len; off++) { uint8_t pad; while ((off < len) && (rsp[off] == USIE_HIP_FRM_CHR)) off++; /* Unstuff the bytes */ for (i = j = 0; ((i + off) < len) && (j < USIE_HIPCNS_MAX); i++) { if (rsp[i + off] == USIE_HIP_FRM_CHR) break; if (rsp[i + off] == USIE_HIP_ESC_CHR) { if ((i + off + 1) >= len) break; tmp[j++] = rsp[i++ + off + 1] ^ 0x20; } else { tmp[j++] = rsp[i + off]; } } off += i; DPRINTF("frame len=%d\n", j); if (j < sizeof(struct usie_hip)) { DPRINTF("too little data\n"); break; } /* * Make sure we are not reading the stack if something * is wrong. */ memset(tmp + j, 0, sizeof(tmp) - j); hip = (struct usie_hip *)tmp; DPRINTF("hip: len=%d msgID=%02x, param=%02x\n", be16toh(hip->len), hip->id, hip->param); pad = (hip->id & USIE_HIP_PAD) ? 1 : 0; if ((hip->id & USIE_HIP_MASK) == USIE_HIP_CNS2H) { cns = (struct usie_cns *)(((uint8_t *)(hip + 1)) + pad); if (j < (sizeof(struct usie_cns) + sizeof(struct usie_hip) + pad)) { DPRINTF("too little data\n"); break; } DPRINTF("cns: obj=%04x, op=%02x, rsv0=%02x, " "app=%08x, rsv1=%02x, len=%d\n", be16toh(cns->obj), cns->op, cns->rsv0, be32toh(cns->id), cns->rsv1, cns->len); if (cns->op & USIE_CNS_OP_ERR) DPRINTF("CnS error response\n"); else usie_cns_rsp(sc, cns); i = sizeof(struct usie_hip) + pad + sizeof(struct usie_cns); j = cns->len; } else { i = sizeof(struct usie_hip) + pad; j = be16toh(hip->len); } #ifdef USB_DEBUG if (usie_debug == 0) continue; while (i < USIE_HIPCNS_MAX && j > 0) { DPRINTF("param[0x%02x] = 0x%02x\n", i, tmp[i]); i++; j--; } #endif } } static int usie_driver_loaded(struct module *mod, int what, void *arg) { switch (what) { case MOD_LOAD: /* register autoinstall handler */ usie_etag = EVENTHANDLER_REGISTER(usb_dev_configured, usie_autoinst, NULL, EVENTHANDLER_PRI_ANY); break; case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(usb_dev_configured, usie_etag); break; default: return (EOPNOTSUPP); } return (0); } Index: head/sys/dev/usb/net/uhso.c =================================================================== --- head/sys/dev/usb/net/uhso.c (revision 357971) +++ head/sys/dev/usb/net/uhso.c (revision 357972) @@ -1,1937 +1,1939 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Fredrik Lindberg * 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. * 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. * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uhso_debug #include #include #include #include #include struct uhso_tty { struct uhso_softc *ht_sc; struct usb_xfer *ht_xfer[3]; int ht_muxport; /* Mux. port no */ int ht_open; char ht_name[32]; }; struct uhso_softc { device_t sc_dev; struct usb_device *sc_udev; struct mtx sc_mtx; uint32_t sc_type; /* Interface definition */ int sc_radio; struct usb_xfer *sc_xfer[3]; uint8_t sc_iface_no; uint8_t sc_iface_index; /* Control pipe */ struct usb_xfer * sc_ctrl_xfer[2]; uint8_t sc_ctrl_iface_no; /* Network */ struct usb_xfer *sc_if_xfer[2]; struct ifnet *sc_ifp; struct mbuf *sc_mwait; /* Partial packet */ size_t sc_waitlen; /* No. of outstanding bytes */ struct mbufq sc_rxq; struct callout sc_c; /* TTY related structures */ struct ucom_super_softc sc_super_ucom; int sc_ttys; struct uhso_tty *sc_tty; struct ucom_softc *sc_ucom; int sc_msr; int sc_lsr; int sc_line; }; #define UHSO_MAX_MTU 2048 /* * There are mainly two type of cards floating around. * The first one has 2,3 or 4 interfaces with a multiplexed serial port * and packet interface on the first interface and bulk serial ports * on the others. * The second type of card has several other interfaces, their purpose * can be detected during run-time. */ #define UHSO_IFACE_SPEC(usb_type, port, port_type) \ (((usb_type) << 24) | ((port) << 16) | (port_type)) #define UHSO_IFACE_USB_TYPE(x) ((x >> 24) & 0xff) #define UHSO_IFACE_PORT(x) ((x >> 16) & 0xff) #define UHSO_IFACE_PORT_TYPE(x) (x & 0xff) /* * USB interface types */ #define UHSO_IF_NET 0x01 /* Network packet interface */ #define UHSO_IF_MUX 0x02 /* Multiplexed serial port */ #define UHSO_IF_BULK 0x04 /* Bulk interface */ /* * Port types */ #define UHSO_PORT_UNKNOWN 0x00 #define UHSO_PORT_SERIAL 0x01 /* Serial port */ #define UHSO_PORT_NETWORK 0x02 /* Network packet interface */ /* * Multiplexed serial port destination sub-port names */ #define UHSO_MPORT_TYPE_CTL 0x00 /* Control port */ #define UHSO_MPORT_TYPE_APP 0x01 /* Application */ #define UHSO_MPORT_TYPE_PCSC 0x02 #define UHSO_MPORT_TYPE_GPS 0x03 #define UHSO_MPORT_TYPE_APP2 0x04 /* Secondary application */ #define UHSO_MPORT_TYPE_MAX UHSO_MPORT_TYPE_APP2 #define UHSO_MPORT_TYPE_NOMAX 8 /* Max number of mux ports */ /* * Port definitions * Note that these definitions are arbitrary and do not match the values * returned by the auto config descriptor. */ #define UHSO_PORT_TYPE_UNKNOWN 0x00 #define UHSO_PORT_TYPE_CTL 0x01 #define UHSO_PORT_TYPE_APP 0x02 #define UHSO_PORT_TYPE_APP2 0x03 #define UHSO_PORT_TYPE_MODEM 0x04 #define UHSO_PORT_TYPE_NETWORK 0x05 #define UHSO_PORT_TYPE_DIAG 0x06 #define UHSO_PORT_TYPE_DIAG2 0x07 #define UHSO_PORT_TYPE_GPS 0x08 #define UHSO_PORT_TYPE_GPSCTL 0x09 #define UHSO_PORT_TYPE_PCSC 0x0a #define UHSO_PORT_TYPE_MSD 0x0b #define UHSO_PORT_TYPE_VOICE 0x0c #define UHSO_PORT_TYPE_MAX 0x0c static eventhandler_tag uhso_etag; /* Overall port type */ static char *uhso_port[] = { "Unknown", "Serial", "Network", "Network/Serial" }; /* * Map between interface port type read from device and description type. * The position in this array is a direct map to the auto config * descriptor values. */ static unsigned char uhso_port_map[] = { UHSO_PORT_TYPE_UNKNOWN, UHSO_PORT_TYPE_DIAG, UHSO_PORT_TYPE_GPS, UHSO_PORT_TYPE_GPSCTL, UHSO_PORT_TYPE_APP, UHSO_PORT_TYPE_APP2, UHSO_PORT_TYPE_CTL, UHSO_PORT_TYPE_NETWORK, UHSO_PORT_TYPE_MODEM, UHSO_PORT_TYPE_MSD, UHSO_PORT_TYPE_PCSC, UHSO_PORT_TYPE_VOICE }; static char uhso_port_map_max = sizeof(uhso_port_map) / sizeof(char); static unsigned char uhso_mux_port_map[] = { UHSO_PORT_TYPE_CTL, UHSO_PORT_TYPE_APP, UHSO_PORT_TYPE_PCSC, UHSO_PORT_TYPE_GPS, UHSO_PORT_TYPE_APP2 }; static char *uhso_port_type[] = { "Unknown", /* Not a valid port */ "Control", "Application", "Application (Secondary)", "Modem", "Network", "Diagnostic", "Diagnostic (Secondary)", "GPS", "GPS Control", "PC Smartcard", "MSD", "Voice", }; static char *uhso_port_type_sysctl[] = { "unknown", "control", "application", "application", "modem", "network", "diagnostic", "diagnostic", "gps", "gps_control", "pcsc", "msd", "voice", }; #define UHSO_STATIC_IFACE 0x01 #define UHSO_AUTO_IFACE 0x02 /* ifnet device unit allocations */ static struct unrhdr *uhso_ifnet_unit = NULL; static const STRUCT_USB_HOST_ID uhso_devs[] = { #define UHSO_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } /* Option GlobeTrotter MAX 7.2 with upgraded firmware */ UHSO_DEV(OPTION, GTMAX72, UHSO_STATIC_IFACE), /* Option GlobeSurfer iCON 7.2 */ UHSO_DEV(OPTION, GSICON72, UHSO_STATIC_IFACE), /* Option iCON 225 */ UHSO_DEV(OPTION, GTHSDPA, UHSO_STATIC_IFACE), /* Option GlobeSurfer iCON HSUPA */ UHSO_DEV(OPTION, GSICONHSUPA, UHSO_STATIC_IFACE), /* Option GlobeTrotter HSUPA */ UHSO_DEV(OPTION, GTHSUPA, UHSO_STATIC_IFACE), /* GE40x */ UHSO_DEV(OPTION, GE40X, UHSO_AUTO_IFACE), UHSO_DEV(OPTION, GE40X_1, UHSO_AUTO_IFACE), UHSO_DEV(OPTION, GE40X_2, UHSO_AUTO_IFACE), UHSO_DEV(OPTION, GE40X_3, UHSO_AUTO_IFACE), /* Option GlobeSurfer iCON 401 */ UHSO_DEV(OPTION, ICON401, UHSO_AUTO_IFACE), /* Option GlobeTrotter Module 382 */ UHSO_DEV(OPTION, GMT382, UHSO_AUTO_IFACE), /* Option GTM661W */ UHSO_DEV(OPTION, GTM661W, UHSO_AUTO_IFACE), /* Option iCON EDGE */ UHSO_DEV(OPTION, ICONEDGE, UHSO_STATIC_IFACE), /* Option Module HSxPA */ UHSO_DEV(OPTION, MODHSXPA, UHSO_STATIC_IFACE), /* Option iCON 321 */ UHSO_DEV(OPTION, ICON321, UHSO_STATIC_IFACE), /* Option iCON 322 */ UHSO_DEV(OPTION, GTICON322, UHSO_STATIC_IFACE), /* Option iCON 505 */ UHSO_DEV(OPTION, ICON505, UHSO_AUTO_IFACE), /* Option iCON 452 */ UHSO_DEV(OPTION, ICON505, UHSO_AUTO_IFACE), #undef UHSO_DEV }; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uhso, CTLFLAG_RW, 0, "USB uhso"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhso, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uhso"); static int uhso_autoswitch = 1; SYSCTL_INT(_hw_usb_uhso, OID_AUTO, auto_switch, CTLFLAG_RWTUN, &uhso_autoswitch, 0, "Automatically switch to modem mode"); #ifdef USB_DEBUG #ifdef UHSO_DEBUG static int uhso_debug = UHSO_DEBUG; #else static int uhso_debug = -1; #endif SYSCTL_INT(_hw_usb_uhso, OID_AUTO, debug, CTLFLAG_RWTUN, &uhso_debug, 0, "Debug level"); #define UHSO_DPRINTF(n, x, ...) {\ if (uhso_debug >= n) {\ printf("%s: " x, __func__, ##__VA_ARGS__);\ }\ } #else #define UHSO_DPRINTF(n, x, ...) #endif #ifdef UHSO_DEBUG_HEXDUMP # define UHSO_HEXDUMP(_buf, _len) do { \ { \ size_t __tmp; \ const char *__buf = (const char *)_buf; \ for (__tmp = 0; __tmp < _len; __tmp++) \ printf("%02hhx ", *__buf++); \ printf("\n"); \ } \ } while(0) #else # define UHSO_HEXDUMP(_buf, _len) #endif enum { UHSO_MUX_ENDPT_INTR = 0, UHSO_MUX_ENDPT_MAX }; enum { UHSO_CTRL_READ = 0, UHSO_CTRL_WRITE, UHSO_CTRL_MAX }; enum { UHSO_IFNET_READ = 0, UHSO_IFNET_WRITE, UHSO_IFNET_MAX }; enum { UHSO_BULK_ENDPT_READ = 0, UHSO_BULK_ENDPT_WRITE, UHSO_BULK_ENDPT_INTR, UHSO_BULK_ENDPT_MAX }; static usb_callback_t uhso_mux_intr_callback; static usb_callback_t uhso_mux_read_callback; static usb_callback_t uhso_mux_write_callback; static usb_callback_t uhso_bs_read_callback; static usb_callback_t uhso_bs_write_callback; static usb_callback_t uhso_bs_intr_callback; static usb_callback_t uhso_ifnet_read_callback; static usb_callback_t uhso_ifnet_write_callback; /* Config used for the default control pipes */ static const struct usb_config uhso_ctrl_config[UHSO_CTRL_MAX] = { [UHSO_CTRL_READ] = { .type = UE_CONTROL, .endpoint = 0x00, .direction = UE_DIR_ANY, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .bufsize = sizeof(struct usb_device_request) + 1024, .callback = &uhso_mux_read_callback }, [UHSO_CTRL_WRITE] = { .type = UE_CONTROL, .endpoint = 0x00, .direction = UE_DIR_ANY, .flags = { .pipe_bof = 1, .force_short_xfer = 1 }, .bufsize = sizeof(struct usb_device_request) + 1024, .timeout = 1000, .callback = &uhso_mux_write_callback } }; /* Config for the multiplexed serial ports */ static const struct usb_config uhso_mux_config[UHSO_MUX_ENDPT_MAX] = { [UHSO_MUX_ENDPT_INTR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .short_xfer_ok = 1 }, .bufsize = 0, .callback = &uhso_mux_intr_callback, } }; /* Config for the raw IP-packet interface */ static const struct usb_config uhso_ifnet_config[UHSO_IFNET_MAX] = { [UHSO_IFNET_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .bufsize = MCLBYTES, .callback = &uhso_ifnet_read_callback }, [UHSO_IFNET_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .flags = { .pipe_bof = 1, .force_short_xfer = 1 }, .bufsize = MCLBYTES, .timeout = 5 * USB_MS_HZ, .callback = &uhso_ifnet_write_callback } }; /* Config for interfaces with normal bulk serial ports */ static const struct usb_config uhso_bs_config[UHSO_BULK_ENDPT_MAX] = { [UHSO_BULK_ENDPT_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .bufsize = 4096, .callback = &uhso_bs_read_callback }, [UHSO_BULK_ENDPT_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .flags = { .pipe_bof = 1, .force_short_xfer = 1 }, .bufsize = 8192, .callback = &uhso_bs_write_callback }, [UHSO_BULK_ENDPT_INTR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .short_xfer_ok = 1 }, .bufsize = 0, .callback = &uhso_bs_intr_callback, } }; static int uhso_probe_iface(struct uhso_softc *, int, int (*probe)(struct usb_device *, int)); static int uhso_probe_iface_auto(struct usb_device *, int); static int uhso_probe_iface_static(struct usb_device *, int); static int uhso_attach_muxserial(struct uhso_softc *, struct usb_interface *, int type); static int uhso_attach_bulkserial(struct uhso_softc *, struct usb_interface *, int type); static int uhso_attach_ifnet(struct uhso_softc *, struct usb_interface *, int type); static void uhso_test_autoinst(void *, struct usb_device *, struct usb_attach_arg *); static int uhso_driver_loaded(struct module *, int, void *); static int uhso_radio_sysctl(SYSCTL_HANDLER_ARGS); static int uhso_radio_ctrl(struct uhso_softc *, int); static void uhso_free(struct ucom_softc *); static void uhso_ucom_start_read(struct ucom_softc *); static void uhso_ucom_stop_read(struct ucom_softc *); static void uhso_ucom_start_write(struct ucom_softc *); static void uhso_ucom_stop_write(struct ucom_softc *); static void uhso_ucom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void uhso_ucom_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uhso_ucom_cfg_set_rts(struct ucom_softc *, uint8_t); static void uhso_if_init(void *); static void uhso_if_start(struct ifnet *); static void uhso_if_stop(struct uhso_softc *); static int uhso_if_ioctl(struct ifnet *, u_long, caddr_t); static int uhso_if_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *); static void uhso_if_rxflush(void *); static device_probe_t uhso_probe; static device_attach_t uhso_attach; static device_detach_t uhso_detach; static void uhso_free_softc(struct uhso_softc *); static device_method_t uhso_methods[] = { DEVMETHOD(device_probe, uhso_probe), DEVMETHOD(device_attach, uhso_attach), DEVMETHOD(device_detach, uhso_detach), { 0, 0 } }; static driver_t uhso_driver = { .name = "uhso", .methods = uhso_methods, .size = sizeof(struct uhso_softc) }; static devclass_t uhso_devclass; DRIVER_MODULE(uhso, uhub, uhso_driver, uhso_devclass, uhso_driver_loaded, 0); MODULE_DEPEND(uhso, ucom, 1, 1, 1); MODULE_DEPEND(uhso, usb, 1, 1, 1); MODULE_VERSION(uhso, 1); USB_PNP_HOST_INFO(uhso_devs); static struct ucom_callback uhso_ucom_callback = { .ucom_cfg_get_status = &uhso_ucom_cfg_get_status, .ucom_cfg_set_dtr = &uhso_ucom_cfg_set_dtr, .ucom_cfg_set_rts = &uhso_ucom_cfg_set_rts, .ucom_start_read = uhso_ucom_start_read, .ucom_stop_read = uhso_ucom_stop_read, .ucom_start_write = uhso_ucom_start_write, .ucom_stop_write = uhso_ucom_stop_write, .ucom_free = &uhso_free, }; static int uhso_probe(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); int error; if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bDeviceClass != 0xff) return (ENXIO); error = usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa); if (error != 0) return (error); /* * Probe device to see if we are able to attach * to this interface or not. */ if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE) { if (uhso_probe_iface_auto(uaa->device, uaa->info.bIfaceNum) == 0) return (ENXIO); } return (error); } static int uhso_attach(device_t self) { struct uhso_softc *sc = device_get_softc(self); struct usb_attach_arg *uaa = device_get_ivars(self); struct usb_interface_descriptor *id; struct sysctl_ctx_list *sctx; struct sysctl_oid *soid; struct sysctl_oid *tree = NULL, *tty_node; struct ucom_softc *ucom; struct uhso_tty *ht; int i, error, port; void *probe_f; usb_error_t uerr; char *desc; sc->sc_dev = self; sc->sc_udev = uaa->device; mtx_init(&sc->sc_mtx, "uhso", NULL, MTX_DEF); mbufq_init(&sc->sc_rxq, INT_MAX); /* XXXGL: sane maximum */ ucom_ref(&sc->sc_super_ucom); sc->sc_radio = 1; id = usbd_get_interface_descriptor(uaa->iface); sc->sc_ctrl_iface_no = id->bInterfaceNumber; sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = uaa->info.bIfaceIndex; /* Setup control pipe */ uerr = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, sc->sc_ctrl_xfer, uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx); if (uerr) { device_printf(self, "Failed to setup control pipe: %s\n", usbd_errstr(uerr)); goto out; } if (USB_GET_DRIVER_INFO(uaa) == UHSO_STATIC_IFACE) probe_f = uhso_probe_iface_static; else if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE) probe_f = uhso_probe_iface_auto; else goto out; error = uhso_probe_iface(sc, uaa->info.bIfaceNum, probe_f); if (error != 0) goto out; sctx = device_get_sysctl_ctx(sc->sc_dev); soid = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "type", CTLFLAG_RD, uhso_port[UHSO_IFACE_PORT(sc->sc_type)], 0, "Port available at this interface"); SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "radio", - CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0, uhso_radio_sysctl, "I", "Enable radio"); + CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, 0, + uhso_radio_sysctl, "I", "Enable radio"); /* * The default interface description on most Option devices isn't * very helpful. So we skip device_set_usb_desc and set the * device description manually. */ device_set_desc_copy(self, uhso_port_type[UHSO_IFACE_PORT_TYPE(sc->sc_type)]); /* Announce device */ device_printf(self, "<%s port> at <%s %s> on %s\n", uhso_port_type[UHSO_IFACE_PORT_TYPE(sc->sc_type)], usb_get_manufacturer(uaa->device), usb_get_product(uaa->device), device_get_nameunit(device_get_parent(self))); if (sc->sc_ttys > 0) { SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "ports", CTLFLAG_RD, &sc->sc_ttys, 0, "Number of attached serial ports"); tree = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, - "port", CTLFLAG_RD, NULL, "Serial ports"); + "port", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Serial ports"); } /* * Loop through the number of found TTYs and create sysctl * nodes for them. */ for (i = 0; i < sc->sc_ttys; i++) { ht = &sc->sc_tty[i]; ucom = &sc->sc_ucom[i]; if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) port = uhso_mux_port_map[ht->ht_muxport]; else port = UHSO_IFACE_PORT_TYPE(sc->sc_type); desc = uhso_port_type_sysctl[port]; tty_node = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(tree), OID_AUTO, - desc, CTLFLAG_RD, NULL, ""); + desc, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); ht->ht_name[0] = 0; if (sc->sc_ttys == 1) snprintf(ht->ht_name, 32, "cuaU%d", ucom->sc_super->sc_unit); else { snprintf(ht->ht_name, 32, "cuaU%d.%d", ucom->sc_super->sc_unit, ucom->sc_subunit); } desc = uhso_port_type[port]; SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO, "tty", CTLFLAG_RD, ht->ht_name, 0, ""); SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO, "desc", CTLFLAG_RD, desc, 0, ""); if (bootverbose) device_printf(sc->sc_dev, "\"%s\" port at %s\n", desc, ht->ht_name); } return (0); out: uhso_detach(sc->sc_dev); return (ENXIO); } static int uhso_detach(device_t self) { struct uhso_softc *sc = device_get_softc(self); int i; usbd_transfer_unsetup(sc->sc_xfer, 3); usbd_transfer_unsetup(sc->sc_ctrl_xfer, UHSO_CTRL_MAX); if (sc->sc_ttys > 0) { ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); for (i = 0; i < sc->sc_ttys; i++) { if (sc->sc_tty[i].ht_muxport != -1) { usbd_transfer_unsetup(sc->sc_tty[i].ht_xfer, UHSO_CTRL_MAX); } } } if (sc->sc_ifp != NULL) { callout_drain(&sc->sc_c); free_unr(uhso_ifnet_unit, sc->sc_ifp->if_dunit); mtx_lock(&sc->sc_mtx); uhso_if_stop(sc); bpfdetach(sc->sc_ifp); if_detach(sc->sc_ifp); if_free(sc->sc_ifp); mtx_unlock(&sc->sc_mtx); usbd_transfer_unsetup(sc->sc_if_xfer, UHSO_IFNET_MAX); } device_claim_softc(self); uhso_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uhso); static void uhso_free_softc(struct uhso_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { free(sc->sc_tty, M_USBDEV); free(sc->sc_ucom, M_USBDEV); mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uhso_free(struct ucom_softc *ucom) { uhso_free_softc(ucom->sc_parent); } static void uhso_test_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; struct usb_interface_descriptor *id; if (uaa->dev_state != UAA_DEV_READY || !uhso_autoswitch) return; iface = usbd_get_iface(udev, 0); if (iface == NULL) return; id = iface->idesc; if (id == NULL || id->bInterfaceClass != UICLASS_MASS) return; if (usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa)) return; /* no device match */ if (usb_msc_eject(udev, 0, MSC_EJECT_REZERO) == 0) { /* success, mark the udev as disappearing */ uaa->dev_state = UAA_DEV_EJECTING; } } static int uhso_driver_loaded(struct module *mod, int what, void *arg) { switch (what) { case MOD_LOAD: /* register our autoinstall handler */ uhso_etag = EVENTHANDLER_REGISTER(usb_dev_configured, uhso_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); /* create our unit allocator for inet devs */ uhso_ifnet_unit = new_unrhdr(0, INT_MAX, NULL); break; case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(usb_dev_configured, uhso_etag); delete_unrhdr(uhso_ifnet_unit); break; default: return (EOPNOTSUPP); } return (0); } /* * Probe the interface type by querying the device. The elements * of an array indicates the capabilities of a particular interface. * Returns a bit mask with the interface capabilities. */ static int uhso_probe_iface_auto(struct usb_device *udev, int index) { struct usb_device_request req; usb_error_t uerr; uint16_t actlen = 0; char port; char buf[17] = {0}; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = 0x86; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 17); uerr = usbd_do_request_flags(udev, NULL, &req, buf, 0, &actlen, USB_MS_HZ); if (uerr != 0) { printf("%s: usbd_do_request_flags failed, %s\n", __func__, usbd_errstr(uerr)); return (0); } UHSO_DPRINTF(1, "actlen=%d\n", actlen); UHSO_HEXDUMP(buf, 17); if (index < 0 || index > 16) { UHSO_DPRINTF(0, "Index %d out of range\n", index); return (0); } UHSO_DPRINTF(1, "index=%d, type=%x[%s]\n", index, buf[index], uhso_port_type[(int)uhso_port_map[(int)buf[index]]]); if (buf[index] >= uhso_port_map_max) port = 0; else port = uhso_port_map[(int)buf[index]]; switch (port) { case UHSO_PORT_TYPE_NETWORK: return (UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX, UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, port)); case UHSO_PORT_TYPE_DIAG: case UHSO_PORT_TYPE_DIAG2: case UHSO_PORT_TYPE_GPS: case UHSO_PORT_TYPE_GPSCTL: case UHSO_PORT_TYPE_CTL: case UHSO_PORT_TYPE_APP: case UHSO_PORT_TYPE_APP2: case UHSO_PORT_TYPE_MODEM: return (UHSO_IFACE_SPEC(UHSO_IF_BULK, UHSO_PORT_SERIAL, port)); case UHSO_PORT_TYPE_MSD: return (0); case UHSO_PORT_TYPE_UNKNOWN: default: return (0); } return (0); } /* * Returns the capabilities of interfaces for devices that don't * support the automatic query. * Returns a bit mask with the interface capabilities. */ static int uhso_probe_iface_static(struct usb_device *udev, int index) { struct usb_config_descriptor *cd; cd = usbd_get_config_descriptor(udev); if (cd->bNumInterface <= 3) { /* Cards with 3 or less interfaces */ switch (index) { case 0: return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX, UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, UHSO_PORT_TYPE_NETWORK); case 1: return UHSO_IFACE_SPEC(UHSO_IF_BULK, UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG); case 2: return UHSO_IFACE_SPEC(UHSO_IF_BULK, UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM); } } else { /* Cards with 4 interfaces */ switch (index) { case 0: return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX, UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, UHSO_PORT_TYPE_NETWORK); case 1: return UHSO_IFACE_SPEC(UHSO_IF_BULK, UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG2); case 2: return UHSO_IFACE_SPEC(UHSO_IF_BULK, UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM); case 3: return UHSO_IFACE_SPEC(UHSO_IF_BULK, UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG); } } return (0); } /* * Probes an interface for its particular capabilities and attaches if * it's a supported interface. */ static int uhso_probe_iface(struct uhso_softc *sc, int index, int (*probe)(struct usb_device *, int)) { struct usb_interface *iface; int type, error; UHSO_DPRINTF(1, "Probing for interface %d, probe_func=%p\n", index, probe); type = probe(sc->sc_udev, index); UHSO_DPRINTF(1, "Probe result %x\n", type); if (type <= 0) return (ENXIO); sc->sc_type = type; iface = usbd_get_iface(sc->sc_udev, index); if (UHSO_IFACE_PORT_TYPE(type) == UHSO_PORT_TYPE_NETWORK) { error = uhso_attach_ifnet(sc, iface, type); if (error) { UHSO_DPRINTF(1, "uhso_attach_ifnet failed"); return (ENXIO); } /* * If there is an additional interrupt endpoint on this * interface then we most likely have a multiplexed serial port * available. */ if (iface->idesc->bNumEndpoints < 3) { sc->sc_type = UHSO_IFACE_SPEC( UHSO_IFACE_USB_TYPE(type) & ~UHSO_IF_MUX, UHSO_IFACE_PORT(type) & ~UHSO_PORT_SERIAL, UHSO_IFACE_PORT_TYPE(type)); return (0); } UHSO_DPRINTF(1, "Trying to attach mux. serial\n"); error = uhso_attach_muxserial(sc, iface, type); if (error == 0 && sc->sc_ttys > 0) { error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx); if (error) { device_printf(sc->sc_dev, "ucom_attach failed\n"); return (ENXIO); } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, sc->sc_dev); mtx_lock(&sc->sc_mtx); usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); mtx_unlock(&sc->sc_mtx); } } else if ((UHSO_IFACE_USB_TYPE(type) & UHSO_IF_BULK) && UHSO_IFACE_PORT(type) & UHSO_PORT_SERIAL) { error = uhso_attach_bulkserial(sc, iface, type); if (error) return (ENXIO); error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx); if (error) { device_printf(sc->sc_dev, "ucom_attach failed\n"); return (ENXIO); } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, sc->sc_dev); } else { UHSO_DPRINTF(0, "Unknown type %x\n", type); return (ENXIO); } return (0); } static int uhso_radio_ctrl(struct uhso_softc *sc, int onoff) { struct usb_device_request req; usb_error_t uerr; req.bmRequestType = UT_VENDOR; req.bRequest = onoff ? 0x82 : 0x81; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 0); uerr = usbd_do_request(sc->sc_udev, NULL, &req, NULL); if (uerr != 0) { device_printf(sc->sc_dev, "usbd_do_request_flags failed: %s\n", usbd_errstr(uerr)); return (-1); } return (onoff); } static int uhso_radio_sysctl(SYSCTL_HANDLER_ARGS) { struct uhso_softc *sc = arg1; int error, radio; radio = sc->sc_radio; error = sysctl_handle_int(oidp, &radio, 0, req); if (error) return (error); if (radio != sc->sc_radio) { radio = radio != 0 ? 1 : 0; error = uhso_radio_ctrl(sc, radio); if (error != -1) sc->sc_radio = radio; } return (0); } /* * Expands allocated memory to fit an additional TTY. * Two arrays are kept with matching indexes, one for ucom and one * for our private data. */ static int uhso_alloc_tty(struct uhso_softc *sc) { sc->sc_ttys++; sc->sc_tty = reallocf(sc->sc_tty, sizeof(struct uhso_tty) * sc->sc_ttys, M_USBDEV, M_WAITOK | M_ZERO); if (sc->sc_tty == NULL) return (-1); sc->sc_ucom = reallocf(sc->sc_ucom, sizeof(struct ucom_softc) * sc->sc_ttys, M_USBDEV, M_WAITOK | M_ZERO); if (sc->sc_ucom == NULL) return (-1); sc->sc_tty[sc->sc_ttys - 1].ht_sc = sc; UHSO_DPRINTF(1, "Allocated TTY %d\n", sc->sc_ttys - 1); return (sc->sc_ttys - 1); } /* * Attach a multiplexed serial port * Data is read/written with requests on the default control pipe. An interrupt * endpoint returns when there is new data to be read. */ static int uhso_attach_muxserial(struct uhso_softc *sc, struct usb_interface *iface, int type) { struct usb_descriptor *desc; int i, port, tty; usb_error_t uerr; /* * The class specific interface (type 0x24) descriptor subtype field * contains a bitmask that specifies which (and how many) ports that * are available through this multiplexed serial port. */ desc = usbd_find_descriptor(sc->sc_udev, NULL, iface->idesc->bInterfaceNumber, UDESC_CS_INTERFACE, 0xff, 0, 0); if (desc == NULL) { UHSO_DPRINTF(0, "Failed to find UDESC_CS_INTERFACE\n"); return (ENXIO); } UHSO_DPRINTF(1, "Mux port mask %x\n", desc->bDescriptorSubtype); if (desc->bDescriptorSubtype == 0) return (ENXIO); /* * The bitmask is one octet, loop through the number of * bits that are set and create a TTY for each. */ for (i = 0; i < 8; i++) { port = (1 << i); if ((port & desc->bDescriptorSubtype) == port) { UHSO_DPRINTF(2, "Found mux port %x (%d)\n", port, i); tty = uhso_alloc_tty(sc); if (tty < 0) return (ENOMEM); sc->sc_tty[tty].ht_muxport = i; uerr = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index, sc->sc_tty[tty].ht_xfer, uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx); if (uerr) { device_printf(sc->sc_dev, "Failed to setup control pipe: %s\n", usbd_errstr(uerr)); return (ENXIO); } } } /* Setup the intr. endpoint */ uerr = usbd_transfer_setup(sc->sc_udev, &iface->idesc->bInterfaceNumber, sc->sc_xfer, uhso_mux_config, 1, sc, &sc->sc_mtx); if (uerr) return (ENXIO); return (0); } /* * Interrupt callback for the multiplexed serial port. Indicates * which serial port has data waiting. */ static void uhso_mux_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_page_cache *pc; struct usb_page_search res; struct uhso_softc *sc = usbd_xfer_softc(xfer); unsigned int i, mux; UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer)); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* * The multiplexed port number can be found at the first byte. * It contains a bit mask, we transform this in to an integer. */ pc = usbd_xfer_get_frame(xfer, 0); usbd_get_page(pc, 0, &res); i = *((unsigned char *)res.buffer); mux = 0; while (i >>= 1) { mux++; } UHSO_DPRINTF(3, "mux port %d (%d)\n", mux, i); if (mux > UHSO_MPORT_TYPE_NOMAX) break; /* Issue a read for this serial port */ usbd_xfer_set_priv( sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ], &sc->sc_tty[mux]); usbd_transfer_start(sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ]); break; case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static void uhso_mux_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct usb_device_request req; struct uhso_tty *ht; int actlen, len; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer)); ht = usbd_xfer_get_priv(xfer); UHSO_DPRINTF(3, "ht=%p open=%d\n", ht, ht->ht_open); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* Got data, send to ucom */ pc = usbd_xfer_get_frame(xfer, 1); len = usbd_xfer_frame_len(xfer, 1); UHSO_DPRINTF(3, "got %d bytes on mux port %d\n", len, ht->ht_muxport); if (len <= 0) { usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); break; } /* Deliver data if the TTY is open, discard otherwise */ if (ht->ht_open) ucom_put_data(&sc->sc_ucom[ht->ht_muxport], pc, 0, len); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: memset(&req, 0, sizeof(struct usb_device_request)); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; USETW(req.wValue, 0); USETW(req.wIndex, ht->ht_muxport); USETW(req.wLength, 1024); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, 1024); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static void uhso_mux_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct uhso_tty *ht; struct usb_page_cache *pc; struct usb_device_request req; int actlen; struct usb_page_search res; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); ht = usbd_xfer_get_priv(xfer); UHSO_DPRINTF(3, "status=%d, using mux port %d\n", USB_GET_STATE(xfer), ht->ht_muxport); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: UHSO_DPRINTF(3, "wrote %zd data bytes to muxport %d\n", actlen - sizeof(struct usb_device_request) , ht->ht_muxport); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: pc = usbd_xfer_get_frame(xfer, 1); if (ucom_get_data(&sc->sc_ucom[ht->ht_muxport], pc, 0, 32, &actlen)) { usbd_get_page(pc, 0, &res); memset(&req, 0, sizeof(struct usb_device_request)); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; USETW(req.wValue, 0); USETW(req.wIndex, ht->ht_muxport); USETW(req.wLength, actlen); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, actlen); usbd_xfer_set_frames(xfer, 2); UHSO_DPRINTF(3, "Prepared %d bytes for transmit " "on muxport %d\n", actlen, ht->ht_muxport); usbd_transfer_submit(xfer); } break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static int uhso_attach_bulkserial(struct uhso_softc *sc, struct usb_interface *iface, int type) { usb_error_t uerr; int tty; /* Try attaching RD/WR/INTR first */ uerr = usbd_transfer_setup(sc->sc_udev, &iface->idesc->bInterfaceNumber, sc->sc_xfer, uhso_bs_config, UHSO_BULK_ENDPT_MAX, sc, &sc->sc_mtx); if (uerr) { /* Try only RD/WR */ uerr = usbd_transfer_setup(sc->sc_udev, &iface->idesc->bInterfaceNumber, sc->sc_xfer, uhso_bs_config, UHSO_BULK_ENDPT_MAX - 1, sc, &sc->sc_mtx); } if (uerr) { UHSO_DPRINTF(0, "usbd_transfer_setup failed"); return (-1); } tty = uhso_alloc_tty(sc); if (tty < 0) { usbd_transfer_unsetup(sc->sc_xfer, UHSO_BULK_ENDPT_MAX); return (ENOMEM); } sc->sc_tty[tty].ht_muxport = -1; return (0); } static void uhso_bs_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom[0], pc, 0, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static void uhso_bs_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom[0], pc, 0, 8192, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } break; break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static void uhso_bs_cfg(struct uhso_softc *sc) { struct usb_device_request req; usb_error_t uerr; if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK)) return; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); uerr = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom[0], &req, NULL, 0, 1000); if (uerr != 0) { device_printf(sc->sc_dev, "failed to set ctrl line state to " "0x%02x: %s\n", sc->sc_line, usbd_errstr(uerr)); } } static void uhso_bs_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; struct usb_cdc_notification cdc; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < UCDC_NOTIFICATION_LENGTH) { UHSO_DPRINTF(0, "UCDC notification too short: %d\n", actlen); goto tr_setup; } else if (actlen > (int)sizeof(struct usb_cdc_notification)) { UHSO_DPRINTF(0, "UCDC notification too large: %d\n", actlen); actlen = sizeof(struct usb_cdc_notification); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &cdc, actlen); if (UGETW(cdc.wIndex) != sc->sc_iface_no) { UHSO_DPRINTF(0, "Interface mismatch, got %d expected %d\n", UGETW(cdc.wIndex), sc->sc_iface_no); goto tr_setup; } if (cdc.bmRequestType == UCDC_NOTIFICATION && cdc.bNotification == UCDC_N_SERIAL_STATE) { UHSO_DPRINTF(2, "notify = 0x%02x\n", cdc.data[0]); sc->sc_msr = 0; sc->sc_lsr = 0; if (cdc.data[0] & UCDC_N_SERIAL_RI) sc->sc_msr |= SER_RI; if (cdc.data[0] & UCDC_N_SERIAL_DSR) sc->sc_msr |= SER_DSR; if (cdc.data[0] & UCDC_N_SERIAL_DCD) sc->sc_msr |= SER_DCD; ucom_status_change(&sc->sc_ucom[0]); } case USB_ST_SETUP: tr_setup: default: if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static void uhso_ucom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uhso_softc *sc = ucom->sc_parent; *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void uhso_ucom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uhso_softc *sc = ucom->sc_parent; if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK)) return; if (onoff) sc->sc_line |= UCDC_LINE_DTR; else sc->sc_line &= ~UCDC_LINE_DTR; uhso_bs_cfg(sc); } static void uhso_ucom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uhso_softc *sc = ucom->sc_parent; if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK)) return; if (onoff) sc->sc_line |= UCDC_LINE_RTS; else sc->sc_line &= ~UCDC_LINE_RTS; uhso_bs_cfg(sc); } static void uhso_ucom_start_read(struct ucom_softc *ucom) { struct uhso_softc *sc = ucom->sc_parent; UHSO_DPRINTF(3, "unit=%d, subunit=%d\n", ucom->sc_super->sc_unit, ucom->sc_subunit); if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { sc->sc_tty[ucom->sc_subunit].ht_open = 1; usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); } else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { sc->sc_tty[0].ht_open = 1; usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]); if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL) usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]); } } static void uhso_ucom_stop_read(struct ucom_softc *ucom) { struct uhso_softc *sc = ucom->sc_parent; if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { sc->sc_tty[ucom->sc_subunit].ht_open = 0; usbd_transfer_stop( sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_READ]); } else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { sc->sc_tty[0].ht_open = 0; usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]); if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL) usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]); } } static void uhso_ucom_start_write(struct ucom_softc *ucom) { struct uhso_softc *sc = ucom->sc_parent; if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { UHSO_DPRINTF(3, "local unit %d\n", ucom->sc_subunit); usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); usbd_xfer_set_priv( sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE], &sc->sc_tty[ucom->sc_subunit]); usbd_transfer_start( sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE]); } else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]); } } static void uhso_ucom_stop_write(struct ucom_softc *ucom) { struct uhso_softc *sc = ucom->sc_parent; if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { usbd_transfer_stop( sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE]); } else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]); } } static int uhso_attach_ifnet(struct uhso_softc *sc, struct usb_interface *iface, int type) { struct ifnet *ifp; usb_error_t uerr; struct sysctl_ctx_list *sctx; struct sysctl_oid *soid; unsigned int devunit; uerr = usbd_transfer_setup(sc->sc_udev, &iface->idesc->bInterfaceNumber, sc->sc_if_xfer, uhso_ifnet_config, UHSO_IFNET_MAX, sc, &sc->sc_mtx); if (uerr) { UHSO_DPRINTF(0, "usbd_transfer_setup failed: %s\n", usbd_errstr(uerr)); return (-1); } sc->sc_ifp = ifp = if_alloc(IFT_OTHER); if (sc->sc_ifp == NULL) { device_printf(sc->sc_dev, "if_alloc() failed\n"); return (-1); } callout_init_mtx(&sc->sc_c, &sc->sc_mtx, 0); mtx_lock(&sc->sc_mtx); callout_reset(&sc->sc_c, 1, uhso_if_rxflush, sc); mtx_unlock(&sc->sc_mtx); /* * We create our own unit numbers for ifnet devices because the * USB interface unit numbers can be at arbitrary positions yielding * odd looking device names. */ devunit = alloc_unr(uhso_ifnet_unit); if_initname(ifp, device_get_name(sc->sc_dev), devunit); ifp->if_mtu = UHSO_MAX_MTU; ifp->if_ioctl = uhso_if_ioctl; ifp->if_init = uhso_if_init; ifp->if_start = uhso_if_start; ifp->if_output = uhso_if_output; ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST | IFF_NOARP; ifp->if_softc = sc; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); if_attach(ifp); bpfattach(ifp, DLT_RAW, 0); sctx = device_get_sysctl_ctx(sc->sc_dev); soid = device_get_sysctl_tree(sc->sc_dev); /* Unlocked read... */ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "netif", CTLFLAG_RD, ifp->if_xname, 0, "Attached network interface"); return (0); } static void uhso_ifnet_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UHSO_DPRINTF(3, "status=%d, actlen=%d\n", USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen > 0 && (sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING)) { pc = usbd_xfer_get_frame(xfer, 0); if (mbufq_full(&sc->sc_rxq)) break; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); usbd_copy_out(pc, 0, mtod(m, uint8_t *), actlen); m->m_pkthdr.len = m->m_len = actlen; /* Enqueue frame for further processing */ mbufq_enqueue(&sc->sc_rxq, m); if (!callout_pending(&sc->sc_c) || !callout_active(&sc->sc_c)) { callout_schedule(&sc->sc_c, 1); } } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } /* * Deferred RX processing, called with mutex locked. * * Each frame we receive might contain several small ip-packets as well * as partial ip-packets. We need to separate/assemble them into individual * packets before sending them to the ip-layer. */ static void uhso_if_rxflush(void *arg) { struct epoch_tracker et; struct uhso_softc *sc = arg; struct ifnet *ifp = sc->sc_ifp; uint8_t *cp; struct mbuf *m, *m0, *mwait; struct ip *ip; #ifdef INET6 struct ip6_hdr *ip6; #endif uint16_t iplen; int isr; m = NULL; mwait = sc->sc_mwait; NET_EPOCH_ENTER(et); for (;;) { if (m == NULL) { if ((m = mbufq_dequeue(&sc->sc_rxq)) == NULL) break; UHSO_DPRINTF(3, "dequeue m=%p, len=%d\n", m, m->m_len); } mtx_unlock(&sc->sc_mtx); /* Do we have a partial packet waiting? */ if (mwait != NULL) { m0 = mwait; mwait = NULL; UHSO_DPRINTF(3, "partial m0=%p(%d), concat w/ m=%p(%d)\n", m0, m0->m_len, m, m->m_len); m_catpkt(m0, m); m = m_pullup(m0, sizeof(struct ip)); if (m == NULL) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); UHSO_DPRINTF(0, "m_pullup failed\n"); mtx_lock(&sc->sc_mtx); continue; } UHSO_DPRINTF(3, "Constructed mbuf=%p, len=%d\n", m, m->m_pkthdr.len); } cp = mtod(m, uint8_t *); ip = (struct ip *)cp; #ifdef INET6 ip6 = (struct ip6_hdr *)cp; #endif /* Check for IPv4 */ if (ip->ip_v == IPVERSION) { iplen = htons(ip->ip_len); isr = NETISR_IP; } #ifdef INET6 /* Check for IPv6 */ else if ((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION) { iplen = htons(ip6->ip6_plen); isr = NETISR_IPV6; } #endif else { UHSO_DPRINTF(0, "got unexpected ip version %d, " "m=%p, len=%d\n", (*cp & 0xf0) >> 4, m, m->m_len); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); UHSO_HEXDUMP(cp, 4); m_freem(m); m = NULL; mtx_lock(&sc->sc_mtx); continue; } if (iplen == 0) { UHSO_DPRINTF(0, "Zero IP length\n"); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); m = NULL; mtx_lock(&sc->sc_mtx); continue; } UHSO_DPRINTF(3, "m=%p, len=%d, cp=%p, iplen=%d\n", m, m->m_pkthdr.len, cp, iplen); m0 = NULL; /* More IP packets in this mbuf */ if (iplen < m->m_pkthdr.len) { m0 = m; /* * Allocate a new mbuf for this IP packet and * copy the IP-packet into it. */ m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR); memcpy(mtod(m, uint8_t *), mtod(m0, uint8_t *), iplen); m->m_pkthdr.len = m->m_len = iplen; /* Adjust the size of the original mbuf */ m_adj(m0, iplen); m0 = m_defrag(m0, M_WAITOK); UHSO_DPRINTF(3, "New mbuf=%p, len=%d/%d, m0=%p, " "m0_len=%d/%d\n", m, m->m_pkthdr.len, m->m_len, m0, m0->m_pkthdr.len, m0->m_len); } else if (iplen > m->m_pkthdr.len) { UHSO_DPRINTF(3, "Deferred mbuf=%p, len=%d\n", m, m->m_pkthdr.len); mwait = m; m = NULL; mtx_lock(&sc->sc_mtx); continue; } if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); m->m_pkthdr.rcvif = ifp; /* Dispatch to IP layer */ BPF_MTAP(sc->sc_ifp, m); M_SETFIB(m, ifp->if_fib); netisr_dispatch(isr, m); m = m0 != NULL ? m0 : NULL; mtx_lock(&sc->sc_mtx); } NET_EPOCH_EXIT(et); sc->sc_mwait = mwait; } static void uhso_ifnet_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhso_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = sc->sc_ifp; struct usb_page_cache *pc; struct mbuf *m; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; case USB_ST_SETUP: tr_setup: IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; ifp->if_drv_flags |= IFF_DRV_OACTIVE; if (m->m_pkthdr.len > MCLBYTES) m->m_pkthdr.len = MCLBYTES; usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); pc = usbd_xfer_get_frame(xfer, 0); usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); usbd_transfer_submit(xfer); BPF_MTAP(ifp, m); m_freem(m); break; default: UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) break; usbd_xfer_set_stall(xfer); goto tr_setup; } } static int uhso_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct uhso_softc *sc; sc = ifp->if_softc; switch (cmd) { case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { uhso_if_init(sc); } } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { mtx_lock(&sc->sc_mtx); uhso_if_stop(sc); mtx_unlock(&sc->sc_mtx); } } break; case SIOCSIFADDR: case SIOCADDMULTI: case SIOCDELMULTI: break; default: return (EINVAL); } return (0); } static void uhso_if_init(void *priv) { struct uhso_softc *sc = priv; struct ifnet *ifp = sc->sc_ifp; mtx_lock(&sc->sc_mtx); uhso_if_stop(sc); ifp = sc->sc_ifp; ifp->if_flags |= IFF_UP; ifp->if_drv_flags |= IFF_DRV_RUNNING; mtx_unlock(&sc->sc_mtx); UHSO_DPRINTF(2, "ifnet initialized\n"); } static int uhso_if_output(struct ifnet *ifp, struct mbuf *m0, const struct sockaddr *dst, struct route *ro) { int error; /* Only IPv4/6 support */ if (dst->sa_family != AF_INET #ifdef INET6 && dst->sa_family != AF_INET6 #endif ) { return (EAFNOSUPPORT); } error = (ifp->if_transmit)(ifp, m0); if (error) { if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); return (0); } static void uhso_if_start(struct ifnet *ifp) { struct uhso_softc *sc = ifp->if_softc; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { UHSO_DPRINTF(1, "Not running\n"); return; } mtx_lock(&sc->sc_mtx); usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_READ]); usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_WRITE]); mtx_unlock(&sc->sc_mtx); UHSO_DPRINTF(3, "interface started\n"); } static void uhso_if_stop(struct uhso_softc *sc) { usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_READ]); usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_WRITE]); sc->sc_ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } Index: head/sys/dev/usb/net/usb_ethernet.c =================================================================== --- head/sys/dev/usb/net/usb_ethernet.c (revision 357971) +++ head/sys/dev/usb/net/usb_ethernet.c (revision 357972) @@ -1,670 +1,670 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009 Andrew Thompson (thompsa@FreeBSD.org) * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -static SYSCTL_NODE(_net, OID_AUTO, ue, CTLFLAG_RD, 0, +static SYSCTL_NODE(_net, OID_AUTO, ue, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "USB Ethernet parameters"); #define UE_LOCK(_ue) mtx_lock((_ue)->ue_mtx) #define UE_UNLOCK(_ue) mtx_unlock((_ue)->ue_mtx) #define UE_LOCK_ASSERT(_ue, t) mtx_assert((_ue)->ue_mtx, t) MODULE_DEPEND(uether, usb, 1, 1, 1); MODULE_DEPEND(uether, miibus, 1, 1, 1); static struct unrhdr *ueunit; static usb_proc_callback_t ue_attach_post_task; static usb_proc_callback_t ue_promisc_task; static usb_proc_callback_t ue_setmulti_task; static usb_proc_callback_t ue_ifmedia_task; static usb_proc_callback_t ue_tick_task; static usb_proc_callback_t ue_start_task; static usb_proc_callback_t ue_stop_task; static void ue_init(void *); static void ue_start(struct ifnet *); static int ue_ifmedia_upd(struct ifnet *); static void ue_watchdog(void *); /* * Return values: * 0: success * Else: device has been detached */ uint8_t uether_pause(struct usb_ether *ue, unsigned int _ticks) { if (usb_proc_is_gone(&ue->ue_tq)) { /* nothing to do */ return (1); } usb_pause_mtx(ue->ue_mtx, _ticks); return (0); } static void ue_queue_command(struct usb_ether *ue, usb_proc_callback_t *fn, struct usb_proc_msg *t0, struct usb_proc_msg *t1) { struct usb_ether_cfg_task *task; UE_LOCK_ASSERT(ue, MA_OWNED); if (usb_proc_is_gone(&ue->ue_tq)) { return; /* nothing to do */ } /* * NOTE: The task cannot get executed before we drop the * "sc_mtx" mutex. It is safe to update fields in the message * structure after that the message got queued. */ task = (struct usb_ether_cfg_task *) usb_proc_msignal(&ue->ue_tq, t0, t1); /* Setup callback and self pointers */ task->hdr.pm_callback = fn; task->ue = ue; /* * Start and stop must be synchronous! */ if ((fn == ue_start_task) || (fn == ue_stop_task)) usb_proc_mwait(&ue->ue_tq, t0, t1); } struct ifnet * uether_getifp(struct usb_ether *ue) { return (ue->ue_ifp); } struct mii_data * uether_getmii(struct usb_ether *ue) { return (device_get_softc(ue->ue_miibus)); } void * uether_getsc(struct usb_ether *ue) { return (ue->ue_sc); } static int ue_sysctl_parent(SYSCTL_HANDLER_ARGS) { struct usb_ether *ue = arg1; const char *name; name = device_get_nameunit(ue->ue_dev); return SYSCTL_OUT_STR(req, name); } int uether_ifattach(struct usb_ether *ue) { int error; /* check some critical parameters */ if ((ue->ue_dev == NULL) || (ue->ue_udev == NULL) || (ue->ue_mtx == NULL) || (ue->ue_methods == NULL)) return (EINVAL); error = usb_proc_create(&ue->ue_tq, ue->ue_mtx, device_get_nameunit(ue->ue_dev), USB_PRI_MED); if (error) { device_printf(ue->ue_dev, "could not setup taskqueue\n"); goto error; } /* fork rest of the attach code */ UE_LOCK(ue); ue_queue_command(ue, ue_attach_post_task, &ue->ue_sync_task[0].hdr, &ue->ue_sync_task[1].hdr); UE_UNLOCK(ue); error: return (error); } void uether_ifattach_wait(struct usb_ether *ue) { UE_LOCK(ue); usb_proc_mwait(&ue->ue_tq, &ue->ue_sync_task[0].hdr, &ue->ue_sync_task[1].hdr); UE_UNLOCK(ue); } static void ue_attach_post_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; struct ifnet *ifp; int error; char num[14]; /* sufficient for 32 bits */ /* first call driver's post attach routine */ ue->ue_methods->ue_attach_post(ue); UE_UNLOCK(ue); ue->ue_unit = alloc_unr(ueunit); usb_callout_init_mtx(&ue->ue_watchdog, ue->ue_mtx, 0); sysctl_ctx_init(&ue->ue_sysctl_ctx); mbufq_init(&ue->ue_rxq, 0 /* unlimited length */); error = 0; CURVNET_SET_QUIET(vnet0); ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(ue->ue_dev, "could not allocate ifnet\n"); goto fail; } ifp->if_softc = ue; if_initname(ifp, "ue", ue->ue_unit); if (ue->ue_methods->ue_attach_post_sub != NULL) { ue->ue_ifp = ifp; error = ue->ue_methods->ue_attach_post_sub(ue); } else { ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; if (ue->ue_methods->ue_ioctl != NULL) ifp->if_ioctl = ue->ue_methods->ue_ioctl; else ifp->if_ioctl = uether_ioctl; ifp->if_start = ue_start; ifp->if_init = ue_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); ue->ue_ifp = ifp; if (ue->ue_methods->ue_mii_upd != NULL && ue->ue_methods->ue_mii_sts != NULL) { /* device_xxx() depends on this */ mtx_lock(&Giant); error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, ue_ifmedia_upd, ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0); mtx_unlock(&Giant); } } if (error) { device_printf(ue->ue_dev, "attaching PHYs failed\n"); goto fail; } if_printf(ifp, " on %s\n", device_get_nameunit(ue->ue_dev)); ether_ifattach(ifp, ue->ue_eaddr); /* Tell upper layer we support VLAN oversized frames. */ if (ifp->if_capabilities & IFCAP_VLAN_MTU) ifp->if_hdrlen = sizeof(struct ether_vlan_header); CURVNET_RESTORE(); snprintf(num, sizeof(num), "%u", ue->ue_unit); ue->ue_sysctl_oid = SYSCTL_ADD_NODE(&ue->ue_sysctl_ctx, &SYSCTL_NODE_CHILDREN(_net, ue), - OID_AUTO, num, CTLFLAG_RD, NULL, ""); + OID_AUTO, num, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); SYSCTL_ADD_PROC(&ue->ue_sysctl_ctx, - SYSCTL_CHILDREN(ue->ue_sysctl_oid), OID_AUTO, - "%parent", CTLTYPE_STRING | CTLFLAG_RD, ue, 0, + SYSCTL_CHILDREN(ue->ue_sysctl_oid), OID_AUTO, "%parent", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, ue, 0, ue_sysctl_parent, "A", "parent device"); UE_LOCK(ue); return; fail: CURVNET_RESTORE(); /* drain mbuf queue */ mbufq_drain(&ue->ue_rxq); /* free unit */ free_unr(ueunit, ue->ue_unit); if (ue->ue_ifp != NULL) { if_free(ue->ue_ifp); ue->ue_ifp = NULL; } UE_LOCK(ue); return; } void uether_ifdetach(struct usb_ether *ue) { struct ifnet *ifp; /* wait for any post attach or other command to complete */ usb_proc_drain(&ue->ue_tq); /* read "ifnet" pointer after taskqueue drain */ ifp = ue->ue_ifp; if (ifp != NULL) { /* we are not running any more */ UE_LOCK(ue); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; UE_UNLOCK(ue); /* drain any callouts */ usb_callout_drain(&ue->ue_watchdog); /* detach miibus */ if (ue->ue_miibus != NULL) { mtx_lock(&Giant); /* device_xxx() depends on this */ device_delete_child(ue->ue_dev, ue->ue_miibus); mtx_unlock(&Giant); } /* detach ethernet */ ether_ifdetach(ifp); /* free interface instance */ if_free(ifp); /* free sysctl */ sysctl_ctx_free(&ue->ue_sysctl_ctx); /* drain mbuf queue */ mbufq_drain(&ue->ue_rxq); /* free unit */ free_unr(ueunit, ue->ue_unit); } /* free taskqueue, if any */ usb_proc_free(&ue->ue_tq); } uint8_t uether_is_gone(struct usb_ether *ue) { return (usb_proc_is_gone(&ue->ue_tq)); } void uether_init(void *arg) { ue_init(arg); } static void ue_init(void *arg) { struct usb_ether *ue = arg; UE_LOCK(ue); ue_queue_command(ue, ue_start_task, &ue->ue_sync_task[0].hdr, &ue->ue_sync_task[1].hdr); UE_UNLOCK(ue); } static void ue_start_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; struct ifnet *ifp = ue->ue_ifp; UE_LOCK_ASSERT(ue, MA_OWNED); ue->ue_methods->ue_init(ue); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; if (ue->ue_methods->ue_tick != NULL) usb_callout_reset(&ue->ue_watchdog, hz, ue_watchdog, ue); } static void ue_stop_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; UE_LOCK_ASSERT(ue, MA_OWNED); usb_callout_stop(&ue->ue_watchdog); ue->ue_methods->ue_stop(ue); } void uether_start(struct ifnet *ifp) { ue_start(ifp); } static void ue_start(struct ifnet *ifp) { struct usb_ether *ue = ifp->if_softc; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; UE_LOCK(ue); ue->ue_methods->ue_start(ue); UE_UNLOCK(ue); } static void ue_promisc_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; ue->ue_methods->ue_setpromisc(ue); } static void ue_setmulti_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; ue->ue_methods->ue_setmulti(ue); } int uether_ifmedia_upd(struct ifnet *ifp) { return (ue_ifmedia_upd(ifp)); } static int ue_ifmedia_upd(struct ifnet *ifp) { struct usb_ether *ue = ifp->if_softc; /* Defer to process context */ UE_LOCK(ue); ue_queue_command(ue, ue_ifmedia_task, &ue->ue_media_task[0].hdr, &ue->ue_media_task[1].hdr); UE_UNLOCK(ue); return (0); } static void ue_ifmedia_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; struct ifnet *ifp = ue->ue_ifp; ue->ue_methods->ue_mii_upd(ifp); } static void ue_watchdog(void *arg) { struct usb_ether *ue = arg; struct ifnet *ifp = ue->ue_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; ue_queue_command(ue, ue_tick_task, &ue->ue_tick_task[0].hdr, &ue->ue_tick_task[1].hdr); usb_callout_reset(&ue->ue_watchdog, hz, ue_watchdog, ue); } static void ue_tick_task(struct usb_proc_msg *_task) { struct usb_ether_cfg_task *task = (struct usb_ether_cfg_task *)_task; struct usb_ether *ue = task->ue; struct ifnet *ifp = ue->ue_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; ue->ue_methods->ue_tick(ue); } int uether_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct usb_ether *ue = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; struct mii_data *mii; int error = 0; switch (command) { case SIOCSIFFLAGS: UE_LOCK(ue); if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING) ue_queue_command(ue, ue_promisc_task, &ue->ue_promisc_task[0].hdr, &ue->ue_promisc_task[1].hdr); else ue_queue_command(ue, ue_start_task, &ue->ue_sync_task[0].hdr, &ue->ue_sync_task[1].hdr); } else { ue_queue_command(ue, ue_stop_task, &ue->ue_sync_task[0].hdr, &ue->ue_sync_task[1].hdr); } UE_UNLOCK(ue); break; case SIOCADDMULTI: case SIOCDELMULTI: UE_LOCK(ue); ue_queue_command(ue, ue_setmulti_task, &ue->ue_multi_task[0].hdr, &ue->ue_multi_task[1].hdr); UE_UNLOCK(ue); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: if (ue->ue_miibus != NULL) { mii = device_get_softc(ue->ue_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); } else error = ether_ioctl(ifp, command, data); break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static int uether_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: ueunit = new_unrhdr(0, INT_MAX, NULL); break; case MOD_UNLOAD: break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t uether_mod = { "uether", uether_modevent, 0 }; struct mbuf * uether_newbuf(void) { struct mbuf *m_new; m_new = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m_new == NULL) return (NULL); m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; m_adj(m_new, ETHER_ALIGN); return (m_new); } int uether_rxmbuf(struct usb_ether *ue, struct mbuf *m, unsigned int len) { struct ifnet *ifp = ue->ue_ifp; UE_LOCK_ASSERT(ue, MA_OWNED); /* finalize mbuf */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; /* enqueue for later when the lock can be released */ (void)mbufq_enqueue(&ue->ue_rxq, m); return (0); } int uether_rxbuf(struct usb_ether *ue, struct usb_page_cache *pc, unsigned int offset, unsigned int len) { struct ifnet *ifp = ue->ue_ifp; struct mbuf *m; UE_LOCK_ASSERT(ue, MA_OWNED); if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) return (1); m = uether_newbuf(); if (m == NULL) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); return (ENOMEM); } usbd_copy_out(pc, offset, mtod(m, uint8_t *), len); /* finalize mbuf */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; /* enqueue for later when the lock can be released */ (void)mbufq_enqueue(&ue->ue_rxq, m); return (0); } void uether_rxflush(struct usb_ether *ue) { struct ifnet *ifp = ue->ue_ifp; struct epoch_tracker et; struct mbuf *m, *n; UE_LOCK_ASSERT(ue, MA_OWNED); n = mbufq_flush(&ue->ue_rxq); UE_UNLOCK(ue); NET_EPOCH_ENTER(et); while ((m = n) != NULL) { n = STAILQ_NEXT(m, m_stailqpkt); m->m_nextpkt = NULL; ifp->if_input(ifp, m); } NET_EPOCH_EXIT(et); UE_LOCK(ue); } /* * USB net drivers are run by DRIVER_MODULE() thus SI_SUB_DRIVERS, * SI_ORDER_MIDDLE. Run uether after that. */ DECLARE_MODULE(uether, uether_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(uether, 1); Index: head/sys/dev/usb/serial/u3g.c =================================================================== --- head/sys/dev/usb/serial/u3g.c (revision 357971) +++ head/sys/dev/usb/serial/u3g.c (revision 357972) @@ -1,1285 +1,1286 @@ /* * Copyright (c) 2008 AnyWi Technologies * Author: Andrea Guzzo * * based on uark.c 1.1 2006/08/14 08:30:22 jsg * * * parts from ubsa.c 183348 2008-09-25 12:00:56Z phk * * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ /* * NOTE: * * - The detour through the tty layer is ridiculously expensive wrt * buffering due to the high speeds. * * We should consider adding a simple r/w device which allows * attaching of PPP in a more efficient way. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR u3g_debug #include #include #include #include #include #ifdef USB_DEBUG static int u3g_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, u3g, CTLFLAG_RW, 0, "USB 3g"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, u3g, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB 3g"); SYSCTL_INT(_hw_usb_u3g, OID_AUTO, debug, CTLFLAG_RWTUN, &u3g_debug, 0, "Debug level"); #endif #define U3G_MAXPORTS 12 #define U3G_CONFIG_INDEX 0 #define U3G_BSIZE 2048 #define U3G_TXSIZE (U3G_BSIZE / U3G_TXFRAMES) #define U3G_TXFRAMES 4 /* Eject methods; See also usb_quirks.h:UQ_MSC_EJECT_* */ #define U3GINIT_HUAWEI 1 /* Requires Huawei init command */ #define U3GINIT_SIERRA 2 /* Requires Sierra init command */ #define U3GINIT_SCSIEJECT 3 /* Requires SCSI eject command */ #define U3GINIT_REZERO 4 /* Requires SCSI rezero command */ #define U3GINIT_ZTESTOR 5 /* Requires ZTE SCSI command */ #define U3GINIT_CMOTECH 6 /* Requires CMOTECH SCSI command */ #define U3GINIT_WAIT 7 /* Device reappears after a delay */ #define U3GINIT_SAEL_M460 8 /* Requires vendor init */ #define U3GINIT_HUAWEISCSI 9 /* Requires Huawei SCSI init command */ #define U3GINIT_HUAWEISCSI2 10 /* Requires Huawei SCSI init command (2) */ #define U3GINIT_TCT 11 /* Requires TCT Mobile init command */ enum { U3G_BULK_WR, U3G_BULK_RD, U3G_INTR, U3G_N_TRANSFER, }; struct u3g_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom[U3G_MAXPORTS]; struct usb_xfer *sc_xfer[U3G_MAXPORTS][U3G_N_TRANSFER]; uint8_t sc_iface[U3G_MAXPORTS]; /* local status register */ uint8_t sc_lsr[U3G_MAXPORTS]; /* local status register */ uint8_t sc_msr[U3G_MAXPORTS]; /* u3g status register */ uint16_t sc_line[U3G_MAXPORTS]; /* line status */ struct usb_device *sc_udev; struct mtx sc_mtx; uint8_t sc_numports; }; static device_probe_t u3g_probe; static device_attach_t u3g_attach; static device_detach_t u3g_detach; static void u3g_free_softc(struct u3g_softc *); static usb_callback_t u3g_write_callback; static usb_callback_t u3g_read_callback; static usb_callback_t u3g_intr_callback; static void u3g_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void u3g_cfg_set_dtr(struct ucom_softc *, uint8_t); static void u3g_cfg_set_rts(struct ucom_softc *, uint8_t); static void u3g_start_read(struct ucom_softc *ucom); static void u3g_stop_read(struct ucom_softc *ucom); static void u3g_start_write(struct ucom_softc *ucom); static void u3g_stop_write(struct ucom_softc *ucom); static void u3g_poll(struct ucom_softc *ucom); static void u3g_free(struct ucom_softc *ucom); static void u3g_test_autoinst(void *, struct usb_device *, struct usb_attach_arg *); static int u3g_driver_loaded(struct module *mod, int what, void *arg); static eventhandler_tag u3g_etag; static const struct usb_config u3g_config[U3G_N_TRANSFER] = { [U3G_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = U3G_BSIZE,/* bytes */ .frames = U3G_TXFRAMES, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &u3g_write_callback, }, [U3G_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = U3G_BSIZE,/* bytes */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &u3g_read_callback, }, [U3G_INTR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &u3g_intr_callback, }, }; static const struct ucom_callback u3g_callback = { .ucom_cfg_get_status = &u3g_cfg_get_status, .ucom_cfg_set_dtr = &u3g_cfg_set_dtr, .ucom_cfg_set_rts = &u3g_cfg_set_rts, .ucom_start_read = &u3g_start_read, .ucom_stop_read = &u3g_stop_read, .ucom_start_write = &u3g_start_write, .ucom_stop_write = &u3g_stop_write, .ucom_poll = &u3g_poll, .ucom_free = &u3g_free, }; static device_method_t u3g_methods[] = { DEVMETHOD(device_probe, u3g_probe), DEVMETHOD(device_attach, u3g_attach), DEVMETHOD(device_detach, u3g_detach), DEVMETHOD_END }; static devclass_t u3g_devclass; static driver_t u3g_driver = { .name = "u3g", .methods = u3g_methods, .size = sizeof(struct u3g_softc), }; static const STRUCT_USB_HOST_ID u3g_devs[] = { #define U3G_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } U3G_DEV(ABIT, AK_020, 0), U3G_DEV(ACERP, H10, 0), U3G_DEV(AIRPLUS, MCD650, 0), U3G_DEV(AIRPRIME, PC5220, 0), U3G_DEV(AIRPRIME, AC313U, 0), U3G_DEV(ALINK, 3G, 0), U3G_DEV(ALINK, 3GU, 0), U3G_DEV(ALINK, DWM652U5, 0), U3G_DEV(ALINK, SIM7600E, 0), U3G_DEV(AMOI, H01, 0), U3G_DEV(AMOI, H01A, 0), U3G_DEV(AMOI, H02, 0), U3G_DEV(ANYDATA, ADU_500A, 0), U3G_DEV(ANYDATA, ADU_620UW, 0), U3G_DEV(ANYDATA, ADU_E100X, 0), U3G_DEV(AXESSTEL, DATAMODEM, 0), U3G_DEV(CMOTECH, CDMA_MODEM1, 0), U3G_DEV(CMOTECH, CGU628, U3GINIT_CMOTECH), U3G_DEV(DELL, U5500, 0), U3G_DEV(DELL, U5505, 0), U3G_DEV(DELL, U5510, 0), U3G_DEV(DELL, U5520, 0), U3G_DEV(DELL, U5520_2, 0), U3G_DEV(DELL, U5520_3, 0), U3G_DEV(DELL, U5700, 0), U3G_DEV(DELL, U5700_2, 0), U3G_DEV(DELL, U5700_3, 0), U3G_DEV(DELL, U5700_4, 0), U3G_DEV(DELL, U5720, 0), U3G_DEV(DELL, U5720_2, 0), U3G_DEV(DELL, U5730, 0), U3G_DEV(DELL, U5730_2, 0), U3G_DEV(DELL, U5730_3, 0), U3G_DEV(DELL, U740, 0), U3G_DEV(DLINK, DWR510_CD, U3GINIT_SCSIEJECT), U3G_DEV(DLINK, DWR510, 0), U3G_DEV(DLINK, DWM157_CD, U3GINIT_SCSIEJECT), U3G_DEV(DLINK, DWM157, 0), U3G_DEV(DLINK, DWM222_CD, U3GINIT_SCSIEJECT), U3G_DEV(DLINK, DWM222, 0), U3G_DEV(DLINK3, DWM652, 0), U3G_DEV(HP, EV2200, 0), U3G_DEV(HP, HS2300, 0), U3G_DEV(HP, UN2420_QDL, 0), U3G_DEV(HP, UN2420, 0), U3G_DEV(HUAWEI, E1401, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1402, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1403, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1404, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1405, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1406, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1407, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1408, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1409, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E140A, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E140B, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E140D, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E140E, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E140F, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1410, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1411, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1412, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1413, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1414, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1415, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1416, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1417, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1418, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1419, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E141A, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E141B, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E141C, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E141D, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E141E, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E141F, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1420, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1421, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1422, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1423, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1424, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1425, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1426, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1427, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1428, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1429, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E142A, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E142B, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E142C, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E142D, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E142E, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E142F, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1430, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1431, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1432, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1433, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1434, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1435, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1436, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1437, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1438, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1439, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E143A, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E143B, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E143C, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E143D, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E143E, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E143F, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E173, 0), U3G_DEV(HUAWEI, E173_INIT, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, E3131, 0), U3G_DEV(HUAWEI, E3131_INIT, U3GINIT_HUAWEISCSI2), U3G_DEV(HUAWEI, E180V, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E220, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E220BIS, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E392, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, ME909U, U3GINIT_HUAWEISCSI2), U3G_DEV(HUAWEI, ME909S, U3GINIT_HUAWEISCSI2), U3G_DEV(HUAWEI, MOBILE, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E1752, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, E1820, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, K3771, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, K3771_INIT, U3GINIT_HUAWEISCSI2), U3G_DEV(HUAWEI, K3772, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, K3772_INIT, U3GINIT_HUAWEISCSI2), U3G_DEV(HUAWEI, K3765, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, K3765_INIT, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, K3770, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, K3770_INIT, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, K4505, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, K4505_INIT, U3GINIT_HUAWEISCSI), U3G_DEV(HUAWEI, ETS2055, U3GINIT_HUAWEI), U3G_DEV(HUAWEI, E3272_INIT, U3GINIT_HUAWEISCSI2), U3G_DEV(HUAWEI, E3272, 0), U3G_DEV(KYOCERA2, CDMA_MSM_K, 0), U3G_DEV(KYOCERA2, KPC680, 0), U3G_DEV(LONGCHEER, WM66, U3GINIT_HUAWEI), U3G_DEV(LONGCHEER, DISK, U3GINIT_TCT), U3G_DEV(LONGCHEER, W14, 0), U3G_DEV(LONGCHEER, XSSTICK, 0), U3G_DEV(MERLIN, V620, 0), U3G_DEV(NEOTEL, PRIME, 0), U3G_DEV(NOVATEL, E725, 0), U3G_DEV(NOVATEL, ES620, 0), U3G_DEV(NOVATEL, ES620_2, 0), U3G_DEV(NOVATEL, EU730, 0), U3G_DEV(NOVATEL, EU740, 0), U3G_DEV(NOVATEL, EU870D, 0), U3G_DEV(NOVATEL, MC760, 0), U3G_DEV(NOVATEL, MC547, 0), U3G_DEV(NOVATEL, MC679, 0), U3G_DEV(NOVATEL, MC950D, 0), U3G_DEV(NOVATEL, MC990D, 0), U3G_DEV(NOVATEL, MIFI2200, U3GINIT_SCSIEJECT), U3G_DEV(NOVATEL, MIFI2200V, U3GINIT_SCSIEJECT), U3G_DEV(NOVATEL, U720, 0), U3G_DEV(NOVATEL, U727, 0), U3G_DEV(NOVATEL, U727_2, 0), U3G_DEV(NOVATEL, U740, 0), U3G_DEV(NOVATEL, U740_2, 0), U3G_DEV(NOVATEL, U760, U3GINIT_SCSIEJECT), U3G_DEV(NOVATEL, U870, 0), U3G_DEV(NOVATEL, V620, 0), U3G_DEV(NOVATEL, V640, 0), U3G_DEV(NOVATEL, V720, 0), U3G_DEV(NOVATEL, V740, 0), U3G_DEV(NOVATEL, X950D, 0), U3G_DEV(NOVATEL, XU870, 0), U3G_DEV(MOTOROLA2, MB886, U3GINIT_SCSIEJECT), U3G_DEV(OPTION, E6500, 0), U3G_DEV(OPTION, E6501, 0), U3G_DEV(OPTION, E6601, 0), U3G_DEV(OPTION, E6721, 0), U3G_DEV(OPTION, E6741, 0), U3G_DEV(OPTION, E6761, 0), U3G_DEV(OPTION, E6800, 0), U3G_DEV(OPTION, E7021, 0), U3G_DEV(OPTION, E7041, 0), U3G_DEV(OPTION, E7061, 0), U3G_DEV(OPTION, E7100, 0), U3G_DEV(OPTION, GE40X, 0), U3G_DEV(OPTION, GT3G, 0), U3G_DEV(OPTION, GT3GPLUS, 0), U3G_DEV(OPTION, GT3GQUAD, 0), U3G_DEV(OPTION, GT3G_1, 0), U3G_DEV(OPTION, GT3G_2, 0), U3G_DEV(OPTION, GT3G_3, 0), U3G_DEV(OPTION, GT3G_4, 0), U3G_DEV(OPTION, GT3G_5, 0), U3G_DEV(OPTION, GT3G_6, 0), U3G_DEV(OPTION, GTHSDPA, 0), U3G_DEV(OPTION, GTM380, 0), U3G_DEV(OPTION, GTMAX36, 0), U3G_DEV(OPTION, GTMAX380HSUPAE, 0), U3G_DEV(OPTION, GTMAXHSUPA, 0), U3G_DEV(OPTION, GTMAXHSUPAE, 0), U3G_DEV(OPTION, VODAFONEMC3G, 0), U3G_DEV(QISDA, H20_1, 0), U3G_DEV(QISDA, H20_2, 0), U3G_DEV(QISDA, H21_1, 0), U3G_DEV(QISDA, H21_2, 0), U3G_DEV(QUALCOMM, NTT_L02C_MODEM, U3GINIT_SCSIEJECT), U3G_DEV(QUALCOMM2, AC8700, 0), U3G_DEV(QUALCOMM2, MF330, 0), U3G_DEV(QUALCOMM2, SIM5218, 0), U3G_DEV(QUALCOMM2, WM620, 0), U3G_DEV(QUALCOMM2, VW110L, U3GINIT_SCSIEJECT), U3G_DEV(QUALCOMM2, GOBI2000_QDL, 0), U3G_DEV(QUALCOMM2, GOBI2000, 0), U3G_DEV(QUALCOMM2, VT80N, 0), U3G_DEV(QUALCOMM3, VFAST2, 0), U3G_DEV(QUALCOMMINC, AC2726, 0), U3G_DEV(QUALCOMMINC, AC682_INIT, U3GINIT_SCSIEJECT), U3G_DEV(QUALCOMMINC, AC682, 0), U3G_DEV(QUALCOMMINC, AC8700, 0), U3G_DEV(QUALCOMMINC, AC8710, 0), U3G_DEV(QUALCOMMINC, CDMA_MSM, U3GINIT_SCSIEJECT), U3G_DEV(QUALCOMMINC, E0002, 0), U3G_DEV(QUALCOMMINC, E0003, 0), U3G_DEV(QUALCOMMINC, E0004, 0), U3G_DEV(QUALCOMMINC, E0005, 0), U3G_DEV(QUALCOMMINC, E0006, 0), U3G_DEV(QUALCOMMINC, E0007, 0), U3G_DEV(QUALCOMMINC, E0008, 0), U3G_DEV(QUALCOMMINC, E0009, 0), U3G_DEV(QUALCOMMINC, E000A, 0), U3G_DEV(QUALCOMMINC, E000B, 0), U3G_DEV(QUALCOMMINC, E000C, 0), U3G_DEV(QUALCOMMINC, E000D, 0), U3G_DEV(QUALCOMMINC, E000E, 0), U3G_DEV(QUALCOMMINC, E000F, 0), U3G_DEV(QUALCOMMINC, E0010, 0), U3G_DEV(QUALCOMMINC, E0011, 0), U3G_DEV(QUALCOMMINC, E0012, 0), U3G_DEV(QUALCOMMINC, E0013, 0), U3G_DEV(QUALCOMMINC, E0014, 0), U3G_DEV(QUALCOMMINC, E0017, 0), U3G_DEV(QUALCOMMINC, E0018, 0), U3G_DEV(QUALCOMMINC, E0019, 0), U3G_DEV(QUALCOMMINC, E0020, 0), U3G_DEV(QUALCOMMINC, E0021, 0), U3G_DEV(QUALCOMMINC, E0022, 0), U3G_DEV(QUALCOMMINC, E0023, 0), U3G_DEV(QUALCOMMINC, E0024, 0), U3G_DEV(QUALCOMMINC, E0025, 0), U3G_DEV(QUALCOMMINC, E0026, 0), U3G_DEV(QUALCOMMINC, E0027, 0), U3G_DEV(QUALCOMMINC, E0028, 0), U3G_DEV(QUALCOMMINC, E0029, 0), U3G_DEV(QUALCOMMINC, E0030, 0), U3G_DEV(QUALCOMMINC, E0032, 0), U3G_DEV(QUALCOMMINC, E0033, 0), U3G_DEV(QUALCOMMINC, E0037, 0), U3G_DEV(QUALCOMMINC, E0039, 0), U3G_DEV(QUALCOMMINC, E0042, 0), U3G_DEV(QUALCOMMINC, E0043, 0), U3G_DEV(QUALCOMMINC, E0048, 0), U3G_DEV(QUALCOMMINC, E0049, 0), U3G_DEV(QUALCOMMINC, E0051, 0), U3G_DEV(QUALCOMMINC, E0052, 0), U3G_DEV(QUALCOMMINC, E0054, 0), U3G_DEV(QUALCOMMINC, E0055, 0), U3G_DEV(QUALCOMMINC, E0057, 0), U3G_DEV(QUALCOMMINC, E0058, 0), U3G_DEV(QUALCOMMINC, E0059, 0), U3G_DEV(QUALCOMMINC, E0060, 0), U3G_DEV(QUALCOMMINC, E0061, 0), U3G_DEV(QUALCOMMINC, E0062, 0), U3G_DEV(QUALCOMMINC, E0063, 0), U3G_DEV(QUALCOMMINC, E0064, 0), U3G_DEV(QUALCOMMINC, E0066, 0), U3G_DEV(QUALCOMMINC, E0069, 0), U3G_DEV(QUALCOMMINC, E0070, 0), U3G_DEV(QUALCOMMINC, E0073, 0), U3G_DEV(QUALCOMMINC, E0076, 0), U3G_DEV(QUALCOMMINC, E0078, 0), U3G_DEV(QUALCOMMINC, E0082, 0), U3G_DEV(QUALCOMMINC, E0086, 0), U3G_DEV(QUALCOMMINC, SURFSTICK, 0), U3G_DEV(QUALCOMMINC, E2002, 0), U3G_DEV(QUALCOMMINC, E2003, 0), U3G_DEV(QUALCOMMINC, K3772_Z, 0), U3G_DEV(QUALCOMMINC, K3772_Z_INIT, U3GINIT_SCSIEJECT), U3G_DEV(QUALCOMMINC, MF112, U3GINIT_ZTESTOR), U3G_DEV(QUALCOMMINC, MF195E, 0), U3G_DEV(QUALCOMMINC, MF195E_INIT, U3GINIT_SCSIEJECT), U3G_DEV(QUALCOMMINC, MF626, 0), U3G_DEV(QUALCOMMINC, MF628, 0), U3G_DEV(QUALCOMMINC, MF633R, 0), /* the following is a RNDIS device, no modem features */ U3G_DEV(QUALCOMMINC, ZTE_MF730M, U3GINIT_SCSIEJECT), U3G_DEV(QUANTA, GKE, 0), U3G_DEV(QUANTA, GLE, 0), U3G_DEV(QUANTA, GLX, 0), U3G_DEV(QUANTA, Q101, 0), U3G_DEV(QUANTA, Q111, 0), U3G_DEV(QUECTEL, EC25, 0), U3G_DEV(SIERRA, AC402, 0), U3G_DEV(SIERRA, AC595U, 0), U3G_DEV(SIERRA, AC313U, 0), U3G_DEV(SIERRA, AC597E, 0), U3G_DEV(SIERRA, AC875, 0), U3G_DEV(SIERRA, AC875E, 0), U3G_DEV(SIERRA, AC875U, 0), U3G_DEV(SIERRA, AC875U_2, 0), U3G_DEV(SIERRA, AC880, 0), U3G_DEV(SIERRA, AC880E, 0), U3G_DEV(SIERRA, AC880U, 0), U3G_DEV(SIERRA, AC881, 0), U3G_DEV(SIERRA, AC881E, 0), U3G_DEV(SIERRA, AC881U, 0), U3G_DEV(SIERRA, AC885E, 0), U3G_DEV(SIERRA, AC885E_2, 0), U3G_DEV(SIERRA, AC885U, 0), U3G_DEV(SIERRA, AIRCARD580, 0), U3G_DEV(SIERRA, AIRCARD595, 0), U3G_DEV(SIERRA, C22, 0), U3G_DEV(SIERRA, C597, 0), U3G_DEV(SIERRA, C888, 0), U3G_DEV(SIERRA, E0029, 0), U3G_DEV(SIERRA, E6892, 0), U3G_DEV(SIERRA, E6893, 0), U3G_DEV(SIERRA, EM5625, 0), U3G_DEV(SIERRA, EM5725, 0), U3G_DEV(SIERRA, MC5720, 0), U3G_DEV(SIERRA, MC5720_2, 0), U3G_DEV(SIERRA, MC5725, 0), U3G_DEV(SIERRA, MC5727, 0), U3G_DEV(SIERRA, MC5727_2, 0), U3G_DEV(SIERRA, MC5728, 0), U3G_DEV(SIERRA, MC7354, 0), U3G_DEV(SIERRA, MC7355, 0), U3G_DEV(SIERRA, MC7430, 0), U3G_DEV(SIERRA, MC8700, 0), U3G_DEV(SIERRA, MC8755, 0), U3G_DEV(SIERRA, MC8755_2, 0), U3G_DEV(SIERRA, MC8755_3, 0), U3G_DEV(SIERRA, MC8755_4, 0), U3G_DEV(SIERRA, MC8765, 0), U3G_DEV(SIERRA, MC8765_2, 0), U3G_DEV(SIERRA, MC8765_3, 0), U3G_DEV(SIERRA, MC8775, 0), U3G_DEV(SIERRA, MC8775_2, 0), U3G_DEV(SIERRA, MC8780, 0), U3G_DEV(SIERRA, MC8780_2, 0), U3G_DEV(SIERRA, MC8780_3, 0), U3G_DEV(SIERRA, MC8781, 0), U3G_DEV(SIERRA, MC8781_2, 0), U3G_DEV(SIERRA, MC8781_3, 0), U3G_DEV(SIERRA, MC8785, 0), U3G_DEV(SIERRA, MC8785_2, 0), U3G_DEV(SIERRA, MC8790, 0), U3G_DEV(SIERRA, MC8791, 0), U3G_DEV(SIERRA, MC8792, 0), U3G_DEV(SIERRA, MINI5725, 0), U3G_DEV(SIERRA, T11, 0), U3G_DEV(SIERRA, T598, 0), U3G_DEV(SILABS, SAEL, U3GINIT_SAEL_M460), U3G_DEV(STELERA, C105, 0), U3G_DEV(STELERA, E1003, 0), U3G_DEV(STELERA, E1004, 0), U3G_DEV(STELERA, E1005, 0), U3G_DEV(STELERA, E1006, 0), U3G_DEV(STELERA, E1007, 0), U3G_DEV(STELERA, E1008, 0), U3G_DEV(STELERA, E1009, 0), U3G_DEV(STELERA, E100A, 0), U3G_DEV(STELERA, E100B, 0), U3G_DEV(STELERA, E100C, 0), U3G_DEV(STELERA, E100D, 0), U3G_DEV(STELERA, E100E, 0), U3G_DEV(STELERA, E100F, 0), U3G_DEV(STELERA, E1010, 0), U3G_DEV(STELERA, E1011, 0), U3G_DEV(STELERA, E1012, 0), U3G_DEV(TCTMOBILE, X060S, 0), U3G_DEV(TCTMOBILE, X080S, U3GINIT_TCT), U3G_DEV(TELIT, UC864E, 0), U3G_DEV(TELIT, UC864G, 0), U3G_DEV(TLAYTECH, TEU800, 0), U3G_DEV(TOSHIBA, G450, 0), U3G_DEV(TOSHIBA, HSDPA, 0), U3G_DEV(YISO, C893, 0), U3G_DEV(WETELECOM, WM_D200, 0), /* Autoinstallers */ U3G_DEV(NOVATEL, ZEROCD, U3GINIT_SCSIEJECT), U3G_DEV(OPTION, GTICON322, U3GINIT_REZERO), U3G_DEV(QUALCOMMINC, ZTE_STOR, U3GINIT_ZTESTOR), U3G_DEV(QUALCOMMINC, ZTE_STOR2, U3GINIT_SCSIEJECT), U3G_DEV(QUANTA, Q101_STOR, U3GINIT_SCSIEJECT), U3G_DEV(SIERRA, TRUINSTALL, U3GINIT_SIERRA), #undef U3G_DEV }; DRIVER_MODULE(u3g, uhub, u3g_driver, u3g_devclass, u3g_driver_loaded, 0); MODULE_DEPEND(u3g, ucom, 1, 1, 1); MODULE_DEPEND(u3g, usb, 1, 1, 1); MODULE_VERSION(u3g, 1); USB_PNP_HOST_INFO(u3g_devs); static int u3g_sierra_init(struct usb_device *udev) { struct usb_device_request req; req.bmRequestType = UT_VENDOR; req.bRequest = UR_SET_INTERFACE; USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); USETW(req.wIndex, UHF_PORT_CONNECTION); USETW(req.wLength, 0); if (usbd_do_request_flags(udev, NULL, &req, NULL, 0, NULL, USB_MS_HZ)) { /* ignore any errors */ } return (0); } static int u3g_huawei_init(struct usb_device *udev) { struct usb_device_request req; req.bmRequestType = UT_WRITE_DEVICE; req.bRequest = UR_SET_FEATURE; USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); USETW(req.wIndex, UHF_PORT_SUSPEND); USETW(req.wLength, 0); if (usbd_do_request_flags(udev, NULL, &req, NULL, 0, NULL, USB_MS_HZ)) { /* ignore any errors */ } return (0); } static int u3g_huawei_is_cdce(uint16_t idVendor, uint8_t bInterfaceSubClass, uint8_t bInterfaceProtocol) { /* * This function returns non-zero if the interface being * probed is of type CDC ethernet, which the U3G driver should * not attach to. See sys/dev/usb/net/if_cdce.c for matching * entries. */ if (idVendor != USB_VENDOR_HUAWEI) goto done; switch (bInterfaceSubClass) { case 0x02: switch (bInterfaceProtocol) { case 0x16: case 0x46: case 0x76: return (1); default: break; } break; case 0x03: switch (bInterfaceProtocol) { case 0x16: return (1); default: break; } break; default: break; } done: return (0); } static void u3g_sael_m460_init(struct usb_device *udev) { static const uint8_t setup[][24] = { { 0x41, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02 }, { 0xc1, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }, { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }, { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 }, { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }, { 0x41, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 }, { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 }, { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }, { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, }; struct usb_device_request req; usb_error_t err; uint16_t len; uint8_t buf[0x300]; uint8_t n; DPRINTFN(1, "\n"); if (usbd_req_set_alt_interface_no(udev, NULL, 0, 0)) { DPRINTFN(0, "Alt setting 0 failed\n"); return; } for (n = 0; n != nitems(setup); n++) { memcpy(&req, setup[n], sizeof(req)); len = UGETW(req.wLength); if (req.bmRequestType & UE_DIR_IN) { if (len > sizeof(buf)) { DPRINTFN(0, "too small buffer\n"); continue; } err = usbd_do_request(udev, NULL, &req, buf); } else { if (len > (sizeof(setup[0]) - 8)) { DPRINTFN(0, "too small buffer\n"); continue; } err = usbd_do_request(udev, NULL, &req, __DECONST(uint8_t *, &setup[n][8])); } if (err) { DPRINTFN(1, "request %u failed\n", (unsigned int)n); /* * Some of the requests will fail. Stop doing * requests when we are getting timeouts so * that we don't block the explore/attach * thread forever. */ if (err == USB_ERR_TIMEOUT) break; } } } /* * The following function handles 3G modem devices (E220, Mobile, * etc.) with auto-install flash disks for Windows/MacOSX on the first * interface. After some command or some delay they change appearance * to a modem. */ static void u3g_test_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; struct usb_interface_descriptor *id; int error; unsigned long method; if (uaa->dev_state != UAA_DEV_READY) return; iface = usbd_get_iface(udev, 0); if (iface == NULL) return; id = iface->idesc; if (id == NULL || id->bInterfaceClass != UICLASS_MASS) return; if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEI)) method = U3GINIT_HUAWEI; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SIERRA)) method = U3GINIT_SIERRA; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SCSIEJECT)) method = U3GINIT_SCSIEJECT; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_REZERO)) method = U3GINIT_REZERO; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_ZTESTOR)) method = U3GINIT_ZTESTOR; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_CMOTECH)) method = U3GINIT_CMOTECH; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_WAIT)) method = U3GINIT_WAIT; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI)) method = U3GINIT_HUAWEISCSI; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI2)) method = U3GINIT_HUAWEISCSI2; else if (usb_test_quirk(uaa, UQ_MSC_EJECT_TCT)) method = U3GINIT_TCT; else if (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa) == 0) method = USB_GET_DRIVER_INFO(uaa); else return; /* no device match */ if (bootverbose) { printf("Ejecting %s %s using method %ld\n", usb_get_manufacturer(udev), usb_get_product(udev), method); } switch (method) { case U3GINIT_HUAWEI: error = u3g_huawei_init(udev); break; case U3GINIT_HUAWEISCSI: error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI); break; case U3GINIT_HUAWEISCSI2: error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI2); break; case U3GINIT_SCSIEJECT: error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT); break; case U3GINIT_REZERO: error = usb_msc_eject(udev, 0, MSC_EJECT_REZERO); break; case U3GINIT_ZTESTOR: error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT); if (error == 0) error = usb_msc_eject(udev, 0, MSC_EJECT_ZTESTOR); break; case U3GINIT_CMOTECH: error = usb_msc_eject(udev, 0, MSC_EJECT_CMOTECH); break; case U3GINIT_TCT: error = usb_msc_eject(udev, 0, MSC_EJECT_TCT); break; case U3GINIT_SIERRA: error = u3g_sierra_init(udev); break; case U3GINIT_WAIT: /* Just pretend we ejected, the card will timeout */ error = 0; break; default: /* no 3G eject quirks */ error = EOPNOTSUPP; break; } if (error == 0) { /* success, mark the udev as disappearing */ uaa->dev_state = UAA_DEV_EJECTING; } } static int u3g_driver_loaded(struct module *mod, int what, void *arg) { switch (what) { case MOD_LOAD: /* register our autoinstall handler */ u3g_etag = EVENTHANDLER_REGISTER(usb_dev_configured, u3g_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); break; case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(usb_dev_configured, u3g_etag); break; default: return (EOPNOTSUPP); } return (0); } static int u3g_probe(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != U3G_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bInterfaceClass != UICLASS_VENDOR) { return (ENXIO); } if (u3g_huawei_is_cdce(uaa->info.idVendor, uaa->info.bInterfaceSubClass, uaa->info.bInterfaceProtocol)) { return (ENXIO); } return (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa)); } static int u3g_attach(device_t dev) { struct usb_config u3g_config_tmp[U3G_N_TRANSFER]; struct usb_attach_arg *uaa = device_get_ivars(dev); struct u3g_softc *sc = device_get_softc(dev); struct usb_interface *iface; struct usb_interface_descriptor *id; uint32_t iface_valid; int error, type, nports; int ep, n; uint8_t i; DPRINTF("sc=%p\n", sc); type = USB_GET_DRIVER_INFO(uaa); if (type == U3GINIT_SAEL_M460 || usb_test_quirk(uaa, UQ_MSC_EJECT_SAEL_M460)) { u3g_sael_m460_init(uaa->device); } /* copy in USB config */ for (n = 0; n != U3G_N_TRANSFER; n++) u3g_config_tmp[n] = u3g_config[n]; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "u3g", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_udev = uaa->device; /* Claim all interfaces on the device */ iface_valid = 0; for (i = uaa->info.bIfaceIndex; i < USB_IFACE_MAX; i++) { iface = usbd_get_iface(uaa->device, i); if (iface == NULL) break; id = usbd_get_interface_descriptor(iface); if (id == NULL || id->bInterfaceClass != UICLASS_VENDOR) continue; if (u3g_huawei_is_cdce(uaa->info.idVendor, id->bInterfaceSubClass, id->bInterfaceProtocol)) continue; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); iface_valid |= (1<device, &i, sc->sc_xfer[nports], u3g_config_tmp, U3G_N_TRANSFER, &sc->sc_ucom[nports], &sc->sc_mtx); if (error) { /* next interface */ i++; ep = 0; continue; } iface = usbd_get_iface(uaa->device, i); id = usbd_get_interface_descriptor(iface); sc->sc_iface[nports] = id->bInterfaceNumber; if (bootverbose && sc->sc_xfer[nports][U3G_INTR]) { device_printf(dev, "port %d supports modem control\n", nports); } /* set stall by default */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_WR]); usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_RD]); mtx_unlock(&sc->sc_mtx); nports++; /* found one port */ ep++; if (nports == U3G_MAXPORTS) break; } if (nports == 0) { device_printf(dev, "no ports found\n"); goto detach; } sc->sc_numports = nports; error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc, &u3g_callback, &sc->sc_mtx); if (error) { DPRINTF("ucom_attach failed\n"); goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); device_printf(dev, "Found %u port%s.\n", sc->sc_numports, sc->sc_numports > 1 ? "s":""); return (0); detach: u3g_detach(dev); return (ENXIO); } static int u3g_detach(device_t dev) { struct u3g_softc *sc = device_get_softc(dev); uint8_t subunit; DPRINTF("sc=%p\n", sc); /* NOTE: It is not dangerous to detach more ports than attached! */ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); for (subunit = 0; subunit != U3G_MAXPORTS; subunit++) usbd_transfer_unsetup(sc->sc_xfer[subunit], U3G_N_TRANSFER); device_claim_softc(dev); u3g_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(u3g); static void u3g_free_softc(struct u3g_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void u3g_free(struct ucom_softc *ucom) { u3g_free_softc(ucom->sc_parent); } static void u3g_start_read(struct ucom_softc *ucom) { struct u3g_softc *sc = ucom->sc_parent; /* start interrupt endpoint (if configured) */ usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]); } static void u3g_stop_read(struct ucom_softc *ucom) { struct u3g_softc *sc = ucom->sc_parent; /* stop interrupt endpoint (if configured) */ usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]); } static void u3g_start_write(struct ucom_softc *ucom) { struct u3g_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]); } static void u3g_stop_write(struct ucom_softc *ucom) { struct u3g_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]); } static void u3g_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ucom_softc *ucom = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; uint32_t frame; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: for (frame = 0; frame != U3G_TXFRAMES; frame++) { usbd_xfer_set_frame_offset(xfer, frame * U3G_TXSIZE, frame); pc = usbd_xfer_get_frame(xfer, frame); if (ucom_get_data(ucom, pc, 0, U3G_TXSIZE, &actlen) == 0) break; usbd_xfer_set_frame_len(xfer, frame, actlen); } if (frame != 0) { usbd_xfer_set_frames(xfer, frame); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* do a builtin clear-stall */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void u3g_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ucom_softc *ucom = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* do a builtin clear-stall */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void u3g_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct u3g_softc *sc = ucom->sc_parent; /* XXX Note: sc_lsr is always zero */ *lsr = sc->sc_lsr[ucom->sc_subunit]; *msr = sc->sc_msr[ucom->sc_subunit]; } static void u3g_cfg_set_line(struct ucom_softc *ucom) { struct u3g_softc *sc = ucom->sc_parent; struct usb_device_request req; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line[ucom->sc_subunit]); req.wIndex[0] = sc->sc_iface[ucom->sc_subunit]; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, ucom, &req, NULL, 0, 1000); } static void u3g_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct u3g_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_DTR; else sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_DTR; u3g_cfg_set_line(ucom); } static void u3g_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct u3g_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_RTS; else sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_RTS; u3g_cfg_set_line(ucom); } static void u3g_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct ucom_softc *ucom = usbd_xfer_softc(xfer); struct u3g_softc *sc = ucom->sc_parent; struct usb_page_cache *pc; struct usb_cdc_notification pkt; int actlen; uint16_t wLen; uint8_t mstatus; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 8) { /* usb_cdc_notification with 2 data bytes */ DPRINTF("message too short (expected 8, received %d)\n", actlen); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, actlen); wLen = UGETW(pkt.wLength); if (wLen < 2) { DPRINTF("message too short (expected 2 data bytes, received %d)\n", wLen); goto tr_setup; } if (pkt.bmRequestType == UCDC_NOTIFICATION && pkt.bNotification == UCDC_N_SERIAL_STATE) { /* * Set the serial state in ucom driver based on * the bits from the notify message */ DPRINTF("notify bytes = 0x%02x, 0x%02x\n", pkt.data[0], pkt.data[1]); /* currently, lsr is always zero. */ sc->sc_lsr[ucom->sc_subunit] = 0; sc->sc_msr[ucom->sc_subunit] = 0; mstatus = pkt.data[0]; if (mstatus & UCDC_N_SERIAL_RI) sc->sc_msr[ucom->sc_subunit] |= SER_RI; if (mstatus & UCDC_N_SERIAL_DSR) sc->sc_msr[ucom->sc_subunit] |= SER_DSR; if (mstatus & UCDC_N_SERIAL_DCD) sc->sc_msr[ucom->sc_subunit] |= SER_DCD; ucom_status_change(ucom); } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void u3g_poll(struct ucom_softc *ucom) { struct u3g_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer[ucom->sc_subunit], U3G_N_TRANSFER); } Index: head/sys/dev/usb/serial/ubsa.c =================================================================== --- head/sys/dev/usb/serial/ubsa.c (revision 357971) +++ head/sys/dev/usb/serial/ubsa.c (revision 357972) @@ -1,700 +1,701 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2002, Alexander Kabaev . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Ichiro FUKUHARA (ichiro@ichiro.org). * * 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. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ubsa_debug #include #include #include #ifdef USB_DEBUG static int ubsa_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ubsa, CTLFLAG_RW, 0, "USB ubsa"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ubsa, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ubsa"); SYSCTL_INT(_hw_usb_ubsa, OID_AUTO, debug, CTLFLAG_RWTUN, &ubsa_debug, 0, "ubsa debug level"); #endif #define UBSA_BSIZE 1024 /* bytes */ #define UBSA_CONFIG_INDEX 0 #define UBSA_IFACE_INDEX 0 #define UBSA_REG_BAUDRATE 0x00 #define UBSA_REG_STOP_BITS 0x01 #define UBSA_REG_DATA_BITS 0x02 #define UBSA_REG_PARITY 0x03 #define UBSA_REG_DTR 0x0A #define UBSA_REG_RTS 0x0B #define UBSA_REG_BREAK 0x0C #define UBSA_REG_FLOW_CTRL 0x10 #define UBSA_PARITY_NONE 0x00 #define UBSA_PARITY_EVEN 0x01 #define UBSA_PARITY_ODD 0x02 #define UBSA_PARITY_MARK 0x03 #define UBSA_PARITY_SPACE 0x04 #define UBSA_FLOW_NONE 0x0000 #define UBSA_FLOW_OCTS 0x0001 #define UBSA_FLOW_ODSR 0x0002 #define UBSA_FLOW_IDSR 0x0004 #define UBSA_FLOW_IDTR 0x0008 #define UBSA_FLOW_IRTS 0x0010 #define UBSA_FLOW_ORTS 0x0020 #define UBSA_FLOW_UNKNOWN 0x0040 #define UBSA_FLOW_OXON 0x0080 #define UBSA_FLOW_IXON 0x0100 /* line status register */ #define UBSA_LSR_TSRE 0x40 /* Transmitter empty: byte sent */ #define UBSA_LSR_TXRDY 0x20 /* Transmitter buffer empty */ #define UBSA_LSR_BI 0x10 /* Break detected */ #define UBSA_LSR_FE 0x08 /* Framing error: bad stop bit */ #define UBSA_LSR_PE 0x04 /* Parity error */ #define UBSA_LSR_OE 0x02 /* Overrun, lost incoming byte */ #define UBSA_LSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ #define UBSA_LSR_RCV_MASK 0x1f /* Mask for incoming data or error */ /* modem status register */ /* All deltas are from the last read of the MSR. */ #define UBSA_MSR_DCD 0x80 /* Current Data Carrier Detect */ #define UBSA_MSR_RI 0x40 /* Current Ring Indicator */ #define UBSA_MSR_DSR 0x20 /* Current Data Set Ready */ #define UBSA_MSR_CTS 0x10 /* Current Clear to Send */ #define UBSA_MSR_DDCD 0x08 /* DCD has changed state */ #define UBSA_MSR_TERI 0x04 /* RI has toggled low to high */ #define UBSA_MSR_DDSR 0x02 /* DSR has changed state */ #define UBSA_MSR_DCTS 0x01 /* CTS has changed state */ enum { UBSA_BULK_DT_WR, UBSA_BULK_DT_RD, UBSA_INTR_DT_RD, UBSA_N_TRANSFER, }; struct ubsa_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UBSA_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint8_t sc_iface_no; /* interface number */ uint8_t sc_iface_index; /* interface index */ uint8_t sc_lsr; /* local status register */ uint8_t sc_msr; /* UBSA status register */ }; static device_probe_t ubsa_probe; static device_attach_t ubsa_attach; static device_detach_t ubsa_detach; static void ubsa_free_softc(struct ubsa_softc *); static usb_callback_t ubsa_write_callback; static usb_callback_t ubsa_read_callback; static usb_callback_t ubsa_intr_callback; static void ubsa_cfg_request(struct ubsa_softc *, uint8_t, uint16_t); static void ubsa_free(struct ucom_softc *); static void ubsa_cfg_set_dtr(struct ucom_softc *, uint8_t); static void ubsa_cfg_set_rts(struct ucom_softc *, uint8_t); static void ubsa_cfg_set_break(struct ucom_softc *, uint8_t); static int ubsa_pre_param(struct ucom_softc *, struct termios *); static void ubsa_cfg_param(struct ucom_softc *, struct termios *); static void ubsa_start_read(struct ucom_softc *); static void ubsa_stop_read(struct ucom_softc *); static void ubsa_start_write(struct ucom_softc *); static void ubsa_stop_write(struct ucom_softc *); static void ubsa_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void ubsa_poll(struct ucom_softc *ucom); static const struct usb_config ubsa_config[UBSA_N_TRANSFER] = { [UBSA_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UBSA_BSIZE, /* bytes */ .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &ubsa_write_callback, }, [UBSA_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UBSA_BSIZE, /* bytes */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &ubsa_read_callback, }, [UBSA_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &ubsa_intr_callback, }, }; static const struct ucom_callback ubsa_callback = { .ucom_cfg_get_status = &ubsa_cfg_get_status, .ucom_cfg_set_dtr = &ubsa_cfg_set_dtr, .ucom_cfg_set_rts = &ubsa_cfg_set_rts, .ucom_cfg_set_break = &ubsa_cfg_set_break, .ucom_cfg_param = &ubsa_cfg_param, .ucom_pre_param = &ubsa_pre_param, .ucom_start_read = &ubsa_start_read, .ucom_stop_read = &ubsa_stop_read, .ucom_start_write = &ubsa_start_write, .ucom_stop_write = &ubsa_stop_write, .ucom_poll = &ubsa_poll, .ucom_free = &ubsa_free, }; static const STRUCT_USB_HOST_ID ubsa_devs[] = { /* AnyData ADU-500A */ {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A, 0)}, /* AnyData ADU-E100A/H */ {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X, 0)}, /* Axesstel MV100H */ {USB_VPI(USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM, 0)}, /* BELKIN F5U103 */ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103, 0)}, /* BELKIN F5U120 */ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120, 0)}, /* GoHubs GO-COM232 */ {USB_VPI(USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM, 0)}, /* GoHubs GO-COM232 */ {USB_VPI(USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232, 0)}, /* Peracom */ {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1, 0)}, }; static device_method_t ubsa_methods[] = { DEVMETHOD(device_probe, ubsa_probe), DEVMETHOD(device_attach, ubsa_attach), DEVMETHOD(device_detach, ubsa_detach), DEVMETHOD_END }; static devclass_t ubsa_devclass; static driver_t ubsa_driver = { .name = "ubsa", .methods = ubsa_methods, .size = sizeof(struct ubsa_softc), }; DRIVER_MODULE(ubsa, uhub, ubsa_driver, ubsa_devclass, NULL, 0); MODULE_DEPEND(ubsa, ucom, 1, 1, 1); MODULE_DEPEND(ubsa, usb, 1, 1, 1); MODULE_VERSION(ubsa, 1); USB_PNP_HOST_INFO(ubsa_devs); static int ubsa_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UBSA_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bIfaceIndex != UBSA_IFACE_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(ubsa_devs, sizeof(ubsa_devs), uaa)); } static int ubsa_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ubsa_softc *sc = device_get_softc(dev); int error; DPRINTF("sc=%p\n", sc); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "ubsa", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_udev = uaa->device; sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = UBSA_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, sc->sc_xfer, ubsa_config, UBSA_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("could not allocate all pipes\n"); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &ubsa_callback, &sc->sc_mtx); if (error) { DPRINTF("ucom_attach failed\n"); goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: ubsa_detach(dev); return (ENXIO); } static int ubsa_detach(device_t dev) { struct ubsa_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UBSA_N_TRANSFER); device_claim_softc(dev); ubsa_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(ubsa); static void ubsa_free_softc(struct ubsa_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void ubsa_free(struct ucom_softc *ucom) { ubsa_free_softc(ucom->sc_parent); } static void ubsa_cfg_request(struct ubsa_softc *sc, uint8_t index, uint16_t value) { struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = index; USETW(req.wValue, value); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); if (err) { DPRINTFN(0, "device request failed, err=%s " "(ignored)\n", usbd_errstr(err)); } } static void ubsa_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct ubsa_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); ubsa_cfg_request(sc, UBSA_REG_DTR, onoff ? 1 : 0); } static void ubsa_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct ubsa_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); ubsa_cfg_request(sc, UBSA_REG_RTS, onoff ? 1 : 0); } static void ubsa_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct ubsa_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); ubsa_cfg_request(sc, UBSA_REG_BREAK, onoff ? 1 : 0); } static int ubsa_pre_param(struct ucom_softc *ucom, struct termios *t) { DPRINTF("sc = %p\n", ucom->sc_parent); switch (t->c_ospeed) { case B0: case B300: case B600: case B1200: case B2400: case B4800: case B9600: case B19200: case B38400: case B57600: case B115200: case B230400: break; default: return (EINVAL); } return (0); } static void ubsa_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct ubsa_softc *sc = ucom->sc_parent; uint16_t value = 0; DPRINTF("sc = %p\n", sc); switch (t->c_ospeed) { case B0: ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, 0); ubsa_cfg_set_dtr(&sc->sc_ucom, 0); ubsa_cfg_set_rts(&sc->sc_ucom, 0); break; case B300: case B600: case B1200: case B2400: case B4800: case B9600: case B19200: case B38400: case B57600: case B115200: case B230400: value = B230400 / t->c_ospeed; ubsa_cfg_request(sc, UBSA_REG_BAUDRATE, value); break; default: return; } if (t->c_cflag & PARENB) value = (t->c_cflag & PARODD) ? UBSA_PARITY_ODD : UBSA_PARITY_EVEN; else value = UBSA_PARITY_NONE; ubsa_cfg_request(sc, UBSA_REG_PARITY, value); switch (t->c_cflag & CSIZE) { case CS5: value = 0; break; case CS6: value = 1; break; case CS7: value = 2; break; default: case CS8: value = 3; break; } ubsa_cfg_request(sc, UBSA_REG_DATA_BITS, value); value = (t->c_cflag & CSTOPB) ? 1 : 0; ubsa_cfg_request(sc, UBSA_REG_STOP_BITS, value); value = 0; if (t->c_cflag & CRTSCTS) value |= UBSA_FLOW_OCTS | UBSA_FLOW_IRTS; if (t->c_iflag & (IXON | IXOFF)) value |= UBSA_FLOW_OXON | UBSA_FLOW_IXON; ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, value); } static void ubsa_start_read(struct ucom_softc *ucom) { struct ubsa_softc *sc = ucom->sc_parent; /* start interrupt endpoint */ usbd_transfer_start(sc->sc_xfer[UBSA_INTR_DT_RD]); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_RD]); } static void ubsa_stop_read(struct ucom_softc *ucom) { struct ubsa_softc *sc = ucom->sc_parent; /* stop interrupt endpoint */ usbd_transfer_stop(sc->sc_xfer[UBSA_INTR_DT_RD]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_RD]); } static void ubsa_start_write(struct ucom_softc *ucom) { struct ubsa_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_WR]); } static void ubsa_stop_write(struct ucom_softc *ucom) { struct ubsa_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_WR]); } static void ubsa_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct ubsa_softc *sc = ucom->sc_parent; DPRINTF("\n"); *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void ubsa_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubsa_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UBSA_BSIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ubsa_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubsa_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ubsa_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubsa_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[4]; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen >= (int)sizeof(buf)) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, sizeof(buf)); /* * MSR bits need translation from ns16550 to SER_* values. * LSR bits are ns16550 in hardware and ucom. */ sc->sc_msr = 0; if (buf[3] & UBSA_MSR_CTS) sc->sc_msr |= SER_CTS; if (buf[3] & UBSA_MSR_DCD) sc->sc_msr |= SER_DCD; if (buf[3] & UBSA_MSR_RI) sc->sc_msr |= SER_RI; if (buf[3] & UBSA_MSR_DSR) sc->sc_msr |= SER_DSR; sc->sc_lsr = buf[2]; DPRINTF("lsr = 0x%02x, msr = 0x%02x\n", sc->sc_lsr, sc->sc_msr); ucom_status_change(&sc->sc_ucom); } else { DPRINTF("ignoring short packet, %d bytes\n", actlen); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ubsa_poll(struct ucom_softc *ucom) { struct ubsa_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UBSA_N_TRANSFER); } Index: head/sys/dev/usb/serial/ubser.c =================================================================== --- head/sys/dev/usb/serial/ubser.c (revision 357971) +++ head/sys/dev/usb/serial/ubser.c (revision 357972) @@ -1,561 +1,562 @@ /*- * Copyright (c) 2004 Bernd Walter * * $URL: https://devel.bwct.de/svn/projects/ubser/ubser.c $ * $Date: 2004-02-29 01:53:10 +0100 (Sun, 29 Feb 2004) $ * $Author: ticso $ * $Rev: 1127 $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2001-2002, Shunsuke Akiyama . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net). * * 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. * * 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 __FBSDID("$FreeBSD$"); /* * BWCT serial adapter driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ubser_debug #include #include #include #define UBSER_UNIT_MAX 32 /* Vendor Interface Requests */ #define VENDOR_GET_NUMSER 0x01 #define VENDOR_SET_BREAK 0x02 #define VENDOR_CLEAR_BREAK 0x03 #ifdef USB_DEBUG static int ubser_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ubser, CTLFLAG_RW, 0, "USB ubser"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ubser, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ubser"); SYSCTL_INT(_hw_usb_ubser, OID_AUTO, debug, CTLFLAG_RWTUN, &ubser_debug, 0, "ubser debug level"); #endif enum { UBSER_BULK_DT_WR, UBSER_BULK_DT_RD, UBSER_N_TRANSFER, }; struct ubser_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom[UBSER_UNIT_MAX]; struct usb_xfer *sc_xfer[UBSER_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint16_t sc_tx_size; uint8_t sc_numser; uint8_t sc_iface_no; uint8_t sc_iface_index; uint8_t sc_curr_tx_unit; }; /* prototypes */ static device_probe_t ubser_probe; static device_attach_t ubser_attach; static device_detach_t ubser_detach; static void ubser_free_softc(struct ubser_softc *); static usb_callback_t ubser_write_callback; static usb_callback_t ubser_read_callback; static void ubser_free(struct ucom_softc *); static int ubser_pre_param(struct ucom_softc *, struct termios *); static void ubser_cfg_set_break(struct ucom_softc *, uint8_t); static void ubser_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void ubser_start_read(struct ucom_softc *); static void ubser_stop_read(struct ucom_softc *); static void ubser_start_write(struct ucom_softc *); static void ubser_stop_write(struct ucom_softc *); static void ubser_poll(struct ucom_softc *ucom); static const struct usb_config ubser_config[UBSER_N_TRANSFER] = { [UBSER_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &ubser_write_callback, }, [UBSER_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &ubser_read_callback, }, }; static const struct ucom_callback ubser_callback = { .ucom_cfg_set_break = &ubser_cfg_set_break, .ucom_cfg_get_status = &ubser_cfg_get_status, .ucom_pre_param = &ubser_pre_param, .ucom_start_read = &ubser_start_read, .ucom_stop_read = &ubser_stop_read, .ucom_start_write = &ubser_start_write, .ucom_stop_write = &ubser_stop_write, .ucom_poll = &ubser_poll, .ucom_free = &ubser_free, }; static device_method_t ubser_methods[] = { DEVMETHOD(device_probe, ubser_probe), DEVMETHOD(device_attach, ubser_attach), DEVMETHOD(device_detach, ubser_detach), DEVMETHOD_END }; static devclass_t ubser_devclass; static driver_t ubser_driver = { .name = "ubser", .methods = ubser_methods, .size = sizeof(struct ubser_softc), }; DRIVER_MODULE(ubser, uhub, ubser_driver, ubser_devclass, NULL, 0); MODULE_DEPEND(ubser, ucom, 1, 1, 1); MODULE_DEPEND(ubser, usb, 1, 1, 1); MODULE_VERSION(ubser, 1); static int ubser_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } /* check if this is a BWCT vendor specific ubser interface */ if ((strcmp(usb_get_manufacturer(uaa->device), "BWCT") == 0) && (uaa->info.bInterfaceClass == 0xff) && (uaa->info.bInterfaceSubClass == 0x00)) return (0); return (ENXIO); } static int ubser_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ubser_softc *sc = device_get_softc(dev); struct usb_device_request req; uint8_t n; int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "ubser", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = uaa->info.bIfaceIndex; sc->sc_udev = uaa->device; /* get number of serials */ req.bmRequestType = UT_READ_VENDOR_INTERFACE; req.bRequest = VENDOR_GET_NUMSER; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 1); error = usbd_do_request_flags(uaa->device, NULL, &req, &sc->sc_numser, 0, NULL, USB_DEFAULT_TIMEOUT); if (error || (sc->sc_numser == 0)) { device_printf(dev, "failed to get number " "of serial ports: %s\n", usbd_errstr(error)); goto detach; } if (sc->sc_numser > UBSER_UNIT_MAX) sc->sc_numser = UBSER_UNIT_MAX; device_printf(dev, "found %i serials\n", sc->sc_numser); error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, sc->sc_xfer, ubser_config, UBSER_N_TRANSFER, sc, &sc->sc_mtx); if (error) { goto detach; } sc->sc_tx_size = usbd_xfer_max_len(sc->sc_xfer[UBSER_BULK_DT_WR]); if (sc->sc_tx_size == 0) { DPRINTFN(0, "invalid tx_size\n"); goto detach; } /* initialize port numbers */ for (n = 0; n < sc->sc_numser; n++) { sc->sc_ucom[n].sc_portno = n; } error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numser, sc, &ubser_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_RD]); usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); return (0); /* success */ detach: ubser_detach(dev); return (ENXIO); /* failure */ } static int ubser_detach(device_t dev) { struct ubser_softc *sc = device_get_softc(dev); DPRINTF("\n"); ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UBSER_N_TRANSFER); device_claim_softc(dev); ubser_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(ubser); static void ubser_free_softc(struct ubser_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void ubser_free(struct ucom_softc *ucom) { ubser_free_softc(ucom->sc_parent); } static int ubser_pre_param(struct ucom_softc *ucom, struct termios *t) { DPRINTF("\n"); /* * The firmware on our devices can only do 8n1@9600bps * without handshake. * We refuse to accept other configurations. */ /* ensure 9600bps */ switch (t->c_ospeed) { case 9600: break; default: return (EINVAL); } /* 2 stop bits not possible */ if (t->c_cflag & CSTOPB) return (EINVAL); /* XXX parity handling not possible with current firmware */ if (t->c_cflag & PARENB) return (EINVAL); /* we can only do 8 data bits */ switch (t->c_cflag & CSIZE) { case CS8: break; default: return (EINVAL); } /* we can't do any kind of hardware handshaking */ if ((t->c_cflag & (CRTS_IFLOW | CDTR_IFLOW | CDSR_OFLOW | CCAR_OFLOW)) != 0) return (EINVAL); /* * XXX xon/xoff not supported by the firmware! * This is handled within FreeBSD only and may overflow buffers * because of delayed reaction due to device buffering. */ return (0); } static __inline void ubser_inc_tx_unit(struct ubser_softc *sc) { sc->sc_curr_tx_unit++; if (sc->sc_curr_tx_unit >= sc->sc_numser) { sc->sc_curr_tx_unit = 0; } } static void ubser_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubser_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[1]; uint8_t first_unit = sc->sc_curr_tx_unit; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); do { if (ucom_get_data(sc->sc_ucom + sc->sc_curr_tx_unit, pc, 1, sc->sc_tx_size - 1, &actlen)) { buf[0] = sc->sc_curr_tx_unit; usbd_copy_in(pc, 0, buf, 1); usbd_xfer_set_frame_len(xfer, 0, actlen + 1); usbd_transfer_submit(xfer); ubser_inc_tx_unit(sc); /* round robin */ break; } ubser_inc_tx_unit(sc); } while (sc->sc_curr_tx_unit != first_unit); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ubser_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ubser_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[1]; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 1) { DPRINTF("invalid actlen=0!\n"); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, 1); if (buf[0] >= sc->sc_numser) { DPRINTF("invalid serial number!\n"); goto tr_setup; } ucom_put_data(sc->sc_ucom + buf[0], pc, 1, actlen - 1); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ubser_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct ubser_softc *sc = ucom->sc_parent; uint8_t x = ucom->sc_portno; struct usb_device_request req; usb_error_t err; if (onoff) { req.bmRequestType = UT_READ_VENDOR_INTERFACE; req.bRequest = VENDOR_SET_BREAK; req.wValue[0] = x; req.wValue[1] = 0; req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); err = ucom_cfg_do_request(sc->sc_udev, ucom, &req, NULL, 0, 1000); if (err) { DPRINTFN(0, "send break failed, error=%s\n", usbd_errstr(err)); } } } static void ubser_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { /* fake status bits */ *lsr = 0; *msr = SER_DCD; } static void ubser_start_read(struct ucom_softc *ucom) { struct ubser_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]); } static void ubser_stop_read(struct ucom_softc *ucom) { struct ubser_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_RD]); } static void ubser_start_write(struct ucom_softc *ucom) { struct ubser_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_WR]); } static void ubser_stop_write(struct ucom_softc *ucom) { struct ubser_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_WR]); } static void ubser_poll(struct ucom_softc *ucom) { struct ubser_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UBSER_N_TRANSFER); } Index: head/sys/dev/usb/serial/uchcom.c =================================================================== --- head/sys/dev/usb/serial/uchcom.c (revision 357971) +++ head/sys/dev/usb/serial/uchcom.c (revision 357972) @@ -1,914 +1,915 @@ /* $NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2007, Takanori Watanabe * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Copyright (c) 2007 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Takuya SHIOZAKI (tshiozak@netbsd.org). * * 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. * * 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 __FBSDID("$FreeBSD$"); /* * Driver for WinChipHead CH341/340, the worst USB-serial chip in the * world. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uchcom_debug #include #include #include #ifdef USB_DEBUG static int uchcom_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uchcom, CTLFLAG_RW, 0, "USB uchcom"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uchcom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uchcom"); SYSCTL_INT(_hw_usb_uchcom, OID_AUTO, debug, CTLFLAG_RWTUN, &uchcom_debug, 0, "uchcom debug level"); #endif #define UCHCOM_IFACE_INDEX 0 #define UCHCOM_CONFIG_INDEX 0 #define UCHCOM_REV_CH340 0x0250 #define UCHCOM_INPUT_BUF_SIZE 8 #define UCHCOM_REQ_GET_VERSION 0x5F #define UCHCOM_REQ_READ_REG 0x95 #define UCHCOM_REQ_WRITE_REG 0x9A #define UCHCOM_REQ_RESET 0xA1 #define UCHCOM_REQ_SET_DTRRTS 0xA4 #define UCHCOM_REG_STAT1 0x06 #define UCHCOM_REG_STAT2 0x07 #define UCHCOM_REG_BPS_PRE 0x12 #define UCHCOM_REG_BPS_DIV 0x13 #define UCHCOM_REG_BPS_MOD 0x14 #define UCHCOM_REG_BPS_PAD 0x0F #define UCHCOM_REG_BREAK1 0x05 #define UCHCOM_REG_LCR1 0x18 #define UCHCOM_REG_LCR2 0x25 #define UCHCOM_VER_20 0x20 #define UCHCOM_VER_30 0x30 #define UCHCOM_BASE_UNKNOWN 0 #define UCHCOM_BPS_MOD_BASE 20000000 #define UCHCOM_BPS_MOD_BASE_OFS 1100 #define UCHCOM_DTR_MASK 0x20 #define UCHCOM_RTS_MASK 0x40 #define UCHCOM_BRK_MASK 0x01 #define UCHCOM_LCR1_MASK 0xAF #define UCHCOM_LCR2_MASK 0x07 #define UCHCOM_LCR1_RX 0x80 #define UCHCOM_LCR1_TX 0x40 #define UCHCOM_LCR1_PARENB 0x08 #define UCHCOM_LCR1_CS8 0x03 #define UCHCOM_LCR2_PAREVEN 0x07 #define UCHCOM_LCR2_PARODD 0x06 #define UCHCOM_LCR2_PARMARK 0x05 #define UCHCOM_LCR2_PARSPACE 0x04 #define UCHCOM_INTR_STAT1 0x02 #define UCHCOM_INTR_STAT2 0x03 #define UCHCOM_INTR_LEAST 4 #define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */ enum { UCHCOM_BULK_DT_WR, UCHCOM_BULK_DT_RD, UCHCOM_INTR_DT_RD, UCHCOM_N_TRANSFER, }; struct uchcom_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UCHCOM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint8_t sc_dtr; /* local copy */ uint8_t sc_rts; /* local copy */ uint8_t sc_version; uint8_t sc_msr; uint8_t sc_lsr; /* local status register */ }; struct uchcom_divider { uint8_t dv_prescaler; uint8_t dv_div; uint8_t dv_mod; }; struct uchcom_divider_record { uint32_t dvr_high; uint32_t dvr_low; uint32_t dvr_base_clock; struct uchcom_divider dvr_divider; }; static const struct uchcom_divider_record dividers[] = { {307200, 307200, UCHCOM_BASE_UNKNOWN, {7, 0xD9, 0}}, {921600, 921600, UCHCOM_BASE_UNKNOWN, {7, 0xF3, 0}}, {2999999, 23530, 6000000, {3, 0, 0}}, {23529, 2942, 750000, {2, 0, 0}}, {2941, 368, 93750, {1, 0, 0}}, {367, 1, 11719, {0, 0, 0}}, }; #define NUM_DIVIDERS nitems(dividers) static const STRUCT_USB_HOST_ID uchcom_devs[] = { {USB_VPI(USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, 0)}, {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER, 0)}, {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER_2, 0)}, }; /* protypes */ static void uchcom_free(struct ucom_softc *); static int uchcom_pre_param(struct ucom_softc *, struct termios *); static void uchcom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void uchcom_cfg_open(struct ucom_softc *ucom); static void uchcom_cfg_param(struct ucom_softc *, struct termios *); static void uchcom_cfg_set_break(struct ucom_softc *, uint8_t); static void uchcom_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uchcom_cfg_set_rts(struct ucom_softc *, uint8_t); static void uchcom_start_read(struct ucom_softc *); static void uchcom_start_write(struct ucom_softc *); static void uchcom_stop_read(struct ucom_softc *); static void uchcom_stop_write(struct ucom_softc *); static void uchcom_update_version(struct uchcom_softc *); static void uchcom_convert_status(struct uchcom_softc *, uint8_t); static void uchcom_update_status(struct uchcom_softc *); static void uchcom_set_dtr_rts(struct uchcom_softc *); static int uchcom_calc_divider_settings(struct uchcom_divider *, uint32_t); static void uchcom_set_baudrate(struct uchcom_softc *, uint32_t); static void uchcom_poll(struct ucom_softc *ucom); static device_probe_t uchcom_probe; static device_attach_t uchcom_attach; static device_detach_t uchcom_detach; static void uchcom_free_softc(struct uchcom_softc *); static usb_callback_t uchcom_intr_callback; static usb_callback_t uchcom_write_callback; static usb_callback_t uchcom_read_callback; static const struct usb_config uchcom_config_data[UCHCOM_N_TRANSFER] = { [UCHCOM_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UCHCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &uchcom_write_callback, }, [UCHCOM_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UCHCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uchcom_read_callback, }, [UCHCOM_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &uchcom_intr_callback, }, }; static struct ucom_callback uchcom_callback = { .ucom_cfg_get_status = &uchcom_cfg_get_status, .ucom_cfg_set_dtr = &uchcom_cfg_set_dtr, .ucom_cfg_set_rts = &uchcom_cfg_set_rts, .ucom_cfg_set_break = &uchcom_cfg_set_break, .ucom_cfg_open = &uchcom_cfg_open, .ucom_cfg_param = &uchcom_cfg_param, .ucom_pre_param = &uchcom_pre_param, .ucom_start_read = &uchcom_start_read, .ucom_stop_read = &uchcom_stop_read, .ucom_start_write = &uchcom_start_write, .ucom_stop_write = &uchcom_stop_write, .ucom_poll = &uchcom_poll, .ucom_free = &uchcom_free, }; /* ---------------------------------------------------------------------- * driver entry points */ static int uchcom_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UCHCOM_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bIfaceIndex != UCHCOM_IFACE_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(uchcom_devs, sizeof(uchcom_devs), uaa)); } static int uchcom_attach(device_t dev) { struct uchcom_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int error; uint8_t iface_index; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uchcom", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_udev = uaa->device; switch (uaa->info.idProduct) { case USB_PRODUCT_WCH2_CH341SER: device_printf(dev, "CH340 detected\n"); break; case USB_PRODUCT_WCH2_CH341SER_2: device_printf(dev, "CH341 detected\n"); break; default: device_printf(dev, "New CH340/CH341 product 0x%04x detected\n", uaa->info.idProduct); break; } iface_index = UCHCOM_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, uchcom_config_data, UCHCOM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("one or more missing USB endpoints, " "error=%s\n", usbd_errstr(error)); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uchcom_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: uchcom_detach(dev); return (ENXIO); } static int uchcom_detach(device_t dev) { struct uchcom_softc *sc = device_get_softc(dev); DPRINTFN(11, "\n"); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UCHCOM_N_TRANSFER); device_claim_softc(dev); uchcom_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uchcom); static void uchcom_free_softc(struct uchcom_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uchcom_free(struct ucom_softc *ucom) { uchcom_free_softc(ucom->sc_parent); } /* ---------------------------------------------------------------------- * low level i/o */ static void uchcom_ctrl_write(struct uchcom_softc *sc, uint8_t reqno, uint16_t value, uint16_t index) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = reqno; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, 0); DPRINTF("WR REQ 0x%02X VAL 0x%04X IDX 0x%04X\n", reqno, value, index); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uchcom_ctrl_read(struct uchcom_softc *sc, uint8_t reqno, uint16_t value, uint16_t index, void *buf, uint16_t buflen) { struct usb_device_request req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = reqno; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, buflen); DPRINTF("RD REQ 0x%02X VAL 0x%04X IDX 0x%04X LEN %d\n", reqno, value, index, buflen); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, buf, USB_SHORT_XFER_OK, 1000); } static void uchcom_write_reg(struct uchcom_softc *sc, uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2) { DPRINTF("0x%02X<-0x%02X, 0x%02X<-0x%02X\n", (unsigned)reg1, (unsigned)val1, (unsigned)reg2, (unsigned)val2); uchcom_ctrl_write( sc, UCHCOM_REQ_WRITE_REG, reg1 | ((uint16_t)reg2 << 8), val1 | ((uint16_t)val2 << 8)); } static void uchcom_read_reg(struct uchcom_softc *sc, uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2) { uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; uchcom_ctrl_read( sc, UCHCOM_REQ_READ_REG, reg1 | ((uint16_t)reg2 << 8), 0, buf, sizeof(buf)); DPRINTF("0x%02X->0x%02X, 0x%02X->0x%02X\n", (unsigned)reg1, (unsigned)buf[0], (unsigned)reg2, (unsigned)buf[1]); if (rval1) *rval1 = buf[0]; if (rval2) *rval2 = buf[1]; } static void uchcom_get_version(struct uchcom_softc *sc, uint8_t *rver) { uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; uchcom_ctrl_read(sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof(buf)); if (rver) *rver = buf[0]; } static void uchcom_get_status(struct uchcom_softc *sc, uint8_t *rval) { uchcom_read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL); } static void uchcom_set_dtr_rts_10(struct uchcom_softc *sc, uint8_t val) { uchcom_write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val); } static void uchcom_set_dtr_rts_20(struct uchcom_softc *sc, uint8_t val) { uchcom_ctrl_write(sc, UCHCOM_REQ_SET_DTRRTS, val, 0); } /* ---------------------------------------------------------------------- * middle layer */ static void uchcom_update_version(struct uchcom_softc *sc) { uchcom_get_version(sc, &sc->sc_version); DPRINTF("Chip version: 0x%02x\n", sc->sc_version); } static void uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur) { sc->sc_dtr = !(cur & UCHCOM_DTR_MASK); sc->sc_rts = !(cur & UCHCOM_RTS_MASK); cur = ~cur & 0x0F; sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur); } static void uchcom_update_status(struct uchcom_softc *sc) { uint8_t cur; uchcom_get_status(sc, &cur); uchcom_convert_status(sc, cur); } static void uchcom_set_dtr_rts(struct uchcom_softc *sc) { uint8_t val = 0; if (sc->sc_dtr) val |= UCHCOM_DTR_MASK; if (sc->sc_rts) val |= UCHCOM_RTS_MASK; if (sc->sc_version < UCHCOM_VER_20) uchcom_set_dtr_rts_10(sc, ~val); else uchcom_set_dtr_rts_20(sc, ~val); } static void uchcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uchcom_softc *sc = ucom->sc_parent; uint8_t brk1; uint8_t brk2; uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_LCR1, &brk2); if (onoff) { /* on - clear bits */ brk1 &= ~UCHCOM_BRK_MASK; brk2 &= ~UCHCOM_LCR1_TX; } else { /* off - set bits */ brk1 |= UCHCOM_BRK_MASK; brk2 |= UCHCOM_LCR1_TX; } uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_LCR1, brk2); } static int uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) { const struct uchcom_divider_record *rp; uint32_t div; uint32_t rem; uint32_t mod; uint8_t i; /* find record */ for (i = 0; i != NUM_DIVIDERS; i++) { if (dividers[i].dvr_high >= rate && dividers[i].dvr_low <= rate) { rp = ÷rs[i]; goto found; } } return (-1); found: dp->dv_prescaler = rp->dvr_divider.dv_prescaler; if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) dp->dv_div = rp->dvr_divider.dv_div; else { div = rp->dvr_base_clock / rate; rem = rp->dvr_base_clock % rate; if (div == 0 || div >= 0xFF) return (-1); if ((rem << 1) >= rate) div += 1; dp->dv_div = (uint8_t)-div; } mod = (UCHCOM_BPS_MOD_BASE / rate) + UCHCOM_BPS_MOD_BASE_OFS; mod = mod + (mod / 2); dp->dv_mod = (mod + 0xFF) / 0x100; return (0); } static void uchcom_set_baudrate(struct uchcom_softc *sc, uint32_t rate) { struct uchcom_divider dv; if (uchcom_calc_divider_settings(&dv, rate)) return; /* * According to linux code we need to set bit 7 of UCHCOM_REG_BPS_PRE, * otherwise the chip will buffer data. */ uchcom_write_reg(sc, UCHCOM_REG_BPS_PRE, dv.dv_prescaler | 0x80, UCHCOM_REG_BPS_DIV, dv.dv_div); uchcom_write_reg(sc, UCHCOM_REG_BPS_MOD, dv.dv_mod, UCHCOM_REG_BPS_PAD, 0); } /* ---------------------------------------------------------------------- * methods for ucom */ static void uchcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uchcom_softc *sc = ucom->sc_parent; DPRINTF("\n"); /* XXX Note: sc_lsr is always zero */ *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void uchcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uchcom_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); sc->sc_dtr = onoff; uchcom_set_dtr_rts(sc); } static void uchcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uchcom_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); sc->sc_rts = onoff; uchcom_set_dtr_rts(sc); } static void uchcom_cfg_open(struct ucom_softc *ucom) { struct uchcom_softc *sc = ucom->sc_parent; DPRINTF("\n"); uchcom_update_version(sc); uchcom_update_status(sc); } static int uchcom_pre_param(struct ucom_softc *ucom, struct termios *t) { struct uchcom_divider dv; switch (t->c_cflag & CSIZE) { case CS8: break; default: return (EIO); } if ((t->c_cflag & CSTOPB) != 0) return (EIO); if ((t->c_cflag & PARENB) != 0) return (EIO); if (uchcom_calc_divider_settings(&dv, t->c_ospeed)) { return (EIO); } return (0); /* success */ } static void uchcom_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uchcom_softc *sc = ucom->sc_parent; uchcom_get_version(sc, NULL); uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0); uchcom_set_baudrate(sc, t->c_ospeed); if (sc->sc_version < UCHCOM_VER_30) { uchcom_read_reg(sc, UCHCOM_REG_LCR1, NULL, UCHCOM_REG_LCR2, NULL); uchcom_write_reg(sc, UCHCOM_REG_LCR1, 0x50, UCHCOM_REG_LCR2, 0x00); } else { /* * Set up line control: * - enable transmit and receive * - set 8n1 mode * To do: support other sizes, parity, stop bits. */ uchcom_write_reg(sc, UCHCOM_REG_LCR1, UCHCOM_LCR1_RX | UCHCOM_LCR1_TX | UCHCOM_LCR1_CS8, UCHCOM_REG_LCR2, 0x00); } uchcom_update_status(sc); uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0x501f, 0xd90a); uchcom_set_baudrate(sc, t->c_ospeed); uchcom_set_dtr_rts(sc); uchcom_update_status(sc); } static void uchcom_start_read(struct ucom_softc *ucom) { struct uchcom_softc *sc = ucom->sc_parent; /* start interrupt endpoint */ usbd_transfer_start(sc->sc_xfer[UCHCOM_INTR_DT_RD]); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_RD]); } static void uchcom_stop_read(struct ucom_softc *ucom) { struct uchcom_softc *sc = ucom->sc_parent; /* stop interrupt endpoint */ usbd_transfer_stop(sc->sc_xfer[UCHCOM_INTR_DT_RD]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_RD]); } static void uchcom_start_write(struct ucom_softc *ucom) { struct uchcom_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_WR]); } static void uchcom_stop_write(struct ucom_softc *ucom) { struct uchcom_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_WR]); } /* ---------------------------------------------------------------------- * callback when the modem status is changed. */ static void uchcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uchcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[UCHCOM_INTR_LEAST]; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen = %u\n", actlen); if (actlen >= UCHCOM_INTR_LEAST) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, UCHCOM_INTR_LEAST); DPRINTF("data = 0x%02X 0x%02X 0x%02X 0x%02X\n", (unsigned)buf[0], (unsigned)buf[1], (unsigned)buf[2], (unsigned)buf[3]); uchcom_convert_status(sc, buf[UCHCOM_INTR_STAT1]); ucom_status_change(&sc->sc_ucom); } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void uchcom_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uchcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) { DPRINTF("actlen = %d\n", actlen); usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void uchcom_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uchcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen > 0) { pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void uchcom_poll(struct ucom_softc *ucom) { struct uchcom_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UCHCOM_N_TRANSFER); } static device_method_t uchcom_methods[] = { /* Device interface */ DEVMETHOD(device_probe, uchcom_probe), DEVMETHOD(device_attach, uchcom_attach), DEVMETHOD(device_detach, uchcom_detach), DEVMETHOD_END }; static driver_t uchcom_driver = { .name = "uchcom", .methods = uchcom_methods, .size = sizeof(struct uchcom_softc) }; static devclass_t uchcom_devclass; DRIVER_MODULE(uchcom, uhub, uchcom_driver, uchcom_devclass, NULL, 0); MODULE_DEPEND(uchcom, ucom, 1, 1, 1); MODULE_DEPEND(uchcom, usb, 1, 1, 1); MODULE_VERSION(uchcom, 1); USB_PNP_HOST_INFO(uchcom_devs); Index: head/sys/dev/usb/serial/ufoma.c =================================================================== --- head/sys/dev/usb/serial/ufoma.c (revision 357971) +++ head/sys/dev/usb/serial/ufoma.c (revision 357972) @@ -1,1269 +1,1269 @@ /* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ #include __FBSDID("$FreeBSD$"); #define UFOMA_HANDSFREE /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2005, Takanori Watanabe All rights reserved. * Copyright (c) 2003 M. Warner Losh * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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. */ /* * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf */ /* * TODO: * - Implement a Call Device for modems without multiplexed commands. */ /* * NOTE: all function names beginning like "ufoma_cfg_" can only * be called from within the config thread function ! */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR usb_debug #include #include #include typedef struct ufoma_mobile_acm_descriptor { uint8_t bFunctionLength; uint8_t bDescriptorType; uint8_t bDescriptorSubtype; uint8_t bType; uint8_t bMode[1]; } __packed usb_mcpc_acm_descriptor; #define UISUBCLASS_MCPC 0x88 #define UDESC_VS_INTERFACE 0x44 #define UDESCSUB_MCPC_ACM 0x11 #define UMCPC_ACM_TYPE_AB1 0x1 #define UMCPC_ACM_TYPE_AB2 0x2 #define UMCPC_ACM_TYPE_AB5 0x5 #define UMCPC_ACM_TYPE_AB6 0x6 #define UMCPC_ACM_MODE_DEACTIVATED 0x0 #define UMCPC_ACM_MODE_MODEM 0x1 #define UMCPC_ACM_MODE_ATCOMMAND 0x2 #define UMCPC_ACM_MODE_OBEX 0x60 #define UMCPC_ACM_MODE_VENDOR1 0xc0 #define UMCPC_ACM_MODE_VENDOR2 0xfe #define UMCPC_ACM_MODE_UNLINKED 0xff #define UMCPC_CM_MOBILE_ACM 0x0 #define UMCPC_ACTIVATE_MODE 0x60 #define UMCPC_GET_MODETABLE 0x61 #define UMCPC_SET_LINK 0x62 #define UMCPC_CLEAR_LINK 0x63 #define UMCPC_REQUEST_ACKNOWLEDGE 0x31 #define UFOMA_MAX_TIMEOUT 15 /* standard says 10 seconds */ #define UFOMA_CMD_BUF_SIZE 64 /* bytes */ #define UFOMA_BULK_BUF_SIZE 1024 /* bytes */ enum { UFOMA_CTRL_ENDPT_INTR, UFOMA_CTRL_ENDPT_READ, UFOMA_CTRL_ENDPT_WRITE, UFOMA_CTRL_ENDPT_MAX, }; enum { UFOMA_BULK_ENDPT_WRITE, UFOMA_BULK_ENDPT_READ, UFOMA_BULK_ENDPT_MAX, }; struct ufoma_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct cv sc_cv; struct mtx sc_mtx; struct usb_xfer *sc_ctrl_xfer[UFOMA_CTRL_ENDPT_MAX]; struct usb_xfer *sc_bulk_xfer[UFOMA_BULK_ENDPT_MAX]; uint8_t *sc_modetable; device_t sc_dev; struct usb_device *sc_udev; uint32_t sc_unit; uint16_t sc_line; uint8_t sc_num_msg; uint8_t sc_nobulk; uint8_t sc_ctrl_iface_no; uint8_t sc_ctrl_iface_index; uint8_t sc_data_iface_no; uint8_t sc_data_iface_index; uint8_t sc_cm_cap; uint8_t sc_acm_cap; uint8_t sc_lsr; uint8_t sc_msr; uint8_t sc_modetoactivate; uint8_t sc_currentmode; }; /* prototypes */ static device_probe_t ufoma_probe; static device_attach_t ufoma_attach; static device_detach_t ufoma_detach; static void ufoma_free_softc(struct ufoma_softc *); static usb_callback_t ufoma_ctrl_read_callback; static usb_callback_t ufoma_ctrl_write_callback; static usb_callback_t ufoma_intr_callback; static usb_callback_t ufoma_bulk_write_callback; static usb_callback_t ufoma_bulk_read_callback; static void *ufoma_get_intconf(struct usb_config_descriptor *, struct usb_interface_descriptor *, uint8_t, uint8_t); static void ufoma_cfg_link_state(struct ufoma_softc *); static void ufoma_cfg_activate_state(struct ufoma_softc *, uint16_t); static void ufoma_free(struct ucom_softc *); static void ufoma_cfg_open(struct ucom_softc *); static void ufoma_cfg_close(struct ucom_softc *); static void ufoma_cfg_set_break(struct ucom_softc *, uint8_t); static void ufoma_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void ufoma_cfg_set_dtr(struct ucom_softc *, uint8_t); static void ufoma_cfg_set_rts(struct ucom_softc *, uint8_t); static int ufoma_pre_param(struct ucom_softc *, struct termios *); static void ufoma_cfg_param(struct ucom_softc *, struct termios *); static int ufoma_modem_setup(device_t, struct ufoma_softc *, struct usb_attach_arg *); static void ufoma_start_read(struct ucom_softc *); static void ufoma_stop_read(struct ucom_softc *); static void ufoma_start_write(struct ucom_softc *); static void ufoma_stop_write(struct ucom_softc *); static void ufoma_poll(struct ucom_softc *ucom); /*sysctl stuff*/ static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS); static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS); static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS); static const struct usb_config ufoma_ctrl_config[UFOMA_CTRL_ENDPT_MAX] = { [UFOMA_CTRL_ENDPT_INTR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = sizeof(struct usb_cdc_notification), .callback = &ufoma_intr_callback, }, [UFOMA_CTRL_ENDPT_READ] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + UFOMA_CMD_BUF_SIZE), .flags = {.short_xfer_ok = 1,}, .callback = &ufoma_ctrl_read_callback, .timeout = 1000, /* 1 second */ }, [UFOMA_CTRL_ENDPT_WRITE] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + 1), .callback = &ufoma_ctrl_write_callback, .timeout = 1000, /* 1 second */ }, }; static const struct usb_config ufoma_bulk_config[UFOMA_BULK_ENDPT_MAX] = { [UFOMA_BULK_ENDPT_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UFOMA_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &ufoma_bulk_write_callback, }, [UFOMA_BULK_ENDPT_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UFOMA_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &ufoma_bulk_read_callback, }, }; static const struct ucom_callback ufoma_callback = { .ucom_cfg_get_status = &ufoma_cfg_get_status, .ucom_cfg_set_dtr = &ufoma_cfg_set_dtr, .ucom_cfg_set_rts = &ufoma_cfg_set_rts, .ucom_cfg_set_break = &ufoma_cfg_set_break, .ucom_cfg_param = &ufoma_cfg_param, .ucom_cfg_open = &ufoma_cfg_open, .ucom_cfg_close = &ufoma_cfg_close, .ucom_pre_param = &ufoma_pre_param, .ucom_start_read = &ufoma_start_read, .ucom_stop_read = &ufoma_stop_read, .ucom_start_write = &ufoma_start_write, .ucom_stop_write = &ufoma_stop_write, .ucom_poll = &ufoma_poll, .ucom_free = &ufoma_free, }; static device_method_t ufoma_methods[] = { /* Device methods */ DEVMETHOD(device_probe, ufoma_probe), DEVMETHOD(device_attach, ufoma_attach), DEVMETHOD(device_detach, ufoma_detach), DEVMETHOD_END }; static devclass_t ufoma_devclass; static driver_t ufoma_driver = { .name = "ufoma", .methods = ufoma_methods, .size = sizeof(struct ufoma_softc), }; static const STRUCT_USB_HOST_ID ufoma_devs[] = { {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_MCPC),}, }; DRIVER_MODULE(ufoma, uhub, ufoma_driver, ufoma_devclass, NULL, 0); MODULE_DEPEND(ufoma, ucom, 1, 1, 1); MODULE_DEPEND(ufoma, usb, 1, 1, 1); MODULE_VERSION(ufoma, 1); USB_PNP_HOST_INFO(ufoma_devs); static int ufoma_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_interface_descriptor *id; struct usb_config_descriptor *cd; usb_mcpc_acm_descriptor *mad; int error; if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); error = usbd_lookup_id_by_uaa(ufoma_devs, sizeof(ufoma_devs), uaa); if (error) return (error); id = usbd_get_interface_descriptor(uaa->iface); cd = usbd_get_config_descriptor(uaa->device); if (id == NULL || cd == NULL) return (ENXIO); mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); if (mad == NULL) return (ENXIO); #ifndef UFOMA_HANDSFREE if ((mad->bType == UMCPC_ACM_TYPE_AB5) || (mad->bType == UMCPC_ACM_TYPE_AB6)) return (ENXIO); #endif return (BUS_PROBE_GENERIC); } static int ufoma_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ufoma_softc *sc = device_get_softc(dev); struct usb_config_descriptor *cd; struct usb_interface_descriptor *id; struct sysctl_ctx_list *sctx; struct sysctl_oid *soid; usb_mcpc_acm_descriptor *mad; uint8_t elements; int32_t error; sc->sc_udev = uaa->device; sc->sc_dev = dev; sc->sc_unit = device_get_unit(dev); mtx_init(&sc->sc_mtx, "ufoma", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); cv_init(&sc->sc_cv, "CWAIT"); device_set_usb_desc(dev); DPRINTF("\n"); /* setup control transfers */ cd = usbd_get_config_descriptor(uaa->device); id = usbd_get_interface_descriptor(uaa->iface); sc->sc_ctrl_iface_no = id->bInterfaceNumber; sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex; error = usbd_transfer_setup(uaa->device, &sc->sc_ctrl_iface_index, sc->sc_ctrl_xfer, ufoma_ctrl_config, UFOMA_CTRL_ENDPT_MAX, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating control USB " "transfers failed\n"); goto detach; } mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); if (mad == NULL) { goto detach; } if (mad->bFunctionLength < sizeof(*mad)) { device_printf(dev, "invalid MAD descriptor\n"); goto detach; } if ((mad->bType == UMCPC_ACM_TYPE_AB5) || (mad->bType == UMCPC_ACM_TYPE_AB6)) { sc->sc_nobulk = 1; } else { sc->sc_nobulk = 0; if (ufoma_modem_setup(dev, sc, uaa)) { goto detach; } } elements = (mad->bFunctionLength - sizeof(*mad) + 1); /* initialize mode variables */ sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK); if (sc->sc_modetable == NULL) { goto detach; } sc->sc_modetable[0] = (elements + 1); memcpy(&sc->sc_modetable[1], mad->bMode, elements); sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED; sc->sc_modetoactivate = mad->bMode[0]; /* clear stall at first run, if any */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &ufoma_callback, &sc->sc_mtx); if (error) { DPRINTF("ucom_attach failed\n"); goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); /*Sysctls*/ sctx = device_get_sysctl_ctx(dev); soid = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "supportmode", - CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_support, - "A", "Supporting port role"); + CTLFLAG_RD | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0, + ufoma_sysctl_support, "A", "Supporting port role"); SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "currentmode", - CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_current, - "A", "Current port role"); + CTLFLAG_RD | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0, + ufoma_sysctl_current, "A", "Current port role"); SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "openmode", - CTLFLAG_RW|CTLTYPE_STRING, sc, 0, ufoma_sysctl_open, - "A", "Mode to transit when port is opened"); + CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0, + ufoma_sysctl_open, "A", "Mode to transit when port is opened"); SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "comunit", CTLFLAG_RD, &(sc->sc_super_ucom.sc_unit), 0, "Unit number as USB serial"); return (0); /* success */ detach: ufoma_detach(dev); return (ENXIO); /* failure */ } static int ufoma_detach(device_t dev) { struct ufoma_softc *sc = device_get_softc(dev); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); usbd_transfer_unsetup(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); if (sc->sc_modetable) { free(sc->sc_modetable, M_USBDEV); } cv_destroy(&sc->sc_cv); device_claim_softc(dev); ufoma_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(ufoma); static void ufoma_free_softc(struct ufoma_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void ufoma_free(struct ucom_softc *ucom) { ufoma_free_softc(ucom->sc_parent); } static void * ufoma_get_intconf(struct usb_config_descriptor *cd, struct usb_interface_descriptor *id, uint8_t type, uint8_t subtype) { struct usb_descriptor *desc = (void *)id; while ((desc = usb_desc_foreach(cd, desc))) { if (desc->bDescriptorType == UDESC_INTERFACE) { return (NULL); } if ((desc->bDescriptorType == type) && (desc->bDescriptorSubtype == subtype)) { break; } } return (desc); } static void ufoma_cfg_link_state(struct ufoma_softc *sc) { struct usb_device_request req; int32_t error; req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; req.bRequest = UMCPC_SET_LINK; USETW(req.wValue, UMCPC_CM_MOBILE_ACM); USETW(req.wIndex, sc->sc_ctrl_iface_no); USETW(req.wLength, sc->sc_modetable[0]); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, sc->sc_modetable, 0, 1000); error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, hz); if (error) { DPRINTF("NO response\n"); } } static void ufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state) { struct usb_device_request req; int32_t error; req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; req.bRequest = UMCPC_ACTIVATE_MODE; USETW(req.wValue, state); USETW(req.wIndex, sc->sc_ctrl_iface_no); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, (UFOMA_MAX_TIMEOUT * hz)); if (error) { DPRINTF("No response\n"); } } static void ufoma_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ufoma_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc0, *pc1; int len, aframes, nframes; usbd_xfer_status(xfer, NULL, NULL, &aframes, &nframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: if (aframes != nframes) goto tr_setup; pc1 = usbd_xfer_get_frame(xfer, 1); len = usbd_xfer_frame_len(xfer, 1); if (len > 0) ucom_put_data(&sc->sc_ucom, pc1, 0, len); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if (sc->sc_num_msg) { sc->sc_num_msg--; req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; USETW(req.wIndex, sc->sc_ctrl_iface_no); USETW(req.wValue, 0); USETW(req.wLength, UFOMA_CMD_BUF_SIZE); pc0 = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc0, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, UFOMA_CMD_BUF_SIZE); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); } return; default: /* Error */ DPRINTF("error = %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) { return; } goto tr_transferred; } } static void ufoma_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ufoma_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: case USB_ST_SETUP: pc = usbd_xfer_get_frame(xfer, 1); if (ucom_get_data(&sc->sc_ucom, pc, 0, 1, &actlen)) { req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; USETW(req.wIndex, sc->sc_ctrl_iface_no); USETW(req.wValue, 0); USETW(req.wLength, 1); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, 1); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); } return; default: /* Error */ DPRINTF("error = %s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) { return; } goto tr_transferred; } } static void ufoma_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct ufoma_softc *sc = usbd_xfer_softc(xfer); struct usb_cdc_notification pkt; struct usb_page_cache *pc; uint16_t wLen; uint16_t temp; uint8_t mstatus; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 8) { DPRINTF("too short message\n"); goto tr_setup; } if (actlen > (int)sizeof(pkt)) { DPRINTF("truncating message\n"); actlen = sizeof(pkt); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, actlen); actlen -= 8; wLen = UGETW(pkt.wLength); if (actlen > wLen) { actlen = wLen; } if ((pkt.bmRequestType == UT_READ_VENDOR_INTERFACE) && (pkt.bNotification == UMCPC_REQUEST_ACKNOWLEDGE)) { temp = UGETW(pkt.wValue); sc->sc_currentmode = (temp >> 8); if (!(temp & 0xff)) { DPRINTF("Mode change failed!\n"); } cv_signal(&sc->sc_cv); } if (pkt.bmRequestType != UCDC_NOTIFICATION) { goto tr_setup; } switch (pkt.bNotification) { case UCDC_N_RESPONSE_AVAILABLE: if (!(sc->sc_nobulk)) { DPRINTF("Wrong serial state!\n"); break; } if (sc->sc_num_msg != 0xFF) { sc->sc_num_msg++; } usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); break; case UCDC_N_SERIAL_STATE: if (sc->sc_nobulk) { DPRINTF("Wrong serial state!\n"); break; } /* * Set the serial state in ucom driver based on * the bits from the notify message */ if (actlen < 2) { DPRINTF("invalid notification " "length, %d bytes!\n", actlen); break; } DPRINTF("notify bytes = 0x%02x, 0x%02x\n", pkt.data[0], pkt.data[1]); /* currently, lsr is always zero. */ sc->sc_lsr = 0; sc->sc_msr = 0; mstatus = pkt.data[0]; if (mstatus & UCDC_N_SERIAL_RI) { sc->sc_msr |= SER_RI; } if (mstatus & UCDC_N_SERIAL_DSR) { sc->sc_msr |= SER_DSR; } if (mstatus & UCDC_N_SERIAL_DCD) { sc->sc_msr |= SER_DCD; } ucom_status_change(&sc->sc_ucom); break; default: break; } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ufoma_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ufoma_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UFOMA_BULK_BUF_SIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ufoma_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ufoma_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void ufoma_cfg_open(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; /* empty input queue */ if (sc->sc_num_msg != 0xFF) { sc->sc_num_msg++; } if (sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED) { ufoma_cfg_link_state(sc); } if (sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED) { ufoma_cfg_activate_state(sc, sc->sc_modetoactivate); } } static void ufoma_cfg_close(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; ufoma_cfg_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED); } static void ufoma_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct ufoma_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t wValue; if (sc->sc_nobulk || (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { return; } if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) { return; } wValue = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_BREAK; USETW(req.wValue, wValue); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void ufoma_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct ufoma_softc *sc = ucom->sc_parent; /* XXX Note: sc_lsr is always zero */ *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void ufoma_cfg_set_line_state(struct ufoma_softc *sc) { struct usb_device_request req; /* Don't send line state emulation request for OBEX port */ if (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX) { return; } req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void ufoma_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct ufoma_softc *sc = ucom->sc_parent; if (sc->sc_nobulk) { return; } if (onoff) sc->sc_line |= UCDC_LINE_DTR; else sc->sc_line &= ~UCDC_LINE_DTR; ufoma_cfg_set_line_state(sc); } static void ufoma_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct ufoma_softc *sc = ucom->sc_parent; if (sc->sc_nobulk) { return; } if (onoff) sc->sc_line |= UCDC_LINE_RTS; else sc->sc_line &= ~UCDC_LINE_RTS; ufoma_cfg_set_line_state(sc); } static int ufoma_pre_param(struct ucom_softc *ucom, struct termios *t) { return (0); /* we accept anything */ } static void ufoma_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct ufoma_softc *sc = ucom->sc_parent; struct usb_device_request req; struct usb_cdc_line_state ls; if (sc->sc_nobulk || (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { return; } DPRINTF("\n"); memset(&ls, 0, sizeof(ls)); USETDW(ls.dwDTERate, t->c_ospeed); if (t->c_cflag & CSTOPB) { ls.bCharFormat = UCDC_STOP_BIT_2; } else { ls.bCharFormat = UCDC_STOP_BIT_1; } if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) { ls.bParityType = UCDC_PARITY_ODD; } else { ls.bParityType = UCDC_PARITY_EVEN; } } else { ls.bParityType = UCDC_PARITY_NONE; } switch (t->c_cflag & CSIZE) { case CS5: ls.bDataBits = 5; break; case CS6: ls.bDataBits = 6; break; case CS7: ls.bDataBits = 7; break; case CS8: ls.bDataBits = 8; break; } req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_LINE_CODING; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, UCDC_LINE_STATE_LENGTH); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &ls, 0, 1000); } static int ufoma_modem_setup(device_t dev, struct ufoma_softc *sc, struct usb_attach_arg *uaa) { struct usb_config_descriptor *cd; struct usb_cdc_acm_descriptor *acm; struct usb_cdc_cm_descriptor *cmd; struct usb_interface_descriptor *id; struct usb_interface *iface; uint8_t i; int32_t error; cd = usbd_get_config_descriptor(uaa->device); id = usbd_get_interface_descriptor(uaa->iface); cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { return (EINVAL); } sc->sc_cm_cap = cmd->bmCapabilities; sc->sc_data_iface_no = cmd->bDataInterface; acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); if ((acm == NULL) || (acm->bLength < sizeof(*acm))) { return (EINVAL); } sc->sc_acm_cap = acm->bmCapabilities; device_printf(dev, "data interface %d, has %sCM over data, " "has %sbreak\n", sc->sc_data_iface_no, sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); /* get the data interface too */ for (i = 0;; i++) { iface = usbd_get_iface(uaa->device, i); if (iface) { id = usbd_get_interface_descriptor(iface); if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { sc->sc_data_iface_index = i; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); break; } } else { device_printf(dev, "no data interface\n"); return (EINVAL); } } error = usbd_transfer_setup(uaa->device, &sc->sc_data_iface_index, sc->sc_bulk_xfer, ufoma_bulk_config, UFOMA_BULK_ENDPT_MAX, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating BULK USB " "transfers failed\n"); return (EINVAL); } return (0); } static void ufoma_start_read(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; /* start interrupt transfer */ usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]); /* start data transfer */ if (sc->sc_nobulk) { usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); } else { usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); } } static void ufoma_stop_read(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; /* stop interrupt transfer */ usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]); /* stop data transfer */ if (sc->sc_nobulk) { usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); } else { usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); } } static void ufoma_start_write(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; if (sc->sc_nobulk) { usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]); } else { usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); } } static void ufoma_stop_write(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; if (sc->sc_nobulk) { usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]); } else { usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); } } static struct umcpc_modetostr_tab{ int mode; char *str; }umcpc_modetostr_tab[]={ {UMCPC_ACM_MODE_DEACTIVATED, "deactivated"}, {UMCPC_ACM_MODE_MODEM, "modem"}, {UMCPC_ACM_MODE_ATCOMMAND, "handsfree"}, {UMCPC_ACM_MODE_OBEX, "obex"}, {UMCPC_ACM_MODE_VENDOR1, "vendor1"}, {UMCPC_ACM_MODE_VENDOR2, "vendor2"}, {UMCPC_ACM_MODE_UNLINKED, "unlinked"}, {0, NULL} }; static char *ufoma_mode_to_str(int mode) { int i; for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ if(umcpc_modetostr_tab[i].mode == mode){ return umcpc_modetostr_tab[i].str; } } return NULL; } static int ufoma_str_to_mode(char *str) { int i; for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ if(strcmp(str, umcpc_modetostr_tab[i].str)==0){ return umcpc_modetostr_tab[i].mode; } } return -1; } static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS) { struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; struct sbuf sb; int i; char *mode; sbuf_new(&sb, NULL, 1, SBUF_AUTOEXTEND); for(i = 1; i < sc->sc_modetable[0]; i++){ mode = ufoma_mode_to_str(sc->sc_modetable[i]); if(mode !=NULL){ sbuf_cat(&sb, mode); }else{ sbuf_printf(&sb, "(%02x)", sc->sc_modetable[i]); } if(i < (sc->sc_modetable[0]-1)) sbuf_cat(&sb, ","); } sbuf_trim(&sb); sbuf_finish(&sb); sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); sbuf_delete(&sb); return 0; } static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS) { struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; char *mode; char subbuf[]="(XXX)"; mode = ufoma_mode_to_str(sc->sc_currentmode); if(!mode){ mode = subbuf; snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_currentmode); } sysctl_handle_string(oidp, mode, strlen(mode), req); return 0; } static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS) { struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; char *mode; char subbuf[40]; int newmode; int error; int i; mode = ufoma_mode_to_str(sc->sc_modetoactivate); if(mode){ strncpy(subbuf, mode, sizeof(subbuf)); }else{ snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_modetoactivate); } error = sysctl_handle_string(oidp, subbuf, sizeof(subbuf), req); if(error != 0 || req->newptr == NULL){ return error; } if((newmode = ufoma_str_to_mode(subbuf)) == -1){ return EINVAL; } for(i = 1 ; i < sc->sc_modetable[0] ; i++){ if(sc->sc_modetable[i] == newmode){ sc->sc_modetoactivate = newmode; return 0; } } return EINVAL; } static void ufoma_poll(struct ucom_softc *ucom) { struct ufoma_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); usbd_transfer_poll(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); } Index: head/sys/dev/usb/serial/uftdi.c =================================================================== --- head/sys/dev/usb/serial/uftdi.c (revision 357971) +++ head/sys/dev/usb/serial/uftdi.c (revision 357972) @@ -1,2012 +1,2013 @@ /* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net). * * 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. * * 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 __FBSDID("$FreeBSD$"); /* * NOTE: all function names beginning like "uftdi_cfg_" can only * be called from within the config thread function ! */ /* * FTDI FT232x, FT2232x, FT4232x, FT8U100AX and FT8U232xM serial adapters. * * Note that we specifically do not do a reset or otherwise alter the state of * the chip during attach, detach, open, and close, because it could be * pre-initialized (via an attached serial eeprom) to power-on into a mode such * as bitbang in which the pins are being driven to a specific state which we * must not perturb. The device gets reset at power-on, and doesn't need to be * reset again after that to function, except as directed by ioctl() calls. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uftdi_debug #include #include #include #include #include -static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uftdi"); #ifdef USB_DEBUG static int uftdi_debug = 0; SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RWTUN, &uftdi_debug, 0, "Debug level"); #endif #define UFTDI_CONFIG_INDEX 0 /* * IO buffer sizes and FTDI device procotol sizes. * * Note that the output packet size in the following defines is not the usb * protocol packet size based on bus speed, it is the size dictated by the FTDI * device itself, and is used only on older chips. * * We allocate buffers bigger than the hardware's packet size, and process * multiple packets within each buffer. This allows the controller to make * optimal use of the usb bus by conducting multiple transfers with the device * during a single bus timeslice to fill or drain the chip's fifos. * * The output data on newer chips has no packet header, and we are able to pack * any number of output bytes into a buffer. On some older chips, each output * packet contains a 1-byte header and up to 63 bytes of payload. The size is * encoded in 6 bits of the header, hence the 64-byte limit on packet size. We * loop to fill the buffer with many of these header+payload packets. * * The input data on all chips consists of packets which contain a 2-byte header * followed by data payload. The total size of the packet is wMaxPacketSize * which can change based on the bus speed (e.g., 64 for full speed, 512 for * high speed). We loop to extract the headers and payloads from the packets * packed into an input buffer. */ #define UFTDI_IBUFSIZE 2048 #define UFTDI_IHDRSIZE 2 #define UFTDI_OBUFSIZE 2048 #define UFTDI_OPKTSIZE 64 enum { UFTDI_BULK_DT_WR, UFTDI_BULK_DT_RD, UFTDI_N_TRANSFER, }; enum { DEVT_SIO, DEVT_232A, DEVT_232B, DEVT_2232D, /* Includes 2232C */ DEVT_232R, DEVT_2232H, DEVT_4232H, DEVT_232H, DEVT_230X, }; #define DEVF_BAUDBITS_HINDEX 0x01 /* Baud bits in high byte of index. */ #define DEVF_BAUDCLK_12M 0X02 /* Base baud clock is 12MHz. */ struct uftdi_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_device *sc_udev; struct usb_xfer *sc_xfer[UFTDI_N_TRANSFER]; device_t sc_dev; struct mtx sc_mtx; uint32_t sc_unit; uint16_t sc_last_lcr; uint16_t sc_bcdDevice; uint8_t sc_devtype; uint8_t sc_devflags; uint8_t sc_hdrlen; uint8_t sc_msr; uint8_t sc_lsr; uint8_t sc_bitmode; }; struct uftdi_param_config { uint16_t baud_lobits; uint16_t baud_hibits; uint16_t lcr; uint8_t v_start; uint8_t v_stop; uint8_t v_flow; }; /* prototypes */ static device_probe_t uftdi_probe; static device_attach_t uftdi_attach; static device_detach_t uftdi_detach; static void uftdi_free_softc(struct uftdi_softc *); static usb_callback_t uftdi_write_callback; static usb_callback_t uftdi_read_callback; static void uftdi_free(struct ucom_softc *); static void uftdi_cfg_open(struct ucom_softc *); static void uftdi_cfg_close(struct ucom_softc *); static void uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uftdi_cfg_set_rts(struct ucom_softc *, uint8_t); static void uftdi_cfg_set_break(struct ucom_softc *, uint8_t); static int uftdi_set_parm_soft(struct ucom_softc *, struct termios *, struct uftdi_param_config *); static int uftdi_pre_param(struct ucom_softc *, struct termios *); static void uftdi_cfg_param(struct ucom_softc *, struct termios *); static void uftdi_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static int uftdi_reset(struct ucom_softc *, int); static int uftdi_set_bitmode(struct ucom_softc *, uint8_t, uint8_t); static int uftdi_get_bitmode(struct ucom_softc *, uint8_t *, uint8_t *); static int uftdi_set_latency(struct ucom_softc *, int); static int uftdi_get_latency(struct ucom_softc *, int *); static int uftdi_set_event_char(struct ucom_softc *, int); static int uftdi_set_error_char(struct ucom_softc *, int); static int uftdi_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); static void uftdi_start_read(struct ucom_softc *); static void uftdi_stop_read(struct ucom_softc *); static void uftdi_start_write(struct ucom_softc *); static void uftdi_stop_write(struct ucom_softc *); static void uftdi_poll(struct ucom_softc *ucom); static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = { [UFTDI_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UFTDI_OBUFSIZE, .flags = {.pipe_bof = 1,}, .callback = &uftdi_write_callback, }, [UFTDI_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UFTDI_IBUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uftdi_read_callback, }, }; static const struct ucom_callback uftdi_callback = { .ucom_cfg_get_status = &uftdi_cfg_get_status, .ucom_cfg_set_dtr = &uftdi_cfg_set_dtr, .ucom_cfg_set_rts = &uftdi_cfg_set_rts, .ucom_cfg_set_break = &uftdi_cfg_set_break, .ucom_cfg_param = &uftdi_cfg_param, .ucom_cfg_open = &uftdi_cfg_open, .ucom_cfg_close = &uftdi_cfg_close, .ucom_pre_param = &uftdi_pre_param, .ucom_ioctl = &uftdi_ioctl, .ucom_start_read = &uftdi_start_read, .ucom_stop_read = &uftdi_stop_read, .ucom_start_write = &uftdi_start_write, .ucom_stop_write = &uftdi_stop_write, .ucom_poll = &uftdi_poll, .ucom_free = &uftdi_free, }; static device_method_t uftdi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, uftdi_probe), DEVMETHOD(device_attach, uftdi_attach), DEVMETHOD(device_detach, uftdi_detach), DEVMETHOD_END }; static devclass_t uftdi_devclass; static driver_t uftdi_driver = { .name = "uftdi", .methods = uftdi_methods, .size = sizeof(struct uftdi_softc), }; static const STRUCT_USB_HOST_ID uftdi_devs[] = { #define UFTDI_DEV(v, p, i) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } UFTDI_DEV(ACTON, SPECTRAPRO, 0), UFTDI_DEV(ALTI2, N3, 0), UFTDI_DEV(ANALOGDEVICES, GNICE, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(ANALOGDEVICES, GNICEPLUS, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(ATMEL, STK541, 0), UFTDI_DEV(BAYER, CONTOUR_CABLE, 0), UFTDI_DEV(BBELECTRONICS, 232USB9M, 0), UFTDI_DEV(BBELECTRONICS, 485USB9F_2W, 0), UFTDI_DEV(BBELECTRONICS, 485USB9F_4W, 0), UFTDI_DEV(BBELECTRONICS, 485USBTB_2W, 0), UFTDI_DEV(BBELECTRONICS, 485USBTB_4W, 0), UFTDI_DEV(BBELECTRONICS, TTL3USB9M, 0), UFTDI_DEV(BBELECTRONICS, TTL5USB9M, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2DR, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2DR_2, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4DR, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4DR2, 0), UFTDI_DEV(BBELECTRONICS, USOTL4, 0), UFTDI_DEV(BBELECTRONICS, USPTL4, 0), UFTDI_DEV(BBELECTRONICS, USTL4, 0), UFTDI_DEV(BBELECTRONICS, ZZ_PROG1_USB, 0), UFTDI_DEV(CONTEC, COM1USBH, 0), UFTDI_DEV(DRESDENELEKTRONIK, SENSORTERMINALBOARD, 0), UFTDI_DEV(DRESDENELEKTRONIK, WIRELESSHANDHELDTERMINAL, 0), UFTDI_DEV(DRESDENELEKTRONIK, DE_RFNODE, 0), UFTDI_DEV(DRESDENELEKTRONIK, LEVELSHIFTERSTICKLOWCOST, 0), UFTDI_DEV(ELEKTOR, FT323R, 0), UFTDI_DEV(EVOLUTION, ER1, 0), UFTDI_DEV(EVOLUTION, HYBRID, 0), UFTDI_DEV(EVOLUTION, RCM4, 0), UFTDI_DEV(FALCOM, SAMBA, 0), UFTDI_DEV(FALCOM, TWIST, 0), UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, 232EX, 0), UFTDI_DEV(FTDI, 232H, 0), UFTDI_DEV(FTDI, 232RL, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_1, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_2, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_3, 0), UFTDI_DEV(FTDI, 8U232AM_ALT, 0), UFTDI_DEV(FTDI, ACCESSO, 0), UFTDI_DEV(FTDI, ACG_HFDUAL, 0), UFTDI_DEV(FTDI, ACTIVE_ROBOTS, 0), UFTDI_DEV(FTDI, ACTZWAVE, 0), UFTDI_DEV(FTDI, AMC232, 0), UFTDI_DEV(FTDI, ARTEMIS, 0), UFTDI_DEV(FTDI, ASK_RDR400, 0), UFTDI_DEV(FTDI, ATIK_ATK16, 0), UFTDI_DEV(FTDI, ATIK_ATK16C, 0), UFTDI_DEV(FTDI, ATIK_ATK16HR, 0), UFTDI_DEV(FTDI, ATIK_ATK16HRC, 0), UFTDI_DEV(FTDI, ATIK_ATK16IC, 0), UFTDI_DEV(FTDI, BCS_SE923, 0), UFTDI_DEV(FTDI, CANDAPTER, 0), UFTDI_DEV(FTDI, CANUSB, 0), UFTDI_DEV(FTDI, CCSICDU20_0, 0), UFTDI_DEV(FTDI, CCSICDU40_1, 0), UFTDI_DEV(FTDI, CCSICDU64_4, 0), UFTDI_DEV(FTDI, CCSLOAD_N_GO_3, 0), UFTDI_DEV(FTDI, CCSMACHX_2, 0), UFTDI_DEV(FTDI, CCSPRIME8_5, 0), UFTDI_DEV(FTDI, CFA_631, 0), UFTDI_DEV(FTDI, CFA_632, 0), UFTDI_DEV(FTDI, CFA_633, 0), UFTDI_DEV(FTDI, CFA_634, 0), UFTDI_DEV(FTDI, CFA_635, 0), UFTDI_DEV(FTDI, CHAMSYS_24_MASTER_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MAXI_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MEDIA_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MIDI_TIMECODE, 0), UFTDI_DEV(FTDI, CHAMSYS_MINI_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_PC_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_USB_DMX, 0), UFTDI_DEV(FTDI, CHAMSYS_WING, 0), UFTDI_DEV(FTDI, COM4SM, 0), UFTDI_DEV(FTDI, CONVERTER_0, 0), UFTDI_DEV(FTDI, CONVERTER_1, 0), UFTDI_DEV(FTDI, CONVERTER_2, 0), UFTDI_DEV(FTDI, CONVERTER_3, 0), UFTDI_DEV(FTDI, CONVERTER_4, 0), UFTDI_DEV(FTDI, CONVERTER_5, 0), UFTDI_DEV(FTDI, CONVERTER_6, 0), UFTDI_DEV(FTDI, CONVERTER_7, 0), UFTDI_DEV(FTDI, CTI_USB_MINI_485, 0), UFTDI_DEV(FTDI, CTI_USB_NANO_485, 0), UFTDI_DEV(FTDI, DMX4ALL, 0), UFTDI_DEV(FTDI, DOMINTELL_DGQG, 0), UFTDI_DEV(FTDI, DOMINTELL_DUSB, 0), UFTDI_DEV(FTDI, DOTEC, 0), UFTDI_DEV(FTDI, ECLO_COM_1WIRE, 0), UFTDI_DEV(FTDI, ECO_PRO_CDS, 0), UFTDI_DEV(FTDI, EISCOU, 0), UFTDI_DEV(FTDI, ELSTER_UNICOM, 0), UFTDI_DEV(FTDI, ELV_ALC8500, 0), UFTDI_DEV(FTDI, ELV_CLI7000, 0), UFTDI_DEV(FTDI, ELV_CSI8, 0), UFTDI_DEV(FTDI, ELV_EC3000, 0), UFTDI_DEV(FTDI, ELV_EM1000DL, 0), UFTDI_DEV(FTDI, ELV_EM1010PC, 0), UFTDI_DEV(FTDI, ELV_FEM, 0), UFTDI_DEV(FTDI, ELV_FHZ1000PC, 0), UFTDI_DEV(FTDI, ELV_FHZ1300PC, 0), UFTDI_DEV(FTDI, ELV_FM3RX, 0), UFTDI_DEV(FTDI, ELV_FS20SIG, 0), UFTDI_DEV(FTDI, ELV_HS485, 0), UFTDI_DEV(FTDI, ELV_KL100, 0), UFTDI_DEV(FTDI, ELV_MSM1, 0), UFTDI_DEV(FTDI, ELV_PCD200, 0), UFTDI_DEV(FTDI, ELV_PCK100, 0), UFTDI_DEV(FTDI, ELV_PPS7330, 0), UFTDI_DEV(FTDI, ELV_RFP500, 0), UFTDI_DEV(FTDI, ELV_T1100, 0), UFTDI_DEV(FTDI, ELV_TFD128, 0), UFTDI_DEV(FTDI, ELV_TFM100, 0), UFTDI_DEV(FTDI, ELV_TWS550, 0), UFTDI_DEV(FTDI, ELV_UAD8, 0), UFTDI_DEV(FTDI, ELV_UDA7, 0), UFTDI_DEV(FTDI, ELV_UDF77, 0), UFTDI_DEV(FTDI, ELV_UIO88, 0), UFTDI_DEV(FTDI, ELV_ULA200, 0), UFTDI_DEV(FTDI, ELV_UM100, 0), UFTDI_DEV(FTDI, ELV_UMS100, 0), UFTDI_DEV(FTDI, ELV_UO100, 0), UFTDI_DEV(FTDI, ELV_UR100, 0), UFTDI_DEV(FTDI, ELV_USI2, 0), UFTDI_DEV(FTDI, ELV_USR, 0), UFTDI_DEV(FTDI, ELV_UTP8, 0), UFTDI_DEV(FTDI, ELV_WS300PC, 0), UFTDI_DEV(FTDI, ELV_WS444PC, 0), UFTDI_DEV(FTDI, ELV_WS500, 0), UFTDI_DEV(FTDI, ELV_WS550, 0), UFTDI_DEV(FTDI, ELV_WS777, 0), UFTDI_DEV(FTDI, ELV_WS888, 0), UFTDI_DEV(FTDI, EMCU2D, 0), UFTDI_DEV(FTDI, EMCU2H, 0), UFTDI_DEV(FTDI, FUTURE_0, 0), UFTDI_DEV(FTDI, FUTURE_1, 0), UFTDI_DEV(FTDI, FUTURE_2, 0), UFTDI_DEV(FTDI, GAMMASCOUT, 0), UFTDI_DEV(FTDI, GENERIC, 0), UFTDI_DEV(FTDI, GUDEADS_E808, 0), UFTDI_DEV(FTDI, GUDEADS_E809, 0), UFTDI_DEV(FTDI, GUDEADS_E80A, 0), UFTDI_DEV(FTDI, GUDEADS_E80B, 0), UFTDI_DEV(FTDI, GUDEADS_E80C, 0), UFTDI_DEV(FTDI, GUDEADS_E80D, 0), UFTDI_DEV(FTDI, GUDEADS_E80E, 0), UFTDI_DEV(FTDI, GUDEADS_E80F, 0), UFTDI_DEV(FTDI, GUDEADS_E88D, 0), UFTDI_DEV(FTDI, GUDEADS_E88E, 0), UFTDI_DEV(FTDI, GUDEADS_E88F, 0), UFTDI_DEV(FTDI, HD_RADIO, 0), UFTDI_DEV(FTDI, HO720, 0), UFTDI_DEV(FTDI, HO730, 0), UFTDI_DEV(FTDI, HO820, 0), UFTDI_DEV(FTDI, HO870, 0), UFTDI_DEV(FTDI, IBS_APP70, 0), UFTDI_DEV(FTDI, IBS_PCMCIA, 0), UFTDI_DEV(FTDI, IBS_PEDO, 0), UFTDI_DEV(FTDI, IBS_PICPRO, 0), UFTDI_DEV(FTDI, IBS_PK1, 0), UFTDI_DEV(FTDI, IBS_PROD, 0), UFTDI_DEV(FTDI, IBS_RS232MON, 0), UFTDI_DEV(FTDI, IBS_US485, 0), UFTDI_DEV(FTDI, IPLUS, 0), UFTDI_DEV(FTDI, IPLUS2, 0), UFTDI_DEV(FTDI, IRTRANS, 0), UFTDI_DEV(FTDI, KBS, 0), UFTDI_DEV(FTDI, KTLINK, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LENZ_LIUSB, 0), UFTDI_DEV(FTDI, LK202, 0), UFTDI_DEV(FTDI, LK204, 0), UFTDI_DEV(FTDI, LM3S_DEVEL_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LM3S_EVAL_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LM3S_ICDI_B_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, MASTERDEVEL2, 0), UFTDI_DEV(FTDI, MAXSTREAM, 0), UFTDI_DEV(FTDI, MHAM_DB9, 0), UFTDI_DEV(FTDI, MHAM_IC, 0), UFTDI_DEV(FTDI, MHAM_KW, 0), UFTDI_DEV(FTDI, MHAM_RS232, 0), UFTDI_DEV(FTDI, MHAM_Y6, 0), UFTDI_DEV(FTDI, MHAM_Y8, 0), UFTDI_DEV(FTDI, MHAM_Y9, 0), UFTDI_DEV(FTDI, MHAM_YS, 0), UFTDI_DEV(FTDI, MICRO_CHAMELEON, 0), UFTDI_DEV(FTDI, MTXORB_5, 0), UFTDI_DEV(FTDI, MTXORB_6, 0), UFTDI_DEV(FTDI, MX2_3, 0), UFTDI_DEV(FTDI, MX4_5, 0), UFTDI_DEV(FTDI, NXTCAM, 0), UFTDI_DEV(FTDI, OCEANIC, 0), UFTDI_DEV(FTDI, OOCDLINK, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, OPENDCC, 0), UFTDI_DEV(FTDI, OPENDCC_GATEWAY, 0), UFTDI_DEV(FTDI, OPENDCC_GBM, 0), UFTDI_DEV(FTDI, OPENDCC_SNIFFER, 0), UFTDI_DEV(FTDI, OPENDCC_THROTTLE, 0), UFTDI_DEV(FTDI, PCDJ_DAC2, 0), UFTDI_DEV(FTDI, PCMSFU, 0), UFTDI_DEV(FTDI, PERLE_ULTRAPORT, 0), UFTDI_DEV(FTDI, PHI_FISCO, 0), UFTDI_DEV(FTDI, PIEGROUP, 0), UFTDI_DEV(FTDI, PROPOX_JTAGCABLEII, 0), UFTDI_DEV(FTDI, R2000KU_TRUE_RNG, 0), UFTDI_DEV(FTDI, R2X0, 0), UFTDI_DEV(FTDI, RELAIS, 0), UFTDI_DEV(FTDI, REU_TINY, 0), UFTDI_DEV(FTDI, RMP200, 0), UFTDI_DEV(FTDI, RM_CANVIEW, 0), UFTDI_DEV(FTDI, RRCIRKITS_LOCOBUFFER, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_HS_LOGBOOK, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_LOGBOOKML, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_LS_LOGBOOK, 0), UFTDI_DEV(FTDI, SCS_DEVICE_0, 0), UFTDI_DEV(FTDI, SCS_DEVICE_1, 0), UFTDI_DEV(FTDI, SCS_DEVICE_2, 0), UFTDI_DEV(FTDI, SCS_DEVICE_3, 0), UFTDI_DEV(FTDI, SCS_DEVICE_4, 0), UFTDI_DEV(FTDI, SCS_DEVICE_5, 0), UFTDI_DEV(FTDI, SCS_DEVICE_6, 0), UFTDI_DEV(FTDI, SCS_DEVICE_7, 0), UFTDI_DEV(FTDI, SCX8_USB_PHOENIX, 0), UFTDI_DEV(FTDI, SDMUSBQSS, 0), UFTDI_DEV(FTDI, SEMC_DSS20, 0), UFTDI_DEV(FTDI, SERIAL_2232C, UFTDI_JTAG_CHECK_STRING), UFTDI_DEV(FTDI, SERIAL_2232D, 0), UFTDI_DEV(FTDI, SERIAL_232RL, 0), UFTDI_DEV(FTDI, SERIAL_4232H, 0), UFTDI_DEV(FTDI, SERIAL_8U100AX, 0), UFTDI_DEV(FTDI, SERIAL_8U232AM, 0), UFTDI_DEV(FTDI, SERIAL_8U232AM4, 0), UFTDI_DEV(FTDI, SIGNALYZER_SH2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_SH4, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_SLITE, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_ST, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SPECIAL_1, 0), UFTDI_DEV(FTDI, SPECIAL_3, 0), UFTDI_DEV(FTDI, SPECIAL_4, 0), UFTDI_DEV(FTDI, SPROG_II, 0), UFTDI_DEV(FTDI, SR_RADIO, 0), UFTDI_DEV(FTDI, SUUNTO_SPORTS, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13M, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13S, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13U, 0), UFTDI_DEV(FTDI, TAVIR_STK500, 0), UFTDI_DEV(FTDI, TERATRONIK_D2XX, 0), UFTDI_DEV(FTDI, TERATRONIK_VCP, 0), UFTDI_DEV(FTDI, THORLABS, 0), UFTDI_DEV(FTDI, TIAO, 0), UFTDI_DEV(FTDI, TNC_X, 0), UFTDI_DEV(FTDI, TTUSB, 0), UFTDI_DEV(FTDI, TURTELIZER2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, UOPTBR, 0), UFTDI_DEV(FTDI, USBSERIAL, 0), UFTDI_DEV(FTDI, USBX_707, 0), UFTDI_DEV(FTDI, USB_UIRT, 0), UFTDI_DEV(FTDI, USINT_CAT, 0), UFTDI_DEV(FTDI, USINT_RS232, 0), UFTDI_DEV(FTDI, USINT_WKEY, 0), UFTDI_DEV(FTDI, VARDAAN, 0), UFTDI_DEV(FTDI, VNHCPCUSB_D, 0), UFTDI_DEV(FTDI, WESTREX_MODEL_777, 0), UFTDI_DEV(FTDI, WESTREX_MODEL_8900F, 0), UFTDI_DEV(FTDI, XDS100V2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, XDS100V3, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, XF_547, 0), UFTDI_DEV(FTDI, XF_640, 0), UFTDI_DEV(FTDI, XF_642, 0), UFTDI_DEV(FTDI, XM_RADIO, 0), UFTDI_DEV(FTDI, YEI_SERVOCENTER31, 0), UFTDI_DEV(GNOTOMETRICS, USB, 0), UFTDI_DEV(ICOM, SP1, 0), UFTDI_DEV(ICOM, OPC_U_UC, 0), UFTDI_DEV(ICOM, RP2C1, 0), UFTDI_DEV(ICOM, RP2C2, 0), UFTDI_DEV(ICOM, RP2D, 0), UFTDI_DEV(ICOM, RP2KVR, 0), UFTDI_DEV(ICOM, RP2KVT, 0), UFTDI_DEV(ICOM, RP2VR, 0), UFTDI_DEV(ICOM, RP2VT, 0), UFTDI_DEV(ICOM, RP4KVR, 0), UFTDI_DEV(ICOM, RP4KVT, 0), UFTDI_DEV(IDTECH, IDT1221U, 0), UFTDI_DEV(INTERBIOMETRICS, IOBOARD, 0), UFTDI_DEV(INTERBIOMETRICS, MINI_IOBOARD, 0), UFTDI_DEV(INTREPIDCS, NEOVI, 0), UFTDI_DEV(INTREPIDCS, VALUECAN, 0), UFTDI_DEV(IONICS, PLUGCOMPUTER, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(JETI, SPC1201, 0), UFTDI_DEV(KOBIL, CONV_B1, 0), UFTDI_DEV(KOBIL, CONV_KAAN, 0), UFTDI_DEV(LARSENBRUSGAARD, ALTITRACK, 0), UFTDI_DEV(MARVELL, SHEEVAPLUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0100, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0101, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0102, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0103, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0104, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0105, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0106, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0107, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0108, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0109, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0110, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0111, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0112, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0113, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0114, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0115, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0116, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0117, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0118, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0119, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0120, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0121, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0122, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0123, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0124, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0125, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0126, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0128, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0129, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0130, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0131, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0132, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0133, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0134, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0135, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0136, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0137, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0138, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0139, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0140, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0141, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0142, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0143, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0144, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0145, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0146, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0147, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0148, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0149, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0150, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0151, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0152, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0159, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0160, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0161, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0162, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0163, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0164, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0165, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0166, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0167, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0168, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0169, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0170, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0171, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0172, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0173, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0174, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0175, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0176, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0177, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0178, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0179, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0180, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0181, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0182, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0183, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0184, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0185, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0186, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0187, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0188, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0189, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0190, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0191, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0192, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0193, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0194, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0195, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0196, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0197, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0198, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0199, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01ED, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FF, 0), UFTDI_DEV(MATRIXORBITAL, MOUA, 0), UFTDI_DEV(MELCO, PCOPRS1, 0), UFTDI_DEV(METAGEEK, TELLSTICK, 0), UFTDI_DEV(MOBILITY, USB_SERIAL, 0), UFTDI_DEV(OLIMEX, ARM_USB_OCD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(OLIMEX, ARM_USB_OCD_H, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(OPTO, CRD7734, 0), UFTDI_DEV(OPTO, CRD7734_1, 0), UFTDI_DEV(PAPOUCH, AD4USB, 0), UFTDI_DEV(PAPOUCH, AP485, 0), UFTDI_DEV(PAPOUCH, AP485_2, 0), UFTDI_DEV(PAPOUCH, DRAK5, 0), UFTDI_DEV(PAPOUCH, DRAK6, 0), UFTDI_DEV(PAPOUCH, GMSR, 0), UFTDI_DEV(PAPOUCH, GMUX, 0), UFTDI_DEV(PAPOUCH, IRAMP, 0), UFTDI_DEV(PAPOUCH, LEC, 0), UFTDI_DEV(PAPOUCH, MU, 0), UFTDI_DEV(PAPOUCH, QUIDO10X1, 0), UFTDI_DEV(PAPOUCH, QUIDO2X16, 0), UFTDI_DEV(PAPOUCH, QUIDO2X2, 0), UFTDI_DEV(PAPOUCH, QUIDO30X3, 0), UFTDI_DEV(PAPOUCH, QUIDO3X32, 0), UFTDI_DEV(PAPOUCH, QUIDO4X4, 0), UFTDI_DEV(PAPOUCH, QUIDO60X3, 0), UFTDI_DEV(PAPOUCH, QUIDO8X8, 0), UFTDI_DEV(PAPOUCH, SB232, 0), UFTDI_DEV(PAPOUCH, SB422, 0), UFTDI_DEV(PAPOUCH, SB422_2, 0), UFTDI_DEV(PAPOUCH, SB485, 0), UFTDI_DEV(PAPOUCH, SB485C, 0), UFTDI_DEV(PAPOUCH, SB485S, 0), UFTDI_DEV(PAPOUCH, SB485_2, 0), UFTDI_DEV(PAPOUCH, SIMUKEY, 0), UFTDI_DEV(PAPOUCH, TMU, 0), UFTDI_DEV(PAPOUCH, UPSUSB, 0), UFTDI_DEV(POSIFLEX, PP7000, 0), UFTDI_DEV(QIHARDWARE, JTAGSERIAL, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(RATOC, REXUSB60F, 0), UFTDI_DEV(RTSYSTEMS, CT29B, 0), UFTDI_DEV(RTSYSTEMS, SERIAL_VX7, 0), UFTDI_DEV(SEALEVEL, 2101, 0), UFTDI_DEV(SEALEVEL, 2102, 0), UFTDI_DEV(SEALEVEL, 2103, 0), UFTDI_DEV(SEALEVEL, 2104, 0), UFTDI_DEV(SEALEVEL, 2106, 0), UFTDI_DEV(SEALEVEL, 2201_1, 0), UFTDI_DEV(SEALEVEL, 2201_2, 0), UFTDI_DEV(SEALEVEL, 2202_1, 0), UFTDI_DEV(SEALEVEL, 2202_2, 0), UFTDI_DEV(SEALEVEL, 2203_1, 0), UFTDI_DEV(SEALEVEL, 2203_2, 0), UFTDI_DEV(SEALEVEL, 2401_1, 0), UFTDI_DEV(SEALEVEL, 2401_2, 0), UFTDI_DEV(SEALEVEL, 2401_3, 0), UFTDI_DEV(SEALEVEL, 2401_4, 0), UFTDI_DEV(SEALEVEL, 2402_1, 0), UFTDI_DEV(SEALEVEL, 2402_2, 0), UFTDI_DEV(SEALEVEL, 2402_3, 0), UFTDI_DEV(SEALEVEL, 2402_4, 0), UFTDI_DEV(SEALEVEL, 2403_1, 0), UFTDI_DEV(SEALEVEL, 2403_2, 0), UFTDI_DEV(SEALEVEL, 2403_3, 0), UFTDI_DEV(SEALEVEL, 2403_4, 0), UFTDI_DEV(SEALEVEL, 2801_1, 0), UFTDI_DEV(SEALEVEL, 2801_2, 0), UFTDI_DEV(SEALEVEL, 2801_3, 0), UFTDI_DEV(SEALEVEL, 2801_4, 0), UFTDI_DEV(SEALEVEL, 2801_5, 0), UFTDI_DEV(SEALEVEL, 2801_6, 0), UFTDI_DEV(SEALEVEL, 2801_7, 0), UFTDI_DEV(SEALEVEL, 2801_8, 0), UFTDI_DEV(SEALEVEL, 2802_1, 0), UFTDI_DEV(SEALEVEL, 2802_2, 0), UFTDI_DEV(SEALEVEL, 2802_3, 0), UFTDI_DEV(SEALEVEL, 2802_4, 0), UFTDI_DEV(SEALEVEL, 2802_5, 0), UFTDI_DEV(SEALEVEL, 2802_6, 0), UFTDI_DEV(SEALEVEL, 2802_7, 0), UFTDI_DEV(SEALEVEL, 2802_8, 0), UFTDI_DEV(SEALEVEL, 2803_1, 0), UFTDI_DEV(SEALEVEL, 2803_2, 0), UFTDI_DEV(SEALEVEL, 2803_3, 0), UFTDI_DEV(SEALEVEL, 2803_4, 0), UFTDI_DEV(SEALEVEL, 2803_5, 0), UFTDI_DEV(SEALEVEL, 2803_6, 0), UFTDI_DEV(SEALEVEL, 2803_7, 0), UFTDI_DEV(SEALEVEL, 2803_8, 0), UFTDI_DEV(SIIG2, DK201, 0), UFTDI_DEV(SIIG2, US2308, 0), UFTDI_DEV(TESTO, USB_INTERFACE, 0), UFTDI_DEV(TML, USB_SERIAL, 0), UFTDI_DEV(TTI, QL355P, 0), UFTDI_DEV(UNKNOWN4, NF_RIC, 0), #undef UFTDI_DEV }; DRIVER_MODULE(uftdi, uhub, uftdi_driver, uftdi_devclass, NULL, NULL); MODULE_DEPEND(uftdi, ucom, 1, 1, 1); MODULE_DEPEND(uftdi, usb, 1, 1, 1); MODULE_VERSION(uftdi, 1); USB_PNP_HOST_INFO(uftdi_devs); /* * Jtag product name strings table. Some products have one or more interfaces * dedicated to jtag or gpio, but use a product ID that's the same as other * products which don't. They are marked with a flag in the table above, and * the following string table is checked for flagged products. The string check * is done with strstr(); in effect there is an implicit wildcard at the * beginning and end of each product name string in table. */ static const struct jtag_by_name { const char * product_name; uint32_t jtag_interfaces; } jtag_products_by_name[] = { /* TI Beaglebone and TI XDS100Vn jtag product line. */ {"XDS100V", UFTDI_JTAG_IFACE(0)}, }; /* * Set up a sysctl and tunable to en/disable the feature of skipping the * creation of tty devices for jtag interfaces. Enabled by default. */ static int skip_jtag_interfaces = 1; SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, skip_jtag_interfaces, CTLFLAG_RWTUN, &skip_jtag_interfaces, 1, "Skip creating tty devices for jtag interfaces"); static boolean_t is_jtag_interface(struct usb_attach_arg *uaa, const struct usb_device_id *id) { int i, iface_bit; const char * product_name; const struct jtag_by_name *jbn; /* We only allocate 8 flag bits for jtag interface flags. */ if (uaa->info.bIfaceIndex >= UFTDI_JTAG_IFACES_MAX) return (0); iface_bit = UFTDI_JTAG_IFACE(uaa->info.bIfaceIndex); /* * If requested, search the name strings table and use the interface * bits from that table when the product name string matches, else use * the jtag interface bits from the main ID table. */ if ((id->driver_info & UFTDI_JTAG_MASK) == UFTDI_JTAG_CHECK_STRING) { product_name = usb_get_product(uaa->device); for (i = 0; i < nitems(jtag_products_by_name); i++) { jbn = &jtag_products_by_name[i]; if (strstr(product_name, jbn->product_name) != NULL && (jbn->jtag_interfaces & iface_bit) != 0) return (1); } } else if ((id->driver_info & iface_bit) != 0) return (1); return (0); } /* * Set up softc fields whose value depends on the device type. * * Note that the 2232C and 2232D devices are the same for our purposes. In the * silicon the difference is that the D series has CPU FIFO mode and C doesn't. * I haven't found any way of determining the C/D difference from info provided * by the chip other than trying to set CPU FIFO mode and having it work or not. * * Due to a hardware bug, a 232B chip without an eeprom reports itself as a * 232A, but if the serial number is also zero we know it's really a 232B. */ static void uftdi_devtype_setup(struct uftdi_softc *sc, struct usb_attach_arg *uaa) { struct usb_device_descriptor *dd; sc->sc_bcdDevice = uaa->info.bcdDevice; switch (uaa->info.bcdDevice) { case 0x200: dd = usbd_get_device_descriptor(sc->sc_udev); if (dd->iSerialNumber == 0) { sc->sc_devtype = DEVT_232B; } else { sc->sc_devtype = DEVT_232A; } sc->sc_ucom.sc_portno = 0; break; case 0x400: sc->sc_devtype = DEVT_232B; sc->sc_ucom.sc_portno = 0; break; case 0x500: sc->sc_devtype = DEVT_2232D; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x600: sc->sc_devtype = DEVT_232R; sc->sc_ucom.sc_portno = 0; break; case 0x700: sc->sc_devtype = DEVT_2232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x800: sc->sc_devtype = DEVT_4232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x900: sc->sc_devtype = DEVT_232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x1000: sc->sc_devtype = DEVT_230X; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; default: if (uaa->info.bcdDevice < 0x200) { sc->sc_devtype = DEVT_SIO; sc->sc_hdrlen = 1; } else { sc->sc_devtype = DEVT_232R; device_printf(sc->sc_dev, "Warning: unknown FTDI " "device type, bcdDevice=0x%04x, assuming 232R\n", uaa->info.bcdDevice); } sc->sc_ucom.sc_portno = 0; break; } } static int uftdi_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); const struct usb_device_id *id; if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { return (ENXIO); } /* * Attach to all present interfaces unless this is a JTAG one, which * we leave for userland. */ id = usbd_lookup_id_by_info(uftdi_devs, sizeof(uftdi_devs), &uaa->info); if (id == NULL) return (ENXIO); if (skip_jtag_interfaces && is_jtag_interface(uaa, id)) { printf("%s: skipping JTAG interface #%d for '%s' at %u.%u\n", device_get_name(dev), uaa->info.bIfaceIndex, usb_get_product(uaa->device), usbd_get_bus_index(uaa->device), usbd_get_device_index(uaa->device)); return (ENXIO); } uaa->driver_info = id->driver_info; return (BUS_PROBE_SPECIFIC); } static int uftdi_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uftdi_softc *sc = device_get_softc(dev); int error; DPRINTF("\n"); sc->sc_udev = uaa->device; sc->sc_dev = dev; sc->sc_unit = device_get_unit(dev); sc->sc_bitmode = UFTDI_BITMODE_NONE; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); uftdi_devtype_setup(sc, uaa); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, uftdi_config, UFTDI_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB " "transfers failed\n"); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); /* set a valid "lcr" value */ sc->sc_last_lcr = (FTDI_SIO_SET_DATA_STOP_BITS_2 | FTDI_SIO_SET_DATA_PARITY_NONE | FTDI_SIO_SET_DATA_BITS(8)); /* Indicate tx bits in sc_lsr can be used to determine busy vs idle. */ ucom_use_lsr_txbits(&sc->sc_ucom); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uftdi_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); /* success */ detach: uftdi_detach(dev); return (ENXIO); } static int uftdi_detach(device_t dev) { struct uftdi_softc *sc = device_get_softc(dev); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER); device_claim_softc(dev); uftdi_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uftdi); static void uftdi_free_softc(struct uftdi_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uftdi_free(struct ucom_softc *ucom) { uftdi_free_softc(ucom->sc_parent); } static void uftdi_cfg_open(struct ucom_softc *ucom) { /* * This do-nothing open routine exists for the sole purpose of this * DPRINTF() so that you can see the point at which open gets called * when debugging is enabled. */ DPRINTF("\n"); } static void uftdi_cfg_close(struct ucom_softc *ucom) { /* * This do-nothing close routine exists for the sole purpose of this * DPRINTF() so that you can see the point at which close gets called * when debugging is enabled. */ DPRINTF("\n"); } static void uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uftdi_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t pktlen; uint32_t buflen; uint8_t buf[1]; DPRINTFN(3, "\n"); switch (USB_GET_STATE(xfer)) { default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); } /* FALLTHROUGH */ case USB_ST_SETUP: case USB_ST_TRANSFERRED: /* * If output packets don't require headers (the common case) we * can just load the buffer up with payload bytes all at once. * Otherwise, loop to format packets into the buffer while there * is data available, and room for a packet header and at least * one byte of payload. * * NOTE: The FTDI chip doesn't accept zero length * packets. This cannot happen because the "pktlen" * will always be non-zero when "ucom_get_data()" * returns non-zero which we check below. */ pc = usbd_xfer_get_frame(xfer, 0); if (sc->sc_hdrlen == 0) { if (ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE, &buflen) == 0) break; } else { buflen = 0; while (buflen < UFTDI_OBUFSIZE - sc->sc_hdrlen - 1 && ucom_get_data(&sc->sc_ucom, pc, buflen + sc->sc_hdrlen, UFTDI_OPKTSIZE - sc->sc_hdrlen, &pktlen) != 0) { buf[0] = FTDI_OUT_TAG(pktlen, sc->sc_ucom.sc_portno); usbd_copy_in(pc, buflen, buf, 1); buflen += pktlen + sc->sc_hdrlen; } } if (buflen != 0) { usbd_xfer_set_frame_len(xfer, 0, buflen); usbd_transfer_submit(xfer); } break; } } static void uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uftdi_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[2]; uint8_t ftdi_msr; uint8_t msr; uint8_t lsr; int buflen; int pktlen; int pktmax; int offset; DPRINTFN(3, "\n"); usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (buflen < UFTDI_IHDRSIZE) goto tr_setup; pc = usbd_xfer_get_frame(xfer, 0); pktmax = xfer->max_packet_size - UFTDI_IHDRSIZE; lsr = 0; msr = 0; offset = 0; /* * Extract packet headers and payload bytes from the buffer. * Feed payload bytes to ucom/tty layer; OR-accumulate the * receiver-related header status bits which are transient and * could toggle with each packet, but for transmitter-related * bits keep only the ones from the last packet. * * After processing all packets in the buffer, process the * accumulated transient MSR and LSR values along with the * non-transient bits from the last packet header. */ while (buflen >= UFTDI_IHDRSIZE) { usbd_copy_out(pc, offset, buf, UFTDI_IHDRSIZE); offset += UFTDI_IHDRSIZE; buflen -= UFTDI_IHDRSIZE; lsr &= ~(ULSR_TXRDY | ULSR_TSRE); lsr |= FTDI_GET_LSR(buf); if (FTDI_GET_MSR(buf) & FTDI_SIO_RI_MASK) msr |= SER_RI; pktlen = min(buflen, pktmax); if (pktlen != 0) { ucom_put_data(&sc->sc_ucom, pc, offset, pktlen); offset += pktlen; buflen -= pktlen; } } ftdi_msr = FTDI_GET_MSR(buf); if (ftdi_msr & FTDI_SIO_CTS_MASK) msr |= SER_CTS; if (ftdi_msr & FTDI_SIO_DSR_MASK) msr |= SER_DSR; if (ftdi_msr & FTDI_SIO_RI_MASK) msr |= SER_RI; if (ftdi_msr & FTDI_SIO_RLSD_MASK) msr |= SER_DCD; if (sc->sc_msr != msr || sc->sc_lsr != lsr) { DPRINTF("status change msr=0x%02x (0x%02x) " "lsr=0x%02x (0x%02x)\n", msr, sc->sc_msr, lsr, sc->sc_lsr); sc->sc_msr = msr; sc->sc_lsr = lsr; ucom_status_change(&sc->sc_ucom); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uftdi_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; DPRINTFN(2, "DTR=%u\n", onoff); wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_MODEM_CTRL; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; DPRINTFN(2, "RTS=%u\n", onoff); wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_MODEM_CTRL; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; DPRINTFN(2, "BREAK=%u\n", onoff); if (onoff) { sc->sc_last_lcr |= FTDI_SIO_SET_BREAK; } else { sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK; } wValue = sc->sc_last_lcr; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_DATA; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } /* * Return true if the given speed is within operational tolerance of the target * speed. FTDI recommends that the hardware speed be within 3% of nominal. */ static inline boolean_t uftdi_baud_within_tolerance(uint64_t speed, uint64_t target) { return ((speed >= (target * 100) / 103) && (speed <= (target * 100) / 97)); } static int uftdi_sio_encode_baudrate(struct uftdi_softc *sc, speed_t speed, struct uftdi_param_config *cfg) { u_int i; const speed_t sio_speeds[] = { 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; /* * The original SIO chips were limited to a small choice of speeds * listed in an internal table of speeds chosen by an index value. */ for (i = 0; i < nitems(sio_speeds); ++i) { if (speed == sio_speeds[i]) { cfg->baud_lobits = i; cfg->baud_hibits = 0; return (0); } } return (ERANGE); } static int uftdi_encode_baudrate(struct uftdi_softc *sc, speed_t speed, struct uftdi_param_config *cfg) { static const uint8_t encoded_fraction[8] = {0, 3, 2, 4, 1, 5, 6, 7}; static const uint8_t roundoff_232a[16] = { 0, 1, 0, 1, 0, -1, 2, 1, 0, -1, -2, -3, 4, 3, 2, 1, }; uint32_t clk, divisor, fastclk_flag, frac, hwspeed; /* * If this chip has the fast clock capability and the speed is within * range, use the 12MHz clock, otherwise the standard clock is 3MHz. */ if ((sc->sc_devflags & DEVF_BAUDCLK_12M) && speed >= 1200) { clk = 12000000; fastclk_flag = (1 << 17); } else { clk = 3000000; fastclk_flag = 0; } /* * Make sure the requested speed is reachable with the available clock * and a 14-bit divisor. */ if (speed < (clk >> 14) || speed > clk) return (ERANGE); /* * Calculate the divisor, initially yielding a fixed point number with a * 4-bit (1/16ths) fraction, then round it to the nearest fraction the * hardware can handle. When the integral part of the divisor is * greater than one, the fractional part is in 1/8ths of the base clock. * The FT8U232AM chips can handle only 0.125, 0.250, and 0.5 fractions. * Later chips can handle all 1/8th fractions. * * If the integral part of the divisor is 1, a special rule applies: the * fractional part can only be .0 or .5 (this is a limitation of the * hardware). We handle this by truncating the fraction rather than * rounding, because this only applies to the two fastest speeds the * chip can achieve and rounding doesn't matter, either you've asked for * that exact speed or you've asked for something the chip can't do. * * For the FT8U232AM chips, use a roundoff table to adjust the result * to the nearest 1/8th fraction that is supported by the hardware, * leaving a fixed-point number with a 3-bit fraction which exactly * represents the math the hardware divider will do. For later-series * chips that support all 8 fractional divisors, just round 16ths to * 8ths by adding 1 and dividing by 2. */ divisor = (clk << 4) / speed; if ((divisor & 0xf) == 1) divisor &= 0xfffffff8; else if (sc->sc_devtype == DEVT_232A) divisor += roundoff_232a[divisor & 0x0f]; else divisor += 1; /* Rounds odd 16ths up to next 8th. */ divisor >>= 1; /* * Ensure the resulting hardware speed will be within operational * tolerance (within 3% of nominal). */ hwspeed = (clk << 3) / divisor; if (!uftdi_baud_within_tolerance(hwspeed, speed)) return (ERANGE); /* * Re-pack the divisor into hardware format. The lower 14-bits hold the * integral part, while the upper bits specify the fraction by indexing * a table of fractions within the hardware which is laid out as: * {0.0, 0.5, 0.25, 0.125, 0.325, 0.625, 0.725, 0.875} * The A-series chips only have the first four table entries; the * roundoff table logic above ensures that the fractional part for those * chips will be one of the first four values. * * When the divisor is 1 a special encoding applies: 1.0 is encoded as * 0.0, and 1.5 is encoded as 1.0. The rounding logic above has already * ensured that the fraction is either .0 or .5 if the integral is 1. */ frac = divisor & 0x07; divisor >>= 3; if (divisor == 1) { if (frac == 0) divisor = 0; /* 1.0 becomes 0.0 */ else frac = 0; /* 1.5 becomes 1.0 */ } divisor |= (encoded_fraction[frac] << 14) | fastclk_flag; cfg->baud_lobits = (uint16_t)divisor; cfg->baud_hibits = (uint16_t)(divisor >> 16); /* * If this chip requires the baud bits to be in the high byte of the * index word, move the bits up to that location. */ if (sc->sc_devflags & DEVF_BAUDBITS_HINDEX) { cfg->baud_hibits <<= 8; } return (0); } static int uftdi_set_parm_soft(struct ucom_softc *ucom, struct termios *t, struct uftdi_param_config *cfg) { struct uftdi_softc *sc = ucom->sc_parent; int err; memset(cfg, 0, sizeof(*cfg)); if (sc->sc_devtype == DEVT_SIO) err = uftdi_sio_encode_baudrate(sc, t->c_ospeed, cfg); else err = uftdi_encode_baudrate(sc, t->c_ospeed, cfg); if (err != 0) return (err); if (t->c_cflag & CSTOPB) cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2; else cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1; if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD; } else { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN; } } else { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE; } switch (t->c_cflag & CSIZE) { case CS5: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5); break; case CS6: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6); break; case CS7: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7); break; case CS8: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8); break; } if (t->c_cflag & CRTSCTS) { cfg->v_flow = FTDI_SIO_RTS_CTS_HS; } else if (t->c_iflag & (IXON | IXOFF)) { cfg->v_flow = FTDI_SIO_XON_XOFF_HS; cfg->v_start = t->c_cc[VSTART]; cfg->v_stop = t->c_cc[VSTOP]; } else { cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL; } return (0); } static int uftdi_pre_param(struct ucom_softc *ucom, struct termios *t) { struct uftdi_param_config cfg; DPRINTF("\n"); return (uftdi_set_parm_soft(ucom, t, &cfg)); } static void uftdi_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; struct uftdi_param_config cfg; struct usb_device_request req; DPRINTF("\n"); if (uftdi_set_parm_soft(ucom, t, &cfg)) { /* should not happen */ return; } sc->sc_last_lcr = cfg.lcr; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_BAUD_RATE; USETW(req.wValue, cfg.baud_lobits); USETW(req.wIndex, cfg.baud_hibits | wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_DATA; USETW(req.wValue, cfg.lcr); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_FLOW_CTRL; USETW2(req.wValue, cfg.v_stop, cfg.v_start); USETW2(req.wIndex, cfg.v_flow, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uftdi_softc *sc = ucom->sc_parent; DPRINTFN(3, "msr=0x%02x lsr=0x%02x\n", sc->sc_msr, sc->sc_lsr); *msr = sc->sc_msr; *lsr = sc->sc_lsr; } static int uftdi_reset(struct ucom_softc *ucom, int reset_type) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; DPRINTFN(2, "\n"); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_RESET; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW(req.wValue, reset_type); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_set_bitmode(struct ucom_softc *ucom, uint8_t bitmode, uint8_t iomask) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; int rv; DPRINTFN(2, "\n"); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_BITMODE; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); if (bitmode == UFTDI_BITMODE_NONE) USETW2(req.wValue, 0, 0); else USETW2(req.wValue, (1 << bitmode), iomask); rv = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); if (rv == USB_ERR_NORMAL_COMPLETION) sc->sc_bitmode = bitmode; return (rv); } static int uftdi_get_bitmode(struct ucom_softc *ucom, uint8_t *bitmode, uint8_t *iomask) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; DPRINTFN(2, "\n"); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_GET_BITMODE; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 1); USETW(req.wValue, 0); *bitmode = sc->sc_bitmode; return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, iomask)); } static int uftdi_set_latency(struct ucom_softc *ucom, int latency) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; DPRINTFN(2, "\n"); if (latency < 0 || latency > 255) return (USB_ERR_INVAL); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_LATENCY; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, 0, latency); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_get_latency(struct ucom_softc *ucom, int *latency) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint8_t buf; DPRINTFN(2, "\n"); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_GET_LATENCY; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 1); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &buf); *latency = buf; return (err); } static int uftdi_set_event_char(struct ucom_softc *ucom, int echar) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; uint8_t enable; DPRINTFN(2, "\n"); enable = (echar == -1) ? 0 : 1; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_EVENT_CHAR; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, enable, echar & 0xff); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_set_error_char(struct ucom_softc *ucom, int echar) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; uint8_t enable; DPRINTFN(2, "\n"); enable = (echar == -1) ? 0 : 1; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_ERROR_CHAR; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, enable, echar & 0xff); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_read_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint16_t widx, wlength, woffset; DPRINTFN(3, "\n"); /* Offset and length must both be evenly divisible by two. */ if ((eeio->offset | eeio->length) & 0x01) return (EINVAL); woffset = eeio->offset / 2U; wlength = eeio->length / 2U; for (widx = 0; widx < wlength; widx++) { req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_READ_EEPROM; USETW(req.wIndex, widx + woffset); USETW(req.wLength, 2); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &eeio->data[widx]); if (err != USB_ERR_NORMAL_COMPLETION) return (err); } return (USB_ERR_NORMAL_COMPLETION); } static int uftdi_write_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint16_t widx, wlength, woffset; DPRINTFN(3, "\n"); /* Offset and length must both be evenly divisible by two. */ if ((eeio->offset | eeio->length) & 0x01) return (EINVAL); woffset = eeio->offset / 2U; wlength = eeio->length / 2U; for (widx = 0; widx < wlength; widx++) { req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_WRITE_EEPROM; USETW(req.wIndex, widx + woffset); USETW(req.wLength, 0); USETW(req.wValue, eeio->data[widx]); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); if (err != USB_ERR_NORMAL_COMPLETION) return (err); } return (USB_ERR_NORMAL_COMPLETION); } static int uftdi_erase_eeprom(struct ucom_softc *ucom, int confirmation) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; DPRINTFN(2, "\n"); /* Small effort to prevent accidental erasure. */ if (confirmation != UFTDI_CONFIRM_ERASE) return (EINVAL); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_ERASE_EEPROM; USETW(req.wIndex, 0); USETW(req.wLength, 0); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); return (err); } static int uftdi_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td) { struct uftdi_softc *sc = ucom->sc_parent; int err; struct uftdi_bitmode * mode; switch (cmd) { case UFTDIIOC_RESET_IO: case UFTDIIOC_RESET_RX: case UFTDIIOC_RESET_TX: err = uftdi_reset(ucom, cmd == UFTDIIOC_RESET_IO ? FTDI_SIO_RESET_SIO : (cmd == UFTDIIOC_RESET_RX ? FTDI_SIO_RESET_PURGE_RX : FTDI_SIO_RESET_PURGE_TX)); break; case UFTDIIOC_SET_BITMODE: mode = (struct uftdi_bitmode *)data; err = uftdi_set_bitmode(ucom, mode->mode, mode->iomask); break; case UFTDIIOC_GET_BITMODE: mode = (struct uftdi_bitmode *)data; err = uftdi_get_bitmode(ucom, &mode->mode, &mode->iomask); break; case UFTDIIOC_SET_LATENCY: err = uftdi_set_latency(ucom, *((int *)data)); break; case UFTDIIOC_GET_LATENCY: err = uftdi_get_latency(ucom, (int *)data); break; case UFTDIIOC_SET_ERROR_CHAR: err = uftdi_set_error_char(ucom, *(int *)data); break; case UFTDIIOC_SET_EVENT_CHAR: err = uftdi_set_event_char(ucom, *(int *)data); break; case UFTDIIOC_GET_HWREV: *(int *)data = sc->sc_bcdDevice; err = 0; break; case UFTDIIOC_READ_EEPROM: err = uftdi_read_eeprom(ucom, (struct uftdi_eeio *)data); break; case UFTDIIOC_WRITE_EEPROM: err = uftdi_write_eeprom(ucom, (struct uftdi_eeio *)data); break; case UFTDIIOC_ERASE_EEPROM: err = uftdi_erase_eeprom(ucom, *(int *)data); break; default: return (ENOIOCTL); } if (err != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static void uftdi_start_read(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]); } static void uftdi_stop_read(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]); } static void uftdi_start_write(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]); } static void uftdi_stop_write(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]); } static void uftdi_poll(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UFTDI_N_TRANSFER); } Index: head/sys/dev/usb/serial/ulpt.c =================================================================== --- head/sys/dev/usb/serial/ulpt.c (revision 357971) +++ head/sys/dev/usb/serial/ulpt.c (revision 357972) @@ -1,764 +1,765 @@ #include __FBSDID("$FreeBSD$"); /* $NetBSD: ulpt.c,v 1.60 2003/10/04 21:19:50 augustss Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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. */ /* * Printer Class spec: http://www.usb.org/developers/data/devclass/usbprint109.PDF * Printer Class spec: http://www.usb.org/developers/devclass_docs/usbprint11.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ulpt_debug #include #include #ifdef USB_DEBUG static int ulpt_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ulpt, CTLFLAG_RW, 0, "USB ulpt"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ulpt, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ulpt"); SYSCTL_INT(_hw_usb_ulpt, OID_AUTO, debug, CTLFLAG_RWTUN, &ulpt_debug, 0, "Debug level"); #endif #define ULPT_BSIZE (1<<15) /* bytes */ #define ULPT_IFQ_MAXLEN 2 /* units */ #define UR_GET_DEVICE_ID 0x00 #define UR_GET_PORT_STATUS 0x01 #define UR_SOFT_RESET 0x02 #define LPS_NERR 0x08 /* printer no error */ #define LPS_SELECT 0x10 /* printer selected */ #define LPS_NOPAPER 0x20 /* printer out of paper */ #define LPS_INVERT (LPS_SELECT|LPS_NERR) #define LPS_MASK (LPS_SELECT|LPS_NERR|LPS_NOPAPER) enum { ULPT_BULK_DT_WR, ULPT_BULK_DT_RD, ULPT_INTR_DT_RD, ULPT_N_TRANSFER, }; struct ulpt_softc { struct usb_fifo_sc sc_fifo; struct usb_fifo_sc sc_fifo_noreset; struct mtx sc_mtx; struct usb_callout sc_watchdog; device_t sc_dev; struct usb_device *sc_udev; struct usb_fifo *sc_fifo_open[2]; struct usb_xfer *sc_xfer[ULPT_N_TRANSFER]; int sc_fflags; /* current open flags, FREAD and * FWRITE */ uint8_t sc_iface_no; uint8_t sc_last_status; uint8_t sc_zlps; /* number of consequtive zero length * packets received */ }; /* prototypes */ static device_probe_t ulpt_probe; static device_attach_t ulpt_attach; static device_detach_t ulpt_detach; static usb_callback_t ulpt_write_callback; static usb_callback_t ulpt_read_callback; static usb_callback_t ulpt_status_callback; static void ulpt_reset(struct ulpt_softc *); static void ulpt_watchdog(void *); static usb_fifo_close_t ulpt_close; static usb_fifo_cmd_t ulpt_start_read; static usb_fifo_cmd_t ulpt_start_write; static usb_fifo_cmd_t ulpt_stop_read; static usb_fifo_cmd_t ulpt_stop_write; static usb_fifo_ioctl_t ulpt_ioctl; static usb_fifo_open_t ulpt_open; static usb_fifo_open_t unlpt_open; static struct usb_fifo_methods ulpt_fifo_methods = { .f_close = &ulpt_close, .f_ioctl = &ulpt_ioctl, .f_open = &ulpt_open, .f_start_read = &ulpt_start_read, .f_start_write = &ulpt_start_write, .f_stop_read = &ulpt_stop_read, .f_stop_write = &ulpt_stop_write, .basename[0] = "ulpt", }; static struct usb_fifo_methods unlpt_fifo_methods = { .f_close = &ulpt_close, .f_ioctl = &ulpt_ioctl, .f_open = &unlpt_open, .f_start_read = &ulpt_start_read, .f_start_write = &ulpt_start_write, .f_stop_read = &ulpt_stop_read, .f_stop_write = &ulpt_stop_write, .basename[0] = "unlpt", }; static void ulpt_reset(struct ulpt_softc *sc) { struct usb_device_request req; DPRINTFN(2, "\n"); req.bRequest = UR_SOFT_RESET; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); /* * There was a mistake in the USB printer 1.0 spec that gave the * request type as UT_WRITE_CLASS_OTHER; it should have been * UT_WRITE_CLASS_INTERFACE. Many printers use the old one, * so we try both. */ mtx_lock(&sc->sc_mtx); req.bmRequestType = UT_WRITE_CLASS_OTHER; if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.0 */ req.bmRequestType = UT_WRITE_CLASS_INTERFACE; if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.1 */ /* ignore error */ } } mtx_unlock(&sc->sc_mtx); } static void ulpt_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ulpt_softc *sc = usbd_xfer_softc(xfer); struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_TX]; struct usb_page_cache *pc; int actlen, max; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); if (f == NULL) { /* should not happen */ DPRINTF("no FIFO\n"); return; } DPRINTF("state=0x%x actlen=%d\n", USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); max = usbd_xfer_max_len(xfer); if (usb_fifo_get_data(f, pc, 0, max, &actlen, 0)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void ulpt_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ulpt_softc *sc = usbd_xfer_softc(xfer); struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_RX]; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); if (f == NULL) { /* should not happen */ DPRINTF("no FIFO\n"); return; } DPRINTF("state=0x%x\n", USB_GET_STATE(xfer)); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen == 0) { if (sc->sc_zlps == 4) { /* enable BULK throttle */ usbd_xfer_set_interval(xfer, 500); /* ms */ } else { sc->sc_zlps++; } } else { /* disable BULK throttle */ usbd_xfer_set_interval(xfer, 0); sc->sc_zlps = 0; } pc = usbd_xfer_get_frame(xfer, 0); usb_fifo_put_data(f, pc, 0, actlen, 1); case USB_ST_SETUP: tr_setup: if (usb_fifo_put_bytes_max(f) != 0) { usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); } break; default: /* Error */ /* disable BULK throttle */ usbd_xfer_set_interval(xfer, 0); sc->sc_zlps = 0; if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void ulpt_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct ulpt_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; uint8_t cur_status; uint8_t new_status; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_out(pc, 0, &cur_status, 1); cur_status = (cur_status ^ LPS_INVERT) & LPS_MASK; new_status = cur_status & ~sc->sc_last_status; sc->sc_last_status = cur_status; if (new_status & LPS_SELECT) log(LOG_NOTICE, "%s: offline\n", device_get_nameunit(sc->sc_dev)); else if (new_status & LPS_NOPAPER) log(LOG_NOTICE, "%s: out of paper\n", device_get_nameunit(sc->sc_dev)); else if (new_status & LPS_NERR) log(LOG_NOTICE, "%s: output error\n", device_get_nameunit(sc->sc_dev)); break; case USB_ST_SETUP: req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_GET_PORT_STATUS; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 1); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, 1); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* wait for next watchdog timeout */ } break; } } static const struct usb_config ulpt_config[ULPT_N_TRANSFER] = { [ULPT_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = ULPT_BSIZE, .flags = {.pipe_bof = 1,.proxy_buffer = 1}, .callback = &ulpt_write_callback, }, [ULPT_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = ULPT_BSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, .callback = &ulpt_read_callback, }, [ULPT_INTR_DT_RD] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + 1, .callback = &ulpt_status_callback, .timeout = 1000, /* 1 second */ }, }; static void ulpt_start_read(struct usb_fifo *fifo) { struct ulpt_softc *sc = usb_fifo_softc(fifo); usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_RD]); } static void ulpt_stop_read(struct usb_fifo *fifo) { struct ulpt_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_RD]); } static void ulpt_start_write(struct usb_fifo *fifo) { struct ulpt_softc *sc = usb_fifo_softc(fifo); usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_WR]); } static void ulpt_stop_write(struct usb_fifo *fifo) { struct ulpt_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_WR]); } static int ulpt_open(struct usb_fifo *fifo, int fflags) { struct ulpt_softc *sc = usb_fifo_softc(fifo); /* we assume that open is a serial process */ if (sc->sc_fflags == 0) { /* reset USB parallel port */ ulpt_reset(sc); } return (unlpt_open(fifo, fflags)); } static int unlpt_open(struct usb_fifo *fifo, int fflags) { struct ulpt_softc *sc = usb_fifo_softc(fifo); if (sc->sc_fflags & fflags) { return (EBUSY); } if (fflags & FREAD) { /* clear stall first */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); if (usb_fifo_alloc_buffer(fifo, usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_RD]), ULPT_IFQ_MAXLEN)) { return (ENOMEM); } /* set which FIFO is opened */ sc->sc_fifo_open[USB_FIFO_RX] = fifo; } if (fflags & FWRITE) { /* clear stall first */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_WR]); mtx_unlock(&sc->sc_mtx); if (usb_fifo_alloc_buffer(fifo, usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_WR]), ULPT_IFQ_MAXLEN)) { return (ENOMEM); } /* set which FIFO is opened */ sc->sc_fifo_open[USB_FIFO_TX] = fifo; } sc->sc_fflags |= fflags & (FREAD | FWRITE); return (0); } static void ulpt_close(struct usb_fifo *fifo, int fflags) { struct ulpt_softc *sc = usb_fifo_softc(fifo); sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); if (fflags & (FREAD | FWRITE)) { usb_fifo_free_buffer(fifo); } } static int ulpt_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags) { return (ENODEV); } static const STRUCT_USB_HOST_ID ulpt_devs[] = { /* Uni-directional USB printer */ {USB_IFACE_CLASS(UICLASS_PRINTER), USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), USB_IFACE_PROTOCOL(UIPROTO_PRINTER_UNI)}, /* Bi-directional USB printer */ {USB_IFACE_CLASS(UICLASS_PRINTER), USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), USB_IFACE_PROTOCOL(UIPROTO_PRINTER_BI)}, /* 1284 USB printer */ {USB_IFACE_CLASS(UICLASS_PRINTER), USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), USB_IFACE_PROTOCOL(UIPROTO_PRINTER_1284)}, }; static int ulpt_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); error = usbd_lookup_id_by_uaa(ulpt_devs, sizeof(ulpt_devs), uaa); if (error) return (error); return (BUS_PROBE_GENERIC); } static int ulpt_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct ulpt_softc *sc = device_get_softc(dev); struct usb_interface_descriptor *id; int unit = device_get_unit(dev); int error; uint8_t iface_index = uaa->info.bIfaceIndex; uint8_t alt_index; DPRINTFN(11, "sc=%p\n", sc); sc->sc_dev = dev; sc->sc_udev = uaa->device; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "ulpt lock", NULL, MTX_DEF | MTX_RECURSE); usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); /* search through all the descriptors looking for bidir mode */ id = usbd_get_interface_descriptor(uaa->iface); alt_index = 0xFF; while (1) { if (id == NULL) { break; } if ((id->bDescriptorType == UDESC_INTERFACE) && (id->bLength >= sizeof(*id))) { if (id->bInterfaceNumber != uaa->info.bIfaceNum) { break; } else { alt_index++; if ((id->bInterfaceClass == UICLASS_PRINTER) && (id->bInterfaceSubClass == UISUBCLASS_PRINTER) && (id->bInterfaceProtocol == UIPROTO_PRINTER_BI)) { goto found; } } } id = (void *)usb_desc_foreach( usbd_get_config_descriptor(uaa->device), (void *)id); } goto detach; found: DPRINTF("setting alternate " "config number: %d\n", alt_index); if (alt_index) { error = usbd_set_alt_interface_index (uaa->device, iface_index, alt_index); if (error) { DPRINTF("could not set alternate " "config, error=%s\n", usbd_errstr(error)); goto detach; } } sc->sc_iface_no = id->bInterfaceNumber; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, ulpt_config, ULPT_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } device_printf(sc->sc_dev, "using bi-directional mode\n"); #if 0 /* * This code is disabled because for some mysterious reason it causes * printing not to work. But only sometimes, and mostly with * UHCI and less often with OHCI. *sigh* */ { struct usb_config_descriptor *cd = usbd_get_config_descriptor(dev); struct usb_device_request req; int len, alen; req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_GET_DEVICE_ID; USETW(req.wValue, cd->bConfigurationValue); USETW2(req.wIndex, id->bInterfaceNumber, id->bAlternateSetting); USETW(req.wLength, sizeof devinfo - 1); error = usbd_do_request_flags(dev, &req, devinfo, USB_SHORT_XFER_OK, &alen, USB_DEFAULT_TIMEOUT); if (error) { device_printf(sc->sc_dev, "cannot get device id\n"); } else if (alen <= 2) { device_printf(sc->sc_dev, "empty device id, no " "printer connected?\n"); } else { /* devinfo now contains an IEEE-1284 device ID */ len = ((devinfo[0] & 0xff) << 8) | (devinfo[1] & 0xff); if (len > sizeof devinfo - 3) len = sizeof devinfo - 3; devinfo[len] = 0; printf("%s: device id <", device_get_nameunit(sc->sc_dev)); ieee1284_print_id(devinfo + 2); printf(">\n"); } } #endif error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, &ulpt_fifo_methods, &sc->sc_fifo, unit, -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (error) { goto detach; } error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, &unlpt_fifo_methods, &sc->sc_fifo_noreset, unit, -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (error) { goto detach; } /* start reading of status */ mtx_lock(&sc->sc_mtx); ulpt_watchdog(sc); mtx_unlock(&sc->sc_mtx); return (0); detach: ulpt_detach(dev); return (ENOMEM); } static int ulpt_detach(device_t dev) { struct ulpt_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); usb_fifo_detach(&sc->sc_fifo); usb_fifo_detach(&sc->sc_fifo_noreset); mtx_lock(&sc->sc_mtx); usb_callout_stop(&sc->sc_watchdog); mtx_unlock(&sc->sc_mtx); usbd_transfer_unsetup(sc->sc_xfer, ULPT_N_TRANSFER); usb_callout_drain(&sc->sc_watchdog); mtx_destroy(&sc->sc_mtx); return (0); } #if 0 /* XXX This does not belong here. */ /* * Compare two strings until the second ends. */ static uint8_t ieee1284_compare(const char *a, const char *b) { while (1) { if (*b == 0) { break; } if (*a != *b) { return 1; } b++; a++; } return 0; } /* * Print select parts of an IEEE 1284 device ID. */ void ieee1284_print_id(char *str) { char *p, *q; for (p = str - 1; p; p = strchr(p, ';')) { p++; /* skip ';' */ if (ieee1284_compare(p, "MFG:") == 0 || ieee1284_compare(p, "MANUFACTURER:") == 0 || ieee1284_compare(p, "MDL:") == 0 || ieee1284_compare(p, "MODEL:") == 0) { q = strchr(p, ';'); if (q) printf("%.*s", (int)(q - p + 1), p); } } } #endif static void ulpt_watchdog(void *arg) { struct ulpt_softc *sc = arg; mtx_assert(&sc->sc_mtx, MA_OWNED); /* * Only read status while the device is not opened, due to * possible hardware or firmware bug in some printers. */ if (sc->sc_fflags == 0) usbd_transfer_start(sc->sc_xfer[ULPT_INTR_DT_RD]); usb_callout_reset(&sc->sc_watchdog, hz, &ulpt_watchdog, sc); } static devclass_t ulpt_devclass; static device_method_t ulpt_methods[] = { DEVMETHOD(device_probe, ulpt_probe), DEVMETHOD(device_attach, ulpt_attach), DEVMETHOD(device_detach, ulpt_detach), DEVMETHOD_END }; static driver_t ulpt_driver = { .name = "ulpt", .methods = ulpt_methods, .size = sizeof(struct ulpt_softc), }; DRIVER_MODULE(ulpt, uhub, ulpt_driver, ulpt_devclass, NULL, 0); MODULE_DEPEND(ulpt, usb, 1, 1, 1); MODULE_VERSION(ulpt, 1); USB_PNP_HOST_INFO(ulpt_devs); Index: head/sys/dev/usb/serial/umcs.c =================================================================== --- head/sys/dev/usb/serial/umcs.c (revision 357971) +++ head/sys/dev/usb/serial/umcs.c (revision 357972) @@ -1,1109 +1,1110 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Lev Serebryakov . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This driver supports several multiport USB-to-RS232 serial adapters driven * by MosChip mos7820 and mos7840, bridge chips. * The adapters are sold under many different brand names. * * Datasheets are available at MosChip www site at * http://www.moschip.com. The datasheets don't contain full * programming information for the chip. * * It is nornal to have only two enabled ports in devices, based on * quad-port mos7840. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR umcs_debug #include #include #include #include #define UMCS7840_MODVER 1 #ifdef USB_DEBUG static int umcs_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW, 0, "USB umcs quadport serial adapter"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB umcs quadport serial adapter"); SYSCTL_INT(_hw_usb_umcs, OID_AUTO, debug, CTLFLAG_RWTUN, &umcs_debug, 0, "Debug level"); #endif /* USB_DEBUG */ /* * Two-port devices (both with 7820 chip and 7840 chip configured as two-port) * have ports 0 and 2, with ports 1 and 3 omitted. * So,PHYSICAL port numbers (indexes) on two-port device will be 0 and 2. * This driver trys to use physical numbers as much as possible. */ /* * Indexed by PHYSICAL port number. * Pack non-regular registers to array to easier if-less access. */ struct umcs7840_port_registers { uint8_t reg_sp; /* SP register. */ uint8_t reg_control; /* CONTROL register. */ uint8_t reg_dcr; /* DCR0 register. DCR1 & DCR2 can be * calculated */ }; static const struct umcs7840_port_registers umcs7840_port_registers[UMCS7840_MAX_PORTS] = { {.reg_sp = MCS7840_DEV_REG_SP1,.reg_control = MCS7840_DEV_REG_CONTROL1,.reg_dcr = MCS7840_DEV_REG_DCR0_1}, {.reg_sp = MCS7840_DEV_REG_SP2,.reg_control = MCS7840_DEV_REG_CONTROL2,.reg_dcr = MCS7840_DEV_REG_DCR0_2}, {.reg_sp = MCS7840_DEV_REG_SP3,.reg_control = MCS7840_DEV_REG_CONTROL3,.reg_dcr = MCS7840_DEV_REG_DCR0_3}, {.reg_sp = MCS7840_DEV_REG_SP4,.reg_control = MCS7840_DEV_REG_CONTROL4,.reg_dcr = MCS7840_DEV_REG_DCR0_4}, }; enum { UMCS7840_BULK_RD_EP, UMCS7840_BULK_WR_EP, UMCS7840_N_TRANSFERS }; struct umcs7840_softc_oneport { struct usb_xfer *sc_xfer[UMCS7840_N_TRANSFERS]; /* Control structures * for two transfers */ uint8_t sc_lcr; /* local line control register */ uint8_t sc_mcr; /* local modem control register */ }; struct umcs7840_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom[UMCS7840_MAX_PORTS]; /* Need to be continuous * array, so indexed by * LOGICAL port * (subunit) number */ struct usb_xfer *sc_intr_xfer; /* Interrupt endpoint */ device_t sc_dev; /* Device for error prints */ struct usb_device *sc_udev; /* USB Device for all operations */ struct mtx sc_mtx; /* ucom requires this */ uint8_t sc_driver_done; /* Flag when enumeration is finished */ uint8_t sc_numports; /* Number of ports (subunits) */ struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS]; /* Indexed by PHYSICAL * port number. */ }; /* prototypes */ static usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t *); static usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t); static usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t *); static usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t); static usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *, uint8_t, uint32_t); static usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *, uint8_t *); static void umcs7840_free(struct ucom_softc *); static void umcs7840_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void umcs7840_cfg_set_dtr(struct ucom_softc *, uint8_t); static void umcs7840_cfg_set_rts(struct ucom_softc *, uint8_t); static void umcs7840_cfg_set_break(struct ucom_softc *, uint8_t); static void umcs7840_cfg_param(struct ucom_softc *, struct termios *); static void umcs7840_cfg_open(struct ucom_softc *); static void umcs7840_cfg_close(struct ucom_softc *); static int umcs7840_pre_param(struct ucom_softc *, struct termios *); static void umcs7840_start_read(struct ucom_softc *); static void umcs7840_stop_read(struct ucom_softc *); static void umcs7840_start_write(struct ucom_softc *); static void umcs7840_stop_write(struct ucom_softc *); static void umcs7840_poll(struct ucom_softc *ucom); static device_probe_t umcs7840_probe; static device_attach_t umcs7840_attach; static device_detach_t umcs7840_detach; static void umcs7840_free_softc(struct umcs7840_softc *); static usb_callback_t umcs7840_intr_callback; static usb_callback_t umcs7840_read_callback1; static usb_callback_t umcs7840_read_callback2; static usb_callback_t umcs7840_read_callback3; static usb_callback_t umcs7840_read_callback4; static usb_callback_t umcs7840_write_callback1; static usb_callback_t umcs7840_write_callback2; static usb_callback_t umcs7840_write_callback3; static usb_callback_t umcs7840_write_callback4; static void umcs7840_read_callbackN(struct usb_xfer *, usb_error_t, uint8_t); static void umcs7840_write_callbackN(struct usb_xfer *, usb_error_t, uint8_t); /* Indexed by LOGICAL port number (subunit), so two-port device uses 0 & 1 */ static usb_callback_t *umcs7840_rw_callbacks[UMCS7840_MAX_PORTS][UMCS7840_N_TRANSFERS] = { {&umcs7840_read_callback1, &umcs7840_write_callback1}, {&umcs7840_read_callback2, &umcs7840_write_callback2}, {&umcs7840_read_callback3, &umcs7840_write_callback3}, {&umcs7840_read_callback4, &umcs7840_write_callback4}, }; static const struct usb_config umcs7840_bulk_config_data[UMCS7840_N_TRANSFERS] = { [UMCS7840_BULK_RD_EP] = { .type = UE_BULK, .endpoint = 0x01, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umcs7840_read_callback1, .if_index = 0, }, [UMCS7840_BULK_WR_EP] = { .type = UE_BULK, .endpoint = 0x02, .direction = UE_DIR_OUT, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umcs7840_write_callback1, .if_index = 0, }, }; static const struct usb_config umcs7840_intr_config_data[1] = { [0] = { .type = UE_INTERRUPT, .endpoint = 0x09, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umcs7840_intr_callback, .if_index = 0, }, }; static struct ucom_callback umcs7840_callback = { .ucom_cfg_get_status = &umcs7840_cfg_get_status, .ucom_cfg_set_dtr = &umcs7840_cfg_set_dtr, .ucom_cfg_set_rts = &umcs7840_cfg_set_rts, .ucom_cfg_set_break = &umcs7840_cfg_set_break, .ucom_cfg_param = &umcs7840_cfg_param, .ucom_cfg_open = &umcs7840_cfg_open, .ucom_cfg_close = &umcs7840_cfg_close, .ucom_pre_param = &umcs7840_pre_param, .ucom_start_read = &umcs7840_start_read, .ucom_stop_read = &umcs7840_stop_read, .ucom_start_write = &umcs7840_start_write, .ucom_stop_write = &umcs7840_stop_write, .ucom_poll = &umcs7840_poll, .ucom_free = &umcs7840_free, }; static const STRUCT_USB_HOST_ID umcs7840_devs[] = { {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7820, 0)}, {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7840, 0)}, }; static device_method_t umcs7840_methods[] = { DEVMETHOD(device_probe, umcs7840_probe), DEVMETHOD(device_attach, umcs7840_attach), DEVMETHOD(device_detach, umcs7840_detach), DEVMETHOD_END }; static devclass_t umcs7840_devclass; static driver_t umcs7840_driver = { .name = "umcs7840", .methods = umcs7840_methods, .size = sizeof(struct umcs7840_softc), }; DRIVER_MODULE(umcs7840, uhub, umcs7840_driver, umcs7840_devclass, 0, 0); MODULE_DEPEND(umcs7840, ucom, 1, 1, 1); MODULE_DEPEND(umcs7840, usb, 1, 1, 1); MODULE_VERSION(umcs7840, UMCS7840_MODVER); USB_PNP_HOST_INFO(umcs7840_devs); static int umcs7840_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != MCS7840_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != MCS7840_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(umcs7840_devs, sizeof(umcs7840_devs), uaa)); } static int umcs7840_attach(device_t dev) { struct usb_config umcs7840_config_tmp[UMCS7840_N_TRANSFERS]; struct usb_attach_arg *uaa = device_get_ivars(dev); struct umcs7840_softc *sc = device_get_softc(dev); uint8_t iface_index = MCS7840_IFACE_INDEX; int error; int subunit; int n; uint8_t data; for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) umcs7840_config_tmp[n] = umcs7840_bulk_config_data[n]; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "umcs7840", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_dev = dev; sc->sc_udev = uaa->device; /* * Get number of ports * Documentation (full datasheet) says, that number of ports is * set as MCS7840_DEV_MODE_SELECT24S bit in MODE R/Only * register. But vendor driver uses these undocumented * register & bit. * * Experiments show, that MODE register can have `0' * (4 ports) bit on 2-port device, so use vendor driver's way. * * Also, see notes in header file for these constants. */ umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_GPIO, &data); if (data & MCS7840_DEV_GPIO_4PORTS) { sc->sc_numports = 4; /* Store physical port numbers in sc_portno */ sc->sc_ucom[0].sc_portno = 0; sc->sc_ucom[1].sc_portno = 1; sc->sc_ucom[2].sc_portno = 2; sc->sc_ucom[3].sc_portno = 3; } else { sc->sc_numports = 2; /* Store physical port numbers in sc_portno */ sc->sc_ucom[0].sc_portno = 0; sc->sc_ucom[1].sc_portno = 2; /* '1' is skipped */ } device_printf(dev, "Chip mcs%04x, found %d active ports\n", uaa->info.idProduct, sc->sc_numports); if (!umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_MODE, &data)) { device_printf(dev, "On-die confguration: RST: active %s, HRD: %s, PLL: %s, POR: %s, Ports: %s, EEPROM write %s, IrDA is %savailable\n", (data & MCS7840_DEV_MODE_RESET) ? "low" : "high", (data & MCS7840_DEV_MODE_SER_PRSNT) ? "yes" : "no", (data & MCS7840_DEV_MODE_PLLBYPASS) ? "bypassed" : "avail", (data & MCS7840_DEV_MODE_PORBYPASS) ? "bypassed" : "avail", (data & MCS7840_DEV_MODE_SELECT24S) ? "2" : "4", (data & MCS7840_DEV_MODE_EEPROMWR) ? "enabled" : "disabled", (data & MCS7840_DEV_MODE_IRDA) ? "" : "not "); } /* Setup all transfers */ for (subunit = 0; subunit < sc->sc_numports; ++subunit) { for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) { /* Set endpoint address */ umcs7840_config_tmp[n].endpoint = umcs7840_bulk_config_data[n].endpoint + 2 * sc->sc_ucom[subunit].sc_portno; umcs7840_config_tmp[n].callback = umcs7840_rw_callbacks[subunit][n]; } error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, umcs7840_config_tmp, UMCS7840_N_TRANSFERS, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed for subunit %d of %d\n", subunit + 1, sc->sc_numports); goto detach; } } error = usbd_transfer_setup(uaa->device, &iface_index, &sc->sc_intr_xfer, umcs7840_intr_config_data, 1, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB transfers failed for interrupt\n"); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); for (subunit = 0; subunit < sc->sc_numports; ++subunit) { usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]); usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]); } mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc, &umcs7840_callback, &sc->sc_mtx); if (error) goto detach; ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: umcs7840_detach(dev); return (ENXIO); } static int umcs7840_detach(device_t dev) { struct umcs7840_softc *sc = device_get_softc(dev); int subunit; ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); for (subunit = 0; subunit < sc->sc_numports; ++subunit) usbd_transfer_unsetup(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, UMCS7840_N_TRANSFERS); usbd_transfer_unsetup(&sc->sc_intr_xfer, 1); device_claim_softc(dev); umcs7840_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(umcs7840); static void umcs7840_free_softc(struct umcs7840_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void umcs7840_free(struct ucom_softc *ucom) { umcs7840_free_softc(ucom->sc_parent); } static void umcs7840_cfg_open(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; uint16_t pn = ucom->sc_portno; uint8_t data; /* If it very first open, finish global configuration */ if (!sc->sc_driver_done) { /* * USB enumeration is finished, pass internal memory to FIFOs * If it is done in the end of "attach", kernel panics. */ if (umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, &data)) return; data |= MCS7840_DEV_CONTROL1_DRIVER_DONE; if (umcs7840_set_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, data)) return; sc->sc_driver_done = 1; } /* Toggle reset bit on-off */ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data)) return; data |= MCS7840_DEV_SPx_UART_RESET; if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) return; data &= ~MCS7840_DEV_SPx_UART_RESET; if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) return; /* Set RS-232 mode */ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_SCRATCHPAD, MCS7840_UART_SCRATCHPAD_RS232)) return; /* Disable RX on time of initialization */ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) return; data |= MCS7840_DEV_CONTROLx_RX_DISABLE; if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) return; /* Disable all interrupts */ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0)) return; /* Reset FIFO -- documented */ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, 0)) return; if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, MCS7840_UART_FCR_ENABLE | MCS7840_UART_FCR_FLUSHRHR | MCS7840_UART_FCR_FLUSHTHR | MCS7840_UART_FCR_RTL_1_14)) return; /* Set 8 bit, no parity, 1 stop bit -- documented */ sc->sc_ports[pn].sc_lcr = MCS7840_UART_LCR_DATALEN8 | MCS7840_UART_LCR_STOPB1; if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr)) return; /* * Enable DTR/RTS on modem control, enable modem interrupts -- * documented */ sc->sc_ports[pn].sc_mcr = MCS7840_UART_MCR_IE; if (ucom->sc_tty == NULL || (ucom->sc_tty->t_termios.c_cflag & CNO_RTSDTR) == 0) sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR | MCS7840_UART_MCR_RTS; if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr)) return; /* Clearing Bulkin and Bulkout FIFO */ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data)) return; data |= MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO; if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) return; data &= ~(MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO); if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) return; /* Set speed 9600 */ if (umcs7840_set_baudrate(sc, pn, 9600)) return; /* Finally enable all interrupts -- documented */ /* * Copied from vendor driver, I don't know why we should read LCR * here */ if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, &sc->sc_ports[pn].sc_lcr)) return; if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, MCS7840_UART_IER_RXSTAT | MCS7840_UART_IER_MODEM)) return; /* Enable RX */ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) return; data &= ~MCS7840_DEV_CONTROLx_RX_DISABLE; if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) return; DPRINTF("Port %d has been opened\n", pn); } static void umcs7840_cfg_close(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; uint16_t pn = ucom->sc_portno; uint8_t data; umcs7840_stop_read(ucom); umcs7840_stop_write(ucom); umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, 0); umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0); /* Disable RX */ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) return; data |= MCS7840_DEV_CONTROLx_RX_DISABLE; if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) return; DPRINTF("Port %d has been closed\n", pn); } static void umcs7840_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; if (onoff) sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR; else sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_DTR; umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); DPRINTF("Port %d DTR set to: %s\n", pn, onoff ? "on" : "off"); } static void umcs7840_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; if (onoff) sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_RTS; else sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_RTS; umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); DPRINTF("Port %d RTS set to: %s\n", pn, onoff ? "on" : "off"); } static void umcs7840_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; if (onoff) sc->sc_ports[pn].sc_lcr |= MCS7840_UART_LCR_BREAK; else sc->sc_ports[pn].sc_lcr &= ~MCS7840_UART_LCR_BREAK; umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr); DPRINTF("Port %d BREAK set to: %s\n", pn, onoff ? "on" : "off"); } static void umcs7840_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; uint8_t lcr = sc->sc_ports[pn].sc_lcr; uint8_t mcr = sc->sc_ports[pn].sc_mcr; DPRINTF("Port %d config:\n", pn); if (t->c_cflag & CSTOPB) { DPRINTF(" 2 stop bits\n"); lcr |= MCS7840_UART_LCR_STOPB2; } else { lcr |= MCS7840_UART_LCR_STOPB1; DPRINTF(" 1 stop bit\n"); } lcr &= ~MCS7840_UART_LCR_PARITYMASK; if (t->c_cflag & PARENB) { lcr |= MCS7840_UART_LCR_PARITYON; if (t->c_cflag & PARODD) { lcr = MCS7840_UART_LCR_PARITYODD; DPRINTF(" parity on - odd\n"); } else { lcr = MCS7840_UART_LCR_PARITYEVEN; DPRINTF(" parity on - even\n"); } } else { lcr &= ~MCS7840_UART_LCR_PARITYON; DPRINTF(" parity off\n"); } lcr &= ~MCS7840_UART_LCR_DATALENMASK; switch (t->c_cflag & CSIZE) { case CS5: lcr |= MCS7840_UART_LCR_DATALEN5; DPRINTF(" 5 bit\n"); break; case CS6: lcr |= MCS7840_UART_LCR_DATALEN6; DPRINTF(" 6 bit\n"); break; case CS7: lcr |= MCS7840_UART_LCR_DATALEN7; DPRINTF(" 7 bit\n"); break; case CS8: lcr |= MCS7840_UART_LCR_DATALEN8; DPRINTF(" 8 bit\n"); break; } if (t->c_cflag & CRTSCTS) { mcr |= MCS7840_UART_MCR_CTSRTS; DPRINTF(" CTS/RTS\n"); } else mcr &= ~MCS7840_UART_MCR_CTSRTS; if (t->c_cflag & (CDTR_IFLOW | CDSR_OFLOW)) { mcr |= MCS7840_UART_MCR_DTRDSR; DPRINTF(" DTR/DSR\n"); } else mcr &= ~MCS7840_UART_MCR_DTRDSR; sc->sc_ports[pn].sc_lcr = lcr; umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr); DPRINTF("Port %d LCR=%02x\n", pn, sc->sc_ports[pn].sc_lcr); sc->sc_ports[pn].sc_mcr = mcr; umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); DPRINTF("Port %d MCR=%02x\n", pn, sc->sc_ports[pn].sc_mcr); umcs7840_set_baudrate(sc, pn, t->c_ospeed); } static int umcs7840_pre_param(struct ucom_softc *ucom, struct termios *t) { uint8_t clk; uint16_t divisor; if (umcs7840_calc_baudrate(t->c_ospeed, &divisor, &clk) || !divisor) return (EINVAL); return (0); } static void umcs7840_start_read(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; /* Start interrupt transfer */ usbd_transfer_start(sc->sc_intr_xfer); /* Start read transfer */ usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]); } static void umcs7840_stop_read(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; /* Stop read transfer */ usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]); } static void umcs7840_start_write(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; /* Start interrupt transfer */ usbd_transfer_start(sc->sc_intr_xfer); /* Start write transfer */ usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]); } static void umcs7840_stop_write(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; /* Stop write transfer */ usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]); } static void umcs7840_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct umcs7840_softc *sc = ucom->sc_parent; uint8_t pn = ucom->sc_portno; uint8_t hw_msr = 0; /* local modem status register */ /* * Read status registers. MSR bits need translation from ns16550 to * SER_* values. LSR bits are ns16550 in hardware and ucom. */ umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, lsr); umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &hw_msr); if (hw_msr & MCS7840_UART_MSR_NEGCTS) *msr |= SER_CTS; if (hw_msr & MCS7840_UART_MSR_NEGDCD) *msr |= SER_DCD; if (hw_msr & MCS7840_UART_MSR_NEGRI) *msr |= SER_RI; if (hw_msr & MCS7840_UART_MSR_NEGDSR) *msr |= SER_DSR; DPRINTF("Port %d status: LSR=%02x MSR=%02x\n", ucom->sc_portno, *lsr, *msr); } static void umcs7840_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct umcs7840_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[13]; int actlen; int subunit; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen == 5 || actlen == 13) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, actlen); /* Check status of all ports */ for (subunit = 0; subunit < sc->sc_numports; ++subunit) { uint8_t pn = sc->sc_ucom[subunit].sc_portno; if (buf[pn] & MCS7840_UART_ISR_NOPENDING) continue; DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK)); switch (buf[pn] & MCS7840_UART_ISR_INTMASK) { case MCS7840_UART_ISR_RXERR: case MCS7840_UART_ISR_RXHASDATA: case MCS7840_UART_ISR_RXTIMEOUT: case MCS7840_UART_ISR_MSCHANGE: ucom_status_change(&sc->sc_ucom[subunit]); break; default: /* Do nothing */ break; } } } else device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error) { umcs7840_read_callbackN(xfer, error, 0); } static void umcs7840_read_callback2(struct usb_xfer *xfer, usb_error_t error) { umcs7840_read_callbackN(xfer, error, 1); } static void umcs7840_read_callback3(struct usb_xfer *xfer, usb_error_t error) { umcs7840_read_callbackN(xfer, error, 2); } static void umcs7840_read_callback4(struct usb_xfer *xfer, usb_error_t error) { umcs7840_read_callbackN(xfer, error, 3); } static void umcs7840_read_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit) { struct umcs7840_softc *sc = usbd_xfer_softc(xfer); struct ucom_softc *ucom = &sc->sc_ucom[subunit]; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); DPRINTF("Port %d read, state = %d, data length = %d\n", ucom->sc_portno, USB_GET_STATE(xfer), actlen); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(ucom, pc, 0, actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umcs7840_write_callback1(struct usb_xfer *xfer, usb_error_t error) { umcs7840_write_callbackN(xfer, error, 0); } static void umcs7840_write_callback2(struct usb_xfer *xfer, usb_error_t error) { umcs7840_write_callbackN(xfer, error, 1); } static void umcs7840_write_callback3(struct usb_xfer *xfer, usb_error_t error) { umcs7840_write_callbackN(xfer, error, 2); } static void umcs7840_write_callback4(struct usb_xfer *xfer, usb_error_t error) { umcs7840_write_callbackN(xfer, error, 3); } static void umcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit) { struct umcs7840_softc *sc = usbd_xfer_softc(xfer); struct ucom_softc *ucom = &sc->sc_ucom[subunit]; struct usb_page_cache *pc; uint32_t actlen; DPRINTF("Port %d write, state = %d\n", ucom->sc_portno, USB_GET_STATE(xfer)); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) { DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen); usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umcs7840_poll(struct ucom_softc *ucom) { struct umcs7840_softc *sc = ucom->sc_parent; DPRINTF("Port %d poll\n", ucom->sc_portno); usbd_transfer_poll(sc->sc_ports[ucom->sc_portno].sc_xfer, UMCS7840_N_TRANSFERS); usbd_transfer_poll(&sc->sc_intr_xfer, 1); } static usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t *data) { struct usb_device_request req; usb_error_t err; uint16_t len; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = MCS7840_RDREQ; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, UMCS7840_READ_LENGTH); err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT); if (err == USB_ERR_NORMAL_COMPLETION && len != 1) { device_printf(sc->sc_dev, "Reading register %d failed: invalid length %d\n", reg, len); return (USB_ERR_INVAL); } else if (err) device_printf(sc->sc_dev, "Reading register %d failed: %s\n", reg, usbd_errstr(err)); return (err); } static usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t data) { struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = MCS7840_WRREQ; USETW(req.wValue, data); USETW(req.wIndex, reg); USETW(req.wLength, 0); err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT); if (err) device_printf(sc->sc_dev, "Writing register %d failed: %s\n", reg, usbd_errstr(err)); return (err); } static usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t *data) { struct usb_device_request req; uint16_t wVal; usb_error_t err; uint16_t len; /* portno is port number */ wVal = ((uint16_t)(portno + 1)) << 8; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = MCS7840_RDREQ; USETW(req.wValue, wVal); USETW(req.wIndex, reg); USETW(req.wLength, UMCS7840_READ_LENGTH); err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT); if (err == USB_ERR_NORMAL_COMPLETION && len != 1) { device_printf(sc->sc_dev, "Reading UART%d register %d failed: invalid length %d\n", portno, reg, len); return (USB_ERR_INVAL); } else if (err) device_printf(sc->sc_dev, "Reading UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err)); return (err); } static usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t data) { struct usb_device_request req; usb_error_t err; uint16_t wVal; /* portno is port number */ wVal = ((uint16_t)(portno + 1)) << 8 | data; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = MCS7840_WRREQ; USETW(req.wValue, wVal); USETW(req.wIndex, reg); USETW(req.wLength, 0); err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT); if (err) device_printf(sc->sc_dev, "Writing UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err)); return (err); } static usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *sc, uint8_t portno, uint32_t rate) { usb_error_t err; uint16_t divisor; uint8_t clk; uint8_t data; if (umcs7840_calc_baudrate(rate, &divisor, &clk)) { DPRINTF("Port %d bad speed: %d\n", portno, rate); return (-1); } if (divisor == 0 || (clk & MCS7840_DEV_SPx_CLOCK_MASK) != clk) { DPRINTF("Port %d bad speed calculation: %d\n", portno, rate); return (-1); } DPRINTF("Port %d set speed: %d (%02x / %d)\n", portno, rate, clk, divisor); /* Set clock source for standard BAUD frequences */ err = umcs7840_get_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, &data); if (err) return (err); data &= MCS7840_DEV_SPx_CLOCK_MASK; data |= clk; err = umcs7840_set_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, data); if (err) return (err); /* Set divider */ sc->sc_ports[portno].sc_lcr |= MCS7840_UART_LCR_DIVISORS; err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr); if (err) return (err); err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLL, (uint8_t)(divisor & 0xff)); if (err) return (err); err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLM, (uint8_t)((divisor >> 8) & 0xff)); if (err) return (err); /* Turn off access to DLL/DLM registers of UART */ sc->sc_ports[portno].sc_lcr &= ~MCS7840_UART_LCR_DIVISORS; err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr); if (err) return (err); return (0); } /* Maximum speeds for standard frequences, when PLL is not used */ static const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,}; static const uint8_t umcs7840_baudrate_divisors_len = nitems(umcs7840_baudrate_divisors); static usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *divisor, uint8_t *clk) { uint8_t i = 0; if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1]) return (-1); for (i = 0; i < umcs7840_baudrate_divisors_len - 1 && !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i); if (rate == 0) *divisor = 1; /* XXX */ else *divisor = umcs7840_baudrate_divisors[i + 1] / rate; /* 0x00 .. 0x70 */ *clk = i << MCS7840_DEV_SPx_CLOCK_SHIFT; return (0); } Index: head/sys/dev/usb/serial/umodem.c =================================================================== --- head/sys/dev/usb/serial/umodem.c (revision 357971) +++ head/sys/dev/usb/serial/umodem.c (revision 357972) @@ -1,1041 +1,1042 @@ /* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2003 M. Warner Losh * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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. */ /* * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip */ /* * TODO: * - Add error recovery in various places; the big problem is what * to do in a callback if there is an error. * - Implement a Call Device for modems without multiplexed commands. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include "usb_if.h" #include #define USB_DEBUG_VAR umodem_debug #include #include #include #include #ifdef USB_DEBUG static int umodem_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB umodem"); SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RWTUN, &umodem_debug, 0, "Debug level"); #endif static const STRUCT_USB_DUAL_ID umodem_dual_devs[] = { /* Generic Modem class match */ {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)}, {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(UIPROTO_CDC_NONE)}, }; static const STRUCT_USB_HOST_ID umodem_host_devs[] = { /* Huawei Modem class match */ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x01)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x02)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x10)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x12)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x61)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x62)}, {USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(0xFF)}, /* Kyocera AH-K3001V */ {USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)}, {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)}, {USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)}, }; /* * As speeds for umodem devices increase, these numbers will need to * be increased. They should be good for G3 speeds and below. * * TODO: The TTY buffers should be increased! */ #define UMODEM_BUF_SIZE 1024 enum { UMODEM_BULK_WR, UMODEM_BULK_RD, UMODEM_INTR_WR, UMODEM_INTR_RD, UMODEM_N_TRANSFER, }; #define UMODEM_MODVER 1 /* module version */ struct umodem_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint16_t sc_line; uint8_t sc_lsr; /* local status register */ uint8_t sc_msr; /* modem status register */ uint8_t sc_ctrl_iface_no; uint8_t sc_data_iface_no; uint8_t sc_iface_index[2]; uint8_t sc_cm_over_data; uint8_t sc_cm_cap; /* CM capabilities */ uint8_t sc_acm_cap; /* ACM capabilities */ uint8_t sc_line_coding[32]; /* used in USB device mode */ uint8_t sc_abstract_state[32]; /* used in USB device mode */ }; static device_probe_t umodem_probe; static device_attach_t umodem_attach; static device_detach_t umodem_detach; static usb_handle_request_t umodem_handle_request; static void umodem_free_softc(struct umodem_softc *); static usb_callback_t umodem_intr_read_callback; static usb_callback_t umodem_intr_write_callback; static usb_callback_t umodem_write_callback; static usb_callback_t umodem_read_callback; static void umodem_free(struct ucom_softc *); static void umodem_start_read(struct ucom_softc *); static void umodem_stop_read(struct ucom_softc *); static void umodem_start_write(struct ucom_softc *); static void umodem_stop_write(struct ucom_softc *); static void umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *); static void umodem_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static int umodem_pre_param(struct ucom_softc *, struct termios *); static void umodem_cfg_param(struct ucom_softc *, struct termios *); static int umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); static void umodem_cfg_set_dtr(struct ucom_softc *, uint8_t); static void umodem_cfg_set_rts(struct ucom_softc *, uint8_t); static void umodem_cfg_set_break(struct ucom_softc *, uint8_t); static void *umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t); static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t, uint16_t, uint16_t); static void umodem_poll(struct ucom_softc *ucom); static void umodem_find_data_iface(struct usb_attach_arg *uaa, uint8_t, uint8_t *, uint8_t *); static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = { [UMODEM_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .bufsize = UMODEM_BUF_SIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &umodem_write_callback, .usb_mode = USB_MODE_DUAL, }, [UMODEM_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .bufsize = UMODEM_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &umodem_read_callback, .usb_mode = USB_MODE_DUAL, }, [UMODEM_INTR_WR] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 1, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umodem_intr_write_callback, .usb_mode = USB_MODE_DEVICE, }, [UMODEM_INTR_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umodem_intr_read_callback, .usb_mode = USB_MODE_HOST, }, }; static const struct ucom_callback umodem_callback = { .ucom_cfg_get_status = &umodem_cfg_get_status, .ucom_cfg_set_dtr = &umodem_cfg_set_dtr, .ucom_cfg_set_rts = &umodem_cfg_set_rts, .ucom_cfg_set_break = &umodem_cfg_set_break, .ucom_cfg_param = &umodem_cfg_param, .ucom_pre_param = &umodem_pre_param, .ucom_ioctl = &umodem_ioctl, .ucom_start_read = &umodem_start_read, .ucom_stop_read = &umodem_stop_read, .ucom_start_write = &umodem_start_write, .ucom_stop_write = &umodem_stop_write, .ucom_poll = &umodem_poll, .ucom_free = &umodem_free, }; static device_method_t umodem_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, umodem_handle_request), /* Device interface */ DEVMETHOD(device_probe, umodem_probe), DEVMETHOD(device_attach, umodem_attach), DEVMETHOD(device_detach, umodem_detach), DEVMETHOD_END }; static devclass_t umodem_devclass; static driver_t umodem_driver = { .name = "umodem", .methods = umodem_methods, .size = sizeof(struct umodem_softc), }; DRIVER_MODULE(umodem, uhub, umodem_driver, umodem_devclass, NULL, 0); MODULE_DEPEND(umodem, ucom, 1, 1, 1); MODULE_DEPEND(umodem, usb, 1, 1, 1); MODULE_VERSION(umodem, UMODEM_MODVER); USB_PNP_DUAL_INFO(umodem_dual_devs); USB_PNP_HOST_INFO(umodem_host_devs); static int umodem_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; DPRINTFN(11, "\n"); error = usbd_lookup_id_by_uaa(umodem_host_devs, sizeof(umodem_host_devs), uaa); if (error) { error = usbd_lookup_id_by_uaa(umodem_dual_devs, sizeof(umodem_dual_devs), uaa); if (error) return (error); } return (BUS_PROBE_GENERIC); } static int umodem_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct umodem_softc *sc = device_get_softc(dev); struct usb_cdc_cm_descriptor *cmd; struct usb_cdc_union_descriptor *cud; uint8_t i; int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index[1] = uaa->info.bIfaceIndex; sc->sc_udev = uaa->device; umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap); /* get the data interface number */ cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { cud = usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_UNION, 0xFF); if ((cud == NULL) || (cud->bLength < sizeof(*cud))) { DPRINTF("Missing descriptor. " "Assuming data interface is next.\n"); if (sc->sc_ctrl_iface_no == 0xFF) { goto detach; } else { uint8_t class_match = 0; /* set default interface number */ sc->sc_data_iface_no = 0xFF; /* try to find the data interface backwards */ umodem_find_data_iface(uaa, uaa->info.bIfaceIndex - 1, &sc->sc_data_iface_no, &class_match); /* try to find the data interface forwards */ umodem_find_data_iface(uaa, uaa->info.bIfaceIndex + 1, &sc->sc_data_iface_no, &class_match); /* check if nothing was found */ if (sc->sc_data_iface_no == 0xFF) goto detach; } } else { sc->sc_data_iface_no = cud->bSlaveInterface[0]; } } else { sc->sc_data_iface_no = cmd->bDataInterface; } device_printf(dev, "data interface %d, has %sCM over " "data, has %sbreak\n", sc->sc_data_iface_no, sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); /* get the data interface too */ for (i = 0;; i++) { struct usb_interface *iface; struct usb_interface_descriptor *id; iface = usbd_get_iface(uaa->device, i); if (iface) { id = usbd_get_interface_descriptor(iface); if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { sc->sc_iface_index[0] = i; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); break; } } else { device_printf(dev, "no data interface\n"); goto detach; } } if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) { sc->sc_cm_over_data = 1; } else { if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) { if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) { error = umodem_set_comm_feature (uaa->device, sc->sc_ctrl_iface_no, UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED); /* ignore any errors */ } sc->sc_cm_over_data = 1; } } error = usbd_transfer_setup(uaa->device, sc->sc_iface_index, sc->sc_xfer, umodem_config, UMODEM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "Can't setup transfer\n"); goto detach; } /* clear stall at first run, if USB host mode */ if (uaa->usb_mode == USB_MODE_HOST) { mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]); usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]); mtx_unlock(&sc->sc_mtx); } ucom_set_usb_mode(&sc->sc_super_ucom, uaa->usb_mode); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &umodem_callback, &sc->sc_mtx); if (error) { device_printf(dev, "Can't attach com\n"); goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: umodem_detach(dev); return (ENXIO); } static void umodem_find_data_iface(struct usb_attach_arg *uaa, uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class) { struct usb_interface_descriptor *id; struct usb_interface *iface; iface = usbd_get_iface(uaa->device, iface_index); /* check for end of interfaces */ if (iface == NULL) return; id = usbd_get_interface_descriptor(iface); /* check for non-matching interface class */ if (id->bInterfaceClass != UICLASS_CDC_DATA || id->bInterfaceSubClass != UISUBCLASS_DATA) { /* if we got a class match then return */ if (*p_match_class) return; } else { *p_match_class = 1; } DPRINTFN(11, "Match at index %u\n", iface_index); *p_data_no = id->bInterfaceNumber; } static void umodem_start_read(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; /* start interrupt endpoint, if any */ usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]); } static void umodem_stop_read(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; /* stop interrupt endpoint, if any */ usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]); } static void umodem_start_write(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_WR]); usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]); } static void umodem_stop_write(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_WR]); usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]); } static void umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm) { struct usb_cdc_cm_descriptor *cmd; struct usb_cdc_acm_descriptor *cad; cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { DPRINTF("no CM desc (faking one)\n"); *cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA; } else *cm = cmd->bmCapabilities; cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); if ((cad == NULL) || (cad->bLength < sizeof(*cad))) { DPRINTF("no ACM desc\n"); *acm = 0; } else *acm = cad->bmCapabilities; } static void umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct umodem_softc *sc = ucom->sc_parent; DPRINTF("\n"); /* XXX Note: sc_lsr is always zero */ *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static int umodem_pre_param(struct ucom_softc *ucom, struct termios *t) { return (0); /* we accept anything */ } static void umodem_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct umodem_softc *sc = ucom->sc_parent; struct usb_cdc_line_state ls; struct usb_device_request req; DPRINTF("sc=%p\n", sc); memset(&ls, 0, sizeof(ls)); USETDW(ls.dwDTERate, t->c_ospeed); ls.bCharFormat = (t->c_cflag & CSTOPB) ? UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1; ls.bParityType = (t->c_cflag & PARENB) ? ((t->c_cflag & PARODD) ? UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE; switch (t->c_cflag & CSIZE) { case CS5: ls.bDataBits = 5; break; case CS6: ls.bDataBits = 6; break; case CS7: ls.bDataBits = 7; break; case CS8: ls.bDataBits = 8; break; } DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", UGETDW(ls.dwDTERate), ls.bCharFormat, ls.bParityType, ls.bDataBits); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_LINE_CODING; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, sizeof(ls)); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &ls, 0, 1000); } static int umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td) { struct umodem_softc *sc = ucom->sc_parent; int error = 0; DPRINTF("cmd=0x%08x\n", cmd); switch (cmd) { case USB_GET_CM_OVER_DATA: *(int *)data = sc->sc_cm_over_data; break; case USB_SET_CM_OVER_DATA: if (*(int *)data != sc->sc_cm_over_data) { /* XXX change it */ } break; default: DPRINTF("unknown\n"); error = ENOIOCTL; break; } return (error); } static void umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct umodem_softc *sc = ucom->sc_parent; struct usb_device_request req; DPRINTF("onoff=%d\n", onoff); if (onoff) sc->sc_line |= UCDC_LINE_DTR; else sc->sc_line &= ~UCDC_LINE_DTR; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct umodem_softc *sc = ucom->sc_parent; struct usb_device_request req; DPRINTF("onoff=%d\n", onoff); if (onoff) sc->sc_line |= UCDC_LINE_RTS; else sc->sc_line &= ~UCDC_LINE_RTS; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct umodem_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t temp; DPRINTF("onoff=%d\n", onoff); if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) { temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_BREAK; USETW(req.wValue, temp); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } } static void umodem_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) { int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("Transferred %d bytes\n", actlen); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* start clear stall */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void umodem_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_cdc_notification pkt; struct umodem_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint16_t wLen; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 8) { DPRINTF("received short packet, " "%d bytes\n", actlen); goto tr_setup; } if (actlen > (int)sizeof(pkt)) { DPRINTF("truncating message\n"); actlen = sizeof(pkt); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, actlen); actlen -= 8; wLen = UGETW(pkt.wLength); if (actlen > wLen) { actlen = wLen; } if (pkt.bmRequestType != UCDC_NOTIFICATION) { DPRINTF("unknown message type, " "0x%02x, on notify pipe!\n", pkt.bmRequestType); goto tr_setup; } switch (pkt.bNotification) { case UCDC_N_SERIAL_STATE: /* * Set the serial state in ucom driver based on * the bits from the notify message */ if (actlen < 2) { DPRINTF("invalid notification " "length, %d bytes!\n", actlen); break; } DPRINTF("notify bytes = %02x%02x\n", pkt.data[0], pkt.data[1]); /* Currently, lsr is always zero. */ sc->sc_lsr = 0; sc->sc_msr = 0; if (pkt.data[0] & UCDC_N_SERIAL_RI) { sc->sc_msr |= SER_RI; } if (pkt.data[0] & UCDC_N_SERIAL_DSR) { sc->sc_msr |= SER_DSR; } if (pkt.data[0] & UCDC_N_SERIAL_DCD) { sc->sc_msr |= SER_DCD; } ucom_status_change(&sc->sc_ucom); break; default: DPRINTF("unknown notify message: 0x%02x\n", pkt.bNotification); break; } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umodem_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umodem_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UMODEM_BUF_SIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umodem_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umodem_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d\n", actlen); pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void * umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype) { return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, type, 0xFF, subtype, 0xFF)); } static usb_error_t umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no, uint16_t feature, uint16_t state) { struct usb_device_request req; struct usb_cdc_abstract_state ast; DPRINTF("feature=%d state=%d\n", feature, state); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_COMM_FEATURE; USETW(req.wValue, feature); req.wIndex[0] = iface_no; req.wIndex[1] = 0; USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH); USETW(ast.wState, state); return (usbd_do_request(udev, NULL, &req, &ast)); } static int umodem_detach(device_t dev) { struct umodem_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER); device_claim_softc(dev); umodem_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(umodem); static void umodem_free_softc(struct umodem_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void umodem_free(struct ucom_softc *ucom) { umodem_free_softc(ucom->sc_parent); } static void umodem_poll(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER); } static int umodem_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct umodem_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; DPRINTF("sc=%p\n", sc); if (!is_complete) { if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UCDC_SET_LINE_CODING) && (req->wIndex[0] == sc->sc_ctrl_iface_no) && (req->wIndex[1] == 0x00) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x00)) { if (offset == 0) { *plen = sizeof(sc->sc_line_coding); *pptr = &sc->sc_line_coding; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->wIndex[0] == sc->sc_ctrl_iface_no) && (req->wIndex[1] == 0x00) && (req->bRequest == UCDC_SET_COMM_FEATURE)) { if (offset == 0) { *plen = sizeof(sc->sc_abstract_state); *pptr = &sc->sc_abstract_state; } else { *plen = 0; } return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->wIndex[0] == sc->sc_ctrl_iface_no) && (req->wIndex[1] == 0x00) && (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) { *plen = 0; return (0); } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->wIndex[0] == sc->sc_ctrl_iface_no) && (req->wIndex[1] == 0x00) && (req->bRequest == UCDC_SEND_BREAK)) { *plen = 0; return (0); } } return (ENXIO); /* use builtin handler */ } Index: head/sys/dev/usb/serial/umoscom.c =================================================================== --- head/sys/dev/usb/serial/umoscom.c (revision 357971) +++ head/sys/dev/usb/serial/umoscom.c (revision 357972) @@ -1,734 +1,735 @@ /* $FreeBSD$ */ /* $OpenBSD: umoscom.c,v 1.2 2006/10/26 06:02:43 jsg Exp $ */ /* * Copyright (c) 2006 Jonathan Gray * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR umoscom_debug #include #include #include #ifdef USB_DEBUG static int umoscom_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, umoscom, CTLFLAG_RW, 0, "USB umoscom"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, umoscom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB umoscom"); SYSCTL_INT(_hw_usb_umoscom, OID_AUTO, debug, CTLFLAG_RWTUN, &umoscom_debug, 0, "Debug level"); #endif #define UMOSCOM_BUFSIZE 1024 /* bytes */ #define UMOSCOM_CONFIG_INDEX 0 #define UMOSCOM_IFACE_INDEX 0 /* interrupt packet */ #define UMOSCOM_IIR_RLS 0x06 #define UMOSCOM_IIR_RDA 0x04 #define UMOSCOM_IIR_CTI 0x0c #define UMOSCOM_IIR_THR 0x02 #define UMOSCOM_IIR_MS 0x00 /* registers */ #define UMOSCOM_READ 0x0d #define UMOSCOM_WRITE 0x0e #define UMOSCOM_UART_REG 0x0300 #define UMOSCOM_VEND_REG 0x0000 #define UMOSCOM_TXBUF 0x00 /* Write */ #define UMOSCOM_RXBUF 0x00 /* Read */ #define UMOSCOM_INT 0x01 #define UMOSCOM_FIFO 0x02 /* Write */ #define UMOSCOM_ISR 0x02 /* Read */ #define UMOSCOM_LCR 0x03 #define UMOSCOM_MCR 0x04 #define UMOSCOM_LSR 0x05 #define UMOSCOM_MSR 0x06 #define UMOSCOM_SCRATCH 0x07 #define UMOSCOM_DIV_LO 0x08 #define UMOSCOM_DIV_HI 0x09 #define UMOSCOM_EFR 0x0a #define UMOSCOM_XON1 0x0b #define UMOSCOM_XON2 0x0c #define UMOSCOM_XOFF1 0x0d #define UMOSCOM_XOFF2 0x0e #define UMOSCOM_BAUDLO 0x00 #define UMOSCOM_BAUDHI 0x01 #define UMOSCOM_INT_RXEN 0x01 #define UMOSCOM_INT_TXEN 0x02 #define UMOSCOM_INT_RSEN 0x04 #define UMOSCOM_INT_MDMEM 0x08 #define UMOSCOM_INT_SLEEP 0x10 #define UMOSCOM_INT_XOFF 0x20 #define UMOSCOM_INT_RTS 0x40 #define UMOSCOM_FIFO_EN 0x01 #define UMOSCOM_FIFO_RXCLR 0x02 #define UMOSCOM_FIFO_TXCLR 0x04 #define UMOSCOM_FIFO_DMA_BLK 0x08 #define UMOSCOM_FIFO_TXLVL_MASK 0x30 #define UMOSCOM_FIFO_TXLVL_8 0x00 #define UMOSCOM_FIFO_TXLVL_16 0x10 #define UMOSCOM_FIFO_TXLVL_32 0x20 #define UMOSCOM_FIFO_TXLVL_56 0x30 #define UMOSCOM_FIFO_RXLVL_MASK 0xc0 #define UMOSCOM_FIFO_RXLVL_8 0x00 #define UMOSCOM_FIFO_RXLVL_16 0x40 #define UMOSCOM_FIFO_RXLVL_56 0x80 #define UMOSCOM_FIFO_RXLVL_80 0xc0 #define UMOSCOM_ISR_MDM 0x00 #define UMOSCOM_ISR_NONE 0x01 #define UMOSCOM_ISR_TX 0x02 #define UMOSCOM_ISR_RX 0x04 #define UMOSCOM_ISR_LINE 0x06 #define UMOSCOM_ISR_RXTIMEOUT 0x0c #define UMOSCOM_ISR_RX_XOFF 0x10 #define UMOSCOM_ISR_RTSCTS 0x20 #define UMOSCOM_ISR_FIFOEN 0xc0 #define UMOSCOM_LCR_DBITS(x) ((x) - 5) #define UMOSCOM_LCR_STOP_BITS_1 0x00 #define UMOSCOM_LCR_STOP_BITS_2 0x04 /* 2 if 6-8 bits/char or 1.5 if 5 */ #define UMOSCOM_LCR_PARITY_NONE 0x00 #define UMOSCOM_LCR_PARITY_ODD 0x08 #define UMOSCOM_LCR_PARITY_EVEN 0x18 #define UMOSCOM_LCR_BREAK 0x40 #define UMOSCOM_LCR_DIVLATCH_EN 0x80 #define UMOSCOM_MCR_DTR 0x01 #define UMOSCOM_MCR_RTS 0x02 #define UMOSCOM_MCR_LOOP 0x04 #define UMOSCOM_MCR_INTEN 0x08 #define UMOSCOM_MCR_LOOPBACK 0x10 #define UMOSCOM_MCR_XONANY 0x20 #define UMOSCOM_MCR_IRDA_EN 0x40 #define UMOSCOM_MCR_BAUD_DIV4 0x80 #define UMOSCOM_LSR_RXDATA 0x01 #define UMOSCOM_LSR_RXOVER 0x02 #define UMOSCOM_LSR_RXPAR_ERR 0x04 #define UMOSCOM_LSR_RXFRM_ERR 0x08 #define UMOSCOM_LSR_RXBREAK 0x10 #define UMOSCOM_LSR_TXEMPTY 0x20 #define UMOSCOM_LSR_TXALLEMPTY 0x40 #define UMOSCOM_LSR_TXFIFO_ERR 0x80 #define UMOSCOM_MSR_CTS_CHG 0x01 #define UMOSCOM_MSR_DSR_CHG 0x02 #define UMOSCOM_MSR_RI_CHG 0x04 #define UMOSCOM_MSR_CD_CHG 0x08 #define UMOSCOM_MSR_CTS 0x10 #define UMOSCOM_MSR_RTS 0x20 #define UMOSCOM_MSR_RI 0x40 #define UMOSCOM_MSR_CD 0x80 #define UMOSCOM_BAUD_REF 115200 enum { UMOSCOM_BULK_DT_WR, UMOSCOM_BULK_DT_RD, UMOSCOM_INTR_DT_RD, UMOSCOM_N_TRANSFER, }; struct umoscom_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UMOSCOM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint8_t sc_mcr; uint8_t sc_lcr; }; /* prototypes */ static device_probe_t umoscom_probe; static device_attach_t umoscom_attach; static device_detach_t umoscom_detach; static void umoscom_free_softc(struct umoscom_softc *); static usb_callback_t umoscom_write_callback; static usb_callback_t umoscom_read_callback; static usb_callback_t umoscom_intr_callback; static void umoscom_free(struct ucom_softc *); static void umoscom_cfg_open(struct ucom_softc *); static void umoscom_cfg_close(struct ucom_softc *); static void umoscom_cfg_set_break(struct ucom_softc *, uint8_t); static void umoscom_cfg_set_dtr(struct ucom_softc *, uint8_t); static void umoscom_cfg_set_rts(struct ucom_softc *, uint8_t); static int umoscom_pre_param(struct ucom_softc *, struct termios *); static void umoscom_cfg_param(struct ucom_softc *, struct termios *); static void umoscom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void umoscom_cfg_write(struct umoscom_softc *, uint16_t, uint16_t); static uint8_t umoscom_cfg_read(struct umoscom_softc *, uint16_t); static void umoscom_start_read(struct ucom_softc *); static void umoscom_stop_read(struct ucom_softc *); static void umoscom_start_write(struct ucom_softc *); static void umoscom_stop_write(struct ucom_softc *); static void umoscom_poll(struct ucom_softc *ucom); static const struct usb_config umoscom_config_data[UMOSCOM_N_TRANSFER] = { [UMOSCOM_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMOSCOM_BUFSIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &umoscom_write_callback, }, [UMOSCOM_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UMOSCOM_BUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &umoscom_read_callback, }, [UMOSCOM_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umoscom_intr_callback, }, }; static const struct ucom_callback umoscom_callback = { /* configuration callbacks */ .ucom_cfg_get_status = &umoscom_cfg_get_status, .ucom_cfg_set_dtr = &umoscom_cfg_set_dtr, .ucom_cfg_set_rts = &umoscom_cfg_set_rts, .ucom_cfg_set_break = &umoscom_cfg_set_break, .ucom_cfg_param = &umoscom_cfg_param, .ucom_cfg_open = &umoscom_cfg_open, .ucom_cfg_close = &umoscom_cfg_close, /* other callbacks */ .ucom_pre_param = &umoscom_pre_param, .ucom_start_read = &umoscom_start_read, .ucom_stop_read = &umoscom_stop_read, .ucom_start_write = &umoscom_start_write, .ucom_stop_write = &umoscom_stop_write, .ucom_poll = &umoscom_poll, .ucom_free = &umoscom_free, }; static device_method_t umoscom_methods[] = { DEVMETHOD(device_probe, umoscom_probe), DEVMETHOD(device_attach, umoscom_attach), DEVMETHOD(device_detach, umoscom_detach), DEVMETHOD_END }; static devclass_t umoscom_devclass; static driver_t umoscom_driver = { .name = "umoscom", .methods = umoscom_methods, .size = sizeof(struct umoscom_softc), }; static const STRUCT_USB_HOST_ID umoscom_devs[] = { {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7703, 0)} }; DRIVER_MODULE(umoscom, uhub, umoscom_driver, umoscom_devclass, NULL, 0); MODULE_DEPEND(umoscom, ucom, 1, 1, 1); MODULE_DEPEND(umoscom, usb, 1, 1, 1); MODULE_VERSION(umoscom, 1); USB_PNP_HOST_INFO(umoscom_devs); static int umoscom_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UMOSCOM_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bIfaceIndex != UMOSCOM_IFACE_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(umoscom_devs, sizeof(umoscom_devs), uaa)); } static int umoscom_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct umoscom_softc *sc = device_get_softc(dev); int error; uint8_t iface_index; sc->sc_udev = uaa->device; sc->sc_mcr = 0x08; /* enable interrupts */ /* XXX the device doesn't provide any ID string, so set a static one */ device_set_desc(dev, "MOSCHIP USB Serial Port Adapter"); device_printf(dev, "\n"); mtx_init(&sc->sc_mtx, "umoscom", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); iface_index = UMOSCOM_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, umoscom_config_data, UMOSCOM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &umoscom_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: device_printf(dev, "attach error: %s\n", usbd_errstr(error)); umoscom_detach(dev); return (ENXIO); } static int umoscom_detach(device_t dev) { struct umoscom_softc *sc = device_get_softc(dev); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UMOSCOM_N_TRANSFER); device_claim_softc(dev); umoscom_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(umoscom); static void umoscom_free_softc(struct umoscom_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void umoscom_free(struct ucom_softc *ucom) { umoscom_free_softc(ucom->sc_parent); } static void umoscom_cfg_open(struct ucom_softc *ucom) { struct umoscom_softc *sc = ucom->sc_parent; DPRINTF("\n"); /* Purge FIFOs or odd things happen */ umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG); /* Enable FIFO */ umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN | UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR | UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK | UMOSCOM_UART_REG); /* Enable Interrupt Registers */ umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG); /* Magic */ umoscom_cfg_write(sc, 0x01, 0x08); /* Magic */ umoscom_cfg_write(sc, 0x00, 0x02); } static void umoscom_cfg_close(struct ucom_softc *ucom) { return; } static void umoscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct umoscom_softc *sc = ucom->sc_parent; uint16_t val; val = sc->sc_lcr; if (onoff) val |= UMOSCOM_LCR_BREAK; umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG); } static void umoscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct umoscom_softc *sc = ucom->sc_parent; if (onoff) sc->sc_mcr |= UMOSCOM_MCR_DTR; else sc->sc_mcr &= ~UMOSCOM_MCR_DTR; umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); } static void umoscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct umoscom_softc *sc = ucom->sc_parent; if (onoff) sc->sc_mcr |= UMOSCOM_MCR_RTS; else sc->sc_mcr &= ~UMOSCOM_MCR_RTS; umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); } static int umoscom_pre_param(struct ucom_softc *ucom, struct termios *t) { if ((t->c_ospeed <= 1) || (t->c_ospeed > 115200)) return (EINVAL); return (0); } static void umoscom_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct umoscom_softc *sc = ucom->sc_parent; uint16_t data; DPRINTF("speed=%d\n", t->c_ospeed); data = ((uint32_t)UMOSCOM_BAUD_REF) / ((uint32_t)t->c_ospeed); if (data == 0) { DPRINTF("invalid baud rate!\n"); return; } umoscom_cfg_write(sc, UMOSCOM_LCR, UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG); umoscom_cfg_write(sc, UMOSCOM_BAUDLO, (data & 0xFF) | UMOSCOM_UART_REG); umoscom_cfg_write(sc, UMOSCOM_BAUDHI, ((data >> 8) & 0xFF) | UMOSCOM_UART_REG); if (t->c_cflag & CSTOPB) data = UMOSCOM_LCR_STOP_BITS_2; else data = UMOSCOM_LCR_STOP_BITS_1; if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) data |= UMOSCOM_LCR_PARITY_ODD; else data |= UMOSCOM_LCR_PARITY_EVEN; } else data |= UMOSCOM_LCR_PARITY_NONE; switch (t->c_cflag & CSIZE) { case CS5: data |= UMOSCOM_LCR_DBITS(5); break; case CS6: data |= UMOSCOM_LCR_DBITS(6); break; case CS7: data |= UMOSCOM_LCR_DBITS(7); break; case CS8: data |= UMOSCOM_LCR_DBITS(8); break; } sc->sc_lcr = data; umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG); } static void umoscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *p_lsr, uint8_t *p_msr) { struct umoscom_softc *sc = ucom->sc_parent; uint8_t msr; DPRINTFN(5, "\n"); /* * Read status registers. MSR bits need translation from ns16550 to * SER_* values. LSR bits are ns16550 in hardware and ucom. */ *p_lsr = umoscom_cfg_read(sc, UMOSCOM_LSR); msr = umoscom_cfg_read(sc, UMOSCOM_MSR); /* translate bits */ if (msr & UMOSCOM_MSR_CTS) *p_msr |= SER_CTS; if (msr & UMOSCOM_MSR_CD) *p_msr |= SER_DCD; if (msr & UMOSCOM_MSR_RI) *p_msr |= SER_RI; if (msr & UMOSCOM_MSR_RTS) *p_msr |= SER_DSR; } static void umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UMOSCOM_WRITE; USETW(req.wValue, val); USETW(req.wIndex, reg); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static uint8_t umoscom_cfg_read(struct umoscom_softc *sc, uint16_t reg) { struct usb_device_request req; uint8_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UMOSCOM_READ; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 1); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &val, 0, 1000); DPRINTF("reg=0x%04x, val=0x%02x\n", reg, val); return (val); } static void umoscom_start_read(struct ucom_softc *ucom) { struct umoscom_softc *sc = ucom->sc_parent; #if 0 /* start interrupt endpoint */ usbd_transfer_start(sc->sc_xfer[UMOSCOM_INTR_DT_RD]); #endif /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); } static void umoscom_stop_read(struct ucom_softc *ucom) { struct umoscom_softc *sc = ucom->sc_parent; /* stop interrupt transfer */ usbd_transfer_stop(sc->sc_xfer[UMOSCOM_INTR_DT_RD]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); } static void umoscom_start_write(struct ucom_softc *ucom) { struct umoscom_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); } static void umoscom_stop_write(struct ucom_softc *ucom) { struct umoscom_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); } static void umoscom_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umoscom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: DPRINTF("\n"); pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UMOSCOM_BUFSIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { DPRINTFN(0, "transfer failed\n"); /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umoscom_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umoscom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("got %d bytes\n", actlen); pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: DPRINTF("\n"); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { DPRINTFN(0, "transfer failed\n"); /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umoscom_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct umoscom_softc *sc = usbd_xfer_softc(xfer); int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 2) { DPRINTF("too short message\n"); goto tr_setup; } ucom_status_change(&sc->sc_ucom); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { DPRINTFN(0, "transfer failed\n"); /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void umoscom_poll(struct ucom_softc *ucom) { struct umoscom_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UMOSCOM_N_TRANSFER); } Index: head/sys/dev/usb/serial/uplcom.c =================================================================== --- head/sys/dev/usb/serial/uplcom.c (revision 357971) +++ head/sys/dev/usb/serial/uplcom.c (revision 357972) @@ -1,1082 +1,1083 @@ /* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Ichiro FUKUHARA (ichiro@ichiro.org). * * 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. * * 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. */ /* * This driver supports several USB-to-RS232 serial adapters driven by * Prolific PL-2303, PL-2303X and probably PL-2303HX USB-to-RS232 * bridge chip. The adapters are sold under many different brand * names. * * Datasheets are available at Prolific www site at * http://www.prolific.com.tw. The datasheets don't contain full * programming information for the chip. * * PL-2303HX is probably programmed the same as PL-2303X. * * There are several differences between PL-2303 and PL-2303(H)X. * PL-2303(H)X can do higher bitrate in bulk mode, has _probably_ * different command for controlling CRTSCTS and needs special * sequence of commands for initialization which aren't also * documented in the datasheet. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uplcom_debug #include #include #include #ifdef USB_DEBUG static int uplcom_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW, 0, "USB uplcom"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uplcom"); SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RWTUN, &uplcom_debug, 0, "Debug level"); #endif #define UPLCOM_MODVER 1 /* module version */ #define UPLCOM_CONFIG_INDEX 0 #define UPLCOM_IFACE_INDEX 0 #define UPLCOM_SECOND_IFACE_INDEX 1 #ifndef UPLCOM_INTR_INTERVAL #define UPLCOM_INTR_INTERVAL 0 /* default */ #endif #define UPLCOM_BULK_BUF_SIZE 1024 /* bytes */ #define UPLCOM_SET_REQUEST 0x01 #define UPLCOM_SET_CRTSCTS 0x41 #define UPLCOM_SET_CRTSCTS_PL2303X 0x61 #define RSAQ_STATUS_CTS 0x80 #define RSAQ_STATUS_OVERRUN_ERROR 0x40 #define RSAQ_STATUS_PARITY_ERROR 0x20 #define RSAQ_STATUS_FRAME_ERROR 0x10 #define RSAQ_STATUS_RING 0x08 #define RSAQ_STATUS_BREAK_ERROR 0x04 #define RSAQ_STATUS_DSR 0x02 #define RSAQ_STATUS_DCD 0x01 #define TYPE_PL2303 0 #define TYPE_PL2303HX 1 #define TYPE_PL2303HXD 2 #define UPLCOM_STATE_INDEX 8 enum { UPLCOM_BULK_DT_WR, UPLCOM_BULK_DT_RD, UPLCOM_INTR_DT_RD, UPLCOM_N_TRANSFER, }; struct uplcom_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UPLCOM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint16_t sc_line; uint8_t sc_lsr; /* local status register */ uint8_t sc_msr; /* uplcom status register */ uint8_t sc_chiptype; /* type of chip */ uint8_t sc_ctrl_iface_no; uint8_t sc_data_iface_no; uint8_t sc_iface_index[2]; }; /* prototypes */ static usb_error_t uplcom_reset(struct uplcom_softc *, struct usb_device *); static usb_error_t uplcom_pl2303_do(struct usb_device *, uint8_t, uint8_t, uint16_t, uint16_t, uint16_t); static int uplcom_pl2303_init(struct usb_device *, uint8_t); static void uplcom_free(struct ucom_softc *); static void uplcom_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uplcom_cfg_set_rts(struct ucom_softc *, uint8_t); static void uplcom_cfg_set_break(struct ucom_softc *, uint8_t); static int uplcom_pre_param(struct ucom_softc *, struct termios *); static void uplcom_cfg_param(struct ucom_softc *, struct termios *); static void uplcom_start_read(struct ucom_softc *); static void uplcom_stop_read(struct ucom_softc *); static void uplcom_start_write(struct ucom_softc *); static void uplcom_stop_write(struct ucom_softc *); static void uplcom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void uplcom_poll(struct ucom_softc *ucom); static device_probe_t uplcom_probe; static device_attach_t uplcom_attach; static device_detach_t uplcom_detach; static void uplcom_free_softc(struct uplcom_softc *); static usb_callback_t uplcom_intr_callback; static usb_callback_t uplcom_write_callback; static usb_callback_t uplcom_read_callback; static const struct usb_config uplcom_config_data[UPLCOM_N_TRANSFER] = { [UPLCOM_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UPLCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &uplcom_write_callback, .if_index = 0, }, [UPLCOM_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UPLCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uplcom_read_callback, .if_index = 0, }, [UPLCOM_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &uplcom_intr_callback, .if_index = 1, }, }; static struct ucom_callback uplcom_callback = { .ucom_cfg_get_status = &uplcom_cfg_get_status, .ucom_cfg_set_dtr = &uplcom_cfg_set_dtr, .ucom_cfg_set_rts = &uplcom_cfg_set_rts, .ucom_cfg_set_break = &uplcom_cfg_set_break, .ucom_cfg_param = &uplcom_cfg_param, .ucom_pre_param = &uplcom_pre_param, .ucom_start_read = &uplcom_start_read, .ucom_stop_read = &uplcom_stop_read, .ucom_start_write = &uplcom_start_write, .ucom_stop_write = &uplcom_stop_write, .ucom_poll = &uplcom_poll, .ucom_free = &uplcom_free, }; #define UPLCOM_DEV(v,p) \ { USB_VENDOR(USB_VENDOR_##v), USB_PRODUCT(USB_PRODUCT_##v##_##p) } static const STRUCT_USB_HOST_ID uplcom_devs[] = { UPLCOM_DEV(ACERP, S81), /* BenQ S81 phone */ UPLCOM_DEV(ADLINK, ND6530), /* ADLINK ND-6530 USB-Serial */ UPLCOM_DEV(ALCATEL, OT535), /* Alcatel One Touch 535/735 */ UPLCOM_DEV(ALCOR, AU9720), /* Alcor AU9720 USB 2.0-RS232 */ UPLCOM_DEV(ANCHOR, SERIAL), /* Anchor Serial adapter */ UPLCOM_DEV(ATEN, UC232A), /* PLANEX USB-RS232 URS-03 */ UPLCOM_DEV(BELKIN, F5U257), /* Belkin F5U257 USB to Serial */ UPLCOM_DEV(COREGA, CGUSBRS232R), /* Corega CG-USBRS232R */ UPLCOM_DEV(EPSON, CRESSI_EDY), /* Cressi Edy diving computer */ UPLCOM_DEV(EPSON, N2ITION3), /* Zeagle N2iTion3 diving computer */ UPLCOM_DEV(ELECOM, UCSGT), /* ELECOM UC-SGT Serial Adapter */ UPLCOM_DEV(ELECOM, UCSGT0), /* ELECOM UC-SGT Serial Adapter */ UPLCOM_DEV(HAL, IMR001), /* HAL Corporation Crossam2+USB */ UPLCOM_DEV(HP, LD220), /* HP LD220 POS Display */ UPLCOM_DEV(IODATA, USBRSAQ), /* I/O DATA USB-RSAQ */ UPLCOM_DEV(IODATA, USBRSAQ5), /* I/O DATA USB-RSAQ5 */ UPLCOM_DEV(ITEGNO, WM1080A), /* iTegno WM1080A GSM/GFPRS modem */ UPLCOM_DEV(ITEGNO, WM2080A), /* iTegno WM2080A CDMA modem */ UPLCOM_DEV(LEADTEK, 9531), /* Leadtek 9531 GPS */ UPLCOM_DEV(MICROSOFT, 700WX), /* Microsoft Palm 700WX */ UPLCOM_DEV(MOBILEACTION, MA620), /* Mobile Action MA-620 Infrared Adapter */ UPLCOM_DEV(NETINDEX, WS002IN), /* Willcom W-S002IN */ UPLCOM_DEV(NOKIA2, CA42), /* Nokia CA-42 cable */ UPLCOM_DEV(OTI, DKU5), /* OTI DKU-5 cable */ UPLCOM_DEV(PANASONIC, TYTP50P6S), /* Panasonic TY-TP50P6-S flat screen */ UPLCOM_DEV(PLX, CA42), /* PLX CA-42 clone cable */ UPLCOM_DEV(PROLIFIC, ALLTRONIX_GPRS), /* Alltronix ACM003U00 modem */ UPLCOM_DEV(PROLIFIC, ALDIGA_AL11U), /* AlDiga AL-11U modem */ UPLCOM_DEV(PROLIFIC, DCU11), /* DCU-11 Phone Cable */ UPLCOM_DEV(PROLIFIC, HCR331), /* HCR331 Card Reader */ UPLCOM_DEV(PROLIFIC, MICROMAX_610U), /* Micromax 610U modem */ UPLCOM_DEV(PROLIFIC, MOTOROLA), /* Motorola cable */ UPLCOM_DEV(PROLIFIC, PHAROS), /* Prolific Pharos */ UPLCOM_DEV(PROLIFIC, PL2303), /* Generic adapter */ UPLCOM_DEV(PROLIFIC, RSAQ2), /* I/O DATA USB-RSAQ2 */ UPLCOM_DEV(PROLIFIC, RSAQ3), /* I/O DATA USB-RSAQ3 */ UPLCOM_DEV(PROLIFIC, UIC_MSR206), /* UIC MSR206 Card Reader */ UPLCOM_DEV(PROLIFIC2, PL2303), /* Prolific adapter */ UPLCOM_DEV(RADIOSHACK, USBCABLE), /* Radio Shack USB Adapter */ UPLCOM_DEV(RATOC, REXUSB60), /* RATOC REX-USB60 */ UPLCOM_DEV(SAGEM, USBSERIAL), /* Sagem USB-Serial Controller */ UPLCOM_DEV(SAMSUNG, I330), /* Samsung I330 phone cradle */ UPLCOM_DEV(SANWA, KB_USB2), /* Sanwa KB-USB2 Multimeter cable */ UPLCOM_DEV(SIEMENS3, EF81), /* Siemens EF81 */ UPLCOM_DEV(SIEMENS3, SX1), /* Siemens SX1 */ UPLCOM_DEV(SIEMENS3, X65), /* Siemens X65 */ UPLCOM_DEV(SIEMENS3, X75), /* Siemens X75 */ UPLCOM_DEV(SITECOM, SERIAL), /* Sitecom USB to Serial */ UPLCOM_DEV(SMART, PL2303), /* SMART Technologies USB to Serial */ UPLCOM_DEV(SONY, QN3), /* Sony QN3 phone cable */ UPLCOM_DEV(SONYERICSSON, DATAPILOT), /* Sony Ericsson Datapilot */ UPLCOM_DEV(SONYERICSSON, DCU10), /* Sony Ericsson DCU-10 Cable */ UPLCOM_DEV(SOURCENEXT, KEIKAI8), /* SOURCENEXT KeikaiDenwa 8 */ UPLCOM_DEV(SOURCENEXT, KEIKAI8_CHG), /* SOURCENEXT KeikaiDenwa 8 with charger */ UPLCOM_DEV(SPEEDDRAGON, MS3303H), /* Speed Dragon USB-Serial */ UPLCOM_DEV(SYNTECH, CPT8001C), /* Syntech CPT-8001C Barcode scanner */ UPLCOM_DEV(TDK, UHA6400), /* TDK USB-PHS Adapter UHA6400 */ UPLCOM_DEV(TDK, UPA9664), /* TDK USB-PHS Adapter UPA9664 */ UPLCOM_DEV(TRIPPLITE, U209), /* Tripp-Lite U209-000-R USB to Serial */ UPLCOM_DEV(YCCABLE, PL2303), /* YC Cable USB-Serial */ }; #undef UPLCOM_DEV static device_method_t uplcom_methods[] = { DEVMETHOD(device_probe, uplcom_probe), DEVMETHOD(device_attach, uplcom_attach), DEVMETHOD(device_detach, uplcom_detach), DEVMETHOD_END }; static devclass_t uplcom_devclass; static driver_t uplcom_driver = { .name = "uplcom", .methods = uplcom_methods, .size = sizeof(struct uplcom_softc), }; DRIVER_MODULE(uplcom, uhub, uplcom_driver, uplcom_devclass, NULL, 0); MODULE_DEPEND(uplcom, ucom, 1, 1, 1); MODULE_DEPEND(uplcom, usb, 1, 1, 1); MODULE_VERSION(uplcom, UPLCOM_MODVER); USB_PNP_HOST_INFO(uplcom_devs); static int uplcom_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UPLCOM_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bIfaceIndex != UPLCOM_IFACE_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(uplcom_devs, sizeof(uplcom_devs), uaa)); } static int uplcom_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uplcom_softc *sc = device_get_softc(dev); struct usb_interface *iface; struct usb_interface_descriptor *id; struct usb_device_descriptor *dd; int error; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uplcom", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); DPRINTF("sc = %p\n", sc); sc->sc_udev = uaa->device; dd = usbd_get_device_descriptor(sc->sc_udev); switch (UGETW(dd->bcdDevice)) { case 0x0300: sc->sc_chiptype = TYPE_PL2303HX; /* or TA, that is HX with external crystal */ break; case 0x0400: sc->sc_chiptype = TYPE_PL2303HXD; /* or EA, that is HXD with ESD protection */ /* or RA, that has internal voltage level converter that works only up to 1Mbaud (!) */ break; case 0x0500: sc->sc_chiptype = TYPE_PL2303HXD; /* in fact it's TB, that is HXD with external crystal */ break; default: /* NOTE: I have no info about the bcdDevice for the base PL2303 (up to 1.2Mbaud, only fixed rates) and for PL2303SA (8-pin chip, up to 115200 baud */ /* Determine the chip type. This algorithm is taken from Linux. */ if (dd->bDeviceClass == 0x02) sc->sc_chiptype = TYPE_PL2303; else if (dd->bMaxPacketSize == 0x40) sc->sc_chiptype = TYPE_PL2303HX; else sc->sc_chiptype = TYPE_PL2303; break; } switch (sc->sc_chiptype) { case TYPE_PL2303: DPRINTF("chiptype: 2303\n"); break; case TYPE_PL2303HX: DPRINTF("chiptype: 2303HX/TA\n"); break; case TYPE_PL2303HXD: DPRINTF("chiptype: 2303HXD/TB/RA/EA\n"); break; default: DPRINTF("chiptype: unknown %d\n", sc->sc_chiptype); break; } /* * USB-RSAQ1 has two interface * * USB-RSAQ1 | USB-RSAQ2 * -----------------+----------------- * Interface 0 |Interface 0 * Interrupt(0x81) | Interrupt(0x81) * -----------------+ BulkIN(0x02) * Interface 1 | BulkOUT(0x83) * BulkIN(0x02) | * BulkOUT(0x83) | */ sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index[1] = UPLCOM_IFACE_INDEX; iface = usbd_get_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX); if (iface) { id = usbd_get_interface_descriptor(iface); if (id == NULL) { device_printf(dev, "no interface descriptor (2)\n"); goto detach; } sc->sc_data_iface_no = id->bInterfaceNumber; sc->sc_iface_index[0] = UPLCOM_SECOND_IFACE_INDEX; usbd_set_parent_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX, uaa->info.bIfaceIndex); } else { sc->sc_data_iface_no = sc->sc_ctrl_iface_no; sc->sc_iface_index[0] = UPLCOM_IFACE_INDEX; } error = usbd_transfer_setup(uaa->device, sc->sc_iface_index, sc->sc_xfer, uplcom_config_data, UPLCOM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("one or more missing USB endpoints, " "error=%s\n", usbd_errstr(error)); goto detach; } error = uplcom_reset(sc, uaa->device); if (error) { device_printf(dev, "reset failed, error=%s\n", usbd_errstr(error)); goto detach; } if (sc->sc_chiptype == TYPE_PL2303) { /* HX variants seem to lock up after a clear stall request. */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); } else { /* reset upstream data pipes */ if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 8, 0, 0) || uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 9, 0, 0)) { goto detach; } } error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uplcom_callback, &sc->sc_mtx); if (error) { goto detach; } /* * do the initialization during attach so that the system does not * sleep during open: */ if (uplcom_pl2303_init(uaa->device, sc->sc_chiptype)) { device_printf(dev, "init failed\n"); goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: uplcom_detach(dev); return (ENXIO); } static int uplcom_detach(device_t dev) { struct uplcom_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UPLCOM_N_TRANSFER); device_claim_softc(dev); uplcom_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uplcom); static void uplcom_free_softc(struct uplcom_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uplcom_free(struct ucom_softc *ucom) { uplcom_free_softc(ucom->sc_parent); } static usb_error_t uplcom_reset(struct uplcom_softc *sc, struct usb_device *udev) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UPLCOM_SET_REQUEST; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_data_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); return (usbd_do_request(udev, NULL, &req, NULL)); } static usb_error_t uplcom_pl2303_do(struct usb_device *udev, uint8_t req_type, uint8_t request, uint16_t value, uint16_t index, uint16_t length) { struct usb_device_request req; usb_error_t err; uint8_t buf[4]; req.bmRequestType = req_type; req.bRequest = request; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, length); err = usbd_do_request(udev, NULL, &req, buf); if (err) { DPRINTF("error=%s\n", usbd_errstr(err)); return (1); } return (0); } static int uplcom_pl2303_init(struct usb_device *udev, uint8_t chiptype) { int err; if (uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0) || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0) || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0) || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0)) return (EIO); if (chiptype != TYPE_PL2303) err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0); else err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x24, 0); if (err) return (EIO); return (0); } static void uplcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uplcom_softc *sc = ucom->sc_parent; struct usb_device_request req; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line |= UCDC_LINE_DTR; else sc->sc_line &= ~UCDC_LINE_DTR; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_data_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uplcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uplcom_softc *sc = ucom->sc_parent; struct usb_device_request req; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line |= UCDC_LINE_RTS; else sc->sc_line &= ~UCDC_LINE_RTS; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_data_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uplcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uplcom_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t temp; DPRINTF("onoff = %d\n", onoff); temp = (onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_BREAK; USETW(req.wValue, temp); req.wIndex[0] = sc->sc_data_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } /* * NOTE: These baud rates are officially supported, they can be written * directly into dwDTERate register. * * Free baudrate setting is not supported by the base PL2303, and on * other models it requires writing a divisor value to dwDTERate instead * of the raw baudrate. The formula for divisor calculation is not published * by the vendor, so it is speculative, though the official product homepage * refers to the Linux module source as a reference implementation. */ static const uint32_t uplcom_rates[] = { /* * Basic 'standard' speed rates, supported by all models * NOTE: 900 and 56000 actually works as well */ 75, 150, 300, 600, 900, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, 28800, 38400, 56000, 57600, 115200, /* * Advanced speed rates up to 6Mbs, supported by HX/TA and HXD/TB/EA/RA * NOTE: regardless of the spec, 256000 does not work */ 128000, 134400, 161280, 201600, 230400, 268800, 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000, /* * Advanced speed rates up to 12, supported by HXD/TB/EA/RA */ 12000000 }; #define N_UPLCOM_RATES nitems(uplcom_rates) static int uplcom_baud_supported(unsigned int speed) { int i; for (i = 0; i < N_UPLCOM_RATES; i++) { if (uplcom_rates[i] == speed) return 1; } return 0; } static int uplcom_pre_param(struct ucom_softc *ucom, struct termios *t) { struct uplcom_softc *sc = ucom->sc_parent; DPRINTF("\n"); /** * Check requested baud rate. * * The PL2303 can only set specific baud rates, up to 1228800 baud. * The PL2303HX can set any baud rate up to 6Mb. * The PL2303HX rev. D can set any baud rate up to 12Mb. * */ /* accept raw divisor data, if someone wants to do the math in user domain */ if (t->c_ospeed & 0x80000000) return 0; switch (sc->sc_chiptype) { case TYPE_PL2303HXD: if (t->c_ospeed <= 12000000) return (0); break; case TYPE_PL2303HX: if (t->c_ospeed <= 6000000) return (0); break; default: if (uplcom_baud_supported(t->c_ospeed)) return (0); break; } DPRINTF("uplcom_param: bad baud rate (%d)\n", t->c_ospeed); return (EIO); } static unsigned int uplcom_encode_baud_rate_divisor(uint8_t *buf, unsigned int baud) { unsigned int baseline, mantissa, exponent; /* Determine the baud rate divisor. This algorithm is taken from Linux. */ /* * Apparently the formula is: * baudrate = baseline / (mantissa * 4^exponent) * where * mantissa = buf[8:0] * exponent = buf[11:9] */ if (baud == 0) baud = 1; baseline = 383385600; mantissa = baseline / baud; if (mantissa == 0) mantissa = 1; exponent = 0; while (mantissa >= 512) { if (exponent < 7) { mantissa >>= 2; /* divide by 4 */ exponent++; } else { /* Exponent is maxed. Trim mantissa and leave. This gives approx. 45.8 baud */ mantissa = 511; break; } } buf[3] = 0x80; buf[2] = 0; buf[1] = exponent << 1 | mantissa >> 8; buf[0] = mantissa & 0xff; /* Calculate and return the exact baud rate. */ baud = (baseline / mantissa) >> (exponent << 1); DPRINTF("real baud rate will be %u\n", baud); return baud; } static void uplcom_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uplcom_softc *sc = ucom->sc_parent; struct usb_cdc_line_state ls; struct usb_device_request req; DPRINTF("sc = %p\n", sc); memset(&ls, 0, sizeof(ls)); /* * NOTE: If unsupported baud rates are set directly, the PL2303* uses 9600 baud. */ if ((t->c_ospeed & 0x80000000) || uplcom_baud_supported(t->c_ospeed)) USETDW(ls.dwDTERate, t->c_ospeed); else t->c_ospeed = uplcom_encode_baud_rate_divisor((uint8_t*)&ls.dwDTERate, t->c_ospeed); if (t->c_cflag & CSTOPB) { if ((t->c_cflag & CSIZE) == CS5) { /* * NOTE: Comply with "real" UARTs / RS232: * use 1.5 instead of 2 stop bits with 5 data bits */ ls.bCharFormat = UCDC_STOP_BIT_1_5; } else { ls.bCharFormat = UCDC_STOP_BIT_2; } } else { ls.bCharFormat = UCDC_STOP_BIT_1; } if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) { ls.bParityType = UCDC_PARITY_ODD; } else { ls.bParityType = UCDC_PARITY_EVEN; } } else { ls.bParityType = UCDC_PARITY_NONE; } switch (t->c_cflag & CSIZE) { case CS5: ls.bDataBits = 5; break; case CS6: ls.bDataBits = 6; break; case CS7: ls.bDataBits = 7; break; case CS8: ls.bDataBits = 8; break; } DPRINTF("rate=0x%08x fmt=%d parity=%d bits=%d\n", UGETDW(ls.dwDTERate), ls.bCharFormat, ls.bParityType, ls.bDataBits); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_LINE_CODING; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_data_iface_no; req.wIndex[1] = 0; USETW(req.wLength, UCDC_LINE_STATE_LENGTH); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &ls, 0, 1000); if (t->c_cflag & CRTSCTS) { DPRINTF("crtscts = on\n"); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UPLCOM_SET_REQUEST; USETW(req.wValue, 0); if (sc->sc_chiptype != TYPE_PL2303) USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X); else USETW(req.wIndex, UPLCOM_SET_CRTSCTS); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } else { req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UPLCOM_SET_REQUEST; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } } static void uplcom_start_read(struct ucom_softc *ucom) { struct uplcom_softc *sc = ucom->sc_parent; /* start interrupt endpoint */ usbd_transfer_start(sc->sc_xfer[UPLCOM_INTR_DT_RD]); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_RD]); } static void uplcom_stop_read(struct ucom_softc *ucom) { struct uplcom_softc *sc = ucom->sc_parent; /* stop interrupt endpoint */ usbd_transfer_stop(sc->sc_xfer[UPLCOM_INTR_DT_RD]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_RD]); } static void uplcom_start_write(struct ucom_softc *ucom) { struct uplcom_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_WR]); } static void uplcom_stop_write(struct ucom_softc *ucom) { struct uplcom_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_WR]); } static void uplcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uplcom_softc *sc = ucom->sc_parent; DPRINTF("\n"); *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void uplcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uplcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[9]; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen = %u\n", actlen); if (actlen >= 9) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, sizeof(buf)); DPRINTF("status = 0x%02x\n", buf[UPLCOM_STATE_INDEX]); sc->sc_lsr = 0; sc->sc_msr = 0; if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_CTS) { sc->sc_msr |= SER_CTS; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_OVERRUN_ERROR) { sc->sc_lsr |= ULSR_OE; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_PARITY_ERROR) { sc->sc_lsr |= ULSR_PE; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_FRAME_ERROR) { sc->sc_lsr |= ULSR_FE; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_RING) { sc->sc_msr |= SER_RI; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_BREAK_ERROR) { sc->sc_lsr |= ULSR_BI; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_DSR) { sc->sc_msr |= SER_DSR; } if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_DCD) { sc->sc_msr |= SER_DCD; } ucom_status_change(&sc->sc_ucom); } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uplcom_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uplcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UPLCOM_BULK_BUF_SIZE, &actlen)) { DPRINTF("actlen = %d\n", actlen); usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uplcom_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uplcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uplcom_poll(struct ucom_softc *ucom) { struct uplcom_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UPLCOM_N_TRANSFER); } Index: head/sys/dev/usb/serial/usb_serial.c =================================================================== --- head/sys/dev/usb/serial/usb_serial.c (revision 357971) +++ head/sys/dev/usb/serial/usb_serial.c (revision 357972) @@ -1,1797 +1,1798 @@ /* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause-NetBSD * * Copyright (c) 2001-2003, 2005, 2008 * Shunsuke Akiyama . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 __FBSDID("$FreeBSD$"); /*- * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ucom_debug #include #include #include #include #include "opt_gdb.h" -static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ucom"); static int ucom_pps_mode; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RWTUN, &ucom_pps_mode, 0, "pulse capture mode: 0/1/2=disabled/CTS/DCD; add 0x10 to invert"); static int ucom_device_mode_console = 1; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, device_mode_console, CTLFLAG_RW, &ucom_device_mode_console, 0, "set to 1 to mark terminals as consoles when in device mode"); #ifdef USB_DEBUG static int ucom_debug = 0; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RWTUN, &ucom_debug, 0, "ucom debug level"); #endif #define UCOM_CONS_BUFSIZE 1024 static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE]; static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE]; static unsigned int ucom_cons_rx_low = 0; static unsigned int ucom_cons_rx_high = 0; static unsigned int ucom_cons_tx_low = 0; static unsigned int ucom_cons_tx_high = 0; static int ucom_cons_unit = -1; static int ucom_cons_subunit = 0; static int ucom_cons_baud = 9600; static struct ucom_softc *ucom_cons_softc = NULL; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RWTUN, &ucom_cons_unit, 0, "console unit number"); SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RWTUN, &ucom_cons_subunit, 0, "console subunit number"); SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RWTUN, &ucom_cons_baud, 0, "console baud rate"); static usb_proc_callback_t ucom_cfg_start_transfers; static usb_proc_callback_t ucom_cfg_open; static usb_proc_callback_t ucom_cfg_close; static usb_proc_callback_t ucom_cfg_line_state; static usb_proc_callback_t ucom_cfg_status_change; static usb_proc_callback_t ucom_cfg_param; static int ucom_unit_alloc(void); static void ucom_unit_free(int); static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *); static void ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *); static void ucom_queue_command(struct ucom_softc *, usb_proc_callback_t *, struct termios *pt, struct usb_proc_msg *t0, struct usb_proc_msg *t1); static void ucom_shutdown(struct ucom_softc *); static void ucom_ring(struct ucom_softc *, uint8_t); static void ucom_break(struct ucom_softc *, uint8_t); static void ucom_dtr(struct ucom_softc *, uint8_t); static void ucom_rts(struct ucom_softc *, uint8_t); static tsw_open_t ucom_open; static tsw_close_t ucom_close; static tsw_ioctl_t ucom_ioctl; static tsw_modem_t ucom_modem; static tsw_param_t ucom_param; static tsw_outwakeup_t ucom_outwakeup; static tsw_inwakeup_t ucom_inwakeup; static tsw_free_t ucom_free; static tsw_busy_t ucom_busy; static struct ttydevsw ucom_class = { .tsw_flags = TF_INITLOCK | TF_CALLOUT, .tsw_open = ucom_open, .tsw_close = ucom_close, .tsw_outwakeup = ucom_outwakeup, .tsw_inwakeup = ucom_inwakeup, .tsw_ioctl = ucom_ioctl, .tsw_param = ucom_param, .tsw_modem = ucom_modem, .tsw_free = ucom_free, .tsw_busy = ucom_busy, }; MODULE_DEPEND(ucom, usb, 1, 1, 1); MODULE_VERSION(ucom, 1); #define UCOM_UNIT_MAX 128 /* maximum number of units */ #define UCOM_TTY_PREFIX "U" static struct unrhdr *ucom_unrhdr; static struct mtx ucom_mtx; static int ucom_close_refs; static void ucom_init(void *arg) { DPRINTF("\n"); ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL); mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF); } SYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL); static void ucom_uninit(void *arg) { struct unrhdr *hdr; hdr = ucom_unrhdr; ucom_unrhdr = NULL; DPRINTF("\n"); if (hdr != NULL) delete_unrhdr(hdr); mtx_destroy(&ucom_mtx); } SYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL); /* * Mark a unit number (the X in cuaUX) as in use. * * Note that devices using a different naming scheme (see ucom_tty_name() * callback) still use this unit allocation. */ static int ucom_unit_alloc(void) { int unit; /* sanity checks */ if (ucom_unrhdr == NULL) { DPRINTF("ucom_unrhdr is NULL\n"); return (-1); } unit = alloc_unr(ucom_unrhdr); DPRINTF("unit %d is allocated\n", unit); return (unit); } /* * Mark the unit number as not in use. */ static void ucom_unit_free(int unit) { /* sanity checks */ if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) { DPRINTF("cannot free unit number\n"); return; } DPRINTF("unit %d is freed\n", unit); free_unr(ucom_unrhdr, unit); } /* * Setup a group of one or more serial ports. * * The mutex pointed to by "mtx" is applied before all * callbacks are called back. Also "mtx" must be applied * before calling into the ucom-layer! */ int ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc, int subunits, void *parent, const struct ucom_callback *callback, struct mtx *mtx) { int subunit; int error = 0; if ((sc == NULL) || (subunits <= 0) || (callback == NULL) || (mtx == NULL)) { return (EINVAL); } /* allocate a uniq unit number */ ssc->sc_unit = ucom_unit_alloc(); if (ssc->sc_unit == -1) return (ENOMEM); /* generate TTY name string */ snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname), UCOM_TTY_PREFIX "%d", ssc->sc_unit); /* create USB request handling process */ error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED); if (error) { ucom_unit_free(ssc->sc_unit); return (error); } ssc->sc_subunits = subunits; ssc->sc_flag = UCOM_FLAG_ATTACHED | UCOM_FLAG_FREE_UNIT | (ssc->sc_flag & UCOM_FLAG_DEVICE_MODE); if (callback->ucom_free == NULL) ssc->sc_flag |= UCOM_FLAG_WAIT_REFS; /* increment reference count */ ucom_ref(ssc); for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { sc[subunit].sc_subunit = subunit; sc[subunit].sc_super = ssc; sc[subunit].sc_mtx = mtx; sc[subunit].sc_parent = parent; sc[subunit].sc_callback = callback; error = ucom_attach_tty(ssc, &sc[subunit]); if (error) { ucom_detach(ssc, &sc[0]); return (error); } /* increment reference count */ ucom_ref(ssc); /* set subunit attached */ sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED; } DPRINTF("tp = %p, unit = %d, subunits = %d\n", sc->sc_tty, ssc->sc_unit, ssc->sc_subunits); return (0); } /* * The following function will do nothing if the structure pointed to * by "ssc" and "sc" is zero or has already been detached. */ void ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc) { int subunit; if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED)) return; /* not initialized */ if (ssc->sc_sysctl_ttyname != NULL) { sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0); ssc->sc_sysctl_ttyname = NULL; } if (ssc->sc_sysctl_ttyports != NULL) { sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0); ssc->sc_sysctl_ttyports = NULL; } usb_proc_drain(&ssc->sc_tq); for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) { ucom_detach_tty(ssc, &sc[subunit]); /* avoid duplicate detach */ sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED; } } usb_proc_free(&ssc->sc_tq); ucom_unref(ssc); if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS) ucom_drain(ssc); /* make sure we don't detach twice */ ssc->sc_flag &= ~UCOM_FLAG_ATTACHED; } void ucom_drain(struct ucom_super_softc *ssc) { mtx_lock(&ucom_mtx); while (ssc->sc_refs > 0) { printf("ucom: Waiting for a TTY device to close.\n"); usb_pause_mtx(&ucom_mtx, hz); } mtx_unlock(&ucom_mtx); } void ucom_drain_all(void *arg) { mtx_lock(&ucom_mtx); while (ucom_close_refs > 0) { printf("ucom: Waiting for all detached TTY " "devices to have open fds closed.\n"); usb_pause_mtx(&ucom_mtx, hz); } mtx_unlock(&ucom_mtx); } static cn_probe_t ucom_cnprobe; static cn_init_t ucom_cninit; static cn_term_t ucom_cnterm; static cn_getc_t ucom_cngetc; static cn_putc_t ucom_cnputc; static cn_grab_t ucom_cngrab; static cn_ungrab_t ucom_cnungrab; const struct consdev_ops ucom_cnops = { .cn_probe = ucom_cnprobe, .cn_init = ucom_cninit, .cn_term = ucom_cnterm, .cn_getc = ucom_cngetc, .cn_putc = ucom_cnputc, .cn_grab = ucom_cngrab, .cn_ungrab = ucom_cnungrab, }; static int ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) { struct tty *tp; char buf[32]; /* temporary TTY device name buffer */ tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx); if (tp == NULL) return (ENOMEM); /* Check if the client has a custom TTY name */ buf[0] = '\0'; if (sc->sc_callback->ucom_tty_name) { sc->sc_callback->ucom_tty_name(sc, buf, sizeof(buf), ssc->sc_unit, sc->sc_subunit); } if (buf[0] == 0) { /* Use default TTY name */ if (ssc->sc_subunits > 1) { /* multiple modems in one */ snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u", ssc->sc_unit, sc->sc_subunit); } else { /* single modem */ snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u", ssc->sc_unit); } } tty_makedev(tp, NULL, "%s", buf); sc->sc_tty = tp; sc->sc_pps.ppscap = PPS_CAPTUREBOTH; sc->sc_pps.driver_abi = PPS_ABI_VERSION; sc->sc_pps.driver_mtx = sc->sc_mtx; pps_init_abi(&sc->sc_pps); DPRINTF("ttycreate: %s\n", buf); /* Check if this device should be a console */ if ((ucom_cons_softc == NULL) && (ssc->sc_unit == ucom_cons_unit) && (sc->sc_subunit == ucom_cons_subunit)) { DPRINTF("unit %d subunit %d is console", ssc->sc_unit, sc->sc_subunit); ucom_cons_softc = sc; tty_init_console(tp, ucom_cons_baud); UCOM_MTX_LOCK(ucom_cons_softc); ucom_cons_rx_low = 0; ucom_cons_rx_high = 0; ucom_cons_tx_low = 0; ucom_cons_tx_high = 0; sc->sc_flag |= UCOM_FLAG_CONSOLE; ucom_open(ucom_cons_softc->sc_tty); ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in); UCOM_MTX_UNLOCK(ucom_cons_softc); } if ((ssc->sc_flag & UCOM_FLAG_DEVICE_MODE) != 0 && ucom_device_mode_console > 0 && ucom_cons_softc == NULL) { struct consdev *cp; cp = malloc(sizeof(struct consdev), M_USBDEV, M_WAITOK|M_ZERO); cp->cn_ops = &ucom_cnops; cp->cn_arg = NULL; cp->cn_pri = CN_NORMAL; strlcpy(cp->cn_name, "tty", sizeof(cp->cn_name)); strlcat(cp->cn_name, buf, sizeof(cp->cn_name)); sc->sc_consdev = cp; cnadd(cp); } return (0); } static void ucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); if (sc->sc_consdev != NULL) { cnremove(sc->sc_consdev); free(sc->sc_consdev, M_USBDEV); sc->sc_consdev = NULL; } if (sc->sc_flag & UCOM_FLAG_CONSOLE) { UCOM_MTX_LOCK(ucom_cons_softc); ucom_close(ucom_cons_softc->sc_tty); sc->sc_flag &= ~UCOM_FLAG_CONSOLE; UCOM_MTX_UNLOCK(ucom_cons_softc); ucom_cons_softc = NULL; } /* the config thread has been stopped when we get here */ UCOM_MTX_LOCK(sc); sc->sc_flag |= UCOM_FLAG_GONE; sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); UCOM_MTX_UNLOCK(sc); if (tp) { mtx_lock(&ucom_mtx); ucom_close_refs++; mtx_unlock(&ucom_mtx); tty_lock(tp); ucom_close(tp); /* close, if any */ tty_rel_gone(tp); UCOM_MTX_LOCK(sc); /* * make sure that read and write transfers are stopped */ if (sc->sc_callback->ucom_stop_read) (sc->sc_callback->ucom_stop_read) (sc); if (sc->sc_callback->ucom_stop_write) (sc->sc_callback->ucom_stop_write) (sc); UCOM_MTX_UNLOCK(sc); } } void ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev) { char buf[64]; uint8_t iface_index; struct usb_attach_arg *uaa; snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits); /* Store the PNP info in the first interface for the device */ uaa = device_get_ivars(dev); iface_index = uaa->info.bIfaceIndex; if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0) device_printf(dev, "Could not set PNP info\n"); /* * The following information is also replicated in the PNP-info * string which is registered above: */ if (ssc->sc_sysctl_ttyname == NULL) { ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0, "TTY device basename"); } if (ssc->sc_sysctl_ttyports == NULL) { ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "ttyports", CTLFLAG_RD, NULL, ssc->sc_subunits, "Number of ports"); } } void ucom_set_usb_mode(struct ucom_super_softc *ssc, enum usb_hc_mode usb_mode) { switch (usb_mode) { case USB_MODE_DEVICE: ssc->sc_flag |= UCOM_FLAG_DEVICE_MODE; break; default: ssc->sc_flag &= ~UCOM_FLAG_DEVICE_MODE; break; } } static void ucom_queue_command(struct ucom_softc *sc, usb_proc_callback_t *fn, struct termios *pt, struct usb_proc_msg *t0, struct usb_proc_msg *t1) { struct ucom_super_softc *ssc = sc->sc_super; struct ucom_param_task *task; UCOM_MTX_ASSERT(sc, MA_OWNED); if (usb_proc_is_gone(&ssc->sc_tq)) { DPRINTF("proc is gone\n"); return; /* nothing to do */ } /* * NOTE: The task cannot get executed before we drop the * "sc_mtx" mutex. It is safe to update fields in the message * structure after that the message got queued. */ task = (struct ucom_param_task *) usb_proc_msignal(&ssc->sc_tq, t0, t1); /* Setup callback and softc pointers */ task->hdr.pm_callback = fn; task->sc = sc; /* * Make a copy of the termios. This field is only present if * the "pt" field is not NULL. */ if (pt != NULL) task->termios_copy = *pt; /* * Closing the device should be synchronous. */ if (fn == ucom_cfg_close) usb_proc_mwait(&ssc->sc_tq, t0, t1); /* * In case of multiple configure requests, * keep track of the last one! */ if (fn == ucom_cfg_start_transfers) sc->sc_last_start_xfer = &task->hdr; } static void ucom_shutdown(struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("\n"); /* * Hang up if necessary: */ if (tp->t_termios.c_cflag & HUPCL) { ucom_modem(tp, 0, SER_DTR); } } /* * Return values: * 0: normal * else: taskqueue is draining or gone */ uint8_t ucom_cfg_is_gone(struct ucom_softc *sc) { struct ucom_super_softc *ssc = sc->sc_super; return (usb_proc_is_gone(&ssc->sc_tq)); } static void ucom_cfg_start_transfers(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* TTY device closed */ return; } if (_task == sc->sc_last_start_xfer) sc->sc_flag |= UCOM_FLAG_GP_DATA; if (sc->sc_callback->ucom_start_read) { (sc->sc_callback->ucom_start_read) (sc); } if (sc->sc_callback->ucom_start_write) { (sc->sc_callback->ucom_start_write) (sc); } } static void ucom_start_transfers(struct ucom_softc *sc) { if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } /* * Make sure that data transfers are started in both * directions: */ if (sc->sc_callback->ucom_start_read) { (sc->sc_callback->ucom_start_read) (sc); } if (sc->sc_callback->ucom_start_write) { (sc->sc_callback->ucom_start_write) (sc); } } static void ucom_cfg_open(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; DPRINTF("\n"); if (sc->sc_flag & UCOM_FLAG_LL_READY) { /* already opened */ } else { sc->sc_flag |= UCOM_FLAG_LL_READY; if (sc->sc_callback->ucom_cfg_open) { (sc->sc_callback->ucom_cfg_open) (sc); /* wait a little */ usb_pause_mtx(sc->sc_mtx, hz / 10); } } } static int ucom_open(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); int error; UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_GONE) { return (ENXIO); } if (sc->sc_flag & UCOM_FLAG_HL_READY) { /* already opened */ return (0); } DPRINTF("tp = %p\n", tp); if (sc->sc_callback->ucom_pre_open) { /* * give the lower layer a chance to disallow TTY open, for * example if the device is not present: */ error = (sc->sc_callback->ucom_pre_open) (sc); if (error) { return (error); } } sc->sc_flag |= UCOM_FLAG_HL_READY; /* Disable transfers */ sc->sc_flag &= ~UCOM_FLAG_GP_DATA; sc->sc_lsr = 0; sc->sc_msr = 0; sc->sc_mcr = 0; /* reset programmed line state */ sc->sc_pls_curr = 0; sc->sc_pls_set = 0; sc->sc_pls_clr = 0; /* reset jitter buffer */ sc->sc_jitterbuf_in = 0; sc->sc_jitterbuf_out = 0; ucom_queue_command(sc, ucom_cfg_open, NULL, &sc->sc_open_task[0].hdr, &sc->sc_open_task[1].hdr); /* Queue transfer enable command last */ ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, &sc->sc_start_task[0].hdr, &sc->sc_start_task[1].hdr); if (sc->sc_tty == NULL || (sc->sc_tty->t_termios.c_cflag & CNO_RTSDTR) == 0) ucom_modem(tp, SER_DTR | SER_RTS, 0); ucom_ring(sc, 0); ucom_break(sc, 0); ucom_status_change(sc); return (0); } static void ucom_cfg_close(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; DPRINTF("\n"); if (sc->sc_flag & UCOM_FLAG_LL_READY) { sc->sc_flag &= ~UCOM_FLAG_LL_READY; if (sc->sc_callback->ucom_cfg_close) (sc->sc_callback->ucom_cfg_close) (sc); } else { /* already closed */ } } static void ucom_close(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("tp=%p\n", tp); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { DPRINTF("tp=%p already closed\n", tp); return; } ucom_shutdown(sc); ucom_queue_command(sc, ucom_cfg_close, NULL, &sc->sc_close_task[0].hdr, &sc->sc_close_task[1].hdr); sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW); if (sc->sc_callback->ucom_stop_read) { (sc->sc_callback->ucom_stop_read) (sc); } } static void ucom_inwakeup(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); uint16_t pos; if (sc == NULL) return; UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("tp=%p\n", tp); if (ttydisc_can_bypass(tp) != 0 || (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 || (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) { return; } /* prevent recursion */ sc->sc_flag |= UCOM_FLAG_INWAKEUP; pos = sc->sc_jitterbuf_out; while (sc->sc_jitterbuf_in != pos) { int c; c = (char)sc->sc_jitterbuf[pos]; if (ttydisc_rint(tp, c, 0) == -1) break; pos++; if (pos >= UCOM_JITTERBUF_SIZE) pos -= UCOM_JITTERBUF_SIZE; } sc->sc_jitterbuf_out = pos; /* clear RTS in async fashion */ if ((sc->sc_jitterbuf_in == pos) && (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)) ucom_rts(sc, 0); sc->sc_flag &= ~UCOM_FLAG_INWAKEUP; } static int ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { struct ucom_softc *sc = tty_softc(tp); int error; UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return (EIO); } DPRINTF("cmd = 0x%08lx\n", cmd); switch (cmd) { #if 0 case TIOCSRING: ucom_ring(sc, 1); error = 0; break; case TIOCCRING: ucom_ring(sc, 0); error = 0; break; #endif case TIOCSBRK: ucom_break(sc, 1); error = 0; break; case TIOCCBRK: ucom_break(sc, 0); error = 0; break; default: if (sc->sc_callback->ucom_ioctl) { error = (sc->sc_callback->ucom_ioctl) (sc, cmd, data, 0, td); } else { error = ENOIOCTL; } if (error == ENOIOCTL) error = pps_ioctl(cmd, data, &sc->sc_pps); break; } return (error); } static int ucom_modem(struct tty *tp, int sigon, int sigoff) { struct ucom_softc *sc = tty_softc(tp); uint8_t onoff; UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return (0); } if ((sigon == 0) && (sigoff == 0)) { if (sc->sc_mcr & SER_DTR) { sigon |= SER_DTR; } if (sc->sc_mcr & SER_RTS) { sigon |= SER_RTS; } if (sc->sc_msr & SER_CTS) { sigon |= SER_CTS; } if (sc->sc_msr & SER_DCD) { sigon |= SER_DCD; } if (sc->sc_msr & SER_DSR) { sigon |= SER_DSR; } if (sc->sc_msr & SER_RI) { sigon |= SER_RI; } return (sigon); } if (sigon & SER_DTR) { sc->sc_mcr |= SER_DTR; } if (sigoff & SER_DTR) { sc->sc_mcr &= ~SER_DTR; } if (sigon & SER_RTS) { sc->sc_mcr |= SER_RTS; } if (sigoff & SER_RTS) { sc->sc_mcr &= ~SER_RTS; } onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; ucom_dtr(sc, onoff); onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; ucom_rts(sc, onoff); return (0); } static void ucom_cfg_line_state(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; uint8_t notch_bits; uint8_t any_bits; uint8_t prev_value; uint8_t last_value; uint8_t mask; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } mask = 0; /* compute callback mask */ if (sc->sc_callback->ucom_cfg_set_dtr) mask |= UCOM_LS_DTR; if (sc->sc_callback->ucom_cfg_set_rts) mask |= UCOM_LS_RTS; if (sc->sc_callback->ucom_cfg_set_break) mask |= UCOM_LS_BREAK; if (sc->sc_callback->ucom_cfg_set_ring) mask |= UCOM_LS_RING; /* compute the bits we are to program */ notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask; any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask; prev_value = sc->sc_pls_curr ^ notch_bits; last_value = sc->sc_pls_curr; /* reset programmed line state */ sc->sc_pls_curr = 0; sc->sc_pls_set = 0; sc->sc_pls_clr = 0; /* ensure that we don't lose any levels */ if (notch_bits & UCOM_LS_DTR) sc->sc_callback->ucom_cfg_set_dtr(sc, (prev_value & UCOM_LS_DTR) ? 1 : 0); if (notch_bits & UCOM_LS_RTS) sc->sc_callback->ucom_cfg_set_rts(sc, (prev_value & UCOM_LS_RTS) ? 1 : 0); if (notch_bits & UCOM_LS_BREAK) sc->sc_callback->ucom_cfg_set_break(sc, (prev_value & UCOM_LS_BREAK) ? 1 : 0); if (notch_bits & UCOM_LS_RING) sc->sc_callback->ucom_cfg_set_ring(sc, (prev_value & UCOM_LS_RING) ? 1 : 0); /* set last value */ if (any_bits & UCOM_LS_DTR) sc->sc_callback->ucom_cfg_set_dtr(sc, (last_value & UCOM_LS_DTR) ? 1 : 0); if (any_bits & UCOM_LS_RTS) sc->sc_callback->ucom_cfg_set_rts(sc, (last_value & UCOM_LS_RTS) ? 1 : 0); if (any_bits & UCOM_LS_BREAK) sc->sc_callback->ucom_cfg_set_break(sc, (last_value & UCOM_LS_BREAK) ? 1 : 0); if (any_bits & UCOM_LS_RING) sc->sc_callback->ucom_cfg_set_ring(sc, (last_value & UCOM_LS_RING) ? 1 : 0); } static void ucom_line_state(struct ucom_softc *sc, uint8_t set_bits, uint8_t clear_bits) { UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits); /* update current programmed line state */ sc->sc_pls_curr |= set_bits; sc->sc_pls_curr &= ~clear_bits; sc->sc_pls_set |= set_bits; sc->sc_pls_clr |= clear_bits; /* defer driver programming */ ucom_queue_command(sc, ucom_cfg_line_state, NULL, &sc->sc_line_state_task[0].hdr, &sc->sc_line_state_task[1].hdr); } static void ucom_ring(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_RING, 0); else ucom_line_state(sc, 0, UCOM_LS_RING); } static void ucom_break(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_BREAK, 0); else ucom_line_state(sc, 0, UCOM_LS_BREAK); } static void ucom_dtr(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_DTR, 0); else ucom_line_state(sc, 0, UCOM_LS_DTR); } static void ucom_rts(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_RTS, 0); else ucom_line_state(sc, 0, UCOM_LS_RTS); } static void ucom_cfg_status_change(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; struct tty *tp; int onoff; uint8_t new_msr; uint8_t new_lsr; uint8_t msr_delta; uint8_t lsr_delta; uint8_t pps_signal; tp = sc->sc_tty; UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->ucom_cfg_get_status == NULL) { return; } /* get status */ new_msr = 0; new_lsr = 0; (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* TTY device closed */ return; } msr_delta = (sc->sc_msr ^ new_msr); lsr_delta = (sc->sc_lsr ^ new_lsr); sc->sc_msr = new_msr; sc->sc_lsr = new_lsr; /* * Time pulse counting support. */ switch(ucom_pps_mode & UART_PPS_SIGNAL_MASK) { case UART_PPS_CTS: pps_signal = SER_CTS; break; case UART_PPS_DCD: pps_signal = SER_DCD; break; default: pps_signal = 0; break; } if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) && (msr_delta & pps_signal)) { pps_capture(&sc->sc_pps); onoff = (sc->sc_msr & pps_signal) ? 1 : 0; if (ucom_pps_mode & UART_PPS_INVERT_PULSE) onoff = !onoff; pps_event(&sc->sc_pps, onoff ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); } if (msr_delta & SER_DCD) { onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; DPRINTF("DCD changed to %d\n", onoff); ttydisc_modem(tp, onoff); } if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) { DPRINTF("BREAK detected\n"); ttydisc_rint(tp, 0, TRE_BREAK); ttydisc_rint_done(tp); } if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) { DPRINTF("Frame error detected\n"); ttydisc_rint(tp, 0, TRE_FRAMING); ttydisc_rint_done(tp); } if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) { DPRINTF("Parity error detected\n"); ttydisc_rint(tp, 0, TRE_PARITY); ttydisc_rint_done(tp); } } void ucom_status_change(struct ucom_softc *sc) { UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_CONSOLE) return; /* not supported */ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("\n"); ucom_queue_command(sc, ucom_cfg_status_change, NULL, &sc->sc_status_task[0].hdr, &sc->sc_status_task[1].hdr); } static void ucom_cfg_param(struct usb_proc_msg *_task) { struct ucom_param_task *task = (struct ucom_param_task *)_task; struct ucom_softc *sc = task->sc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->ucom_cfg_param == NULL) { return; } (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy); /* wait a little */ usb_pause_mtx(sc->sc_mtx, hz / 10); } static int ucom_param(struct tty *tp, struct termios *t) { struct ucom_softc *sc = tty_softc(tp); uint8_t opened; int error; UCOM_MTX_ASSERT(sc, MA_OWNED); opened = 0; error = 0; if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* XXX the TTY layer should call "open()" first! */ /* * Not quite: Its ordering is partly backwards, but * some parameters must be set early in ttydev_open(), * possibly before calling ttydevsw_open(). */ error = ucom_open(tp); if (error) goto done; opened = 1; } DPRINTF("sc = %p\n", sc); /* Check requested parameters. */ if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { /* XXX c_ospeed == 0 is perfectly valid. */ DPRINTF("mismatch ispeed and ospeed\n"); error = EINVAL; goto done; } t->c_ispeed = t->c_ospeed; if (sc->sc_callback->ucom_pre_param) { /* Let the lower layer verify the parameters */ error = (sc->sc_callback->ucom_pre_param) (sc, t); if (error) { DPRINTF("callback error = %d\n", error); goto done; } } /* Disable transfers */ sc->sc_flag &= ~UCOM_FLAG_GP_DATA; /* Queue baud rate programming command first */ ucom_queue_command(sc, ucom_cfg_param, t, &sc->sc_param_task[0].hdr, &sc->sc_param_task[1].hdr); /* Queue transfer enable command last */ ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, &sc->sc_start_task[0].hdr, &sc->sc_start_task[1].hdr); if (t->c_cflag & CRTS_IFLOW) { sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; ucom_modem(tp, SER_RTS, 0); } done: if (error) { if (opened) { ucom_close(tp); } } return (error); } static void ucom_outwakeup(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("sc = %p\n", sc); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* The higher layer is not ready */ return; } ucom_start_transfers(sc); } static bool ucom_busy(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); const uint8_t txidle = ULSR_TXRDY | ULSR_TSRE; UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTFN(3, "sc = %p lsr 0x%02x\n", sc, sc->sc_lsr); /* * If the driver maintains the txidle bits in LSR, we can use them to * determine whether the transmitter is busy or idle. Otherwise we have * to assume it is idle to avoid hanging forever on tcdrain(3). */ if (sc->sc_flag & UCOM_FLAG_LSRTXIDLE) return ((sc->sc_lsr & txidle) != txidle); else return (false); } /*------------------------------------------------------------------------* * ucom_get_data * * Return values: * 0: No data is available. * Else: Data is available. *------------------------------------------------------------------------*/ uint8_t ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc, uint32_t offset, uint32_t len, uint32_t *actlen) { struct usb_page_search res; struct tty *tp = sc->sc_tty; uint32_t cnt; uint32_t offset_orig; UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_CONSOLE) { unsigned int temp; /* get total TX length */ temp = ucom_cons_tx_high - ucom_cons_tx_low; temp %= UCOM_CONS_BUFSIZE; /* limit TX length */ if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low)) temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low); if (temp > len) temp = len; /* copy in data */ usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp); /* update counters */ ucom_cons_tx_low += temp; ucom_cons_tx_low %= UCOM_CONS_BUFSIZE; /* store actual length */ *actlen = temp; return (temp ? 1 : 0); } if (tty_gone(tp) || !(sc->sc_flag & UCOM_FLAG_GP_DATA)) { actlen[0] = 0; return (0); /* multiport device polling */ } offset_orig = offset; while (len != 0) { usbd_get_page(pc, offset, &res); if (res.length > len) { res.length = len; } /* copy data directly into USB buffer */ cnt = ttydisc_getc(tp, res.buffer, res.length); offset += cnt; len -= cnt; if (cnt < res.length) { /* end of buffer */ break; } } actlen[0] = offset - offset_orig; DPRINTF("cnt=%d\n", actlen[0]); if (actlen[0] == 0) { return (0); } return (1); } void ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc, uint32_t offset, uint32_t len) { struct usb_page_search res; struct tty *tp = sc->sc_tty; char *buf; uint32_t cnt; UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_CONSOLE) { unsigned int temp; /* get maximum RX length */ temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low; temp %= UCOM_CONS_BUFSIZE; /* limit RX length */ if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high)) temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high); if (temp > len) temp = len; /* copy out data */ usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp); /* update counters */ ucom_cons_rx_high += temp; ucom_cons_rx_high %= UCOM_CONS_BUFSIZE; return; } if (tty_gone(tp)) return; /* multiport device polling */ if (len == 0) return; /* no data */ /* set a flag to prevent recursation ? */ while (len > 0) { usbd_get_page(pc, offset, &res); if (res.length > len) { res.length = len; } len -= res.length; offset += res.length; /* pass characters to tty layer */ buf = res.buffer; cnt = res.length; /* first check if we can pass the buffer directly */ if (ttydisc_can_bypass(tp)) { /* clear any jitter buffer */ sc->sc_jitterbuf_in = 0; sc->sc_jitterbuf_out = 0; if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { DPRINTF("tp=%p, data lost\n", tp); } continue; } /* need to loop */ for (cnt = 0; cnt != res.length; cnt++) { if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out || ttydisc_rint(tp, buf[cnt], 0) == -1) { uint16_t end; uint16_t pos; pos = sc->sc_jitterbuf_in; end = sc->sc_jitterbuf_out + UCOM_JITTERBUF_SIZE - 1; if (end >= UCOM_JITTERBUF_SIZE) end -= UCOM_JITTERBUF_SIZE; for (; cnt != res.length; cnt++) { if (pos == end) break; sc->sc_jitterbuf[pos] = buf[cnt]; pos++; if (pos >= UCOM_JITTERBUF_SIZE) pos -= UCOM_JITTERBUF_SIZE; } sc->sc_jitterbuf_in = pos; /* set RTS in async fashion */ if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) ucom_rts(sc, 1); DPRINTF("tp=%p, lost %d " "chars\n", tp, res.length - cnt); break; } } } ttydisc_rint_done(tp); } static void ucom_free(void *xsc) { struct ucom_softc *sc = xsc; if (sc->sc_callback->ucom_free != NULL) sc->sc_callback->ucom_free(sc); else ucom_unref(sc->sc_super); mtx_lock(&ucom_mtx); ucom_close_refs--; mtx_unlock(&ucom_mtx); } CONSOLE_DRIVER(ucom); static void ucom_cnprobe(struct consdev *cp) { if (ucom_cons_unit != -1) cp->cn_pri = CN_NORMAL; else cp->cn_pri = CN_DEAD; strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name)); } static void ucom_cninit(struct consdev *cp) { } static void ucom_cnterm(struct consdev *cp) { } static void ucom_cngrab(struct consdev *cp) { } static void ucom_cnungrab(struct consdev *cp) { } static int ucom_cngetc(struct consdev *cd) { struct ucom_softc *sc = ucom_cons_softc; int c; if (sc == NULL) return (-1); UCOM_MTX_LOCK(sc); if (ucom_cons_rx_low != ucom_cons_rx_high) { c = ucom_cons_rx_buf[ucom_cons_rx_low]; ucom_cons_rx_low ++; ucom_cons_rx_low %= UCOM_CONS_BUFSIZE; } else { c = -1; } /* start USB transfers */ ucom_outwakeup(sc->sc_tty); UCOM_MTX_UNLOCK(sc); /* poll if necessary */ if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) (sc->sc_callback->ucom_poll) (sc); return (c); } static void ucom_cnputc(struct consdev *cd, int c) { struct ucom_softc *sc = ucom_cons_softc; unsigned int temp; if (sc == NULL) return; repeat: UCOM_MTX_LOCK(sc); /* compute maximum TX length */ temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low; temp %= UCOM_CONS_BUFSIZE; if (temp) { ucom_cons_tx_buf[ucom_cons_tx_high] = c; ucom_cons_tx_high ++; ucom_cons_tx_high %= UCOM_CONS_BUFSIZE; } /* start USB transfers */ ucom_outwakeup(sc->sc_tty); UCOM_MTX_UNLOCK(sc); /* poll if necessary */ if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) { (sc->sc_callback->ucom_poll) (sc); /* simple flow control */ if (temp == 0) goto repeat; } } /*------------------------------------------------------------------------* * ucom_ref * * This function will increment the super UCOM reference count. *------------------------------------------------------------------------*/ void ucom_ref(struct ucom_super_softc *ssc) { mtx_lock(&ucom_mtx); ssc->sc_refs++; mtx_unlock(&ucom_mtx); } /*------------------------------------------------------------------------* * ucom_free_unit * * This function will free the super UCOM's allocated unit * number. This function can be called on a zero-initialized * structure. This function can be called multiple times. *------------------------------------------------------------------------*/ static void ucom_free_unit(struct ucom_super_softc *ssc) { if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT)) return; ucom_unit_free(ssc->sc_unit); ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT; } /*------------------------------------------------------------------------* * ucom_unref * * This function will decrement the super UCOM reference count. * * Return values: * 0: UCOM structures are still referenced. * Else: UCOM structures are no longer referenced. *------------------------------------------------------------------------*/ int ucom_unref(struct ucom_super_softc *ssc) { int retval; mtx_lock(&ucom_mtx); retval = (ssc->sc_refs < 2); ssc->sc_refs--; mtx_unlock(&ucom_mtx); if (retval) ucom_free_unit(ssc); return (retval); } #if defined(GDB) #include static gdb_probe_f ucom_gdbprobe; static gdb_init_f ucom_gdbinit; static gdb_term_f ucom_gdbterm; static gdb_getc_f ucom_gdbgetc; static gdb_putc_f ucom_gdbputc; GDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc); static int ucom_gdbprobe(void) { return ((ucom_cons_softc != NULL) ? 0 : -1); } static void ucom_gdbinit(void) { } static void ucom_gdbterm(void) { } static void ucom_gdbputc(int c) { ucom_cnputc(NULL, c); } static int ucom_gdbgetc(void) { return (ucom_cngetc(NULL)); } #endif Index: head/sys/dev/usb/serial/uslcom.c =================================================================== --- head/sys/dev/usb/serial/uslcom.c (revision 357971) +++ head/sys/dev/usb/serial/uslcom.c (revision 357972) @@ -1,964 +1,965 @@ /* $OpenBSD: uslcom.c,v 1.17 2007/11/24 10:52:12 jsg Exp $ */ #include __FBSDID("$FreeBSD$"); /* * Copyright (c) 2006 Jonathan Gray * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Driver for Silicon Laboratories CP2101/CP2102/CP2103/CP2104/CP2105 * USB-Serial adapters. Based on datasheet AN571, publicly available from * http://www.silabs.com/Support%20Documents/TechnicalDocs/AN571.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uslcom_debug #include #include #include #ifdef USB_DEBUG static int uslcom_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uslcom, CTLFLAG_RW, 0, "USB uslcom"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uslcom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uslcom"); SYSCTL_INT(_hw_usb_uslcom, OID_AUTO, debug, CTLFLAG_RWTUN, &uslcom_debug, 0, "Debug level"); #endif #define USLCOM_BULK_BUF_SIZE 1024 #define USLCOM_CONFIG_INDEX 0 /* Request types */ #define USLCOM_WRITE 0x41 #define USLCOM_READ 0xc1 /* Request codes */ #define USLCOM_IFC_ENABLE 0x00 #define USLCOM_SET_BAUDDIV 0x01 #define USLCOM_SET_LINE_CTL 0x03 #define USLCOM_SET_BREAK 0x05 #define USLCOM_SET_MHS 0x07 #define USLCOM_GET_MDMSTS 0x08 #define USLCOM_SET_FLOW 0x13 #define USLCOM_SET_BAUDRATE 0x1e #define USLCOM_VENDOR_SPECIFIC 0xff /* USLCOM_IFC_ENABLE values */ #define USLCOM_IFC_ENABLE_DIS 0x00 #define USLCOM_IFC_ENABLE_EN 0x01 /* USLCOM_SET_MHS/USLCOM_GET_MDMSTS values */ #define USLCOM_MHS_DTR_ON 0x0001 #define USLCOM_MHS_DTR_SET 0x0100 #define USLCOM_MHS_RTS_ON 0x0002 #define USLCOM_MHS_RTS_SET 0x0200 #define USLCOM_MHS_CTS 0x0010 #define USLCOM_MHS_DSR 0x0020 #define USLCOM_MHS_RI 0x0040 #define USLCOM_MHS_DCD 0x0080 /* USLCOM_SET_BAUDDIV values */ #define USLCOM_BAUDDIV_REF 3686400 /* 3.6864 MHz */ /* USLCOM_SET_LINE_CTL values */ #define USLCOM_STOP_BITS_1 0x00 #define USLCOM_STOP_BITS_2 0x02 #define USLCOM_PARITY_NONE 0x00 #define USLCOM_PARITY_ODD 0x10 #define USLCOM_PARITY_EVEN 0x20 #define USLCOM_SET_DATA_BITS(x) ((x) << 8) /* USLCOM_SET_BREAK values */ #define USLCOM_SET_BREAK_OFF 0x00 #define USLCOM_SET_BREAK_ON 0x01 /* USLCOM_SET_FLOW values - 1st word */ #define USLCOM_FLOW_DTR_ON 0x00000001 /* DTR static active */ #define USLCOM_FLOW_CTS_HS 0x00000008 /* CTS handshake */ /* USLCOM_SET_FLOW values - 2nd word */ #define USLCOM_FLOW_RTS_ON 0x00000040 /* RTS static active */ #define USLCOM_FLOW_RTS_HS 0x00000080 /* RTS handshake */ /* USLCOM_VENDOR_SPECIFIC values */ #define USLCOM_GET_PARTNUM 0x370B #define USLCOM_WRITE_LATCH 0x37E1 #define USLCOM_READ_LATCH 0x00C2 /* USLCOM_GET_PARTNUM values from hardware */ #define USLCOM_PARTNUM_CP2101 1 #define USLCOM_PARTNUM_CP2102 2 #define USLCOM_PARTNUM_CP2103 3 #define USLCOM_PARTNUM_CP2104 4 #define USLCOM_PARTNUM_CP2105 5 enum { USLCOM_BULK_DT_WR, USLCOM_BULK_DT_RD, USLCOM_CTRL_DT_RD, USLCOM_N_TRANSFER, }; struct uslcom_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_callout sc_watchdog; struct usb_xfer *sc_xfer[USLCOM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint8_t sc_msr; uint8_t sc_lsr; uint8_t sc_iface_no; uint8_t sc_partnum; }; static device_probe_t uslcom_probe; static device_attach_t uslcom_attach; static device_detach_t uslcom_detach; static void uslcom_free_softc(struct uslcom_softc *); static usb_callback_t uslcom_write_callback; static usb_callback_t uslcom_read_callback; static usb_callback_t uslcom_control_callback; static void uslcom_free(struct ucom_softc *); static uint8_t uslcom_get_partnum(struct uslcom_softc *); static void uslcom_cfg_open(struct ucom_softc *); static void uslcom_cfg_close(struct ucom_softc *); static void uslcom_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uslcom_cfg_set_rts(struct ucom_softc *, uint8_t); static void uslcom_cfg_set_break(struct ucom_softc *, uint8_t); static void uslcom_cfg_param(struct ucom_softc *, struct termios *); static void uslcom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static int uslcom_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); static int uslcom_pre_param(struct ucom_softc *, struct termios *); static void uslcom_start_read(struct ucom_softc *); static void uslcom_stop_read(struct ucom_softc *); static void uslcom_start_write(struct ucom_softc *); static void uslcom_stop_write(struct ucom_softc *); static void uslcom_poll(struct ucom_softc *ucom); static const struct usb_config uslcom_config[USLCOM_N_TRANSFER] = { [USLCOM_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = USLCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,}, .callback = &uslcom_write_callback, }, [USLCOM_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = USLCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uslcom_read_callback, }, [USLCOM_CTRL_DT_RD] = { .type = UE_CONTROL, .endpoint = 0x00, .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + 8, .flags = {.pipe_bof = 1,}, .callback = &uslcom_control_callback, .timeout = 1000, /* 1 second timeout */ }, }; static struct ucom_callback uslcom_callback = { .ucom_cfg_get_status = &uslcom_cfg_get_status, .ucom_cfg_set_dtr = &uslcom_cfg_set_dtr, .ucom_cfg_set_rts = &uslcom_cfg_set_rts, .ucom_cfg_set_break = &uslcom_cfg_set_break, .ucom_cfg_open = &uslcom_cfg_open, .ucom_cfg_close = &uslcom_cfg_close, .ucom_cfg_param = &uslcom_cfg_param, .ucom_pre_param = &uslcom_pre_param, .ucom_ioctl = &uslcom_ioctl, .ucom_start_read = &uslcom_start_read, .ucom_stop_read = &uslcom_stop_read, .ucom_start_write = &uslcom_start_write, .ucom_stop_write = &uslcom_stop_write, .ucom_poll = &uslcom_poll, .ucom_free = &uslcom_free, }; static const STRUCT_USB_HOST_ID uslcom_devs[] = { #define USLCOM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } USLCOM_DEV(BALTECH, CARDREADER), USLCOM_DEV(CLIPSAL, 5000CT2), USLCOM_DEV(CLIPSAL, 5500PACA), USLCOM_DEV(CLIPSAL, 5500PCU), USLCOM_DEV(CLIPSAL, 560884), USLCOM_DEV(CLIPSAL, 5800PC), USLCOM_DEV(CLIPSAL, C5000CT2), USLCOM_DEV(CLIPSAL, L51xx), USLCOM_DEV(DATAAPEX, MULTICOM), USLCOM_DEV(DELL, DW700), USLCOM_DEV(DIGIANSWER, ZIGBEE802154), USLCOM_DEV(DYNASTREAM, ANTDEVBOARD), USLCOM_DEV(DYNASTREAM, ANTDEVBOARD2), USLCOM_DEV(DYNASTREAM, ANT2USB), USLCOM_DEV(ELV, USBI2C), USLCOM_DEV(FESTO, CMSP), USLCOM_DEV(FESTO, CPX_USB), USLCOM_DEV(FOXCONN, PIRELLI_DP_L10), USLCOM_DEV(FOXCONN, TCOM_TC_300), USLCOM_DEV(GEMALTO, PROXPU), USLCOM_DEV(JABLOTRON, PC60B), USLCOM_DEV(KAMSTRUP, OPTICALEYE), USLCOM_DEV(KAMSTRUP, MBUS_250D), USLCOM_DEV(LAKESHORE, 121), USLCOM_DEV(LAKESHORE, 218A), USLCOM_DEV(LAKESHORE, 219), USLCOM_DEV(LAKESHORE, 233), USLCOM_DEV(LAKESHORE, 235), USLCOM_DEV(LAKESHORE, 335), USLCOM_DEV(LAKESHORE, 336), USLCOM_DEV(LAKESHORE, 350), USLCOM_DEV(LAKESHORE, 371), USLCOM_DEV(LAKESHORE, 411), USLCOM_DEV(LAKESHORE, 425), USLCOM_DEV(LAKESHORE, 455A), USLCOM_DEV(LAKESHORE, 465), USLCOM_DEV(LAKESHORE, 475A), USLCOM_DEV(LAKESHORE, 625A), USLCOM_DEV(LAKESHORE, 642A), USLCOM_DEV(LAKESHORE, 648), USLCOM_DEV(LAKESHORE, 737), USLCOM_DEV(LAKESHORE, 776), USLCOM_DEV(LINKINSTRUMENTS, MSO19), USLCOM_DEV(LINKINSTRUMENTS, MSO28), USLCOM_DEV(LINKINSTRUMENTS, MSO28_2), USLCOM_DEV(MEI, CASHFLOW_SC), USLCOM_DEV(MEI, S2000), USLCOM_DEV(NETGEAR, M4100), USLCOM_DEV(OWEN, AC4), USLCOM_DEV(OWL, CM_160), USLCOM_DEV(PHILIPS, ACE1001), USLCOM_DEV(PLX, CA42), USLCOM_DEV(RENESAS, RX610), USLCOM_DEV(SEL, C662), USLCOM_DEV(SILABS, AC_SERV_CAN), USLCOM_DEV(SILABS, AC_SERV_CIS), USLCOM_DEV(SILABS, AC_SERV_IBUS), USLCOM_DEV(SILABS, AC_SERV_OBD), USLCOM_DEV(SILABS, AEROCOMM), USLCOM_DEV(SILABS, AMBER_AMB2560), USLCOM_DEV(SILABS, ARGUSISP), USLCOM_DEV(SILABS, ARKHAM_DS101_A), USLCOM_DEV(SILABS, ARKHAM_DS101_M), USLCOM_DEV(SILABS, ARYGON_MIFARE), USLCOM_DEV(SILABS, AVIT_USB_TTL), USLCOM_DEV(SILABS, B_G_H3000), USLCOM_DEV(SILABS, BALLUFF_RFID), USLCOM_DEV(SILABS, BEI_VCP), USLCOM_DEV(SILABS, BSM7DUSB), USLCOM_DEV(SILABS, BURNSIDE), USLCOM_DEV(SILABS, C2_EDGE_MODEM), USLCOM_DEV(SILABS, CP2102), USLCOM_DEV(SILABS, CP210X_2), USLCOM_DEV(SILABS, CP210X_3), USLCOM_DEV(SILABS, CP210X_4), USLCOM_DEV(SILABS, CRUMB128), USLCOM_DEV(SILABS, CYGNAL), USLCOM_DEV(SILABS, CYGNAL_DEBUG), USLCOM_DEV(SILABS, CYGNAL_GPS), USLCOM_DEV(SILABS, DEGREE), USLCOM_DEV(SILABS, DEKTEK_DTAPLUS), USLCOM_DEV(SILABS, EMS_C1007), USLCOM_DEV(SILABS, HAMLINKUSB), USLCOM_DEV(SILABS, HELICOM), USLCOM_DEV(SILABS, HUBZ), USLCOM_DEV(SILABS, BV_AV2010_10), USLCOM_DEV(SILABS, IMS_USB_RS422), USLCOM_DEV(SILABS, INFINITY_MIC), USLCOM_DEV(SILABS, INGENI_ZIGBEE), USLCOM_DEV(SILABS, INSYS_MODEM), USLCOM_DEV(SILABS, IRZ_SG10), USLCOM_DEV(SILABS, KYOCERA_GPS), USLCOM_DEV(SILABS, LIPOWSKY_HARP), USLCOM_DEV(SILABS, LIPOWSKY_JTAG), USLCOM_DEV(SILABS, LIPOWSKY_LIN), USLCOM_DEV(SILABS, MC35PU), USLCOM_DEV(SILABS, MMB_ZIGBEE), USLCOM_DEV(SILABS, MJS_TOSLINK), USLCOM_DEV(SILABS, MSD_DASHHAWK), USLCOM_DEV(SILABS, MULTIPLEX_RC), USLCOM_DEV(SILABS, OPTRIS_MSPRO), USLCOM_DEV(SILABS, POLOLU), USLCOM_DEV(SILABS, PROCYON_AVS), USLCOM_DEV(SILABS, SB_PARAMOUNT_ME), USLCOM_DEV(SILABS, SUUNTO), USLCOM_DEV(SILABS, TAMSMASTER), USLCOM_DEV(SILABS, TELEGESIS_ETRX2), USLCOM_DEV(SILABS, TRACIENT), USLCOM_DEV(SILABS, TRAQMATE), USLCOM_DEV(SILABS, USBCOUNT50), USLCOM_DEV(SILABS, USBPULSE100), USLCOM_DEV(SILABS, USBSCOPE50), USLCOM_DEV(SILABS, USBWAVE12), USLCOM_DEV(SILABS, V_PREON32), USLCOM_DEV(SILABS, VSTABI), USLCOM_DEV(SILABS, WAVIT), USLCOM_DEV(SILABS, WMRBATT), USLCOM_DEV(SILABS, WMRRIGBLASTER), USLCOM_DEV(SILABS, WMRRIGTALK), USLCOM_DEV(SILABS, ZEPHYR_BIO), USLCOM_DEV(SILABS2, DCU11CLONE), USLCOM_DEV(SILABS3, GPRS_MODEM), USLCOM_DEV(SILABS4, 100EU_MODEM), USLCOM_DEV(SYNTECH, CYPHERLAB100), USLCOM_DEV(USI, MC60), USLCOM_DEV(VAISALA, CABLE), USLCOM_DEV(WAGO, SERVICECABLE), USLCOM_DEV(WAVESENSE, JAZZ), USLCOM_DEV(WESTMOUNTAIN, RIGBLASTER_ADVANTAGE), USLCOM_DEV(WIENERPLEINBAUS, PL512), USLCOM_DEV(WIENERPLEINBAUS, RCM), USLCOM_DEV(WIENERPLEINBAUS, MPOD), USLCOM_DEV(WIENERPLEINBAUS, CML), #undef USLCOM_DEV }; static device_method_t uslcom_methods[] = { DEVMETHOD(device_probe, uslcom_probe), DEVMETHOD(device_attach, uslcom_attach), DEVMETHOD(device_detach, uslcom_detach), DEVMETHOD_END }; static devclass_t uslcom_devclass; static driver_t uslcom_driver = { .name = "uslcom", .methods = uslcom_methods, .size = sizeof(struct uslcom_softc), }; DRIVER_MODULE(uslcom, uhub, uslcom_driver, uslcom_devclass, NULL, 0); MODULE_DEPEND(uslcom, ucom, 1, 1, 1); MODULE_DEPEND(uslcom, usb, 1, 1, 1); MODULE_VERSION(uslcom, 1); USB_PNP_HOST_INFO(uslcom_devs); static void uslcom_watchdog(void *arg) { struct uslcom_softc *sc = arg; mtx_assert(&sc->sc_mtx, MA_OWNED); usbd_transfer_start(sc->sc_xfer[USLCOM_CTRL_DT_RD]); usb_callout_reset(&sc->sc_watchdog, hz / 4, &uslcom_watchdog, sc); } static int uslcom_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != USLCOM_CONFIG_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(uslcom_devs, sizeof(uslcom_devs), uaa)); } static int uslcom_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uslcom_softc *sc = device_get_softc(dev); int error; DPRINTFN(11, "\n"); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uslcom", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); sc->sc_udev = uaa->device; /* use the interface number from the USB interface descriptor */ sc->sc_iface_no = uaa->info.bIfaceNum; error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, uslcom_config, USLCOM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("one or more missing USB endpoints, " "error=%s\n", usbd_errstr(error)); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); sc->sc_partnum = uslcom_get_partnum(sc); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uslcom_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: uslcom_detach(dev); return (ENXIO); } static int uslcom_detach(device_t dev) { struct uslcom_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, USLCOM_N_TRANSFER); usb_callout_drain(&sc->sc_watchdog); device_claim_softc(dev); uslcom_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uslcom); static void uslcom_free_softc(struct uslcom_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uslcom_free(struct ucom_softc *ucom) { uslcom_free_softc(ucom->sc_parent); } static void uslcom_cfg_open(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_IFC_ENABLE; USETW(req.wValue, USLCOM_IFC_ENABLE_EN); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("UART enable failed (ignored)\n"); } /* start polling status */ uslcom_watchdog(sc); } static void uslcom_cfg_close(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; /* stop polling status */ usb_callout_stop(&sc->sc_watchdog); req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_IFC_ENABLE; USETW(req.wValue, USLCOM_IFC_ENABLE_DIS); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("UART disable failed (ignored)\n"); } } static uint8_t uslcom_get_partnum(struct uslcom_softc *sc) { struct usb_device_request req; uint8_t partnum; /* Find specific chip type */ partnum = 0; req.bmRequestType = USLCOM_READ; req.bRequest = USLCOM_VENDOR_SPECIFIC; USETW(req.wValue, USLCOM_GET_PARTNUM); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, sizeof(partnum)); if (usbd_do_request_flags(sc->sc_udev, NULL, &req, &partnum, 0, NULL, 1000)) { DPRINTF("GET_PARTNUM failed\n"); } return(partnum); } static void uslcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t ctl; DPRINTF("onoff = %d\n", onoff); ctl = onoff ? USLCOM_MHS_DTR_ON : 0; ctl |= USLCOM_MHS_DTR_SET; req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_SET_MHS; USETW(req.wValue, ctl); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("Setting DTR failed (ignored)\n"); } } static void uslcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t ctl; DPRINTF("onoff = %d\n", onoff); ctl = onoff ? USLCOM_MHS_RTS_ON : 0; ctl |= USLCOM_MHS_RTS_SET; req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_SET_MHS; USETW(req.wValue, ctl); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("Setting DTR failed (ignored)\n"); } } static int uslcom_pre_param(struct ucom_softc *ucom, struct termios *t) { struct uslcom_softc *sc = ucom->sc_parent; uint32_t maxspeed; switch (sc->sc_partnum) { case USLCOM_PARTNUM_CP2104: case USLCOM_PARTNUM_CP2105: maxspeed = 2000000; break; case USLCOM_PARTNUM_CP2101: case USLCOM_PARTNUM_CP2102: case USLCOM_PARTNUM_CP2103: default: /* * Datasheet for cp2102 says 921600 max. Testing shows that * 1228800 and 1843200 work fine. */ maxspeed = 1843200; break; } if (t->c_ospeed <= 0 || t->c_ospeed > maxspeed) return (EINVAL); return (0); } static void uslcom_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; uint32_t baudrate, flowctrl[4]; uint16_t data; DPRINTF("\n"); baudrate = t->c_ospeed; req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_SET_BAUDRATE; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, sizeof(baudrate)); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &baudrate, 0, 1000)) { printf("Set baudrate failed (ignored)\n"); } if (t->c_cflag & CSTOPB) data = USLCOM_STOP_BITS_2; else data = USLCOM_STOP_BITS_1; if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) data |= USLCOM_PARITY_ODD; else data |= USLCOM_PARITY_EVEN; } else data |= USLCOM_PARITY_NONE; switch (t->c_cflag & CSIZE) { case CS5: data |= USLCOM_SET_DATA_BITS(5); break; case CS6: data |= USLCOM_SET_DATA_BITS(6); break; case CS7: data |= USLCOM_SET_DATA_BITS(7); break; case CS8: data |= USLCOM_SET_DATA_BITS(8); break; } req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_SET_LINE_CTL; USETW(req.wValue, data); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("Set format failed (ignored)\n"); } if (t->c_cflag & CRTSCTS) { flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON | USLCOM_FLOW_CTS_HS); flowctrl[1] = htole32(USLCOM_FLOW_RTS_HS); } else { flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON); flowctrl[1] = htole32(USLCOM_FLOW_RTS_ON); } flowctrl[2] = 0; flowctrl[3] = 0; req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_SET_FLOW; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, sizeof(flowctrl)); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, flowctrl, 0, 1000)) { DPRINTF("Set flowcontrol failed (ignored)\n"); } } static void uslcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uslcom_softc *sc = ucom->sc_parent; DPRINTF("\n"); /* XXX Note: sc_lsr is always zero */ *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void uslcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t brk = onoff ? USLCOM_SET_BREAK_ON : USLCOM_SET_BREAK_OFF; req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_SET_BREAK; USETW(req.wValue, brk); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("Set BREAK failed (ignored)\n"); } } static int uslcom_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td) { struct uslcom_softc *sc = ucom->sc_parent; struct usb_device_request req; int error = 0; uint8_t latch; DPRINTF("cmd=0x%08x\n", cmd); switch (cmd) { case USB_GET_GPIO: if (sc->sc_partnum < USLCOM_PARTNUM_CP2103) { error = ENODEV; break; } req.bmRequestType = USLCOM_READ; req.bRequest = USLCOM_VENDOR_SPECIFIC; USETW(req.wValue, USLCOM_READ_LATCH); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, sizeof(latch)); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &latch, 0, 1000)) { DPRINTF("Get LATCH failed\n"); error = EIO; } *(int *)data = latch; break; case USB_SET_GPIO: if (sc->sc_partnum < USLCOM_PARTNUM_CP2103) error = ENODEV; else if ((sc->sc_partnum == USLCOM_PARTNUM_CP2103) || (sc->sc_partnum == USLCOM_PARTNUM_CP2104)) { req.bmRequestType = USLCOM_WRITE; req.bRequest = USLCOM_VENDOR_SPECIFIC; USETW(req.wValue, USLCOM_WRITE_LATCH); USETW(req.wIndex, (*(int *)data)); USETW(req.wLength, 0); if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000)) { DPRINTF("Set LATCH failed\n"); error = EIO; } } else error = ENODEV; /* Not yet */ break; default: DPRINTF("Unknown IOCTL\n"); error = ENOIOCTL; break; } return (error); } static void uslcom_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uslcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, USLCOM_BULK_BUF_SIZE, &actlen)) { DPRINTF("actlen = %d\n", actlen); usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uslcom_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uslcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uslcom_control_callback(struct usb_xfer *xfer, usb_error_t error) { struct uslcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; struct usb_device_request req; uint8_t msr = 0; uint8_t buf; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_out(pc, 0, &buf, sizeof(buf)); if (buf & USLCOM_MHS_CTS) msr |= SER_CTS; if (buf & USLCOM_MHS_DSR) msr |= SER_DSR; if (buf & USLCOM_MHS_RI) msr |= SER_RI; if (buf & USLCOM_MHS_DCD) msr |= SER_DCD; if (msr != sc->sc_msr) { DPRINTF("status change msr=0x%02x " "(was 0x%02x)\n", msr, sc->sc_msr); sc->sc_msr = msr; ucom_status_change(&sc->sc_ucom); } break; case USB_ST_SETUP: req.bmRequestType = USLCOM_READ; req.bRequest = USLCOM_GET_MDMSTS; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_iface_no); USETW(req.wLength, sizeof(buf)); usbd_xfer_set_frames(xfer, 2); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_transfer_submit(xfer); break; default: /* error */ if (error != USB_ERR_CANCELLED) DPRINTF("error=%s\n", usbd_errstr(error)); break; } } static void uslcom_start_read(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_RD]); } static void uslcom_stop_read(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_RD]); } static void uslcom_start_write(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_WR]); } static void uslcom_stop_write(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_WR]); } static void uslcom_poll(struct ucom_softc *ucom) { struct uslcom_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, USLCOM_N_TRANSFER); } Index: head/sys/dev/usb/serial/uvisor.c =================================================================== --- head/sys/dev/usb/serial/uvisor.c (revision 357971) +++ head/sys/dev/usb/serial/uvisor.c (revision 357972) @@ -1,679 +1,680 @@ /* $NetBSD: uvisor.c,v 1.9 2001/01/23 14:04:14 augustss Exp $ */ /* $FreeBSD$ */ /* Also already merged from NetBSD: * $NetBSD: uvisor.c,v 1.12 2001/11/13 06:24:57 lukem Exp $ * $NetBSD: uvisor.c,v 1.13 2002/02/11 15:11:49 augustss Exp $ * $NetBSD: uvisor.c,v 1.14 2002/02/27 23:00:03 augustss Exp $ * $NetBSD: uvisor.c,v 1.15 2002/06/16 15:01:31 augustss Exp $ * $NetBSD: uvisor.c,v 1.16 2002/07/11 21:14:36 augustss Exp $ * $NetBSD: uvisor.c,v 1.17 2002/08/13 11:38:15 augustss Exp $ * $NetBSD: uvisor.c,v 1.18 2003/02/05 00:50:14 augustss Exp $ * $NetBSD: uvisor.c,v 1.19 2003/02/07 18:12:37 augustss Exp $ * $NetBSD: uvisor.c,v 1.20 2003/04/11 01:30:10 simonb Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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. * * 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. */ /* * Handspring Visor (Palmpilot compatible PDA) driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uvisor_debug #include #include #include #ifdef USB_DEBUG static int uvisor_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uvisor, CTLFLAG_RW, 0, "USB uvisor"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uvisor, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uvisor"); SYSCTL_INT(_hw_usb_uvisor, OID_AUTO, debug, CTLFLAG_RWTUN, &uvisor_debug, 0, "Debug level"); #endif #define UVISOR_CONFIG_INDEX 0 #define UVISOR_IFACE_INDEX 0 /* * The following buffer sizes are hardcoded due to the way the Palm * firmware works. It looks like the device is not short terminating * the data transferred. */ #define UVISORIBUFSIZE 0 /* Use wMaxPacketSize */ #define UVISOROBUFSIZE 32 /* bytes */ #define UVISOROFRAMES 32 /* units */ /* From the Linux driver */ /* * UVISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that * are available to be transferred to the host for the specified endpoint. * Currently this is not used, and always returns 0x0001 */ #define UVISOR_REQUEST_BYTES_AVAILABLE 0x01 /* * UVISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host * is now closing the pipe. An empty packet is sent in response. */ #define UVISOR_CLOSE_NOTIFICATION 0x02 /* * UVISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to * get the endpoints used by the connection. */ #define UVISOR_GET_CONNECTION_INFORMATION 0x03 /* * UVISOR_GET_CONNECTION_INFORMATION returns data in the following format */ #define UVISOR_MAX_CONN 8 struct uvisor_connection_info { uWord num_ports; struct { uByte port_function_id; uByte port; } __packed connections[UVISOR_MAX_CONN]; } __packed; #define UVISOR_CONNECTION_INFO_SIZE 18 /* struct uvisor_connection_info.connection[x].port defines: */ #define UVISOR_ENDPOINT_1 0x01 #define UVISOR_ENDPOINT_2 0x02 /* struct uvisor_connection_info.connection[x].port_function_id defines: */ #define UVISOR_FUNCTION_GENERIC 0x00 #define UVISOR_FUNCTION_DEBUGGER 0x01 #define UVISOR_FUNCTION_HOTSYNC 0x02 #define UVISOR_FUNCTION_CONSOLE 0x03 #define UVISOR_FUNCTION_REMOTE_FILE_SYS 0x04 /* * Unknown PalmOS stuff. */ #define UVISOR_GET_PALM_INFORMATION 0x04 #define UVISOR_GET_PALM_INFORMATION_LEN 0x44 struct uvisor_palm_connection_info { uByte num_ports; uByte endpoint_numbers_different; uWord reserved1; struct { uDWord port_function_id; uByte port; uByte end_point_info; uWord reserved; } __packed connections[UVISOR_MAX_CONN]; } __packed; enum { UVISOR_BULK_DT_WR, UVISOR_BULK_DT_RD, UVISOR_N_TRANSFER, }; struct uvisor_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UVISOR_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint16_t sc_flag; #define UVISOR_FLAG_PALM4 0x0001 #define UVISOR_FLAG_VISOR 0x0002 #define UVISOR_FLAG_PALM35 0x0004 #define UVISOR_FLAG_SEND_NOTIFY 0x0008 uint8_t sc_iface_no; uint8_t sc_iface_index; }; /* prototypes */ static device_probe_t uvisor_probe; static device_attach_t uvisor_attach; static device_detach_t uvisor_detach; static void uvisor_free_softc(struct uvisor_softc *); static usb_callback_t uvisor_write_callback; static usb_callback_t uvisor_read_callback; static usb_error_t uvisor_init(struct uvisor_softc *, struct usb_device *, struct usb_config *); static void uvisor_free(struct ucom_softc *); static void uvisor_cfg_open(struct ucom_softc *); static void uvisor_cfg_close(struct ucom_softc *); static void uvisor_start_read(struct ucom_softc *); static void uvisor_stop_read(struct ucom_softc *); static void uvisor_start_write(struct ucom_softc *); static void uvisor_stop_write(struct ucom_softc *); static const struct usb_config uvisor_config[UVISOR_N_TRANSFER] = { [UVISOR_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UVISOROBUFSIZE * UVISOROFRAMES, .frames = UVISOROFRAMES, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &uvisor_write_callback, }, [UVISOR_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UVISORIBUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uvisor_read_callback, }, }; static const struct ucom_callback uvisor_callback = { .ucom_cfg_open = &uvisor_cfg_open, .ucom_cfg_close = &uvisor_cfg_close, .ucom_start_read = &uvisor_start_read, .ucom_stop_read = &uvisor_stop_read, .ucom_start_write = &uvisor_start_write, .ucom_stop_write = &uvisor_stop_write, .ucom_free = &uvisor_free, }; static device_method_t uvisor_methods[] = { DEVMETHOD(device_probe, uvisor_probe), DEVMETHOD(device_attach, uvisor_attach), DEVMETHOD(device_detach, uvisor_detach), DEVMETHOD_END }; static devclass_t uvisor_devclass; static driver_t uvisor_driver = { .name = "uvisor", .methods = uvisor_methods, .size = sizeof(struct uvisor_softc), }; static const STRUCT_USB_HOST_ID uvisor_devs[] = { #define UVISOR_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } UVISOR_DEV(ACEECA, MEZ1000, UVISOR_FLAG_PALM4), UVISOR_DEV(ALPHASMART, DANA_SYNC, UVISOR_FLAG_PALM4), UVISOR_DEV(GARMIN, IQUE_3600, UVISOR_FLAG_PALM4), UVISOR_DEV(FOSSIL, WRISTPDA, UVISOR_FLAG_PALM4), UVISOR_DEV(HANDSPRING, VISOR, UVISOR_FLAG_VISOR), UVISOR_DEV(HANDSPRING, TREO, UVISOR_FLAG_PALM4), UVISOR_DEV(HANDSPRING, TREO600, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, M500, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, M505, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, M515, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, I705, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, M125, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, M130, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, TUNGSTEN_Z, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, TUNGSTEN_T, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, ZIRE, UVISOR_FLAG_PALM4), UVISOR_DEV(PALM, ZIRE31, UVISOR_FLAG_PALM4), UVISOR_DEV(SAMSUNG, I500, UVISOR_FLAG_PALM4), UVISOR_DEV(SONY, CLIE_40, 0), UVISOR_DEV(SONY, CLIE_41, 0), UVISOR_DEV(SONY, CLIE_S360, UVISOR_FLAG_PALM4), UVISOR_DEV(SONY, CLIE_NX60, UVISOR_FLAG_PALM4), UVISOR_DEV(SONY, CLIE_35, UVISOR_FLAG_PALM35), /* UVISOR_DEV(SONY, CLIE_25, UVISOR_FLAG_PALM4 ), */ UVISOR_DEV(SONY, CLIE_TJ37, UVISOR_FLAG_PALM4), /* UVISOR_DEV(SONY, CLIE_TH55, UVISOR_FLAG_PALM4 ), See PR 80935 */ UVISOR_DEV(TAPWAVE, ZODIAC, UVISOR_FLAG_PALM4), #undef UVISOR_DEV }; DRIVER_MODULE(uvisor, uhub, uvisor_driver, uvisor_devclass, NULL, 0); MODULE_DEPEND(uvisor, ucom, 1, 1, 1); MODULE_DEPEND(uvisor, usb, 1, 1, 1); MODULE_VERSION(uvisor, 1); USB_PNP_HOST_INFO(uvisor_devs); static int uvisor_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UVISOR_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bIfaceIndex != UVISOR_IFACE_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(uvisor_devs, sizeof(uvisor_devs), uaa)); } static int uvisor_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uvisor_softc *sc = device_get_softc(dev); struct usb_config uvisor_config_copy[UVISOR_N_TRANSFER]; int error; DPRINTF("sc=%p\n", sc); memcpy(uvisor_config_copy, uvisor_config, sizeof(uvisor_config_copy)); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uvisor", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_udev = uaa->device; /* configure the device */ sc->sc_flag = USB_GET_DRIVER_INFO(uaa); sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = UVISOR_IFACE_INDEX; error = uvisor_init(sc, uaa->device, uvisor_config_copy); if (error) { DPRINTF("init failed, error=%s\n", usbd_errstr(error)); goto detach; } error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, sc->sc_xfer, uvisor_config_copy, UVISOR_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("could not allocate all pipes\n"); goto detach; } error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uvisor_callback, &sc->sc_mtx); if (error) { DPRINTF("ucom_attach failed\n"); goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: uvisor_detach(dev); return (ENXIO); } static int uvisor_detach(device_t dev) { struct uvisor_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UVISOR_N_TRANSFER); device_claim_softc(dev); uvisor_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uvisor); static void uvisor_free_softc(struct uvisor_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uvisor_free(struct ucom_softc *ucom) { uvisor_free_softc(ucom->sc_parent); } static usb_error_t uvisor_init(struct uvisor_softc *sc, struct usb_device *udev, struct usb_config *config) { usb_error_t err = 0; struct usb_device_request req; struct uvisor_connection_info coninfo; struct uvisor_palm_connection_info pconinfo; uint16_t actlen; uint8_t buffer[256]; if (sc->sc_flag & UVISOR_FLAG_VISOR) { DPRINTF("getting connection info\n"); req.bmRequestType = UT_READ_VENDOR_ENDPOINT; req.bRequest = UVISOR_GET_CONNECTION_INFORMATION; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); err = usbd_do_request_flags(udev, NULL, &req, &coninfo, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (err) { goto done; } } #ifdef USB_DEBUG if (sc->sc_flag & UVISOR_FLAG_VISOR) { uint16_t i, np; const char *desc; np = UGETW(coninfo.num_ports); if (np > UVISOR_MAX_CONN) { np = UVISOR_MAX_CONN; } DPRINTF("Number of ports: %d\n", np); for (i = 0; i < np; ++i) { switch (coninfo.connections[i].port_function_id) { case UVISOR_FUNCTION_GENERIC: desc = "Generic"; break; case UVISOR_FUNCTION_DEBUGGER: desc = "Debugger"; break; case UVISOR_FUNCTION_HOTSYNC: desc = "HotSync"; break; case UVISOR_FUNCTION_REMOTE_FILE_SYS: desc = "Remote File System"; break; default: desc = "unknown"; break; } DPRINTF("Port %d is for %s\n", coninfo.connections[i].port, desc); } } #endif if (sc->sc_flag & UVISOR_FLAG_PALM4) { uint8_t port; /* Palm OS 4.0 Hack */ req.bmRequestType = UT_READ_VENDOR_ENDPOINT; req.bRequest = UVISOR_GET_PALM_INFORMATION; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); err = usbd_do_request_flags (udev, NULL, &req, &pconinfo, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (err) { goto done; } if (actlen < 12) { DPRINTF("too little data\n"); err = USB_ERR_INVAL; goto done; } if (pconinfo.endpoint_numbers_different) { port = pconinfo.connections[0].end_point_info; config[0].endpoint = (port & 0xF); /* output */ config[1].endpoint = (port >> 4); /* input */ } else { port = pconinfo.connections[0].port; config[0].endpoint = (port & 0xF); /* output */ config[1].endpoint = (port & 0xF); /* input */ } #if 0 req.bmRequestType = UT_READ_VENDOR_ENDPOINT; req.bRequest = UVISOR_GET_PALM_INFORMATION; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); err = usbd_do_request(udev, &req, buffer); if (err) { goto done; } #endif } if (sc->sc_flag & UVISOR_FLAG_PALM35) { /* get the config number */ DPRINTF("getting config info\n"); req.bmRequestType = UT_READ; req.bRequest = UR_GET_CONFIG; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 1); err = usbd_do_request(udev, NULL, &req, buffer); if (err) { goto done; } /* get the interface number */ DPRINTF("get the interface number\n"); req.bmRequestType = UT_READ_DEVICE; req.bRequest = UR_GET_INTERFACE; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 1); err = usbd_do_request(udev, NULL, &req, buffer); if (err) { goto done; } } #if 0 uWord wAvail; DPRINTF("getting available bytes\n"); req.bmRequestType = UT_READ_VENDOR_ENDPOINT; req.bRequest = UVISOR_REQUEST_BYTES_AVAILABLE; USETW(req.wValue, 0); USETW(req.wIndex, 5); USETW(req.wLength, sizeof(wAvail)); err = usbd_do_request(udev, NULL, &req, &wAvail); if (err) { goto done; } DPRINTF("avail=%d\n", UGETW(wAvail)); #endif DPRINTF("done\n"); done: return (err); } static void uvisor_cfg_open(struct ucom_softc *ucom) { return; } static void uvisor_cfg_close(struct ucom_softc *ucom) { struct uvisor_softc *sc = ucom->sc_parent; uint8_t buffer[UVISOR_CONNECTION_INFO_SIZE]; struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_READ_VENDOR_ENDPOINT; /* XXX read? */ req.bRequest = UVISOR_CLOSE_NOTIFICATION; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, buffer, 0, 1000); if (err) { DPRINTFN(0, "close notification failed, error=%s\n", usbd_errstr(err)); } } static void uvisor_start_read(struct ucom_softc *ucom) { struct uvisor_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_RD]); } static void uvisor_stop_read(struct ucom_softc *ucom) { struct uvisor_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_RD]); } static void uvisor_start_write(struct ucom_softc *ucom) { struct uvisor_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_WR]); } static void uvisor_stop_write(struct ucom_softc *ucom) { struct uvisor_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_WR]); } static void uvisor_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uvisor_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; uint8_t x; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: for (x = 0; x != UVISOROFRAMES; x++) { usbd_xfer_set_frame_offset(xfer, x * UVISOROBUFSIZE, x); pc = usbd_xfer_get_frame(xfer, x); if (ucom_get_data(&sc->sc_ucom, pc, 0, UVISOROBUFSIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, x, actlen); } else { break; } } /* check for data */ if (x != 0) { usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void uvisor_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uvisor_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } Index: head/sys/dev/usb/serial/uvscom.c =================================================================== --- head/sys/dev/usb/serial/uvscom.c (revision 357971) +++ head/sys/dev/usb/serial/uvscom.c (revision 357972) @@ -1,773 +1,774 @@ /* $NetBSD: usb/uvscom.c,v 1.1 2002/03/19 15:08:42 augustss Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama . * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ /* * uvscom: SUNTAC Slipper U VS-10U driver. * Slipper U is a PC Card to USB converter for data communication card * adapter. It supports DDI Pocket's Air H" C@rd, C@rd H" 64, NTT's P-in, * P-in m@ater and various data communication card adapters. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uvscom_debug #include #include #include #ifdef USB_DEBUG static int uvscom_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uvscom, CTLFLAG_RW, 0, "USB uvscom"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uvscom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB uvscom"); SYSCTL_INT(_hw_usb_uvscom, OID_AUTO, debug, CTLFLAG_RWTUN, &uvscom_debug, 0, "Debug level"); #endif #define UVSCOM_MODVER 1 /* module version */ #define UVSCOM_CONFIG_INDEX 0 #define UVSCOM_IFACE_INDEX 0 /* Request */ #define UVSCOM_SET_SPEED 0x10 #define UVSCOM_LINE_CTL 0x11 #define UVSCOM_SET_PARAM 0x12 #define UVSCOM_READ_STATUS 0xd0 #define UVSCOM_SHUTDOWN 0xe0 /* UVSCOM_SET_SPEED parameters */ #define UVSCOM_SPEED_150BPS 0x00 #define UVSCOM_SPEED_300BPS 0x01 #define UVSCOM_SPEED_600BPS 0x02 #define UVSCOM_SPEED_1200BPS 0x03 #define UVSCOM_SPEED_2400BPS 0x04 #define UVSCOM_SPEED_4800BPS 0x05 #define UVSCOM_SPEED_9600BPS 0x06 #define UVSCOM_SPEED_19200BPS 0x07 #define UVSCOM_SPEED_38400BPS 0x08 #define UVSCOM_SPEED_57600BPS 0x09 #define UVSCOM_SPEED_115200BPS 0x0a /* UVSCOM_LINE_CTL parameters */ #define UVSCOM_BREAK 0x40 #define UVSCOM_RTS 0x02 #define UVSCOM_DTR 0x01 #define UVSCOM_LINE_INIT 0x08 /* UVSCOM_SET_PARAM parameters */ #define UVSCOM_DATA_MASK 0x03 #define UVSCOM_DATA_BIT_8 0x03 #define UVSCOM_DATA_BIT_7 0x02 #define UVSCOM_DATA_BIT_6 0x01 #define UVSCOM_DATA_BIT_5 0x00 #define UVSCOM_STOP_MASK 0x04 #define UVSCOM_STOP_BIT_2 0x04 #define UVSCOM_STOP_BIT_1 0x00 #define UVSCOM_PARITY_MASK 0x18 #define UVSCOM_PARITY_EVEN 0x18 #define UVSCOM_PARITY_ODD 0x08 #define UVSCOM_PARITY_NONE 0x00 /* Status bits */ #define UVSCOM_TXRDY 0x04 #define UVSCOM_RXRDY 0x01 #define UVSCOM_DCD 0x08 #define UVSCOM_NOCARD 0x04 #define UVSCOM_DSR 0x02 #define UVSCOM_CTS 0x01 #define UVSCOM_USTAT_MASK (UVSCOM_NOCARD | UVSCOM_DSR | UVSCOM_CTS) #define UVSCOM_BULK_BUF_SIZE 1024 /* bytes */ enum { UVSCOM_BULK_DT_WR, UVSCOM_BULK_DT_RD, UVSCOM_INTR_DT_RD, UVSCOM_N_TRANSFER, }; struct uvscom_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UVSCOM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint16_t sc_line; /* line control register */ uint8_t sc_iface_no; /* interface number */ uint8_t sc_iface_index; /* interface index */ uint8_t sc_lsr; /* local status register */ uint8_t sc_msr; /* uvscom status register */ uint8_t sc_unit_status; /* unit status */ }; /* prototypes */ static device_probe_t uvscom_probe; static device_attach_t uvscom_attach; static device_detach_t uvscom_detach; static void uvscom_free_softc(struct uvscom_softc *); static usb_callback_t uvscom_write_callback; static usb_callback_t uvscom_read_callback; static usb_callback_t uvscom_intr_callback; static void uvscom_free(struct ucom_softc *); static void uvscom_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uvscom_cfg_set_rts(struct ucom_softc *, uint8_t); static void uvscom_cfg_set_break(struct ucom_softc *, uint8_t); static int uvscom_pre_param(struct ucom_softc *, struct termios *); static void uvscom_cfg_param(struct ucom_softc *, struct termios *); static int uvscom_pre_open(struct ucom_softc *); static void uvscom_cfg_open(struct ucom_softc *); static void uvscom_cfg_close(struct ucom_softc *); static void uvscom_start_read(struct ucom_softc *); static void uvscom_stop_read(struct ucom_softc *); static void uvscom_start_write(struct ucom_softc *); static void uvscom_stop_write(struct ucom_softc *); static void uvscom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static void uvscom_cfg_write(struct uvscom_softc *, uint8_t, uint16_t); static uint16_t uvscom_cfg_read_status(struct uvscom_softc *); static void uvscom_poll(struct ucom_softc *ucom); static const struct usb_config uvscom_config[UVSCOM_N_TRANSFER] = { [UVSCOM_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UVSCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &uvscom_write_callback, }, [UVSCOM_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UVSCOM_BULK_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uvscom_read_callback, }, [UVSCOM_INTR_DT_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &uvscom_intr_callback, }, }; static const struct ucom_callback uvscom_callback = { .ucom_cfg_get_status = &uvscom_cfg_get_status, .ucom_cfg_set_dtr = &uvscom_cfg_set_dtr, .ucom_cfg_set_rts = &uvscom_cfg_set_rts, .ucom_cfg_set_break = &uvscom_cfg_set_break, .ucom_cfg_param = &uvscom_cfg_param, .ucom_cfg_open = &uvscom_cfg_open, .ucom_cfg_close = &uvscom_cfg_close, .ucom_pre_open = &uvscom_pre_open, .ucom_pre_param = &uvscom_pre_param, .ucom_start_read = &uvscom_start_read, .ucom_stop_read = &uvscom_stop_read, .ucom_start_write = &uvscom_start_write, .ucom_stop_write = &uvscom_stop_write, .ucom_poll = &uvscom_poll, .ucom_free = &uvscom_free, }; static const STRUCT_USB_HOST_ID uvscom_devs[] = { /* SUNTAC U-Cable type A4 */ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4, 0)}, /* SUNTAC U-Cable type D2 */ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L, 0)}, /* SUNTAC Ir-Trinity */ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U, 0)}, /* SUNTAC U-Cable type P1 */ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1, 0)}, /* SUNTAC Slipper U */ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U, 0)}, }; static device_method_t uvscom_methods[] = { DEVMETHOD(device_probe, uvscom_probe), DEVMETHOD(device_attach, uvscom_attach), DEVMETHOD(device_detach, uvscom_detach), DEVMETHOD_END }; static devclass_t uvscom_devclass; static driver_t uvscom_driver = { .name = "uvscom", .methods = uvscom_methods, .size = sizeof(struct uvscom_softc), }; DRIVER_MODULE(uvscom, uhub, uvscom_driver, uvscom_devclass, NULL, 0); MODULE_DEPEND(uvscom, ucom, 1, 1, 1); MODULE_DEPEND(uvscom, usb, 1, 1, 1); MODULE_VERSION(uvscom, UVSCOM_MODVER); USB_PNP_HOST_INFO(uvscom_devs); static int uvscom_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UVSCOM_CONFIG_INDEX) { return (ENXIO); } if (uaa->info.bIfaceIndex != UVSCOM_IFACE_INDEX) { return (ENXIO); } return (usbd_lookup_id_by_uaa(uvscom_devs, sizeof(uvscom_devs), uaa)); } static int uvscom_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uvscom_softc *sc = device_get_softc(dev); int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uvscom", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_udev = uaa->device; DPRINTF("sc=%p\n", sc); sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = UVSCOM_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, sc->sc_xfer, uvscom_config, UVSCOM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("could not allocate all USB transfers!\n"); goto detach; } sc->sc_line = UVSCOM_LINE_INIT; /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uvscom_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); /* start interrupt pipe */ mtx_lock(&sc->sc_mtx); usbd_transfer_start(sc->sc_xfer[UVSCOM_INTR_DT_RD]); mtx_unlock(&sc->sc_mtx); return (0); detach: uvscom_detach(dev); return (ENXIO); } static int uvscom_detach(device_t dev) { struct uvscom_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); /* stop interrupt pipe */ if (sc->sc_xfer[UVSCOM_INTR_DT_RD]) usbd_transfer_stop(sc->sc_xfer[UVSCOM_INTR_DT_RD]); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UVSCOM_N_TRANSFER); device_claim_softc(dev); uvscom_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uvscom); static void uvscom_free_softc(struct uvscom_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uvscom_free(struct ucom_softc *ucom) { uvscom_free_softc(ucom->sc_parent); } static void uvscom_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uvscom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UVSCOM_BULK_BUF_SIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uvscom_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uvscom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uvscom_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uvscom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[2]; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen >= 2) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, sizeof(buf)); sc->sc_lsr = 0; sc->sc_msr = 0; sc->sc_unit_status = buf[1]; if (buf[0] & UVSCOM_TXRDY) { sc->sc_lsr |= ULSR_TXRDY; } if (buf[0] & UVSCOM_RXRDY) { sc->sc_lsr |= ULSR_RXRDY; } if (buf[1] & UVSCOM_CTS) { sc->sc_msr |= SER_CTS; } if (buf[1] & UVSCOM_DSR) { sc->sc_msr |= SER_DSR; } if (buf[1] & UVSCOM_DCD) { sc->sc_msr |= SER_DCD; } /* * the UCOM layer will ignore this call if the TTY * device is closed! */ ucom_status_change(&sc->sc_ucom); } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uvscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uvscom_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line |= UVSCOM_DTR; else sc->sc_line &= ~UVSCOM_DTR; uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); } static void uvscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uvscom_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line |= UVSCOM_RTS; else sc->sc_line &= ~UVSCOM_RTS; uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); } static void uvscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uvscom_softc *sc = ucom->sc_parent; DPRINTF("onoff = %d\n", onoff); if (onoff) sc->sc_line |= UVSCOM_BREAK; else sc->sc_line &= ~UVSCOM_BREAK; uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); } static int uvscom_pre_param(struct ucom_softc *ucom, struct termios *t) { switch (t->c_ospeed) { case B150: case B300: case B600: case B1200: case B2400: case B4800: case B9600: case B19200: case B38400: case B57600: case B115200: default: return (EINVAL); } return (0); } static void uvscom_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uvscom_softc *sc = ucom->sc_parent; uint16_t value; DPRINTF("\n"); switch (t->c_ospeed) { case B150: value = UVSCOM_SPEED_150BPS; break; case B300: value = UVSCOM_SPEED_300BPS; break; case B600: value = UVSCOM_SPEED_600BPS; break; case B1200: value = UVSCOM_SPEED_1200BPS; break; case B2400: value = UVSCOM_SPEED_2400BPS; break; case B4800: value = UVSCOM_SPEED_4800BPS; break; case B9600: value = UVSCOM_SPEED_9600BPS; break; case B19200: value = UVSCOM_SPEED_19200BPS; break; case B38400: value = UVSCOM_SPEED_38400BPS; break; case B57600: value = UVSCOM_SPEED_57600BPS; break; case B115200: value = UVSCOM_SPEED_115200BPS; break; default: return; } uvscom_cfg_write(sc, UVSCOM_SET_SPEED, value); value = 0; if (t->c_cflag & CSTOPB) { value |= UVSCOM_STOP_BIT_2; } if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) { value |= UVSCOM_PARITY_ODD; } else { value |= UVSCOM_PARITY_EVEN; } } else { value |= UVSCOM_PARITY_NONE; } switch (t->c_cflag & CSIZE) { case CS5: value |= UVSCOM_DATA_BIT_5; break; case CS6: value |= UVSCOM_DATA_BIT_6; break; case CS7: value |= UVSCOM_DATA_BIT_7; break; default: case CS8: value |= UVSCOM_DATA_BIT_8; break; } uvscom_cfg_write(sc, UVSCOM_SET_PARAM, value); } static int uvscom_pre_open(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; DPRINTF("sc = %p\n", sc); /* check if PC card was inserted */ if (sc->sc_unit_status & UVSCOM_NOCARD) { DPRINTF("no PC card!\n"); return (ENXIO); } return (0); } static void uvscom_cfg_open(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; DPRINTF("sc = %p\n", sc); uvscom_cfg_read_status(sc); } static void uvscom_cfg_close(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; DPRINTF("sc=%p\n", sc); uvscom_cfg_write(sc, UVSCOM_SHUTDOWN, 0); } static void uvscom_start_read(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_RD]); } static void uvscom_stop_read(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_RD]); } static void uvscom_start_write(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_WR]); } static void uvscom_stop_write(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_WR]); } static void uvscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uvscom_softc *sc = ucom->sc_parent; *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static void uvscom_cfg_write(struct uvscom_softc *sc, uint8_t index, uint16_t value) { struct usb_device_request req; usb_error_t err; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = index; USETW(req.wValue, value); USETW(req.wIndex, 0); USETW(req.wLength, 0); err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); if (err) { DPRINTFN(0, "device request failed, err=%s " "(ignored)\n", usbd_errstr(err)); } } static uint16_t uvscom_cfg_read_status(struct uvscom_softc *sc) { struct usb_device_request req; usb_error_t err; uint8_t data[2]; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UVSCOM_READ_STATUS; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 2); err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, data, 0, 1000); if (err) { DPRINTFN(0, "device request failed, err=%s " "(ignored)\n", usbd_errstr(err)); } return (data[0] | (data[1] << 8)); } static void uvscom_poll(struct ucom_softc *ucom) { struct uvscom_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UVSCOM_N_TRANSFER); } Index: head/sys/dev/usb/storage/cfumass.c =================================================================== --- head/sys/dev/usb/storage/cfumass.c (revision 357971) +++ head/sys/dev/usb/storage/cfumass.c (revision 357972) @@ -1,1000 +1,1000 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2016 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ /* * USB Mass Storage Class Bulk-Only (BBB) Transport target. * * http://www.usb.org/developers/docs/devclass_docs/usbmassbulk_10.pdf * * This code implements the USB Mass Storage frontend driver for the CAM * Target Layer (ctl(4)) subsystem. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include "usb_if.h" #include #include #include #include #include #include #include #include #include #include #include -SYSCTL_NODE(_hw_usb, OID_AUTO, cfumass, CTLFLAG_RW, 0, +SYSCTL_NODE(_hw_usb, OID_AUTO, cfumass, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "CAM Target Layer USB Mass Storage Frontend"); static int debug = 1; SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 1, "Enable debug messages"); static int max_lun = 0; SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, max_lun, CTLFLAG_RWTUN, &max_lun, 1, "Maximum advertised LUN number"); static int ignore_stop = 1; SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, ignore_stop, CTLFLAG_RWTUN, &ignore_stop, 1, "Ignore START STOP UNIT with START and LOEJ bits cleared"); /* * The driver uses a single, global CTL port. It could create its ports * in cfumass_attach() instead, but that would make it impossible to specify * "port cfumass0" in ctl.conf(5), as the port generally wouldn't exist * at the time ctld(8) gets run. */ struct ctl_port cfumass_port; bool cfumass_port_online; volatile u_int cfumass_refcount; #ifndef CFUMASS_BULK_SIZE #define CFUMASS_BULK_SIZE (1U << 17) /* bytes */ #endif /* * USB transfer definitions. */ #define CFUMASS_T_COMMAND 0 #define CFUMASS_T_DATA_OUT 1 #define CFUMASS_T_DATA_IN 2 #define CFUMASS_T_STATUS 3 #define CFUMASS_T_MAX 4 /* * USB interface specific control requests. */ #define UR_RESET 0xff /* Bulk-Only Mass Storage Reset */ #define UR_GET_MAX_LUN 0xfe /* Get Max LUN */ /* * Command Block Wrapper. */ struct cfumass_cbw_t { uDWord dCBWSignature; #define CBWSIGNATURE 0x43425355 /* "USBC" */ uDWord dCBWTag; uDWord dCBWDataTransferLength; uByte bCBWFlags; #define CBWFLAGS_OUT 0x00 #define CBWFLAGS_IN 0x80 uByte bCBWLUN; uByte bCDBLength; #define CBWCBLENGTH 16 uByte CBWCB[CBWCBLENGTH]; } __packed; #define CFUMASS_CBW_SIZE 31 CTASSERT(sizeof(struct cfumass_cbw_t) == CFUMASS_CBW_SIZE); /* * Command Status Wrapper. */ struct cfumass_csw_t { uDWord dCSWSignature; #define CSWSIGNATURE 0x53425355 /* "USBS" */ uDWord dCSWTag; uDWord dCSWDataResidue; uByte bCSWStatus; #define CSWSTATUS_GOOD 0x0 #define CSWSTATUS_FAILED 0x1 #define CSWSTATUS_PHASE 0x2 } __packed; #define CFUMASS_CSW_SIZE 13 CTASSERT(sizeof(struct cfumass_csw_t) == CFUMASS_CSW_SIZE); struct cfumass_softc { device_t sc_dev; struct usb_device *sc_udev; struct usb_xfer *sc_xfer[CFUMASS_T_MAX]; struct cfumass_cbw_t *sc_cbw; struct cfumass_csw_t *sc_csw; struct mtx sc_mtx; int sc_online; int sc_ctl_initid; /* * This is used to communicate between CTL callbacks * and USB callbacks; basically, it holds the state * for the current command ("the" command, since there * is no queueing in USB Mass Storage). */ bool sc_current_stalled; /* * The following are set upon receiving a SCSI command. */ int sc_current_tag; int sc_current_transfer_length; int sc_current_flags; /* * The following are set in ctl_datamove(). */ int sc_current_residue; union ctl_io *sc_ctl_io; /* * The following is set in cfumass_done(). */ int sc_current_status; /* * Number of requests queued to CTL. */ volatile u_int sc_queued; }; /* * USB interface. */ static device_probe_t cfumass_probe; static device_attach_t cfumass_attach; static device_detach_t cfumass_detach; static device_suspend_t cfumass_suspend; static device_resume_t cfumass_resume; static usb_handle_request_t cfumass_handle_request; static usb_callback_t cfumass_t_command_callback; static usb_callback_t cfumass_t_data_callback; static usb_callback_t cfumass_t_status_callback; static device_method_t cfumass_methods[] = { /* USB interface. */ DEVMETHOD(usb_handle_request, cfumass_handle_request), /* Device interface. */ DEVMETHOD(device_probe, cfumass_probe), DEVMETHOD(device_attach, cfumass_attach), DEVMETHOD(device_detach, cfumass_detach), DEVMETHOD(device_suspend, cfumass_suspend), DEVMETHOD(device_resume, cfumass_resume), DEVMETHOD_END }; static driver_t cfumass_driver = { .name = "cfumass", .methods = cfumass_methods, .size = sizeof(struct cfumass_softc), }; static devclass_t cfumass_devclass; DRIVER_MODULE(cfumass, uhub, cfumass_driver, cfumass_devclass, NULL, 0); MODULE_VERSION(cfumass, 0); MODULE_DEPEND(cfumass, usb, 1, 1, 1); MODULE_DEPEND(cfumass, usb_template, 1, 1, 1); static struct usb_config cfumass_config[CFUMASS_T_MAX] = { [CFUMASS_T_COMMAND] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(struct cfumass_cbw_t), .callback = &cfumass_t_command_callback, .usb_mode = USB_MODE_DEVICE, }, [CFUMASS_T_DATA_OUT] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = CFUMASS_BULK_SIZE, .flags = {.proxy_buffer = 1, .short_xfer_ok = 1, .ext_buffer = 1}, .callback = &cfumass_t_data_callback, .usb_mode = USB_MODE_DEVICE, }, [CFUMASS_T_DATA_IN] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = CFUMASS_BULK_SIZE, .flags = {.proxy_buffer = 1, .short_xfer_ok = 1, .ext_buffer = 1}, .callback = &cfumass_t_data_callback, .usb_mode = USB_MODE_DEVICE, }, [CFUMASS_T_STATUS] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(struct cfumass_csw_t), .flags = {.short_xfer_ok = 1}, .callback = &cfumass_t_status_callback, .usb_mode = USB_MODE_DEVICE, }, }; /* * CTL frontend interface. */ static int cfumass_init(void); static int cfumass_shutdown(void); static void cfumass_online(void *arg); static void cfumass_offline(void *arg); static void cfumass_datamove(union ctl_io *io); static void cfumass_done(union ctl_io *io); static struct ctl_frontend cfumass_frontend = { .name = "umass", .init = cfumass_init, .shutdown = cfumass_shutdown, }; CTL_FRONTEND_DECLARE(ctlcfumass, cfumass_frontend); #define CFUMASS_DEBUG(S, X, ...) \ do { \ if (debug > 1) { \ device_printf(S->sc_dev, "%s: " X "\n", \ __func__, ## __VA_ARGS__); \ } \ } while (0) #define CFUMASS_WARN(S, X, ...) \ do { \ if (debug > 0) { \ device_printf(S->sc_dev, "WARNING: %s: " X "\n",\ __func__, ## __VA_ARGS__); \ } \ } while (0) #define CFUMASS_LOCK(X) mtx_lock(&X->sc_mtx) #define CFUMASS_UNLOCK(X) mtx_unlock(&X->sc_mtx) static void cfumass_transfer_start(struct cfumass_softc *sc, uint8_t xfer_index); static void cfumass_terminate(struct cfumass_softc *sc); static int cfumass_probe(device_t dev) { struct usb_attach_arg *uaa; struct usb_interface_descriptor *id; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_DEVICE) return (ENXIO); /* * Check for a compliant device. */ id = usbd_get_interface_descriptor(uaa->iface); if ((id == NULL) || (id->bInterfaceClass != UICLASS_MASS) || (id->bInterfaceSubClass != UISUBCLASS_SCSI) || (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) { return (ENXIO); } return (BUS_PROBE_GENERIC); } static int cfumass_attach(device_t dev) { struct cfumass_softc *sc; struct usb_attach_arg *uaa; int error; sc = device_get_softc(dev); uaa = device_get_ivars(dev); sc->sc_dev = dev; sc->sc_udev = uaa->device; CFUMASS_DEBUG(sc, "go"); usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "cfumass", NULL, MTX_DEF); refcount_acquire(&cfumass_refcount); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, cfumass_config, CFUMASS_T_MAX, sc, &sc->sc_mtx); if (error != 0) { CFUMASS_WARN(sc, "usbd_transfer_setup() failed: %s", usbd_errstr(error)); refcount_release(&cfumass_refcount); return (ENXIO); } sc->sc_cbw = usbd_xfer_get_frame_buffer(sc->sc_xfer[CFUMASS_T_COMMAND], 0); sc->sc_csw = usbd_xfer_get_frame_buffer(sc->sc_xfer[CFUMASS_T_STATUS], 0); sc->sc_ctl_initid = ctl_add_initiator(&cfumass_port, -1, 0, NULL); if (sc->sc_ctl_initid < 0) { CFUMASS_WARN(sc, "ctl_add_initiator() failed with error %d", sc->sc_ctl_initid); usbd_transfer_unsetup(sc->sc_xfer, CFUMASS_T_MAX); refcount_release(&cfumass_refcount); return (ENXIO); } refcount_init(&sc->sc_queued, 0); CFUMASS_LOCK(sc); cfumass_transfer_start(sc, CFUMASS_T_COMMAND); CFUMASS_UNLOCK(sc); return (0); } static int cfumass_detach(device_t dev) { struct cfumass_softc *sc; int error; sc = device_get_softc(dev); CFUMASS_DEBUG(sc, "go"); CFUMASS_LOCK(sc); cfumass_terminate(sc); CFUMASS_UNLOCK(sc); usbd_transfer_unsetup(sc->sc_xfer, CFUMASS_T_MAX); if (sc->sc_ctl_initid != -1) { error = ctl_remove_initiator(&cfumass_port, sc->sc_ctl_initid); if (error != 0) { CFUMASS_WARN(sc, "ctl_remove_initiator() failed " "with error %d", error); } sc->sc_ctl_initid = -1; } mtx_destroy(&sc->sc_mtx); refcount_release(&cfumass_refcount); return (0); } static int cfumass_suspend(device_t dev) { struct cfumass_softc *sc; sc = device_get_softc(dev); CFUMASS_DEBUG(sc, "go"); return (0); } static int cfumass_resume(device_t dev) { struct cfumass_softc *sc; sc = device_get_softc(dev); CFUMASS_DEBUG(sc, "go"); return (0); } static void cfumass_transfer_start(struct cfumass_softc *sc, uint8_t xfer_index) { usbd_transfer_start(sc->sc_xfer[xfer_index]); } static void cfumass_transfer_stop_and_drain(struct cfumass_softc *sc, uint8_t xfer_index) { usbd_transfer_stop(sc->sc_xfer[xfer_index]); CFUMASS_UNLOCK(sc); usbd_transfer_drain(sc->sc_xfer[xfer_index]); CFUMASS_LOCK(sc); } static void cfumass_terminate(struct cfumass_softc *sc) { int last; for (;;) { cfumass_transfer_stop_and_drain(sc, CFUMASS_T_COMMAND); cfumass_transfer_stop_and_drain(sc, CFUMASS_T_DATA_IN); cfumass_transfer_stop_and_drain(sc, CFUMASS_T_DATA_OUT); if (sc->sc_ctl_io != NULL) { CFUMASS_DEBUG(sc, "terminating CTL transfer"); ctl_set_data_phase_error(&sc->sc_ctl_io->scsiio); sc->sc_ctl_io->scsiio.be_move_done(sc->sc_ctl_io); sc->sc_ctl_io = NULL; } cfumass_transfer_stop_and_drain(sc, CFUMASS_T_STATUS); refcount_acquire(&sc->sc_queued); last = refcount_release(&sc->sc_queued); if (last != 0) break; CFUMASS_DEBUG(sc, "%d CTL tasks pending", sc->sc_queued); msleep(__DEVOLATILE(void *, &sc->sc_queued), &sc->sc_mtx, 0, "cfumass_reset", hz / 100); } } static int cfumass_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { static uint8_t max_lun_tmp; struct cfumass_softc *sc; const struct usb_device_request *req; uint8_t is_complete; sc = device_get_softc(dev); req = preq; is_complete = *pstate; CFUMASS_DEBUG(sc, "go"); if (is_complete) return (ENXIO); if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UR_RESET)) { CFUMASS_WARN(sc, "received Bulk-Only Mass Storage Reset"); *plen = 0; CFUMASS_LOCK(sc); cfumass_terminate(sc); cfumass_transfer_start(sc, CFUMASS_T_COMMAND); CFUMASS_UNLOCK(sc); CFUMASS_DEBUG(sc, "Bulk-Only Mass Storage Reset done"); return (0); } if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && (req->bRequest == UR_GET_MAX_LUN)) { CFUMASS_DEBUG(sc, "received Get Max LUN"); if (offset == 0) { *plen = 1; /* * The protocol doesn't support LUN numbers higher * than 15. Also, some initiators (namely Windows XP * SP3 Version 2002) can't properly query the number * of LUNs, resulting in inaccessible "fake" ones - thus * the default limit of one LUN. */ if (max_lun < 0 || max_lun > 15) { CFUMASS_WARN(sc, "invalid hw.usb.cfumass.max_lun, must be " "between 0 and 15; defaulting to 0"); max_lun_tmp = 0; } else { max_lun_tmp = max_lun; } *pptr = &max_lun_tmp; } else { *plen = 0; } return (0); } return (ENXIO); } static int cfumass_quirk(struct cfumass_softc *sc, unsigned char *cdb, int cdb_len) { struct scsi_start_stop_unit *sssu; switch (cdb[0]) { case START_STOP_UNIT: /* * Some initiators - eg OSX, Darwin Kernel Version 15.6.0, * root:xnu-3248.60.11~2/RELEASE_X86_64 - attempt to stop * the unit on eject, but fail to start it when it's plugged * back. Just ignore the command. */ if (cdb_len < sizeof(*sssu)) { CFUMASS_DEBUG(sc, "received START STOP UNIT with " "bCDBLength %d, should be %zd", cdb_len, sizeof(*sssu)); break; } sssu = (struct scsi_start_stop_unit *)cdb; if ((sssu->how & SSS_PC_MASK) != 0) break; if ((sssu->how & SSS_START) != 0) break; if ((sssu->how & SSS_LOEJ) != 0) break; if (ignore_stop == 0) { break; } else if (ignore_stop == 1) { CFUMASS_WARN(sc, "ignoring START STOP UNIT request"); } else { CFUMASS_DEBUG(sc, "ignoring START STOP UNIT request"); } sc->sc_current_status = 0; cfumass_transfer_start(sc, CFUMASS_T_STATUS); return (1); default: break; } return (0); } static void cfumass_t_command_callback(struct usb_xfer *xfer, usb_error_t usb_error) { struct cfumass_softc *sc; uint32_t signature; union ctl_io *io; int error = 0; sc = usbd_xfer_softc(xfer); KASSERT(sc->sc_ctl_io == NULL, ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED"); signature = UGETDW(sc->sc_cbw->dCBWSignature); if (signature != CBWSIGNATURE) { CFUMASS_WARN(sc, "wrong dCBWSignature 0x%08x, " "should be 0x%08x", signature, CBWSIGNATURE); break; } if (sc->sc_cbw->bCDBLength <= 0 || sc->sc_cbw->bCDBLength > sizeof(sc->sc_cbw->CBWCB)) { CFUMASS_WARN(sc, "invalid bCDBLength %d, should be <= %zd", sc->sc_cbw->bCDBLength, sizeof(sc->sc_cbw->CBWCB)); break; } sc->sc_current_stalled = false; sc->sc_current_status = 0; sc->sc_current_tag = UGETDW(sc->sc_cbw->dCBWTag); sc->sc_current_transfer_length = UGETDW(sc->sc_cbw->dCBWDataTransferLength); sc->sc_current_flags = sc->sc_cbw->bCBWFlags; /* * Make sure to report proper residue if the datamove wasn't * required, or wasn't called due to SCSI error. */ sc->sc_current_residue = sc->sc_current_transfer_length; if (cfumass_quirk(sc, sc->sc_cbw->CBWCB, sc->sc_cbw->bCDBLength) != 0) break; if (!cfumass_port_online) { CFUMASS_DEBUG(sc, "cfumass port is offline; stalling"); usbd_xfer_set_stall(xfer); break; } /* * Those CTL functions cannot be called with mutex held. */ CFUMASS_UNLOCK(sc); io = ctl_alloc_io(cfumass_port.ctl_pool_ref); ctl_zero_io(io); io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = sc; io->io_hdr.io_type = CTL_IO_SCSI; io->io_hdr.nexus.initid = sc->sc_ctl_initid; io->io_hdr.nexus.targ_port = cfumass_port.targ_port; io->io_hdr.nexus.targ_lun = ctl_decode_lun(sc->sc_cbw->bCBWLUN); io->scsiio.tag_num = UGETDW(sc->sc_cbw->dCBWTag); io->scsiio.tag_type = CTL_TAG_UNTAGGED; io->scsiio.cdb_len = sc->sc_cbw->bCDBLength; memcpy(io->scsiio.cdb, sc->sc_cbw->CBWCB, sc->sc_cbw->bCDBLength); refcount_acquire(&sc->sc_queued); error = ctl_queue(io); if (error != CTL_RETVAL_COMPLETE) { CFUMASS_WARN(sc, "ctl_queue() failed; error %d; stalling", error); ctl_free_io(io); refcount_release(&sc->sc_queued); CFUMASS_LOCK(sc); usbd_xfer_set_stall(xfer); break; } CFUMASS_LOCK(sc); break; case USB_ST_SETUP: tr_setup: CFUMASS_DEBUG(sc, "USB_ST_SETUP"); usbd_xfer_set_frame_len(xfer, 0, sizeof(*sc->sc_cbw)); usbd_transfer_submit(xfer); break; default: if (usb_error == USB_ERR_CANCELLED) { CFUMASS_DEBUG(sc, "USB_ERR_CANCELLED"); break; } CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); goto tr_setup; } } static void cfumass_t_data_callback(struct usb_xfer *xfer, usb_error_t usb_error) { struct cfumass_softc *sc = usbd_xfer_softc(xfer); union ctl_io *io = sc->sc_ctl_io; uint32_t max_bulk; struct ctl_sg_entry sg_entry, *sglist; int actlen, sumlen, sg_count; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED"); usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); sc->sc_current_residue -= actlen; io->scsiio.ext_data_filled += actlen; io->scsiio.kern_data_resid -= actlen; if (actlen < sumlen || sc->sc_current_residue == 0 || io->scsiio.kern_data_resid == 0) { sc->sc_ctl_io = NULL; io->scsiio.be_move_done(io); break; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: CFUMASS_DEBUG(sc, "USB_ST_SETUP"); if (io->scsiio.kern_sg_entries > 0) { sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; sg_count = io->scsiio.kern_sg_entries; } else { sglist = &sg_entry; sglist->addr = io->scsiio.kern_data_ptr; sglist->len = io->scsiio.kern_data_len; sg_count = 1; } sumlen = io->scsiio.ext_data_filled - io->scsiio.kern_rel_offset; while (sumlen >= sglist->len && sg_count > 0) { sumlen -= sglist->len; sglist++; sg_count--; } KASSERT(sg_count > 0, ("Run out of S/G list entries")); max_bulk = usbd_xfer_max_len(xfer); actlen = min(sglist->len - sumlen, max_bulk); actlen = min(actlen, sc->sc_current_transfer_length - io->scsiio.ext_data_filled); CFUMASS_DEBUG(sc, "requested %d, done %d, max_bulk %d, " "segment %zd => transfer %d", sc->sc_current_transfer_length, io->scsiio.ext_data_filled, max_bulk, sglist->len - sumlen, actlen); usbd_xfer_set_frame_data(xfer, 0, (uint8_t *)sglist->addr + sumlen, actlen); usbd_transfer_submit(xfer); break; default: if (usb_error == USB_ERR_CANCELLED) { CFUMASS_DEBUG(sc, "USB_ERR_CANCELLED"); break; } CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); goto tr_setup; } } static void cfumass_t_status_callback(struct usb_xfer *xfer, usb_error_t usb_error) { struct cfumass_softc *sc; sc = usbd_xfer_softc(xfer); KASSERT(sc->sc_ctl_io == NULL, ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED"); cfumass_transfer_start(sc, CFUMASS_T_COMMAND); break; case USB_ST_SETUP: tr_setup: CFUMASS_DEBUG(sc, "USB_ST_SETUP"); if (sc->sc_current_residue > 0 && !sc->sc_current_stalled) { CFUMASS_DEBUG(sc, "non-zero residue, stalling"); usbd_xfer_set_stall(xfer); sc->sc_current_stalled = true; } USETDW(sc->sc_csw->dCSWSignature, CSWSIGNATURE); USETDW(sc->sc_csw->dCSWTag, sc->sc_current_tag); USETDW(sc->sc_csw->dCSWDataResidue, sc->sc_current_residue); sc->sc_csw->bCSWStatus = sc->sc_current_status; usbd_xfer_set_frame_len(xfer, 0, sizeof(*sc->sc_csw)); usbd_transfer_submit(xfer); break; default: if (usb_error == USB_ERR_CANCELLED) { CFUMASS_DEBUG(sc, "USB_ERR_CANCELLED"); break; } CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); goto tr_setup; } } static void cfumass_online(void *arg __unused) { cfumass_port_online = true; } static void cfumass_offline(void *arg __unused) { cfumass_port_online = false; } static void cfumass_datamove(union ctl_io *io) { struct cfumass_softc *sc; sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; CFUMASS_DEBUG(sc, "go"); CFUMASS_LOCK(sc); KASSERT(sc->sc_ctl_io == NULL, ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); sc->sc_ctl_io = io; if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { /* * Verify that CTL wants us to send the data in the direction * expected by the initiator. */ if (sc->sc_current_flags != CBWFLAGS_IN) { CFUMASS_WARN(sc, "wrong bCBWFlags 0x%x, should be 0x%x", sc->sc_current_flags, CBWFLAGS_IN); goto fail; } cfumass_transfer_start(sc, CFUMASS_T_DATA_IN); } else { if (sc->sc_current_flags != CBWFLAGS_OUT) { CFUMASS_WARN(sc, "wrong bCBWFlags 0x%x, should be 0x%x", sc->sc_current_flags, CBWFLAGS_OUT); goto fail; } cfumass_transfer_start(sc, CFUMASS_T_DATA_OUT); } CFUMASS_UNLOCK(sc); return; fail: ctl_set_data_phase_error(&io->scsiio); io->scsiio.be_move_done(io); sc->sc_ctl_io = NULL; } static void cfumass_done(union ctl_io *io) { struct cfumass_softc *sc; sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; CFUMASS_DEBUG(sc, "go"); KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE), ("invalid CTL status %#x", io->io_hdr.status)); KASSERT(sc->sc_ctl_io == NULL, ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); if (io->io_hdr.io_type == CTL_IO_TASK && io->taskio.task_action == CTL_TASK_I_T_NEXUS_RESET) { /* * Implicit task termination has just completed; nothing to do. */ ctl_free_io(io); return; } /* * Do not return status for aborted commands. * There are exceptions, but none supported by CTL yet. */ if (((io->io_hdr.flags & CTL_FLAG_ABORT) && (io->io_hdr.flags & CTL_FLAG_ABORT_STATUS) == 0) || (io->io_hdr.flags & CTL_FLAG_STATUS_SENT)) { ctl_free_io(io); return; } if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) sc->sc_current_status = 0; else sc->sc_current_status = 1; /* XXX: How should we report BUSY, RESERVATION CONFLICT, etc? */ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SCSI_ERROR && io->scsiio.scsi_status == SCSI_STATUS_CHECK_COND) ctl_queue_sense(io); else ctl_free_io(io); CFUMASS_LOCK(sc); cfumass_transfer_start(sc, CFUMASS_T_STATUS); CFUMASS_UNLOCK(sc); refcount_release(&sc->sc_queued); } int cfumass_init(void) { int error; cfumass_port.frontend = &cfumass_frontend; cfumass_port.port_type = CTL_PORT_UMASS; cfumass_port.num_requested_ctl_io = 1; cfumass_port.port_name = "cfumass"; cfumass_port.physical_port = 0; cfumass_port.virtual_port = 0; cfumass_port.port_online = cfumass_online; cfumass_port.port_offline = cfumass_offline; cfumass_port.onoff_arg = NULL; cfumass_port.fe_datamove = cfumass_datamove; cfumass_port.fe_done = cfumass_done; cfumass_port.targ_port = -1; error = ctl_port_register(&cfumass_port); if (error != 0) { printf("%s: ctl_port_register() failed " "with error %d", __func__, error); } cfumass_port_online = true; refcount_init(&cfumass_refcount, 0); return (error); } int cfumass_shutdown(void) { int error; if (cfumass_refcount > 0) { if (debug > 1) { printf("%s: still have %u attachments; " "returning EBUSY\n", __func__, cfumass_refcount); } return (EBUSY); } error = ctl_port_deregister(&cfumass_port); if (error != 0) { printf("%s: ctl_port_deregister() failed " "with error %d\n", __func__, error); } return (error); } Index: head/sys/dev/usb/storage/umass.c =================================================================== --- head/sys/dev/usb/storage/umass.c (revision 357971) +++ head/sys/dev/usb/storage/umass.c (revision 357972) @@ -1,3019 +1,3020 @@ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1999 MAEKAWA Masahide , * Nick Hibma * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ * $NetBSD: umass.c,v 1.28 2000/04/02 23:46:53 augustss Exp $ */ /* Also already merged from NetBSD: * $NetBSD: umass.c,v 1.67 2001/11/25 19:05:22 augustss Exp $ * $NetBSD: umass.c,v 1.90 2002/11/04 19:17:33 pooka Exp $ * $NetBSD: umass.c,v 1.108 2003/11/07 17:03:25 wiz Exp $ * $NetBSD: umass.c,v 1.109 2003/12/04 13:57:31 keihan Exp $ */ /* * Universal Serial Bus Mass Storage Class specs: * http://www.usb.org/developers/devclass_docs/usb_msc_overview_1.2.pdf * http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf * http://www.usb.org/developers/devclass_docs/usb_msc_cbi_1.1.pdf * http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf */ /* * Ported to NetBSD by Lennart Augustsson . * Parts of the code written by Jason R. Thorpe . */ /* * The driver handles 3 Wire Protocols * - Command/Bulk/Interrupt (CBI) * - Command/Bulk/Interrupt with Command Completion Interrupt (CBI with CCI) * - Mass Storage Bulk-Only (BBB) * (BBB refers Bulk/Bulk/Bulk for Command/Data/Status phases) * * Over these wire protocols it handles the following command protocols * - SCSI * - UFI (floppy command set) * - 8070i (ATAPI) * * UFI and 8070i (ATAPI) are transformed versions of the SCSI command set. The * sc->sc_transform method is used to convert the commands into the appropriate * format (if at all necessary). For example, UFI requires all commands to be * 12 bytes in length amongst other things. * * The source code below is marked and can be split into a number of pieces * (in this order): * * - probe/attach/detach * - generic transfer routines * - BBB * - CBI * - CBI_I (in addition to functions from CBI) * - CAM (Common Access Method) * - SCSI * - UFI * - 8070i (ATAPI) * * The protocols are implemented using a state machine, for the transfers as * well as for the resets. The state machine is contained in umass_t_*_callback. * The state machine is started through either umass_command_start() or * umass_reset(). * * The reason for doing this is a) CAM performs a lot better this way and b) it * avoids using tsleep from interrupt context (for example after a failed * transfer). */ /* * The SCSI related part of this driver has been derived from the * dev/ppbus/vpo.c driver, by Nicolas Souchu (nsouch@FreeBSD.org). * * The CAM layer uses so called actions which are messages sent to the host * adapter for completion. The actions come in through umass_cam_action. The * appropriate block of routines is called depending on the transport protocol * in use. When the transfer has finished, these routines call * umass_cam_cb again to complete the CAM command. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #include #include #ifdef USB_DEBUG #define DIF(m, x) \ do { \ if (umass_debug & (m)) { x ; } \ } while (0) #define DPRINTF(sc, m, fmt, ...) \ do { \ if (umass_debug & (m)) { \ printf("%s:%s: " fmt, \ (sc) ? (const char *)(sc)->sc_name : \ (const char *)"umassX", \ __FUNCTION__ ,## __VA_ARGS__); \ } \ } while (0) #define UDMASS_GEN 0x00010000 /* general */ #define UDMASS_SCSI 0x00020000 /* scsi */ #define UDMASS_UFI 0x00040000 /* ufi command set */ #define UDMASS_ATAPI 0x00080000 /* 8070i command set */ #define UDMASS_CMD (UDMASS_SCSI|UDMASS_UFI|UDMASS_ATAPI) #define UDMASS_USB 0x00100000 /* USB general */ #define UDMASS_BBB 0x00200000 /* Bulk-Only transfers */ #define UDMASS_CBI 0x00400000 /* CBI transfers */ #define UDMASS_WIRE (UDMASS_BBB|UDMASS_CBI) #define UDMASS_ALL 0xffff0000 /* all of the above */ static int umass_debug; static int umass_throttle; -static SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW, 0, "USB umass"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB umass"); SYSCTL_INT(_hw_usb_umass, OID_AUTO, debug, CTLFLAG_RWTUN, &umass_debug, 0, "umass debug level"); SYSCTL_INT(_hw_usb_umass, OID_AUTO, throttle, CTLFLAG_RWTUN, &umass_throttle, 0, "Forced delay between commands in milliseconds"); #else #define DIF(...) do { } while (0) #define DPRINTF(...) do { } while (0) #endif #define UMASS_BULK_SIZE (1 << 17) #define UMASS_CBI_DIAGNOSTIC_CMDLEN 12 /* bytes */ #define UMASS_MAX_CMDLEN MAX(12, CAM_MAX_CDBLEN) /* bytes */ /* USB transfer definitions */ #define UMASS_T_BBB_RESET1 0 /* Bulk-Only */ #define UMASS_T_BBB_RESET2 1 #define UMASS_T_BBB_RESET3 2 #define UMASS_T_BBB_COMMAND 3 #define UMASS_T_BBB_DATA_READ 4 #define UMASS_T_BBB_DATA_RD_CS 5 #define UMASS_T_BBB_DATA_WRITE 6 #define UMASS_T_BBB_DATA_WR_CS 7 #define UMASS_T_BBB_STATUS 8 #define UMASS_T_BBB_MAX 9 #define UMASS_T_CBI_RESET1 0 /* CBI */ #define UMASS_T_CBI_RESET2 1 #define UMASS_T_CBI_RESET3 2 #define UMASS_T_CBI_COMMAND 3 #define UMASS_T_CBI_DATA_READ 4 #define UMASS_T_CBI_DATA_RD_CS 5 #define UMASS_T_CBI_DATA_WRITE 6 #define UMASS_T_CBI_DATA_WR_CS 7 #define UMASS_T_CBI_STATUS 8 #define UMASS_T_CBI_RESET4 9 #define UMASS_T_CBI_MAX 10 #define UMASS_T_MAX MAX(UMASS_T_CBI_MAX, UMASS_T_BBB_MAX) /* Generic definitions */ /* Direction for transfer */ #define DIR_NONE 0 #define DIR_IN 1 #define DIR_OUT 2 /* device name */ #define DEVNAME "umass" #define DEVNAME_SIM "umass-sim" /* Approximate maximum transfer speeds (assumes 33% overhead). */ #define UMASS_FULL_TRANSFER_SPEED 1000 #define UMASS_HIGH_TRANSFER_SPEED 40000 #define UMASS_SUPER_TRANSFER_SPEED 400000 #define UMASS_FLOPPY_TRANSFER_SPEED 20 #define UMASS_TIMEOUT 5000 /* ms */ /* CAM specific definitions */ #define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */ #define UMASS_SCSIID_HOST UMASS_SCSIID_MAX /* Bulk-Only features */ #define UR_BBB_RESET 0xff /* Bulk-Only reset */ #define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ /* Command Block Wrapper */ typedef struct { uDWord dCBWSignature; #define CBWSIGNATURE 0x43425355 uDWord dCBWTag; uDWord dCBWDataTransferLength; uByte bCBWFlags; #define CBWFLAGS_OUT 0x00 #define CBWFLAGS_IN 0x80 uByte bCBWLUN; uByte bCDBLength; #define CBWCDBLENGTH 16 uByte CBWCDB[CBWCDBLENGTH]; } __packed umass_bbb_cbw_t; #define UMASS_BBB_CBW_SIZE 31 /* Command Status Wrapper */ typedef struct { uDWord dCSWSignature; #define CSWSIGNATURE 0x53425355 #define CSWSIGNATURE_IMAGINATION_DBX1 0x43425355 #define CSWSIGNATURE_OLYMPUS_C1 0x55425355 uDWord dCSWTag; uDWord dCSWDataResidue; uByte bCSWStatus; #define CSWSTATUS_GOOD 0x0 #define CSWSTATUS_FAILED 0x1 #define CSWSTATUS_PHASE 0x2 } __packed umass_bbb_csw_t; #define UMASS_BBB_CSW_SIZE 13 /* CBI features */ #define UR_CBI_ADSC 0x00 typedef union { struct { uint8_t type; #define IDB_TYPE_CCI 0x00 uint8_t value; #define IDB_VALUE_PASS 0x00 #define IDB_VALUE_FAIL 0x01 #define IDB_VALUE_PHASE 0x02 #define IDB_VALUE_PERSISTENT 0x03 #define IDB_VALUE_STATUS_MASK 0x03 } __packed common; struct { uint8_t asc; uint8_t ascq; } __packed ufi; } __packed umass_cbi_sbl_t; struct umass_softc; /* see below */ typedef void (umass_callback_t)(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status); #define STATUS_CMD_OK 0 /* everything ok */ #define STATUS_CMD_UNKNOWN 1 /* will have to fetch sense */ #define STATUS_CMD_FAILED 2 /* transfer was ok, command failed */ #define STATUS_WIRE_FAILED 3 /* couldn't even get command across */ typedef uint8_t (umass_transform_t)(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len); /* Wire and command protocol */ #define UMASS_PROTO_BBB 0x0001 /* USB wire protocol */ #define UMASS_PROTO_CBI 0x0002 #define UMASS_PROTO_CBI_I 0x0004 #define UMASS_PROTO_WIRE 0x00ff /* USB wire protocol mask */ #define UMASS_PROTO_SCSI 0x0100 /* command protocol */ #define UMASS_PROTO_ATAPI 0x0200 #define UMASS_PROTO_UFI 0x0400 #define UMASS_PROTO_RBC 0x0800 #define UMASS_PROTO_COMMAND 0xff00 /* command protocol mask */ /* Device specific quirks */ #define NO_QUIRKS 0x0000 /* * The drive does not support Test Unit Ready. Convert to Start Unit */ #define NO_TEST_UNIT_READY 0x0001 /* * The drive does not reset the Unit Attention state after REQUEST * SENSE has been sent. The INQUIRY command does not reset the UA * either, and so CAM runs in circles trying to retrieve the initial * INQUIRY data. */ #define RS_NO_CLEAR_UA 0x0002 /* The drive does not support START STOP. */ #define NO_START_STOP 0x0004 /* Don't ask for full inquiry data (255b). */ #define FORCE_SHORT_INQUIRY 0x0008 /* Needs to be initialised the Shuttle way */ #define SHUTTLE_INIT 0x0010 /* Drive needs to be switched to alternate iface 1 */ #define ALT_IFACE_1 0x0020 /* Drive does not do 1Mb/s, but just floppy speeds (20kb/s) */ #define FLOPPY_SPEED 0x0040 /* The device can't count and gets the residue of transfers wrong */ #define IGNORE_RESIDUE 0x0080 /* No GetMaxLun call */ #define NO_GETMAXLUN 0x0100 /* The device uses a weird CSWSIGNATURE. */ #define WRONG_CSWSIG 0x0200 /* Device cannot handle INQUIRY so fake a generic response */ #define NO_INQUIRY 0x0400 /* Device cannot handle INQUIRY EVPD, return CHECK CONDITION */ #define NO_INQUIRY_EVPD 0x0800 /* Pad all RBC requests to 12 bytes. */ #define RBC_PAD_TO_12 0x1000 /* * Device reports number of sectors from READ_CAPACITY, not max * sector number. */ #define READ_CAPACITY_OFFBY1 0x2000 /* * Device cannot handle a SCSI synchronize cache command. Normally * this quirk would be handled in the cam layer, but for IDE bridges * we need to associate the quirk with the bridge and not the * underlying disk device. This is handled by faking a success * result. */ #define NO_SYNCHRONIZE_CACHE 0x4000 /* Device does not support 'PREVENT/ALLOW MEDIUM REMOVAL'. */ #define NO_PREVENT_ALLOW 0x8000 struct umass_softc { struct scsi_sense cam_scsi_sense; struct scsi_test_unit_ready cam_scsi_test_unit_ready; struct mtx sc_mtx; struct { uint8_t *data_ptr; union ccb *ccb; umass_callback_t *callback; uint32_t data_len; /* bytes */ uint32_t data_rem; /* bytes */ uint32_t data_timeout; /* ms */ uint32_t actlen; /* bytes */ uint8_t cmd_data[UMASS_MAX_CMDLEN]; uint8_t cmd_len; /* bytes */ uint8_t dir; uint8_t lun; } sc_transfer; /* Bulk specific variables for transfers in progress */ umass_bbb_cbw_t cbw; /* command block wrapper */ umass_bbb_csw_t csw; /* command status wrapper */ /* CBI specific variables for transfers in progress */ umass_cbi_sbl_t sbl; /* status block */ device_t sc_dev; struct usb_device *sc_udev; struct cam_sim *sc_sim; /* SCSI Interface Module */ struct usb_xfer *sc_xfer[UMASS_T_MAX]; /* * The command transform function is used to convert the SCSI * commands into their derivatives, like UFI, ATAPI, and friends. */ umass_transform_t *sc_transform; uint32_t sc_unit; uint32_t sc_quirks; /* they got it almost right */ uint32_t sc_proto; /* wire and cmd protocol */ uint8_t sc_name[16]; uint8_t sc_iface_no; /* interface number */ uint8_t sc_maxlun; /* maximum LUN number, inclusive */ uint8_t sc_last_xfer_index; uint8_t sc_status_try; }; struct umass_probe_proto { uint32_t quirks; uint32_t proto; int error; }; /* prototypes */ static device_probe_t umass_probe; static device_attach_t umass_attach; static device_detach_t umass_detach; static usb_callback_t umass_tr_error; static usb_callback_t umass_t_bbb_reset1_callback; static usb_callback_t umass_t_bbb_reset2_callback; static usb_callback_t umass_t_bbb_reset3_callback; static usb_callback_t umass_t_bbb_command_callback; static usb_callback_t umass_t_bbb_data_read_callback; static usb_callback_t umass_t_bbb_data_rd_cs_callback; static usb_callback_t umass_t_bbb_data_write_callback; static usb_callback_t umass_t_bbb_data_wr_cs_callback; static usb_callback_t umass_t_bbb_status_callback; static usb_callback_t umass_t_cbi_reset1_callback; static usb_callback_t umass_t_cbi_reset2_callback; static usb_callback_t umass_t_cbi_reset3_callback; static usb_callback_t umass_t_cbi_reset4_callback; static usb_callback_t umass_t_cbi_command_callback; static usb_callback_t umass_t_cbi_data_read_callback; static usb_callback_t umass_t_cbi_data_rd_cs_callback; static usb_callback_t umass_t_cbi_data_write_callback; static usb_callback_t umass_t_cbi_data_wr_cs_callback; static usb_callback_t umass_t_cbi_status_callback; static void umass_cancel_ccb(struct umass_softc *); static void umass_init_shuttle(struct umass_softc *); static void umass_reset(struct umass_softc *); static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t, uint8_t, usb_error_t); static void umass_command_start(struct umass_softc *, uint8_t, void *, uint32_t, uint32_t, umass_callback_t *, union ccb *); static uint8_t umass_bbb_get_max_lun(struct umass_softc *); static void umass_cbi_start_status(struct umass_softc *); static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *, uint8_t, uint8_t, usb_error_t); static int umass_cam_attach_sim(struct umass_softc *); static void umass_cam_attach(struct umass_softc *); static void umass_cam_detach_sim(struct umass_softc *); static void umass_cam_action(struct cam_sim *, union ccb *); static void umass_cam_poll(struct cam_sim *); static void umass_cam_cb(struct umass_softc *, union ccb *, uint32_t, uint8_t); static void umass_cam_sense_cb(struct umass_softc *, union ccb *, uint32_t, uint8_t); static void umass_cam_quirk_cb(struct umass_softc *, union ccb *, uint32_t, uint8_t); static uint8_t umass_scsi_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_rbc_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_ufi_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_atapi_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_no_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_std_transform(struct umass_softc *, union ccb *, uint8_t *, uint8_t); #ifdef USB_DEBUG static void umass_bbb_dump_cbw(struct umass_softc *, umass_bbb_cbw_t *); static void umass_bbb_dump_csw(struct umass_softc *, umass_bbb_csw_t *); static void umass_cbi_dump_cmd(struct umass_softc *, void *, uint8_t); static void umass_dump_buffer(struct umass_softc *, uint8_t *, uint32_t, uint32_t); #endif static struct usb_config umass_bbb_config[UMASS_T_BBB_MAX] = { [UMASS_T_BBB_RESET1] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_reset1_callback, .timeout = 5000, /* 5 seconds */ .interval = 500, /* 500 milliseconds */ }, [UMASS_T_BBB_RESET2] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_reset2_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_BBB_RESET3] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_reset3_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_BBB_COMMAND] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(umass_bbb_cbw_t), .callback = &umass_t_bbb_command_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_BBB_DATA_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_bbb_data_read_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_BBB_DATA_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_data_rd_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_BBB_DATA_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_bbb_data_write_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_BBB_DATA_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_data_wr_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_BBB_STATUS] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(umass_bbb_csw_t), .flags = {.short_xfer_ok = 1,}, .callback = &umass_t_bbb_status_callback, .timeout = 5000, /* ms */ }, }; static struct usb_config umass_cbi_config[UMASS_T_CBI_MAX] = { [UMASS_T_CBI_RESET1] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + UMASS_CBI_DIAGNOSTIC_CMDLEN), .callback = &umass_t_cbi_reset1_callback, .timeout = 5000, /* 5 seconds */ .interval = 500, /* 500 milliseconds */ }, [UMASS_T_CBI_RESET2] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_reset2_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_CBI_RESET3] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_reset3_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_CBI_COMMAND] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + UMASS_MAX_CMDLEN), .callback = &umass_t_cbi_command_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_CBI_DATA_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_cbi_data_read_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_CBI_DATA_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_data_rd_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_CBI_DATA_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_cbi_data_write_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_CBI_DATA_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_data_wr_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_CBI_STATUS] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,}, .bufsize = sizeof(umass_cbi_sbl_t), .callback = &umass_t_cbi_status_callback, .timeout = 5000, /* ms */ }, [UMASS_T_CBI_RESET4] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_reset4_callback, .timeout = 5000, /* ms */ }, }; /* If device cannot return valid inquiry data, fake it */ static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = { 0, /* removable */ 0x80, SCSI_REV_2, SCSI_REV_2, /* additional_length */ 31, 0, 0, 0 }; #define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */ #define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */ static devclass_t umass_devclass; static device_method_t umass_methods[] = { /* Device interface */ DEVMETHOD(device_probe, umass_probe), DEVMETHOD(device_attach, umass_attach), DEVMETHOD(device_detach, umass_detach), DEVMETHOD_END }; static driver_t umass_driver = { .name = "umass", .methods = umass_methods, .size = sizeof(struct umass_softc), }; static const STRUCT_USB_HOST_ID __used umass_devs[] = { /* generic mass storage class */ {USB_IFACE_CLASS(UICLASS_MASS),}, }; DRIVER_MODULE(umass, uhub, umass_driver, umass_devclass, NULL, 0); MODULE_DEPEND(umass, usb, 1, 1, 1); MODULE_DEPEND(umass, cam, 1, 1, 1); MODULE_VERSION(umass, 1); USB_PNP_HOST_INFO(umass_devs); /* * USB device probe/attach/detach */ static uint16_t umass_get_proto(struct usb_interface *iface) { struct usb_interface_descriptor *id; uint16_t retval; retval = 0; /* Check for a standards compliant device */ id = usbd_get_interface_descriptor(iface); if ((id == NULL) || (id->bInterfaceClass != UICLASS_MASS)) { goto done; } switch (id->bInterfaceSubClass) { case UISUBCLASS_SCSI: retval |= UMASS_PROTO_SCSI; break; case UISUBCLASS_UFI: retval |= UMASS_PROTO_UFI; break; case UISUBCLASS_RBC: retval |= UMASS_PROTO_RBC; break; case UISUBCLASS_SFF8020I: case UISUBCLASS_SFF8070I: retval |= UMASS_PROTO_ATAPI; break; default: goto done; } switch (id->bInterfaceProtocol) { case UIPROTO_MASS_CBI: retval |= UMASS_PROTO_CBI; break; case UIPROTO_MASS_CBI_I: retval |= UMASS_PROTO_CBI_I; break; case UIPROTO_MASS_BBB_OLD: case UIPROTO_MASS_BBB: retval |= UMASS_PROTO_BBB; break; default: goto done; } done: return (retval); } /* * Match the device we are seeing with the devices supported. */ static struct umass_probe_proto umass_probe_proto(device_t dev, struct usb_attach_arg *uaa) { struct umass_probe_proto ret; uint32_t quirks = NO_QUIRKS; uint32_t proto = umass_get_proto(uaa->iface); memset(&ret, 0, sizeof(ret)); ret.error = BUS_PROBE_GENERIC; /* Search for protocol enforcement */ if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_BBB)) { proto &= ~UMASS_PROTO_WIRE; proto |= UMASS_PROTO_BBB; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI)) { proto &= ~UMASS_PROTO_WIRE; proto |= UMASS_PROTO_CBI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI_I)) { proto &= ~UMASS_PROTO_WIRE; proto |= UMASS_PROTO_CBI_I; } if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_SCSI)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_SCSI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_ATAPI)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_ATAPI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_UFI)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_UFI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_RBC)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_RBC; } /* Check if the protocol is invalid */ if ((proto & UMASS_PROTO_COMMAND) == 0) { ret.error = ENXIO; goto done; } if ((proto & UMASS_PROTO_WIRE) == 0) { ret.error = ENXIO; goto done; } /* Search for quirks */ if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY)) quirks |= NO_TEST_UNIT_READY; if (usb_test_quirk(uaa, UQ_MSC_NO_RS_CLEAR_UA)) quirks |= RS_NO_CLEAR_UA; if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP)) quirks |= NO_START_STOP; if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN)) quirks |= NO_GETMAXLUN; if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY)) quirks |= NO_INQUIRY; if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY_EVPD)) quirks |= NO_INQUIRY_EVPD; if (usb_test_quirk(uaa, UQ_MSC_NO_PREVENT_ALLOW)) quirks |= NO_PREVENT_ALLOW; if (usb_test_quirk(uaa, UQ_MSC_NO_SYNC_CACHE)) quirks |= NO_SYNCHRONIZE_CACHE; if (usb_test_quirk(uaa, UQ_MSC_SHUTTLE_INIT)) quirks |= SHUTTLE_INIT; if (usb_test_quirk(uaa, UQ_MSC_ALT_IFACE_1)) quirks |= ALT_IFACE_1; if (usb_test_quirk(uaa, UQ_MSC_FLOPPY_SPEED)) quirks |= FLOPPY_SPEED; if (usb_test_quirk(uaa, UQ_MSC_IGNORE_RESIDUE)) quirks |= IGNORE_RESIDUE; if (usb_test_quirk(uaa, UQ_MSC_WRONG_CSWSIG)) quirks |= WRONG_CSWSIG; if (usb_test_quirk(uaa, UQ_MSC_RBC_PAD_TO_12)) quirks |= RBC_PAD_TO_12; if (usb_test_quirk(uaa, UQ_MSC_READ_CAP_OFFBY1)) quirks |= READ_CAPACITY_OFFBY1; if (usb_test_quirk(uaa, UQ_MSC_FORCE_SHORT_INQ)) quirks |= FORCE_SHORT_INQUIRY; done: ret.quirks = quirks; ret.proto = proto; return (ret); } static int umass_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct umass_probe_proto temp; if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } temp = umass_probe_proto(dev, uaa); return (temp.error); } static int umass_attach(device_t dev) { struct umass_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct umass_probe_proto temp = umass_probe_proto(dev, uaa); struct usb_interface_descriptor *id; int err; /* * NOTE: the softc struct is cleared in device_set_driver. * We can safely call umass_detach without specifically * initializing the struct. */ sc->sc_dev = dev; sc->sc_udev = uaa->device; sc->sc_proto = temp.proto; sc->sc_quirks = temp.quirks; sc->sc_unit = device_get_unit(dev); snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", device_get_nameunit(dev)); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF | MTX_RECURSE); /* get interface index */ id = usbd_get_interface_descriptor(uaa->iface); if (id == NULL) { device_printf(dev, "failed to get " "interface number\n"); goto detach; } sc->sc_iface_no = id->bInterfaceNumber; #ifdef USB_DEBUG device_printf(dev, " "); switch (sc->sc_proto & UMASS_PROTO_COMMAND) { case UMASS_PROTO_SCSI: printf("SCSI"); break; case UMASS_PROTO_ATAPI: printf("8070i (ATAPI)"); break; case UMASS_PROTO_UFI: printf("UFI"); break; case UMASS_PROTO_RBC: printf("RBC"); break; default: printf("(unknown 0x%02x)", sc->sc_proto & UMASS_PROTO_COMMAND); break; } printf(" over "); switch (sc->sc_proto & UMASS_PROTO_WIRE) { case UMASS_PROTO_BBB: printf("Bulk-Only"); break; case UMASS_PROTO_CBI: /* uses Comand/Bulk pipes */ printf("CBI"); break; case UMASS_PROTO_CBI_I: /* uses Comand/Bulk/Interrupt pipes */ printf("CBI with CCI"); break; default: printf("(unknown 0x%02x)", sc->sc_proto & UMASS_PROTO_WIRE); } printf("; quirks = 0x%04x\n", sc->sc_quirks); #endif if (sc->sc_quirks & ALT_IFACE_1) { err = usbd_set_alt_interface_index (uaa->device, uaa->info.bIfaceIndex, 1); if (err) { DPRINTF(sc, UDMASS_USB, "could not switch to " "Alt Interface 1\n"); goto detach; } } /* allocate all required USB transfers */ if (sc->sc_proto & UMASS_PROTO_BBB) { err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, umass_bbb_config, UMASS_T_BBB_MAX, sc, &sc->sc_mtx); /* skip reset first time */ sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; } else if (sc->sc_proto & (UMASS_PROTO_CBI | UMASS_PROTO_CBI_I)) { err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, umass_cbi_config, UMASS_T_CBI_MAX, sc, &sc->sc_mtx); /* skip reset first time */ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; } else { err = USB_ERR_INVAL; } if (err) { device_printf(dev, "could not setup required " "transfers, %s\n", usbd_errstr(err)); goto detach; } #ifdef USB_DEBUG if (umass_throttle > 0) { uint8_t x; int iv; iv = umass_throttle; if (iv < 1) iv = 1; else if (iv > 8000) iv = 8000; for (x = 0; x != UMASS_T_MAX; x++) { if (sc->sc_xfer[x] != NULL) usbd_xfer_set_interval(sc->sc_xfer[x], iv); } } #endif sc->sc_transform = (sc->sc_proto & UMASS_PROTO_SCSI) ? &umass_scsi_transform : (sc->sc_proto & UMASS_PROTO_UFI) ? &umass_ufi_transform : (sc->sc_proto & UMASS_PROTO_ATAPI) ? &umass_atapi_transform : (sc->sc_proto & UMASS_PROTO_RBC) ? &umass_rbc_transform : &umass_no_transform; /* from here onwards the device can be used. */ if (sc->sc_quirks & SHUTTLE_INIT) { umass_init_shuttle(sc); } /* get the maximum LUN supported by the device */ if (((sc->sc_proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) && !(sc->sc_quirks & NO_GETMAXLUN)) sc->sc_maxlun = umass_bbb_get_max_lun(sc); else sc->sc_maxlun = 0; /* Prepare the SCSI command block */ sc->cam_scsi_sense.opcode = REQUEST_SENSE; sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY; /* register the SIM */ err = umass_cam_attach_sim(sc); if (err) { goto detach; } /* scan the SIM */ umass_cam_attach(sc); DPRINTF(sc, UDMASS_GEN, "Attach finished\n"); return (0); /* success */ detach: umass_detach(dev); return (ENXIO); /* failure */ } static int umass_detach(device_t dev) { struct umass_softc *sc = device_get_softc(dev); DPRINTF(sc, UDMASS_USB, "\n"); /* teardown our statemachine */ usbd_transfer_unsetup(sc->sc_xfer, UMASS_T_MAX); mtx_lock(&sc->sc_mtx); /* cancel any leftover CCB's */ umass_cancel_ccb(sc); umass_cam_detach_sim(sc); mtx_unlock(&sc->sc_mtx); mtx_destroy(&sc->sc_mtx); return (0); /* success */ } static void umass_init_shuttle(struct umass_softc *sc) { struct usb_device_request req; uint8_t status[2] = {0, 0}; /* * The Linux driver does this, but no one can tell us what the * command does. */ req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = 1; /* XXX unknown command */ USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, sizeof(status)); usbd_do_request(sc->sc_udev, NULL, &req, &status); DPRINTF(sc, UDMASS_GEN, "Shuttle init returned 0x%02x%02x\n", status[0], status[1]); } /* * Generic functions to handle transfers */ static void umass_transfer_start(struct umass_softc *sc, uint8_t xfer_index) { DPRINTF(sc, UDMASS_GEN, "transfer index = " "%d\n", xfer_index); if (sc->sc_xfer[xfer_index]) { sc->sc_last_xfer_index = xfer_index; usbd_transfer_start(sc->sc_xfer[xfer_index]); } else { umass_cancel_ccb(sc); } } static void umass_reset(struct umass_softc *sc) { DPRINTF(sc, UDMASS_GEN, "resetting device\n"); /* * stop the last transfer, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); umass_transfer_start(sc, 0); } static void umass_cancel_ccb(struct umass_softc *sc) { union ccb *ccb; USB_MTX_ASSERT(&sc->sc_mtx, MA_OWNED); ccb = sc->sc_transfer.ccb; sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = 0; if (ccb) { (sc->sc_transfer.callback) (sc, ccb, (sc->sc_transfer.data_len - sc->sc_transfer.actlen), STATUS_WIRE_FAILED); } } static void umass_tr_error(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); if (error != USB_ERR_CANCELLED) { DPRINTF(sc, UDMASS_GEN, "transfer error, %s -> " "reset\n", usbd_errstr(error)); } umass_cancel_ccb(sc); } /* * BBB protocol specific functions */ static void umass_t_bbb_reset1_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: umass_transfer_start(sc, UMASS_T_BBB_RESET2); return; case USB_ST_SETUP: /* * Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class) * * For Reset Recovery the host shall issue in the following order: * a) a Bulk-Only Mass Storage Reset * b) a Clear Feature HALT to the Bulk-In endpoint * c) a Clear Feature HALT to the Bulk-Out endpoint * * This is done in 3 steps, using 3 transfers: * UMASS_T_BBB_RESET1 * UMASS_T_BBB_RESET2 * UMASS_T_BBB_RESET3 */ DPRINTF(sc, UDMASS_BBB, "BBB reset!\n"); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_BBB_RESET; /* bulk only reset */ USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); return; default: /* Error */ umass_tr_error(xfer, error); return; } } static void umass_t_bbb_reset2_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_RESET3, UMASS_T_BBB_DATA_READ, error); } static void umass_t_bbb_reset3_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_COMMAND, UMASS_T_BBB_DATA_WRITE, error); } static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: umass_transfer_start(sc, next_xfer); return; case USB_ST_SETUP: if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { goto tr_transferred; } return; default: /* Error */ umass_tr_error(xfer, error); return; } } static void umass_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_page_cache *pc; uint32_t tag; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: umass_transfer_start (sc, ((sc->sc_transfer.dir == DIR_IN) ? UMASS_T_BBB_DATA_READ : (sc->sc_transfer.dir == DIR_OUT) ? UMASS_T_BBB_DATA_WRITE : UMASS_T_BBB_STATUS)); return; case USB_ST_SETUP: sc->sc_status_try = 0; if (ccb) { /* * the initial value is not important, * as long as the values are unique: */ tag = UGETDW(sc->cbw.dCBWTag) + 1; USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); USETDW(sc->cbw.dCBWTag, tag); /* * dCBWDataTransferLength: * This field indicates the number of bytes of data that the host * intends to transfer on the IN or OUT Bulk endpoint(as indicated by * the Direction bit) during the execution of this command. If this * field is set to 0, the device will expect that no data will be * transferred IN or OUT during this command, regardless of the value * of the Direction bit defined in dCBWFlags. */ USETDW(sc->cbw.dCBWDataTransferLength, sc->sc_transfer.data_len); /* * dCBWFlags: * The bits of the Flags field are defined as follows: * Bits 0-6 reserved * Bit 7 Direction - this bit shall be ignored if the * dCBWDataTransferLength field is zero. * 0 = data Out from host to device * 1 = data In from device to host */ sc->cbw.bCBWFlags = ((sc->sc_transfer.dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); sc->cbw.bCBWLUN = sc->sc_transfer.lun; if (sc->sc_transfer.cmd_len > sizeof(sc->cbw.CBWCDB)) { sc->sc_transfer.cmd_len = sizeof(sc->cbw.CBWCDB); DPRINTF(sc, UDMASS_BBB, "Truncating long command!\n"); } sc->cbw.bCDBLength = sc->sc_transfer.cmd_len; /* copy SCSI command data */ memcpy(sc->cbw.CBWCDB, sc->sc_transfer.cmd_data, sc->sc_transfer.cmd_len); /* clear remaining command area */ memset(sc->cbw.CBWCDB + sc->sc_transfer.cmd_len, 0, sizeof(sc->cbw.CBWCDB) - sc->sc_transfer.cmd_len); DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &sc->cbw, sizeof(sc->cbw)); usbd_xfer_set_frame_len(xfer, 0, sizeof(sc->cbw)); usbd_transfer_submit(xfer); } return; default: /* Error */ umass_tr_error(xfer, error); return; } } static void umass_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_transfer_start(sc, UMASS_T_BBB_STATUS); return; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); return; default: /* Error */ if (error == USB_ERR_CANCELLED) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); } return; } } static void umass_t_bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, UMASS_T_BBB_DATA_READ, error); } static void umass_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_transfer_start(sc, UMASS_T_BBB_STATUS); return; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); return; default: /* Error */ if (error == USB_ERR_CANCELLED) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_BBB_DATA_WR_CS); } return; } } static void umass_t_bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, UMASS_T_BBB_DATA_WRITE, error); } static void umass_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_page_cache *pc; uint32_t residue; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* * Do a full reset if there is something wrong with the CSW: */ sc->sc_status_try = 1; /* Zero missing parts of the CSW: */ if (actlen < (int)sizeof(sc->csw)) memset(&sc->csw, 0, sizeof(sc->csw)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &sc->csw, actlen); DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw)); residue = UGETDW(sc->csw.dCSWDataResidue); if ((!residue) || (sc->sc_quirks & IGNORE_RESIDUE)) { residue = (sc->sc_transfer.data_len - sc->sc_transfer.actlen); } if (residue > sc->sc_transfer.data_len) { DPRINTF(sc, UDMASS_BBB, "truncating residue from %d " "to %d bytes\n", residue, sc->sc_transfer.data_len); residue = sc->sc_transfer.data_len; } /* translate weird command-status signatures: */ if (sc->sc_quirks & WRONG_CSWSIG) { uint32_t temp = UGETDW(sc->csw.dCSWSignature); if ((temp == CSWSIGNATURE_OLYMPUS_C1) || (temp == CSWSIGNATURE_IMAGINATION_DBX1)) { USETDW(sc->csw.dCSWSignature, CSWSIGNATURE); } } /* check CSW and handle eventual error */ if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) { DPRINTF(sc, UDMASS_BBB, "bad CSW signature 0x%08x != 0x%08x\n", UGETDW(sc->csw.dCSWSignature), CSWSIGNATURE); /* * Invalid CSW: Wrong signature or wrong tag might * indicate that we lost synchronization. Reset the * device. */ goto tr_error; } else if (UGETDW(sc->csw.dCSWTag) != UGETDW(sc->cbw.dCBWTag)) { DPRINTF(sc, UDMASS_BBB, "Invalid CSW: tag 0x%08x should be " "0x%08x\n", UGETDW(sc->csw.dCSWTag), UGETDW(sc->cbw.dCBWTag)); goto tr_error; } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) { DPRINTF(sc, UDMASS_BBB, "Invalid CSW: status %d > %d\n", sc->csw.bCSWStatus, CSWSTATUS_PHASE); goto tr_error; } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) { DPRINTF(sc, UDMASS_BBB, "Phase error, residue = " "%d\n", residue); goto tr_error; } else if (sc->sc_transfer.actlen > sc->sc_transfer.data_len) { DPRINTF(sc, UDMASS_BBB, "Buffer overrun %d > %d\n", sc->sc_transfer.actlen, sc->sc_transfer.data_len); goto tr_error; } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) { DPRINTF(sc, UDMASS_BBB, "Command failed, residue = " "%d\n", residue); sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, STATUS_CMD_FAILED); } else { sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, STATUS_CMD_OK); } return; case USB_ST_SETUP: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: tr_error: DPRINTF(sc, UDMASS_BBB, "Failed to read CSW: %s, try %d\n", usbd_errstr(error), sc->sc_status_try); if ((error == USB_ERR_CANCELLED) || (sc->sc_status_try)) { umass_tr_error(xfer, error); } else { sc->sc_status_try = 1; umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); } return; } } static void umass_command_start(struct umass_softc *sc, uint8_t dir, void *data_ptr, uint32_t data_len, uint32_t data_timeout, umass_callback_t *callback, union ccb *ccb) { sc->sc_transfer.lun = ccb->ccb_h.target_lun; /* * NOTE: assumes that "sc->sc_transfer.cmd_data" and * "sc->sc_transfer.cmd_len" has been properly * initialized. */ sc->sc_transfer.dir = data_len ? dir : DIR_NONE; sc->sc_transfer.data_ptr = data_ptr; sc->sc_transfer.data_len = data_len; sc->sc_transfer.data_rem = data_len; sc->sc_transfer.data_timeout = (data_timeout + UMASS_TIMEOUT); sc->sc_transfer.actlen = 0; sc->sc_transfer.callback = callback; sc->sc_transfer.ccb = ccb; if (sc->sc_xfer[sc->sc_last_xfer_index]) { usbd_transfer_start(sc->sc_xfer[sc->sc_last_xfer_index]); } else { umass_cancel_ccb(sc); } } static uint8_t umass_bbb_get_max_lun(struct umass_softc *sc) { struct usb_device_request req; usb_error_t err; uint8_t buf = 0; /* The Get Max Lun command is a class-specific request. */ req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_BBB_GET_MAX_LUN; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 1); err = usbd_do_request(sc->sc_udev, NULL, &req, &buf); if (err) { buf = 0; /* Device doesn't support Get Max Lun request. */ printf("%s: Get Max Lun not supported (%s)\n", sc->sc_name, usbd_errstr(err)); } return (buf); } /* * Command/Bulk/Interrupt (CBI) specific functions */ static void umass_cbi_start_status(struct umass_softc *sc) { if (sc->sc_xfer[UMASS_T_CBI_STATUS]) { umass_transfer_start(sc, UMASS_T_CBI_STATUS); } else { union ccb *ccb = sc->sc_transfer.ccb; sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; (sc->sc_transfer.callback) (sc, ccb, (sc->sc_transfer.data_len - sc->sc_transfer.actlen), STATUS_CMD_UNKNOWN); } } static void umass_t_cbi_reset1_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; uint8_t buf[UMASS_CBI_DIAGNOSTIC_CMDLEN]; uint8_t i; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: umass_transfer_start(sc, UMASS_T_CBI_RESET2); break; case USB_ST_SETUP: /* * Command Block Reset Protocol * * First send a reset request to the device. Then clear * any possibly stalled bulk endpoints. * * This is done in 3 steps, using 3 transfers: * UMASS_T_CBI_RESET1 * UMASS_T_CBI_RESET2 * UMASS_T_CBI_RESET3 * UMASS_T_CBI_RESET4 (only if there is an interrupt endpoint) */ DPRINTF(sc, UDMASS_CBI, "CBI reset!\n"); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_CBI_ADSC; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, UMASS_CBI_DIAGNOSTIC_CMDLEN); /* * The 0x1d code is the SEND DIAGNOSTIC command. To * distinguish between the two, the last 10 bytes of the CBL * is filled with 0xff (section 2.2 of the CBI * specification) */ buf[0] = 0x1d; /* Command Block Reset */ buf[1] = 0x04; for (i = 2; i < UMASS_CBI_DIAGNOSTIC_CMDLEN; i++) { buf[i] = 0xff; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, buf, sizeof(buf)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) umass_tr_error(xfer, error); else umass_transfer_start(sc, UMASS_T_CBI_RESET2); break; } } static void umass_t_cbi_reset2_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_RESET3, UMASS_T_CBI_DATA_READ, error); } static void umass_t_cbi_reset3_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); umass_t_cbi_data_clear_stall_callback (xfer, (sc->sc_xfer[UMASS_T_CBI_RESET4] && sc->sc_xfer[UMASS_T_CBI_STATUS]) ? UMASS_T_CBI_RESET4 : UMASS_T_CBI_COMMAND, UMASS_T_CBI_DATA_WRITE, error); } static void umass_t_cbi_reset4_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_COMMAND, UMASS_T_CBI_STATUS, error); } static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: if (next_xfer == UMASS_T_CBI_STATUS) { umass_cbi_start_status(sc); } else { umass_transfer_start(sc, next_xfer); } break; case USB_ST_SETUP: if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { goto tr_transferred; /* should not happen */ } break; default: /* Error */ umass_tr_error(xfer, error); break; } } static void umass_t_cbi_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_device_request req; struct usb_page_cache *pc; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (sc->sc_transfer.dir == DIR_NONE) { umass_cbi_start_status(sc); } else { umass_transfer_start (sc, (sc->sc_transfer.dir == DIR_IN) ? UMASS_T_CBI_DATA_READ : UMASS_T_CBI_DATA_WRITE); } break; case USB_ST_SETUP: if (ccb) { /* * do a CBI transfer with cmd_len bytes from * cmd_data, possibly a data phase of data_len * bytes from/to the device and finally a status * read phase. */ req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_CBI_ADSC; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; req.wLength[0] = sc->sc_transfer.cmd_len; req.wLength[1] = 0; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, sc->sc_transfer.cmd_data, sc->sc_transfer.cmd_len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, sc->sc_transfer.cmd_len); usbd_xfer_set_frames(xfer, sc->sc_transfer.cmd_len ? 2 : 1); DIF(UDMASS_CBI, umass_cbi_dump_cmd(sc, sc->sc_transfer.cmd_data, sc->sc_transfer.cmd_len)); usbd_transfer_submit(xfer); } break; default: /* Error */ /* * STALL on the control pipe can be result of the command error. * Attempt to clear this STALL same as for bulk pipe also * results in command completion interrupt, but ASC/ASCQ there * look like not always valid, so don't bother about it. */ if ((error == USB_ERR_STALLED) || (sc->sc_transfer.callback == &umass_cam_cb)) { sc->sc_transfer.ccb = NULL; (sc->sc_transfer.callback) (sc, ccb, sc->sc_transfer.data_len, STATUS_CMD_UNKNOWN); } else { umass_tr_error(xfer, error); /* skip reset */ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; } break; } } static void umass_t_cbi_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_cbi_start_status(sc); break; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if ((error == USB_ERR_CANCELLED) || (sc->sc_transfer.callback != &umass_cam_cb)) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_CBI_DATA_RD_CS); } break; } } static void umass_t_cbi_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, UMASS_T_CBI_DATA_READ, error); } static void umass_t_cbi_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_cbi_start_status(sc); break; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if ((error == USB_ERR_CANCELLED) || (sc->sc_transfer.callback != &umass_cam_cb)) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_CBI_DATA_WR_CS); } break; } } static void umass_t_cbi_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, UMASS_T_CBI_DATA_WRITE, error); } static void umass_t_cbi_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_page_cache *pc; uint32_t residue; uint8_t status; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < (int)sizeof(sc->sbl)) { goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &sc->sbl, sizeof(sc->sbl)); residue = (sc->sc_transfer.data_len - sc->sc_transfer.actlen); /* dissect the information in the buffer */ if (sc->sc_proto & UMASS_PROTO_UFI) { /* * Section 3.4.3.1.3 specifies that the UFI command * protocol returns an ASC and ASCQ in the interrupt * data block. */ DPRINTF(sc, UDMASS_CBI, "UFI CCI, ASC = 0x%02x, " "ASCQ = 0x%02x\n", sc->sbl.ufi.asc, sc->sbl.ufi.ascq); status = (((sc->sbl.ufi.asc == 0) && (sc->sbl.ufi.ascq == 0)) ? STATUS_CMD_OK : STATUS_CMD_FAILED); sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, status); break; } else { /* Command Interrupt Data Block */ DPRINTF(sc, UDMASS_CBI, "type=0x%02x, value=0x%02x\n", sc->sbl.common.type, sc->sbl.common.value); if (sc->sbl.common.type == IDB_TYPE_CCI) { status = (sc->sbl.common.value & IDB_VALUE_STATUS_MASK); status = ((status == IDB_VALUE_PASS) ? STATUS_CMD_OK : (status == IDB_VALUE_FAIL) ? STATUS_CMD_FAILED : (status == IDB_VALUE_PERSISTENT) ? STATUS_CMD_FAILED : STATUS_WIRE_FAILED); sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, status); break; } } /* fallthrough */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF(sc, UDMASS_CBI, "Failed to read CSW: %s\n", usbd_errstr(error)); umass_tr_error(xfer, error); break; } } /* * CAM specific functions (used by SCSI, UFI, 8070i (ATAPI)) */ static int umass_cam_attach_sim(struct umass_softc *sc) { struct cam_devq *devq; /* Per device Queue */ /* * A HBA is attached to the CAM layer. * * The CAM layer will then after a while start probing for devices on * the bus. The number of SIMs is limited to one. */ devq = cam_simq_alloc(1 /* maximum openings */ ); if (devq == NULL) { return (ENOMEM); } sc->sc_sim = cam_sim_alloc (&umass_cam_action, &umass_cam_poll, DEVNAME_SIM, sc /* priv */ , sc->sc_unit /* unit number */ , &sc->sc_mtx /* mutex */ , 1 /* maximum device openings */ , 0 /* maximum tagged device openings */ , devq); if (sc->sc_sim == NULL) { cam_simq_free(devq); return (ENOMEM); } mtx_lock(&sc->sc_mtx); if (xpt_bus_register(sc->sc_sim, sc->sc_dev, sc->sc_unit) != CAM_SUCCESS) { mtx_unlock(&sc->sc_mtx); return (ENOMEM); } mtx_unlock(&sc->sc_mtx); return (0); } static void umass_cam_attach(struct umass_softc *sc) { #ifndef USB_DEBUG if (bootverbose) #endif printf("%s:%d:%d: Attached to scbus%d\n", sc->sc_name, cam_sim_path(sc->sc_sim), sc->sc_unit, cam_sim_path(sc->sc_sim)); } /* umass_cam_detach * detach from the CAM layer */ static void umass_cam_detach_sim(struct umass_softc *sc) { if (sc->sc_sim != NULL) { if (xpt_bus_deregister(cam_sim_path(sc->sc_sim))) { /* accessing the softc is not possible after this */ sc->sc_sim->softc = NULL; cam_sim_free(sc->sc_sim, /* free_devq */ TRUE); } else { panic("%s: CAM layer is busy\n", sc->sc_name); } sc->sc_sim = NULL; } } /* umass_cam_action * CAM requests for action come through here */ static void umass_cam_action(struct cam_sim *sim, union ccb *ccb) { struct umass_softc *sc = (struct umass_softc *)sim->softc; if (sc == NULL) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; xpt_done(ccb); return; } /* Perform the requested action */ switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: { uint8_t *cmd; uint8_t dir; if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); } else { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); } DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SCSI_IO: " "cmd: 0x%02x, flags: 0x%02x, " "%db cmd/%db data/%db sense\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, cmd[0], ccb->ccb_h.flags & CAM_DIR_MASK, ccb->csio.cdb_len, ccb->csio.dxfer_len, ccb->csio.sense_len); if (sc->sc_transfer.ccb) { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SCSI_IO: " "I/O in progress, deferring\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); ccb->ccb_h.status = CAM_SCSI_BUSY; xpt_done(ccb); goto done; } switch (ccb->ccb_h.flags & CAM_DIR_MASK) { case CAM_DIR_IN: dir = DIR_IN; break; case CAM_DIR_OUT: dir = DIR_OUT; DIF(UDMASS_SCSI, umass_dump_buffer(sc, ccb->csio.data_ptr, ccb->csio.dxfer_len, 48)); break; default: dir = DIR_NONE; } ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; /* * sc->sc_transform will convert the command to the * command format needed by the specific command set * and return the converted command in * "sc->sc_transfer.cmd_data" */ if (umass_std_transform(sc, ccb, cmd, ccb->csio.cdb_len)) { if (sc->sc_transfer.cmd_data[0] == INQUIRY) { const char *pserial; pserial = usb_get_serial(sc->sc_udev); /* * Umass devices don't generally report their serial numbers * in the usual SCSI way. Emulate it here. */ if ((sc->sc_transfer.cmd_data[1] & SI_EVPD) && (sc->sc_transfer.cmd_data[2] == SVPD_UNIT_SERIAL_NUMBER) && (pserial[0] != '\0')) { struct scsi_vpd_unit_serial_number *vpd_serial; vpd_serial = (struct scsi_vpd_unit_serial_number *)ccb->csio.data_ptr; vpd_serial->length = strlen(pserial); if (vpd_serial->length > sizeof(vpd_serial->serial_num)) vpd_serial->length = sizeof(vpd_serial->serial_num); memcpy(vpd_serial->serial_num, pserial, vpd_serial->length); ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } /* * Handle EVPD inquiry for broken devices first * NO_INQUIRY also implies NO_INQUIRY_EVPD */ if ((sc->sc_quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) && (sc->sc_transfer.cmd_data[1] & SI_EVPD)) { scsi_set_sense_data(&ccb->csio.sense_data, /*sense_format*/ SSD_TYPE_NONE, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x24, /*ascq*/ 0x00, /*extra args*/ SSD_ELEM_NONE); ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, 1); xpt_done(ccb); goto done; } /* * Return fake inquiry data for * broken devices */ if (sc->sc_quirks & NO_INQUIRY) { memcpy(ccb->csio.data_ptr, &fake_inq_data, sizeof(fake_inq_data)); ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { ccb->csio.dxfer_len = SHORT_INQUIRY_LENGTH; } } else if (sc->sc_transfer.cmd_data[0] == PREVENT_ALLOW) { if (sc->sc_quirks & NO_PREVENT_ALLOW) { ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } } else if (sc->sc_transfer.cmd_data[0] == SYNCHRONIZE_CACHE) { if (sc->sc_quirks & NO_SYNCHRONIZE_CACHE) { ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } } umass_command_start(sc, dir, ccb->csio.data_ptr, ccb->csio.dxfer_len, ccb->ccb_h.timeout, &umass_cam_cb, ccb); } break; } case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_PATH_INQ:.\n", sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); /* host specific information */ cpi->version_num = 1; cpi->hba_inquiry = 0; cpi->target_sprt = 0; cpi->hba_misc = PIM_NO_6_BYTE; cpi->hba_eng_cnt = 0; cpi->max_target = UMASS_SCSIID_MAX; /* one target */ cpi->initiator_id = UMASS_SCSIID_HOST; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = sc->sc_unit; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->transport = XPORT_USB; cpi->transport_version = 0; if (sc == NULL) { cpi->base_transfer_speed = 0; cpi->max_lun = 0; } else { if (sc->sc_quirks & FLOPPY_SPEED) { cpi->base_transfer_speed = UMASS_FLOPPY_TRANSFER_SPEED; } else { switch (usbd_get_speed(sc->sc_udev)) { case USB_SPEED_SUPER: cpi->base_transfer_speed = UMASS_SUPER_TRANSFER_SPEED; cpi->maxio = MAXPHYS; break; case USB_SPEED_HIGH: cpi->base_transfer_speed = UMASS_HIGH_TRANSFER_SPEED; break; default: cpi->base_transfer_speed = UMASS_FULL_TRANSFER_SPEED; break; } } cpi->max_lun = sc->sc_maxlun; } cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_RESET_DEV: { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_RESET_DEV:.\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); umass_reset(sc); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_GET_TRAN_SETTINGS:.\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_USB; cts->transport_version = 0; cts->xport_specific.valid = 0; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_SET_TRAN_SETTINGS: { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SET_TRAN_SETTINGS:.\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { cam_calc_geometry(&ccb->ccg, /* extended */ 1); xpt_done(ccb); break; } case XPT_NOOP: { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_NOOP:.\n", sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } default: DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:func_code 0x%04x: " "Not implemented\n", sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, ccb->ccb_h.func_code); ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; xpt_done(ccb); break; } done: return; } static void umass_cam_poll(struct cam_sim *sim) { struct umass_softc *sc = (struct umass_softc *)sim->softc; if (sc == NULL) return; DPRINTF(sc, UDMASS_SCSI, "CAM poll\n"); usbd_transfer_poll(sc->sc_xfer, UMASS_T_MAX); } /* umass_cam_cb * finalise a completed CAM command */ static void umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status) { ccb->csio.resid = residue; switch (status) { case STATUS_CMD_OK: ccb->ccb_h.status = CAM_REQ_CMP; if ((sc->sc_quirks & READ_CAPACITY_OFFBY1) && (ccb->ccb_h.func_code == XPT_SCSI_IO) && (ccb->csio.cdb_io.cdb_bytes[0] == READ_CAPACITY)) { struct scsi_read_capacity_data *rcap; uint32_t maxsector; rcap = (void *)(ccb->csio.data_ptr); maxsector = scsi_4btoul(rcap->addr) - 1; scsi_ulto4b(maxsector, rcap->addr); } /* * We have to add SVPD_UNIT_SERIAL_NUMBER to the list * of pages supported by the device - otherwise, CAM * will never ask us for the serial number if the * device cannot handle that by itself. */ if (ccb->ccb_h.func_code == XPT_SCSI_IO && sc->sc_transfer.cmd_data[0] == INQUIRY && (sc->sc_transfer.cmd_data[1] & SI_EVPD) && sc->sc_transfer.cmd_data[2] == SVPD_SUPPORTED_PAGE_LIST && (usb_get_serial(sc->sc_udev)[0] != '\0')) { struct ccb_scsiio *csio; struct scsi_vpd_supported_page_list *page_list; csio = &ccb->csio; page_list = (struct scsi_vpd_supported_page_list *)csio->data_ptr; if (page_list->length + 1 < SVPD_SUPPORTED_PAGES_SIZE) { page_list->list[page_list->length] = SVPD_UNIT_SERIAL_NUMBER; page_list->length++; } } xpt_done(ccb); break; case STATUS_CMD_UNKNOWN: case STATUS_CMD_FAILED: /* fetch sense data */ /* the rest of the command was filled in at attach */ sc->cam_scsi_sense.length = ccb->csio.sense_len; DPRINTF(sc, UDMASS_SCSI, "Fetching %d bytes of " "sense data\n", ccb->csio.sense_len); if (umass_std_transform(sc, ccb, &sc->cam_scsi_sense.opcode, sizeof(sc->cam_scsi_sense))) { if ((sc->sc_quirks & FORCE_SHORT_INQUIRY) && (sc->sc_transfer.cmd_data[0] == INQUIRY)) { ccb->csio.sense_len = SHORT_INQUIRY_LENGTH; } umass_command_start(sc, DIR_IN, &ccb->csio.sense_data.error_code, ccb->csio.sense_len, ccb->ccb_h.timeout, &umass_cam_sense_cb, ccb); } break; default: /* * The wire protocol failed and will hopefully have * recovered. We return an error to CAM and let CAM * retry the command if necessary. */ xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN; xpt_done(ccb); break; } } /* * Finalise a completed autosense operation */ static void umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status) { uint8_t *cmd; switch (status) { case STATUS_CMD_OK: case STATUS_CMD_UNKNOWN: case STATUS_CMD_FAILED: { int key, sense_len; ccb->csio.sense_resid = residue; sense_len = ccb->csio.sense_len - ccb->csio.sense_resid; key = scsi_get_sense_key(&ccb->csio.sense_data, sense_len, /*show_errors*/ 1); if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); } else { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); } /* * Getting sense data always succeeds (apart from wire * failures): */ if ((sc->sc_quirks & RS_NO_CLEAR_UA) && (cmd[0] == INQUIRY) && (key == SSD_KEY_UNIT_ATTENTION)) { /* * Ignore unit attention errors in the case where * the Unit Attention state is not cleared on * REQUEST SENSE. They will appear again at the next * command. */ ccb->ccb_h.status = CAM_REQ_CMP; } else if (key == SSD_KEY_NO_SENSE) { /* * No problem after all (in the case of CBI without * CCI) */ ccb->ccb_h.status = CAM_REQ_CMP; } else if ((sc->sc_quirks & RS_NO_CLEAR_UA) && (cmd[0] == READ_CAPACITY) && (key == SSD_KEY_UNIT_ATTENTION)) { /* * Some devices do not clear the unit attention error * on request sense. We insert a test unit ready * command to make sure we clear the unit attention * condition, then allow the retry to proceed as * usual. */ xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; #if 0 DELAY(300000); #endif DPRINTF(sc, UDMASS_SCSI, "Doing a sneaky" "TEST_UNIT_READY\n"); /* the rest of the command was filled in at attach */ if ((sc->sc_transform)(sc, &sc->cam_scsi_test_unit_ready.opcode, sizeof(sc->cam_scsi_test_unit_ready)) == 1) { umass_command_start(sc, DIR_NONE, NULL, 0, ccb->ccb_h.timeout, &umass_cam_quirk_cb, ccb); break; } } else { xpt_freeze_devq(ccb->ccb_h.path, 1); if (key >= 0) { ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; } else ccb->ccb_h.status = CAM_AUTOSENSE_FAIL | CAM_DEV_QFRZN; } xpt_done(ccb); break; } default: DPRINTF(sc, UDMASS_SCSI, "Autosense failed, " "status %d\n", status); xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_AUTOSENSE_FAIL | CAM_DEV_QFRZN; xpt_done(ccb); } } /* * This completion code just handles the fact that we sent a test-unit-ready * after having previously failed a READ CAPACITY with CHECK_COND. The CCB * status for CAM is already set earlier. */ static void umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status) { DPRINTF(sc, UDMASS_SCSI, "Test unit ready " "returned status %d\n", status); xpt_done(ccb); } /* * SCSI specific functions */ static uint8_t umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } sc->sc_transfer.cmd_len = cmd_len; switch (cmd_ptr[0]) { case TEST_UNIT_READY: if (sc->sc_quirks & NO_TEST_UNIT_READY) { DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " "to START_UNIT\n"); memset(sc->sc_transfer.cmd_data, 0, cmd_len); sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; sc->sc_transfer.cmd_data[4] = SSS_START; return (1); } break; case INQUIRY: /* * some drives wedge when asked for full inquiry * information. */ if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; return (1); } break; } memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); return (1); } static uint8_t umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } switch (cmd_ptr[0]) { /* these commands are defined in RBC: */ case READ_10: case READ_CAPACITY: case START_STOP_UNIT: case SYNCHRONIZE_CACHE: case WRITE_10: case VERIFY_10: case INQUIRY: case MODE_SELECT_10: case MODE_SENSE_10: case TEST_UNIT_READY: case WRITE_BUFFER: /* * The following commands are not listed in my copy of the * RBC specs. CAM however seems to want those, and at least * the Sony DSC device appears to support those as well */ case REQUEST_SENSE: case PREVENT_ALLOW: memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); if ((sc->sc_quirks & RBC_PAD_TO_12) && (cmd_len < 12)) { memset(sc->sc_transfer.cmd_data + cmd_len, 0, 12 - cmd_len); cmd_len = 12; } sc->sc_transfer.cmd_len = cmd_len; return (1); /* success */ /* All other commands are not legal in RBC */ default: DPRINTF(sc, UDMASS_SCSI, "Unsupported RBC " "command 0x%02x\n", cmd_ptr[0]); return (0); /* failure */ } } static uint8_t umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } /* An UFI command is always 12 bytes in length */ sc->sc_transfer.cmd_len = UFI_COMMAND_LENGTH; /* Zero the command data */ memset(sc->sc_transfer.cmd_data, 0, UFI_COMMAND_LENGTH); switch (cmd_ptr[0]) { /* * Commands of which the format has been verified. They * should work. Copy the command into the (zeroed out) * destination buffer. */ case TEST_UNIT_READY: if (sc->sc_quirks & NO_TEST_UNIT_READY) { /* * Some devices do not support this command. Start * Stop Unit should give the same results */ DPRINTF(sc, UDMASS_UFI, "Converted TEST_UNIT_READY " "to START_UNIT\n"); sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; sc->sc_transfer.cmd_data[4] = SSS_START; return (1); } break; case REZERO_UNIT: case REQUEST_SENSE: case FORMAT_UNIT: case INQUIRY: case START_STOP_UNIT: case SEND_DIAGNOSTIC: case PREVENT_ALLOW: case READ_CAPACITY: case READ_10: case WRITE_10: case POSITION_TO_ELEMENT: /* SEEK_10 */ case WRITE_AND_VERIFY: case VERIFY: case MODE_SELECT_10: case MODE_SENSE_10: case READ_12: case WRITE_12: case READ_FORMAT_CAPACITIES: break; /* * SYNCHRONIZE_CACHE isn't supported by UFI, nor should it be * required for UFI devices, so it is appropriate to fake * success. */ case SYNCHRONIZE_CACHE: return (2); default: DPRINTF(sc, UDMASS_SCSI, "Unsupported UFI " "command 0x%02x\n", cmd_ptr[0]); return (0); /* failure */ } memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); return (1); /* success */ } /* * 8070i (ATAPI) specific functions */ static uint8_t umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } /* An ATAPI command is always 12 bytes in length. */ sc->sc_transfer.cmd_len = ATAPI_COMMAND_LENGTH; /* Zero the command data */ memset(sc->sc_transfer.cmd_data, 0, ATAPI_COMMAND_LENGTH); switch (cmd_ptr[0]) { /* * Commands of which the format has been verified. They * should work. Copy the command into the destination * buffer. */ case INQUIRY: /* * some drives wedge when asked for full inquiry * information. */ if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; return (1); } break; case TEST_UNIT_READY: if (sc->sc_quirks & NO_TEST_UNIT_READY) { DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " "to START_UNIT\n"); sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; sc->sc_transfer.cmd_data[4] = SSS_START; return (1); } break; case REZERO_UNIT: case REQUEST_SENSE: case START_STOP_UNIT: case SEND_DIAGNOSTIC: case PREVENT_ALLOW: case READ_CAPACITY: case READ_10: case WRITE_10: case POSITION_TO_ELEMENT: /* SEEK_10 */ case SYNCHRONIZE_CACHE: case MODE_SELECT_10: case MODE_SENSE_10: case READ_BUFFER: case 0x42: /* READ_SUBCHANNEL */ case 0x43: /* READ_TOC */ case 0x44: /* READ_HEADER */ case 0x47: /* PLAY_MSF (Play Minute/Second/Frame) */ case 0x48: /* PLAY_TRACK */ case 0x49: /* PLAY_TRACK_REL */ case 0x4b: /* PAUSE */ case 0x51: /* READ_DISK_INFO */ case 0x52: /* READ_TRACK_INFO */ case 0x54: /* SEND_OPC */ case 0x59: /* READ_MASTER_CUE */ case 0x5b: /* CLOSE_TR_SESSION */ case 0x5c: /* READ_BUFFER_CAP */ case 0x5d: /* SEND_CUE_SHEET */ case 0xa1: /* BLANK */ case 0xa5: /* PLAY_12 */ case 0xa6: /* EXCHANGE_MEDIUM */ case 0xad: /* READ_DVD_STRUCTURE */ case 0xbb: /* SET_CD_SPEED */ case 0xe5: /* READ_TRACK_INFO_PHILIPS */ break; case READ_12: case WRITE_12: default: DPRINTF(sc, UDMASS_SCSI, "Unsupported ATAPI " "command 0x%02x - trying anyway\n", cmd_ptr[0]); break; } memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); return (1); /* success */ } static uint8_t umass_no_transform(struct umass_softc *sc, uint8_t *cmd, uint8_t cmdlen) { return (0); /* failure */ } static uint8_t umass_std_transform(struct umass_softc *sc, union ccb *ccb, uint8_t *cmd, uint8_t cmdlen) { uint8_t retval; retval = (sc->sc_transform) (sc, cmd, cmdlen); if (retval == 2) { ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); return (0); } else if (retval == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_REQ_INVALID | CAM_DEV_QFRZN; xpt_done(ccb); return (0); } /* Command should be executed */ return (1); } #ifdef USB_DEBUG static void umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw) { uint8_t *c = cbw->CBWCDB; uint32_t dlen = UGETDW(cbw->dCBWDataTransferLength); uint32_t tag = UGETDW(cbw->dCBWTag); uint8_t clen = cbw->bCDBLength; uint8_t flags = cbw->bCBWFlags; uint8_t lun = cbw->bCBWLUN; DPRINTF(sc, UDMASS_BBB, "CBW %d: cmd = %db " "(0x%02x%02x%02x%02x%02x%02x%s), " "data = %db, lun = %d, dir = %s\n", tag, clen, c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6 ? "..." : ""), dlen, lun, (flags == CBWFLAGS_IN ? "in" : (flags == CBWFLAGS_OUT ? "out" : ""))); } static void umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw) { uint32_t sig = UGETDW(csw->dCSWSignature); uint32_t tag = UGETDW(csw->dCSWTag); uint32_t res = UGETDW(csw->dCSWDataResidue); uint8_t status = csw->bCSWStatus; DPRINTF(sc, UDMASS_BBB, "CSW %d: sig = 0x%08x (%s), tag = 0x%08x, " "res = %d, status = 0x%02x (%s)\n", tag, sig, (sig == CSWSIGNATURE ? "valid" : "invalid"), tag, res, status, (status == CSWSTATUS_GOOD ? "good" : (status == CSWSTATUS_FAILED ? "failed" : (status == CSWSTATUS_PHASE ? "phase" : "")))); } static void umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen) { uint8_t *c = cmd; uint8_t dir = sc->sc_transfer.dir; DPRINTF(sc, UDMASS_BBB, "cmd = %db " "(0x%02x%02x%02x%02x%02x%02x%s), " "data = %db, dir = %s\n", cmdlen, c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6 ? "..." : ""), sc->sc_transfer.data_len, (dir == DIR_IN ? "in" : (dir == DIR_OUT ? "out" : (dir == DIR_NONE ? "no data phase" : "")))); } static void umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen, uint32_t printlen) { uint32_t i, j; char s1[40]; char s2[40]; char s3[5]; s1[0] = '\0'; s3[0] = '\0'; sprintf(s2, " buffer=%p, buflen=%d", buffer, buflen); for (i = 0; (i < buflen) && (i < printlen); i++) { j = i % 16; if (j == 0 && i != 0) { DPRINTF(sc, UDMASS_GEN, "0x %s%s\n", s1, s2); s2[0] = '\0'; } sprintf(&s1[j * 2], "%02x", buffer[i] & 0xff); } if (buflen > printlen) sprintf(s3, " ..."); DPRINTF(sc, UDMASS_GEN, "0x %s%s%s\n", s1, s2, s3); } #endif Index: head/sys/dev/usb/storage/urio.c =================================================================== --- head/sys/dev/usb/storage/urio.c (revision 357971) +++ head/sys/dev/usb/storage/urio.c (revision 357972) @@ -1,499 +1,500 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2000 Iwasa Kazmi * 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. * This code includes software developed by the NetBSD Foundation, Inc. and * its contributors. */ #include __FBSDID("$FreeBSD$"); /* * 2000/3/24 added NetBSD/OpenBSD support (from Alex Nemirovsky) * 2000/3/07 use two bulk-pipe handles for read and write (Dirk) * 2000/3/06 change major number(143), and copyright header * some fix for 4.0 (Dirk) * 2000/3/05 codes for FreeBSD 4.x - CURRENT (Thanks to Dirk-Willem van Gulik) * 2000/3/01 remove retry code from urioioctl() * change method of bulk transfer (no interrupt) * 2000/2/28 small fixes for new rio_usb.h * 2000/2/24 first version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #define USB_DEBUG_VAR urio_debug #include #include #ifdef USB_DEBUG static int urio_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, urio, CTLFLAG_RW, 0, "USB urio"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, urio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB urio"); SYSCTL_INT(_hw_usb_urio, OID_AUTO, debug, CTLFLAG_RWTUN, &urio_debug, 0, "urio debug level"); #endif #define URIO_T_WR 0 #define URIO_T_RD 1 #define URIO_T_WR_CS 2 #define URIO_T_RD_CS 3 #define URIO_T_MAX 4 #define URIO_BSIZE (1<<12) /* bytes */ #define URIO_IFQ_MAXLEN 2 /* units */ struct urio_softc { struct usb_fifo_sc sc_fifo; struct mtx sc_mtx; struct usb_device *sc_udev; struct usb_xfer *sc_xfer[URIO_T_MAX]; uint8_t sc_flags; #define URIO_FLAG_READ_STALL 0x01 /* read transfer stalled */ #define URIO_FLAG_WRITE_STALL 0x02 /* write transfer stalled */ uint8_t sc_name[16]; }; /* prototypes */ static device_probe_t urio_probe; static device_attach_t urio_attach; static device_detach_t urio_detach; static usb_callback_t urio_write_callback; static usb_callback_t urio_write_clear_stall_callback; static usb_callback_t urio_read_callback; static usb_callback_t urio_read_clear_stall_callback; static usb_fifo_close_t urio_close; static usb_fifo_cmd_t urio_start_read; static usb_fifo_cmd_t urio_start_write; static usb_fifo_cmd_t urio_stop_read; static usb_fifo_cmd_t urio_stop_write; static usb_fifo_ioctl_t urio_ioctl; static usb_fifo_open_t urio_open; static struct usb_fifo_methods urio_fifo_methods = { .f_close = &urio_close, .f_ioctl = &urio_ioctl, .f_open = &urio_open, .f_start_read = &urio_start_read, .f_start_write = &urio_start_write, .f_stop_read = &urio_stop_read, .f_stop_write = &urio_stop_write, .basename[0] = "urio", }; static const struct usb_config urio_config[URIO_T_MAX] = { [URIO_T_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = URIO_BSIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.proxy_buffer = 1,}, .callback = &urio_write_callback, }, [URIO_T_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = URIO_BSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, .callback = &urio_read_callback, }, [URIO_T_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &urio_write_clear_stall_callback, .timeout = 1000, /* 1 second */ .interval = 50, /* 50ms */ }, [URIO_T_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &urio_read_clear_stall_callback, .timeout = 1000, /* 1 second */ .interval = 50, /* 50ms */ }, }; static devclass_t urio_devclass; static device_method_t urio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, urio_probe), DEVMETHOD(device_attach, urio_attach), DEVMETHOD(device_detach, urio_detach), DEVMETHOD_END }; static driver_t urio_driver = { .name = "urio", .methods = urio_methods, .size = sizeof(struct urio_softc), }; static const STRUCT_USB_HOST_ID urio_devs[] = { {USB_VPI(USB_VENDOR_DIAMOND, USB_PRODUCT_DIAMOND_RIO500USB, 0)}, {USB_VPI(USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO600USB, 0)}, {USB_VPI(USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO800USB, 0)}, }; DRIVER_MODULE(urio, uhub, urio_driver, urio_devclass, NULL, 0); MODULE_DEPEND(urio, usb, 1, 1, 1); MODULE_VERSION(urio, 1); USB_PNP_HOST_INFO(urio_devs); static int urio_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(urio_devs, sizeof(urio_devs), uaa)); } static int urio_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct urio_softc *sc = device_get_softc(dev); int error; device_set_usb_desc(dev); sc->sc_udev = uaa->device; mtx_init(&sc->sc_mtx, "urio lock", NULL, MTX_DEF | MTX_RECURSE); snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", device_get_nameunit(dev)); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, urio_config, URIO_T_MAX, sc, &sc->sc_mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, &urio_fifo_methods, &sc->sc_fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (error) { goto detach; } return (0); /* success */ detach: urio_detach(dev); return (ENOMEM); /* failure */ } static void urio_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct urio_softc *sc = usbd_xfer_softc(xfer); struct usb_fifo *f = sc->sc_fifo.fp[USB_FIFO_TX]; struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: if (sc->sc_flags & URIO_FLAG_WRITE_STALL) { usbd_transfer_start(sc->sc_xfer[URIO_T_WR_CS]); return; } pc = usbd_xfer_get_frame(xfer, 0); if (usb_fifo_get_data(f, pc, 0, usbd_xfer_max_len(xfer), &actlen, 0)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ sc->sc_flags |= URIO_FLAG_WRITE_STALL; usbd_transfer_start(sc->sc_xfer[URIO_T_WR_CS]); } return; } } static void urio_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct urio_softc *sc = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = sc->sc_xfer[URIO_T_WR]; if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTF("stall cleared\n"); sc->sc_flags &= ~URIO_FLAG_WRITE_STALL; usbd_transfer_start(xfer_other); } } static void urio_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct urio_softc *sc = usbd_xfer_softc(xfer); struct usb_fifo *f = sc->sc_fifo.fp[USB_FIFO_RX]; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); usb_fifo_put_data(f, pc, 0, actlen, 1); case USB_ST_SETUP: if (sc->sc_flags & URIO_FLAG_READ_STALL) { usbd_transfer_start(sc->sc_xfer[URIO_T_RD_CS]); return; } if (usb_fifo_put_bytes_max(f) != 0) { usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); } return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ sc->sc_flags |= URIO_FLAG_READ_STALL; usbd_transfer_start(sc->sc_xfer[URIO_T_RD_CS]); } return; } } static void urio_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct urio_softc *sc = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = sc->sc_xfer[URIO_T_RD]; if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTF("stall cleared\n"); sc->sc_flags &= ~URIO_FLAG_READ_STALL; usbd_transfer_start(xfer_other); } } static void urio_start_read(struct usb_fifo *fifo) { struct urio_softc *sc = usb_fifo_softc(fifo); usbd_transfer_start(sc->sc_xfer[URIO_T_RD]); } static void urio_stop_read(struct usb_fifo *fifo) { struct urio_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[URIO_T_RD_CS]); usbd_transfer_stop(sc->sc_xfer[URIO_T_RD]); } static void urio_start_write(struct usb_fifo *fifo) { struct urio_softc *sc = usb_fifo_softc(fifo); usbd_transfer_start(sc->sc_xfer[URIO_T_WR]); } static void urio_stop_write(struct usb_fifo *fifo) { struct urio_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[URIO_T_WR_CS]); usbd_transfer_stop(sc->sc_xfer[URIO_T_WR]); } static int urio_open(struct usb_fifo *fifo, int fflags) { struct urio_softc *sc = usb_fifo_softc(fifo); if (fflags & FREAD) { /* clear stall first */ mtx_lock(&sc->sc_mtx); sc->sc_flags |= URIO_FLAG_READ_STALL; mtx_unlock(&sc->sc_mtx); if (usb_fifo_alloc_buffer(fifo, usbd_xfer_max_len(sc->sc_xfer[URIO_T_RD]), URIO_IFQ_MAXLEN)) { return (ENOMEM); } } if (fflags & FWRITE) { /* clear stall first */ sc->sc_flags |= URIO_FLAG_WRITE_STALL; if (usb_fifo_alloc_buffer(fifo, usbd_xfer_max_len(sc->sc_xfer[URIO_T_WR]), URIO_IFQ_MAXLEN)) { return (ENOMEM); } } return (0); /* success */ } static void urio_close(struct usb_fifo *fifo, int fflags) { if (fflags & (FREAD | FWRITE)) { usb_fifo_free_buffer(fifo); } } static int urio_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { struct usb_ctl_request ur; struct RioCommand *rio_cmd; int error; switch (cmd) { case RIO_RECV_COMMAND: if (!(fflags & FWRITE)) { error = EPERM; goto done; } memset(&ur, 0, sizeof(ur)); rio_cmd = addr; ur.ucr_request.bmRequestType = rio_cmd->requesttype | UT_READ_VENDOR_DEVICE; break; case RIO_SEND_COMMAND: if (!(fflags & FWRITE)) { error = EPERM; goto done; } memset(&ur, 0, sizeof(ur)); rio_cmd = addr; ur.ucr_request.bmRequestType = rio_cmd->requesttype | UT_WRITE_VENDOR_DEVICE; break; default: error = EINVAL; goto done; } DPRINTFN(2, "Sending command\n"); /* Send rio control message */ ur.ucr_request.bRequest = rio_cmd->request; USETW(ur.ucr_request.wValue, rio_cmd->value); USETW(ur.ucr_request.wIndex, rio_cmd->index); USETW(ur.ucr_request.wLength, rio_cmd->length); ur.ucr_data = rio_cmd->buffer; /* reuse generic USB code */ error = ugen_do_request(fifo, &ur); done: return (error); } static int urio_detach(device_t dev) { struct urio_softc *sc = device_get_softc(dev); DPRINTF("\n"); usb_fifo_detach(&sc->sc_fifo); usbd_transfer_unsetup(sc->sc_xfer, URIO_T_MAX); mtx_destroy(&sc->sc_mtx); return (0); } Index: head/sys/dev/usb/storage/ustorage_fs.c =================================================================== --- head/sys/dev/usb/storage/ustorage_fs.c (revision 357971) +++ head/sys/dev/usb/storage/ustorage_fs.c (revision 357972) @@ -1,1961 +1,1962 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (C) 2003-2005 Alan Stern * Copyright (C) 2008 Hans Petter Selasky * 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, * without modification. * 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 names of the above-listed copyright holders may not be used * to endorse or promote products derived from this software without * specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. */ /* * NOTE: Much of the SCSI statemachine handling code derives from the * Linux USB gadget stack. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include "usb_if.h" #define USB_DEBUG_VAR ustorage_fs_debug #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #ifdef USB_DEBUG static int ustorage_fs_debug = 0; -SYSCTL_NODE(_hw_usb, OID_AUTO, ustorage_fs, CTLFLAG_RW, 0, "USB ustorage_fs"); +SYSCTL_NODE(_hw_usb, OID_AUTO, ustorage_fs, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ustorage_fs"); SYSCTL_INT(_hw_usb_ustorage_fs, OID_AUTO, debug, CTLFLAG_RWTUN, &ustorage_fs_debug, 0, "ustorage_fs debug level"); #endif /* Define some limits */ #ifndef USTORAGE_FS_BULK_SIZE #define USTORAGE_FS_BULK_SIZE (1U << 17) /* bytes */ #endif #ifndef USTORAGE_FS_MAX_LUN #define USTORAGE_FS_MAX_LUN 8 /* units */ #endif #ifndef USTORAGE_QDATA_MAX #define USTORAGE_QDATA_MAX 40 /* bytes */ #endif /* * The SCSI ID string must be exactly 28 characters long * exluding the terminating zero. */ #ifndef USTORAGE_FS_ID_STRING #define USTORAGE_FS_ID_STRING \ "FreeBSD " /* 8 */ \ "File-Stor Gadget" /* 16 */ \ "0101" /* 4 */ #endif /* * The following macro defines the number of * sectors to be allocated for the RAM disk: */ #ifndef USTORAGE_FS_RAM_SECT #define USTORAGE_FS_RAM_SECT (1UL << 13) #endif static uint8_t *ustorage_fs_ramdisk; /* USB transfer definitions */ #define USTORAGE_FS_T_BBB_COMMAND 0 #define USTORAGE_FS_T_BBB_DATA_DUMP 1 #define USTORAGE_FS_T_BBB_DATA_READ 2 #define USTORAGE_FS_T_BBB_DATA_WRITE 3 #define USTORAGE_FS_T_BBB_STATUS 4 #define USTORAGE_FS_T_BBB_MAX 5 /* USB data stage direction */ #define DIR_NONE 0 #define DIR_READ 1 #define DIR_WRITE 2 /* USB interface specific control request */ #define UR_BBB_RESET 0xff /* Bulk-Only reset */ #define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ /* Command Block Wrapper */ typedef struct { uDWord dCBWSignature; #define CBWSIGNATURE 0x43425355 uDWord dCBWTag; uDWord dCBWDataTransferLength; uByte bCBWFlags; #define CBWFLAGS_OUT 0x00 #define CBWFLAGS_IN 0x80 uByte bCBWLUN; uByte bCDBLength; #define CBWCDBLENGTH 16 uByte CBWCDB[CBWCDBLENGTH]; } __packed ustorage_fs_bbb_cbw_t; #define USTORAGE_FS_BBB_CBW_SIZE 31 /* Command Status Wrapper */ typedef struct { uDWord dCSWSignature; #define CSWSIGNATURE 0x53425355 uDWord dCSWTag; uDWord dCSWDataResidue; uByte bCSWStatus; #define CSWSTATUS_GOOD 0x0 #define CSWSTATUS_FAILED 0x1 #define CSWSTATUS_PHASE 0x2 } __packed ustorage_fs_bbb_csw_t; #define USTORAGE_FS_BBB_CSW_SIZE 13 struct ustorage_fs_lun { uint8_t *memory_image; uint32_t num_sectors; uint32_t sense_data; uint32_t sense_data_info; uint32_t unit_attention_data; uint8_t read_only:1; uint8_t prevent_medium_removal:1; uint8_t info_valid:1; uint8_t removable:1; }; struct ustorage_fs_softc { ustorage_fs_bbb_cbw_t *sc_cbw; /* Command Wrapper Block */ ustorage_fs_bbb_csw_t *sc_csw; /* Command Status Block */ void *sc_dma_ptr; /* Main data buffer */ struct mtx sc_mtx; struct ustorage_fs_lun sc_lun[USTORAGE_FS_MAX_LUN]; struct { uint8_t *data_ptr; struct ustorage_fs_lun *currlun; uint32_t data_rem; /* bytes, as reported by the command * block wrapper */ uint32_t offset; /* bytes */ uint8_t cbw_dir; uint8_t cmd_dir; uint8_t lun; uint8_t cmd_len; uint8_t data_short:1; uint8_t data_error:1; } sc_transfer; device_t sc_dev; struct usb_device *sc_udev; struct usb_xfer *sc_xfer[USTORAGE_FS_T_BBB_MAX]; uint8_t sc_iface_no; /* interface number */ uint8_t sc_last_lun; uint8_t sc_last_xfer_index; uint8_t sc_qdata[USTORAGE_QDATA_MAX]; }; /* prototypes */ static device_probe_t ustorage_fs_probe; static device_attach_t ustorage_fs_attach; static device_detach_t ustorage_fs_detach; static device_suspend_t ustorage_fs_suspend; static device_resume_t ustorage_fs_resume; static usb_handle_request_t ustorage_fs_handle_request; static usb_callback_t ustorage_fs_t_bbb_command_callback; static usb_callback_t ustorage_fs_t_bbb_data_dump_callback; static usb_callback_t ustorage_fs_t_bbb_data_read_callback; static usb_callback_t ustorage_fs_t_bbb_data_write_callback; static usb_callback_t ustorage_fs_t_bbb_status_callback; static void ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index); static void ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_verify(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_inquiry(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_request_sense(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_read_capacity(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_mode_sense(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_start_stop(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_mode_select(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask); static uint8_t ustorage_fs_read(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_write(struct ustorage_fs_softc *sc); static uint8_t ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t cmd_size, uint16_t mask, uint8_t needs_medium); static uint8_t ustorage_fs_do_cmd(struct ustorage_fs_softc *sc); static device_method_t ustorage_fs_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, ustorage_fs_handle_request), /* Device interface */ DEVMETHOD(device_probe, ustorage_fs_probe), DEVMETHOD(device_attach, ustorage_fs_attach), DEVMETHOD(device_detach, ustorage_fs_detach), DEVMETHOD(device_suspend, ustorage_fs_suspend), DEVMETHOD(device_resume, ustorage_fs_resume), DEVMETHOD_END }; static driver_t ustorage_fs_driver = { .name = "ustorage_fs", .methods = ustorage_fs_methods, .size = sizeof(struct ustorage_fs_softc), }; static devclass_t ustorage_fs_devclass; DRIVER_MODULE(ustorage_fs, uhub, ustorage_fs_driver, ustorage_fs_devclass, NULL, 0); MODULE_VERSION(ustorage_fs, 0); MODULE_DEPEND(ustorage_fs, usb, 1, 1, 1); static struct usb_config ustorage_fs_bbb_config[USTORAGE_FS_T_BBB_MAX] = { [USTORAGE_FS_T_BBB_COMMAND] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(ustorage_fs_bbb_cbw_t), .callback = &ustorage_fs_t_bbb_command_callback, .usb_mode = USB_MODE_DEVICE, }, [USTORAGE_FS_T_BBB_DATA_DUMP] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, .callback = &ustorage_fs_t_bbb_data_dump_callback, .usb_mode = USB_MODE_DEVICE, }, [USTORAGE_FS_T_BBB_DATA_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = USTORAGE_FS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1}, .callback = &ustorage_fs_t_bbb_data_read_callback, .usb_mode = USB_MODE_DEVICE, }, [USTORAGE_FS_T_BBB_DATA_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = USTORAGE_FS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer = 1}, .callback = &ustorage_fs_t_bbb_data_write_callback, .usb_mode = USB_MODE_DEVICE, }, [USTORAGE_FS_T_BBB_STATUS] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(ustorage_fs_bbb_csw_t), .flags = {.short_xfer_ok = 1}, .callback = &ustorage_fs_t_bbb_status_callback, .usb_mode = USB_MODE_DEVICE, }, }; /* * USB device probe/attach/detach */ static int ustorage_fs_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_interface_descriptor *id; if (uaa->usb_mode != USB_MODE_DEVICE) { return (ENXIO); } /* Check for a standards compliant device */ id = usbd_get_interface_descriptor(uaa->iface); if ((id == NULL) || (id->bInterfaceClass != UICLASS_MASS) || (id->bInterfaceSubClass != UISUBCLASS_SCSI) || (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) { return (ENXIO); } return (BUS_PROBE_GENERIC); } static int ustorage_fs_attach(device_t dev) { struct ustorage_fs_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_interface_descriptor *id; int err; int unit; /* * NOTE: the softc struct is cleared in device_set_driver. * We can safely call ustorage_fs_detach without specifically * initializing the struct. */ sc->sc_dev = dev; sc->sc_udev = uaa->device; unit = device_get_unit(dev); /* enable power saving mode */ usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); if (unit == 0) { if (ustorage_fs_ramdisk == NULL) { /* * allocate a memory image for our ramdisk until * further */ ustorage_fs_ramdisk = malloc(USTORAGE_FS_RAM_SECT << 9, M_USB, M_ZERO | M_WAITOK); if (ustorage_fs_ramdisk == NULL) { return (ENOMEM); } } sc->sc_lun[0].memory_image = ustorage_fs_ramdisk; sc->sc_lun[0].num_sectors = USTORAGE_FS_RAM_SECT; sc->sc_lun[0].removable = 1; } device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "USTORAGE_FS lock", NULL, (MTX_DEF | MTX_RECURSE)); /* get interface index */ id = usbd_get_interface_descriptor(uaa->iface); if (id == NULL) { device_printf(dev, "failed to get " "interface number\n"); goto detach; } sc->sc_iface_no = id->bInterfaceNumber; err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, ustorage_fs_bbb_config, USTORAGE_FS_T_BBB_MAX, sc, &sc->sc_mtx); if (err) { device_printf(dev, "could not setup required " "transfers, %s\n", usbd_errstr(err)); goto detach; } sc->sc_cbw = usbd_xfer_get_frame_buffer(sc->sc_xfer[ USTORAGE_FS_T_BBB_COMMAND], 0); sc->sc_csw = usbd_xfer_get_frame_buffer(sc->sc_xfer[ USTORAGE_FS_T_BBB_STATUS], 0); sc->sc_dma_ptr = usbd_xfer_get_frame_buffer(sc->sc_xfer[ USTORAGE_FS_T_BBB_DATA_READ], 0); /* start Mass Storage State Machine */ mtx_lock(&sc->sc_mtx); ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); mtx_unlock(&sc->sc_mtx); return (0); /* success */ detach: ustorage_fs_detach(dev); return (ENXIO); /* failure */ } static int ustorage_fs_detach(device_t dev) { struct ustorage_fs_softc *sc = device_get_softc(dev); /* teardown our statemachine */ usbd_transfer_unsetup(sc->sc_xfer, USTORAGE_FS_T_BBB_MAX); mtx_destroy(&sc->sc_mtx); return (0); /* success */ } static int ustorage_fs_suspend(device_t dev) { device_printf(dev, "suspending\n"); return (0); /* success */ } static int ustorage_fs_resume(device_t dev) { device_printf(dev, "resuming\n"); return (0); /* success */ } /* * Generic functions to handle transfers */ static void ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index) { if (sc->sc_xfer[xfer_index]) { sc->sc_last_xfer_index = xfer_index; usbd_transfer_start(sc->sc_xfer[xfer_index]); } } static void ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc) { usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); mtx_unlock(&sc->sc_mtx); usbd_transfer_drain(sc->sc_xfer[sc->sc_last_xfer_index]); mtx_lock(&sc->sc_mtx); } static int ustorage_fs_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct ustorage_fs_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; if (!is_complete) { if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && (req->bRequest == UR_BBB_RESET)) { *plen = 0; mtx_lock(&sc->sc_mtx); ustorage_fs_transfer_stop(sc); sc->sc_transfer.data_error = 1; ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); mtx_unlock(&sc->sc_mtx); return (0); } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && (req->bRequest == UR_BBB_GET_MAX_LUN)) { if (offset == 0) { *plen = 1; *pptr = &sc->sc_last_lun; } else { *plen = 0; } return (0); } } return (ENXIO); /* use builtin handler */ } static void ustorage_fs_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); uint32_t tag; uint8_t err = 0; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tag = UGETDW(sc->sc_cbw->dCBWSignature); if (tag != CBWSIGNATURE) { /* do nothing */ DPRINTF("invalid signature 0x%08x\n", tag); break; } tag = UGETDW(sc->sc_cbw->dCBWTag); /* echo back tag */ USETDW(sc->sc_csw->dCSWTag, tag); /* reset status */ sc->sc_csw->bCSWStatus = 0; /* reset data offset, data length and data remainder */ sc->sc_transfer.offset = 0; sc->sc_transfer.data_rem = UGETDW(sc->sc_cbw->dCBWDataTransferLength); /* reset data flags */ sc->sc_transfer.data_short = 0; /* extract LUN */ sc->sc_transfer.lun = sc->sc_cbw->bCBWLUN; if (sc->sc_transfer.data_rem == 0) { sc->sc_transfer.cbw_dir = DIR_NONE; } else { if (sc->sc_cbw->bCBWFlags & CBWFLAGS_IN) { sc->sc_transfer.cbw_dir = DIR_WRITE; } else { sc->sc_transfer.cbw_dir = DIR_READ; } } sc->sc_transfer.cmd_len = sc->sc_cbw->bCDBLength; if ((sc->sc_transfer.cmd_len > sizeof(sc->sc_cbw->CBWCDB)) || (sc->sc_transfer.cmd_len == 0)) { /* just halt - this is invalid */ DPRINTF("invalid command length %d bytes\n", sc->sc_transfer.cmd_len); break; } err = ustorage_fs_do_cmd(sc); if (err) { /* got an error */ DPRINTF("command failed\n"); break; } if ((sc->sc_transfer.data_rem > 0) && (sc->sc_transfer.cbw_dir != sc->sc_transfer.cmd_dir)) { /* contradicting data transfer direction */ err = 1; DPRINTF("data direction mismatch\n"); break; } switch (sc->sc_transfer.cbw_dir) { case DIR_READ: ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_READ); break; case DIR_WRITE: ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_WRITE); break; default: ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); break; } break; case USB_ST_SETUP: tr_setup: if (sc->sc_transfer.data_error) { sc->sc_transfer.data_error = 0; usbd_xfer_set_stall(xfer); DPRINTF("stall pipe\n"); } usbd_xfer_set_frame_len(xfer, 0, sizeof(ustorage_fs_bbb_cbw_t)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error\n"); if (error == USB_ERR_CANCELLED) { break; } /* If the pipe is already stalled, don't do another stall */ if (!usbd_xfer_is_stalled(xfer)) sc->sc_transfer.data_error = 1; /* try again */ goto tr_setup; } if (err) { if (sc->sc_csw->bCSWStatus == 0) { /* set some default error code */ sc->sc_csw->bCSWStatus = CSWSTATUS_FAILED; } if (sc->sc_transfer.cbw_dir == DIR_READ) { /* dump all data */ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_DUMP); return; } if (sc->sc_transfer.cbw_dir == DIR_WRITE) { /* need to stall before status */ sc->sc_transfer.data_error = 1; } ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); } } static void ustorage_fs_t_bbb_data_dump_callback(struct usb_xfer *xfer, usb_error_t error) { struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.offset += actlen; if (actlen != sumlen || sc->sc_transfer.data_rem == 0) { /* short transfer or end of data */ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); break; } /* Fallthrough */ case USB_ST_SETUP: tr_setup: if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } if (sc->sc_transfer.data_error) { sc->sc_transfer.data_error = 0; usbd_xfer_set_stall(xfer); } usbd_xfer_set_frame_len(xfer, 0, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { break; } /* * If the pipe is already stalled, don't do another stall: */ if (!usbd_xfer_is_stalled(xfer)) sc->sc_transfer.data_error = 1; /* try again */ goto tr_setup; } } static void ustorage_fs_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* XXX copy data from DMA buffer */ memcpy(sc->sc_transfer.data_ptr, sc->sc_dma_ptr, actlen); sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.offset += actlen; if (actlen != sumlen || sc->sc_transfer.data_rem == 0) { /* short transfer or end of data */ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); break; } /* Fallthrough */ case USB_ST_SETUP: tr_setup: if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } if (sc->sc_transfer.data_error) { sc->sc_transfer.data_error = 0; usbd_xfer_set_stall(xfer); } usbd_xfer_set_frame_data(xfer, 0, sc->sc_dma_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { break; } /* If the pipe is already stalled, don't do another stall */ if (!usbd_xfer_is_stalled(xfer)) sc->sc_transfer.data_error = 1; /* try again */ goto tr_setup; } } static void ustorage_fs_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.offset += actlen; if (actlen != sumlen || sc->sc_transfer.data_rem == 0) { /* short transfer or end of data */ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); break; } case USB_ST_SETUP: tr_setup: if (max_bulk >= sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; if (sc->sc_transfer.data_short) usbd_xfer_set_flag(xfer, USB_FORCE_SHORT_XFER); else usbd_xfer_clr_flag(xfer, USB_FORCE_SHORT_XFER); } else usbd_xfer_clr_flag(xfer, USB_FORCE_SHORT_XFER); if (sc->sc_transfer.data_error) { sc->sc_transfer.data_error = 0; usbd_xfer_set_stall(xfer); } /* XXX copy data to DMA buffer */ memcpy(sc->sc_dma_ptr, sc->sc_transfer.data_ptr, max_bulk); usbd_xfer_set_frame_data(xfer, 0, sc->sc_dma_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { break; } /* * If the pipe is already stalled, don't do another * stall */ if (!usbd_xfer_is_stalled(xfer)) sc->sc_transfer.data_error = 1; /* try again */ goto tr_setup; } } static void ustorage_fs_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); break; case USB_ST_SETUP: tr_setup: USETDW(sc->sc_csw->dCSWSignature, CSWSIGNATURE); USETDW(sc->sc_csw->dCSWDataResidue, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_error) { sc->sc_transfer.data_error = 0; usbd_xfer_set_stall(xfer); } usbd_xfer_set_frame_len(xfer, 0, sizeof(ustorage_fs_bbb_csw_t)); usbd_transfer_submit(xfer); break; default: if (error == USB_ERR_CANCELLED) { break; } /* If the pipe is already stalled, don't do another stall */ if (!usbd_xfer_is_stalled(xfer)) sc->sc_transfer.data_error = 1; /* try again */ goto tr_setup; } } /* SCSI commands that we recognize */ #define SC_FORMAT_UNIT 0x04 #define SC_INQUIRY 0x12 #define SC_MODE_SELECT_6 0x15 #define SC_MODE_SELECT_10 0x55 #define SC_MODE_SENSE_6 0x1a #define SC_MODE_SENSE_10 0x5a #define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e #define SC_READ_6 0x08 #define SC_READ_10 0x28 #define SC_READ_12 0xa8 #define SC_READ_CAPACITY 0x25 #define SC_READ_FORMAT_CAPACITIES 0x23 #define SC_RELEASE 0x17 #define SC_REQUEST_SENSE 0x03 #define SC_RESERVE 0x16 #define SC_SEND_DIAGNOSTIC 0x1d #define SC_START_STOP_UNIT 0x1b #define SC_SYNCHRONIZE_CACHE 0x35 #define SC_TEST_UNIT_READY 0x00 #define SC_VERIFY 0x2f #define SC_WRITE_6 0x0a #define SC_WRITE_10 0x2a #define SC_WRITE_12 0xaa /* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ #define SS_NO_SENSE 0 #define SS_COMMUNICATION_FAILURE 0x040800 #define SS_INVALID_COMMAND 0x052000 #define SS_INVALID_FIELD_IN_CDB 0x052400 #define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 #define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 #define SS_MEDIUM_NOT_PRESENT 0x023a00 #define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 #define SS_NOT_READY_TO_READY_TRANSITION 0x062800 #define SS_RESET_OCCURRED 0x062900 #define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 #define SS_UNRECOVERED_READ_ERROR 0x031100 #define SS_WRITE_ERROR 0x030c02 #define SS_WRITE_PROTECTED 0x072700 #define SK(x) ((uint8_t) ((x) >> 16)) /* Sense Key byte, etc. */ #define ASC(x) ((uint8_t) ((x) >> 8)) #define ASCQ(x) ((uint8_t) (x)) /* Routines for unaligned data access */ static uint16_t get_be16(uint8_t *buf) { return ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]); } static uint32_t get_be32(uint8_t *buf) { return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3]); } static void put_be16(uint8_t *buf, uint16_t val) { buf[0] = val >> 8; buf[1] = val; } static void put_be32(uint8_t *buf, uint32_t val) { buf[0] = val >> 24; buf[1] = val >> 16; buf[2] = val >> 8; buf[3] = val & 0xff; } /*------------------------------------------------------------------------* * ustorage_fs_verify * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_verify(struct ustorage_fs_softc *sc) { struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint32_t lba; uint32_t vlen; uint64_t file_offset; uint64_t amount_left; /* * Get the starting Logical Block Address */ lba = get_be32(&sc->sc_cbw->CBWCDB[2]); /* * We allow DPO (Disable Page Out = don't save data in the cache) * but we don't implement it. */ if ((sc->sc_cbw->CBWCDB[1] & ~0x10) != 0) { currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } vlen = get_be16(&sc->sc_cbw->CBWCDB[7]); if (vlen == 0) { goto done; } /* No default reply */ /* Prepare to carry out the file verify */ amount_left = vlen; amount_left <<= 9; file_offset = lba; file_offset <<= 9; /* Range check */ vlen += lba; if ((vlen < lba) || (vlen > currlun->num_sectors) || (lba >= currlun->num_sectors)) { currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; return (1); } /* XXX TODO: verify that data is readable */ done: return (ustorage_fs_min_len(sc, 0, -1U)); } /*------------------------------------------------------------------------* * ustorage_fs_inquiry * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_inquiry(struct ustorage_fs_softc *sc) { uint8_t *buf = sc->sc_transfer.data_ptr; struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; if (!sc->sc_transfer.currlun) { /* Unsupported LUNs are okay */ memset(buf, 0, 36); buf[0] = 0x7f; /* Unsupported, no device - type */ return (ustorage_fs_min_len(sc, 36, -1U)); } memset(buf, 0, 8); /* Non - removable, direct - access device */ if (currlun->removable) buf[1] = 0x80; buf[2] = 2; /* ANSI SCSI level 2 */ buf[3] = 2; /* SCSI - 2 INQUIRY data format */ buf[4] = 31; /* Additional length */ /* No special options */ /* Copy in ID string */ memcpy(buf + 8, USTORAGE_FS_ID_STRING, 28); #if (USTORAGE_QDATA_MAX < 36) #error "(USTORAGE_QDATA_MAX < 36)" #endif return (ustorage_fs_min_len(sc, 36, -1U)); } /*------------------------------------------------------------------------* * ustorage_fs_request_sense * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_request_sense(struct ustorage_fs_softc *sc) { uint8_t *buf = sc->sc_transfer.data_ptr; struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint32_t sd; uint32_t sdinfo; uint8_t valid; /* * From the SCSI-2 spec., section 7.9 (Unit attention condition): * * If a REQUEST SENSE command is received from an initiator * with a pending unit attention condition (before the target * generates the contingent allegiance condition), then the * target shall either: * a) report any pending sense data and preserve the unit * attention condition on the logical unit, or, * b) report the unit attention condition, may discard any * pending sense data, and clear the unit attention * condition on the logical unit for that initiator. * * FSG normally uses option a); enable this code to use option b). */ #if 0 if (currlun && currlun->unit_attention_data != SS_NO_SENSE) { currlun->sense_data = currlun->unit_attention_data; currlun->unit_attention_data = SS_NO_SENSE; } #endif if (!currlun) { /* Unsupported LUNs are okay */ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; sdinfo = 0; valid = 0; } else { sd = currlun->sense_data; sdinfo = currlun->sense_data_info; valid = currlun->info_valid << 7; currlun->sense_data = SS_NO_SENSE; currlun->sense_data_info = 0; currlun->info_valid = 0; } memset(buf, 0, 18); buf[0] = valid | 0x70; /* Valid, current error */ buf[2] = SK(sd); put_be32(&buf[3], sdinfo); /* Sense information */ buf[7] = 18 - 8; /* Additional sense length */ buf[12] = ASC(sd); buf[13] = ASCQ(sd); #if (USTORAGE_QDATA_MAX < 18) #error "(USTORAGE_QDATA_MAX < 18)" #endif return (ustorage_fs_min_len(sc, 18, -1U)); } /*------------------------------------------------------------------------* * ustorage_fs_read_capacity * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_read_capacity(struct ustorage_fs_softc *sc) { uint8_t *buf = sc->sc_transfer.data_ptr; struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint32_t lba = get_be32(&sc->sc_cbw->CBWCDB[2]); uint8_t pmi = sc->sc_cbw->CBWCDB[8]; /* Check the PMI and LBA fields */ if ((pmi > 1) || ((pmi == 0) && (lba != 0))) { currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } /* Max logical block */ put_be32(&buf[0], currlun->num_sectors - 1); /* Block length */ put_be32(&buf[4], 512); #if (USTORAGE_QDATA_MAX < 8) #error "(USTORAGE_QDATA_MAX < 8)" #endif return (ustorage_fs_min_len(sc, 8, -1U)); } /*------------------------------------------------------------------------* * ustorage_fs_mode_sense * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_mode_sense(struct ustorage_fs_softc *sc) { uint8_t *buf = sc->sc_transfer.data_ptr; struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint8_t *buf0; uint16_t len; uint16_t limit; uint8_t mscmnd = sc->sc_cbw->CBWCDB[0]; uint8_t pc; uint8_t page_code; uint8_t changeable_values; uint8_t all_pages; buf0 = buf; if ((sc->sc_cbw->CBWCDB[1] & ~0x08) != 0) { /* Mask away DBD */ currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } pc = sc->sc_cbw->CBWCDB[2] >> 6; page_code = sc->sc_cbw->CBWCDB[2] & 0x3f; if (pc == 3) { currlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; return (1); } changeable_values = (pc == 1); all_pages = (page_code == 0x3f); /* * Write the mode parameter header. Fixed values are: default * medium type, no cache control (DPOFUA), and no block descriptors. * The only variable value is the WriteProtect bit. We will fill in * the mode data length later. */ memset(buf, 0, 8); if (mscmnd == SC_MODE_SENSE_6) { buf[2] = (currlun->read_only ? 0x80 : 0x00); /* WP, DPOFUA */ buf += 4; limit = 255; } else { /* SC_MODE_SENSE_10 */ buf[3] = (currlun->read_only ? 0x80 : 0x00); /* WP, DPOFUA */ buf += 8; limit = 65535; /* Should really be mod_data.buflen */ } /* No block descriptors */ /* * The mode pages, in numerical order. */ if ((page_code == 0x08) || all_pages) { buf[0] = 0x08; /* Page code */ buf[1] = 10; /* Page length */ memset(buf + 2, 0, 10); /* None of the fields are changeable */ if (!changeable_values) { buf[2] = 0x04; /* Write cache enable, */ /* Read cache not disabled */ /* No cache retention priorities */ put_be16(&buf[4], 0xffff); /* Don 't disable prefetch */ /* Minimum prefetch = 0 */ put_be16(&buf[8], 0xffff); /* Maximum prefetch */ put_be16(&buf[10], 0xffff); /* Maximum prefetch ceiling */ } buf += 12; } /* * Check that a valid page was requested and the mode data length * isn't too long. */ len = buf - buf0; if (len > limit) { currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } /* Store the mode data length */ if (mscmnd == SC_MODE_SENSE_6) buf0[0] = len - 1; else put_be16(buf0, len - 2); #if (USTORAGE_QDATA_MAX < 24) #error "(USTORAGE_QDATA_MAX < 24)" #endif return (ustorage_fs_min_len(sc, len, -1U)); } /*------------------------------------------------------------------------* * ustorage_fs_start_stop * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_start_stop(struct ustorage_fs_softc *sc) { struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint8_t loej; uint8_t start; uint8_t immed; if (!currlun->removable) { currlun->sense_data = SS_INVALID_COMMAND; return (1); } immed = sc->sc_cbw->CBWCDB[1] & 0x01; loej = sc->sc_cbw->CBWCDB[4] & 0x02; start = sc->sc_cbw->CBWCDB[4] & 0x01; if (immed || loej || start) { /* compile fix */ } return (0); } /*------------------------------------------------------------------------* * ustorage_fs_prevent_allow * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc) { struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint8_t prevent; if (!currlun->removable) { currlun->sense_data = SS_INVALID_COMMAND; return (1); } prevent = sc->sc_cbw->CBWCDB[4] & 0x01; if ((sc->sc_cbw->CBWCDB[4] & ~0x01) != 0) { /* Mask away Prevent */ currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } if (currlun->prevent_medium_removal && !prevent) { //fsync_sub(currlun); } currlun->prevent_medium_removal = prevent; return (0); } /*------------------------------------------------------------------------* * ustorage_fs_read_format_capacities * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc) { uint8_t *buf = sc->sc_transfer.data_ptr; struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; buf[0] = buf[1] = buf[2] = 0; buf[3] = 8; /* Only the Current / Maximum Capacity Descriptor */ buf += 4; /* Number of blocks */ put_be32(&buf[0], currlun->num_sectors); /* Block length */ put_be32(&buf[4], 512); /* Current capacity */ buf[4] = 0x02; #if (USTORAGE_QDATA_MAX < 12) #error "(USTORAGE_QDATA_MAX < 12)" #endif return (ustorage_fs_min_len(sc, 12, -1U)); } /*------------------------------------------------------------------------* * ustorage_fs_mode_select * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_mode_select(struct ustorage_fs_softc *sc) { struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; /* We don't support MODE SELECT */ currlun->sense_data = SS_INVALID_COMMAND; return (1); } /*------------------------------------------------------------------------* * ustorage_fs_synchronize_cache * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_synchronize_cache(struct ustorage_fs_softc *sc) { #if 0 struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint8_t rc; /* * We ignore the requested LBA and write out all dirty data buffers. */ rc = 0; if (rc) { currlun->sense_data = SS_WRITE_ERROR; } #endif return (0); } /*------------------------------------------------------------------------* * ustorage_fs_read - read data from disk * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_read(struct ustorage_fs_softc *sc) { struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint64_t file_offset; uint32_t lba; uint32_t len; /* * Get the starting Logical Block Address and check that it's not * too big */ if (sc->sc_cbw->CBWCDB[0] == SC_READ_6) { lba = (((uint32_t)sc->sc_cbw->CBWCDB[1]) << 16) | get_be16(&sc->sc_cbw->CBWCDB[2]); } else { lba = get_be32(&sc->sc_cbw->CBWCDB[2]); /* * We allow DPO (Disable Page Out = don't save data in the * cache) and FUA (Force Unit Access = don't read from the * cache), but we don't implement them. */ if ((sc->sc_cbw->CBWCDB[1] & ~0x18) != 0) { currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } } len = sc->sc_transfer.data_rem >> 9; len += lba; if ((len < lba) || (len > currlun->num_sectors) || (lba >= currlun->num_sectors)) { currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; return (1); } file_offset = lba; file_offset <<= 9; sc->sc_transfer.data_ptr = currlun->memory_image + file_offset; return (0); } /*------------------------------------------------------------------------* * ustorage_fs_write - write data to disk * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_write(struct ustorage_fs_softc *sc) { struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; uint64_t file_offset; uint32_t lba; uint32_t len; if (currlun->read_only) { currlun->sense_data = SS_WRITE_PROTECTED; return (1); } /* XXX clear SYNC */ /* * Get the starting Logical Block Address and check that it's not * too big. */ if (sc->sc_cbw->CBWCDB[0] == SC_WRITE_6) lba = (((uint32_t)sc->sc_cbw->CBWCDB[1]) << 16) | get_be16(&sc->sc_cbw->CBWCDB[2]); else { lba = get_be32(&sc->sc_cbw->CBWCDB[2]); /* * We allow DPO (Disable Page Out = don't save data in the * cache) and FUA (Force Unit Access = write directly to the * medium). We don't implement DPO; we implement FUA by * performing synchronous output. */ if ((sc->sc_cbw->CBWCDB[1] & ~0x18) != 0) { currlun->sense_data = SS_INVALID_FIELD_IN_CDB; return (1); } if (sc->sc_cbw->CBWCDB[1] & 0x08) { /* FUA */ /* XXX set SYNC flag here */ } } len = sc->sc_transfer.data_rem >> 9; len += lba; if ((len < lba) || (len > currlun->num_sectors) || (lba >= currlun->num_sectors)) { currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; return (1); } file_offset = lba; file_offset <<= 9; sc->sc_transfer.data_ptr = currlun->memory_image + file_offset; return (0); } /*------------------------------------------------------------------------* * ustorage_fs_min_len * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask) { if (len != sc->sc_transfer.data_rem) { if (sc->sc_transfer.cbw_dir == DIR_READ) { /* * there must be something wrong about this SCSI * command */ sc->sc_csw->bCSWStatus = CSWSTATUS_PHASE; return (1); } /* compute the minimum length */ if (sc->sc_transfer.data_rem > len) { /* data ends prematurely */ sc->sc_transfer.data_rem = len; sc->sc_transfer.data_short = 1; } /* check length alignment */ if (sc->sc_transfer.data_rem & ~mask) { /* data ends prematurely */ sc->sc_transfer.data_rem &= mask; sc->sc_transfer.data_short = 1; } } return (0); } /*------------------------------------------------------------------------* * ustorage_fs_check_cmd - check command routine * * Check whether the command is properly formed and whether its data * size and direction agree with the values we already have. * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t min_cmd_size, uint16_t mask, uint8_t needs_medium) { struct ustorage_fs_lun *currlun; uint8_t lun = (sc->sc_cbw->CBWCDB[1] >> 5); uint8_t i; /* Verify the length of the command itself */ if (min_cmd_size > sc->sc_transfer.cmd_len) { DPRINTF("%u > %u\n", min_cmd_size, sc->sc_transfer.cmd_len); sc->sc_csw->bCSWStatus = CSWSTATUS_PHASE; return (1); } /* Mask away the LUN */ sc->sc_cbw->CBWCDB[1] &= 0x1f; /* Check if LUN is correct */ if (lun != sc->sc_transfer.lun) { } /* Check the LUN */ if (sc->sc_transfer.lun <= sc->sc_last_lun) { sc->sc_transfer.currlun = currlun = sc->sc_lun + sc->sc_transfer.lun; if (sc->sc_cbw->CBWCDB[0] != SC_REQUEST_SENSE) { currlun->sense_data = SS_NO_SENSE; currlun->sense_data_info = 0; currlun->info_valid = 0; } /* * If a unit attention condition exists, only INQUIRY * and REQUEST SENSE commands are allowed. Anything * else must fail! */ if ((currlun->unit_attention_data != SS_NO_SENSE) && (sc->sc_cbw->CBWCDB[0] != SC_INQUIRY) && (sc->sc_cbw->CBWCDB[0] != SC_REQUEST_SENSE)) { currlun->sense_data = currlun->unit_attention_data; currlun->unit_attention_data = SS_NO_SENSE; return (1); } } else { sc->sc_transfer.currlun = currlun = NULL; /* * INQUIRY and REQUEST SENSE commands are explicitly allowed * to use unsupported LUNs; all others may not. */ if ((sc->sc_cbw->CBWCDB[0] != SC_INQUIRY) && (sc->sc_cbw->CBWCDB[0] != SC_REQUEST_SENSE)) { return (1); } } /* * Check that only command bytes listed in the mask are * non-zero. */ for (i = 0; i != min_cmd_size; i++) { if (sc->sc_cbw->CBWCDB[i] && !(mask & (1UL << i))) { if (currlun) { currlun->sense_data = SS_INVALID_FIELD_IN_CDB; } return (1); } } /* * If the medium isn't mounted and the command needs to access * it, return an error. */ if (currlun && (!currlun->memory_image) && needs_medium) { currlun->sense_data = SS_MEDIUM_NOT_PRESENT; return (1); } return (0); } /*------------------------------------------------------------------------* * ustorage_fs_do_cmd - do command * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t ustorage_fs_do_cmd(struct ustorage_fs_softc *sc) { uint8_t error = 1; uint8_t i; uint32_t temp; const uint32_t mask9 = (0xFFFFFFFFUL >> 9) << 9; /* set default data transfer pointer */ sc->sc_transfer.data_ptr = sc->sc_qdata; DPRINTF("cmd_data[0]=0x%02x, data_rem=0x%08x\n", sc->sc_cbw->CBWCDB[0], sc->sc_transfer.data_rem); switch (sc->sc_cbw->CBWCDB[0]) { case SC_INQUIRY: sc->sc_transfer.cmd_dir = DIR_WRITE; error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (1UL << 4) | 1, 0); if (error) { break; } error = ustorage_fs_inquiry(sc); break; case SC_MODE_SELECT_6: sc->sc_transfer.cmd_dir = DIR_READ; error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (1UL << 1) | (1UL << 4) | 1, 0); if (error) { break; } error = ustorage_fs_mode_select(sc); break; case SC_MODE_SELECT_10: sc->sc_transfer.cmd_dir = DIR_READ; error = ustorage_fs_min_len(sc, get_be16(&sc->sc_cbw->CBWCDB[7]), -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (1UL << 1) | (3UL << 7) | 1, 0); if (error) { break; } error = ustorage_fs_mode_select(sc); break; case SC_MODE_SENSE_6: sc->sc_transfer.cmd_dir = DIR_WRITE; error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (1UL << 1) | (1UL << 2) | (1UL << 4) | 1, 0); if (error) { break; } error = ustorage_fs_mode_sense(sc); break; case SC_MODE_SENSE_10: sc->sc_transfer.cmd_dir = DIR_WRITE; error = ustorage_fs_min_len(sc, get_be16(&sc->sc_cbw->CBWCDB[7]), -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (1UL << 1) | (1UL << 2) | (3UL << 7) | 1, 0); if (error) { break; } error = ustorage_fs_mode_sense(sc); break; case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: error = ustorage_fs_min_len(sc, 0, -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (1UL << 4) | 1, 0); if (error) { break; } error = ustorage_fs_prevent_allow(sc); break; case SC_READ_6: i = sc->sc_cbw->CBWCDB[4]; sc->sc_transfer.cmd_dir = DIR_WRITE; temp = ((i == 0) ? 256UL : i); error = ustorage_fs_min_len(sc, temp << 9, mask9); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (7UL << 1) | (1UL << 4) | 1, 1); if (error) { break; } error = ustorage_fs_read(sc); break; case SC_READ_10: sc->sc_transfer.cmd_dir = DIR_WRITE; temp = get_be16(&sc->sc_cbw->CBWCDB[7]); error = ustorage_fs_min_len(sc, temp << 9, mask9); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1); if (error) { break; } error = ustorage_fs_read(sc); break; case SC_READ_12: sc->sc_transfer.cmd_dir = DIR_WRITE; temp = get_be32(&sc->sc_cbw->CBWCDB[6]); if (temp >= (1UL << (32 - 9))) { /* numerical overflow */ sc->sc_csw->bCSWStatus = CSWSTATUS_FAILED; error = 1; break; } error = ustorage_fs_min_len(sc, temp << 9, mask9); if (error) { break; } error = ustorage_fs_check_cmd(sc, 12, (1UL << 1) | (0xfUL << 2) | (0xfUL << 6) | 1, 1); if (error) { break; } error = ustorage_fs_read(sc); break; case SC_READ_CAPACITY: sc->sc_transfer.cmd_dir = DIR_WRITE; error = ustorage_fs_check_cmd(sc, 10, (0xfUL << 2) | (1UL << 8) | 1, 1); if (error) { break; } error = ustorage_fs_read_capacity(sc); break; case SC_READ_FORMAT_CAPACITIES: sc->sc_transfer.cmd_dir = DIR_WRITE; error = ustorage_fs_min_len(sc, get_be16(&sc->sc_cbw->CBWCDB[7]), -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (3UL << 7) | 1, 1); if (error) { break; } error = ustorage_fs_read_format_capacities(sc); break; case SC_REQUEST_SENSE: sc->sc_transfer.cmd_dir = DIR_WRITE; error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (1UL << 4) | 1, 0); if (error) { break; } error = ustorage_fs_request_sense(sc); break; case SC_START_STOP_UNIT: error = ustorage_fs_min_len(sc, 0, -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (1UL << 1) | (1UL << 4) | 1, 0); if (error) { break; } error = ustorage_fs_start_stop(sc); break; case SC_SYNCHRONIZE_CACHE: error = ustorage_fs_min_len(sc, 0, -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (0xfUL << 2) | (3UL << 7) | 1, 1); if (error) { break; } error = ustorage_fs_synchronize_cache(sc); break; case SC_TEST_UNIT_READY: error = ustorage_fs_min_len(sc, 0, -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, 0 | 1, 1); break; /* * Although optional, this command is used by MS-Windows. * We support a minimal version: BytChk must be 0. */ case SC_VERIFY: error = ustorage_fs_min_len(sc, 0, -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1); if (error) { break; } error = ustorage_fs_verify(sc); break; case SC_WRITE_6: i = sc->sc_cbw->CBWCDB[4]; sc->sc_transfer.cmd_dir = DIR_READ; temp = ((i == 0) ? 256UL : i); error = ustorage_fs_min_len(sc, temp << 9, mask9); if (error) { break; } error = ustorage_fs_check_cmd(sc, 6, (7UL << 1) | (1UL << 4) | 1, 1); if (error) { break; } error = ustorage_fs_write(sc); break; case SC_WRITE_10: sc->sc_transfer.cmd_dir = DIR_READ; temp = get_be16(&sc->sc_cbw->CBWCDB[7]); error = ustorage_fs_min_len(sc, temp << 9, mask9); if (error) { break; } error = ustorage_fs_check_cmd(sc, 10, (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1); if (error) { break; } error = ustorage_fs_write(sc); break; case SC_WRITE_12: sc->sc_transfer.cmd_dir = DIR_READ; temp = get_be32(&sc->sc_cbw->CBWCDB[6]); if (temp > (mask9 >> 9)) { /* numerical overflow */ sc->sc_csw->bCSWStatus = CSWSTATUS_FAILED; error = 1; break; } error = ustorage_fs_min_len(sc, temp << 9, mask9); if (error) { break; } error = ustorage_fs_check_cmd(sc, 12, (1UL << 1) | (0xfUL << 2) | (0xfUL << 6) | 1, 1); if (error) { break; } error = ustorage_fs_write(sc); break; /* * Some mandatory commands that we recognize but don't * implement. They don't mean much in this setting. * It's left as an exercise for anyone interested to * implement RESERVE and RELEASE in terms of Posix * locks. */ case SC_FORMAT_UNIT: case SC_RELEASE: case SC_RESERVE: case SC_SEND_DIAGNOSTIC: /* Fallthrough */ default: error = ustorage_fs_min_len(sc, 0, -1U); if (error) { break; } error = ustorage_fs_check_cmd(sc, sc->sc_transfer.cmd_len, 0xff, 0); if (error) { break; } sc->sc_transfer.currlun->sense_data = SS_INVALID_COMMAND; error = 1; break; } return (error); } Index: head/sys/dev/usb/template/usb_template.c =================================================================== --- head/sys/dev/usb/template/usb_template.c (revision 357971) +++ head/sys/dev/usb/template/usb_template.c (revision 357972) @@ -1,1487 +1,1487 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2007 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains sub-routines to build up USB descriptors from * USB templates. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ MODULE_DEPEND(usb_template, usb, 1, 1, 1); MODULE_VERSION(usb_template, 1); /* function prototypes */ static int sysctl_hw_usb_template_power(SYSCTL_HANDLER_ARGS); static void usb_make_raw_desc(struct usb_temp_setup *, const uint8_t *); static void usb_make_endpoint_desc(struct usb_temp_setup *, const struct usb_temp_endpoint_desc *); static void usb_make_interface_desc(struct usb_temp_setup *, const struct usb_temp_interface_desc *); static void usb_make_config_desc(struct usb_temp_setup *, const struct usb_temp_config_desc *); static void usb_make_device_desc(struct usb_temp_setup *, const struct usb_temp_device_desc *); static uint8_t usb_hw_ep_match(const struct usb_hw_ep_profile *, uint8_t, uint8_t); static uint8_t usb_hw_ep_find_match(struct usb_hw_ep_scratch *, struct usb_hw_ep_scratch_sub *, uint8_t); static uint8_t usb_hw_ep_get_needs(struct usb_hw_ep_scratch *, uint8_t, uint8_t); static usb_error_t usb_hw_ep_resolve(struct usb_device *, struct usb_descriptor *); static const struct usb_temp_device_desc *usb_temp_get_tdd(struct usb_device *); static void *usb_temp_get_device_desc(struct usb_device *); static void *usb_temp_get_qualifier_desc(struct usb_device *); static void *usb_temp_get_config_desc(struct usb_device *, uint16_t *, uint8_t); static const void *usb_temp_get_string_desc(struct usb_device *, uint16_t, uint8_t); static const void *usb_temp_get_vendor_desc(struct usb_device *, const struct usb_device_request *, uint16_t *plen); static const void *usb_temp_get_hub_desc(struct usb_device *); static usb_error_t usb_temp_get_desc(struct usb_device *, struct usb_device_request *, const void **, uint16_t *); static usb_error_t usb_temp_setup_by_index(struct usb_device *, uint16_t index); static void usb_temp_init(void *); -SYSCTL_NODE(_hw_usb, OID_AUTO, templates, CTLFLAG_RW, 0, +SYSCTL_NODE(_hw_usb, OID_AUTO, templates, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB device side templates"); SYSCTL_PROC(_hw_usb, OID_AUTO, template_power, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL, 0, sysctl_hw_usb_template_power, "I", "USB bus power consumption in mA at 5V"); static int usb_template_power = 500; /* 500mA */ static int sysctl_hw_usb_template_power(SYSCTL_HANDLER_ARGS) { int error, val; val = usb_template_power; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (val < 0 || val > 500) return (EINVAL); usb_template_power = val; return (0); } /*------------------------------------------------------------------------* * usb_decode_str_desc * * Helper function to decode string descriptors into a C string. *------------------------------------------------------------------------*/ void usb_decode_str_desc(struct usb_string_descriptor *sd, char *buf, size_t buflen) { size_t i; if (sd->bLength < 2) { buf[0] = '\0'; return; } for (i = 0; i < buflen - 1 && i < (sd->bLength / 2) - 1; i++) buf[i] = UGETW(sd->bString[i]); buf[i] = '\0'; } /*------------------------------------------------------------------------* * usb_temp_sysctl * * Callback for SYSCTL_PROC(9), to set and retrieve template string * descriptors. *------------------------------------------------------------------------*/ int usb_temp_sysctl(SYSCTL_HANDLER_ARGS) { char buf[128]; struct usb_string_descriptor *sd = arg1; size_t len, sdlen = arg2; int error; usb_decode_str_desc(sd, buf, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); len = usb_make_str_desc(sd, sdlen, buf); if (len == 0) return (EINVAL); return (0); } /*------------------------------------------------------------------------* * usb_make_raw_desc * * This function will insert a raw USB descriptor into the generated * USB configuration. *------------------------------------------------------------------------*/ static void usb_make_raw_desc(struct usb_temp_setup *temp, const uint8_t *raw) { void *dst; uint8_t len; /* * The first byte of any USB descriptor gives the length. */ if (raw) { len = raw[0]; if (temp->buf) { dst = USB_ADD_BYTES(temp->buf, temp->size); memcpy(dst, raw, len); /* check if we have got a CDC union descriptor */ if ((raw[0] == sizeof(struct usb_cdc_union_descriptor)) && (raw[1] == UDESC_CS_INTERFACE) && (raw[2] == UDESCSUB_CDC_UNION)) { struct usb_cdc_union_descriptor *ud = (void *)dst; /* update the interface numbers */ ud->bMasterInterface += temp->bInterfaceNumber; ud->bSlaveInterface[0] += temp->bInterfaceNumber; } /* check if we have got an interface association descriptor */ if ((raw[0] == sizeof(struct usb_interface_assoc_descriptor)) && (raw[1] == UDESC_IFACE_ASSOC)) { struct usb_interface_assoc_descriptor *iad = (void *)dst; /* update the interface number */ iad->bFirstInterface += temp->bInterfaceNumber; } /* check if we have got a call management descriptor */ if ((raw[0] == sizeof(struct usb_cdc_cm_descriptor)) && (raw[1] == UDESC_CS_INTERFACE) && (raw[2] == UDESCSUB_CDC_CM)) { struct usb_cdc_cm_descriptor *ccd = (void *)dst; /* update the interface number */ ccd->bDataInterface += temp->bInterfaceNumber; } } temp->size += len; } } /*------------------------------------------------------------------------* * usb_make_endpoint_desc * * This function will generate an USB endpoint descriptor from the * given USB template endpoint descriptor, which will be inserted into * the USB configuration. *------------------------------------------------------------------------*/ static void usb_make_endpoint_desc(struct usb_temp_setup *temp, const struct usb_temp_endpoint_desc *ted) { struct usb_endpoint_descriptor *ed; const void **rd; uint16_t old_size; uint16_t mps; uint8_t ea; /* Endpoint Address */ uint8_t et; /* Endpiont Type */ /* Reserve memory */ old_size = temp->size; ea = (ted->bEndpointAddress & (UE_ADDR | UE_DIR_IN | UE_DIR_OUT)); et = (ted->bmAttributes & UE_XFERTYPE); if (et == UE_ISOCHRONOUS) { /* account for extra byte fields */ temp->size += sizeof(*ed) + 2; } else { temp->size += sizeof(*ed); } /* Scan all Raw Descriptors first */ rd = ted->ppRawDesc; if (rd) { while (*rd) { usb_make_raw_desc(temp, *rd); rd++; } } if (ted->pPacketSize == NULL) { /* not initialized */ temp->err = USB_ERR_INVAL; return; } mps = ted->pPacketSize->mps[temp->usb_speed]; if (mps == 0) { /* not initialized */ temp->err = USB_ERR_INVAL; return; } else if (mps == UE_ZERO_MPS) { /* escape for Zero Max Packet Size */ mps = 0; } /* * Fill out the real USB endpoint descriptor * in case there is a buffer present: */ if (temp->buf) { ed = USB_ADD_BYTES(temp->buf, old_size); if (et == UE_ISOCHRONOUS) ed->bLength = sizeof(*ed) + 2; else ed->bLength = sizeof(*ed); ed->bDescriptorType = UDESC_ENDPOINT; ed->bEndpointAddress = ea; ed->bmAttributes = ted->bmAttributes; USETW(ed->wMaxPacketSize, mps); /* setup bInterval parameter */ if (ted->pIntervals && ted->pIntervals->bInterval[temp->usb_speed]) { ed->bInterval = ted->pIntervals->bInterval[temp->usb_speed]; } else { switch (et) { case UE_BULK: case UE_CONTROL: ed->bInterval = 0; /* not used */ break; case UE_INTERRUPT: switch (temp->usb_speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: ed->bInterval = 1; /* 1 ms */ break; default: ed->bInterval = 4; /* 1 ms */ break; } break; default: /* UE_ISOCHRONOUS */ switch (temp->usb_speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: ed->bInterval = 1; /* 1 ms */ break; default: ed->bInterval = 1; /* 125 us */ break; } break; } } } temp->bNumEndpoints++; } /*------------------------------------------------------------------------* * usb_make_interface_desc * * This function will generate an USB interface descriptor from the * given USB template interface descriptor, which will be inserted * into the USB configuration. *------------------------------------------------------------------------*/ static void usb_make_interface_desc(struct usb_temp_setup *temp, const struct usb_temp_interface_desc *tid) { struct usb_interface_descriptor *id; const struct usb_temp_endpoint_desc **ted; const void **rd; uint16_t old_size; /* Reserve memory */ old_size = temp->size; temp->size += sizeof(*id); /* Update interface and alternate interface numbers */ if (tid->isAltInterface == 0) { temp->bAlternateSetting = 0; temp->bInterfaceNumber++; } else { temp->bAlternateSetting++; } /* Scan all Raw Descriptors first */ rd = tid->ppRawDesc; if (rd) { while (*rd) { usb_make_raw_desc(temp, *rd); rd++; } } /* Reset some counters */ temp->bNumEndpoints = 0; /* Scan all Endpoint Descriptors second */ ted = tid->ppEndpoints; if (ted) { while (*ted) { usb_make_endpoint_desc(temp, *ted); ted++; } } /* * Fill out the real USB interface descriptor * in case there is a buffer present: */ if (temp->buf) { id = USB_ADD_BYTES(temp->buf, old_size); id->bLength = sizeof(*id); id->bDescriptorType = UDESC_INTERFACE; id->bInterfaceNumber = temp->bInterfaceNumber; id->bAlternateSetting = temp->bAlternateSetting; id->bNumEndpoints = temp->bNumEndpoints; id->bInterfaceClass = tid->bInterfaceClass; id->bInterfaceSubClass = tid->bInterfaceSubClass; id->bInterfaceProtocol = tid->bInterfaceProtocol; id->iInterface = tid->iInterface; } } /*------------------------------------------------------------------------* * usb_make_config_desc * * This function will generate an USB config descriptor from the given * USB template config descriptor, which will be inserted into the USB * configuration. *------------------------------------------------------------------------*/ static void usb_make_config_desc(struct usb_temp_setup *temp, const struct usb_temp_config_desc *tcd) { struct usb_config_descriptor *cd; const struct usb_temp_interface_desc **tid; uint16_t old_size; int power; /* Reserve memory */ old_size = temp->size; temp->size += sizeof(*cd); /* Reset some counters */ temp->bInterfaceNumber = 0xFF; temp->bAlternateSetting = 0; /* Scan all the USB interfaces */ tid = tcd->ppIfaceDesc; if (tid) { while (*tid) { usb_make_interface_desc(temp, *tid); tid++; } } /* * Fill out the real USB config descriptor * in case there is a buffer present: */ if (temp->buf) { cd = USB_ADD_BYTES(temp->buf, old_size); /* compute total size */ old_size = temp->size - old_size; cd->bLength = sizeof(*cd); cd->bDescriptorType = UDESC_CONFIG; USETW(cd->wTotalLength, old_size); cd->bNumInterface = temp->bInterfaceNumber + 1; cd->bConfigurationValue = temp->bConfigurationValue; cd->iConfiguration = tcd->iConfiguration; cd->bmAttributes = tcd->bmAttributes; power = usb_template_power; cd->bMaxPower = power / 2; /* 2 mA units */ cd->bmAttributes |= UC_REMOTE_WAKEUP; if (power > 0) { cd->bmAttributes |= UC_BUS_POWERED; cd->bmAttributes &= ~UC_SELF_POWERED; } else { cd->bmAttributes &= ~UC_BUS_POWERED; cd->bmAttributes |= UC_SELF_POWERED; } } } /*------------------------------------------------------------------------* * usb_make_device_desc * * This function will generate an USB device descriptor from the * given USB template device descriptor. *------------------------------------------------------------------------*/ static void usb_make_device_desc(struct usb_temp_setup *temp, const struct usb_temp_device_desc *tdd) { struct usb_temp_data *utd; const struct usb_temp_config_desc **tcd; uint16_t old_size; /* Reserve memory */ old_size = temp->size; temp->size += sizeof(*utd); /* Scan all the USB configs */ temp->bConfigurationValue = 1; tcd = tdd->ppConfigDesc; if (tcd) { while (*tcd) { usb_make_config_desc(temp, *tcd); temp->bConfigurationValue++; tcd++; } } /* * Fill out the real USB device descriptor * in case there is a buffer present: */ if (temp->buf) { utd = USB_ADD_BYTES(temp->buf, old_size); /* Store a pointer to our template device descriptor */ utd->tdd = tdd; /* Fill out USB device descriptor */ utd->udd.bLength = sizeof(utd->udd); utd->udd.bDescriptorType = UDESC_DEVICE; utd->udd.bDeviceClass = tdd->bDeviceClass; utd->udd.bDeviceSubClass = tdd->bDeviceSubClass; utd->udd.bDeviceProtocol = tdd->bDeviceProtocol; USETW(utd->udd.idVendor, tdd->idVendor); USETW(utd->udd.idProduct, tdd->idProduct); USETW(utd->udd.bcdDevice, tdd->bcdDevice); utd->udd.iManufacturer = tdd->iManufacturer; utd->udd.iProduct = tdd->iProduct; utd->udd.iSerialNumber = tdd->iSerialNumber; utd->udd.bNumConfigurations = temp->bConfigurationValue - 1; /* * Fill out the USB device qualifier. Pretend that we * don't support any other speeds by setting * "bNumConfigurations" equal to zero. That saves us * generating an extra set of configuration * descriptors. */ utd->udq.bLength = sizeof(utd->udq); utd->udq.bDescriptorType = UDESC_DEVICE_QUALIFIER; utd->udq.bDeviceClass = tdd->bDeviceClass; utd->udq.bDeviceSubClass = tdd->bDeviceSubClass; utd->udq.bDeviceProtocol = tdd->bDeviceProtocol; utd->udq.bNumConfigurations = 0; USETW(utd->udq.bcdUSB, 0x0200); utd->udq.bMaxPacketSize0 = 0; switch (temp->usb_speed) { case USB_SPEED_LOW: USETW(utd->udd.bcdUSB, 0x0110); utd->udd.bMaxPacketSize = 8; break; case USB_SPEED_FULL: USETW(utd->udd.bcdUSB, 0x0110); utd->udd.bMaxPacketSize = 32; break; case USB_SPEED_HIGH: USETW(utd->udd.bcdUSB, 0x0200); utd->udd.bMaxPacketSize = 64; break; case USB_SPEED_VARIABLE: USETW(utd->udd.bcdUSB, 0x0250); utd->udd.bMaxPacketSize = 255; /* 512 bytes */ break; case USB_SPEED_SUPER: USETW(utd->udd.bcdUSB, 0x0300); utd->udd.bMaxPacketSize = 9; /* 2**9 = 512 bytes */ break; default: temp->err = USB_ERR_INVAL; break; } } } /*------------------------------------------------------------------------* * usb_hw_ep_match * * Return values: * 0: The endpoint profile does not match the criteria * Else: The endpoint profile matches the criteria *------------------------------------------------------------------------*/ static uint8_t usb_hw_ep_match(const struct usb_hw_ep_profile *pf, uint8_t ep_type, uint8_t ep_dir_in) { if (ep_type == UE_CONTROL) { /* special */ return (pf->support_control); } if ((pf->support_in && ep_dir_in) || (pf->support_out && !ep_dir_in)) { if ((pf->support_interrupt && (ep_type == UE_INTERRUPT)) || (pf->support_isochronous && (ep_type == UE_ISOCHRONOUS)) || (pf->support_bulk && (ep_type == UE_BULK))) { return (1); } } return (0); } /*------------------------------------------------------------------------* * usb_hw_ep_find_match * * This function is used to find the best matching endpoint profile * for and endpoint belonging to an USB descriptor. * * Return values: * 0: Success. Got a match. * Else: Failure. No match. *------------------------------------------------------------------------*/ static uint8_t usb_hw_ep_find_match(struct usb_hw_ep_scratch *ues, struct usb_hw_ep_scratch_sub *ep, uint8_t is_simplex) { const struct usb_hw_ep_profile *pf; uint16_t distance; uint16_t temp; uint16_t max_frame_size; uint8_t n; uint8_t best_n; uint8_t dir_in; uint8_t dir_out; distance = 0xFFFF; best_n = 0; if ((!ep->needs_in) && (!ep->needs_out)) { return (0); /* we are done */ } if (ep->needs_ep_type == UE_CONTROL) { dir_in = 1; dir_out = 1; } else { if (ep->needs_in) { dir_in = 1; dir_out = 0; } else { dir_in = 0; dir_out = 1; } } for (n = 1; n != (USB_EP_MAX / 2); n++) { /* get HW endpoint profile */ (ues->methods->get_hw_ep_profile) (ues->udev, &pf, n); if (pf == NULL) { /* end of profiles */ break; } /* check if IN-endpoint is reserved */ if (dir_in || pf->is_simplex) { if (ues->bmInAlloc[n / 8] & (1 << (n % 8))) { /* mismatch */ continue; } } /* check if OUT-endpoint is reserved */ if (dir_out || pf->is_simplex) { if (ues->bmOutAlloc[n / 8] & (1 << (n % 8))) { /* mismatch */ continue; } } /* check simplex */ if (pf->is_simplex == is_simplex) { /* mismatch */ continue; } /* check if HW endpoint matches */ if (!usb_hw_ep_match(pf, ep->needs_ep_type, dir_in)) { /* mismatch */ continue; } /* get maximum frame size */ if (dir_in) max_frame_size = pf->max_in_frame_size; else max_frame_size = pf->max_out_frame_size; /* check if we have a matching profile */ if (max_frame_size >= ep->max_frame_size) { temp = (max_frame_size - ep->max_frame_size); if (distance > temp) { distance = temp; best_n = n; ep->pf = pf; } } } /* see if we got a match */ if (best_n != 0) { /* get the correct profile */ pf = ep->pf; /* reserve IN-endpoint */ if (dir_in) { ues->bmInAlloc[best_n / 8] |= (1 << (best_n % 8)); ep->hw_endpoint_in = best_n | UE_DIR_IN; ep->needs_in = 0; } /* reserve OUT-endpoint */ if (dir_out) { ues->bmOutAlloc[best_n / 8] |= (1 << (best_n % 8)); ep->hw_endpoint_out = best_n | UE_DIR_OUT; ep->needs_out = 0; } return (0); /* got a match */ } return (1); /* failure */ } /*------------------------------------------------------------------------* * usb_hw_ep_get_needs * * This function will figure out the type and number of endpoints * which are needed for an USB configuration. * * Return values: * 0: Success. * Else: Failure. *------------------------------------------------------------------------*/ static uint8_t usb_hw_ep_get_needs(struct usb_hw_ep_scratch *ues, uint8_t ep_type, uint8_t is_complete) { const struct usb_hw_ep_profile *pf; struct usb_hw_ep_scratch_sub *ep_iface; struct usb_hw_ep_scratch_sub *ep_curr; struct usb_hw_ep_scratch_sub *ep_max; struct usb_hw_ep_scratch_sub *ep_end; struct usb_descriptor *desc; struct usb_interface_descriptor *id; struct usb_endpoint_descriptor *ed; enum usb_dev_speed speed; uint16_t wMaxPacketSize; uint16_t temp; uint8_t ep_no; ep_iface = ues->ep_max; ep_curr = ues->ep_max; ep_end = ues->ep + USB_EP_MAX; ep_max = ues->ep_max; desc = NULL; speed = usbd_get_speed(ues->udev); repeat: while ((desc = usb_desc_foreach(ues->cd, desc))) { if ((desc->bDescriptorType == UDESC_INTERFACE) && (desc->bLength >= sizeof(*id))) { id = (void *)desc; if (id->bAlternateSetting == 0) { /* going forward */ ep_iface = ep_max; } else { /* reset */ ep_curr = ep_iface; } } if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= sizeof(*ed))) { ed = (void *)desc; goto handle_endpoint_desc; } } ues->ep_max = ep_max; return (0); handle_endpoint_desc: temp = (ed->bmAttributes & UE_XFERTYPE); if (temp == ep_type) { if (ep_curr == ep_end) { /* too many endpoints */ return (1); /* failure */ } wMaxPacketSize = UGETW(ed->wMaxPacketSize); if ((wMaxPacketSize & 0xF800) && (speed == USB_SPEED_HIGH)) { /* handle packet multiplier */ temp = (wMaxPacketSize >> 11) & 3; wMaxPacketSize &= 0x7FF; if (temp == 1) { wMaxPacketSize *= 2; } else { wMaxPacketSize *= 3; } } /* * Check if we have a fixed endpoint number, else the * endpoint number is allocated dynamically: */ ep_no = (ed->bEndpointAddress & UE_ADDR); if (ep_no != 0) { /* get HW endpoint profile */ (ues->methods->get_hw_ep_profile) (ues->udev, &pf, ep_no); if (pf == NULL) { /* HW profile does not exist - failure */ DPRINTFN(0, "Endpoint profile %u " "does not exist\n", ep_no); return (1); } /* reserve fixed endpoint number */ if (ep_type == UE_CONTROL) { ues->bmInAlloc[ep_no / 8] |= (1 << (ep_no % 8)); ues->bmOutAlloc[ep_no / 8] |= (1 << (ep_no % 8)); if ((pf->max_in_frame_size < wMaxPacketSize) || (pf->max_out_frame_size < wMaxPacketSize)) { DPRINTFN(0, "Endpoint profile %u " "has too small buffer\n", ep_no); return (1); } } else if (ed->bEndpointAddress & UE_DIR_IN) { ues->bmInAlloc[ep_no / 8] |= (1 << (ep_no % 8)); if (pf->max_in_frame_size < wMaxPacketSize) { DPRINTFN(0, "Endpoint profile %u " "has too small buffer\n", ep_no); return (1); } } else { ues->bmOutAlloc[ep_no / 8] |= (1 << (ep_no % 8)); if (pf->max_out_frame_size < wMaxPacketSize) { DPRINTFN(0, "Endpoint profile %u " "has too small buffer\n", ep_no); return (1); } } } else if (is_complete) { /* check if we have enough buffer space */ if (wMaxPacketSize > ep_curr->max_frame_size) { return (1); /* failure */ } if (ed->bEndpointAddress & UE_DIR_IN) { ed->bEndpointAddress = ep_curr->hw_endpoint_in; } else { ed->bEndpointAddress = ep_curr->hw_endpoint_out; } } else { /* compute the maximum frame size */ if (ep_curr->max_frame_size < wMaxPacketSize) { ep_curr->max_frame_size = wMaxPacketSize; } if (temp == UE_CONTROL) { ep_curr->needs_in = 1; ep_curr->needs_out = 1; } else { if (ed->bEndpointAddress & UE_DIR_IN) { ep_curr->needs_in = 1; } else { ep_curr->needs_out = 1; } } ep_curr->needs_ep_type = ep_type; } ep_curr++; if (ep_max < ep_curr) { ep_max = ep_curr; } } goto repeat; } /*------------------------------------------------------------------------* * usb_hw_ep_resolve * * This function will try to resolve endpoint requirements by the * given endpoint profiles that the USB hardware reports. * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static usb_error_t usb_hw_ep_resolve(struct usb_device *udev, struct usb_descriptor *desc) { struct usb_hw_ep_scratch *ues; struct usb_hw_ep_scratch_sub *ep; const struct usb_hw_ep_profile *pf; const struct usb_bus_methods *methods; struct usb_device_descriptor *dd; uint16_t mps; if (desc == NULL) return (USB_ERR_INVAL); /* get bus methods */ methods = udev->bus->methods; if (methods->get_hw_ep_profile == NULL) return (USB_ERR_INVAL); if (desc->bDescriptorType == UDESC_DEVICE) { if (desc->bLength < sizeof(*dd)) return (USB_ERR_INVAL); dd = (void *)desc; /* get HW control endpoint 0 profile */ (methods->get_hw_ep_profile) (udev, &pf, 0); if (pf == NULL) { return (USB_ERR_INVAL); } if (!usb_hw_ep_match(pf, UE_CONTROL, 0)) { DPRINTFN(0, "Endpoint 0 does not " "support control\n"); return (USB_ERR_INVAL); } mps = dd->bMaxPacketSize; if (udev->speed == USB_SPEED_FULL) { /* * We can optionally choose another packet size ! */ while (1) { /* check if "mps" is ok */ if (pf->max_in_frame_size >= mps) { break; } /* reduce maximum packet size */ mps /= 2; /* check if "mps" is too small */ if (mps < 8) { return (USB_ERR_INVAL); } } dd->bMaxPacketSize = mps; } else { /* We only have one choice */ if (mps == 255) { mps = 512; } /* Check if we support the specified wMaxPacketSize */ if (pf->max_in_frame_size < mps) { return (USB_ERR_INVAL); } } return (0); /* success */ } if (desc->bDescriptorType != UDESC_CONFIG) return (USB_ERR_INVAL); if (desc->bLength < sizeof(*(ues->cd))) return (USB_ERR_INVAL); ues = udev->scratch.hw_ep_scratch; memset(ues, 0, sizeof(*ues)); ues->ep_max = ues->ep; ues->cd = (void *)desc; ues->methods = methods; ues->udev = udev; /* Get all the endpoints we need */ if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 0) || usb_hw_ep_get_needs(ues, UE_INTERRUPT, 0) || usb_hw_ep_get_needs(ues, UE_CONTROL, 0) || usb_hw_ep_get_needs(ues, UE_BULK, 0)) { DPRINTFN(0, "Could not get needs\n"); return (USB_ERR_INVAL); } for (ep = ues->ep; ep != ues->ep_max; ep++) { while (ep->needs_in || ep->needs_out) { /* * First try to use a simplex endpoint. * Then try to use a duplex endpoint. */ if (usb_hw_ep_find_match(ues, ep, 1) && usb_hw_ep_find_match(ues, ep, 0)) { DPRINTFN(0, "Could not find match\n"); return (USB_ERR_INVAL); } } } ues->ep_max = ues->ep; /* Update all endpoint addresses */ if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 1) || usb_hw_ep_get_needs(ues, UE_INTERRUPT, 1) || usb_hw_ep_get_needs(ues, UE_CONTROL, 1) || usb_hw_ep_get_needs(ues, UE_BULK, 1)) { DPRINTFN(0, "Could not update endpoint address\n"); return (USB_ERR_INVAL); } return (0); /* success */ } /*------------------------------------------------------------------------* * usb_temp_get_tdd * * Returns: * NULL: No USB template device descriptor found. * Else: Pointer to the USB template device descriptor. *------------------------------------------------------------------------*/ static const struct usb_temp_device_desc * usb_temp_get_tdd(struct usb_device *udev) { if (udev->usb_template_ptr == NULL) { return (NULL); } return (udev->usb_template_ptr->tdd); } /*------------------------------------------------------------------------* * usb_temp_get_device_desc * * Returns: * NULL: No USB device descriptor found. * Else: Pointer to USB device descriptor. *------------------------------------------------------------------------*/ static void * usb_temp_get_device_desc(struct usb_device *udev) { struct usb_device_descriptor *dd; if (udev->usb_template_ptr == NULL) { return (NULL); } dd = &udev->usb_template_ptr->udd; if (dd->bDescriptorType != UDESC_DEVICE) { /* sanity check failed */ return (NULL); } return (dd); } /*------------------------------------------------------------------------* * usb_temp_get_qualifier_desc * * Returns: * NULL: No USB device_qualifier descriptor found. * Else: Pointer to USB device_qualifier descriptor. *------------------------------------------------------------------------*/ static void * usb_temp_get_qualifier_desc(struct usb_device *udev) { struct usb_device_qualifier *dq; if (udev->usb_template_ptr == NULL) { return (NULL); } dq = &udev->usb_template_ptr->udq; if (dq->bDescriptorType != UDESC_DEVICE_QUALIFIER) { /* sanity check failed */ return (NULL); } return (dq); } /*------------------------------------------------------------------------* * usb_temp_get_config_desc * * Returns: * NULL: No USB config descriptor found. * Else: Pointer to USB config descriptor having index "index". *------------------------------------------------------------------------*/ static void * usb_temp_get_config_desc(struct usb_device *udev, uint16_t *pLength, uint8_t index) { struct usb_device_descriptor *dd; struct usb_config_descriptor *cd; uint16_t temp; if (udev->usb_template_ptr == NULL) { return (NULL); } dd = &udev->usb_template_ptr->udd; cd = (void *)(udev->usb_template_ptr + 1); if (index >= dd->bNumConfigurations) { /* out of range */ return (NULL); } while (index--) { if (cd->bDescriptorType != UDESC_CONFIG) { /* sanity check failed */ return (NULL); } temp = UGETW(cd->wTotalLength); cd = USB_ADD_BYTES(cd, temp); } if (pLength) { *pLength = UGETW(cd->wTotalLength); } return (cd); } /*------------------------------------------------------------------------* * usb_temp_get_vendor_desc * * Returns: * NULL: No vendor descriptor found. * Else: Pointer to a vendor descriptor. *------------------------------------------------------------------------*/ static const void * usb_temp_get_vendor_desc(struct usb_device *udev, const struct usb_device_request *req, uint16_t *plen) { const struct usb_temp_device_desc *tdd; tdd = usb_temp_get_tdd(udev); if (tdd == NULL) { return (NULL); } if (tdd->getVendorDesc == NULL) { return (NULL); } return ((tdd->getVendorDesc) (req, plen)); } /*------------------------------------------------------------------------* * usb_temp_get_string_desc * * Returns: * NULL: No string descriptor found. * Else: Pointer to a string descriptor. *------------------------------------------------------------------------*/ static const void * usb_temp_get_string_desc(struct usb_device *udev, uint16_t lang_id, uint8_t string_index) { const struct usb_temp_device_desc *tdd; tdd = usb_temp_get_tdd(udev); if (tdd == NULL) { return (NULL); } if (tdd->getStringDesc == NULL) { return (NULL); } return ((tdd->getStringDesc) (lang_id, string_index)); } /*------------------------------------------------------------------------* * usb_temp_get_hub_desc * * Returns: * NULL: No USB HUB descriptor found. * Else: Pointer to a USB HUB descriptor. *------------------------------------------------------------------------*/ static const void * usb_temp_get_hub_desc(struct usb_device *udev) { return (NULL); /* needs to be implemented */ } /*------------------------------------------------------------------------* * usb_temp_get_desc * * This function is a demultiplexer for local USB device side control * endpoint requests. *------------------------------------------------------------------------*/ static usb_error_t usb_temp_get_desc(struct usb_device *udev, struct usb_device_request *req, const void **pPtr, uint16_t *pLength) { const uint8_t *buf; uint16_t len; buf = NULL; len = 0; switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; default: goto tr_stalled; } case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; default: goto tr_stalled; } default: goto tr_stalled; } tr_handle_get_descriptor: switch (req->wValue[1]) { case UDESC_DEVICE: if (req->wValue[0]) { goto tr_stalled; } buf = usb_temp_get_device_desc(udev); goto tr_valid; case UDESC_DEVICE_QUALIFIER: if (udev->speed != USB_SPEED_HIGH) { goto tr_stalled; } if (req->wValue[0]) { goto tr_stalled; } buf = usb_temp_get_qualifier_desc(udev); goto tr_valid; case UDESC_OTHER_SPEED_CONFIGURATION: if (udev->speed != USB_SPEED_HIGH) { goto tr_stalled; } case UDESC_CONFIG: buf = usb_temp_get_config_desc(udev, &len, req->wValue[0]); goto tr_valid; case UDESC_STRING: buf = usb_temp_get_string_desc(udev, UGETW(req->wIndex), req->wValue[0]); goto tr_valid; default: goto tr_stalled; } tr_handle_get_class_descriptor: if (req->wValue[0]) { goto tr_stalled; } buf = usb_temp_get_hub_desc(udev); goto tr_valid; tr_valid: if (buf == NULL) goto tr_stalled; if (len == 0) len = buf[0]; *pPtr = buf; *pLength = len; return (0); /* success */ tr_stalled: /* try to get a vendor specific descriptor */ len = 0; buf = usb_temp_get_vendor_desc(udev, req, &len); if (buf != NULL) goto tr_valid; *pPtr = NULL; *pLength = 0; return (0); /* we ignore failures */ } /*------------------------------------------------------------------------* * usb_temp_setup * * This function generates USB descriptors according to the given USB * template device descriptor. It will also try to figure out the best * matching endpoint addresses using the hardware endpoint profiles. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usb_temp_setup(struct usb_device *udev, const struct usb_temp_device_desc *tdd) { struct usb_temp_setup *uts; void *buf; usb_error_t error; uint8_t n; uint8_t do_unlock; /* be NULL safe */ if (tdd == NULL) return (0); /* Protect scratch area */ do_unlock = usbd_ctrl_lock(udev); uts = udev->scratch.temp_setup; memset(uts, 0, sizeof(*uts)); uts->usb_speed = udev->speed; uts->self_powered = udev->flags.self_powered; /* first pass */ usb_make_device_desc(uts, tdd); if (uts->err) { /* some error happened */ goto done; } /* sanity check */ if (uts->size == 0) { uts->err = USB_ERR_INVAL; goto done; } /* allocate zeroed memory */ uts->buf = usbd_alloc_config_desc(udev, uts->size); /* * Allow malloc() to return NULL regardless of M_WAITOK flag. * This helps when porting the software to non-FreeBSD * systems. */ if (uts->buf == NULL) { /* could not allocate memory */ uts->err = USB_ERR_NOMEM; goto done; } /* second pass */ uts->size = 0; usb_make_device_desc(uts, tdd); /* * Store a pointer to our descriptors: */ udev->usb_template_ptr = uts->buf; if (uts->err) { /* some error happened during second pass */ goto done; } /* * Resolve all endpoint addresses ! */ buf = usb_temp_get_device_desc(udev); uts->err = usb_hw_ep_resolve(udev, buf); if (uts->err) { DPRINTFN(0, "Could not resolve endpoints for " "Device Descriptor, error = %s\n", usbd_errstr(uts->err)); goto done; } for (n = 0;; n++) { buf = usb_temp_get_config_desc(udev, NULL, n); if (buf == NULL) { break; } uts->err = usb_hw_ep_resolve(udev, buf); if (uts->err) { DPRINTFN(0, "Could not resolve endpoints for " "Config Descriptor %u, error = %s\n", n, usbd_errstr(uts->err)); goto done; } } done: error = uts->err; if (error) usb_temp_unsetup(udev); if (do_unlock) usbd_ctrl_unlock(udev); return (error); } /*------------------------------------------------------------------------* * usb_temp_unsetup * * This function frees any memory associated with the currently * setup template, if any. *------------------------------------------------------------------------*/ void usb_temp_unsetup(struct usb_device *udev) { usbd_free_config_desc(udev, udev->usb_template_ptr); udev->usb_template_ptr = NULL; } static usb_error_t usb_temp_setup_by_index(struct usb_device *udev, uint16_t index) { usb_error_t err; switch (index) { case USB_TEMP_MSC: err = usb_temp_setup(udev, &usb_template_msc); break; case USB_TEMP_CDCE: err = usb_temp_setup(udev, &usb_template_cdce); break; case USB_TEMP_MTP: err = usb_temp_setup(udev, &usb_template_mtp); break; case USB_TEMP_MODEM: err = usb_temp_setup(udev, &usb_template_modem); break; case USB_TEMP_AUDIO: err = usb_temp_setup(udev, &usb_template_audio); break; case USB_TEMP_KBD: err = usb_temp_setup(udev, &usb_template_kbd); break; case USB_TEMP_MOUSE: err = usb_temp_setup(udev, &usb_template_mouse); break; case USB_TEMP_PHONE: err = usb_temp_setup(udev, &usb_template_phone); break; case USB_TEMP_SERIALNET: err = usb_temp_setup(udev, &usb_template_serialnet); break; case USB_TEMP_MIDI: err = usb_temp_setup(udev, &usb_template_midi); break; case USB_TEMP_MULTI: err = usb_temp_setup(udev, &usb_template_multi); break; case USB_TEMP_CDCEEM: err = usb_temp_setup(udev, &usb_template_cdceem); break; default: return (USB_ERR_INVAL); } return (err); } static void usb_temp_init(void *arg) { /* register our functions */ usb_temp_get_desc_p = &usb_temp_get_desc; usb_temp_setup_by_index_p = &usb_temp_setup_by_index; usb_temp_unsetup_p = &usb_temp_unsetup; } SYSINIT(usb_temp_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_temp_init, NULL); SYSUNINIT(usb_temp_unload, SI_SUB_LOCK, SI_ORDER_ANY, usb_temp_unload, NULL); Index: head/sys/dev/usb/template/usb_template_audio.c =================================================================== --- head/sys/dev/usb/template/usb_template_audio.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_audio.c (revision 357972) @@ -1,480 +1,480 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for an USB Audio Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { AUDIO_LANG_INDEX, AUDIO_MIXER_INDEX, AUDIO_RECORD_INDEX, AUDIO_PLAYBACK_INDEX, AUDIO_MANUFACTURER_INDEX, AUDIO_PRODUCT_INDEX, AUDIO_SERIAL_NUMBER_INDEX, AUDIO_MAX_INDEX, }; #define AUDIO_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define AUDIO_DEFAULT_PRODUCT_ID 0x27e0 #define AUDIO_DEFAULT_MIXER "Mixer interface" #define AUDIO_DEFAULT_RECORD "Record interface" #define AUDIO_DEFAULT_PLAYBACK "Playback interface" #define AUDIO_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define AUDIO_DEFAULT_PRODUCT "Audio Test Device" #define AUDIO_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor audio_mixer; static struct usb_string_descriptor audio_record; static struct usb_string_descriptor audio_playback; static struct usb_string_descriptor audio_manufacturer; static struct usb_string_descriptor audio_product; static struct usb_string_descriptor audio_serial_number; static struct sysctl_ctx_list audio_ctx_list; /* prototypes */ /* * Audio Mixer description structures * * Some of the audio descriptors were dumped * from a Creative Labs USB audio device. */ static const uint8_t audio_raw_desc_0[] = { 0x0a, 0x24, 0x01, 0x00, 0x01, 0xa9, 0x00, 0x02, 0x01, 0x02 }; static const uint8_t audio_raw_desc_1[] = { 0x0c, 0x24, 0x02, 0x01, 0x01, 0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_2[] = { 0x0c, 0x24, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_3[] = { 0x0c, 0x24, 0x02, 0x03, 0x03, 0x06, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_4[] = { 0x0c, 0x24, 0x02, 0x04, 0x05, 0x06, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_5[] = { 0x09, 0x24, 0x03, 0x05, 0x05, 0x06, 0x00, 0x01, 0x00 }; static const uint8_t audio_raw_desc_6[] = { 0x09, 0x24, 0x03, 0x06, 0x01, 0x03, 0x00, 0x09, 0x00 }; static const uint8_t audio_raw_desc_7[] = { 0x09, 0x24, 0x03, 0x07, 0x01, 0x01, 0x00, 0x08, 0x00 }; static const uint8_t audio_raw_desc_8[] = { 0x09, 0x24, 0x05, 0x08, 0x03, 0x0a, 0x0b, 0x0c, 0x00 }; static const uint8_t audio_raw_desc_9[] = { 0x0a, 0x24, 0x06, 0x09, 0x0f, 0x01, 0x01, 0x02, 0x02, 0x00 }; static const uint8_t audio_raw_desc_10[] = { 0x0a, 0x24, 0x06, 0x0a, 0x02, 0x01, 0x43, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_11[] = { 0x0a, 0x24, 0x06, 0x0b, 0x03, 0x01, 0x01, 0x02, 0x02, 0x00 }; static const uint8_t audio_raw_desc_12[] = { 0x0a, 0x24, 0x06, 0x0c, 0x04, 0x01, 0x01, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_13[] = { 0x0a, 0x24, 0x06, 0x0d, 0x02, 0x01, 0x03, 0x00, 0x00, 0x00 }; static const uint8_t audio_raw_desc_14[] = { 0x0a, 0x24, 0x06, 0x0e, 0x03, 0x01, 0x01, 0x02, 0x02, 0x00 }; static const uint8_t audio_raw_desc_15[] = { 0x0f, 0x24, 0x04, 0x0f, 0x03, 0x01, 0x0d, 0x0e, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const void *audio_raw_iface_0_desc[] = { audio_raw_desc_0, audio_raw_desc_1, audio_raw_desc_2, audio_raw_desc_3, audio_raw_desc_4, audio_raw_desc_5, audio_raw_desc_6, audio_raw_desc_7, audio_raw_desc_8, audio_raw_desc_9, audio_raw_desc_10, audio_raw_desc_11, audio_raw_desc_12, audio_raw_desc_13, audio_raw_desc_14, audio_raw_desc_15, NULL, }; static const struct usb_temp_interface_desc audio_iface_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = audio_raw_iface_0_desc, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOCONTROL, .bInterfaceProtocol = 0, .iInterface = AUDIO_MIXER_INDEX, }; static const uint8_t audio_raw_desc_20[] = { 0x07, 0x24, 0x01, 0x01, 0x03, 0x01, 0x00 }; static const uint8_t audio_raw_desc_21[] = { 0x0b, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01, /* 48kHz */ 0x80, 0xbb, 0x00 }; static const uint8_t audio_raw_desc_22[] = { 0x07, 0x25, 0x01, 0x00, 0x01, 0x04, 0x00 }; static const void *audio_raw_iface_1_desc[] = { audio_raw_desc_20, audio_raw_desc_21, NULL, }; static const void *audio_raw_ep_1_desc[] = { audio_raw_desc_22, NULL, }; static const struct usb_temp_packet_size audio_isoc_mps = { .mps[USB_SPEED_FULL] = 0xC8, .mps[USB_SPEED_HIGH] = 0xC8, }; static const struct usb_temp_interval audio_isoc_interval = { .bInterval[USB_SPEED_FULL] = 1, /* 1:1 */ .bInterval[USB_SPEED_HIGH] = 4, /* 1:8 */ }; static const struct usb_temp_endpoint_desc audio_isoc_out_ep = { .ppRawDesc = audio_raw_ep_1_desc, .pPacketSize = &audio_isoc_mps, .pIntervals = &audio_isoc_interval, .bEndpointAddress = UE_DIR_OUT, .bmAttributes = UE_ISOCHRONOUS | UE_ISO_ADAPT, }; static const struct usb_temp_endpoint_desc *audio_iface_1_ep[] = { &audio_isoc_out_ep, NULL, }; static const struct usb_temp_interface_desc audio_iface_1_alt_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = NULL, /* no raw descriptors */ .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = AUDIO_PLAYBACK_INDEX, }; static const struct usb_temp_interface_desc audio_iface_1_alt_1 = { .ppEndpoints = audio_iface_1_ep, .ppRawDesc = audio_raw_iface_1_desc, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = AUDIO_PLAYBACK_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const uint8_t audio_raw_desc_30[] = { 0x07, 0x24, 0x01, 0x07, 0x01, 0x01, 0x00 }; static const uint8_t audio_raw_desc_31[] = { 0x0b, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01, /* 48kHz */ 0x80, 0xbb, 0x00 }; static const uint8_t audio_raw_desc_32[] = { 0x07, 0x25, 0x01, 0x01, 0x00, 0x00, 0x00 }; static const void *audio_raw_iface_2_desc[] = { audio_raw_desc_30, audio_raw_desc_31, NULL, }; static const void *audio_raw_ep_2_desc[] = { audio_raw_desc_32, NULL, }; static const struct usb_temp_endpoint_desc audio_isoc_in_ep = { .ppRawDesc = audio_raw_ep_2_desc, .pPacketSize = &audio_isoc_mps, .pIntervals = &audio_isoc_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_ISOCHRONOUS | UE_ISO_ADAPT, }; static const struct usb_temp_endpoint_desc *audio_iface_2_ep[] = { &audio_isoc_in_ep, NULL, }; static const struct usb_temp_interface_desc audio_iface_2_alt_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = NULL, /* no raw descriptors */ .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = AUDIO_RECORD_INDEX, }; static const struct usb_temp_interface_desc audio_iface_2_alt_1 = { .ppEndpoints = audio_iface_2_ep, .ppRawDesc = audio_raw_iface_2_desc, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = AUDIO_RECORD_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const struct usb_temp_interface_desc *audio_interfaces[] = { &audio_iface_0, &audio_iface_1_alt_0, &audio_iface_1_alt_1, &audio_iface_2_alt_0, &audio_iface_2_alt_1, NULL, }; static const struct usb_temp_config_desc audio_config_desc = { .ppIfaceDesc = audio_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = AUDIO_PRODUCT_INDEX, }; static const struct usb_temp_config_desc *audio_configs[] = { &audio_config_desc, NULL, }; static usb_temp_get_string_desc_t audio_get_string_desc; struct usb_temp_device_desc usb_template_audio = { .getStringDesc = &audio_get_string_desc, .ppConfigDesc = audio_configs, .idVendor = AUDIO_DEFAULT_VENDOR_ID, .idProduct = AUDIO_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = AUDIO_MANUFACTURER_INDEX, .iProduct = AUDIO_PRODUCT_INDEX, .iSerialNumber = AUDIO_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * audio_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * audio_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[AUDIO_MAX_INDEX] = { [AUDIO_LANG_INDEX] = &usb_string_lang_en, [AUDIO_MIXER_INDEX] = &audio_mixer, [AUDIO_RECORD_INDEX] = &audio_record, [AUDIO_PLAYBACK_INDEX] = &audio_playback, [AUDIO_MANUFACTURER_INDEX] = &audio_manufacturer, [AUDIO_PRODUCT_INDEX] = &audio_product, [AUDIO_SERIAL_NUMBER_INDEX] = &audio_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < AUDIO_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void audio_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&audio_mixer, sizeof(audio_mixer), AUDIO_DEFAULT_MIXER); usb_make_str_desc(&audio_record, sizeof(audio_record), AUDIO_DEFAULT_RECORD); usb_make_str_desc(&audio_playback, sizeof(audio_playback), AUDIO_DEFAULT_PLAYBACK); usb_make_str_desc(&audio_manufacturer, sizeof(audio_manufacturer), AUDIO_DEFAULT_MANUFACTURER); usb_make_str_desc(&audio_product, sizeof(audio_product), AUDIO_DEFAULT_PRODUCT); usb_make_str_desc(&audio_serial_number, sizeof(audio_serial_number), AUDIO_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_AUDIO); sysctl_ctx_init(&audio_ctx_list); parent = SYSCTL_ADD_NODE(&audio_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Audio Interface device side template"); SYSCTL_ADD_U16(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_audio.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_audio.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "mixer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &audio_mixer, sizeof(audio_mixer), usb_temp_sysctl, "A", "Mixer interface string"); SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "record", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &audio_record, sizeof(audio_record), usb_temp_sysctl, "A", "Record interface string"); SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "playback", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &audio_playback, sizeof(audio_playback), usb_temp_sysctl, "A", "Playback interface string"); #endif SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &audio_manufacturer, sizeof(audio_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &audio_product, sizeof(audio_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &audio_serial_number, sizeof(audio_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void audio_uninit(void *arg __unused) { sysctl_ctx_free(&audio_ctx_list); } SYSINIT(audio_init, SI_SUB_LOCK, SI_ORDER_FIRST, audio_init, NULL); SYSUNINIT(audio_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, audio_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_cdce.c =================================================================== --- head/sys/dev/usb/template/usb_template_cdce.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_cdce.c (revision 357972) @@ -1,353 +1,353 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2007 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB templates for a CDC USB ethernet device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { ETH_LANG_INDEX, ETH_MAC_INDEX, ETH_CONTROL_INDEX, ETH_DATA_INDEX, ETH_CONFIGURATION_INDEX, ETH_MANUFACTURER_INDEX, ETH_PRODUCT_INDEX, ETH_SERIAL_NUMBER_INDEX, ETH_MAX_INDEX, }; #define ETH_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define ETH_DEFAULT_PRODUCT_ID 0x27e1 #define ETH_DEFAULT_MAC "2A02030405060789AB" #define ETH_DEFAULT_CONTROL "USB Ethernet Comm Interface" #define ETH_DEFAULT_DATA "USB Ethernet Data Interface" #define ETH_DEFAULT_CONFIG "Default Config" #define ETH_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define ETH_DEFAULT_PRODUCT "USB Ethernet Adapter" #define ETH_DEFAULT_SERIAL_NUMBER "December 2007" static struct usb_string_descriptor eth_mac; static struct usb_string_descriptor eth_control; static struct usb_string_descriptor eth_data; static struct usb_string_descriptor eth_configuration; static struct usb_string_descriptor eth_manufacturer; static struct usb_string_descriptor eth_product; static struct usb_string_descriptor eth_serial_number; static struct sysctl_ctx_list eth_ctx_list; /* prototypes */ static usb_temp_get_string_desc_t eth_get_string_desc; static const struct usb_cdc_union_descriptor eth_union_desc = { .bLength = sizeof(eth_union_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_UNION, .bMasterInterface = 0, /* this is automatically updated */ .bSlaveInterface[0] = 1, /* this is automatically updated */ }; static const struct usb_cdc_header_descriptor eth_header_desc = { .bLength = sizeof(eth_header_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_HEADER, .bcdCDC[0] = 0x10, .bcdCDC[1] = 0x01, }; static const struct usb_cdc_ethernet_descriptor eth_enf_desc = { .bLength = sizeof(eth_enf_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_ENF, .iMacAddress = ETH_MAC_INDEX, .bmEthernetStatistics = {0, 0, 0, 0}, .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */ .wNumberMCFilters = {0, 0}, .bNumberPowerFilters = 0, }; static const void *eth_control_if_desc[] = { ð_union_desc, ð_header_desc, ð_enf_desc, NULL, }; static const struct usb_temp_packet_size bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size intr_mps = { .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_endpoint_desc bulk_in_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc bulk_out_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc intr_in_ep = { .pPacketSize = &intr_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = { &intr_in_ep, NULL, }; static const struct usb_temp_interface_desc eth_control_interface = { .ppEndpoints = eth_intr_endpoints, .ppRawDesc = eth_control_if_desc, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, .bInterfaceProtocol = 0, .iInterface = ETH_CONTROL_INDEX, }; static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = { &bulk_in_ep, &bulk_out_ep, NULL, }; static const struct usb_temp_interface_desc eth_data_null_interface = { .ppEndpoints = NULL, /* no endpoints */ .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = ETH_DATA_INDEX, }; static const struct usb_temp_interface_desc eth_data_interface = { .ppEndpoints = eth_data_endpoints, .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = ETH_DATA_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const struct usb_temp_interface_desc *eth_interfaces[] = { ð_control_interface, ð_data_null_interface, ð_data_interface, NULL, }; static const struct usb_temp_config_desc eth_config_desc = { .ppIfaceDesc = eth_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = ETH_CONFIGURATION_INDEX, }; static const struct usb_temp_config_desc *eth_configs[] = { ð_config_desc, NULL, }; struct usb_temp_device_desc usb_template_cdce = { .getStringDesc = ð_get_string_desc, .ppConfigDesc = eth_configs, .idVendor = ETH_DEFAULT_VENDOR_ID, .idProduct = ETH_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = ETH_MANUFACTURER_INDEX, .iProduct = ETH_PRODUCT_INDEX, .iSerialNumber = ETH_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * eth_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * eth_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[ETH_MAX_INDEX] = { [ETH_LANG_INDEX] = &usb_string_lang_en, [ETH_MAC_INDEX] = ð_mac, [ETH_CONTROL_INDEX] = ð_control, [ETH_DATA_INDEX] = ð_data, [ETH_CONFIGURATION_INDEX] = ð_configuration, [ETH_MANUFACTURER_INDEX] = ð_manufacturer, [ETH_PRODUCT_INDEX] = ð_product, [ETH_SERIAL_NUMBER_INDEX] = ð_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < ETH_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void eth_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(ð_mac, sizeof(eth_mac), ETH_DEFAULT_MAC); usb_make_str_desc(ð_control, sizeof(eth_control), ETH_DEFAULT_CONTROL); usb_make_str_desc(ð_data, sizeof(eth_data), ETH_DEFAULT_DATA); usb_make_str_desc(ð_configuration, sizeof(eth_configuration), ETH_DEFAULT_CONFIG); usb_make_str_desc(ð_manufacturer, sizeof(eth_manufacturer), ETH_DEFAULT_MANUFACTURER); usb_make_str_desc(ð_product, sizeof(eth_product), ETH_DEFAULT_PRODUCT); usb_make_str_desc(ð_serial_number, sizeof(eth_serial_number), ETH_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_CDCE); sysctl_ctx_init(ð_ctx_list); parent = SYSCTL_ADD_NODE(ð_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB CDC Ethernet device side template"); SYSCTL_ADD_U16(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_cdce.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_cdce.idProduct, 1, "Product identifier"); SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "mac", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_mac, sizeof(eth_mac), usb_temp_sysctl, "A", "MAC address string"); #if 0 SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "control", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_control, sizeof(eth_control), usb_temp_sysctl, "A", "Control interface string"); SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "data", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_data, sizeof(eth_data), usb_temp_sysctl, "A", "Data interface string"); SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_configuration, sizeof(eth_configuration), usb_temp_sysctl, "A", "Configuration string"); #endif SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_manufacturer, sizeof(eth_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_product, sizeof(eth_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(ð_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, ð_serial_number, sizeof(eth_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void eth_uninit(void *arg __unused) { sysctl_ctx_free(ð_ctx_list); } SYSINIT(eth_init, SI_SUB_LOCK, SI_ORDER_FIRST, eth_init, NULL); SYSUNINIT(eth_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, eth_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_cdceem.c =================================================================== --- head/sys/dev/usb/template/usb_template_cdceem.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_cdceem.c (revision 357972) @@ -1,263 +1,263 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * Copyright (c) 2019 Edward Tomasz Napierala * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB templates for an USB Mass Storage Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { CDCEEM_LANG_INDEX, CDCEEM_INTERFACE_INDEX, CDCEEM_CONFIGURATION_INDEX, CDCEEM_MANUFACTURER_INDEX, CDCEEM_PRODUCT_INDEX, CDCEEM_SERIAL_NUMBER_INDEX, CDCEEM_MAX_INDEX, }; #define CDCEEM_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define CDCEEM_DEFAULT_PRODUCT_ID 0x27df #define CDCEEM_DEFAULT_INTERFACE "USB CDC EEM Interface" #define CDCEEM_DEFAULT_CONFIGURATION "Default Config" #define CDCEEM_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define CDCEEM_DEFAULT_PRODUCT "CDC EEM" #define CDCEEM_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor cdceem_interface; static struct usb_string_descriptor cdceem_configuration; static struct usb_string_descriptor cdceem_manufacturer; static struct usb_string_descriptor cdceem_product; static struct usb_string_descriptor cdceem_serial_number; static struct sysctl_ctx_list cdceem_ctx_list; /* prototypes */ static usb_temp_get_string_desc_t cdceem_get_string_desc; static const struct usb_temp_packet_size bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_endpoint_desc bulk_in_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc bulk_out_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *cdceem_data_endpoints[] = { &bulk_in_ep, &bulk_out_ep, NULL, }; static const struct usb_temp_interface_desc cdceem_data_interface = { .ppEndpoints = cdceem_data_endpoints, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ETHERNET_EMULATION_MODEL, .bInterfaceProtocol = UIPROTO_CDC_EEM, .iInterface = CDCEEM_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *cdceem_interfaces[] = { &cdceem_data_interface, NULL, }; static const struct usb_temp_config_desc cdceem_config_desc = { .ppIfaceDesc = cdceem_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = CDCEEM_CONFIGURATION_INDEX, }; static const struct usb_temp_config_desc *cdceem_configs[] = { &cdceem_config_desc, NULL, }; struct usb_temp_device_desc usb_template_cdceem = { .getStringDesc = &cdceem_get_string_desc, .ppConfigDesc = cdceem_configs, .idVendor = CDCEEM_DEFAULT_VENDOR_ID, .idProduct = CDCEEM_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = CDCEEM_MANUFACTURER_INDEX, .iProduct = CDCEEM_PRODUCT_INDEX, .iSerialNumber = CDCEEM_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * cdceem_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * cdceem_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[CDCEEM_MAX_INDEX] = { [CDCEEM_LANG_INDEX] = &usb_string_lang_en, [CDCEEM_INTERFACE_INDEX] = &cdceem_interface, [CDCEEM_CONFIGURATION_INDEX] = &cdceem_configuration, [CDCEEM_MANUFACTURER_INDEX] = &cdceem_manufacturer, [CDCEEM_PRODUCT_INDEX] = &cdceem_product, [CDCEEM_SERIAL_NUMBER_INDEX] = &cdceem_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < CDCEEM_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void cdceem_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&cdceem_interface, sizeof(cdceem_interface), CDCEEM_DEFAULT_INTERFACE); usb_make_str_desc(&cdceem_configuration, sizeof(cdceem_configuration), CDCEEM_DEFAULT_CONFIGURATION); usb_make_str_desc(&cdceem_manufacturer, sizeof(cdceem_manufacturer), CDCEEM_DEFAULT_MANUFACTURER); usb_make_str_desc(&cdceem_product, sizeof(cdceem_product), CDCEEM_DEFAULT_PRODUCT); usb_make_str_desc(&cdceem_serial_number, sizeof(cdceem_serial_number), CDCEEM_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_CDCEEM); sysctl_ctx_init(&cdceem_ctx_list); parent = SYSCTL_ADD_NODE(&cdceem_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB CDC EEM device side template"); SYSCTL_ADD_U16(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_cdceem.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_cdceem.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &cdceem_interface, sizeof(cdceem_interface), usb_temp_sysctl, "A", "Interface string"); SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &cdceem_configuration, sizeof(cdceem_configuration), usb_temp_sysctl, "A", "Configuration string"); #endif SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &cdceem_manufacturer, sizeof(cdceem_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &cdceem_product, sizeof(cdceem_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &cdceem_serial_number, sizeof(cdceem_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void cdceem_uninit(void *arg __unused) { sysctl_ctx_free(&cdceem_ctx_list); } SYSINIT(cdceem_init, SI_SUB_LOCK, SI_ORDER_FIRST, cdceem_init, NULL); SYSUNINIT(cdceem_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, cdceem_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_kbd.c =================================================================== --- head/sys/dev/usb/template/usb_template_kbd.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_kbd.c (revision 357972) @@ -1,294 +1,294 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for an USB Keyboard Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { KBD_LANG_INDEX, KBD_INTERFACE_INDEX, KBD_MANUFACTURER_INDEX, KBD_PRODUCT_INDEX, KBD_SERIAL_NUMBER_INDEX, KBD_MAX_INDEX, }; #define KBD_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define KBD_DEFAULT_PRODUCT_ID 0x27db #define KBD_DEFAULT_INTERFACE "Keyboard Interface" #define KBD_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define KBD_DEFAULT_PRODUCT "Keyboard Test Device" #define KBD_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor kbd_interface; static struct usb_string_descriptor kbd_manufacturer; static struct usb_string_descriptor kbd_product; static struct usb_string_descriptor kbd_serial_number; static struct sysctl_ctx_list kbd_ctx_list; /* prototypes */ static const struct usb_temp_packet_size keyboard_intr_mps = { .mps[USB_SPEED_LOW] = 16, .mps[USB_SPEED_FULL] = 16, .mps[USB_SPEED_HIGH] = 16, }; static const struct usb_temp_interval keyboard_intr_interval = { .bInterval[USB_SPEED_LOW] = 2, /* 2 ms */ .bInterval[USB_SPEED_FULL] = 2, /* 2 ms */ .bInterval[USB_SPEED_HIGH] = 5, /* 2 ms */ }; /* The following HID descriptor was dumped from a HP keyboard. */ static uint8_t keyboard_hid_descriptor[] = { 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x03, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91, 0x02, 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0 }; static const struct usb_temp_endpoint_desc keyboard_ep_0 = { .ppRawDesc = NULL, /* no raw descriptors */ .pPacketSize = &keyboard_intr_mps, .pIntervals = &keyboard_intr_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc *keyboard_endpoints[] = { &keyboard_ep_0, NULL, }; static const uint8_t keyboard_raw_desc[] = { 0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(keyboard_hid_descriptor), 0x00 }; static const void *keyboard_iface_0_desc[] = { keyboard_raw_desc, NULL, }; static const struct usb_temp_interface_desc keyboard_iface_0 = { .ppRawDesc = keyboard_iface_0_desc, .ppEndpoints = keyboard_endpoints, .bInterfaceClass = UICLASS_HID, .bInterfaceSubClass = UISUBCLASS_BOOT, .bInterfaceProtocol = UIPROTO_BOOT_KEYBOARD, .iInterface = KBD_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *keyboard_interfaces[] = { &keyboard_iface_0, NULL, }; static const struct usb_temp_config_desc keyboard_config_desc = { .ppIfaceDesc = keyboard_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = KBD_PRODUCT_INDEX, }; static const struct usb_temp_config_desc *keyboard_configs[] = { &keyboard_config_desc, NULL, }; static usb_temp_get_string_desc_t keyboard_get_string_desc; static usb_temp_get_vendor_desc_t keyboard_get_vendor_desc; struct usb_temp_device_desc usb_template_kbd = { .getStringDesc = &keyboard_get_string_desc, .getVendorDesc = &keyboard_get_vendor_desc, .ppConfigDesc = keyboard_configs, .idVendor = KBD_DEFAULT_VENDOR_ID, .idProduct = KBD_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = KBD_MANUFACTURER_INDEX, .iProduct = KBD_PRODUCT_INDEX, .iSerialNumber = KBD_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * keyboard_get_vendor_desc * * Return values: * NULL: Failure. No such vendor descriptor. * Else: Success. Pointer to vendor descriptor is returned. *------------------------------------------------------------------------*/ static const void * keyboard_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) { if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) && (req->wIndex[1] == 0) && (req->wIndex[0] == 0)) { *plen = sizeof(keyboard_hid_descriptor); return (keyboard_hid_descriptor); } return (NULL); } /*------------------------------------------------------------------------* * keyboard_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * keyboard_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[KBD_MAX_INDEX] = { [KBD_LANG_INDEX] = &usb_string_lang_en, [KBD_INTERFACE_INDEX] = &kbd_interface, [KBD_MANUFACTURER_INDEX] = &kbd_manufacturer, [KBD_PRODUCT_INDEX] = &kbd_product, [KBD_SERIAL_NUMBER_INDEX] = &kbd_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < KBD_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void kbd_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&kbd_interface, sizeof(kbd_interface), KBD_DEFAULT_INTERFACE); usb_make_str_desc(&kbd_manufacturer, sizeof(kbd_manufacturer), KBD_DEFAULT_MANUFACTURER); usb_make_str_desc(&kbd_product, sizeof(kbd_product), KBD_DEFAULT_PRODUCT); usb_make_str_desc(&kbd_serial_number, sizeof(kbd_serial_number), KBD_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_KBD); sysctl_ctx_init(&kbd_ctx_list); parent = SYSCTL_ADD_NODE(&kbd_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Keyboard device side template"); SYSCTL_ADD_U16(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_kbd.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_kbd.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &kbd_interface, sizeof(kbd_interface), usb_temp_sysctl, "A", "Interface string"); #endif SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &kbd_manufacturer, sizeof(kbd_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &kbd_product, sizeof(kbd_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &kbd_serial_number, sizeof(kbd_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void kbd_uninit(void *arg __unused) { sysctl_ctx_free(&kbd_ctx_list); } SYSINIT(kbd_init, SI_SUB_LOCK, SI_ORDER_FIRST, kbd_init, NULL); SYSUNINIT(kbd_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, kbd_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_midi.c =================================================================== --- head/sys/dev/usb/template/usb_template_midi.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_midi.c (revision 357972) @@ -1,314 +1,314 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2015 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for an USB MIDI Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { MIDI_LANG_INDEX, MIDI_INTERFACE_INDEX, MIDI_MANUFACTURER_INDEX, MIDI_PRODUCT_INDEX, MIDI_SERIAL_NUMBER_INDEX, MIDI_MAX_INDEX, }; #define MIDI_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define MIDI_DEFAULT_PRODUCT_ID 0x27de #define MIDI_DEFAULT_INTERFACE "MIDI interface" #define MIDI_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define MIDI_DEFAULT_PRODUCT "MIDI Test Device" #define MIDI_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor midi_interface; static struct usb_string_descriptor midi_manufacturer; static struct usb_string_descriptor midi_product; static struct usb_string_descriptor midi_serial_number; static struct sysctl_ctx_list midi_ctx_list; /* prototypes */ static const uint8_t midi_desc_raw_0[9] = { 0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x01 }; static const void *midi_descs_0[] = { &midi_desc_raw_0, NULL }; static const struct usb_temp_interface_desc midi_iface_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = midi_descs_0, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOCONTROL, .bInterfaceProtocol = 0, .iInterface = MIDI_INTERFACE_INDEX, }; static const struct usb_temp_packet_size midi_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const uint8_t midi_desc_raw_7[5] = { 0x05, 0x25, 0x01, 0x01, 0x01 }; static const void *midi_descs_2[] = { &midi_desc_raw_7, NULL }; static const struct usb_temp_endpoint_desc midi_bulk_out_ep = { .ppRawDesc = midi_descs_2, .pPacketSize = &midi_mps, .bEndpointAddress = UE_DIR_OUT, .bmAttributes = UE_BULK, }; static const uint8_t midi_desc_raw_6[5] = { 0x05, 0x25, 0x01, 0x01, 0x03, }; static const void *midi_descs_3[] = { &midi_desc_raw_6, NULL }; static const struct usb_temp_endpoint_desc midi_bulk_in_ep = { .ppRawDesc = midi_descs_3, .pPacketSize = &midi_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *midi_iface_1_ep[] = { &midi_bulk_out_ep, &midi_bulk_in_ep, NULL, }; static const uint8_t midi_desc_raw_1[7] = { 0x07, 0x24, 0x01, 0x00, 0x01, /* wTotalLength: */ 0x41, 0x00 }; static const uint8_t midi_desc_raw_2[6] = { 0x06, 0x24, 0x02, 0x01, 0x01, 0x00 }; static const uint8_t midi_desc_raw_3[6] = { 0x06, 0x24, 0x02, 0x02, 0x02, 0x00 }; static const uint8_t midi_desc_raw_4[9] = { 0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00 }; static const uint8_t midi_desc_raw_5[9] = { 0x09, 0x24, 0x03, 0x02, 0x04, 0x01, 0x01, 0x01, 0x00 }; static const void *midi_descs_1[] = { &midi_desc_raw_1, &midi_desc_raw_2, &midi_desc_raw_3, &midi_desc_raw_4, &midi_desc_raw_5, NULL }; static const struct usb_temp_interface_desc midi_iface_1 = { .ppRawDesc = midi_descs_1, .ppEndpoints = midi_iface_1_ep, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_MIDISTREAM, .bInterfaceProtocol = 0, .iInterface = MIDI_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *midi_interfaces[] = { &midi_iface_0, &midi_iface_1, NULL, }; static const struct usb_temp_config_desc midi_config_desc = { .ppIfaceDesc = midi_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = MIDI_PRODUCT_INDEX, }; static const struct usb_temp_config_desc *midi_configs[] = { &midi_config_desc, NULL, }; static usb_temp_get_string_desc_t midi_get_string_desc; struct usb_temp_device_desc usb_template_midi = { .getStringDesc = &midi_get_string_desc, .ppConfigDesc = midi_configs, .idVendor = MIDI_DEFAULT_VENDOR_ID, .idProduct = MIDI_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = MIDI_MANUFACTURER_INDEX, .iProduct = MIDI_PRODUCT_INDEX, .iSerialNumber = MIDI_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * midi_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * midi_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[MIDI_MAX_INDEX] = { [MIDI_LANG_INDEX] = &usb_string_lang_en, [MIDI_INTERFACE_INDEX] = &midi_interface, [MIDI_MANUFACTURER_INDEX] = &midi_manufacturer, [MIDI_PRODUCT_INDEX] = &midi_product, [MIDI_SERIAL_NUMBER_INDEX] = &midi_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < MIDI_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void midi_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&midi_interface, sizeof(midi_interface), MIDI_DEFAULT_INTERFACE); usb_make_str_desc(&midi_manufacturer, sizeof(midi_manufacturer), MIDI_DEFAULT_MANUFACTURER); usb_make_str_desc(&midi_product, sizeof(midi_product), MIDI_DEFAULT_PRODUCT); usb_make_str_desc(&midi_serial_number, sizeof(midi_serial_number), MIDI_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MIDI); sysctl_ctx_init(&midi_ctx_list); parent = SYSCTL_ADD_NODE(&midi_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB MIDI device side template"); SYSCTL_ADD_U16(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_midi.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_midi.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &midi_interface, sizeof(midi_interface), usb_temp_sysctl, "A", "Interface string"); #endif SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &midi_manufacturer, sizeof(midi_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &midi_product, sizeof(midi_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &midi_serial_number, sizeof(midi_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void midi_uninit(void *arg __unused) { sysctl_ctx_free(&midi_ctx_list); } SYSINIT(midi_init, SI_SUB_LOCK, SI_ORDER_FIRST, midi_init, NULL); SYSUNINIT(midi_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, midi_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_modem.c =================================================================== --- head/sys/dev/usb/template/usb_template_modem.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_modem.c (revision 357972) @@ -1,328 +1,328 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for an USB Modem Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { MODEM_LANG_INDEX, MODEM_INTERFACE_INDEX, MODEM_MANUFACTURER_INDEX, MODEM_PRODUCT_INDEX, MODEM_SERIAL_NUMBER_INDEX, MODEM_MAX_INDEX, }; #define MODEM_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define MODEM_DEFAULT_PRODUCT_ID 0x27dd #define MODEM_DEFAULT_INTERFACE "Virtual serial port" #define MODEM_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define MODEM_DEFAULT_PRODUCT "Virtual serial port" /* * The reason for this being called like this is that OSX * derives the device node name from it, resulting in a somewhat * user-friendly "/dev/cu.usbmodemFreeBSD1". And yes, the "1" * needs to be there, otherwise OSX will mangle it. */ #define MODEM_DEFAULT_SERIAL_NUMBER "FreeBSD1" static struct usb_string_descriptor modem_interface; static struct usb_string_descriptor modem_manufacturer; static struct usb_string_descriptor modem_product; static struct usb_string_descriptor modem_serial_number; static struct sysctl_ctx_list modem_ctx_list; #define MODEM_IFACE_0 0 #define MODEM_IFACE_1 1 /* prototypes */ static const struct usb_temp_packet_size modem_bulk_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size modem_intr_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_interval modem_intr_interval = { .bInterval[USB_SPEED_LOW] = 8, /* 8ms */ .bInterval[USB_SPEED_FULL] = 8, /* 8ms */ .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */ }; static const struct usb_temp_endpoint_desc modem_ep_0 = { .pPacketSize = &modem_intr_mps, .pIntervals = &modem_intr_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc modem_ep_1 = { .pPacketSize = &modem_bulk_mps, .bEndpointAddress = UE_DIR_OUT, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc modem_ep_2 = { .pPacketSize = &modem_bulk_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = { &modem_ep_0, NULL, }; static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = { &modem_ep_1, &modem_ep_2, NULL, }; static const uint8_t modem_raw_desc_0[] = { 0x05, 0x24, 0x00, 0x10, 0x01 }; static const uint8_t modem_raw_desc_1[] = { 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1 }; static const uint8_t modem_raw_desc_2[] = { 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1 }; static const uint8_t modem_raw_desc_3[] = { 0x04, 0x24, 0x02, 0x07 }; static const void *modem_iface_0_desc[] = { &modem_raw_desc_0, &modem_raw_desc_1, &modem_raw_desc_2, &modem_raw_desc_3, NULL, }; static const struct usb_temp_interface_desc modem_iface_0 = { .ppRawDesc = modem_iface_0_desc, .ppEndpoints = modem_iface_0_ep, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ABSTRACT_CONTROL_MODEL, .bInterfaceProtocol = UIPROTO_CDC_NONE, .iInterface = MODEM_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc modem_iface_1 = { .ppEndpoints = modem_iface_1_ep, .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = UIPROTO_CDC_NONE, .iInterface = MODEM_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *modem_interfaces[] = { &modem_iface_0, &modem_iface_1, NULL, }; static const struct usb_temp_config_desc modem_config_desc = { .ppIfaceDesc = modem_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = MODEM_PRODUCT_INDEX, }; static const struct usb_temp_config_desc *modem_configs[] = { &modem_config_desc, NULL, }; static usb_temp_get_string_desc_t modem_get_string_desc; static usb_temp_get_vendor_desc_t modem_get_vendor_desc; struct usb_temp_device_desc usb_template_modem = { .getStringDesc = &modem_get_string_desc, .getVendorDesc = &modem_get_vendor_desc, .ppConfigDesc = modem_configs, .idVendor = MODEM_DEFAULT_VENDOR_ID, .idProduct = MODEM_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = MODEM_MANUFACTURER_INDEX, .iProduct = MODEM_PRODUCT_INDEX, .iSerialNumber = MODEM_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * modem_get_vendor_desc * * Return values: * NULL: Failure. No such vendor descriptor. * Else: Success. Pointer to vendor descriptor is returned. *------------------------------------------------------------------------*/ static const void * modem_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) { return (NULL); } /*------------------------------------------------------------------------* * modem_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * modem_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[MODEM_MAX_INDEX] = { [MODEM_LANG_INDEX] = &usb_string_lang_en, [MODEM_INTERFACE_INDEX] = &modem_interface, [MODEM_MANUFACTURER_INDEX] = &modem_manufacturer, [MODEM_PRODUCT_INDEX] = &modem_product, [MODEM_SERIAL_NUMBER_INDEX] = &modem_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < MODEM_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void modem_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&modem_interface, sizeof(modem_interface), MODEM_DEFAULT_INTERFACE); usb_make_str_desc(&modem_manufacturer, sizeof(modem_manufacturer), MODEM_DEFAULT_MANUFACTURER); usb_make_str_desc(&modem_product, sizeof(modem_product), MODEM_DEFAULT_PRODUCT); usb_make_str_desc(&modem_serial_number, sizeof(modem_serial_number), MODEM_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MODEM); sysctl_ctx_init(&modem_ctx_list); parent = SYSCTL_ADD_NODE(&modem_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Virtual serial port device side template"); SYSCTL_ADD_U16(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_modem.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_modem.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "keyboard", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &modem_interface, sizeof(modem_interface), usb_temp_sysctl, "A", "Interface string"); #endif SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &modem_manufacturer, sizeof(modem_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &modem_product, sizeof(modem_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &modem_serial_number, sizeof(modem_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void modem_uninit(void *arg __unused) { sysctl_ctx_free(&modem_ctx_list); } SYSINIT(modem_init, SI_SUB_LOCK, SI_ORDER_FIRST, modem_init, NULL); SYSUNINIT(modem_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, modem_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_mouse.c =================================================================== --- head/sys/dev/usb/template/usb_template_mouse.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_mouse.c (revision 357972) @@ -1,292 +1,292 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for an USB Mouse Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { MOUSE_LANG_INDEX, MOUSE_INTERFACE_INDEX, MOUSE_MANUFACTURER_INDEX, MOUSE_PRODUCT_INDEX, MOUSE_SERIAL_NUMBER_INDEX, MOUSE_MAX_INDEX, }; #define MOUSE_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define MOUSE_DEFAULT_PRODUCT_ID 0x27da #define MOUSE_DEFAULT_INTERFACE "Mouse interface" #define MOUSE_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define MOUSE_DEFAULT_PRODUCT "Mouse Test Interface" #define MOUSE_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor mouse_interface; static struct usb_string_descriptor mouse_manufacturer; static struct usb_string_descriptor mouse_product; static struct usb_string_descriptor mouse_serial_number; static struct sysctl_ctx_list mouse_ctx_list; /* prototypes */ /* The following HID descriptor was dumped from a HP mouse. */ static uint8_t mouse_hid_descriptor[] = { 0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, 0x05, 0x81, 0x03, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06, 0xc0, 0xc0 }; static const struct usb_temp_packet_size mouse_intr_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_interval mouse_intr_interval = { .bInterval[USB_SPEED_LOW] = 2, /* 2ms */ .bInterval[USB_SPEED_FULL] = 2, /* 2ms */ .bInterval[USB_SPEED_HIGH] = 5, /* 2ms */ }; static const struct usb_temp_endpoint_desc mouse_ep_0 = { .ppRawDesc = NULL, /* no raw descriptors */ .pPacketSize = &mouse_intr_mps, .pIntervals = &mouse_intr_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc *mouse_endpoints[] = { &mouse_ep_0, NULL, }; static const uint8_t mouse_raw_desc[] = { 0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(mouse_hid_descriptor), 0x00 }; static const void *mouse_iface_0_desc[] = { mouse_raw_desc, NULL, }; static const struct usb_temp_interface_desc mouse_iface_0 = { .ppRawDesc = mouse_iface_0_desc, .ppEndpoints = mouse_endpoints, .bInterfaceClass = UICLASS_HID, .bInterfaceSubClass = UISUBCLASS_BOOT, .bInterfaceProtocol = UIPROTO_MOUSE, .iInterface = MOUSE_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *mouse_interfaces[] = { &mouse_iface_0, NULL, }; static const struct usb_temp_config_desc mouse_config_desc = { .ppIfaceDesc = mouse_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = MOUSE_INTERFACE_INDEX, }; static const struct usb_temp_config_desc *mouse_configs[] = { &mouse_config_desc, NULL, }; static usb_temp_get_string_desc_t mouse_get_string_desc; static usb_temp_get_vendor_desc_t mouse_get_vendor_desc; struct usb_temp_device_desc usb_template_mouse = { .getStringDesc = &mouse_get_string_desc, .getVendorDesc = &mouse_get_vendor_desc, .ppConfigDesc = mouse_configs, .idVendor = MOUSE_DEFAULT_VENDOR_ID, .idProduct = MOUSE_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = MOUSE_MANUFACTURER_INDEX, .iProduct = MOUSE_PRODUCT_INDEX, .iSerialNumber = MOUSE_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * mouse_get_vendor_desc * * Return values: * NULL: Failure. No such vendor descriptor. * Else: Success. Pointer to vendor descriptor is returned. *------------------------------------------------------------------------*/ static const void * mouse_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) { if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) && (req->wIndex[1] == 0) && (req->wIndex[0] == 0)) { *plen = sizeof(mouse_hid_descriptor); return (mouse_hid_descriptor); } return (NULL); } /*------------------------------------------------------------------------* * mouse_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * mouse_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[MOUSE_MAX_INDEX] = { [MOUSE_LANG_INDEX] = &usb_string_lang_en, [MOUSE_INTERFACE_INDEX] = &mouse_interface, [MOUSE_MANUFACTURER_INDEX] = &mouse_manufacturer, [MOUSE_PRODUCT_INDEX] = &mouse_product, [MOUSE_SERIAL_NUMBER_INDEX] = &mouse_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < MOUSE_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void mouse_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&mouse_interface, sizeof(mouse_interface), MOUSE_DEFAULT_INTERFACE); usb_make_str_desc(&mouse_manufacturer, sizeof(mouse_manufacturer), MOUSE_DEFAULT_MANUFACTURER); usb_make_str_desc(&mouse_product, sizeof(mouse_product), MOUSE_DEFAULT_PRODUCT); usb_make_str_desc(&mouse_serial_number, sizeof(mouse_serial_number), MOUSE_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MOUSE); sysctl_ctx_init(&mouse_ctx_list); parent = SYSCTL_ADD_NODE(&mouse_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Mouse device side template"); SYSCTL_ADD_U16(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_mouse.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_mouse.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mouse_interface, sizeof(mouse_interface), usb_temp_sysctl, "A", "Interface string"); #endif SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mouse_manufacturer, sizeof(mouse_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mouse_product, sizeof(mouse_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mouse_serial_number, sizeof(mouse_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void mouse_uninit(void *arg __unused) { sysctl_ctx_free(&mouse_ctx_list); } SYSINIT(mouse_init, SI_SUB_LOCK, SI_ORDER_FIRST, mouse_init, NULL); SYSUNINIT(mouse_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, mouse_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_msc.c =================================================================== --- head/sys/dev/usb/template/usb_template_msc.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_msc.c (revision 357972) @@ -1,262 +1,262 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB templates for an USB Mass Storage Device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { MSC_LANG_INDEX, MSC_INTERFACE_INDEX, MSC_CONFIGURATION_INDEX, MSC_MANUFACTURER_INDEX, MSC_PRODUCT_INDEX, MSC_SERIAL_NUMBER_INDEX, MSC_MAX_INDEX, }; #define MSC_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define MSC_DEFAULT_PRODUCT_ID 0x27df #define MSC_DEFAULT_INTERFACE "USB Mass Storage Interface" #define MSC_DEFAULT_CONFIGURATION "Default Config" #define MSC_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define MSC_DEFAULT_PRODUCT "USB Memory Stick" #define MSC_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor msc_interface; static struct usb_string_descriptor msc_configuration; static struct usb_string_descriptor msc_manufacturer; static struct usb_string_descriptor msc_product; static struct usb_string_descriptor msc_serial_number; static struct sysctl_ctx_list msc_ctx_list; /* prototypes */ static usb_temp_get_string_desc_t msc_get_string_desc; static const struct usb_temp_packet_size bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_endpoint_desc bulk_in_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc bulk_out_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *msc_data_endpoints[] = { &bulk_in_ep, &bulk_out_ep, NULL, }; static const struct usb_temp_interface_desc msc_data_interface = { .ppEndpoints = msc_data_endpoints, .bInterfaceClass = UICLASS_MASS, .bInterfaceSubClass = UISUBCLASS_SCSI, .bInterfaceProtocol = UIPROTO_MASS_BBB, .iInterface = MSC_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *msc_interfaces[] = { &msc_data_interface, NULL, }; static const struct usb_temp_config_desc msc_config_desc = { .ppIfaceDesc = msc_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = MSC_CONFIGURATION_INDEX, }; static const struct usb_temp_config_desc *msc_configs[] = { &msc_config_desc, NULL, }; struct usb_temp_device_desc usb_template_msc = { .getStringDesc = &msc_get_string_desc, .ppConfigDesc = msc_configs, .idVendor = MSC_DEFAULT_VENDOR_ID, .idProduct = MSC_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_COMM, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = MSC_MANUFACTURER_INDEX, .iProduct = MSC_PRODUCT_INDEX, .iSerialNumber = MSC_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * msc_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * msc_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[MSC_MAX_INDEX] = { [MSC_LANG_INDEX] = &usb_string_lang_en, [MSC_INTERFACE_INDEX] = &msc_interface, [MSC_CONFIGURATION_INDEX] = &msc_configuration, [MSC_MANUFACTURER_INDEX] = &msc_manufacturer, [MSC_PRODUCT_INDEX] = &msc_product, [MSC_SERIAL_NUMBER_INDEX] = &msc_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < MSC_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void msc_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&msc_interface, sizeof(msc_interface), MSC_DEFAULT_INTERFACE); usb_make_str_desc(&msc_configuration, sizeof(msc_configuration), MSC_DEFAULT_CONFIGURATION); usb_make_str_desc(&msc_manufacturer, sizeof(msc_manufacturer), MSC_DEFAULT_MANUFACTURER); usb_make_str_desc(&msc_product, sizeof(msc_product), MSC_DEFAULT_PRODUCT); usb_make_str_desc(&msc_serial_number, sizeof(msc_serial_number), MSC_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MSC); sysctl_ctx_init(&msc_ctx_list); parent = SYSCTL_ADD_NODE(&msc_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Mass Storage device side template"); SYSCTL_ADD_U16(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_msc.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_msc.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &msc_interface, sizeof(msc_interface), usb_temp_sysctl, "A", "Interface string"); SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &msc_configuration, sizeof(msc_configuration), usb_temp_sysctl, "A", "Configuration string"); #endif SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &msc_manufacturer, sizeof(msc_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &msc_product, sizeof(msc_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &msc_serial_number, sizeof(msc_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void msc_uninit(void *arg __unused) { sysctl_ctx_free(&msc_ctx_list); } SYSINIT(msc_init, SI_SUB_LOCK, SI_ORDER_FIRST, msc_init, NULL); SYSUNINIT(msc_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, msc_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_mtp.c =================================================================== --- head/sys/dev/usb/template/usb_template_mtp.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_mtp.c (revision 357972) @@ -1,329 +1,329 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB templates for an USB Media Transfer * Protocol device. * * NOTE: It is common practice that MTP devices use some dummy * descriptor cludges to be automatically detected by the host * operating system. These descriptors are documented in the LibMTP * library at sourceforge.net. The alternative is to supply the host * operating system the VID and PID of your device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #define MTP_BREQUEST 0x08 enum { MTP_LANG_INDEX, MTP_INTERFACE_INDEX, MTP_CONFIGURATION_INDEX, MTP_MANUFACTURER_INDEX, MTP_PRODUCT_INDEX, MTP_SERIAL_NUMBER_INDEX, MTP_MAX_INDEX, }; #define MTP_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define MTP_DEFAULT_PRODUCT_ID 0x27e2 #define MTP_DEFAULT_INTERFACE "USB MTP Interface" #define MTP_DEFAULT_CONFIGURATION "Default Config" #define MTP_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define MTP_DEFAULT_PRODUCT "USB MTP" #define MTP_DEFAULT_SERIAL_NUMBER "June 2008" static struct usb_string_descriptor mtp_interface; static struct usb_string_descriptor mtp_configuration; static struct usb_string_descriptor mtp_manufacturer; static struct usb_string_descriptor mtp_product; static struct usb_string_descriptor mtp_serial_number; static struct sysctl_ctx_list mtp_ctx_list; /* prototypes */ static usb_temp_get_string_desc_t mtp_get_string_desc; static usb_temp_get_vendor_desc_t mtp_get_vendor_desc; static const struct usb_temp_packet_size bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size intr_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 64, }; static const struct usb_temp_endpoint_desc bulk_out_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc intr_in_ep = { .pPacketSize = &intr_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc bulk_in_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *mtp_data_endpoints[] = { &bulk_in_ep, &bulk_out_ep, &intr_in_ep, NULL, }; static const struct usb_temp_interface_desc mtp_data_interface = { .ppEndpoints = mtp_data_endpoints, .bInterfaceClass = UICLASS_IMAGE, .bInterfaceSubClass = UISUBCLASS_SIC, /* Still Image Class */ .bInterfaceProtocol = 1, /* PIMA 15740 */ .iInterface = MTP_INTERFACE_INDEX, }; static const struct usb_temp_interface_desc *mtp_interfaces[] = { &mtp_data_interface, NULL, }; static const struct usb_temp_config_desc mtp_config_desc = { .ppIfaceDesc = mtp_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = MTP_CONFIGURATION_INDEX, }; static const struct usb_temp_config_desc *mtp_configs[] = { &mtp_config_desc, NULL, }; struct usb_temp_device_desc usb_template_mtp = { .getStringDesc = &mtp_get_string_desc, .getVendorDesc = &mtp_get_vendor_desc, .ppConfigDesc = mtp_configs, .idVendor = MTP_DEFAULT_VENDOR_ID, .idProduct = MTP_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = MTP_MANUFACTURER_INDEX, .iProduct = MTP_PRODUCT_INDEX, .iSerialNumber = MTP_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * mtp_get_vendor_desc * * Return values: * NULL: Failure. No such vendor descriptor. * Else: Success. Pointer to vendor descriptor is returned. *------------------------------------------------------------------------*/ static const void * mtp_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) { static const uint8_t dummy_desc[0x28] = { 0x28, 0, 0, 0, 0, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0x4D, 0x54, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; if ((req->bmRequestType == UT_READ_VENDOR_DEVICE) && (req->bRequest == MTP_BREQUEST) && (req->wValue[0] == 0) && (req->wValue[1] == 0) && (req->wIndex[1] == 0) && ((req->wIndex[0] == 4) || (req->wIndex[0] == 5))) { /* * By returning this descriptor LibMTP will * automatically pickup our device. */ return (dummy_desc); } return (NULL); } /*------------------------------------------------------------------------* * mtp_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * mtp_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[MTP_MAX_INDEX] = { [MTP_LANG_INDEX] = &usb_string_lang_en, [MTP_INTERFACE_INDEX] = &mtp_interface, [MTP_CONFIGURATION_INDEX] = &mtp_configuration, [MTP_MANUFACTURER_INDEX] = &mtp_manufacturer, [MTP_PRODUCT_INDEX] = &mtp_product, [MTP_SERIAL_NUMBER_INDEX] = &mtp_serial_number, }; static const uint8_t dummy_desc[0x12] = { 0x12, 0x03, 0x4D, 0x00, 0x53, 0x00, 0x46, 0x00, 0x54, 0x00, 0x31, 0x00, 0x30, 0x00, 0x30, 0x00, MTP_BREQUEST, 0x00, }; if (string_index == 0xEE) { /* * By returning this string LibMTP will automatically * pickup our device. */ return (dummy_desc); } if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < MTP_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void mtp_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&mtp_interface, sizeof(mtp_interface), MTP_DEFAULT_INTERFACE); usb_make_str_desc(&mtp_configuration, sizeof(mtp_configuration), MTP_DEFAULT_CONFIGURATION); usb_make_str_desc(&mtp_manufacturer, sizeof(mtp_manufacturer), MTP_DEFAULT_MANUFACTURER); usb_make_str_desc(&mtp_product, sizeof(mtp_product), MTP_DEFAULT_PRODUCT); usb_make_str_desc(&mtp_serial_number, sizeof(mtp_serial_number), MTP_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MTP); sysctl_ctx_init(&mtp_ctx_list); parent = SYSCTL_ADD_NODE(&mtp_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Media Transfer Protocol device side template"); SYSCTL_ADD_U16(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_mtp.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_mtp.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mtp_interface, sizeof(mtp_interface), usb_temp_sysctl, "A", "Interface string"); SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mtp_configuration, sizeof(mtp_configuration), usb_temp_sysctl, "A", "Configuration string"); #endif SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mtp_manufacturer, sizeof(mtp_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mtp_product, sizeof(mtp_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &mtp_serial_number, sizeof(mtp_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void mtp_uninit(void *arg __unused) { sysctl_ctx_free(&mtp_ctx_list); } SYSINIT(mtp_init, SI_SUB_LOCK, SI_ORDER_FIRST, mtp_init, NULL); SYSUNINIT(mtp_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, mtp_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_multi.c =================================================================== --- head/sys/dev/usb/template/usb_template_multi.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_multi.c (revision 357972) @@ -1,517 +1,517 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2015 Ruslan Bukin * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB template for CDC ACM (serial), CDC ECM (network), and CDC MSC (storage). */ #include __FBSDID("$FreeBSD$"); #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #define MODEM_IFACE_0 0 #define MODEM_IFACE_1 1 enum { MULTI_LANG_INDEX, MULTI_MODEM_INDEX, MULTI_ETH_MAC_INDEX, MULTI_ETH_CONTROL_INDEX, MULTI_ETH_DATA_INDEX, MULTI_STORAGE_INDEX, MULTI_CONFIGURATION_INDEX, MULTI_MANUFACTURER_INDEX, MULTI_PRODUCT_INDEX, MULTI_SERIAL_NUMBER_INDEX, MULTI_MAX_INDEX, }; #define MULTI_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define MULTI_DEFAULT_PRODUCT_ID 0x05dc #define MULTI_DEFAULT_MODEM "Virtual serial port" #define MULTI_DEFAULT_ETH_MAC "2A02030405060789AB" #define MULTI_DEFAULT_ETH_CONTROL "Ethernet Comm Interface" #define MULTI_DEFAULT_ETH_DATA "Ethernet Data Interface" #define MULTI_DEFAULT_STORAGE "Mass Storage Interface" #define MULTI_DEFAULT_CONFIGURATION "Default configuration" #define MULTI_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define MULTI_DEFAULT_PRODUCT "Multifunction Device" /* * The reason for this being called like this is that OSX * derives the device node name from it, resulting in a somewhat * user-friendly "/dev/cu.usbmodemFreeBSD1". And yes, the "1" * needs to be there, otherwise OSX will mangle it. */ #define MULTI_DEFAULT_SERIAL_NUMBER "FreeBSD1" static struct usb_string_descriptor multi_modem; static struct usb_string_descriptor multi_eth_mac; static struct usb_string_descriptor multi_eth_control; static struct usb_string_descriptor multi_eth_data; static struct usb_string_descriptor multi_storage; static struct usb_string_descriptor multi_configuration; static struct usb_string_descriptor multi_manufacturer; static struct usb_string_descriptor multi_product; static struct usb_string_descriptor multi_serial_number; static struct sysctl_ctx_list multi_ctx_list; /* prototypes */ static usb_temp_get_string_desc_t multi_get_string_desc; static const struct usb_cdc_union_descriptor eth_union_desc = { .bLength = sizeof(eth_union_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_UNION, .bMasterInterface = 0, /* this is automatically updated */ .bSlaveInterface[0] = 1, /* this is automatically updated */ }; static const struct usb_cdc_header_descriptor eth_header_desc = { .bLength = sizeof(eth_header_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_HEADER, .bcdCDC[0] = 0x10, .bcdCDC[1] = 0x01, }; static const struct usb_cdc_ethernet_descriptor eth_enf_desc = { .bLength = sizeof(eth_enf_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_ENF, .iMacAddress = MULTI_ETH_MAC_INDEX, .bmEthernetStatistics = {0, 0, 0, 0}, .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */ .wNumberMCFilters = {0, 0}, .bNumberPowerFilters = 0, }; static const void *eth_control_if_desc[] = { ð_union_desc, ð_header_desc, ð_enf_desc, NULL, }; static const struct usb_temp_packet_size bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size intr_mps = { .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_endpoint_desc bulk_in_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc bulk_out_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc intr_in_ep = { .pPacketSize = &intr_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = { &intr_in_ep, NULL, }; static const struct usb_temp_interface_desc eth_control_interface = { .ppEndpoints = eth_intr_endpoints, .ppRawDesc = eth_control_if_desc, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, .bInterfaceProtocol = UIPROTO_CDC_NONE, .iInterface = MULTI_ETH_CONTROL_INDEX, }; static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = { &bulk_in_ep, &bulk_out_ep, NULL, }; static const struct usb_temp_interface_desc eth_data_null_interface = { .ppEndpoints = NULL, /* no endpoints */ .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = MULTI_ETH_DATA_INDEX, }; static const struct usb_temp_interface_desc eth_data_interface = { .ppEndpoints = eth_data_endpoints, .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = MULTI_ETH_DATA_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const struct usb_temp_packet_size modem_bulk_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size modem_intr_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_interval modem_intr_interval = { .bInterval[USB_SPEED_LOW] = 8, /* 8ms */ .bInterval[USB_SPEED_FULL] = 8, /* 8ms */ .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */ }; static const struct usb_temp_endpoint_desc modem_ep_0 = { .pPacketSize = &modem_intr_mps, .pIntervals = &modem_intr_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc modem_ep_1 = { .pPacketSize = &modem_bulk_mps, .bEndpointAddress = UE_DIR_OUT, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc modem_ep_2 = { .pPacketSize = &modem_bulk_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = { &modem_ep_0, NULL, }; static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = { &modem_ep_1, &modem_ep_2, NULL, }; static const uint8_t modem_raw_desc_0[] = { 0x05, 0x24, 0x00, 0x10, 0x01 }; static const uint8_t modem_raw_desc_1[] = { 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1 }; static const uint8_t modem_raw_desc_2[] = { 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1 }; static const uint8_t modem_raw_desc_3[] = { 0x04, 0x24, 0x02, 0x07 }; static const void *modem_iface_0_desc[] = { &modem_raw_desc_0, &modem_raw_desc_1, &modem_raw_desc_2, &modem_raw_desc_3, NULL, }; static const struct usb_temp_interface_desc modem_iface_0 = { .ppRawDesc = modem_iface_0_desc, .ppEndpoints = modem_iface_0_ep, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ABSTRACT_CONTROL_MODEL, .bInterfaceProtocol = UIPROTO_CDC_NONE, .iInterface = MULTI_MODEM_INDEX, }; static const struct usb_temp_interface_desc modem_iface_1 = { .ppEndpoints = modem_iface_1_ep, .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = MULTI_MODEM_INDEX, }; static const struct usb_temp_packet_size msc_bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_endpoint_desc msc_bulk_in_ep = { .pPacketSize = &msc_bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc msc_bulk_out_ep = { .pPacketSize = &msc_bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *msc_data_endpoints[] = { &msc_bulk_in_ep, &msc_bulk_out_ep, NULL, }; static const struct usb_temp_interface_desc msc_data_interface = { .ppEndpoints = msc_data_endpoints, .bInterfaceClass = UICLASS_MASS, .bInterfaceSubClass = UISUBCLASS_SCSI, .bInterfaceProtocol = UIPROTO_MASS_BBB, .iInterface = MULTI_STORAGE_INDEX, }; static const struct usb_temp_interface_desc *multi_interfaces[] = { &modem_iface_0, &modem_iface_1, ð_control_interface, ð_data_null_interface, ð_data_interface, &msc_data_interface, NULL, }; static const struct usb_temp_config_desc multi_config_desc = { .ppIfaceDesc = multi_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = MULTI_CONFIGURATION_INDEX, }; static const struct usb_temp_config_desc *multi_configs[] = { &multi_config_desc, NULL, }; struct usb_temp_device_desc usb_template_multi = { .getStringDesc = &multi_get_string_desc, .ppConfigDesc = multi_configs, .idVendor = MULTI_DEFAULT_VENDOR_ID, .idProduct = MULTI_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_IN_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = MULTI_MANUFACTURER_INDEX, .iProduct = MULTI_PRODUCT_INDEX, .iSerialNumber = MULTI_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * multi_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * multi_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[MULTI_MAX_INDEX] = { [MULTI_LANG_INDEX] = &usb_string_lang_en, [MULTI_MODEM_INDEX] = &multi_modem, [MULTI_ETH_MAC_INDEX] = &multi_eth_mac, [MULTI_ETH_CONTROL_INDEX] = &multi_eth_control, [MULTI_ETH_DATA_INDEX] = &multi_eth_data, [MULTI_STORAGE_INDEX] = &multi_storage, [MULTI_CONFIGURATION_INDEX] = &multi_configuration, [MULTI_MANUFACTURER_INDEX] = &multi_manufacturer, [MULTI_PRODUCT_INDEX] = &multi_product, [MULTI_SERIAL_NUMBER_INDEX] = &multi_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < MULTI_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void multi_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&multi_modem, sizeof(multi_modem), MULTI_DEFAULT_MODEM); usb_make_str_desc(&multi_eth_mac, sizeof(multi_eth_mac), MULTI_DEFAULT_ETH_MAC); usb_make_str_desc(&multi_eth_control, sizeof(multi_eth_control), MULTI_DEFAULT_ETH_CONTROL); usb_make_str_desc(&multi_eth_data, sizeof(multi_eth_data), MULTI_DEFAULT_ETH_DATA); usb_make_str_desc(&multi_storage, sizeof(multi_storage), MULTI_DEFAULT_STORAGE); usb_make_str_desc(&multi_configuration, sizeof(multi_configuration), MULTI_DEFAULT_CONFIGURATION); usb_make_str_desc(&multi_manufacturer, sizeof(multi_manufacturer), MULTI_DEFAULT_MANUFACTURER); usb_make_str_desc(&multi_product, sizeof(multi_product), MULTI_DEFAULT_PRODUCT); usb_make_str_desc(&multi_serial_number, sizeof(multi_serial_number), MULTI_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MULTI); sysctl_ctx_init(&multi_ctx_list); parent = SYSCTL_ADD_NODE(&multi_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Multifunction device side template"); SYSCTL_ADD_U16(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_multi.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_multi.idProduct, 1, "Product identifier"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "eth_mac", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_eth_mac, sizeof(multi_eth_mac), usb_temp_sysctl, "A", "Ethernet MAC address string"); #if 0 SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "modem", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_modem, sizeof(multi_modem), usb_temp_sysctl, "A", "Modem interface string"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "eth_control", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_eth_control, sizeof(multi_eth_data), usb_temp_sysctl, "A", "Ethernet control interface string"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "eth_data", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_eth_data, sizeof(multi_eth_data), usb_temp_sysctl, "A", "Ethernet data interface string"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_storage, sizeof(multi_storage), usb_temp_sysctl, "A", "Storage interface string"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_configuration, sizeof(multi_configuration), usb_temp_sysctl, "A", "Configuration string"); #endif SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_manufacturer, sizeof(multi_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_product, sizeof(multi_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &multi_serial_number, sizeof(multi_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void multi_uninit(void *arg __unused) { sysctl_ctx_free(&multi_ctx_list); } SYSINIT(multi_init, SI_SUB_LOCK, SI_ORDER_FIRST, multi_init, NULL); SYSUNINIT(multi_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, multi_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_phone.c =================================================================== --- head/sys/dev/usb/template/usb_template_phone.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_phone.c (revision 357972) @@ -1,505 +1,505 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 Hans Petter Selasky * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for an USB phone device. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { PHONE_LANG_INDEX, PHONE_MIXER_INDEX, PHONE_RECORD_INDEX, PHONE_PLAYBACK_INDEX, PHONE_HID_INDEX, PHONE_MANUFACTURER_INDEX, PHONE_PRODUCT_INDEX, PHONE_SERIAL_NUMBER_INDEX, PHONE_MAX_INDEX, }; #define PHONE_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define PHONE_DEFAULT_PRODUCT_ID 0x05dc #define PHONE_DEFAULT_MIXER "Mixer interface" #define PHONE_DEFAULT_RECORD "Record interface" #define PHONE_DEFAULT_PLAYBACK "Playback interface" #define PHONE_DEFAULT_HID "HID interface" #define PHONE_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define PHONE_DEFAULT_PRODUCT "USB Phone Device" #define PHONE_DEFAULT_SERIAL_NUMBER "March 2008" static struct usb_string_descriptor phone_mixer; static struct usb_string_descriptor phone_record; static struct usb_string_descriptor phone_playback; static struct usb_string_descriptor phone_hid; static struct usb_string_descriptor phone_manufacturer; static struct usb_string_descriptor phone_product; static struct usb_string_descriptor phone_serial_number; static struct sysctl_ctx_list phone_ctx_list; /* prototypes */ /* * Phone Mixer description structures * * Some of the phone descriptors were dumped from no longer in * production Yealink VOIP USB phone adapter: */ static uint8_t phone_hid_descriptor[] = { 0x05, 0x0b, 0x09, 0x01, 0xa1, 0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x3f, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x80, 0x81, 0x00, 0x05, 0x08, 0x19, 0x01, 0x29, 0x10, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x80, 0x91, 0x00, 0xc0 }; static const uint8_t phone_raw_desc_0[] = { 0x0a, 0x24, 0x01, 0x00, 0x01, 0x4a, 0x00, 0x02, 0x01, 0x02 }; static const uint8_t phone_raw_desc_1[] = { 0x0c, 0x24, 0x02, 0x01, 0x01, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }; static const uint8_t phone_raw_desc_2[] = { 0x0c, 0x24, 0x02, 0x02, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }; static const uint8_t phone_raw_desc_3[] = { 0x09, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x06, 0x00 }; static const uint8_t phone_raw_desc_4[] = { 0x09, 0x24, 0x03, 0x04, 0x01, 0x01, 0x00, 0x05, 0x00 }; static const uint8_t phone_raw_desc_5[] = { 0x0b, 0x24, 0x06, 0x05, 0x01, 0x02, 0x03, 0x00, 0x03, 0x00, 0x00 }; static const uint8_t phone_raw_desc_6[] = { 0x0b, 0x24, 0x06, 0x06, 0x02, 0x02, 0x03, 0x00, 0x03, 0x00, 0x00 }; static const void *phone_raw_iface_0_desc[] = { phone_raw_desc_0, phone_raw_desc_1, phone_raw_desc_2, phone_raw_desc_3, phone_raw_desc_4, phone_raw_desc_5, phone_raw_desc_6, NULL, }; static const struct usb_temp_interface_desc phone_iface_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = phone_raw_iface_0_desc, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOCONTROL, .bInterfaceProtocol = 0, .iInterface = PHONE_MIXER_INDEX, }; static const uint8_t phone_raw_desc_20[] = { 0x07, 0x24, 0x01, 0x04, 0x01, 0x01, 0x00 }; static const uint8_t phone_raw_desc_21[] = { 0x0b, 0x24, 0x02, 0x01, 0x01, 0x02, 0x10, 0x01, /* 8kHz */ 0x40, 0x1f, 0x00 }; static const uint8_t phone_raw_desc_22[] = { 0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00 }; static const void *phone_raw_iface_1_desc[] = { phone_raw_desc_20, phone_raw_desc_21, NULL, }; static const void *phone_raw_ep_1_desc[] = { phone_raw_desc_22, NULL, }; static const struct usb_temp_packet_size phone_isoc_mps = { .mps[USB_SPEED_FULL] = 0x10, .mps[USB_SPEED_HIGH] = 0x10, }; static const struct usb_temp_interval phone_isoc_interval = { .bInterval[USB_SPEED_FULL] = 1, /* 1:1 */ .bInterval[USB_SPEED_HIGH] = 4, /* 1:8 */ }; static const struct usb_temp_endpoint_desc phone_isoc_in_ep = { .ppRawDesc = phone_raw_ep_1_desc, .pPacketSize = &phone_isoc_mps, .pIntervals = &phone_isoc_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_ISOCHRONOUS, }; static const struct usb_temp_endpoint_desc *phone_iface_1_ep[] = { &phone_isoc_in_ep, NULL, }; static const struct usb_temp_interface_desc phone_iface_1_alt_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = NULL, /* no raw descriptors */ .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = PHONE_PLAYBACK_INDEX, }; static const struct usb_temp_interface_desc phone_iface_1_alt_1 = { .ppEndpoints = phone_iface_1_ep, .ppRawDesc = phone_raw_iface_1_desc, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = PHONE_PLAYBACK_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const uint8_t phone_raw_desc_30[] = { 0x07, 0x24, 0x01, 0x02, 0x01, 0x01, 0x00 }; static const uint8_t phone_raw_desc_31[] = { 0x0b, 0x24, 0x02, 0x01, 0x01, 0x02, 0x10, 0x01, /* 8kHz */ 0x40, 0x1f, 0x00 }; static const uint8_t phone_raw_desc_32[] = { 0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00 }; static const void *phone_raw_iface_2_desc[] = { phone_raw_desc_30, phone_raw_desc_31, NULL, }; static const void *phone_raw_ep_2_desc[] = { phone_raw_desc_32, NULL, }; static const struct usb_temp_endpoint_desc phone_isoc_out_ep = { .ppRawDesc = phone_raw_ep_2_desc, .pPacketSize = &phone_isoc_mps, .pIntervals = &phone_isoc_interval, .bEndpointAddress = UE_DIR_OUT, .bmAttributes = UE_ISOCHRONOUS, }; static const struct usb_temp_endpoint_desc *phone_iface_2_ep[] = { &phone_isoc_out_ep, NULL, }; static const struct usb_temp_interface_desc phone_iface_2_alt_0 = { .ppEndpoints = NULL, /* no endpoints */ .ppRawDesc = NULL, /* no raw descriptors */ .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = PHONE_RECORD_INDEX, }; static const struct usb_temp_interface_desc phone_iface_2_alt_1 = { .ppEndpoints = phone_iface_2_ep, .ppRawDesc = phone_raw_iface_2_desc, .bInterfaceClass = UICLASS_AUDIO, .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM, .bInterfaceProtocol = 0, .iInterface = PHONE_RECORD_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const uint8_t phone_hid_raw_desc_0[] = { 0x09, 0x21, 0x00, 0x01, 0x00, 0x01, 0x22, sizeof(phone_hid_descriptor), 0x00 }; static const void *phone_hid_desc_0[] = { phone_hid_raw_desc_0, NULL, }; static const struct usb_temp_packet_size phone_hid_mps = { .mps[USB_SPEED_FULL] = 0x10, .mps[USB_SPEED_HIGH] = 0x10, }; static const struct usb_temp_interval phone_hid_interval = { .bInterval[USB_SPEED_FULL] = 2, /* 2ms */ .bInterval[USB_SPEED_HIGH] = 2, /* 2ms */ }; static const struct usb_temp_endpoint_desc phone_hid_in_ep = { .pPacketSize = &phone_hid_mps, .pIntervals = &phone_hid_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc *phone_iface_3_ep[] = { &phone_hid_in_ep, NULL, }; static const struct usb_temp_interface_desc phone_iface_3 = { .ppEndpoints = phone_iface_3_ep, .ppRawDesc = phone_hid_desc_0, .bInterfaceClass = UICLASS_HID, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = PHONE_HID_INDEX, }; static const struct usb_temp_interface_desc *phone_interfaces[] = { &phone_iface_0, &phone_iface_1_alt_0, &phone_iface_1_alt_1, &phone_iface_2_alt_0, &phone_iface_2_alt_1, &phone_iface_3, NULL, }; static const struct usb_temp_config_desc phone_config_desc = { .ppIfaceDesc = phone_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = PHONE_PRODUCT_INDEX, }; static const struct usb_temp_config_desc *phone_configs[] = { &phone_config_desc, NULL, }; static usb_temp_get_string_desc_t phone_get_string_desc; static usb_temp_get_vendor_desc_t phone_get_vendor_desc; struct usb_temp_device_desc usb_template_phone = { .getStringDesc = &phone_get_string_desc, .getVendorDesc = &phone_get_vendor_desc, .ppConfigDesc = phone_configs, .idVendor = PHONE_DEFAULT_VENDOR_ID, .idProduct = PHONE_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_IN_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = PHONE_MANUFACTURER_INDEX, .iProduct = PHONE_PRODUCT_INDEX, .iSerialNumber = PHONE_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * phone_get_vendor_desc * * Return values: * NULL: Failure. No such vendor descriptor. * Else: Success. Pointer to vendor descriptor is returned. *------------------------------------------------------------------------*/ static const void * phone_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) { if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) && (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) && (req->wIndex[1] == 0) && (req->wIndex[0] == 3 /* iface */)) { *plen = sizeof(phone_hid_descriptor); return (phone_hid_descriptor); } return (NULL); } /*------------------------------------------------------------------------* * phone_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * phone_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[PHONE_MAX_INDEX] = { [PHONE_LANG_INDEX] = &usb_string_lang_en, [PHONE_MIXER_INDEX] = &phone_mixer, [PHONE_RECORD_INDEX] = &phone_record, [PHONE_PLAYBACK_INDEX] = &phone_playback, [PHONE_HID_INDEX] = &phone_hid, [PHONE_MANUFACTURER_INDEX] = &phone_manufacturer, [PHONE_PRODUCT_INDEX] = &phone_product, [PHONE_SERIAL_NUMBER_INDEX] = &phone_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < PHONE_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void phone_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&phone_mixer, sizeof(phone_mixer), PHONE_DEFAULT_MIXER); usb_make_str_desc(&phone_record, sizeof(phone_record), PHONE_DEFAULT_RECORD); usb_make_str_desc(&phone_playback, sizeof(phone_playback), PHONE_DEFAULT_PLAYBACK); usb_make_str_desc(&phone_hid, sizeof(phone_hid), PHONE_DEFAULT_HID); usb_make_str_desc(&phone_manufacturer, sizeof(phone_manufacturer), PHONE_DEFAULT_MANUFACTURER); usb_make_str_desc(&phone_product, sizeof(phone_product), PHONE_DEFAULT_PRODUCT); usb_make_str_desc(&phone_serial_number, sizeof(phone_serial_number), PHONE_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_PHONE); sysctl_ctx_init(&phone_ctx_list); parent = SYSCTL_ADD_NODE(&phone_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB Phone device side template"); SYSCTL_ADD_U16(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_cdce.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_cdce.idProduct, 1, "Product identifier"); #if 0 SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "mixer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_mixer, sizeof(phone_mixer), usb_temp_sysctl, "A", "Mixer interface string"); SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "record", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_record, sizeof(phone_record), usb_temp_sysctl, "A", "Record interface string"); SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "playback", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_playback, sizeof(phone_playback), usb_temp_sysctl, "A", "Playback interface string"); SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "hid", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_hid, sizeof(phone_hid), usb_temp_sysctl, "A", "HID interface string"); #endif SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_manufacturer, sizeof(phone_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_product, sizeof(phone_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &phone_serial_number, sizeof(phone_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void phone_uninit(void *arg __unused) { sysctl_ctx_free(&phone_ctx_list); } SYSINIT(phone_init, SI_SUB_LOCK, SI_ORDER_FIRST, phone_init, NULL); SYSUNINIT(phone_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, phone_uninit, NULL); Index: head/sys/dev/usb/template/usb_template_serialnet.c =================================================================== --- head/sys/dev/usb/template/usb_template_serialnet.c (revision 357971) +++ head/sys/dev/usb/template/usb_template_serialnet.c (revision 357972) @@ -1,467 +1,467 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2015 Ruslan Bukin * Copyright (c) 2018 The FreeBSD Foundation * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This file contains the USB template for USB Networking and Serial */ #include __FBSDID("$FreeBSD$"); #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #define MODEM_IFACE_0 0 #define MODEM_IFACE_1 1 enum { SERIALNET_LANG_INDEX, SERIALNET_MODEM_INDEX, SERIALNET_ETH_MAC_INDEX, SERIALNET_ETH_CONTROL_INDEX, SERIALNET_ETH_DATA_INDEX, SERIALNET_CONFIGURATION_INDEX, SERIALNET_MANUFACTURER_INDEX, SERIALNET_PRODUCT_INDEX, SERIALNET_SERIAL_NUMBER_INDEX, SERIALNET_MAX_INDEX, }; #define SERIALNET_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR #define SERIALNET_DEFAULT_PRODUCT_ID 0x05dc #define SERIALNET_DEFAULT_MODEM "Virtual serial port" #define SERIALNET_DEFAULT_ETH_MAC "2A02030405060789AB" #define SERIALNET_DEFAULT_ETH_CONTROL "USB Ethernet Comm Interface" #define SERIALNET_DEFAULT_ETH_DATA "USB Ethernet Data Interface" #define SERIALNET_DEFAULT_CONFIGURATION "Default configuration" #define SERIALNET_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER #define SERIALNET_DEFAULT_PRODUCT "Serial/Ethernet device" /* * The reason for this being called like this is that OSX * derives the device node name from it, resulting in a somewhat * user-friendly "/dev/cu.usbmodemFreeBSD1". And yes, the "1" * needs to be there, otherwise OSX will mangle it. */ #define SERIALNET_DEFAULT_SERIAL_NUMBER "FreeBSD1" static struct usb_string_descriptor serialnet_modem; static struct usb_string_descriptor serialnet_eth_mac; static struct usb_string_descriptor serialnet_eth_control; static struct usb_string_descriptor serialnet_eth_data; static struct usb_string_descriptor serialnet_configuration; static struct usb_string_descriptor serialnet_manufacturer; static struct usb_string_descriptor serialnet_product; static struct usb_string_descriptor serialnet_serial_number; static struct sysctl_ctx_list serialnet_ctx_list; /* prototypes */ static usb_temp_get_string_desc_t serialnet_get_string_desc; static const struct usb_cdc_union_descriptor eth_union_desc = { .bLength = sizeof(eth_union_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_UNION, .bMasterInterface = 0, /* this is automatically updated */ .bSlaveInterface[0] = 1, /* this is automatically updated */ }; static const struct usb_cdc_header_descriptor eth_header_desc = { .bLength = sizeof(eth_header_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_HEADER, .bcdCDC[0] = 0x10, .bcdCDC[1] = 0x01, }; static const struct usb_cdc_ethernet_descriptor eth_enf_desc = { .bLength = sizeof(eth_enf_desc), .bDescriptorType = UDESC_CS_INTERFACE, .bDescriptorSubtype = UDESCSUB_CDC_ENF, .iMacAddress = SERIALNET_ETH_MAC_INDEX, .bmEthernetStatistics = {0, 0, 0, 0}, .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */ .wNumberMCFilters = {0, 0}, .bNumberPowerFilters = 0, }; static const void *eth_control_if_desc[] = { ð_union_desc, ð_header_desc, ð_enf_desc, NULL, }; static const struct usb_temp_packet_size bulk_mps = { .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size intr_mps = { .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_endpoint_desc bulk_in_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_IN_EP_0 .bEndpointAddress = USB_HIP_IN_EP_0, #else .bEndpointAddress = UE_DIR_IN, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc bulk_out_ep = { .pPacketSize = &bulk_mps, #ifdef USB_HIP_OUT_EP_0 .bEndpointAddress = USB_HIP_OUT_EP_0, #else .bEndpointAddress = UE_DIR_OUT, #endif .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc intr_in_ep = { .pPacketSize = &intr_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = { &intr_in_ep, NULL, }; static const struct usb_temp_interface_desc eth_control_interface = { .ppEndpoints = eth_intr_endpoints, .ppRawDesc = eth_control_if_desc, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, .bInterfaceProtocol = UIPROTO_CDC_NONE, .iInterface = SERIALNET_ETH_CONTROL_INDEX, }; static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = { &bulk_in_ep, &bulk_out_ep, NULL, }; static const struct usb_temp_interface_desc eth_data_null_interface = { .ppEndpoints = NULL, /* no endpoints */ .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = SERIALNET_ETH_DATA_INDEX, }; static const struct usb_temp_interface_desc eth_data_interface = { .ppEndpoints = eth_data_endpoints, .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = SERIALNET_ETH_DATA_INDEX, .isAltInterface = 1, /* this is an alternate setting */ }; static const struct usb_temp_packet_size modem_bulk_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 64, .mps[USB_SPEED_HIGH] = 512, }; static const struct usb_temp_packet_size modem_intr_mps = { .mps[USB_SPEED_LOW] = 8, .mps[USB_SPEED_FULL] = 8, .mps[USB_SPEED_HIGH] = 8, }; static const struct usb_temp_interval modem_intr_interval = { .bInterval[USB_SPEED_LOW] = 8, /* 8ms */ .bInterval[USB_SPEED_FULL] = 8, /* 8ms */ .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */ }; static const struct usb_temp_endpoint_desc modem_ep_0 = { .pPacketSize = &modem_intr_mps, .pIntervals = &modem_intr_interval, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_INTERRUPT, }; static const struct usb_temp_endpoint_desc modem_ep_1 = { .pPacketSize = &modem_bulk_mps, .bEndpointAddress = UE_DIR_OUT, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc modem_ep_2 = { .pPacketSize = &modem_bulk_mps, .bEndpointAddress = UE_DIR_IN, .bmAttributes = UE_BULK, }; static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = { &modem_ep_0, NULL, }; static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = { &modem_ep_1, &modem_ep_2, NULL, }; static const uint8_t modem_raw_desc_0[] = { 0x05, 0x24, 0x00, 0x10, 0x01 }; static const uint8_t modem_raw_desc_1[] = { 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1 }; static const uint8_t modem_raw_desc_2[] = { 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1 }; static const uint8_t modem_raw_desc_3[] = { 0x04, 0x24, 0x02, 0x07 }; static const void *modem_iface_0_desc[] = { &modem_raw_desc_0, &modem_raw_desc_1, &modem_raw_desc_2, &modem_raw_desc_3, NULL, }; static const struct usb_temp_interface_desc modem_iface_0 = { .ppRawDesc = modem_iface_0_desc, .ppEndpoints = modem_iface_0_ep, .bInterfaceClass = UICLASS_CDC, .bInterfaceSubClass = UISUBCLASS_ABSTRACT_CONTROL_MODEL, .bInterfaceProtocol = UIPROTO_CDC_NONE, .iInterface = SERIALNET_MODEM_INDEX, }; static const struct usb_temp_interface_desc modem_iface_1 = { .ppEndpoints = modem_iface_1_ep, .bInterfaceClass = UICLASS_CDC_DATA, .bInterfaceSubClass = UISUBCLASS_DATA, .bInterfaceProtocol = 0, .iInterface = SERIALNET_MODEM_INDEX, }; static const struct usb_temp_interface_desc *serialnet_interfaces[] = { &modem_iface_0, &modem_iface_1, ð_control_interface, ð_data_null_interface, ð_data_interface, NULL, }; static const struct usb_temp_config_desc serialnet_config_desc = { .ppIfaceDesc = serialnet_interfaces, .bmAttributes = 0, .bMaxPower = 0, .iConfiguration = SERIALNET_CONFIGURATION_INDEX, }; static const struct usb_temp_config_desc *serialnet_configs[] = { &serialnet_config_desc, NULL, }; struct usb_temp_device_desc usb_template_serialnet = { .getStringDesc = &serialnet_get_string_desc, .ppConfigDesc = serialnet_configs, .idVendor = SERIALNET_DEFAULT_VENDOR_ID, .idProduct = SERIALNET_DEFAULT_PRODUCT_ID, .bcdDevice = 0x0100, .bDeviceClass = UDCLASS_IN_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .iManufacturer = SERIALNET_MANUFACTURER_INDEX, .iProduct = SERIALNET_PRODUCT_INDEX, .iSerialNumber = SERIALNET_SERIAL_NUMBER_INDEX, }; /*------------------------------------------------------------------------* * serialnet_get_string_desc * * Return values: * NULL: Failure. No such string. * Else: Success. Pointer to string descriptor is returned. *------------------------------------------------------------------------*/ static const void * serialnet_get_string_desc(uint16_t lang_id, uint8_t string_index) { static const void *ptr[SERIALNET_MAX_INDEX] = { [SERIALNET_LANG_INDEX] = &usb_string_lang_en, [SERIALNET_MODEM_INDEX] = &serialnet_modem, [SERIALNET_ETH_MAC_INDEX] = &serialnet_eth_mac, [SERIALNET_ETH_CONTROL_INDEX] = &serialnet_eth_control, [SERIALNET_ETH_DATA_INDEX] = &serialnet_eth_data, [SERIALNET_CONFIGURATION_INDEX] = &serialnet_configuration, [SERIALNET_MANUFACTURER_INDEX] = &serialnet_manufacturer, [SERIALNET_PRODUCT_INDEX] = &serialnet_product, [SERIALNET_SERIAL_NUMBER_INDEX] = &serialnet_serial_number, }; if (string_index == 0) { return (&usb_string_lang_en); } if (lang_id != 0x0409) { return (NULL); } if (string_index < SERIALNET_MAX_INDEX) { return (ptr[string_index]); } return (NULL); } static void serialnet_init(void *arg __unused) { struct sysctl_oid *parent; char parent_name[3]; usb_make_str_desc(&serialnet_modem, sizeof(serialnet_modem), SERIALNET_DEFAULT_MODEM); usb_make_str_desc(&serialnet_eth_mac, sizeof(serialnet_eth_mac), SERIALNET_DEFAULT_ETH_MAC); usb_make_str_desc(&serialnet_eth_control, sizeof(serialnet_eth_control), SERIALNET_DEFAULT_ETH_CONTROL); usb_make_str_desc(&serialnet_eth_data, sizeof(serialnet_eth_data), SERIALNET_DEFAULT_ETH_DATA); usb_make_str_desc(&serialnet_configuration, sizeof(serialnet_configuration), SERIALNET_DEFAULT_CONFIGURATION); usb_make_str_desc(&serialnet_manufacturer, sizeof(serialnet_manufacturer), SERIALNET_DEFAULT_MANUFACTURER); usb_make_str_desc(&serialnet_product, sizeof(serialnet_product), SERIALNET_DEFAULT_PRODUCT); usb_make_str_desc(&serialnet_serial_number, sizeof(serialnet_serial_number), SERIALNET_DEFAULT_SERIAL_NUMBER); snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_SERIALNET); sysctl_ctx_init(&serialnet_ctx_list); parent = SYSCTL_ADD_NODE(&serialnet_ctx_list, SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO, - parent_name, CTLFLAG_RW, + parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB CDC Serial/Ethernet device side template"); SYSCTL_ADD_U16(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "vendor_id", CTLFLAG_RWTUN, &usb_template_serialnet.idVendor, 1, "Vendor identifier"); SYSCTL_ADD_U16(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product_id", CTLFLAG_RWTUN, &usb_template_serialnet.idProduct, 1, "Product identifier"); SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "eth_mac", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_eth_mac, sizeof(serialnet_eth_mac), usb_temp_sysctl, "A", "Ethernet MAC address string"); #if 0 SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "modem", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_modem, sizeof(serialnet_modem), usb_temp_sysctl, "A", "Modem interface string"); SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "eth_control", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_eth_control, sizeof(serialnet_eth_data), usb_temp_sysctl, "A", "Ethernet control interface string"); SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "eth_data", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_eth_data, sizeof(serialnet_eth_data), usb_temp_sysctl, "A", "Ethernet data interface string"); SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_configuration, sizeof(serialnet_configuration), usb_temp_sysctl, "A", "Configuration string"); #endif SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_manufacturer, sizeof(serialnet_manufacturer), usb_temp_sysctl, "A", "Manufacturer string"); SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_product, sizeof(serialnet_product), usb_temp_sysctl, "A", "Product string"); SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO, "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &serialnet_serial_number, sizeof(serialnet_serial_number), usb_temp_sysctl, "A", "Serial number string"); } static void serialnet_uninit(void *arg __unused) { sysctl_ctx_free(&serialnet_ctx_list); } SYSINIT(serialnet_init, SI_SUB_LOCK, SI_ORDER_FIRST, serialnet_init, NULL); SYSUNINIT(serialnet_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, serialnet_uninit, NULL); Index: head/sys/dev/usb/usb_debug.c =================================================================== --- head/sys/dev/usb/usb_debug.c (revision 357971) +++ head/sys/dev/usb/usb_debug.c (revision 357972) @@ -1,305 +1,319 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ /* * Define this unconditionally in case a kernel module is loaded that * has been compiled with debugging options. */ int usb_debug = 0; -SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW, 0, "USB debugging"); +SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB debugging"); SYSCTL_INT(_hw_usb, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_debug, 0, "Debug level"); #ifdef USB_DEBUG /* * Sysctls to modify timings/delays */ -static SYSCTL_NODE(_hw_usb, OID_AUTO, timings, CTLFLAG_RW, 0, "Timings"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, timings, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "Timings"); static int usb_timings_sysctl_handler(SYSCTL_HANDLER_ARGS); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_reset_delay, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_port_reset_delay, sizeof(usb_port_reset_delay), - usb_timings_sysctl_handler, "IU", "Port Reset Delay"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_root_reset_delay, CTLTYPE_UINT | CTLFLAG_RWTUN, +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_reset_delay, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_reset_delay, + sizeof(usb_port_reset_delay), usb_timings_sysctl_handler, "IU", + "Port Reset Delay"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_root_reset_delay, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_root_reset_delay, sizeof(usb_port_root_reset_delay), - usb_timings_sysctl_handler, "IU", "Root Port Reset Delay"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_reset_recovery, CTLTYPE_UINT | CTLFLAG_RWTUN, + usb_timings_sysctl_handler, "IU", + "Root Port Reset Delay"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_reset_recovery, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_reset_recovery, sizeof(usb_port_reset_recovery), - usb_timings_sysctl_handler, "IU", "Port Reset Recovery"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_powerup_delay, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_port_powerup_delay, sizeof(usb_port_powerup_delay), - usb_timings_sysctl_handler, "IU", "Port PowerUp Delay"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_resume_delay, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_port_resume_delay, sizeof(usb_port_resume_delay), - usb_timings_sysctl_handler, "IU", "Port Resume Delay"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, set_address_settle, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_set_address_settle, sizeof(usb_set_address_settle), - usb_timings_sysctl_handler, "IU", "Set Address Settle"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_delay, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_resume_delay, sizeof(usb_resume_delay), - usb_timings_sysctl_handler, "IU", "Resume Delay"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_wait, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_resume_wait, sizeof(usb_resume_wait), - usb_timings_sysctl_handler, "IU", "Resume Wait"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_recovery, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_resume_recovery, sizeof(usb_resume_recovery), - usb_timings_sysctl_handler, "IU", "Resume Recovery"); -SYSCTL_PROC(_hw_usb_timings, OID_AUTO, extra_power_up_time, CTLTYPE_UINT | CTLFLAG_RWTUN, - &usb_extra_power_up_time, sizeof(usb_extra_power_up_time), - usb_timings_sysctl_handler, "IU", "Extra PowerUp Time"); + usb_timings_sysctl_handler, "IU", + "Port Reset Recovery"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_powerup_delay, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_powerup_delay, + sizeof(usb_port_powerup_delay), usb_timings_sysctl_handler, "IU", + "Port PowerUp Delay"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_resume_delay, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_resume_delay, + sizeof(usb_port_resume_delay), usb_timings_sysctl_handler, "IU", + "Port Resume Delay"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, set_address_settle, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_set_address_settle, + sizeof(usb_set_address_settle), usb_timings_sysctl_handler, "IU", + "Set Address Settle"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_delay, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_resume_delay, + sizeof(usb_resume_delay), usb_timings_sysctl_handler, "IU", + "Resume Delay"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_wait, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_resume_wait, + sizeof(usb_resume_wait), usb_timings_sysctl_handler, "IU", + "Resume Wait"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_recovery, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_resume_recovery, + sizeof(usb_resume_recovery), usb_timings_sysctl_handler, "IU", + "Resume Recovery"); +SYSCTL_PROC(_hw_usb_timings, OID_AUTO, extra_power_up_time, + CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_extra_power_up_time, + sizeof(usb_extra_power_up_time), usb_timings_sysctl_handler, "IU", + "Extra PowerUp Time"); #endif /*------------------------------------------------------------------------* * usb_dump_iface * * This function dumps information about an USB interface. *------------------------------------------------------------------------*/ void usb_dump_iface(struct usb_interface *iface) { printf("usb_dump_iface: iface=%p\n", iface); if (iface == NULL) { return; } printf(" iface=%p idesc=%p altindex=%d\n", iface, iface->idesc, iface->alt_index); } /*------------------------------------------------------------------------* * usb_dump_device * * This function dumps information about an USB device. *------------------------------------------------------------------------*/ void usb_dump_device(struct usb_device *udev) { printf("usb_dump_device: dev=%p\n", udev); if (udev == NULL) { return; } printf(" bus=%p \n" " address=%d config=%d depth=%d speed=%d self_powered=%d\n" " power=%d langid=%d\n", udev->bus, udev->address, udev->curr_config_no, udev->depth, udev->speed, udev->flags.self_powered, udev->power, udev->langid); } /*------------------------------------------------------------------------* * usb_dump_queue * * This function dumps the USB transfer that are queued up on an USB endpoint. *------------------------------------------------------------------------*/ void usb_dump_queue(struct usb_endpoint *ep) { struct usb_xfer *xfer; usb_stream_t x; printf("usb_dump_queue: endpoint=%p xfer: ", ep); for (x = 0; x != USB_MAX_EP_STREAMS; x++) { TAILQ_FOREACH(xfer, &ep->endpoint_q[x].head, wait_entry) printf(" %p", xfer); } printf("\n"); } /*------------------------------------------------------------------------* * usb_dump_endpoint * * This function dumps information about an USB endpoint. *------------------------------------------------------------------------*/ void usb_dump_endpoint(struct usb_endpoint *ep) { if (ep) { printf("usb_dump_endpoint: endpoint=%p", ep); printf(" edesc=%p isoc_next=%d toggle_next=%d", ep->edesc, ep->isoc_next, ep->toggle_next); if (ep->edesc) { printf(" bEndpointAddress=0x%02x", ep->edesc->bEndpointAddress); } printf("\n"); usb_dump_queue(ep); } else { printf("usb_dump_endpoint: endpoint=NULL\n"); } } /*------------------------------------------------------------------------* * usb_dump_xfer * * This function dumps information about an USB transfer. *------------------------------------------------------------------------*/ void usb_dump_xfer(struct usb_xfer *xfer) { struct usb_device *udev; printf("usb_dump_xfer: xfer=%p\n", xfer); if (xfer == NULL) { return; } if (xfer->endpoint == NULL) { printf("xfer %p: endpoint=NULL\n", xfer); return; } udev = xfer->xroot->udev; printf("xfer %p: udev=%p vid=0x%04x pid=0x%04x addr=%d " "endpoint=%p ep=0x%02x attr=0x%02x\n", xfer, udev, UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct), udev->address, xfer->endpoint, xfer->endpoint->edesc->bEndpointAddress, xfer->endpoint->edesc->bmAttributes); } #ifdef USB_DEBUG unsigned int usb_port_reset_delay = USB_PORT_RESET_DELAY; unsigned int usb_port_root_reset_delay = USB_PORT_ROOT_RESET_DELAY; unsigned int usb_port_reset_recovery = USB_PORT_RESET_RECOVERY; unsigned int usb_port_powerup_delay = USB_PORT_POWERUP_DELAY; unsigned int usb_port_resume_delay = USB_PORT_RESUME_DELAY; unsigned int usb_set_address_settle = USB_SET_ADDRESS_SETTLE; unsigned int usb_resume_delay = USB_RESUME_DELAY; unsigned int usb_resume_wait = USB_RESUME_WAIT; unsigned int usb_resume_recovery = USB_RESUME_RECOVERY; unsigned int usb_extra_power_up_time = USB_EXTRA_POWER_UP_TIME; /*------------------------------------------------------------------------* * usb_timings_sysctl_handler * * This function updates timings variables, adjusting them where necessary. *------------------------------------------------------------------------*/ static int usb_timings_sysctl_handler(SYSCTL_HANDLER_ARGS) { int error = 0; unsigned int val; /* * Attempt to get a coherent snapshot by making a copy of the data. */ if (arg1) val = *(unsigned int *)arg1; else val = arg2; error = SYSCTL_OUT(req, &val, sizeof(int)); if (error || !req->newptr) return (error); if (!arg1) return EPERM; error = SYSCTL_IN(req, &val, sizeof(unsigned int)); if (error) return (error); /* * Now make sure the values are decent, and certainly no lower than * what the USB spec prescribes. */ unsigned int *p = (unsigned int *)arg1; if (p == &usb_port_reset_delay) { if (val < USB_PORT_RESET_DELAY_SPEC) return (EINVAL); } else if (p == &usb_port_root_reset_delay) { if (val < USB_PORT_ROOT_RESET_DELAY_SPEC) return (EINVAL); } else if (p == &usb_port_reset_recovery) { if (val < USB_PORT_RESET_RECOVERY_SPEC) return (EINVAL); } else if (p == &usb_port_powerup_delay) { if (val < USB_PORT_POWERUP_DELAY_SPEC) return (EINVAL); } else if (p == &usb_port_resume_delay) { if (val < USB_PORT_RESUME_DELAY_SPEC) return (EINVAL); } else if (p == &usb_set_address_settle) { if (val < USB_SET_ADDRESS_SETTLE_SPEC) return (EINVAL); } else if (p == &usb_resume_delay) { if (val < USB_RESUME_DELAY_SPEC) return (EINVAL); } else if (p == &usb_resume_wait) { if (val < USB_RESUME_WAIT_SPEC) return (EINVAL); } else if (p == &usb_resume_recovery) { if (val < USB_RESUME_RECOVERY_SPEC) return (EINVAL); } else if (p == &usb_extra_power_up_time) { if (val < USB_EXTRA_POWER_UP_TIME_SPEC) return (EINVAL); } else { /* noop */ } *p = val; return 0; } #endif Index: head/sys/dev/usb/usb_dev.c =================================================================== --- head/sys/dev/usb/usb_dev.c (revision 357971) +++ head/sys/dev/usb/usb_dev.c (revision 357972) @@ -1,2470 +1,2471 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2006-2008 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * * usb_dev.c - An abstraction layer for creating devices under /dev/... */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_fifo_debug #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #if USB_HAVE_UGEN #ifdef USB_DEBUG static int usb_fifo_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB device"); SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_fifo_debug, 0, "Debug Level"); #endif #if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \ ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000))) #define USB_UCRED struct ucred *ucred, #else #define USB_UCRED #endif /* prototypes */ static int usb_fifo_open(struct usb_cdev_privdata *, struct usb_fifo *, int); static void usb_fifo_close(struct usb_fifo *, int); static void usb_dev_init(void *); static void usb_dev_init_post(void *); static void usb_dev_uninit(void *); static int usb_fifo_uiomove(struct usb_fifo *, void *, int, struct uio *); static void usb_fifo_check_methods(struct usb_fifo_methods *); static struct usb_fifo *usb_fifo_alloc(struct mtx *); static struct usb_endpoint *usb_dev_get_ep(struct usb_device *, uint8_t, uint8_t); static void usb_loc_fill(struct usb_fs_privdata *, struct usb_cdev_privdata *); static void usb_close(void *); static usb_error_t usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *, int); static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); static void usb_unref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); static d_open_t usb_open; static d_ioctl_t usb_ioctl; static d_read_t usb_read; static d_write_t usb_write; static d_poll_t usb_poll; static d_kqfilter_t usb_kqfilter; static d_ioctl_t usb_static_ioctl; static usb_fifo_open_t usb_fifo_dummy_open; static usb_fifo_close_t usb_fifo_dummy_close; static usb_fifo_ioctl_t usb_fifo_dummy_ioctl; static usb_fifo_cmd_t usb_fifo_dummy_cmd; /* character device structure used for devices (/dev/ugenX.Y and /dev/uXXX) */ struct cdevsw usb_devsw = { .d_version = D_VERSION, .d_open = usb_open, .d_ioctl = usb_ioctl, .d_name = "usbdev", .d_flags = D_TRACKCLOSE, .d_read = usb_read, .d_write = usb_write, .d_poll = usb_poll, .d_kqfilter = usb_kqfilter, }; static struct cdev* usb_dev = NULL; /* character device structure used for /dev/usb */ static struct cdevsw usb_static_devsw = { .d_version = D_VERSION, .d_ioctl = usb_static_ioctl, .d_name = "usb" }; static TAILQ_HEAD(, usb_symlink) usb_sym_head; static struct sx usb_sym_lock; struct mtx usb_ref_lock; /*------------------------------------------------------------------------* * usb_loc_fill * * This is used to fill out a usb_cdev_privdata structure based on the * device's address as contained in usb_fs_privdata. *------------------------------------------------------------------------*/ static void usb_loc_fill(struct usb_fs_privdata* pd, struct usb_cdev_privdata *cpd) { cpd->bus_index = pd->bus_index; cpd->dev_index = pd->dev_index; cpd->ep_addr = pd->ep_addr; cpd->fifo_index = pd->fifo_index; } /*------------------------------------------------------------------------* * usb_ref_device * * This function is used to atomically refer an USB device by its * device location. If this function returns success the USB device * will not disappear until the USB device is unreferenced. * * Return values: * 0: Success, refcount incremented on the given USB device. * Else: Failure. *------------------------------------------------------------------------*/ static usb_error_t usb_ref_device(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd, int need_uref) { struct usb_fifo **ppf; struct usb_fifo *f; DPRINTFN(2, "cpd=%p need uref=%d\n", cpd, need_uref); /* clear all refs */ memset(crd, 0, sizeof(*crd)); mtx_lock(&usb_ref_lock); cpd->bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index); if (cpd->bus == NULL) { DPRINTFN(2, "no bus at %u\n", cpd->bus_index); goto error; } cpd->udev = cpd->bus->devices[cpd->dev_index]; if (cpd->udev == NULL) { DPRINTFN(2, "no device at %u\n", cpd->dev_index); goto error; } if (cpd->udev->state == USB_STATE_DETACHED && (need_uref != 2)) { DPRINTFN(2, "device is detached\n"); goto error; } if (need_uref) { DPRINTFN(2, "ref udev - needed\n"); if (cpd->udev->refcount == USB_DEV_REF_MAX) { DPRINTFN(2, "no dev ref\n"); goto error; } cpd->udev->refcount++; mtx_unlock(&usb_ref_lock); /* * We need to grab the enumeration SX-lock before * grabbing the FIFO refs to avoid deadlock at detach! */ crd->do_unlock = usbd_enum_lock_sig(cpd->udev); mtx_lock(&usb_ref_lock); /* * Set "is_uref" after grabbing the default SX lock */ crd->is_uref = 1; /* check for signal */ if (crd->do_unlock > 1) { crd->do_unlock = 0; goto error; } } /* check if we are doing an open */ if (cpd->fflags == 0) { /* use zero defaults */ } else { /* check for write */ if (cpd->fflags & FWRITE) { ppf = cpd->udev->fifo; f = ppf[cpd->fifo_index + USB_FIFO_TX]; crd->txfifo = f; crd->is_write = 1; /* ref */ if (f == NULL || f->refcount == USB_FIFO_REF_MAX) goto error; if (f->curr_cpd != cpd) goto error; /* check if USB-FS is active */ if (f->fs_ep_max != 0) { crd->is_usbfs = 1; } } /* check for read */ if (cpd->fflags & FREAD) { ppf = cpd->udev->fifo; f = ppf[cpd->fifo_index + USB_FIFO_RX]; crd->rxfifo = f; crd->is_read = 1; /* ref */ if (f == NULL || f->refcount == USB_FIFO_REF_MAX) goto error; if (f->curr_cpd != cpd) goto error; /* check if USB-FS is active */ if (f->fs_ep_max != 0) { crd->is_usbfs = 1; } } } /* when everything is OK we increment the refcounts */ if (crd->is_write) { DPRINTFN(2, "ref write\n"); crd->txfifo->refcount++; } if (crd->is_read) { DPRINTFN(2, "ref read\n"); crd->rxfifo->refcount++; } mtx_unlock(&usb_ref_lock); return (0); error: if (crd->do_unlock) usbd_enum_unlock(cpd->udev); if (crd->is_uref) { if (--(cpd->udev->refcount) == 0) cv_broadcast(&cpd->udev->ref_cv); } mtx_unlock(&usb_ref_lock); DPRINTFN(2, "fail\n"); /* clear all refs */ memset(crd, 0, sizeof(*crd)); return (USB_ERR_INVAL); } /*------------------------------------------------------------------------* * usb_usb_ref_device * * This function is used to upgrade an USB reference to include the * USB device reference on a USB location. * * Return values: * 0: Success, refcount incremented on the given USB device. * Else: Failure. *------------------------------------------------------------------------*/ static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd) { /* * Check if we already got an USB reference on this location: */ if (crd->is_uref) return (0); /* success */ /* * To avoid deadlock at detach we need to drop the FIFO ref * and re-acquire a new ref! */ usb_unref_device(cpd, crd); return (usb_ref_device(cpd, crd, 1 /* need uref */)); } /*------------------------------------------------------------------------* * usb_unref_device * * This function will release the reference count by one unit for the * given USB device. *------------------------------------------------------------------------*/ static void usb_unref_device(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd) { DPRINTFN(2, "cpd=%p is_uref=%d\n", cpd, crd->is_uref); if (crd->do_unlock) usbd_enum_unlock(cpd->udev); mtx_lock(&usb_ref_lock); if (crd->is_read) { if (--(crd->rxfifo->refcount) == 0) { cv_signal(&crd->rxfifo->cv_drain); } crd->is_read = 0; } if (crd->is_write) { if (--(crd->txfifo->refcount) == 0) { cv_signal(&crd->txfifo->cv_drain); } crd->is_write = 0; } if (crd->is_uref) { crd->is_uref = 0; if (--(cpd->udev->refcount) == 0) cv_broadcast(&cpd->udev->ref_cv); } mtx_unlock(&usb_ref_lock); } static struct usb_fifo * usb_fifo_alloc(struct mtx *mtx) { struct usb_fifo *f; f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); if (f != NULL) { cv_init(&f->cv_io, "FIFO-IO"); cv_init(&f->cv_drain, "FIFO-DRAIN"); f->priv_mtx = mtx; f->refcount = 1; knlist_init_mtx(&f->selinfo.si_note, mtx); } return (f); } /*------------------------------------------------------------------------* * usb_fifo_create *------------------------------------------------------------------------*/ static int usb_fifo_create(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd) { struct usb_device *udev = cpd->udev; struct usb_fifo *f; struct usb_endpoint *ep; uint8_t n; uint8_t is_tx; uint8_t is_rx; uint8_t no_null; uint8_t is_busy; int e = cpd->ep_addr; is_tx = (cpd->fflags & FWRITE) ? 1 : 0; is_rx = (cpd->fflags & FREAD) ? 1 : 0; no_null = 1; is_busy = 0; /* Preallocated FIFO */ if (e < 0) { DPRINTFN(5, "Preallocated FIFO\n"); if (is_tx) { f = udev->fifo[cpd->fifo_index + USB_FIFO_TX]; if (f == NULL) return (EINVAL); crd->txfifo = f; } if (is_rx) { f = udev->fifo[cpd->fifo_index + USB_FIFO_RX]; if (f == NULL) return (EINVAL); crd->rxfifo = f; } return (0); } KASSERT(e >= 0 && e <= 15, ("endpoint %d out of range", e)); /* search for a free FIFO slot */ DPRINTFN(5, "Endpoint device, searching for 0x%02x\n", e); for (n = 0;; n += 2) { if (n == USB_FIFO_MAX) { if (no_null) { no_null = 0; n = 0; } else { /* end of FIFOs reached */ DPRINTFN(5, "out of FIFOs\n"); return (ENOMEM); } } /* Check for TX FIFO */ if (is_tx) { f = udev->fifo[n + USB_FIFO_TX]; if (f != NULL) { if (f->dev_ep_index != e) { /* wrong endpoint index */ continue; } if (f->curr_cpd != NULL) { /* FIFO is opened */ is_busy = 1; continue; } } else if (no_null) { continue; } } /* Check for RX FIFO */ if (is_rx) { f = udev->fifo[n + USB_FIFO_RX]; if (f != NULL) { if (f->dev_ep_index != e) { /* wrong endpoint index */ continue; } if (f->curr_cpd != NULL) { /* FIFO is opened */ is_busy = 1; continue; } } else if (no_null) { continue; } } break; } if (no_null == 0) { if (e >= (USB_EP_MAX / 2)) { /* we don't create any endpoints in this range */ DPRINTFN(5, "ep out of range\n"); return (is_busy ? EBUSY : EINVAL); } } if ((e != 0) && is_busy) { /* * Only the default control endpoint is allowed to be * opened multiple times! */ DPRINTFN(5, "busy\n"); return (EBUSY); } /* Check TX FIFO */ if (is_tx && (udev->fifo[n + USB_FIFO_TX] == NULL)) { ep = usb_dev_get_ep(udev, e, USB_FIFO_TX); DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_TX); if (ep == NULL) { DPRINTFN(5, "dev_get_endpoint returned NULL\n"); return (EINVAL); } f = usb_fifo_alloc(&udev->device_mtx); if (f == NULL) { DPRINTFN(5, "could not alloc tx fifo\n"); return (ENOMEM); } /* update some fields */ f->fifo_index = n + USB_FIFO_TX; f->dev_ep_index = e; f->priv_sc0 = ep; f->methods = &usb_ugen_methods; f->iface_index = ep->iface_index; f->udev = udev; mtx_lock(&usb_ref_lock); udev->fifo[n + USB_FIFO_TX] = f; mtx_unlock(&usb_ref_lock); } /* Check RX FIFO */ if (is_rx && (udev->fifo[n + USB_FIFO_RX] == NULL)) { ep = usb_dev_get_ep(udev, e, USB_FIFO_RX); DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_RX); if (ep == NULL) { DPRINTFN(5, "dev_get_endpoint returned NULL\n"); return (EINVAL); } f = usb_fifo_alloc(&udev->device_mtx); if (f == NULL) { DPRINTFN(5, "could not alloc rx fifo\n"); return (ENOMEM); } /* update some fields */ f->fifo_index = n + USB_FIFO_RX; f->dev_ep_index = e; f->priv_sc0 = ep; f->methods = &usb_ugen_methods; f->iface_index = ep->iface_index; f->udev = udev; mtx_lock(&usb_ref_lock); udev->fifo[n + USB_FIFO_RX] = f; mtx_unlock(&usb_ref_lock); } if (is_tx) { crd->txfifo = udev->fifo[n + USB_FIFO_TX]; } if (is_rx) { crd->rxfifo = udev->fifo[n + USB_FIFO_RX]; } /* fill out fifo index */ DPRINTFN(5, "fifo index = %d\n", n); cpd->fifo_index = n; /* complete */ return (0); } void usb_fifo_free(struct usb_fifo *f) { uint8_t n; if (f == NULL) { /* be NULL safe */ return; } /* destroy symlink devices, if any */ for (n = 0; n != 2; n++) { if (f->symlink[n]) { usb_free_symlink(f->symlink[n]); f->symlink[n] = NULL; } } mtx_lock(&usb_ref_lock); /* delink ourselves to stop calls from userland */ if ((f->fifo_index < USB_FIFO_MAX) && (f->udev != NULL) && (f->udev->fifo[f->fifo_index] == f)) { f->udev->fifo[f->fifo_index] = NULL; } else { DPRINTFN(0, "USB FIFO %p has not been linked\n", f); } /* decrease refcount */ f->refcount--; /* need to wait until all callers have exited */ while (f->refcount != 0) { mtx_unlock(&usb_ref_lock); /* avoid LOR */ mtx_lock(f->priv_mtx); /* prevent write flush, if any */ f->flag_iserror = 1; /* get I/O thread out of any sleep state */ if (f->flag_sleeping) { f->flag_sleeping = 0; cv_broadcast(&f->cv_io); } mtx_unlock(f->priv_mtx); mtx_lock(&usb_ref_lock); /* * Check if the "f->refcount" variable reached zero * during the unlocked time before entering wait: */ if (f->refcount == 0) break; /* wait for sync */ cv_wait(&f->cv_drain, &usb_ref_lock); } mtx_unlock(&usb_ref_lock); /* take care of closing the device here, if any */ usb_fifo_close(f, 0); cv_destroy(&f->cv_io); cv_destroy(&f->cv_drain); knlist_clear(&f->selinfo.si_note, 0); seldrain(&f->selinfo); knlist_destroy(&f->selinfo.si_note); free(f, M_USBDEV); } static struct usb_endpoint * usb_dev_get_ep(struct usb_device *udev, uint8_t ep_index, uint8_t dir) { struct usb_endpoint *ep; uint8_t ep_dir; if (ep_index == 0) { ep = &udev->ctrl_ep; } else { if (dir == USB_FIFO_RX) { if (udev->flags.usb_mode == USB_MODE_HOST) { ep_dir = UE_DIR_IN; } else { ep_dir = UE_DIR_OUT; } } else { if (udev->flags.usb_mode == USB_MODE_HOST) { ep_dir = UE_DIR_OUT; } else { ep_dir = UE_DIR_IN; } } ep = usbd_get_ep_by_addr(udev, ep_index | ep_dir); } if (ep == NULL) { /* if the endpoint does not exist then return */ return (NULL); } if (ep->edesc == NULL) { /* invalid endpoint */ return (NULL); } return (ep); /* success */ } /*------------------------------------------------------------------------* * usb_fifo_open * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int usb_fifo_open(struct usb_cdev_privdata *cpd, struct usb_fifo *f, int fflags) { int err; if (f == NULL) { /* no FIFO there */ DPRINTFN(2, "no FIFO\n"); return (ENXIO); } /* remove FWRITE and FREAD flags */ fflags &= ~(FWRITE | FREAD); /* set correct file flags */ if ((f->fifo_index & 1) == USB_FIFO_TX) { fflags |= FWRITE; } else { fflags |= FREAD; } /* check if we are already opened */ /* we don't need any locks when checking this variable */ if (f->curr_cpd != NULL) { err = EBUSY; goto done; } /* reset short flag before open */ f->flag_short = 0; /* call open method */ err = (f->methods->f_open) (f, fflags); if (err) { goto done; } mtx_lock(f->priv_mtx); /* reset sleep flag */ f->flag_sleeping = 0; /* reset error flag */ f->flag_iserror = 0; /* reset complete flag */ f->flag_iscomplete = 0; /* reset select flag */ f->flag_isselect = 0; /* reset flushing flag */ f->flag_flushing = 0; /* reset ASYNC proc flag */ f->async_p = NULL; mtx_lock(&usb_ref_lock); /* flag the fifo as opened to prevent others */ f->curr_cpd = cpd; mtx_unlock(&usb_ref_lock); /* reset queue */ usb_fifo_reset(f); mtx_unlock(f->priv_mtx); done: return (err); } /*------------------------------------------------------------------------* * usb_fifo_reset *------------------------------------------------------------------------*/ void usb_fifo_reset(struct usb_fifo *f) { struct usb_mbuf *m; if (f == NULL) { return; } while (1) { USB_IF_DEQUEUE(&f->used_q, m); if (m) { USB_IF_ENQUEUE(&f->free_q, m); } else { break; } } /* reset have fragment flag */ f->flag_have_fragment = 0; } /*------------------------------------------------------------------------* * usb_fifo_close *------------------------------------------------------------------------*/ static void usb_fifo_close(struct usb_fifo *f, int fflags) { int err; /* check if we are not opened */ if (f->curr_cpd == NULL) { /* nothing to do - already closed */ return; } mtx_lock(f->priv_mtx); /* clear current cdev private data pointer */ mtx_lock(&usb_ref_lock); f->curr_cpd = NULL; mtx_unlock(&usb_ref_lock); /* check if we are watched by kevent */ KNOTE_LOCKED(&f->selinfo.si_note, 0); /* check if we are selected */ if (f->flag_isselect) { selwakeup(&f->selinfo); f->flag_isselect = 0; } /* check if a thread wants SIGIO */ if (f->async_p != NULL) { PROC_LOCK(f->async_p); kern_psignal(f->async_p, SIGIO); PROC_UNLOCK(f->async_p); f->async_p = NULL; } /* remove FWRITE and FREAD flags */ fflags &= ~(FWRITE | FREAD); /* flush written data, if any */ if ((f->fifo_index & 1) == USB_FIFO_TX) { if (!f->flag_iserror) { /* set flushing flag */ f->flag_flushing = 1; /* get the last packet in */ if (f->flag_have_fragment) { struct usb_mbuf *m; f->flag_have_fragment = 0; USB_IF_DEQUEUE(&f->free_q, m); if (m) { USB_IF_ENQUEUE(&f->used_q, m); } } /* start write transfer, if not already started */ (f->methods->f_start_write) (f); /* check if flushed already */ while (f->flag_flushing && (!f->flag_iserror)) { /* wait until all data has been written */ f->flag_sleeping = 1; err = cv_timedwait_sig(&f->cv_io, f->priv_mtx, USB_MS_TO_TICKS(USB_DEFAULT_TIMEOUT)); if (err) { DPRINTF("signal received\n"); break; } } } fflags |= FWRITE; /* stop write transfer, if not already stopped */ (f->methods->f_stop_write) (f); } else { fflags |= FREAD; /* stop write transfer, if not already stopped */ (f->methods->f_stop_read) (f); } /* check if we are sleeping */ if (f->flag_sleeping) { DPRINTFN(2, "Sleeping at close!\n"); } mtx_unlock(f->priv_mtx); /* call close method */ (f->methods->f_close) (f, fflags); DPRINTF("closed\n"); } /*------------------------------------------------------------------------* * usb_open - cdev callback *------------------------------------------------------------------------*/ static int usb_open(struct cdev *dev, int fflags, int devtype, struct thread *td) { struct usb_fs_privdata* pd = (struct usb_fs_privdata*)dev->si_drv1; struct usb_cdev_refdata refs; struct usb_cdev_privdata *cpd; int err; DPRINTFN(2, "%s fflags=0x%08x\n", devtoname(dev), fflags); KASSERT(fflags & (FREAD|FWRITE), ("invalid open flags")); if (((fflags & FREAD) && !(pd->mode & FREAD)) || ((fflags & FWRITE) && !(pd->mode & FWRITE))) { DPRINTFN(2, "access mode not supported\n"); return (EPERM); } cpd = malloc(sizeof(*cpd), M_USBDEV, M_WAITOK | M_ZERO); usb_loc_fill(pd, cpd); err = usb_ref_device(cpd, &refs, 1); if (err) { DPRINTFN(2, "cannot ref device\n"); free(cpd, M_USBDEV); return (ENXIO); } cpd->fflags = fflags; /* access mode for open lifetime */ /* create FIFOs, if any */ err = usb_fifo_create(cpd, &refs); /* check for error */ if (err) { DPRINTFN(2, "cannot create fifo\n"); usb_unref_device(cpd, &refs); free(cpd, M_USBDEV); return (err); } if (fflags & FREAD) { err = usb_fifo_open(cpd, refs.rxfifo, fflags); if (err) { DPRINTFN(2, "read open failed\n"); usb_unref_device(cpd, &refs); free(cpd, M_USBDEV); return (err); } } if (fflags & FWRITE) { err = usb_fifo_open(cpd, refs.txfifo, fflags); if (err) { DPRINTFN(2, "write open failed\n"); if (fflags & FREAD) { usb_fifo_close(refs.rxfifo, fflags); } usb_unref_device(cpd, &refs); free(cpd, M_USBDEV); return (err); } } usb_unref_device(cpd, &refs); devfs_set_cdevpriv(cpd, usb_close); return (0); } /*------------------------------------------------------------------------* * usb_close - cdev callback *------------------------------------------------------------------------*/ static void usb_close(void *arg) { struct usb_cdev_refdata refs; struct usb_cdev_privdata *cpd = arg; int err; DPRINTFN(2, "cpd=%p\n", cpd); err = usb_ref_device(cpd, &refs, 2 /* uref and allow detached state */); if (err) { DPRINTFN(2, "Cannot grab USB reference when " "closing USB file handle\n"); goto done; } if (cpd->fflags & FREAD) { usb_fifo_close(refs.rxfifo, cpd->fflags); } if (cpd->fflags & FWRITE) { usb_fifo_close(refs.txfifo, cpd->fflags); } usb_unref_device(cpd, &refs); done: free(cpd, M_USBDEV); } static void usb_dev_init(void *arg) { mtx_init(&usb_ref_lock, "USB ref mutex", NULL, MTX_DEF); sx_init(&usb_sym_lock, "USB sym mutex"); TAILQ_INIT(&usb_sym_head); /* check the UGEN methods */ usb_fifo_check_methods(&usb_ugen_methods); } SYSINIT(usb_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb_dev_init, NULL); static void usb_dev_init_post(void *arg) { /* * Create /dev/usb - this is needed for usbconfig(8), which * needs a well-known device name to access. */ usb_dev = make_dev(&usb_static_devsw, 0, UID_ROOT, GID_OPERATOR, 0644, USB_DEVICE_NAME); if (usb_dev == NULL) { DPRINTFN(0, "Could not create usb bus device\n"); } } SYSINIT(usb_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb_dev_init_post, NULL); static void usb_dev_uninit(void *arg) { if (usb_dev != NULL) { destroy_dev(usb_dev); usb_dev = NULL; } mtx_destroy(&usb_ref_lock); sx_destroy(&usb_sym_lock); } SYSUNINIT(usb_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb_dev_uninit, NULL); static int usb_ioctl_f_sub(struct usb_fifo *f, u_long cmd, void *addr, struct thread *td) { int error = 0; switch (cmd) { case FIODTYPE: *(int *)addr = 0; /* character device */ break; case FIONBIO: /* handled by upper FS layer */ break; case FIOASYNC: if (*(int *)addr) { if (f->async_p != NULL) { error = EBUSY; break; } f->async_p = USB_TD_GET_PROC(td); } else { f->async_p = NULL; } break; /* XXX this is not the most general solution */ case TIOCSPGRP: if (f->async_p == NULL) { error = EINVAL; break; } if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) { error = EPERM; break; } break; default: return (ENOIOCTL); } DPRINTFN(3, "cmd 0x%lx = %d\n", cmd, error); return (error); } /*------------------------------------------------------------------------* * usb_ioctl - cdev callback *------------------------------------------------------------------------*/ static int usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread* td) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; int fflags; int err; DPRINTFN(2, "cmd=0x%lx\n", cmd); err = devfs_get_cdevpriv((void **)&cpd); if (err != 0) return (err); /* * Performance optimisation: We try to check for IOCTL's that * don't need the USB reference first. Then we grab the USB * reference if we need it! */ err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); if (err) return (ENXIO); fflags = cpd->fflags; f = NULL; /* set default value */ err = ENOIOCTL; /* set default value */ if (fflags & FWRITE) { f = refs.txfifo; err = usb_ioctl_f_sub(f, cmd, addr, td); } if (fflags & FREAD) { f = refs.rxfifo; err = usb_ioctl_f_sub(f, cmd, addr, td); } KASSERT(f != NULL, ("fifo not found")); if (err != ENOIOCTL) goto done; err = (f->methods->f_ioctl) (f, cmd, addr, fflags); DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err); if (err != ENOIOCTL) goto done; if (usb_usb_ref_device(cpd, &refs)) { /* we lost the reference */ return (ENXIO); } err = (f->methods->f_ioctl_post) (f, cmd, addr, fflags); DPRINTFN(2, "f_ioctl_post cmd 0x%lx = %d\n", cmd, err); if (err == ENOIOCTL) err = ENOTTY; if (err) goto done; /* Wait for re-enumeration, if any */ while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) { usb_unref_device(cpd, &refs); usb_pause_mtx(NULL, hz / 128); while (usb_ref_device(cpd, &refs, 1 /* need uref */)) { if (usb_ref_device(cpd, &refs, 0)) { /* device no longer exists */ return (ENXIO); } usb_unref_device(cpd, &refs); usb_pause_mtx(NULL, hz / 128); } } done: usb_unref_device(cpd, &refs); return (err); } static void usb_filter_detach(struct knote *kn) { struct usb_fifo *f = kn->kn_hook; knlist_remove(&f->selinfo.si_note, kn, 0); } static int usb_filter_write(struct knote *kn, long hint) { struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; DPRINTFN(2, "\n"); f = kn->kn_hook; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); cpd = f->curr_cpd; if (cpd == NULL) { m = (void *)1; } else if (f->fs_ep_max == 0) { if (f->flag_iserror) { /* we got an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start write transfer, if not * already started */ (f->methods->f_start_write) (f); } /* check if any packets are available */ USB_IF_POLL(&f->free_q, m); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } return (m ? 1 : 0); } static int usb_filter_read(struct knote *kn, long hint) { struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; DPRINTFN(2, "\n"); f = kn->kn_hook; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); cpd = f->curr_cpd; if (cpd == NULL) { m = (void *)1; } else if (f->fs_ep_max == 0) { if (f->flag_iserror) { /* we have an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start read transfer, if not * already started */ (f->methods->f_start_read) (f); } /* check if any packets are available */ USB_IF_POLL(&f->used_q, m); /* start reading data, if any */ if (m == NULL) (f->methods->f_start_read) (f); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } return (m ? 1 : 0); } static struct filterops usb_filtops_write = { .f_isfd = 1, .f_detach = usb_filter_detach, .f_event = usb_filter_write, }; static struct filterops usb_filtops_read = { .f_isfd = 1, .f_detach = usb_filter_detach, .f_event = usb_filter_read, }; /* ARGSUSED */ static int usb_kqfilter(struct cdev* dev, struct knote *kn) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; int fflags; int err = EINVAL; DPRINTFN(2, "\n"); if (devfs_get_cdevpriv((void **)&cpd) != 0 || usb_ref_device(cpd, &refs, 0) != 0) return (ENXIO); fflags = cpd->fflags; /* Figure out who needs service */ switch (kn->kn_filter) { case EVFILT_WRITE: if (fflags & FWRITE) { f = refs.txfifo; kn->kn_fop = &usb_filtops_write; err = 0; } break; case EVFILT_READ: if (fflags & FREAD) { f = refs.rxfifo; kn->kn_fop = &usb_filtops_read; err = 0; } break; default: err = EOPNOTSUPP; break; } if (err == 0) { kn->kn_hook = f; mtx_lock(f->priv_mtx); knlist_add(&f->selinfo.si_note, kn, 1); mtx_unlock(f->priv_mtx); } usb_unref_device(cpd, &refs); return (err); } /* ARGSUSED */ static int usb_poll(struct cdev* dev, int events, struct thread* td) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; int fflags, revents; if (devfs_get_cdevpriv((void **)&cpd) != 0 || usb_ref_device(cpd, &refs, 0) != 0) return (events & (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); fflags = cpd->fflags; /* Figure out who needs service */ revents = 0; if ((events & (POLLOUT | POLLWRNORM)) && (fflags & FWRITE)) { f = refs.txfifo; mtx_lock(f->priv_mtx); if (!refs.is_usbfs) { if (f->flag_iserror) { /* we got an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start write transfer, if not * already started */ (f->methods->f_start_write) (f); } /* check if any packets are available */ USB_IF_POLL(&f->free_q, m); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } if (m) { revents |= events & (POLLOUT | POLLWRNORM); } else { f->flag_isselect = 1; selrecord(td, &f->selinfo); } mtx_unlock(f->priv_mtx); } if ((events & (POLLIN | POLLRDNORM)) && (fflags & FREAD)) { f = refs.rxfifo; mtx_lock(f->priv_mtx); if (!refs.is_usbfs) { if (f->flag_iserror) { /* we have an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start read transfer, if not * already started */ (f->methods->f_start_read) (f); } /* check if any packets are available */ USB_IF_POLL(&f->used_q, m); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } if (m) { revents |= events & (POLLIN | POLLRDNORM); } else { f->flag_isselect = 1; selrecord(td, &f->selinfo); if (!refs.is_usbfs) { /* start reading data */ (f->methods->f_start_read) (f); } } mtx_unlock(f->priv_mtx); } usb_unref_device(cpd, &refs); return (revents); } static int usb_read(struct cdev *dev, struct uio *uio, int ioflag) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; int io_len; int err; uint8_t tr_data = 0; err = devfs_get_cdevpriv((void **)&cpd); if (err != 0) return (err); err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); if (err) return (ENXIO); f = refs.rxfifo; if (f == NULL) { /* should not happen */ usb_unref_device(cpd, &refs); return (EPERM); } mtx_lock(f->priv_mtx); /* check for permanent read error */ if (f->flag_iserror) { err = EIO; goto done; } /* check if USB-FS interface is active */ if (refs.is_usbfs) { /* * The queue is used for events that should be * retrieved using the "USB_FS_COMPLETE" ioctl. */ err = EINVAL; goto done; } while (uio->uio_resid > 0) { USB_IF_DEQUEUE(&f->used_q, m); if (m == NULL) { /* start read transfer, if not already started */ (f->methods->f_start_read) (f); if (ioflag & IO_NDELAY) { if (tr_data) { /* return length before error */ break; } err = EWOULDBLOCK; break; } DPRINTF("sleeping\n"); err = usb_fifo_wait(f); if (err) { break; } continue; } if (f->methods->f_filter_read) { /* * Sometimes it is convenient to process data at the * expense of a userland process instead of a kernel * process. */ (f->methods->f_filter_read) (f, m); } tr_data = 1; io_len = MIN(m->cur_data_len, uio->uio_resid); DPRINTFN(2, "transfer %d bytes from %p\n", io_len, m->cur_data_ptr); err = usb_fifo_uiomove(f, m->cur_data_ptr, io_len, uio); m->cur_data_len -= io_len; m->cur_data_ptr += io_len; if (m->cur_data_len == 0) { uint8_t last_packet; last_packet = m->last_packet; USB_IF_ENQUEUE(&f->free_q, m); if (last_packet) { /* keep framing */ break; } } else { USB_IF_PREPEND(&f->used_q, m); } if (err) { break; } } done: mtx_unlock(f->priv_mtx); usb_unref_device(cpd, &refs); return (err); } static int usb_write(struct cdev *dev, struct uio *uio, int ioflag) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; uint8_t *pdata; int io_len; int err; uint8_t tr_data = 0; DPRINTFN(2, "\n"); err = devfs_get_cdevpriv((void **)&cpd); if (err != 0) return (err); err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); if (err) return (ENXIO); f = refs.txfifo; if (f == NULL) { /* should not happen */ usb_unref_device(cpd, &refs); return (EPERM); } mtx_lock(f->priv_mtx); /* check for permanent write error */ if (f->flag_iserror) { err = EIO; goto done; } /* check if USB-FS interface is active */ if (refs.is_usbfs) { /* * The queue is used for events that should be * retrieved using the "USB_FS_COMPLETE" ioctl. */ err = EINVAL; goto done; } if (f->queue_data == NULL) { /* start write transfer, if not already started */ (f->methods->f_start_write) (f); } /* we allow writing zero length data */ do { USB_IF_DEQUEUE(&f->free_q, m); if (m == NULL) { if (ioflag & IO_NDELAY) { if (tr_data) { /* return length before error */ break; } err = EWOULDBLOCK; break; } DPRINTF("sleeping\n"); err = usb_fifo_wait(f); if (err) { break; } continue; } tr_data = 1; if (f->flag_have_fragment == 0) { USB_MBUF_RESET(m); io_len = m->cur_data_len; pdata = m->cur_data_ptr; if (io_len > uio->uio_resid) io_len = uio->uio_resid; m->cur_data_len = io_len; } else { io_len = m->max_data_len - m->cur_data_len; pdata = m->cur_data_ptr + m->cur_data_len; if (io_len > uio->uio_resid) io_len = uio->uio_resid; m->cur_data_len += io_len; } DPRINTFN(2, "transfer %d bytes to %p\n", io_len, pdata); err = usb_fifo_uiomove(f, pdata, io_len, uio); if (err) { f->flag_have_fragment = 0; USB_IF_ENQUEUE(&f->free_q, m); break; } /* check if the buffer is ready to be transmitted */ if ((f->flag_write_defrag == 0) || (m->cur_data_len == m->max_data_len)) { f->flag_have_fragment = 0; /* * Check for write filter: * * Sometimes it is convenient to process data * at the expense of a userland process * instead of a kernel process. */ if (f->methods->f_filter_write) { (f->methods->f_filter_write) (f, m); } /* Put USB mbuf in the used queue */ USB_IF_ENQUEUE(&f->used_q, m); /* Start writing data, if not already started */ (f->methods->f_start_write) (f); } else { /* Wait for more data or close */ f->flag_have_fragment = 1; USB_IF_PREPEND(&f->free_q, m); } } while (uio->uio_resid > 0); done: mtx_unlock(f->priv_mtx); usb_unref_device(cpd, &refs); return (err); } int usb_static_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { union { struct usb_read_dir *urd; void* data; } u; int err; u.data = data; switch (cmd) { case USB_READ_DIR: err = usb_read_symlink(u.urd->urd_data, u.urd->urd_startentry, u.urd->urd_maxlen); break; case USB_DEV_QUIRK_GET: case USB_QUIRK_NAME_GET: case USB_DEV_QUIRK_ADD: case USB_DEV_QUIRK_REMOVE: err = usb_quirk_ioctl_p(cmd, data, fflag, td); break; case USB_GET_TEMPLATE: *(int *)data = usb_template; err = 0; break; case USB_SET_TEMPLATE: err = priv_check(curthread, PRIV_DRIVER); if (err) break; usb_template = *(int *)data; break; default: err = ENOTTY; break; } return (err); } static int usb_fifo_uiomove(struct usb_fifo *f, void *cp, int n, struct uio *uio) { int error; mtx_unlock(f->priv_mtx); /* * "uiomove()" can sleep so one needs to make a wrapper, * exiting the mutex and checking things: */ error = uiomove(cp, n, uio); mtx_lock(f->priv_mtx); return (error); } int usb_fifo_wait(struct usb_fifo *f) { int err; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); if (f->flag_iserror) { /* we are gone */ return (EIO); } f->flag_sleeping = 1; err = cv_wait_sig(&f->cv_io, f->priv_mtx); if (f->flag_iserror) { /* we are gone */ err = EIO; } return (err); } void usb_fifo_signal(struct usb_fifo *f) { if (f->flag_sleeping) { f->flag_sleeping = 0; cv_broadcast(&f->cv_io); } } void usb_fifo_wakeup(struct usb_fifo *f) { usb_fifo_signal(f); KNOTE_LOCKED(&f->selinfo.si_note, 0); if (f->flag_isselect) { selwakeup(&f->selinfo); f->flag_isselect = 0; } if (f->async_p != NULL) { PROC_LOCK(f->async_p); kern_psignal(f->async_p, SIGIO); PROC_UNLOCK(f->async_p); } } static int usb_fifo_dummy_open(struct usb_fifo *fifo, int fflags) { return (0); } static void usb_fifo_dummy_close(struct usb_fifo *fifo, int fflags) { return; } static int usb_fifo_dummy_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { return (ENOIOCTL); } static void usb_fifo_dummy_cmd(struct usb_fifo *fifo) { fifo->flag_flushing = 0; /* not flushing */ } static void usb_fifo_check_methods(struct usb_fifo_methods *pm) { /* check that all callback functions are OK */ if (pm->f_open == NULL) pm->f_open = &usb_fifo_dummy_open; if (pm->f_close == NULL) pm->f_close = &usb_fifo_dummy_close; if (pm->f_ioctl == NULL) pm->f_ioctl = &usb_fifo_dummy_ioctl; if (pm->f_ioctl_post == NULL) pm->f_ioctl_post = &usb_fifo_dummy_ioctl; if (pm->f_start_read == NULL) pm->f_start_read = &usb_fifo_dummy_cmd; if (pm->f_stop_read == NULL) pm->f_stop_read = &usb_fifo_dummy_cmd; if (pm->f_start_write == NULL) pm->f_start_write = &usb_fifo_dummy_cmd; if (pm->f_stop_write == NULL) pm->f_stop_write = &usb_fifo_dummy_cmd; } /*------------------------------------------------------------------------* * usb_fifo_attach * * The following function will create a duplex FIFO. * * Return values: * 0: Success. * Else: Failure. *------------------------------------------------------------------------*/ int usb_fifo_attach(struct usb_device *udev, void *priv_sc, struct mtx *priv_mtx, struct usb_fifo_methods *pm, struct usb_fifo_sc *f_sc, uint16_t unit, int16_t subunit, uint8_t iface_index, uid_t uid, gid_t gid, int mode) { struct usb_fifo *f_tx; struct usb_fifo *f_rx; char devname[32]; uint8_t n; f_sc->fp[USB_FIFO_TX] = NULL; f_sc->fp[USB_FIFO_RX] = NULL; if (pm == NULL) return (EINVAL); /* check the methods */ usb_fifo_check_methods(pm); if (priv_mtx == NULL) priv_mtx = &Giant; /* search for a free FIFO slot */ for (n = 0;; n += 2) { if (n == USB_FIFO_MAX) { /* end of FIFOs reached */ return (ENOMEM); } /* Check for TX FIFO */ if (udev->fifo[n + USB_FIFO_TX] != NULL) { continue; } /* Check for RX FIFO */ if (udev->fifo[n + USB_FIFO_RX] != NULL) { continue; } break; } f_tx = usb_fifo_alloc(priv_mtx); f_rx = usb_fifo_alloc(priv_mtx); if ((f_tx == NULL) || (f_rx == NULL)) { usb_fifo_free(f_tx); usb_fifo_free(f_rx); return (ENOMEM); } /* initialise FIFO structures */ f_tx->fifo_index = n + USB_FIFO_TX; f_tx->dev_ep_index = -1; f_tx->priv_sc0 = priv_sc; f_tx->methods = pm; f_tx->iface_index = iface_index; f_tx->udev = udev; f_rx->fifo_index = n + USB_FIFO_RX; f_rx->dev_ep_index = -1; f_rx->priv_sc0 = priv_sc; f_rx->methods = pm; f_rx->iface_index = iface_index; f_rx->udev = udev; f_sc->fp[USB_FIFO_TX] = f_tx; f_sc->fp[USB_FIFO_RX] = f_rx; mtx_lock(&usb_ref_lock); udev->fifo[f_tx->fifo_index] = f_tx; udev->fifo[f_rx->fifo_index] = f_rx; mtx_unlock(&usb_ref_lock); for (n = 0; n != 4; n++) { if (pm->basename[n] == NULL) { continue; } if (subunit < 0) { if (snprintf(devname, sizeof(devname), "%s%u%s", pm->basename[n], unit, pm->postfix[n] ? pm->postfix[n] : "")) { /* ignore */ } } else { if (snprintf(devname, sizeof(devname), "%s%u.%d%s", pm->basename[n], unit, subunit, pm->postfix[n] ? pm->postfix[n] : "")) { /* ignore */ } } /* * Distribute the symbolic links into two FIFO structures: */ if (n & 1) { f_rx->symlink[n / 2] = usb_alloc_symlink(devname); } else { f_tx->symlink[n / 2] = usb_alloc_symlink(devname); } /* Create the device */ f_sc->dev = usb_make_dev(udev, devname, -1, f_tx->fifo_index & f_rx->fifo_index, FREAD|FWRITE, uid, gid, mode); } DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx); return (0); } /*------------------------------------------------------------------------* * usb_fifo_alloc_buffer * * Return values: * 0: Success * Else failure *------------------------------------------------------------------------*/ int usb_fifo_alloc_buffer(struct usb_fifo *f, usb_size_t bufsize, uint16_t nbuf) { usb_fifo_free_buffer(f); /* allocate an endpoint */ f->free_q.ifq_maxlen = nbuf; f->used_q.ifq_maxlen = nbuf; f->queue_data = usb_alloc_mbufs( M_USBDEV, &f->free_q, bufsize, nbuf); if ((f->queue_data == NULL) && bufsize && nbuf) { return (ENOMEM); } return (0); /* success */ } /*------------------------------------------------------------------------* * usb_fifo_free_buffer * * This function will free the buffers associated with a FIFO. This * function can be called multiple times in a row. *------------------------------------------------------------------------*/ void usb_fifo_free_buffer(struct usb_fifo *f) { if (f->queue_data) { /* free old buffer */ free(f->queue_data, M_USBDEV); f->queue_data = NULL; } /* reset queues */ memset(&f->free_q, 0, sizeof(f->free_q)); memset(&f->used_q, 0, sizeof(f->used_q)); } void usb_fifo_detach(struct usb_fifo_sc *f_sc) { if (f_sc == NULL) { return; } usb_fifo_free(f_sc->fp[USB_FIFO_TX]); usb_fifo_free(f_sc->fp[USB_FIFO_RX]); f_sc->fp[USB_FIFO_TX] = NULL; f_sc->fp[USB_FIFO_RX] = NULL; usb_destroy_dev(f_sc->dev); f_sc->dev = NULL; DPRINTFN(2, "detached %p\n", f_sc); } usb_size_t usb_fifo_put_bytes_max(struct usb_fifo *f) { struct usb_mbuf *m; usb_size_t len; USB_IF_POLL(&f->free_q, m); if (m) { len = m->max_data_len; } else { len = 0; } return (len); } /*------------------------------------------------------------------------* * usb_fifo_put_data * * what: * 0 - normal operation * 1 - set last packet flag to enforce framing *------------------------------------------------------------------------*/ void usb_fifo_put_data(struct usb_fifo *f, struct usb_page_cache *pc, usb_frlength_t offset, usb_frlength_t len, uint8_t what) { struct usb_mbuf *m; usb_frlength_t io_len; while (len || (what == 1)) { USB_IF_DEQUEUE(&f->free_q, m); if (m) { USB_MBUF_RESET(m); io_len = MIN(len, m->cur_data_len); usbd_copy_out(pc, offset, m->cur_data_ptr, io_len); m->cur_data_len = io_len; offset += io_len; len -= io_len; if ((len == 0) && (what == 1)) { m->last_packet = 1; } USB_IF_ENQUEUE(&f->used_q, m); usb_fifo_wakeup(f); if ((len == 0) || (what == 1)) { break; } } else { break; } } } void usb_fifo_put_data_linear(struct usb_fifo *f, void *ptr, usb_size_t len, uint8_t what) { struct usb_mbuf *m; usb_size_t io_len; while (len || (what == 1)) { USB_IF_DEQUEUE(&f->free_q, m); if (m) { USB_MBUF_RESET(m); io_len = MIN(len, m->cur_data_len); memcpy(m->cur_data_ptr, ptr, io_len); m->cur_data_len = io_len; ptr = USB_ADD_BYTES(ptr, io_len); len -= io_len; if ((len == 0) && (what == 1)) { m->last_packet = 1; } USB_IF_ENQUEUE(&f->used_q, m); usb_fifo_wakeup(f); if ((len == 0) || (what == 1)) { break; } } else { break; } } } uint8_t usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len) { struct usb_mbuf *m; USB_IF_DEQUEUE(&f->free_q, m); if (m) { m->cur_data_len = len; m->cur_data_ptr = ptr; USB_IF_ENQUEUE(&f->used_q, m); usb_fifo_wakeup(f); return (1); } return (0); } void usb_fifo_put_data_error(struct usb_fifo *f) { f->flag_iserror = 1; usb_fifo_wakeup(f); } /*------------------------------------------------------------------------* * usb_fifo_get_data * * what: * 0 - normal operation * 1 - only get one "usb_mbuf" * * returns: * 0 - no more data * 1 - data in buffer *------------------------------------------------------------------------*/ uint8_t usb_fifo_get_data(struct usb_fifo *f, struct usb_page_cache *pc, usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen, uint8_t what) { struct usb_mbuf *m; usb_frlength_t io_len; uint8_t tr_data = 0; actlen[0] = 0; while (1) { USB_IF_DEQUEUE(&f->used_q, m); if (m) { tr_data = 1; io_len = MIN(len, m->cur_data_len); usbd_copy_in(pc, offset, m->cur_data_ptr, io_len); len -= io_len; offset += io_len; actlen[0] += io_len; m->cur_data_ptr += io_len; m->cur_data_len -= io_len; if ((m->cur_data_len == 0) || (what == 1)) { USB_IF_ENQUEUE(&f->free_q, m); usb_fifo_wakeup(f); if (what == 1) { break; } } else { USB_IF_PREPEND(&f->used_q, m); } } else { if (tr_data) { /* wait for data to be written out */ break; } if (f->flag_flushing) { /* check if we should send a short packet */ if (f->flag_short != 0) { f->flag_short = 0; tr_data = 1; break; } /* flushing complete */ f->flag_flushing = 0; usb_fifo_wakeup(f); } break; } if (len == 0) { break; } } return (tr_data); } uint8_t usb_fifo_get_data_linear(struct usb_fifo *f, void *ptr, usb_size_t len, usb_size_t *actlen, uint8_t what) { struct usb_mbuf *m; usb_size_t io_len; uint8_t tr_data = 0; actlen[0] = 0; while (1) { USB_IF_DEQUEUE(&f->used_q, m); if (m) { tr_data = 1; io_len = MIN(len, m->cur_data_len); memcpy(ptr, m->cur_data_ptr, io_len); len -= io_len; ptr = USB_ADD_BYTES(ptr, io_len); actlen[0] += io_len; m->cur_data_ptr += io_len; m->cur_data_len -= io_len; if ((m->cur_data_len == 0) || (what == 1)) { USB_IF_ENQUEUE(&f->free_q, m); usb_fifo_wakeup(f); if (what == 1) { break; } } else { USB_IF_PREPEND(&f->used_q, m); } } else { if (tr_data) { /* wait for data to be written out */ break; } if (f->flag_flushing) { /* check if we should send a short packet */ if (f->flag_short != 0) { f->flag_short = 0; tr_data = 1; break; } /* flushing complete */ f->flag_flushing = 0; usb_fifo_wakeup(f); } break; } if (len == 0) { break; } } return (tr_data); } uint8_t usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, usb_size_t *plen) { struct usb_mbuf *m; USB_IF_POLL(&f->used_q, m); if (m) { *plen = m->cur_data_len; *pptr = m->cur_data_ptr; return (1); } return (0); } void usb_fifo_get_data_error(struct usb_fifo *f) { f->flag_iserror = 1; usb_fifo_wakeup(f); } /*------------------------------------------------------------------------* * usb_alloc_symlink * * Return values: * NULL: Failure * Else: Pointer to symlink entry *------------------------------------------------------------------------*/ struct usb_symlink * usb_alloc_symlink(const char *target) { struct usb_symlink *ps; ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK); if (ps == NULL) { return (ps); } /* XXX no longer needed */ strlcpy(ps->src_path, target, sizeof(ps->src_path)); ps->src_len = strlen(ps->src_path); strlcpy(ps->dst_path, target, sizeof(ps->dst_path)); ps->dst_len = strlen(ps->dst_path); sx_xlock(&usb_sym_lock); TAILQ_INSERT_TAIL(&usb_sym_head, ps, sym_entry); sx_unlock(&usb_sym_lock); return (ps); } /*------------------------------------------------------------------------* * usb_free_symlink *------------------------------------------------------------------------*/ void usb_free_symlink(struct usb_symlink *ps) { if (ps == NULL) { return; } sx_xlock(&usb_sym_lock); TAILQ_REMOVE(&usb_sym_head, ps, sym_entry); sx_unlock(&usb_sym_lock); free(ps, M_USBDEV); } /*------------------------------------------------------------------------* * usb_read_symlink * * Return value: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ int usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len) { struct usb_symlink *ps; uint32_t temp; uint32_t delta = 0; uint8_t len; int error = 0; sx_xlock(&usb_sym_lock); TAILQ_FOREACH(ps, &usb_sym_head, sym_entry) { /* * Compute total length of source and destination symlink * strings pluss one length byte and two NUL bytes: */ temp = ps->src_len + ps->dst_len + 3; if (temp > 255) { /* * Skip entry because this length cannot fit * into one byte: */ continue; } if (startentry != 0) { /* decrement read offset */ startentry--; continue; } if (temp > user_len) { /* out of buffer space */ break; } len = temp; /* copy out total length */ error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); if (error) { break; } delta += 1; /* copy out source string */ error = copyout(ps->src_path, USB_ADD_BYTES(user_ptr, delta), ps->src_len); if (error) { break; } len = 0; delta += ps->src_len; error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); if (error) { break; } delta += 1; /* copy out destination string */ error = copyout(ps->dst_path, USB_ADD_BYTES(user_ptr, delta), ps->dst_len); if (error) { break; } len = 0; delta += ps->dst_len; error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); if (error) { break; } delta += 1; user_len -= temp; } /* a zero length entry indicates the end */ if ((user_len != 0) && (error == 0)) { len = 0; error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); } sx_unlock(&usb_sym_lock); return (error); } void usb_fifo_set_close_zlp(struct usb_fifo *f, uint8_t onoff) { if (f == NULL) return; /* send a Zero Length Packet, ZLP, before close */ f->flag_short = onoff; } void usb_fifo_set_write_defrag(struct usb_fifo *f, uint8_t onoff) { if (f == NULL) return; /* defrag written data */ f->flag_write_defrag = onoff; /* reset defrag state */ f->flag_have_fragment = 0; } void * usb_fifo_softc(struct usb_fifo *f) { return (f->priv_sc0); } #endif /* USB_HAVE_UGEN */ Index: head/sys/dev/usb/usb_generic.c =================================================================== --- head/sys/dev/usb/usb_generic.c (revision 357971) +++ head/sys/dev/usb/usb_generic.c (revision 357972) @@ -1,2385 +1,2386 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ugen_debug #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #if USB_HAVE_UGEN /* defines */ #define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */ #define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */ #define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */ /* function prototypes */ static usb_callback_t ugen_read_clear_stall_callback; static usb_callback_t ugen_write_clear_stall_callback; static usb_callback_t ugen_ctrl_read_callback; static usb_callback_t ugen_ctrl_write_callback; static usb_callback_t ugen_isoc_read_callback; static usb_callback_t ugen_isoc_write_callback; static usb_callback_t ugen_ctrl_fs_callback; static usb_fifo_open_t ugen_open; static usb_fifo_close_t ugen_close; static usb_fifo_ioctl_t ugen_ioctl; static usb_fifo_ioctl_t ugen_ioctl_post; static usb_fifo_cmd_t ugen_start_read; static usb_fifo_cmd_t ugen_start_write; static usb_fifo_cmd_t ugen_stop_io; static int ugen_transfer_setup(struct usb_fifo *, const struct usb_config *, uint8_t); static int ugen_open_pipe_write(struct usb_fifo *); static int ugen_open_pipe_read(struct usb_fifo *); static int ugen_set_config(struct usb_fifo *, uint8_t); static int ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t); static int ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *); static int ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *); static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd); static int usb_gen_fill_deviceinfo(struct usb_fifo *, struct usb_device_info *); static int ugen_re_enumerate(struct usb_fifo *); static int ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int); static uint8_t ugen_fs_get_complete(struct usb_fifo *, uint8_t *); static int ugen_fs_uninit(struct usb_fifo *f); /* structures */ struct usb_fifo_methods usb_ugen_methods = { .f_open = &ugen_open, .f_close = &ugen_close, .f_ioctl = &ugen_ioctl, .f_ioctl_post = &ugen_ioctl_post, .f_start_read = &ugen_start_read, .f_stop_read = &ugen_stop_io, .f_start_write = &ugen_start_write, .f_stop_write = &ugen_stop_io, }; #ifdef USB_DEBUG static int ugen_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB generic"); SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RWTUN, &ugen_debug, 0, "Debug level"); #endif /* prototypes */ static int ugen_transfer_setup(struct usb_fifo *f, const struct usb_config *setup, uint8_t n_setup) { struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_device *udev = f->udev; uint8_t iface_index = ep->iface_index; int error; mtx_unlock(f->priv_mtx); /* * "usbd_transfer_setup()" can sleep so one needs to make a wrapper, * exiting the mutex and checking things */ error = usbd_transfer_setup(udev, &iface_index, f->xfer, setup, n_setup, f, f->priv_mtx); if (error == 0) { if (f->xfer[0]->nframes == 1) { error = usb_fifo_alloc_buffer(f, f->xfer[0]->max_data_length, 2); } else { error = usb_fifo_alloc_buffer(f, f->xfer[0]->max_frame_size, 2 * f->xfer[0]->nframes); } if (error) { usbd_transfer_unsetup(f->xfer, n_setup); } } mtx_lock(f->priv_mtx); return (error); } static int ugen_open(struct usb_fifo *f, int fflags) { struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_endpoint_descriptor *ed = ep->edesc; uint8_t type; DPRINTFN(1, "flag=0x%x pid=%d name=%s\n", fflags, curthread->td_proc->p_pid, curthread->td_proc->p_comm); mtx_lock(f->priv_mtx); switch (usbd_get_speed(f->udev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: f->nframes = UGEN_HW_FRAMES; f->bufsize = UGEN_BULK_FS_BUFFER_SIZE; break; default: f->nframes = UGEN_HW_FRAMES * 8; f->bufsize = UGEN_BULK_HS_BUFFER_SIZE; break; } type = ed->bmAttributes & UE_XFERTYPE; if (type == UE_INTERRUPT) { f->bufsize = 0; /* use "wMaxPacketSize" */ } f->timeout = USB_NO_TIMEOUT; f->flag_short = 0; f->fifo_zlp = 0; mtx_unlock(f->priv_mtx); return (0); } static void ugen_close(struct usb_fifo *f, int fflags) { DPRINTFN(1, "flag=0x%x pid=%d name=%s\n", fflags, curthread->td_proc->p_pid, curthread->td_proc->p_comm); /* cleanup */ mtx_lock(f->priv_mtx); usbd_transfer_stop(f->xfer[0]); usbd_transfer_stop(f->xfer[1]); mtx_unlock(f->priv_mtx); usbd_transfer_unsetup(f->xfer, 2); usb_fifo_free_buffer(f); if (ugen_fs_uninit(f)) { /* ignore any errors - we are closing */ DPRINTFN(6, "no FIFOs\n"); } } static int ugen_open_pipe_write(struct usb_fifo *f) { struct usb_config usb_config[2]; struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_endpoint_descriptor *ed = ep->edesc; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); if (f->xfer[0] || f->xfer[1]) { /* transfers are already opened */ return (0); } memset(usb_config, 0, sizeof(usb_config)); usb_config[1].type = UE_CONTROL; usb_config[1].endpoint = 0; usb_config[1].direction = UE_DIR_ANY; usb_config[1].timeout = 1000; /* 1 second */ usb_config[1].interval = 50;/* 50 milliseconds */ usb_config[1].bufsize = sizeof(struct usb_device_request); usb_config[1].callback = &ugen_write_clear_stall_callback; usb_config[1].usb_mode = USB_MODE_HOST; usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; usb_config[0].stream_id = 0; /* XXX support more stream ID's */ usb_config[0].direction = UE_DIR_TX; usb_config[0].interval = USB_DEFAULT_INTERVAL; usb_config[0].flags.proxy_buffer = 1; usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ switch (ed->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_BULK: if (f->flag_short) { usb_config[0].flags.force_short_xfer = 1; } usb_config[0].callback = &ugen_ctrl_write_callback; usb_config[0].timeout = f->timeout; usb_config[0].frames = 1; usb_config[0].bufsize = f->bufsize; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } /* first transfer does not clear stall */ f->flag_stall = 0; break; case UE_ISOCHRONOUS: usb_config[0].flags.short_xfer_ok = 1; usb_config[0].bufsize = 0; /* use default */ usb_config[0].frames = f->nframes; usb_config[0].callback = &ugen_isoc_write_callback; usb_config[0].timeout = 0; /* clone configuration */ usb_config[1] = usb_config[0]; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } break; default: return (EINVAL); } return (0); } static int ugen_open_pipe_read(struct usb_fifo *f) { struct usb_config usb_config[2]; struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_endpoint_descriptor *ed = ep->edesc; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); if (f->xfer[0] || f->xfer[1]) { /* transfers are already opened */ return (0); } memset(usb_config, 0, sizeof(usb_config)); usb_config[1].type = UE_CONTROL; usb_config[1].endpoint = 0; usb_config[1].direction = UE_DIR_ANY; usb_config[1].timeout = 1000; /* 1 second */ usb_config[1].interval = 50;/* 50 milliseconds */ usb_config[1].bufsize = sizeof(struct usb_device_request); usb_config[1].callback = &ugen_read_clear_stall_callback; usb_config[1].usb_mode = USB_MODE_HOST; usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; usb_config[0].stream_id = 0; /* XXX support more stream ID's */ usb_config[0].direction = UE_DIR_RX; usb_config[0].interval = USB_DEFAULT_INTERVAL; usb_config[0].flags.proxy_buffer = 1; usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ switch (ed->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_BULK: if (f->flag_short) { usb_config[0].flags.short_xfer_ok = 1; } usb_config[0].timeout = f->timeout; usb_config[0].frames = 1; usb_config[0].callback = &ugen_ctrl_read_callback; usb_config[0].bufsize = f->bufsize; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } /* first transfer does not clear stall */ f->flag_stall = 0; break; case UE_ISOCHRONOUS: usb_config[0].flags.short_xfer_ok = 1; usb_config[0].bufsize = 0; /* use default */ usb_config[0].frames = f->nframes; usb_config[0].callback = &ugen_isoc_read_callback; usb_config[0].timeout = 0; /* clone configuration */ usb_config[1] = usb_config[0]; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } break; default: return (EINVAL); } return (0); } static void ugen_start_read(struct usb_fifo *f) { /* check that pipes are open */ if (ugen_open_pipe_read(f)) { /* signal error */ usb_fifo_put_data_error(f); } /* start transfers */ usbd_transfer_start(f->xfer[0]); usbd_transfer_start(f->xfer[1]); } static void ugen_start_write(struct usb_fifo *f) { /* check that pipes are open */ if (ugen_open_pipe_write(f)) { /* signal error */ usb_fifo_get_data_error(f); } /* start transfers */ usbd_transfer_start(f->xfer[0]); usbd_transfer_start(f->xfer[1]); } static void ugen_stop_io(struct usb_fifo *f) { /* stop transfers */ usbd_transfer_stop(f->xfer[0]); usbd_transfer_stop(f->xfer[1]); } static void ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); struct usb_mbuf *m; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (xfer->actlen == 0) { if (f->fifo_zlp != 4) { f->fifo_zlp++; } else { /* * Throttle a little bit we have multiple ZLPs * in a row! */ xfer->interval = 64; /* ms */ } } else { /* clear throttle */ xfer->interval = 0; f->fifo_zlp = 0; } usb_fifo_put_data(f, xfer->frbuffers, 0, xfer->actlen, 1); case USB_ST_SETUP: if (f->flag_stall) { usbd_transfer_start(f->xfer[1]); break; } USB_IF_POLL(&f->free_q, m); if (m) { usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); } break; default: /* Error */ if (xfer->error != USB_ERR_CANCELLED) { /* send a zero length packet to userland */ usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1); f->flag_stall = 1; f->fifo_zlp = 0; usbd_transfer_start(f->xfer[1]); } break; } } static void ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); usb_frlength_t actlen; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: /* * If writing is in stall, just jump to clear stall * callback and solve the situation. */ if (f->flag_stall) { usbd_transfer_start(f->xfer[1]); break; } /* * Write data, setup and perform hardware transfer. */ if (usb_fifo_get_data(f, xfer->frbuffers, 0, xfer->max_data_length, &actlen, 0)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } break; default: /* Error */ if (xfer->error != USB_ERR_CANCELLED) { f->flag_stall = 1; usbd_transfer_start(f->xfer[1]); } break; } } static void ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = f->xfer[0]; if (f->flag_stall == 0) { /* nothing to do */ return; } if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTFN(5, "f=%p: stall cleared\n", f); f->flag_stall = 0; usbd_transfer_start(xfer_other); } } static void ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = f->xfer[0]; if (f->flag_stall == 0) { /* nothing to do */ return; } if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTFN(5, "f=%p: stall cleared\n", f); f->flag_stall = 0; usbd_transfer_start(xfer_other); } } static void ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); usb_frlength_t offset; usb_frcount_t n; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "actlen=%d\n", xfer->actlen); offset = 0; for (n = 0; n != xfer->aframes; n++) { usb_fifo_put_data(f, xfer->frbuffers, offset, xfer->frlengths[n], 1); offset += xfer->max_frame_size; } case USB_ST_SETUP: tr_setup: for (n = 0; n != xfer->nframes; n++) { /* setup size for next transfer */ usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size); } usbd_transfer_submit(xfer); break; default: /* Error */ if (xfer->error == USB_ERR_CANCELLED) { break; } goto tr_setup; } } static void ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); usb_frlength_t actlen; usb_frlength_t offset; usb_frcount_t n; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: offset = 0; for (n = 0; n != xfer->nframes; n++) { if (usb_fifo_get_data(f, xfer->frbuffers, offset, xfer->max_frame_size, &actlen, 1)) { usbd_xfer_set_frame_len(xfer, n, actlen); offset += actlen; } else { break; } } for (; n != xfer->nframes; n++) { /* fill in zero frames */ usbd_xfer_set_frame_len(xfer, n, 0); } usbd_transfer_submit(xfer); break; default: /* Error */ if (xfer->error == USB_ERR_CANCELLED) { break; } goto tr_setup; } } static int ugen_set_config(struct usb_fifo *f, uint8_t index) { DPRINTFN(2, "index %u\n", index); if (f->udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ return (ENOTTY); } /* make sure all FIFO's are gone */ /* else there can be a deadlock */ if (ugen_fs_uninit(f)) { /* ignore any errors */ DPRINTFN(6, "no FIFOs\n"); } if (usbd_start_set_config(f->udev, index) != 0) return (EIO); return (0); } static int ugen_set_interface(struct usb_fifo *f, uint8_t iface_index, uint8_t alt_index) { DPRINTFN(2, "%u, %u\n", iface_index, alt_index); if (f->udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ return (ENOTTY); } /* make sure all FIFO's are gone */ /* else there can be a deadlock */ if (ugen_fs_uninit(f)) { /* ignore any errors */ DPRINTFN(6, "no FIFOs\n"); } /* change setting - will free generic FIFOs, if any */ if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) { return (EIO); } /* probe and attach */ if (usb_probe_and_attach(f->udev, iface_index)) { return (EIO); } return (0); } /*------------------------------------------------------------------------* * ugen_get_cdesc * * This function will retrieve the complete configuration descriptor * at the given index. *------------------------------------------------------------------------*/ static int ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) { struct usb_config_descriptor *cdesc; struct usb_device *udev = f->udev; int error; uint16_t len; uint8_t free_data; DPRINTFN(6, "\n"); if (ugd->ugd_data == NULL) { /* userland pointer should not be zero */ return (EINVAL); } if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) || (ugd->ugd_config_index == udev->curr_config_index)) { cdesc = usbd_get_config_descriptor(udev); if (cdesc == NULL) return (ENXIO); free_data = 0; } else { #if (USB_HAVE_FIXED_CONFIG == 0) if (usbd_req_get_config_desc_full(udev, NULL, &cdesc, ugd->ugd_config_index)) { return (ENXIO); } free_data = 1; #else /* configuration descriptor data is shared */ return (EINVAL); #endif } len = UGETW(cdesc->wTotalLength); if (len > ugd->ugd_maxlen) { len = ugd->ugd_maxlen; } DPRINTFN(6, "len=%u\n", len); ugd->ugd_actlen = len; ugd->ugd_offset = 0; error = copyout(cdesc, ugd->ugd_data, len); if (free_data) usbd_free_config_desc(udev, cdesc); return (error); } static int ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) { void *ptr; uint16_t size; int error; uint8_t do_unlock; /* Protect scratch area */ do_unlock = usbd_ctrl_lock(f->udev); ptr = f->udev->scratch.data; size = sizeof(f->udev->scratch.data); if (usbd_req_get_string_desc(f->udev, NULL, ptr, size, ugd->ugd_lang_id, ugd->ugd_string_index)) { error = EINVAL; } else { if (size > ((uint8_t *)ptr)[0]) { size = ((uint8_t *)ptr)[0]; } if (size > ugd->ugd_maxlen) { size = ugd->ugd_maxlen; } ugd->ugd_actlen = size; ugd->ugd_offset = 0; error = copyout(ptr, ugd->ugd_data, size); } if (do_unlock) usbd_ctrl_unlock(f->udev); return (error); } /*------------------------------------------------------------------------* * ugen_get_iface_driver * * This function generates an USB interface description for userland. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd) { struct usb_device *udev = f->udev; struct usb_interface *iface; const char *ptr; const char *desc; unsigned int len; unsigned int maxlen; char buf[128]; int error; DPRINTFN(6, "\n"); if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) { /* userland pointer should not be zero */ return (EINVAL); } iface = usbd_get_iface(udev, ugd->ugd_iface_index); if ((iface == NULL) || (iface->idesc == NULL)) { /* invalid interface index */ return (EINVAL); } /* read out device nameunit string, if any */ if ((iface->subdev != NULL) && device_is_attached(iface->subdev) && (ptr = device_get_nameunit(iface->subdev)) && (desc = device_get_desc(iface->subdev))) { /* print description */ snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc); /* range checks */ maxlen = ugd->ugd_maxlen - 1; len = strlen(buf); if (len > maxlen) len = maxlen; /* update actual length, including terminating zero */ ugd->ugd_actlen = len + 1; /* copy out interface description */ error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen); } else { /* zero length string is default */ error = copyout("", ugd->ugd_data, 1); } return (error); } /*------------------------------------------------------------------------* * usb_gen_fill_deviceinfo * * This function dumps information about an USB device to the * structure pointed to by the "di" argument. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int usb_gen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di) { struct usb_device *udev; struct usb_device *hub; udev = f->udev; bzero(di, sizeof(di[0])); di->udi_bus = device_get_unit(udev->bus->bdev); di->udi_addr = udev->address; di->udi_index = udev->device_index; strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial)); strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor)); strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product)); usb_printbcd(di->udi_release, sizeof(di->udi_release), UGETW(udev->ddesc.bcdDevice)); di->udi_vendorNo = UGETW(udev->ddesc.idVendor); di->udi_productNo = UGETW(udev->ddesc.idProduct); di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice); di->udi_class = udev->ddesc.bDeviceClass; di->udi_subclass = udev->ddesc.bDeviceSubClass; di->udi_protocol = udev->ddesc.bDeviceProtocol; di->udi_config_no = udev->curr_config_no; di->udi_config_index = udev->curr_config_index; di->udi_power = udev->flags.self_powered ? 0 : udev->power; di->udi_speed = udev->speed; di->udi_mode = udev->flags.usb_mode; di->udi_power_mode = udev->power_mode; di->udi_suspended = udev->flags.peer_suspended; hub = udev->parent_hub; if (hub) { di->udi_hubaddr = hub->address; di->udi_hubindex = hub->device_index; di->udi_hubport = udev->port_no; } return (0); } /*------------------------------------------------------------------------* * ugen_check_request * * Return values: * 0: Access allowed * Else: No access *------------------------------------------------------------------------*/ static int ugen_check_request(struct usb_device *udev, struct usb_device_request *req) { struct usb_endpoint *ep; int error; /* * Avoid requests that would damage the bus integrity: */ if (((req->bmRequestType == UT_WRITE_DEVICE) && (req->bRequest == UR_SET_ADDRESS)) || ((req->bmRequestType == UT_WRITE_DEVICE) && (req->bRequest == UR_SET_CONFIG)) || ((req->bmRequestType == UT_WRITE_INTERFACE) && (req->bRequest == UR_SET_INTERFACE))) { /* * These requests can be useful for testing USB drivers. */ error = priv_check(curthread, PRIV_DRIVER); if (error) { return (error); } } /* * Special case - handle clearing of stall */ if (req->bmRequestType == UT_WRITE_ENDPOINT) { ep = usbd_get_ep_by_addr(udev, req->wIndex[0]); if (ep == NULL) { return (EINVAL); } if ((req->bRequest == UR_CLEAR_FEATURE) && (UGETW(req->wValue) == UF_ENDPOINT_HALT)) { usbd_clear_data_toggle(udev, ep); } } /* TODO: add more checks to verify the interface index */ return (0); } int ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur) { int error; uint16_t len; uint16_t actlen; if (ugen_check_request(f->udev, &ur->ucr_request)) { return (EPERM); } len = UGETW(ur->ucr_request.wLength); /* check if "ucr_data" is valid */ if (len != 0) { if (ur->ucr_data == NULL) { return (EFAULT); } } /* do the USB request */ error = usbd_do_request_flags (f->udev, NULL, &ur->ucr_request, ur->ucr_data, (ur->ucr_flags & USB_SHORT_XFER_OK) | USB_USER_DATA_PTR, &actlen, USB_DEFAULT_TIMEOUT); ur->ucr_actlen = actlen; if (error) { error = EIO; } return (error); } /*------------------------------------------------------------------------ * ugen_re_enumerate *------------------------------------------------------------------------*/ static int ugen_re_enumerate(struct usb_fifo *f) { struct usb_device *udev = f->udev; int error; /* * This request can be useful for testing USB drivers: */ error = priv_check(curthread, PRIV_DRIVER); if (error) { return (error); } if (udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ DPRINTFN(6, "device mode\n"); return (ENOTTY); } /* make sure all FIFO's are gone */ /* else there can be a deadlock */ if (ugen_fs_uninit(f)) { /* ignore any errors */ DPRINTFN(6, "no FIFOs\n"); } /* start re-enumeration of device */ usbd_start_re_enumerate(udev); return (0); } int ugen_fs_uninit(struct usb_fifo *f) { if (f->fs_xfer == NULL) { return (EINVAL); } usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max); free(f->fs_xfer, M_USB); f->fs_xfer = NULL; f->fs_ep_max = 0; f->fs_ep_ptr = NULL; f->flag_iscomplete = 0; usb_fifo_free_buffer(f); return (0); } static uint8_t ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex) { struct usb_mbuf *m; USB_IF_DEQUEUE(&f->used_q, m); if (m) { *pindex = *((uint8_t *)(m->cur_data_ptr)); USB_IF_ENQUEUE(&f->free_q, m); return (0); /* success */ } else { *pindex = 0; /* fix compiler warning */ f->flag_iscomplete = 0; } return (1); /* failure */ } static void ugen_fs_set_complete(struct usb_fifo *f, uint8_t index) { struct usb_mbuf *m; USB_IF_DEQUEUE(&f->free_q, m); if (m == NULL) { /* can happen during close */ DPRINTF("out of buffers\n"); return; } USB_MBUF_RESET(m); *((uint8_t *)(m->cur_data_ptr)) = index; USB_IF_ENQUEUE(&f->used_q, m); f->flag_iscomplete = 1; usb_fifo_wakeup(f); } static int ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index) { struct usb_device_request *req; struct usb_xfer *xfer; struct usb_fs_endpoint fs_ep; void *uaddr; /* userland pointer */ void *kaddr; usb_frlength_t offset; usb_frlength_t rem; usb_frcount_t n; uint32_t length; int error; uint8_t isread; if (ep_index >= f->fs_ep_max) { return (EINVAL); } xfer = f->fs_xfer[ep_index]; if (xfer == NULL) { return (EINVAL); } mtx_lock(f->priv_mtx); if (usbd_transfer_pending(xfer)) { mtx_unlock(f->priv_mtx); return (EBUSY); /* should not happen */ } mtx_unlock(f->priv_mtx); error = copyin(f->fs_ep_ptr + ep_index, &fs_ep, sizeof(fs_ep)); if (error) { return (error); } /* security checks */ if (fs_ep.nFrames > xfer->max_frame_count) { xfer->error = USB_ERR_INVAL; goto complete; } if (fs_ep.nFrames == 0) { xfer->error = USB_ERR_INVAL; goto complete; } error = copyin(fs_ep.ppBuffer, &uaddr, sizeof(uaddr)); if (error) { return (error); } /* reset first frame */ usbd_xfer_set_frame_offset(xfer, 0, 0); if (xfer->flags_int.control_xfr) { req = xfer->frbuffers[0].buffer; error = copyin(fs_ep.pLength, &length, sizeof(length)); if (error) { return (error); } if (length != sizeof(*req)) { xfer->error = USB_ERR_INVAL; goto complete; } if (length != 0) { error = copyin(uaddr, req, length); if (error) { return (error); } } if (ugen_check_request(f->udev, req)) { xfer->error = USB_ERR_INVAL; goto complete; } usbd_xfer_set_frame_len(xfer, 0, length); /* Host mode only ! */ if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { isread = 1; } else { isread = 0; } n = 1; offset = sizeof(*req); } else { /* Device and Host mode */ if (USB_GET_DATA_ISREAD(xfer)) { isread = 1; } else { isread = 0; } n = 0; offset = 0; } rem = usbd_xfer_max_len(xfer); xfer->nframes = fs_ep.nFrames; xfer->timeout = fs_ep.timeout; if (xfer->timeout > 65535) { xfer->timeout = 65535; } if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK) xfer->flags.short_xfer_ok = 1; else xfer->flags.short_xfer_ok = 0; if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK) xfer->flags.short_frames_ok = 1; else xfer->flags.short_frames_ok = 0; if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT) xfer->flags.force_short_xfer = 1; else xfer->flags.force_short_xfer = 0; if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL) usbd_xfer_set_stall(xfer); else xfer->flags.stall_pipe = 0; for (; n != xfer->nframes; n++) { error = copyin(fs_ep.pLength + n, &length, sizeof(length)); if (error) { break; } usbd_xfer_set_frame_len(xfer, n, length); if (length > rem) { xfer->error = USB_ERR_INVAL; goto complete; } rem -= length; if (!isread) { /* we need to know the source buffer */ error = copyin(fs_ep.ppBuffer + n, &uaddr, sizeof(uaddr)); if (error) { break; } if (xfer->flags_int.isochronous_xfr) { /* get kernel buffer address */ kaddr = xfer->frbuffers[0].buffer; kaddr = USB_ADD_BYTES(kaddr, offset); } else { /* set current frame offset */ usbd_xfer_set_frame_offset(xfer, offset, n); /* get kernel buffer address */ kaddr = xfer->frbuffers[n].buffer; } /* move data */ error = copyin(uaddr, kaddr, length); if (error) { break; } } offset += length; } return (error); complete: mtx_lock(f->priv_mtx); ugen_fs_set_complete(f, ep_index); mtx_unlock(f->priv_mtx); return (0); } static int ugen_fs_copy_out_cancelled(struct usb_fs_endpoint *fs_ep_uptr) { struct usb_fs_endpoint fs_ep; int error; error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); if (error) return (error); fs_ep.status = USB_ERR_CANCELLED; fs_ep.aFrames = 0; fs_ep.isoc_time_complete = 0; /* update "aFrames" */ error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, sizeof(fs_ep.aFrames)); if (error) goto done; /* update "isoc_time_complete" */ error = copyout(&fs_ep.isoc_time_complete, &fs_ep_uptr->isoc_time_complete, sizeof(fs_ep.isoc_time_complete)); if (error) goto done; /* update "status" */ error = copyout(&fs_ep.status, &fs_ep_uptr->status, sizeof(fs_ep.status)); done: return (error); } static int ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index) { struct usb_device_request *req; struct usb_xfer *xfer; struct usb_fs_endpoint fs_ep; struct usb_fs_endpoint *fs_ep_uptr; /* userland ptr */ void *uaddr; /* userland ptr */ void *kaddr; usb_frlength_t offset; usb_frlength_t rem; usb_frcount_t n; uint32_t length; uint32_t temp; int error; uint8_t isread; if (ep_index >= f->fs_ep_max) return (EINVAL); xfer = f->fs_xfer[ep_index]; if (xfer == NULL) return (EINVAL); mtx_lock(f->priv_mtx); if (!xfer->flags_int.transferring && !xfer->flags_int.started) { mtx_unlock(f->priv_mtx); DPRINTF("Returning fake cancel event\n"); return (ugen_fs_copy_out_cancelled(f->fs_ep_ptr + ep_index)); } else if (usbd_transfer_pending(xfer)) { mtx_unlock(f->priv_mtx); return (EBUSY); /* should not happen */ } mtx_unlock(f->priv_mtx); fs_ep_uptr = f->fs_ep_ptr + ep_index; error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); if (error) { return (error); } fs_ep.status = xfer->error; fs_ep.aFrames = xfer->aframes; fs_ep.isoc_time_complete = xfer->isoc_time_complete; if (xfer->error) { goto complete; } if (xfer->flags_int.control_xfr) { req = xfer->frbuffers[0].buffer; /* Host mode only ! */ if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { isread = 1; } else { isread = 0; } if (xfer->nframes == 0) n = 0; /* should never happen */ else n = 1; } else { /* Device and Host mode */ if (USB_GET_DATA_ISREAD(xfer)) { isread = 1; } else { isread = 0; } n = 0; } /* Update lengths and copy out data */ rem = usbd_xfer_max_len(xfer); offset = 0; for (; n != xfer->nframes; n++) { /* get initial length into "temp" */ error = copyin(fs_ep.pLength + n, &temp, sizeof(temp)); if (error) { return (error); } if (temp > rem) { /* the userland length has been corrupted */ DPRINTF("corrupt userland length " "%u > %u\n", temp, rem); fs_ep.status = USB_ERR_INVAL; goto complete; } rem -= temp; /* get actual transfer length */ length = xfer->frlengths[n]; if (length > temp) { /* data overflow */ fs_ep.status = USB_ERR_INVAL; DPRINTF("data overflow %u > %u\n", length, temp); goto complete; } if (isread) { /* we need to know the destination buffer */ error = copyin(fs_ep.ppBuffer + n, &uaddr, sizeof(uaddr)); if (error) { return (error); } if (xfer->flags_int.isochronous_xfr) { /* only one frame buffer */ kaddr = USB_ADD_BYTES( xfer->frbuffers[0].buffer, offset); } else { /* multiple frame buffers */ kaddr = xfer->frbuffers[n].buffer; } /* move data */ error = copyout(kaddr, uaddr, length); if (error) { return (error); } } /* * Update offset according to initial length, which is * needed by isochronous transfers! */ offset += temp; /* update length */ error = copyout(&length, fs_ep.pLength + n, sizeof(length)); if (error) { return (error); } } complete: /* update "aFrames" */ error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, sizeof(fs_ep.aFrames)); if (error) goto done; /* update "isoc_time_complete" */ error = copyout(&fs_ep.isoc_time_complete, &fs_ep_uptr->isoc_time_complete, sizeof(fs_ep.isoc_time_complete)); if (error) goto done; /* update "status" */ error = copyout(&fs_ep.status, &fs_ep_uptr->status, sizeof(fs_ep.status)); done: return (error); } static uint8_t ugen_fifo_in_use(struct usb_fifo *f, int fflags) { struct usb_fifo *f_rx; struct usb_fifo *f_tx; f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; if ((fflags & FREAD) && f_rx && (f_rx->xfer[0] || f_rx->xfer[1])) { return (1); /* RX FIFO in use */ } if ((fflags & FWRITE) && f_tx && (f_tx->xfer[0] || f_tx->xfer[1])) { return (1); /* TX FIFO in use */ } return (0); /* not in use */ } static int ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) { struct usb_config usb_config[1]; struct usb_device_request req; union { struct usb_fs_complete *pcomp; struct usb_fs_start *pstart; struct usb_fs_stop *pstop; struct usb_fs_open *popen; struct usb_fs_open_stream *popen_stream; struct usb_fs_close *pclose; struct usb_fs_clear_stall_sync *pstall; void *addr; } u; struct usb_endpoint *ep; struct usb_endpoint_descriptor *ed; struct usb_xfer *xfer; int error = 0; uint8_t iface_index; uint8_t isread; uint8_t ep_index; uint8_t pre_scale; u.addr = addr; DPRINTFN(6, "cmd=0x%08lx\n", cmd); switch (cmd) { case USB_FS_COMPLETE: mtx_lock(f->priv_mtx); error = ugen_fs_get_complete(f, &ep_index); mtx_unlock(f->priv_mtx); if (error) { error = EBUSY; break; } u.pcomp->ep_index = ep_index; error = ugen_fs_copy_out(f, u.pcomp->ep_index); break; case USB_FS_START: error = ugen_fs_copy_in(f, u.pstart->ep_index); if (error) break; mtx_lock(f->priv_mtx); xfer = f->fs_xfer[u.pstart->ep_index]; usbd_transfer_start(xfer); mtx_unlock(f->priv_mtx); break; case USB_FS_STOP: if (u.pstop->ep_index >= f->fs_ep_max) { error = EINVAL; break; } mtx_lock(f->priv_mtx); xfer = f->fs_xfer[u.pstart->ep_index]; if (usbd_transfer_pending(xfer)) { usbd_transfer_stop(xfer); /* * Check if the USB transfer was stopped * before it was even started and fake a * cancel event. */ if (!xfer->flags_int.transferring && !xfer->flags_int.started) { DPRINTF("Issuing fake completion event\n"); ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); } } mtx_unlock(f->priv_mtx); break; case USB_FS_OPEN: case USB_FS_OPEN_STREAM: if (u.popen->ep_index >= f->fs_ep_max) { error = EINVAL; break; } if (f->fs_xfer[u.popen->ep_index] != NULL) { error = EBUSY; break; } if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) { u.popen->max_bufsize = USB_FS_MAX_BUFSIZE; } if (u.popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) { pre_scale = 1; u.popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE; } else { pre_scale = 0; } if (u.popen->max_frames > USB_FS_MAX_FRAMES) { u.popen->max_frames = USB_FS_MAX_FRAMES; break; } if (u.popen->max_frames == 0) { error = EINVAL; break; } ep = usbd_get_ep_by_addr(f->udev, u.popen->ep_no); if (ep == NULL) { error = EINVAL; break; } ed = ep->edesc; if (ed == NULL) { error = ENXIO; break; } iface_index = ep->iface_index; memset(usb_config, 0, sizeof(usb_config)); usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); usb_config[0].interval = USB_DEFAULT_INTERVAL; usb_config[0].flags.proxy_buffer = 1; if (pre_scale != 0) usb_config[0].flags.pre_scale_frames = 1; usb_config[0].callback = &ugen_ctrl_fs_callback; usb_config[0].timeout = 0; /* no timeout */ usb_config[0].frames = u.popen->max_frames; usb_config[0].bufsize = u.popen->max_bufsize; usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ if (cmd == USB_FS_OPEN_STREAM) usb_config[0].stream_id = u.popen_stream->stream_id; if (usb_config[0].type == UE_CONTROL) { if (f->udev->flags.usb_mode != USB_MODE_HOST) { error = EINVAL; break; } } else { isread = ((usb_config[0].endpoint & (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN); if (f->udev->flags.usb_mode != USB_MODE_HOST) { isread = !isread; } /* check permissions */ if (isread) { if (!(fflags & FREAD)) { error = EPERM; break; } } else { if (!(fflags & FWRITE)) { error = EPERM; break; } } } error = usbd_transfer_setup(f->udev, &iface_index, f->fs_xfer + u.popen->ep_index, usb_config, 1, f, f->priv_mtx); if (error == 0) { /* update maximums */ u.popen->max_packet_length = f->fs_xfer[u.popen->ep_index]->max_frame_size; u.popen->max_bufsize = f->fs_xfer[u.popen->ep_index]->max_data_length; /* update number of frames */ u.popen->max_frames = f->fs_xfer[u.popen->ep_index]->nframes; /* store index of endpoint */ f->fs_xfer[u.popen->ep_index]->priv_fifo = ((uint8_t *)0) + u.popen->ep_index; } else { error = ENOMEM; } break; case USB_FS_CLOSE: if (u.pclose->ep_index >= f->fs_ep_max) { error = EINVAL; break; } if (f->fs_xfer[u.pclose->ep_index] == NULL) { error = EINVAL; break; } usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1); break; case USB_FS_CLEAR_STALL_SYNC: if (u.pstall->ep_index >= f->fs_ep_max) { error = EINVAL; break; } if (f->fs_xfer[u.pstall->ep_index] == NULL) { error = EINVAL; break; } if (f->udev->flags.usb_mode != USB_MODE_HOST) { error = EINVAL; break; } mtx_lock(f->priv_mtx); error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]); mtx_unlock(f->priv_mtx); if (error) { return (EBUSY); } ep = f->fs_xfer[u.pstall->ep_index]->endpoint; /* setup a clear-stall packet */ req.bmRequestType = UT_WRITE_ENDPOINT; req.bRequest = UR_CLEAR_FEATURE; USETW(req.wValue, UF_ENDPOINT_HALT); req.wIndex[0] = ep->edesc->bEndpointAddress; req.wIndex[1] = 0; USETW(req.wLength, 0); error = usbd_do_request(f->udev, NULL, &req, NULL); if (error == 0) { usbd_clear_data_toggle(f->udev, ep); } else { error = ENXIO; } break; default: error = ENOIOCTL; break; } DPRINTFN(6, "error=%d\n", error); return (error); } static int ugen_set_short_xfer(struct usb_fifo *f, void *addr) { uint8_t t; if (*(int *)addr) t = 1; else t = 0; if (f->flag_short == t) { /* same value like before - accept */ return (0); } if (f->xfer[0] || f->xfer[1]) { /* cannot change this during transfer */ return (EBUSY); } f->flag_short = t; return (0); } static int ugen_set_timeout(struct usb_fifo *f, void *addr) { f->timeout = *(int *)addr; if (f->timeout > 65535) { /* limit user input */ f->timeout = 65535; } return (0); } static int ugen_get_frame_size(struct usb_fifo *f, void *addr) { if (f->xfer[0]) { *(int *)addr = f->xfer[0]->max_frame_size; } else { return (EINVAL); } return (0); } static int ugen_set_buffer_size(struct usb_fifo *f, void *addr) { usb_frlength_t t; if (*(int *)addr < 0) t = 0; /* use "wMaxPacketSize" */ else if (*(int *)addr < (256 * 1024)) t = *(int *)addr; else t = 256 * 1024; if (f->bufsize == t) { /* same value like before - accept */ return (0); } if (f->xfer[0] || f->xfer[1]) { /* cannot change this during transfer */ return (EBUSY); } f->bufsize = t; return (0); } static int ugen_get_buffer_size(struct usb_fifo *f, void *addr) { *(int *)addr = f->bufsize; return (0); } static int ugen_get_iface_desc(struct usb_fifo *f, struct usb_interface_descriptor *idesc) { struct usb_interface *iface; iface = usbd_get_iface(f->udev, f->iface_index); if (iface && iface->idesc) { *idesc = *(iface->idesc); } else { return (EIO); } return (0); } static int ugen_get_endpoint_desc(struct usb_fifo *f, struct usb_endpoint_descriptor *ed) { struct usb_endpoint *ep; ep = usb_fifo_softc(f); if (ep && ep->edesc) { *ed = *ep->edesc; } else { return (EINVAL); } return (0); } static int ugen_set_power_mode(struct usb_fifo *f, int mode) { struct usb_device *udev = f->udev; int err; uint8_t old_mode; if ((udev == NULL) || (udev->parent_hub == NULL)) { return (EINVAL); } err = priv_check(curthread, PRIV_DRIVER); if (err) return (err); /* get old power mode */ old_mode = udev->power_mode; /* if no change, then just return */ if (old_mode == mode) return (0); switch (mode) { case USB_POWER_MODE_OFF: if (udev->flags.usb_mode == USB_MODE_HOST && udev->re_enumerate_wait == USB_RE_ENUM_DONE) { udev->re_enumerate_wait = USB_RE_ENUM_PWR_OFF; } /* set power mode will wake up the explore thread */ break; case USB_POWER_MODE_ON: case USB_POWER_MODE_SAVE: break; case USB_POWER_MODE_RESUME: #if USB_HAVE_POWERD /* let USB-powerd handle resume */ USB_BUS_LOCK(udev->bus); udev->pwr_save.write_refs++; udev->pwr_save.last_xfer_time = ticks; USB_BUS_UNLOCK(udev->bus); /* set new power mode */ usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); /* wait for resume to complete */ usb_pause_mtx(NULL, hz / 4); /* clear write reference */ USB_BUS_LOCK(udev->bus); udev->pwr_save.write_refs--; USB_BUS_UNLOCK(udev->bus); #endif mode = USB_POWER_MODE_SAVE; break; case USB_POWER_MODE_SUSPEND: #if USB_HAVE_POWERD /* let USB-powerd handle suspend */ USB_BUS_LOCK(udev->bus); udev->pwr_save.last_xfer_time = ticks - (256 * hz); USB_BUS_UNLOCK(udev->bus); #endif mode = USB_POWER_MODE_SAVE; break; default: return (EINVAL); } if (err) return (ENXIO); /* I/O failure */ /* if we are powered off we need to re-enumerate first */ if (old_mode == USB_POWER_MODE_OFF) { if (udev->flags.usb_mode == USB_MODE_HOST && udev->re_enumerate_wait == USB_RE_ENUM_DONE) { udev->re_enumerate_wait = USB_RE_ENUM_START; } /* set power mode will wake up the explore thread */ } /* set new power mode */ usbd_set_power_mode(udev, mode); return (0); /* success */ } static int ugen_get_power_mode(struct usb_fifo *f) { struct usb_device *udev = f->udev; if (udev == NULL) return (USB_POWER_MODE_ON); return (udev->power_mode); } static int ugen_get_port_path(struct usb_fifo *f, struct usb_device_port_path *dpp) { struct usb_device *udev = f->udev; struct usb_device *next; unsigned int nlevel = 0; if (udev == NULL) goto error; dpp->udp_bus = device_get_unit(udev->bus->bdev); dpp->udp_index = udev->device_index; /* count port levels */ next = udev; while (next->parent_hub != NULL) { nlevel++; next = next->parent_hub; } /* check if too many levels */ if (nlevel > USB_DEVICE_PORT_PATH_MAX) goto error; /* store total level of ports */ dpp->udp_port_level = nlevel; /* store port index array */ next = udev; while (next->parent_hub != NULL) { dpp->udp_port_no[--nlevel] = next->port_no; next = next->parent_hub; } return (0); /* success */ error: return (EINVAL); /* failure */ } static int ugen_get_power_usage(struct usb_fifo *f) { struct usb_device *udev = f->udev; if (udev == NULL) return (0); return (udev->power); } static int ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no, uint8_t set, uint16_t feature) { struct usb_device *udev = f->udev; struct usb_hub *hub; int err; err = priv_check(curthread, PRIV_DRIVER); if (err) { return (err); } if (port_no == 0) { return (EINVAL); } if ((udev == NULL) || (udev->hub == NULL)) { return (EINVAL); } hub = udev->hub; if (port_no > hub->nports) { return (EINVAL); } if (set) err = usbd_req_set_port_feature(udev, NULL, port_no, feature); else err = usbd_req_clear_port_feature(udev, NULL, port_no, feature); if (err) return (ENXIO); /* failure */ return (0); /* success */ } static int ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) { struct usb_fifo *f_rx; struct usb_fifo *f_tx; int error = 0; f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; switch (cmd) { case USB_SET_RX_SHORT_XFER: if (fflags & FREAD) { error = ugen_set_short_xfer(f_rx, addr); } else { error = EINVAL; } break; case USB_SET_TX_FORCE_SHORT: if (fflags & FWRITE) { error = ugen_set_short_xfer(f_tx, addr); } else { error = EINVAL; } break; case USB_SET_RX_TIMEOUT: if (fflags & FREAD) { error = ugen_set_timeout(f_rx, addr); } else { error = EINVAL; } break; case USB_SET_TX_TIMEOUT: if (fflags & FWRITE) { error = ugen_set_timeout(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_FRAME_SIZE: if (fflags & FREAD) { error = ugen_get_frame_size(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_FRAME_SIZE: if (fflags & FWRITE) { error = ugen_get_frame_size(f_tx, addr); } else { error = EINVAL; } break; case USB_SET_RX_BUFFER_SIZE: if (fflags & FREAD) { error = ugen_set_buffer_size(f_rx, addr); } else { error = EINVAL; } break; case USB_SET_TX_BUFFER_SIZE: if (fflags & FWRITE) { error = ugen_set_buffer_size(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_BUFFER_SIZE: if (fflags & FREAD) { error = ugen_get_buffer_size(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_BUFFER_SIZE: if (fflags & FWRITE) { error = ugen_get_buffer_size(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_INTERFACE_DESC: if (fflags & FREAD) { error = ugen_get_iface_desc(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_INTERFACE_DESC: if (fflags & FWRITE) { error = ugen_get_iface_desc(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_ENDPOINT_DESC: if (fflags & FREAD) { error = ugen_get_endpoint_desc(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_ENDPOINT_DESC: if (fflags & FWRITE) { error = ugen_get_endpoint_desc(f_tx, addr); } else { error = EINVAL; } break; case USB_SET_RX_STALL_FLAG: if ((fflags & FREAD) && (*(int *)addr)) { f_rx->flag_stall = 1; } break; case USB_SET_TX_STALL_FLAG: if ((fflags & FWRITE) && (*(int *)addr)) { f_tx->flag_stall = 1; } break; default: error = ENOIOCTL; break; } return (error); } static int ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags) { union { struct usb_interface_descriptor *idesc; struct usb_alt_interface *ai; struct usb_device_descriptor *ddesc; struct usb_config_descriptor *cdesc; struct usb_device_stats *stat; struct usb_fs_init *pinit; struct usb_fs_uninit *puninit; struct usb_device_port_path *dpp; uint32_t *ptime; void *addr; int *pint; } u; struct usb_device_descriptor *dtemp; struct usb_config_descriptor *ctemp; struct usb_interface *iface; int error = 0; uint8_t n; u.addr = addr; DPRINTFN(6, "cmd=0x%08lx\n", cmd); switch (cmd) { case USB_DISCOVER: usb_needs_explore_all(); break; case USB_SETDEBUG: if (!(fflags & FWRITE)) { error = EPERM; break; } usb_debug = *(int *)addr; break; case USB_GET_CONFIG: *(int *)addr = f->udev->curr_config_index; break; case USB_SET_CONFIG: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_set_config(f, *(int *)addr); break; case USB_GET_ALTINTERFACE: iface = usbd_get_iface(f->udev, u.ai->uai_interface_index); if (iface && iface->idesc) { u.ai->uai_alt_index = iface->alt_index; } else { error = EINVAL; } break; case USB_SET_ALTINTERFACE: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_set_interface(f, u.ai->uai_interface_index, u.ai->uai_alt_index); break; case USB_GET_DEVICE_DESC: dtemp = usbd_get_device_descriptor(f->udev); if (!dtemp) { error = EIO; break; } *u.ddesc = *dtemp; break; case USB_GET_CONFIG_DESC: ctemp = usbd_get_config_descriptor(f->udev); if (!ctemp) { error = EIO; break; } *u.cdesc = *ctemp; break; case USB_GET_FULL_DESC: error = ugen_get_cdesc(f, addr); break; case USB_GET_STRING_DESC: error = ugen_get_sdesc(f, addr); break; case USB_GET_IFACE_DRIVER: error = ugen_get_iface_driver(f, addr); break; case USB_REQUEST: case USB_DO_REQUEST: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_do_request(f, addr); break; case USB_DEVICEINFO: case USB_GET_DEVICEINFO: error = usb_gen_fill_deviceinfo(f, addr); break; case USB_DEVICESTATS: for (n = 0; n != 4; n++) { u.stat->uds_requests_fail[n] = f->udev->stats_err.uds_requests[n]; u.stat->uds_requests_ok[n] = f->udev->stats_ok.uds_requests[n]; } break; case USB_DEVICEENUMERATE: error = ugen_re_enumerate(f); break; case USB_GET_PLUGTIME: *u.ptime = f->udev->plugtime; break; case USB_CLAIM_INTERFACE: case USB_RELEASE_INTERFACE: /* TODO */ break; case USB_IFACE_DRIVER_ACTIVE: n = *u.pint & 0xFF; iface = usbd_get_iface(f->udev, n); if (iface && iface->subdev) error = 0; else error = ENXIO; break; case USB_IFACE_DRIVER_DETACH: error = priv_check(curthread, PRIV_DRIVER); if (error) break; n = *u.pint & 0xFF; if (n == USB_IFACE_INDEX_ANY) { error = EINVAL; break; } /* * Detach the currently attached driver. */ usb_detach_device(f->udev, n, 0); /* * Set parent to self, this should keep attach away * until the next set configuration event. */ usbd_set_parent_iface(f->udev, n, n); break; case USB_SET_POWER_MODE: error = ugen_set_power_mode(f, *u.pint); break; case USB_GET_POWER_MODE: *u.pint = ugen_get_power_mode(f); break; case USB_GET_DEV_PORT_PATH: error = ugen_get_port_path(f, u.dpp); break; case USB_GET_POWER_USAGE: *u.pint = ugen_get_power_usage(f); break; case USB_SET_PORT_ENABLE: error = ugen_do_port_feature(f, *u.pint, 1, UHF_PORT_ENABLE); break; case USB_SET_PORT_DISABLE: error = ugen_do_port_feature(f, *u.pint, 0, UHF_PORT_ENABLE); break; case USB_FS_INIT: /* verify input parameters */ if (u.pinit->pEndpoints == NULL) { error = EINVAL; break; } if (u.pinit->ep_index_max > 127) { error = EINVAL; break; } if (u.pinit->ep_index_max == 0) { error = EINVAL; break; } if (f->fs_xfer != NULL) { error = EBUSY; break; } if (f->dev_ep_index != 0) { error = EINVAL; break; } if (ugen_fifo_in_use(f, fflags)) { error = EBUSY; break; } error = usb_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max); if (error) { break; } f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) * u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO); if (f->fs_xfer == NULL) { usb_fifo_free_buffer(f); error = ENOMEM; break; } f->fs_ep_max = u.pinit->ep_index_max; f->fs_ep_ptr = u.pinit->pEndpoints; break; case USB_FS_UNINIT: if (u.puninit->dummy != 0) { error = EINVAL; break; } error = ugen_fs_uninit(f); break; default: mtx_lock(f->priv_mtx); error = ugen_iface_ioctl(f, cmd, addr, fflags); mtx_unlock(f->priv_mtx); break; } DPRINTFN(6, "error=%d\n", error); return (error); } static void ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error) { ; /* workaround for a bug in "indent" */ DPRINTF("st=%u alen=%u aframes=%u\n", USB_GET_STATE(xfer), xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: usbd_transfer_submit(xfer); break; default: ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); break; } } #endif /* USB_HAVE_UGEN */ Index: head/sys/dev/usb/usb_hub.c =================================================================== --- head/sys/dev/usb/usb_hub.c (revision 357971) +++ head/sys/dev/usb/usb_hub.c (revision 357972) @@ -1,2929 +1,2930 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. All rights reserved. * Copyright (c) 2008-2010 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB spec: http://www.usb.org/developers/docs/usbspec.zip */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR uhub_debug #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #ifdef USB_DEBUG static int uhub_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB HUB"); SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RWTUN, &uhub_debug, 0, "Debug level"); #endif #if USB_HAVE_POWERD static int usb_power_timeout = 30; /* seconds */ SYSCTL_INT(_hw_usb, OID_AUTO, power_timeout, CTLFLAG_RWTUN, &usb_power_timeout, 0, "USB power timeout"); #endif #if USB_HAVE_DISABLE_ENUM static int usb_disable_enumeration = 0; SYSCTL_INT(_hw_usb, OID_AUTO, disable_enumeration, CTLFLAG_RWTUN, &usb_disable_enumeration, 0, "Set to disable all USB device enumeration. " "This can secure against USB devices turning evil, " "for example a USB memory stick becoming a USB keyboard."); static int usb_disable_port_power = 0; SYSCTL_INT(_hw_usb, OID_AUTO, disable_port_power, CTLFLAG_RWTUN, &usb_disable_port_power, 0, "Set to disable all USB port power."); #endif #define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol) #define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) #define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) #define UHUB_IS_MULTI_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBMTT) #define UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB) /* prototypes for type checking: */ static device_suspend_t uhub_suspend; static device_resume_t uhub_resume; static bus_driver_added_t uhub_driver_added; static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string; static usb_callback_t uhub_intr_callback; #if USB_HAVE_TT_SUPPORT static usb_callback_t uhub_reset_tt_callback; #endif static void usb_dev_resume_peer(struct usb_device *udev); static void usb_dev_suspend_peer(struct usb_device *udev); static uint8_t usb_peer_should_wakeup(struct usb_device *udev); static const struct usb_config uhub_config[UHUB_N_TRANSFER] = { [UHUB_INTR_TRANSFER] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_ANY, .timeout = 0, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &uhub_intr_callback, .interval = UHUB_INTR_INTERVAL, }, #if USB_HAVE_TT_SUPPORT [UHUB_RESET_TT_TRANSFER] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &uhub_reset_tt_callback, .timeout = 1000, /* 1 second */ .usb_mode = USB_MODE_HOST, }, #endif }; /* * driver instance for "hub" connected to "usb" * and "hub" connected to "hub" */ static devclass_t uhub_devclass; static device_method_t uhub_methods[] = { DEVMETHOD(device_probe, uhub_probe), DEVMETHOD(device_attach, uhub_attach), DEVMETHOD(device_detach, uhub_detach), DEVMETHOD(device_suspend, uhub_suspend), DEVMETHOD(device_resume, uhub_resume), DEVMETHOD(bus_child_location_str, uhub_child_location_string), DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string), DEVMETHOD(bus_driver_added, uhub_driver_added), DEVMETHOD_END }; driver_t uhub_driver = { .name = "uhub", .methods = uhub_methods, .size = sizeof(struct uhub_softc) }; DRIVER_MODULE(uhub, usbus, uhub_driver, uhub_devclass, 0, 0); DRIVER_MODULE(uhub, uhub, uhub_driver, uhub_devclass, NULL, 0); MODULE_VERSION(uhub, 1); static void uhub_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhub_softc *sc = usbd_xfer_softc(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(2, "\n"); /* * This is an indication that some port * has changed status. Notify the bus * event handler thread that we need * to be explored again: */ usb_needs_explore(sc->sc_udev->bus, 0); case USB_ST_SETUP: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (xfer->error != USB_ERR_CANCELLED) { /* * Do a clear-stall. The "stall_pipe" flag * will get cleared before next callback by * the USB stack. */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); } break; } } /*------------------------------------------------------------------------* * uhub_reset_tt_proc * * This function starts the TT reset USB request *------------------------------------------------------------------------*/ #if USB_HAVE_TT_SUPPORT static void uhub_reset_tt_proc(struct usb_proc_msg *_pm) { struct usb_udev_msg *pm = (void *)_pm; struct usb_device *udev = pm->udev; struct usb_hub *hub; struct uhub_softc *sc; hub = udev->hub; if (hub == NULL) return; sc = hub->hubsoftc; if (sc == NULL) return; /* Change lock */ USB_BUS_UNLOCK(udev->bus); USB_MTX_LOCK(&sc->sc_mtx); /* Start transfer */ usbd_transfer_start(sc->sc_xfer[UHUB_RESET_TT_TRANSFER]); /* Change lock */ USB_MTX_UNLOCK(&sc->sc_mtx); USB_BUS_LOCK(udev->bus); } #endif /*------------------------------------------------------------------------* * uhub_tt_buffer_reset_async_locked * * This function queues a TT reset for the given USB device and endpoint. *------------------------------------------------------------------------*/ #if USB_HAVE_TT_SUPPORT void uhub_tt_buffer_reset_async_locked(struct usb_device *child, struct usb_endpoint *ep) { struct usb_device_request req; struct usb_device *udev; struct usb_hub *hub; struct usb_port *up; uint16_t wValue; uint8_t port; if (child == NULL || ep == NULL) return; udev = child->parent_hs_hub; port = child->hs_port_no; if (udev == NULL) return; hub = udev->hub; if ((hub == NULL) || (udev->speed != USB_SPEED_HIGH) || (child->speed != USB_SPEED_LOW && child->speed != USB_SPEED_FULL) || (child->flags.usb_mode != USB_MODE_HOST) || (port == 0) || (ep->edesc == NULL)) { /* not applicable */ return; } USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); up = hub->ports + port - 1; if (udev->ddesc.bDeviceClass == UDCLASS_HUB && udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT) port = 1; /* if we already received a clear buffer request, reset the whole TT */ if (up->req_reset_tt.bRequest != 0) { req.bmRequestType = UT_WRITE_CLASS_OTHER; req.bRequest = UR_RESET_TT; USETW(req.wValue, 0); req.wIndex[0] = port; req.wIndex[1] = 0; USETW(req.wLength, 0); } else { wValue = (ep->edesc->bEndpointAddress & 0xF) | ((child->address & 0x7F) << 4) | ((ep->edesc->bEndpointAddress & 0x80) << 8) | ((ep->edesc->bmAttributes & 3) << 12); req.bmRequestType = UT_WRITE_CLASS_OTHER; req.bRequest = UR_CLEAR_TT_BUFFER; USETW(req.wValue, wValue); req.wIndex[0] = port; req.wIndex[1] = 0; USETW(req.wLength, 0); } up->req_reset_tt = req; /* get reset transfer started */ usb_proc_msignal(USB_BUS_TT_PROC(udev->bus), &hub->tt_msg[0], &hub->tt_msg[1]); } #endif #if USB_HAVE_TT_SUPPORT static void uhub_reset_tt_callback(struct usb_xfer *xfer, usb_error_t error) { struct uhub_softc *sc; struct usb_device *udev; struct usb_port *up; uint8_t x; DPRINTF("TT buffer reset\n"); sc = usbd_xfer_softc(xfer); udev = sc->sc_udev; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: USB_BUS_LOCK(udev->bus); /* find first port which needs a TT reset */ for (x = 0; x != udev->hub->nports; x++) { up = udev->hub->ports + x; if (up->req_reset_tt.bRequest == 0) continue; /* copy in the transfer */ usbd_copy_in(xfer->frbuffers, 0, &up->req_reset_tt, sizeof(up->req_reset_tt)); /* reset buffer */ memset(&up->req_reset_tt, 0, sizeof(up->req_reset_tt)); /* set length */ usbd_xfer_set_frame_len(xfer, 0, sizeof(up->req_reset_tt)); xfer->nframes = 1; USB_BUS_UNLOCK(udev->bus); usbd_transfer_submit(xfer); return; } USB_BUS_UNLOCK(udev->bus); break; default: if (error == USB_ERR_CANCELLED) break; DPRINTF("TT buffer reset failed (%s)\n", usbd_errstr(error)); goto tr_setup; } } #endif /*------------------------------------------------------------------------* * uhub_count_active_host_ports * * This function counts the number of active ports at the given speed. *------------------------------------------------------------------------*/ uint8_t uhub_count_active_host_ports(struct usb_device *udev, enum usb_dev_speed speed) { struct uhub_softc *sc; struct usb_device *child; struct usb_hub *hub; struct usb_port *up; uint8_t retval = 0; uint8_t x; if (udev == NULL) goto done; hub = udev->hub; if (hub == NULL) goto done; sc = hub->hubsoftc; if (sc == NULL) goto done; for (x = 0; x != hub->nports; x++) { up = hub->ports + x; child = usb_bus_port_get_device(udev->bus, up); if (child != NULL && child->flags.usb_mode == USB_MODE_HOST && child->speed == speed) retval++; } done: return (retval); } void uhub_explore_handle_re_enumerate(struct usb_device *child) { uint8_t do_unlock; usb_error_t err; /* check if device should be re-enumerated */ if (child->flags.usb_mode != USB_MODE_HOST) return; do_unlock = usbd_enum_lock(child); switch (child->re_enumerate_wait) { case USB_RE_ENUM_START: err = usbd_set_config_index(child, USB_UNCONFIG_INDEX); if (err != 0) { DPRINTF("Unconfigure failed: %s: Ignored.\n", usbd_errstr(err)); } if (child->parent_hub == NULL) { /* the root HUB cannot be re-enumerated */ DPRINTFN(6, "cannot reset root HUB\n"); err = 0; } else { err = usbd_req_re_enumerate(child, NULL); } if (err == 0) err = usbd_set_config_index(child, 0); if (err == 0) { err = usb_probe_and_attach(child, USB_IFACE_INDEX_ANY); } child->re_enumerate_wait = USB_RE_ENUM_DONE; break; case USB_RE_ENUM_PWR_OFF: /* get the device unconfigured */ err = usbd_set_config_index(child, USB_UNCONFIG_INDEX); if (err) { DPRINTFN(0, "Could not unconfigure " "device (ignored)\n"); } if (child->parent_hub == NULL) { /* the root HUB cannot be re-enumerated */ DPRINTFN(6, "cannot set port feature\n"); err = 0; } else { /* clear port enable */ err = usbd_req_clear_port_feature(child->parent_hub, NULL, child->port_no, UHF_PORT_ENABLE); if (err) { DPRINTFN(0, "Could not disable port " "(ignored)\n"); } } child->re_enumerate_wait = USB_RE_ENUM_DONE; break; case USB_RE_ENUM_SET_CONFIG: err = usbd_set_config_index(child, child->next_config_index); if (err != 0) { DPRINTF("Configure failed: %s: Ignored.\n", usbd_errstr(err)); } else { err = usb_probe_and_attach(child, USB_IFACE_INDEX_ANY); } child->re_enumerate_wait = USB_RE_ENUM_DONE; break; default: child->re_enumerate_wait = USB_RE_ENUM_DONE; break; } if (do_unlock) usbd_enum_unlock(child); } /*------------------------------------------------------------------------* * uhub_explore_sub - subroutine * * Return values: * 0: Success * Else: A control transaction failed *------------------------------------------------------------------------*/ static usb_error_t uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up) { struct usb_bus *bus; struct usb_device *child; uint8_t refcount; usb_error_t err; bus = sc->sc_udev->bus; err = 0; /* get driver added refcount from USB bus */ refcount = bus->driver_added_refcount; /* get device assosiated with the given port */ child = usb_bus_port_get_device(bus, up); if (child == NULL) { /* nothing to do */ goto done; } uhub_explore_handle_re_enumerate(child); /* check if probe and attach should be done */ if (child->driver_added_refcount != refcount) { child->driver_added_refcount = refcount; err = usb_probe_and_attach(child, USB_IFACE_INDEX_ANY); if (err) { goto done; } } /* start control transfer, if device mode */ if (child->flags.usb_mode == USB_MODE_DEVICE) usbd_ctrl_transfer_setup(child); /* if a HUB becomes present, do a recursive HUB explore */ if (child->hub) err = (child->hub->explore) (child); done: return (err); } /*------------------------------------------------------------------------* * uhub_read_port_status - factored out code *------------------------------------------------------------------------*/ static usb_error_t uhub_read_port_status(struct uhub_softc *sc, uint8_t portno) { struct usb_port_status ps; usb_error_t err; if (sc->sc_usb_port_errors >= UHUB_USB_PORT_ERRORS_MAX) { DPRINTFN(4, "port %d, HUB looks dead, too many errors\n", portno); sc->sc_st.port_status = 0; sc->sc_st.port_change = 0; return (USB_ERR_TIMEOUT); } err = usbd_req_get_port_status( sc->sc_udev, NULL, &ps, portno); if (err == 0) { sc->sc_st.port_status = UGETW(ps.wPortStatus); sc->sc_st.port_change = UGETW(ps.wPortChange); sc->sc_usb_port_errors = 0; } else { sc->sc_st.port_status = 0; sc->sc_st.port_change = 0; sc->sc_usb_port_errors++; } /* debugging print */ DPRINTFN(4, "port %d, wPortStatus=0x%04x, " "wPortChange=0x%04x, err=%s\n", portno, sc->sc_st.port_status, sc->sc_st.port_change, usbd_errstr(err)); return (err); } /*------------------------------------------------------------------------* * uhub_reattach_port * * Returns: * 0: Success * Else: A control transaction failed *------------------------------------------------------------------------*/ static usb_error_t uhub_reattach_port(struct uhub_softc *sc, uint8_t portno) { struct usb_device *child; struct usb_device *udev; enum usb_dev_speed speed; enum usb_hc_mode mode; usb_error_t err; uint16_t power_mask; uint8_t timeout; DPRINTF("reattaching port %d\n", portno); timeout = 0; udev = sc->sc_udev; child = usb_bus_port_get_device(udev->bus, udev->hub->ports + portno - 1); repeat: /* first clear the port connection change bit */ err = usbd_req_clear_port_feature(udev, NULL, portno, UHF_C_PORT_CONNECTION); if (err) goto error; /* check if there is a child */ if (child != NULL) { /* * Free USB device and all subdevices, if any. */ usb_free_device(child, 0); child = NULL; } /* get fresh status */ err = uhub_read_port_status(sc, portno); if (err) goto error; #if USB_HAVE_DISABLE_ENUM /* check if we should skip enumeration from this USB HUB */ if (usb_disable_enumeration != 0 || sc->sc_disable_enumeration != 0) { DPRINTF("Enumeration is disabled!\n"); goto error; } #endif /* check if nothing is connected to the port */ if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS)) goto error; /* check if there is no power on the port and print a warning */ switch (udev->speed) { case USB_SPEED_HIGH: case USB_SPEED_FULL: case USB_SPEED_LOW: power_mask = UPS_PORT_POWER; break; case USB_SPEED_SUPER: if (udev->parent_hub == NULL) power_mask = UPS_PORT_POWER; else power_mask = UPS_PORT_POWER_SS; break; default: power_mask = 0; break; } if (!(sc->sc_st.port_status & power_mask)) { DPRINTF("WARNING: strange, connected port %d " "has no power\n", portno); } /* check if the device is in Host Mode */ if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) { DPRINTF("Port %d is in Host Mode\n", portno); if (sc->sc_st.port_status & UPS_SUSPEND) { /* * NOTE: Should not get here in SuperSpeed * mode, because the HUB should report this * bit as zero. */ DPRINTF("Port %d was still " "suspended, clearing.\n", portno); err = usbd_req_clear_port_feature(udev, NULL, portno, UHF_PORT_SUSPEND); } /* USB Host Mode */ /* wait for maximum device power up time */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_powerup_delay)); /* reset port, which implies enabling it */ err = usbd_req_reset_port(udev, NULL, portno); if (err) { DPRINTFN(0, "port %d reset " "failed, error=%s\n", portno, usbd_errstr(err)); goto error; } /* get port status again, it might have changed during reset */ err = uhub_read_port_status(sc, portno); if (err) { goto error; } /* check if something changed during port reset */ if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) || (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) { if (timeout) { DPRINTFN(0, "giving up port reset " "- device vanished\n"); goto error; } timeout = 1; goto repeat; } } else { DPRINTF("Port %d is in Device Mode\n", portno); } /* * Figure out the device speed */ switch (udev->speed) { case USB_SPEED_HIGH: if (sc->sc_st.port_status & UPS_HIGH_SPEED) speed = USB_SPEED_HIGH; else if (sc->sc_st.port_status & UPS_LOW_SPEED) speed = USB_SPEED_LOW; else speed = USB_SPEED_FULL; break; case USB_SPEED_FULL: if (sc->sc_st.port_status & UPS_LOW_SPEED) speed = USB_SPEED_LOW; else speed = USB_SPEED_FULL; break; case USB_SPEED_LOW: speed = USB_SPEED_LOW; break; case USB_SPEED_SUPER: if (udev->parent_hub == NULL) { /* Root HUB - special case */ switch (sc->sc_st.port_status & UPS_OTHER_SPEED) { case 0: speed = USB_SPEED_FULL; break; case UPS_LOW_SPEED: speed = USB_SPEED_LOW; break; case UPS_HIGH_SPEED: speed = USB_SPEED_HIGH; break; default: speed = USB_SPEED_SUPER; break; } } else { speed = USB_SPEED_SUPER; } break; default: /* same speed like parent */ speed = udev->speed; break; } if (speed == USB_SPEED_SUPER) { err = usbd_req_set_hub_u1_timeout(udev, NULL, portno, 128 - (2 * udev->depth)); if (err) { DPRINTFN(0, "port %d U1 timeout " "failed, error=%s\n", portno, usbd_errstr(err)); } err = usbd_req_set_hub_u2_timeout(udev, NULL, portno, 128 - (2 * udev->depth)); if (err) { DPRINTFN(0, "port %d U2 timeout " "failed, error=%s\n", portno, usbd_errstr(err)); } } /* * Figure out the device mode * * NOTE: This part is currently FreeBSD specific. */ if (udev->parent_hub != NULL) { /* inherit mode from the parent HUB */ mode = udev->parent_hub->flags.usb_mode; } else if (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE) mode = USB_MODE_DEVICE; else mode = USB_MODE_HOST; /* need to create a new child */ child = usb_alloc_device(sc->sc_dev, udev->bus, udev, udev->depth + 1, portno - 1, portno, speed, mode); if (child == NULL) { DPRINTFN(0, "could not allocate new device\n"); goto error; } return (0); /* success */ error: if (child != NULL) { /* * Free USB device and all subdevices, if any. */ usb_free_device(child, 0); child = NULL; } if (err == 0) { if (sc->sc_st.port_status & UPS_PORT_ENABLED) { err = usbd_req_clear_port_feature( sc->sc_udev, NULL, portno, UHF_PORT_ENABLE); } } if (err) { DPRINTFN(0, "device problem (%s), " "disabling port %d\n", usbd_errstr(err), portno); } return (err); } /*------------------------------------------------------------------------* * usb_device_20_compatible * * Returns: * 0: HUB does not support suspend and resume * Else: HUB supports suspend and resume *------------------------------------------------------------------------*/ static uint8_t usb_device_20_compatible(struct usb_device *udev) { if (udev == NULL) return (0); switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: return (1); default: return (0); } } /*------------------------------------------------------------------------* * uhub_suspend_resume_port * * Returns: * 0: Success * Else: A control transaction failed *------------------------------------------------------------------------*/ static usb_error_t uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) { struct usb_device *child; struct usb_device *udev; uint8_t is_suspend; usb_error_t err; DPRINTF("port %d\n", portno); udev = sc->sc_udev; child = usb_bus_port_get_device(udev->bus, udev->hub->ports + portno - 1); /* first clear the port suspend change bit */ if (usb_device_20_compatible(udev)) { err = usbd_req_clear_port_feature(udev, NULL, portno, UHF_C_PORT_SUSPEND); } else { err = usbd_req_clear_port_feature(udev, NULL, portno, UHF_C_PORT_LINK_STATE); } if (err) { DPRINTF("clearing suspend failed.\n"); goto done; } /* get fresh status */ err = uhub_read_port_status(sc, portno); if (err) { DPRINTF("reading port status failed.\n"); goto done; } /* convert current state */ if (usb_device_20_compatible(udev)) { if (sc->sc_st.port_status & UPS_SUSPEND) { is_suspend = 1; } else { is_suspend = 0; } } else { switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) { case UPS_PORT_LS_U3: is_suspend = 1; break; case UPS_PORT_LS_SS_INA: usbd_req_warm_reset_port(udev, NULL, portno); is_suspend = 0; break; default: is_suspend = 0; break; } } DPRINTF("suspended=%u\n", is_suspend); /* do the suspend or resume */ if (child) { /* * This code handle two cases: 1) Host Mode - we can only * receive resume here 2) Device Mode - we can receive * suspend and resume here */ if (is_suspend == 0) usb_dev_resume_peer(child); else if (child->flags.usb_mode == USB_MODE_DEVICE) usb_dev_suspend_peer(child); } done: return (err); } /*------------------------------------------------------------------------* * uhub_root_interrupt * * This function is called when a Root HUB interrupt has * happened. "ptr" and "len" makes up the Root HUB interrupt * packet. This function is called having the "bus_mtx" locked. *------------------------------------------------------------------------*/ void uhub_root_intr(struct usb_bus *bus, const uint8_t *ptr, uint8_t len) { USB_BUS_LOCK_ASSERT(bus, MA_OWNED); usb_needs_explore(bus, 0); } static uint8_t uhub_is_too_deep(struct usb_device *udev) { switch (udev->speed) { case USB_SPEED_FULL: case USB_SPEED_LOW: case USB_SPEED_HIGH: if (udev->depth > USB_HUB_MAX_DEPTH) return (1); break; case USB_SPEED_SUPER: if (udev->depth > USB_SS_HUB_DEPTH_MAX) return (1); break; default: break; } return (0); } /*------------------------------------------------------------------------* * uhub_explore * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static usb_error_t uhub_explore(struct usb_device *udev) { struct usb_hub *hub; struct uhub_softc *sc; struct usb_port *up; usb_error_t err; uint8_t portno; uint8_t x; uint8_t do_unlock; hub = udev->hub; sc = hub->hubsoftc; DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address); /* ignore devices that are too deep */ if (uhub_is_too_deep(udev)) return (USB_ERR_TOO_DEEP); /* check if device is suspended */ if (udev->flags.self_suspended) { /* need to wait until the child signals resume */ DPRINTF("Device is suspended!\n"); return (0); } /* * Make sure we don't race against user-space applications * like LibUSB: */ do_unlock = usbd_enum_lock(udev); for (x = 0; x != hub->nports; x++) { up = hub->ports + x; portno = x + 1; err = uhub_read_port_status(sc, portno); if (err) { /* most likely the HUB is gone */ break; } if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) { DPRINTF("Overcurrent on port %u.\n", portno); err = usbd_req_clear_port_feature( udev, NULL, portno, UHF_C_PORT_OVER_CURRENT); if (err) { /* most likely the HUB is gone */ break; } } if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) { /* * Fake a connect status change so that the * status gets checked initially! */ sc->sc_st.port_change |= UPS_C_CONNECT_STATUS; } if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) { err = usbd_req_clear_port_feature( udev, NULL, portno, UHF_C_PORT_ENABLE); if (err) { /* most likely the HUB is gone */ break; } if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { /* * Ignore the port error if the device * has vanished ! */ } else if (sc->sc_st.port_status & UPS_PORT_ENABLED) { DPRINTFN(0, "illegal enable change, " "port %d\n", portno); } else { if (up->restartcnt == USB_RESTART_MAX) { /* XXX could try another speed ? */ DPRINTFN(0, "port error, giving up " "port %d\n", portno); } else { sc->sc_st.port_change |= UPS_C_CONNECT_STATUS; up->restartcnt++; } } } if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { err = uhub_reattach_port(sc, portno); if (err) { /* most likely the HUB is gone */ break; } } if (sc->sc_st.port_change & (UPS_C_SUSPEND | UPS_C_PORT_LINK_STATE)) { err = uhub_suspend_resume_port(sc, portno); if (err) { /* most likely the HUB is gone */ break; } } err = uhub_explore_sub(sc, up); if (err) { /* no device(s) present */ continue; } /* explore succeeded - reset restart counter */ up->restartcnt = 0; } if (do_unlock) usbd_enum_unlock(udev); /* initial status checked */ sc->sc_flags |= UHUB_FLAG_DID_EXPLORE; /* return success */ return (USB_ERR_NORMAL_COMPLETION); } int uhub_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); /* * The subclass for USB HUBs is currently ignored because it * is 0 for some and 1 for others. */ if (uaa->info.bConfigIndex == 0 && uaa->info.bDeviceClass == UDCLASS_HUB) return (BUS_PROBE_DEFAULT); return (ENXIO); } /* NOTE: The information returned by this function can be wrong. */ usb_error_t uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt) { struct usb_hub_descriptor hubdesc20; struct usb_hub_ss_descriptor hubdesc30; usb_error_t err; uint8_t nports; uint8_t tt; if (udev->ddesc.bDeviceClass != UDCLASS_HUB) return (USB_ERR_INVAL); nports = 0; tt = 0; switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: /* assuming that there is one port */ err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1); if (err) { DPRINTFN(0, "getting USB 2.0 HUB descriptor failed," "error=%s\n", usbd_errstr(err)); break; } nports = hubdesc20.bNbrPorts; if (nports > 127) nports = 127; if (udev->speed == USB_SPEED_HIGH) tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3; break; case USB_SPEED_SUPER: err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); if (err) { DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed," "error=%s\n", usbd_errstr(err)); break; } nports = hubdesc30.bNbrPorts; if (nports > 16) nports = 16; break; default: err = USB_ERR_INVAL; break; } if (pnports != NULL) *pnports = nports; if (ptt != NULL) *ptt = tt; return (err); } int uhub_attach(device_t dev) { struct uhub_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_device *udev = uaa->device; struct usb_device *parent_hub = udev->parent_hub; struct usb_hub *hub; struct usb_hub_descriptor hubdesc20; struct usb_hub_ss_descriptor hubdesc30; #if USB_HAVE_DISABLE_ENUM struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; #endif uint16_t pwrdly; uint16_t nports; uint8_t x; uint8_t portno; uint8_t removable; uint8_t iface_index; usb_error_t err; sc->sc_udev = udev; sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "USB HUB mutex", NULL, MTX_DEF); device_set_usb_desc(dev); DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, " "parent->selfpowered=%d\n", udev->depth, udev->flags.self_powered, parent_hub, parent_hub ? parent_hub->flags.self_powered : 0); if (uhub_is_too_deep(udev)) { DPRINTFN(0, "HUB at depth %d, " "exceeds maximum. HUB ignored\n", (int)udev->depth); goto error; } if (!udev->flags.self_powered && parent_hub && !parent_hub->flags.self_powered) { DPRINTFN(0, "Bus powered HUB connected to " "bus powered HUB. HUB ignored\n"); goto error; } if (UHUB_IS_MULTI_TT(sc)) { err = usbd_set_alt_interface_index(udev, 0, 1); if (err) { device_printf(dev, "MTT could not be enabled\n"); goto error; } device_printf(dev, "MTT enabled\n"); } /* get HUB descriptor */ DPRINTFN(2, "Getting HUB descriptor\n"); switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: /* assuming that there is one port */ err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1); if (err) { DPRINTFN(0, "getting USB 2.0 HUB descriptor failed," "error=%s\n", usbd_errstr(err)); goto error; } /* get number of ports */ nports = hubdesc20.bNbrPorts; /* get power delay */ pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + usb_extra_power_up_time); /* get complete HUB descriptor */ if (nports >= 8) { /* check number of ports */ if (nports > 127) { DPRINTFN(0, "Invalid number of USB 2.0 ports," "error=%s\n", usbd_errstr(err)); goto error; } /* get complete HUB descriptor */ err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports); if (err) { DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed," "error=%s\n", usbd_errstr(err)); goto error; } if (hubdesc20.bNbrPorts != nports) { DPRINTFN(0, "Number of ports changed\n"); goto error; } } break; case USB_SPEED_SUPER: if (udev->parent_hub != NULL) { err = usbd_req_set_hub_depth(udev, NULL, udev->depth - 1); if (err) { DPRINTFN(0, "Setting USB 3.0 HUB depth failed," "error=%s\n", usbd_errstr(err)); goto error; } } err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); if (err) { DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed," "error=%s\n", usbd_errstr(err)); goto error; } /* get number of ports */ nports = hubdesc30.bNbrPorts; /* get power delay */ pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + usb_extra_power_up_time); /* get complete HUB descriptor */ if (nports >= 8) { /* check number of ports */ if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) { DPRINTFN(0, "Invalid number of USB 3.0 ports," "error=%s\n", usbd_errstr(err)); goto error; } /* get complete HUB descriptor */ err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports); if (err) { DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed," "error=%s\n", usbd_errstr(err)); goto error; } if (hubdesc30.bNbrPorts != nports) { DPRINTFN(0, "Number of ports changed\n"); goto error; } } break; default: DPRINTF("Assuming HUB has only one port\n"); /* default number of ports */ nports = 1; /* default power delay */ pwrdly = ((10 * UHD_PWRON_FACTOR) + usb_extra_power_up_time); break; } if (nports == 0) { DPRINTFN(0, "portless HUB\n"); goto error; } if (nports > USB_MAX_PORTS) { DPRINTF("Port limit exceeded\n"); goto error; } #if (USB_HAVE_FIXED_PORT == 0) hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports), M_USBDEV, M_WAITOK | M_ZERO); if (hub == NULL) goto error; #else hub = &sc->sc_hub; #endif udev->hub = hub; /* initialize HUB structure */ hub->hubsoftc = sc; hub->explore = &uhub_explore; hub->nports = nports; hub->hubudev = udev; #if USB_HAVE_TT_SUPPORT hub->tt_msg[0].hdr.pm_callback = &uhub_reset_tt_proc; hub->tt_msg[0].udev = udev; hub->tt_msg[1].hdr.pm_callback = &uhub_reset_tt_proc; hub->tt_msg[1].udev = udev; #endif /* if self powered hub, give ports maximum current */ if (udev->flags.self_powered) { hub->portpower = USB_MAX_POWER; } else { hub->portpower = USB_MIN_POWER; } /* set up interrupt pipe */ iface_index = 0; if (udev->parent_hub == NULL) { /* root HUB is special */ err = 0; } else { /* normal HUB */ err = usbd_transfer_setup(udev, &iface_index, sc->sc_xfer, uhub_config, UHUB_N_TRANSFER, sc, &sc->sc_mtx); } if (err) { DPRINTFN(0, "cannot setup interrupt transfer, " "errstr=%s\n", usbd_errstr(err)); goto error; } /* wait with power off for a while */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME)); #if USB_HAVE_DISABLE_ENUM /* Add device sysctls */ sysctl_ctx = device_get_sysctl_ctx(dev); sysctl_tree = device_get_sysctl_tree(dev); if (sysctl_ctx != NULL && sysctl_tree != NULL) { (void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "disable_enumeration", CTLFLAG_RWTUN, &sc->sc_disable_enumeration, 0, "Set to disable enumeration on this USB HUB."); (void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "disable_port_power", CTLFLAG_RWTUN, &sc->sc_disable_port_power, 0, "Set to disable USB port power on this USB HUB."); } #endif /* * To have the best chance of success we do things in the exact same * order as Windoze98. This should not be necessary, but some * devices do not follow the USB specs to the letter. * * These are the events on the bus when a hub is attached: * Get device and config descriptors (see attach code) * Get hub descriptor (see above) * For all ports * turn on power * wait for power to become stable * (all below happens in explore code) * For all ports * clear C_PORT_CONNECTION * For all ports * get port status * if device connected * wait 100 ms * turn on reset * wait * clear C_PORT_RESET * get port status * proceed with device attachment */ /* XXX should check for none, individual, or ganged power? */ removable = 0; for (x = 0; x != nports; x++) { /* set up data structures */ struct usb_port *up = hub->ports + x; up->device_index = 0; up->restartcnt = 0; portno = x + 1; /* check if port is removable */ switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: if (!UHD_NOT_REMOV(&hubdesc20, portno)) removable++; break; case USB_SPEED_SUPER: if (!UHD_NOT_REMOV(&hubdesc30, portno)) removable++; break; default: DPRINTF("Assuming removable port\n"); removable++; break; } if (err == 0) { #if USB_HAVE_DISABLE_ENUM /* check if we should disable USB port power or not */ if (usb_disable_port_power != 0 || sc->sc_disable_port_power != 0) { /* turn the power off */ DPRINTFN(2, "Turning port %d power off\n", portno); err = usbd_req_clear_port_feature(udev, NULL, portno, UHF_PORT_POWER); } else { #endif /* turn the power on */ DPRINTFN(2, "Turning port %d power on\n", portno); err = usbd_req_set_port_feature(udev, NULL, portno, UHF_PORT_POWER); #if USB_HAVE_DISABLE_ENUM } #endif } if (err != 0) { DPRINTFN(0, "port %d power on or off failed, %s\n", portno, usbd_errstr(err)); } DPRINTF("turn on port %d power\n", portno); /* wait for stable power */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(pwrdly)); } device_printf(dev, "%d port%s with %d " "removable, %s powered\n", nports, (nports != 1) ? "s" : "", removable, udev->flags.self_powered ? "self" : "bus"); /* Start the interrupt endpoint, if any */ USB_MTX_LOCK(&sc->sc_mtx); usbd_transfer_start(sc->sc_xfer[UHUB_INTR_TRANSFER]); USB_MTX_UNLOCK(&sc->sc_mtx); /* Enable automatic power save on all USB HUBs */ usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); return (0); error: usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER); #if (USB_HAVE_FIXED_PORT == 0) free(udev->hub, M_USBDEV); #endif udev->hub = NULL; mtx_destroy(&sc->sc_mtx); return (ENXIO); } /* * Called from process context when the hub is gone. * Detach all devices on active ports. */ int uhub_detach(device_t dev) { struct uhub_softc *sc = device_get_softc(dev); struct usb_hub *hub = sc->sc_udev->hub; struct usb_bus *bus = sc->sc_udev->bus; struct usb_device *child; uint8_t x; if (hub == NULL) /* must be partially working */ return (0); /* Make sure interrupt transfer is gone. */ usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER); /* Detach all ports */ for (x = 0; x != hub->nports; x++) { child = usb_bus_port_get_device(bus, hub->ports + x); if (child == NULL) { continue; } /* * Free USB device and all subdevices, if any. */ usb_free_device(child, 0); } #if USB_HAVE_TT_SUPPORT /* Make sure our TT messages are not queued anywhere */ USB_BUS_LOCK(bus); usb_proc_mwait(USB_BUS_TT_PROC(bus), &hub->tt_msg[0], &hub->tt_msg[1]); USB_BUS_UNLOCK(bus); #endif #if (USB_HAVE_FIXED_PORT == 0) free(hub, M_USBDEV); #endif sc->sc_udev->hub = NULL; mtx_destroy(&sc->sc_mtx); return (0); } static int uhub_suspend(device_t dev) { DPRINTF("\n"); /* Sub-devices are not suspended here! */ return (0); } static int uhub_resume(device_t dev) { DPRINTF("\n"); /* Sub-devices are not resumed here! */ return (0); } static void uhub_driver_added(device_t dev, driver_t *driver) { usb_needs_explore_all(); } void uhub_find_iface_index(struct usb_hub *hub, device_t child, struct hub_result *res) { struct usb_interface *iface; struct usb_device *udev; uint8_t nports; uint8_t x; uint8_t i; nports = hub->nports; for (x = 0; x != nports; x++) { udev = usb_bus_port_get_device(hub->hubudev->bus, hub->ports + x); if (!udev) { continue; } for (i = 0; i != USB_IFACE_MAX; i++) { iface = usbd_get_iface(udev, i); if (iface && (iface->subdev == child)) { res->iface_index = i; res->udev = udev; res->portno = x + 1; return; } } } res->iface_index = 0; res->udev = NULL; res->portno = 0; } int uhub_child_location_string(device_t parent, device_t child, char *buf, size_t buflen) { struct uhub_softc *sc; struct usb_hub *hub; struct hub_result res; if (!device_is_attached(parent)) { if (buflen) buf[0] = 0; return (0); } sc = device_get_softc(parent); hub = sc->sc_udev->hub; mtx_lock(&Giant); uhub_find_iface_index(hub, child, &res); if (!res.udev) { DPRINTF("device not on hub\n"); if (buflen) { buf[0] = '\0'; } goto done; } snprintf(buf, buflen, "bus=%u hubaddr=%u port=%u devaddr=%u" " interface=%u" #if USB_HAVE_UGEN " ugen=%s" #endif , device_get_unit(res.udev->bus->bdev) , (res.udev->parent_hub != NULL) ? res.udev->parent_hub->device_index : 0 , res.portno, res.udev->device_index, res.iface_index #if USB_HAVE_UGEN , res.udev->ugen_name #endif ); done: mtx_unlock(&Giant); return (0); } static int uhub_child_pnpinfo_string(device_t parent, device_t child, char *buf, size_t buflen) { struct uhub_softc *sc; struct usb_hub *hub; struct usb_interface *iface; struct hub_result res; if (!device_is_attached(parent)) { if (buflen) buf[0] = 0; return (0); } sc = device_get_softc(parent); hub = sc->sc_udev->hub; mtx_lock(&Giant); uhub_find_iface_index(hub, child, &res); if (!res.udev) { DPRINTF("device not on hub\n"); if (buflen) { buf[0] = '\0'; } goto done; } iface = usbd_get_iface(res.udev, res.iface_index); if (iface && iface->idesc) { snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " "devclass=0x%02x devsubclass=0x%02x " "devproto=0x%02x " "sernum=\"%s\" " "release=0x%04x " "mode=%s " "intclass=0x%02x intsubclass=0x%02x " "intprotocol=0x%02x" "%s%s", UGETW(res.udev->ddesc.idVendor), UGETW(res.udev->ddesc.idProduct), res.udev->ddesc.bDeviceClass, res.udev->ddesc.bDeviceSubClass, res.udev->ddesc.bDeviceProtocol, usb_get_serial(res.udev), UGETW(res.udev->ddesc.bcdDevice), (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", iface->idesc->bInterfaceClass, iface->idesc->bInterfaceSubClass, iface->idesc->bInterfaceProtocol, iface->pnpinfo ? " " : "", iface->pnpinfo ? iface->pnpinfo : ""); } else { if (buflen) { buf[0] = '\0'; } goto done; } done: mtx_unlock(&Giant); return (0); } /* * The USB Transaction Translator: * =============================== * * When doing LOW- and FULL-speed USB transfers across a HIGH-speed * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT * USB transfers. To utilize bandwidth dynamically the "scatter and * gather" principle must be applied. This means that bandwidth must * be divided into equal parts of bandwidth. With regard to USB all * data is transferred in smaller packets with length * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is * not a constant! * * The bandwidth scheduler which I have implemented will simply pack * the USB transfers back to back until there is no more space in the * schedule. Out of the 8 microframes which the USB 2.0 standard * provides, only 6 are available for non-HIGH-speed devices. I have * reserved the first 4 microframes for ISOCHRONOUS transfers. The * last 2 microframes I have reserved for INTERRUPT transfers. Without * this division, it is very difficult to allocate and free bandwidth * dynamically. * * NOTE about the Transaction Translator in USB HUBs: * * USB HUBs have a very simple Transaction Translator, that will * simply pipeline all the SPLIT transactions. That means that the * transactions will be executed in the order they are queued! * */ /*------------------------------------------------------------------------* * usb_intr_find_best_slot * * Return value: * The best Transaction Translation slot for an interrupt endpoint. *------------------------------------------------------------------------*/ static uint8_t usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start, uint8_t end, uint8_t mask) { usb_size_t min = (usb_size_t)-1; usb_size_t sum; uint8_t x; uint8_t y; uint8_t z; y = 0; /* find the last slot with lesser used bandwidth */ for (x = start; x < end; x++) { sum = 0; /* compute sum of bandwidth */ for (z = x; z < end; z++) { if (mask & (1U << (z - x))) sum += ptr[z]; } /* check if the current multi-slot is more optimal */ if (min >= sum) { min = sum; y = x; } /* check if the mask is about to be shifted out */ if (mask & (1U << (end - 1 - x))) break; } return (y); } /*------------------------------------------------------------------------* * usb_hs_bandwidth_adjust * * This function will update the bandwidth usage for the microframe * having index "slot" by "len" bytes. "len" can be negative. If the * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX" * the "slot" argument will be replaced by the slot having least used * bandwidth. The "mask" argument is used for multi-slot allocations. * * Returns: * The slot in which the bandwidth update was done: 0..7 *------------------------------------------------------------------------*/ static uint8_t usb_hs_bandwidth_adjust(struct usb_device *udev, int16_t len, uint8_t slot, uint8_t mask) { struct usb_bus *bus = udev->bus; struct usb_hub *hub; enum usb_dev_speed speed; uint8_t x; USB_BUS_LOCK_ASSERT(bus, MA_OWNED); speed = usbd_get_speed(udev); switch (speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: if (speed == USB_SPEED_LOW) { len *= 8; } /* * The Host Controller Driver should have * performed checks so that the lookup * below does not result in a NULL pointer * access. */ hub = udev->parent_hs_hub->hub; if (slot >= USB_HS_MICRO_FRAMES_MAX) { slot = usb_intr_find_best_slot(hub->uframe_usage, USB_FS_ISOC_UFRAME_MAX, 6, mask); } for (x = slot; x < 8; x++) { if (mask & (1U << (x - slot))) { hub->uframe_usage[x] += len; bus->uframe_usage[x] += len; } } break; default: if (slot >= USB_HS_MICRO_FRAMES_MAX) { slot = usb_intr_find_best_slot(bus->uframe_usage, 0, USB_HS_MICRO_FRAMES_MAX, mask); } for (x = slot; x < 8; x++) { if (mask & (1U << (x - slot))) { bus->uframe_usage[x] += len; } } break; } return (slot); } /*------------------------------------------------------------------------* * usb_hs_bandwidth_alloc * * This function is a wrapper function for "usb_hs_bandwidth_adjust()". *------------------------------------------------------------------------*/ void usb_hs_bandwidth_alloc(struct usb_xfer *xfer) { struct usb_device *udev; uint8_t slot; uint8_t mask; uint8_t speed; udev = xfer->xroot->udev; if (udev->flags.usb_mode != USB_MODE_HOST) return; /* not supported */ xfer->endpoint->refcount_bw++; if (xfer->endpoint->refcount_bw != 1) return; /* already allocated */ speed = usbd_get_speed(udev); switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: /* allocate a microframe slot */ mask = 0x01; slot = usb_hs_bandwidth_adjust(udev, xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask); xfer->endpoint->usb_uframe = slot; xfer->endpoint->usb_smask = mask << slot; if ((speed != USB_SPEED_FULL) && (speed != USB_SPEED_LOW)) { xfer->endpoint->usb_cmask = 0x00 ; } else { xfer->endpoint->usb_cmask = (-(0x04 << slot)) & 0xFE; } break; case UE_ISOCHRONOUS: switch (usbd_xfer_get_fps_shift(xfer)) { case 0: mask = 0xFF; break; case 1: mask = 0x55; break; case 2: mask = 0x11; break; default: mask = 0x01; break; } /* allocate a microframe multi-slot */ slot = usb_hs_bandwidth_adjust(udev, xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask); xfer->endpoint->usb_uframe = slot; xfer->endpoint->usb_cmask = 0; xfer->endpoint->usb_smask = mask << slot; break; default: xfer->endpoint->usb_uframe = 0; xfer->endpoint->usb_cmask = 0; xfer->endpoint->usb_smask = 0; break; } DPRINTFN(11, "slot=%d, mask=0x%02x\n", xfer->endpoint->usb_uframe, xfer->endpoint->usb_smask >> xfer->endpoint->usb_uframe); } /*------------------------------------------------------------------------* * usb_hs_bandwidth_free * * This function is a wrapper function for "usb_hs_bandwidth_adjust()". *------------------------------------------------------------------------*/ void usb_hs_bandwidth_free(struct usb_xfer *xfer) { struct usb_device *udev; uint8_t slot; uint8_t mask; udev = xfer->xroot->udev; if (udev->flags.usb_mode != USB_MODE_HOST) return; /* not supported */ xfer->endpoint->refcount_bw--; if (xfer->endpoint->refcount_bw != 0) return; /* still allocated */ switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_ISOCHRONOUS: slot = xfer->endpoint->usb_uframe; mask = xfer->endpoint->usb_smask; /* free microframe slot(s): */ usb_hs_bandwidth_adjust(udev, -xfer->max_frame_size, slot, mask >> slot); DPRINTFN(11, "slot=%d, mask=0x%02x\n", slot, mask >> slot); xfer->endpoint->usb_uframe = 0; xfer->endpoint->usb_cmask = 0; xfer->endpoint->usb_smask = 0; break; default: break; } } /*------------------------------------------------------------------------* * usb_isoc_time_expand * * This function will expand the time counter from 7-bit to 16-bit. * * Returns: * 16-bit isochronous time counter. *------------------------------------------------------------------------*/ uint16_t usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr) { uint16_t rem; USB_BUS_LOCK_ASSERT(bus, MA_OWNED); rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1); isoc_time_curr &= (USB_ISOC_TIME_MAX - 1); if (isoc_time_curr < rem) { /* the time counter wrapped around */ bus->isoc_time_last += USB_ISOC_TIME_MAX; } /* update the remainder */ bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1); bus->isoc_time_last |= isoc_time_curr; return (bus->isoc_time_last); } /*------------------------------------------------------------------------* * usbd_fs_isoc_schedule_alloc_slot * * This function will allocate bandwidth for an isochronous FULL speed * transaction in the FULL speed schedule. * * Returns: * <8: Success * Else: Error *------------------------------------------------------------------------*/ #if USB_HAVE_TT_SUPPORT uint8_t usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time) { struct usb_xfer *xfer; struct usb_xfer *pipe_xfer; struct usb_bus *bus; usb_frlength_t len; usb_frlength_t data_len; uint16_t delta; uint16_t slot; uint8_t retval; data_len = 0; slot = 0; bus = isoc_xfer->xroot->bus; TAILQ_FOREACH(xfer, &bus->intr_q.head, wait_entry) { /* skip self, if any */ if (xfer == isoc_xfer) continue; /* check if this USB transfer is going through the same TT */ if (xfer->xroot->udev->parent_hs_hub != isoc_xfer->xroot->udev->parent_hs_hub) { continue; } if ((isoc_xfer->xroot->udev->parent_hs_hub-> ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) && (xfer->xroot->udev->hs_port_no != isoc_xfer->xroot->udev->hs_port_no)) { continue; } if (xfer->endpoint->methods != isoc_xfer->endpoint->methods) continue; /* check if isoc_time is part of this transfer */ delta = xfer->isoc_time_complete - isoc_time; if (delta > 0 && delta <= xfer->nframes) { delta = xfer->nframes - delta; len = xfer->frlengths[delta]; len += 8; len *= 7; len /= 6; data_len += len; } /* * Check double buffered transfers. Only stream ID * equal to zero is valid here! */ TAILQ_FOREACH(pipe_xfer, &xfer->endpoint->endpoint_q[0].head, wait_entry) { /* skip self, if any */ if (pipe_xfer == isoc_xfer) continue; /* check if isoc_time is part of this transfer */ delta = pipe_xfer->isoc_time_complete - isoc_time; if (delta > 0 && delta <= pipe_xfer->nframes) { delta = pipe_xfer->nframes - delta; len = pipe_xfer->frlengths[delta]; len += 8; len *= 7; len /= 6; data_len += len; } } } while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) { data_len -= USB_FS_BYTES_PER_HS_UFRAME; slot++; } /* check for overflow */ if (slot >= USB_FS_ISOC_UFRAME_MAX) return (255); retval = slot; delta = isoc_xfer->isoc_time_complete - isoc_time; if (delta > 0 && delta <= isoc_xfer->nframes) { delta = isoc_xfer->nframes - delta; len = isoc_xfer->frlengths[delta]; len += 8; len *= 7; len /= 6; data_len += len; } while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) { data_len -= USB_FS_BYTES_PER_HS_UFRAME; slot++; } /* check for overflow */ if (slot >= USB_FS_ISOC_UFRAME_MAX) return (255); return (retval); } #endif /*------------------------------------------------------------------------* * usb_bus_port_get_device * * This function is NULL safe. *------------------------------------------------------------------------*/ struct usb_device * usb_bus_port_get_device(struct usb_bus *bus, struct usb_port *up) { if ((bus == NULL) || (up == NULL)) { /* be NULL safe */ return (NULL); } if (up->device_index == 0) { /* nothing to do */ return (NULL); } return (bus->devices[up->device_index]); } /*------------------------------------------------------------------------* * usb_bus_port_set_device * * This function is NULL safe. *------------------------------------------------------------------------*/ void usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up, struct usb_device *udev, uint8_t device_index) { if (bus == NULL) { /* be NULL safe */ return; } /* * There is only one case where we don't * have an USB port, and that is the Root Hub! */ if (up) { if (udev) { up->device_index = device_index; } else { device_index = up->device_index; up->device_index = 0; } } /* * Make relationships to our new device */ if (device_index != 0) { #if USB_HAVE_UGEN mtx_lock(&usb_ref_lock); #endif bus->devices[device_index] = udev; #if USB_HAVE_UGEN mtx_unlock(&usb_ref_lock); #endif } /* * Debug print */ DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev); } /*------------------------------------------------------------------------* * usb_needs_explore * * This functions is called when the USB event thread needs to run. *------------------------------------------------------------------------*/ void usb_needs_explore(struct usb_bus *bus, uint8_t do_probe) { uint8_t do_unlock; DPRINTF("\n"); if (cold != 0) { DPRINTF("Cold\n"); return; } if (bus == NULL) { DPRINTF("No bus pointer!\n"); return; } if ((bus->devices == NULL) || (bus->devices[USB_ROOT_HUB_ADDR] == NULL)) { DPRINTF("No root HUB\n"); return; } if (mtx_owned(&bus->bus_mtx)) { do_unlock = 0; } else { USB_BUS_LOCK(bus); do_unlock = 1; } if (do_probe) { bus->do_probe = 1; } if (usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->explore_msg[0], &bus->explore_msg[1])) { /* ignore */ } if (do_unlock) { USB_BUS_UNLOCK(bus); } } /*------------------------------------------------------------------------* * usb_needs_explore_all * * This function is called whenever a new driver is loaded and will * cause that all USB buses are re-explored. *------------------------------------------------------------------------*/ void usb_needs_explore_all(void) { struct usb_bus *bus; devclass_t dc; device_t dev; int max; DPRINTFN(3, "\n"); dc = usb_devclass_ptr; if (dc == NULL) { DPRINTFN(0, "no devclass\n"); return; } /* * Explore all USB buses in parallel. */ max = devclass_get_maxunit(dc); while (max >= 0) { dev = devclass_get_device(dc, max); if (dev) { bus = device_get_softc(dev); if (bus) { usb_needs_explore(bus, 1); } } max--; } } /*------------------------------------------------------------------------* * usb_needs_explore_init * * This function will ensure that the USB controllers are not enumerated * until the "cold" variable is cleared. *------------------------------------------------------------------------*/ static void usb_needs_explore_init(void *arg) { /* * The cold variable should be cleared prior to this function * being called: */ if (cold == 0) usb_needs_explore_all(); else DPRINTFN(-1, "Cold variable is still set!\n"); } SYSINIT(usb_needs_explore_init, SI_SUB_KICK_SCHEDULER, SI_ORDER_SECOND, usb_needs_explore_init, NULL); /*------------------------------------------------------------------------* * usb_bus_power_update * * This function will ensure that all USB devices on the given bus are * properly suspended or resumed according to the device transfer * state. *------------------------------------------------------------------------*/ #if USB_HAVE_POWERD void usb_bus_power_update(struct usb_bus *bus) { usb_needs_explore(bus, 0 /* no probe */ ); } #endif /*------------------------------------------------------------------------* * usbd_transfer_power_ref * * This function will modify the power save reference counts and * wakeup the USB device associated with the given USB transfer, if * needed. *------------------------------------------------------------------------*/ #if USB_HAVE_POWERD void usbd_transfer_power_ref(struct usb_xfer *xfer, int val) { static const usb_power_mask_t power_mask[4] = { [UE_CONTROL] = USB_HW_POWER_CONTROL, [UE_BULK] = USB_HW_POWER_BULK, [UE_INTERRUPT] = USB_HW_POWER_INTERRUPT, [UE_ISOCHRONOUS] = USB_HW_POWER_ISOC, }; struct usb_device *udev; uint8_t needs_explore; uint8_t needs_hw_power; uint8_t xfer_type; udev = xfer->xroot->udev; if (udev->device_index == USB_ROOT_HUB_ADDR) { /* no power save for root HUB */ return; } USB_BUS_LOCK(udev->bus); xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; udev->pwr_save.last_xfer_time = ticks; udev->pwr_save.type_refs[xfer_type] += val; if (xfer->flags_int.control_xfr) { udev->pwr_save.read_refs += val; if (xfer->flags_int.usb_mode == USB_MODE_HOST) { /* * It is not allowed to suspend during a * control transfer: */ udev->pwr_save.write_refs += val; } } else if (USB_GET_DATA_ISREAD(xfer)) { udev->pwr_save.read_refs += val; } else { udev->pwr_save.write_refs += val; } if (val > 0) { if (udev->flags.self_suspended) needs_explore = usb_peer_should_wakeup(udev); else needs_explore = 0; if (!(udev->bus->hw_power_state & power_mask[xfer_type])) { DPRINTF("Adding type %u to power state\n", xfer_type); udev->bus->hw_power_state |= power_mask[xfer_type]; needs_hw_power = 1; } else { needs_hw_power = 0; } } else { needs_explore = 0; needs_hw_power = 0; } USB_BUS_UNLOCK(udev->bus); if (needs_explore) { DPRINTF("update\n"); usb_bus_power_update(udev->bus); } else if (needs_hw_power) { DPRINTF("needs power\n"); if (udev->bus->methods->set_hw_power != NULL) { (udev->bus->methods->set_hw_power) (udev->bus); } } } #endif /*------------------------------------------------------------------------* * usb_peer_should_wakeup * * This function returns non-zero if the current device should wake up. *------------------------------------------------------------------------*/ static uint8_t usb_peer_should_wakeup(struct usb_device *udev) { return (((udev->power_mode == USB_POWER_MODE_ON) && (udev->flags.usb_mode == USB_MODE_HOST)) || (udev->driver_added_refcount != udev->bus->driver_added_refcount) || (udev->re_enumerate_wait != USB_RE_ENUM_DONE) || (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) || (udev->pwr_save.write_refs != 0) || ((udev->pwr_save.read_refs != 0) && (udev->flags.usb_mode == USB_MODE_HOST) && (usb_peer_can_wakeup(udev) == 0))); } /*------------------------------------------------------------------------* * usb_bus_powerd * * This function implements the USB power daemon and is called * regularly from the USB explore thread. *------------------------------------------------------------------------*/ #if USB_HAVE_POWERD void usb_bus_powerd(struct usb_bus *bus) { struct usb_device *udev; usb_ticks_t temp; usb_ticks_t limit; usb_ticks_t mintime; usb_size_t type_refs[5]; uint8_t x; limit = usb_power_timeout; if (limit == 0) limit = hz; else if (limit > 255) limit = 255 * hz; else limit = limit * hz; DPRINTF("bus=%p\n", bus); USB_BUS_LOCK(bus); /* * The root HUB device is never suspended * and we simply skip it. */ for (x = USB_ROOT_HUB_ADDR + 1; x != bus->devices_max; x++) { udev = bus->devices[x]; if (udev == NULL) continue; temp = ticks - udev->pwr_save.last_xfer_time; if (usb_peer_should_wakeup(udev)) { /* check if we are suspended */ if (udev->flags.self_suspended != 0) { USB_BUS_UNLOCK(bus); usb_dev_resume_peer(udev); USB_BUS_LOCK(bus); } } else if ((temp >= limit) && (udev->flags.usb_mode == USB_MODE_HOST) && (udev->flags.self_suspended == 0)) { /* try to do suspend */ USB_BUS_UNLOCK(bus); usb_dev_suspend_peer(udev); USB_BUS_LOCK(bus); } } /* reset counters */ mintime = (usb_ticks_t)-1; type_refs[0] = 0; type_refs[1] = 0; type_refs[2] = 0; type_refs[3] = 0; type_refs[4] = 0; /* Re-loop all the devices to get the actual state */ for (x = USB_ROOT_HUB_ADDR + 1; x != bus->devices_max; x++) { udev = bus->devices[x]; if (udev == NULL) continue; /* we found a non-Root-Hub USB device */ type_refs[4] += 1; /* "last_xfer_time" can be updated by a resume */ temp = ticks - udev->pwr_save.last_xfer_time; /* * Compute minimum time since last transfer for the complete * bus: */ if (temp < mintime) mintime = temp; if (udev->flags.self_suspended == 0) { type_refs[0] += udev->pwr_save.type_refs[0]; type_refs[1] += udev->pwr_save.type_refs[1]; type_refs[2] += udev->pwr_save.type_refs[2]; type_refs[3] += udev->pwr_save.type_refs[3]; } } if (mintime >= (usb_ticks_t)(1 * hz)) { /* recompute power masks */ DPRINTF("Recomputing power masks\n"); bus->hw_power_state = 0; if (type_refs[UE_CONTROL] != 0) bus->hw_power_state |= USB_HW_POWER_CONTROL; if (type_refs[UE_BULK] != 0) bus->hw_power_state |= USB_HW_POWER_BULK; if (type_refs[UE_INTERRUPT] != 0) bus->hw_power_state |= USB_HW_POWER_INTERRUPT; if (type_refs[UE_ISOCHRONOUS] != 0) bus->hw_power_state |= USB_HW_POWER_ISOC; if (type_refs[4] != 0) bus->hw_power_state |= USB_HW_POWER_NON_ROOT_HUB; } USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power != NULL) { /* always update hardware power! */ (bus->methods->set_hw_power) (bus); } return; } #endif /*------------------------------------------------------------------------* * usb_dev_resume_peer * * This function will resume an USB peer and do the required USB * signalling to get an USB device out of the suspended state. *------------------------------------------------------------------------*/ static void usb_dev_resume_peer(struct usb_device *udev) { struct usb_bus *bus; int err; /* be NULL safe */ if (udev == NULL) return; /* check if already resumed */ if (udev->flags.self_suspended == 0) return; /* we need a parent HUB to do resume */ if (udev->parent_hub == NULL) return; DPRINTF("udev=%p\n", udev); if ((udev->flags.usb_mode == USB_MODE_DEVICE) && (udev->flags.remote_wakeup == 0)) { /* * If the host did not set the remote wakeup feature, we can * not wake it up either! */ DPRINTF("remote wakeup is not set!\n"); return; } /* get bus pointer */ bus = udev->bus; /* resume parent hub first */ usb_dev_resume_peer(udev->parent_hub); /* reduce chance of instant resume failure by waiting a little bit */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(20)); if (usb_device_20_compatible(udev)) { /* resume current port (Valid in Host and Device Mode) */ err = usbd_req_clear_port_feature(udev->parent_hub, NULL, udev->port_no, UHF_PORT_SUSPEND); if (err) { DPRINTFN(0, "Resuming port failed\n"); return; } } else { /* resume current port (Valid in Host and Device Mode) */ err = usbd_req_set_port_link_state(udev->parent_hub, NULL, udev->port_no, UPS_PORT_LS_U0); if (err) { DPRINTFN(0, "Resuming port failed\n"); return; } } /* resume settle time */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay)); if (bus->methods->device_resume != NULL) { /* resume USB device on the USB controller */ (bus->methods->device_resume) (udev); } USB_BUS_LOCK(bus); /* set that this device is now resumed */ udev->flags.self_suspended = 0; #if USB_HAVE_POWERD /* make sure that we don't go into suspend right away */ udev->pwr_save.last_xfer_time = ticks; /* make sure the needed power masks are on */ if (udev->pwr_save.type_refs[UE_CONTROL] != 0) bus->hw_power_state |= USB_HW_POWER_CONTROL; if (udev->pwr_save.type_refs[UE_BULK] != 0) bus->hw_power_state |= USB_HW_POWER_BULK; if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0) bus->hw_power_state |= USB_HW_POWER_INTERRUPT; if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) bus->hw_power_state |= USB_HW_POWER_ISOC; #endif USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power != NULL) { /* always update hardware power! */ (bus->methods->set_hw_power) (bus); } usbd_sr_lock(udev); /* notify all sub-devices about resume */ err = usb_suspend_resume(udev, 0); usbd_sr_unlock(udev); /* check if peer has wakeup capability */ if (usb_peer_can_wakeup(udev)) { /* clear remote wakeup */ err = usbd_req_clear_device_feature(udev, NULL, UF_DEVICE_REMOTE_WAKEUP); if (err) { DPRINTFN(0, "Clearing device " "remote wakeup failed: %s\n", usbd_errstr(err)); } } } /*------------------------------------------------------------------------* * usb_dev_suspend_peer * * This function will suspend an USB peer and do the required USB * signalling to get an USB device into the suspended state. *------------------------------------------------------------------------*/ static void usb_dev_suspend_peer(struct usb_device *udev) { struct usb_device *child; int err; uint8_t x; uint8_t nports; repeat: /* be NULL safe */ if (udev == NULL) return; /* check if already suspended */ if (udev->flags.self_suspended) return; /* we need a parent HUB to do suspend */ if (udev->parent_hub == NULL) return; DPRINTF("udev=%p\n", udev); /* check if the current device is a HUB */ if (udev->hub != NULL) { nports = udev->hub->nports; /* check if all devices on the HUB are suspended */ for (x = 0; x != nports; x++) { child = usb_bus_port_get_device(udev->bus, udev->hub->ports + x); if (child == NULL) continue; if (child->flags.self_suspended) continue; DPRINTFN(1, "Port %u is busy on the HUB!\n", x + 1); return; } } if (usb_peer_can_wakeup(udev)) { /* * This request needs to be done before we set * "udev->flags.self_suspended": */ /* allow device to do remote wakeup */ err = usbd_req_set_device_feature(udev, NULL, UF_DEVICE_REMOTE_WAKEUP); if (err) { DPRINTFN(0, "Setting device " "remote wakeup failed\n"); } } USB_BUS_LOCK(udev->bus); /* * Checking for suspend condition and setting suspended bit * must be atomic! */ err = usb_peer_should_wakeup(udev); if (err == 0) { /* * Set that this device is suspended. This variable * must be set before calling USB controller suspend * callbacks. */ udev->flags.self_suspended = 1; } USB_BUS_UNLOCK(udev->bus); if (err != 0) { if (usb_peer_can_wakeup(udev)) { /* allow device to do remote wakeup */ err = usbd_req_clear_device_feature(udev, NULL, UF_DEVICE_REMOTE_WAKEUP); if (err) { DPRINTFN(0, "Setting device " "remote wakeup failed\n"); } } if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* resume parent HUB first */ usb_dev_resume_peer(udev->parent_hub); /* reduce chance of instant resume failure by waiting a little bit */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(20)); /* resume current port (Valid in Host and Device Mode) */ err = usbd_req_clear_port_feature(udev->parent_hub, NULL, udev->port_no, UHF_PORT_SUSPEND); /* resume settle time */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay)); } DPRINTF("Suspend was cancelled!\n"); return; } usbd_sr_lock(udev); /* notify all sub-devices about suspend */ err = usb_suspend_resume(udev, 1); usbd_sr_unlock(udev); if (udev->bus->methods->device_suspend != NULL) { usb_timeout_t temp; /* suspend device on the USB controller */ (udev->bus->methods->device_suspend) (udev); /* do DMA delay */ temp = usbd_get_dma_delay(udev); if (temp != 0) usb_pause_mtx(NULL, USB_MS_TO_TICKS(temp)); } if (usb_device_20_compatible(udev)) { /* suspend current port */ err = usbd_req_set_port_feature(udev->parent_hub, NULL, udev->port_no, UHF_PORT_SUSPEND); if (err) { DPRINTFN(0, "Suspending port failed\n"); return; } } else { /* suspend current port */ err = usbd_req_set_port_link_state(udev->parent_hub, NULL, udev->port_no, UPS_PORT_LS_U3); if (err) { DPRINTFN(0, "Suspending port failed\n"); return; } } udev = udev->parent_hub; goto repeat; } /*------------------------------------------------------------------------* * usbd_set_power_mode * * This function will set the power mode, see USB_POWER_MODE_XXX for a * USB device. *------------------------------------------------------------------------*/ void usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode) { /* filter input argument */ if ((power_mode != USB_POWER_MODE_ON) && (power_mode != USB_POWER_MODE_OFF)) power_mode = USB_POWER_MODE_SAVE; power_mode = usbd_filter_power_mode(udev, power_mode); udev->power_mode = power_mode; /* update copy of power mode */ #if USB_HAVE_POWERD usb_bus_power_update(udev->bus); #else usb_needs_explore(udev->bus, 0 /* no probe */ ); #endif } /*------------------------------------------------------------------------* * usbd_filter_power_mode * * This function filters the power mode based on hardware requirements. *------------------------------------------------------------------------*/ uint8_t usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode) { const struct usb_bus_methods *mtod; int8_t temp; mtod = udev->bus->methods; temp = -1; if (mtod->get_power_mode != NULL) (mtod->get_power_mode) (udev, &temp); /* check if we should not filter */ if (temp < 0) return (power_mode); /* use fixed power mode given by hardware driver */ return (temp); } /*------------------------------------------------------------------------* * usbd_start_re_enumerate * * This function starts re-enumeration of the given USB device. This * function does not need to be called BUS-locked. This function does * not wait until the re-enumeration is completed. *------------------------------------------------------------------------*/ void usbd_start_re_enumerate(struct usb_device *udev) { if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) { udev->re_enumerate_wait = USB_RE_ENUM_START; usb_needs_explore(udev->bus, 0); } } /*-----------------------------------------------------------------------* * usbd_start_set_config * * This function starts setting a USB configuration. This function * does not need to be called BUS-locked. This function does not wait * until the set USB configuratino is completed. *------------------------------------------------------------------------*/ usb_error_t usbd_start_set_config(struct usb_device *udev, uint8_t index) { if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) { if (udev->curr_config_index == index) { /* no change needed */ return (0); } udev->next_config_index = index; udev->re_enumerate_wait = USB_RE_ENUM_SET_CONFIG; usb_needs_explore(udev->bus, 0); return (0); } else if (udev->re_enumerate_wait == USB_RE_ENUM_SET_CONFIG) { if (udev->next_config_index == index) { /* no change needed */ return (0); } } return (USB_ERR_PENDING_REQUESTS); } Index: head/sys/dev/usb/usb_hub_acpi.c =================================================================== --- head/sys/dev/usb/usb_hub_acpi.c (revision 357971) +++ head/sys/dev/usb/usb_hub_acpi.c (revision 357972) @@ -1,607 +1,605 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. All rights reserved. * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved. * Copyright (c) 2019 Takanori Watanabe. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * USB spec: http://www.usb.org/developers/docs/usbspec.zip */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR uhub_debug #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #include #include #include #define ACPI_PLD_SIZE 20 struct acpi_uhub_port { ACPI_HANDLE handle; #define ACPI_UPC_CONNECTABLE 0x80000000 #define ACPI_UPC_PORTTYPE(x) ((x)&0xff) uint32_t upc; uint8_t pld[ACPI_PLD_SIZE]; }; struct acpi_uhub_softc { struct uhub_softc usc; uint8_t nports; ACPI_HANDLE ah; struct acpi_uhub_port *port; }; static UINT32 acpi_uhub_find_rh_cb(ACPI_HANDLE ah, UINT32 nl, void *ctx, void **status) { ACPI_DEVICE_INFO *devinfo; UINT32 ret; *status = NULL; devinfo = NULL; ret = AcpiGetObjectInfo(ah, &devinfo); if (ACPI_SUCCESS(ret)) { if ((devinfo->Valid & ACPI_VALID_ADR) && (devinfo->Address == 0)) { ret = AE_CTRL_TERMINATE; *status = ah; } AcpiOsFree(devinfo); } return (ret); } static const char * acpi_uhub_upc_type(uint8_t type) { const char *typelist[] = {"TypeA", "MiniAB", "Express", "USB3-A", "USB3-B", "USB-MicroB", "USB3-MicroAB", "USB3-PowerB", "TypeC-USB2", "TypeC-Switch", "TypeC-nonSwitch"}; const int last = sizeof(typelist) / sizeof(typelist[0]); if (type == 0xff) { return "Proprietary"; } return (type < last) ? typelist[type] : "Unknown"; } static int acpi_uhub_parse_upc(device_t dev, unsigned int p, ACPI_HANDLE ah, struct sysctl_oid_list *poid) { ACPI_BUFFER buf; struct acpi_uhub_softc *sc = device_get_softc(dev); struct acpi_uhub_port *port = &sc->port[p - 1]; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; if (AcpiEvaluateObject(ah, "_UPC", NULL, &buf) == AE_OK) { ACPI_OBJECT *obj = buf.Pointer; UINT64 porttypenum, conn; uint8_t *connectable; acpi_PkgInt(obj, 0, &conn); acpi_PkgInt(obj, 1, &porttypenum); connectable = conn ? "" : "non"; port->upc = porttypenum; port->upc |= (conn) ? (ACPI_UPC_CONNECTABLE) : 0; if (usb_debug) device_printf(dev, "Port %u %sconnectable %s\n", p, connectable, acpi_uhub_upc_type(porttypenum)); SYSCTL_ADD_U32( device_get_sysctl_ctx(dev), poid, OID_AUTO, "upc", CTLFLAG_RD | CTLFLAG_MPSAFE, SYSCTL_NULL_U32_PTR, port->upc, "UPC value. MSB is visible flag"); } AcpiOsFree(buf.Pointer); return (0); } static int acpi_uhub_port_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_uhub_port *port = oidp->oid_arg1; struct sbuf sb; int error; sbuf_new_for_sysctl(&sb, NULL, 256, req); sbuf_printf(&sb, "Handle %s\n", acpi_name(port->handle)); if (port->upc == 0xffffffff) { sbuf_printf(&sb, "\tNo information\n"); goto end; } sbuf_printf(&sb, "\t"); if (port->upc & ACPI_UPC_CONNECTABLE) { sbuf_printf(&sb, "Connectable "); } sbuf_printf(&sb, "%s port\n", acpi_uhub_upc_type(port->upc & 0xff)); if ((port->pld[0] & 0x80) == 0) { sbuf_printf(&sb, "\tColor:#%02x%02x%02x\n", port->pld[1], port->pld[2], port->pld[3]); } sbuf_printf(&sb, "\tWidth %d mm Height %d mm\n", port->pld[4] | (port->pld[5] << 8), port->pld[6] | (port->pld[7] << 8)); if (port->pld[8] & 1) { sbuf_printf(&sb, "\tVisible\n"); } if (port->pld[8] & 2) { sbuf_printf(&sb, "\tDock\n"); } if (port->pld[8] & 4) { sbuf_printf(&sb, "\tLid\n"); } { int panelpos = (port->pld[8] >> 3) & 7; const char *panposstr[] = {"Top", "Bottom", "Left", "Right", "Front", "Back", "Unknown", "Invalid"}; const char *shapestr[] = { "Round", "Oval", "Square", "VRect", "HRect", "VTrape", "HTrape", "Unknown", "Chamferd", "Rsvd", "Rsvd", "Rsvd", "Rsvd", "Rsvd", "Rsvd", "Rsvd", "Rsvd"}; sbuf_printf(&sb, "\tPanelPosition: %s\n", panposstr[panelpos]); if (panelpos < 6) { const char *posstr[] = {"Upper", "Center", "Lower", "Invalid"}; sbuf_printf(&sb, "\tVertPosition: %s\n", posstr[(port->pld[8] >> 6) & 3]); sbuf_printf(&sb, "\tHorizPosition: %s\n", posstr[(port->pld[9]) & 3]); } sbuf_printf(&sb, "\tShape: %s\n", shapestr[(port->pld[9] >> 2) & 0xf]); sbuf_printf(&sb, "\tGroup Orientation %s\n", ((port->pld[9] >> 6) & 1) ? "Vertical" : "Horizontal"); sbuf_printf(&sb, "\tGroupToken %x\n", ((port->pld[9] >> 7) | (port->pld[10] << 1)) & 0xff); sbuf_printf(&sb, "\tGroupPosition %x\n", ((port->pld[10] >> 7) | (port->pld[11] << 1)) & 0xff); sbuf_printf(&sb, "\t%s %s %s\n", (port->pld[11] & 0x80) ? "Bay" : "", (port->pld[12] & 1) ? "Eject" : "", (port->pld[12] & 2) ? "OSPM" : "" ); } if ((port->pld[0] & 0x7f) >= 2) { sbuf_printf(&sb, "\tVOFF%d mm HOFF %dmm", port->pld[16] | (port->pld[17] << 8), port->pld[18] | (port->pld[19] << 8)); } end: error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } static int acpi_uhub_parse_pld(device_t dev, unsigned int p, ACPI_HANDLE ah, struct sysctl_oid_list *tree) { ACPI_BUFFER buf; struct acpi_uhub_softc *sc = device_get_softc(dev); struct acpi_uhub_port *port = &sc->port[p - 1]; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; if (AcpiEvaluateObject(ah, "_PLD", NULL, &buf) == AE_OK) { ACPI_OBJECT *obj; unsigned char *resbuf; int len; obj = buf.Pointer; if (obj->Type == ACPI_TYPE_PACKAGE && obj->Package.Elements[0].Type == ACPI_TYPE_BUFFER) { ACPI_OBJECT *obj1; obj1 = &obj->Package.Elements[0]; len = obj1->Buffer.Length; resbuf = obj1->Buffer.Pointer; } else if (obj->Type == ACPI_TYPE_BUFFER) { len = obj->Buffer.Length; resbuf = obj->Buffer.Pointer; } else { goto skip; } len = (len < ACPI_PLD_SIZE) ? len : ACPI_PLD_SIZE; memcpy(port->pld, resbuf, len); SYSCTL_ADD_OPAQUE( device_get_sysctl_ctx(dev), tree, OID_AUTO, "pldraw", CTLFLAG_RD | CTLFLAG_MPSAFE, port->pld, len, "A", "Raw PLD value"); if (usb_debug) { device_printf(dev, "Revision:%d\n", resbuf[0] & 0x7f); if ((resbuf[0] & 0x80) == 0) { device_printf(dev, "Color:#%02x%02x%02x\n", resbuf[1], resbuf[2], resbuf[3]); } device_printf(dev, "Width %d mm Height %d mm\n", resbuf[4] | (resbuf[5] << 8), resbuf[6] | (resbuf[7] << 8)); if (resbuf[8] & 1) { device_printf(dev, "Visible\n"); } if (resbuf[8] & 2) { device_printf(dev, "Dock\n"); } if (resbuf[8] & 4) { device_printf(dev, "Lid\n"); } device_printf(dev, "PanelPosition: %d\n", (resbuf[8] >> 3) & 7); device_printf(dev, "VertPosition: %d\n", (resbuf[8] >> 6) & 3); device_printf(dev, "HorizPosition: %d\n", (resbuf[9]) & 3); device_printf(dev, "Shape: %d\n", (resbuf[9] >> 2) & 0xf); device_printf(dev, "80: %02x, %02x, %02x\n", resbuf[9], resbuf[10], resbuf[11]); device_printf(dev, "96: %02x, %02x, %02x, %02x\n", resbuf[12], resbuf[13], resbuf[14], resbuf[15]); if ((resbuf[0] & 0x7f) >= 2) { device_printf(dev, "VOFF%d mm HOFF %dmm", resbuf[16] | (resbuf[17] << 8), resbuf[18] | (resbuf[19] << 8)); } } skip: AcpiOsFree(buf.Pointer); } return (0); } static ACPI_STATUS acpi_uhub_find_rh(device_t dev, ACPI_HANDLE *ah) { device_t grand; ACPI_HANDLE gah; *ah = NULL; grand = device_get_parent(device_get_parent(dev)); if ((gah = acpi_get_handle(grand)) == NULL) return (AE_ERROR); return (AcpiWalkNamespace(ACPI_TYPE_DEVICE, gah, 1, acpi_uhub_find_rh_cb, NULL, dev, ah)); } static ACPI_STATUS acpi_usb_hub_port_probe_cb(ACPI_HANDLE ah, UINT32 lv, void *ctx, void **rv) { ACPI_DEVICE_INFO *devinfo; device_t dev = ctx; struct acpi_uhub_softc *sc = device_get_softc(dev); UINT32 ret; ret = AcpiGetObjectInfo(ah, &devinfo); if (ACPI_SUCCESS(ret)) { if ((devinfo->Valid & ACPI_VALID_ADR) && (devinfo->Address > 0) && (devinfo->Address <= (uint64_t)sc->nports)) { char buf[] = "portXXX"; struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); struct sysctl_oid *oid; struct sysctl_oid_list *tree; snprintf(buf, sizeof(buf), "port%ju", (uintmax_t)devinfo->Address); oid = SYSCTL_ADD_NODE(ctx, - SYSCTL_CHILDREN( - device_get_sysctl_tree(dev)), - OID_AUTO, buf, CTLFLAG_RD, - NULL, "port nodes"); + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, buf, CTLFLAG_RD | CTLFLAG_MPSAFE, + NULL, "port nodes"); tree = SYSCTL_CHILDREN(oid); sc->port[devinfo->Address - 1].handle = ah; sc->port[devinfo->Address - 1].upc = 0xffffffff; acpi_uhub_parse_upc(dev, devinfo->Address, ah, tree); acpi_uhub_parse_pld(dev, devinfo->Address, ah, tree); - SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), - tree, OID_AUTO, "info", - CTLTYPE_STRING | CTLFLAG_RD, - &sc->port[devinfo->Address - 1], 0, - acpi_uhub_port_sysctl, - "A", "Port information"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), tree, + OID_AUTO, "info", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + &sc->port[devinfo->Address - 1], 0, + acpi_uhub_port_sysctl, "A", "Port information"); } AcpiOsFree(devinfo); } return (AE_OK); } static ACPI_STATUS acpi_usb_hub_port_probe(device_t dev, ACPI_HANDLE ah) { return (AcpiWalkNamespace(ACPI_TYPE_DEVICE, ah, 1, acpi_usb_hub_port_probe_cb, NULL, dev, NULL)); } static int acpi_uhub_root_probe(device_t dev) { ACPI_STATUS status; ACPI_HANDLE ah; if (acpi_disabled("usb")) return (ENXIO); status = acpi_uhub_find_rh(dev, &ah); if (ACPI_SUCCESS(status) && ah != NULL && uhub_probe(dev) <= 0) { /* success prior than non-ACPI USB HUB */ return (BUS_PROBE_DEFAULT + 1); } return (ENXIO); } static int acpi_uhub_probe(device_t dev) { ACPI_HANDLE ah; if (acpi_disabled("usb")) return (ENXIO); ah = acpi_get_handle(dev); if (ah == NULL) return (ENXIO); if (uhub_probe(dev) <= 0) { /* success prior than non-ACPI USB HUB */ return (BUS_PROBE_DEFAULT + 1); } return (ENXIO); } static int acpi_uhub_attach_common(device_t dev) { struct usb_hub *uh; struct acpi_uhub_softc *sc = device_get_softc(dev); ACPI_STATUS status; int ret = ENXIO; uh = sc->usc.sc_udev->hub; sc->nports = uh->nports; sc->port = malloc(sizeof(struct acpi_uhub_port) * uh->nports, M_USBDEV, M_WAITOK | M_ZERO); status = acpi_usb_hub_port_probe(dev, sc->ah); if (ACPI_SUCCESS(status)){ ret = 0; } return (ret); } static int acpi_uhub_detach(device_t dev) { struct acpi_uhub_softc *sc = device_get_softc(dev); free(sc->port, M_USBDEV); return (uhub_detach(dev)); } static int acpi_uhub_root_attach(device_t dev) { int ret; struct acpi_uhub_softc *sc = device_get_softc(dev); if (ACPI_FAILURE(acpi_uhub_find_rh(dev, &sc->ah)) || (sc->ah == NULL)) { return (ENXIO); } if ((ret = uhub_attach(dev)) != 0) { return (ret); } if ((ret = acpi_uhub_attach_common(dev)) != 0) { acpi_uhub_detach(dev); } return ret; } static int acpi_uhub_attach(device_t dev) { int ret; struct acpi_uhub_softc *sc = device_get_softc(dev); sc->ah = acpi_get_handle(dev); if (sc->ah == NULL) { return (ENXIO); } if ((ret = uhub_attach(dev)) != 0) { return (ret); } if ((ret = acpi_uhub_attach_common(dev)) != 0) { acpi_uhub_detach(dev); } return (ret); } static int acpi_uhub_read_ivar(device_t dev, device_t child, int idx, uintptr_t *res) { struct hub_result hres; struct acpi_uhub_softc *sc = device_get_softc(dev); ACPI_HANDLE ah; mtx_lock(&Giant); uhub_find_iface_index(sc->usc.sc_udev->hub, child, &hres); mtx_unlock(&Giant); if ((idx == ACPI_IVAR_HANDLE) && (hres.portno > 0) && (hres.portno <= sc->nports) && (ah = sc->port[hres.portno - 1].handle)) { *res = (uintptr_t)ah; return (0); } return (ENXIO); } static int acpi_uhub_child_location_string(device_t parent, device_t child, char *buf, size_t buflen) { ACPI_HANDLE ah; uhub_child_location_string(parent, child, buf, buflen); ah = acpi_get_handle(child); if (ah != NULL) { strlcat(buf, " handle=", buflen); strlcat(buf, acpi_name(ah), buflen); } return (0); } static device_method_t acpi_uhub_methods[] = { DEVMETHOD(device_probe, acpi_uhub_probe), DEVMETHOD(device_attach, acpi_uhub_attach), DEVMETHOD(device_detach, acpi_uhub_detach), DEVMETHOD(bus_child_location_str, acpi_uhub_child_location_string), DEVMETHOD(bus_read_ivar, acpi_uhub_read_ivar), DEVMETHOD_END }; static device_method_t acpi_uhub_root_methods[] = { DEVMETHOD(device_probe, acpi_uhub_root_probe), DEVMETHOD(device_attach, acpi_uhub_root_attach), DEVMETHOD(device_detach, acpi_uhub_detach), DEVMETHOD(bus_read_ivar, acpi_uhub_read_ivar), DEVMETHOD(bus_child_location_str, acpi_uhub_child_location_string), DEVMETHOD_END }; static devclass_t uhub_devclass; extern driver_t uhub_driver; static kobj_class_t uhub_baseclasses[] = {&uhub_driver, NULL}; static driver_t acpi_uhub_driver = { .name = "uhub", .methods = acpi_uhub_methods, .size = sizeof(struct acpi_uhub_softc), .baseclasses = uhub_baseclasses, }; static driver_t acpi_uhub_root_driver = { .name = "uhub", .methods = acpi_uhub_root_methods, .size = sizeof(struct acpi_uhub_softc), .baseclasses = uhub_baseclasses, }; DRIVER_MODULE(uacpi, uhub, acpi_uhub_driver, uhub_devclass, 0, 0); DRIVER_MODULE(uacpi, usbus, acpi_uhub_root_driver, uhub_devclass, 0, 0); MODULE_DEPEND(uacpi, acpi, 1, 1, 1); MODULE_DEPEND(uacpi, usb, 1, 1, 1); MODULE_VERSION(uacpi, 1); Index: head/sys/dev/usb/usb_process.c =================================================================== --- head/sys/dev/usb/usb_process.c (revision 357971) +++ head/sys/dev/usb/usb_process.c (revision 357972) @@ -1,518 +1,519 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. 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. * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_proc_debug #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #if (__FreeBSD_version < 700000) #define thread_lock(td) mtx_lock_spin(&sched_lock) #define thread_unlock(td) mtx_unlock_spin(&sched_lock) #endif #if (__FreeBSD_version >= 800000) static struct proc *usbproc; static int usb_pcount; #define USB_THREAD_CREATE(f, s, p, ...) \ kproc_kthread_add((f), (s), &usbproc, (p), RFHIGHPID, \ 0, "usb", __VA_ARGS__) #if (__FreeBSD_version >= 900000) #define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check() #else #define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check(curthread) #endif #define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) #define USB_THREAD_EXIT(err) kthread_exit() #else #define USB_THREAD_CREATE(f, s, p, ...) \ kthread_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) #define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check(curproc) #define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) #define USB_THREAD_EXIT(err) kthread_exit(err) #endif #ifdef USB_DEBUG static int usb_proc_debug; -static SYSCTL_NODE(_hw_usb, OID_AUTO, proc, CTLFLAG_RW, 0, "USB process"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, proc, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB process"); SYSCTL_INT(_hw_usb_proc, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_proc_debug, 0, "Debug level"); #endif /*------------------------------------------------------------------------* * usb_process * * This function is the USB process dispatcher. *------------------------------------------------------------------------*/ static void usb_process(void *arg) { struct usb_process *up = arg; struct usb_proc_msg *pm; struct thread *td; /* in case of attach error, check for suspended */ USB_THREAD_SUSPEND_CHECK(); /* adjust priority */ td = curthread; thread_lock(td); sched_prio(td, up->up_prio); thread_unlock(td); USB_MTX_LOCK(up->up_mtx); up->up_curtd = td; while (1) { if (up->up_gone) break; /* * NOTE to reimplementors: dequeueing a command from the * "used" queue and executing it must be atomic, with regard * to the "up_mtx" mutex. That means any attempt to queue a * command by another thread must be blocked until either: * * 1) the command sleeps * * 2) the command returns * * Here is a practical example that shows how this helps * solving a problem: * * Assume that you want to set the baud rate on a USB serial * device. During the programming of the device you don't * want to receive nor transmit any data, because it will be * garbage most likely anyway. The programming of our USB * device takes 20 milliseconds and it needs to call * functions that sleep. * * Non-working solution: Before we queue the programming * command, we stop transmission and reception of data. Then * we queue a programming command. At the end of the * programming command we enable transmission and reception * of data. * * Problem: If a second programming command is queued while the * first one is sleeping, we end up enabling transmission * and reception of data too early. * * Working solution: Before we queue the programming command, * we stop transmission and reception of data. Then we queue * a programming command. Then we queue a second command * that only enables transmission and reception of data. * * Why it works: If a second programming command is queued * while the first one is sleeping, then the queueing of a * second command to enable the data transfers, will cause * the previous one, which is still on the queue, to be * removed from the queue, and re-inserted after the last * baud rate programming command, which then gives the * desired result. */ pm = TAILQ_FIRST(&up->up_qhead); if (pm) { DPRINTF("Message pm=%p, cb=%p (enter)\n", pm, pm->pm_callback); (pm->pm_callback) (pm); if (pm == TAILQ_FIRST(&up->up_qhead)) { /* nothing changed */ TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry); pm->pm_qentry.tqe_prev = NULL; } DPRINTF("Message pm=%p (leave)\n", pm); continue; } /* end of messages - check if anyone is waiting for sync */ if (up->up_dsleep) { up->up_dsleep = 0; cv_broadcast(&up->up_drain); } up->up_msleep = 1; cv_wait(&up->up_cv, up->up_mtx); } up->up_ptr = NULL; cv_signal(&up->up_cv); USB_MTX_UNLOCK(up->up_mtx); #if (__FreeBSD_version >= 800000) /* Clear the proc pointer if this is the last thread. */ if (--usb_pcount == 0) usbproc = NULL; #endif USB_THREAD_EXIT(0); } /*------------------------------------------------------------------------* * usb_proc_create * * This function will create a process using the given "prio" that can * execute callbacks. The mutex pointed to by "p_mtx" will be applied * before calling the callbacks and released after that the callback * has returned. The structure pointed to by "up" is assumed to be * zeroed before this function is called. * * Return values: * 0: success * Else: failure *------------------------------------------------------------------------*/ int usb_proc_create(struct usb_process *up, struct mtx *p_mtx, const char *pmesg, uint8_t prio) { up->up_mtx = p_mtx; up->up_prio = prio; TAILQ_INIT(&up->up_qhead); cv_init(&up->up_cv, "-"); cv_init(&up->up_drain, "usbdrain"); if (USB_THREAD_CREATE(&usb_process, up, &up->up_ptr, "%s", pmesg)) { DPRINTFN(0, "Unable to create USB process."); up->up_ptr = NULL; goto error; } #if (__FreeBSD_version >= 800000) usb_pcount++; #endif return (0); error: usb_proc_free(up); return (ENOMEM); } /*------------------------------------------------------------------------* * usb_proc_free * * NOTE: If the structure pointed to by "up" is all zero, this * function does nothing. * * NOTE: Messages that are pending on the process queue will not be * removed nor called. *------------------------------------------------------------------------*/ void usb_proc_free(struct usb_process *up) { /* check if not initialised */ if (up->up_mtx == NULL) return; usb_proc_drain(up); cv_destroy(&up->up_cv); cv_destroy(&up->up_drain); /* make sure that we do not enter here again */ up->up_mtx = NULL; } /*------------------------------------------------------------------------* * usb_proc_msignal * * This function will queue one of the passed USB process messages on * the USB process queue. The first message that is not already queued * will get queued. If both messages are already queued the one queued * last will be removed from the queue and queued in the end. The USB * process mutex must be locked when calling this function. This * function exploits the fact that a process can only do one callback * at a time. The message that was queued is returned. *------------------------------------------------------------------------*/ void * usb_proc_msignal(struct usb_process *up, void *_pm0, void *_pm1) { struct usb_proc_msg *pm0 = _pm0; struct usb_proc_msg *pm1 = _pm1; struct usb_proc_msg *pm2; usb_size_t d; uint8_t t; /* check if gone or in polling mode, return dummy value */ if (up->up_gone != 0 || USB_IN_POLLING_MODE_FUNC() != 0) return (_pm0); USB_MTX_ASSERT(up->up_mtx, MA_OWNED); t = 0; if (pm0->pm_qentry.tqe_prev) { t |= 1; } if (pm1->pm_qentry.tqe_prev) { t |= 2; } if (t == 0) { /* * No entries are queued. Queue "pm0" and use the existing * message number. */ pm2 = pm0; } else if (t == 1) { /* Check if we need to increment the message number. */ if (pm0->pm_num == up->up_msg_num) { up->up_msg_num++; } pm2 = pm1; } else if (t == 2) { /* Check if we need to increment the message number. */ if (pm1->pm_num == up->up_msg_num) { up->up_msg_num++; } pm2 = pm0; } else if (t == 3) { /* * Both entries are queued. Re-queue the entry closest to * the end. */ d = (pm1->pm_num - pm0->pm_num); /* Check sign after subtraction */ if (d & 0x80000000) { pm2 = pm0; } else { pm2 = pm1; } TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry); } else { pm2 = NULL; /* panic - should not happen */ } DPRINTF(" t=%u, num=%u\n", t, up->up_msg_num); /* Put message last on queue */ pm2->pm_num = up->up_msg_num; TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry); /* Check if we need to wakeup the USB process. */ if (up->up_msleep) { up->up_msleep = 0; /* save "cv_signal()" calls */ cv_signal(&up->up_cv); } return (pm2); } /*------------------------------------------------------------------------* * usb_proc_is_gone * * Return values: * 0: USB process is running * Else: USB process is tearing down *------------------------------------------------------------------------*/ uint8_t usb_proc_is_gone(struct usb_process *up) { if (up->up_gone) return (1); /* * Allow calls when up_mtx is NULL, before the USB process * structure is initialised. */ if (up->up_mtx != NULL) USB_MTX_ASSERT(up->up_mtx, MA_OWNED); return (0); } /*------------------------------------------------------------------------* * usb_proc_mwait * * This function will return when the USB process message pointed to * by "pm" is no longer on a queue. This function must be called * having "up->up_mtx" locked. *------------------------------------------------------------------------*/ void usb_proc_mwait(struct usb_process *up, void *_pm0, void *_pm1) { struct usb_proc_msg *pm0 = _pm0; struct usb_proc_msg *pm1 = _pm1; /* check if gone */ if (up->up_gone) return; USB_MTX_ASSERT(up->up_mtx, MA_OWNED); if (up->up_curtd == curthread) { /* Just remove the messages from the queue. */ if (pm0->pm_qentry.tqe_prev) { TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry); pm0->pm_qentry.tqe_prev = NULL; } if (pm1->pm_qentry.tqe_prev) { TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry); pm1->pm_qentry.tqe_prev = NULL; } } else while (pm0->pm_qentry.tqe_prev || pm1->pm_qentry.tqe_prev) { /* check if config thread is gone */ if (up->up_gone) break; up->up_dsleep = 1; cv_wait(&up->up_drain, up->up_mtx); } } /*------------------------------------------------------------------------* * usb_proc_drain * * This function will tear down an USB process, waiting for the * currently executing command to return. * * NOTE: If the structure pointed to by "up" is all zero, * this function does nothing. *------------------------------------------------------------------------*/ void usb_proc_drain(struct usb_process *up) { /* check if not initialised */ if (up->up_mtx == NULL) return; /* handle special case with Giant */ if (up->up_mtx != &Giant) USB_MTX_ASSERT(up->up_mtx, MA_NOTOWNED); USB_MTX_LOCK(up->up_mtx); /* Set the gone flag */ up->up_gone = 1; while (up->up_ptr) { /* Check if we need to wakeup the USB process */ if (up->up_msleep || up->up_csleep) { up->up_msleep = 0; up->up_csleep = 0; cv_signal(&up->up_cv); } #ifndef EARLY_AP_STARTUP /* Check if we are still cold booted */ if (cold) { USB_THREAD_SUSPEND(up->up_ptr); printf("WARNING: A USB process has " "been left suspended\n"); break; } #endif cv_wait(&up->up_cv, up->up_mtx); } /* Check if someone is waiting - should not happen */ if (up->up_dsleep) { up->up_dsleep = 0; cv_broadcast(&up->up_drain); DPRINTF("WARNING: Someone is waiting " "for USB process drain!\n"); } USB_MTX_UNLOCK(up->up_mtx); } /*------------------------------------------------------------------------* * usb_proc_rewakeup * * This function is called to re-wakeup the given USB * process. This usually happens after that the USB system has been in * polling mode, like during a panic. This function must be called * having "up->up_mtx" locked. *------------------------------------------------------------------------*/ void usb_proc_rewakeup(struct usb_process *up) { /* check if not initialised */ if (up->up_mtx == NULL) return; /* check if gone */ if (up->up_gone) return; USB_MTX_ASSERT(up->up_mtx, MA_OWNED); if (up->up_msleep == 0) { /* re-wakeup */ cv_signal(&up->up_cv); } } /*------------------------------------------------------------------------* * usb_proc_is_called_from * * This function will return non-zero if called from inside the USB * process passed as first argument. Else this function returns zero. *------------------------------------------------------------------------*/ int usb_proc_is_called_from(struct usb_process *up) { return (up->up_curtd == curthread); } Index: head/sys/dev/usb/video/udl.c =================================================================== --- head/sys/dev/usb/video/udl.c (revision 357971) +++ head/sys/dev/usb/video/udl.c (revision 357972) @@ -1,1154 +1,1155 @@ /* $OpenBSD: udl.c,v 1.81 2014/12/09 07:05:06 doug Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 2015 Hans Petter Selasky * Copyright (c) 2009 Marcus Glocker * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Driver for the "DisplayLink DL-120 / DL-160" graphic chips based on * the reversed engineered specifications of Florian Echtler * : * * http://floe.butterbrot.org/displaylink/doku.php */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include "fb_if.h" #undef DPRINTF #undef DPRINTFN #define USB_DEBUG_VAR udl_debug #include -static SYSCTL_NODE(_hw_usb, OID_AUTO, udl, CTLFLAG_RW, 0, "USB UDL"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, udl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB UDL"); #ifdef USB_DEBUG static int udl_debug = 0; SYSCTL_INT(_hw_usb_udl, OID_AUTO, debug, CTLFLAG_RWTUN, &udl_debug, 0, "Debug level"); #endif #define UDL_FPS_MAX 60 #define UDL_FPS_MIN 1 static int udl_fps = 25; SYSCTL_INT(_hw_usb_udl, OID_AUTO, fps, CTLFLAG_RWTUN, &udl_fps, 0, "Frames Per Second, 1-60"); static struct mtx udl_buffer_mtx; static struct udl_buffer_head udl_buffer_head; MALLOC_DEFINE(M_USB_DL, "USB", "USB DisplayLink"); /* * Prototypes. */ static usb_callback_t udl_bulk_write_callback; static device_probe_t udl_probe; static device_attach_t udl_attach; static device_detach_t udl_detach; static fb_getinfo_t udl_fb_getinfo; static fb_setblankmode_t udl_fb_setblankmode; static void udl_select_chip(struct udl_softc *, struct usb_attach_arg *); static int udl_init_chip(struct udl_softc *); static void udl_select_mode(struct udl_softc *); static int udl_init_resolution(struct udl_softc *); static void udl_fbmem_alloc(struct udl_softc *); static int udl_cmd_write_buf_le16(struct udl_softc *, const uint8_t *, uint32_t, uint8_t, int); static int udl_cmd_buf_copy_le16(struct udl_softc *, uint32_t, uint32_t, uint8_t, int); static void udl_cmd_insert_int_1(struct udl_cmd_buf *, uint8_t); static void udl_cmd_insert_int_3(struct udl_cmd_buf *, uint32_t); static void udl_cmd_insert_buf_le16(struct udl_cmd_buf *, const uint8_t *, uint32_t); static void udl_cmd_write_reg_1(struct udl_cmd_buf *, uint8_t, uint8_t); static void udl_cmd_write_reg_3(struct udl_cmd_buf *, uint8_t, uint32_t); static int udl_power_save(struct udl_softc *, int, int); static const struct usb_config udl_config[UDL_N_TRANSFER] = { [UDL_BULK_WRITE_0] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, .bufsize = UDL_CMD_MAX_DATA_SIZE * UDL_CMD_MAX_FRAMES, .callback = &udl_bulk_write_callback, .frames = UDL_CMD_MAX_FRAMES, .timeout = 5000, /* 5 seconds */ }, [UDL_BULK_WRITE_1] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, .bufsize = UDL_CMD_MAX_DATA_SIZE * UDL_CMD_MAX_FRAMES, .callback = &udl_bulk_write_callback, .frames = UDL_CMD_MAX_FRAMES, .timeout = 5000, /* 5 seconds */ }, }; /* * Driver glue. */ static devclass_t udl_devclass; static device_method_t udl_methods[] = { DEVMETHOD(device_probe, udl_probe), DEVMETHOD(device_attach, udl_attach), DEVMETHOD(device_detach, udl_detach), DEVMETHOD(fb_getinfo, udl_fb_getinfo), DEVMETHOD_END }; static driver_t udl_driver = { .name = "udl", .methods = udl_methods, .size = sizeof(struct udl_softc), }; DRIVER_MODULE(udl, uhub, udl_driver, udl_devclass, NULL, NULL); MODULE_DEPEND(udl, usb, 1, 1, 1); MODULE_DEPEND(udl, fbd, 1, 1, 1); MODULE_DEPEND(udl, videomode, 1, 1, 1); MODULE_VERSION(udl, 1); /* * Matching devices. */ static const STRUCT_USB_HOST_ID udl_devs[] = { {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD4300U, DL120)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD8000U, DL120)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_GUC2020, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LD220, DL165)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VCUD60, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DLDVI, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VGA10, DL120)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_WSDVI, DLUNK)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_EC008, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_HPDOCK, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_NL571, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_M01061, DL195)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_NBDOCK, DL165)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_SWDVI, DLUNK)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_UM7X0, DL120)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_CONV, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_PLUGABLE, DL160)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LUM70, DL125)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_POLARIS2, DLUNK)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LT1421, DLUNK)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_ITEC, DL165)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DVI_19, DL165)}, }; static void udl_buffer_init(void *arg) { mtx_init(&udl_buffer_mtx, "USB", "UDL", MTX_DEF); TAILQ_INIT(&udl_buffer_head); } SYSINIT(udl_buffer_init, SI_SUB_LOCK, SI_ORDER_FIRST, udl_buffer_init, NULL); CTASSERT(sizeof(struct udl_buffer) < PAGE_SIZE); static void * udl_buffer_alloc(uint32_t size) { struct udl_buffer *buf; mtx_lock(&udl_buffer_mtx); TAILQ_FOREACH(buf, &udl_buffer_head, entry) { if (buf->size == size) { TAILQ_REMOVE(&udl_buffer_head, buf, entry); break; } } mtx_unlock(&udl_buffer_mtx); if (buf != NULL) { uint8_t *ptr = ((uint8_t *)buf) - size; /* wipe and recycle buffer */ memset(ptr, 0, size); /* return buffer pointer */ return (ptr); } /* allocate new buffer */ return (malloc(size + sizeof(*buf), M_USB_DL, M_WAITOK | M_ZERO)); } static void udl_buffer_free(void *_buf, uint32_t size) { struct udl_buffer *buf; /* check for NULL pointer */ if (_buf == NULL) return; /* compute pointer to recycle list */ buf = (struct udl_buffer *)(((uint8_t *)_buf) + size); /* * Memory mapped buffers should never be freed. * Put display buffer into a recycle list. */ mtx_lock(&udl_buffer_mtx); buf->size = size; TAILQ_INSERT_TAIL(&udl_buffer_head, buf, entry); mtx_unlock(&udl_buffer_mtx); } static uint32_t udl_get_fb_size(struct udl_softc *sc) { unsigned i = sc->sc_cur_mode; return ((uint32_t)udl_modes[i].hdisplay * (uint32_t)udl_modes[i].vdisplay * 2); } static uint32_t udl_get_fb_width(struct udl_softc *sc) { unsigned i = sc->sc_cur_mode; return (udl_modes[i].hdisplay); } static uint32_t udl_get_fb_height(struct udl_softc *sc) { unsigned i = sc->sc_cur_mode; return (udl_modes[i].vdisplay); } static uint32_t udl_get_fb_hz(struct udl_softc *sc) { unsigned i = sc->sc_cur_mode; return (udl_modes[i].hz); } static void udl_callout(void *arg) { struct udl_softc *sc = arg; const uint32_t max = udl_get_fb_size(sc); int fps; if (sc->sc_power_save == 0) { fps = udl_fps; /* figure out number of frames per second */ if (fps < UDL_FPS_MIN) fps = UDL_FPS_MIN; else if (fps > UDL_FPS_MAX) fps = UDL_FPS_MAX; if (sc->sc_sync_off >= max) sc->sc_sync_off = 0; usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_0]); usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_1]); } else { fps = 1; } callout_reset(&sc->sc_callout, hz / fps, &udl_callout, sc); } static int udl_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(udl_devs, sizeof(udl_devs), uaa)); } static int udl_attach(device_t dev) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); struct sysctl_oid *tree = device_get_sysctl_tree(dev); struct udl_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); int error; int i; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "UDL lock", NULL, MTX_DEF); cv_init(&sc->sc_cv, "UDLCV"); callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); sc->sc_udev = uaa->device; error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, udl_config, UDL_N_TRANSFER, sc, &sc->sc_mtx); if (error) { DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); goto detach; } usbd_xfer_set_priv(sc->sc_xfer[UDL_BULK_WRITE_0], &sc->sc_xfer_head[0]); usbd_xfer_set_priv(sc->sc_xfer[UDL_BULK_WRITE_1], &sc->sc_xfer_head[1]); TAILQ_INIT(&sc->sc_xfer_head[0]); TAILQ_INIT(&sc->sc_xfer_head[1]); TAILQ_INIT(&sc->sc_cmd_buf_free); TAILQ_INIT(&sc->sc_cmd_buf_pending); sc->sc_def_chip = -1; sc->sc_chip = USB_GET_DRIVER_INFO(uaa); sc->sc_def_mode = -1; sc->sc_cur_mode = UDL_MAX_MODES; /* Allow chip ID to be overwritten */ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "chipid_force", CTLFLAG_RWTUN, &sc->sc_def_chip, 0, "chip ID"); /* Export current chip ID */ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "chipid", CTLFLAG_RD, &sc->sc_chip, 0, "chip ID"); if (sc->sc_def_chip > -1 && sc->sc_def_chip <= DLMAX) { device_printf(dev, "Forcing chip ID to 0x%04x\n", sc->sc_def_chip); sc->sc_chip = sc->sc_def_chip; } /* * The product might have more than one chip */ if (sc->sc_chip == DLUNK) udl_select_chip(sc, uaa); for (i = 0; i != UDL_CMD_MAX_BUFFERS; i++) { struct udl_cmd_buf *cb = &sc->sc_cmd_buf_temp[i]; TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_free, cb, entry); } /* * Initialize chip. */ error = udl_init_chip(sc); if (error != USB_ERR_NORMAL_COMPLETION) goto detach; /* * Select edid mode. */ udl_select_mode(sc); /* Allow default mode to be overwritten */ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mode_force", CTLFLAG_RWTUN, &sc->sc_def_mode, 0, "mode"); /* Export current mode */ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mode", CTLFLAG_RD, &sc->sc_cur_mode, 0, "mode"); i = sc->sc_def_mode; if (i > -1 && i < UDL_MAX_MODES) { if (udl_modes[i].chip <= sc->sc_chip) { device_printf(dev, "Forcing mode to %d\n", i); sc->sc_cur_mode = i; } } /* Printout current mode */ device_printf(dev, "Mode selected %dx%d @ %dHz\n", (int)udl_get_fb_width(sc), (int)udl_get_fb_height(sc), (int)udl_get_fb_hz(sc)); udl_init_resolution(sc); /* Allocate frame buffer */ udl_fbmem_alloc(sc); UDL_LOCK(sc); udl_callout(sc); UDL_UNLOCK(sc); sc->sc_fb_info.fb_name = device_get_nameunit(dev); sc->sc_fb_info.fb_size = sc->sc_fb_size; sc->sc_fb_info.fb_bpp = 16; sc->sc_fb_info.fb_depth = 16; sc->sc_fb_info.fb_width = udl_get_fb_width(sc); sc->sc_fb_info.fb_height = udl_get_fb_height(sc); sc->sc_fb_info.fb_stride = sc->sc_fb_info.fb_width * 2; sc->sc_fb_info.fb_pbase = 0; sc->sc_fb_info.fb_vbase = (uintptr_t)sc->sc_fb_addr; sc->sc_fb_info.fb_priv = sc; sc->sc_fb_info.setblankmode = &udl_fb_setblankmode; sc->sc_fbdev = device_add_child(dev, "fbd", -1); if (sc->sc_fbdev == NULL) goto detach; if (device_probe_and_attach(sc->sc_fbdev) != 0) goto detach; return (0); detach: udl_detach(dev); return (ENXIO); } static int udl_detach(device_t dev) { struct udl_softc *sc = device_get_softc(dev); /* delete all child devices */ device_delete_children(dev); UDL_LOCK(sc); sc->sc_gone = 1; callout_stop(&sc->sc_callout); UDL_UNLOCK(sc); usbd_transfer_unsetup(sc->sc_xfer, UDL_N_TRANSFER); callout_drain(&sc->sc_callout); mtx_destroy(&sc->sc_mtx); cv_destroy(&sc->sc_cv); /* put main framebuffer into a recycle list, if any */ udl_buffer_free(sc->sc_fb_addr, sc->sc_fb_size); /* free shadow framebuffer memory, if any */ free(sc->sc_fb_copy, M_USB_DL); return (0); } static struct fb_info * udl_fb_getinfo(device_t dev) { struct udl_softc *sc = device_get_softc(dev); return (&sc->sc_fb_info); } static int udl_fb_setblankmode(void *arg, int mode) { struct udl_softc *sc = arg; switch (mode) { case V_DISPLAY_ON: udl_power_save(sc, 1, M_WAITOK); break; case V_DISPLAY_BLANK: udl_power_save(sc, 1, M_WAITOK); if (sc->sc_fb_addr != 0) { const uint32_t max = udl_get_fb_size(sc); memset((void *)sc->sc_fb_addr, 0, max); } break; case V_DISPLAY_STAND_BY: case V_DISPLAY_SUSPEND: udl_power_save(sc, 0, M_WAITOK); break; } return (0); } static struct udl_cmd_buf * udl_cmd_buf_alloc_locked(struct udl_softc *sc, int flags) { struct udl_cmd_buf *cb; while ((cb = TAILQ_FIRST(&sc->sc_cmd_buf_free)) == NULL) { if (flags != M_WAITOK) break; cv_wait(&sc->sc_cv, &sc->sc_mtx); } if (cb != NULL) { TAILQ_REMOVE(&sc->sc_cmd_buf_free, cb, entry); cb->off = 0; } return (cb); } static struct udl_cmd_buf * udl_cmd_buf_alloc(struct udl_softc *sc, int flags) { struct udl_cmd_buf *cb; UDL_LOCK(sc); cb = udl_cmd_buf_alloc_locked(sc, flags); UDL_UNLOCK(sc); return (cb); } static void udl_cmd_buf_send(struct udl_softc *sc, struct udl_cmd_buf *cb) { UDL_LOCK(sc); if (sc->sc_gone) { TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_free, cb, entry); } else { /* mark end of command stack */ udl_cmd_insert_int_1(cb, UDL_BULK_SOC); udl_cmd_insert_int_1(cb, UDL_BULK_CMD_EOC); TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_pending, cb, entry); usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_0]); usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_1]); } UDL_UNLOCK(sc); } static struct udl_cmd_buf * udl_fb_synchronize_locked(struct udl_softc *sc) { const uint32_t max = udl_get_fb_size(sc); /* check if framebuffer is not ready */ if (sc->sc_fb_addr == NULL || sc->sc_fb_copy == NULL) return (NULL); while (sc->sc_sync_off < max) { uint32_t delta = max - sc->sc_sync_off; if (delta > UDL_CMD_MAX_PIXEL_COUNT * 2) delta = UDL_CMD_MAX_PIXEL_COUNT * 2; if (bcmp(sc->sc_fb_addr + sc->sc_sync_off, sc->sc_fb_copy + sc->sc_sync_off, delta) != 0) { struct udl_cmd_buf *cb; cb = udl_cmd_buf_alloc_locked(sc, M_NOWAIT); if (cb == NULL) goto done; memcpy(sc->sc_fb_copy + sc->sc_sync_off, sc->sc_fb_addr + sc->sc_sync_off, delta); udl_cmd_insert_int_1(cb, UDL_BULK_SOC); udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD); udl_cmd_insert_int_3(cb, sc->sc_sync_off); udl_cmd_insert_int_1(cb, delta / 2); udl_cmd_insert_buf_le16(cb, sc->sc_fb_copy + sc->sc_sync_off, delta); sc->sc_sync_off += delta; return (cb); } else { sc->sc_sync_off += delta; } } done: return (NULL); } static void udl_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct udl_softc *sc = usbd_xfer_softc(xfer); struct udl_cmd_head *phead = usbd_xfer_get_priv(xfer); struct udl_cmd_buf *cb; unsigned i; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: TAILQ_CONCAT(&sc->sc_cmd_buf_free, phead, entry); case USB_ST_SETUP: tr_setup: for (i = 0; i != UDL_CMD_MAX_FRAMES; i++) { cb = TAILQ_FIRST(&sc->sc_cmd_buf_pending); if (cb == NULL) { cb = udl_fb_synchronize_locked(sc); if (cb == NULL) break; } else { TAILQ_REMOVE(&sc->sc_cmd_buf_pending, cb, entry); } TAILQ_INSERT_TAIL(phead, cb, entry); usbd_xfer_set_frame_data(xfer, i, cb->buf, cb->off); } if (i != 0) { usbd_xfer_set_frames(xfer, i); usbd_transfer_submit(xfer); } break; default: TAILQ_CONCAT(&sc->sc_cmd_buf_free, phead, entry); if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } /* wakeup any waiters */ cv_signal(&sc->sc_cv); } static int udl_power_save(struct udl_softc *sc, int on, int flags) { struct udl_cmd_buf *cb; /* get new buffer */ cb = udl_cmd_buf_alloc(sc, flags); if (cb == NULL) return (EAGAIN); DPRINTF("screen %s\n", on ? "ON" : "OFF"); sc->sc_power_save = on ? 0 : 1; if (on) udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_ON); else udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_OFF); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); udl_cmd_buf_send(sc, cb); return (0); } static int udl_ctrl_msg(struct udl_softc *sc, uint8_t rt, uint8_t r, uint16_t index, uint16_t value, uint8_t *buf, size_t len) { usb_device_request_t req; int error; req.bmRequestType = rt; req.bRequest = r; USETW(req.wIndex, index); USETW(req.wValue, value); USETW(req.wLength, len); error = usbd_do_request_flags(sc->sc_udev, NULL, &req, buf, 0, NULL, USB_DEFAULT_TIMEOUT); DPRINTF("%s\n", usbd_errstr(error)); return (error); } static int udl_poll(struct udl_softc *sc, uint32_t *buf) { uint32_t lbuf; int error; error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, UDL_CTRL_CMD_POLL, 0x0000, 0x0000, (uint8_t *)&lbuf, sizeof(lbuf)); if (error == USB_ERR_NORMAL_COMPLETION) *buf = le32toh(lbuf); return (error); } static int udl_read_1(struct udl_softc *sc, uint16_t addr, uint8_t *buf) { uint8_t lbuf[1]; int error; error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, UDL_CTRL_CMD_READ_1, addr, 0x0000, lbuf, 1); if (error == USB_ERR_NORMAL_COMPLETION) *buf = *(uint8_t *)lbuf; return (error); } static int udl_write_1(struct udl_softc *sc, uint16_t addr, uint8_t buf) { int error; error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE, UDL_CTRL_CMD_WRITE_1, addr, 0x0000, &buf, 1); return (error); } static int udl_read_edid(struct udl_softc *sc, uint8_t *buf) { uint8_t lbuf[64]; uint16_t offset; int error; offset = 0; error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64); if (error != USB_ERR_NORMAL_COMPLETION) goto fail; bcopy(lbuf + 1, buf + offset, 63); offset += 63; error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64); if (error != USB_ERR_NORMAL_COMPLETION) goto fail; bcopy(lbuf + 1, buf + offset, 63); offset += 63; error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 3); if (error != USB_ERR_NORMAL_COMPLETION) goto fail; bcopy(lbuf + 1, buf + offset, 2); fail: return (error); } static uint8_t udl_lookup_mode(uint16_t hdisplay, uint16_t vdisplay, uint8_t hz, uint16_t chip, uint32_t clock) { uint8_t idx; /* * Check first if we have a matching mode with pixelclock */ for (idx = 0; idx != UDL_MAX_MODES; idx++) { if ((udl_modes[idx].hdisplay == hdisplay) && (udl_modes[idx].vdisplay == vdisplay) && (udl_modes[idx].clock == clock) && (udl_modes[idx].chip <= chip)) { return (idx); } } /* * If not, check for matching mode with update frequency */ for (idx = 0; idx != UDL_MAX_MODES; idx++) { if ((udl_modes[idx].hdisplay == hdisplay) && (udl_modes[idx].vdisplay == vdisplay) && (udl_modes[idx].hz == hz) && (udl_modes[idx].chip <= chip)) { return (idx); } } return (idx); } static void udl_select_chip(struct udl_softc *sc, struct usb_attach_arg *uaa) { const char *pserial; pserial = usb_get_serial(uaa->device); sc->sc_chip = DL120; if ((uaa->info.idVendor == USB_VENDOR_DISPLAYLINK) && (uaa->info.idProduct == USB_PRODUCT_DISPLAYLINK_WSDVI)) { /* * WS Tech DVI is DL120 or DL160. All deviced uses the * same revision (0.04) so iSerialNumber must be used * to determin which chip it is. */ if (strlen(pserial) > 7) { if (strncmp(pserial, "0198-13", 7) == 0) sc->sc_chip = DL160; } DPRINTF("iSerialNumber (%s) used to select chip (%d)\n", pserial, sc->sc_chip); } if ((uaa->info.idVendor == USB_VENDOR_DISPLAYLINK) && (uaa->info.idProduct == USB_PRODUCT_DISPLAYLINK_SWDVI)) { /* * SUNWEIT DVI is DL160, DL125, DL165 or DL195. Major revision * can be used to differ between DL1x0 and DL1x5. Minor to * differ between DL1x5. iSerialNumber seems not to be uniqe. */ sc->sc_chip = DL160; if (uaa->info.bcdDevice >= 0x100) { sc->sc_chip = DL165; if (uaa->info.bcdDevice == 0x104) sc->sc_chip = DL195; if (uaa->info.bcdDevice == 0x108) sc->sc_chip = DL125; } DPRINTF("bcdDevice (%02x) used to select chip (%d)\n", uaa->info.bcdDevice, sc->sc_chip); } } static int udl_set_enc_key(struct udl_softc *sc, uint8_t *buf, uint8_t len) { int error; error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE, UDL_CTRL_CMD_SET_KEY, 0x0000, 0x0000, buf, len); return (error); } static void udl_fbmem_alloc(struct udl_softc *sc) { uint32_t size; size = udl_get_fb_size(sc); size = round_page(size); /* check for zero size */ if (size == 0) size = PAGE_SIZE; /* * It is assumed that allocations above PAGE_SIZE bytes will * be PAGE_SIZE aligned for use with mmap() */ sc->sc_fb_addr = udl_buffer_alloc(size); sc->sc_fb_copy = malloc(size, M_USB_DL, M_WAITOK | M_ZERO); sc->sc_fb_size = size; } static void udl_cmd_insert_int_1(struct udl_cmd_buf *cb, uint8_t value) { cb->buf[cb->off] = value; cb->off += 1; } #if 0 static void udl_cmd_insert_int_2(struct udl_cmd_buf *cb, uint16_t value) { uint16_t lvalue; lvalue = htobe16(value); bcopy(&lvalue, cb->buf + cb->off, 2); cb->off += 2; } #endif static void udl_cmd_insert_int_3(struct udl_cmd_buf *cb, uint32_t value) { uint32_t lvalue; #if BYTE_ORDER == BIG_ENDIAN lvalue = htobe32(value) << 8; #else lvalue = htobe32(value) >> 8; #endif bcopy(&lvalue, cb->buf + cb->off, 3); cb->off += 3; } #if 0 static void udl_cmd_insert_int_4(struct udl_cmd_buf *cb, uint32_t value) { uint32_t lvalue; lvalue = htobe32(value); bcopy(&lvalue, cb->buf + cb->off, 4); cb->off += 4; } #endif static void udl_cmd_insert_buf_le16(struct udl_cmd_buf *cb, const uint8_t *buf, uint32_t len) { uint32_t x; for (x = 0; x != len; x += 2) { /* byte swap from little endian to big endian */ cb->buf[cb->off + x + 0] = buf[x + 1]; cb->buf[cb->off + x + 1] = buf[x + 0]; } cb->off += len; } static void udl_cmd_write_reg_1(struct udl_cmd_buf *cb, uint8_t reg, uint8_t val) { udl_cmd_insert_int_1(cb, UDL_BULK_SOC); udl_cmd_insert_int_1(cb, UDL_BULK_CMD_REG_WRITE_1); udl_cmd_insert_int_1(cb, reg); udl_cmd_insert_int_1(cb, val); } static void udl_cmd_write_reg_3(struct udl_cmd_buf *cb, uint8_t reg, uint32_t val) { udl_cmd_write_reg_1(cb, reg + 0, (val >> 16) & 0xff); udl_cmd_write_reg_1(cb, reg + 1, (val >> 8) & 0xff); udl_cmd_write_reg_1(cb, reg + 2, (val >> 0) & 0xff); } static int udl_init_chip(struct udl_softc *sc) { uint32_t ui32; uint8_t ui8; int error; error = udl_poll(sc, &ui32); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("poll=0x%08x\n", ui32); /* Some products may use later chip too */ switch (ui32 & 0xff) { case 0xf1: /* DL1x5 */ switch (sc->sc_chip) { case DL120: sc->sc_chip = DL125; break; case DL160: sc->sc_chip = DL165; break; } break; } DPRINTF("chip 0x%04x\n", sc->sc_chip); error = udl_read_1(sc, 0xc484, &ui8); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("read 0x%02x from 0xc484\n", ui8); error = udl_write_1(sc, 0xc41f, 0x01); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("write 0x01 to 0xc41f\n"); error = udl_read_edid(sc, sc->sc_edid); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("read EDID\n"); error = udl_set_enc_key(sc, __DECONST(void *, udl_null_key_1), sizeof(udl_null_key_1)); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("set encryption key\n"); error = udl_write_1(sc, 0xc40b, 0x00); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("write 0x00 to 0xc40b\n"); return (USB_ERR_NORMAL_COMPLETION); } static void udl_init_fb_offsets(struct udl_cmd_buf *cb, uint32_t start16, uint32_t stride16, uint32_t start8, uint32_t stride8) { udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0x00); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_START16, start16); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_STRIDE16, stride16); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_START8, start8); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_STRIDE8, stride8); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); } static int udl_init_resolution(struct udl_softc *sc) { const uint32_t max = udl_get_fb_size(sc); const uint8_t *buf = udl_modes[sc->sc_cur_mode].mode; struct udl_cmd_buf *cb; uint32_t delta; uint32_t i; int error; /* get new buffer */ cb = udl_cmd_buf_alloc(sc, M_WAITOK); if (cb == NULL) return (EAGAIN); /* write resolution values and set video memory offsets */ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0x00); for (i = 0; i < UDL_MODE_SIZE; i++) udl_cmd_write_reg_1(cb, i, buf[i]); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); udl_init_fb_offsets(cb, 0x000000, 0x000a00, 0x555555, 0x000500); udl_cmd_buf_send(sc, cb); /* fill screen with black color */ for (i = 0; i < max; i += delta) { static const uint8_t udl_black[UDL_CMD_MAX_PIXEL_COUNT * 2] __aligned(4); delta = max - i; if (delta > UDL_CMD_MAX_PIXEL_COUNT * 2) delta = UDL_CMD_MAX_PIXEL_COUNT * 2; if (i == 0) error = udl_cmd_write_buf_le16(sc, udl_black, i, delta / 2, M_WAITOK); else error = udl_cmd_buf_copy_le16(sc, 0, i, delta / 2, M_WAITOK); if (error) return (error); } /* get new buffer */ cb = udl_cmd_buf_alloc(sc, M_WAITOK); if (cb == NULL) return (EAGAIN); /* show framebuffer content */ udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_ON); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); udl_cmd_buf_send(sc, cb); return (0); } static void udl_select_mode(struct udl_softc *sc) { struct udl_mode mode; int index = UDL_MAX_MODES; int i; /* try to get the preferred mode from EDID */ edid_parse(sc->sc_edid, &sc->sc_edid_info); #ifdef USB_DEBUG edid_print(&sc->sc_edid_info); #endif if (sc->sc_edid_info.edid_preferred_mode != NULL) { mode.hz = (sc->sc_edid_info.edid_preferred_mode->dot_clock * 1000) / (sc->sc_edid_info.edid_preferred_mode->htotal * sc->sc_edid_info.edid_preferred_mode->vtotal); mode.clock = sc->sc_edid_info.edid_preferred_mode->dot_clock / 10; mode.hdisplay = sc->sc_edid_info.edid_preferred_mode->hdisplay; mode.vdisplay = sc->sc_edid_info.edid_preferred_mode->vdisplay; index = udl_lookup_mode(mode.hdisplay, mode.vdisplay, mode.hz, sc->sc_chip, mode.clock); sc->sc_cur_mode = index; } else { DPRINTF("no preferred mode found!\n"); } if (index == UDL_MAX_MODES) { DPRINTF("no mode line found\n"); i = 0; while (i < sc->sc_edid_info.edid_nmodes) { mode.hz = (sc->sc_edid_info.edid_modes[i].dot_clock * 1000) / (sc->sc_edid_info.edid_modes[i].htotal * sc->sc_edid_info.edid_modes[i].vtotal); mode.clock = sc->sc_edid_info.edid_modes[i].dot_clock / 10; mode.hdisplay = sc->sc_edid_info.edid_modes[i].hdisplay; mode.vdisplay = sc->sc_edid_info.edid_modes[i].vdisplay; index = udl_lookup_mode(mode.hdisplay, mode.vdisplay, mode.hz, sc->sc_chip, mode.clock); if (index < UDL_MAX_MODES) if ((sc->sc_cur_mode == UDL_MAX_MODES) || (index > sc->sc_cur_mode)) sc->sc_cur_mode = index; i++; } } /* * If no mode found use default. */ if (sc->sc_cur_mode == UDL_MAX_MODES) sc->sc_cur_mode = udl_lookup_mode(800, 600, 60, sc->sc_chip, 0); } static int udl_cmd_write_buf_le16(struct udl_softc *sc, const uint8_t *buf, uint32_t off, uint8_t pixels, int flags) { struct udl_cmd_buf *cb; cb = udl_cmd_buf_alloc(sc, flags); if (cb == NULL) return (EAGAIN); udl_cmd_insert_int_1(cb, UDL_BULK_SOC); udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD); udl_cmd_insert_int_3(cb, off); udl_cmd_insert_int_1(cb, pixels); udl_cmd_insert_buf_le16(cb, buf, 2 * pixels); udl_cmd_buf_send(sc, cb); return (0); } static int udl_cmd_buf_copy_le16(struct udl_softc *sc, uint32_t src, uint32_t dst, uint8_t pixels, int flags) { struct udl_cmd_buf *cb; cb = udl_cmd_buf_alloc(sc, flags); if (cb == NULL) return (EAGAIN); udl_cmd_insert_int_1(cb, UDL_BULK_SOC); udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_COPY | UDL_BULK_CMD_FB_WORD); udl_cmd_insert_int_3(cb, dst); udl_cmd_insert_int_1(cb, pixels); udl_cmd_insert_int_3(cb, src); udl_cmd_buf_send(sc, cb); return (0); } Index: head/sys/dev/usb/wlan/if_rsu.c =================================================================== --- head/sys/dev/usb/wlan/if_rsu.c (revision 357971) +++ head/sys/dev/usb/wlan/if_rsu.c (revision 357972) @@ -1,3765 +1,3766 @@ /* $OpenBSD: if_rsu.c,v 1.17 2013/04/15 09:23:01 mglocker Exp $ */ /*- * Copyright (c) 2010 Damien Bergamini * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for Realtek RTL8188SU/RTL8191SU/RTL8192SU. * * TODO: * o tx a-mpdu * o hostap / ibss / mesh * o power-save operation */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include /* XXX */ #include #define RSU_RATE_IS_CCK RTWN_RATE_IS_CCK #ifdef USB_DEBUG static int rsu_debug = 0; -SYSCTL_NODE(_hw_usb, OID_AUTO, rsu, CTLFLAG_RW, 0, "USB rsu"); +SYSCTL_NODE(_hw_usb, OID_AUTO, rsu, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB rsu"); SYSCTL_INT(_hw_usb_rsu, OID_AUTO, debug, CTLFLAG_RWTUN, &rsu_debug, 0, "Debug level"); #define RSU_DPRINTF(_sc, _flg, ...) \ do \ if (((_flg) == (RSU_DEBUG_ANY)) || (rsu_debug & (_flg))) \ device_printf((_sc)->sc_dev, __VA_ARGS__); \ while (0) #else #define RSU_DPRINTF(_sc, _flg, ...) #endif static int rsu_enable_11n = 1; TUNABLE_INT("hw.usb.rsu.enable_11n", &rsu_enable_11n); #define RSU_DEBUG_ANY 0xffffffff #define RSU_DEBUG_TX 0x00000001 #define RSU_DEBUG_RX 0x00000002 #define RSU_DEBUG_RESET 0x00000004 #define RSU_DEBUG_CALIB 0x00000008 #define RSU_DEBUG_STATE 0x00000010 #define RSU_DEBUG_SCAN 0x00000020 #define RSU_DEBUG_FWCMD 0x00000040 #define RSU_DEBUG_TXDONE 0x00000080 #define RSU_DEBUG_FW 0x00000100 #define RSU_DEBUG_FWDBG 0x00000200 #define RSU_DEBUG_AMPDU 0x00000400 #define RSU_DEBUG_KEY 0x00000800 #define RSU_DEBUG_USB 0x00001000 static const STRUCT_USB_HOST_ID rsu_devs[] = { #define RSU_HT_NOT_SUPPORTED 0 #define RSU_HT_SUPPORTED 1 #define RSU_DEV_HT(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ RSU_HT_SUPPORTED) } #define RSU_DEV(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ RSU_HT_NOT_SUPPORTED) } RSU_DEV(ASUS, RTL8192SU), RSU_DEV(AZUREWAVE, RTL8192SU_4), RSU_DEV(SITECOMEU, WLA1000), RSU_DEV_HT(ACCTON, RTL8192SU), RSU_DEV_HT(ASUS, USBN10), RSU_DEV_HT(AZUREWAVE, RTL8192SU_1), RSU_DEV_HT(AZUREWAVE, RTL8192SU_2), RSU_DEV_HT(AZUREWAVE, RTL8192SU_3), RSU_DEV_HT(AZUREWAVE, RTL8192SU_5), RSU_DEV_HT(BELKIN, RTL8192SU_1), RSU_DEV_HT(BELKIN, RTL8192SU_2), RSU_DEV_HT(BELKIN, RTL8192SU_3), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_1), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_2), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_3), RSU_DEV_HT(COREGA, RTL8192SU), RSU_DEV_HT(DLINK2, DWA131A1), RSU_DEV_HT(DLINK2, RTL8192SU_1), RSU_DEV_HT(DLINK2, RTL8192SU_2), RSU_DEV_HT(EDIMAX, RTL8192SU_1), RSU_DEV_HT(EDIMAX, RTL8192SU_2), RSU_DEV_HT(EDIMAX, EW7622UMN), RSU_DEV_HT(GUILLEMOT, HWGUN54), RSU_DEV_HT(GUILLEMOT, HWNUM300), RSU_DEV_HT(HAWKING, RTL8192SU_1), RSU_DEV_HT(HAWKING, RTL8192SU_2), RSU_DEV_HT(PLANEX2, GWUSNANO), RSU_DEV_HT(REALTEK, RTL8171), RSU_DEV_HT(REALTEK, RTL8172), RSU_DEV_HT(REALTEK, RTL8173), RSU_DEV_HT(REALTEK, RTL8174), RSU_DEV_HT(REALTEK, RTL8192SU), RSU_DEV_HT(REALTEK, RTL8712), RSU_DEV_HT(REALTEK, RTL8713), RSU_DEV_HT(SENAO, RTL8192SU_1), RSU_DEV_HT(SENAO, RTL8192SU_2), RSU_DEV_HT(SITECOMEU, WL349V1), RSU_DEV_HT(SITECOMEU, WL353), RSU_DEV_HT(SWEEX2, LW154), RSU_DEV_HT(TRENDNET, TEW646UBH), #undef RSU_DEV_HT #undef RSU_DEV }; static device_probe_t rsu_match; static device_attach_t rsu_attach; static device_detach_t rsu_detach; static usb_callback_t rsu_bulk_tx_callback_be_bk; static usb_callback_t rsu_bulk_tx_callback_vi_vo; static usb_callback_t rsu_bulk_tx_callback_h2c; static usb_callback_t rsu_bulk_rx_callback; static usb_error_t rsu_do_request(struct rsu_softc *, struct usb_device_request *, void *); static struct ieee80211vap * rsu_vap_create(struct ieee80211com *, const char name[], int, enum ieee80211_opmode, int, const uint8_t bssid[], const uint8_t mac[]); static void rsu_vap_delete(struct ieee80211vap *); static void rsu_scan_start(struct ieee80211com *); static void rsu_scan_end(struct ieee80211com *); static void rsu_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static void rsu_set_channel(struct ieee80211com *); static void rsu_scan_curchan(struct ieee80211_scan_state *, unsigned long); static void rsu_scan_mindwell(struct ieee80211_scan_state *); static void rsu_update_promisc(struct ieee80211com *); static uint8_t rsu_get_multi_pos(const uint8_t[]); static void rsu_set_multi(struct rsu_softc *); static void rsu_update_mcast(struct ieee80211com *); static int rsu_alloc_rx_list(struct rsu_softc *); static void rsu_free_rx_list(struct rsu_softc *); static int rsu_alloc_tx_list(struct rsu_softc *); static void rsu_free_tx_list(struct rsu_softc *); static void rsu_free_list(struct rsu_softc *, struct rsu_data [], int); static struct rsu_data *_rsu_getbuf(struct rsu_softc *); static struct rsu_data *rsu_getbuf(struct rsu_softc *); static void rsu_freebuf(struct rsu_softc *, struct rsu_data *); static int rsu_write_region_1(struct rsu_softc *, uint16_t, uint8_t *, int); static void rsu_write_1(struct rsu_softc *, uint16_t, uint8_t); static void rsu_write_2(struct rsu_softc *, uint16_t, uint16_t); static void rsu_write_4(struct rsu_softc *, uint16_t, uint32_t); static int rsu_read_region_1(struct rsu_softc *, uint16_t, uint8_t *, int); static uint8_t rsu_read_1(struct rsu_softc *, uint16_t); static uint16_t rsu_read_2(struct rsu_softc *, uint16_t); static uint32_t rsu_read_4(struct rsu_softc *, uint16_t); static int rsu_fw_iocmd(struct rsu_softc *, uint32_t); static uint8_t rsu_efuse_read_1(struct rsu_softc *, uint16_t); static int rsu_read_rom(struct rsu_softc *); static int rsu_fw_cmd(struct rsu_softc *, uint8_t, void *, int); static void rsu_calib_task(void *, int); static void rsu_tx_task(void *, int); static void rsu_set_led(struct rsu_softc *, int); static int rsu_monitor_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int rsu_key_alloc(struct ieee80211vap *, struct ieee80211_key *, ieee80211_keyix *, ieee80211_keyix *); static int rsu_process_key(struct ieee80211vap *, const struct ieee80211_key *, int); static int rsu_key_set(struct ieee80211vap *, const struct ieee80211_key *); static int rsu_key_delete(struct ieee80211vap *, const struct ieee80211_key *); static int rsu_cam_read(struct rsu_softc *, uint8_t, uint32_t *); static void rsu_cam_write(struct rsu_softc *, uint8_t, uint32_t); static int rsu_key_check(struct rsu_softc *, ieee80211_keyix, int); static uint8_t rsu_crypto_mode(struct rsu_softc *, u_int, int); static int rsu_set_key_group(struct rsu_softc *, const struct ieee80211_key *); static int rsu_set_key_pair(struct rsu_softc *, const struct ieee80211_key *); static int rsu_reinit_static_keys(struct rsu_softc *); static int rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix); static void rsu_delete_key_pair_cb(void *, int); static int rsu_site_survey(struct rsu_softc *, struct ieee80211_scan_ssid *); static int rsu_join_bss(struct rsu_softc *, struct ieee80211_node *); static int rsu_disconnect(struct rsu_softc *); static int rsu_hwrssi_to_rssi(struct rsu_softc *, int hw_rssi); static void rsu_event_survey(struct rsu_softc *, uint8_t *, int); static void rsu_event_join_bss(struct rsu_softc *, uint8_t *, int); static void rsu_rx_event(struct rsu_softc *, uint8_t, uint8_t *, int); static void rsu_rx_multi_event(struct rsu_softc *, uint8_t *, int); static int8_t rsu_get_rssi(struct rsu_softc *, int, void *); static struct mbuf * rsu_rx_copy_to_mbuf(struct rsu_softc *, struct r92s_rx_stat *, int); static uint32_t rsu_get_tsf_low(struct rsu_softc *); static uint32_t rsu_get_tsf_high(struct rsu_softc *); static struct ieee80211_node * rsu_rx_frame(struct rsu_softc *, struct mbuf *); static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *, uint8_t *, int); static struct mbuf * rsu_rxeof(struct usb_xfer *, struct rsu_data *); static void rsu_txeof(struct usb_xfer *, struct rsu_data *); static int rsu_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void rsu_rxfilter_init(struct rsu_softc *); static void rsu_rxfilter_set(struct rsu_softc *, uint32_t, uint32_t); static void rsu_rxfilter_refresh(struct rsu_softc *); static int rsu_init(struct rsu_softc *); static int rsu_tx_start(struct rsu_softc *, struct ieee80211_node *, struct mbuf *, struct rsu_data *); static int rsu_transmit(struct ieee80211com *, struct mbuf *); static void rsu_start(struct rsu_softc *); static void _rsu_start(struct rsu_softc *); static int rsu_ioctl_net(struct ieee80211com *, u_long, void *); static void rsu_parent(struct ieee80211com *); static void rsu_stop(struct rsu_softc *); static void rsu_ms_delay(struct rsu_softc *, int); static device_method_t rsu_methods[] = { DEVMETHOD(device_probe, rsu_match), DEVMETHOD(device_attach, rsu_attach), DEVMETHOD(device_detach, rsu_detach), DEVMETHOD_END }; static driver_t rsu_driver = { .name = "rsu", .methods = rsu_methods, .size = sizeof(struct rsu_softc) }; static devclass_t rsu_devclass; DRIVER_MODULE(rsu, uhub, rsu_driver, rsu_devclass, NULL, 0); MODULE_DEPEND(rsu, wlan, 1, 1, 1); MODULE_DEPEND(rsu, usb, 1, 1, 1); MODULE_DEPEND(rsu, firmware, 1, 1, 1); MODULE_VERSION(rsu, 1); USB_PNP_HOST_INFO(rsu_devs); static uint8_t rsu_wme_ac_xfer_map[4] = { [WME_AC_BE] = RSU_BULK_TX_BE_BK, [WME_AC_BK] = RSU_BULK_TX_BE_BK, [WME_AC_VI] = RSU_BULK_TX_VI_VO, [WME_AC_VO] = RSU_BULK_TX_VI_VO, }; /* XXX hard-coded */ #define RSU_H2C_ENDPOINT 3 static const struct usb_config rsu_config[RSU_N_TRANSFER] = { [RSU_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = RSU_RXBUFSZ, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = rsu_bulk_rx_callback }, [RSU_BULK_TX_BE_BK] = { .type = UE_BULK, .endpoint = 0x06, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = rsu_bulk_tx_callback_be_bk, .timeout = RSU_TX_TIMEOUT }, [RSU_BULK_TX_VI_VO] = { .type = UE_BULK, .endpoint = 0x04, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = rsu_bulk_tx_callback_vi_vo, .timeout = RSU_TX_TIMEOUT }, [RSU_BULK_TX_H2C] = { .type = UE_BULK, .endpoint = 0x0d, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = rsu_bulk_tx_callback_h2c, .timeout = RSU_TX_TIMEOUT }, }; static int rsu_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST || uaa->info.bIfaceIndex != 0 || uaa->info.bConfigIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(rsu_devs, sizeof(rsu_devs), uaa)); } static int rsu_send_mgmt(struct ieee80211_node *ni, int type, int arg) { return (ENOTSUP); } static void rsu_update_chw(struct ieee80211com *ic) { } /* * notification from net80211 that it'd like to do A-MPDU on the given TID. * * Note: this actually hangs traffic at the present moment, so don't use it. * The firmware debug does indiciate it's sending and establishing a TX AMPDU * session, but then no traffic flows. */ static int rsu_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { #if 0 struct rsu_softc *sc = ni->ni_ic->ic_softc; struct r92s_add_ba_req req; /* Don't enable if it's requested or running */ if (IEEE80211_AMPDU_REQUESTED(tap)) return (0); if (IEEE80211_AMPDU_RUNNING(tap)) return (0); /* We've decided to send addba; so send it */ req.tid = htole32(tap->txa_tid); /* Attempt net80211 state */ if (ieee80211_ampdu_tx_request_ext(ni, tap->txa_tid) != 1) return (0); /* Send the firmware command */ RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: establishing AMPDU TX for TID %d\n", __func__, tap->txa_tid); RSU_LOCK(sc); if (rsu_fw_cmd(sc, R92S_CMD_ADDBA_REQ, &req, sizeof(req)) != 1) { RSU_UNLOCK(sc); /* Mark failure */ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 0); return (0); } RSU_UNLOCK(sc); /* Mark success; we don't get any further notifications */ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 1); #endif /* Return 0, we're driving this ourselves */ return (0); } static int rsu_wme_update(struct ieee80211com *ic) { /* Firmware handles this; not our problem */ return (0); } static int rsu_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct rsu_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; int error; uint8_t iface_index; struct usb_interface *iface; const char *rft; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; sc->sc_rx_checksum_enable = 1; if (rsu_enable_11n) sc->sc_ht = !! (USB_GET_DRIVER_INFO(uaa) & RSU_HT_SUPPORTED); /* Get number of endpoints */ iface = usbd_get_iface(sc->sc_udev, 0); sc->sc_nendpoints = iface->idesc->bNumEndpoints; /* Endpoints are hard-coded for now, so enforce 4-endpoint only */ if (sc->sc_nendpoints != 4) { device_printf(sc->sc_dev, "the driver currently only supports 4-endpoint devices\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF); RSU_DELKEY_BMAP_LOCK_INIT(sc); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0, rsu_calib_task, sc); TASK_INIT(&sc->del_key_task, 0, rsu_delete_key_pair_cb, sc); TASK_INIT(&sc->tx_task, 0, rsu_tx_task, sc); mbufq_init(&sc->sc_snd, ifqmaxlen); /* Allocate Tx/Rx buffers. */ error = rsu_alloc_rx_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Rx buffers\n"); goto fail_usb; } error = rsu_alloc_tx_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Tx buffers\n"); rsu_free_rx_list(sc); goto fail_usb; } iface_index = 0; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, rsu_config, RSU_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(sc->sc_dev, "could not allocate USB transfers, err=%s\n", usbd_errstr(error)); goto fail_usb; } RSU_LOCK(sc); /* Read chip revision. */ sc->cut = MS(rsu_read_4(sc, R92S_PMC_FSM), R92S_PMC_FSM_CUT); if (sc->cut != 3) sc->cut = (sc->cut >> 1) + 1; error = rsu_read_rom(sc); RSU_UNLOCK(sc); if (error != 0) { device_printf(self, "could not read ROM\n"); goto fail_rom; } /* Figure out TX/RX streams */ switch (sc->rom[84]) { case 0x0: sc->sc_rftype = RTL8712_RFCONFIG_1T1R; sc->sc_nrxstream = 1; sc->sc_ntxstream = 1; rft = "1T1R"; break; case 0x1: sc->sc_rftype = RTL8712_RFCONFIG_1T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 1; rft = "1T2R"; break; case 0x2: sc->sc_rftype = RTL8712_RFCONFIG_2T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 2; rft = "2T2R"; break; case 0x3: /* "green" NIC */ sc->sc_rftype = RTL8712_RFCONFIG_1T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 1; rft = "1T2R ('green')"; break; default: device_printf(sc->sc_dev, "%s: unknown board type (rfconfig=0x%02x)\n", __func__, sc->rom[84]); goto fail_rom; } IEEE80211_ADDR_COPY(ic->ic_macaddr, &sc->rom[0x12]); device_printf(self, "MAC/BB RTL8712 cut %d %s\n", sc->cut, rft); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); ic->ic_phytype = IEEE80211_T_OFDM; /* Not only, but not used. */ ic->ic_opmode = IEEE80211_M_STA; /* Default to BSS mode. */ /* Set device capabilities. */ ic->ic_caps = IEEE80211_C_STA | /* station mode */ IEEE80211_C_MONITOR | /* monitor mode supported */ #if 0 IEEE80211_C_BGSCAN | /* Background scan. */ #endif IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */ IEEE80211_C_WME | /* WME/QoS */ IEEE80211_C_SHSLOT | /* Short slot time supported. */ IEEE80211_C_WPA; /* WPA/RSN. */ ic->ic_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_TKIP | IEEE80211_CRYPTO_AES_CCM; /* Check if HT support is present. */ if (sc->sc_ht) { device_printf(sc->sc_dev, "%s: enabling 11n\n", __func__); /* Enable basic HT */ ic->ic_htcaps = IEEE80211_HTC_HT | #if 0 IEEE80211_HTC_AMPDU | #endif IEEE80211_HTC_AMSDU | IEEE80211_HTCAP_MAXAMSDU_3839 | IEEE80211_HTCAP_SMPS_OFF; ic->ic_htcaps |= IEEE80211_HTCAP_CHWIDTH40; /* set number of spatial streams */ ic->ic_txstream = sc->sc_ntxstream; ic->ic_rxstream = sc->sc_nrxstream; } ic->ic_flags_ext |= IEEE80211_FEXT_SCAN_OFFLOAD; rsu_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_raw_xmit = rsu_raw_xmit; ic->ic_scan_start = rsu_scan_start; ic->ic_scan_end = rsu_scan_end; ic->ic_getradiocaps = rsu_getradiocaps; ic->ic_set_channel = rsu_set_channel; ic->ic_scan_curchan = rsu_scan_curchan; ic->ic_scan_mindwell = rsu_scan_mindwell; ic->ic_vap_create = rsu_vap_create; ic->ic_vap_delete = rsu_vap_delete; ic->ic_update_promisc = rsu_update_promisc; ic->ic_update_mcast = rsu_update_mcast; ic->ic_ioctl = rsu_ioctl_net; ic->ic_parent = rsu_parent; ic->ic_transmit = rsu_transmit; ic->ic_send_mgmt = rsu_send_mgmt; ic->ic_update_chw = rsu_update_chw; ic->ic_ampdu_enable = rsu_ampdu_enable; ic->ic_wme.wme_update = rsu_wme_update; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), RSU_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), RSU_RX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); return (0); fail_rom: usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER); fail_usb: mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int rsu_detach(device_t self) { struct rsu_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; rsu_stop(sc); usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER); /* * Free buffers /before/ we detach from net80211, else node * references to destroyed vaps will lead to a panic. */ /* Free Tx/Rx buffers. */ RSU_LOCK(sc); rsu_free_tx_list(sc); rsu_free_rx_list(sc); RSU_UNLOCK(sc); /* Frames are freed; detach from net80211 */ ieee80211_ifdetach(ic); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->del_key_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); RSU_DELKEY_BMAP_LOCK_DESTROY(sc); mtx_destroy(&sc->sc_mtx); return (0); } static usb_error_t rsu_do_request(struct rsu_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; RSU_ASSERT_LOCKED(sc); while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0 || err == USB_ERR_NOT_CONFIGURED) break; RSU_DPRINTF(sc, RSU_DEBUG_USB, "Control request failed, %s (retries left: %d)\n", usbd_errstr(err), ntries); rsu_ms_delay(sc, 10); } return (err); } static struct ieee80211vap * rsu_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct rsu_softc *sc = ic->ic_softc; struct rsu_vap *uvp; struct ieee80211vap *vap; struct ifnet *ifp; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct rsu_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } ifp = vap->iv_ifp; ifp->if_capabilities = IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6; RSU_LOCK(sc); if (sc->sc_rx_checksum_enable) ifp->if_capenable |= IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6; RSU_UNLOCK(sc); /* override state transition machine */ uvp->newstate = vap->iv_newstate; if (opmode == IEEE80211_M_MONITOR) vap->iv_newstate = rsu_monitor_newstate; else vap->iv_newstate = rsu_newstate; vap->iv_key_alloc = rsu_key_alloc; vap->iv_key_set = rsu_key_set; vap->iv_key_delete = rsu_key_delete; /* Limits from the r92su driver */ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16; vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K; /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void rsu_vap_delete(struct ieee80211vap *vap) { struct rsu_vap *uvp = RSU_VAP(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void rsu_scan_start(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; struct ieee80211_scan_state *ss = ic->ic_scan; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); int error; /* Scanning is done by the firmware. */ RSU_LOCK(sc); sc->sc_active_scan = !!(ss->ss_flags & IEEE80211_SCAN_ACTIVE); /* XXX TODO: force awake if in network-sleep? */ error = rsu_site_survey(sc, ss->ss_nssid > 0 ? &ss->ss_ssid[0] : NULL); RSU_UNLOCK(sc); if (error != 0) { device_printf(sc->sc_dev, "could not send site survey command\n"); ieee80211_cancel_scan(vap); } } static void rsu_scan_end(struct ieee80211com *ic) { /* Nothing to do here. */ } static void rsu_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct rsu_softc *sc = ic->ic_softc; uint8_t bands[IEEE80211_MODE_BYTES]; /* Set supported .11b and .11g rates. */ memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); if (sc->sc_ht) setbit(bands, IEEE80211_MODE_11NG); ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) != 0); } static void rsu_set_channel(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; /* * Only need to set the channel in Monitor mode. AP scanning and auth * are already taken care of by their respective firmware commands. */ if (ic->ic_opmode == IEEE80211_M_MONITOR) { struct r92s_set_channel cmd; int error; cmd.channel = IEEE80211_CHAN2IEEE(ic->ic_curchan); RSU_LOCK(sc); error = rsu_fw_cmd(sc, R92S_CMD_SET_CHANNEL, &cmd, sizeof(cmd)); if (error != 0) { device_printf(sc->sc_dev, "%s: error %d setting channel\n", __func__, error); } RSU_UNLOCK(sc); } } static void rsu_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { /* Scan is done in rsu_scan_start(). */ } /** * Called by the net80211 framework to indicate * the minimum dwell time has been met, terminate the scan. * We don't actually terminate the scan as the firmware will notify * us when it's finished and we have no way to interrupt it. */ static void rsu_scan_mindwell(struct ieee80211_scan_state *ss) { /* NB: don't try to abort scan; wait for firmware to finish */ } static void rsu_update_promisc(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; RSU_LOCK(sc); if (sc->sc_running) rsu_rxfilter_refresh(sc); RSU_UNLOCK(sc); } /* * The same as rtwn_get_multi_pos() / rtwn_set_multi(). */ static uint8_t rsu_get_multi_pos(const uint8_t maddr[]) { uint64_t mask = 0x00004d101df481b4; uint8_t pos = 0x27; /* initial value */ int i, j; for (i = 0; i < IEEE80211_ADDR_LEN; i++) for (j = (i == 0) ? 1 : 0; j < 8; j++) if ((maddr[i] >> j) & 1) pos ^= (mask >> (i * 8 + j - 1)); pos &= 0x3f; return (pos); } static u_int rsu_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t *mfilt = arg; uint8_t pos; pos = rsu_get_multi_pos(LLADDR(sdl)); mfilt[pos / 32] |= (1 << (pos % 32)); return (1); } static void rsu_set_multi(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t mfilt[2]; RSU_ASSERT_LOCKED(sc); /* general structure was copied from ath(4). */ if (ic->ic_allmulti == 0) { struct ieee80211vap *vap; /* * Merge multicast addresses to form the hardware filter. */ mfilt[0] = mfilt[1] = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if_foreach_llmaddr(vap->iv_ifp, rsu_hash_maddr, &mfilt); } else mfilt[0] = mfilt[1] = ~0; rsu_write_4(sc, R92S_MAR + 0, mfilt[0]); rsu_write_4(sc, R92S_MAR + 4, mfilt[1]); RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: MC filter %08x:%08x\n", __func__, mfilt[0], mfilt[1]); } static void rsu_update_mcast(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; RSU_LOCK(sc); if (sc->sc_running) rsu_set_multi(sc); RSU_UNLOCK(sc); } static int rsu_alloc_list(struct rsu_softc *sc, struct rsu_data data[], int ndata, int maxsz) { int i, error; for (i = 0; i < ndata; i++) { struct rsu_data *dp = &data[i]; dp->sc = sc; dp->m = NULL; dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); if (dp->buf == NULL) { device_printf(sc->sc_dev, "could not allocate buffer\n"); error = ENOMEM; goto fail; } dp->ni = NULL; } return (0); fail: rsu_free_list(sc, data, ndata); return (error); } static int rsu_alloc_rx_list(struct rsu_softc *sc) { int error, i; error = rsu_alloc_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT, RSU_RXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < RSU_RX_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); return (0); } static int rsu_alloc_tx_list(struct rsu_softc *sc) { int error, i; error = rsu_alloc_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT, RSU_TXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != RSU_N_TRANSFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } for (i = 0; i < RSU_TX_LIST_COUNT; i++) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); } return (0); } static void rsu_free_tx_list(struct rsu_softc *sc) { int i; /* prevent further allocations from TX list(s) */ STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != RSU_N_TRANSFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } rsu_free_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT); } static void rsu_free_rx_list(struct rsu_softc *sc) { /* prevent further allocations from RX list(s) */ STAILQ_INIT(&sc->sc_rx_inactive); STAILQ_INIT(&sc->sc_rx_active); rsu_free_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT); } static void rsu_free_list(struct rsu_softc *sc, struct rsu_data data[], int ndata) { int i; for (i = 0; i < ndata; i++) { struct rsu_data *dp = &data[i]; if (dp->buf != NULL) { free(dp->buf, M_USBDEV); dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static struct rsu_data * _rsu_getbuf(struct rsu_softc *sc) { struct rsu_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); else bf = NULL; return (bf); } static struct rsu_data * rsu_getbuf(struct rsu_softc *sc) { struct rsu_data *bf; RSU_ASSERT_LOCKED(sc); bf = _rsu_getbuf(sc); if (bf == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: no buffers\n", __func__); } return (bf); } static void rsu_freebuf(struct rsu_softc *sc, struct rsu_data *bf) { RSU_ASSERT_LOCKED(sc); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, bf, next); } static int rsu_write_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = R92S_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (rsu_do_request(sc, &req, buf)); } static void rsu_write_1(struct rsu_softc *sc, uint16_t addr, uint8_t val) { rsu_write_region_1(sc, addr, &val, 1); } static void rsu_write_2(struct rsu_softc *sc, uint16_t addr, uint16_t val) { val = htole16(val); rsu_write_region_1(sc, addr, (uint8_t *)&val, 2); } static void rsu_write_4(struct rsu_softc *sc, uint16_t addr, uint32_t val) { val = htole32(val); rsu_write_region_1(sc, addr, (uint8_t *)&val, 4); } static int rsu_read_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = R92S_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (rsu_do_request(sc, &req, buf)); } static uint8_t rsu_read_1(struct rsu_softc *sc, uint16_t addr) { uint8_t val; if (rsu_read_region_1(sc, addr, &val, 1) != 0) return (0xff); return (val); } static uint16_t rsu_read_2(struct rsu_softc *sc, uint16_t addr) { uint16_t val; if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 2) != 0) return (0xffff); return (le16toh(val)); } static uint32_t rsu_read_4(struct rsu_softc *sc, uint16_t addr) { uint32_t val; if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 4) != 0) return (0xffffffff); return (le32toh(val)); } static int rsu_fw_iocmd(struct rsu_softc *sc, uint32_t iocmd) { int ntries; rsu_write_4(sc, R92S_IOCMD_CTRL, iocmd); rsu_ms_delay(sc, 1); for (ntries = 0; ntries < 50; ntries++) { if (rsu_read_4(sc, R92S_IOCMD_CTRL) == 0) return (0); rsu_ms_delay(sc, 1); } return (ETIMEDOUT); } static uint8_t rsu_efuse_read_1(struct rsu_softc *sc, uint16_t addr) { uint32_t reg; int ntries; reg = rsu_read_4(sc, R92S_EFUSE_CTRL); reg = RW(reg, R92S_EFUSE_CTRL_ADDR, addr); reg &= ~R92S_EFUSE_CTRL_VALID; rsu_write_4(sc, R92S_EFUSE_CTRL, reg); /* Wait for read operation to complete. */ for (ntries = 0; ntries < 100; ntries++) { reg = rsu_read_4(sc, R92S_EFUSE_CTRL); if (reg & R92S_EFUSE_CTRL_VALID) return (MS(reg, R92S_EFUSE_CTRL_DATA)); rsu_ms_delay(sc, 1); } device_printf(sc->sc_dev, "could not read efuse byte at address 0x%x\n", addr); return (0xff); } static int rsu_read_rom(struct rsu_softc *sc) { uint8_t *rom = sc->rom; uint16_t addr = 0; uint32_t reg; uint8_t off, msk; int i; /* Make sure that ROM type is eFuse and that autoload succeeded. */ reg = rsu_read_1(sc, R92S_EE_9346CR); if ((reg & (R92S_9356SEL | R92S_EEPROM_EN)) != R92S_EEPROM_EN) return (EIO); /* Turn on 2.5V to prevent eFuse leakage. */ reg = rsu_read_1(sc, R92S_EFUSE_TEST + 3); rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg | 0x80); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg & ~0x80); /* Read full ROM image. */ memset(&sc->rom, 0xff, sizeof(sc->rom)); while (addr < 512) { reg = rsu_efuse_read_1(sc, addr); if (reg == 0xff) break; addr++; off = reg >> 4; msk = reg & 0xf; for (i = 0; i < 4; i++) { if (msk & (1 << i)) continue; rom[off * 8 + i * 2 + 0] = rsu_efuse_read_1(sc, addr); addr++; rom[off * 8 + i * 2 + 1] = rsu_efuse_read_1(sc, addr); addr++; } } #ifdef USB_DEBUG if (rsu_debug & RSU_DEBUG_RESET) { /* Dump ROM content. */ printf("\n"); for (i = 0; i < sizeof(sc->rom); i++) printf("%02x:", rom[i]); printf("\n"); } #endif return (0); } static int rsu_fw_cmd(struct rsu_softc *sc, uint8_t code, void *buf, int len) { const uint8_t which = RSU_H2C_ENDPOINT; struct rsu_data *data; struct r92s_tx_desc *txd; struct r92s_fw_cmd_hdr *cmd; int cmdsz; int xferlen; RSU_ASSERT_LOCKED(sc); data = rsu_getbuf(sc); if (data == NULL) return (ENOMEM); /* Blank the entire payload, just to be safe */ memset(data->buf, '\0', RSU_TXBUFSZ); /* Round-up command length to a multiple of 8 bytes. */ /* XXX TODO: is this required? */ cmdsz = (len + 7) & ~7; xferlen = sizeof(*txd) + sizeof(*cmd) + cmdsz; KASSERT(xferlen <= RSU_TXBUFSZ, ("%s: invalid length", __func__)); memset(data->buf, 0, xferlen); /* Setup Tx descriptor. */ txd = (struct r92s_tx_desc *)data->buf; txd->txdw0 = htole32( SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | SM(R92S_TXDW0_PKTLEN, sizeof(*cmd) + cmdsz) | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); txd->txdw1 = htole32(SM(R92S_TXDW1_QSEL, R92S_TXDW1_QSEL_H2C)); /* Setup command header. */ cmd = (struct r92s_fw_cmd_hdr *)&txd[1]; cmd->len = htole16(cmdsz); cmd->code = code; cmd->seq = sc->cmd_seq; sc->cmd_seq = (sc->cmd_seq + 1) & 0x7f; /* Copy command payload. */ memcpy(&cmd[1], buf, len); RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FWCMD, "%s: Tx cmd code=0x%x len=0x%x\n", __func__, code, cmdsz); data->buflen = xferlen; STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); usbd_transfer_start(sc->sc_xfer[which]); return (0); } /* ARGSUSED */ static void rsu_calib_task(void *arg, int pending __unused) { struct rsu_softc *sc = arg; #ifdef notyet uint32_t reg; #endif RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: running calibration task\n", __func__); RSU_LOCK(sc); #ifdef notyet /* Read WPS PBC status. */ rsu_write_1(sc, R92S_MAC_PINMUX_CTRL, R92S_GPIOMUX_EN | SM(R92S_GPIOSEL_GPIO, R92S_GPIOSEL_GPIO_JTAG)); rsu_write_1(sc, R92S_GPIO_IO_SEL, rsu_read_1(sc, R92S_GPIO_IO_SEL) & ~R92S_GPIO_WPS); reg = rsu_read_1(sc, R92S_GPIO_CTRL); if (reg != 0xff && (reg & R92S_GPIO_WPS)) RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "WPS PBC is pushed\n"); #endif /* Read current signal level. */ if (rsu_fw_iocmd(sc, 0xf4000001) == 0) { sc->sc_currssi = rsu_read_4(sc, R92S_IOCMD_DATA); RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: RSSI=%d (%d)\n", __func__, sc->sc_currssi, rsu_hwrssi_to_rssi(sc, sc->sc_currssi)); } if (sc->sc_calibrating) taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz); RSU_UNLOCK(sc); } static void rsu_tx_task(void *arg, int pending __unused) { struct rsu_softc *sc = arg; RSU_LOCK(sc); _rsu_start(sc); RSU_UNLOCK(sc); } #define RSU_PWR_UNKNOWN 0x0 #define RSU_PWR_ACTIVE 0x1 #define RSU_PWR_OFF 0x2 #define RSU_PWR_SLEEP 0x3 /* * Set the current power state. * * The rtlwifi code doesn't do this so aggressively; it * waits for an idle period after association with * no traffic before doing this. * * For now - it's on in all states except RUN, and * in RUN it'll transition to allow sleep. */ struct r92s_pwr_cmd { uint8_t mode; uint8_t smart_ps; uint8_t bcn_pass_time; }; static int rsu_set_fw_power_state(struct rsu_softc *sc, int state) { struct r92s_set_pwr_mode cmd; //struct r92s_pwr_cmd cmd; int error; RSU_ASSERT_LOCKED(sc); /* only change state if required */ if (sc->sc_curpwrstate == state) return (0); memset(&cmd, 0, sizeof(cmd)); switch (state) { case RSU_PWR_ACTIVE: /* Force the hardware awake */ rsu_write_1(sc, R92S_USB_HRPWM, R92S_USB_HRPWM_PS_ST_ACTIVE | R92S_USB_HRPWM_PS_ALL_ON); cmd.mode = R92S_PS_MODE_ACTIVE; break; case RSU_PWR_SLEEP: cmd.mode = R92S_PS_MODE_DTIM; /* XXX configurable? */ cmd.smart_ps = 1; /* XXX 2 if doing p2p */ cmd.bcn_pass_time = 5; /* in 100mS usb.c, linux/rtlwifi */ break; case RSU_PWR_OFF: cmd.mode = R92S_PS_MODE_RADIOOFF; break; default: device_printf(sc->sc_dev, "%s: unknown ps mode (%d)\n", __func__, state); return (ENXIO); } RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting ps mode to %d (mode %d)\n", __func__, state, cmd.mode); error = rsu_fw_cmd(sc, R92S_CMD_SET_PWR_MODE, &cmd, sizeof(cmd)); if (error == 0) sc->sc_curpwrstate = state; return (error); } static void rsu_set_led(struct rsu_softc *sc, int on) { rsu_write_1(sc, R92S_LEDCFG, (rsu_read_1(sc, R92S_LEDCFG) & 0xf0) | (!on << 3)); } static int rsu_monitor_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct rsu_softc *sc = ic->ic_softc; struct rsu_vap *uvp = RSU_VAP(vap); if (vap->iv_state != nstate) { IEEE80211_UNLOCK(ic); RSU_LOCK(sc); switch (nstate) { case IEEE80211_S_INIT: sc->sc_vap_is_running = 0; rsu_set_led(sc, 0); break; case IEEE80211_S_RUN: sc->sc_vap_is_running = 1; rsu_set_led(sc, 1); break; default: /* NOTREACHED */ break; } rsu_rxfilter_refresh(sc); RSU_UNLOCK(sc); IEEE80211_LOCK(ic); } return (uvp->newstate(vap, nstate, arg)); } static int rsu_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct rsu_vap *uvp = RSU_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct rsu_softc *sc = ic->ic_softc; struct ieee80211_node *ni; struct ieee80211_rateset *rs; enum ieee80211_state ostate; int error, startcal = 0; ostate = vap->iv_state; RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); if (ostate == IEEE80211_S_RUN) { RSU_LOCK(sc); /* Stop calibration. */ sc->sc_calibrating = 0; /* Pause Tx for AC queues. */ rsu_write_1(sc, R92S_TXPAUSE, R92S_TXPAUSE_AC); usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); RSU_UNLOCK(sc); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); RSU_LOCK(sc); /* Disassociate from our current BSS. */ rsu_disconnect(sc); usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); /* Refresh Rx filter (may be modified by firmware). */ sc->sc_vap_is_running = 0; rsu_rxfilter_refresh(sc); /* Reinstall static keys. */ if (sc->sc_running) rsu_reinit_static_keys(sc); } else RSU_LOCK(sc); switch (nstate) { case IEEE80211_S_INIT: (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); break; case IEEE80211_S_AUTH: ni = ieee80211_ref_node(vap->iv_bss); (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); error = rsu_join_bss(sc, ni); ieee80211_free_node(ni); if (error != 0) { device_printf(sc->sc_dev, "could not send join command\n"); } break; case IEEE80211_S_RUN: /* Flush all AC queues. */ rsu_write_1(sc, R92S_TXPAUSE, 0); ni = ieee80211_ref_node(vap->iv_bss); rs = &ni->ni_rates; /* Indicate highest supported rate. */ ni->ni_txrate = rs->rs_rates[rs->rs_nrates - 1]; (void) rsu_set_fw_power_state(sc, RSU_PWR_SLEEP); ieee80211_free_node(ni); startcal = 1; break; default: break; } if (startcal != 0) { sc->sc_calibrating = 1; /* Start periodic calibration. */ taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz); } RSU_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } static int rsu_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { struct rsu_softc *sc = vap->iv_ic->ic_softc; int is_checked = 0; if (&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { *keyix = ieee80211_crypto_get_key_wepidx(vap, k); } else { if (vap->iv_opmode != IEEE80211_M_STA) { *keyix = 0; /* TODO: obtain keyix from node id */ is_checked = 1; k->wk_flags |= IEEE80211_KEY_SWCRYPT; } else *keyix = R92S_MACID_BSS; } if (!is_checked) { RSU_LOCK(sc); if (isset(sc->keys_bmap, *keyix)) { device_printf(sc->sc_dev, "%s: key slot %d is already used!\n", __func__, *keyix); RSU_UNLOCK(sc); return (0); } setbit(sc->keys_bmap, *keyix); RSU_UNLOCK(sc); } *rxkeyix = *keyix; return (1); } static int rsu_process_key(struct ieee80211vap *vap, const struct ieee80211_key *k, int set) { struct rsu_softc *sc = vap->iv_ic->ic_softc; int ret; if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return (1); } /* Handle group keys. */ if (&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { KASSERT(k->wk_keyix < nitems(sc->group_keys), ("keyix %u > %zu\n", k->wk_keyix, nitems(sc->group_keys))); RSU_LOCK(sc); sc->group_keys[k->wk_keyix] = (set ? k : NULL); if (!sc->sc_running) { /* Static keys will be set during device startup. */ RSU_UNLOCK(sc); return (1); } if (set) ret = rsu_set_key_group(sc, k); else ret = rsu_delete_key(sc, k->wk_keyix); RSU_UNLOCK(sc); return (!ret); } if (set) { /* wait for pending key removal */ taskqueue_drain(taskqueue_thread, &sc->del_key_task); RSU_LOCK(sc); ret = rsu_set_key_pair(sc, k); RSU_UNLOCK(sc); } else { RSU_DELKEY_BMAP_LOCK(sc); setbit(sc->free_keys_bmap, k->wk_keyix); RSU_DELKEY_BMAP_UNLOCK(sc); /* workaround ieee80211_node_delucastkey() locking */ taskqueue_enqueue(taskqueue_thread, &sc->del_key_task); ret = 0; /* fake success */ } return (!ret); } static int rsu_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { return (rsu_process_key(vap, k, 1)); } static int rsu_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { return (rsu_process_key(vap, k, 0)); } static int rsu_cam_read(struct rsu_softc *sc, uint8_t addr, uint32_t *val) { int ntries; rsu_write_4(sc, R92S_CAMCMD, R92S_CAMCMD_POLLING | SM(R92S_CAMCMD_ADDR, addr)); for (ntries = 0; ntries < 10; ntries++) { if (!(rsu_read_4(sc, R92S_CAMCMD) & R92S_CAMCMD_POLLING)) break; usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1)); } if (ntries == 10) { device_printf(sc->sc_dev, "%s: cannot read CAM entry at address %02X\n", __func__, addr); return (ETIMEDOUT); } *val = rsu_read_4(sc, R92S_CAMREAD); return (0); } static void rsu_cam_write(struct rsu_softc *sc, uint8_t addr, uint32_t data) { rsu_write_4(sc, R92S_CAMWRITE, data); rsu_write_4(sc, R92S_CAMCMD, R92S_CAMCMD_POLLING | R92S_CAMCMD_WRITE | SM(R92S_CAMCMD_ADDR, addr)); } static int rsu_key_check(struct rsu_softc *sc, ieee80211_keyix keyix, int is_valid) { uint32_t val; int error, ntries; for (ntries = 0; ntries < 20; ntries++) { usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1)); error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot check key status!\n", __func__); return (error); } if (((val & R92S_CAM_VALID) == 0) ^ is_valid) break; } if (ntries == 20) { device_printf(sc->sc_dev, "%s: key %d is %s marked as valid, rejecting request\n", __func__, keyix, is_valid ? "not" : "still"); return (EIO); } return (0); } /* * Map net80211 cipher to RTL8712 security mode. */ static uint8_t rsu_crypto_mode(struct rsu_softc *sc, u_int cipher, int keylen) { switch (cipher) { case IEEE80211_CIPHER_WEP: return keylen < 8 ? R92S_KEY_ALGO_WEP40 : R92S_KEY_ALGO_WEP104; case IEEE80211_CIPHER_TKIP: return R92S_KEY_ALGO_TKIP; case IEEE80211_CIPHER_AES_CCM: return R92S_KEY_ALGO_AES; default: device_printf(sc->sc_dev, "unknown cipher %d\n", cipher); return R92S_KEY_ALGO_INVALID; } } static int rsu_set_key_group(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key key; uint8_t algo; int error; RSU_ASSERT_LOCKED(sc); /* Map net80211 cipher to HW crypto algorithm. */ algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); if (algo == R92S_KEY_ALGO_INVALID) return (EINVAL); memset(&key, 0, sizeof(key)); key.algo = algo; key.cam_id = k->wk_keyix; key.grpkey = (k->wk_flags & IEEE80211_KEY_GROUP) != 0; memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, "%s: keyix %u, group %u, algo %u/%u, flags %04X, len %u, " "macaddr %s\n", __func__, key.cam_id, key.grpkey, k->wk_cipher->ic_cipher, key.algo, k->wk_flags, k->wk_keylen, ether_sprintf(k->wk_macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot send firmware command, error %d\n", __func__, error); return (error); } return (rsu_key_check(sc, k->wk_keyix, 1)); } static int rsu_set_key_pair(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key_mac key; uint8_t algo; int error; RSU_ASSERT_LOCKED(sc); if (!sc->sc_running) return (ESHUTDOWN); /* Map net80211 cipher to HW crypto algorithm. */ algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); if (algo == R92S_KEY_ALGO_INVALID) return (EINVAL); memset(&key, 0, sizeof(key)); key.algo = algo; memcpy(key.macaddr, k->wk_macaddr, sizeof(key.macaddr)); memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, "%s: keyix %u, algo %u/%u, flags %04X, len %u, macaddr %s\n", __func__, k->wk_keyix, k->wk_cipher->ic_cipher, key.algo, k->wk_flags, k->wk_keylen, ether_sprintf(key.macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_STA_KEY, &key, sizeof(key)); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot send firmware command, error %d\n", __func__, error); return (error); } return (rsu_key_check(sc, k->wk_keyix, 1)); } static int rsu_reinit_static_keys(struct rsu_softc *sc) { int i, error; for (i = 0; i < nitems(sc->group_keys); i++) { if (sc->group_keys[i] != NULL) { error = rsu_set_key_group(sc, sc->group_keys[i]); if (error != 0) { device_printf(sc->sc_dev, "%s: failed to set static key %d, " "error %d\n", __func__, i, error); return (error); } } } return (0); } static int rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix keyix) { struct r92s_fw_cmd_set_key key; uint32_t val; int error; RSU_ASSERT_LOCKED(sc); if (!sc->sc_running) return (0); /* check if it was automatically removed by firmware */ error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val); if (error == 0 && (val & R92S_CAM_VALID) == 0) { RSU_DPRINTF(sc, RSU_DEBUG_KEY, "%s: key %u does not exist\n", __func__, keyix); clrbit(sc->keys_bmap, keyix); return (0); } memset(&key, 0, sizeof(key)); key.cam_id = keyix; RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, "%s: removing key %u\n", __func__, key.cam_id); error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot send firmware command, error %d\n", __func__, error); goto finish; } usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(5)); /* * Clear 'valid' bit manually (cannot be done via firmware command). * Used for key check + when firmware command cannot be sent. */ finish: rsu_cam_write(sc, R92S_CAM_CTL0(keyix), 0); clrbit(sc->keys_bmap, keyix); return (rsu_key_check(sc, keyix, 0)); } static void rsu_delete_key_pair_cb(void *arg, int pending __unused) { struct rsu_softc *sc = arg; int i; RSU_DELKEY_BMAP_LOCK(sc); for (i = IEEE80211_WEP_NKID; i < R92S_CAM_ENTRY_LIMIT; i++) { if (isset(sc->free_keys_bmap, i)) { RSU_DELKEY_BMAP_UNLOCK(sc); RSU_LOCK(sc); RSU_DPRINTF(sc, RSU_DEBUG_KEY, "%s: calling rsu_delete_key() with keyix = %d\n", __func__, i); (void) rsu_delete_key(sc, i); RSU_UNLOCK(sc); RSU_DELKEY_BMAP_LOCK(sc); clrbit(sc->free_keys_bmap, i); /* bmap can be changed */ i = IEEE80211_WEP_NKID - 1; continue; } } RSU_DELKEY_BMAP_UNLOCK(sc); } static int rsu_site_survey(struct rsu_softc *sc, struct ieee80211_scan_ssid *ssid) { struct r92s_fw_cmd_sitesurvey cmd; RSU_ASSERT_LOCKED(sc); memset(&cmd, 0, sizeof(cmd)); /* TODO: passive channels? */ if (sc->sc_active_scan) cmd.active = htole32(1); cmd.limit = htole32(48); if (ssid != NULL) { sc->sc_extra_scan = 1; cmd.ssidlen = htole32(ssid->len); memcpy(cmd.ssid, ssid->ssid, ssid->len); } #ifdef USB_DEBUG if (rsu_debug & (RSU_DEBUG_SCAN | RSU_DEBUG_FWCMD)) { device_printf(sc->sc_dev, "sending site survey command, active %d", le32toh(cmd.active)); if (ssid != NULL) { printf(", ssid: "); ieee80211_print_essid(cmd.ssid, le32toh(cmd.ssidlen)); } printf("\n"); } #endif return (rsu_fw_cmd(sc, R92S_CMD_SITE_SURVEY, &cmd, sizeof(cmd))); } static int rsu_join_bss(struct rsu_softc *sc, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ndis_wlan_bssid_ex *bss; struct ndis_802_11_fixed_ies *fixed; struct r92s_fw_cmd_auth auth; uint8_t buf[sizeof(*bss) + 128] __aligned(4); uint8_t *frm; uint8_t opmode; int error; RSU_ASSERT_LOCKED(sc); /* Let the FW decide the opmode based on the capinfo field. */ opmode = NDIS802_11AUTOUNKNOWN; RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting operating mode to %d\n", __func__, opmode); error = rsu_fw_cmd(sc, R92S_CMD_SET_OPMODE, &opmode, sizeof(opmode)); if (error != 0) return (error); memset(&auth, 0, sizeof(auth)); if (vap->iv_flags & IEEE80211_F_WPA) { auth.mode = R92S_AUTHMODE_WPA; auth.dot1x = (ni->ni_authmode == IEEE80211_AUTH_8021X); } else auth.mode = R92S_AUTHMODE_OPEN; RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting auth mode to %d\n", __func__, auth.mode); error = rsu_fw_cmd(sc, R92S_CMD_SET_AUTH, &auth, sizeof(auth)); if (error != 0) return (error); memset(buf, 0, sizeof(buf)); bss = (struct ndis_wlan_bssid_ex *)buf; IEEE80211_ADDR_COPY(bss->macaddr, ni->ni_bssid); bss->ssid.ssidlen = htole32(ni->ni_esslen); memcpy(bss->ssid.ssid, ni->ni_essid, ni->ni_esslen); if (vap->iv_flags & (IEEE80211_F_PRIVACY | IEEE80211_F_WPA)) bss->privacy = htole32(1); bss->rssi = htole32(ni->ni_avgrssi); if (ic->ic_curmode == IEEE80211_MODE_11B) bss->networktype = htole32(NDIS802_11DS); else bss->networktype = htole32(NDIS802_11OFDM24); bss->config.len = htole32(sizeof(bss->config)); bss->config.bintval = htole32(ni->ni_intval); bss->config.dsconfig = htole32(ieee80211_chan2ieee(ic, ni->ni_chan)); bss->inframode = htole32(NDIS802_11INFRASTRUCTURE); /* XXX verify how this is supposed to look! */ memcpy(bss->supprates, ni->ni_rates.rs_rates, ni->ni_rates.rs_nrates); /* Write the fixed fields of the beacon frame. */ fixed = (struct ndis_802_11_fixed_ies *)&bss[1]; memcpy(&fixed->tstamp, ni->ni_tstamp.data, 8); fixed->bintval = htole16(ni->ni_intval); fixed->capabilities = htole16(ni->ni_capinfo); /* Write IEs to be included in the association request. */ frm = (uint8_t *)&fixed[1]; frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_wpa(frm, vap); frm = ieee80211_add_qos(frm, ni); if ((ic->ic_flags & IEEE80211_F_WME) && (ni->ni_ies.wme_ie != NULL)) frm = ieee80211_add_wme_info(frm, &ic->ic_wme); if (ni->ni_flags & IEEE80211_NODE_HT) { frm = ieee80211_add_htcap(frm, ni); frm = ieee80211_add_htinfo(frm, ni); } bss->ieslen = htole32(frm - (uint8_t *)fixed); bss->len = htole32(((frm - buf) + 3) & ~3); RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_FWCMD, "%s: sending join bss command to %s chan %d\n", __func__, ether_sprintf(bss->macaddr), le32toh(bss->config.dsconfig)); return (rsu_fw_cmd(sc, R92S_CMD_JOIN_BSS, buf, sizeof(buf))); } static int rsu_disconnect(struct rsu_softc *sc) { uint32_t zero = 0; /* :-) */ /* Disassociate from our current BSS. */ RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: sending disconnect command\n", __func__); return (rsu_fw_cmd(sc, R92S_CMD_DISCONNECT, &zero, sizeof(zero))); } /* * Map the hardware provided RSSI value to a signal level. * For the most part it's just something we divide by and cap * so it doesn't overflow the representation by net80211. */ static int rsu_hwrssi_to_rssi(struct rsu_softc *sc, int hw_rssi) { int v; if (hw_rssi == 0) return (0); v = hw_rssi >> 4; if (v > 80) v = 80; return (v); } CTASSERT(MCLBYTES > sizeof(struct ieee80211_frame)); static void rsu_event_survey(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ndis_wlan_bssid_ex *bss; struct ieee80211_rx_stats rxs; struct mbuf *m; uint32_t ieslen; uint32_t pktlen; if (__predict_false(len < sizeof(*bss))) return; bss = (struct ndis_wlan_bssid_ex *)buf; ieslen = le32toh(bss->ieslen); /* range check length of information element */ if (__predict_false(ieslen > (uint32_t)(len - sizeof(*bss)))) return; RSU_DPRINTF(sc, RSU_DEBUG_SCAN, "%s: found BSS %s: len=%d chan=%d inframode=%d " "networktype=%d privacy=%d, RSSI=%d\n", __func__, ether_sprintf(bss->macaddr), ieslen, le32toh(bss->config.dsconfig), le32toh(bss->inframode), le32toh(bss->networktype), le32toh(bss->privacy), le32toh(bss->rssi)); /* Build a fake beacon frame to let net80211 do all the parsing. */ /* XXX TODO: just call the new scan API methods! */ if (__predict_false(ieslen > (size_t)(MCLBYTES - sizeof(*wh)))) return; pktlen = sizeof(*wh) + ieslen; m = m_get2(pktlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) return; wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; USETW(wh->i_dur, 0); IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, bss->macaddr); IEEE80211_ADDR_COPY(wh->i_addr3, bss->macaddr); *(uint16_t *)wh->i_seq = 0; memcpy(&wh[1], (uint8_t *)&bss[1], ieslen); /* Finalize mbuf. */ m->m_pkthdr.len = m->m_len = pktlen; /* Set channel flags for input path */ bzero(&rxs, sizeof(rxs)); rxs.r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ; rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_ieee = le32toh(bss->config.dsconfig); rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_2GHZ); /* This is a number from 0..100; so let's just divide it down a bit */ rxs.c_rssi = le32toh(bss->rssi) / 2; rxs.c_nf = -96; if (ieee80211_add_rx_params(m, &rxs) == 0) return; /* XXX avoid a LOR */ RSU_UNLOCK(sc); ieee80211_input_mimo_all(ic, m); RSU_LOCK(sc); } static void rsu_event_join_bss(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni = vap->iv_bss; struct r92s_event_join_bss *rsp; uint32_t tmp; int res; if (__predict_false(len < sizeof(*rsp))) return; rsp = (struct r92s_event_join_bss *)buf; res = (int)le32toh(rsp->join_res); RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: Rx join BSS event len=%d res=%d\n", __func__, len, res); /* * XXX Don't do this; there's likely a better way to tell * the caller we failed. */ if (res <= 0) { RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); return; } tmp = le32toh(rsp->associd); if (tmp >= vap->iv_max_aid) { RSU_DPRINTF(sc, RSU_DEBUG_ANY, "Assoc ID overflow\n"); tmp = 1; } RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: associated with %s associd=%d\n", __func__, ether_sprintf(rsp->bss.macaddr), tmp); /* XXX is this required? What's the top two bits for again? */ ni->ni_associd = tmp | 0xc000; /* Refresh Rx filter (was changed by firmware). */ sc->sc_vap_is_running = 1; rsu_rxfilter_refresh(sc); RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_RUN, IEEE80211_FC0_SUBTYPE_ASSOC_RESP); RSU_LOCK(sc); } static void rsu_event_addba_req_report(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct r92s_add_ba_event *ba = (void *) buf; struct ieee80211_node *ni; if (len < sizeof(*ba)) { device_printf(sc->sc_dev, "%s: short read (%d)\n", __func__, len); return; } if (vap == NULL) return; RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: mac=%s, tid=%d, ssn=%d\n", __func__, ether_sprintf(ba->mac_addr), (int) ba->tid, (int) le16toh(ba->ssn)); /* XXX do node lookup; this is STA specific */ ni = ieee80211_ref_node(vap->iv_bss); ieee80211_ampdu_rx_start_ext(ni, ba->tid, le16toh(ba->ssn) >> 4, 32); ieee80211_free_node(ni); } static void rsu_rx_event(struct rsu_softc *sc, uint8_t code, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD, "%s: Rx event code=%d len=%d\n", __func__, code, len); switch (code) { case R92S_EVT_SURVEY: rsu_event_survey(sc, buf, len); break; case R92S_EVT_SURVEY_DONE: RSU_DPRINTF(sc, RSU_DEBUG_SCAN, "%s: %s scan done, found %d BSS\n", __func__, sc->sc_extra_scan ? "direct" : "broadcast", le32toh(*(uint32_t *)buf)); if (sc->sc_extra_scan == 1) { /* Send broadcast probe request. */ sc->sc_extra_scan = 0; if (vap != NULL && rsu_site_survey(sc, NULL) != 0) { RSU_UNLOCK(sc); ieee80211_cancel_scan(vap); RSU_LOCK(sc); } break; } if (vap != NULL) { RSU_UNLOCK(sc); ieee80211_scan_done(vap); RSU_LOCK(sc); } break; case R92S_EVT_JOIN_BSS: if (vap->iv_state == IEEE80211_S_AUTH) rsu_event_join_bss(sc, buf, len); break; case R92S_EVT_DEL_STA: RSU_DPRINTF(sc, RSU_DEBUG_FWCMD | RSU_DEBUG_STATE, "%s: disassociated from %s\n", __func__, ether_sprintf(buf)); if (vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, buf)) { RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); } break; case R92S_EVT_WPS_PBC: RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD, "%s: WPS PBC pushed.\n", __func__); break; case R92S_EVT_FWDBG: buf[60] = '\0'; RSU_DPRINTF(sc, RSU_DEBUG_FWDBG, "FWDBG: %s\n", (char *)buf); break; case R92S_EVT_ADDBA_REQ_REPORT: rsu_event_addba_req_report(sc, buf, len); break; default: device_printf(sc->sc_dev, "%s: unhandled code (%d)\n", __func__, code); break; } } static void rsu_rx_multi_event(struct rsu_softc *sc, uint8_t *buf, int len) { struct r92s_fw_cmd_hdr *cmd; int cmdsz; RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx events len=%d\n", __func__, len); /* Skip Rx status. */ buf += sizeof(struct r92s_rx_stat); len -= sizeof(struct r92s_rx_stat); /* Process all events. */ for (;;) { /* Check that command header fits. */ if (__predict_false(len < sizeof(*cmd))) break; cmd = (struct r92s_fw_cmd_hdr *)buf; /* Check that command payload fits. */ cmdsz = le16toh(cmd->len); if (__predict_false(len < sizeof(*cmd) + cmdsz)) break; /* Process firmware event. */ rsu_rx_event(sc, cmd->code, (uint8_t *)&cmd[1], cmdsz); if (!(cmd->seq & R92S_FW_CMD_MORE)) break; buf += sizeof(*cmd) + cmdsz; len -= sizeof(*cmd) + cmdsz; } } static int8_t rsu_get_rssi(struct rsu_softc *sc, int rate, void *physt) { static const int8_t cckoff[] = { 14, -2, -20, -40 }; struct r92s_rx_phystat *phy; struct r92s_rx_cck *cck; uint8_t rpt; int8_t rssi; if (rate <= 3) { cck = (struct r92s_rx_cck *)physt; rpt = (cck->agc_rpt >> 6) & 0x3; rssi = cck->agc_rpt & 0x3e; rssi = cckoff[rpt] - rssi; } else { /* OFDM/HT. */ phy = (struct r92s_rx_phystat *)physt; rssi = ((le32toh(phy->phydw1) >> 1) & 0x7f) - 106; } return (rssi); } static struct mbuf * rsu_rx_copy_to_mbuf(struct rsu_softc *sc, struct r92s_rx_stat *stat, int totlen) { struct ieee80211com *ic = &sc->sc_ic; struct mbuf *m; uint32_t rxdw0; int pktlen; rxdw0 = le32toh(stat->rxdw0); if (__predict_false(rxdw0 & (R92S_RXDW0_CRCERR | R92S_RXDW0_ICVERR))) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: RX flags error (%s)\n", __func__, rxdw0 & R92S_RXDW0_CRCERR ? "CRC" : "ICV"); goto fail; } pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); if (__predict_false(pktlen < sizeof (struct ieee80211_frame_ack))) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: frame is too short: %d\n", __func__, pktlen); goto fail; } m = m_get2(totlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) { device_printf(sc->sc_dev, "%s: could not allocate RX mbuf, totlen %d\n", __func__, totlen); goto fail; } /* Finalize mbuf. */ memcpy(mtod(m, uint8_t *), (uint8_t *)stat, totlen); m->m_pkthdr.len = m->m_len = totlen; return (m); fail: counter_u64_add(ic->ic_ierrors, 1); return (NULL); } static uint32_t rsu_get_tsf_low(struct rsu_softc *sc) { return (rsu_read_4(sc, R92S_TSFTR)); } static uint32_t rsu_get_tsf_high(struct rsu_softc *sc) { return (rsu_read_4(sc, R92S_TSFTR + 4)); } static struct ieee80211_node * rsu_rx_frame(struct rsu_softc *sc, struct mbuf *m) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame_min *wh; struct ieee80211_rx_stats rxs; struct r92s_rx_stat *stat; uint32_t rxdw0, rxdw3; uint8_t cipher, rate; int infosz; int rssi; stat = mtod(m, struct r92s_rx_stat *); rxdw0 = le32toh(stat->rxdw0); rxdw3 = le32toh(stat->rxdw3); rate = MS(rxdw3, R92S_RXDW3_RATE); cipher = MS(rxdw0, R92S_RXDW0_CIPHER); infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; /* Get RSSI from PHY status descriptor if present. */ if (infosz != 0 && (rxdw0 & R92S_RXDW0_PHYST)) rssi = rsu_get_rssi(sc, rate, &stat[1]); else { /* Cheat and get the last calibrated RSSI */ rssi = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); } /* Hardware does Rx TCP checksum offload. */ /* * This flag can be set for some other * (e.g., EAPOL) frame types, so don't rely on it. */ if (rxdw3 & R92S_RXDW3_TCPCHKVALID) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: TCP/IP checksums: %schecked / %schecked\n", __func__, (rxdw3 & R92S_RXDW3_TCPCHKRPT) ? "" : "not ", (rxdw3 & R92S_RXDW3_IPCHKRPT) ? "" : "not "); /* * 'IP header checksum valid' bit will not be set if * the frame was not checked / has incorrect checksum / * does not have checksum (IPv6). * * NB: if DF bit is not set then frame will not be checked. */ if (rxdw3 & R92S_RXDW3_IPCHKRPT) { m->m_pkthdr.csum_flags = CSUM_IP_CHECKED; m->m_pkthdr.csum_flags |= CSUM_IP_VALID; } /* * This is independent of the above check. */ if (rxdw3 & R92S_RXDW3_TCPCHKRPT) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; m->m_pkthdr.csum_flags |= CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } /* RX flags */ /* Set channel flags for input path */ bzero(&rxs, sizeof(rxs)); /* normal RSSI */ rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_rssi = rssi; rxs.c_nf = -96; /* Rate */ if (rate < 12) { rxs.c_rate = ridx2rate[rate]; if (RSU_RATE_IS_CCK(rate)) rxs.c_pktflags |= IEEE80211_RX_F_CCK; else rxs.c_pktflags |= IEEE80211_RX_F_OFDM; } else { rxs.c_rate = IEEE80211_RATE_MCS | (rate - 12); rxs.c_pktflags |= IEEE80211_RX_F_HT; } if (ieee80211_radiotap_active(ic)) { struct rsu_rx_radiotap_header *tap = &sc->sc_rxtap; /* Map HW rate index to 802.11 rate. */ tap->wr_flags = 0; /* TODO */ tap->wr_tsft = rsu_get_tsf_high(sc); if (le32toh(stat->tsf_low) > rsu_get_tsf_low(sc)) tap->wr_tsft--; tap->wr_tsft = (uint64_t)htole32(tap->wr_tsft) << 32; tap->wr_tsft += stat->tsf_low; tap->wr_rate = rxs.c_rate; tap->wr_dbm_antsignal = rssi; }; (void) ieee80211_add_rx_params(m, &rxs); /* Drop descriptor. */ m_adj(m, sizeof(*stat) + infosz); wh = mtod(m, struct ieee80211_frame_min *); if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && cipher != R92S_KEY_ALGO_NONE) { m->m_flags |= M_WEP; } RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx frame len %d, rate %d, infosz %d\n", __func__, m->m_len, rate, infosz); if (m->m_len >= sizeof(*wh)) return (ieee80211_find_rxnode(ic, wh)); return (NULL); } static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *sc, uint8_t *buf, int len) { struct r92s_rx_stat *stat; uint32_t rxdw0; int totlen, pktlen, infosz, npkts; struct mbuf *m, *m0 = NULL, *prevm = NULL; /* * don't pass packets to the ieee80211 framework if the driver isn't * RUNNING. */ if (!sc->sc_running) return (NULL); /* Get the number of encapsulated frames. */ stat = (struct r92s_rx_stat *)buf; npkts = MS(le32toh(stat->rxdw2), R92S_RXDW2_PKTCNT); RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx %d frames in one chunk\n", __func__, npkts); /* Process all of them. */ while (npkts-- > 0) { if (__predict_false(len < sizeof(*stat))) break; stat = (struct r92s_rx_stat *)buf; rxdw0 = le32toh(stat->rxdw0); pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); if (__predict_false(pktlen == 0)) break; infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; /* Make sure everything fits in xfer. */ totlen = sizeof(*stat) + infosz + pktlen; if (__predict_false(totlen > len)) break; /* Process 802.11 frame. */ m = rsu_rx_copy_to_mbuf(sc, stat, totlen); if (m0 == NULL) m0 = m; if (prevm == NULL) prevm = m; else { prevm->m_next = m; prevm = m; } /* Next chunk is 128-byte aligned. */ totlen = (totlen + 127) & ~127; buf += totlen; len -= totlen; } return (m0); } static struct mbuf * rsu_rxeof(struct usb_xfer *xfer, struct rsu_data *data) { struct rsu_softc *sc = data->sc; struct ieee80211com *ic = &sc->sc_ic; struct r92s_rx_stat *stat; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); if (__predict_false(len < sizeof(*stat))) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "xfer too short %d\n", len); counter_u64_add(ic->ic_ierrors, 1); return (NULL); } /* Determine if it is a firmware C2H event or an 802.11 frame. */ stat = (struct r92s_rx_stat *)data->buf; if ((le32toh(stat->rxdw1) & 0x1ff) == 0x1ff) { rsu_rx_multi_event(sc, data->buf, len); /* No packets to process. */ return (NULL); } else return (rsu_rx_multi_frame(sc, data->buf, len)); } static void rsu_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct epoch_tracker et; struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; struct mbuf *m = NULL, *next; struct rsu_data *data; RSU_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); m = rsu_rxeof(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) { KASSERT(m == NULL, ("mbuf isn't NULL")); return; } STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ NET_EPOCH_ENTER(et); while (m != NULL) { next = m->m_next; m->m_next = NULL; ni = rsu_rx_frame(sc, m); RSU_UNLOCK(sc); if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void)ieee80211_input_mimo(ni, m); ieee80211_free_node(ni); } else (void)ieee80211_input_mimo_all(ic, m); RSU_LOCK(sc); m = next; } NET_EPOCH_EXIT(et); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } break; } } static void rsu_txeof(struct usb_xfer *xfer, struct rsu_data *data) { #ifdef USB_DEBUG struct rsu_softc *sc = usbd_xfer_softc(xfer); #endif RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: called; data=%p\n", __func__, data); if (data->m) { /* XXX status? */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } } static void rsu_bulk_tx_callback_sub(struct usb_xfer *xfer, usb_error_t error, uint8_t which) { struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct rsu_data *data; RSU_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data == NULL) goto tr_setup; RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: transfer done %p\n", __func__, data); STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); rsu_txeof(xfer, data); rsu_freebuf(sc, data); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_tx_pending[which]); if (data == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: empty pending queue sc %p\n", __func__, sc); return; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending[which], next); STAILQ_INSERT_TAIL(&sc->sc_tx_active[which], data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: submitting transfer %p\n", __func__, data); usbd_transfer_submit(xfer); break; default: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); rsu_txeof(xfer, data); rsu_freebuf(sc, data); } counter_u64_add(ic->ic_oerrors, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } /* * XXX TODO: if the queue is low, flush out FF TX frames. * Remember to unlock the driver for now; net80211 doesn't * defer it for us. */ } static void rsu_bulk_tx_callback_be_bk(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_BE_BK); /* This kicks the TX taskqueue */ rsu_start(sc); } static void rsu_bulk_tx_callback_vi_vo(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_VI_VO); /* This kicks the TX taskqueue */ rsu_start(sc); } static void rsu_bulk_tx_callback_h2c(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_H2C); /* This kicks the TX taskqueue */ rsu_start(sc); } /* * Transmit the given frame. * * This doesn't free the node or mbuf upon failure. */ static int rsu_tx_start(struct rsu_softc *sc, struct ieee80211_node *ni, struct mbuf *m0, struct rsu_data *data) { const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct r92s_tx_desc *txd; uint8_t rate, ridx, type, cipher, qos; int prio = 0; uint8_t which; int hasqos; int ismcast; int xferlen; int qid; RSU_ASSERT_LOCKED(sc); wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: data=%p, m=%p\n", __func__, data, m0); /* Choose a TX rate index. */ if (type == IEEE80211_FC0_TYPE_MGT || type == IEEE80211_FC0_TYPE_CTL || (m0->m_flags & M_EAPOL) != 0) rate = tp->mgmtrate; else if (ismcast) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else rate = 0; if (rate != 0) ridx = rate2ridx(rate); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { device_printf(sc->sc_dev, "ieee80211_crypto_encap returns NULL.\n"); /* XXX we don't expect the fragmented frames */ return (ENOBUFS); } wh = mtod(m0, struct ieee80211_frame *); } /* If we have QoS then use it */ /* XXX TODO: mbuf WME/PRI versus TID? */ if (IEEE80211_QOS_HAS_SEQ(wh)) { /* Has QoS */ prio = M_WME_GETAC(m0); which = rsu_wme_ac_xfer_map[prio]; hasqos = 1; qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; } else { /* Non-QoS TID */ /* XXX TODO: tid=0 for non-qos TID? */ which = rsu_wme_ac_xfer_map[WME_AC_BE]; hasqos = 0; prio = 0; qos = 0; } qid = rsu_ac2qid[prio]; #if 0 switch (type) { case IEEE80211_FC0_TYPE_CTL: case IEEE80211_FC0_TYPE_MGT: which = rsu_wme_ac_xfer_map[WME_AC_VO]; break; default: which = rsu_wme_ac_xfer_map[M_WME_GETAC(m0)]; break; } hasqos = 0; #endif RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: pri=%d, which=%d, hasqos=%d\n", __func__, prio, which, hasqos); /* Fill Tx descriptor. */ txd = (struct r92s_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); txd->txdw0 |= htole32( SM(R92S_TXDW0_PKTLEN, m0->m_pkthdr.len) | SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); txd->txdw1 |= htole32( SM(R92S_TXDW1_MACID, R92S_MACID_BSS) | SM(R92S_TXDW1_QSEL, qid)); if (!hasqos) txd->txdw1 |= htole32(R92S_TXDW1_NONQOS); if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWENCRYPT)) { switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: cipher = R92S_TXDW1_CIPHER_WEP; break; case IEEE80211_CIPHER_TKIP: cipher = R92S_TXDW1_CIPHER_TKIP; break; case IEEE80211_CIPHER_AES_CCM: cipher = R92S_TXDW1_CIPHER_AES; break; default: cipher = R92S_TXDW1_CIPHER_NONE; } txd->txdw1 |= htole32( SM(R92S_TXDW1_CIPHER, cipher) | SM(R92S_TXDW1_KEYIDX, k->wk_keyix)); } /* XXX todo: set AGGEN bit if appropriate? */ txd->txdw2 |= htole32(R92S_TXDW2_BK); if (ismcast) txd->txdw2 |= htole32(R92S_TXDW2_BMCAST); if (!ismcast && (!qos || (qos & IEEE80211_QOS_ACKPOLICY) != IEEE80211_QOS_ACKPOLICY_NOACK)) { txd->txdw2 |= htole32(R92S_TXDW2_RTY_LMT_ENA); txd->txdw2 |= htole32(SM(R92S_TXDW2_RTY_LMT, tp->maxretry)); } /* Force mgmt / mcast / ucast rate if needed. */ if (rate != 0) { /* Data rate fallback limit (max). */ txd->txdw5 |= htole32(SM(R92S_TXDW5_DATARATE_FB_LMT, 0x1f)); txd->txdw5 |= htole32(SM(R92S_TXDW5_DATARATE, ridx)); txd->txdw4 |= htole32(R92S_TXDW4_DRVRATE); } /* * Firmware will use and increment the sequence number for the * specified priority. */ txd->txdw3 |= htole32(SM(R92S_TXDW3_SEQ, prio)); if (ieee80211_radiotap_active_vap(vap)) { struct rsu_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; ieee80211_radiotap_tx(vap, m0); } xferlen = sizeof(*txd) + m0->m_pkthdr.len; m_copydata(m0, 0, m0->m_pkthdr.len, (caddr_t)&txd[1]); data->buflen = xferlen; data->ni = ni; data->m = m0; STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); /* start transfer, if any */ usbd_transfer_start(sc->sc_xfer[which]); return (0); } static int rsu_transmit(struct ieee80211com *ic, struct mbuf *m) { struct rsu_softc *sc = ic->ic_softc; int error; RSU_LOCK(sc); if (!sc->sc_running) { RSU_UNLOCK(sc); return (ENXIO); } /* * XXX TODO: ensure that we treat 'm' as a list of frames * to transmit! */ error = mbufq_enqueue(&sc->sc_snd, m); if (error) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: mbufq_enable: failed (%d)\n", __func__, error); RSU_UNLOCK(sc); return (error); } RSU_UNLOCK(sc); /* This kicks the TX taskqueue */ rsu_start(sc); return (0); } static void rsu_drain_mbufq(struct rsu_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; RSU_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static void _rsu_start(struct rsu_softc *sc) { struct ieee80211_node *ni; struct rsu_data *bf; struct mbuf *m; RSU_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = rsu_getbuf(sc); if (bf == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: failed to get buffer\n", __func__); mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; if (rsu_tx_start(sc, ni, m, bf) != 0) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: failed to transmit\n", __func__); if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); rsu_freebuf(sc, bf); ieee80211_free_node(ni); m_freem(m); break; } } } static void rsu_start(struct rsu_softc *sc) { taskqueue_enqueue(taskqueue_thread, &sc->tx_task); } static int rsu_ioctl_net(struct ieee80211com *ic, u_long cmd, void *data) { struct rsu_softc *sc = ic->ic_softc; struct ifreq *ifr = (struct ifreq *)data; int error; error = 0; switch (cmd) { case SIOCSIFCAP: { struct ieee80211vap *vap; int rxmask; rxmask = ifr->ifr_reqcap & (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6); RSU_LOCK(sc); /* Both RXCSUM bits must be set (or unset). */ if (sc->sc_rx_checksum_enable && rxmask != (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6)) { rxmask = 0; sc->sc_rx_checksum_enable = 0; rsu_rxfilter_set(sc, R92S_RCR_TCP_OFFLD_EN, 0); } else if (!sc->sc_rx_checksum_enable && rxmask != 0) { rxmask = IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6; sc->sc_rx_checksum_enable = 1; rsu_rxfilter_set(sc, 0, R92S_RCR_TCP_OFFLD_EN); } else { /* Nothing to do. */ RSU_UNLOCK(sc); break; } RSU_UNLOCK(sc); IEEE80211_LOCK(ic); /* XXX */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ifnet *ifp = vap->iv_ifp; ifp->if_capenable &= ~(IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6); ifp->if_capenable |= rxmask; } IEEE80211_UNLOCK(ic); break; } default: error = ENOTTY; /* for net80211 */ break; } return (error); } static void rsu_parent(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; if (ic->ic_nrunning > 0) { if (rsu_init(sc) == 0) ieee80211_start_all(ic); else { struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); if (vap != NULL) ieee80211_stop(vap); } } else rsu_stop(sc); } /* * Power on sequence for A-cut adapters. */ static void rsu_power_on_acut(struct rsu_softc *sc) { uint32_t reg; rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53); rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57); /* Enable AFE macro block's bandgap and Mbias. */ rsu_write_1(sc, R92S_AFE_MISC, rsu_read_1(sc, R92S_AFE_MISC) | R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN); /* Enable LDOA15 block. */ rsu_write_1(sc, R92S_LDOA15_CTRL, rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN); rsu_write_1(sc, R92S_SPS1_CTRL, rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_LDEN); rsu_ms_delay(sc, 2000); /* Enable switch regulator block. */ rsu_write_1(sc, R92S_SPS1_CTRL, rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_SWEN); rsu_write_4(sc, R92S_SPS1_CTRL, 0x00a7b267); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x90); /* Enable AFE clock. */ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1, rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04); /* Enable AFE PLL macro block. */ rsu_write_1(sc, R92S_AFE_PLL_CTRL, rsu_read_1(sc, R92S_AFE_PLL_CTRL) | 0x11); /* Attach AFE PLL to MACTOP/BB. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL, rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11); /* Switch to 40MHz clock instead of 80MHz. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) & ~R92S_SYS_CLKSEL); /* Enable MAC clock. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) | R92S_MAC_CLK_EN | R92S_SYS_CLK_EN); rsu_write_1(sc, R92S_PMC_FSM, 0x02); /* Enable digital core and IOREG R/W. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80); /* Switch the control path to firmware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL; rsu_write_2(sc, R92S_SYS_CLKR, reg); rsu_write_2(sc, R92S_CR, 0x37fc); /* Fix USB RX FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); rsu_write_1(sc, 0x00ab, rsu_read_1(sc, 0x00ab) | 0xc0); rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL); } /* * Power on sequence for B-cut and C-cut adapters. */ static void rsu_power_on_bcut(struct rsu_softc *sc) { uint32_t reg; int ntries; /* Prevent eFuse leakage. */ rsu_write_1(sc, 0x37, 0xb0); rsu_ms_delay(sc, 10); rsu_write_1(sc, 0x37, 0x30); /* Switch the control path to hardware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); if (reg & R92S_FWHW_SEL) { rsu_write_2(sc, R92S_SYS_CLKR, reg & ~(R92S_SWHW_SEL | R92S_FWHW_SEL)); } rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) & ~0x8c); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53); rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57); reg = rsu_read_1(sc, R92S_AFE_MISC); rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN); rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN | R92S_AFE_MISC_I32_EN); /* Enable PLL. */ rsu_write_1(sc, R92S_LDOA15_CTRL, rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN); rsu_write_1(sc, R92S_LDOV12D_CTRL, rsu_read_1(sc, R92S_LDOV12D_CTRL) | R92S_LDV12_EN); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20); /* Support 64KB IMEM. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x97); /* Enable AFE clock. */ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1, rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04); /* Enable AFE PLL macro block. */ reg = rsu_read_1(sc, R92S_AFE_PLL_CTRL); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x51); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11); rsu_ms_delay(sc, 1); /* Attach AFE PLL to MACTOP/BB. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL, rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11); /* Switch to 40MHz clock. */ rsu_write_1(sc, R92S_SYS_CLKR, 0x00); /* Disable CPU clock and 80MHz SSC. */ rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) | 0xa0); /* Enable MAC clock. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) | R92S_MAC_CLK_EN | R92S_SYS_CLK_EN); rsu_write_1(sc, R92S_PMC_FSM, 0x02); /* Enable digital core and IOREG R/W. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80); /* Switch the control path to firmware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL; rsu_write_2(sc, R92S_SYS_CLKR, reg); rsu_write_2(sc, R92S_CR, 0x37fc); /* Fix USB RX FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL); rsu_write_1(sc, 0xfe1c, 0x80); /* Make sure TxDMA is ready to download firmware. */ for (ntries = 0; ntries < 20; ntries++) { reg = rsu_read_1(sc, R92S_TCR); if ((reg & (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) == (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) break; rsu_ms_delay(sc, 1); } if (ntries == 20) { RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_TX, "%s: TxDMA is not ready\n", __func__); /* Reset TxDMA. */ reg = rsu_read_1(sc, R92S_CR); rsu_write_1(sc, R92S_CR, reg & ~R92S_CR_TXDMA_EN); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_CR, reg | R92S_CR_TXDMA_EN); } } static void rsu_power_off(struct rsu_softc *sc) { /* Turn RF off. */ rsu_write_1(sc, R92S_RF_CTRL, 0x00); rsu_ms_delay(sc, 5); /* Turn MAC off. */ /* Switch control path. */ rsu_write_1(sc, R92S_SYS_CLKR + 1, 0x38); /* Reset MACTOP. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x70); rsu_write_1(sc, R92S_PMC_FSM, 0x06); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 0, 0xf9); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, 0xe8); /* Disable AFE PLL. */ rsu_write_1(sc, R92S_AFE_PLL_CTRL, 0x00); /* Disable A15V. */ rsu_write_1(sc, R92S_LDOA15_CTRL, 0x54); /* Disable eFuse 1.2V. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x50); rsu_write_1(sc, R92S_LDOV12D_CTRL, 0x24); /* Enable AFE macro block's bandgap and Mbias. */ rsu_write_1(sc, R92S_AFE_MISC, 0x30); /* Disable 1.6V LDO. */ rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x56); rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x43); /* Firmware - tell it to switch things off */ (void) rsu_set_fw_power_state(sc, RSU_PWR_OFF); } static int rsu_fw_loadsection(struct rsu_softc *sc, const uint8_t *buf, int len) { const uint8_t which = rsu_wme_ac_xfer_map[WME_AC_VO]; struct rsu_data *data; struct r92s_tx_desc *txd; int mlen; while (len > 0) { data = rsu_getbuf(sc); if (data == NULL) return (ENOMEM); txd = (struct r92s_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); if (len <= RSU_TXBUFSZ - sizeof(*txd)) { /* Last chunk. */ txd->txdw0 |= htole32(R92S_TXDW0_LINIP); mlen = len; } else mlen = RSU_TXBUFSZ - sizeof(*txd); txd->txdw0 |= htole32(SM(R92S_TXDW0_PKTLEN, mlen)); memcpy(&txd[1], buf, mlen); data->buflen = sizeof(*txd) + mlen; RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FW | RSU_DEBUG_RESET, "%s: starting transfer %p\n", __func__, data); STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); buf += mlen; len -= mlen; } usbd_transfer_start(sc->sc_xfer[which]); return (0); } CTASSERT(sizeof(size_t) >= sizeof(uint32_t)); static int rsu_load_firmware(struct rsu_softc *sc) { const struct r92s_fw_hdr *hdr; struct r92s_fw_priv *dmem; struct ieee80211com *ic = &sc->sc_ic; const uint8_t *imem, *emem; uint32_t imemsz, ememsz; const struct firmware *fw; size_t size; uint32_t reg; int ntries, error; if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY) { RSU_DPRINTF(sc, RSU_DEBUG_ANY, "%s: Firmware already loaded\n", __func__); return (0); } RSU_UNLOCK(sc); /* Read firmware image from the filesystem. */ if ((fw = firmware_get("rsu-rtl8712fw")) == NULL) { device_printf(sc->sc_dev, "%s: failed load firmware of file rsu-rtl8712fw\n", __func__); RSU_LOCK(sc); return (ENXIO); } RSU_LOCK(sc); size = fw->datasize; if (size < sizeof(*hdr)) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } hdr = (const struct r92s_fw_hdr *)fw->data; if (hdr->signature != htole16(0x8712) && hdr->signature != htole16(0x8192)) { device_printf(sc->sc_dev, "invalid firmware signature 0x%x\n", le16toh(hdr->signature)); error = EINVAL; goto fail; } RSU_DPRINTF(sc, RSU_DEBUG_FW, "FW V%d %02x-%02x %02x:%02x\n", le16toh(hdr->version), hdr->month, hdr->day, hdr->hour, hdr->minute); /* Make sure that driver and firmware are in sync. */ if (hdr->privsz != htole32(sizeof(*dmem))) { device_printf(sc->sc_dev, "unsupported firmware image\n"); error = EINVAL; goto fail; } /* Get FW sections sizes. */ imemsz = le32toh(hdr->imemsz); ememsz = le32toh(hdr->sramsz); /* Check that all FW sections fit in image. */ if (imemsz > (size_t)(size - sizeof(*hdr)) || ememsz > (size_t)(size - sizeof(*hdr) - imemsz)) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } imem = (const uint8_t *)&hdr[1]; emem = imem + imemsz; /* Load IMEM section. */ error = rsu_fw_loadsection(sc, imem, imemsz); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "IMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries != 50; ntries++) { rsu_ms_delay(sc, 10); reg = rsu_read_1(sc, R92S_TCR); if (reg & R92S_TCR_IMEM_CODE_DONE) break; } if (ntries == 50) { device_printf(sc->sc_dev, "timeout waiting for IMEM transfer\n"); error = ETIMEDOUT; goto fail; } /* Load EMEM section. */ error = rsu_fw_loadsection(sc, emem, ememsz); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "EMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries != 50; ntries++) { rsu_ms_delay(sc, 10); reg = rsu_read_2(sc, R92S_TCR); if (reg & R92S_TCR_EMEM_CODE_DONE) break; } if (ntries == 50) { device_printf(sc->sc_dev, "timeout waiting for EMEM transfer\n"); error = ETIMEDOUT; goto fail; } /* Enable CPU. */ rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) | R92S_SYS_CPU_CLKSEL); if (!(rsu_read_1(sc, R92S_SYS_CLKR) & R92S_SYS_CPU_CLKSEL)) { device_printf(sc->sc_dev, "could not enable system clock\n"); error = EIO; goto fail; } rsu_write_2(sc, R92S_SYS_FUNC_EN, rsu_read_2(sc, R92S_SYS_FUNC_EN) | R92S_FEN_CPUEN); if (!(rsu_read_2(sc, R92S_SYS_FUNC_EN) & R92S_FEN_CPUEN)) { device_printf(sc->sc_dev, "could not enable microcontroller\n"); error = EIO; goto fail; } /* Wait for CPU to initialize. */ for (ntries = 0; ntries < 100; ntries++) { if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_IMEM_RDY) break; rsu_ms_delay(sc, 1); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for microcontroller\n"); error = ETIMEDOUT; goto fail; } /* Update DMEM section before loading. */ dmem = __DECONST(struct r92s_fw_priv *, &hdr->priv); memset(dmem, 0, sizeof(*dmem)); dmem->hci_sel = R92S_HCI_SEL_USB | R92S_HCI_SEL_8172; dmem->nendpoints = sc->sc_nendpoints; dmem->chip_version = sc->cut; dmem->rf_config = sc->sc_rftype; dmem->vcs_type = R92S_VCS_TYPE_AUTO; dmem->vcs_mode = R92S_VCS_MODE_RTS_CTS; dmem->turbo_mode = 0; dmem->bw40_en = !! (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40); dmem->amsdu2ampdu_en = !! (sc->sc_ht); dmem->ampdu_en = !! (sc->sc_ht); dmem->agg_offload = !! (sc->sc_ht); dmem->qos_en = 1; dmem->ps_offload = 1; dmem->lowpower_mode = 1; /* XXX TODO: configurable? */ /* Load DMEM section. */ error = rsu_fw_loadsection(sc, (uint8_t *)dmem, sizeof(*dmem)); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "DMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries < 100; ntries++) { if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_DMEM_CODE_DONE) break; rsu_ms_delay(sc, 1); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for %s transfer\n", "DMEM"); error = ETIMEDOUT; goto fail; } /* Wait for firmware readiness. */ for (ntries = 0; ntries < 60; ntries++) { if (!(rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY)) break; rsu_ms_delay(sc, 1); } if (ntries == 60) { device_printf(sc->sc_dev, "timeout waiting for firmware readiness\n"); error = ETIMEDOUT; goto fail; } fail: firmware_put(fw, FIRMWARE_UNLOAD); return (error); } static int rsu_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct rsu_softc *sc = ic->ic_softc; struct rsu_data *bf; /* prevent management frames from being sent if we're not ready */ if (!sc->sc_running) { m_freem(m); return (ENETDOWN); } RSU_LOCK(sc); bf = rsu_getbuf(sc); if (bf == NULL) { m_freem(m); RSU_UNLOCK(sc); return (ENOBUFS); } if (rsu_tx_start(sc, ni, m, bf) != 0) { m_freem(m); rsu_freebuf(sc, bf); RSU_UNLOCK(sc); return (EIO); } RSU_UNLOCK(sc); return (0); } static void rsu_rxfilter_init(struct rsu_softc *sc) { uint32_t reg; RSU_ASSERT_LOCKED(sc); /* Setup multicast filter. */ rsu_set_multi(sc); /* Adjust Rx filter. */ reg = rsu_read_4(sc, R92S_RCR); reg &= ~R92S_RCR_AICV; reg |= R92S_RCR_APP_PHYSTS; if (sc->sc_rx_checksum_enable) reg |= R92S_RCR_TCP_OFFLD_EN; rsu_write_4(sc, R92S_RCR, reg); /* Update dynamic Rx filter parts. */ rsu_rxfilter_refresh(sc); } static void rsu_rxfilter_set(struct rsu_softc *sc, uint32_t clear, uint32_t set) { /* NB: firmware can touch this register too. */ rsu_write_4(sc, R92S_RCR, (rsu_read_4(sc, R92S_RCR) & ~clear) | set); } static void rsu_rxfilter_refresh(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t mask_all, mask_min; RSU_ASSERT_LOCKED(sc); /* NB: RCR_AMF / RXFLTMAP_MGT are used by firmware. */ mask_all = R92S_RCR_ACF | R92S_RCR_AAP; mask_min = R92S_RCR_APM; if (sc->sc_vap_is_running) mask_min |= R92S_RCR_CBSSID; else mask_all |= R92S_RCR_ADF; if (ic->ic_opmode == IEEE80211_M_MONITOR) { uint16_t rxfltmap; if (sc->sc_vap_is_running) rxfltmap = 0; else rxfltmap = R92S_RXFLTMAP_MGT_DEF; rsu_write_2(sc, R92S_RXFLTMAP_MGT, rxfltmap); } if (ic->ic_promisc == 0 && ic->ic_opmode != IEEE80211_M_MONITOR) rsu_rxfilter_set(sc, mask_all, mask_min); else rsu_rxfilter_set(sc, mask_min, mask_all); } static int rsu_init(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint8_t macaddr[IEEE80211_ADDR_LEN]; int error; int i; RSU_LOCK(sc); if (sc->sc_running) { RSU_UNLOCK(sc); return (0); } /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); /* Reset power management state. */ rsu_write_1(sc, R92S_USB_HRPWM, 0); /* Power on adapter. */ if (sc->cut == 1) rsu_power_on_acut(sc); else rsu_power_on_bcut(sc); /* Load firmware. */ error = rsu_load_firmware(sc); if (error != 0) goto fail; rsu_write_4(sc, R92S_CR, rsu_read_4(sc, R92S_CR) & ~0xff000000); /* Use 128 bytes pages. */ rsu_write_1(sc, 0x00b5, rsu_read_1(sc, 0x00b5) | 0x01); /* Enable USB Rx aggregation. */ rsu_write_1(sc, 0x00bd, rsu_read_1(sc, 0x00bd) | 0x80); /* Set USB Rx aggregation threshold. */ rsu_write_1(sc, 0x00d9, 0x01); /* Set USB Rx aggregation timeout (1.7ms/4). */ rsu_write_1(sc, 0xfe5b, 0x04); /* Fix USB Rx FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); /* Set MAC address. */ IEEE80211_ADDR_COPY(macaddr, vap ? vap->iv_myaddr : ic->ic_macaddr); rsu_write_region_1(sc, R92S_MACID, macaddr, IEEE80211_ADDR_LEN); /* It really takes 1.5 seconds for the firmware to boot: */ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(2000)); RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting MAC address to %s\n", __func__, ether_sprintf(macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_MAC_ADDRESS, macaddr, IEEE80211_ADDR_LEN); if (error != 0) { device_printf(sc->sc_dev, "could not set MAC address\n"); goto fail; } /* Initialize Rx filter. */ rsu_rxfilter_init(sc); /* Set PS mode fully active */ error = rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); if (error != 0) { device_printf(sc->sc_dev, "could not set PS mode\n"); goto fail; } /* Install static keys (if any). */ error = rsu_reinit_static_keys(sc); if (error != 0) goto fail; sc->sc_extra_scan = 0; usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]); /* We're ready to go. */ sc->sc_running = 1; RSU_UNLOCK(sc); return (0); fail: /* Need to stop all failed transfers, if any */ for (i = 0; i != RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); RSU_UNLOCK(sc); return (error); } static void rsu_stop(struct rsu_softc *sc) { int i; RSU_LOCK(sc); if (!sc->sc_running) { RSU_UNLOCK(sc); return; } sc->sc_running = 0; sc->sc_vap_is_running = 0; sc->sc_calibrating = 0; taskqueue_cancel_timeout(taskqueue_thread, &sc->calib_task, NULL); taskqueue_cancel(taskqueue_thread, &sc->tx_task, NULL); /* Power off adapter. */ rsu_power_off(sc); /* * CAM is not accessible after shutdown; * all entries are marked (by firmware?) as invalid. */ memset(sc->free_keys_bmap, 0, sizeof(sc->free_keys_bmap)); memset(sc->keys_bmap, 0, sizeof(sc->keys_bmap)); for (i = 0; i < RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); RSU_UNLOCK(sc); } /* * Note: usb_pause_mtx() actually releases the mutex before calling pause(), * which breaks any kind of driver serialisation. */ static void rsu_ms_delay(struct rsu_softc *sc, int ms) { //usb_pause_mtx(&sc->sc_mtx, hz / 1000); DELAY(ms * 1000); } Index: head/sys/dev/usb/wlan/if_rum.c =================================================================== --- head/sys/dev/usb/wlan/if_rum.c (revision 357971) +++ head/sys/dev/usb/wlan/if_rum.c (revision 357972) @@ -1,3302 +1,3303 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2005-2007 Damien Bergamini * Copyright (c) 2006 Niall O'Higgins * Copyright (c) 2007-2008 Hans Petter Selasky * Copyright (c) 2015 Andriy Voskoboinyk * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /*- * Ralink Technology RT2501USB/RT2601USB chipset driver * http://www.ralinktech.com.tw/ */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR rum_debug #include #include #include #include #ifdef USB_DEBUG static int rum_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, rum, CTLFLAG_RW, 0, "USB rum"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, rum, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB rum"); SYSCTL_INT(_hw_usb_rum, OID_AUTO, debug, CTLFLAG_RWTUN, &rum_debug, 0, "Debug level"); #endif static const STRUCT_USB_HOST_ID rum_devs[] = { #define RUM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } RUM_DEV(ABOCOM, HWU54DM), RUM_DEV(ABOCOM, RT2573_2), RUM_DEV(ABOCOM, RT2573_3), RUM_DEV(ABOCOM, RT2573_4), RUM_DEV(ABOCOM, WUG2700), RUM_DEV(AMIT, CGWLUSB2GO), RUM_DEV(ASUS, RT2573_1), RUM_DEV(ASUS, RT2573_2), RUM_DEV(BELKIN, F5D7050A), RUM_DEV(BELKIN, F5D9050V3), RUM_DEV(CISCOLINKSYS, WUSB54GC), RUM_DEV(CISCOLINKSYS, WUSB54GR), RUM_DEV(CONCEPTRONIC2, C54RU2), RUM_DEV(COREGA, CGWLUSB2GL), RUM_DEV(COREGA, CGWLUSB2GPX), RUM_DEV(DICKSMITH, CWD854F), RUM_DEV(DICKSMITH, RT2573), RUM_DEV(EDIMAX, EW7318USG), RUM_DEV(DLINK2, DWLG122C1), RUM_DEV(DLINK2, WUA1340), RUM_DEV(DLINK2, DWA111), RUM_DEV(DLINK2, DWA110), RUM_DEV(GIGABYTE, GNWB01GS), RUM_DEV(GIGABYTE, GNWI05GS), RUM_DEV(GIGASET, RT2573), RUM_DEV(GOODWAY, RT2573), RUM_DEV(GUILLEMOT, HWGUSB254LB), RUM_DEV(GUILLEMOT, HWGUSB254V2AP), RUM_DEV(HUAWEI3COM, WUB320G), RUM_DEV(MELCO, G54HP), RUM_DEV(MELCO, SG54HP), RUM_DEV(MELCO, SG54HG), RUM_DEV(MELCO, WLIUCG), RUM_DEV(MELCO, WLRUCG), RUM_DEV(MELCO, WLRUCGAOSS), RUM_DEV(MSI, RT2573_1), RUM_DEV(MSI, RT2573_2), RUM_DEV(MSI, RT2573_3), RUM_DEV(MSI, RT2573_4), RUM_DEV(NOVATECH, RT2573), RUM_DEV(PLANEX2, GWUS54HP), RUM_DEV(PLANEX2, GWUS54MINI2), RUM_DEV(PLANEX2, GWUSMM), RUM_DEV(QCOM, RT2573), RUM_DEV(QCOM, RT2573_2), RUM_DEV(QCOM, RT2573_3), RUM_DEV(RALINK, RT2573), RUM_DEV(RALINK, RT2573_2), RUM_DEV(RALINK, RT2671), RUM_DEV(SITECOMEU, WL113R2), RUM_DEV(SITECOMEU, WL172), RUM_DEV(SPARKLAN, RT2573), RUM_DEV(SURECOM, RT2573), #undef RUM_DEV }; static device_probe_t rum_match; static device_attach_t rum_attach; static device_detach_t rum_detach; static usb_callback_t rum_bulk_read_callback; static usb_callback_t rum_bulk_write_callback; static usb_error_t rum_do_request(struct rum_softc *sc, struct usb_device_request *req, void *data); static usb_error_t rum_do_mcu_request(struct rum_softc *sc, int); static struct ieee80211vap *rum_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void rum_vap_delete(struct ieee80211vap *); static void rum_cmdq_cb(void *, int); static int rum_cmd_sleepable(struct rum_softc *, const void *, size_t, uint8_t, CMD_FUNC_PROTO); static void rum_tx_free(struct rum_tx_data *, int); static void rum_setup_tx_list(struct rum_softc *); static void rum_reset_tx_list(struct rum_softc *, struct ieee80211vap *); static void rum_unsetup_tx_list(struct rum_softc *); static void rum_beacon_miss(struct ieee80211vap *); static void rum_sta_recv_mgmt(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *, int, int); static int rum_set_power_state(struct rum_softc *, int); static int rum_newstate(struct ieee80211vap *, enum ieee80211_state, int); static uint8_t rum_crypto_mode(struct rum_softc *, u_int, int); static void rum_setup_tx_desc(struct rum_softc *, struct rum_tx_desc *, struct ieee80211_key *, uint32_t, uint8_t, uint8_t, int, int, int); static uint32_t rum_tx_crypto_flags(struct rum_softc *, struct ieee80211_node *, const struct ieee80211_key *); static int rum_tx_mgt(struct rum_softc *, struct mbuf *, struct ieee80211_node *); static int rum_tx_raw(struct rum_softc *, struct mbuf *, struct ieee80211_node *, const struct ieee80211_bpf_params *); static int rum_tx_data(struct rum_softc *, struct mbuf *, struct ieee80211_node *); static int rum_transmit(struct ieee80211com *, struct mbuf *); static void rum_start(struct rum_softc *); static void rum_parent(struct ieee80211com *); static void rum_eeprom_read(struct rum_softc *, uint16_t, void *, int); static uint32_t rum_read(struct rum_softc *, uint16_t); static void rum_read_multi(struct rum_softc *, uint16_t, void *, int); static usb_error_t rum_write(struct rum_softc *, uint16_t, uint32_t); static usb_error_t rum_write_multi(struct rum_softc *, uint16_t, void *, size_t); static usb_error_t rum_setbits(struct rum_softc *, uint16_t, uint32_t); static usb_error_t rum_clrbits(struct rum_softc *, uint16_t, uint32_t); static usb_error_t rum_modbits(struct rum_softc *, uint16_t, uint32_t, uint32_t); static int rum_bbp_busy(struct rum_softc *); static void rum_bbp_write(struct rum_softc *, uint8_t, uint8_t); static uint8_t rum_bbp_read(struct rum_softc *, uint8_t); static void rum_rf_write(struct rum_softc *, uint8_t, uint32_t); static void rum_select_antenna(struct rum_softc *); static void rum_enable_mrr(struct rum_softc *); static void rum_set_txpreamble(struct rum_softc *); static void rum_set_basicrates(struct rum_softc *); static void rum_select_band(struct rum_softc *, struct ieee80211_channel *); static void rum_set_chan(struct rum_softc *, struct ieee80211_channel *); static void rum_set_maxretry(struct rum_softc *, struct ieee80211vap *); static int rum_enable_tsf_sync(struct rum_softc *); static void rum_enable_tsf(struct rum_softc *); static void rum_abort_tsf_sync(struct rum_softc *); static void rum_get_tsf(struct rum_softc *, uint64_t *); static void rum_update_slot_cb(struct rum_softc *, union sec_param *, uint8_t); static void rum_update_slot(struct ieee80211com *); static int rum_wme_update(struct ieee80211com *); static void rum_set_bssid(struct rum_softc *, const uint8_t *); static void rum_set_macaddr(struct rum_softc *, const uint8_t *); static void rum_update_mcast(struct ieee80211com *); static void rum_update_promisc(struct ieee80211com *); static void rum_setpromisc(struct rum_softc *); static const char *rum_get_rf(int); static void rum_read_eeprom(struct rum_softc *); static int rum_bbp_wakeup(struct rum_softc *); static int rum_bbp_init(struct rum_softc *); static void rum_clr_shkey_regs(struct rum_softc *); static int rum_init(struct rum_softc *); static void rum_stop(struct rum_softc *); static void rum_load_microcode(struct rum_softc *, const uint8_t *, size_t); static int rum_set_sleep_time(struct rum_softc *, uint16_t); static int rum_reset(struct ieee80211vap *, u_long); static int rum_set_beacon(struct rum_softc *, struct ieee80211vap *); static int rum_alloc_beacon(struct rum_softc *, struct ieee80211vap *); static void rum_update_beacon_cb(struct rum_softc *, union sec_param *, uint8_t); static void rum_update_beacon(struct ieee80211vap *, int); static int rum_common_key_set(struct rum_softc *, struct ieee80211_key *, uint16_t); static void rum_group_key_set_cb(struct rum_softc *, union sec_param *, uint8_t); static void rum_group_key_del_cb(struct rum_softc *, union sec_param *, uint8_t); static void rum_pair_key_set_cb(struct rum_softc *, union sec_param *, uint8_t); static void rum_pair_key_del_cb(struct rum_softc *, union sec_param *, uint8_t); static int rum_key_alloc(struct ieee80211vap *, struct ieee80211_key *, ieee80211_keyix *, ieee80211_keyix *); static int rum_key_set(struct ieee80211vap *, const struct ieee80211_key *); static int rum_key_delete(struct ieee80211vap *, const struct ieee80211_key *); static int rum_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void rum_scan_start(struct ieee80211com *); static void rum_scan_end(struct ieee80211com *); static void rum_set_channel(struct ieee80211com *); static void rum_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static int rum_get_rssi(struct rum_softc *, uint8_t); static void rum_ratectl_start(struct rum_softc *, struct ieee80211_node *); static void rum_ratectl_timeout(void *); static void rum_ratectl_task(void *, int); static int rum_pause(struct rum_softc *, int); static const struct { uint32_t reg; uint32_t val; } rum_def_mac[] = { { RT2573_TXRX_CSR0, 0x025fb032 }, { RT2573_TXRX_CSR1, 0x9eaa9eaf }, { RT2573_TXRX_CSR2, 0x8a8b8c8d }, { RT2573_TXRX_CSR3, 0x00858687 }, { RT2573_TXRX_CSR7, 0x2e31353b }, { RT2573_TXRX_CSR8, 0x2a2a2a2c }, { RT2573_TXRX_CSR15, 0x0000000f }, { RT2573_MAC_CSR6, 0x00000fff }, { RT2573_MAC_CSR8, 0x016c030a }, { RT2573_MAC_CSR10, 0x00000718 }, { RT2573_MAC_CSR12, 0x00000004 }, { RT2573_MAC_CSR13, 0x00007f00 }, { RT2573_SEC_CSR2, 0x00000000 }, { RT2573_SEC_CSR3, 0x00000000 }, { RT2573_SEC_CSR4, 0x00000000 }, { RT2573_PHY_CSR1, 0x000023b0 }, { RT2573_PHY_CSR5, 0x00040a06 }, { RT2573_PHY_CSR6, 0x00080606 }, { RT2573_PHY_CSR7, 0x00000408 }, { RT2573_AIFSN_CSR, 0x00002273 }, { RT2573_CWMIN_CSR, 0x00002344 }, { RT2573_CWMAX_CSR, 0x000034aa } }; static const struct { uint8_t reg; uint8_t val; } rum_def_bbp[] = { { 3, 0x80 }, { 15, 0x30 }, { 17, 0x20 }, { 21, 0xc8 }, { 22, 0x38 }, { 23, 0x06 }, { 24, 0xfe }, { 25, 0x0a }, { 26, 0x0d }, { 32, 0x0b }, { 34, 0x12 }, { 37, 0x07 }, { 39, 0xf8 }, { 41, 0x60 }, { 53, 0x10 }, { 54, 0x18 }, { 60, 0x10 }, { 61, 0x04 }, { 62, 0x04 }, { 75, 0xfe }, { 86, 0xfe }, { 88, 0xfe }, { 90, 0x0f }, { 99, 0x00 }, { 102, 0x16 }, { 107, 0x04 } }; static const uint8_t rum_chan_5ghz[] = { 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165 }; static const struct rfprog { uint8_t chan; uint32_t r1, r2, r3, r4; } rum_rf5226[] = { { 1, 0x00b03, 0x001e1, 0x1a014, 0x30282 }, { 2, 0x00b03, 0x001e1, 0x1a014, 0x30287 }, { 3, 0x00b03, 0x001e2, 0x1a014, 0x30282 }, { 4, 0x00b03, 0x001e2, 0x1a014, 0x30287 }, { 5, 0x00b03, 0x001e3, 0x1a014, 0x30282 }, { 6, 0x00b03, 0x001e3, 0x1a014, 0x30287 }, { 7, 0x00b03, 0x001e4, 0x1a014, 0x30282 }, { 8, 0x00b03, 0x001e4, 0x1a014, 0x30287 }, { 9, 0x00b03, 0x001e5, 0x1a014, 0x30282 }, { 10, 0x00b03, 0x001e5, 0x1a014, 0x30287 }, { 11, 0x00b03, 0x001e6, 0x1a014, 0x30282 }, { 12, 0x00b03, 0x001e6, 0x1a014, 0x30287 }, { 13, 0x00b03, 0x001e7, 0x1a014, 0x30282 }, { 14, 0x00b03, 0x001e8, 0x1a014, 0x30284 }, { 34, 0x00b03, 0x20266, 0x36014, 0x30282 }, { 38, 0x00b03, 0x20267, 0x36014, 0x30284 }, { 42, 0x00b03, 0x20268, 0x36014, 0x30286 }, { 46, 0x00b03, 0x20269, 0x36014, 0x30288 }, { 36, 0x00b03, 0x00266, 0x26014, 0x30288 }, { 40, 0x00b03, 0x00268, 0x26014, 0x30280 }, { 44, 0x00b03, 0x00269, 0x26014, 0x30282 }, { 48, 0x00b03, 0x0026a, 0x26014, 0x30284 }, { 52, 0x00b03, 0x0026b, 0x26014, 0x30286 }, { 56, 0x00b03, 0x0026c, 0x26014, 0x30288 }, { 60, 0x00b03, 0x0026e, 0x26014, 0x30280 }, { 64, 0x00b03, 0x0026f, 0x26014, 0x30282 }, { 100, 0x00b03, 0x0028a, 0x2e014, 0x30280 }, { 104, 0x00b03, 0x0028b, 0x2e014, 0x30282 }, { 108, 0x00b03, 0x0028c, 0x2e014, 0x30284 }, { 112, 0x00b03, 0x0028d, 0x2e014, 0x30286 }, { 116, 0x00b03, 0x0028e, 0x2e014, 0x30288 }, { 120, 0x00b03, 0x002a0, 0x2e014, 0x30280 }, { 124, 0x00b03, 0x002a1, 0x2e014, 0x30282 }, { 128, 0x00b03, 0x002a2, 0x2e014, 0x30284 }, { 132, 0x00b03, 0x002a3, 0x2e014, 0x30286 }, { 136, 0x00b03, 0x002a4, 0x2e014, 0x30288 }, { 140, 0x00b03, 0x002a6, 0x2e014, 0x30280 }, { 149, 0x00b03, 0x002a8, 0x2e014, 0x30287 }, { 153, 0x00b03, 0x002a9, 0x2e014, 0x30289 }, { 157, 0x00b03, 0x002ab, 0x2e014, 0x30281 }, { 161, 0x00b03, 0x002ac, 0x2e014, 0x30283 }, { 165, 0x00b03, 0x002ad, 0x2e014, 0x30285 } }, rum_rf5225[] = { { 1, 0x00b33, 0x011e1, 0x1a014, 0x30282 }, { 2, 0x00b33, 0x011e1, 0x1a014, 0x30287 }, { 3, 0x00b33, 0x011e2, 0x1a014, 0x30282 }, { 4, 0x00b33, 0x011e2, 0x1a014, 0x30287 }, { 5, 0x00b33, 0x011e3, 0x1a014, 0x30282 }, { 6, 0x00b33, 0x011e3, 0x1a014, 0x30287 }, { 7, 0x00b33, 0x011e4, 0x1a014, 0x30282 }, { 8, 0x00b33, 0x011e4, 0x1a014, 0x30287 }, { 9, 0x00b33, 0x011e5, 0x1a014, 0x30282 }, { 10, 0x00b33, 0x011e5, 0x1a014, 0x30287 }, { 11, 0x00b33, 0x011e6, 0x1a014, 0x30282 }, { 12, 0x00b33, 0x011e6, 0x1a014, 0x30287 }, { 13, 0x00b33, 0x011e7, 0x1a014, 0x30282 }, { 14, 0x00b33, 0x011e8, 0x1a014, 0x30284 }, { 34, 0x00b33, 0x01266, 0x26014, 0x30282 }, { 38, 0x00b33, 0x01267, 0x26014, 0x30284 }, { 42, 0x00b33, 0x01268, 0x26014, 0x30286 }, { 46, 0x00b33, 0x01269, 0x26014, 0x30288 }, { 36, 0x00b33, 0x01266, 0x26014, 0x30288 }, { 40, 0x00b33, 0x01268, 0x26014, 0x30280 }, { 44, 0x00b33, 0x01269, 0x26014, 0x30282 }, { 48, 0x00b33, 0x0126a, 0x26014, 0x30284 }, { 52, 0x00b33, 0x0126b, 0x26014, 0x30286 }, { 56, 0x00b33, 0x0126c, 0x26014, 0x30288 }, { 60, 0x00b33, 0x0126e, 0x26014, 0x30280 }, { 64, 0x00b33, 0x0126f, 0x26014, 0x30282 }, { 100, 0x00b33, 0x0128a, 0x2e014, 0x30280 }, { 104, 0x00b33, 0x0128b, 0x2e014, 0x30282 }, { 108, 0x00b33, 0x0128c, 0x2e014, 0x30284 }, { 112, 0x00b33, 0x0128d, 0x2e014, 0x30286 }, { 116, 0x00b33, 0x0128e, 0x2e014, 0x30288 }, { 120, 0x00b33, 0x012a0, 0x2e014, 0x30280 }, { 124, 0x00b33, 0x012a1, 0x2e014, 0x30282 }, { 128, 0x00b33, 0x012a2, 0x2e014, 0x30284 }, { 132, 0x00b33, 0x012a3, 0x2e014, 0x30286 }, { 136, 0x00b33, 0x012a4, 0x2e014, 0x30288 }, { 140, 0x00b33, 0x012a6, 0x2e014, 0x30280 }, { 149, 0x00b33, 0x012a8, 0x2e014, 0x30287 }, { 153, 0x00b33, 0x012a9, 0x2e014, 0x30289 }, { 157, 0x00b33, 0x012ab, 0x2e014, 0x30281 }, { 161, 0x00b33, 0x012ac, 0x2e014, 0x30283 }, { 165, 0x00b33, 0x012ad, 0x2e014, 0x30285 } }; static const struct usb_config rum_config[RUM_N_TRANSFER] = { [RUM_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (MCLBYTES + RT2573_TX_DESC_SIZE + 8), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = rum_bulk_write_callback, .timeout = 5000, /* ms */ }, [RUM_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (MCLBYTES + RT2573_RX_DESC_SIZE), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = rum_bulk_read_callback, }, }; static int rum_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != RT2573_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(rum_devs, sizeof(rum_devs), uaa)); } static int rum_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct rum_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; uint32_t tmp; uint8_t iface_index; int error, ntries; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; RUM_LOCK_INIT(sc); RUM_CMDQ_LOCK_INIT(sc); mbufq_init(&sc->sc_snd, ifqmaxlen); iface_index = RT2573_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, rum_config, RUM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(self, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto detach; } RUM_LOCK(sc); /* retrieve RT2573 rev. no */ for (ntries = 0; ntries < 100; ntries++) { if ((tmp = rum_read(sc, RT2573_MAC_CSR0)) != 0) break; if (rum_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for chip to settle\n"); RUM_UNLOCK(sc); goto detach; } /* retrieve MAC address and various other things from EEPROM */ rum_read_eeprom(sc); device_printf(sc->sc_dev, "MAC/BBP RT2573 (rev 0x%05x), RF %s\n", tmp, rum_get_rf(sc->rf_rev)); rum_load_microcode(sc, rt2573_ucode, sizeof(rt2573_ucode)); RUM_UNLOCK(sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_IBSS /* IBSS mode supported */ | IEEE80211_C_MONITOR /* monitor mode supported */ | IEEE80211_C_HOSTAP /* HostAp mode supported */ | IEEE80211_C_AHDEMO /* adhoc demo mode */ | IEEE80211_C_TXPMGT /* tx power management */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_BGSCAN /* bg scanning supported */ | IEEE80211_C_WPA /* 802.11i */ | IEEE80211_C_WME /* 802.11e */ | IEEE80211_C_PMGT /* Station-side power mgmt */ | IEEE80211_C_SWSLEEP /* net80211 managed power mgmt */ ; ic->ic_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_AES_CCM | IEEE80211_CRYPTO_TKIPMIC | IEEE80211_CRYPTO_TKIP; rum_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_update_promisc = rum_update_promisc; ic->ic_raw_xmit = rum_raw_xmit; ic->ic_scan_start = rum_scan_start; ic->ic_scan_end = rum_scan_end; ic->ic_set_channel = rum_set_channel; ic->ic_getradiocaps = rum_getradiocaps; ic->ic_transmit = rum_transmit; ic->ic_parent = rum_parent; ic->ic_vap_create = rum_vap_create; ic->ic_vap_delete = rum_vap_delete; ic->ic_updateslot = rum_update_slot; ic->ic_wme.wme_update = rum_wme_update; ic->ic_update_mcast = rum_update_mcast; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), RT2573_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), RT2573_RX_RADIOTAP_PRESENT); TASK_INIT(&sc->cmdq_task, 0, rum_cmdq_cb, sc); if (bootverbose) ieee80211_announce(ic); return (0); detach: rum_detach(self); return (ENXIO); /* failure */ } static int rum_detach(device_t self) { struct rum_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; /* Prevent further ioctls */ RUM_LOCK(sc); sc->sc_detached = 1; RUM_UNLOCK(sc); /* stop all USB transfers */ usbd_transfer_unsetup(sc->sc_xfer, RUM_N_TRANSFER); /* free TX list, if any */ RUM_LOCK(sc); rum_unsetup_tx_list(sc); RUM_UNLOCK(sc); if (ic->ic_softc == sc) { ieee80211_draintask(ic, &sc->cmdq_task); ieee80211_ifdetach(ic); } mbufq_drain(&sc->sc_snd); RUM_CMDQ_LOCK_DESTROY(sc); RUM_LOCK_DESTROY(sc); return (0); } static usb_error_t rum_do_request(struct rum_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0) break; DPRINTFN(1, "Control request failed, %s (retrying)\n", usbd_errstr(err)); if (rum_pause(sc, hz / 100)) break; } return (err); } static usb_error_t rum_do_mcu_request(struct rum_softc *sc, int request) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RT2573_MCU_CNTL; USETW(req.wValue, request); USETW(req.wIndex, 0); USETW(req.wLength, 0); return (rum_do_request(sc, &req, NULL)); } static struct ieee80211vap * rum_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct rum_softc *sc = ic->ic_softc; struct rum_vap *rvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; rvp = malloc(sizeof(struct rum_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &rvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(rvp, M_80211_VAP); return (NULL); } /* override state transition machine */ rvp->newstate = vap->iv_newstate; vap->iv_newstate = rum_newstate; vap->iv_key_alloc = rum_key_alloc; vap->iv_key_set = rum_key_set; vap->iv_key_delete = rum_key_delete; vap->iv_update_beacon = rum_update_beacon; vap->iv_reset = rum_reset; vap->iv_max_aid = RT2573_ADDR_MAX; if (opmode == IEEE80211_M_STA) { /* * Move device to the sleep state when * beacon is received and there is no data for us. * * Used only for IEEE80211_S_SLEEP state. */ rvp->recv_mgmt = vap->iv_recv_mgmt; vap->iv_recv_mgmt = rum_sta_recv_mgmt; /* Ignored while sleeping. */ rvp->bmiss = vap->iv_bmiss; vap->iv_bmiss = rum_beacon_miss; } usb_callout_init_mtx(&rvp->ratectl_ch, &sc->sc_mtx, 0); TASK_INIT(&rvp->ratectl_task, 0, rum_ratectl_task, rvp); ieee80211_ratectl_init(vap); ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return vap; } static void rum_vap_delete(struct ieee80211vap *vap) { struct rum_vap *rvp = RUM_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct rum_softc *sc = ic->ic_softc; /* Put vap into INIT state. */ ieee80211_new_state(vap, IEEE80211_S_INIT, -1); ieee80211_draintask(ic, &vap->iv_nstate_task); RUM_LOCK(sc); /* Cancel any unfinished Tx. */ rum_reset_tx_list(sc, vap); RUM_UNLOCK(sc); usb_callout_drain(&rvp->ratectl_ch); ieee80211_draintask(ic, &rvp->ratectl_task); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); m_freem(rvp->bcn_mbuf); free(rvp, M_80211_VAP); } static void rum_cmdq_cb(void *arg, int pending) { struct rum_softc *sc = arg; struct rum_cmdq *rc; RUM_CMDQ_LOCK(sc); while (sc->cmdq[sc->cmdq_first].func != NULL) { rc = &sc->cmdq[sc->cmdq_first]; RUM_CMDQ_UNLOCK(sc); RUM_LOCK(sc); rc->func(sc, &rc->data, rc->rvp_id); RUM_UNLOCK(sc); RUM_CMDQ_LOCK(sc); memset(rc, 0, sizeof (*rc)); sc->cmdq_first = (sc->cmdq_first + 1) % RUM_CMDQ_SIZE; } RUM_CMDQ_UNLOCK(sc); } static int rum_cmd_sleepable(struct rum_softc *sc, const void *ptr, size_t len, uint8_t rvp_id, CMD_FUNC_PROTO) { struct ieee80211com *ic = &sc->sc_ic; KASSERT(len <= sizeof(union sec_param), ("buffer overflow")); RUM_CMDQ_LOCK(sc); if (sc->cmdq[sc->cmdq_last].func != NULL) { device_printf(sc->sc_dev, "%s: cmdq overflow\n", __func__); RUM_CMDQ_UNLOCK(sc); return EAGAIN; } if (ptr != NULL) memcpy(&sc->cmdq[sc->cmdq_last].data, ptr, len); sc->cmdq[sc->cmdq_last].rvp_id = rvp_id; sc->cmdq[sc->cmdq_last].func = func; sc->cmdq_last = (sc->cmdq_last + 1) % RUM_CMDQ_SIZE; RUM_CMDQ_UNLOCK(sc); ieee80211_runtask(ic, &sc->cmdq_task); return 0; } static void rum_tx_free(struct rum_tx_data *data, int txerr) { struct rum_softc *sc = data->sc; if (data->m != NULL) { ieee80211_tx_complete(data->ni, data->m, txerr); data->m = NULL; data->ni = NULL; } STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } static void rum_setup_tx_list(struct rum_softc *sc) { struct rum_tx_data *data; int i; sc->tx_nfree = 0; STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); for (i = 0; i < RUM_TX_LIST_COUNT; i++) { data = &sc->tx_data[i]; data->sc = sc; STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } } static void rum_reset_tx_list(struct rum_softc *sc, struct ieee80211vap *vap) { struct rum_tx_data *data, *tmp; KASSERT(vap != NULL, ("%s: vap is NULL\n", __func__)); STAILQ_FOREACH_SAFE(data, &sc->tx_q, next, tmp) { if (data->ni != NULL && data->ni->ni_vap == vap) { ieee80211_free_node(data->ni); data->ni = NULL; KASSERT(data->m != NULL, ("%s: m is NULL\n", __func__)); m_freem(data->m); data->m = NULL; STAILQ_REMOVE(&sc->tx_q, data, rum_tx_data, next); STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } } } static void rum_unsetup_tx_list(struct rum_softc *sc) { struct rum_tx_data *data; int i; /* make sure any subsequent use of the queues will fail */ sc->tx_nfree = 0; STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); /* free up all node references and mbufs */ for (i = 0; i < RUM_TX_LIST_COUNT; i++) { data = &sc->tx_data[i]; if (data->m != NULL) { m_freem(data->m); data->m = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } } static void rum_beacon_miss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct rum_softc *sc = ic->ic_softc; struct rum_vap *rvp = RUM_VAP(vap); int sleep; RUM_LOCK(sc); if (sc->sc_sleeping && sc->sc_sleep_end < ticks) { DPRINTFN(12, "dropping 'sleeping' bit, " "device must be awake now\n"); sc->sc_sleeping = 0; } sleep = sc->sc_sleeping; RUM_UNLOCK(sc); if (!sleep) rvp->bmiss(vap); #ifdef USB_DEBUG else DPRINTFN(13, "bmiss event is ignored whilst sleeping\n"); #endif } static void rum_sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct rum_softc *sc = vap->iv_ic->ic_softc; struct rum_vap *rvp = RUM_VAP(vap); if (vap->iv_state == IEEE80211_S_SLEEP && subtype == IEEE80211_FC0_SUBTYPE_BEACON) { RUM_LOCK(sc); DPRINTFN(12, "beacon, mybss %d (flags %02X)\n", !!(sc->last_rx_flags & RT2573_RX_MYBSS), sc->last_rx_flags); if ((sc->last_rx_flags & (RT2573_RX_MYBSS | RT2573_RX_BC)) == (RT2573_RX_MYBSS | RT2573_RX_BC)) { /* * Put it to sleep here; in case if there is a data * for us, iv_recv_mgmt() will wakeup the device via * SLEEP -> RUN state transition. */ rum_set_power_state(sc, 1); } RUM_UNLOCK(sc); } rvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf); } static int rum_set_power_state(struct rum_softc *sc, int sleep) { usb_error_t uerror; RUM_LOCK_ASSERT(sc); DPRINTFN(12, "moving to %s state (sleep time %u)\n", sleep ? "sleep" : "awake", sc->sc_sleep_time); uerror = rum_do_mcu_request(sc, sleep ? RT2573_MCU_SLEEP : RT2573_MCU_WAKEUP); if (uerror != USB_ERR_NORMAL_COMPLETION) { device_printf(sc->sc_dev, "%s: could not change power state: %s\n", __func__, usbd_errstr(uerror)); return (EIO); } sc->sc_sleeping = !!sleep; sc->sc_sleep_end = sleep ? ticks + sc->sc_sleep_time : 0; return (0); } static int rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct rum_vap *rvp = RUM_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct rum_softc *sc = ic->ic_softc; const struct ieee80211_txparam *tp; enum ieee80211_state ostate; struct ieee80211_node *ni; usb_error_t uerror; int ret = 0; ostate = vap->iv_state; DPRINTF("%s -> %s\n", ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); RUM_LOCK(sc); usb_callout_stop(&rvp->ratectl_ch); if (ostate == IEEE80211_S_SLEEP && vap->iv_opmode == IEEE80211_M_STA) { rum_clrbits(sc, RT2573_TXRX_CSR4, RT2573_ACKCTS_PWRMGT); rum_clrbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); /* * Ignore any errors; * any subsequent TX will wakeup it anyway */ (void) rum_set_power_state(sc, 0); } switch (nstate) { case IEEE80211_S_INIT: if (ostate == IEEE80211_S_RUN) rum_abort_tsf_sync(sc); break; case IEEE80211_S_RUN: if (ostate == IEEE80211_S_SLEEP) break; /* already handled */ ni = ieee80211_ref_node(vap->iv_bss); if (vap->iv_opmode != IEEE80211_M_MONITOR) { if (ic->ic_bsschan == IEEE80211_CHAN_ANYC || ni->ni_chan == IEEE80211_CHAN_ANYC) { ret = EINVAL; goto run_fail; } rum_update_slot_cb(sc, NULL, 0); rum_enable_mrr(sc); rum_set_txpreamble(sc); rum_set_basicrates(sc); rum_set_maxretry(sc, vap); IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); rum_set_bssid(sc, sc->sc_bssid); } if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { if ((ret = rum_alloc_beacon(sc, vap)) != 0) goto run_fail; } if (vap->iv_opmode != IEEE80211_M_MONITOR && vap->iv_opmode != IEEE80211_M_AHDEMO) { if ((ret = rum_enable_tsf_sync(sc)) != 0) goto run_fail; } else rum_enable_tsf(sc); /* enable automatic rate adaptation */ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) rum_ratectl_start(sc, ni); run_fail: ieee80211_free_node(ni); break; case IEEE80211_S_SLEEP: /* Implemented for STA mode only. */ if (vap->iv_opmode != IEEE80211_M_STA) break; uerror = rum_setbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); if (uerror != USB_ERR_NORMAL_COMPLETION) { ret = EIO; break; } uerror = rum_setbits(sc, RT2573_TXRX_CSR4, RT2573_ACKCTS_PWRMGT); if (uerror != USB_ERR_NORMAL_COMPLETION) { ret = EIO; break; } ret = rum_set_power_state(sc, 1); if (ret != 0) { device_printf(sc->sc_dev, "%s: could not move to the SLEEP state: %s\n", __func__, usbd_errstr(uerror)); } break; default: break; } RUM_UNLOCK(sc); IEEE80211_LOCK(ic); return (ret == 0 ? rvp->newstate(vap, nstate, arg) : ret); } static void rum_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct rum_softc *sc = usbd_xfer_softc(xfer); struct ieee80211vap *vap; struct rum_tx_data *data; struct mbuf *m; struct usb_page_cache *pc; unsigned int len; int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete, %d bytes\n", actlen); /* free resources */ data = usbd_xfer_get_priv(xfer); rum_tx_free(data, 0); usbd_xfer_set_priv(xfer, NULL); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->tx_q); if (data) { STAILQ_REMOVE_HEAD(&sc->tx_q, next); m = data->m; if (m->m_pkthdr.len > (int)(MCLBYTES + RT2573_TX_DESC_SIZE)) { DPRINTFN(0, "data overflow, %u bytes\n", m->m_pkthdr.len); m->m_pkthdr.len = (MCLBYTES + RT2573_TX_DESC_SIZE); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &data->desc, RT2573_TX_DESC_SIZE); usbd_m_copy_in(pc, RT2573_TX_DESC_SIZE, m, 0, m->m_pkthdr.len); vap = data->ni->ni_vap; if (ieee80211_radiotap_active_vap(vap)) { struct rum_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = data->rate; tap->wt_antenna = sc->tx_ant; ieee80211_radiotap_tx(vap, m); } /* align end on a 4-bytes boundary */ len = (RT2573_TX_DESC_SIZE + m->m_pkthdr.len + 3) & ~3; if ((len % 64) == 0) len += 4; DPRINTFN(11, "sending frame len=%u xferlen=%u\n", m->m_pkthdr.len, len); usbd_xfer_set_frame_len(xfer, 0, len); usbd_xfer_set_priv(xfer, data); usbd_transfer_submit(xfer); } rum_start(sc); break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); counter_u64_add(sc->sc_ic.ic_oerrors, 1); data = usbd_xfer_get_priv(xfer); if (data != NULL) { rum_tx_free(data, error); usbd_xfer_set_priv(xfer, NULL); } if (error != USB_ERR_CANCELLED) { if (error == USB_ERR_TIMEOUT) device_printf(sc->sc_dev, "device timeout\n"); /* * Try to clear stall first, also if other * errors occur, hence clearing stall * introduces a 50 ms delay: */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void rum_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct rum_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame_min *wh; struct ieee80211_node *ni; struct epoch_tracker et; struct mbuf *m = NULL; struct usb_page_cache *pc; uint32_t flags; uint8_t rssi = 0; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(15, "rx done, actlen=%d\n", len); if (len < RT2573_RX_DESC_SIZE) { DPRINTF("%s: xfer too short %d\n", device_get_nameunit(sc->sc_dev), len); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } len -= RT2573_RX_DESC_SIZE; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &sc->sc_rx_desc, RT2573_RX_DESC_SIZE); rssi = rum_get_rssi(sc, sc->sc_rx_desc.rssi); flags = le32toh(sc->sc_rx_desc.flags); sc->last_rx_flags = flags; if (len < ((flags >> 16) & 0xfff)) { DPRINTFN(5, "%s: frame is truncated from %d to %d " "bytes\n", device_get_nameunit(sc->sc_dev), (flags >> 16) & 0xfff, len); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } len = (flags >> 16) & 0xfff; if (len < sizeof(struct ieee80211_frame_ack)) { DPRINTFN(5, "%s: frame too short %d\n", device_get_nameunit(sc->sc_dev), len); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } if (flags & RT2573_RX_CRC_ERROR) { /* * This should not happen since we did not * request to receive those frames when we * filled RUM_TXRX_CSR2: */ DPRINTFN(5, "PHY or CRC error\n"); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } if ((flags & RT2573_RX_DEC_MASK) != RT2573_RX_DEC_OK) { switch (flags & RT2573_RX_DEC_MASK) { case RT2573_RX_IV_ERROR: DPRINTFN(5, "IV/EIV error\n"); break; case RT2573_RX_MIC_ERROR: DPRINTFN(5, "MIC error\n"); break; case RT2573_RX_KEY_ERROR: DPRINTFN(5, "Key error\n"); break; } counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } m = m_get2(len, M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { DPRINTF("could not allocate mbuf\n"); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } usbd_copy_out(pc, RT2573_RX_DESC_SIZE, mtod(m, uint8_t *), len); wh = mtod(m, struct ieee80211_frame_min *); if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && (flags & RT2573_RX_CIP_MASK) != RT2573_RX_CIP_MODE(RT2573_MODE_NOSEC)) { wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; m->m_flags |= M_WEP; } /* finalize mbuf */ m->m_pkthdr.len = m->m_len = len; if (ieee80211_radiotap_active(ic)) { struct rum_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate, (flags & RT2573_RX_OFDM) ? IEEE80211_T_OFDM : IEEE80211_T_CCK); rum_get_tsf(sc, &tap->wr_tsf); tap->wr_antsignal = RT2573_NOISE_FLOOR + rssi; tap->wr_antnoise = RT2573_NOISE_FLOOR; tap->wr_antenna = sc->rx_ant; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * At the end of a USB callback it is always safe to unlock * the private mutex of a device! That is why we do the * "ieee80211_input" here, and not some lines up! */ RUM_UNLOCK(sc); if (m) { if (m->m_len >= sizeof(struct ieee80211_frame_min)) ni = ieee80211_find_rxnode(ic, wh); else ni = NULL; NET_EPOCH_ENTER(et); if (ni != NULL) { (void) ieee80211_input(ni, m, rssi, RT2573_NOISE_FLOOR); ieee80211_free_node(ni); } else (void) ieee80211_input_all(ic, m, rssi, RT2573_NOISE_FLOOR); NET_EPOCH_EXIT(et); } RUM_LOCK(sc); rum_start(sc); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static uint8_t rum_plcp_signal(int rate) { switch (rate) { /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return 0xb; case 18: return 0xf; case 24: return 0xa; case 36: return 0xe; case 48: return 0x9; case 72: return 0xd; case 96: return 0x8; case 108: return 0xc; /* CCK rates (NB: not IEEE std, device-specific) */ case 2: return 0x0; case 4: return 0x1; case 11: return 0x2; case 22: return 0x3; } return 0xff; /* XXX unsupported/unknown rate */ } /* * Map net80211 cipher to RT2573 security mode. */ static uint8_t rum_crypto_mode(struct rum_softc *sc, u_int cipher, int keylen) { switch (cipher) { case IEEE80211_CIPHER_WEP: return (keylen < 8 ? RT2573_MODE_WEP40 : RT2573_MODE_WEP104); case IEEE80211_CIPHER_TKIP: return RT2573_MODE_TKIP; case IEEE80211_CIPHER_AES_CCM: return RT2573_MODE_AES_CCMP; default: device_printf(sc->sc_dev, "unknown cipher %d\n", cipher); return 0; } } static void rum_setup_tx_desc(struct rum_softc *sc, struct rum_tx_desc *desc, struct ieee80211_key *k, uint32_t flags, uint8_t xflags, uint8_t qid, int hdrlen, int len, int rate) { struct ieee80211com *ic = &sc->sc_ic; struct wmeParams *wmep = &sc->wme_params[qid]; uint16_t plcp_length; int remainder; flags |= RT2573_TX_VALID; flags |= len << 16; if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWCRYPT)) { const struct ieee80211_cipher *cip = k->wk_cipher; len += cip->ic_header + cip->ic_trailer + cip->ic_miclen; desc->eiv = 0; /* for WEP */ cip->ic_setiv(k, (uint8_t *)&desc->iv); } /* setup PLCP fields */ desc->plcp_signal = rum_plcp_signal(rate); desc->plcp_service = 4; len += IEEE80211_CRC_LEN; if (ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) { flags |= RT2573_TX_OFDM; plcp_length = len & 0xfff; desc->plcp_length_hi = plcp_length >> 6; desc->plcp_length_lo = plcp_length & 0x3f; } else { if (rate == 0) rate = 2; /* avoid division by zero */ plcp_length = howmany(16 * len, rate); if (rate == 22) { remainder = (16 * len) % 22; if (remainder != 0 && remainder < 7) desc->plcp_service |= RT2573_PLCP_LENGEXT; } desc->plcp_length_hi = plcp_length >> 8; desc->plcp_length_lo = plcp_length & 0xff; if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) desc->plcp_signal |= 0x08; } desc->flags = htole32(flags); desc->hdrlen = hdrlen; desc->xflags = xflags; desc->wme = htole16(RT2573_QID(qid) | RT2573_AIFSN(wmep->wmep_aifsn) | RT2573_LOGCWMIN(wmep->wmep_logcwmin) | RT2573_LOGCWMAX(wmep->wmep_logcwmax)); } static int rum_sendprot(struct rum_softc *sc, const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) { struct ieee80211com *ic = ni->ni_ic; struct rum_tx_data *data; struct mbuf *mprot; int protrate, flags; RUM_LOCK_ASSERT(sc); mprot = ieee80211_alloc_prot(ni, m, rate, prot); if (mprot == NULL) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); device_printf(sc->sc_dev, "could not allocate mbuf for protection mode %d\n", prot); return (ENOBUFS); } protrate = ieee80211_ctl_rate(ic->ic_rt, rate); flags = 0; if (prot == IEEE80211_PROT_RTSCTS) flags |= RT2573_TX_NEED_ACK; data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; data->m = mprot; data->ni = ieee80211_ref_node(ni); data->rate = protrate; rum_setup_tx_desc(sc, &data->desc, NULL, flags, 0, 0, 0, mprot->m_pkthdr.len, protrate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); return 0; } static uint32_t rum_tx_crypto_flags(struct rum_softc *sc, struct ieee80211_node *ni, const struct ieee80211_key *k) { struct ieee80211vap *vap = ni->ni_vap; u_int cipher; uint32_t flags = 0; uint8_t mode, pos; if (!(k->wk_flags & IEEE80211_KEY_SWCRYPT)) { cipher = k->wk_cipher->ic_cipher; pos = k->wk_keyix; mode = rum_crypto_mode(sc, cipher, k->wk_keylen); if (mode == 0) return 0; flags |= RT2573_TX_CIP_MODE(mode); /* Do not trust GROUP flag */ if (!(k >= &vap->iv_nw_keys[0] && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID])) flags |= RT2573_TX_KEY_PAIR; else pos += 0 * RT2573_SKEY_MAX; /* vap id */ flags |= RT2573_TX_KEY_ID(pos); if (cipher == IEEE80211_CIPHER_TKIP) flags |= RT2573_TX_TKIPMIC; } return flags; } static int rum_tx_mgt(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211com *ic = &sc->sc_ic; struct rum_tx_data *data; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; uint32_t flags = 0; uint16_t dur; uint8_t ac, type, xflags = 0; int hdrlen; RUM_LOCK_ASSERT(sc); data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; hdrlen = ieee80211_anyhdrsize(wh); ac = M_WME_GETAC(m0); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_get_txkey(ni, m0); if (k == NULL) return (ENOENT); if ((k->wk_flags & IEEE80211_KEY_SWCRYPT) && !k->wk_cipher->ic_encap(k, m0)) return (ENOBUFS); wh = mtod(m0, struct ieee80211_frame *); } if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= RT2573_TX_NEED_ACK; dur = ieee80211_ack_duration(ic->ic_rt, tp->mgmtrate, ic->ic_flags & IEEE80211_F_SHPREAMBLE); USETW(wh->i_dur, dur); /* tell hardware to add timestamp for probe responses */ if (type == IEEE80211_FC0_TYPE_MGT && (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= RT2573_TX_TIMESTAMP; } if (type != IEEE80211_FC0_TYPE_CTL && !IEEE80211_QOS_HAS_SEQ(wh)) xflags |= RT2573_TX_HWSEQ; if (k != NULL) flags |= rum_tx_crypto_flags(sc, ni, k); data->m = m0; data->ni = ni; data->rate = tp->mgmtrate; rum_setup_tx_desc(sc, &data->desc, k, flags, xflags, ac, hdrlen, m0->m_pkthdr.len, tp->mgmtrate); DPRINTFN(10, "sending mgt frame len=%d rate=%d\n", m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, tp->mgmtrate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); return (0); } static int rum_tx_raw(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; struct rum_tx_data *data; uint32_t flags; uint8_t ac, type, xflags = 0; int rate, error; RUM_LOCK_ASSERT(sc); wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ac = params->ibp_pri & 3; rate = params->ibp_rate0; if (!ieee80211_isratevalid(ic->ic_rt, rate)) return (EINVAL); flags = 0; if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) flags |= RT2573_TX_NEED_ACK; if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { error = rum_sendprot(sc, m0, ni, params->ibp_flags & IEEE80211_BPF_RTS ? IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, rate); if (error || sc->tx_nfree == 0) return (ENOBUFS); flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; } if (type != IEEE80211_FC0_TYPE_CTL && !IEEE80211_QOS_HAS_SEQ(wh)) xflags |= RT2573_TX_HWSEQ; data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; data->m = m0; data->ni = ni; data->rate = rate; /* XXX need to setup descriptor ourself */ rum_setup_tx_desc(sc, &data->desc, NULL, flags, xflags, ac, 0, m0->m_pkthdr.len, rate); DPRINTFN(10, "sending raw frame len=%u rate=%u\n", m0->m_pkthdr.len, rate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); return 0; } static int rum_tx_data(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = &sc->sc_ic; struct rum_tx_data *data; struct ieee80211_frame *wh; const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211_key *k = NULL; uint32_t flags = 0; uint16_t dur; uint8_t ac, type, qos, xflags = 0; int error, hdrlen, rate; RUM_LOCK_ASSERT(sc); wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; hdrlen = ieee80211_anyhdrsize(wh); if (IEEE80211_QOS_HAS_SEQ(wh)) qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; else qos = 0; ac = M_WME_GETAC(m0); if (m0->m_flags & M_EAPOL) rate = tp->mgmtrate; else if (IEEE80211_IS_MULTICAST(wh->i_addr1)) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else { (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; } if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_get_txkey(ni, m0); if (k == NULL) { m_freem(m0); return (ENOENT); } if ((k->wk_flags & IEEE80211_KEY_SWCRYPT) && !k->wk_cipher->ic_encap(k, m0)) { m_freem(m0); return (ENOBUFS); } /* packet header may have moved, reset our local pointer */ wh = mtod(m0, struct ieee80211_frame *); } if (type != IEEE80211_FC0_TYPE_CTL && !IEEE80211_QOS_HAS_SEQ(wh)) xflags |= RT2573_TX_HWSEQ; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { int prot = IEEE80211_PROT_NONE; if (m0->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) prot = IEEE80211_PROT_RTSCTS; else if ((ic->ic_flags & IEEE80211_F_USEPROT) && ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) prot = ic->ic_protmode; if (prot != IEEE80211_PROT_NONE) { error = rum_sendprot(sc, m0, ni, prot, rate); if (error || sc->tx_nfree == 0) { m_freem(m0); return ENOBUFS; } flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; } } if (k != NULL) flags |= rum_tx_crypto_flags(sc, ni, k); data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; data->m = m0; data->ni = ni; data->rate = rate; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* Unicast frame, check if an ACK is expected. */ if (!qos || (qos & IEEE80211_QOS_ACKPOLICY) != IEEE80211_QOS_ACKPOLICY_NOACK) flags |= RT2573_TX_NEED_ACK; dur = ieee80211_ack_duration(ic->ic_rt, rate, ic->ic_flags & IEEE80211_F_SHPREAMBLE); USETW(wh->i_dur, dur); } rum_setup_tx_desc(sc, &data->desc, k, flags, xflags, ac, hdrlen, m0->m_pkthdr.len, rate); DPRINTFN(10, "sending frame len=%d rate=%d\n", m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, rate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); return 0; } static int rum_transmit(struct ieee80211com *ic, struct mbuf *m) { struct rum_softc *sc = ic->ic_softc; int error; RUM_LOCK(sc); if (!sc->sc_running) { RUM_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { RUM_UNLOCK(sc); return (error); } rum_start(sc); RUM_UNLOCK(sc); return (0); } static void rum_start(struct rum_softc *sc) { struct ieee80211_node *ni; struct mbuf *m; RUM_LOCK_ASSERT(sc); if (!sc->sc_running) return; while (sc->tx_nfree >= RUM_TX_MINFREE && (m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; if (rum_tx_data(sc, m, ni) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); break; } } } static void rum_parent(struct ieee80211com *ic) { struct rum_softc *sc = ic->ic_softc; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); RUM_LOCK(sc); if (sc->sc_detached) { RUM_UNLOCK(sc); return; } RUM_UNLOCK(sc); if (ic->ic_nrunning > 0) { if (rum_init(sc) == 0) ieee80211_start_all(ic); else ieee80211_stop(vap); } else rum_stop(sc); } static void rum_eeprom_read(struct rum_softc *sc, uint16_t addr, void *buf, int len) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RT2573_READ_EEPROM; USETW(req.wValue, 0); USETW(req.wIndex, addr); USETW(req.wLength, len); error = rum_do_request(sc, &req, buf); if (error != 0) { device_printf(sc->sc_dev, "could not read EEPROM: %s\n", usbd_errstr(error)); } } static uint32_t rum_read(struct rum_softc *sc, uint16_t reg) { uint32_t val; rum_read_multi(sc, reg, &val, sizeof val); return le32toh(val); } static void rum_read_multi(struct rum_softc *sc, uint16_t reg, void *buf, int len) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RT2573_READ_MULTI_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, len); error = rum_do_request(sc, &req, buf); if (error != 0) { device_printf(sc->sc_dev, "could not multi read MAC register: %s\n", usbd_errstr(error)); } } static usb_error_t rum_write(struct rum_softc *sc, uint16_t reg, uint32_t val) { uint32_t tmp = htole32(val); return (rum_write_multi(sc, reg, &tmp, sizeof tmp)); } static usb_error_t rum_write_multi(struct rum_softc *sc, uint16_t reg, void *buf, size_t len) { struct usb_device_request req; usb_error_t error; size_t offset; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RT2573_WRITE_MULTI_MAC; USETW(req.wValue, 0); /* write at most 64 bytes at a time */ for (offset = 0; offset < len; offset += 64) { USETW(req.wIndex, reg + offset); USETW(req.wLength, MIN(len - offset, 64)); error = rum_do_request(sc, &req, (char *)buf + offset); if (error != 0) { device_printf(sc->sc_dev, "could not multi write MAC register: %s\n", usbd_errstr(error)); return (error); } } return (USB_ERR_NORMAL_COMPLETION); } static usb_error_t rum_setbits(struct rum_softc *sc, uint16_t reg, uint32_t mask) { return (rum_write(sc, reg, rum_read(sc, reg) | mask)); } static usb_error_t rum_clrbits(struct rum_softc *sc, uint16_t reg, uint32_t mask) { return (rum_write(sc, reg, rum_read(sc, reg) & ~mask)); } static usb_error_t rum_modbits(struct rum_softc *sc, uint16_t reg, uint32_t set, uint32_t unset) { return (rum_write(sc, reg, (rum_read(sc, reg) & ~unset) | set)); } static int rum_bbp_busy(struct rum_softc *sc) { int ntries; for (ntries = 0; ntries < 100; ntries++) { if (!(rum_read(sc, RT2573_PHY_CSR3) & RT2573_BBP_BUSY)) break; if (rum_pause(sc, hz / 100)) break; } if (ntries == 100) return (ETIMEDOUT); return (0); } static void rum_bbp_write(struct rum_softc *sc, uint8_t reg, uint8_t val) { uint32_t tmp; DPRINTFN(2, "reg=0x%08x\n", reg); if (rum_bbp_busy(sc) != 0) { device_printf(sc->sc_dev, "could not write to BBP\n"); return; } tmp = RT2573_BBP_BUSY | (reg & 0x7f) << 8 | val; rum_write(sc, RT2573_PHY_CSR3, tmp); } static uint8_t rum_bbp_read(struct rum_softc *sc, uint8_t reg) { uint32_t val; int ntries; DPRINTFN(2, "reg=0x%08x\n", reg); if (rum_bbp_busy(sc) != 0) { device_printf(sc->sc_dev, "could not read BBP\n"); return 0; } val = RT2573_BBP_BUSY | RT2573_BBP_READ | reg << 8; rum_write(sc, RT2573_PHY_CSR3, val); for (ntries = 0; ntries < 100; ntries++) { val = rum_read(sc, RT2573_PHY_CSR3); if (!(val & RT2573_BBP_BUSY)) return val & 0xff; if (rum_pause(sc, hz / 100)) break; } device_printf(sc->sc_dev, "could not read BBP\n"); return 0; } static void rum_rf_write(struct rum_softc *sc, uint8_t reg, uint32_t val) { uint32_t tmp; int ntries; for (ntries = 0; ntries < 100; ntries++) { if (!(rum_read(sc, RT2573_PHY_CSR4) & RT2573_RF_BUSY)) break; if (rum_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "could not write to RF\n"); return; } tmp = RT2573_RF_BUSY | RT2573_RF_20BIT | (val & 0xfffff) << 2 | (reg & 3); rum_write(sc, RT2573_PHY_CSR4, tmp); /* remember last written value in sc */ sc->rf_regs[reg] = val; DPRINTFN(15, "RF R[%u] <- 0x%05x\n", reg & 3, val & 0xfffff); } static void rum_select_antenna(struct rum_softc *sc) { uint8_t bbp4, bbp77; uint32_t tmp; bbp4 = rum_bbp_read(sc, 4); bbp77 = rum_bbp_read(sc, 77); /* TBD */ /* make sure Rx is disabled before switching antenna */ tmp = rum_read(sc, RT2573_TXRX_CSR0); rum_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); rum_bbp_write(sc, 4, bbp4); rum_bbp_write(sc, 77, bbp77); rum_write(sc, RT2573_TXRX_CSR0, tmp); } /* * Enable multi-rate retries for frames sent at OFDM rates. * In 802.11b/g mode, allow fallback to CCK rates. */ static void rum_enable_mrr(struct rum_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; if (!IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) { rum_setbits(sc, RT2573_TXRX_CSR4, RT2573_MRR_ENABLED | RT2573_MRR_CCK_FALLBACK); } else { rum_modbits(sc, RT2573_TXRX_CSR4, RT2573_MRR_ENABLED, RT2573_MRR_CCK_FALLBACK); } } static void rum_set_txpreamble(struct rum_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) rum_setbits(sc, RT2573_TXRX_CSR4, RT2573_SHORT_PREAMBLE); else rum_clrbits(sc, RT2573_TXRX_CSR4, RT2573_SHORT_PREAMBLE); } static void rum_set_basicrates(struct rum_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; /* update basic rate set */ if (ic->ic_curmode == IEEE80211_MODE_11B) { /* 11b basic rates: 1, 2Mbps */ rum_write(sc, RT2573_TXRX_CSR5, 0x3); } else if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) { /* 11a basic rates: 6, 12, 24Mbps */ rum_write(sc, RT2573_TXRX_CSR5, 0x150); } else { /* 11b/g basic rates: 1, 2, 5.5, 11Mbps */ rum_write(sc, RT2573_TXRX_CSR5, 0xf); } } /* * Reprogram MAC/BBP to switch to a new band. Values taken from the reference * driver. */ static void rum_select_band(struct rum_softc *sc, struct ieee80211_channel *c) { uint8_t bbp17, bbp35, bbp96, bbp97, bbp98, bbp104; /* update all BBP registers that depend on the band */ bbp17 = 0x20; bbp96 = 0x48; bbp104 = 0x2c; bbp35 = 0x50; bbp97 = 0x48; bbp98 = 0x48; if (IEEE80211_IS_CHAN_5GHZ(c)) { bbp17 += 0x08; bbp96 += 0x10; bbp104 += 0x0c; bbp35 += 0x10; bbp97 += 0x10; bbp98 += 0x10; } if ((IEEE80211_IS_CHAN_2GHZ(c) && sc->ext_2ghz_lna) || (IEEE80211_IS_CHAN_5GHZ(c) && sc->ext_5ghz_lna)) { bbp17 += 0x10; bbp96 += 0x10; bbp104 += 0x10; } sc->bbp17 = bbp17; rum_bbp_write(sc, 17, bbp17); rum_bbp_write(sc, 96, bbp96); rum_bbp_write(sc, 104, bbp104); if ((IEEE80211_IS_CHAN_2GHZ(c) && sc->ext_2ghz_lna) || (IEEE80211_IS_CHAN_5GHZ(c) && sc->ext_5ghz_lna)) { rum_bbp_write(sc, 75, 0x80); rum_bbp_write(sc, 86, 0x80); rum_bbp_write(sc, 88, 0x80); } rum_bbp_write(sc, 35, bbp35); rum_bbp_write(sc, 97, bbp97); rum_bbp_write(sc, 98, bbp98); if (IEEE80211_IS_CHAN_2GHZ(c)) { rum_modbits(sc, RT2573_PHY_CSR0, RT2573_PA_PE_2GHZ, RT2573_PA_PE_5GHZ); } else { rum_modbits(sc, RT2573_PHY_CSR0, RT2573_PA_PE_5GHZ, RT2573_PA_PE_2GHZ); } } static void rum_set_chan(struct rum_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; const struct rfprog *rfprog; uint8_t bbp3, bbp94 = RT2573_BBPR94_DEFAULT; int8_t power; int i, chan; chan = ieee80211_chan2ieee(ic, c); if (chan == 0 || chan == IEEE80211_CHAN_ANY) return; /* select the appropriate RF settings based on what EEPROM says */ rfprog = (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_2527) ? rum_rf5225 : rum_rf5226; /* find the settings for this channel (we know it exists) */ for (i = 0; rfprog[i].chan != chan; i++); power = sc->txpow[i]; if (power < 0) { bbp94 += power; power = 0; } else if (power > 31) { bbp94 += power - 31; power = 31; } /* * If we are switching from the 2GHz band to the 5GHz band or * vice-versa, BBP registers need to be reprogrammed. */ if (c->ic_flags != ic->ic_curchan->ic_flags) { rum_select_band(sc, c); rum_select_antenna(sc); } ic->ic_curchan = c; rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7); rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7 | 1); rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7); rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); rum_pause(sc, hz / 100); /* enable smart mode for MIMO-capable RFs */ bbp3 = rum_bbp_read(sc, 3); bbp3 &= ~RT2573_SMART_MODE; if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_2527) bbp3 |= RT2573_SMART_MODE; rum_bbp_write(sc, 3, bbp3); if (bbp94 != RT2573_BBPR94_DEFAULT) rum_bbp_write(sc, 94, bbp94); /* give the chip some extra time to do the switchover */ rum_pause(sc, hz / 100); } static void rum_set_maxretry(struct rum_softc *sc, struct ieee80211vap *vap) { struct ieee80211_node *ni = vap->iv_bss; const struct ieee80211_txparam *tp = ni->ni_txparms; struct rum_vap *rvp = RUM_VAP(vap); rvp->maxretry = MIN(tp->maxretry, 0xf); rum_modbits(sc, RT2573_TXRX_CSR4, RT2573_SHORT_RETRY(rvp->maxretry) | RT2573_LONG_RETRY(rvp->maxretry), RT2573_SHORT_RETRY_MASK | RT2573_LONG_RETRY_MASK); } /* * Enable TSF synchronization and tell h/w to start sending beacons for IBSS * and HostAP operating modes. */ static int rum_enable_tsf_sync(struct rum_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t tmp; uint16_t bintval; if (vap->iv_opmode != IEEE80211_M_STA) { /* * Change default 16ms TBTT adjustment to 8ms. * Must be done before enabling beacon generation. */ if (rum_write(sc, RT2573_TXRX_CSR10, 1 << 12 | 8) != 0) return EIO; } tmp = rum_read(sc, RT2573_TXRX_CSR9) & 0xff000000; /* set beacon interval (in 1/16ms unit) */ bintval = vap->iv_bss->ni_intval; tmp |= bintval * 16; tmp |= RT2573_TSF_TIMER_EN | RT2573_TBTT_TIMER_EN; switch (vap->iv_opmode) { case IEEE80211_M_STA: /* * Local TSF is always updated with remote TSF on beacon * reception. */ tmp |= RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_STA); break; case IEEE80211_M_IBSS: /* * Local TSF is updated with remote TSF on beacon reception * only if the remote TSF is greater than local TSF. */ tmp |= RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_IBSS); tmp |= RT2573_BCN_TX_EN; break; case IEEE80211_M_HOSTAP: /* SYNC with nobody */ tmp |= RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_HOSTAP); tmp |= RT2573_BCN_TX_EN; break; default: device_printf(sc->sc_dev, "Enabling TSF failed. undefined opmode %d\n", vap->iv_opmode); return EINVAL; } if (rum_write(sc, RT2573_TXRX_CSR9, tmp) != 0) return EIO; /* refresh current sleep time */ return (rum_set_sleep_time(sc, bintval)); } static void rum_enable_tsf(struct rum_softc *sc) { rum_modbits(sc, RT2573_TXRX_CSR9, RT2573_TSF_TIMER_EN | RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_DIS), 0x00ffffff); } static void rum_abort_tsf_sync(struct rum_softc *sc) { rum_clrbits(sc, RT2573_TXRX_CSR9, 0x00ffffff); } static void rum_get_tsf(struct rum_softc *sc, uint64_t *buf) { rum_read_multi(sc, RT2573_TXRX_CSR12, buf, sizeof (*buf)); } static void rum_update_slot_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id) { struct ieee80211com *ic = &sc->sc_ic; uint8_t slottime; slottime = IEEE80211_GET_SLOTTIME(ic); rum_modbits(sc, RT2573_MAC_CSR9, slottime, 0xff); DPRINTF("setting slot time to %uus\n", slottime); } static void rum_update_slot(struct ieee80211com *ic) { rum_cmd_sleepable(ic->ic_softc, NULL, 0, 0, rum_update_slot_cb); } static int rum_wme_update(struct ieee80211com *ic) { struct chanAccParams chp; const struct wmeParams *chanp; struct rum_softc *sc = ic->ic_softc; int error = 0; ieee80211_wme_ic_getparams(ic, &chp); chanp = chp.cap_wmeParams; RUM_LOCK(sc); error = rum_write(sc, RT2573_AIFSN_CSR, chanp[WME_AC_VO].wmep_aifsn << 12 | chanp[WME_AC_VI].wmep_aifsn << 8 | chanp[WME_AC_BK].wmep_aifsn << 4 | chanp[WME_AC_BE].wmep_aifsn); if (error) goto print_err; error = rum_write(sc, RT2573_CWMIN_CSR, chanp[WME_AC_VO].wmep_logcwmin << 12 | chanp[WME_AC_VI].wmep_logcwmin << 8 | chanp[WME_AC_BK].wmep_logcwmin << 4 | chanp[WME_AC_BE].wmep_logcwmin); if (error) goto print_err; error = rum_write(sc, RT2573_CWMAX_CSR, chanp[WME_AC_VO].wmep_logcwmax << 12 | chanp[WME_AC_VI].wmep_logcwmax << 8 | chanp[WME_AC_BK].wmep_logcwmax << 4 | chanp[WME_AC_BE].wmep_logcwmax); if (error) goto print_err; error = rum_write(sc, RT2573_TXOP01_CSR, chanp[WME_AC_BK].wmep_txopLimit << 16 | chanp[WME_AC_BE].wmep_txopLimit); if (error) goto print_err; error = rum_write(sc, RT2573_TXOP23_CSR, chanp[WME_AC_VO].wmep_txopLimit << 16 | chanp[WME_AC_VI].wmep_txopLimit); if (error) goto print_err; memcpy(sc->wme_params, chanp, sizeof(*chanp) * WME_NUM_AC); print_err: RUM_UNLOCK(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: WME update failed, error %d\n", __func__, error); } return (error); } static void rum_set_bssid(struct rum_softc *sc, const uint8_t *bssid) { rum_write(sc, RT2573_MAC_CSR4, bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24); rum_write(sc, RT2573_MAC_CSR5, bssid[4] | bssid[5] << 8 | RT2573_NUM_BSSID_MSK(1)); } static void rum_set_macaddr(struct rum_softc *sc, const uint8_t *addr) { rum_write(sc, RT2573_MAC_CSR2, addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24); rum_write(sc, RT2573_MAC_CSR3, addr[4] | addr[5] << 8 | 0xff << 16); } static void rum_setpromisc(struct rum_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; if (ic->ic_promisc == 0) rum_setbits(sc, RT2573_TXRX_CSR0, RT2573_DROP_NOT_TO_ME); else rum_clrbits(sc, RT2573_TXRX_CSR0, RT2573_DROP_NOT_TO_ME); DPRINTF("%s promiscuous mode\n", ic->ic_promisc > 0 ? "entering" : "leaving"); } static void rum_update_promisc(struct ieee80211com *ic) { struct rum_softc *sc = ic->ic_softc; RUM_LOCK(sc); if (sc->sc_running) rum_setpromisc(sc); RUM_UNLOCK(sc); } static void rum_update_mcast(struct ieee80211com *ic) { /* Ignore. */ } static const char * rum_get_rf(int rev) { switch (rev) { case RT2573_RF_2527: return "RT2527 (MIMO XR)"; case RT2573_RF_2528: return "RT2528"; case RT2573_RF_5225: return "RT5225 (MIMO XR)"; case RT2573_RF_5226: return "RT5226"; default: return "unknown"; } } static void rum_read_eeprom(struct rum_softc *sc) { uint16_t val; #ifdef RUM_DEBUG int i; #endif /* read MAC address */ rum_eeprom_read(sc, RT2573_EEPROM_ADDRESS, sc->sc_ic.ic_macaddr, 6); rum_eeprom_read(sc, RT2573_EEPROM_ANTENNA, &val, 2); val = le16toh(val); sc->rf_rev = (val >> 11) & 0x1f; sc->hw_radio = (val >> 10) & 0x1; sc->rx_ant = (val >> 4) & 0x3; sc->tx_ant = (val >> 2) & 0x3; sc->nb_ant = val & 0x3; DPRINTF("RF revision=%d\n", sc->rf_rev); rum_eeprom_read(sc, RT2573_EEPROM_CONFIG2, &val, 2); val = le16toh(val); sc->ext_5ghz_lna = (val >> 6) & 0x1; sc->ext_2ghz_lna = (val >> 4) & 0x1; DPRINTF("External 2GHz LNA=%d\nExternal 5GHz LNA=%d\n", sc->ext_2ghz_lna, sc->ext_5ghz_lna); rum_eeprom_read(sc, RT2573_EEPROM_RSSI_2GHZ_OFFSET, &val, 2); val = le16toh(val); if ((val & 0xff) != 0xff) sc->rssi_2ghz_corr = (int8_t)(val & 0xff); /* signed */ /* Only [-10, 10] is valid */ if (sc->rssi_2ghz_corr < -10 || sc->rssi_2ghz_corr > 10) sc->rssi_2ghz_corr = 0; rum_eeprom_read(sc, RT2573_EEPROM_RSSI_5GHZ_OFFSET, &val, 2); val = le16toh(val); if ((val & 0xff) != 0xff) sc->rssi_5ghz_corr = (int8_t)(val & 0xff); /* signed */ /* Only [-10, 10] is valid */ if (sc->rssi_5ghz_corr < -10 || sc->rssi_5ghz_corr > 10) sc->rssi_5ghz_corr = 0; if (sc->ext_2ghz_lna) sc->rssi_2ghz_corr -= 14; if (sc->ext_5ghz_lna) sc->rssi_5ghz_corr -= 14; DPRINTF("RSSI 2GHz corr=%d\nRSSI 5GHz corr=%d\n", sc->rssi_2ghz_corr, sc->rssi_5ghz_corr); rum_eeprom_read(sc, RT2573_EEPROM_FREQ_OFFSET, &val, 2); val = le16toh(val); if ((val & 0xff) != 0xff) sc->rffreq = val & 0xff; DPRINTF("RF freq=%d\n", sc->rffreq); /* read Tx power for all a/b/g channels */ rum_eeprom_read(sc, RT2573_EEPROM_TXPOWER, sc->txpow, 14); /* XXX default Tx power for 802.11a channels */ memset(sc->txpow + 14, 24, sizeof (sc->txpow) - 14); #ifdef RUM_DEBUG for (i = 0; i < 14; i++) DPRINTF("Channel=%d Tx power=%d\n", i + 1, sc->txpow[i]); #endif /* read default values for BBP registers */ rum_eeprom_read(sc, RT2573_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); #ifdef RUM_DEBUG for (i = 0; i < 14; i++) { if (sc->bbp_prom[i].reg == 0 || sc->bbp_prom[i].reg == 0xff) continue; DPRINTF("BBP R%d=%02x\n", sc->bbp_prom[i].reg, sc->bbp_prom[i].val); } #endif } static int rum_bbp_wakeup(struct rum_softc *sc) { unsigned int ntries; for (ntries = 0; ntries < 100; ntries++) { if (rum_read(sc, RT2573_MAC_CSR12) & 8) break; rum_write(sc, RT2573_MAC_CSR12, 4); /* force wakeup */ if (rum_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for BBP/RF to wakeup\n"); return (ETIMEDOUT); } return (0); } static int rum_bbp_init(struct rum_softc *sc) { int i, ntries; /* wait for BBP to be ready */ for (ntries = 0; ntries < 100; ntries++) { const uint8_t val = rum_bbp_read(sc, 0); if (val != 0 && val != 0xff) break; if (rum_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for BBP\n"); return EIO; } /* initialize BBP registers to default values */ for (i = 0; i < nitems(rum_def_bbp); i++) rum_bbp_write(sc, rum_def_bbp[i].reg, rum_def_bbp[i].val); /* write vendor-specific BBP values (from EEPROM) */ for (i = 0; i < 16; i++) { if (sc->bbp_prom[i].reg == 0 || sc->bbp_prom[i].reg == 0xff) continue; rum_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); } return 0; } static void rum_clr_shkey_regs(struct rum_softc *sc) { rum_write(sc, RT2573_SEC_CSR0, 0); rum_write(sc, RT2573_SEC_CSR1, 0); rum_write(sc, RT2573_SEC_CSR5, 0); } static int rum_init(struct rum_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t tmp; int i, ret; RUM_LOCK(sc); if (sc->sc_running) { ret = 0; goto end; } /* initialize MAC registers to default values */ for (i = 0; i < nitems(rum_def_mac); i++) rum_write(sc, rum_def_mac[i].reg, rum_def_mac[i].val); /* reset some WME parameters to default values */ sc->wme_params[0].wmep_aifsn = 2; sc->wme_params[0].wmep_logcwmin = 4; sc->wme_params[0].wmep_logcwmax = 10; /* set host ready */ rum_write(sc, RT2573_MAC_CSR1, RT2573_RESET_ASIC | RT2573_RESET_BBP); rum_write(sc, RT2573_MAC_CSR1, 0); /* wait for BBP/RF to wakeup */ if ((ret = rum_bbp_wakeup(sc)) != 0) goto end; if ((ret = rum_bbp_init(sc)) != 0) goto end; /* select default channel */ rum_select_band(sc, ic->ic_curchan); rum_select_antenna(sc); rum_set_chan(sc, ic->ic_curchan); /* clear STA registers */ rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof sc->sta); /* clear security registers (if required) */ if (sc->sc_clr_shkeys == 0) { rum_clr_shkey_regs(sc); sc->sc_clr_shkeys = 1; } rum_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr); /* initialize ASIC */ rum_write(sc, RT2573_MAC_CSR1, RT2573_HOST_READY); /* * Allocate Tx and Rx xfer queues. */ rum_setup_tx_list(sc); /* update Rx filter */ tmp = rum_read(sc, RT2573_TXRX_CSR0) & 0xffff; tmp |= RT2573_DROP_PHY_ERROR | RT2573_DROP_CRC_ERROR; if (ic->ic_opmode != IEEE80211_M_MONITOR) { tmp |= RT2573_DROP_CTL | RT2573_DROP_VER_ERROR | RT2573_DROP_ACKCTS; if (ic->ic_opmode != IEEE80211_M_HOSTAP) tmp |= RT2573_DROP_TODS; if (ic->ic_promisc == 0) tmp |= RT2573_DROP_NOT_TO_ME; } rum_write(sc, RT2573_TXRX_CSR0, tmp); sc->sc_running = 1; usbd_xfer_set_stall(sc->sc_xfer[RUM_BULK_WR]); usbd_transfer_start(sc->sc_xfer[RUM_BULK_RD]); end: RUM_UNLOCK(sc); if (ret != 0) rum_stop(sc); return ret; } static void rum_stop(struct rum_softc *sc) { RUM_LOCK(sc); if (!sc->sc_running) { RUM_UNLOCK(sc); return; } sc->sc_running = 0; RUM_UNLOCK(sc); /* * Drain the USB transfers, if not already drained: */ usbd_transfer_drain(sc->sc_xfer[RUM_BULK_WR]); usbd_transfer_drain(sc->sc_xfer[RUM_BULK_RD]); RUM_LOCK(sc); rum_unsetup_tx_list(sc); /* disable Rx */ rum_setbits(sc, RT2573_TXRX_CSR0, RT2573_DISABLE_RX); /* reset ASIC */ rum_write(sc, RT2573_MAC_CSR1, RT2573_RESET_ASIC | RT2573_RESET_BBP); rum_write(sc, RT2573_MAC_CSR1, 0); RUM_UNLOCK(sc); } static void rum_load_microcode(struct rum_softc *sc, const uint8_t *ucode, size_t size) { uint16_t reg = RT2573_MCU_CODE_BASE; usb_error_t err; /* copy firmware image into NIC */ for (; size >= 4; reg += 4, ucode += 4, size -= 4) { err = rum_write(sc, reg, UGETDW(ucode)); if (err) { /* firmware already loaded ? */ device_printf(sc->sc_dev, "Firmware load " "failure! (ignored)\n"); break; } } err = rum_do_mcu_request(sc, RT2573_MCU_RUN); if (err != USB_ERR_NORMAL_COMPLETION) { device_printf(sc->sc_dev, "could not run firmware: %s\n", usbd_errstr(err)); } /* give the chip some time to boot */ rum_pause(sc, hz / 8); } static int rum_set_sleep_time(struct rum_softc *sc, uint16_t bintval) { struct ieee80211com *ic = &sc->sc_ic; usb_error_t uerror; int exp, delay; RUM_LOCK_ASSERT(sc); exp = ic->ic_lintval / bintval; delay = ic->ic_lintval % bintval; if (exp > RT2573_TBCN_EXP_MAX) exp = RT2573_TBCN_EXP_MAX; if (delay > RT2573_TBCN_DELAY_MAX) delay = RT2573_TBCN_DELAY_MAX; uerror = rum_modbits(sc, RT2573_MAC_CSR11, RT2573_TBCN_EXP(exp) | RT2573_TBCN_DELAY(delay), RT2573_TBCN_EXP(RT2573_TBCN_EXP_MAX) | RT2573_TBCN_DELAY(RT2573_TBCN_DELAY_MAX)); if (uerror != USB_ERR_NORMAL_COMPLETION) return (EIO); sc->sc_sleep_time = IEEE80211_TU_TO_TICKS(exp * bintval + delay); return (0); } static int rum_reset(struct ieee80211vap *vap, u_long cmd) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; struct rum_softc *sc = ic->ic_softc; int error; switch (cmd) { case IEEE80211_IOC_POWERSAVE: case IEEE80211_IOC_PROTMODE: case IEEE80211_IOC_RTSTHRESHOLD: error = 0; break; case IEEE80211_IOC_POWERSAVESLEEP: ni = ieee80211_ref_node(vap->iv_bss); RUM_LOCK(sc); error = rum_set_sleep_time(sc, ni->ni_intval); if (vap->iv_state == IEEE80211_S_SLEEP) { /* Use new values for wakeup timer. */ rum_clrbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); rum_setbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); } /* XXX send reassoc */ RUM_UNLOCK(sc); ieee80211_free_node(ni); break; default: error = ENETRESET; break; } return (error); } static int rum_set_beacon(struct rum_softc *sc, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct rum_vap *rvp = RUM_VAP(vap); struct mbuf *m = rvp->bcn_mbuf; const struct ieee80211_txparam *tp; struct rum_tx_desc desc; RUM_LOCK_ASSERT(sc); if (m == NULL) return EINVAL; if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) return EINVAL; tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; rum_setup_tx_desc(sc, &desc, NULL, RT2573_TX_TIMESTAMP, RT2573_TX_HWSEQ, 0, 0, m->m_pkthdr.len, tp->mgmtrate); /* copy the Tx descriptor into NIC memory */ if (rum_write_multi(sc, RT2573_HW_BCN_BASE(0), (uint8_t *)&desc, RT2573_TX_DESC_SIZE) != 0) return EIO; /* copy beacon header and payload into NIC memory */ if (rum_write_multi(sc, RT2573_HW_BCN_BASE(0) + RT2573_TX_DESC_SIZE, mtod(m, uint8_t *), m->m_pkthdr.len) != 0) return EIO; return 0; } static int rum_alloc_beacon(struct rum_softc *sc, struct ieee80211vap *vap) { struct rum_vap *rvp = RUM_VAP(vap); struct ieee80211_node *ni = vap->iv_bss; struct mbuf *m; if (ni->ni_chan == IEEE80211_CHAN_ANYC) return EINVAL; m = ieee80211_beacon_alloc(ni); if (m == NULL) return ENOMEM; if (rvp->bcn_mbuf != NULL) m_freem(rvp->bcn_mbuf); rvp->bcn_mbuf = m; return (rum_set_beacon(sc, vap)); } static void rum_update_beacon_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id) { struct ieee80211vap *vap = data->vap; rum_set_beacon(sc, vap); } static void rum_update_beacon(struct ieee80211vap *vap, int item) { struct ieee80211com *ic = vap->iv_ic; struct rum_softc *sc = ic->ic_softc; struct rum_vap *rvp = RUM_VAP(vap); struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211_node *ni = vap->iv_bss; struct mbuf *m = rvp->bcn_mbuf; int mcast = 0; RUM_LOCK(sc); if (m == NULL) { m = ieee80211_beacon_alloc(ni); if (m == NULL) { device_printf(sc->sc_dev, "%s: could not allocate beacon frame\n", __func__); RUM_UNLOCK(sc); return; } rvp->bcn_mbuf = m; } switch (item) { case IEEE80211_BEACON_ERP: rum_update_slot(ic); break; case IEEE80211_BEACON_TIM: mcast = 1; /*TODO*/ break; default: break; } RUM_UNLOCK(sc); setbit(bo->bo_flags, item); ieee80211_beacon_update(ni, m, mcast); rum_cmd_sleepable(sc, &vap, sizeof(vap), 0, rum_update_beacon_cb); } static int rum_common_key_set(struct rum_softc *sc, struct ieee80211_key *k, uint16_t base) { if (rum_write_multi(sc, base, k->wk_key, k->wk_keylen)) return EIO; if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) { if (rum_write_multi(sc, base + IEEE80211_KEYBUF_SIZE, k->wk_txmic, 8)) return EIO; if (rum_write_multi(sc, base + IEEE80211_KEYBUF_SIZE + 8, k->wk_rxmic, 8)) return EIO; } return 0; } static void rum_group_key_set_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id) { struct ieee80211_key *k = &data->key; uint8_t mode; if (sc->sc_clr_shkeys == 0) { rum_clr_shkey_regs(sc); sc->sc_clr_shkeys = 1; } mode = rum_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); if (mode == 0) goto print_err; DPRINTFN(1, "setting group key %d for vap %d, mode %d " "(tx %s, rx %s)\n", k->wk_keyix, rvp_id, mode, (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off", (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off"); /* Install the key. */ if (rum_common_key_set(sc, k, RT2573_SKEY(rvp_id, k->wk_keyix)) != 0) goto print_err; /* Set cipher mode. */ if (rum_modbits(sc, rvp_id < 2 ? RT2573_SEC_CSR1 : RT2573_SEC_CSR5, mode << (rvp_id % 2 + k->wk_keyix) * RT2573_SKEY_MAX, RT2573_MODE_MASK << (rvp_id % 2 + k->wk_keyix) * RT2573_SKEY_MAX) != 0) goto print_err; /* Mark this key as valid. */ if (rum_setbits(sc, RT2573_SEC_CSR0, 1 << (rvp_id * RT2573_SKEY_MAX + k->wk_keyix)) != 0) goto print_err; return; print_err: device_printf(sc->sc_dev, "%s: cannot set group key %d for vap %d\n", __func__, k->wk_keyix, rvp_id); } static void rum_group_key_del_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id) { struct ieee80211_key *k = &data->key; DPRINTF("%s: removing group key %d for vap %d\n", __func__, k->wk_keyix, rvp_id); rum_clrbits(sc, rvp_id < 2 ? RT2573_SEC_CSR1 : RT2573_SEC_CSR5, RT2573_MODE_MASK << (rvp_id % 2 + k->wk_keyix) * RT2573_SKEY_MAX); rum_clrbits(sc, RT2573_SEC_CSR0, rvp_id * RT2573_SKEY_MAX + k->wk_keyix); } static void rum_pair_key_set_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id) { struct ieee80211_key *k = &data->key; uint8_t buf[IEEE80211_ADDR_LEN + 1]; uint8_t mode; mode = rum_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); if (mode == 0) goto print_err; DPRINTFN(1, "setting pairwise key %d for vap %d, mode %d " "(tx %s, rx %s)\n", k->wk_keyix, rvp_id, mode, (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off", (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off"); /* Install the key. */ if (rum_common_key_set(sc, k, RT2573_PKEY(k->wk_keyix)) != 0) goto print_err; IEEE80211_ADDR_COPY(buf, k->wk_macaddr); buf[IEEE80211_ADDR_LEN] = mode; /* Set transmitter address and cipher mode. */ if (rum_write_multi(sc, RT2573_ADDR_ENTRY(k->wk_keyix), buf, sizeof buf) != 0) goto print_err; /* Enable key table lookup for this vap. */ if (sc->vap_key_count[rvp_id]++ == 0) if (rum_setbits(sc, RT2573_SEC_CSR4, 1 << rvp_id) != 0) goto print_err; /* Mark this key as valid. */ if (rum_setbits(sc, k->wk_keyix < 32 ? RT2573_SEC_CSR2 : RT2573_SEC_CSR3, 1 << (k->wk_keyix % 32)) != 0) goto print_err; return; print_err: device_printf(sc->sc_dev, "%s: cannot set pairwise key %d, vap %d\n", __func__, k->wk_keyix, rvp_id); } static void rum_pair_key_del_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id) { struct ieee80211_key *k = &data->key; DPRINTF("%s: removing key %d\n", __func__, k->wk_keyix); rum_clrbits(sc, (k->wk_keyix < 32) ? RT2573_SEC_CSR2 : RT2573_SEC_CSR3, 1 << (k->wk_keyix % 32)); sc->keys_bmap &= ~(1ULL << k->wk_keyix); if (--sc->vap_key_count[rvp_id] == 0) rum_clrbits(sc, RT2573_SEC_CSR4, 1 << rvp_id); } static int rum_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { struct rum_softc *sc = vap->iv_ic->ic_softc; uint8_t i; if (!(&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID])) { if (!(k->wk_flags & IEEE80211_KEY_SWCRYPT)) { RUM_LOCK(sc); for (i = 0; i < RT2573_ADDR_MAX; i++) { if ((sc->keys_bmap & (1ULL << i)) == 0) { sc->keys_bmap |= (1ULL << i); *keyix = i; break; } } RUM_UNLOCK(sc); if (i == RT2573_ADDR_MAX) { device_printf(sc->sc_dev, "%s: no free space in the key table\n", __func__); return 0; } } else *keyix = 0; } else { *keyix = ieee80211_crypto_get_key_wepidx(vap, k); } *rxkeyix = *keyix; return 1; } static int rum_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { struct rum_softc *sc = vap->iv_ic->ic_softc; int group; if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return 1; } group = k >= &vap->iv_nw_keys[0] && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]; return !rum_cmd_sleepable(sc, k, sizeof(*k), 0, group ? rum_group_key_set_cb : rum_pair_key_set_cb); } static int rum_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { struct rum_softc *sc = vap->iv_ic->ic_softc; int group; if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return 1; } group = k >= &vap->iv_nw_keys[0] && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]; return !rum_cmd_sleepable(sc, k, sizeof(*k), 0, group ? rum_group_key_del_cb : rum_pair_key_del_cb); } static int rum_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct rum_softc *sc = ni->ni_ic->ic_softc; int ret; RUM_LOCK(sc); /* prevent management frames from being sent if we're not ready */ if (!sc->sc_running) { ret = ENETDOWN; goto bad; } if (sc->tx_nfree < RUM_TX_MINFREE) { ret = EIO; goto bad; } if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ if ((ret = rum_tx_mgt(sc, m, ni)) != 0) goto bad; } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ if ((ret = rum_tx_raw(sc, m, ni, params)) != 0) goto bad; } RUM_UNLOCK(sc); return 0; bad: RUM_UNLOCK(sc); m_freem(m); return ret; } static void rum_ratectl_start(struct rum_softc *sc, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct rum_vap *rvp = RUM_VAP(vap); /* clear statistic registers (STA_CSR0 to STA_CSR5) */ rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof sc->sta); usb_callout_reset(&rvp->ratectl_ch, hz, rum_ratectl_timeout, rvp); } static void rum_ratectl_timeout(void *arg) { struct rum_vap *rvp = arg; struct ieee80211vap *vap = &rvp->vap; struct ieee80211com *ic = vap->iv_ic; ieee80211_runtask(ic, &rvp->ratectl_task); } static void rum_ratectl_task(void *arg, int pending) { struct rum_vap *rvp = arg; struct ieee80211vap *vap = &rvp->vap; struct rum_softc *sc = vap->iv_ic->ic_softc; struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs; int ok[3], fail; RUM_LOCK(sc); /* read and clear statistic registers (STA_CSR0 to STA_CSR5) */ rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof(sc->sta)); ok[0] = (le32toh(sc->sta[4]) & 0xffff); /* TX ok w/o retry */ ok[1] = (le32toh(sc->sta[4]) >> 16); /* TX ok w/ one retry */ ok[2] = (le32toh(sc->sta[5]) & 0xffff); /* TX ok w/ multiple retries */ fail = (le32toh(sc->sta[5]) >> 16); /* TX retry-fail count */ txs->flags = IEEE80211_RATECTL_TX_STATS_RETRIES; txs->nframes = ok[0] + ok[1] + ok[2] + fail; txs->nsuccess = txs->nframes - fail; /* XXX at least */ txs->nretries = ok[1] + ok[2] * 2 + fail * (rvp->maxretry + 1); if (txs->nframes != 0) ieee80211_ratectl_tx_update(vap, txs); /* count TX retry-fail as Tx errors */ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, fail); usb_callout_reset(&rvp->ratectl_ch, hz, rum_ratectl_timeout, rvp); RUM_UNLOCK(sc); } static void rum_scan_start(struct ieee80211com *ic) { struct rum_softc *sc = ic->ic_softc; RUM_LOCK(sc); rum_abort_tsf_sync(sc); rum_set_bssid(sc, ieee80211broadcastaddr); RUM_UNLOCK(sc); } static void rum_scan_end(struct ieee80211com *ic) { struct rum_softc *sc = ic->ic_softc; if (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) { RUM_LOCK(sc); if (ic->ic_opmode != IEEE80211_M_AHDEMO) rum_enable_tsf_sync(sc); else rum_enable_tsf(sc); rum_set_bssid(sc, sc->sc_bssid); RUM_UNLOCK(sc); } } static void rum_set_channel(struct ieee80211com *ic) { struct rum_softc *sc = ic->ic_softc; RUM_LOCK(sc); rum_set_chan(sc, ic->ic_curchan); RUM_UNLOCK(sc); } static void rum_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct rum_softc *sc = ic->ic_softc; uint8_t bands[IEEE80211_MODE_BYTES]; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0); if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_5226) { setbit(bands, IEEE80211_MODE_11A); ieee80211_add_channel_list_5ghz(chans, maxchans, nchans, rum_chan_5ghz, nitems(rum_chan_5ghz), bands, 0); } } static int rum_get_rssi(struct rum_softc *sc, uint8_t raw) { struct ieee80211com *ic = &sc->sc_ic; int lna, agc, rssi; lna = (raw >> 5) & 0x3; agc = raw & 0x1f; if (lna == 0) { /* * No RSSI mapping * * NB: Since RSSI is relative to noise floor, -1 is * adequate for caller to know error happened. */ return -1; } rssi = (2 * agc) - RT2573_NOISE_FLOOR; if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { rssi += sc->rssi_2ghz_corr; if (lna == 1) rssi -= 64; else if (lna == 2) rssi -= 74; else if (lna == 3) rssi -= 90; } else { rssi += sc->rssi_5ghz_corr; if (!sc->ext_5ghz_lna && lna != 1) rssi += 4; if (lna == 1) rssi -= 64; else if (lna == 2) rssi -= 86; else if (lna == 3) rssi -= 100; } return rssi; } static int rum_pause(struct rum_softc *sc, int timeout) { usb_pause_mtx(&sc->sc_mtx, timeout); return (0); } static device_method_t rum_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rum_match), DEVMETHOD(device_attach, rum_attach), DEVMETHOD(device_detach, rum_detach), DEVMETHOD_END }; static driver_t rum_driver = { .name = "rum", .methods = rum_methods, .size = sizeof(struct rum_softc), }; static devclass_t rum_devclass; DRIVER_MODULE(rum, uhub, rum_driver, rum_devclass, NULL, 0); MODULE_DEPEND(rum, wlan, 1, 1, 1); MODULE_DEPEND(rum, usb, 1, 1, 1); MODULE_VERSION(rum, 1); USB_PNP_HOST_INFO(rum_devs); Index: head/sys/dev/usb/wlan/if_run.c =================================================================== --- head/sys/dev/usb/wlan/if_run.c (revision 357971) +++ head/sys/dev/usb/wlan/if_run.c (revision 357972) @@ -1,6334 +1,6335 @@ /*- * Copyright (c) 2008,2010 Damien Bergamini * ported to FreeBSD by Akinori Furukoshi * USB Consulting, Hans Petter Selasky * Copyright (c) 2013-2014 Kevin Lo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /*- * Ralink Technology RT2700U/RT2800U/RT3000U/RT3900E chipset driver. * http://www.ralinktech.com/ */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR run_debug #include #include #include #include #ifdef USB_DEBUG #define RUN_DEBUG #endif #ifdef RUN_DEBUG int run_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, run, CTLFLAG_RW, 0, "USB run"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, run, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB run"); SYSCTL_INT(_hw_usb_run, OID_AUTO, debug, CTLFLAG_RWTUN, &run_debug, 0, "run debug level"); enum { RUN_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ RUN_DEBUG_XMIT_DESC = 0x00000002, /* xmit descriptors */ RUN_DEBUG_RECV = 0x00000004, /* basic recv operation */ RUN_DEBUG_RECV_DESC = 0x00000008, /* recv descriptors */ RUN_DEBUG_STATE = 0x00000010, /* 802.11 state transitions */ RUN_DEBUG_RATE = 0x00000020, /* rate adaptation */ RUN_DEBUG_USB = 0x00000040, /* usb requests */ RUN_DEBUG_FIRMWARE = 0x00000080, /* firmware(9) loading debug */ RUN_DEBUG_BEACON = 0x00000100, /* beacon handling */ RUN_DEBUG_INTR = 0x00000200, /* ISR */ RUN_DEBUG_TEMP = 0x00000400, /* temperature calibration */ RUN_DEBUG_ROM = 0x00000800, /* various ROM info */ RUN_DEBUG_KEY = 0x00001000, /* crypto keys management */ RUN_DEBUG_TXPWR = 0x00002000, /* dump Tx power values */ RUN_DEBUG_RSSI = 0x00004000, /* dump RSSI lookups */ RUN_DEBUG_RESET = 0x00008000, /* initialization progress */ RUN_DEBUG_CALIB = 0x00010000, /* calibration progress */ RUN_DEBUG_CMD = 0x00020000, /* command queue */ RUN_DEBUG_ANY = 0xffffffff }; #define RUN_DPRINTF(_sc, _m, ...) do { \ if (run_debug & (_m)) \ device_printf((_sc)->sc_dev, __VA_ARGS__); \ } while(0) #else #define RUN_DPRINTF(_sc, _m, ...) do { (void) _sc; } while (0) #endif #define IEEE80211_HAS_ADDR4(wh) IEEE80211_IS_DSTODS(wh) /* * Because of LOR in run_key_delete(), use atomic instead. * '& RUN_CMDQ_MASQ' is to loop cmdq[]. */ #define RUN_CMDQ_GET(c) (atomic_fetchadd_32((c), 1) & RUN_CMDQ_MASQ) static const STRUCT_USB_HOST_ID run_devs[] = { #define RUN_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } #define RUN_DEV_EJECT(v,p) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, RUN_EJECT) } #define RUN_EJECT 1 RUN_DEV(ABOCOM, RT2770), RUN_DEV(ABOCOM, RT2870), RUN_DEV(ABOCOM, RT3070), RUN_DEV(ABOCOM, RT3071), RUN_DEV(ABOCOM, RT3072), RUN_DEV(ABOCOM2, RT2870_1), RUN_DEV(ACCTON, RT2770), RUN_DEV(ACCTON, RT2870_1), RUN_DEV(ACCTON, RT2870_2), RUN_DEV(ACCTON, RT2870_3), RUN_DEV(ACCTON, RT2870_4), RUN_DEV(ACCTON, RT2870_5), RUN_DEV(ACCTON, RT3070), RUN_DEV(ACCTON, RT3070_1), RUN_DEV(ACCTON, RT3070_2), RUN_DEV(ACCTON, RT3070_3), RUN_DEV(ACCTON, RT3070_4), RUN_DEV(ACCTON, RT3070_5), RUN_DEV(AIRTIES, RT3070), RUN_DEV(ALLWIN, RT2070), RUN_DEV(ALLWIN, RT2770), RUN_DEV(ALLWIN, RT2870), RUN_DEV(ALLWIN, RT3070), RUN_DEV(ALLWIN, RT3071), RUN_DEV(ALLWIN, RT3072), RUN_DEV(ALLWIN, RT3572), RUN_DEV(AMIGO, RT2870_1), RUN_DEV(AMIGO, RT2870_2), RUN_DEV(AMIT, CGWLUSB2GNR), RUN_DEV(AMIT, RT2870_1), RUN_DEV(AMIT2, RT2870), RUN_DEV(ASUS, RT2870_1), RUN_DEV(ASUS, RT2870_2), RUN_DEV(ASUS, RT2870_3), RUN_DEV(ASUS, RT2870_4), RUN_DEV(ASUS, RT2870_5), RUN_DEV(ASUS, USBN13), RUN_DEV(ASUS, RT3070_1), RUN_DEV(ASUS, USBN66), RUN_DEV(ASUS, USB_N53), RUN_DEV(ASUS2, USBN11), RUN_DEV(AZUREWAVE, RT2870_1), RUN_DEV(AZUREWAVE, RT2870_2), RUN_DEV(AZUREWAVE, RT3070_1), RUN_DEV(AZUREWAVE, RT3070_2), RUN_DEV(AZUREWAVE, RT3070_3), RUN_DEV(BELKIN, F9L1103), RUN_DEV(BELKIN, F5D8053V3), RUN_DEV(BELKIN, F5D8055), RUN_DEV(BELKIN, F5D8055V2), RUN_DEV(BELKIN, F6D4050V1), RUN_DEV(BELKIN, F6D4050V2), RUN_DEV(BELKIN, RT2870_1), RUN_DEV(BELKIN, RT2870_2), RUN_DEV(CISCOLINKSYS, AE1000), RUN_DEV(CISCOLINKSYS2, RT3070), RUN_DEV(CISCOLINKSYS3, RT3070), RUN_DEV(CONCEPTRONIC2, RT2870_1), RUN_DEV(CONCEPTRONIC2, RT2870_2), RUN_DEV(CONCEPTRONIC2, RT2870_3), RUN_DEV(CONCEPTRONIC2, RT2870_4), RUN_DEV(CONCEPTRONIC2, RT2870_5), RUN_DEV(CONCEPTRONIC2, RT2870_6), RUN_DEV(CONCEPTRONIC2, RT2870_7), RUN_DEV(CONCEPTRONIC2, RT2870_8), RUN_DEV(CONCEPTRONIC2, RT3070_1), RUN_DEV(CONCEPTRONIC2, RT3070_2), RUN_DEV(CONCEPTRONIC2, VIGORN61), RUN_DEV(COREGA, CGWLUSB300GNM), RUN_DEV(COREGA, RT2870_1), RUN_DEV(COREGA, RT2870_2), RUN_DEV(COREGA, RT2870_3), RUN_DEV(COREGA, RT3070), RUN_DEV(CYBERTAN, RT2870), RUN_DEV(DLINK, RT2870), RUN_DEV(DLINK, RT3072), RUN_DEV(DLINK, DWA125A3), RUN_DEV(DLINK, DWA127), RUN_DEV(DLINK, DWA140B3), RUN_DEV(DLINK, DWA160B2), RUN_DEV(DLINK, DWA140D1), RUN_DEV(DLINK, DWA162), RUN_DEV(DLINK2, DWA130), RUN_DEV(DLINK2, RT2870_1), RUN_DEV(DLINK2, RT2870_2), RUN_DEV(DLINK2, RT3070_1), RUN_DEV(DLINK2, RT3070_2), RUN_DEV(DLINK2, RT3070_3), RUN_DEV(DLINK2, RT3070_4), RUN_DEV(DLINK2, RT3070_5), RUN_DEV(DLINK2, RT3072), RUN_DEV(DLINK2, RT3072_1), RUN_DEV(EDIMAX, EW7717), RUN_DEV(EDIMAX, EW7718), RUN_DEV(EDIMAX, EW7733UND), RUN_DEV(EDIMAX, RT2870_1), RUN_DEV(ENCORE, RT3070_1), RUN_DEV(ENCORE, RT3070_2), RUN_DEV(ENCORE, RT3070_3), RUN_DEV(GIGABYTE, GNWB31N), RUN_DEV(GIGABYTE, GNWB32L), RUN_DEV(GIGABYTE, RT2870_1), RUN_DEV(GIGASET, RT3070_1), RUN_DEV(GIGASET, RT3070_2), RUN_DEV(GUILLEMOT, HWNU300), RUN_DEV(HAWKING, HWUN2), RUN_DEV(HAWKING, RT2870_1), RUN_DEV(HAWKING, RT2870_2), RUN_DEV(HAWKING, RT3070), RUN_DEV(IODATA, RT3072_1), RUN_DEV(IODATA, RT3072_2), RUN_DEV(IODATA, RT3072_3), RUN_DEV(IODATA, RT3072_4), RUN_DEV(LINKSYS4, RT3070), RUN_DEV(LINKSYS4, WUSB100), RUN_DEV(LINKSYS4, WUSB54GCV3), RUN_DEV(LINKSYS4, WUSB600N), RUN_DEV(LINKSYS4, WUSB600NV2), RUN_DEV(LOGITEC, RT2870_1), RUN_DEV(LOGITEC, RT2870_2), RUN_DEV(LOGITEC, RT2870_3), RUN_DEV(LOGITEC, LANW300NU2), RUN_DEV(LOGITEC, LANW150NU2), RUN_DEV(LOGITEC, LANW300NU2S), RUN_DEV(MELCO, WLIUCG300HP), RUN_DEV(MELCO, RT2870_2), RUN_DEV(MELCO, WLIUCAG300N), RUN_DEV(MELCO, WLIUCG300N), RUN_DEV(MELCO, WLIUCG301N), RUN_DEV(MELCO, WLIUCGN), RUN_DEV(MELCO, WLIUCGNM), RUN_DEV(MELCO, WLIUCG300HPV1), RUN_DEV(MELCO, WLIUCGNM2), RUN_DEV(MOTOROLA4, RT2770), RUN_DEV(MOTOROLA4, RT3070), RUN_DEV(MSI, RT3070_1), RUN_DEV(MSI, RT3070_2), RUN_DEV(MSI, RT3070_3), RUN_DEV(MSI, RT3070_4), RUN_DEV(MSI, RT3070_5), RUN_DEV(MSI, RT3070_6), RUN_DEV(MSI, RT3070_7), RUN_DEV(MSI, RT3070_8), RUN_DEV(MSI, RT3070_9), RUN_DEV(MSI, RT3070_10), RUN_DEV(MSI, RT3070_11), RUN_DEV(NETGEAR, WNDA4100), RUN_DEV(OVISLINK, RT3072), RUN_DEV(PARA, RT3070), RUN_DEV(PEGATRON, RT2870), RUN_DEV(PEGATRON, RT3070), RUN_DEV(PEGATRON, RT3070_2), RUN_DEV(PEGATRON, RT3070_3), RUN_DEV(PHILIPS, RT2870), RUN_DEV(PLANEX2, GWUS300MINIS), RUN_DEV(PLANEX2, GWUSMICRON), RUN_DEV(PLANEX2, RT2870), RUN_DEV(PLANEX2, RT3070), RUN_DEV(QCOM, RT2870), RUN_DEV(QUANTA, RT3070), RUN_DEV(RALINK, RT2070), RUN_DEV(RALINK, RT2770), RUN_DEV(RALINK, RT2870), RUN_DEV(RALINK, RT3070), RUN_DEV(RALINK, RT3071), RUN_DEV(RALINK, RT3072), RUN_DEV(RALINK, RT3370), RUN_DEV(RALINK, RT3572), RUN_DEV(RALINK, RT3573), RUN_DEV(RALINK, RT5370), RUN_DEV(RALINK, RT5372), RUN_DEV(RALINK, RT5572), RUN_DEV(RALINK, RT8070), RUN_DEV(SAMSUNG, WIS09ABGN), RUN_DEV(SAMSUNG2, RT2870_1), RUN_DEV(SENAO, RT2870_1), RUN_DEV(SENAO, RT2870_2), RUN_DEV(SENAO, RT2870_3), RUN_DEV(SENAO, RT2870_4), RUN_DEV(SENAO, RT3070), RUN_DEV(SENAO, RT3071), RUN_DEV(SENAO, RT3072_1), RUN_DEV(SENAO, RT3072_2), RUN_DEV(SENAO, RT3072_3), RUN_DEV(SENAO, RT3072_4), RUN_DEV(SENAO, RT3072_5), RUN_DEV(SITECOMEU, RT2770), RUN_DEV(SITECOMEU, RT2870_1), RUN_DEV(SITECOMEU, RT2870_2), RUN_DEV(SITECOMEU, RT2870_3), RUN_DEV(SITECOMEU, RT2870_4), RUN_DEV(SITECOMEU, RT3070), RUN_DEV(SITECOMEU, RT3070_2), RUN_DEV(SITECOMEU, RT3070_3), RUN_DEV(SITECOMEU, RT3070_4), RUN_DEV(SITECOMEU, RT3071), RUN_DEV(SITECOMEU, RT3072_1), RUN_DEV(SITECOMEU, RT3072_2), RUN_DEV(SITECOMEU, RT3072_3), RUN_DEV(SITECOMEU, RT3072_4), RUN_DEV(SITECOMEU, RT3072_5), RUN_DEV(SITECOMEU, RT3072_6), RUN_DEV(SITECOMEU, WL608), RUN_DEV(SPARKLAN, RT2870_1), RUN_DEV(SPARKLAN, RT3070), RUN_DEV(SWEEX2, LW153), RUN_DEV(SWEEX2, LW303), RUN_DEV(SWEEX2, LW313), RUN_DEV(TOSHIBA, RT3070), RUN_DEV(UMEDIA, RT2870_1), RUN_DEV(ZCOM, RT2870_1), RUN_DEV(ZCOM, RT2870_2), RUN_DEV(ZINWELL, RT2870_1), RUN_DEV(ZINWELL, RT2870_2), RUN_DEV(ZINWELL, RT3070), RUN_DEV(ZINWELL, RT3072_1), RUN_DEV(ZINWELL, RT3072_2), RUN_DEV(ZYXEL, RT2870_1), RUN_DEV(ZYXEL, RT2870_2), RUN_DEV(ZYXEL, RT3070), RUN_DEV_EJECT(ZYXEL, NWD2705), RUN_DEV_EJECT(RALINK, RT_STOR), #undef RUN_DEV_EJECT #undef RUN_DEV }; static device_probe_t run_match; static device_attach_t run_attach; static device_detach_t run_detach; static usb_callback_t run_bulk_rx_callback; static usb_callback_t run_bulk_tx_callback0; static usb_callback_t run_bulk_tx_callback1; static usb_callback_t run_bulk_tx_callback2; static usb_callback_t run_bulk_tx_callback3; static usb_callback_t run_bulk_tx_callback4; static usb_callback_t run_bulk_tx_callback5; static void run_autoinst(void *, struct usb_device *, struct usb_attach_arg *); static int run_driver_loaded(struct module *, int, void *); static void run_bulk_tx_callbackN(struct usb_xfer *xfer, usb_error_t error, u_int index); static struct ieee80211vap *run_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void run_vap_delete(struct ieee80211vap *); static void run_cmdq_cb(void *, int); static void run_setup_tx_list(struct run_softc *, struct run_endpoint_queue *); static void run_unsetup_tx_list(struct run_softc *, struct run_endpoint_queue *); static int run_load_microcode(struct run_softc *); static int run_reset(struct run_softc *); static usb_error_t run_do_request(struct run_softc *, struct usb_device_request *, void *); static int run_read(struct run_softc *, uint16_t, uint32_t *); static int run_read_region_1(struct run_softc *, uint16_t, uint8_t *, int); static int run_write_2(struct run_softc *, uint16_t, uint16_t); static int run_write(struct run_softc *, uint16_t, uint32_t); static int run_write_region_1(struct run_softc *, uint16_t, const uint8_t *, int); static int run_set_region_4(struct run_softc *, uint16_t, uint32_t, int); static int run_efuse_read(struct run_softc *, uint16_t, uint16_t *, int); static int run_efuse_read_2(struct run_softc *, uint16_t, uint16_t *); static int run_eeprom_read_2(struct run_softc *, uint16_t, uint16_t *); static int run_rt2870_rf_write(struct run_softc *, uint32_t); static int run_rt3070_rf_read(struct run_softc *, uint8_t, uint8_t *); static int run_rt3070_rf_write(struct run_softc *, uint8_t, uint8_t); static int run_bbp_read(struct run_softc *, uint8_t, uint8_t *); static int run_bbp_write(struct run_softc *, uint8_t, uint8_t); static int run_mcu_cmd(struct run_softc *, uint8_t, uint16_t); static const char *run_get_rf(uint16_t); static void run_rt3593_get_txpower(struct run_softc *); static void run_get_txpower(struct run_softc *); static int run_read_eeprom(struct run_softc *); static struct ieee80211_node *run_node_alloc(struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN]); static int run_media_change(struct ifnet *); static int run_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int run_wme_update(struct ieee80211com *); static void run_key_set_cb(void *); static int run_key_set(struct ieee80211vap *, struct ieee80211_key *); static void run_key_delete_cb(void *); static int run_key_delete(struct ieee80211vap *, struct ieee80211_key *); static void run_ratectl_to(void *); static void run_ratectl_cb(void *, int); static void run_drain_fifo(void *); static void run_iter_func(void *, struct ieee80211_node *); static void run_newassoc_cb(void *); static void run_newassoc(struct ieee80211_node *, int); static void run_recv_mgmt(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *, int, int); static void run_rx_frame(struct run_softc *, struct mbuf *, uint32_t); static void run_tx_free(struct run_endpoint_queue *pq, struct run_tx_data *, int); static void run_set_tx_desc(struct run_softc *, struct run_tx_data *); static int run_tx(struct run_softc *, struct mbuf *, struct ieee80211_node *); static int run_tx_mgt(struct run_softc *, struct mbuf *, struct ieee80211_node *); static int run_sendprot(struct run_softc *, const struct mbuf *, struct ieee80211_node *, int, int); static int run_tx_param(struct run_softc *, struct mbuf *, struct ieee80211_node *, const struct ieee80211_bpf_params *); static int run_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static int run_transmit(struct ieee80211com *, struct mbuf *); static void run_start(struct run_softc *); static void run_parent(struct ieee80211com *); static void run_iq_calib(struct run_softc *, u_int); static void run_set_agc(struct run_softc *, uint8_t); static void run_select_chan_group(struct run_softc *, int); static void run_set_rx_antenna(struct run_softc *, int); static void run_rt2870_set_chan(struct run_softc *, u_int); static void run_rt3070_set_chan(struct run_softc *, u_int); static void run_rt3572_set_chan(struct run_softc *, u_int); static void run_rt3593_set_chan(struct run_softc *, u_int); static void run_rt5390_set_chan(struct run_softc *, u_int); static void run_rt5592_set_chan(struct run_softc *, u_int); static int run_set_chan(struct run_softc *, struct ieee80211_channel *); static void run_set_channel(struct ieee80211com *); static void run_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static void run_scan_start(struct ieee80211com *); static void run_scan_end(struct ieee80211com *); static void run_update_beacon(struct ieee80211vap *, int); static void run_update_beacon_cb(void *); static void run_updateprot(struct ieee80211com *); static void run_updateprot_cb(void *); static void run_usb_timeout_cb(void *); static void run_reset_livelock(struct run_softc *); static void run_enable_tsf_sync(struct run_softc *); static void run_enable_tsf(struct run_softc *); static void run_disable_tsf(struct run_softc *); static void run_get_tsf(struct run_softc *, uint64_t *); static void run_enable_mrr(struct run_softc *); static void run_set_txpreamble(struct run_softc *); static void run_set_basicrates(struct run_softc *); static void run_set_leds(struct run_softc *, uint16_t); static void run_set_bssid(struct run_softc *, const uint8_t *); static void run_set_macaddr(struct run_softc *, const uint8_t *); static void run_updateslot(struct ieee80211com *); static void run_updateslot_cb(void *); static void run_update_mcast(struct ieee80211com *); static int8_t run_rssi2dbm(struct run_softc *, uint8_t, uint8_t); static void run_update_promisc_locked(struct run_softc *); static void run_update_promisc(struct ieee80211com *); static void run_rt5390_bbp_init(struct run_softc *); static int run_bbp_init(struct run_softc *); static int run_rt3070_rf_init(struct run_softc *); static void run_rt3593_rf_init(struct run_softc *); static void run_rt5390_rf_init(struct run_softc *); static int run_rt3070_filter_calib(struct run_softc *, uint8_t, uint8_t, uint8_t *); static void run_rt3070_rf_setup(struct run_softc *); static void run_rt3593_rf_setup(struct run_softc *); static void run_rt5390_rf_setup(struct run_softc *); static int run_txrx_enable(struct run_softc *); static void run_adjust_freq_offset(struct run_softc *); static void run_init_locked(struct run_softc *); static void run_stop(void *); static void run_delay(struct run_softc *, u_int); static eventhandler_tag run_etag; static const struct rt2860_rate { uint8_t rate; uint8_t mcs; enum ieee80211_phytype phy; uint8_t ctl_ridx; uint16_t sp_ack_dur; uint16_t lp_ack_dur; } rt2860_rates[] = { { 2, 0, IEEE80211_T_DS, 0, 314, 314 }, { 4, 1, IEEE80211_T_DS, 1, 258, 162 }, { 11, 2, IEEE80211_T_DS, 2, 223, 127 }, { 22, 3, IEEE80211_T_DS, 3, 213, 117 }, { 12, 0, IEEE80211_T_OFDM, 4, 60, 60 }, { 18, 1, IEEE80211_T_OFDM, 4, 52, 52 }, { 24, 2, IEEE80211_T_OFDM, 6, 48, 48 }, { 36, 3, IEEE80211_T_OFDM, 6, 44, 44 }, { 48, 4, IEEE80211_T_OFDM, 8, 44, 44 }, { 72, 5, IEEE80211_T_OFDM, 8, 40, 40 }, { 96, 6, IEEE80211_T_OFDM, 8, 40, 40 }, { 108, 7, IEEE80211_T_OFDM, 8, 40, 40 } }; static const struct { uint16_t reg; uint32_t val; } rt2870_def_mac[] = { RT2870_DEF_MAC }; static const struct { uint8_t reg; uint8_t val; } rt2860_def_bbp[] = { RT2860_DEF_BBP },rt5390_def_bbp[] = { RT5390_DEF_BBP },rt5592_def_bbp[] = { RT5592_DEF_BBP }; /* * Default values for BBP register R196 for RT5592. */ static const uint8_t rt5592_bbp_r196[] = { 0xe0, 0x1f, 0x38, 0x32, 0x08, 0x28, 0x19, 0x0a, 0xff, 0x00, 0x16, 0x10, 0x10, 0x0b, 0x36, 0x2c, 0x26, 0x24, 0x42, 0x36, 0x30, 0x2d, 0x4c, 0x46, 0x3d, 0x40, 0x3e, 0x42, 0x3d, 0x40, 0x3c, 0x34, 0x2c, 0x2f, 0x3c, 0x35, 0x2e, 0x2a, 0x49, 0x41, 0x36, 0x31, 0x30, 0x30, 0x0e, 0x0d, 0x28, 0x21, 0x1c, 0x16, 0x50, 0x4a, 0x43, 0x40, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x14, 0x32, 0x2c, 0x36, 0x4c, 0x43, 0x2c, 0x2e, 0x36, 0x30, 0x6e }; static const struct rfprog { uint8_t chan; uint32_t r1, r2, r3, r4; } rt2860_rf2850[] = { RT2860_RF2850 }; struct { uint8_t n, r, k; } rt3070_freqs[] = { RT3070_RF3052 }; static const struct rt5592_freqs { uint16_t n; uint8_t k, m, r; } rt5592_freqs_20mhz[] = { RT5592_RF5592_20MHZ },rt5592_freqs_40mhz[] = { RT5592_RF5592_40MHZ }; static const struct { uint8_t reg; uint8_t val; } rt3070_def_rf[] = { RT3070_DEF_RF },rt3572_def_rf[] = { RT3572_DEF_RF },rt3593_def_rf[] = { RT3593_DEF_RF },rt5390_def_rf[] = { RT5390_DEF_RF },rt5392_def_rf[] = { RT5392_DEF_RF },rt5592_def_rf[] = { RT5592_DEF_RF },rt5592_2ghz_def_rf[] = { RT5592_2GHZ_DEF_RF },rt5592_5ghz_def_rf[] = { RT5592_5GHZ_DEF_RF }; static const struct { u_int firstchan; u_int lastchan; uint8_t reg; uint8_t val; } rt5592_chan_5ghz[] = { RT5592_CHAN_5GHZ }; static const struct usb_config run_config[RUN_N_XFER] = { [RUN_BULK_TX_BE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .ep_index = 0, .direction = UE_DIR_OUT, .bufsize = RUN_MAX_TXSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = run_bulk_tx_callback0, .timeout = 5000, /* ms */ }, [RUN_BULK_TX_BK] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .ep_index = 1, .bufsize = RUN_MAX_TXSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = run_bulk_tx_callback1, .timeout = 5000, /* ms */ }, [RUN_BULK_TX_VI] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .ep_index = 2, .bufsize = RUN_MAX_TXSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = run_bulk_tx_callback2, .timeout = 5000, /* ms */ }, [RUN_BULK_TX_VO] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .ep_index = 3, .bufsize = RUN_MAX_TXSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = run_bulk_tx_callback3, .timeout = 5000, /* ms */ }, [RUN_BULK_TX_HCCA] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .ep_index = 4, .bufsize = RUN_MAX_TXSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, .callback = run_bulk_tx_callback4, .timeout = 5000, /* ms */ }, [RUN_BULK_TX_PRIO] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .ep_index = 5, .bufsize = RUN_MAX_TXSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, .callback = run_bulk_tx_callback5, .timeout = 5000, /* ms */ }, [RUN_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = RUN_MAX_RXSZ, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = run_bulk_rx_callback, } }; static void run_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; struct usb_interface_descriptor *id; if (uaa->dev_state != UAA_DEV_READY) return; iface = usbd_get_iface(udev, 0); if (iface == NULL) return; id = iface->idesc; if (id == NULL || id->bInterfaceClass != UICLASS_MASS) return; if (usbd_lookup_id_by_uaa(run_devs, sizeof(run_devs), uaa)) return; if (usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT) == 0) uaa->dev_state = UAA_DEV_EJECTING; } static int run_driver_loaded(struct module *mod, int what, void *arg) { switch (what) { case MOD_LOAD: run_etag = EVENTHANDLER_REGISTER(usb_dev_configured, run_autoinst, NULL, EVENTHANDLER_PRI_ANY); break; case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(usb_dev_configured, run_etag); break; default: return (EOPNOTSUPP); } return (0); } static int run_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != RT2860_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(run_devs, sizeof(run_devs), uaa)); } static int run_attach(device_t self) { struct run_softc *sc = device_get_softc(self); struct usb_attach_arg *uaa = device_get_ivars(self); struct ieee80211com *ic = &sc->sc_ic; uint32_t ver; uint8_t iface_index; int ntries, error; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; if (USB_GET_DRIVER_INFO(uaa) != RUN_EJECT) sc->sc_flags |= RUN_FLAG_FWLOAD_NEEDED; mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, MTX_DEF); mbufq_init(&sc->sc_snd, ifqmaxlen); iface_index = RT2860_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, run_config, RUN_N_XFER, sc, &sc->sc_mtx); if (error) { device_printf(self, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto detach; } RUN_LOCK(sc); /* wait for the chip to settle */ for (ntries = 0; ntries < 100; ntries++) { if (run_read(sc, RT2860_ASIC_VER_ID, &ver) != 0) { RUN_UNLOCK(sc); goto detach; } if (ver != 0 && ver != 0xffffffff) break; run_delay(sc, 10); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for NIC to initialize\n"); RUN_UNLOCK(sc); goto detach; } sc->mac_ver = ver >> 16; sc->mac_rev = ver & 0xffff; /* retrieve RF rev. no and various other things from EEPROM */ run_read_eeprom(sc); device_printf(sc->sc_dev, "MAC/BBP RT%04X (rev 0x%04X), RF %s (MIMO %dT%dR), address %s\n", sc->mac_ver, sc->mac_rev, run_get_rf(sc->rf_rev), sc->ntxchains, sc->nrxchains, ether_sprintf(ic->ic_macaddr)); RUN_UNLOCK(sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA | /* station mode supported */ IEEE80211_C_MONITOR | /* monitor mode supported */ IEEE80211_C_IBSS | IEEE80211_C_HOSTAP | IEEE80211_C_WDS | /* 4-address traffic works */ IEEE80211_C_MBSS | IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_WME | /* WME */ IEEE80211_C_WPA; /* WPA1|WPA2(RSN) */ ic->ic_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_AES_CCM | IEEE80211_CRYPTO_TKIPMIC | IEEE80211_CRYPTO_TKIP; ic->ic_flags |= IEEE80211_F_DATAPAD; ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS; run_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_scan_start = run_scan_start; ic->ic_scan_end = run_scan_end; ic->ic_set_channel = run_set_channel; ic->ic_getradiocaps = run_getradiocaps; ic->ic_node_alloc = run_node_alloc; ic->ic_newassoc = run_newassoc; ic->ic_updateslot = run_updateslot; ic->ic_update_mcast = run_update_mcast; ic->ic_wme.wme_update = run_wme_update; ic->ic_raw_xmit = run_raw_xmit; ic->ic_update_promisc = run_update_promisc; ic->ic_vap_create = run_vap_create; ic->ic_vap_delete = run_vap_delete; ic->ic_transmit = run_transmit; ic->ic_parent = run_parent; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), RUN_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), RUN_RX_RADIOTAP_PRESENT); TASK_INIT(&sc->cmdq_task, 0, run_cmdq_cb, sc); TASK_INIT(&sc->ratectl_task, 0, run_ratectl_cb, sc); usb_callout_init_mtx(&sc->ratectl_ch, &sc->sc_mtx, 0); if (bootverbose) ieee80211_announce(ic); return (0); detach: run_detach(self); return (ENXIO); } static void run_drain_mbufq(struct run_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; RUN_LOCK_ASSERT(sc, MA_OWNED); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static int run_detach(device_t self) { struct run_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; int i; RUN_LOCK(sc); sc->sc_detached = 1; RUN_UNLOCK(sc); /* stop all USB transfers */ usbd_transfer_unsetup(sc->sc_xfer, RUN_N_XFER); RUN_LOCK(sc); sc->ratectl_run = RUN_RATECTL_OFF; sc->cmdq_run = sc->cmdq_key_set = RUN_CMDQ_ABORT; /* free TX list, if any */ for (i = 0; i != RUN_EP_QUEUES; i++) run_unsetup_tx_list(sc, &sc->sc_epq[i]); /* Free TX queue */ run_drain_mbufq(sc); RUN_UNLOCK(sc); if (sc->sc_ic.ic_softc == sc) { /* drain tasks */ usb_callout_drain(&sc->ratectl_ch); ieee80211_draintask(ic, &sc->cmdq_task); ieee80211_draintask(ic, &sc->ratectl_task); ieee80211_ifdetach(ic); } mtx_destroy(&sc->sc_mtx); return (0); } static struct ieee80211vap * run_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct run_softc *sc = ic->ic_softc; struct run_vap *rvp; struct ieee80211vap *vap; int i; if (sc->rvp_cnt >= RUN_VAP_MAX) { device_printf(sc->sc_dev, "number of VAPs maxed out\n"); return (NULL); } switch (opmode) { case IEEE80211_M_STA: /* enable s/w bmiss handling for sta mode */ flags |= IEEE80211_CLONE_NOBEACONS; /* fall though */ case IEEE80211_M_IBSS: case IEEE80211_M_MONITOR: case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: /* other than WDS vaps, only one at a time */ if (!TAILQ_EMPTY(&ic->ic_vaps)) return (NULL); break; case IEEE80211_M_WDS: TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next){ if(vap->iv_opmode != IEEE80211_M_HOSTAP) continue; /* WDS vap's always share the local mac address. */ flags &= ~IEEE80211_CLONE_BSSID; break; } if (vap == NULL) { device_printf(sc->sc_dev, "wds only supported in ap mode\n"); return (NULL); } break; default: device_printf(sc->sc_dev, "unknown opmode %d\n", opmode); return (NULL); } rvp = malloc(sizeof(struct run_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &rvp->vap; if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid) != 0) { /* out of memory */ free(rvp, M_80211_VAP); return (NULL); } vap->iv_update_beacon = run_update_beacon; vap->iv_max_aid = RT2870_WCID_MAX; /* * To delete the right key from h/w, we need wcid. * Luckily, there is unused space in ieee80211_key{}, wk_pad, * and matching wcid will be written into there. So, cast * some spells to remove 'const' from ieee80211_key{} */ vap->iv_key_delete = (void *)run_key_delete; vap->iv_key_set = (void *)run_key_set; /* override state transition machine */ rvp->newstate = vap->iv_newstate; vap->iv_newstate = run_newstate; if (opmode == IEEE80211_M_IBSS) { rvp->recv_mgmt = vap->iv_recv_mgmt; vap->iv_recv_mgmt = run_recv_mgmt; } ieee80211_ratectl_init(vap); ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); /* complete setup */ ieee80211_vap_attach(vap, run_media_change, ieee80211_media_status, mac); /* make sure id is always unique */ for (i = 0; i < RUN_VAP_MAX; i++) { if((sc->rvp_bmap & 1 << i) == 0){ sc->rvp_bmap |= 1 << i; rvp->rvp_id = i; break; } } if (sc->rvp_cnt++ == 0) ic->ic_opmode = opmode; if (opmode == IEEE80211_M_HOSTAP) sc->cmdq_run = RUN_CMDQ_GO; RUN_DPRINTF(sc, RUN_DEBUG_STATE, "rvp_id=%d bmap=%x rvp_cnt=%d\n", rvp->rvp_id, sc->rvp_bmap, sc->rvp_cnt); return (vap); } static void run_vap_delete(struct ieee80211vap *vap) { struct run_vap *rvp = RUN_VAP(vap); struct ieee80211com *ic; struct run_softc *sc; uint8_t rvp_id; if (vap == NULL) return; ic = vap->iv_ic; sc = ic->ic_softc; RUN_LOCK(sc); m_freem(rvp->beacon_mbuf); rvp->beacon_mbuf = NULL; rvp_id = rvp->rvp_id; sc->ratectl_run &= ~(1 << rvp_id); sc->rvp_bmap &= ~(1 << rvp_id); run_set_region_4(sc, RT2860_SKEY(rvp_id, 0), 0, 128); run_set_region_4(sc, RT2860_BCN_BASE(rvp_id), 0, 512); --sc->rvp_cnt; RUN_DPRINTF(sc, RUN_DEBUG_STATE, "vap=%p rvp_id=%d bmap=%x rvp_cnt=%d\n", vap, rvp_id, sc->rvp_bmap, sc->rvp_cnt); RUN_UNLOCK(sc); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(rvp, M_80211_VAP); } /* * There are numbers of functions need to be called in context thread. * Rather than creating taskqueue event for each of those functions, * here is all-for-one taskqueue callback function. This function * guarantees deferred functions are executed in the same order they * were enqueued. * '& RUN_CMDQ_MASQ' is to loop cmdq[]. */ static void run_cmdq_cb(void *arg, int pending) { struct run_softc *sc = arg; uint8_t i; /* call cmdq[].func locked */ RUN_LOCK(sc); for (i = sc->cmdq_exec; sc->cmdq[i].func && pending; i = sc->cmdq_exec, pending--) { RUN_DPRINTF(sc, RUN_DEBUG_CMD, "cmdq_exec=%d pending=%d\n", i, pending); if (sc->cmdq_run == RUN_CMDQ_GO) { /* * If arg0 is NULL, callback func needs more * than one arg. So, pass ptr to cmdq struct. */ if (sc->cmdq[i].arg0) sc->cmdq[i].func(sc->cmdq[i].arg0); else sc->cmdq[i].func(&sc->cmdq[i]); } sc->cmdq[i].arg0 = NULL; sc->cmdq[i].func = NULL; sc->cmdq_exec++; sc->cmdq_exec &= RUN_CMDQ_MASQ; } RUN_UNLOCK(sc); } static void run_setup_tx_list(struct run_softc *sc, struct run_endpoint_queue *pq) { struct run_tx_data *data; memset(pq, 0, sizeof(*pq)); STAILQ_INIT(&pq->tx_qh); STAILQ_INIT(&pq->tx_fh); for (data = &pq->tx_data[0]; data < &pq->tx_data[RUN_TX_RING_COUNT]; data++) { data->sc = sc; STAILQ_INSERT_TAIL(&pq->tx_fh, data, next); } pq->tx_nfree = RUN_TX_RING_COUNT; } static void run_unsetup_tx_list(struct run_softc *sc, struct run_endpoint_queue *pq) { struct run_tx_data *data; /* make sure any subsequent use of the queues will fail */ pq->tx_nfree = 0; STAILQ_INIT(&pq->tx_fh); STAILQ_INIT(&pq->tx_qh); /* free up all node references and mbufs */ for (data = &pq->tx_data[0]; data < &pq->tx_data[RUN_TX_RING_COUNT]; data++) { if (data->m != NULL) { m_freem(data->m); data->m = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } } static int run_load_microcode(struct run_softc *sc) { usb_device_request_t req; const struct firmware *fw; const u_char *base; uint32_t tmp; int ntries, error; const uint64_t *temp; uint64_t bytes; RUN_UNLOCK(sc); fw = firmware_get("runfw"); RUN_LOCK(sc); if (fw == NULL) { device_printf(sc->sc_dev, "failed loadfirmware of file %s\n", "runfw"); return ENOENT; } if (fw->datasize != 8192) { device_printf(sc->sc_dev, "invalid firmware size (should be 8KB)\n"); error = EINVAL; goto fail; } /* * RT3071/RT3072 use a different firmware * run-rt2870 (8KB) contains both, * first half (4KB) is for rt2870, * last half is for rt3071. */ base = fw->data; if ((sc->mac_ver) != 0x2860 && (sc->mac_ver) != 0x2872 && (sc->mac_ver) != 0x3070) { base += 4096; } /* cheap sanity check */ temp = fw->data; bytes = *temp; if (bytes != be64toh(0xffffff0210280210ULL)) { device_printf(sc->sc_dev, "firmware checksum failed\n"); error = EINVAL; goto fail; } /* write microcode image */ if (sc->sc_flags & RUN_FLAG_FWLOAD_NEEDED) { run_write_region_1(sc, RT2870_FW_BASE, base, 4096); run_write(sc, RT2860_H2M_MAILBOX_CID, 0xffffffff); run_write(sc, RT2860_H2M_MAILBOX_STATUS, 0xffffffff); } req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RT2870_RESET; USETW(req.wValue, 8); USETW(req.wIndex, 0); USETW(req.wLength, 0); if ((error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)) != 0) { device_printf(sc->sc_dev, "firmware reset failed\n"); goto fail; } run_delay(sc, 10); run_write(sc, RT2860_H2M_BBPAGENT, 0); run_write(sc, RT2860_H2M_MAILBOX, 0); run_write(sc, RT2860_H2M_INTSRC, 0); if ((error = run_mcu_cmd(sc, RT2860_MCU_CMD_RFRESET, 0)) != 0) goto fail; /* wait until microcontroller is ready */ for (ntries = 0; ntries < 1000; ntries++) { if ((error = run_read(sc, RT2860_SYS_CTRL, &tmp)) != 0) goto fail; if (tmp & RT2860_MCU_READY) break; run_delay(sc, 10); } if (ntries == 1000) { device_printf(sc->sc_dev, "timeout waiting for MCU to initialize\n"); error = ETIMEDOUT; goto fail; } device_printf(sc->sc_dev, "firmware %s ver. %u.%u loaded\n", (base == fw->data) ? "RT2870" : "RT3071", *(base + 4092), *(base + 4093)); fail: firmware_put(fw, FIRMWARE_UNLOAD); return (error); } static int run_reset(struct run_softc *sc) { usb_device_request_t req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RT2870_RESET; USETW(req.wValue, 1); USETW(req.wIndex, 0); USETW(req.wLength, 0); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static usb_error_t run_do_request(struct run_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; RUN_LOCK_ASSERT(sc, MA_OWNED); while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0) break; RUN_DPRINTF(sc, RUN_DEBUG_USB, "Control request failed, %s (retrying)\n", usbd_errstr(err)); run_delay(sc, 10); } return (err); } static int run_read(struct run_softc *sc, uint16_t reg, uint32_t *val) { uint32_t tmp; int error; error = run_read_region_1(sc, reg, (uint8_t *)&tmp, sizeof tmp); if (error == 0) *val = le32toh(tmp); else *val = 0xffffffff; return (error); } static int run_read_region_1(struct run_softc *sc, uint16_t reg, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RT2870_READ_REGION_1; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, len); return (run_do_request(sc, &req, buf)); } static int run_write_2(struct run_softc *sc, uint16_t reg, uint16_t val) { usb_device_request_t req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RT2870_WRITE_2; USETW(req.wValue, val); USETW(req.wIndex, reg); USETW(req.wLength, 0); return (run_do_request(sc, &req, NULL)); } static int run_write(struct run_softc *sc, uint16_t reg, uint32_t val) { int error; if ((error = run_write_2(sc, reg, val & 0xffff)) == 0) error = run_write_2(sc, reg + 2, val >> 16); return (error); } static int run_write_region_1(struct run_softc *sc, uint16_t reg, const uint8_t *buf, int len) { #if 1 int i, error = 0; /* * NB: the WRITE_REGION_1 command is not stable on RT2860. * We thus issue multiple WRITE_2 commands instead. */ KASSERT((len & 1) == 0, ("run_write_region_1: Data too long.\n")); for (i = 0; i < len && error == 0; i += 2) error = run_write_2(sc, reg + i, buf[i] | buf[i + 1] << 8); return (error); #else usb_device_request_t req; int error = 0; /* * NOTE: It appears the WRITE_REGION_1 command cannot be * passed a huge amount of data, which will crash the * firmware. Limit amount of data passed to 64-bytes at a * time. */ while (len > 0) { int delta = 64; if (delta > len) delta = len; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RT2870_WRITE_REGION_1; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, delta); error = run_do_request(sc, &req, __DECONST(uint8_t *, buf)); if (error != 0) break; reg += delta; buf += delta; len -= delta; } return (error); #endif } static int run_set_region_4(struct run_softc *sc, uint16_t reg, uint32_t val, int len) { int i, error = 0; KASSERT((len & 3) == 0, ("run_set_region_4: Invalid data length.\n")); for (i = 0; i < len && error == 0; i += 4) error = run_write(sc, reg + i, val); return (error); } static int run_efuse_read(struct run_softc *sc, uint16_t addr, uint16_t *val, int count) { uint32_t tmp; uint16_t reg; int error, ntries; if ((error = run_read(sc, RT3070_EFUSE_CTRL, &tmp)) != 0) return (error); if (count == 2) addr *= 2; /*- * Read one 16-byte block into registers EFUSE_DATA[0-3]: * DATA0: F E D C * DATA1: B A 9 8 * DATA2: 7 6 5 4 * DATA3: 3 2 1 0 */ tmp &= ~(RT3070_EFSROM_MODE_MASK | RT3070_EFSROM_AIN_MASK); tmp |= (addr & ~0xf) << RT3070_EFSROM_AIN_SHIFT | RT3070_EFSROM_KICK; run_write(sc, RT3070_EFUSE_CTRL, tmp); for (ntries = 0; ntries < 100; ntries++) { if ((error = run_read(sc, RT3070_EFUSE_CTRL, &tmp)) != 0) return (error); if (!(tmp & RT3070_EFSROM_KICK)) break; run_delay(sc, 2); } if (ntries == 100) return (ETIMEDOUT); if ((tmp & RT3070_EFUSE_AOUT_MASK) == RT3070_EFUSE_AOUT_MASK) { *val = 0xffff; /* address not found */ return (0); } /* determine to which 32-bit register our 16-bit word belongs */ reg = RT3070_EFUSE_DATA3 - (addr & 0xc); if ((error = run_read(sc, reg, &tmp)) != 0) return (error); tmp >>= (8 * (addr & 0x3)); *val = (addr & 1) ? tmp >> 16 : tmp & 0xffff; return (0); } /* Read 16-bit from eFUSE ROM for RT3xxx. */ static int run_efuse_read_2(struct run_softc *sc, uint16_t addr, uint16_t *val) { return (run_efuse_read(sc, addr, val, 2)); } static int run_eeprom_read_2(struct run_softc *sc, uint16_t addr, uint16_t *val) { usb_device_request_t req; uint16_t tmp; int error; addr *= 2; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RT2870_EEPROM_READ; USETW(req.wValue, 0); USETW(req.wIndex, addr); USETW(req.wLength, sizeof(tmp)); error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &tmp); if (error == 0) *val = le16toh(tmp); else *val = 0xffff; return (error); } static __inline int run_srom_read(struct run_softc *sc, uint16_t addr, uint16_t *val) { /* either eFUSE ROM or EEPROM */ return sc->sc_srom_read(sc, addr, val); } static int run_rt2870_rf_write(struct run_softc *sc, uint32_t val) { uint32_t tmp; int error, ntries; for (ntries = 0; ntries < 10; ntries++) { if ((error = run_read(sc, RT2860_RF_CSR_CFG0, &tmp)) != 0) return (error); if (!(tmp & RT2860_RF_REG_CTRL)) break; } if (ntries == 10) return (ETIMEDOUT); return (run_write(sc, RT2860_RF_CSR_CFG0, val)); } static int run_rt3070_rf_read(struct run_softc *sc, uint8_t reg, uint8_t *val) { uint32_t tmp; int error, ntries; for (ntries = 0; ntries < 100; ntries++) { if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0) return (error); if (!(tmp & RT3070_RF_KICK)) break; } if (ntries == 100) return (ETIMEDOUT); tmp = RT3070_RF_KICK | reg << 8; if ((error = run_write(sc, RT3070_RF_CSR_CFG, tmp)) != 0) return (error); for (ntries = 0; ntries < 100; ntries++) { if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0) return (error); if (!(tmp & RT3070_RF_KICK)) break; } if (ntries == 100) return (ETIMEDOUT); *val = tmp & 0xff; return (0); } static int run_rt3070_rf_write(struct run_softc *sc, uint8_t reg, uint8_t val) { uint32_t tmp; int error, ntries; for (ntries = 0; ntries < 10; ntries++) { if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0) return (error); if (!(tmp & RT3070_RF_KICK)) break; } if (ntries == 10) return (ETIMEDOUT); tmp = RT3070_RF_WRITE | RT3070_RF_KICK | reg << 8 | val; return (run_write(sc, RT3070_RF_CSR_CFG, tmp)); } static int run_bbp_read(struct run_softc *sc, uint8_t reg, uint8_t *val) { uint32_t tmp; int ntries, error; for (ntries = 0; ntries < 10; ntries++) { if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0) return (error); if (!(tmp & RT2860_BBP_CSR_KICK)) break; } if (ntries == 10) return (ETIMEDOUT); tmp = RT2860_BBP_CSR_READ | RT2860_BBP_CSR_KICK | reg << 8; if ((error = run_write(sc, RT2860_BBP_CSR_CFG, tmp)) != 0) return (error); for (ntries = 0; ntries < 10; ntries++) { if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0) return (error); if (!(tmp & RT2860_BBP_CSR_KICK)) break; } if (ntries == 10) return (ETIMEDOUT); *val = tmp & 0xff; return (0); } static int run_bbp_write(struct run_softc *sc, uint8_t reg, uint8_t val) { uint32_t tmp; int ntries, error; for (ntries = 0; ntries < 10; ntries++) { if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0) return (error); if (!(tmp & RT2860_BBP_CSR_KICK)) break; } if (ntries == 10) return (ETIMEDOUT); tmp = RT2860_BBP_CSR_KICK | reg << 8 | val; return (run_write(sc, RT2860_BBP_CSR_CFG, tmp)); } /* * Send a command to the 8051 microcontroller unit. */ static int run_mcu_cmd(struct run_softc *sc, uint8_t cmd, uint16_t arg) { uint32_t tmp; int error, ntries; for (ntries = 0; ntries < 100; ntries++) { if ((error = run_read(sc, RT2860_H2M_MAILBOX, &tmp)) != 0) return error; if (!(tmp & RT2860_H2M_BUSY)) break; } if (ntries == 100) return ETIMEDOUT; tmp = RT2860_H2M_BUSY | RT2860_TOKEN_NO_INTR << 16 | arg; if ((error = run_write(sc, RT2860_H2M_MAILBOX, tmp)) == 0) error = run_write(sc, RT2860_HOST_CMD, cmd); return (error); } /* * Add `delta' (signed) to each 4-bit sub-word of a 32-bit word. * Used to adjust per-rate Tx power registers. */ static __inline uint32_t b4inc(uint32_t b32, int8_t delta) { int8_t i, b4; for (i = 0; i < 8; i++) { b4 = b32 & 0xf; b4 += delta; if (b4 < 0) b4 = 0; else if (b4 > 0xf) b4 = 0xf; b32 = b32 >> 4 | b4 << 28; } return (b32); } static const char * run_get_rf(uint16_t rev) { switch (rev) { case RT2860_RF_2820: return "RT2820"; case RT2860_RF_2850: return "RT2850"; case RT2860_RF_2720: return "RT2720"; case RT2860_RF_2750: return "RT2750"; case RT3070_RF_3020: return "RT3020"; case RT3070_RF_2020: return "RT2020"; case RT3070_RF_3021: return "RT3021"; case RT3070_RF_3022: return "RT3022"; case RT3070_RF_3052: return "RT3052"; case RT3593_RF_3053: return "RT3053"; case RT5592_RF_5592: return "RT5592"; case RT5390_RF_5370: return "RT5370"; case RT5390_RF_5372: return "RT5372"; } return ("unknown"); } static void run_rt3593_get_txpower(struct run_softc *sc) { uint16_t addr, val; int i; /* Read power settings for 2GHz channels. */ for (i = 0; i < 14; i += 2) { addr = (sc->ntxchains == 3) ? RT3593_EEPROM_PWR2GHZ_BASE1 : RT2860_EEPROM_PWR2GHZ_BASE1; run_srom_read(sc, addr + i / 2, &val); sc->txpow1[i + 0] = (int8_t)(val & 0xff); sc->txpow1[i + 1] = (int8_t)(val >> 8); addr = (sc->ntxchains == 3) ? RT3593_EEPROM_PWR2GHZ_BASE2 : RT2860_EEPROM_PWR2GHZ_BASE2; run_srom_read(sc, addr + i / 2, &val); sc->txpow2[i + 0] = (int8_t)(val & 0xff); sc->txpow2[i + 1] = (int8_t)(val >> 8); if (sc->ntxchains == 3) { run_srom_read(sc, RT3593_EEPROM_PWR2GHZ_BASE3 + i / 2, &val); sc->txpow3[i + 0] = (int8_t)(val & 0xff); sc->txpow3[i + 1] = (int8_t)(val >> 8); } } /* Fix broken Tx power entries. */ for (i = 0; i < 14; i++) { if (sc->txpow1[i] > 31) sc->txpow1[i] = 5; if (sc->txpow2[i] > 31) sc->txpow2[i] = 5; if (sc->ntxchains == 3) { if (sc->txpow3[i] > 31) sc->txpow3[i] = 5; } } /* Read power settings for 5GHz channels. */ for (i = 0; i < 40; i += 2) { run_srom_read(sc, RT3593_EEPROM_PWR5GHZ_BASE1 + i / 2, &val); sc->txpow1[i + 14] = (int8_t)(val & 0xff); sc->txpow1[i + 15] = (int8_t)(val >> 8); run_srom_read(sc, RT3593_EEPROM_PWR5GHZ_BASE2 + i / 2, &val); sc->txpow2[i + 14] = (int8_t)(val & 0xff); sc->txpow2[i + 15] = (int8_t)(val >> 8); if (sc->ntxchains == 3) { run_srom_read(sc, RT3593_EEPROM_PWR5GHZ_BASE3 + i / 2, &val); sc->txpow3[i + 14] = (int8_t)(val & 0xff); sc->txpow3[i + 15] = (int8_t)(val >> 8); } } } static void run_get_txpower(struct run_softc *sc) { uint16_t val; int i; /* Read power settings for 2GHz channels. */ for (i = 0; i < 14; i += 2) { run_srom_read(sc, RT2860_EEPROM_PWR2GHZ_BASE1 + i / 2, &val); sc->txpow1[i + 0] = (int8_t)(val & 0xff); sc->txpow1[i + 1] = (int8_t)(val >> 8); if (sc->mac_ver != 0x5390) { run_srom_read(sc, RT2860_EEPROM_PWR2GHZ_BASE2 + i / 2, &val); sc->txpow2[i + 0] = (int8_t)(val & 0xff); sc->txpow2[i + 1] = (int8_t)(val >> 8); } } /* Fix broken Tx power entries. */ for (i = 0; i < 14; i++) { if (sc->mac_ver >= 0x5390) { if (sc->txpow1[i] < 0 || sc->txpow1[i] > 39) sc->txpow1[i] = 5; } else { if (sc->txpow1[i] < 0 || sc->txpow1[i] > 31) sc->txpow1[i] = 5; } if (sc->mac_ver > 0x5390) { if (sc->txpow2[i] < 0 || sc->txpow2[i] > 39) sc->txpow2[i] = 5; } else if (sc->mac_ver < 0x5390) { if (sc->txpow2[i] < 0 || sc->txpow2[i] > 31) sc->txpow2[i] = 5; } RUN_DPRINTF(sc, RUN_DEBUG_TXPWR, "chan %d: power1=%d, power2=%d\n", rt2860_rf2850[i].chan, sc->txpow1[i], sc->txpow2[i]); } /* Read power settings for 5GHz channels. */ for (i = 0; i < 40; i += 2) { run_srom_read(sc, RT2860_EEPROM_PWR5GHZ_BASE1 + i / 2, &val); sc->txpow1[i + 14] = (int8_t)(val & 0xff); sc->txpow1[i + 15] = (int8_t)(val >> 8); run_srom_read(sc, RT2860_EEPROM_PWR5GHZ_BASE2 + i / 2, &val); sc->txpow2[i + 14] = (int8_t)(val & 0xff); sc->txpow2[i + 15] = (int8_t)(val >> 8); } /* Fix broken Tx power entries. */ for (i = 0; i < 40; i++ ) { if (sc->mac_ver != 0x5592) { if (sc->txpow1[14 + i] < -7 || sc->txpow1[14 + i] > 15) sc->txpow1[14 + i] = 5; if (sc->txpow2[14 + i] < -7 || sc->txpow2[14 + i] > 15) sc->txpow2[14 + i] = 5; } RUN_DPRINTF(sc, RUN_DEBUG_TXPWR, "chan %d: power1=%d, power2=%d\n", rt2860_rf2850[14 + i].chan, sc->txpow1[14 + i], sc->txpow2[14 + i]); } } static int run_read_eeprom(struct run_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; int8_t delta_2ghz, delta_5ghz; uint32_t tmp; uint16_t val; int ridx, ant, i; /* check whether the ROM is eFUSE ROM or EEPROM */ sc->sc_srom_read = run_eeprom_read_2; if (sc->mac_ver >= 0x3070) { run_read(sc, RT3070_EFUSE_CTRL, &tmp); RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EFUSE_CTRL=0x%08x\n", tmp); if ((tmp & RT3070_SEL_EFUSE) || sc->mac_ver == 0x3593) sc->sc_srom_read = run_efuse_read_2; } /* read ROM version */ run_srom_read(sc, RT2860_EEPROM_VERSION, &val); RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM rev=%d, FAE=%d\n", val >> 8, val & 0xff); /* read MAC address */ run_srom_read(sc, RT2860_EEPROM_MAC01, &val); ic->ic_macaddr[0] = val & 0xff; ic->ic_macaddr[1] = val >> 8; run_srom_read(sc, RT2860_EEPROM_MAC23, &val); ic->ic_macaddr[2] = val & 0xff; ic->ic_macaddr[3] = val >> 8; run_srom_read(sc, RT2860_EEPROM_MAC45, &val); ic->ic_macaddr[4] = val & 0xff; ic->ic_macaddr[5] = val >> 8; if (sc->mac_ver < 0x3593) { /* read vender BBP settings */ for (i = 0; i < 10; i++) { run_srom_read(sc, RT2860_EEPROM_BBP_BASE + i, &val); sc->bbp[i].val = val & 0xff; sc->bbp[i].reg = val >> 8; RUN_DPRINTF(sc, RUN_DEBUG_ROM, "BBP%d=0x%02x\n", sc->bbp[i].reg, sc->bbp[i].val); } if (sc->mac_ver >= 0x3071) { /* read vendor RF settings */ for (i = 0; i < 10; i++) { run_srom_read(sc, RT3071_EEPROM_RF_BASE + i, &val); sc->rf[i].val = val & 0xff; sc->rf[i].reg = val >> 8; RUN_DPRINTF(sc, RUN_DEBUG_ROM, "RF%d=0x%02x\n", sc->rf[i].reg, sc->rf[i].val); } } } /* read RF frequency offset from EEPROM */ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_FREQ_LEDS : RT3593_EEPROM_FREQ, &val); sc->freq = ((val & 0xff) != 0xff) ? val & 0xff : 0; RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM freq offset %d\n", sc->freq & 0xff); run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_FREQ_LEDS : RT3593_EEPROM_FREQ_LEDS, &val); if (val >> 8 != 0xff) { /* read LEDs operating mode */ sc->leds = val >> 8; run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LED1 : RT3593_EEPROM_LED1, &sc->led[0]); run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LED2 : RT3593_EEPROM_LED2, &sc->led[1]); run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LED3 : RT3593_EEPROM_LED3, &sc->led[2]); } else { /* broken EEPROM, use default settings */ sc->leds = 0x01; sc->led[0] = 0x5555; sc->led[1] = 0x2221; sc->led[2] = 0x5627; /* differs from RT2860 */ } RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM LED mode=0x%02x, LEDs=0x%04x/0x%04x/0x%04x\n", sc->leds, sc->led[0], sc->led[1], sc->led[2]); /* read RF information */ if (sc->mac_ver == 0x5390 || sc->mac_ver ==0x5392) run_srom_read(sc, 0x00, &val); else run_srom_read(sc, RT2860_EEPROM_ANTENNA, &val); if (val == 0xffff) { device_printf(sc->sc_dev, "invalid EEPROM antenna info, using default\n"); if (sc->mac_ver == 0x3572) { /* default to RF3052 2T2R */ sc->rf_rev = RT3070_RF_3052; sc->ntxchains = 2; sc->nrxchains = 2; } else if (sc->mac_ver >= 0x3070) { /* default to RF3020 1T1R */ sc->rf_rev = RT3070_RF_3020; sc->ntxchains = 1; sc->nrxchains = 1; } else { /* default to RF2820 1T2R */ sc->rf_rev = RT2860_RF_2820; sc->ntxchains = 1; sc->nrxchains = 2; } } else { if (sc->mac_ver == 0x5390 || sc->mac_ver ==0x5392) { sc->rf_rev = val; run_srom_read(sc, RT2860_EEPROM_ANTENNA, &val); } else sc->rf_rev = (val >> 8) & 0xf; sc->ntxchains = (val >> 4) & 0xf; sc->nrxchains = val & 0xf; } RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM RF rev=0x%04x chains=%dT%dR\n", sc->rf_rev, sc->ntxchains, sc->nrxchains); /* check if RF supports automatic Tx access gain control */ run_srom_read(sc, RT2860_EEPROM_CONFIG, &val); RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM CFG 0x%04x\n", val); /* check if driver should patch the DAC issue */ if ((val >> 8) != 0xff) sc->patch_dac = (val >> 15) & 1; if ((val & 0xff) != 0xff) { sc->ext_5ghz_lna = (val >> 3) & 1; sc->ext_2ghz_lna = (val >> 2) & 1; /* check if RF supports automatic Tx access gain control */ sc->calib_2ghz = sc->calib_5ghz = (val >> 1) & 1; /* check if we have a hardware radio switch */ sc->rfswitch = val & 1; } /* Read Tx power settings. */ if (sc->mac_ver == 0x3593) run_rt3593_get_txpower(sc); else run_get_txpower(sc); /* read Tx power compensation for each Tx rate */ run_srom_read(sc, RT2860_EEPROM_DELTAPWR, &val); delta_2ghz = delta_5ghz = 0; if ((val & 0xff) != 0xff && (val & 0x80)) { delta_2ghz = val & 0xf; if (!(val & 0x40)) /* negative number */ delta_2ghz = -delta_2ghz; } val >>= 8; if ((val & 0xff) != 0xff && (val & 0x80)) { delta_5ghz = val & 0xf; if (!(val & 0x40)) /* negative number */ delta_5ghz = -delta_5ghz; } RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_TXPWR, "power compensation=%d (2GHz), %d (5GHz)\n", delta_2ghz, delta_5ghz); for (ridx = 0; ridx < 5; ridx++) { uint32_t reg; run_srom_read(sc, RT2860_EEPROM_RPWR + ridx * 2, &val); reg = val; run_srom_read(sc, RT2860_EEPROM_RPWR + ridx * 2 + 1, &val); reg |= (uint32_t)val << 16; sc->txpow20mhz[ridx] = reg; sc->txpow40mhz_2ghz[ridx] = b4inc(reg, delta_2ghz); sc->txpow40mhz_5ghz[ridx] = b4inc(reg, delta_5ghz); RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_TXPWR, "ridx %d: power 20MHz=0x%08x, 40MHz/2GHz=0x%08x, " "40MHz/5GHz=0x%08x\n", ridx, sc->txpow20mhz[ridx], sc->txpow40mhz_2ghz[ridx], sc->txpow40mhz_5ghz[ridx]); } /* Read RSSI offsets and LNA gains from EEPROM. */ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI1_2GHZ : RT3593_EEPROM_RSSI1_2GHZ, &val); sc->rssi_2ghz[0] = val & 0xff; /* Ant A */ sc->rssi_2ghz[1] = val >> 8; /* Ant B */ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI2_2GHZ : RT3593_EEPROM_RSSI2_2GHZ, &val); if (sc->mac_ver >= 0x3070) { if (sc->mac_ver == 0x3593) { sc->txmixgain_2ghz = 0; sc->rssi_2ghz[2] = val & 0xff; /* Ant C */ } else { /* * On RT3070 chips (limited to 2 Rx chains), this ROM * field contains the Tx mixer gain for the 2GHz band. */ if ((val & 0xff) != 0xff) sc->txmixgain_2ghz = val & 0x7; } RUN_DPRINTF(sc, RUN_DEBUG_ROM, "tx mixer gain=%u (2GHz)\n", sc->txmixgain_2ghz); } else sc->rssi_2ghz[2] = val & 0xff; /* Ant C */ if (sc->mac_ver == 0x3593) run_srom_read(sc, RT3593_EEPROM_LNA_5GHZ, &val); sc->lna[2] = val >> 8; /* channel group 2 */ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI1_5GHZ : RT3593_EEPROM_RSSI1_5GHZ, &val); sc->rssi_5ghz[0] = val & 0xff; /* Ant A */ sc->rssi_5ghz[1] = val >> 8; /* Ant B */ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI2_5GHZ : RT3593_EEPROM_RSSI2_5GHZ, &val); if (sc->mac_ver == 0x3572) { /* * On RT3572 chips (limited to 2 Rx chains), this ROM * field contains the Tx mixer gain for the 5GHz band. */ if ((val & 0xff) != 0xff) sc->txmixgain_5ghz = val & 0x7; RUN_DPRINTF(sc, RUN_DEBUG_ROM, "tx mixer gain=%u (5GHz)\n", sc->txmixgain_5ghz); } else sc->rssi_5ghz[2] = val & 0xff; /* Ant C */ if (sc->mac_ver == 0x3593) { sc->txmixgain_5ghz = 0; run_srom_read(sc, RT3593_EEPROM_LNA_5GHZ, &val); } sc->lna[3] = val >> 8; /* channel group 3 */ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LNA : RT3593_EEPROM_LNA, &val); sc->lna[0] = val & 0xff; /* channel group 0 */ sc->lna[1] = val >> 8; /* channel group 1 */ /* fix broken 5GHz LNA entries */ if (sc->lna[2] == 0 || sc->lna[2] == 0xff) { RUN_DPRINTF(sc, RUN_DEBUG_ROM, "invalid LNA for channel group %d\n", 2); sc->lna[2] = sc->lna[1]; } if (sc->lna[3] == 0 || sc->lna[3] == 0xff) { RUN_DPRINTF(sc, RUN_DEBUG_ROM, "invalid LNA for channel group %d\n", 3); sc->lna[3] = sc->lna[1]; } /* fix broken RSSI offset entries */ for (ant = 0; ant < 3; ant++) { if (sc->rssi_2ghz[ant] < -10 || sc->rssi_2ghz[ant] > 10) { RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_RSSI, "invalid RSSI%d offset: %d (2GHz)\n", ant + 1, sc->rssi_2ghz[ant]); sc->rssi_2ghz[ant] = 0; } if (sc->rssi_5ghz[ant] < -10 || sc->rssi_5ghz[ant] > 10) { RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_RSSI, "invalid RSSI%d offset: %d (5GHz)\n", ant + 1, sc->rssi_5ghz[ant]); sc->rssi_5ghz[ant] = 0; } } return (0); } static struct ieee80211_node * run_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { return malloc(sizeof (struct run_node), M_80211_NODE, M_NOWAIT | M_ZERO); } static int run_media_change(struct ifnet *ifp) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_txparam *tp; struct run_softc *sc = ic->ic_softc; uint8_t rate, ridx; int error; RUN_LOCK(sc); error = ieee80211_media_change(ifp); if (error != ENETRESET) { RUN_UNLOCK(sc); return (error); } tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { struct ieee80211_node *ni; struct run_node *rn; rate = ic->ic_sup_rates[ic->ic_curmode]. rs_rates[tp->ucastrate] & IEEE80211_RATE_VAL; for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) if (rt2860_rates[ridx].rate == rate) break; ni = ieee80211_ref_node(vap->iv_bss); rn = RUN_NODE(ni); rn->fix_ridx = ridx; RUN_DPRINTF(sc, RUN_DEBUG_RATE, "rate=%d, fix_ridx=%d\n", rate, rn->fix_ridx); ieee80211_free_node(ni); } #if 0 if ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & RUN_RUNNING)){ run_init_locked(sc); } #endif RUN_UNLOCK(sc); return (0); } static int run_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { const struct ieee80211_txparam *tp; struct ieee80211com *ic = vap->iv_ic; struct run_softc *sc = ic->ic_softc; struct run_vap *rvp = RUN_VAP(vap); enum ieee80211_state ostate; uint32_t sta[3]; uint8_t ratectl; uint8_t restart_ratectl = 0; uint8_t bid = 1 << rvp->rvp_id; ostate = vap->iv_state; RUN_DPRINTF(sc, RUN_DEBUG_STATE, "%s -> %s\n", ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); RUN_LOCK(sc); ratectl = sc->ratectl_run; /* remember current state */ sc->ratectl_run = RUN_RATECTL_OFF; usb_callout_stop(&sc->ratectl_ch); if (ostate == IEEE80211_S_RUN) { /* turn link LED off */ run_set_leds(sc, RT2860_LED_RADIO); } switch (nstate) { case IEEE80211_S_INIT: restart_ratectl = 1; if (ostate != IEEE80211_S_RUN) break; ratectl &= ~bid; sc->runbmap &= ~bid; /* abort TSF synchronization if there is no vap running */ if (--sc->running == 0) run_disable_tsf(sc); break; case IEEE80211_S_RUN: if (!(sc->runbmap & bid)) { if(sc->running++) restart_ratectl = 1; sc->runbmap |= bid; } m_freem(rvp->beacon_mbuf); rvp->beacon_mbuf = NULL; switch (vap->iv_opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: sc->ap_running |= bid; ic->ic_opmode = vap->iv_opmode; run_update_beacon_cb(vap); break; case IEEE80211_M_IBSS: sc->adhoc_running |= bid; if (!sc->ap_running) ic->ic_opmode = vap->iv_opmode; run_update_beacon_cb(vap); break; case IEEE80211_M_STA: sc->sta_running |= bid; if (!sc->ap_running && !sc->adhoc_running) ic->ic_opmode = vap->iv_opmode; /* read statistic counters (clear on read) */ run_read_region_1(sc, RT2860_TX_STA_CNT0, (uint8_t *)sta, sizeof sta); break; default: ic->ic_opmode = vap->iv_opmode; break; } if (vap->iv_opmode != IEEE80211_M_MONITOR) { struct ieee80211_node *ni; if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) { RUN_UNLOCK(sc); IEEE80211_LOCK(ic); return (-1); } run_updateslot(ic); run_enable_mrr(sc); run_set_txpreamble(sc); run_set_basicrates(sc); ni = ieee80211_ref_node(vap->iv_bss); IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); run_set_bssid(sc, sc->sc_bssid); ieee80211_free_node(ni); run_enable_tsf_sync(sc); /* enable automatic rate adaptation */ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) ratectl |= bid; } else run_enable_tsf(sc); /* turn link LED on */ run_set_leds(sc, RT2860_LED_RADIO | (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan) ? RT2860_LED_LINK_2GHZ : RT2860_LED_LINK_5GHZ)); break; default: RUN_DPRINTF(sc, RUN_DEBUG_STATE, "undefined state\n"); break; } /* restart amrr for running VAPs */ if ((sc->ratectl_run = ratectl) && restart_ratectl) usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc); RUN_UNLOCK(sc); IEEE80211_LOCK(ic); return(rvp->newstate(vap, nstate, arg)); } static int run_wme_update(struct ieee80211com *ic) { struct chanAccParams chp; struct run_softc *sc = ic->ic_softc; const struct wmeParams *ac; int aci, error = 0; ieee80211_wme_ic_getparams(ic, &chp); ac = chp.cap_wmeParams; /* update MAC TX configuration registers */ RUN_LOCK(sc); for (aci = 0; aci < WME_NUM_AC; aci++) { error = run_write(sc, RT2860_EDCA_AC_CFG(aci), ac[aci].wmep_logcwmax << 16 | ac[aci].wmep_logcwmin << 12 | ac[aci].wmep_aifsn << 8 | ac[aci].wmep_txopLimit); if (error) goto err; } /* update SCH/DMA registers too */ error = run_write(sc, RT2860_WMM_AIFSN_CFG, ac[WME_AC_VO].wmep_aifsn << 12 | ac[WME_AC_VI].wmep_aifsn << 8 | ac[WME_AC_BK].wmep_aifsn << 4 | ac[WME_AC_BE].wmep_aifsn); if (error) goto err; error = run_write(sc, RT2860_WMM_CWMIN_CFG, ac[WME_AC_VO].wmep_logcwmin << 12 | ac[WME_AC_VI].wmep_logcwmin << 8 | ac[WME_AC_BK].wmep_logcwmin << 4 | ac[WME_AC_BE].wmep_logcwmin); if (error) goto err; error = run_write(sc, RT2860_WMM_CWMAX_CFG, ac[WME_AC_VO].wmep_logcwmax << 12 | ac[WME_AC_VI].wmep_logcwmax << 8 | ac[WME_AC_BK].wmep_logcwmax << 4 | ac[WME_AC_BE].wmep_logcwmax); if (error) goto err; error = run_write(sc, RT2860_WMM_TXOP0_CFG, ac[WME_AC_BK].wmep_txopLimit << 16 | ac[WME_AC_BE].wmep_txopLimit); if (error) goto err; error = run_write(sc, RT2860_WMM_TXOP1_CFG, ac[WME_AC_VO].wmep_txopLimit << 16 | ac[WME_AC_VI].wmep_txopLimit); err: RUN_UNLOCK(sc); if (error) RUN_DPRINTF(sc, RUN_DEBUG_USB, "WME update failed\n"); return (error); } static void run_key_set_cb(void *arg) { struct run_cmdq *cmdq = arg; struct ieee80211vap *vap = cmdq->arg1; struct ieee80211_key *k = cmdq->k; struct ieee80211com *ic = vap->iv_ic; struct run_softc *sc = ic->ic_softc; struct ieee80211_node *ni; u_int cipher = k->wk_cipher->ic_cipher; uint32_t attr; uint16_t base, associd; uint8_t mode, wcid, iv[8]; RUN_LOCK_ASSERT(sc, MA_OWNED); if (vap->iv_opmode == IEEE80211_M_HOSTAP) ni = ieee80211_find_vap_node(&ic->ic_sta, vap, cmdq->mac); else ni = vap->iv_bss; associd = (ni != NULL) ? ni->ni_associd : 0; /* map net80211 cipher to RT2860 security mode */ switch (cipher) { case IEEE80211_CIPHER_WEP: if(k->wk_keylen < 8) mode = RT2860_MODE_WEP40; else mode = RT2860_MODE_WEP104; break; case IEEE80211_CIPHER_TKIP: mode = RT2860_MODE_TKIP; break; case IEEE80211_CIPHER_AES_CCM: mode = RT2860_MODE_AES_CCMP; break; default: RUN_DPRINTF(sc, RUN_DEBUG_KEY, "undefined case\n"); return; } RUN_DPRINTF(sc, RUN_DEBUG_KEY, "associd=%x, keyix=%d, mode=%x, type=%s, tx=%s, rx=%s\n", associd, k->wk_keyix, mode, (k->wk_flags & IEEE80211_KEY_GROUP) ? "group" : "pairwise", (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off", (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off"); if (k->wk_flags & IEEE80211_KEY_GROUP) { wcid = 0; /* NB: update WCID0 for group keys */ base = RT2860_SKEY(RUN_VAP(vap)->rvp_id, k->wk_keyix); } else { wcid = (vap->iv_opmode == IEEE80211_M_STA) ? 1 : RUN_AID2WCID(associd); base = RT2860_PKEY(wcid); } if (cipher == IEEE80211_CIPHER_TKIP) { if(run_write_region_1(sc, base, k->wk_key, 16)) return; if(run_write_region_1(sc, base + 16, &k->wk_key[16], 8)) /* wk_txmic */ return; if(run_write_region_1(sc, base + 24, &k->wk_key[24], 8)) /* wk_rxmic */ return; } else { /* roundup len to 16-bit: XXX fix write_region_1() instead */ if(run_write_region_1(sc, base, k->wk_key, (k->wk_keylen + 1) & ~1)) return; } if (!(k->wk_flags & IEEE80211_KEY_GROUP) || (k->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV))) { /* set initial packet number in IV+EIV */ if (cipher == IEEE80211_CIPHER_WEP) { memset(iv, 0, sizeof iv); iv[3] = vap->iv_def_txkey << 6; } else { if (cipher == IEEE80211_CIPHER_TKIP) { iv[0] = k->wk_keytsc >> 8; iv[1] = (iv[0] | 0x20) & 0x7f; iv[2] = k->wk_keytsc; } else /* CCMP */ { iv[0] = k->wk_keytsc; iv[1] = k->wk_keytsc >> 8; iv[2] = 0; } iv[3] = k->wk_keyix << 6 | IEEE80211_WEP_EXTIV; iv[4] = k->wk_keytsc >> 16; iv[5] = k->wk_keytsc >> 24; iv[6] = k->wk_keytsc >> 32; iv[7] = k->wk_keytsc >> 40; } if (run_write_region_1(sc, RT2860_IVEIV(wcid), iv, 8)) return; } if (k->wk_flags & IEEE80211_KEY_GROUP) { /* install group key */ if (run_read(sc, RT2860_SKEY_MODE_0_7, &attr)) return; attr &= ~(0xf << (k->wk_keyix * 4)); attr |= mode << (k->wk_keyix * 4); if (run_write(sc, RT2860_SKEY_MODE_0_7, attr)) return; } else { /* install pairwise key */ if (run_read(sc, RT2860_WCID_ATTR(wcid), &attr)) return; attr = (attr & ~0xf) | (mode << 1) | RT2860_RX_PKEY_EN; if (run_write(sc, RT2860_WCID_ATTR(wcid), attr)) return; } /* TODO create a pass-thru key entry? */ /* need wcid to delete the right key later */ k->wk_pad = wcid; } /* * Don't have to be deferred, but in order to keep order of * execution, i.e. with run_key_delete(), defer this and let * run_cmdq_cb() maintain the order. * * return 0 on error */ static int run_key_set(struct ieee80211vap *vap, struct ieee80211_key *k) { struct ieee80211com *ic = vap->iv_ic; struct run_softc *sc = ic->ic_softc; uint32_t i; i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_KEY, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_key_set_cb; sc->cmdq[i].arg0 = NULL; sc->cmdq[i].arg1 = vap; sc->cmdq[i].k = k; IEEE80211_ADDR_COPY(sc->cmdq[i].mac, k->wk_macaddr); ieee80211_runtask(ic, &sc->cmdq_task); /* * To make sure key will be set when hostapd * calls iv_key_set() before if_init(). */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) { RUN_LOCK(sc); sc->cmdq_key_set = RUN_CMDQ_GO; RUN_UNLOCK(sc); } return (1); } /* * If wlan is destroyed without being brought down i.e. without * wlan down or wpa_cli terminate, this function is called after * vap is gone. Don't refer it. */ static void run_key_delete_cb(void *arg) { struct run_cmdq *cmdq = arg; struct run_softc *sc = cmdq->arg1; struct ieee80211_key *k = &cmdq->key; uint32_t attr; uint8_t wcid; RUN_LOCK_ASSERT(sc, MA_OWNED); if (k->wk_flags & IEEE80211_KEY_GROUP) { /* remove group key */ RUN_DPRINTF(sc, RUN_DEBUG_KEY, "removing group key\n"); run_read(sc, RT2860_SKEY_MODE_0_7, &attr); attr &= ~(0xf << (k->wk_keyix * 4)); run_write(sc, RT2860_SKEY_MODE_0_7, attr); } else { /* remove pairwise key */ RUN_DPRINTF(sc, RUN_DEBUG_KEY, "removing key for wcid %x\n", k->wk_pad); /* matching wcid was written to wk_pad in run_key_set() */ wcid = k->wk_pad; run_read(sc, RT2860_WCID_ATTR(wcid), &attr); attr &= ~0xf; run_write(sc, RT2860_WCID_ATTR(wcid), attr); run_set_region_4(sc, RT2860_WCID_ENTRY(wcid), 0, 8); } k->wk_pad = 0; } /* * return 0 on error */ static int run_key_delete(struct ieee80211vap *vap, struct ieee80211_key *k) { struct ieee80211com *ic = vap->iv_ic; struct run_softc *sc = ic->ic_softc; struct ieee80211_key *k0; uint32_t i; /* * When called back, key might be gone. So, make a copy * of some values need to delete keys before deferring. * But, because of LOR with node lock, cannot use lock here. * So, use atomic instead. */ i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_KEY, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_key_delete_cb; sc->cmdq[i].arg0 = NULL; sc->cmdq[i].arg1 = sc; k0 = &sc->cmdq[i].key; k0->wk_flags = k->wk_flags; k0->wk_keyix = k->wk_keyix; /* matching wcid was written to wk_pad in run_key_set() */ k0->wk_pad = k->wk_pad; ieee80211_runtask(ic, &sc->cmdq_task); return (1); /* return fake success */ } static void run_ratectl_to(void *arg) { struct run_softc *sc = arg; /* do it in a process context, so it can go sleep */ ieee80211_runtask(&sc->sc_ic, &sc->ratectl_task); /* next timeout will be rescheduled in the callback task */ } /* ARGSUSED */ static void run_ratectl_cb(void *arg, int pending) { struct run_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); if (vap == NULL) return; if (sc->rvp_cnt > 1 || vap->iv_opmode != IEEE80211_M_STA) { /* * run_reset_livelock() doesn't do anything with AMRR, * but Ralink wants us to call it every 1 sec. So, we * piggyback here rather than creating another callout. * Livelock may occur only in HOSTAP or IBSS mode * (when h/w is sending beacons). */ RUN_LOCK(sc); run_reset_livelock(sc); /* just in case, there are some stats to drain */ run_drain_fifo(sc); RUN_UNLOCK(sc); } ieee80211_iterate_nodes(&ic->ic_sta, run_iter_func, sc); RUN_LOCK(sc); if(sc->ratectl_run != RUN_RATECTL_OFF) usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc); RUN_UNLOCK(sc); } static void run_drain_fifo(void *arg) { struct run_softc *sc = arg; uint32_t stat; uint16_t (*wstat)[3]; uint8_t wcid, mcs, pid; int8_t retry; RUN_LOCK_ASSERT(sc, MA_OWNED); for (;;) { /* drain Tx status FIFO (maxsize = 16) */ run_read(sc, RT2860_TX_STAT_FIFO, &stat); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "tx stat 0x%08x\n", stat); if (!(stat & RT2860_TXQ_VLD)) break; wcid = (stat >> RT2860_TXQ_WCID_SHIFT) & 0xff; /* if no ACK was requested, no feedback is available */ if (!(stat & RT2860_TXQ_ACKREQ) || wcid > RT2870_WCID_MAX || wcid == 0) continue; /* * Even though each stat is Tx-complete-status like format, * the device can poll stats. Because there is no guarantee * that the referring node is still around when read the stats. * So that, if we use ieee80211_ratectl_tx_update(), we will * have hard time not to refer already freed node. * * To eliminate such page faults, we poll stats in softc. * Then, update the rates later with ieee80211_ratectl_tx_update(). */ wstat = &(sc->wcid_stats[wcid]); (*wstat)[RUN_TXCNT]++; if (stat & RT2860_TXQ_OK) (*wstat)[RUN_SUCCESS]++; else counter_u64_add(sc->sc_ic.ic_oerrors, 1); /* * Check if there were retries, ie if the Tx success rate is * different from the requested rate. Note that it works only * because we do not allow rate fallback from OFDM to CCK. */ mcs = (stat >> RT2860_TXQ_MCS_SHIFT) & 0x7f; pid = (stat >> RT2860_TXQ_PID_SHIFT) & 0xf; if ((retry = pid -1 - mcs) > 0) { (*wstat)[RUN_TXCNT] += retry; (*wstat)[RUN_RETRY] += retry; } } RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "count=%d\n", sc->fifo_cnt); sc->fifo_cnt = 0; } static void run_iter_func(void *arg, struct ieee80211_node *ni) { struct run_softc *sc = arg; struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs; struct ieee80211vap *vap = ni->ni_vap; struct run_node *rn = RUN_NODE(ni); union run_stats sta[2]; uint16_t (*wstat)[3]; int error; RUN_LOCK(sc); /* Check for special case */ if (sc->rvp_cnt <= 1 && vap->iv_opmode == IEEE80211_M_STA && ni != vap->iv_bss) goto fail; txs->flags = IEEE80211_RATECTL_TX_STATS_NODE | IEEE80211_RATECTL_TX_STATS_RETRIES; txs->ni = ni; if (sc->rvp_cnt <= 1 && (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_STA)) { /* read statistic counters (clear on read) and update AMRR state */ error = run_read_region_1(sc, RT2860_TX_STA_CNT0, (uint8_t *)sta, sizeof sta); if (error != 0) goto fail; /* count failed TX as errors */ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, le16toh(sta[0].error.fail)); txs->nretries = le16toh(sta[1].tx.retry); txs->nsuccess = le16toh(sta[1].tx.success); /* nretries??? */ txs->nframes = txs->nretries + txs->nsuccess + le16toh(sta[0].error.fail); RUN_DPRINTF(sc, RUN_DEBUG_RATE, "retrycnt=%d success=%d failcnt=%d\n", txs->nretries, txs->nsuccess, le16toh(sta[0].error.fail)); } else { wstat = &(sc->wcid_stats[RUN_AID2WCID(ni->ni_associd)]); if (wstat == &(sc->wcid_stats[0]) || wstat > &(sc->wcid_stats[RT2870_WCID_MAX])) goto fail; txs->nretries = (*wstat)[RUN_RETRY]; txs->nsuccess = (*wstat)[RUN_SUCCESS]; txs->nframes = (*wstat)[RUN_TXCNT]; RUN_DPRINTF(sc, RUN_DEBUG_RATE, "retrycnt=%d txcnt=%d success=%d\n", txs->nretries, txs->nframes, txs->nsuccess); memset(wstat, 0, sizeof(*wstat)); } ieee80211_ratectl_tx_update(vap, txs); rn->amrr_ridx = ieee80211_ratectl_rate(ni, NULL, 0); fail: RUN_UNLOCK(sc); RUN_DPRINTF(sc, RUN_DEBUG_RATE, "ridx=%d\n", rn->amrr_ridx); } static void run_newassoc_cb(void *arg) { struct run_cmdq *cmdq = arg; struct ieee80211_node *ni = cmdq->arg1; struct run_softc *sc = ni->ni_vap->iv_ic->ic_softc; uint8_t wcid = cmdq->wcid; RUN_LOCK_ASSERT(sc, MA_OWNED); run_write_region_1(sc, RT2860_WCID_ENTRY(wcid), ni->ni_macaddr, IEEE80211_ADDR_LEN); memset(&(sc->wcid_stats[wcid]), 0, sizeof(sc->wcid_stats[wcid])); } static void run_newassoc(struct ieee80211_node *ni, int isnew) { struct run_node *rn = RUN_NODE(ni); struct ieee80211_rateset *rs = &ni->ni_rates; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = vap->iv_ic; struct run_softc *sc = ic->ic_softc; uint8_t rate; uint8_t ridx; uint8_t wcid; int i, j; wcid = (vap->iv_opmode == IEEE80211_M_STA) ? 1 : RUN_AID2WCID(ni->ni_associd); if (wcid > RT2870_WCID_MAX) { device_printf(sc->sc_dev, "wcid=%d out of range\n", wcid); return; } /* only interested in true associations */ if (isnew && ni->ni_associd != 0) { /* * This function could is called though timeout function. * Need to defer. */ uint32_t cnt = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_STATE, "cmdq_store=%d\n", cnt); sc->cmdq[cnt].func = run_newassoc_cb; sc->cmdq[cnt].arg0 = NULL; sc->cmdq[cnt].arg1 = ni; sc->cmdq[cnt].wcid = wcid; ieee80211_runtask(ic, &sc->cmdq_task); } RUN_DPRINTF(sc, RUN_DEBUG_STATE, "new assoc isnew=%d associd=%x addr=%s\n", isnew, ni->ni_associd, ether_sprintf(ni->ni_macaddr)); for (i = 0; i < rs->rs_nrates; i++) { rate = rs->rs_rates[i] & IEEE80211_RATE_VAL; /* convert 802.11 rate to hardware rate index */ for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) if (rt2860_rates[ridx].rate == rate) break; rn->ridx[i] = ridx; /* determine rate of control response frames */ for (j = i; j >= 0; j--) { if ((rs->rs_rates[j] & IEEE80211_RATE_BASIC) && rt2860_rates[rn->ridx[i]].phy == rt2860_rates[rn->ridx[j]].phy) break; } if (j >= 0) { rn->ctl_ridx[i] = rn->ridx[j]; } else { /* no basic rate found, use mandatory one */ rn->ctl_ridx[i] = rt2860_rates[ridx].ctl_ridx; } RUN_DPRINTF(sc, RUN_DEBUG_STATE | RUN_DEBUG_RATE, "rate=0x%02x ridx=%d ctl_ridx=%d\n", rs->rs_rates[i], rn->ridx[i], rn->ctl_ridx[i]); } rate = vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)].mgmtrate; for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) if (rt2860_rates[ridx].rate == rate) break; rn->mgt_ridx = ridx; RUN_DPRINTF(sc, RUN_DEBUG_STATE | RUN_DEBUG_RATE, "rate=%d, mgmt_ridx=%d\n", rate, rn->mgt_ridx); RUN_LOCK(sc); if(sc->ratectl_run != RUN_RATECTL_OFF) usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc); RUN_UNLOCK(sc); } /* * Return the Rx chain with the highest RSSI for a given frame. */ static __inline uint8_t run_maxrssi_chain(struct run_softc *sc, const struct rt2860_rxwi *rxwi) { uint8_t rxchain = 0; if (sc->nrxchains > 1) { if (rxwi->rssi[1] > rxwi->rssi[rxchain]) rxchain = 1; if (sc->nrxchains > 2) if (rxwi->rssi[2] > rxwi->rssi[rxchain]) rxchain = 2; } return (rxchain); } static void run_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct run_softc *sc = vap->iv_ic->ic_softc; struct run_vap *rvp = RUN_VAP(vap); uint64_t ni_tstamp, rx_tstamp; rvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf); if (vap->iv_state == IEEE80211_S_RUN && (subtype == IEEE80211_FC0_SUBTYPE_BEACON || subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) { ni_tstamp = le64toh(ni->ni_tstamp.tsf); RUN_LOCK(sc); run_get_tsf(sc, &rx_tstamp); RUN_UNLOCK(sc); rx_tstamp = le64toh(rx_tstamp); if (ni_tstamp >= rx_tstamp) { RUN_DPRINTF(sc, RUN_DEBUG_RECV | RUN_DEBUG_BEACON, "ibss merge, tsf %ju tstamp %ju\n", (uintmax_t)rx_tstamp, (uintmax_t)ni_tstamp); (void) ieee80211_ibss_merge(ni); } } } static void run_rx_frame(struct run_softc *sc, struct mbuf *m, uint32_t dmalen) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct epoch_tracker et; struct rt2870_rxd *rxd; struct rt2860_rxwi *rxwi; uint32_t flags; uint16_t len, rxwisize; uint8_t ant, rssi; int8_t nf; rxwisize = sizeof(struct rt2860_rxwi); if (sc->mac_ver == 0x5592) rxwisize += sizeof(uint64_t); else if (sc->mac_ver == 0x3593) rxwisize += sizeof(uint32_t); if (__predict_false(dmalen < rxwisize + sizeof(struct ieee80211_frame_ack))) { RUN_DPRINTF(sc, RUN_DEBUG_RECV, "payload is too short: dma length %u < %zu\n", dmalen, rxwisize + sizeof(struct ieee80211_frame_ack)); goto fail; } rxwi = mtod(m, struct rt2860_rxwi *); len = le16toh(rxwi->len) & 0xfff; if (__predict_false(len > dmalen - rxwisize)) { RUN_DPRINTF(sc, RUN_DEBUG_RECV, "bad RXWI length %u > %u\n", len, dmalen); goto fail; } /* Rx descriptor is located at the end */ rxd = (struct rt2870_rxd *)(mtod(m, caddr_t) + dmalen); flags = le32toh(rxd->flags); if (__predict_false(flags & (RT2860_RX_CRCERR | RT2860_RX_ICVERR))) { RUN_DPRINTF(sc, RUN_DEBUG_RECV, "%s error.\n", (flags & RT2860_RX_CRCERR)?"CRC":"ICV"); goto fail; } if (flags & RT2860_RX_L2PAD) { RUN_DPRINTF(sc, RUN_DEBUG_RECV, "received RT2860_RX_L2PAD frame\n"); len += 2; } m->m_data += rxwisize; m->m_pkthdr.len = m->m_len = len; wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) != 0 && (flags & RT2860_RX_DEC) != 0) { wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; m->m_flags |= M_WEP; } if (len >= sizeof(struct ieee80211_frame_min)) { ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); } else ni = NULL; if (__predict_false(flags & RT2860_RX_MICERR)) { /* report MIC failures to net80211 for TKIP */ if (ni != NULL) ieee80211_notify_michael_failure(ni->ni_vap, wh, rxwi->keyidx); RUN_DPRINTF(sc, RUN_DEBUG_RECV, "MIC error. Someone is lying.\n"); goto fail; } ant = run_maxrssi_chain(sc, rxwi); rssi = rxwi->rssi[ant]; nf = run_rssi2dbm(sc, rssi, ant); if (__predict_false(ieee80211_radiotap_active(ic))) { struct run_rx_radiotap_header *tap = &sc->sc_rxtap; uint16_t phy; tap->wr_flags = 0; if (flags & RT2860_RX_L2PAD) tap->wr_flags |= IEEE80211_RADIOTAP_F_DATAPAD; tap->wr_antsignal = rssi; tap->wr_antenna = ant; tap->wr_dbm_antsignal = run_rssi2dbm(sc, rssi, ant); tap->wr_rate = 2; /* in case it can't be found below */ RUN_LOCK(sc); run_get_tsf(sc, &tap->wr_tsf); RUN_UNLOCK(sc); phy = le16toh(rxwi->phy); switch (phy & RT2860_PHY_MODE) { case RT2860_PHY_CCK: switch ((phy & RT2860_PHY_MCS) & ~RT2860_PHY_SHPRE) { case 0: tap->wr_rate = 2; break; case 1: tap->wr_rate = 4; break; case 2: tap->wr_rate = 11; break; case 3: tap->wr_rate = 22; break; } if (phy & RT2860_PHY_SHPRE) tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; break; case RT2860_PHY_OFDM: switch (phy & RT2860_PHY_MCS) { case 0: tap->wr_rate = 12; break; case 1: tap->wr_rate = 18; break; case 2: tap->wr_rate = 24; break; case 3: tap->wr_rate = 36; break; case 4: tap->wr_rate = 48; break; case 5: tap->wr_rate = 72; break; case 6: tap->wr_rate = 96; break; case 7: tap->wr_rate = 108; break; } break; } } NET_EPOCH_ENTER(et); if (ni != NULL) { (void)ieee80211_input(ni, m, rssi, nf); ieee80211_free_node(ni); } else { (void)ieee80211_input_all(ic, m, rssi, nf); } NET_EPOCH_EXIT(et); return; fail: m_freem(m); counter_u64_add(ic->ic_ierrors, 1); } static void run_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct run_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct mbuf *m = NULL; struct mbuf *m0; uint32_t dmalen, mbuf_len; uint16_t rxwisize; int xferlen; rxwisize = sizeof(struct rt2860_rxwi); if (sc->mac_ver == 0x5592) rxwisize += sizeof(uint64_t); else if (sc->mac_ver == 0x3593) rxwisize += sizeof(uint32_t); usbd_xfer_status(xfer, &xferlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: RUN_DPRINTF(sc, RUN_DEBUG_RECV, "rx done, actlen=%d\n", xferlen); if (xferlen < (int)(sizeof(uint32_t) + rxwisize + sizeof(struct rt2870_rxd))) { RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB, "xfer too short %d\n", xferlen); goto tr_setup; } m = sc->rx_m; sc->rx_m = NULL; /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: if (sc->rx_m == NULL) { sc->rx_m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE /* xfer can be bigger than MCLBYTES */); } if (sc->rx_m == NULL) { RUN_DPRINTF(sc, RUN_DEBUG_RECV | RUN_DEBUG_RECV_DESC, "could not allocate mbuf - idle with stall\n"); counter_u64_add(ic->ic_ierrors, 1); usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); } else { /* * Directly loading a mbuf cluster into DMA to * save some data copying. This works because * there is only one cluster. */ usbd_xfer_set_frame_data(xfer, 0, mtod(sc->rx_m, caddr_t), RUN_MAX_RXSZ); usbd_xfer_set_frames(xfer, 1); } usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); if (error == USB_ERR_TIMEOUT) device_printf(sc->sc_dev, "device timeout\n"); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } if (sc->rx_m != NULL) { m_freem(sc->rx_m); sc->rx_m = NULL; } break; } if (m == NULL) return; /* inputting all the frames must be last */ RUN_UNLOCK(sc); m->m_pkthdr.len = m->m_len = xferlen; /* HW can aggregate multiple 802.11 frames in a single USB xfer */ for(;;) { dmalen = le32toh(*mtod(m, uint32_t *)) & 0xffff; if ((dmalen >= (uint32_t)-8) || (dmalen == 0) || ((dmalen & 3) != 0)) { RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB, "bad DMA length %u\n", dmalen); break; } if ((dmalen + 8) > (uint32_t)xferlen) { RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB, "bad DMA length %u > %d\n", dmalen + 8, xferlen); break; } /* If it is the last one or a single frame, we won't copy. */ if ((xferlen -= dmalen + 8) <= 8) { /* trim 32-bit DMA-len header */ m->m_data += 4; m->m_pkthdr.len = m->m_len -= 4; run_rx_frame(sc, m, dmalen); m = NULL; /* don't free source buffer */ break; } mbuf_len = dmalen + sizeof(struct rt2870_rxd); if (__predict_false(mbuf_len > MCLBYTES)) { RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB, "payload is too big: mbuf_len %u\n", mbuf_len); counter_u64_add(ic->ic_ierrors, 1); break; } /* copy aggregated frames to another mbuf */ m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m0 == NULL)) { RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC, "could not allocate mbuf\n"); counter_u64_add(ic->ic_ierrors, 1); break; } m_copydata(m, 4 /* skip 32-bit DMA-len header */, mbuf_len, mtod(m0, caddr_t)); m0->m_pkthdr.len = m0->m_len = mbuf_len; run_rx_frame(sc, m0, dmalen); /* update data ptr */ m->m_data += mbuf_len + 4; m->m_pkthdr.len = m->m_len -= mbuf_len + 4; } /* make sure we free the source buffer, if any */ m_freem(m); RUN_LOCK(sc); } static void run_tx_free(struct run_endpoint_queue *pq, struct run_tx_data *data, int txerr) { ieee80211_tx_complete(data->ni, data->m, txerr); data->m = NULL; data->ni = NULL; STAILQ_INSERT_TAIL(&pq->tx_fh, data, next); pq->tx_nfree++; } static void run_bulk_tx_callbackN(struct usb_xfer *xfer, usb_error_t error, u_int index) { struct run_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct run_tx_data *data; struct ieee80211vap *vap = NULL; struct usb_page_cache *pc; struct run_endpoint_queue *pq = &sc->sc_epq[index]; struct mbuf *m; usb_frlength_t size; int actlen; int sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB, "transfer complete: %d bytes @ index %d\n", actlen, index); data = usbd_xfer_get_priv(xfer); run_tx_free(pq, data, 0); usbd_xfer_set_priv(xfer, NULL); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&pq->tx_qh); if (data == NULL) break; STAILQ_REMOVE_HEAD(&pq->tx_qh, next); m = data->m; size = (sc->mac_ver == 0x5592) ? sizeof(data->desc) + sizeof(uint32_t) : sizeof(data->desc); if ((m->m_pkthdr.len + size + 3 + 8) > RUN_MAX_TXSZ) { RUN_DPRINTF(sc, RUN_DEBUG_XMIT_DESC | RUN_DEBUG_USB, "data overflow, %u bytes\n", m->m_pkthdr.len); run_tx_free(pq, data, 1); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &data->desc, size); usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len); size += m->m_pkthdr.len; /* * Align end on a 4-byte boundary, pad 8 bytes (CRC + * 4-byte padding), and be sure to zero those trailing * bytes: */ usbd_frame_zero(pc, size, ((-size) & 3) + 8); size += ((-size) & 3) + 8; vap = data->ni->ni_vap; if (ieee80211_radiotap_active_vap(vap)) { const struct ieee80211_frame *wh; struct run_tx_radiotap_header *tap = &sc->sc_txtap; struct rt2860_txwi *txwi = (struct rt2860_txwi *)(&data->desc + sizeof(struct rt2870_txd)); int has_l2pad; wh = mtod(m, struct ieee80211_frame *); has_l2pad = IEEE80211_HAS_ADDR4(wh) != IEEE80211_QOS_HAS_SEQ(wh); tap->wt_flags = 0; tap->wt_rate = rt2860_rates[data->ridx].rate; tap->wt_hwqueue = index; if (le16toh(txwi->phy) & RT2860_PHY_SHPRE) tap->wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; if (has_l2pad) tap->wt_flags |= IEEE80211_RADIOTAP_F_DATAPAD; ieee80211_radiotap_tx(vap, m); } RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB, "sending frame len=%u/%u @ index %d\n", m->m_pkthdr.len, size, index); usbd_xfer_set_frame_len(xfer, 0, size); usbd_xfer_set_priv(xfer, data); usbd_transfer_submit(xfer); run_start(sc); break; default: RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB, "USB transfer error, %s\n", usbd_errstr(error)); data = usbd_xfer_get_priv(xfer); if (data != NULL) { if(data->ni != NULL) vap = data->ni->ni_vap; run_tx_free(pq, data, error); usbd_xfer_set_priv(xfer, NULL); } if (vap == NULL) vap = TAILQ_FIRST(&ic->ic_vaps); if (error != USB_ERR_CANCELLED) { if (error == USB_ERR_TIMEOUT) { device_printf(sc->sc_dev, "device timeout\n"); uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_usb_timeout_cb; sc->cmdq[i].arg0 = vap; ieee80211_runtask(ic, &sc->cmdq_task); } /* * Try to clear stall first, also if other * errors occur, hence clearing stall * introduces a 50 ms delay: */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void run_bulk_tx_callback0(struct usb_xfer *xfer, usb_error_t error) { run_bulk_tx_callbackN(xfer, error, 0); } static void run_bulk_tx_callback1(struct usb_xfer *xfer, usb_error_t error) { run_bulk_tx_callbackN(xfer, error, 1); } static void run_bulk_tx_callback2(struct usb_xfer *xfer, usb_error_t error) { run_bulk_tx_callbackN(xfer, error, 2); } static void run_bulk_tx_callback3(struct usb_xfer *xfer, usb_error_t error) { run_bulk_tx_callbackN(xfer, error, 3); } static void run_bulk_tx_callback4(struct usb_xfer *xfer, usb_error_t error) { run_bulk_tx_callbackN(xfer, error, 4); } static void run_bulk_tx_callback5(struct usb_xfer *xfer, usb_error_t error) { run_bulk_tx_callbackN(xfer, error, 5); } static void run_set_tx_desc(struct run_softc *sc, struct run_tx_data *data) { struct mbuf *m = data->m; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = data->ni->ni_vap; struct ieee80211_frame *wh; struct rt2870_txd *txd; struct rt2860_txwi *txwi; uint16_t xferlen, txwisize; uint16_t mcs; uint8_t ridx = data->ridx; uint8_t pad; /* get MCS code from rate index */ mcs = rt2860_rates[ridx].mcs; txwisize = (sc->mac_ver == 0x5592) ? sizeof(*txwi) + sizeof(uint32_t) : sizeof(*txwi); xferlen = txwisize + m->m_pkthdr.len; /* roundup to 32-bit alignment */ xferlen = (xferlen + 3) & ~3; txd = (struct rt2870_txd *)&data->desc; txd->len = htole16(xferlen); wh = mtod(m, struct ieee80211_frame *); /* * Ether both are true or both are false, the header * are nicely aligned to 32-bit. So, no L2 padding. */ if(IEEE80211_HAS_ADDR4(wh) == IEEE80211_QOS_HAS_SEQ(wh)) pad = 0; else pad = 2; /* setup TX Wireless Information */ txwi = (struct rt2860_txwi *)(txd + 1); txwi->len = htole16(m->m_pkthdr.len - pad); if (rt2860_rates[ridx].phy == IEEE80211_T_DS) { mcs |= RT2860_PHY_CCK; if (ridx != RT2860_RIDX_CCK1 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) mcs |= RT2860_PHY_SHPRE; } else mcs |= RT2860_PHY_OFDM; txwi->phy = htole16(mcs); /* check if RTS/CTS or CTS-to-self protection is required */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1) && (m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold || ((ic->ic_flags & IEEE80211_F_USEPROT) && rt2860_rates[ridx].phy == IEEE80211_T_OFDM))) txwi->txop |= RT2860_TX_TXOP_HT; else txwi->txop |= RT2860_TX_TXOP_BACKOFF; if (vap->iv_opmode != IEEE80211_M_STA && !IEEE80211_QOS_HAS_SEQ(wh)) txwi->xflags |= RT2860_TX_NSEQ; } /* This function must be called locked */ static int run_tx(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; const struct ieee80211_txparam *tp = ni->ni_txparms; struct run_node *rn = RUN_NODE(ni); struct run_tx_data *data; struct rt2870_txd *txd; struct rt2860_txwi *txwi; uint16_t qos; uint16_t dur; uint16_t qid; uint8_t type; uint8_t tid; uint8_t ridx; uint8_t ctl_ridx; uint8_t qflags; uint8_t xflags = 0; int hasqos; RUN_LOCK_ASSERT(sc, MA_OWNED); wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; /* * There are 7 bulk endpoints: 1 for RX * and 6 for TX (4 EDCAs + HCCA + Prio). * Update 03-14-2009: some devices like the Planex GW-US300MiniS * seem to have only 4 TX bulk endpoints (Fukaumi Naoki). */ if ((hasqos = IEEE80211_QOS_HAS_SEQ(wh))) { uint8_t *frm; frm = ieee80211_getqos(wh); qos = le16toh(*(const uint16_t *)frm); tid = qos & IEEE80211_QOS_TID; qid = TID_TO_WME_AC(tid); } else { qos = 0; tid = 0; qid = WME_AC_BE; } qflags = (qid < 4) ? RT2860_TX_QSEL_EDCA : RT2860_TX_QSEL_HCCA; RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "qos %d\tqid %d\ttid %d\tqflags %x\n", qos, qid, tid, qflags); /* pickup a rate index */ if (IEEE80211_IS_MULTICAST(wh->i_addr1) || type != IEEE80211_FC0_TYPE_DATA || m->m_flags & M_EAPOL) { ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ? RT2860_RIDX_OFDM6 : RT2860_RIDX_CCK1; ctl_ridx = rt2860_rates[ridx].ctl_ridx; } else { if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) ridx = rn->fix_ridx; else ridx = rn->amrr_ridx; ctl_ridx = rt2860_rates[ridx].ctl_ridx; } if (!IEEE80211_IS_MULTICAST(wh->i_addr1) && (!hasqos || (qos & IEEE80211_QOS_ACKPOLICY) != IEEE80211_QOS_ACKPOLICY_NOACK)) { xflags |= RT2860_TX_ACK; if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) dur = rt2860_rates[ctl_ridx].sp_ack_dur; else dur = rt2860_rates[ctl_ridx].lp_ack_dur; USETW(wh->i_dur, dur); } /* reserve slots for mgmt packets, just in case */ if (sc->sc_epq[qid].tx_nfree < 3) { RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "tx ring %d is full\n", qid); return (-1); } data = STAILQ_FIRST(&sc->sc_epq[qid].tx_fh); STAILQ_REMOVE_HEAD(&sc->sc_epq[qid].tx_fh, next); sc->sc_epq[qid].tx_nfree--; txd = (struct rt2870_txd *)&data->desc; txd->flags = qflags; txwi = (struct rt2860_txwi *)(txd + 1); txwi->xflags = xflags; if (IEEE80211_IS_MULTICAST(wh->i_addr1)) txwi->wcid = 0; else txwi->wcid = (vap->iv_opmode == IEEE80211_M_STA) ? 1 : RUN_AID2WCID(ni->ni_associd); /* clear leftover garbage bits */ txwi->flags = 0; txwi->txop = 0; data->m = m; data->ni = ni; data->ridx = ridx; run_set_tx_desc(sc, data); /* * The chip keeps track of 2 kind of Tx stats, * * TX_STAT_FIFO, for per WCID stats, and * * TX_STA_CNT0 for all-TX-in-one stats. * * To use FIFO stats, we need to store MCS into the driver-private * PacketID field. So that, we can tell whose stats when we read them. * We add 1 to the MCS because setting the PacketID field to 0 means * that we don't want feedback in TX_STAT_FIFO. * And, that's what we want for STA mode, since TX_STA_CNT0 does the job. * * FIFO stats doesn't count Tx with WCID 0xff, so we do this in run_tx(). */ if (sc->rvp_cnt > 1 || vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { uint16_t pid = (rt2860_rates[ridx].mcs + 1) & 0xf; txwi->len |= htole16(pid << RT2860_TX_PID_SHIFT); /* * Unlike PCI based devices, we don't get any interrupt from * USB devices, so we simulate FIFO-is-full interrupt here. * Ralink recommends to drain FIFO stats every 100 ms, but 16 slots * quickly get fulled. To prevent overflow, increment a counter on * every FIFO stat request, so we know how many slots are left. * We do this only in HOSTAP or multiple vap mode since FIFO stats * are used only in those modes. * We just drain stats. AMRR gets updated every 1 sec by * run_ratectl_cb() via callout. * Call it early. Otherwise overflow. */ if (sc->fifo_cnt++ == 10) { /* * With multiple vaps or if_bridge, if_start() is called * with a non-sleepable lock, tcpinp. So, need to defer. */ uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_drain_fifo; sc->cmdq[i].arg0 = sc; ieee80211_runtask(ic, &sc->cmdq_task); } } STAILQ_INSERT_TAIL(&sc->sc_epq[qid].tx_qh, data, next); usbd_transfer_start(sc->sc_xfer[qid]); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending data frame len=%d rate=%d qid=%d\n", m->m_pkthdr.len + (int)(sizeof(struct rt2870_txd) + sizeof(struct rt2860_txwi)), rt2860_rates[ridx].rate, qid); return (0); } static int run_tx_mgt(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct run_node *rn = RUN_NODE(ni); struct run_tx_data *data; struct ieee80211_frame *wh; struct rt2870_txd *txd; struct rt2860_txwi *txwi; uint16_t dur; uint8_t ridx = rn->mgt_ridx; uint8_t xflags = 0; uint8_t wflags = 0; RUN_LOCK_ASSERT(sc, MA_OWNED); wh = mtod(m, struct ieee80211_frame *); /* tell hardware to add timestamp for probe responses */ if ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP)) wflags |= RT2860_TX_TS; else if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { xflags |= RT2860_TX_ACK; dur = ieee80211_ack_duration(ic->ic_rt, rt2860_rates[ridx].rate, ic->ic_flags & IEEE80211_F_SHPREAMBLE); USETW(wh->i_dur, dur); } if (sc->sc_epq[0].tx_nfree == 0) /* let caller free mbuf */ return (EIO); data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh); STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next); sc->sc_epq[0].tx_nfree--; txd = (struct rt2870_txd *)&data->desc; txd->flags = RT2860_TX_QSEL_EDCA; txwi = (struct rt2860_txwi *)(txd + 1); txwi->wcid = 0xff; txwi->flags = wflags; txwi->xflags = xflags; txwi->txop = 0; /* clear leftover garbage bits */ data->m = m; data->ni = ni; data->ridx = ridx; run_set_tx_desc(sc, data); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending mgt frame len=%d rate=%d\n", m->m_pkthdr.len + (int)(sizeof(struct rt2870_txd) + sizeof(struct rt2860_txwi)), rt2860_rates[ridx].rate); STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next); usbd_transfer_start(sc->sc_xfer[0]); return (0); } static int run_sendprot(struct run_softc *sc, const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) { struct ieee80211com *ic = ni->ni_ic; struct run_tx_data *data; struct rt2870_txd *txd; struct rt2860_txwi *txwi; struct mbuf *mprot; int ridx; int protrate; uint8_t wflags = 0; uint8_t xflags = 0; RUN_LOCK_ASSERT(sc, MA_OWNED); /* check that there are free slots before allocating the mbuf */ if (sc->sc_epq[0].tx_nfree == 0) /* let caller free mbuf */ return (ENOBUFS); mprot = ieee80211_alloc_prot(ni, m, rate, prot); if (mprot == NULL) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "could not allocate mbuf\n"); return (ENOBUFS); } protrate = ieee80211_ctl_rate(ic->ic_rt, rate); wflags = RT2860_TX_FRAG; xflags = 0; if (prot == IEEE80211_PROT_RTSCTS) xflags |= RT2860_TX_ACK; data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh); STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next); sc->sc_epq[0].tx_nfree--; txd = (struct rt2870_txd *)&data->desc; txd->flags = RT2860_TX_QSEL_EDCA; txwi = (struct rt2860_txwi *)(txd + 1); txwi->wcid = 0xff; txwi->flags = wflags; txwi->xflags = xflags; txwi->txop = 0; /* clear leftover garbage bits */ data->m = mprot; data->ni = ieee80211_ref_node(ni); for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) if (rt2860_rates[ridx].rate == protrate) break; data->ridx = ridx; run_set_tx_desc(sc, data); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending prot len=%u rate=%u\n", m->m_pkthdr.len, rate); STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next); usbd_transfer_start(sc->sc_xfer[0]); return (0); } static int run_tx_param(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct run_tx_data *data; struct rt2870_txd *txd; struct rt2860_txwi *txwi; uint8_t ridx; uint8_t rate; uint8_t opflags = 0; uint8_t xflags = 0; int error; RUN_LOCK_ASSERT(sc, MA_OWNED); KASSERT(params != NULL, ("no raw xmit params")); rate = params->ibp_rate0; if (!ieee80211_isratevalid(ic->ic_rt, rate)) { /* let caller free mbuf */ return (EINVAL); } if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) xflags |= RT2860_TX_ACK; if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { error = run_sendprot(sc, m, ni, params->ibp_flags & IEEE80211_BPF_RTS ? IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, rate); if (error) { /* let caller free mbuf */ return error; } opflags |= /*XXX RT2573_TX_LONG_RETRY |*/ RT2860_TX_TXOP_SIFS; } if (sc->sc_epq[0].tx_nfree == 0) { /* let caller free mbuf */ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending raw frame, but tx ring is full\n"); return (EIO); } data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh); STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next); sc->sc_epq[0].tx_nfree--; txd = (struct rt2870_txd *)&data->desc; txd->flags = RT2860_TX_QSEL_EDCA; txwi = (struct rt2860_txwi *)(txd + 1); txwi->wcid = 0xff; txwi->xflags = xflags; txwi->txop = opflags; txwi->flags = 0; /* clear leftover garbage bits */ data->m = m; data->ni = ni; for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) if (rt2860_rates[ridx].rate == rate) break; data->ridx = ridx; run_set_tx_desc(sc, data); RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending raw frame len=%u rate=%u\n", m->m_pkthdr.len, rate); STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next); usbd_transfer_start(sc->sc_xfer[0]); return (0); } static int run_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct run_softc *sc = ni->ni_ic->ic_softc; int error = 0; RUN_LOCK(sc); /* prevent management frames from being sent if we're not ready */ if (!(sc->sc_flags & RUN_RUNNING)) { error = ENETDOWN; goto done; } if (params == NULL) { /* tx mgt packet */ if ((error = run_tx_mgt(sc, m, ni)) != 0) { RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "mgt tx failed\n"); goto done; } } else { /* tx raw packet with param */ if ((error = run_tx_param(sc, m, ni, params)) != 0) { RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "tx with param failed\n"); goto done; } } done: RUN_UNLOCK(sc); if (error != 0) { if(m != NULL) m_freem(m); } return (error); } static int run_transmit(struct ieee80211com *ic, struct mbuf *m) { struct run_softc *sc = ic->ic_softc; int error; RUN_LOCK(sc); if ((sc->sc_flags & RUN_RUNNING) == 0) { RUN_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { RUN_UNLOCK(sc); return (error); } run_start(sc); RUN_UNLOCK(sc); return (0); } static void run_start(struct run_softc *sc) { struct ieee80211_node *ni; struct mbuf *m; RUN_LOCK_ASSERT(sc, MA_OWNED); if ((sc->sc_flags & RUN_RUNNING) == 0) return; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; if (run_tx(sc, m, ni) != 0) { mbufq_prepend(&sc->sc_snd, m); break; } } } static void run_parent(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; int startall = 0; RUN_LOCK(sc); if (sc->sc_detached) { RUN_UNLOCK(sc); return; } if (ic->ic_nrunning > 0) { if (!(sc->sc_flags & RUN_RUNNING)) { startall = 1; run_init_locked(sc); } else run_update_promisc_locked(sc); } else if ((sc->sc_flags & RUN_RUNNING) && sc->rvp_cnt <= 1) run_stop(sc); RUN_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static void run_iq_calib(struct run_softc *sc, u_int chan) { uint16_t val; /* Tx0 IQ gain. */ run_bbp_write(sc, 158, 0x2c); if (chan <= 14) run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX0_2GHZ, &val, 1); else if (chan <= 64) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH36_TO_CH64_5GHZ, &val, 1); } else if (chan <= 138) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH100_TO_CH138_5GHZ, &val, 1); } else if (chan <= 165) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH140_TO_CH165_5GHZ, &val, 1); } else val = 0; run_bbp_write(sc, 159, val); /* Tx0 IQ phase. */ run_bbp_write(sc, 158, 0x2d); if (chan <= 14) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX0_2GHZ, &val, 1); } else if (chan <= 64) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH36_TO_CH64_5GHZ, &val, 1); } else if (chan <= 138) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH100_TO_CH138_5GHZ, &val, 1); } else if (chan <= 165) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH140_TO_CH165_5GHZ, &val, 1); } else val = 0; run_bbp_write(sc, 159, val); /* Tx1 IQ gain. */ run_bbp_write(sc, 158, 0x4a); if (chan <= 14) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX1_2GHZ, &val, 1); } else if (chan <= 64) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH36_TO_CH64_5GHZ, &val, 1); } else if (chan <= 138) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH100_TO_CH138_5GHZ, &val, 1); } else if (chan <= 165) { run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH140_TO_CH165_5GHZ, &val, 1); } else val = 0; run_bbp_write(sc, 159, val); /* Tx1 IQ phase. */ run_bbp_write(sc, 158, 0x4b); if (chan <= 14) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX1_2GHZ, &val, 1); } else if (chan <= 64) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH36_TO_CH64_5GHZ, &val, 1); } else if (chan <= 138) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH100_TO_CH138_5GHZ, &val, 1); } else if (chan <= 165) { run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH140_TO_CH165_5GHZ, &val, 1); } else val = 0; run_bbp_write(sc, 159, val); /* RF IQ compensation control. */ run_bbp_write(sc, 158, 0x04); run_efuse_read(sc, RT5390_EEPROM_RF_IQ_COMPENSATION_CTL, &val, 1); run_bbp_write(sc, 159, val); /* RF IQ imbalance compensation control. */ run_bbp_write(sc, 158, 0x03); run_efuse_read(sc, RT5390_EEPROM_RF_IQ_IMBALANCE_COMPENSATION_CTL, &val, 1); run_bbp_write(sc, 159, val); } static void run_set_agc(struct run_softc *sc, uint8_t agc) { uint8_t bbp; if (sc->mac_ver == 0x3572) { run_bbp_read(sc, 27, &bbp); bbp &= ~(0x3 << 5); run_bbp_write(sc, 27, bbp | 0 << 5); /* select Rx0 */ run_bbp_write(sc, 66, agc); run_bbp_write(sc, 27, bbp | 1 << 5); /* select Rx1 */ run_bbp_write(sc, 66, agc); } else run_bbp_write(sc, 66, agc); } static void run_select_chan_group(struct run_softc *sc, int group) { uint32_t tmp; uint8_t agc; run_bbp_write(sc, 62, 0x37 - sc->lna[group]); run_bbp_write(sc, 63, 0x37 - sc->lna[group]); run_bbp_write(sc, 64, 0x37 - sc->lna[group]); if (sc->mac_ver < 0x3572) run_bbp_write(sc, 86, 0x00); if (sc->mac_ver == 0x3593) { run_bbp_write(sc, 77, 0x98); run_bbp_write(sc, 83, (group == 0) ? 0x8a : 0x9a); } if (group == 0) { if (sc->ext_2ghz_lna) { if (sc->mac_ver >= 0x5390) run_bbp_write(sc, 75, 0x52); else { run_bbp_write(sc, 82, 0x62); run_bbp_write(sc, 75, 0x46); } } else { if (sc->mac_ver == 0x5592) { run_bbp_write(sc, 79, 0x1c); run_bbp_write(sc, 80, 0x0e); run_bbp_write(sc, 81, 0x3a); run_bbp_write(sc, 82, 0x62); run_bbp_write(sc, 195, 0x80); run_bbp_write(sc, 196, 0xe0); run_bbp_write(sc, 195, 0x81); run_bbp_write(sc, 196, 0x1f); run_bbp_write(sc, 195, 0x82); run_bbp_write(sc, 196, 0x38); run_bbp_write(sc, 195, 0x83); run_bbp_write(sc, 196, 0x32); run_bbp_write(sc, 195, 0x85); run_bbp_write(sc, 196, 0x28); run_bbp_write(sc, 195, 0x86); run_bbp_write(sc, 196, 0x19); } else if (sc->mac_ver >= 0x5390) run_bbp_write(sc, 75, 0x50); else { run_bbp_write(sc, 82, (sc->mac_ver == 0x3593) ? 0x62 : 0x84); run_bbp_write(sc, 75, 0x50); } } } else { if (sc->mac_ver == 0x5592) { run_bbp_write(sc, 79, 0x18); run_bbp_write(sc, 80, 0x08); run_bbp_write(sc, 81, 0x38); run_bbp_write(sc, 82, 0x92); run_bbp_write(sc, 195, 0x80); run_bbp_write(sc, 196, 0xf0); run_bbp_write(sc, 195, 0x81); run_bbp_write(sc, 196, 0x1e); run_bbp_write(sc, 195, 0x82); run_bbp_write(sc, 196, 0x28); run_bbp_write(sc, 195, 0x83); run_bbp_write(sc, 196, 0x20); run_bbp_write(sc, 195, 0x85); run_bbp_write(sc, 196, 0x7f); run_bbp_write(sc, 195, 0x86); run_bbp_write(sc, 196, 0x7f); } else if (sc->mac_ver == 0x3572) run_bbp_write(sc, 82, 0x94); else run_bbp_write(sc, 82, (sc->mac_ver == 0x3593) ? 0x82 : 0xf2); if (sc->ext_5ghz_lna) run_bbp_write(sc, 75, 0x46); else run_bbp_write(sc, 75, 0x50); } run_read(sc, RT2860_TX_BAND_CFG, &tmp); tmp &= ~(RT2860_5G_BAND_SEL_N | RT2860_5G_BAND_SEL_P); tmp |= (group == 0) ? RT2860_5G_BAND_SEL_N : RT2860_5G_BAND_SEL_P; run_write(sc, RT2860_TX_BAND_CFG, tmp); /* enable appropriate Power Amplifiers and Low Noise Amplifiers */ tmp = RT2860_RFTR_EN | RT2860_TRSW_EN | RT2860_LNA_PE0_EN; if (sc->mac_ver == 0x3593) tmp |= 1 << 29 | 1 << 28; if (sc->nrxchains > 1) tmp |= RT2860_LNA_PE1_EN; if (group == 0) { /* 2GHz */ tmp |= RT2860_PA_PE_G0_EN; if (sc->ntxchains > 1) tmp |= RT2860_PA_PE_G1_EN; if (sc->mac_ver == 0x3593) { if (sc->ntxchains > 2) tmp |= 1 << 25; } } else { /* 5GHz */ tmp |= RT2860_PA_PE_A0_EN; if (sc->ntxchains > 1) tmp |= RT2860_PA_PE_A1_EN; } if (sc->mac_ver == 0x3572) { run_rt3070_rf_write(sc, 8, 0x00); run_write(sc, RT2860_TX_PIN_CFG, tmp); run_rt3070_rf_write(sc, 8, 0x80); } else run_write(sc, RT2860_TX_PIN_CFG, tmp); if (sc->mac_ver == 0x5592) { run_bbp_write(sc, 195, 0x8d); run_bbp_write(sc, 196, 0x1a); } if (sc->mac_ver == 0x3593) { run_read(sc, RT2860_GPIO_CTRL, &tmp); tmp &= ~0x01010000; if (group == 0) tmp |= 0x00010000; tmp = (tmp & ~0x00009090) | 0x00000090; run_write(sc, RT2860_GPIO_CTRL, tmp); } /* set initial AGC value */ if (group == 0) { /* 2GHz band */ if (sc->mac_ver >= 0x3070) agc = 0x1c + sc->lna[0] * 2; else agc = 0x2e + sc->lna[0]; } else { /* 5GHz band */ if (sc->mac_ver == 0x5592) agc = 0x24 + sc->lna[group] * 2; else if (sc->mac_ver == 0x3572 || sc->mac_ver == 0x3593) agc = 0x22 + (sc->lna[group] * 5) / 3; else agc = 0x32 + (sc->lna[group] * 5) / 3; } run_set_agc(sc, agc); } static void run_rt2870_set_chan(struct run_softc *sc, u_int chan) { const struct rfprog *rfprog = rt2860_rf2850; uint32_t r2, r3, r4; int8_t txpow1, txpow2; int i; /* find the settings for this channel (we know it exists) */ for (i = 0; rfprog[i].chan != chan; i++); r2 = rfprog[i].r2; if (sc->ntxchains == 1) r2 |= 1 << 14; /* 1T: disable Tx chain 2 */ if (sc->nrxchains == 1) r2 |= 1 << 17 | 1 << 6; /* 1R: disable Rx chains 2 & 3 */ else if (sc->nrxchains == 2) r2 |= 1 << 6; /* 2R: disable Rx chain 3 */ /* use Tx power values from EEPROM */ txpow1 = sc->txpow1[i]; txpow2 = sc->txpow2[i]; /* Initialize RF R3 and R4. */ r3 = rfprog[i].r3 & 0xffffc1ff; r4 = (rfprog[i].r4 & ~(0x001f87c0)) | (sc->freq << 15); if (chan > 14) { if (txpow1 >= 0) { txpow1 = (txpow1 > 0xf) ? (0xf) : (txpow1); r3 |= (txpow1 << 10) | (1 << 9); } else { txpow1 += 7; /* txpow1 is not possible larger than 15. */ r3 |= (txpow1 << 10); } if (txpow2 >= 0) { txpow2 = (txpow2 > 0xf) ? (0xf) : (txpow2); r4 |= (txpow2 << 7) | (1 << 6); } else { txpow2 += 7; r4 |= (txpow2 << 7); } } else { /* Set Tx0 power. */ r3 |= (txpow1 << 9); /* Set frequency offset and Tx1 power. */ r4 |= (txpow2 << 6); } run_rt2870_rf_write(sc, rfprog[i].r1); run_rt2870_rf_write(sc, r2); run_rt2870_rf_write(sc, r3 & ~(1 << 2)); run_rt2870_rf_write(sc, r4); run_delay(sc, 10); run_rt2870_rf_write(sc, rfprog[i].r1); run_rt2870_rf_write(sc, r2); run_rt2870_rf_write(sc, r3 | (1 << 2)); run_rt2870_rf_write(sc, r4); run_delay(sc, 10); run_rt2870_rf_write(sc, rfprog[i].r1); run_rt2870_rf_write(sc, r2); run_rt2870_rf_write(sc, r3 & ~(1 << 2)); run_rt2870_rf_write(sc, r4); } static void run_rt3070_set_chan(struct run_softc *sc, u_int chan) { int8_t txpow1, txpow2; uint8_t rf; int i; /* find the settings for this channel (we know it exists) */ for (i = 0; rt2860_rf2850[i].chan != chan; i++); /* use Tx power values from EEPROM */ txpow1 = sc->txpow1[i]; txpow2 = sc->txpow2[i]; run_rt3070_rf_write(sc, 2, rt3070_freqs[i].n); /* RT3370/RT3390: RF R3 [7:4] is not reserved bits. */ run_rt3070_rf_read(sc, 3, &rf); rf = (rf & ~0x0f) | rt3070_freqs[i].k; run_rt3070_rf_write(sc, 3, rf); run_rt3070_rf_read(sc, 6, &rf); rf = (rf & ~0x03) | rt3070_freqs[i].r; run_rt3070_rf_write(sc, 6, rf); /* set Tx0 power */ run_rt3070_rf_read(sc, 12, &rf); rf = (rf & ~0x1f) | txpow1; run_rt3070_rf_write(sc, 12, rf); /* set Tx1 power */ run_rt3070_rf_read(sc, 13, &rf); rf = (rf & ~0x1f) | txpow2; run_rt3070_rf_write(sc, 13, rf); run_rt3070_rf_read(sc, 1, &rf); rf &= ~0xfc; if (sc->ntxchains == 1) rf |= 1 << 7 | 1 << 5; /* 1T: disable Tx chains 2 & 3 */ else if (sc->ntxchains == 2) rf |= 1 << 7; /* 2T: disable Tx chain 3 */ if (sc->nrxchains == 1) rf |= 1 << 6 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */ else if (sc->nrxchains == 2) rf |= 1 << 6; /* 2R: disable Rx chain 3 */ run_rt3070_rf_write(sc, 1, rf); /* set RF offset */ run_rt3070_rf_read(sc, 23, &rf); rf = (rf & ~0x7f) | sc->freq; run_rt3070_rf_write(sc, 23, rf); /* program RF filter */ run_rt3070_rf_read(sc, 24, &rf); /* Tx */ rf = (rf & ~0x3f) | sc->rf24_20mhz; run_rt3070_rf_write(sc, 24, rf); run_rt3070_rf_read(sc, 31, &rf); /* Rx */ rf = (rf & ~0x3f) | sc->rf24_20mhz; run_rt3070_rf_write(sc, 31, rf); /* enable RF tuning */ run_rt3070_rf_read(sc, 7, &rf); run_rt3070_rf_write(sc, 7, rf | 0x01); } static void run_rt3572_set_chan(struct run_softc *sc, u_int chan) { int8_t txpow1, txpow2; uint32_t tmp; uint8_t rf; int i; /* find the settings for this channel (we know it exists) */ for (i = 0; rt2860_rf2850[i].chan != chan; i++); /* use Tx power values from EEPROM */ txpow1 = sc->txpow1[i]; txpow2 = sc->txpow2[i]; if (chan <= 14) { run_bbp_write(sc, 25, sc->bbp25); run_bbp_write(sc, 26, sc->bbp26); } else { /* enable IQ phase correction */ run_bbp_write(sc, 25, 0x09); run_bbp_write(sc, 26, 0xff); } run_rt3070_rf_write(sc, 2, rt3070_freqs[i].n); run_rt3070_rf_write(sc, 3, rt3070_freqs[i].k); run_rt3070_rf_read(sc, 6, &rf); rf = (rf & ~0x0f) | rt3070_freqs[i].r; rf |= (chan <= 14) ? 0x08 : 0x04; run_rt3070_rf_write(sc, 6, rf); /* set PLL mode */ run_rt3070_rf_read(sc, 5, &rf); rf &= ~(0x08 | 0x04); rf |= (chan <= 14) ? 0x04 : 0x08; run_rt3070_rf_write(sc, 5, rf); /* set Tx power for chain 0 */ if (chan <= 14) rf = 0x60 | txpow1; else rf = 0xe0 | (txpow1 & 0xc) << 1 | (txpow1 & 0x3); run_rt3070_rf_write(sc, 12, rf); /* set Tx power for chain 1 */ if (chan <= 14) rf = 0x60 | txpow2; else rf = 0xe0 | (txpow2 & 0xc) << 1 | (txpow2 & 0x3); run_rt3070_rf_write(sc, 13, rf); /* set Tx/Rx streams */ run_rt3070_rf_read(sc, 1, &rf); rf &= ~0xfc; if (sc->ntxchains == 1) rf |= 1 << 7 | 1 << 5; /* 1T: disable Tx chains 2 & 3 */ else if (sc->ntxchains == 2) rf |= 1 << 7; /* 2T: disable Tx chain 3 */ if (sc->nrxchains == 1) rf |= 1 << 6 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */ else if (sc->nrxchains == 2) rf |= 1 << 6; /* 2R: disable Rx chain 3 */ run_rt3070_rf_write(sc, 1, rf); /* set RF offset */ run_rt3070_rf_read(sc, 23, &rf); rf = (rf & ~0x7f) | sc->freq; run_rt3070_rf_write(sc, 23, rf); /* program RF filter */ rf = sc->rf24_20mhz; run_rt3070_rf_write(sc, 24, rf); /* Tx */ run_rt3070_rf_write(sc, 31, rf); /* Rx */ /* enable RF tuning */ run_rt3070_rf_read(sc, 7, &rf); rf = (chan <= 14) ? 0xd8 : ((rf & ~0xc8) | 0x14); run_rt3070_rf_write(sc, 7, rf); /* TSSI */ rf = (chan <= 14) ? 0xc3 : 0xc0; run_rt3070_rf_write(sc, 9, rf); /* set loop filter 1 */ run_rt3070_rf_write(sc, 10, 0xf1); /* set loop filter 2 */ run_rt3070_rf_write(sc, 11, (chan <= 14) ? 0xb9 : 0x00); /* set tx_mx2_ic */ run_rt3070_rf_write(sc, 15, (chan <= 14) ? 0x53 : 0x43); /* set tx_mx1_ic */ if (chan <= 14) rf = 0x48 | sc->txmixgain_2ghz; else rf = 0x78 | sc->txmixgain_5ghz; run_rt3070_rf_write(sc, 16, rf); /* set tx_lo1 */ run_rt3070_rf_write(sc, 17, 0x23); /* set tx_lo2 */ if (chan <= 14) rf = 0x93; else if (chan <= 64) rf = 0xb7; else if (chan <= 128) rf = 0x74; else rf = 0x72; run_rt3070_rf_write(sc, 19, rf); /* set rx_lo1 */ if (chan <= 14) rf = 0xb3; else if (chan <= 64) rf = 0xf6; else if (chan <= 128) rf = 0xf4; else rf = 0xf3; run_rt3070_rf_write(sc, 20, rf); /* set pfd_delay */ if (chan <= 14) rf = 0x15; else if (chan <= 64) rf = 0x3d; else rf = 0x01; run_rt3070_rf_write(sc, 25, rf); /* set rx_lo2 */ run_rt3070_rf_write(sc, 26, (chan <= 14) ? 0x85 : 0x87); /* set ldo_rf_vc */ run_rt3070_rf_write(sc, 27, (chan <= 14) ? 0x00 : 0x01); /* set drv_cc */ run_rt3070_rf_write(sc, 29, (chan <= 14) ? 0x9b : 0x9f); run_read(sc, RT2860_GPIO_CTRL, &tmp); tmp &= ~0x8080; if (chan <= 14) tmp |= 0x80; run_write(sc, RT2860_GPIO_CTRL, tmp); /* enable RF tuning */ run_rt3070_rf_read(sc, 7, &rf); run_rt3070_rf_write(sc, 7, rf | 0x01); run_delay(sc, 2); } static void run_rt3593_set_chan(struct run_softc *sc, u_int chan) { int8_t txpow1, txpow2, txpow3; uint8_t h20mhz, rf; int i; /* find the settings for this channel (we know it exists) */ for (i = 0; rt2860_rf2850[i].chan != chan; i++); /* use Tx power values from EEPROM */ txpow1 = sc->txpow1[i]; txpow2 = sc->txpow2[i]; txpow3 = (sc->ntxchains == 3) ? sc->txpow3[i] : 0; if (chan <= 14) { run_bbp_write(sc, 25, sc->bbp25); run_bbp_write(sc, 26, sc->bbp26); } else { /* Enable IQ phase correction. */ run_bbp_write(sc, 25, 0x09); run_bbp_write(sc, 26, 0xff); } run_rt3070_rf_write(sc, 8, rt3070_freqs[i].n); run_rt3070_rf_write(sc, 9, rt3070_freqs[i].k & 0x0f); run_rt3070_rf_read(sc, 11, &rf); rf = (rf & ~0x03) | (rt3070_freqs[i].r & 0x03); run_rt3070_rf_write(sc, 11, rf); /* Set pll_idoh. */ run_rt3070_rf_read(sc, 11, &rf); rf &= ~0x4c; rf |= (chan <= 14) ? 0x44 : 0x48; run_rt3070_rf_write(sc, 11, rf); if (chan <= 14) rf = txpow1 & 0x1f; else rf = 0x40 | ((txpow1 & 0x18) << 1) | (txpow1 & 0x07); run_rt3070_rf_write(sc, 53, rf); if (chan <= 14) rf = txpow2 & 0x1f; else rf = 0x40 | ((txpow2 & 0x18) << 1) | (txpow2 & 0x07); run_rt3070_rf_write(sc, 55, rf); if (chan <= 14) rf = txpow3 & 0x1f; else rf = 0x40 | ((txpow3 & 0x18) << 1) | (txpow3 & 0x07); run_rt3070_rf_write(sc, 54, rf); rf = RT3070_RF_BLOCK | RT3070_PLL_PD; if (sc->ntxchains == 3) rf |= RT3070_TX0_PD | RT3070_TX1_PD | RT3070_TX2_PD; else rf |= RT3070_TX0_PD | RT3070_TX1_PD; rf |= RT3070_RX0_PD | RT3070_RX1_PD | RT3070_RX2_PD; run_rt3070_rf_write(sc, 1, rf); run_adjust_freq_offset(sc); run_rt3070_rf_write(sc, 31, (chan <= 14) ? 0xa0 : 0x80); h20mhz = (sc->rf24_20mhz & 0x20) >> 5; run_rt3070_rf_read(sc, 30, &rf); rf = (rf & ~0x06) | (h20mhz << 1) | (h20mhz << 2); run_rt3070_rf_write(sc, 30, rf); run_rt3070_rf_read(sc, 36, &rf); if (chan <= 14) rf |= 0x80; else rf &= ~0x80; run_rt3070_rf_write(sc, 36, rf); /* Set vcolo_bs. */ run_rt3070_rf_write(sc, 34, (chan <= 14) ? 0x3c : 0x20); /* Set pfd_delay. */ run_rt3070_rf_write(sc, 12, (chan <= 14) ? 0x1a : 0x12); /* Set vco bias current control. */ run_rt3070_rf_read(sc, 6, &rf); rf &= ~0xc0; if (chan <= 14) rf |= 0x40; else if (chan <= 128) rf |= 0x80; else rf |= 0x40; run_rt3070_rf_write(sc, 6, rf); run_rt3070_rf_read(sc, 30, &rf); rf = (rf & ~0x18) | 0x10; run_rt3070_rf_write(sc, 30, rf); run_rt3070_rf_write(sc, 10, (chan <= 14) ? 0xd3 : 0xd8); run_rt3070_rf_write(sc, 13, (chan <= 14) ? 0x12 : 0x23); run_rt3070_rf_read(sc, 51, &rf); rf = (rf & ~0x03) | 0x01; run_rt3070_rf_write(sc, 51, rf); /* Set tx_mx1_cc. */ run_rt3070_rf_read(sc, 51, &rf); rf &= ~0x1c; rf |= (chan <= 14) ? 0x14 : 0x10; run_rt3070_rf_write(sc, 51, rf); /* Set tx_mx1_ic. */ run_rt3070_rf_read(sc, 51, &rf); rf &= ~0xe0; rf |= (chan <= 14) ? 0x60 : 0x40; run_rt3070_rf_write(sc, 51, rf); /* Set tx_lo1_ic. */ run_rt3070_rf_read(sc, 49, &rf); rf &= ~0x1c; rf |= (chan <= 14) ? 0x0c : 0x08; run_rt3070_rf_write(sc, 49, rf); /* Set tx_lo1_en. */ run_rt3070_rf_read(sc, 50, &rf); run_rt3070_rf_write(sc, 50, rf & ~0x20); /* Set drv_cc. */ run_rt3070_rf_read(sc, 57, &rf); rf &= ~0xfc; rf |= (chan <= 14) ? 0x6c : 0x3c; run_rt3070_rf_write(sc, 57, rf); /* Set rx_mix1_ic, rxa_lnactr, lna_vc, lna_inbias_en and lna_en. */ run_rt3070_rf_write(sc, 44, (chan <= 14) ? 0x93 : 0x9b); /* Set drv_gnd_a, tx_vga_cc_a and tx_mx2_gain. */ run_rt3070_rf_write(sc, 52, (chan <= 14) ? 0x45 : 0x05); /* Enable VCO calibration. */ run_rt3070_rf_read(sc, 3, &rf); rf &= ~RT5390_VCOCAL; rf |= (chan <= 14) ? RT5390_VCOCAL : 0xbe; run_rt3070_rf_write(sc, 3, rf); if (chan <= 14) rf = 0x23; else if (chan <= 64) rf = 0x36; else if (chan <= 128) rf = 0x32; else rf = 0x30; run_rt3070_rf_write(sc, 39, rf); if (chan <= 14) rf = 0xbb; else if (chan <= 64) rf = 0xeb; else if (chan <= 128) rf = 0xb3; else rf = 0x9b; run_rt3070_rf_write(sc, 45, rf); /* Set FEQ/AEQ control. */ run_bbp_write(sc, 105, 0x34); } static void run_rt5390_set_chan(struct run_softc *sc, u_int chan) { int8_t txpow1, txpow2; uint8_t rf; int i; /* find the settings for this channel (we know it exists) */ for (i = 0; rt2860_rf2850[i].chan != chan; i++); /* use Tx power values from EEPROM */ txpow1 = sc->txpow1[i]; txpow2 = sc->txpow2[i]; run_rt3070_rf_write(sc, 8, rt3070_freqs[i].n); run_rt3070_rf_write(sc, 9, rt3070_freqs[i].k & 0x0f); run_rt3070_rf_read(sc, 11, &rf); rf = (rf & ~0x03) | (rt3070_freqs[i].r & 0x03); run_rt3070_rf_write(sc, 11, rf); run_rt3070_rf_read(sc, 49, &rf); rf = (rf & ~0x3f) | (txpow1 & 0x3f); /* The valid range of the RF R49 is 0x00 to 0x27. */ if ((rf & 0x3f) > 0x27) rf = (rf & ~0x3f) | 0x27; run_rt3070_rf_write(sc, 49, rf); if (sc->mac_ver == 0x5392) { run_rt3070_rf_read(sc, 50, &rf); rf = (rf & ~0x3f) | (txpow2 & 0x3f); /* The valid range of the RF R50 is 0x00 to 0x27. */ if ((rf & 0x3f) > 0x27) rf = (rf & ~0x3f) | 0x27; run_rt3070_rf_write(sc, 50, rf); } run_rt3070_rf_read(sc, 1, &rf); rf |= RT3070_RF_BLOCK | RT3070_PLL_PD | RT3070_RX0_PD | RT3070_TX0_PD; if (sc->mac_ver == 0x5392) rf |= RT3070_RX1_PD | RT3070_TX1_PD; run_rt3070_rf_write(sc, 1, rf); if (sc->mac_ver != 0x5392) { run_rt3070_rf_read(sc, 2, &rf); rf |= 0x80; run_rt3070_rf_write(sc, 2, rf); run_delay(sc, 10); rf &= 0x7f; run_rt3070_rf_write(sc, 2, rf); } run_adjust_freq_offset(sc); if (sc->mac_ver == 0x5392) { /* Fix for RT5392C. */ if (sc->mac_rev >= 0x0223) { if (chan <= 4) rf = 0x0f; else if (chan >= 5 && chan <= 7) rf = 0x0e; else rf = 0x0d; run_rt3070_rf_write(sc, 23, rf); if (chan <= 4) rf = 0x0c; else if (chan == 5) rf = 0x0b; else if (chan >= 6 && chan <= 7) rf = 0x0a; else if (chan >= 8 && chan <= 10) rf = 0x09; else rf = 0x08; run_rt3070_rf_write(sc, 59, rf); } else { if (chan <= 11) rf = 0x0f; else rf = 0x0b; run_rt3070_rf_write(sc, 59, rf); } } else { /* Fix for RT5390F. */ if (sc->mac_rev >= 0x0502) { if (chan <= 11) rf = 0x43; else rf = 0x23; run_rt3070_rf_write(sc, 55, rf); if (chan <= 11) rf = 0x0f; else if (chan == 12) rf = 0x0d; else rf = 0x0b; run_rt3070_rf_write(sc, 59, rf); } else { run_rt3070_rf_write(sc, 55, 0x44); run_rt3070_rf_write(sc, 59, 0x8f); } } /* Enable VCO calibration. */ run_rt3070_rf_read(sc, 3, &rf); rf |= RT5390_VCOCAL; run_rt3070_rf_write(sc, 3, rf); } static void run_rt5592_set_chan(struct run_softc *sc, u_int chan) { const struct rt5592_freqs *freqs; uint32_t tmp; uint8_t reg, rf, txpow_bound; int8_t txpow1, txpow2; int i; run_read(sc, RT5592_DEBUG_INDEX, &tmp); freqs = (tmp & RT5592_SEL_XTAL) ? rt5592_freqs_40mhz : rt5592_freqs_20mhz; /* find the settings for this channel (we know it exists) */ for (i = 0; rt2860_rf2850[i].chan != chan; i++, freqs++); /* use Tx power values from EEPROM */ txpow1 = sc->txpow1[i]; txpow2 = sc->txpow2[i]; run_read(sc, RT3070_LDO_CFG0, &tmp); tmp &= ~0x1c000000; if (chan > 14) tmp |= 0x14000000; run_write(sc, RT3070_LDO_CFG0, tmp); /* N setting. */ run_rt3070_rf_write(sc, 8, freqs->n & 0xff); run_rt3070_rf_read(sc, 9, &rf); rf &= ~(1 << 4); rf |= ((freqs->n & 0x0100) >> 8) << 4; run_rt3070_rf_write(sc, 9, rf); /* K setting. */ run_rt3070_rf_read(sc, 9, &rf); rf &= ~0x0f; rf |= (freqs->k & 0x0f); run_rt3070_rf_write(sc, 9, rf); /* Mode setting. */ run_rt3070_rf_read(sc, 11, &rf); rf &= ~0x0c; rf |= ((freqs->m - 0x8) & 0x3) << 2; run_rt3070_rf_write(sc, 11, rf); run_rt3070_rf_read(sc, 9, &rf); rf &= ~(1 << 7); rf |= (((freqs->m - 0x8) & 0x4) >> 2) << 7; run_rt3070_rf_write(sc, 9, rf); /* R setting. */ run_rt3070_rf_read(sc, 11, &rf); rf &= ~0x03; rf |= (freqs->r - 0x1); run_rt3070_rf_write(sc, 11, rf); if (chan <= 14) { /* Initialize RF registers for 2GHZ. */ for (i = 0; i < nitems(rt5592_2ghz_def_rf); i++) { run_rt3070_rf_write(sc, rt5592_2ghz_def_rf[i].reg, rt5592_2ghz_def_rf[i].val); } rf = (chan <= 10) ? 0x07 : 0x06; run_rt3070_rf_write(sc, 23, rf); run_rt3070_rf_write(sc, 59, rf); run_rt3070_rf_write(sc, 55, 0x43); /* * RF R49/R50 Tx power ALC code. * G-band bit<7:6>=1:0, bit<5:0> range from 0x0 ~ 0x27. */ reg = 2; txpow_bound = 0x27; } else { /* Initialize RF registers for 5GHZ. */ for (i = 0; i < nitems(rt5592_5ghz_def_rf); i++) { run_rt3070_rf_write(sc, rt5592_5ghz_def_rf[i].reg, rt5592_5ghz_def_rf[i].val); } for (i = 0; i < nitems(rt5592_chan_5ghz); i++) { if (chan >= rt5592_chan_5ghz[i].firstchan && chan <= rt5592_chan_5ghz[i].lastchan) { run_rt3070_rf_write(sc, rt5592_chan_5ghz[i].reg, rt5592_chan_5ghz[i].val); } } /* * RF R49/R50 Tx power ALC code. * A-band bit<7:6>=1:1, bit<5:0> range from 0x0 ~ 0x2b. */ reg = 3; txpow_bound = 0x2b; } /* RF R49 ch0 Tx power ALC code. */ run_rt3070_rf_read(sc, 49, &rf); rf &= ~0xc0; rf |= (reg << 6); rf = (rf & ~0x3f) | (txpow1 & 0x3f); if ((rf & 0x3f) > txpow_bound) rf = (rf & ~0x3f) | txpow_bound; run_rt3070_rf_write(sc, 49, rf); /* RF R50 ch1 Tx power ALC code. */ run_rt3070_rf_read(sc, 50, &rf); rf &= ~(1 << 7 | 1 << 6); rf |= (reg << 6); rf = (rf & ~0x3f) | (txpow2 & 0x3f); if ((rf & 0x3f) > txpow_bound) rf = (rf & ~0x3f) | txpow_bound; run_rt3070_rf_write(sc, 50, rf); /* Enable RF_BLOCK, PLL_PD, RX0_PD, and TX0_PD. */ run_rt3070_rf_read(sc, 1, &rf); rf |= (RT3070_RF_BLOCK | RT3070_PLL_PD | RT3070_RX0_PD | RT3070_TX0_PD); if (sc->ntxchains > 1) rf |= RT3070_TX1_PD; if (sc->nrxchains > 1) rf |= RT3070_RX1_PD; run_rt3070_rf_write(sc, 1, rf); run_rt3070_rf_write(sc, 6, 0xe4); run_rt3070_rf_write(sc, 30, 0x10); run_rt3070_rf_write(sc, 31, 0x80); run_rt3070_rf_write(sc, 32, 0x80); run_adjust_freq_offset(sc); /* Enable VCO calibration. */ run_rt3070_rf_read(sc, 3, &rf); rf |= RT5390_VCOCAL; run_rt3070_rf_write(sc, 3, rf); } static void run_set_rx_antenna(struct run_softc *sc, int aux) { uint32_t tmp; uint8_t bbp152; if (aux) { if (sc->rf_rev == RT5390_RF_5370) { run_bbp_read(sc, 152, &bbp152); run_bbp_write(sc, 152, bbp152 & ~0x80); } else { run_mcu_cmd(sc, RT2860_MCU_CMD_ANTSEL, 0); run_read(sc, RT2860_GPIO_CTRL, &tmp); run_write(sc, RT2860_GPIO_CTRL, (tmp & ~0x0808) | 0x08); } } else { if (sc->rf_rev == RT5390_RF_5370) { run_bbp_read(sc, 152, &bbp152); run_bbp_write(sc, 152, bbp152 | 0x80); } else { run_mcu_cmd(sc, RT2860_MCU_CMD_ANTSEL, 1); run_read(sc, RT2860_GPIO_CTRL, &tmp); run_write(sc, RT2860_GPIO_CTRL, tmp & ~0x0808); } } } static int run_set_chan(struct run_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; u_int chan, group; chan = ieee80211_chan2ieee(ic, c); if (chan == 0 || chan == IEEE80211_CHAN_ANY) return (EINVAL); if (sc->mac_ver == 0x5592) run_rt5592_set_chan(sc, chan); else if (sc->mac_ver >= 0x5390) run_rt5390_set_chan(sc, chan); else if (sc->mac_ver == 0x3593) run_rt3593_set_chan(sc, chan); else if (sc->mac_ver == 0x3572) run_rt3572_set_chan(sc, chan); else if (sc->mac_ver >= 0x3070) run_rt3070_set_chan(sc, chan); else run_rt2870_set_chan(sc, chan); /* determine channel group */ if (chan <= 14) group = 0; else if (chan <= 64) group = 1; else if (chan <= 128) group = 2; else group = 3; /* XXX necessary only when group has changed! */ run_select_chan_group(sc, group); run_delay(sc, 10); /* Perform IQ calibration. */ if (sc->mac_ver >= 0x5392) run_iq_calib(sc, chan); return (0); } static void run_set_channel(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; RUN_LOCK(sc); run_set_chan(sc, ic->ic_curchan); RUN_UNLOCK(sc); return; } static void run_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct run_softc *sc = ic->ic_softc; uint8_t bands[IEEE80211_MODE_BYTES]; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0); if (sc->rf_rev == RT2860_RF_2750 || sc->rf_rev == RT2860_RF_2850 || sc->rf_rev == RT3070_RF_3052 || sc->rf_rev == RT3593_RF_3053 || sc->rf_rev == RT5592_RF_5592) { setbit(bands, IEEE80211_MODE_11A); ieee80211_add_channel_list_5ghz(chans, maxchans, nchans, run_chan_5ghz, nitems(run_chan_5ghz), bands, 0); } } static void run_scan_start(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; RUN_LOCK(sc); /* abort TSF synchronization */ run_disable_tsf(sc); run_set_bssid(sc, ieee80211broadcastaddr); RUN_UNLOCK(sc); return; } static void run_scan_end(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; RUN_LOCK(sc); run_enable_tsf_sync(sc); run_set_bssid(sc, sc->sc_bssid); RUN_UNLOCK(sc); return; } /* * Could be called from ieee80211_node_timeout() * (non-sleepable thread) */ static void run_update_beacon(struct ieee80211vap *vap, int item) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211_node *ni = vap->iv_bss; struct run_softc *sc = ic->ic_softc; struct run_vap *rvp = RUN_VAP(vap); int mcast = 0; uint32_t i; switch (item) { case IEEE80211_BEACON_ERP: run_updateslot(ic); break; case IEEE80211_BEACON_HTINFO: run_updateprot(ic); break; case IEEE80211_BEACON_TIM: mcast = 1; /*TODO*/ break; default: break; } setbit(bo->bo_flags, item); if (rvp->beacon_mbuf == NULL) { rvp->beacon_mbuf = ieee80211_beacon_alloc(ni); if (rvp->beacon_mbuf == NULL) return; } ieee80211_beacon_update(ni, rvp->beacon_mbuf, mcast); i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_update_beacon_cb; sc->cmdq[i].arg0 = vap; ieee80211_runtask(ic, &sc->cmdq_task); return; } static void run_update_beacon_cb(void *arg) { struct ieee80211vap *vap = arg; struct ieee80211_node *ni = vap->iv_bss; struct run_vap *rvp = RUN_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct run_softc *sc = ic->ic_softc; struct rt2860_txwi txwi; struct mbuf *m; uint16_t txwisize; uint8_t ridx; if (ni->ni_chan == IEEE80211_CHAN_ANYC) return; if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) return; /* * No need to call ieee80211_beacon_update(), run_update_beacon() * is taking care of appropriate calls. */ if (rvp->beacon_mbuf == NULL) { rvp->beacon_mbuf = ieee80211_beacon_alloc(ni); if (rvp->beacon_mbuf == NULL) return; } m = rvp->beacon_mbuf; memset(&txwi, 0, sizeof(txwi)); txwi.wcid = 0xff; txwi.len = htole16(m->m_pkthdr.len); /* send beacons at the lowest available rate */ ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ? RT2860_RIDX_OFDM6 : RT2860_RIDX_CCK1; txwi.phy = htole16(rt2860_rates[ridx].mcs); if (rt2860_rates[ridx].phy == IEEE80211_T_OFDM) txwi.phy |= htole16(RT2860_PHY_OFDM); txwi.txop = RT2860_TX_TXOP_HT; txwi.flags = RT2860_TX_TS; txwi.xflags = RT2860_TX_NSEQ; txwisize = (sc->mac_ver == 0x5592) ? sizeof(txwi) + sizeof(uint32_t) : sizeof(txwi); run_write_region_1(sc, RT2860_BCN_BASE(rvp->rvp_id), (uint8_t *)&txwi, txwisize); run_write_region_1(sc, RT2860_BCN_BASE(rvp->rvp_id) + txwisize, mtod(m, uint8_t *), (m->m_pkthdr.len + 1) & ~1); } static void run_updateprot(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; uint32_t i; i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_updateprot_cb; sc->cmdq[i].arg0 = ic; ieee80211_runtask(ic, &sc->cmdq_task); } static void run_updateprot_cb(void *arg) { struct ieee80211com *ic = arg; struct run_softc *sc = ic->ic_softc; uint32_t tmp; tmp = RT2860_RTSTH_EN | RT2860_PROT_NAV_SHORT | RT2860_TXOP_ALLOW_ALL; /* setup protection frame rate (MCS code) */ tmp |= (ic->ic_curmode == IEEE80211_MODE_11A) ? rt2860_rates[RT2860_RIDX_OFDM6].mcs | RT2860_PHY_OFDM : rt2860_rates[RT2860_RIDX_CCK11].mcs; /* CCK frames don't require protection */ run_write(sc, RT2860_CCK_PROT_CFG, tmp); if (ic->ic_flags & IEEE80211_F_USEPROT) { if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) tmp |= RT2860_PROT_CTRL_RTS_CTS; else if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) tmp |= RT2860_PROT_CTRL_CTS; } run_write(sc, RT2860_OFDM_PROT_CFG, tmp); } static void run_usb_timeout_cb(void *arg) { struct ieee80211vap *vap = arg; struct run_softc *sc = vap->iv_ic->ic_softc; RUN_LOCK_ASSERT(sc, MA_OWNED); if(vap->iv_state == IEEE80211_S_RUN && vap->iv_opmode != IEEE80211_M_STA) run_reset_livelock(sc); else if (vap->iv_state == IEEE80211_S_SCAN) { RUN_DPRINTF(sc, RUN_DEBUG_USB | RUN_DEBUG_STATE, "timeout caused by scan\n"); /* cancel bgscan */ ieee80211_cancel_scan(vap); } else RUN_DPRINTF(sc, RUN_DEBUG_USB | RUN_DEBUG_STATE, "timeout by unknown cause\n"); } static void run_reset_livelock(struct run_softc *sc) { uint32_t tmp; RUN_LOCK_ASSERT(sc, MA_OWNED); /* * In IBSS or HostAP modes (when the hardware sends beacons), the MAC * can run into a livelock and start sending CTS-to-self frames like * crazy if protection is enabled. Reset MAC/BBP for a while */ run_read(sc, RT2860_DEBUG, &tmp); RUN_DPRINTF(sc, RUN_DEBUG_RESET, "debug reg %08x\n", tmp); if ((tmp & (1 << 29)) && (tmp & (1 << 7 | 1 << 5))) { RUN_DPRINTF(sc, RUN_DEBUG_RESET, "CTS-to-self livelock detected\n"); run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_SRST); run_delay(sc, 1); run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_RX_EN | RT2860_MAC_TX_EN); } } static void run_update_promisc_locked(struct run_softc *sc) { uint32_t tmp; run_read(sc, RT2860_RX_FILTR_CFG, &tmp); tmp |= RT2860_DROP_UC_NOME; if (sc->sc_ic.ic_promisc > 0) tmp &= ~RT2860_DROP_UC_NOME; run_write(sc, RT2860_RX_FILTR_CFG, tmp); RUN_DPRINTF(sc, RUN_DEBUG_RECV, "%s promiscuous mode\n", (sc->sc_ic.ic_promisc > 0) ? "entering" : "leaving"); } static void run_update_promisc(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; if ((sc->sc_flags & RUN_RUNNING) == 0) return; RUN_LOCK(sc); run_update_promisc_locked(sc); RUN_UNLOCK(sc); } static void run_enable_tsf_sync(struct run_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t tmp; RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "rvp_id=%d ic_opmode=%d\n", RUN_VAP(vap)->rvp_id, ic->ic_opmode); run_read(sc, RT2860_BCN_TIME_CFG, &tmp); tmp &= ~0x1fffff; tmp |= vap->iv_bss->ni_intval * 16; tmp |= RT2860_TSF_TIMER_EN | RT2860_TBTT_TIMER_EN; if (ic->ic_opmode == IEEE80211_M_STA) { /* * Local TSF is always updated with remote TSF on beacon * reception. */ tmp |= 1 << RT2860_TSF_SYNC_MODE_SHIFT; } else if (ic->ic_opmode == IEEE80211_M_IBSS) { tmp |= RT2860_BCN_TX_EN; /* * Local TSF is updated with remote TSF on beacon reception * only if the remote TSF is greater than local TSF. */ tmp |= 2 << RT2860_TSF_SYNC_MODE_SHIFT; } else if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_MBSS) { tmp |= RT2860_BCN_TX_EN; /* SYNC with nobody */ tmp |= 3 << RT2860_TSF_SYNC_MODE_SHIFT; } else { RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "Enabling TSF failed. undefined opmode\n"); return; } run_write(sc, RT2860_BCN_TIME_CFG, tmp); } static void run_enable_tsf(struct run_softc *sc) { uint32_t tmp; if (run_read(sc, RT2860_BCN_TIME_CFG, &tmp) == 0) { tmp &= ~(RT2860_BCN_TX_EN | RT2860_TBTT_TIMER_EN); tmp |= RT2860_TSF_TIMER_EN; run_write(sc, RT2860_BCN_TIME_CFG, tmp); } } static void run_disable_tsf(struct run_softc *sc) { uint32_t tmp; if (run_read(sc, RT2860_BCN_TIME_CFG, &tmp) == 0) { tmp &= ~(RT2860_BCN_TX_EN | RT2860_TSF_TIMER_EN | RT2860_TBTT_TIMER_EN); run_write(sc, RT2860_BCN_TIME_CFG, tmp); } } static void run_get_tsf(struct run_softc *sc, uint64_t *buf) { run_read_region_1(sc, RT2860_TSF_TIMER_DW0, (uint8_t *)buf, sizeof(*buf)); } static void run_enable_mrr(struct run_softc *sc) { #define CCK(mcs) (mcs) #define OFDM(mcs) (1 << 3 | (mcs)) run_write(sc, RT2860_LG_FBK_CFG0, OFDM(6) << 28 | /* 54->48 */ OFDM(5) << 24 | /* 48->36 */ OFDM(4) << 20 | /* 36->24 */ OFDM(3) << 16 | /* 24->18 */ OFDM(2) << 12 | /* 18->12 */ OFDM(1) << 8 | /* 12-> 9 */ OFDM(0) << 4 | /* 9-> 6 */ OFDM(0)); /* 6-> 6 */ run_write(sc, RT2860_LG_FBK_CFG1, CCK(2) << 12 | /* 11->5.5 */ CCK(1) << 8 | /* 5.5-> 2 */ CCK(0) << 4 | /* 2-> 1 */ CCK(0)); /* 1-> 1 */ #undef OFDM #undef CCK } static void run_set_txpreamble(struct run_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t tmp; run_read(sc, RT2860_AUTO_RSP_CFG, &tmp); if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) tmp |= RT2860_CCK_SHORT_EN; else tmp &= ~RT2860_CCK_SHORT_EN; run_write(sc, RT2860_AUTO_RSP_CFG, tmp); } static void run_set_basicrates(struct run_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; /* set basic rates mask */ if (ic->ic_curmode == IEEE80211_MODE_11B) run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x003); else if (ic->ic_curmode == IEEE80211_MODE_11A) run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x150); else /* 11g */ run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x15f); } static void run_set_leds(struct run_softc *sc, uint16_t which) { (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LEDS, which | (sc->leds & 0x7f)); } static void run_set_bssid(struct run_softc *sc, const uint8_t *bssid) { run_write(sc, RT2860_MAC_BSSID_DW0, bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24); run_write(sc, RT2860_MAC_BSSID_DW1, bssid[4] | bssid[5] << 8); } static void run_set_macaddr(struct run_softc *sc, const uint8_t *addr) { run_write(sc, RT2860_MAC_ADDR_DW0, addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24); run_write(sc, RT2860_MAC_ADDR_DW1, addr[4] | addr[5] << 8 | 0xff << 16); } static void run_updateslot(struct ieee80211com *ic) { struct run_softc *sc = ic->ic_softc; uint32_t i; i = RUN_CMDQ_GET(&sc->cmdq_store); RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "cmdq_store=%d\n", i); sc->cmdq[i].func = run_updateslot_cb; sc->cmdq[i].arg0 = ic; ieee80211_runtask(ic, &sc->cmdq_task); return; } /* ARGSUSED */ static void run_updateslot_cb(void *arg) { struct ieee80211com *ic = arg; struct run_softc *sc = ic->ic_softc; uint32_t tmp; run_read(sc, RT2860_BKOFF_SLOT_CFG, &tmp); tmp &= ~0xff; tmp |= IEEE80211_GET_SLOTTIME(ic); run_write(sc, RT2860_BKOFF_SLOT_CFG, tmp); } static void run_update_mcast(struct ieee80211com *ic) { } static int8_t run_rssi2dbm(struct run_softc *sc, uint8_t rssi, uint8_t rxchain) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_channel *c = ic->ic_curchan; int delta; if (IEEE80211_IS_CHAN_5GHZ(c)) { u_int chan = ieee80211_chan2ieee(ic, c); delta = sc->rssi_5ghz[rxchain]; /* determine channel group */ if (chan <= 64) delta -= sc->lna[1]; else if (chan <= 128) delta -= sc->lna[2]; else delta -= sc->lna[3]; } else delta = sc->rssi_2ghz[rxchain] - sc->lna[0]; return (-12 - delta - rssi); } static void run_rt5390_bbp_init(struct run_softc *sc) { u_int i; uint8_t bbp; /* Apply maximum likelihood detection for 2 stream case. */ run_bbp_read(sc, 105, &bbp); if (sc->nrxchains > 1) run_bbp_write(sc, 105, bbp | RT5390_MLD); /* Avoid data lost and CRC error. */ run_bbp_read(sc, 4, &bbp); run_bbp_write(sc, 4, bbp | RT5390_MAC_IF_CTRL); if (sc->mac_ver == 0x5592) { for (i = 0; i < nitems(rt5592_def_bbp); i++) { run_bbp_write(sc, rt5592_def_bbp[i].reg, rt5592_def_bbp[i].val); } for (i = 0; i < nitems(rt5592_bbp_r196); i++) { run_bbp_write(sc, 195, i + 0x80); run_bbp_write(sc, 196, rt5592_bbp_r196[i]); } } else { for (i = 0; i < nitems(rt5390_def_bbp); i++) { run_bbp_write(sc, rt5390_def_bbp[i].reg, rt5390_def_bbp[i].val); } } if (sc->mac_ver == 0x5392) { run_bbp_write(sc, 88, 0x90); run_bbp_write(sc, 95, 0x9a); run_bbp_write(sc, 98, 0x12); run_bbp_write(sc, 106, 0x12); run_bbp_write(sc, 134, 0xd0); run_bbp_write(sc, 135, 0xf6); run_bbp_write(sc, 148, 0x84); } run_bbp_read(sc, 152, &bbp); run_bbp_write(sc, 152, bbp | 0x80); /* Fix BBP254 for RT5592C. */ if (sc->mac_ver == 0x5592 && sc->mac_rev >= 0x0221) { run_bbp_read(sc, 254, &bbp); run_bbp_write(sc, 254, bbp | 0x80); } /* Disable hardware antenna diversity. */ if (sc->mac_ver == 0x5390) run_bbp_write(sc, 154, 0); /* Initialize Rx CCK/OFDM frequency offset report. */ run_bbp_write(sc, 142, 1); run_bbp_write(sc, 143, 57); } static int run_bbp_init(struct run_softc *sc) { int i, error, ntries; uint8_t bbp0; /* wait for BBP to wake up */ for (ntries = 0; ntries < 20; ntries++) { if ((error = run_bbp_read(sc, 0, &bbp0)) != 0) return error; if (bbp0 != 0 && bbp0 != 0xff) break; } if (ntries == 20) return (ETIMEDOUT); /* initialize BBP registers to default values */ if (sc->mac_ver >= 0x5390) run_rt5390_bbp_init(sc); else { for (i = 0; i < nitems(rt2860_def_bbp); i++) { run_bbp_write(sc, rt2860_def_bbp[i].reg, rt2860_def_bbp[i].val); } } if (sc->mac_ver == 0x3593) { run_bbp_write(sc, 79, 0x13); run_bbp_write(sc, 80, 0x05); run_bbp_write(sc, 81, 0x33); run_bbp_write(sc, 86, 0x46); run_bbp_write(sc, 137, 0x0f); } /* fix BBP84 for RT2860E */ if (sc->mac_ver == 0x2860 && sc->mac_rev != 0x0101) run_bbp_write(sc, 84, 0x19); if (sc->mac_ver >= 0x3070 && (sc->mac_ver != 0x3593 && sc->mac_ver != 0x5592)) { run_bbp_write(sc, 79, 0x13); run_bbp_write(sc, 80, 0x05); run_bbp_write(sc, 81, 0x33); } else if (sc->mac_ver == 0x2860 && sc->mac_rev == 0x0100) { run_bbp_write(sc, 69, 0x16); run_bbp_write(sc, 73, 0x12); } return (0); } static int run_rt3070_rf_init(struct run_softc *sc) { uint32_t tmp; uint8_t bbp4, mingain, rf, target; u_int i; run_rt3070_rf_read(sc, 30, &rf); /* toggle RF R30 bit 7 */ run_rt3070_rf_write(sc, 30, rf | 0x80); run_delay(sc, 10); run_rt3070_rf_write(sc, 30, rf & ~0x80); /* initialize RF registers to default value */ if (sc->mac_ver == 0x3572) { for (i = 0; i < nitems(rt3572_def_rf); i++) { run_rt3070_rf_write(sc, rt3572_def_rf[i].reg, rt3572_def_rf[i].val); } } else { for (i = 0; i < nitems(rt3070_def_rf); i++) { run_rt3070_rf_write(sc, rt3070_def_rf[i].reg, rt3070_def_rf[i].val); } } if (sc->mac_ver == 0x3070 && sc->mac_rev < 0x0201) { /* * Change voltage from 1.2V to 1.35V for RT3070. * The DAC issue (RT3070_LDO_CFG0) has been fixed * in RT3070(F). */ run_read(sc, RT3070_LDO_CFG0, &tmp); tmp = (tmp & ~0x0f000000) | 0x0d000000; run_write(sc, RT3070_LDO_CFG0, tmp); } else if (sc->mac_ver == 0x3071) { run_rt3070_rf_read(sc, 6, &rf); run_rt3070_rf_write(sc, 6, rf | 0x40); run_rt3070_rf_write(sc, 31, 0x14); run_read(sc, RT3070_LDO_CFG0, &tmp); tmp &= ~0x1f000000; if (sc->mac_rev < 0x0211) tmp |= 0x0d000000; /* 1.3V */ else tmp |= 0x01000000; /* 1.2V */ run_write(sc, RT3070_LDO_CFG0, tmp); /* patch LNA_PE_G1 */ run_read(sc, RT3070_GPIO_SWITCH, &tmp); run_write(sc, RT3070_GPIO_SWITCH, tmp & ~0x20); } else if (sc->mac_ver == 0x3572) { run_rt3070_rf_read(sc, 6, &rf); run_rt3070_rf_write(sc, 6, rf | 0x40); /* increase voltage from 1.2V to 1.35V */ run_read(sc, RT3070_LDO_CFG0, &tmp); tmp = (tmp & ~0x1f000000) | 0x0d000000; run_write(sc, RT3070_LDO_CFG0, tmp); if (sc->mac_rev < 0x0211 || !sc->patch_dac) { run_delay(sc, 1); /* wait for 1msec */ /* decrease voltage back to 1.2V */ tmp = (tmp & ~0x1f000000) | 0x01000000; run_write(sc, RT3070_LDO_CFG0, tmp); } } /* select 20MHz bandwidth */ run_rt3070_rf_read(sc, 31, &rf); run_rt3070_rf_write(sc, 31, rf & ~0x20); /* calibrate filter for 20MHz bandwidth */ sc->rf24_20mhz = 0x1f; /* default value */ target = (sc->mac_ver < 0x3071) ? 0x16 : 0x13; run_rt3070_filter_calib(sc, 0x07, target, &sc->rf24_20mhz); /* select 40MHz bandwidth */ run_bbp_read(sc, 4, &bbp4); run_bbp_write(sc, 4, (bbp4 & ~0x18) | 0x10); run_rt3070_rf_read(sc, 31, &rf); run_rt3070_rf_write(sc, 31, rf | 0x20); /* calibrate filter for 40MHz bandwidth */ sc->rf24_40mhz = 0x2f; /* default value */ target = (sc->mac_ver < 0x3071) ? 0x19 : 0x15; run_rt3070_filter_calib(sc, 0x27, target, &sc->rf24_40mhz); /* go back to 20MHz bandwidth */ run_bbp_read(sc, 4, &bbp4); run_bbp_write(sc, 4, bbp4 & ~0x18); if (sc->mac_ver == 0x3572) { /* save default BBP registers 25 and 26 values */ run_bbp_read(sc, 25, &sc->bbp25); run_bbp_read(sc, 26, &sc->bbp26); } else if (sc->mac_rev < 0x0201 || sc->mac_rev < 0x0211) run_rt3070_rf_write(sc, 27, 0x03); run_read(sc, RT3070_OPT_14, &tmp); run_write(sc, RT3070_OPT_14, tmp | 1); if (sc->mac_ver == 0x3070 || sc->mac_ver == 0x3071) { run_rt3070_rf_read(sc, 17, &rf); rf &= ~RT3070_TX_LO1; if ((sc->mac_ver == 0x3070 || (sc->mac_ver == 0x3071 && sc->mac_rev >= 0x0211)) && !sc->ext_2ghz_lna) rf |= 0x20; /* fix for long range Rx issue */ mingain = (sc->mac_ver == 0x3070) ? 1 : 2; if (sc->txmixgain_2ghz >= mingain) rf = (rf & ~0x7) | sc->txmixgain_2ghz; run_rt3070_rf_write(sc, 17, rf); } if (sc->mac_ver == 0x3071) { run_rt3070_rf_read(sc, 1, &rf); rf &= ~(RT3070_RX0_PD | RT3070_TX0_PD); rf |= RT3070_RF_BLOCK | RT3070_RX1_PD | RT3070_TX1_PD; run_rt3070_rf_write(sc, 1, rf); run_rt3070_rf_read(sc, 15, &rf); run_rt3070_rf_write(sc, 15, rf & ~RT3070_TX_LO2); run_rt3070_rf_read(sc, 20, &rf); run_rt3070_rf_write(sc, 20, rf & ~RT3070_RX_LO1); run_rt3070_rf_read(sc, 21, &rf); run_rt3070_rf_write(sc, 21, rf & ~RT3070_RX_LO2); } if (sc->mac_ver == 0x3070 || sc->mac_ver == 0x3071) { /* fix Tx to Rx IQ glitch by raising RF voltage */ run_rt3070_rf_read(sc, 27, &rf); rf &= ~0x77; if (sc->mac_rev < 0x0211) rf |= 0x03; run_rt3070_rf_write(sc, 27, rf); } return (0); } static void run_rt3593_rf_init(struct run_softc *sc) { uint32_t tmp; uint8_t rf; u_int i; /* Disable the GPIO bits 4 and 7 for LNA PE control. */ run_read(sc, RT3070_GPIO_SWITCH, &tmp); tmp &= ~(1 << 4 | 1 << 7); run_write(sc, RT3070_GPIO_SWITCH, tmp); /* Initialize RF registers to default value. */ for (i = 0; i < nitems(rt3593_def_rf); i++) { run_rt3070_rf_write(sc, rt3593_def_rf[i].reg, rt3593_def_rf[i].val); } /* Toggle RF R2 to initiate calibration. */ run_rt3070_rf_write(sc, 2, RT5390_RESCAL); /* Initialize RF frequency offset. */ run_adjust_freq_offset(sc); run_rt3070_rf_read(sc, 18, &rf); run_rt3070_rf_write(sc, 18, rf | RT3593_AUTOTUNE_BYPASS); /* * Increase voltage from 1.2V to 1.35V, wait for 1 msec to * decrease voltage back to 1.2V. */ run_read(sc, RT3070_LDO_CFG0, &tmp); tmp = (tmp & ~0x1f000000) | 0x0d000000; run_write(sc, RT3070_LDO_CFG0, tmp); run_delay(sc, 1); tmp = (tmp & ~0x1f000000) | 0x01000000; run_write(sc, RT3070_LDO_CFG0, tmp); sc->rf24_20mhz = 0x1f; sc->rf24_40mhz = 0x2f; /* Save default BBP registers 25 and 26 values. */ run_bbp_read(sc, 25, &sc->bbp25); run_bbp_read(sc, 26, &sc->bbp26); run_read(sc, RT3070_OPT_14, &tmp); run_write(sc, RT3070_OPT_14, tmp | 1); } static void run_rt5390_rf_init(struct run_softc *sc) { uint32_t tmp; uint8_t rf; u_int i; /* Toggle RF R2 to initiate calibration. */ if (sc->mac_ver == 0x5390) { run_rt3070_rf_read(sc, 2, &rf); run_rt3070_rf_write(sc, 2, rf | RT5390_RESCAL); run_delay(sc, 10); run_rt3070_rf_write(sc, 2, rf & ~RT5390_RESCAL); } else { run_rt3070_rf_write(sc, 2, RT5390_RESCAL); run_delay(sc, 10); } /* Initialize RF registers to default value. */ if (sc->mac_ver == 0x5592) { for (i = 0; i < nitems(rt5592_def_rf); i++) { run_rt3070_rf_write(sc, rt5592_def_rf[i].reg, rt5592_def_rf[i].val); } /* Initialize RF frequency offset. */ run_adjust_freq_offset(sc); } else if (sc->mac_ver == 0x5392) { for (i = 0; i < nitems(rt5392_def_rf); i++) { run_rt3070_rf_write(sc, rt5392_def_rf[i].reg, rt5392_def_rf[i].val); } if (sc->mac_rev >= 0x0223) { run_rt3070_rf_write(sc, 23, 0x0f); run_rt3070_rf_write(sc, 24, 0x3e); run_rt3070_rf_write(sc, 51, 0x32); run_rt3070_rf_write(sc, 53, 0x22); run_rt3070_rf_write(sc, 56, 0xc1); run_rt3070_rf_write(sc, 59, 0x0f); } } else { for (i = 0; i < nitems(rt5390_def_rf); i++) { run_rt3070_rf_write(sc, rt5390_def_rf[i].reg, rt5390_def_rf[i].val); } if (sc->mac_rev >= 0x0502) { run_rt3070_rf_write(sc, 6, 0xe0); run_rt3070_rf_write(sc, 25, 0x80); run_rt3070_rf_write(sc, 46, 0x73); run_rt3070_rf_write(sc, 53, 0x00); run_rt3070_rf_write(sc, 56, 0x42); run_rt3070_rf_write(sc, 61, 0xd1); } } sc->rf24_20mhz = 0x1f; /* default value */ sc->rf24_40mhz = (sc->mac_ver == 0x5592) ? 0 : 0x2f; if (sc->mac_rev < 0x0211) run_rt3070_rf_write(sc, 27, 0x3); run_read(sc, RT3070_OPT_14, &tmp); run_write(sc, RT3070_OPT_14, tmp | 1); } static int run_rt3070_filter_calib(struct run_softc *sc, uint8_t init, uint8_t target, uint8_t *val) { uint8_t rf22, rf24; uint8_t bbp55_pb, bbp55_sb, delta; int ntries; /* program filter */ run_rt3070_rf_read(sc, 24, &rf24); rf24 = (rf24 & 0xc0) | init; /* initial filter value */ run_rt3070_rf_write(sc, 24, rf24); /* enable baseband loopback mode */ run_rt3070_rf_read(sc, 22, &rf22); run_rt3070_rf_write(sc, 22, rf22 | 0x01); /* set power and frequency of passband test tone */ run_bbp_write(sc, 24, 0x00); for (ntries = 0; ntries < 100; ntries++) { /* transmit test tone */ run_bbp_write(sc, 25, 0x90); run_delay(sc, 10); /* read received power */ run_bbp_read(sc, 55, &bbp55_pb); if (bbp55_pb != 0) break; } if (ntries == 100) return (ETIMEDOUT); /* set power and frequency of stopband test tone */ run_bbp_write(sc, 24, 0x06); for (ntries = 0; ntries < 100; ntries++) { /* transmit test tone */ run_bbp_write(sc, 25, 0x90); run_delay(sc, 10); /* read received power */ run_bbp_read(sc, 55, &bbp55_sb); delta = bbp55_pb - bbp55_sb; if (delta > target) break; /* reprogram filter */ rf24++; run_rt3070_rf_write(sc, 24, rf24); } if (ntries < 100) { if (rf24 != init) rf24--; /* backtrack */ *val = rf24; run_rt3070_rf_write(sc, 24, rf24); } /* restore initial state */ run_bbp_write(sc, 24, 0x00); /* disable baseband loopback mode */ run_rt3070_rf_read(sc, 22, &rf22); run_rt3070_rf_write(sc, 22, rf22 & ~0x01); return (0); } static void run_rt3070_rf_setup(struct run_softc *sc) { uint8_t bbp, rf; int i; if (sc->mac_ver == 0x3572) { /* enable DC filter */ if (sc->mac_rev >= 0x0201) run_bbp_write(sc, 103, 0xc0); run_bbp_read(sc, 138, &bbp); if (sc->ntxchains == 1) bbp |= 0x20; /* turn off DAC1 */ if (sc->nrxchains == 1) bbp &= ~0x02; /* turn off ADC1 */ run_bbp_write(sc, 138, bbp); if (sc->mac_rev >= 0x0211) { /* improve power consumption */ run_bbp_read(sc, 31, &bbp); run_bbp_write(sc, 31, bbp & ~0x03); } run_rt3070_rf_read(sc, 16, &rf); rf = (rf & ~0x07) | sc->txmixgain_2ghz; run_rt3070_rf_write(sc, 16, rf); } else if (sc->mac_ver == 0x3071) { if (sc->mac_rev >= 0x0211) { /* enable DC filter */ run_bbp_write(sc, 103, 0xc0); /* improve power consumption */ run_bbp_read(sc, 31, &bbp); run_bbp_write(sc, 31, bbp & ~0x03); } run_bbp_read(sc, 138, &bbp); if (sc->ntxchains == 1) bbp |= 0x20; /* turn off DAC1 */ if (sc->nrxchains == 1) bbp &= ~0x02; /* turn off ADC1 */ run_bbp_write(sc, 138, bbp); run_write(sc, RT2860_TX_SW_CFG1, 0); if (sc->mac_rev < 0x0211) { run_write(sc, RT2860_TX_SW_CFG2, sc->patch_dac ? 0x2c : 0x0f); } else run_write(sc, RT2860_TX_SW_CFG2, 0); } else if (sc->mac_ver == 0x3070) { if (sc->mac_rev >= 0x0201) { /* enable DC filter */ run_bbp_write(sc, 103, 0xc0); /* improve power consumption */ run_bbp_read(sc, 31, &bbp); run_bbp_write(sc, 31, bbp & ~0x03); } if (sc->mac_rev < 0x0201) { run_write(sc, RT2860_TX_SW_CFG1, 0); run_write(sc, RT2860_TX_SW_CFG2, 0x2c); } else run_write(sc, RT2860_TX_SW_CFG2, 0); } /* initialize RF registers from ROM for >=RT3071*/ if (sc->mac_ver >= 0x3071) { for (i = 0; i < 10; i++) { if (sc->rf[i].reg == 0 || sc->rf[i].reg == 0xff) continue; run_rt3070_rf_write(sc, sc->rf[i].reg, sc->rf[i].val); } } } static void run_rt3593_rf_setup(struct run_softc *sc) { uint8_t bbp, rf; if (sc->mac_rev >= 0x0211) { /* Enable DC filter. */ run_bbp_write(sc, 103, 0xc0); } run_write(sc, RT2860_TX_SW_CFG1, 0); if (sc->mac_rev < 0x0211) { run_write(sc, RT2860_TX_SW_CFG2, sc->patch_dac ? 0x2c : 0x0f); } else run_write(sc, RT2860_TX_SW_CFG2, 0); run_rt3070_rf_read(sc, 50, &rf); run_rt3070_rf_write(sc, 50, rf & ~RT3593_TX_LO2); run_rt3070_rf_read(sc, 51, &rf); rf = (rf & ~(RT3593_TX_LO1 | 0x0c)) | ((sc->txmixgain_2ghz & 0x07) << 2); run_rt3070_rf_write(sc, 51, rf); run_rt3070_rf_read(sc, 38, &rf); run_rt3070_rf_write(sc, 38, rf & ~RT5390_RX_LO1); run_rt3070_rf_read(sc, 39, &rf); run_rt3070_rf_write(sc, 39, rf & ~RT5390_RX_LO2); run_rt3070_rf_read(sc, 1, &rf); run_rt3070_rf_write(sc, 1, rf & ~(RT3070_RF_BLOCK | RT3070_PLL_PD)); run_rt3070_rf_read(sc, 30, &rf); rf = (rf & ~0x18) | 0x10; run_rt3070_rf_write(sc, 30, rf); /* Apply maximum likelihood detection for 2 stream case. */ run_bbp_read(sc, 105, &bbp); if (sc->nrxchains > 1) run_bbp_write(sc, 105, bbp | RT5390_MLD); /* Avoid data lost and CRC error. */ run_bbp_read(sc, 4, &bbp); run_bbp_write(sc, 4, bbp | RT5390_MAC_IF_CTRL); run_bbp_write(sc, 92, 0x02); run_bbp_write(sc, 82, 0x82); run_bbp_write(sc, 106, 0x05); run_bbp_write(sc, 104, 0x92); run_bbp_write(sc, 88, 0x90); run_bbp_write(sc, 148, 0xc8); run_bbp_write(sc, 47, 0x48); run_bbp_write(sc, 120, 0x50); run_bbp_write(sc, 163, 0x9d); /* SNR mapping. */ run_bbp_write(sc, 142, 0x06); run_bbp_write(sc, 143, 0xa0); run_bbp_write(sc, 142, 0x07); run_bbp_write(sc, 143, 0xa1); run_bbp_write(sc, 142, 0x08); run_bbp_write(sc, 143, 0xa2); run_bbp_write(sc, 31, 0x08); run_bbp_write(sc, 68, 0x0b); run_bbp_write(sc, 105, 0x04); } static void run_rt5390_rf_setup(struct run_softc *sc) { uint8_t bbp, rf; if (sc->mac_rev >= 0x0211) { /* Enable DC filter. */ run_bbp_write(sc, 103, 0xc0); if (sc->mac_ver != 0x5592) { /* Improve power consumption. */ run_bbp_read(sc, 31, &bbp); run_bbp_write(sc, 31, bbp & ~0x03); } } run_bbp_read(sc, 138, &bbp); if (sc->ntxchains == 1) bbp |= 0x20; /* turn off DAC1 */ if (sc->nrxchains == 1) bbp &= ~0x02; /* turn off ADC1 */ run_bbp_write(sc, 138, bbp); run_rt3070_rf_read(sc, 38, &rf); run_rt3070_rf_write(sc, 38, rf & ~RT5390_RX_LO1); run_rt3070_rf_read(sc, 39, &rf); run_rt3070_rf_write(sc, 39, rf & ~RT5390_RX_LO2); /* Avoid data lost and CRC error. */ run_bbp_read(sc, 4, &bbp); run_bbp_write(sc, 4, bbp | RT5390_MAC_IF_CTRL); run_rt3070_rf_read(sc, 30, &rf); rf = (rf & ~0x18) | 0x10; run_rt3070_rf_write(sc, 30, rf); if (sc->mac_ver != 0x5592) { run_write(sc, RT2860_TX_SW_CFG1, 0); if (sc->mac_rev < 0x0211) { run_write(sc, RT2860_TX_SW_CFG2, sc->patch_dac ? 0x2c : 0x0f); } else run_write(sc, RT2860_TX_SW_CFG2, 0); } } static int run_txrx_enable(struct run_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t tmp; int error, ntries; run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_TX_EN); for (ntries = 0; ntries < 200; ntries++) { if ((error = run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp)) != 0) return (error); if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0) break; run_delay(sc, 50); } if (ntries == 200) return (ETIMEDOUT); run_delay(sc, 50); tmp |= RT2860_RX_DMA_EN | RT2860_TX_DMA_EN | RT2860_TX_WB_DDONE; run_write(sc, RT2860_WPDMA_GLO_CFG, tmp); /* enable Rx bulk aggregation (set timeout and limit) */ tmp = RT2860_USB_TX_EN | RT2860_USB_RX_EN | RT2860_USB_RX_AGG_EN | RT2860_USB_RX_AGG_TO(128) | RT2860_USB_RX_AGG_LMT(2); run_write(sc, RT2860_USB_DMA_CFG, tmp); /* set Rx filter */ tmp = RT2860_DROP_CRC_ERR | RT2860_DROP_PHY_ERR; if (ic->ic_opmode != IEEE80211_M_MONITOR) { tmp |= RT2860_DROP_UC_NOME | RT2860_DROP_DUPL | RT2860_DROP_CTS | RT2860_DROP_BA | RT2860_DROP_ACK | RT2860_DROP_VER_ERR | RT2860_DROP_CTRL_RSV | RT2860_DROP_CFACK | RT2860_DROP_CFEND; if (ic->ic_opmode == IEEE80211_M_STA) tmp |= RT2860_DROP_RTS | RT2860_DROP_PSPOLL; } run_write(sc, RT2860_RX_FILTR_CFG, tmp); run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_RX_EN | RT2860_MAC_TX_EN); return (0); } static void run_adjust_freq_offset(struct run_softc *sc) { uint8_t rf, tmp; run_rt3070_rf_read(sc, 17, &rf); tmp = rf; rf = (rf & ~0x7f) | (sc->freq & 0x7f); rf = MIN(rf, 0x5f); if (tmp != rf) run_mcu_cmd(sc, 0x74, (tmp << 8 ) | rf); } static void run_init_locked(struct run_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t tmp; uint8_t bbp1, bbp3; int i; int ridx; int ntries; if (ic->ic_nrunning > 1) return; run_stop(sc); if (run_load_microcode(sc) != 0) { device_printf(sc->sc_dev, "could not load 8051 microcode\n"); goto fail; } for (ntries = 0; ntries < 100; ntries++) { if (run_read(sc, RT2860_ASIC_VER_ID, &tmp) != 0) goto fail; if (tmp != 0 && tmp != 0xffffffff) break; run_delay(sc, 10); } if (ntries == 100) goto fail; for (i = 0; i != RUN_EP_QUEUES; i++) run_setup_tx_list(sc, &sc->sc_epq[i]); run_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr); for (ntries = 0; ntries < 100; ntries++) { if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0) goto fail; if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0) break; run_delay(sc, 10); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for DMA engine\n"); goto fail; } tmp &= 0xff0; tmp |= RT2860_TX_WB_DDONE; run_write(sc, RT2860_WPDMA_GLO_CFG, tmp); /* turn off PME_OEN to solve high-current issue */ run_read(sc, RT2860_SYS_CTRL, &tmp); run_write(sc, RT2860_SYS_CTRL, tmp & ~RT2860_PME_OEN); run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_BBP_HRST | RT2860_MAC_SRST); run_write(sc, RT2860_USB_DMA_CFG, 0); if (run_reset(sc) != 0) { device_printf(sc->sc_dev, "could not reset chipset\n"); goto fail; } run_write(sc, RT2860_MAC_SYS_CTRL, 0); /* init Tx power for all Tx rates (from EEPROM) */ for (ridx = 0; ridx < 5; ridx++) { if (sc->txpow20mhz[ridx] == 0xffffffff) continue; run_write(sc, RT2860_TX_PWR_CFG(ridx), sc->txpow20mhz[ridx]); } for (i = 0; i < nitems(rt2870_def_mac); i++) run_write(sc, rt2870_def_mac[i].reg, rt2870_def_mac[i].val); run_write(sc, RT2860_WMM_AIFSN_CFG, 0x00002273); run_write(sc, RT2860_WMM_CWMIN_CFG, 0x00002344); run_write(sc, RT2860_WMM_CWMAX_CFG, 0x000034aa); if (sc->mac_ver >= 0x5390) { run_write(sc, RT2860_TX_SW_CFG0, 4 << RT2860_DLY_PAPE_EN_SHIFT | 4); if (sc->mac_ver >= 0x5392) { run_write(sc, RT2860_MAX_LEN_CFG, 0x00002fff); if (sc->mac_ver == 0x5592) { run_write(sc, RT2860_HT_FBK_CFG1, 0xedcba980); run_write(sc, RT2860_TXOP_HLDR_ET, 0x00000082); } else { run_write(sc, RT2860_HT_FBK_CFG1, 0xedcb4980); run_write(sc, RT2860_LG_FBK_CFG0, 0xedcba322); } } } else if (sc->mac_ver == 0x3593) { run_write(sc, RT2860_TX_SW_CFG0, 4 << RT2860_DLY_PAPE_EN_SHIFT | 2); } else if (sc->mac_ver >= 0x3070) { /* set delay of PA_PE assertion to 1us (unit of 0.25us) */ run_write(sc, RT2860_TX_SW_CFG0, 4 << RT2860_DLY_PAPE_EN_SHIFT); } /* wait while MAC is busy */ for (ntries = 0; ntries < 100; ntries++) { if (run_read(sc, RT2860_MAC_STATUS_REG, &tmp) != 0) goto fail; if (!(tmp & (RT2860_RX_STATUS_BUSY | RT2860_TX_STATUS_BUSY))) break; run_delay(sc, 10); } if (ntries == 100) goto fail; /* clear Host to MCU mailbox */ run_write(sc, RT2860_H2M_BBPAGENT, 0); run_write(sc, RT2860_H2M_MAILBOX, 0); run_delay(sc, 10); if (run_bbp_init(sc) != 0) { device_printf(sc->sc_dev, "could not initialize BBP\n"); goto fail; } /* abort TSF synchronization */ run_disable_tsf(sc); /* clear RX WCID search table */ run_set_region_4(sc, RT2860_WCID_ENTRY(0), 0, 512); /* clear WCID attribute table */ run_set_region_4(sc, RT2860_WCID_ATTR(0), 0, 8 * 32); /* hostapd sets a key before init. So, don't clear it. */ if (sc->cmdq_key_set != RUN_CMDQ_GO) { /* clear shared key table */ run_set_region_4(sc, RT2860_SKEY(0, 0), 0, 8 * 32); /* clear shared key mode */ run_set_region_4(sc, RT2860_SKEY_MODE_0_7, 0, 4); } run_read(sc, RT2860_US_CYC_CNT, &tmp); tmp = (tmp & ~0xff) | 0x1e; run_write(sc, RT2860_US_CYC_CNT, tmp); if (sc->mac_rev != 0x0101) run_write(sc, RT2860_TXOP_CTRL_CFG, 0x0000583f); run_write(sc, RT2860_WMM_TXOP0_CFG, 0); run_write(sc, RT2860_WMM_TXOP1_CFG, 48 << 16 | 96); /* write vendor-specific BBP values (from EEPROM) */ if (sc->mac_ver < 0x3593) { for (i = 0; i < 10; i++) { if (sc->bbp[i].reg == 0 || sc->bbp[i].reg == 0xff) continue; run_bbp_write(sc, sc->bbp[i].reg, sc->bbp[i].val); } } /* select Main antenna for 1T1R devices */ if (sc->rf_rev == RT3070_RF_3020 || sc->rf_rev == RT5390_RF_5370) run_set_rx_antenna(sc, 0); /* send LEDs operating mode to microcontroller */ (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED1, sc->led[0]); (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED2, sc->led[1]); (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED3, sc->led[2]); if (sc->mac_ver >= 0x5390) run_rt5390_rf_init(sc); else if (sc->mac_ver == 0x3593) run_rt3593_rf_init(sc); else if (sc->mac_ver >= 0x3070) run_rt3070_rf_init(sc); /* disable non-existing Rx chains */ run_bbp_read(sc, 3, &bbp3); bbp3 &= ~(1 << 3 | 1 << 4); if (sc->nrxchains == 2) bbp3 |= 1 << 3; else if (sc->nrxchains == 3) bbp3 |= 1 << 4; run_bbp_write(sc, 3, bbp3); /* disable non-existing Tx chains */ run_bbp_read(sc, 1, &bbp1); if (sc->ntxchains == 1) bbp1 &= ~(1 << 3 | 1 << 4); run_bbp_write(sc, 1, bbp1); if (sc->mac_ver >= 0x5390) run_rt5390_rf_setup(sc); else if (sc->mac_ver == 0x3593) run_rt3593_rf_setup(sc); else if (sc->mac_ver >= 0x3070) run_rt3070_rf_setup(sc); /* select default channel */ run_set_chan(sc, ic->ic_curchan); /* setup initial protection mode */ run_updateprot_cb(ic); /* turn radio LED on */ run_set_leds(sc, RT2860_LED_RADIO); sc->sc_flags |= RUN_RUNNING; sc->cmdq_run = RUN_CMDQ_GO; for (i = 0; i != RUN_N_XFER; i++) usbd_xfer_set_stall(sc->sc_xfer[i]); usbd_transfer_start(sc->sc_xfer[RUN_BULK_RX]); if (run_txrx_enable(sc) != 0) goto fail; return; fail: run_stop(sc); } static void run_stop(void *arg) { struct run_softc *sc = (struct run_softc *)arg; uint32_t tmp; int i; int ntries; RUN_LOCK_ASSERT(sc, MA_OWNED); if (sc->sc_flags & RUN_RUNNING) run_set_leds(sc, 0); /* turn all LEDs off */ sc->sc_flags &= ~RUN_RUNNING; sc->ratectl_run = RUN_RATECTL_OFF; sc->cmdq_run = sc->cmdq_key_set; RUN_UNLOCK(sc); for(i = 0; i < RUN_N_XFER; i++) usbd_transfer_drain(sc->sc_xfer[i]); RUN_LOCK(sc); run_drain_mbufq(sc); if (sc->rx_m != NULL) { m_free(sc->rx_m); sc->rx_m = NULL; } /* Disable Tx/Rx DMA. */ if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0) return; tmp &= ~(RT2860_RX_DMA_EN | RT2860_TX_DMA_EN); run_write(sc, RT2860_WPDMA_GLO_CFG, tmp); for (ntries = 0; ntries < 100; ntries++) { if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0) return; if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0) break; run_delay(sc, 10); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for DMA engine\n"); return; } /* disable Tx/Rx */ run_read(sc, RT2860_MAC_SYS_CTRL, &tmp); tmp &= ~(RT2860_MAC_RX_EN | RT2860_MAC_TX_EN); run_write(sc, RT2860_MAC_SYS_CTRL, tmp); /* wait for pending Tx to complete */ for (ntries = 0; ntries < 100; ntries++) { if (run_read(sc, RT2860_TXRXQ_PCNT, &tmp) != 0) { RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_RESET, "Cannot read Tx queue count\n"); break; } if ((tmp & RT2860_TX2Q_PCNT_MASK) == 0) { RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_RESET, "All Tx cleared\n"); break; } run_delay(sc, 10); } if (ntries >= 100) RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_RESET, "There are still pending Tx\n"); run_delay(sc, 10); run_write(sc, RT2860_USB_DMA_CFG, 0); run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_BBP_HRST | RT2860_MAC_SRST); run_write(sc, RT2860_MAC_SYS_CTRL, 0); for (i = 0; i != RUN_EP_QUEUES; i++) run_unsetup_tx_list(sc, &sc->sc_epq[i]); } static void run_delay(struct run_softc *sc, u_int ms) { usb_pause_mtx(mtx_owned(&sc->sc_mtx) ? &sc->sc_mtx : NULL, USB_MS_TO_TICKS(ms)); } static device_method_t run_methods[] = { /* Device interface */ DEVMETHOD(device_probe, run_match), DEVMETHOD(device_attach, run_attach), DEVMETHOD(device_detach, run_detach), DEVMETHOD_END }; static driver_t run_driver = { .name = "run", .methods = run_methods, .size = sizeof(struct run_softc) }; static devclass_t run_devclass; DRIVER_MODULE(run, uhub, run_driver, run_devclass, run_driver_loaded, NULL); MODULE_DEPEND(run, wlan, 1, 1, 1); MODULE_DEPEND(run, usb, 1, 1, 1); MODULE_DEPEND(run, firmware, 1, 1, 1); MODULE_VERSION(run, 1); USB_PNP_HOST_INFO(run_devs); Index: head/sys/dev/usb/wlan/if_uath.c =================================================================== --- head/sys/dev/usb/wlan/if_uath.c (revision 357971) +++ head/sys/dev/usb/wlan/if_uath.c (revision 357972) @@ -1,2878 +1,2879 @@ /*- * SPDX-License-Identifier: (BSD-2-Clause-FreeBSD AND BSD-1-Clause) * * Copyright (c) 2006 Sam Leffler, Errno Consulting * Copyright (c) 2008-2009 Weongyo Jeong * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ /* * This driver is distantly derived from a driver of the same name * by Damien Bergamini. The original copyright is included below: * * Copyright (c) 2006 * Damien Bergamini * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /*- * Driver for Atheros AR5523 USB parts. * * The driver requires firmware to be loaded into the device. This * is done on device discovery from a user application (uathload) * that is launched by devd when a device with suitable product ID * is recognized. Once firmware has been loaded the device will * reset the USB port and re-attach with the original product ID+1 * and this driver will be attached. The firmware is licensed for * general use (royalty free) and may be incorporated in products. * Note that the firmware normally packaged with the NDIS drivers * for these devices does not work in this way and so does not work * with this driver. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #include #include #include #include #include #include #include "usbdevs.h" #include #include -static SYSCTL_NODE(_hw_usb, OID_AUTO, uath, CTLFLAG_RW, 0, "USB Atheros"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, uath, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB Atheros"); static int uath_countrycode = CTRY_DEFAULT; /* country code */ SYSCTL_INT(_hw_usb_uath, OID_AUTO, countrycode, CTLFLAG_RWTUN, &uath_countrycode, 0, "country code"); static int uath_regdomain = 0; /* regulatory domain */ SYSCTL_INT(_hw_usb_uath, OID_AUTO, regdomain, CTLFLAG_RD, &uath_regdomain, 0, "regulatory domain"); #ifdef UATH_DEBUG int uath_debug = 0; SYSCTL_INT(_hw_usb_uath, OID_AUTO, debug, CTLFLAG_RWTUN, &uath_debug, 0, "uath debug level"); enum { UATH_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ UATH_DEBUG_XMIT_DUMP = 0x00000002, /* xmit dump */ UATH_DEBUG_RECV = 0x00000004, /* basic recv operation */ UATH_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */ UATH_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */ UATH_DEBUG_RECV_ALL = 0x00000020, /* trace all frames (beacons) */ UATH_DEBUG_INIT = 0x00000040, /* initialization of dev */ UATH_DEBUG_DEVCAP = 0x00000080, /* dev caps */ UATH_DEBUG_CMDS = 0x00000100, /* commands */ UATH_DEBUG_CMDS_DUMP = 0x00000200, /* command buffer dump */ UATH_DEBUG_RESET = 0x00000400, /* reset processing */ UATH_DEBUG_STATE = 0x00000800, /* 802.11 state transitions */ UATH_DEBUG_MULTICAST = 0x00001000, /* multicast */ UATH_DEBUG_WME = 0x00002000, /* WME */ UATH_DEBUG_CHANNEL = 0x00004000, /* channel */ UATH_DEBUG_RATES = 0x00008000, /* rates */ UATH_DEBUG_CRYPTO = 0x00010000, /* crypto */ UATH_DEBUG_LED = 0x00020000, /* LED */ UATH_DEBUG_ANY = 0xffffffff }; #define DPRINTF(sc, m, fmt, ...) do { \ if (sc->sc_debug & (m)) \ printf(fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, m, fmt, ...) do { \ (void) sc; \ } while (0) #endif /* recognized device vendors/products */ static const STRUCT_USB_HOST_ID uath_devs[] = { #define UATH_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } UATH_DEV(ACCTON, SMCWUSBTG2), UATH_DEV(ATHEROS, AR5523), UATH_DEV(ATHEROS2, AR5523_1), UATH_DEV(ATHEROS2, AR5523_2), UATH_DEV(ATHEROS2, AR5523_3), UATH_DEV(CONCEPTRONIC, AR5523_1), UATH_DEV(CONCEPTRONIC, AR5523_2), UATH_DEV(DLINK, DWLAG122), UATH_DEV(DLINK, DWLAG132), UATH_DEV(DLINK, DWLG132), UATH_DEV(DLINK2, DWA120), UATH_DEV(GIGASET, AR5523), UATH_DEV(GIGASET, SMCWUSBTG), UATH_DEV(GLOBALSUN, AR5523_1), UATH_DEV(GLOBALSUN, AR5523_2), UATH_DEV(NETGEAR, WG111U), UATH_DEV(NETGEAR3, WG111T), UATH_DEV(NETGEAR3, WPN111), UATH_DEV(NETGEAR3, WPN111_2), UATH_DEV(UMEDIA, TEW444UBEU), UATH_DEV(UMEDIA, AR5523_2), UATH_DEV(WISTRONNEWEB, AR5523_1), UATH_DEV(WISTRONNEWEB, AR5523_2), UATH_DEV(ZCOM, AR5523) #undef UATH_DEV }; static usb_callback_t uath_intr_rx_callback; static usb_callback_t uath_intr_tx_callback; static usb_callback_t uath_bulk_rx_callback; static usb_callback_t uath_bulk_tx_callback; static const struct usb_config uath_usbconfig[UATH_N_XFERS] = { [UATH_INTR_RX] = { .type = UE_BULK, .endpoint = 0x1, .direction = UE_DIR_IN, .bufsize = UATH_MAX_CMDSZ, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = uath_intr_rx_callback }, [UATH_INTR_TX] = { .type = UE_BULK, .endpoint = 0x1, .direction = UE_DIR_OUT, .bufsize = UATH_MAX_CMDSZ * UATH_CMD_LIST_COUNT, .flags = { .force_short_xfer = 1, .pipe_bof = 1, }, .callback = uath_intr_tx_callback, .timeout = UATH_CMD_TIMEOUT }, [UATH_BULK_RX] = { .type = UE_BULK, .endpoint = 0x2, .direction = UE_DIR_IN, .bufsize = MCLBYTES, .flags = { .ext_buffer = 1, .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = uath_bulk_rx_callback }, [UATH_BULK_TX] = { .type = UE_BULK, .endpoint = 0x2, .direction = UE_DIR_OUT, .bufsize = UATH_MAX_TXBUFSZ * UATH_TX_DATA_LIST_COUNT, .flags = { .force_short_xfer = 1, .pipe_bof = 1 }, .callback = uath_bulk_tx_callback, .timeout = UATH_DATA_TIMEOUT } }; static struct ieee80211vap *uath_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void uath_vap_delete(struct ieee80211vap *); static int uath_alloc_cmd_list(struct uath_softc *, struct uath_cmd []); static void uath_free_cmd_list(struct uath_softc *, struct uath_cmd []); static int uath_host_available(struct uath_softc *); static int uath_get_capability(struct uath_softc *, uint32_t, uint32_t *); static int uath_get_devcap(struct uath_softc *); static struct uath_cmd * uath_get_cmdbuf(struct uath_softc *); static int uath_cmd_read(struct uath_softc *, uint32_t, const void *, int, void *, int, int); static int uath_cmd_write(struct uath_softc *, uint32_t, const void *, int, int); static void uath_stat(void *); #ifdef UATH_DEBUG static void uath_dump_cmd(const uint8_t *, int, char); static const char * uath_codename(int); #endif static int uath_get_devstatus(struct uath_softc *, uint8_t macaddr[IEEE80211_ADDR_LEN]); static int uath_get_status(struct uath_softc *, uint32_t, void *, int); static int uath_alloc_rx_data_list(struct uath_softc *); static int uath_alloc_tx_data_list(struct uath_softc *); static void uath_free_rx_data_list(struct uath_softc *); static void uath_free_tx_data_list(struct uath_softc *); static int uath_init(struct uath_softc *); static void uath_stop(struct uath_softc *); static void uath_parent(struct ieee80211com *); static int uath_transmit(struct ieee80211com *, struct mbuf *); static void uath_start(struct uath_softc *); static int uath_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void uath_scan_start(struct ieee80211com *); static void uath_scan_end(struct ieee80211com *); static void uath_set_channel(struct ieee80211com *); static void uath_update_mcast(struct ieee80211com *); static void uath_update_promisc(struct ieee80211com *); static int uath_config(struct uath_softc *, uint32_t, uint32_t); static int uath_config_multi(struct uath_softc *, uint32_t, const void *, int); static int uath_switch_channel(struct uath_softc *, struct ieee80211_channel *); static int uath_set_rxfilter(struct uath_softc *, uint32_t, uint32_t); static void uath_watchdog(void *); static void uath_abort_xfers(struct uath_softc *); static int uath_dataflush(struct uath_softc *); static int uath_cmdflush(struct uath_softc *); static int uath_flush(struct uath_softc *); static int uath_set_ledstate(struct uath_softc *, int); static int uath_set_chan(struct uath_softc *, struct ieee80211_channel *); static int uath_reset_tx_queues(struct uath_softc *); static int uath_wme_init(struct uath_softc *); static struct uath_data * uath_getbuf(struct uath_softc *); static int uath_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int uath_set_key(struct uath_softc *, const struct ieee80211_key *, int); static int uath_set_keys(struct uath_softc *, struct ieee80211vap *); static void uath_sysctl_node(struct uath_softc *); static int uath_match(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != UATH_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != UATH_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(uath_devs, sizeof(uath_devs), uaa)); } static int uath_attach(device_t dev) { struct uath_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct ieee80211com *ic = &sc->sc_ic; uint8_t bands[IEEE80211_MODE_BYTES]; uint8_t iface_index = UATH_IFACE_INDEX; /* XXX */ usb_error_t error; sc->sc_dev = dev; sc->sc_udev = uaa->device; #ifdef UATH_DEBUG sc->sc_debug = uath_debug; #endif device_set_usb_desc(dev); /* * Only post-firmware devices here. */ mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init(&sc->stat_ch, 0); callout_init_mtx(&sc->watchdog_ch, &sc->sc_mtx, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, uath_usbconfig, UATH_N_XFERS, sc, &sc->sc_mtx); if (error) { device_printf(dev, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto fail; } sc->sc_cmd_dma_buf = usbd_xfer_get_frame_buffer(sc->sc_xfer[UATH_INTR_TX], 0); sc->sc_tx_dma_buf = usbd_xfer_get_frame_buffer(sc->sc_xfer[UATH_BULK_TX], 0); /* * Setup buffers for firmware commands. */ error = uath_alloc_cmd_list(sc, sc->sc_cmd); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Tx command list\n"); goto fail1; } /* * We're now ready to send+receive firmware commands. */ UATH_LOCK(sc); error = uath_host_available(sc); if (error != 0) { device_printf(sc->sc_dev, "could not initialize adapter\n"); goto fail2; } error = uath_get_devcap(sc); if (error != 0) { device_printf(sc->sc_dev, "could not get device capabilities\n"); goto fail2; } UATH_UNLOCK(sc); /* Create device sysctl node. */ uath_sysctl_node(sc); UATH_LOCK(sc); error = uath_get_devstatus(sc, ic->ic_macaddr); if (error != 0) { device_printf(sc->sc_dev, "could not get device status\n"); goto fail2; } /* * Allocate xfers for Rx/Tx data pipes. */ error = uath_alloc_rx_data_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Rx data list\n"); goto fail2; } error = uath_alloc_tx_data_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Tx data list\n"); goto fail2; } UATH_UNLOCK(sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA | /* station mode */ IEEE80211_C_MONITOR | /* monitor mode supported */ IEEE80211_C_TXPMGT | /* tx power management */ IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_WPA | /* 802.11i */ IEEE80211_C_BGSCAN | /* capable of bg scanning */ IEEE80211_C_TXFRAG; /* handle tx frags */ /* put a regulatory domain to reveal informations. */ uath_regdomain = sc->sc_devcap.regDomain; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); if ((sc->sc_devcap.analog5GhzRevision & 0xf0) == 0x30) setbit(bands, IEEE80211_MODE_11A); /* XXX turbo */ ieee80211_init_channels(ic, NULL, bands); ieee80211_ifattach(ic); ic->ic_raw_xmit = uath_raw_xmit; ic->ic_scan_start = uath_scan_start; ic->ic_scan_end = uath_scan_end; ic->ic_set_channel = uath_set_channel; ic->ic_vap_create = uath_vap_create; ic->ic_vap_delete = uath_vap_delete; ic->ic_update_mcast = uath_update_mcast; ic->ic_update_promisc = uath_update_promisc; ic->ic_transmit = uath_transmit; ic->ic_parent = uath_parent; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), UATH_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), UATH_RX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); return (0); fail2: UATH_UNLOCK(sc); uath_free_cmd_list(sc, sc->sc_cmd); fail1: usbd_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS); fail: return (error); } static int uath_detach(device_t dev) { struct uath_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; unsigned int x; /* * Prevent further allocations from RX/TX/CMD * data lists and ioctls */ UATH_LOCK(sc); sc->sc_flags |= UATH_FLAG_INVALID; STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); STAILQ_INIT(&sc->sc_cmd_active); STAILQ_INIT(&sc->sc_cmd_pending); STAILQ_INIT(&sc->sc_cmd_waiting); STAILQ_INIT(&sc->sc_cmd_inactive); uath_stop(sc); UATH_UNLOCK(sc); callout_drain(&sc->stat_ch); callout_drain(&sc->watchdog_ch); /* drain USB transfers */ for (x = 0; x != UATH_N_XFERS; x++) usbd_transfer_drain(sc->sc_xfer[x]); /* free data buffers */ UATH_LOCK(sc); uath_free_rx_data_list(sc); uath_free_tx_data_list(sc); uath_free_cmd_list(sc, sc->sc_cmd); UATH_UNLOCK(sc); /* free USB transfers and some data buffers */ usbd_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS); ieee80211_ifdetach(ic); mbufq_drain(&sc->sc_snd); mtx_destroy(&sc->sc_mtx); return (0); } static void uath_free_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[]) { int i; for (i = 0; i != UATH_CMD_LIST_COUNT; i++) cmds[i].buf = NULL; } static int uath_alloc_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[]) { int i; STAILQ_INIT(&sc->sc_cmd_active); STAILQ_INIT(&sc->sc_cmd_pending); STAILQ_INIT(&sc->sc_cmd_waiting); STAILQ_INIT(&sc->sc_cmd_inactive); for (i = 0; i != UATH_CMD_LIST_COUNT; i++) { struct uath_cmd *cmd = &cmds[i]; cmd->sc = sc; /* backpointer for callbacks */ cmd->msgid = i; cmd->buf = ((uint8_t *)sc->sc_cmd_dma_buf) + (i * UATH_MAX_CMDSZ); STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next); UATH_STAT_INC(sc, st_cmd_inactive); } return (0); } static int uath_host_available(struct uath_softc *sc) { struct uath_cmd_host_available setup; UATH_ASSERT_LOCKED(sc); /* inform target the host is available */ setup.sw_ver_major = htobe32(ATH_SW_VER_MAJOR); setup.sw_ver_minor = htobe32(ATH_SW_VER_MINOR); setup.sw_ver_patch = htobe32(ATH_SW_VER_PATCH); setup.sw_ver_build = htobe32(ATH_SW_VER_BUILD); return uath_cmd_read(sc, WDCMSG_HOST_AVAILABLE, &setup, sizeof setup, NULL, 0, 0); } #ifdef UATH_DEBUG static void uath_dump_cmd(const uint8_t *buf, int len, char prefix) { const char *sep = ""; int i; for (i = 0; i < len; i++) { if ((i % 16) == 0) { printf("%s%c ", sep, prefix); sep = "\n"; } else if ((i % 4) == 0) printf(" "); printf("%02x", buf[i]); } printf("\n"); } static const char * uath_codename(int code) { static const char *names[] = { "0x00", "HOST_AVAILABLE", "BIND", "TARGET_RESET", "TARGET_GET_CAPABILITY", "TARGET_SET_CONFIG", "TARGET_GET_STATUS", "TARGET_GET_STATS", "TARGET_START", "TARGET_STOP", "TARGET_ENABLE", "TARGET_DISABLE", "CREATE_CONNECTION", "UPDATE_CONNECT_ATTR", "DELETE_CONNECT", "SEND", "FLUSH", "STATS_UPDATE", "BMISS", "DEVICE_AVAIL", "SEND_COMPLETE", "DATA_AVAIL", "SET_PWR_MODE", "BMISS_ACK", "SET_LED_STEADY", "SET_LED_BLINK", "SETUP_BEACON_DESC", "BEACON_INIT", "RESET_KEY_CACHE", "RESET_KEY_CACHE_ENTRY", "SET_KEY_CACHE_ENTRY", "SET_DECOMP_MASK", "SET_REGULATORY_DOMAIN", "SET_LED_STATE", "WRITE_ASSOCID", "SET_STA_BEACON_TIMERS", "GET_TSF", "RESET_TSF", "SET_ADHOC_MODE", "SET_BASIC_RATE", "MIB_CONTROL", "GET_CHANNEL_DATA", "GET_CUR_RSSI", "SET_ANTENNA_SWITCH", "0x2c", "0x2d", "0x2e", "USE_SHORT_SLOT_TIME", "SET_POWER_MODE", "SETUP_PSPOLL_DESC", "SET_RX_MULTICAST_FILTER", "RX_FILTER", "PER_CALIBRATION", "RESET", "DISABLE", "PHY_DISABLE", "SET_TX_POWER_LIMIT", "SET_TX_QUEUE_PARAMS", "SETUP_TX_QUEUE", "RELEASE_TX_QUEUE", }; static char buf[8]; if (code < nitems(names)) return names[code]; if (code == WDCMSG_SET_DEFAULT_KEY) return "SET_DEFAULT_KEY"; snprintf(buf, sizeof(buf), "0x%02x", code); return buf; } #endif /* * Low-level function to send read or write commands to the firmware. */ static int uath_cmdsend(struct uath_softc *sc, uint32_t code, const void *idata, int ilen, void *odata, int olen, int flags) { struct uath_cmd_hdr *hdr; struct uath_cmd *cmd; int error; UATH_ASSERT_LOCKED(sc); /* grab a xfer */ cmd = uath_get_cmdbuf(sc); if (cmd == NULL) { device_printf(sc->sc_dev, "%s: empty inactive queue\n", __func__); return (ENOBUFS); } cmd->flags = flags; /* always bulk-out a multiple of 4 bytes */ cmd->buflen = roundup2(sizeof(struct uath_cmd_hdr) + ilen, 4); hdr = (struct uath_cmd_hdr *)cmd->buf; memset(hdr, 0, sizeof(struct uath_cmd_hdr)); hdr->len = htobe32(cmd->buflen); hdr->code = htobe32(code); hdr->msgid = cmd->msgid; /* don't care about endianness */ hdr->magic = htobe32((cmd->flags & UATH_CMD_FLAG_MAGIC) ? 1 << 24 : 0); memcpy((uint8_t *)(hdr + 1), idata, ilen); #ifdef UATH_DEBUG if (sc->sc_debug & UATH_DEBUG_CMDS) { printf("%s: send %s [flags 0x%x] olen %d\n", __func__, uath_codename(code), cmd->flags, olen); if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) uath_dump_cmd(cmd->buf, cmd->buflen, '+'); } #endif cmd->odata = odata; KASSERT(odata == NULL || olen < UATH_MAX_CMDSZ - sizeof(*hdr) + sizeof(uint32_t), ("odata %p olen %u", odata, olen)); cmd->olen = olen; STAILQ_INSERT_TAIL(&sc->sc_cmd_pending, cmd, next); UATH_STAT_INC(sc, st_cmd_pending); usbd_transfer_start(sc->sc_xfer[UATH_INTR_TX]); if (cmd->flags & UATH_CMD_FLAG_READ) { usbd_transfer_start(sc->sc_xfer[UATH_INTR_RX]); /* wait at most two seconds for command reply */ error = mtx_sleep(cmd, &sc->sc_mtx, 0, "uathcmd", 2 * hz); cmd->odata = NULL; /* in case reply comes too late */ if (error != 0) { device_printf(sc->sc_dev, "timeout waiting for reply " "to cmd 0x%x (%u)\n", code, code); } else if (cmd->olen != olen) { device_printf(sc->sc_dev, "unexpected reply data count " "to cmd 0x%x (%u), got %u, expected %u\n", code, code, cmd->olen, olen); error = EINVAL; } return (error); } return (0); } static int uath_cmd_read(struct uath_softc *sc, uint32_t code, const void *idata, int ilen, void *odata, int olen, int flags) { flags |= UATH_CMD_FLAG_READ; return uath_cmdsend(sc, code, idata, ilen, odata, olen, flags); } static int uath_cmd_write(struct uath_softc *sc, uint32_t code, const void *data, int len, int flags) { flags &= ~UATH_CMD_FLAG_READ; return uath_cmdsend(sc, code, data, len, NULL, 0, flags); } static struct uath_cmd * uath_get_cmdbuf(struct uath_softc *sc) { struct uath_cmd *uc; UATH_ASSERT_LOCKED(sc); uc = STAILQ_FIRST(&sc->sc_cmd_inactive); if (uc != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_cmd_inactive, next); UATH_STAT_DEC(sc, st_cmd_inactive); } else uc = NULL; if (uc == NULL) DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__, "out of command xmit buffers"); return (uc); } /* * This function is called periodically (every second) when associated to * query device statistics. */ static void uath_stat(void *arg) { struct uath_softc *sc = arg; int error; UATH_LOCK(sc); /* * Send request for statistics asynchronously. The timer will be * restarted when we'll get the stats notification. */ error = uath_cmd_write(sc, WDCMSG_TARGET_GET_STATS, NULL, 0, UATH_CMD_FLAG_ASYNC); if (error != 0) { device_printf(sc->sc_dev, "could not query stats, error %d\n", error); } UATH_UNLOCK(sc); } static int uath_get_capability(struct uath_softc *sc, uint32_t cap, uint32_t *val) { int error; cap = htobe32(cap); error = uath_cmd_read(sc, WDCMSG_TARGET_GET_CAPABILITY, &cap, sizeof cap, val, sizeof(uint32_t), UATH_CMD_FLAG_MAGIC); if (error != 0) { device_printf(sc->sc_dev, "could not read capability %u\n", be32toh(cap)); return (error); } *val = be32toh(*val); return (error); } static int uath_get_devcap(struct uath_softc *sc) { #define GETCAP(x, v) do { \ error = uath_get_capability(sc, x, &v); \ if (error != 0) \ return (error); \ DPRINTF(sc, UATH_DEBUG_DEVCAP, \ "%s: %s=0x%08x\n", __func__, #x, v); \ } while (0) struct uath_devcap *cap = &sc->sc_devcap; int error; /* collect device capabilities */ GETCAP(CAP_TARGET_VERSION, cap->targetVersion); GETCAP(CAP_TARGET_REVISION, cap->targetRevision); GETCAP(CAP_MAC_VERSION, cap->macVersion); GETCAP(CAP_MAC_REVISION, cap->macRevision); GETCAP(CAP_PHY_REVISION, cap->phyRevision); GETCAP(CAP_ANALOG_5GHz_REVISION, cap->analog5GhzRevision); GETCAP(CAP_ANALOG_2GHz_REVISION, cap->analog2GhzRevision); GETCAP(CAP_REG_DOMAIN, cap->regDomain); GETCAP(CAP_REG_CAP_BITS, cap->regCapBits); #if 0 /* NB: not supported in rev 1.5 */ GETCAP(CAP_COUNTRY_CODE, cap->countryCode); #endif GETCAP(CAP_WIRELESS_MODES, cap->wirelessModes); GETCAP(CAP_CHAN_SPREAD_SUPPORT, cap->chanSpreadSupport); GETCAP(CAP_COMPRESS_SUPPORT, cap->compressSupport); GETCAP(CAP_BURST_SUPPORT, cap->burstSupport); GETCAP(CAP_FAST_FRAMES_SUPPORT, cap->fastFramesSupport); GETCAP(CAP_CHAP_TUNING_SUPPORT, cap->chapTuningSupport); GETCAP(CAP_TURBOG_SUPPORT, cap->turboGSupport); GETCAP(CAP_TURBO_PRIME_SUPPORT, cap->turboPrimeSupport); GETCAP(CAP_DEVICE_TYPE, cap->deviceType); GETCAP(CAP_WME_SUPPORT, cap->wmeSupport); GETCAP(CAP_TOTAL_QUEUES, cap->numTxQueues); GETCAP(CAP_CONNECTION_ID_MAX, cap->connectionIdMax); GETCAP(CAP_LOW_5GHZ_CHAN, cap->low5GhzChan); GETCAP(CAP_HIGH_5GHZ_CHAN, cap->high5GhzChan); GETCAP(CAP_LOW_2GHZ_CHAN, cap->low2GhzChan); GETCAP(CAP_HIGH_2GHZ_CHAN, cap->high2GhzChan); GETCAP(CAP_TWICE_ANTENNAGAIN_5G, cap->twiceAntennaGain5G); GETCAP(CAP_TWICE_ANTENNAGAIN_2G, cap->twiceAntennaGain2G); GETCAP(CAP_CIPHER_AES_CCM, cap->supportCipherAES_CCM); GETCAP(CAP_CIPHER_TKIP, cap->supportCipherTKIP); GETCAP(CAP_MIC_TKIP, cap->supportMicTKIP); cap->supportCipherWEP = 1; /* NB: always available */ return (0); } static int uath_get_devstatus(struct uath_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN]) { int error; /* retrieve MAC address */ error = uath_get_status(sc, ST_MAC_ADDR, macaddr, IEEE80211_ADDR_LEN); if (error != 0) { device_printf(sc->sc_dev, "could not read MAC address\n"); return (error); } error = uath_get_status(sc, ST_SERIAL_NUMBER, &sc->sc_serial[0], sizeof(sc->sc_serial)); if (error != 0) { device_printf(sc->sc_dev, "could not read device serial number\n"); return (error); } return (0); } static int uath_get_status(struct uath_softc *sc, uint32_t which, void *odata, int olen) { int error; which = htobe32(which); error = uath_cmd_read(sc, WDCMSG_TARGET_GET_STATUS, &which, sizeof(which), odata, olen, UATH_CMD_FLAG_MAGIC); if (error != 0) device_printf(sc->sc_dev, "could not read EEPROM offset 0x%02x\n", be32toh(which)); return (error); } static void uath_free_data_list(struct uath_softc *sc, struct uath_data data[], int ndata, int fillmbuf) { int i; for (i = 0; i < ndata; i++) { struct uath_data *dp = &data[i]; if (fillmbuf == 1) { if (dp->m != NULL) { m_freem(dp->m); dp->m = NULL; dp->buf = NULL; } } else { dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static int uath_alloc_data_list(struct uath_softc *sc, struct uath_data data[], int ndata, int maxsz, void *dma_buf) { int i, error; for (i = 0; i < ndata; i++) { struct uath_data *dp = &data[i]; dp->sc = sc; if (dma_buf == NULL) { /* XXX check maxsz */ dp->m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (dp->m == NULL) { device_printf(sc->sc_dev, "could not allocate rx mbuf\n"); error = ENOMEM; goto fail; } dp->buf = mtod(dp->m, uint8_t *); } else { dp->m = NULL; dp->buf = ((uint8_t *)dma_buf) + (i * maxsz); } dp->ni = NULL; } return (0); fail: uath_free_data_list(sc, data, ndata, 1 /* free mbufs */); return (error); } static int uath_alloc_rx_data_list(struct uath_softc *sc) { int error, i; /* XXX is it enough to store the RX packet with MCLBYTES bytes? */ error = uath_alloc_data_list(sc, sc->sc_rx, UATH_RX_DATA_LIST_COUNT, MCLBYTES, NULL /* setup mbufs */); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < UATH_RX_DATA_LIST_COUNT; i++) { STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); UATH_STAT_INC(sc, st_rx_inactive); } return (0); } static int uath_alloc_tx_data_list(struct uath_softc *sc) { int error, i; error = uath_alloc_data_list(sc, sc->sc_tx, UATH_TX_DATA_LIST_COUNT, UATH_MAX_TXBUFSZ, sc->sc_tx_dma_buf); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); for (i = 0; i < UATH_TX_DATA_LIST_COUNT; i++) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); UATH_STAT_INC(sc, st_tx_inactive); } return (0); } static void uath_free_rx_data_list(struct uath_softc *sc) { uath_free_data_list(sc, sc->sc_rx, UATH_RX_DATA_LIST_COUNT, 1 /* free mbufs */); } static void uath_free_tx_data_list(struct uath_softc *sc) { uath_free_data_list(sc, sc->sc_tx, UATH_TX_DATA_LIST_COUNT, 0 /* no mbufs */); } static struct ieee80211vap * uath_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct uath_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct uath_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = uath_newstate; /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void uath_vap_delete(struct ieee80211vap *vap) { struct uath_vap *uvp = UATH_VAP(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static int uath_init(struct uath_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t val; int error; UATH_ASSERT_LOCKED(sc); if (sc->sc_flags & UATH_FLAG_INITDONE) uath_stop(sc); /* reset variables */ sc->sc_intrx_nextnum = sc->sc_msgid = 0; val = htobe32(0); uath_cmd_write(sc, WDCMSG_BIND, &val, sizeof val, 0); /* set MAC address */ uath_config_multi(sc, CFG_MAC_ADDR, vap ? vap->iv_myaddr : ic->ic_macaddr, IEEE80211_ADDR_LEN); /* XXX honor net80211 state */ uath_config(sc, CFG_RATE_CONTROL_ENABLE, 0x00000001); uath_config(sc, CFG_DIVERSITY_CTL, 0x00000001); uath_config(sc, CFG_ABOLT, 0x0000003f); uath_config(sc, CFG_WME_ENABLED, 0x00000001); uath_config(sc, CFG_SERVICE_TYPE, 1); uath_config(sc, CFG_TP_SCALE, 0x00000000); uath_config(sc, CFG_TPC_HALF_DBM5, 0x0000003c); uath_config(sc, CFG_TPC_HALF_DBM2, 0x0000003c); uath_config(sc, CFG_OVERRD_TX_POWER, 0x00000000); uath_config(sc, CFG_GMODE_PROTECTION, 0x00000000); uath_config(sc, CFG_GMODE_PROTECT_RATE_INDEX, 0x00000003); uath_config(sc, CFG_PROTECTION_TYPE, 0x00000000); uath_config(sc, CFG_MODE_CTS, 0x00000002); error = uath_cmd_read(sc, WDCMSG_TARGET_START, NULL, 0, &val, sizeof(val), UATH_CMD_FLAG_MAGIC); if (error) { device_printf(sc->sc_dev, "could not start target, error %d\n", error); goto fail; } DPRINTF(sc, UATH_DEBUG_INIT, "%s returns handle: 0x%x\n", uath_codename(WDCMSG_TARGET_START), be32toh(val)); /* set default channel */ error = uath_switch_channel(sc, ic->ic_curchan); if (error) { device_printf(sc->sc_dev, "could not switch channel, error %d\n", error); goto fail; } val = htobe32(TARGET_DEVICE_AWAKE); uath_cmd_write(sc, WDCMSG_SET_PWR_MODE, &val, sizeof val, 0); /* XXX? check */ uath_cmd_write(sc, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0); usbd_transfer_start(sc->sc_xfer[UATH_BULK_RX]); /* enable Rx */ uath_set_rxfilter(sc, 0x0, UATH_FILTER_OP_INIT); uath_set_rxfilter(sc, UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON, UATH_FILTER_OP_SET); sc->sc_flags |= UATH_FLAG_INITDONE; callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc); return (0); fail: uath_stop(sc); return (error); } static void uath_stop(struct uath_softc *sc) { UATH_ASSERT_LOCKED(sc); sc->sc_flags &= ~UATH_FLAG_INITDONE; callout_stop(&sc->stat_ch); callout_stop(&sc->watchdog_ch); sc->sc_tx_timer = 0; /* abort pending transmits */ uath_abort_xfers(sc); /* flush data & control requests into the target */ (void)uath_flush(sc); /* set a LED status to the disconnected. */ uath_set_ledstate(sc, 0); /* stop the target */ uath_cmd_write(sc, WDCMSG_TARGET_STOP, NULL, 0, 0); } static int uath_config(struct uath_softc *sc, uint32_t reg, uint32_t val) { struct uath_write_mac write; int error; write.reg = htobe32(reg); write.len = htobe32(0); /* 0 = single write */ *(uint32_t *)write.data = htobe32(val); error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, 3 * sizeof (uint32_t), 0); if (error != 0) { device_printf(sc->sc_dev, "could not write register 0x%02x\n", reg); } return (error); } static int uath_config_multi(struct uath_softc *sc, uint32_t reg, const void *data, int len) { struct uath_write_mac write; int error; write.reg = htobe32(reg); write.len = htobe32(len); bcopy(data, write.data, len); /* properly handle the case where len is zero (reset) */ error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, (len == 0) ? sizeof (uint32_t) : 2 * sizeof (uint32_t) + len, 0); if (error != 0) { device_printf(sc->sc_dev, "could not write %d bytes to register 0x%02x\n", len, reg); } return (error); } static int uath_switch_channel(struct uath_softc *sc, struct ieee80211_channel *c) { int error; UATH_ASSERT_LOCKED(sc); /* set radio frequency */ error = uath_set_chan(sc, c); if (error) { device_printf(sc->sc_dev, "could not set channel, error %d\n", error); goto failed; } /* reset Tx rings */ error = uath_reset_tx_queues(sc); if (error) { device_printf(sc->sc_dev, "could not reset Tx queues, error %d\n", error); goto failed; } /* set Tx rings WME properties */ error = uath_wme_init(sc); if (error) { device_printf(sc->sc_dev, "could not init Tx queues, error %d\n", error); goto failed; } error = uath_set_ledstate(sc, 0); if (error) { device_printf(sc->sc_dev, "could not set led state, error %d\n", error); goto failed; } error = uath_flush(sc); if (error) { device_printf(sc->sc_dev, "could not flush pipes, error %d\n", error); goto failed; } failed: return (error); } static int uath_set_rxfilter(struct uath_softc *sc, uint32_t bits, uint32_t op) { struct uath_cmd_rx_filter rxfilter; rxfilter.bits = htobe32(bits); rxfilter.op = htobe32(op); DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, "setting Rx filter=0x%x flags=0x%x\n", bits, op); return uath_cmd_write(sc, WDCMSG_RX_FILTER, &rxfilter, sizeof rxfilter, 0); } static void uath_watchdog(void *arg) { struct uath_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; if (sc->sc_tx_timer > 0) { if (--sc->sc_tx_timer == 0) { device_printf(sc->sc_dev, "device timeout\n"); counter_u64_add(ic->ic_oerrors, 1); ieee80211_restart_all(ic); return; } callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc); } } static void uath_abort_xfers(struct uath_softc *sc) { int i; UATH_ASSERT_LOCKED(sc); /* abort any pending transfers */ for (i = 0; i < UATH_N_XFERS; i++) usbd_transfer_stop(sc->sc_xfer[i]); } static int uath_flush(struct uath_softc *sc) { int error; error = uath_dataflush(sc); if (error != 0) goto failed; error = uath_cmdflush(sc); if (error != 0) goto failed; failed: return (error); } static int uath_cmdflush(struct uath_softc *sc) { return uath_cmd_write(sc, WDCMSG_FLUSH, NULL, 0, 0); } static int uath_dataflush(struct uath_softc *sc) { struct uath_data *data; struct uath_chunk *chunk; struct uath_tx_desc *desc; UATH_ASSERT_LOCKED(sc); data = uath_getbuf(sc); if (data == NULL) return (ENOBUFS); data->buflen = sizeof(struct uath_chunk) + sizeof(struct uath_tx_desc); data->m = NULL; data->ni = NULL; chunk = (struct uath_chunk *)data->buf; desc = (struct uath_tx_desc *)(chunk + 1); /* one chunk only */ chunk->seqnum = 0; chunk->flags = UATH_CFLAGS_FINAL; chunk->length = htobe16(sizeof (struct uath_tx_desc)); memset(desc, 0, sizeof(struct uath_tx_desc)); desc->msglen = htobe32(sizeof(struct uath_tx_desc)); desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */ desc->type = htobe32(WDCMSG_FLUSH); desc->txqid = htobe32(0); desc->connid = htobe32(0); desc->flags = htobe32(0); #ifdef UATH_DEBUG if (sc->sc_debug & UATH_DEBUG_CMDS) { DPRINTF(sc, UATH_DEBUG_RESET, "send flush ix %d\n", desc->msgid); if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) uath_dump_cmd(data->buf, data->buflen, '+'); } #endif STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); UATH_STAT_INC(sc, st_tx_pending); sc->sc_tx_timer = 5; usbd_transfer_start(sc->sc_xfer[UATH_BULK_TX]); return (0); } static struct uath_data * _uath_getbuf(struct uath_softc *sc) { struct uath_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); UATH_STAT_DEC(sc, st_tx_inactive); } else bf = NULL; if (bf == NULL) DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__, "out of xmit buffers"); return (bf); } static struct uath_data * uath_getbuf(struct uath_softc *sc) { struct uath_data *bf; UATH_ASSERT_LOCKED(sc); bf = _uath_getbuf(sc); if (bf == NULL) DPRINTF(sc, UATH_DEBUG_XMIT, "%s: stop queue\n", __func__); return (bf); } static int uath_set_ledstate(struct uath_softc *sc, int connected) { DPRINTF(sc, UATH_DEBUG_LED, "set led state %sconnected\n", connected ? "" : "!"); connected = htobe32(connected); return uath_cmd_write(sc, WDCMSG_SET_LED_STATE, &connected, sizeof connected, 0); } static int uath_set_chan(struct uath_softc *sc, struct ieee80211_channel *c) { #ifdef UATH_DEBUG struct ieee80211com *ic = &sc->sc_ic; #endif struct uath_cmd_reset reset; memset(&reset, 0, sizeof(reset)); if (IEEE80211_IS_CHAN_2GHZ(c)) reset.flags |= htobe32(UATH_CHAN_2GHZ); if (IEEE80211_IS_CHAN_5GHZ(c)) reset.flags |= htobe32(UATH_CHAN_5GHZ); /* NB: 11g =>'s 11b so don't specify both OFDM and CCK */ if (IEEE80211_IS_CHAN_OFDM(c)) reset.flags |= htobe32(UATH_CHAN_OFDM); else if (IEEE80211_IS_CHAN_CCK(c)) reset.flags |= htobe32(UATH_CHAN_CCK); /* turbo can be used in either 2GHz or 5GHz */ if (c->ic_flags & IEEE80211_CHAN_TURBO) reset.flags |= htobe32(UATH_CHAN_TURBO); reset.freq = htobe32(c->ic_freq); reset.maxrdpower = htobe32(50); /* XXX */ reset.channelchange = htobe32(1); reset.keeprccontent = htobe32(0); DPRINTF(sc, UATH_DEBUG_CHANNEL, "set channel %d, flags 0x%x freq %u\n", ieee80211_chan2ieee(ic, c), be32toh(reset.flags), be32toh(reset.freq)); return uath_cmd_write(sc, WDCMSG_RESET, &reset, sizeof reset, 0); } static int uath_reset_tx_queues(struct uath_softc *sc) { int ac, error; DPRINTF(sc, UATH_DEBUG_RESET, "%s: reset Tx queues\n", __func__); for (ac = 0; ac < 4; ac++) { const uint32_t qid = htobe32(ac); error = uath_cmd_write(sc, WDCMSG_RELEASE_TX_QUEUE, &qid, sizeof qid, 0); if (error != 0) break; } return (error); } static int uath_wme_init(struct uath_softc *sc) { /* XXX get from net80211 */ static const struct uath_wme_settings uath_wme_11g[4] = { { 7, 4, 10, 0, 0 }, /* Background */ { 3, 4, 10, 0, 0 }, /* Best-Effort */ { 3, 3, 4, 26, 0 }, /* Video */ { 2, 2, 3, 47, 0 } /* Voice */ }; struct uath_cmd_txq_setup qinfo; int ac, error; DPRINTF(sc, UATH_DEBUG_WME, "%s: setup Tx queues\n", __func__); for (ac = 0; ac < 4; ac++) { qinfo.qid = htobe32(ac); qinfo.len = htobe32(sizeof(qinfo.attr)); qinfo.attr.priority = htobe32(ac); /* XXX */ qinfo.attr.aifs = htobe32(uath_wme_11g[ac].aifsn); qinfo.attr.logcwmin = htobe32(uath_wme_11g[ac].logcwmin); qinfo.attr.logcwmax = htobe32(uath_wme_11g[ac].logcwmax); qinfo.attr.bursttime = htobe32(IEEE80211_TXOP_TO_US( uath_wme_11g[ac].txop)); qinfo.attr.mode = htobe32(uath_wme_11g[ac].acm);/*XXX? */ qinfo.attr.qflags = htobe32(1); /* XXX? */ error = uath_cmd_write(sc, WDCMSG_SETUP_TX_QUEUE, &qinfo, sizeof qinfo, 0); if (error != 0) break; } return (error); } static void uath_parent(struct ieee80211com *ic) { struct uath_softc *sc = ic->ic_softc; int startall = 0; UATH_LOCK(sc); if (sc->sc_flags & UATH_FLAG_INVALID) { UATH_UNLOCK(sc); return; } if (ic->ic_nrunning > 0) { if (!(sc->sc_flags & UATH_FLAG_INITDONE)) { uath_init(sc); startall = 1; } } else if (sc->sc_flags & UATH_FLAG_INITDONE) uath_stop(sc); UATH_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static int uath_tx_start(struct uath_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, struct uath_data *data) { struct ieee80211vap *vap = ni->ni_vap; struct uath_chunk *chunk; struct uath_tx_desc *desc; const struct ieee80211_frame *wh; struct ieee80211_key *k; int framelen, msglen; UATH_ASSERT_LOCKED(sc); data->ni = ni; data->m = m0; chunk = (struct uath_chunk *)data->buf; desc = (struct uath_tx_desc *)(chunk + 1); if (ieee80211_radiotap_active_vap(vap)) { struct uath_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; if (m0->m_flags & M_FRAG) tap->wt_flags |= IEEE80211_RADIOTAP_F_FRAG; ieee80211_radiotap_tx(vap, m0); } wh = mtod(m0, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { m_freem(m0); return (ENOBUFS); } /* packet header may have moved, reset our local pointer */ wh = mtod(m0, struct ieee80211_frame *); } m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(desc + 1)); framelen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; msglen = framelen + sizeof (struct uath_tx_desc); data->buflen = msglen + sizeof (struct uath_chunk); /* one chunk only for now */ chunk->seqnum = sc->sc_seqnum++; chunk->flags = (m0->m_flags & M_FRAG) ? 0 : UATH_CFLAGS_FINAL; if (m0->m_flags & M_LASTFRAG) chunk->flags |= UATH_CFLAGS_FINAL; chunk->flags = UATH_CFLAGS_FINAL; chunk->length = htobe16(msglen); /* fill Tx descriptor */ desc->msglen = htobe32(msglen); /* NB: to get UATH_TX_NOTIFY reply, `msgid' must be larger than 0 */ desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */ desc->type = htobe32(WDCMSG_SEND); switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { case IEEE80211_FC0_TYPE_CTL: case IEEE80211_FC0_TYPE_MGT: /* NB: force all management frames to highest queue */ if (ni->ni_flags & IEEE80211_NODE_QOS) { /* NB: force all management frames to highest queue */ desc->txqid = htobe32(WME_AC_VO | UATH_TXQID_MINRATE); } else desc->txqid = htobe32(WME_AC_BE | UATH_TXQID_MINRATE); break; case IEEE80211_FC0_TYPE_DATA: /* XXX multicast frames should honor mcastrate */ desc->txqid = htobe32(M_WME_GETAC(m0)); break; default: device_printf(sc->sc_dev, "bogus frame type 0x%x (%s)\n", wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__); m_freem(m0); return (EIO); } if (vap->iv_state == IEEE80211_S_AUTH || vap->iv_state == IEEE80211_S_ASSOC || vap->iv_state == IEEE80211_S_RUN) desc->connid = htobe32(UATH_ID_BSS); else desc->connid = htobe32(UATH_ID_INVALID); desc->flags = htobe32(0 /* no UATH_TX_NOTIFY */); desc->buflen = htobe32(m0->m_pkthdr.len); #ifdef UATH_DEBUG DPRINTF(sc, UATH_DEBUG_XMIT, "send frame ix %u framelen %d msglen %d connid 0x%x txqid 0x%x\n", desc->msgid, framelen, msglen, be32toh(desc->connid), be32toh(desc->txqid)); if (sc->sc_debug & UATH_DEBUG_XMIT_DUMP) uath_dump_cmd(data->buf, data->buflen, '+'); #endif STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); UATH_STAT_INC(sc, st_tx_pending); usbd_transfer_start(sc->sc_xfer[UATH_BULK_TX]); return (0); } /* * Cleanup driver resources when we run out of buffers while processing * fragments; return the tx buffers allocated and drop node references. */ static void uath_txfrag_cleanup(struct uath_softc *sc, uath_datahead *frags, struct ieee80211_node *ni) { struct uath_data *bf, *next; UATH_ASSERT_LOCKED(sc); STAILQ_FOREACH_SAFE(bf, frags, next, next) { /* NB: bf assumed clean */ STAILQ_REMOVE_HEAD(frags, next); STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); UATH_STAT_INC(sc, st_tx_inactive); ieee80211_node_decref(ni); } } /* * Setup xmit of a fragmented frame. Allocate a buffer for each frag and bump * the node reference count to reflect the held reference to be setup by * uath_tx_start. */ static int uath_txfrag_setup(struct uath_softc *sc, uath_datahead *frags, struct mbuf *m0, struct ieee80211_node *ni) { struct mbuf *m; struct uath_data *bf; UATH_ASSERT_LOCKED(sc); for (m = m0->m_nextpkt; m != NULL; m = m->m_nextpkt) { bf = uath_getbuf(sc); if (bf == NULL) { /* out of buffers, cleanup */ uath_txfrag_cleanup(sc, frags, ni); break; } ieee80211_node_incref(ni); STAILQ_INSERT_TAIL(frags, bf, next); } return !STAILQ_EMPTY(frags); } static int uath_transmit(struct ieee80211com *ic, struct mbuf *m) { struct uath_softc *sc = ic->ic_softc; int error; UATH_LOCK(sc); if ((sc->sc_flags & UATH_FLAG_INITDONE) == 0) { UATH_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { UATH_UNLOCK(sc); return (error); } uath_start(sc); UATH_UNLOCK(sc); return (0); } static void uath_start(struct uath_softc *sc) { struct uath_data *bf; struct ieee80211_node *ni; struct mbuf *m, *next; uath_datahead frags; UATH_ASSERT_LOCKED(sc); if ((sc->sc_flags & UATH_FLAG_INITDONE) == 0 || (sc->sc_flags & UATH_FLAG_INVALID)) return; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = uath_getbuf(sc); if (bf == NULL) { mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; /* * Check for fragmentation. If this frame has been broken up * verify we have enough buffers to send all the fragments * so all go out or none... */ STAILQ_INIT(&frags); if ((m->m_flags & M_FRAG) && !uath_txfrag_setup(sc, &frags, m, ni)) { DPRINTF(sc, UATH_DEBUG_XMIT, "%s: out of txfrag buffers\n", __func__); ieee80211_free_mbuf(m); goto bad; } sc->sc_seqnum = 0; nextfrag: /* * Pass the frame to the h/w for transmission. * Fragmented frames have each frag chained together * with m_nextpkt. We know there are sufficient uath_data's * to send all the frags because of work done by * uath_txfrag_setup. */ next = m->m_nextpkt; if (uath_tx_start(sc, m, ni, bf) != 0) { bad: if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); reclaim: STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); UATH_STAT_INC(sc, st_tx_inactive); uath_txfrag_cleanup(sc, &frags, ni); ieee80211_free_node(ni); continue; } if (next != NULL) { /* * Beware of state changing between frags. XXX check sta power-save state? */ if (ni->ni_vap->iv_state != IEEE80211_S_RUN) { DPRINTF(sc, UATH_DEBUG_XMIT, "%s: flush fragmented packet, state %s\n", __func__, ieee80211_state_name[ni->ni_vap->iv_state]); ieee80211_free_mbuf(next); goto reclaim; } m = next; bf = STAILQ_FIRST(&frags); KASSERT(bf != NULL, ("no buf for txfrag")); STAILQ_REMOVE_HEAD(&frags, next); goto nextfrag; } sc->sc_tx_timer = 5; } } static int uath_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct uath_data *bf; struct uath_softc *sc = ic->ic_softc; UATH_LOCK(sc); /* prevent management frames from being sent if we're not ready */ if ((sc->sc_flags & UATH_FLAG_INVALID) || !(sc->sc_flags & UATH_FLAG_INITDONE)) { m_freem(m); UATH_UNLOCK(sc); return (ENETDOWN); } /* grab a TX buffer */ bf = uath_getbuf(sc); if (bf == NULL) { m_freem(m); UATH_UNLOCK(sc); return (ENOBUFS); } sc->sc_seqnum = 0; if (uath_tx_start(sc, m, ni, bf) != 0) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); UATH_STAT_INC(sc, st_tx_inactive); UATH_UNLOCK(sc); return (EIO); } UATH_UNLOCK(sc); sc->sc_tx_timer = 5; return (0); } static void uath_scan_start(struct ieee80211com *ic) { /* do nothing */ } static void uath_scan_end(struct ieee80211com *ic) { /* do nothing */ } static void uath_set_channel(struct ieee80211com *ic) { struct uath_softc *sc = ic->ic_softc; UATH_LOCK(sc); if ((sc->sc_flags & UATH_FLAG_INVALID) || (sc->sc_flags & UATH_FLAG_INITDONE) == 0) { UATH_UNLOCK(sc); return; } (void)uath_switch_channel(sc, ic->ic_curchan); UATH_UNLOCK(sc); } static int uath_set_rxmulti_filter(struct uath_softc *sc) { /* XXX broken */ return (0); } static void uath_update_mcast(struct ieee80211com *ic) { struct uath_softc *sc = ic->ic_softc; UATH_LOCK(sc); if ((sc->sc_flags & UATH_FLAG_INVALID) || (sc->sc_flags & UATH_FLAG_INITDONE) == 0) { UATH_UNLOCK(sc); return; } /* * this is for avoiding the race condition when we're try to * connect to the AP with WPA. */ if (sc->sc_flags & UATH_FLAG_INITDONE) (void)uath_set_rxmulti_filter(sc); UATH_UNLOCK(sc); } static void uath_update_promisc(struct ieee80211com *ic) { struct uath_softc *sc = ic->ic_softc; UATH_LOCK(sc); if ((sc->sc_flags & UATH_FLAG_INVALID) || (sc->sc_flags & UATH_FLAG_INITDONE) == 0) { UATH_UNLOCK(sc); return; } if (sc->sc_flags & UATH_FLAG_INITDONE) { uath_set_rxfilter(sc, UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON | UATH_FILTER_RX_PROM, UATH_FILTER_OP_SET); } UATH_UNLOCK(sc); } static int uath_create_connection(struct uath_softc *sc, uint32_t connid) { const struct ieee80211_rateset *rs; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni; struct uath_cmd_create_connection create; ni = ieee80211_ref_node(vap->iv_bss); memset(&create, 0, sizeof(create)); create.connid = htobe32(connid); create.bssid = htobe32(0); /* XXX packed or not? */ create.size = htobe32(sizeof(struct uath_cmd_rateset)); rs = &ni->ni_rates; create.connattr.rateset.length = rs->rs_nrates; bcopy(rs->rs_rates, &create.connattr.rateset.set[0], rs->rs_nrates); /* XXX turbo */ if (IEEE80211_IS_CHAN_A(ni->ni_chan)) create.connattr.wlanmode = htobe32(WLAN_MODE_11a); else if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan)) create.connattr.wlanmode = htobe32(WLAN_MODE_11g); else create.connattr.wlanmode = htobe32(WLAN_MODE_11b); ieee80211_free_node(ni); return uath_cmd_write(sc, WDCMSG_CREATE_CONNECTION, &create, sizeof create, 0); } static int uath_set_rates(struct uath_softc *sc, const struct ieee80211_rateset *rs) { struct uath_cmd_rates rates; memset(&rates, 0, sizeof(rates)); rates.connid = htobe32(UATH_ID_BSS); /* XXX */ rates.size = htobe32(sizeof(struct uath_cmd_rateset)); /* XXX bounds check rs->rs_nrates */ rates.rateset.length = rs->rs_nrates; bcopy(rs->rs_rates, &rates.rateset.set[0], rs->rs_nrates); DPRINTF(sc, UATH_DEBUG_RATES, "setting supported rates nrates=%d\n", rs->rs_nrates); return uath_cmd_write(sc, WDCMSG_SET_BASIC_RATE, &rates, sizeof rates, 0); } static int uath_write_associd(struct uath_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni; struct uath_cmd_set_associd associd; ni = ieee80211_ref_node(vap->iv_bss); memset(&associd, 0, sizeof(associd)); associd.defaultrateix = htobe32(1); /* XXX */ associd.associd = htobe32(ni->ni_associd); associd.timoffset = htobe32(0x3b); /* XXX */ IEEE80211_ADDR_COPY(associd.bssid, ni->ni_bssid); ieee80211_free_node(ni); return uath_cmd_write(sc, WDCMSG_WRITE_ASSOCID, &associd, sizeof associd, 0); } static int uath_set_ledsteady(struct uath_softc *sc, int lednum, int ledmode) { struct uath_cmd_ledsteady led; led.lednum = htobe32(lednum); led.ledmode = htobe32(ledmode); DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (steady)\n", (lednum == UATH_LED_LINK) ? "link" : "activity", ledmode ? "on" : "off"); return uath_cmd_write(sc, WDCMSG_SET_LED_STEADY, &led, sizeof led, 0); } static int uath_set_ledblink(struct uath_softc *sc, int lednum, int ledmode, int blinkrate, int slowmode) { struct uath_cmd_ledblink led; led.lednum = htobe32(lednum); led.ledmode = htobe32(ledmode); led.blinkrate = htobe32(blinkrate); led.slowmode = htobe32(slowmode); DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (blink)\n", (lednum == UATH_LED_LINK) ? "link" : "activity", ledmode ? "on" : "off"); return uath_cmd_write(sc, WDCMSG_SET_LED_BLINK, &led, sizeof led, 0); } static int uath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { enum ieee80211_state ostate = vap->iv_state; int error; struct ieee80211_node *ni; struct ieee80211com *ic = vap->iv_ic; struct uath_softc *sc = ic->ic_softc; struct uath_vap *uvp = UATH_VAP(vap); DPRINTF(sc, UATH_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); UATH_LOCK(sc); callout_stop(&sc->stat_ch); callout_stop(&sc->watchdog_ch); ni = ieee80211_ref_node(vap->iv_bss); switch (nstate) { case IEEE80211_S_INIT: if (ostate == IEEE80211_S_RUN) { /* turn link and activity LEDs off */ uath_set_ledstate(sc, 0); } break; case IEEE80211_S_SCAN: break; case IEEE80211_S_AUTH: /* XXX good place? set RTS threshold */ uath_config(sc, CFG_USER_RTS_THRESHOLD, vap->iv_rtsthreshold); /* XXX bad place */ error = uath_set_keys(sc, vap); if (error != 0) { device_printf(sc->sc_dev, "could not set crypto keys, error %d\n", error); break; } if (uath_switch_channel(sc, ni->ni_chan) != 0) { device_printf(sc->sc_dev, "could not switch channel\n"); break; } if (uath_create_connection(sc, UATH_ID_BSS) != 0) { device_printf(sc->sc_dev, "could not create connection\n"); break; } break; case IEEE80211_S_ASSOC: if (uath_set_rates(sc, &ni->ni_rates) != 0) { device_printf(sc->sc_dev, "could not set negotiated rate set\n"); break; } break; case IEEE80211_S_RUN: /* XXX monitor mode doesn't be tested */ if (ic->ic_opmode == IEEE80211_M_MONITOR) { uath_set_ledstate(sc, 1); break; } /* * Tx rate is controlled by firmware, report the maximum * negotiated rate in ifconfig output. */ ni->ni_txrate = ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates-1]; if (uath_write_associd(sc) != 0) { device_printf(sc->sc_dev, "could not write association id\n"); break; } /* turn link LED on */ uath_set_ledsteady(sc, UATH_LED_LINK, UATH_LED_ON); /* make activity LED blink */ uath_set_ledblink(sc, UATH_LED_ACTIVITY, UATH_LED_ON, 1, 2); /* set state to associated */ uath_set_ledstate(sc, 1); /* start statistics timer */ callout_reset(&sc->stat_ch, hz, uath_stat, sc); break; default: break; } ieee80211_free_node(ni); UATH_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } static int uath_set_key(struct uath_softc *sc, const struct ieee80211_key *wk, int index) { #if 0 struct uath_cmd_crypto crypto; int i; memset(&crypto, 0, sizeof(crypto)); crypto.keyidx = htobe32(index); crypto.magic1 = htobe32(1); crypto.size = htobe32(368); crypto.mask = htobe32(0xffff); crypto.flags = htobe32(0x80000068); if (index != UATH_DEFAULT_KEY) crypto.flags |= htobe32(index << 16); memset(crypto.magic2, 0xff, sizeof(crypto.magic2)); /* * Each byte of the key must be XOR'ed with 10101010 before being * transmitted to the firmware. */ for (i = 0; i < wk->wk_keylen; i++) crypto.key[i] = wk->wk_key[i] ^ 0xaa; DPRINTF(sc, UATH_DEBUG_CRYPTO, "setting crypto key index=%d len=%d\n", index, wk->wk_keylen); return uath_cmd_write(sc, WDCMSG_SET_KEY_CACHE_ENTRY, &crypto, sizeof crypto, 0); #else /* XXX support H/W cryto */ return (0); #endif } static int uath_set_keys(struct uath_softc *sc, struct ieee80211vap *vap) { int i, error; error = 0; for (i = 0; i < IEEE80211_WEP_NKID; i++) { const struct ieee80211_key *wk = &vap->iv_nw_keys[i]; if (wk->wk_flags & (IEEE80211_KEY_XMIT|IEEE80211_KEY_RECV)) { error = uath_set_key(sc, wk, i); if (error) return (error); } } if (vap->iv_def_txkey != IEEE80211_KEYIX_NONE) { error = uath_set_key(sc, &vap->iv_nw_keys[vap->iv_def_txkey], UATH_DEFAULT_KEY); } return (error); } #define UATH_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) static void uath_sysctl_node(struct uath_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child; struct sysctl_oid *tree; struct uath_stat *stats; stats = &sc->sc_stat; ctx = device_get_sysctl_ctx(sc->sc_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); - tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, - NULL, "UATH statistics"); + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "UATH statistics"); child = SYSCTL_CHILDREN(tree); UATH_SYSCTL_STAT_ADD32(ctx, child, "badchunkseqnum", &stats->st_badchunkseqnum, "Bad chunk sequence numbers"); UATH_SYSCTL_STAT_ADD32(ctx, child, "invalidlen", &stats->st_invalidlen, "Invalid length"); UATH_SYSCTL_STAT_ADD32(ctx, child, "multichunk", &stats->st_multichunk, "Multi chunks"); UATH_SYSCTL_STAT_ADD32(ctx, child, "toobigrxpkt", &stats->st_toobigrxpkt, "Too big rx packets"); UATH_SYSCTL_STAT_ADD32(ctx, child, "stopinprogress", &stats->st_stopinprogress, "Stop in progress"); UATH_SYSCTL_STAT_ADD32(ctx, child, "crcerrs", &stats->st_crcerr, "CRC errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "phyerr", &stats->st_phyerr, "PHY errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_crcerr", &stats->st_decrypt_crcerr, "Decryption CRC errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_micerr", &stats->st_decrypt_micerr, "Decryption Misc errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "decomperr", &stats->st_decomperr, "Decomp errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "keyerr", &stats->st_keyerr, "Key errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "err", &stats->st_err, "Unknown errors"); UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_active", &stats->st_cmd_active, "Active numbers in Command queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_inactive", &stats->st_cmd_inactive, "Inactive numbers in Command queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_pending", &stats->st_cmd_pending, "Pending numbers in Command queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_waiting", &stats->st_cmd_waiting, "Waiting numbers in Command queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_active", &stats->st_rx_active, "Active numbers in RX queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_inactive", &stats->st_rx_inactive, "Inactive numbers in RX queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_active", &stats->st_tx_active, "Active numbers in TX queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive", &stats->st_tx_inactive, "Inactive numbers in TX queue"); UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_pending", &stats->st_tx_pending, "Pending numbers in TX queue"); } #undef UATH_SYSCTL_STAT_ADD32 CTASSERT(sizeof(u_int) >= sizeof(uint32_t)); static void uath_cmdeof(struct uath_softc *sc, struct uath_cmd *cmd) { struct uath_cmd_hdr *hdr; uint32_t dlen; hdr = (struct uath_cmd_hdr *)cmd->buf; /* NB: msgid is passed thru w/o byte swapping */ #ifdef UATH_DEBUG if (sc->sc_debug & UATH_DEBUG_CMDS) { uint32_t len = be32toh(hdr->len); printf("%s: %s [ix %u] len %u status %u\n", __func__, uath_codename(be32toh(hdr->code)), hdr->msgid, len, be32toh(hdr->magic)); if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) uath_dump_cmd(cmd->buf, len > UATH_MAX_CMDSZ ? sizeof(*hdr) : len, '-'); } #endif hdr->code = be32toh(hdr->code); hdr->len = be32toh(hdr->len); hdr->magic = be32toh(hdr->magic); /* target status on return */ switch (hdr->code & 0xff) { /* reply to a read command */ default: DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, "%s: code %d hdr len %u\n", __func__, hdr->code & 0xff, hdr->len); /* * The first response from the target after the * HOST_AVAILABLE has an invalid msgid so we must * treat it specially. */ if (hdr->msgid < UATH_CMD_LIST_COUNT) { uint32_t *rp = (uint32_t *)(hdr+1); u_int olen; if (sizeof(*hdr) > hdr->len || hdr->len >= UATH_MAX_CMDSZ) { device_printf(sc->sc_dev, "%s: invalid WDC msg length %u; " "msg ignored\n", __func__, hdr->len); return; } /* * Calculate return/receive payload size; the * first word, if present, always gives the * number of bytes--unless it's 0 in which * case a single 32-bit word should be present. */ dlen = hdr->len - sizeof(*hdr); if (dlen >= sizeof(uint32_t)) { olen = be32toh(rp[0]); dlen -= sizeof(uint32_t); if (olen == 0) { /* convention is 0 =>'s one word */ olen = sizeof(uint32_t); /* XXX KASSERT(olen == dlen ) */ } } else olen = 0; if (cmd->odata != NULL) { /* NB: cmd->olen validated in uath_cmd */ if (olen > (u_int)cmd->olen) { /* XXX complain? */ device_printf(sc->sc_dev, "%s: cmd 0x%x olen %u cmd olen %u\n", __func__, hdr->code, olen, cmd->olen); olen = cmd->olen; } if (olen > dlen) { /* XXX complain, shouldn't happen */ device_printf(sc->sc_dev, "%s: cmd 0x%x olen %u dlen %u\n", __func__, hdr->code, olen, dlen); olen = dlen; } /* XXX have submitter do this */ /* copy answer into caller's supplied buffer */ bcopy(&rp[1], cmd->odata, olen); cmd->olen = olen; } } wakeup_one(cmd); /* wake up caller */ break; case WDCMSG_TARGET_START: if (hdr->msgid >= UATH_CMD_LIST_COUNT) { /* XXX */ return; } dlen = hdr->len - sizeof(*hdr); if (dlen != sizeof(uint32_t)) { device_printf(sc->sc_dev, "%s: dlen (%u) != %zu!\n", __func__, dlen, sizeof(uint32_t)); return; } /* XXX have submitter do this */ /* copy answer into caller's supplied buffer */ bcopy(hdr+1, cmd->odata, sizeof(uint32_t)); cmd->olen = sizeof(uint32_t); wakeup_one(cmd); /* wake up caller */ break; case WDCMSG_SEND_COMPLETE: /* this notification is sent when UATH_TX_NOTIFY is set */ DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, "%s: received Tx notification\n", __func__); break; case WDCMSG_TARGET_GET_STATS: DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, "%s: received device statistics\n", __func__); callout_reset(&sc->stat_ch, hz, uath_stat, sc); break; } } static void uath_intr_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uath_softc *sc = usbd_xfer_softc(xfer); struct uath_cmd *cmd; struct uath_cmd_hdr *hdr; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); UATH_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: cmd = STAILQ_FIRST(&sc->sc_cmd_waiting); if (cmd == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_cmd_waiting, next); UATH_STAT_DEC(sc, st_cmd_waiting); STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next); UATH_STAT_INC(sc, st_cmd_inactive); if (actlen < sizeof(struct uath_cmd_hdr)) { device_printf(sc->sc_dev, "%s: short xfer error (actlen %d)\n", __func__, actlen); goto setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, cmd->buf, actlen); hdr = (struct uath_cmd_hdr *)cmd->buf; hdr->len = be32toh(hdr->len); if (hdr->len > (uint32_t)actlen) { device_printf(sc->sc_dev, "%s: truncated xfer (len %u, actlen %d)\n", __func__, hdr->len, actlen); goto setup; } uath_cmdeof(sc, cmd); case USB_ST_SETUP: setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto setup; } break; } } static void uath_intr_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uath_softc *sc = usbd_xfer_softc(xfer); struct uath_cmd *cmd; UATH_ASSERT_LOCKED(sc); cmd = STAILQ_FIRST(&sc->sc_cmd_active); if (cmd != NULL && USB_GET_STATE(xfer) != USB_ST_SETUP) { STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next); UATH_STAT_DEC(sc, st_cmd_active); STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_READ) ? &sc->sc_cmd_waiting : &sc->sc_cmd_inactive, cmd, next); if (cmd->flags & UATH_CMD_FLAG_READ) UATH_STAT_INC(sc, st_cmd_waiting); else UATH_STAT_INC(sc, st_cmd_inactive); } switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: setup: cmd = STAILQ_FIRST(&sc->sc_cmd_pending); if (cmd == NULL) { DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n", __func__); return; } STAILQ_REMOVE_HEAD(&sc->sc_cmd_pending, next); UATH_STAT_DEC(sc, st_cmd_pending); STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_ASYNC) ? &sc->sc_cmd_inactive : &sc->sc_cmd_active, cmd, next); if (cmd->flags & UATH_CMD_FLAG_ASYNC) UATH_STAT_INC(sc, st_cmd_inactive); else UATH_STAT_INC(sc, st_cmd_active); usbd_xfer_set_frame_data(xfer, 0, cmd->buf, cmd->buflen); usbd_transfer_submit(xfer); break; default: if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto setup; } break; } } static void uath_update_rxstat(struct uath_softc *sc, uint32_t status) { switch (status) { case UATH_STATUS_STOP_IN_PROGRESS: UATH_STAT_INC(sc, st_stopinprogress); break; case UATH_STATUS_CRC_ERR: UATH_STAT_INC(sc, st_crcerr); break; case UATH_STATUS_PHY_ERR: UATH_STAT_INC(sc, st_phyerr); break; case UATH_STATUS_DECRYPT_CRC_ERR: UATH_STAT_INC(sc, st_decrypt_crcerr); break; case UATH_STATUS_DECRYPT_MIC_ERR: UATH_STAT_INC(sc, st_decrypt_micerr); break; case UATH_STATUS_DECOMP_ERR: UATH_STAT_INC(sc, st_decomperr); break; case UATH_STATUS_KEY_ERR: UATH_STAT_INC(sc, st_keyerr); break; case UATH_STATUS_ERR: UATH_STAT_INC(sc, st_err); break; default: break; } } CTASSERT(UATH_MIN_RXBUFSZ >= sizeof(struct uath_chunk)); static struct mbuf * uath_data_rxeof(struct usb_xfer *xfer, struct uath_data *data, struct uath_rx_desc **pdesc) { struct uath_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct uath_chunk *chunk; struct uath_rx_desc *desc; struct mbuf *m = data->m, *mnew, *mp; uint16_t chunklen; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); if (actlen < (int)UATH_MIN_RXBUFSZ) { DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, "%s: wrong xfer size (len=%d)\n", __func__, actlen); counter_u64_add(ic->ic_ierrors, 1); return (NULL); } chunk = (struct uath_chunk *)data->buf; chunklen = be16toh(chunk->length); if (chunk->seqnum == 0 && chunk->flags == 0 && chunklen == 0) { device_printf(sc->sc_dev, "%s: strange response\n", __func__); counter_u64_add(ic->ic_ierrors, 1); UATH_RESET_INTRX(sc); return (NULL); } if (chunklen > actlen) { device_printf(sc->sc_dev, "%s: invalid chunk length (len %u > actlen %d)\n", __func__, chunklen, actlen); counter_u64_add(ic->ic_ierrors, 1); /* XXX cleanup? */ UATH_RESET_INTRX(sc); return (NULL); } if (chunk->seqnum != sc->sc_intrx_nextnum) { DPRINTF(sc, UATH_DEBUG_XMIT, "invalid seqnum %d, expected %d\n", chunk->seqnum, sc->sc_intrx_nextnum); UATH_STAT_INC(sc, st_badchunkseqnum); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } /* check multi-chunk frames */ if ((chunk->seqnum == 0 && !(chunk->flags & UATH_CFLAGS_FINAL)) || (chunk->seqnum != 0 && (chunk->flags & UATH_CFLAGS_FINAL)) || chunk->flags & UATH_CFLAGS_RXMSG) UATH_STAT_INC(sc, st_multichunk); if (chunk->flags & UATH_CFLAGS_FINAL) { if (chunklen < sizeof(struct uath_rx_desc)) { device_printf(sc->sc_dev, "%s: invalid chunk length %d\n", __func__, chunklen); counter_u64_add(ic->ic_ierrors, 1); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } chunklen -= sizeof(struct uath_rx_desc); } if (chunklen > 0 && (!(chunk->flags & UATH_CFLAGS_FINAL) || !(chunk->seqnum == 0))) { /* we should use intermediate RX buffer */ if (chunk->seqnum == 0) UATH_RESET_INTRX(sc); if ((sc->sc_intrx_len + sizeof(struct uath_rx_desc) + chunklen) > UATH_MAX_INTRX_SIZE) { UATH_STAT_INC(sc, st_invalidlen); counter_u64_add(ic->ic_ierrors, 1); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } m->m_len = chunklen; m->m_data += sizeof(struct uath_chunk); if (sc->sc_intrx_head == NULL) { sc->sc_intrx_head = m; sc->sc_intrx_tail = m; } else { m->m_flags &= ~M_PKTHDR; sc->sc_intrx_tail->m_next = m; sc->sc_intrx_tail = m; } } sc->sc_intrx_len += chunklen; mnew = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (mnew == NULL) { DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, "%s: can't get new mbuf, drop frame\n", __func__); counter_u64_add(ic->ic_ierrors, 1); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } data->m = mnew; data->buf = mtod(mnew, uint8_t *); /* if the frame is not final continue the transfer */ if (!(chunk->flags & UATH_CFLAGS_FINAL)) { sc->sc_intrx_nextnum++; UATH_RESET_INTRX(sc); return (NULL); } /* * if the frame is not set UATH_CFLAGS_RXMSG, then rx descriptor is * located at the end, 32-bit aligned */ desc = (chunk->flags & UATH_CFLAGS_RXMSG) ? (struct uath_rx_desc *)(chunk + 1) : (struct uath_rx_desc *)(((uint8_t *)chunk) + sizeof(struct uath_chunk) + be16toh(chunk->length) - sizeof(struct uath_rx_desc)); if ((uint8_t *)chunk + actlen - sizeof(struct uath_rx_desc) < (uint8_t *)desc) { device_printf(sc->sc_dev, "%s: wrong Rx descriptor pointer " "(desc %p chunk %p actlen %d)\n", __func__, desc, chunk, actlen); counter_u64_add(ic->ic_ierrors, 1); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } *pdesc = desc; DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, "%s: frame len %u code %u status %u rate %u antenna %u " "rssi %d channel %u phyerror %u connix %u decrypterror %u " "keycachemiss %u\n", __func__, be32toh(desc->framelen) , be32toh(desc->code), be32toh(desc->status), be32toh(desc->rate) , be32toh(desc->antenna), be32toh(desc->rssi), be32toh(desc->channel) , be32toh(desc->phyerror), be32toh(desc->connix) , be32toh(desc->decrypterror), be32toh(desc->keycachemiss)); if (be32toh(desc->len) > MCLBYTES) { DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, "%s: bad descriptor (len=%d)\n", __func__, be32toh(desc->len)); counter_u64_add(ic->ic_ierrors, 1); UATH_STAT_INC(sc, st_toobigrxpkt); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } uath_update_rxstat(sc, be32toh(desc->status)); /* finalize mbuf */ if (sc->sc_intrx_head == NULL) { uint32_t framelen; if (be32toh(desc->framelen) < UATH_RX_DUMMYSIZE) { device_printf(sc->sc_dev, "%s: framelen too small (%u)\n", __func__, be32toh(desc->framelen)); counter_u64_add(ic->ic_ierrors, 1); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } framelen = be32toh(desc->framelen) - UATH_RX_DUMMYSIZE; if (framelen > actlen - sizeof(struct uath_chunk) || framelen < sizeof(struct ieee80211_frame_ack)) { device_printf(sc->sc_dev, "%s: wrong frame length (%u, actlen %d)!\n", __func__, framelen, actlen); counter_u64_add(ic->ic_ierrors, 1); if (sc->sc_intrx_head != NULL) m_freem(sc->sc_intrx_head); UATH_RESET_INTRX(sc); return (NULL); } m->m_pkthdr.len = m->m_len = framelen; m->m_data += sizeof(struct uath_chunk); } else { mp = sc->sc_intrx_head; mp->m_flags |= M_PKTHDR; mp->m_pkthdr.len = sc->sc_intrx_len; m = mp; } /* there are a lot more fields in the RX descriptor */ if ((sc->sc_flags & UATH_FLAG_INVALID) == 0 && ieee80211_radiotap_active(ic)) { struct uath_rx_radiotap_header *tap = &sc->sc_rxtap; uint32_t tsf_hi = be32toh(desc->tstamp_high); uint32_t tsf_lo = be32toh(desc->tstamp_low); /* XXX only get low order 24bits of tsf from h/w */ tap->wr_tsf = htole64(((uint64_t)tsf_hi << 32) | tsf_lo); tap->wr_flags = 0; if (be32toh(desc->status) == UATH_STATUS_CRC_ERR) tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; /* XXX map other status to BADFCS? */ /* XXX ath h/w rate code, need to map */ tap->wr_rate = be32toh(desc->rate); tap->wr_antenna = be32toh(desc->antenna); tap->wr_antsignal = -95 + be32toh(desc->rssi); tap->wr_antnoise = -95; } UATH_RESET_INTRX(sc); return (m); } static void uath_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uath_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct epoch_tracker et; struct mbuf *m = NULL; struct uath_data *data; struct uath_rx_desc *desc = NULL; int8_t nf; UATH_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); UATH_STAT_DEC(sc, st_rx_active); m = uath_data_rxeof(xfer, data, &desc); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); UATH_STAT_INC(sc, st_rx_inactive); /* FALLTHROUGH */ case USB_ST_SETUP: setup: data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) return; STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); UATH_STAT_DEC(sc, st_rx_inactive); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); UATH_STAT_INC(sc, st_rx_active); usbd_xfer_set_frame_data(xfer, 0, data->buf, MCLBYTES); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ if (sc->sc_flags & UATH_FLAG_INVALID) { if (m != NULL) m_freem(m); return; } UATH_UNLOCK(sc); if (m != NULL && desc != NULL) { wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); nf = -95; /* XXX */ NET_EPOCH_ENTER(et); if (ni != NULL) { (void) ieee80211_input(ni, m, (int)be32toh(desc->rssi), nf); /* node is no longer needed */ ieee80211_free_node(ni); } else (void) ieee80211_input_all(ic, m, (int)be32toh(desc->rssi), nf); NET_EPOCH_EXIT(et); m = NULL; desc = NULL; } UATH_LOCK(sc); uath_start(sc); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); UATH_STAT_DEC(sc, st_rx_active); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); UATH_STAT_INC(sc, st_rx_inactive); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto setup; } break; } } static void uath_data_txeof(struct usb_xfer *xfer, struct uath_data *data) { struct uath_softc *sc = usbd_xfer_softc(xfer); UATH_ASSERT_LOCKED(sc); if (data->m) { /* XXX status? */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } sc->sc_tx_timer = 0; } static void uath_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uath_softc *sc = usbd_xfer_softc(xfer); struct uath_data *data; UATH_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); UATH_STAT_DEC(sc, st_tx_active); uath_data_txeof(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); UATH_STAT_INC(sc, st_tx_inactive); /* FALLTHROUGH */ case USB_ST_SETUP: setup: data = STAILQ_FIRST(&sc->sc_tx_pending); if (data == NULL) { DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n", __func__); return; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); UATH_STAT_DEC(sc, st_tx_pending); STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); UATH_STAT_INC(sc, st_tx_active); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); usbd_transfer_submit(xfer); uath_start(sc); break; default: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto setup; if (data->ni != NULL) { if_inc_counter(data->ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); if ((sc->sc_flags & UATH_FLAG_INVALID) == 0) ieee80211_free_node(data->ni); data->ni = NULL; } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto setup; } break; } } static device_method_t uath_methods[] = { DEVMETHOD(device_probe, uath_match), DEVMETHOD(device_attach, uath_attach), DEVMETHOD(device_detach, uath_detach), DEVMETHOD_END }; static driver_t uath_driver = { .name = "uath", .methods = uath_methods, .size = sizeof(struct uath_softc) }; static devclass_t uath_devclass; DRIVER_MODULE(uath, uhub, uath_driver, uath_devclass, NULL, 0); MODULE_DEPEND(uath, wlan, 1, 1, 1); MODULE_DEPEND(uath, usb, 1, 1, 1); MODULE_VERSION(uath, 1); USB_PNP_HOST_INFO(uath_devs); Index: head/sys/dev/usb/wlan/if_upgt.c =================================================================== --- head/sys/dev/usb/wlan/if_upgt.c (revision 357971) +++ head/sys/dev/usb/wlan/if_upgt.c (revision 357972) @@ -1,2355 +1,2355 @@ /* $OpenBSD: if_upgt.c,v 1.35 2008/04/16 18:32:15 damien Exp $ */ /* $FreeBSD$ */ /* * Copyright (c) 2007 Marcus Glocker * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include /* * Driver for the USB PrismGT devices. * * For now just USB 2.0 devices with the GW3887 chipset are supported. * The driver has been written based on the firmware version 2.13.1.0_LM87. * * TODO's: * - MONITOR mode test. * - Add HOSTAP mode. * - Add IBSS mode. * - Support the USB 1.0 devices (NET2280, ISL3880, ISL3886 chipsets). * * Parts of this driver has been influenced by reading the p54u driver * written by Jean-Baptiste Note and * Sebastien Bourdeauducq . */ -static SYSCTL_NODE(_hw, OID_AUTO, upgt, CTLFLAG_RD, 0, +static SYSCTL_NODE(_hw, OID_AUTO, upgt, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "USB PrismGT GW3887 driver parameters"); #ifdef UPGT_DEBUG int upgt_debug = 0; SYSCTL_INT(_hw_upgt, OID_AUTO, debug, CTLFLAG_RWTUN, &upgt_debug, 0, "control debugging printfs"); enum { UPGT_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ UPGT_DEBUG_RECV = 0x00000002, /* basic recv operation */ UPGT_DEBUG_RESET = 0x00000004, /* reset processing */ UPGT_DEBUG_INTR = 0x00000008, /* INTR */ UPGT_DEBUG_TX_PROC = 0x00000010, /* tx ISR proc */ UPGT_DEBUG_RX_PROC = 0x00000020, /* rx ISR proc */ UPGT_DEBUG_STATE = 0x00000040, /* 802.11 state transitions */ UPGT_DEBUG_STAT = 0x00000080, /* statistic */ UPGT_DEBUG_FW = 0x00000100, /* firmware */ UPGT_DEBUG_ANY = 0xffffffff }; #define DPRINTF(sc, m, fmt, ...) do { \ if (sc->sc_debug & (m)) \ printf(fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, m, fmt, ...) do { \ (void) sc; \ } while (0) #endif /* * Prototypes. */ static device_probe_t upgt_match; static device_attach_t upgt_attach; static device_detach_t upgt_detach; static int upgt_alloc_tx(struct upgt_softc *); static int upgt_alloc_rx(struct upgt_softc *); static int upgt_device_reset(struct upgt_softc *); static void upgt_bulk_tx(struct upgt_softc *, struct upgt_data *); static int upgt_fw_verify(struct upgt_softc *); static int upgt_mem_init(struct upgt_softc *); static int upgt_fw_load(struct upgt_softc *); static int upgt_fw_copy(const uint8_t *, char *, int); static uint32_t upgt_crc32_le(const void *, size_t); static struct mbuf * upgt_rxeof(struct usb_xfer *, struct upgt_data *, int *); static struct mbuf * upgt_rx(struct upgt_softc *, uint8_t *, int, int *); static void upgt_txeof(struct usb_xfer *, struct upgt_data *); static int upgt_eeprom_read(struct upgt_softc *); static int upgt_eeprom_parse(struct upgt_softc *); static void upgt_eeprom_parse_hwrx(struct upgt_softc *, uint8_t *); static void upgt_eeprom_parse_freq3(struct upgt_softc *, uint8_t *, int); static void upgt_eeprom_parse_freq4(struct upgt_softc *, uint8_t *, int); static void upgt_eeprom_parse_freq6(struct upgt_softc *, uint8_t *, int); static uint32_t upgt_chksum_le(const uint32_t *, size_t); static void upgt_tx_done(struct upgt_softc *, uint8_t *); static void upgt_init(struct upgt_softc *); static void upgt_parent(struct ieee80211com *); static int upgt_transmit(struct ieee80211com *, struct mbuf *); static void upgt_start(struct upgt_softc *); static int upgt_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void upgt_scan_start(struct ieee80211com *); static void upgt_scan_end(struct ieee80211com *); static void upgt_set_channel(struct ieee80211com *); static struct ieee80211vap *upgt_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void upgt_vap_delete(struct ieee80211vap *); static void upgt_update_mcast(struct ieee80211com *); static uint8_t upgt_rx_rate(struct upgt_softc *, const int); static void upgt_set_multi(void *); static void upgt_stop(struct upgt_softc *); static void upgt_setup_rates(struct ieee80211vap *, struct ieee80211com *); static int upgt_set_macfilter(struct upgt_softc *, uint8_t); static int upgt_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void upgt_set_chan(struct upgt_softc *, struct ieee80211_channel *); static void upgt_set_led(struct upgt_softc *, int); static void upgt_set_led_blink(void *); static void upgt_get_stats(struct upgt_softc *); static void upgt_mem_free(struct upgt_softc *, uint32_t); static uint32_t upgt_mem_alloc(struct upgt_softc *); static void upgt_free_tx(struct upgt_softc *); static void upgt_free_rx(struct upgt_softc *); static void upgt_watchdog(void *); static void upgt_abort_xfers(struct upgt_softc *); static void upgt_abort_xfers_locked(struct upgt_softc *); static void upgt_sysctl_node(struct upgt_softc *); static struct upgt_data * upgt_getbuf(struct upgt_softc *); static struct upgt_data * upgt_gettxbuf(struct upgt_softc *); static int upgt_tx_start(struct upgt_softc *, struct mbuf *, struct ieee80211_node *, struct upgt_data *); static const char *upgt_fwname = "upgt-gw3887"; static const STRUCT_USB_HOST_ID upgt_devs[] = { #define UPGT_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } /* version 2 devices */ UPGT_DEV(ACCTON, PRISM_GT), UPGT_DEV(BELKIN, F5D7050), UPGT_DEV(CISCOLINKSYS, WUSB54AG), UPGT_DEV(CONCEPTRONIC, PRISM_GT), UPGT_DEV(DELL, PRISM_GT_1), UPGT_DEV(DELL, PRISM_GT_2), UPGT_DEV(FSC, E5400), UPGT_DEV(GLOBESPAN, PRISM_GT_1), UPGT_DEV(GLOBESPAN, PRISM_GT_2), UPGT_DEV(NETGEAR, WG111V1_2), UPGT_DEV(INTERSIL, PRISM_GT), UPGT_DEV(SMC, 2862WG), UPGT_DEV(USR, USR5422), UPGT_DEV(WISTRONNEWEB, UR045G), UPGT_DEV(XYRATEX, PRISM_GT_1), UPGT_DEV(XYRATEX, PRISM_GT_2), UPGT_DEV(ZCOM, XG703A), UPGT_DEV(ZCOM, XM142) }; static usb_callback_t upgt_bulk_rx_callback; static usb_callback_t upgt_bulk_tx_callback; static const struct usb_config upgt_config[UPGT_N_XFERS] = { [UPGT_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = MCLBYTES * UPGT_TX_MAXCOUNT, .flags = { .force_short_xfer = 1, .pipe_bof = 1 }, .callback = upgt_bulk_tx_callback, .timeout = UPGT_USB_TIMEOUT, /* ms */ }, [UPGT_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = MCLBYTES * UPGT_RX_MAXCOUNT, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = upgt_bulk_rx_callback, }, }; static int upgt_match(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != UPGT_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != UPGT_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(upgt_devs, sizeof(upgt_devs), uaa)); } static int upgt_attach(device_t dev) { struct upgt_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; struct usb_attach_arg *uaa = device_get_ivars(dev); uint8_t bands[IEEE80211_MODE_BYTES]; uint8_t iface_index = UPGT_IFACE_INDEX; int error; sc->sc_dev = dev; sc->sc_udev = uaa->device; #ifdef UPGT_DEBUG sc->sc_debug = upgt_debug; #endif device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init(&sc->sc_led_ch, 0); callout_init(&sc->sc_watchdog_ch, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, upgt_config, UPGT_N_XFERS, sc, &sc->sc_mtx); if (error) { device_printf(dev, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto fail1; } sc->sc_rx_dma_buf = usbd_xfer_get_frame_buffer( sc->sc_xfer[UPGT_BULK_RX], 0); sc->sc_tx_dma_buf = usbd_xfer_get_frame_buffer( sc->sc_xfer[UPGT_BULK_TX], 0); /* Setup TX and RX buffers */ error = upgt_alloc_tx(sc); if (error) goto fail2; error = upgt_alloc_rx(sc); if (error) goto fail3; /* Initialize the device. */ error = upgt_device_reset(sc); if (error) goto fail4; /* Verify the firmware. */ error = upgt_fw_verify(sc); if (error) goto fail4; /* Calculate device memory space. */ if (sc->sc_memaddr_frame_start == 0 || sc->sc_memaddr_frame_end == 0) { device_printf(dev, "could not find memory space addresses on FW\n"); error = EIO; goto fail4; } sc->sc_memaddr_frame_end -= UPGT_MEMSIZE_RX + 1; sc->sc_memaddr_rx_start = sc->sc_memaddr_frame_end + 1; DPRINTF(sc, UPGT_DEBUG_FW, "memory address frame start=0x%08x\n", sc->sc_memaddr_frame_start); DPRINTF(sc, UPGT_DEBUG_FW, "memory address frame end=0x%08x\n", sc->sc_memaddr_frame_end); DPRINTF(sc, UPGT_DEBUG_FW, "memory address rx start=0x%08x\n", sc->sc_memaddr_rx_start); upgt_mem_init(sc); /* Load the firmware. */ error = upgt_fw_load(sc); if (error) goto fail4; /* Read the whole EEPROM content and parse it. */ error = upgt_eeprom_read(sc); if (error) goto fail4; error = upgt_eeprom_parse(sc); if (error) goto fail4; /* all works related with the device have done here. */ upgt_abort_xfers(sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA /* station mode */ | IEEE80211_C_MONITOR /* monitor mode */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_BGSCAN /* capable of bg scanning */ | IEEE80211_C_WPA /* 802.11i */ ; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); ieee80211_init_channels(ic, NULL, bands); ieee80211_ifattach(ic); ic->ic_raw_xmit = upgt_raw_xmit; ic->ic_scan_start = upgt_scan_start; ic->ic_scan_end = upgt_scan_end; ic->ic_set_channel = upgt_set_channel; ic->ic_vap_create = upgt_vap_create; ic->ic_vap_delete = upgt_vap_delete; ic->ic_update_mcast = upgt_update_mcast; ic->ic_transmit = upgt_transmit; ic->ic_parent = upgt_parent; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), UPGT_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), UPGT_RX_RADIOTAP_PRESENT); upgt_sysctl_node(sc); if (bootverbose) ieee80211_announce(ic); return (0); fail4: upgt_free_rx(sc); fail3: upgt_free_tx(sc); fail2: usbd_transfer_unsetup(sc->sc_xfer, UPGT_N_XFERS); fail1: mtx_destroy(&sc->sc_mtx); return (error); } static void upgt_txeof(struct usb_xfer *xfer, struct upgt_data *data) { if (data->m) { /* XXX status? */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } } static void upgt_get_stats(struct upgt_softc *sc) { struct upgt_data *data_cmd; struct upgt_lmac_mem *mem; struct upgt_lmac_stats *stats; data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__); return; } /* * Transmit the URB containing the CMD data. */ memset(data_cmd->buf, 0, MCLBYTES); mem = (struct upgt_lmac_mem *)data_cmd->buf; mem->addr = htole32(sc->sc_memaddr_frame_start + UPGT_MEMSIZE_FRAME_HEAD); stats = (struct upgt_lmac_stats *)(mem + 1); stats->header1.flags = 0; stats->header1.type = UPGT_H1_TYPE_CTRL; stats->header1.len = htole16( sizeof(struct upgt_lmac_stats) - sizeof(struct upgt_lmac_header)); stats->header2.reqid = htole32(sc->sc_memaddr_frame_start); stats->header2.type = htole16(UPGT_H2_TYPE_STATS); stats->header2.flags = 0; data_cmd->buflen = sizeof(*mem) + sizeof(*stats); mem->chksum = upgt_chksum_le((uint32_t *)stats, data_cmd->buflen - sizeof(*mem)); upgt_bulk_tx(sc, data_cmd); } static void upgt_parent(struct ieee80211com *ic) { struct upgt_softc *sc = ic->ic_softc; int startall = 0; UPGT_LOCK(sc); if (sc->sc_flags & UPGT_FLAG_DETACHED) { UPGT_UNLOCK(sc); return; } if (ic->ic_nrunning > 0) { if (sc->sc_flags & UPGT_FLAG_INITDONE) { if (ic->ic_allmulti > 0 || ic->ic_promisc > 0) upgt_set_multi(sc); } else { upgt_init(sc); startall = 1; } } else if (sc->sc_flags & UPGT_FLAG_INITDONE) upgt_stop(sc); UPGT_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static void upgt_stop(struct upgt_softc *sc) { UPGT_ASSERT_LOCKED(sc); if (sc->sc_flags & UPGT_FLAG_INITDONE) upgt_set_macfilter(sc, IEEE80211_S_INIT); upgt_abort_xfers_locked(sc); /* device down */ sc->sc_tx_timer = 0; sc->sc_flags &= ~UPGT_FLAG_INITDONE; } static void upgt_set_led(struct upgt_softc *sc, int action) { struct upgt_data *data_cmd; struct upgt_lmac_mem *mem; struct upgt_lmac_led *led; data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__); return; } /* * Transmit the URB containing the CMD data. */ memset(data_cmd->buf, 0, MCLBYTES); mem = (struct upgt_lmac_mem *)data_cmd->buf; mem->addr = htole32(sc->sc_memaddr_frame_start + UPGT_MEMSIZE_FRAME_HEAD); led = (struct upgt_lmac_led *)(mem + 1); led->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; led->header1.type = UPGT_H1_TYPE_CTRL; led->header1.len = htole16( sizeof(struct upgt_lmac_led) - sizeof(struct upgt_lmac_header)); led->header2.reqid = htole32(sc->sc_memaddr_frame_start); led->header2.type = htole16(UPGT_H2_TYPE_LED); led->header2.flags = 0; switch (action) { case UPGT_LED_OFF: led->mode = htole16(UPGT_LED_MODE_SET); led->action_fix = 0; led->action_tmp = htole16(UPGT_LED_ACTION_OFF); led->action_tmp_dur = 0; break; case UPGT_LED_ON: led->mode = htole16(UPGT_LED_MODE_SET); led->action_fix = 0; led->action_tmp = htole16(UPGT_LED_ACTION_ON); led->action_tmp_dur = 0; break; case UPGT_LED_BLINK: if (sc->sc_state != IEEE80211_S_RUN) { STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next); return; } if (sc->sc_led_blink) { /* previous blink was not finished */ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next); return; } led->mode = htole16(UPGT_LED_MODE_SET); led->action_fix = htole16(UPGT_LED_ACTION_OFF); led->action_tmp = htole16(UPGT_LED_ACTION_ON); led->action_tmp_dur = htole16(UPGT_LED_ACTION_TMP_DUR); /* lock blink */ sc->sc_led_blink = 1; callout_reset(&sc->sc_led_ch, hz, upgt_set_led_blink, sc); break; default: STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next); return; } data_cmd->buflen = sizeof(*mem) + sizeof(*led); mem->chksum = upgt_chksum_le((uint32_t *)led, data_cmd->buflen - sizeof(*mem)); upgt_bulk_tx(sc, data_cmd); } static void upgt_set_led_blink(void *arg) { struct upgt_softc *sc = arg; /* blink finished, we are ready for a next one */ sc->sc_led_blink = 0; } static void upgt_init(struct upgt_softc *sc) { UPGT_ASSERT_LOCKED(sc); if (sc->sc_flags & UPGT_FLAG_INITDONE) upgt_stop(sc); usbd_transfer_start(sc->sc_xfer[UPGT_BULK_RX]); (void)upgt_set_macfilter(sc, IEEE80211_S_SCAN); sc->sc_flags |= UPGT_FLAG_INITDONE; callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); } static int upgt_set_macfilter(struct upgt_softc *sc, uint8_t state) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni; struct upgt_data *data_cmd; struct upgt_lmac_mem *mem; struct upgt_lmac_filter *filter; UPGT_ASSERT_LOCKED(sc); data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { device_printf(sc->sc_dev, "out of TX buffers.\n"); return (ENOBUFS); } /* * Transmit the URB containing the CMD data. */ memset(data_cmd->buf, 0, MCLBYTES); mem = (struct upgt_lmac_mem *)data_cmd->buf; mem->addr = htole32(sc->sc_memaddr_frame_start + UPGT_MEMSIZE_FRAME_HEAD); filter = (struct upgt_lmac_filter *)(mem + 1); filter->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; filter->header1.type = UPGT_H1_TYPE_CTRL; filter->header1.len = htole16( sizeof(struct upgt_lmac_filter) - sizeof(struct upgt_lmac_header)); filter->header2.reqid = htole32(sc->sc_memaddr_frame_start); filter->header2.type = htole16(UPGT_H2_TYPE_MACFILTER); filter->header2.flags = 0; switch (state) { case IEEE80211_S_INIT: DPRINTF(sc, UPGT_DEBUG_STATE, "%s: set MAC filter to INIT\n", __func__); filter->type = htole16(UPGT_FILTER_TYPE_RESET); break; case IEEE80211_S_SCAN: DPRINTF(sc, UPGT_DEBUG_STATE, "set MAC filter to SCAN (bssid %s)\n", ether_sprintf(ieee80211broadcastaddr)); filter->type = htole16(UPGT_FILTER_TYPE_NONE); IEEE80211_ADDR_COPY(filter->dst, vap ? vap->iv_myaddr : ic->ic_macaddr); IEEE80211_ADDR_COPY(filter->src, ieee80211broadcastaddr); filter->unknown1 = htole16(UPGT_FILTER_UNKNOWN1); filter->rxaddr = htole32(sc->sc_memaddr_rx_start); filter->unknown2 = htole16(UPGT_FILTER_UNKNOWN2); filter->rxhw = htole32(sc->sc_eeprom_hwrx); filter->unknown3 = htole16(UPGT_FILTER_UNKNOWN3); break; case IEEE80211_S_RUN: ni = ieee80211_ref_node(vap->iv_bss); /* XXX monitor mode isn't tested yet. */ if (vap->iv_opmode == IEEE80211_M_MONITOR) { filter->type = htole16(UPGT_FILTER_TYPE_MONITOR); IEEE80211_ADDR_COPY(filter->dst, vap ? vap->iv_myaddr : ic->ic_macaddr); IEEE80211_ADDR_COPY(filter->src, ni->ni_bssid); filter->unknown1 = htole16(UPGT_FILTER_MONITOR_UNKNOWN1); filter->rxaddr = htole32(sc->sc_memaddr_rx_start); filter->unknown2 = htole16(UPGT_FILTER_MONITOR_UNKNOWN2); filter->rxhw = htole32(sc->sc_eeprom_hwrx); filter->unknown3 = htole16(UPGT_FILTER_MONITOR_UNKNOWN3); } else { DPRINTF(sc, UPGT_DEBUG_STATE, "set MAC filter to RUN (bssid %s)\n", ether_sprintf(ni->ni_bssid)); filter->type = htole16(UPGT_FILTER_TYPE_STA); IEEE80211_ADDR_COPY(filter->dst, vap ? vap->iv_myaddr : ic->ic_macaddr); IEEE80211_ADDR_COPY(filter->src, ni->ni_bssid); filter->unknown1 = htole16(UPGT_FILTER_UNKNOWN1); filter->rxaddr = htole32(sc->sc_memaddr_rx_start); filter->unknown2 = htole16(UPGT_FILTER_UNKNOWN2); filter->rxhw = htole32(sc->sc_eeprom_hwrx); filter->unknown3 = htole16(UPGT_FILTER_UNKNOWN3); } ieee80211_free_node(ni); break; default: device_printf(sc->sc_dev, "MAC filter does not know that state\n"); break; } data_cmd->buflen = sizeof(*mem) + sizeof(*filter); mem->chksum = upgt_chksum_le((uint32_t *)filter, data_cmd->buflen - sizeof(*mem)); upgt_bulk_tx(sc, data_cmd); return (0); } static void upgt_setup_rates(struct ieee80211vap *vap, struct ieee80211com *ic) { struct upgt_softc *sc = ic->ic_softc; const struct ieee80211_txparam *tp; /* * 0x01 = OFMD6 0x10 = DS1 * 0x04 = OFDM9 0x11 = DS2 * 0x06 = OFDM12 0x12 = DS5 * 0x07 = OFDM18 0x13 = DS11 * 0x08 = OFDM24 * 0x09 = OFDM36 * 0x0a = OFDM48 * 0x0b = OFDM54 */ const uint8_t rateset_auto_11b[] = { 0x13, 0x13, 0x12, 0x11, 0x11, 0x10, 0x10, 0x10 }; const uint8_t rateset_auto_11g[] = { 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x04, 0x01 }; const uint8_t rateset_fix_11bg[] = { 0x10, 0x11, 0x12, 0x13, 0x01, 0x04, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b }; tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; /* XXX */ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) { /* * Automatic rate control is done by the device. * We just pass the rateset from which the device * will pickup a rate. */ if (ic->ic_curmode == IEEE80211_MODE_11B) memcpy(sc->sc_cur_rateset, rateset_auto_11b, sizeof(sc->sc_cur_rateset)); if (ic->ic_curmode == IEEE80211_MODE_11G || ic->ic_curmode == IEEE80211_MODE_AUTO) memcpy(sc->sc_cur_rateset, rateset_auto_11g, sizeof(sc->sc_cur_rateset)); } else { /* set a fixed rate */ memset(sc->sc_cur_rateset, rateset_fix_11bg[tp->ucastrate], sizeof(sc->sc_cur_rateset)); } } static void upgt_set_multi(void *arg) { /* XXX don't know how to set a device. Lack of docs. */ } static int upgt_transmit(struct ieee80211com *ic, struct mbuf *m) { struct upgt_softc *sc = ic->ic_softc; int error; UPGT_LOCK(sc); if ((sc->sc_flags & UPGT_FLAG_INITDONE) == 0) { UPGT_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { UPGT_UNLOCK(sc); return (error); } upgt_start(sc); UPGT_UNLOCK(sc); return (0); } static void upgt_start(struct upgt_softc *sc) { struct upgt_data *data_tx; struct ieee80211_node *ni; struct mbuf *m; UPGT_ASSERT_LOCKED(sc); if ((sc->sc_flags & UPGT_FLAG_INITDONE) == 0) return; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { data_tx = upgt_gettxbuf(sc); if (data_tx == NULL) { mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; if (upgt_tx_start(sc, m, ni, data_tx) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, data_tx, next); UPGT_STAT_INC(sc, st_tx_inactive); ieee80211_free_node(ni); continue; } sc->sc_tx_timer = 5; } } static int upgt_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct upgt_softc *sc = ic->ic_softc; struct upgt_data *data_tx = NULL; UPGT_LOCK(sc); /* prevent management frames from being sent if we're not ready */ if (!(sc->sc_flags & UPGT_FLAG_INITDONE)) { m_freem(m); UPGT_UNLOCK(sc); return ENETDOWN; } data_tx = upgt_gettxbuf(sc); if (data_tx == NULL) { m_freem(m); UPGT_UNLOCK(sc); return (ENOBUFS); } if (upgt_tx_start(sc, m, ni, data_tx) != 0) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, data_tx, next); UPGT_STAT_INC(sc, st_tx_inactive); UPGT_UNLOCK(sc); return (EIO); } UPGT_UNLOCK(sc); sc->sc_tx_timer = 5; return (0); } static void upgt_watchdog(void *arg) { struct upgt_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; if (sc->sc_tx_timer > 0) { if (--sc->sc_tx_timer == 0) { device_printf(sc->sc_dev, "watchdog timeout\n"); /* upgt_init(sc); XXX needs a process context ? */ counter_u64_add(ic->ic_oerrors, 1); return; } callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); } } static uint32_t upgt_mem_alloc(struct upgt_softc *sc) { int i; for (i = 0; i < sc->sc_memory.pages; i++) { if (sc->sc_memory.page[i].used == 0) { sc->sc_memory.page[i].used = 1; return (sc->sc_memory.page[i].addr); } } return (0); } static void upgt_scan_start(struct ieee80211com *ic) { /* do nothing. */ } static void upgt_scan_end(struct ieee80211com *ic) { /* do nothing. */ } static void upgt_set_channel(struct ieee80211com *ic) { struct upgt_softc *sc = ic->ic_softc; UPGT_LOCK(sc); upgt_set_chan(sc, ic->ic_curchan); UPGT_UNLOCK(sc); } static void upgt_set_chan(struct upgt_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; struct upgt_data *data_cmd; struct upgt_lmac_mem *mem; struct upgt_lmac_channel *chan; int channel; UPGT_ASSERT_LOCKED(sc); channel = ieee80211_chan2ieee(ic, c); if (channel == 0 || channel == IEEE80211_CHAN_ANY) { /* XXX should NEVER happen */ device_printf(sc->sc_dev, "%s: invalid channel %x\n", __func__, channel); return; } DPRINTF(sc, UPGT_DEBUG_STATE, "%s: channel %d\n", __func__, channel); data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__); return; } /* * Transmit the URB containing the CMD data. */ memset(data_cmd->buf, 0, MCLBYTES); mem = (struct upgt_lmac_mem *)data_cmd->buf; mem->addr = htole32(sc->sc_memaddr_frame_start + UPGT_MEMSIZE_FRAME_HEAD); chan = (struct upgt_lmac_channel *)(mem + 1); chan->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; chan->header1.type = UPGT_H1_TYPE_CTRL; chan->header1.len = htole16( sizeof(struct upgt_lmac_channel) - sizeof(struct upgt_lmac_header)); chan->header2.reqid = htole32(sc->sc_memaddr_frame_start); chan->header2.type = htole16(UPGT_H2_TYPE_CHANNEL); chan->header2.flags = 0; chan->unknown1 = htole16(UPGT_CHANNEL_UNKNOWN1); chan->unknown2 = htole16(UPGT_CHANNEL_UNKNOWN2); chan->freq6 = sc->sc_eeprom_freq6[channel]; chan->settings = sc->sc_eeprom_freq6_settings; chan->unknown3 = UPGT_CHANNEL_UNKNOWN3; memcpy(chan->freq3_1, &sc->sc_eeprom_freq3[channel].data, sizeof(chan->freq3_1)); memcpy(chan->freq4, &sc->sc_eeprom_freq4[channel], sizeof(sc->sc_eeprom_freq4[channel])); memcpy(chan->freq3_2, &sc->sc_eeprom_freq3[channel].data, sizeof(chan->freq3_2)); data_cmd->buflen = sizeof(*mem) + sizeof(*chan); mem->chksum = upgt_chksum_le((uint32_t *)chan, data_cmd->buflen - sizeof(*mem)); upgt_bulk_tx(sc, data_cmd); } static struct ieee80211vap * upgt_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct upgt_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; uvp = malloc(sizeof(struct upgt_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = upgt_newstate; /* setup device rates */ upgt_setup_rates(vap, ic); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return vap; } static int upgt_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct upgt_vap *uvp = UPGT_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct upgt_softc *sc = ic->ic_softc; /* do it in a process context */ sc->sc_state = nstate; IEEE80211_UNLOCK(ic); UPGT_LOCK(sc); callout_stop(&sc->sc_led_ch); callout_stop(&sc->sc_watchdog_ch); switch (nstate) { case IEEE80211_S_INIT: /* do not accept any frames if the device is down */ (void)upgt_set_macfilter(sc, sc->sc_state); upgt_set_led(sc, UPGT_LED_OFF); break; case IEEE80211_S_SCAN: upgt_set_chan(sc, ic->ic_curchan); break; case IEEE80211_S_AUTH: upgt_set_chan(sc, ic->ic_curchan); break; case IEEE80211_S_ASSOC: break; case IEEE80211_S_RUN: upgt_set_macfilter(sc, sc->sc_state); upgt_set_led(sc, UPGT_LED_ON); break; default: break; } UPGT_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } static void upgt_vap_delete(struct ieee80211vap *vap) { struct upgt_vap *uvp = UPGT_VAP(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void upgt_update_mcast(struct ieee80211com *ic) { struct upgt_softc *sc = ic->ic_softc; upgt_set_multi(sc); } static int upgt_eeprom_parse(struct upgt_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct upgt_eeprom_header *eeprom_header; struct upgt_eeprom_option *eeprom_option; uint16_t option_len; uint16_t option_type; uint16_t preamble_len; int option_end = 0; /* calculate eeprom options start offset */ eeprom_header = (struct upgt_eeprom_header *)sc->sc_eeprom; preamble_len = le16toh(eeprom_header->preamble_len); eeprom_option = (struct upgt_eeprom_option *)(sc->sc_eeprom + (sizeof(struct upgt_eeprom_header) + preamble_len)); while (!option_end) { /* sanity check */ if (eeprom_option >= (struct upgt_eeprom_option *) (sc->sc_eeprom + UPGT_EEPROM_SIZE)) { return (EINVAL); } /* the eeprom option length is stored in words */ option_len = (le16toh(eeprom_option->len) - 1) * sizeof(uint16_t); option_type = le16toh(eeprom_option->type); /* sanity check */ if (option_len == 0 || option_len >= UPGT_EEPROM_SIZE) return (EINVAL); switch (option_type) { case UPGT_EEPROM_TYPE_NAME: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM name len=%d\n", option_len); break; case UPGT_EEPROM_TYPE_SERIAL: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM serial len=%d\n", option_len); break; case UPGT_EEPROM_TYPE_MAC: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM mac len=%d\n", option_len); IEEE80211_ADDR_COPY(ic->ic_macaddr, eeprom_option->data); break; case UPGT_EEPROM_TYPE_HWRX: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM hwrx len=%d\n", option_len); upgt_eeprom_parse_hwrx(sc, eeprom_option->data); break; case UPGT_EEPROM_TYPE_CHIP: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM chip len=%d\n", option_len); break; case UPGT_EEPROM_TYPE_FREQ3: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM freq3 len=%d\n", option_len); upgt_eeprom_parse_freq3(sc, eeprom_option->data, option_len); break; case UPGT_EEPROM_TYPE_FREQ4: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM freq4 len=%d\n", option_len); upgt_eeprom_parse_freq4(sc, eeprom_option->data, option_len); break; case UPGT_EEPROM_TYPE_FREQ5: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM freq5 len=%d\n", option_len); break; case UPGT_EEPROM_TYPE_FREQ6: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM freq6 len=%d\n", option_len); upgt_eeprom_parse_freq6(sc, eeprom_option->data, option_len); break; case UPGT_EEPROM_TYPE_END: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM end len=%d\n", option_len); option_end = 1; break; case UPGT_EEPROM_TYPE_OFF: DPRINTF(sc, UPGT_DEBUG_FW, "%s: EEPROM off without end option\n", __func__); return (EIO); default: DPRINTF(sc, UPGT_DEBUG_FW, "EEPROM unknown type 0x%04x len=%d\n", option_type, option_len); break; } /* jump to next EEPROM option */ eeprom_option = (struct upgt_eeprom_option *) (eeprom_option->data + option_len); } return (0); } static void upgt_eeprom_parse_freq3(struct upgt_softc *sc, uint8_t *data, int len) { struct upgt_eeprom_freq3_header *freq3_header; struct upgt_lmac_freq3 *freq3; int i; int elements; int flags; unsigned channel; freq3_header = (struct upgt_eeprom_freq3_header *)data; freq3 = (struct upgt_lmac_freq3 *)(freq3_header + 1); flags = freq3_header->flags; elements = freq3_header->elements; DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d\n", flags, elements); if (elements >= (int)(UPGT_EEPROM_SIZE / sizeof(freq3[0]))) return; for (i = 0; i < elements; i++) { channel = ieee80211_mhz2ieee(le16toh(freq3[i].freq), 0); if (channel >= IEEE80211_CHAN_MAX) continue; sc->sc_eeprom_freq3[channel] = freq3[i]; DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", le16toh(sc->sc_eeprom_freq3[channel].freq), channel); } } void upgt_eeprom_parse_freq4(struct upgt_softc *sc, uint8_t *data, int len) { struct upgt_eeprom_freq4_header *freq4_header; struct upgt_eeprom_freq4_1 *freq4_1; struct upgt_eeprom_freq4_2 *freq4_2; int i; int j; int elements; int settings; int flags; unsigned channel; freq4_header = (struct upgt_eeprom_freq4_header *)data; freq4_1 = (struct upgt_eeprom_freq4_1 *)(freq4_header + 1); flags = freq4_header->flags; elements = freq4_header->elements; settings = freq4_header->settings; /* we need this value later */ sc->sc_eeprom_freq6_settings = freq4_header->settings; DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d settings=%d\n", flags, elements, settings); if (elements >= (int)(UPGT_EEPROM_SIZE / sizeof(freq4_1[0]))) return; for (i = 0; i < elements; i++) { channel = ieee80211_mhz2ieee(le16toh(freq4_1[i].freq), 0); if (channel >= IEEE80211_CHAN_MAX) continue; freq4_2 = (struct upgt_eeprom_freq4_2 *)freq4_1[i].data; for (j = 0; j < settings; j++) { sc->sc_eeprom_freq4[channel][j].cmd = freq4_2[j]; sc->sc_eeprom_freq4[channel][j].pad = 0; } DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", le16toh(freq4_1[i].freq), channel); } } void upgt_eeprom_parse_freq6(struct upgt_softc *sc, uint8_t *data, int len) { struct upgt_lmac_freq6 *freq6; int i; int elements; unsigned channel; freq6 = (struct upgt_lmac_freq6 *)data; elements = len / sizeof(struct upgt_lmac_freq6); DPRINTF(sc, UPGT_DEBUG_FW, "elements=%d\n", elements); if (elements >= (int)(UPGT_EEPROM_SIZE / sizeof(freq6[0]))) return; for (i = 0; i < elements; i++) { channel = ieee80211_mhz2ieee(le16toh(freq6[i].freq), 0); if (channel >= IEEE80211_CHAN_MAX) continue; sc->sc_eeprom_freq6[channel] = freq6[i]; DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", le16toh(sc->sc_eeprom_freq6[channel].freq), channel); } } static void upgt_eeprom_parse_hwrx(struct upgt_softc *sc, uint8_t *data) { struct upgt_eeprom_option_hwrx *option_hwrx; option_hwrx = (struct upgt_eeprom_option_hwrx *)data; sc->sc_eeprom_hwrx = option_hwrx->rxfilter - UPGT_EEPROM_RX_CONST; DPRINTF(sc, UPGT_DEBUG_FW, "hwrx option value=0x%04x\n", sc->sc_eeprom_hwrx); } static int upgt_eeprom_read(struct upgt_softc *sc) { struct upgt_data *data_cmd; struct upgt_lmac_mem *mem; struct upgt_lmac_eeprom *eeprom; int block, error, offset; UPGT_LOCK(sc); usb_pause_mtx(&sc->sc_mtx, 100); offset = 0; block = UPGT_EEPROM_BLOCK_SIZE; while (offset < UPGT_EEPROM_SIZE) { DPRINTF(sc, UPGT_DEBUG_FW, "request EEPROM block (offset=%d, len=%d)\n", offset, block); data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { UPGT_UNLOCK(sc); return (ENOBUFS); } /* * Transmit the URB containing the CMD data. */ memset(data_cmd->buf, 0, MCLBYTES); mem = (struct upgt_lmac_mem *)data_cmd->buf; mem->addr = htole32(sc->sc_memaddr_frame_start + UPGT_MEMSIZE_FRAME_HEAD); eeprom = (struct upgt_lmac_eeprom *)(mem + 1); eeprom->header1.flags = 0; eeprom->header1.type = UPGT_H1_TYPE_CTRL; eeprom->header1.len = htole16(( sizeof(struct upgt_lmac_eeprom) - sizeof(struct upgt_lmac_header)) + block); eeprom->header2.reqid = htole32(sc->sc_memaddr_frame_start); eeprom->header2.type = htole16(UPGT_H2_TYPE_EEPROM); eeprom->header2.flags = 0; eeprom->offset = htole16(offset); eeprom->len = htole16(block); data_cmd->buflen = sizeof(*mem) + sizeof(*eeprom) + block; mem->chksum = upgt_chksum_le((uint32_t *)eeprom, data_cmd->buflen - sizeof(*mem)); upgt_bulk_tx(sc, data_cmd); error = mtx_sleep(sc, &sc->sc_mtx, 0, "eeprom_request", hz); if (error != 0) { device_printf(sc->sc_dev, "timeout while waiting for EEPROM data\n"); UPGT_UNLOCK(sc); return (EIO); } offset += block; if (UPGT_EEPROM_SIZE - offset < block) block = UPGT_EEPROM_SIZE - offset; } UPGT_UNLOCK(sc); return (0); } /* * When a rx data came in the function returns a mbuf and a rssi values. */ static struct mbuf * upgt_rxeof(struct usb_xfer *xfer, struct upgt_data *data, int *rssi) { struct mbuf *m = NULL; struct upgt_softc *sc = usbd_xfer_softc(xfer); struct upgt_lmac_header *header; struct upgt_lmac_eeprom *eeprom; uint8_t h1_type; uint16_t h2_type; int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); UPGT_ASSERT_LOCKED(sc); if (actlen < 1) return (NULL); /* Check only at the very beginning. */ if (!(sc->sc_flags & UPGT_FLAG_FWLOADED) && (memcmp(data->buf, "OK", 2) == 0)) { sc->sc_flags |= UPGT_FLAG_FWLOADED; wakeup_one(sc); return (NULL); } if (actlen < (int)UPGT_RX_MINSZ) return (NULL); /* * Check what type of frame came in. */ header = (struct upgt_lmac_header *)(data->buf + 4); h1_type = header->header1.type; h2_type = le16toh(header->header2.type); if (h1_type == UPGT_H1_TYPE_CTRL && h2_type == UPGT_H2_TYPE_EEPROM) { eeprom = (struct upgt_lmac_eeprom *)(data->buf + 4); uint16_t eeprom_offset = le16toh(eeprom->offset); uint16_t eeprom_len = le16toh(eeprom->len); DPRINTF(sc, UPGT_DEBUG_FW, "received EEPROM block (offset=%d, len=%d)\n", eeprom_offset, eeprom_len); memcpy(sc->sc_eeprom + eeprom_offset, data->buf + sizeof(struct upgt_lmac_eeprom) + 4, eeprom_len); /* EEPROM data has arrived in time, wakeup. */ wakeup(sc); } else if (h1_type == UPGT_H1_TYPE_CTRL && h2_type == UPGT_H2_TYPE_TX_DONE) { DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: received 802.11 TX done\n", __func__); upgt_tx_done(sc, data->buf + 4); } else if (h1_type == UPGT_H1_TYPE_RX_DATA || h1_type == UPGT_H1_TYPE_RX_DATA_MGMT) { DPRINTF(sc, UPGT_DEBUG_RECV, "%s: received 802.11 RX data\n", __func__); m = upgt_rx(sc, data->buf + 4, le16toh(header->header1.len), rssi); } else if (h1_type == UPGT_H1_TYPE_CTRL && h2_type == UPGT_H2_TYPE_STATS) { DPRINTF(sc, UPGT_DEBUG_STAT, "%s: received statistic data\n", __func__); /* TODO: what could we do with the statistic data? */ } else { /* ignore unknown frame types */ DPRINTF(sc, UPGT_DEBUG_INTR, "received unknown frame type 0x%02x\n", header->header1.type); } return (m); } /* * The firmware awaits a checksum for each frame we send to it. * The algorithm used therefor is uncommon but somehow similar to CRC32. */ static uint32_t upgt_chksum_le(const uint32_t *buf, size_t size) { size_t i; uint32_t crc = 0; for (i = 0; i < size; i += sizeof(uint32_t)) { crc = htole32(crc ^ *buf++); crc = htole32((crc >> 5) ^ (crc << 3)); } return (crc); } static struct mbuf * upgt_rx(struct upgt_softc *sc, uint8_t *data, int pkglen, int *rssi) { struct ieee80211com *ic = &sc->sc_ic; struct upgt_lmac_rx_desc *rxdesc; struct mbuf *m; /* * don't pass packets to the ieee80211 framework if the driver isn't * RUNNING. */ if (!(sc->sc_flags & UPGT_FLAG_INITDONE)) return (NULL); /* access RX packet descriptor */ rxdesc = (struct upgt_lmac_rx_desc *)data; /* create mbuf which is suitable for strict alignment archs */ KASSERT((pkglen + ETHER_ALIGN) < MCLBYTES, ("A current mbuf storage is small (%d)", pkglen + ETHER_ALIGN)); m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { device_printf(sc->sc_dev, "could not create RX mbuf\n"); return (NULL); } m_adj(m, ETHER_ALIGN); memcpy(mtod(m, char *), rxdesc->data, pkglen); /* trim FCS */ m->m_len = m->m_pkthdr.len = pkglen - IEEE80211_CRC_LEN; if (ieee80211_radiotap_active(ic)) { struct upgt_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; tap->wr_rate = upgt_rx_rate(sc, rxdesc->rate); tap->wr_antsignal = rxdesc->rssi; } DPRINTF(sc, UPGT_DEBUG_RX_PROC, "%s: RX done\n", __func__); *rssi = rxdesc->rssi; return (m); } static uint8_t upgt_rx_rate(struct upgt_softc *sc, const int rate) { struct ieee80211com *ic = &sc->sc_ic; static const uint8_t cck_upgt2rate[4] = { 2, 4, 11, 22 }; static const uint8_t ofdm_upgt2rate[12] = { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 }; if (ic->ic_curmode == IEEE80211_MODE_11B && !(rate < 0 || rate > 3)) return cck_upgt2rate[rate & 0xf]; if (ic->ic_curmode == IEEE80211_MODE_11G && !(rate < 0 || rate > 11)) return ofdm_upgt2rate[rate & 0xf]; return (0); } static void upgt_tx_done(struct upgt_softc *sc, uint8_t *data) { struct upgt_lmac_tx_done_desc *desc; int i, freed = 0; UPGT_ASSERT_LOCKED(sc); desc = (struct upgt_lmac_tx_done_desc *)data; for (i = 0; i < UPGT_TX_MAXCOUNT; i++) { struct upgt_data *data_tx = &sc->sc_tx_data[i]; if (data_tx->addr == le32toh(desc->header2.reqid)) { upgt_mem_free(sc, data_tx->addr); data_tx->ni = NULL; data_tx->addr = 0; data_tx->m = NULL; DPRINTF(sc, UPGT_DEBUG_TX_PROC, "TX done: memaddr=0x%08x, status=0x%04x, rssi=%d, ", le32toh(desc->header2.reqid), le16toh(desc->status), le16toh(desc->rssi)); DPRINTF(sc, UPGT_DEBUG_TX_PROC, "seq=%d\n", le16toh(desc->seq)); freed++; } } if (freed != 0) { UPGT_UNLOCK(sc); sc->sc_tx_timer = 0; upgt_start(sc); UPGT_LOCK(sc); } } static void upgt_mem_free(struct upgt_softc *sc, uint32_t addr) { int i; for (i = 0; i < sc->sc_memory.pages; i++) { if (sc->sc_memory.page[i].addr == addr) { sc->sc_memory.page[i].used = 0; return; } } device_printf(sc->sc_dev, "could not free memory address 0x%08x\n", addr); } static int upgt_fw_load(struct upgt_softc *sc) { const struct firmware *fw; struct upgt_data *data_cmd; struct upgt_fw_x2_header *x2; char start_fwload_cmd[] = { 0x3c, 0x0d }; int error = 0; size_t offset; int bsize; int n; uint32_t crc32; fw = firmware_get(upgt_fwname); if (fw == NULL) { device_printf(sc->sc_dev, "could not read microcode %s\n", upgt_fwname); return (EIO); } UPGT_LOCK(sc); /* send firmware start load command */ data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { error = ENOBUFS; goto fail; } data_cmd->buflen = sizeof(start_fwload_cmd); memcpy(data_cmd->buf, start_fwload_cmd, data_cmd->buflen); upgt_bulk_tx(sc, data_cmd); /* send X2 header */ data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { error = ENOBUFS; goto fail; } data_cmd->buflen = sizeof(struct upgt_fw_x2_header); x2 = (struct upgt_fw_x2_header *)data_cmd->buf; memcpy(x2->signature, UPGT_X2_SIGNATURE, UPGT_X2_SIGNATURE_SIZE); x2->startaddr = htole32(UPGT_MEMADDR_FIRMWARE_START); x2->len = htole32(fw->datasize); x2->crc = upgt_crc32_le((uint8_t *)data_cmd->buf + UPGT_X2_SIGNATURE_SIZE, sizeof(struct upgt_fw_x2_header) - UPGT_X2_SIGNATURE_SIZE - sizeof(uint32_t)); upgt_bulk_tx(sc, data_cmd); /* download firmware */ for (offset = 0; offset < fw->datasize; offset += bsize) { if (fw->datasize - offset > UPGT_FW_BLOCK_SIZE) bsize = UPGT_FW_BLOCK_SIZE; else bsize = fw->datasize - offset; data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { error = ENOBUFS; goto fail; } n = upgt_fw_copy((const uint8_t *)fw->data + offset, data_cmd->buf, bsize); data_cmd->buflen = bsize; upgt_bulk_tx(sc, data_cmd); DPRINTF(sc, UPGT_DEBUG_FW, "FW offset=%zu, read=%d, sent=%d\n", offset, n, bsize); bsize = n; } DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware downloaded\n", __func__); /* load firmware */ data_cmd = upgt_getbuf(sc); if (data_cmd == NULL) { error = ENOBUFS; goto fail; } crc32 = upgt_crc32_le(fw->data, fw->datasize); *((uint32_t *)(data_cmd->buf) ) = crc32; *((uint8_t *)(data_cmd->buf) + 4) = 'g'; *((uint8_t *)(data_cmd->buf) + 5) = '\r'; data_cmd->buflen = 6; upgt_bulk_tx(sc, data_cmd); /* waiting 'OK' response. */ usbd_transfer_start(sc->sc_xfer[UPGT_BULK_RX]); error = mtx_sleep(sc, &sc->sc_mtx, 0, "upgtfw", 2 * hz); if (error != 0) { device_printf(sc->sc_dev, "firmware load failed\n"); error = EIO; } DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware loaded\n", __func__); fail: UPGT_UNLOCK(sc); firmware_put(fw, FIRMWARE_UNLOAD); return (error); } static uint32_t upgt_crc32_le(const void *buf, size_t size) { uint32_t crc; crc = ether_crc32_le(buf, size); /* apply final XOR value as common for CRC-32 */ crc = htole32(crc ^ 0xffffffffU); return (crc); } /* * While copying the version 2 firmware, we need to replace two characters: * * 0x7e -> 0x7d 0x5e * 0x7d -> 0x7d 0x5d */ static int upgt_fw_copy(const uint8_t *src, char *dst, int size) { int i, j; for (i = 0, j = 0; i < size && j < size; i++) { switch (src[i]) { case 0x7e: dst[j] = 0x7d; j++; dst[j] = 0x5e; j++; break; case 0x7d: dst[j] = 0x7d; j++; dst[j] = 0x5d; j++; break; default: dst[j] = src[i]; j++; break; } } return (i); } static int upgt_mem_init(struct upgt_softc *sc) { int i; for (i = 0; i < UPGT_MEMORY_MAX_PAGES; i++) { sc->sc_memory.page[i].used = 0; if (i == 0) { /* * The first memory page is always reserved for * command data. */ sc->sc_memory.page[i].addr = sc->sc_memaddr_frame_start + MCLBYTES; } else { sc->sc_memory.page[i].addr = sc->sc_memory.page[i - 1].addr + MCLBYTES; } if (sc->sc_memory.page[i].addr + MCLBYTES >= sc->sc_memaddr_frame_end) break; DPRINTF(sc, UPGT_DEBUG_FW, "memory address page %d=0x%08x\n", i, sc->sc_memory.page[i].addr); } sc->sc_memory.pages = i; DPRINTF(sc, UPGT_DEBUG_FW, "memory pages=%d\n", sc->sc_memory.pages); return (0); } static int upgt_fw_verify(struct upgt_softc *sc) { const struct firmware *fw; const struct upgt_fw_bra_option *bra_opt; const struct upgt_fw_bra_descr *descr; const uint8_t *p; const uint32_t *uc; uint32_t bra_option_type, bra_option_len; size_t offset; int bra_end = 0; int error = 0; fw = firmware_get(upgt_fwname); if (fw == NULL) { device_printf(sc->sc_dev, "could not read microcode %s\n", upgt_fwname); return EIO; } /* * Seek to beginning of Boot Record Area (BRA). */ for (offset = 0; offset < fw->datasize; offset += sizeof(*uc)) { uc = (const uint32_t *)((const uint8_t *)fw->data + offset); if (*uc == 0) break; } for (; offset < fw->datasize; offset += sizeof(*uc)) { uc = (const uint32_t *)((const uint8_t *)fw->data + offset); if (*uc != 0) break; } if (offset == fw->datasize) { device_printf(sc->sc_dev, "firmware Boot Record Area not found\n"); error = EIO; goto fail; } DPRINTF(sc, UPGT_DEBUG_FW, "firmware Boot Record Area found at offset %zu\n", offset); /* * Parse Boot Record Area (BRA) options. */ while (offset < fw->datasize && bra_end == 0) { /* get current BRA option */ p = (const uint8_t *)fw->data + offset; bra_opt = (const struct upgt_fw_bra_option *)p; bra_option_type = le32toh(bra_opt->type); bra_option_len = le32toh(bra_opt->len) * sizeof(*uc); switch (bra_option_type) { case UPGT_BRA_TYPE_FW: DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_FW len=%d\n", bra_option_len); if (bra_option_len != UPGT_BRA_FWTYPE_SIZE) { device_printf(sc->sc_dev, "wrong UPGT_BRA_TYPE_FW len\n"); error = EIO; goto fail; } if (memcmp(UPGT_BRA_FWTYPE_LM86, bra_opt->data, bra_option_len) == 0) { sc->sc_fw_type = UPGT_FWTYPE_LM86; break; } if (memcmp(UPGT_BRA_FWTYPE_LM87, bra_opt->data, bra_option_len) == 0) { sc->sc_fw_type = UPGT_FWTYPE_LM87; break; } device_printf(sc->sc_dev, "unsupported firmware type\n"); error = EIO; goto fail; case UPGT_BRA_TYPE_VERSION: DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_VERSION len=%d\n", bra_option_len); break; case UPGT_BRA_TYPE_DEPIF: DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_DEPIF len=%d\n", bra_option_len); break; case UPGT_BRA_TYPE_EXPIF: DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_EXPIF len=%d\n", bra_option_len); break; case UPGT_BRA_TYPE_DESCR: DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_DESCR len=%d\n", bra_option_len); descr = (const struct upgt_fw_bra_descr *)bra_opt->data; sc->sc_memaddr_frame_start = le32toh(descr->memaddr_space_start); sc->sc_memaddr_frame_end = le32toh(descr->memaddr_space_end); DPRINTF(sc, UPGT_DEBUG_FW, "memory address space start=0x%08x\n", sc->sc_memaddr_frame_start); DPRINTF(sc, UPGT_DEBUG_FW, "memory address space end=0x%08x\n", sc->sc_memaddr_frame_end); break; case UPGT_BRA_TYPE_END: DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_END len=%d\n", bra_option_len); bra_end = 1; break; default: DPRINTF(sc, UPGT_DEBUG_FW, "unknown BRA option len=%d\n", bra_option_len); error = EIO; goto fail; } /* jump to next BRA option */ offset += sizeof(struct upgt_fw_bra_option) + bra_option_len; } DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware verified", __func__); fail: firmware_put(fw, FIRMWARE_UNLOAD); return (error); } static void upgt_bulk_tx(struct upgt_softc *sc, struct upgt_data *data) { UPGT_ASSERT_LOCKED(sc); STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); UPGT_STAT_INC(sc, st_tx_pending); usbd_transfer_start(sc->sc_xfer[UPGT_BULK_TX]); } static int upgt_device_reset(struct upgt_softc *sc) { struct upgt_data *data; char init_cmd[] = { 0x7e, 0x7e, 0x7e, 0x7e }; UPGT_LOCK(sc); data = upgt_getbuf(sc); if (data == NULL) { UPGT_UNLOCK(sc); return (ENOBUFS); } memcpy(data->buf, init_cmd, sizeof(init_cmd)); data->buflen = sizeof(init_cmd); upgt_bulk_tx(sc, data); usb_pause_mtx(&sc->sc_mtx, 100); UPGT_UNLOCK(sc); DPRINTF(sc, UPGT_DEBUG_FW, "%s: device initialized\n", __func__); return (0); } static int upgt_alloc_tx(struct upgt_softc *sc) { int i; STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); for (i = 0; i < UPGT_TX_MAXCOUNT; i++) { struct upgt_data *data = &sc->sc_tx_data[i]; data->buf = ((uint8_t *)sc->sc_tx_dma_buf) + (i * MCLBYTES); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); UPGT_STAT_INC(sc, st_tx_inactive); } return (0); } static int upgt_alloc_rx(struct upgt_softc *sc) { int i; STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < UPGT_RX_MAXCOUNT; i++) { struct upgt_data *data = &sc->sc_rx_data[i]; data->buf = ((uint8_t *)sc->sc_rx_dma_buf) + (i * MCLBYTES); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } return (0); } static int upgt_detach(device_t dev) { struct upgt_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; unsigned int x; /* * Prevent further allocations from RX/TX/CMD * data lists and ioctls */ UPGT_LOCK(sc); sc->sc_flags |= UPGT_FLAG_DETACHED; STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); upgt_stop(sc); UPGT_UNLOCK(sc); callout_drain(&sc->sc_led_ch); callout_drain(&sc->sc_watchdog_ch); /* drain USB transfers */ for (x = 0; x != UPGT_N_XFERS; x++) usbd_transfer_drain(sc->sc_xfer[x]); /* free data buffers */ UPGT_LOCK(sc); upgt_free_rx(sc); upgt_free_tx(sc); UPGT_UNLOCK(sc); /* free USB transfers and some data buffers */ usbd_transfer_unsetup(sc->sc_xfer, UPGT_N_XFERS); ieee80211_ifdetach(ic); mbufq_drain(&sc->sc_snd); mtx_destroy(&sc->sc_mtx); return (0); } static void upgt_free_rx(struct upgt_softc *sc) { int i; for (i = 0; i < UPGT_RX_MAXCOUNT; i++) { struct upgt_data *data = &sc->sc_rx_data[i]; data->buf = NULL; data->ni = NULL; } } static void upgt_free_tx(struct upgt_softc *sc) { int i; for (i = 0; i < UPGT_TX_MAXCOUNT; i++) { struct upgt_data *data = &sc->sc_tx_data[i]; if (data->ni != NULL) ieee80211_free_node(data->ni); data->buf = NULL; data->ni = NULL; } } static void upgt_abort_xfers_locked(struct upgt_softc *sc) { int i; UPGT_ASSERT_LOCKED(sc); /* abort any pending transfers */ for (i = 0; i < UPGT_N_XFERS; i++) usbd_transfer_stop(sc->sc_xfer[i]); } static void upgt_abort_xfers(struct upgt_softc *sc) { UPGT_LOCK(sc); upgt_abort_xfers_locked(sc); UPGT_UNLOCK(sc); } #define UPGT_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) static void upgt_sysctl_node(struct upgt_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child; struct sysctl_oid *tree; struct upgt_stat *stats; stats = &sc->sc_stat; ctx = device_get_sysctl_ctx(sc->sc_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); - tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, - NULL, "UPGT statistics"); + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "UPGT statistics"); child = SYSCTL_CHILDREN(tree); UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_active", &stats->st_tx_active, "Active numbers in TX queue"); UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive", &stats->st_tx_inactive, "Inactive numbers in TX queue"); UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_pending", &stats->st_tx_pending, "Pending numbers in TX queue"); } #undef UPGT_SYSCTL_STAT_ADD32 static struct upgt_data * _upgt_getbuf(struct upgt_softc *sc) { struct upgt_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); UPGT_STAT_DEC(sc, st_tx_inactive); } else bf = NULL; if (bf == NULL) DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: %s\n", __func__, "out of xmit buffers"); return (bf); } static struct upgt_data * upgt_getbuf(struct upgt_softc *sc) { struct upgt_data *bf; UPGT_ASSERT_LOCKED(sc); bf = _upgt_getbuf(sc); if (bf == NULL) DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: stop queue\n", __func__); return (bf); } static struct upgt_data * upgt_gettxbuf(struct upgt_softc *sc) { struct upgt_data *bf; UPGT_ASSERT_LOCKED(sc); bf = upgt_getbuf(sc); if (bf == NULL) return (NULL); bf->addr = upgt_mem_alloc(sc); if (bf->addr == 0) { DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: no free prism memory!\n", __func__); STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); UPGT_STAT_INC(sc, st_tx_inactive); return (NULL); } return (bf); } static int upgt_tx_start(struct upgt_softc *sc, struct mbuf *m, struct ieee80211_node *ni, struct upgt_data *data) { struct ieee80211vap *vap = ni->ni_vap; int error = 0, len; struct ieee80211_frame *wh; struct ieee80211_key *k; struct upgt_lmac_mem *mem; struct upgt_lmac_tx_desc *txdesc; UPGT_ASSERT_LOCKED(sc); upgt_set_led(sc, UPGT_LED_BLINK); /* * Software crypto. */ wh = mtod(m, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m); if (k == NULL) { device_printf(sc->sc_dev, "ieee80211_crypto_encap returns NULL.\n"); error = EIO; goto done; } /* in case packet header moved, reset pointer */ wh = mtod(m, struct ieee80211_frame *); } /* Transmit the URB containing the TX data. */ memset(data->buf, 0, MCLBYTES); mem = (struct upgt_lmac_mem *)data->buf; mem->addr = htole32(data->addr); txdesc = (struct upgt_lmac_tx_desc *)(mem + 1); if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_MGT) { /* mgmt frames */ txdesc->header1.flags = UPGT_H1_FLAGS_TX_MGMT; /* always send mgmt frames at lowest rate (DS1) */ memset(txdesc->rates, 0x10, sizeof(txdesc->rates)); } else { /* data frames */ txdesc->header1.flags = UPGT_H1_FLAGS_TX_DATA; memcpy(txdesc->rates, sc->sc_cur_rateset, sizeof(txdesc->rates)); } txdesc->header1.type = UPGT_H1_TYPE_TX_DATA; txdesc->header1.len = htole16(m->m_pkthdr.len); txdesc->header2.reqid = htole32(data->addr); txdesc->header2.type = htole16(UPGT_H2_TYPE_TX_ACK_YES); txdesc->header2.flags = htole16(UPGT_H2_FLAGS_TX_ACK_YES); txdesc->type = htole32(UPGT_TX_DESC_TYPE_DATA); txdesc->pad3[0] = UPGT_TX_DESC_PAD3_SIZE; if (ieee80211_radiotap_active_vap(vap)) { struct upgt_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = 0; /* XXX where to get from? */ ieee80211_radiotap_tx(vap, m); } /* copy frame below our TX descriptor header */ m_copydata(m, 0, m->m_pkthdr.len, data->buf + (sizeof(*mem) + sizeof(*txdesc))); /* calculate frame size */ len = sizeof(*mem) + sizeof(*txdesc) + m->m_pkthdr.len; /* we need to align the frame to a 4 byte boundary */ len = (len + 3) & ~3; /* calculate frame checksum */ mem->chksum = upgt_chksum_le((uint32_t *)txdesc, len - sizeof(*mem)); data->ni = ni; data->m = m; data->buflen = len; DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: TX start data sending (%d bytes)\n", __func__, len); KASSERT(len <= MCLBYTES, ("mbuf is small for saving data")); upgt_bulk_tx(sc, data); done: /* * If we don't regulary read the device statistics, the RX queue * will stall. It's strange, but it works, so we keep reading * the statistics here. *shrug* */ if (!(vap->iv_ifp->if_get_counter(vap->iv_ifp, IFCOUNTER_OPACKETS) % UPGT_TX_STAT_INTERVAL)) upgt_get_stats(sc); return (error); } static void upgt_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct upgt_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct epoch_tracker et; struct mbuf *m = NULL; struct upgt_data *data; int8_t nf; int rssi = -1; UPGT_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); m = upgt_rxeof(xfer, data, &rssi); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: setup: data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) return; STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, MCLBYTES); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ UPGT_UNLOCK(sc); if (m != NULL) { wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); nf = -95; /* XXX */ NET_EPOCH_ENTER(et); if (ni != NULL) { (void) ieee80211_input(ni, m, rssi, nf); /* node is no longer needed */ ieee80211_free_node(ni); } else (void) ieee80211_input_all(ic, m, rssi, nf); NET_EPOCH_EXIT(et); m = NULL; } UPGT_LOCK(sc); upgt_start(sc); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto setup; } break; } } static void upgt_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct upgt_softc *sc = usbd_xfer_softc(xfer); struct upgt_data *data; UPGT_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); UPGT_STAT_DEC(sc, st_tx_active); upgt_txeof(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); UPGT_STAT_INC(sc, st_tx_inactive); /* FALLTHROUGH */ case USB_ST_SETUP: setup: data = STAILQ_FIRST(&sc->sc_tx_pending); if (data == NULL) { DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: empty pending queue\n", __func__); return; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); UPGT_STAT_DEC(sc, st_tx_pending); STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); UPGT_STAT_INC(sc, st_tx_active); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); usbd_transfer_submit(xfer); upgt_start(sc); break; default: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto setup; if (data->ni != NULL) { if_inc_counter(data->ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(data->ni); data->ni = NULL; } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto setup; } break; } } static device_method_t upgt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, upgt_match), DEVMETHOD(device_attach, upgt_attach), DEVMETHOD(device_detach, upgt_detach), DEVMETHOD_END }; static driver_t upgt_driver = { .name = "upgt", .methods = upgt_methods, .size = sizeof(struct upgt_softc) }; static devclass_t upgt_devclass; DRIVER_MODULE(if_upgt, uhub, upgt_driver, upgt_devclass, NULL, 0); MODULE_VERSION(if_upgt, 1); MODULE_DEPEND(if_upgt, usb, 1, 1, 1); MODULE_DEPEND(if_upgt, wlan, 1, 1, 1); MODULE_DEPEND(if_upgt, upgtfw_fw, 1, 1, 1); USB_PNP_HOST_INFO(upgt_devs); Index: head/sys/dev/usb/wlan/if_ural.c =================================================================== --- head/sys/dev/usb/wlan/if_ural.c (revision 357971) +++ head/sys/dev/usb/wlan/if_ural.c (revision 357972) @@ -1,2225 +1,2226 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2005, 2006 * Damien Bergamini * * Copyright (c) 2006, 2008 * Hans Petter Selasky * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /*- * Ralink Technology RT2500USB chipset driver * http://www.ralinktech.com/ */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR ural_debug #include #include #include #ifdef USB_DEBUG static int ural_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW, 0, "USB ural"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB ural"); SYSCTL_INT(_hw_usb_ural, OID_AUTO, debug, CTLFLAG_RWTUN, &ural_debug, 0, "Debug level"); #endif #define URAL_RSSI(rssi) \ ((rssi) > (RAL_NOISE_FLOOR + RAL_RSSI_CORR) ? \ ((rssi) - (RAL_NOISE_FLOOR + RAL_RSSI_CORR)) : 0) /* various supported device vendors/products */ static const STRUCT_USB_HOST_ID ural_devs[] = { #define URAL_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } URAL_DEV(ASUS, WL167G), URAL_DEV(ASUS, RT2570), URAL_DEV(BELKIN, F5D7050), URAL_DEV(BELKIN, F5D7051), URAL_DEV(CISCOLINKSYS, HU200TS), URAL_DEV(CISCOLINKSYS, WUSB54G), URAL_DEV(CISCOLINKSYS, WUSB54GP), URAL_DEV(CONCEPTRONIC2, C54RU), URAL_DEV(DLINK, DWLG122), URAL_DEV(GIGABYTE, GN54G), URAL_DEV(GIGABYTE, GNWBKG), URAL_DEV(GUILLEMOT, HWGUSB254), URAL_DEV(MELCO, KG54), URAL_DEV(MELCO, KG54AI), URAL_DEV(MELCO, KG54YB), URAL_DEV(MELCO, NINWIFI), URAL_DEV(MSI, RT2570), URAL_DEV(MSI, RT2570_2), URAL_DEV(MSI, RT2570_3), URAL_DEV(NOVATECH, NV902), URAL_DEV(RALINK, RT2570), URAL_DEV(RALINK, RT2570_2), URAL_DEV(RALINK, RT2570_3), URAL_DEV(SIEMENS2, WL54G), URAL_DEV(SMC, 2862WG), URAL_DEV(SPHAIRON, UB801R), URAL_DEV(SURECOM, RT2570), URAL_DEV(VTECH, RT2570), URAL_DEV(ZINWELL, RT2570), #undef URAL_DEV }; static usb_callback_t ural_bulk_read_callback; static usb_callback_t ural_bulk_write_callback; static usb_error_t ural_do_request(struct ural_softc *sc, struct usb_device_request *req, void *data); static struct ieee80211vap *ural_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void ural_vap_delete(struct ieee80211vap *); static void ural_tx_free(struct ural_tx_data *, int); static void ural_setup_tx_list(struct ural_softc *); static void ural_unsetup_tx_list(struct ural_softc *); static int ural_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void ural_setup_tx_desc(struct ural_softc *, struct ural_tx_desc *, uint32_t, int, int); static int ural_tx_bcn(struct ural_softc *, struct mbuf *, struct ieee80211_node *); static int ural_tx_mgt(struct ural_softc *, struct mbuf *, struct ieee80211_node *); static int ural_tx_data(struct ural_softc *, struct mbuf *, struct ieee80211_node *); static int ural_transmit(struct ieee80211com *, struct mbuf *); static void ural_start(struct ural_softc *); static void ural_parent(struct ieee80211com *); static void ural_set_testmode(struct ural_softc *); static void ural_eeprom_read(struct ural_softc *, uint16_t, void *, int); static uint16_t ural_read(struct ural_softc *, uint16_t); static void ural_read_multi(struct ural_softc *, uint16_t, void *, int); static void ural_write(struct ural_softc *, uint16_t, uint16_t); static void ural_write_multi(struct ural_softc *, uint16_t, void *, int) __unused; static void ural_bbp_write(struct ural_softc *, uint8_t, uint8_t); static uint8_t ural_bbp_read(struct ural_softc *, uint8_t); static void ural_rf_write(struct ural_softc *, uint8_t, uint32_t); static void ural_scan_start(struct ieee80211com *); static void ural_scan_end(struct ieee80211com *); static void ural_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static void ural_set_channel(struct ieee80211com *); static void ural_set_chan(struct ural_softc *, struct ieee80211_channel *); static void ural_disable_rf_tune(struct ural_softc *); static void ural_enable_tsf_sync(struct ural_softc *); static void ural_enable_tsf(struct ural_softc *); static void ural_update_slot(struct ural_softc *); static void ural_set_txpreamble(struct ural_softc *); static void ural_set_basicrates(struct ural_softc *, const struct ieee80211_channel *); static void ural_set_bssid(struct ural_softc *, const uint8_t *); static void ural_set_macaddr(struct ural_softc *, const uint8_t *); static void ural_update_promisc(struct ieee80211com *); static void ural_setpromisc(struct ural_softc *); static const char *ural_get_rf(int); static void ural_read_eeprom(struct ural_softc *); static int ural_bbp_init(struct ural_softc *); static void ural_set_txantenna(struct ural_softc *, int); static void ural_set_rxantenna(struct ural_softc *, int); static void ural_init(struct ural_softc *); static void ural_stop(struct ural_softc *); static int ural_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void ural_ratectl_start(struct ural_softc *, struct ieee80211_node *); static void ural_ratectl_timeout(void *); static void ural_ratectl_task(void *, int); static int ural_pause(struct ural_softc *sc, int timeout); /* * Default values for MAC registers; values taken from the reference driver. */ static const struct { uint16_t reg; uint16_t val; } ural_def_mac[] = { { RAL_TXRX_CSR5, 0x8c8d }, { RAL_TXRX_CSR6, 0x8b8a }, { RAL_TXRX_CSR7, 0x8687 }, { RAL_TXRX_CSR8, 0x0085 }, { RAL_MAC_CSR13, 0x1111 }, { RAL_MAC_CSR14, 0x1e11 }, { RAL_TXRX_CSR21, 0xe78f }, { RAL_MAC_CSR9, 0xff1d }, { RAL_MAC_CSR11, 0x0002 }, { RAL_MAC_CSR22, 0x0053 }, { RAL_MAC_CSR15, 0x0000 }, { RAL_MAC_CSR8, RAL_FRAME_SIZE }, { RAL_TXRX_CSR19, 0x0000 }, { RAL_TXRX_CSR18, 0x005a }, { RAL_PHY_CSR2, 0x0000 }, { RAL_TXRX_CSR0, 0x1ec0 }, { RAL_PHY_CSR4, 0x000f } }; /* * Default values for BBP registers; values taken from the reference driver. */ static const struct { uint8_t reg; uint8_t val; } ural_def_bbp[] = { { 3, 0x02 }, { 4, 0x19 }, { 14, 0x1c }, { 15, 0x30 }, { 16, 0xac }, { 17, 0x48 }, { 18, 0x18 }, { 19, 0xff }, { 20, 0x1e }, { 21, 0x08 }, { 22, 0x08 }, { 23, 0x08 }, { 24, 0x80 }, { 25, 0x50 }, { 26, 0x08 }, { 27, 0x23 }, { 30, 0x10 }, { 31, 0x2b }, { 32, 0xb9 }, { 34, 0x12 }, { 35, 0x50 }, { 39, 0xc4 }, { 40, 0x02 }, { 41, 0x60 }, { 53, 0x10 }, { 54, 0x18 }, { 56, 0x08 }, { 57, 0x10 }, { 58, 0x08 }, { 61, 0x60 }, { 62, 0x10 }, { 75, 0xff } }; /* * Default values for RF register R2 indexed by channel numbers. */ static const uint32_t ural_rf2522_r2[] = { 0x307f6, 0x307fb, 0x30800, 0x30805, 0x3080a, 0x3080f, 0x30814, 0x30819, 0x3081e, 0x30823, 0x30828, 0x3082d, 0x30832, 0x3083e }; static const uint32_t ural_rf2523_r2[] = { 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 }; static const uint32_t ural_rf2524_r2[] = { 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 }; static const uint32_t ural_rf2525_r2[] = { 0x20327, 0x20328, 0x20329, 0x2032a, 0x2032b, 0x2032c, 0x2032d, 0x2032e, 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20346 }; static const uint32_t ural_rf2525_hi_r2[] = { 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20344, 0x20345, 0x20346, 0x20347, 0x20348, 0x20349, 0x2034a, 0x2034b, 0x2034e }; static const uint32_t ural_rf2525e_r2[] = { 0x2044d, 0x2044e, 0x2044f, 0x20460, 0x20461, 0x20462, 0x20463, 0x20464, 0x20465, 0x20466, 0x20467, 0x20468, 0x20469, 0x2046b }; static const uint32_t ural_rf2526_hi_r2[] = { 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d, 0x0022d, 0x0022e, 0x0022e, 0x0022f, 0x0022d, 0x00240, 0x00240, 0x00241 }; static const uint32_t ural_rf2526_r2[] = { 0x00226, 0x00227, 0x00227, 0x00228, 0x00228, 0x00229, 0x00229, 0x0022a, 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d }; /* * For dual-band RF, RF registers R1 and R4 also depend on channel number; * values taken from the reference driver. */ static const struct { uint8_t chan; uint32_t r1; uint32_t r2; uint32_t r4; } ural_rf5222[] = { { 1, 0x08808, 0x0044d, 0x00282 }, { 2, 0x08808, 0x0044e, 0x00282 }, { 3, 0x08808, 0x0044f, 0x00282 }, { 4, 0x08808, 0x00460, 0x00282 }, { 5, 0x08808, 0x00461, 0x00282 }, { 6, 0x08808, 0x00462, 0x00282 }, { 7, 0x08808, 0x00463, 0x00282 }, { 8, 0x08808, 0x00464, 0x00282 }, { 9, 0x08808, 0x00465, 0x00282 }, { 10, 0x08808, 0x00466, 0x00282 }, { 11, 0x08808, 0x00467, 0x00282 }, { 12, 0x08808, 0x00468, 0x00282 }, { 13, 0x08808, 0x00469, 0x00282 }, { 14, 0x08808, 0x0046b, 0x00286 }, { 36, 0x08804, 0x06225, 0x00287 }, { 40, 0x08804, 0x06226, 0x00287 }, { 44, 0x08804, 0x06227, 0x00287 }, { 48, 0x08804, 0x06228, 0x00287 }, { 52, 0x08804, 0x06229, 0x00287 }, { 56, 0x08804, 0x0622a, 0x00287 }, { 60, 0x08804, 0x0622b, 0x00287 }, { 64, 0x08804, 0x0622c, 0x00287 }, { 100, 0x08804, 0x02200, 0x00283 }, { 104, 0x08804, 0x02201, 0x00283 }, { 108, 0x08804, 0x02202, 0x00283 }, { 112, 0x08804, 0x02203, 0x00283 }, { 116, 0x08804, 0x02204, 0x00283 }, { 120, 0x08804, 0x02205, 0x00283 }, { 124, 0x08804, 0x02206, 0x00283 }, { 128, 0x08804, 0x02207, 0x00283 }, { 132, 0x08804, 0x02208, 0x00283 }, { 136, 0x08804, 0x02209, 0x00283 }, { 140, 0x08804, 0x0220a, 0x00283 }, { 149, 0x08808, 0x02429, 0x00281 }, { 153, 0x08808, 0x0242b, 0x00281 }, { 157, 0x08808, 0x0242d, 0x00281 }, { 161, 0x08808, 0x0242f, 0x00281 } }; static const uint8_t ural_chan_5ghz[] = { 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 149, 153, 157, 161 }; static const struct usb_config ural_config[URAL_N_TRANSFER] = { [URAL_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE + 4), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = ural_bulk_write_callback, .timeout = 5000, /* ms */ }, [URAL_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = (RAL_FRAME_SIZE + RAL_RX_DESC_SIZE), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = ural_bulk_read_callback, }, }; static device_probe_t ural_match; static device_attach_t ural_attach; static device_detach_t ural_detach; static device_method_t ural_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ural_match), DEVMETHOD(device_attach, ural_attach), DEVMETHOD(device_detach, ural_detach), DEVMETHOD_END }; static driver_t ural_driver = { .name = "ural", .methods = ural_methods, .size = sizeof(struct ural_softc), }; static devclass_t ural_devclass; DRIVER_MODULE(ural, uhub, ural_driver, ural_devclass, NULL, 0); MODULE_DEPEND(ural, usb, 1, 1, 1); MODULE_DEPEND(ural, wlan, 1, 1, 1); MODULE_VERSION(ural, 1); USB_PNP_HOST_INFO(ural_devs); static int ural_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != RAL_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(ural_devs, sizeof(ural_devs), uaa)); } static int ural_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct ural_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; uint8_t iface_index; int error; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF); mbufq_init(&sc->sc_snd, ifqmaxlen); iface_index = RAL_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, ural_config, URAL_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(self, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto detach; } RAL_LOCK(sc); /* retrieve RT2570 rev. no */ sc->asic_rev = ural_read(sc, RAL_MAC_CSR0); /* retrieve MAC address and various other things from EEPROM */ ural_read_eeprom(sc); RAL_UNLOCK(sc); device_printf(self, "MAC/BBP RT2570 (rev 0x%02x), RF %s\n", sc->asic_rev, ural_get_rf(sc->rf_rev)); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_IBSS /* IBSS mode supported */ | IEEE80211_C_MONITOR /* monitor mode supported */ | IEEE80211_C_HOSTAP /* HostAp mode supported */ | IEEE80211_C_TXPMGT /* tx power management */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_BGSCAN /* bg scanning supported */ | IEEE80211_C_WPA /* 802.11i */ ; ural_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_update_promisc = ural_update_promisc; ic->ic_raw_xmit = ural_raw_xmit; ic->ic_scan_start = ural_scan_start; ic->ic_scan_end = ural_scan_end; ic->ic_getradiocaps = ural_getradiocaps; ic->ic_set_channel = ural_set_channel; ic->ic_parent = ural_parent; ic->ic_transmit = ural_transmit; ic->ic_vap_create = ural_vap_create; ic->ic_vap_delete = ural_vap_delete; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), RAL_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), RAL_RX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); return (0); detach: ural_detach(self); return (ENXIO); /* failure */ } static int ural_detach(device_t self) { struct ural_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; /* prevent further ioctls */ RAL_LOCK(sc); sc->sc_detached = 1; RAL_UNLOCK(sc); /* stop all USB transfers */ usbd_transfer_unsetup(sc->sc_xfer, URAL_N_TRANSFER); /* free TX list, if any */ RAL_LOCK(sc); ural_unsetup_tx_list(sc); RAL_UNLOCK(sc); if (ic->ic_softc == sc) ieee80211_ifdetach(ic); mbufq_drain(&sc->sc_snd); mtx_destroy(&sc->sc_mtx); return (0); } static usb_error_t ural_do_request(struct ural_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0) break; DPRINTFN(1, "Control request failed, %s (retrying)\n", usbd_errstr(err)); if (ural_pause(sc, hz / 100)) break; } return (err); } static struct ieee80211vap * ural_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct ural_softc *sc = ic->ic_softc; struct ural_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; uvp = malloc(sizeof(struct ural_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = ural_newstate; usb_callout_init_mtx(&uvp->ratectl_ch, &sc->sc_mtx, 0); TASK_INIT(&uvp->ratectl_task, 0, ural_ratectl_task, uvp); ieee80211_ratectl_init(vap); ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return vap; } static void ural_vap_delete(struct ieee80211vap *vap) { struct ural_vap *uvp = URAL_VAP(vap); struct ieee80211com *ic = vap->iv_ic; usb_callout_drain(&uvp->ratectl_ch); ieee80211_draintask(ic, &uvp->ratectl_task); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void ural_tx_free(struct ural_tx_data *data, int txerr) { struct ural_softc *sc = data->sc; if (data->m != NULL) { ieee80211_tx_complete(data->ni, data->m, txerr); data->m = NULL; data->ni = NULL; } STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } static void ural_setup_tx_list(struct ural_softc *sc) { struct ural_tx_data *data; int i; sc->tx_nfree = 0; STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); for (i = 0; i < RAL_TX_LIST_COUNT; i++) { data = &sc->tx_data[i]; data->sc = sc; STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } } static void ural_unsetup_tx_list(struct ural_softc *sc) { struct ural_tx_data *data; int i; /* make sure any subsequent use of the queues will fail */ sc->tx_nfree = 0; STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); /* free up all node references and mbufs */ for (i = 0; i < RAL_TX_LIST_COUNT; i++) { data = &sc->tx_data[i]; if (data->m != NULL) { m_freem(data->m); data->m = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } } static int ural_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ural_vap *uvp = URAL_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct ural_softc *sc = ic->ic_softc; const struct ieee80211_txparam *tp; struct ieee80211_node *ni; struct mbuf *m; DPRINTF("%s -> %s\n", ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); RAL_LOCK(sc); usb_callout_stop(&uvp->ratectl_ch); switch (nstate) { case IEEE80211_S_INIT: if (vap->iv_state == IEEE80211_S_RUN) { /* abort TSF synchronization */ ural_write(sc, RAL_TXRX_CSR19, 0); /* force tx led to stop blinking */ ural_write(sc, RAL_MAC_CSR20, 0); } break; case IEEE80211_S_RUN: ni = ieee80211_ref_node(vap->iv_bss); if (vap->iv_opmode != IEEE80211_M_MONITOR) { if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) goto fail; ural_update_slot(sc); ural_set_txpreamble(sc); ural_set_basicrates(sc, ic->ic_bsschan); IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); ural_set_bssid(sc, sc->sc_bssid); } if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { m = ieee80211_beacon_alloc(ni); if (m == NULL) { device_printf(sc->sc_dev, "could not allocate beacon\n"); goto fail; } ieee80211_ref_node(ni); if (ural_tx_bcn(sc, m, ni) != 0) { device_printf(sc->sc_dev, "could not send beacon\n"); goto fail; } } /* make tx led blink on tx (controlled by ASIC) */ ural_write(sc, RAL_MAC_CSR20, 1); if (vap->iv_opmode != IEEE80211_M_MONITOR) ural_enable_tsf_sync(sc); else ural_enable_tsf(sc); /* enable automatic rate adaptation */ /* XXX should use ic_bsschan but not valid until after newstate call below */ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) ural_ratectl_start(sc, ni); ieee80211_free_node(ni); break; default: break; } RAL_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); fail: RAL_UNLOCK(sc); IEEE80211_LOCK(ic); ieee80211_free_node(ni); return (-1); } static void ural_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct ural_softc *sc = usbd_xfer_softc(xfer); struct ieee80211vap *vap; struct ural_tx_data *data; struct mbuf *m; struct usb_page_cache *pc; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete, %d bytes\n", len); /* free resources */ data = usbd_xfer_get_priv(xfer); ural_tx_free(data, 0); usbd_xfer_set_priv(xfer, NULL); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->tx_q); if (data) { STAILQ_REMOVE_HEAD(&sc->tx_q, next); m = data->m; if (m->m_pkthdr.len > (int)(RAL_FRAME_SIZE + RAL_TX_DESC_SIZE)) { DPRINTFN(0, "data overflow, %u bytes\n", m->m_pkthdr.len); m->m_pkthdr.len = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &data->desc, RAL_TX_DESC_SIZE); usbd_m_copy_in(pc, RAL_TX_DESC_SIZE, m, 0, m->m_pkthdr.len); vap = data->ni->ni_vap; if (ieee80211_radiotap_active_vap(vap)) { struct ural_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = data->rate; tap->wt_antenna = sc->tx_ant; ieee80211_radiotap_tx(vap, m); } /* xfer length needs to be a multiple of two! */ len = (RAL_TX_DESC_SIZE + m->m_pkthdr.len + 1) & ~1; if ((len % 64) == 0) len += 2; DPRINTFN(11, "sending frame len=%u xferlen=%u\n", m->m_pkthdr.len, len); usbd_xfer_set_frame_len(xfer, 0, len); usbd_xfer_set_priv(xfer, data); usbd_transfer_submit(xfer); } ural_start(sc); break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); data = usbd_xfer_get_priv(xfer); if (data != NULL) { ural_tx_free(data, error); usbd_xfer_set_priv(xfer, NULL); } if (error == USB_ERR_STALLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } if (error == USB_ERR_TIMEOUT) device_printf(sc->sc_dev, "device timeout\n"); break; } } static void ural_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct ural_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; struct epoch_tracker et; struct mbuf *m = NULL; struct usb_page_cache *pc; uint32_t flags; int8_t rssi = 0, nf = 0; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(15, "rx done, actlen=%d\n", len); if (len < (int)(RAL_RX_DESC_SIZE + IEEE80211_MIN_LEN)) { DPRINTF("%s: xfer too short %d\n", device_get_nameunit(sc->sc_dev), len); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } len -= RAL_RX_DESC_SIZE; /* rx descriptor is located at the end */ pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, len, &sc->sc_rx_desc, RAL_RX_DESC_SIZE); rssi = URAL_RSSI(sc->sc_rx_desc.rssi); nf = RAL_NOISE_FLOOR; flags = le32toh(sc->sc_rx_desc.flags); if (flags & (RAL_RX_PHY_ERROR | RAL_RX_CRC_ERROR)) { /* * This should not happen since we did not * request to receive those frames when we * filled RAL_TXRX_CSR2: */ DPRINTFN(5, "PHY or CRC error\n"); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { DPRINTF("could not allocate mbuf\n"); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } usbd_copy_out(pc, 0, mtod(m, uint8_t *), len); /* finalize mbuf */ m->m_pkthdr.len = m->m_len = (flags >> 16) & 0xfff; if (ieee80211_radiotap_active(ic)) { struct ural_rx_radiotap_header *tap = &sc->sc_rxtap; /* XXX set once */ tap->wr_flags = 0; tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate, (flags & RAL_RX_OFDM) ? IEEE80211_T_OFDM : IEEE80211_T_CCK); tap->wr_antenna = sc->rx_ant; tap->wr_antsignal = nf + rssi; tap->wr_antnoise = nf; } /* Strip trailing 802.11 MAC FCS. */ m_adj(m, -IEEE80211_CRC_LEN); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * At the end of a USB callback it is always safe to unlock * the private mutex of a device! That is why we do the * "ieee80211_input" here, and not some lines up! */ RAL_UNLOCK(sc); if (m) { ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); NET_EPOCH_ENTER(et); if (ni != NULL) { (void) ieee80211_input(ni, m, rssi, nf); ieee80211_free_node(ni); } else (void) ieee80211_input_all(ic, m, rssi, nf); NET_EPOCH_EXIT(et); } RAL_LOCK(sc); ural_start(sc); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static uint8_t ural_plcp_signal(int rate) { switch (rate) { /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return 0xb; case 18: return 0xf; case 24: return 0xa; case 36: return 0xe; case 48: return 0x9; case 72: return 0xd; case 96: return 0x8; case 108: return 0xc; /* CCK rates (NB: not IEEE std, device-specific) */ case 2: return 0x0; case 4: return 0x1; case 11: return 0x2; case 22: return 0x3; } return 0xff; /* XXX unsupported/unknown rate */ } static void ural_setup_tx_desc(struct ural_softc *sc, struct ural_tx_desc *desc, uint32_t flags, int len, int rate) { struct ieee80211com *ic = &sc->sc_ic; uint16_t plcp_length; int remainder; desc->flags = htole32(flags); desc->flags |= htole32(RAL_TX_NEWSEQ); desc->flags |= htole32(len << 16); desc->wme = htole16(RAL_AIFSN(2) | RAL_LOGCWMIN(3) | RAL_LOGCWMAX(5)); desc->wme |= htole16(RAL_IVOFFSET(sizeof (struct ieee80211_frame))); /* setup PLCP fields */ desc->plcp_signal = ural_plcp_signal(rate); desc->plcp_service = 4; len += IEEE80211_CRC_LEN; if (ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) { desc->flags |= htole32(RAL_TX_OFDM); plcp_length = len & 0xfff; desc->plcp_length_hi = plcp_length >> 6; desc->plcp_length_lo = plcp_length & 0x3f; } else { if (rate == 0) rate = 2; /* avoid division by zero */ plcp_length = howmany(16 * len, rate); if (rate == 22) { remainder = (16 * len) % 22; if (remainder != 0 && remainder < 7) desc->plcp_service |= RAL_PLCP_LENGEXT; } desc->plcp_length_hi = plcp_length >> 8; desc->plcp_length_lo = plcp_length & 0xff; if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) desc->plcp_signal |= 0x08; } desc->iv = 0; desc->eiv = 0; } #define RAL_TX_TIMEOUT 5000 static int ural_tx_bcn(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; const struct ieee80211_txparam *tp; struct ural_tx_data *data; if (sc->tx_nfree == 0) { m_freem(m0); ieee80211_free_node(ni); return (EIO); } if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) { m_freem(m0); ieee80211_free_node(ni); return (ENXIO); } data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; data->m = m0; data->ni = ni; data->rate = tp->mgmtrate; ural_setup_tx_desc(sc, &data->desc, RAL_TX_IFS_NEWBACKOFF | RAL_TX_TIMESTAMP, m0->m_pkthdr.len, tp->mgmtrate); DPRINTFN(10, "sending beacon frame len=%u rate=%u\n", m0->m_pkthdr.len, tp->mgmtrate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); return (0); } static int ural_tx_mgt(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211com *ic = ni->ni_ic; struct ural_tx_data *data; struct ieee80211_frame *wh; struct ieee80211_key *k; uint32_t flags; uint16_t dur; RAL_LOCK_ASSERT(sc, MA_OWNED); data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; wh = mtod(m0, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { m_freem(m0); return ENOBUFS; } wh = mtod(m0, struct ieee80211_frame *); } data->m = m0; data->ni = ni; data->rate = tp->mgmtrate; flags = 0; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= RAL_TX_ACK; dur = ieee80211_ack_duration(ic->ic_rt, tp->mgmtrate, ic->ic_flags & IEEE80211_F_SHPREAMBLE); USETW(wh->i_dur, dur); /* tell hardware to add timestamp for probe responses */ if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_MGT && (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= RAL_TX_TIMESTAMP; } ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, tp->mgmtrate); DPRINTFN(10, "sending mgt frame len=%u rate=%u\n", m0->m_pkthdr.len, tp->mgmtrate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); return 0; } static int ural_sendprot(struct ural_softc *sc, const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) { struct ieee80211com *ic = ni->ni_ic; struct ural_tx_data *data; struct mbuf *mprot; int protrate, flags; mprot = ieee80211_alloc_prot(ni, m, rate, prot); if (mprot == NULL) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); device_printf(sc->sc_dev, "could not allocate mbuf for protection mode %d\n", prot); return ENOBUFS; } protrate = ieee80211_ctl_rate(ic->ic_rt, rate); flags = RAL_TX_RETRY(7); if (prot == IEEE80211_PROT_RTSCTS) flags |= RAL_TX_ACK; data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; data->m = mprot; data->ni = ieee80211_ref_node(ni); data->rate = protrate; ural_setup_tx_desc(sc, &data->desc, flags, mprot->m_pkthdr.len, protrate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); return 0; } static int ural_tx_raw(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ural_tx_data *data; uint32_t flags; int error; int rate; RAL_LOCK_ASSERT(sc, MA_OWNED); KASSERT(params != NULL, ("no raw xmit params")); rate = params->ibp_rate0; if (!ieee80211_isratevalid(ic->ic_rt, rate)) { m_freem(m0); return EINVAL; } flags = 0; if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) flags |= RAL_TX_ACK; if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { error = ural_sendprot(sc, m0, ni, params->ibp_flags & IEEE80211_BPF_RTS ? IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, rate); if (error || sc->tx_nfree == 0) { m_freem(m0); return ENOBUFS; } flags |= RAL_TX_IFS_SIFS; } data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; data->m = m0; data->ni = ni; data->rate = rate; /* XXX need to setup descriptor ourself */ ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, rate); DPRINTFN(10, "sending raw frame len=%u rate=%u\n", m0->m_pkthdr.len, rate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); return 0; } static int ural_tx_data(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ural_tx_data *data; struct ieee80211_frame *wh; const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211_key *k; uint32_t flags = 0; uint16_t dur; int error, rate; RAL_LOCK_ASSERT(sc, MA_OWNED); wh = mtod(m0, struct ieee80211_frame *); if (m0->m_flags & M_EAPOL) rate = tp->mgmtrate; else if (IEEE80211_IS_MULTICAST(wh->i_addr1)) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else { (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; } if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { m_freem(m0); return ENOBUFS; } /* packet header may have moved, reset our local pointer */ wh = mtod(m0, struct ieee80211_frame *); } if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { int prot = IEEE80211_PROT_NONE; if (m0->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) prot = IEEE80211_PROT_RTSCTS; else if ((ic->ic_flags & IEEE80211_F_USEPROT) && ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) prot = ic->ic_protmode; if (prot != IEEE80211_PROT_NONE) { error = ural_sendprot(sc, m0, ni, prot, rate); if (error || sc->tx_nfree == 0) { m_freem(m0); return ENOBUFS; } flags |= RAL_TX_IFS_SIFS; } } data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; data->m = m0; data->ni = ni; data->rate = rate; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= RAL_TX_ACK; flags |= RAL_TX_RETRY(7); dur = ieee80211_ack_duration(ic->ic_rt, rate, ic->ic_flags & IEEE80211_F_SHPREAMBLE); USETW(wh->i_dur, dur); } ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, rate); DPRINTFN(10, "sending data frame len=%u rate=%u\n", m0->m_pkthdr.len, rate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); return 0; } static int ural_transmit(struct ieee80211com *ic, struct mbuf *m) { struct ural_softc *sc = ic->ic_softc; int error; RAL_LOCK(sc); if (!sc->sc_running) { RAL_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { RAL_UNLOCK(sc); return (error); } ural_start(sc); RAL_UNLOCK(sc); return (0); } static void ural_start(struct ural_softc *sc) { struct ieee80211_node *ni; struct mbuf *m; RAL_LOCK_ASSERT(sc, MA_OWNED); if (sc->sc_running == 0) return; while (sc->tx_nfree >= RAL_TX_MINFREE && (m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; if (ural_tx_data(sc, m, ni) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); break; } } } static void ural_parent(struct ieee80211com *ic) { struct ural_softc *sc = ic->ic_softc; int startall = 0; RAL_LOCK(sc); if (sc->sc_detached) { RAL_UNLOCK(sc); return; } if (ic->ic_nrunning > 0) { if (sc->sc_running == 0) { ural_init(sc); startall = 1; } else ural_setpromisc(sc); } else if (sc->sc_running) ural_stop(sc); RAL_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static void ural_set_testmode(struct ural_softc *sc) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_VENDOR_REQUEST; USETW(req.wValue, 4); USETW(req.wIndex, 1); USETW(req.wLength, 0); error = ural_do_request(sc, &req, NULL); if (error != 0) { device_printf(sc->sc_dev, "could not set test mode: %s\n", usbd_errstr(error)); } } static void ural_eeprom_read(struct ural_softc *sc, uint16_t addr, void *buf, int len) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_EEPROM; USETW(req.wValue, 0); USETW(req.wIndex, addr); USETW(req.wLength, len); error = ural_do_request(sc, &req, buf); if (error != 0) { device_printf(sc->sc_dev, "could not read EEPROM: %s\n", usbd_errstr(error)); } } static uint16_t ural_read(struct ural_softc *sc, uint16_t reg) { struct usb_device_request req; usb_error_t error; uint16_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, sizeof (uint16_t)); error = ural_do_request(sc, &req, &val); if (error != 0) { device_printf(sc->sc_dev, "could not read MAC register: %s\n", usbd_errstr(error)); return 0; } return le16toh(val); } static void ural_read_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_MULTI_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, len); error = ural_do_request(sc, &req, buf); if (error != 0) { device_printf(sc->sc_dev, "could not read MAC register: %s\n", usbd_errstr(error)); } } static void ural_write(struct ural_softc *sc, uint16_t reg, uint16_t val) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_WRITE_MAC; USETW(req.wValue, val); USETW(req.wIndex, reg); USETW(req.wLength, 0); error = ural_do_request(sc, &req, NULL); if (error != 0) { device_printf(sc->sc_dev, "could not write MAC register: %s\n", usbd_errstr(error)); } } static void ural_write_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_WRITE_MULTI_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, len); error = ural_do_request(sc, &req, buf); if (error != 0) { device_printf(sc->sc_dev, "could not write MAC register: %s\n", usbd_errstr(error)); } } static void ural_bbp_write(struct ural_softc *sc, uint8_t reg, uint8_t val) { uint16_t tmp; int ntries; for (ntries = 0; ntries < 100; ntries++) { if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) break; if (ural_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "could not write to BBP\n"); return; } tmp = reg << 8 | val; ural_write(sc, RAL_PHY_CSR7, tmp); } static uint8_t ural_bbp_read(struct ural_softc *sc, uint8_t reg) { uint16_t val; int ntries; val = RAL_BBP_WRITE | reg << 8; ural_write(sc, RAL_PHY_CSR7, val); for (ntries = 0; ntries < 100; ntries++) { if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) break; if (ural_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "could not read BBP\n"); return 0; } return ural_read(sc, RAL_PHY_CSR7) & 0xff; } static void ural_rf_write(struct ural_softc *sc, uint8_t reg, uint32_t val) { uint32_t tmp; int ntries; for (ntries = 0; ntries < 100; ntries++) { if (!(ural_read(sc, RAL_PHY_CSR10) & RAL_RF_LOBUSY)) break; if (ural_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "could not write to RF\n"); return; } tmp = RAL_RF_BUSY | RAL_RF_20BIT | (val & 0xfffff) << 2 | (reg & 0x3); ural_write(sc, RAL_PHY_CSR9, tmp & 0xffff); ural_write(sc, RAL_PHY_CSR10, tmp >> 16); /* remember last written value in sc */ sc->rf_regs[reg] = val; DPRINTFN(15, "RF R[%u] <- 0x%05x\n", reg & 0x3, val & 0xfffff); } static void ural_scan_start(struct ieee80211com *ic) { struct ural_softc *sc = ic->ic_softc; RAL_LOCK(sc); ural_write(sc, RAL_TXRX_CSR19, 0); ural_set_bssid(sc, ieee80211broadcastaddr); RAL_UNLOCK(sc); } static void ural_scan_end(struct ieee80211com *ic) { struct ural_softc *sc = ic->ic_softc; RAL_LOCK(sc); ural_enable_tsf_sync(sc); ural_set_bssid(sc, sc->sc_bssid); RAL_UNLOCK(sc); } static void ural_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct ural_softc *sc = ic->ic_softc; uint8_t bands[IEEE80211_MODE_BYTES]; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0); if (sc->rf_rev == RAL_RF_5222) { setbit(bands, IEEE80211_MODE_11A); ieee80211_add_channel_list_5ghz(chans, maxchans, nchans, ural_chan_5ghz, nitems(ural_chan_5ghz), bands, 0); } } static void ural_set_channel(struct ieee80211com *ic) { struct ural_softc *sc = ic->ic_softc; RAL_LOCK(sc); ural_set_chan(sc, ic->ic_curchan); RAL_UNLOCK(sc); } static void ural_set_chan(struct ural_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; uint8_t power, tmp; int i, chan; chan = ieee80211_chan2ieee(ic, c); if (chan == 0 || chan == IEEE80211_CHAN_ANY) return; if (IEEE80211_IS_CHAN_2GHZ(c)) power = min(sc->txpow[chan - 1], 31); else power = 31; /* adjust txpower using ifconfig settings */ power -= (100 - ic->ic_txpowlimit) / 8; DPRINTFN(2, "setting channel to %u, txpower to %u\n", chan, power); switch (sc->rf_rev) { case RAL_RF_2522: ural_rf_write(sc, RAL_RF1, 0x00814); ural_rf_write(sc, RAL_RF2, ural_rf2522_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); break; case RAL_RF_2523: ural_rf_write(sc, RAL_RF1, 0x08804); ural_rf_write(sc, RAL_RF2, ural_rf2523_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x38044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); break; case RAL_RF_2524: ural_rf_write(sc, RAL_RF1, 0x0c808); ural_rf_write(sc, RAL_RF2, ural_rf2524_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); break; case RAL_RF_2525: ural_rf_write(sc, RAL_RF1, 0x08808); ural_rf_write(sc, RAL_RF2, ural_rf2525_hi_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); ural_rf_write(sc, RAL_RF1, 0x08808); ural_rf_write(sc, RAL_RF2, ural_rf2525_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); break; case RAL_RF_2525E: ural_rf_write(sc, RAL_RF1, 0x08808); ural_rf_write(sc, RAL_RF2, ural_rf2525e_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00286 : 0x00282); break; case RAL_RF_2526: ural_rf_write(sc, RAL_RF2, ural_rf2526_hi_r2[chan - 1]); ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); ural_rf_write(sc, RAL_RF1, 0x08804); ural_rf_write(sc, RAL_RF2, ural_rf2526_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); break; /* dual-band RF */ case RAL_RF_5222: for (i = 0; ural_rf5222[i].chan != chan; i++); ural_rf_write(sc, RAL_RF1, ural_rf5222[i].r1); ural_rf_write(sc, RAL_RF2, ural_rf5222[i].r2); ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); ural_rf_write(sc, RAL_RF4, ural_rf5222[i].r4); break; } if (ic->ic_opmode != IEEE80211_M_MONITOR && (ic->ic_flags & IEEE80211_F_SCAN) == 0) { /* set Japan filter bit for channel 14 */ tmp = ural_bbp_read(sc, 70); tmp &= ~RAL_JAPAN_FILTER; if (chan == 14) tmp |= RAL_JAPAN_FILTER; ural_bbp_write(sc, 70, tmp); /* clear CRC errors */ ural_read(sc, RAL_STA_CSR0); ural_pause(sc, hz / 100); ural_disable_rf_tune(sc); } /* XXX doesn't belong here */ /* update basic rate set */ ural_set_basicrates(sc, c); /* give the hardware some time to do the switchover */ ural_pause(sc, hz / 100); } /* * Disable RF auto-tuning. */ static void ural_disable_rf_tune(struct ural_softc *sc) { uint32_t tmp; if (sc->rf_rev != RAL_RF_2523) { tmp = sc->rf_regs[RAL_RF1] & ~RAL_RF1_AUTOTUNE; ural_rf_write(sc, RAL_RF1, tmp); } tmp = sc->rf_regs[RAL_RF3] & ~RAL_RF3_AUTOTUNE; ural_rf_write(sc, RAL_RF3, tmp); DPRINTFN(2, "disabling RF autotune\n"); } /* * Refer to IEEE Std 802.11-1999 pp. 123 for more information on TSF * synchronization. */ static void ural_enable_tsf_sync(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint16_t logcwmin, preload, tmp; /* first, disable TSF synchronization */ ural_write(sc, RAL_TXRX_CSR19, 0); tmp = (16 * vap->iv_bss->ni_intval) << 4; ural_write(sc, RAL_TXRX_CSR18, tmp); logcwmin = (ic->ic_opmode == IEEE80211_M_IBSS) ? 2 : 0; preload = (ic->ic_opmode == IEEE80211_M_IBSS) ? 320 : 6; tmp = logcwmin << 12 | preload; ural_write(sc, RAL_TXRX_CSR20, tmp); /* finally, enable TSF synchronization */ tmp = RAL_ENABLE_TSF | RAL_ENABLE_TBCN; if (ic->ic_opmode == IEEE80211_M_STA) tmp |= RAL_ENABLE_TSF_SYNC(1); else tmp |= RAL_ENABLE_TSF_SYNC(2) | RAL_ENABLE_BEACON_GENERATOR; ural_write(sc, RAL_TXRX_CSR19, tmp); DPRINTF("enabling TSF synchronization\n"); } static void ural_enable_tsf(struct ural_softc *sc) { /* first, disable TSF synchronization */ ural_write(sc, RAL_TXRX_CSR19, 0); ural_write(sc, RAL_TXRX_CSR19, RAL_ENABLE_TSF | RAL_ENABLE_TSF_SYNC(2)); } #define RAL_RXTX_TURNAROUND 5 /* us */ static void ural_update_slot(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint16_t slottime, sifs, eifs; slottime = IEEE80211_GET_SLOTTIME(ic); /* * These settings may sound a bit inconsistent but this is what the * reference driver does. */ if (ic->ic_curmode == IEEE80211_MODE_11B) { sifs = 16 - RAL_RXTX_TURNAROUND; eifs = 364; } else { sifs = 10 - RAL_RXTX_TURNAROUND; eifs = 64; } ural_write(sc, RAL_MAC_CSR10, slottime); ural_write(sc, RAL_MAC_CSR11, sifs); ural_write(sc, RAL_MAC_CSR12, eifs); } static void ural_set_txpreamble(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint16_t tmp; tmp = ural_read(sc, RAL_TXRX_CSR10); tmp &= ~RAL_SHORT_PREAMBLE; if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) tmp |= RAL_SHORT_PREAMBLE; ural_write(sc, RAL_TXRX_CSR10, tmp); } static void ural_set_basicrates(struct ural_softc *sc, const struct ieee80211_channel *c) { /* XXX wrong, take from rate set */ /* update basic rate set */ if (IEEE80211_IS_CHAN_5GHZ(c)) { /* 11a basic rates: 6, 12, 24Mbps */ ural_write(sc, RAL_TXRX_CSR11, 0x150); } else if (IEEE80211_IS_CHAN_ANYG(c)) { /* 11g basic rates: 1, 2, 5.5, 11, 6, 12, 24Mbps */ ural_write(sc, RAL_TXRX_CSR11, 0x15f); } else { /* 11b basic rates: 1, 2Mbps */ ural_write(sc, RAL_TXRX_CSR11, 0x3); } } static void ural_set_bssid(struct ural_softc *sc, const uint8_t *bssid) { uint16_t tmp; tmp = bssid[0] | bssid[1] << 8; ural_write(sc, RAL_MAC_CSR5, tmp); tmp = bssid[2] | bssid[3] << 8; ural_write(sc, RAL_MAC_CSR6, tmp); tmp = bssid[4] | bssid[5] << 8; ural_write(sc, RAL_MAC_CSR7, tmp); DPRINTF("setting BSSID to %6D\n", bssid, ":"); } static void ural_set_macaddr(struct ural_softc *sc, const uint8_t *addr) { uint16_t tmp; tmp = addr[0] | addr[1] << 8; ural_write(sc, RAL_MAC_CSR2, tmp); tmp = addr[2] | addr[3] << 8; ural_write(sc, RAL_MAC_CSR3, tmp); tmp = addr[4] | addr[5] << 8; ural_write(sc, RAL_MAC_CSR4, tmp); DPRINTF("setting MAC address to %6D\n", addr, ":"); } static void ural_setpromisc(struct ural_softc *sc) { uint32_t tmp; tmp = ural_read(sc, RAL_TXRX_CSR2); tmp &= ~RAL_DROP_NOT_TO_ME; if (sc->sc_ic.ic_promisc == 0) tmp |= RAL_DROP_NOT_TO_ME; ural_write(sc, RAL_TXRX_CSR2, tmp); DPRINTF("%s promiscuous mode\n", sc->sc_ic.ic_promisc ? "entering" : "leaving"); } static void ural_update_promisc(struct ieee80211com *ic) { struct ural_softc *sc = ic->ic_softc; RAL_LOCK(sc); if (sc->sc_running) ural_setpromisc(sc); RAL_UNLOCK(sc); } static const char * ural_get_rf(int rev) { switch (rev) { case RAL_RF_2522: return "RT2522"; case RAL_RF_2523: return "RT2523"; case RAL_RF_2524: return "RT2524"; case RAL_RF_2525: return "RT2525"; case RAL_RF_2525E: return "RT2525e"; case RAL_RF_2526: return "RT2526"; case RAL_RF_5222: return "RT5222"; default: return "unknown"; } } static void ural_read_eeprom(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint16_t val; ural_eeprom_read(sc, RAL_EEPROM_CONFIG0, &val, 2); val = le16toh(val); sc->rf_rev = (val >> 11) & 0x7; sc->hw_radio = (val >> 10) & 0x1; sc->led_mode = (val >> 6) & 0x7; sc->rx_ant = (val >> 4) & 0x3; sc->tx_ant = (val >> 2) & 0x3; sc->nb_ant = val & 0x3; /* read MAC address */ ural_eeprom_read(sc, RAL_EEPROM_ADDRESS, ic->ic_macaddr, 6); /* read default values for BBP registers */ ural_eeprom_read(sc, RAL_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); /* read Tx power for all b/g channels */ ural_eeprom_read(sc, RAL_EEPROM_TXPOWER, sc->txpow, 14); } static int ural_bbp_init(struct ural_softc *sc) { int i, ntries; /* wait for BBP to be ready */ for (ntries = 0; ntries < 100; ntries++) { if (ural_bbp_read(sc, RAL_BBP_VERSION) != 0) break; if (ural_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for BBP\n"); return EIO; } /* initialize BBP registers to default values */ for (i = 0; i < nitems(ural_def_bbp); i++) ural_bbp_write(sc, ural_def_bbp[i].reg, ural_def_bbp[i].val); #if 0 /* initialize BBP registers to values stored in EEPROM */ for (i = 0; i < 16; i++) { if (sc->bbp_prom[i].reg == 0xff) continue; ural_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); } #endif return 0; } static void ural_set_txantenna(struct ural_softc *sc, int antenna) { uint16_t tmp; uint8_t tx; tx = ural_bbp_read(sc, RAL_BBP_TX) & ~RAL_BBP_ANTMASK; if (antenna == 1) tx |= RAL_BBP_ANTA; else if (antenna == 2) tx |= RAL_BBP_ANTB; else tx |= RAL_BBP_DIVERSITY; /* need to force I/Q flip for RF 2525e, 2526 and 5222 */ if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526 || sc->rf_rev == RAL_RF_5222) tx |= RAL_BBP_FLIPIQ; ural_bbp_write(sc, RAL_BBP_TX, tx); /* update values in PHY_CSR5 and PHY_CSR6 */ tmp = ural_read(sc, RAL_PHY_CSR5) & ~0x7; ural_write(sc, RAL_PHY_CSR5, tmp | (tx & 0x7)); tmp = ural_read(sc, RAL_PHY_CSR6) & ~0x7; ural_write(sc, RAL_PHY_CSR6, tmp | (tx & 0x7)); } static void ural_set_rxantenna(struct ural_softc *sc, int antenna) { uint8_t rx; rx = ural_bbp_read(sc, RAL_BBP_RX) & ~RAL_BBP_ANTMASK; if (antenna == 1) rx |= RAL_BBP_ANTA; else if (antenna == 2) rx |= RAL_BBP_ANTB; else rx |= RAL_BBP_DIVERSITY; /* need to force no I/Q flip for RF 2525e and 2526 */ if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526) rx &= ~RAL_BBP_FLIPIQ; ural_bbp_write(sc, RAL_BBP_RX, rx); } static void ural_init(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint16_t tmp; int i, ntries; RAL_LOCK_ASSERT(sc, MA_OWNED); ural_set_testmode(sc); ural_write(sc, 0x308, 0x00f0); /* XXX magic */ ural_stop(sc); /* initialize MAC registers to default values */ for (i = 0; i < nitems(ural_def_mac); i++) ural_write(sc, ural_def_mac[i].reg, ural_def_mac[i].val); /* wait for BBP and RF to wake up (this can take a long time!) */ for (ntries = 0; ntries < 100; ntries++) { tmp = ural_read(sc, RAL_MAC_CSR17); if ((tmp & (RAL_BBP_AWAKE | RAL_RF_AWAKE)) == (RAL_BBP_AWAKE | RAL_RF_AWAKE)) break; if (ural_pause(sc, hz / 100)) break; } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for BBP/RF to wakeup\n"); goto fail; } /* we're ready! */ ural_write(sc, RAL_MAC_CSR1, RAL_HOST_READY); /* set basic rate set (will be updated later) */ ural_write(sc, RAL_TXRX_CSR11, 0x15f); if (ural_bbp_init(sc) != 0) goto fail; ural_set_chan(sc, ic->ic_curchan); /* clear statistic registers (STA_CSR0 to STA_CSR10) */ ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); ural_set_txantenna(sc, sc->tx_ant); ural_set_rxantenna(sc, sc->rx_ant); ural_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr); /* * Allocate Tx and Rx xfer queues. */ ural_setup_tx_list(sc); /* kick Rx */ tmp = RAL_DROP_PHY | RAL_DROP_CRC; if (ic->ic_opmode != IEEE80211_M_MONITOR) { tmp |= RAL_DROP_CTL | RAL_DROP_BAD_VERSION; if (ic->ic_opmode != IEEE80211_M_HOSTAP) tmp |= RAL_DROP_TODS; if (ic->ic_promisc == 0) tmp |= RAL_DROP_NOT_TO_ME; } ural_write(sc, RAL_TXRX_CSR2, tmp); sc->sc_running = 1; usbd_xfer_set_stall(sc->sc_xfer[URAL_BULK_WR]); usbd_transfer_start(sc->sc_xfer[URAL_BULK_RD]); return; fail: ural_stop(sc); } static void ural_stop(struct ural_softc *sc) { RAL_LOCK_ASSERT(sc, MA_OWNED); sc->sc_running = 0; /* * Drain all the transfers, if not already drained: */ RAL_UNLOCK(sc); usbd_transfer_drain(sc->sc_xfer[URAL_BULK_WR]); usbd_transfer_drain(sc->sc_xfer[URAL_BULK_RD]); RAL_LOCK(sc); ural_unsetup_tx_list(sc); /* disable Rx */ ural_write(sc, RAL_TXRX_CSR2, RAL_DISABLE_RX); /* reset ASIC and BBP (but won't reset MAC registers!) */ ural_write(sc, RAL_MAC_CSR1, RAL_RESET_ASIC | RAL_RESET_BBP); /* wait a little */ ural_pause(sc, hz / 10); ural_write(sc, RAL_MAC_CSR1, 0); /* wait a little */ ural_pause(sc, hz / 10); } static int ural_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ural_softc *sc = ic->ic_softc; RAL_LOCK(sc); /* prevent management frames from being sent if we're not ready */ if (!sc->sc_running) { RAL_UNLOCK(sc); m_freem(m); return ENETDOWN; } if (sc->tx_nfree < RAL_TX_MINFREE) { RAL_UNLOCK(sc); m_freem(m); return EIO; } if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ if (ural_tx_mgt(sc, m, ni) != 0) goto bad; } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ if (ural_tx_raw(sc, m, ni, params) != 0) goto bad; } RAL_UNLOCK(sc); return 0; bad: RAL_UNLOCK(sc); return EIO; /* XXX */ } static void ural_ratectl_start(struct ural_softc *sc, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ural_vap *uvp = URAL_VAP(vap); /* clear statistic registers (STA_CSR0 to STA_CSR10) */ ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); usb_callout_reset(&uvp->ratectl_ch, hz, ural_ratectl_timeout, uvp); } static void ural_ratectl_timeout(void *arg) { struct ural_vap *uvp = arg; struct ieee80211vap *vap = &uvp->vap; struct ieee80211com *ic = vap->iv_ic; ieee80211_runtask(ic, &uvp->ratectl_task); } static void ural_ratectl_task(void *arg, int pending) { struct ural_vap *uvp = arg; struct ieee80211vap *vap = &uvp->vap; struct ural_softc *sc = vap->iv_ic->ic_softc; struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs; int fail; RAL_LOCK(sc); /* read and clear statistic registers (STA_CSR0 to STA_CSR10) */ ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof(sc->sta)); txs->flags = IEEE80211_RATECTL_TX_STATS_RETRIES; txs->nsuccess = sc->sta[7] + /* TX ok w/o retry */ sc->sta[8]; /* TX ok w/ retry */ fail = sc->sta[9]; /* TX retry-fail count */ txs->nframes = txs->nsuccess + fail; /* XXX fail * maxretry */ txs->nretries = sc->sta[8] + fail; ieee80211_ratectl_tx_update(vap, txs); /* count TX retry-fail as Tx errors */ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, fail); usb_callout_reset(&uvp->ratectl_ch, hz, ural_ratectl_timeout, uvp); RAL_UNLOCK(sc); } static int ural_pause(struct ural_softc *sc, int timeout) { usb_pause_mtx(&sc->sc_mtx, timeout); return (0); } Index: head/sys/dev/usb/wlan/if_urtw.c =================================================================== --- head/sys/dev/usb/wlan/if_urtw.c (revision 357971) +++ head/sys/dev/usb/wlan/if_urtw.c (revision 357972) @@ -1,4440 +1,4441 @@ /*- * Copyright (c) 2008 Weongyo Jeong * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #include #include #include #include #include #include "usbdevs.h" #include #include /* copy some rate indices from if_rtwn_ridx.h */ #define URTW_RIDX_CCK5 2 #define URTW_RIDX_CCK11 3 #define URTW_RIDX_OFDM6 4 #define URTW_RIDX_OFDM24 8 -static SYSCTL_NODE(_hw_usb, OID_AUTO, urtw, CTLFLAG_RW, 0, "USB Realtek 8187L"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, urtw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB Realtek 8187L"); #ifdef URTW_DEBUG int urtw_debug = 0; SYSCTL_INT(_hw_usb_urtw, OID_AUTO, debug, CTLFLAG_RWTUN, &urtw_debug, 0, "control debugging printfs"); enum { URTW_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ URTW_DEBUG_RECV = 0x00000002, /* basic recv operation */ URTW_DEBUG_RESET = 0x00000004, /* reset processing */ URTW_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */ URTW_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */ URTW_DEBUG_STATE = 0x00000020, /* 802.11 state transitions */ URTW_DEBUG_STAT = 0x00000040, /* statistic */ URTW_DEBUG_INIT = 0x00000080, /* initialization of dev */ URTW_DEBUG_TXSTATUS = 0x00000100, /* tx status */ URTW_DEBUG_ANY = 0xffffffff }; #define DPRINTF(sc, m, fmt, ...) do { \ if (sc->sc_debug & (m)) \ printf(fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, m, fmt, ...) do { \ (void) sc; \ } while (0) #endif static int urtw_preamble_mode = URTW_PREAMBLE_MODE_LONG; SYSCTL_INT(_hw_usb_urtw, OID_AUTO, preamble_mode, CTLFLAG_RWTUN, &urtw_preamble_mode, 0, "set the preable mode (long or short)"); /* recognized device vendors/products */ #define urtw_lookup(v, p) \ ((const struct urtw_type *)usb_lookup(urtw_devs, v, p)) #define URTW_DEV_B(v,p) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTW_REV_RTL8187B) } #define URTW_DEV_L(v,p) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTW_REV_RTL8187L) } #define URTW_REV_RTL8187B 0 #define URTW_REV_RTL8187L 1 static const STRUCT_USB_HOST_ID urtw_devs[] = { URTW_DEV_B(NETGEAR, WG111V3), URTW_DEV_B(REALTEK, RTL8187B_0), URTW_DEV_B(REALTEK, RTL8187B_1), URTW_DEV_B(REALTEK, RTL8187B_2), URTW_DEV_B(SITECOMEU, WL168V4), URTW_DEV_L(ASUS, P5B_WIFI), URTW_DEV_L(BELKIN, F5D7050E), URTW_DEV_L(LINKSYS4, WUSB54GCV2), URTW_DEV_L(NETGEAR, WG111V2), URTW_DEV_L(REALTEK, RTL8187), URTW_DEV_L(SITECOMEU, WL168V1), URTW_DEV_L(SURECOM, EP9001G2A), { USB_VPI(USB_VENDOR_OVISLINK, 0x8187, URTW_REV_RTL8187L) }, { USB_VPI(USB_VENDOR_DICKSMITH, 0x9401, URTW_REV_RTL8187L) }, { USB_VPI(USB_VENDOR_HP, 0xca02, URTW_REV_RTL8187L) }, { USB_VPI(USB_VENDOR_LOGITEC, 0x010c, URTW_REV_RTL8187L) }, { USB_VPI(USB_VENDOR_NETGEAR, 0x6100, URTW_REV_RTL8187L) }, { USB_VPI(USB_VENDOR_SPHAIRON, 0x0150, URTW_REV_RTL8187L) }, { USB_VPI(USB_VENDOR_QCOM, 0x6232, URTW_REV_RTL8187L) }, #undef URTW_DEV_L #undef URTW_DEV_B }; #define urtw_read8_m(sc, val, data) do { \ error = urtw_read8_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_write8_m(sc, val, data) do { \ error = urtw_write8_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_read16_m(sc, val, data) do { \ error = urtw_read16_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_write16_m(sc, val, data) do { \ error = urtw_write16_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_read32_m(sc, val, data) do { \ error = urtw_read32_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_write32_m(sc, val, data) do { \ error = urtw_write32_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_8187_write_phy_ofdm(sc, val, data) do { \ error = urtw_8187_write_phy_ofdm_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_8187_write_phy_cck(sc, val, data) do { \ error = urtw_8187_write_phy_cck_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define urtw_8225_write(sc, val, data) do { \ error = urtw_8225_write_c(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) struct urtw_pair { uint32_t reg; uint32_t val; }; static uint8_t urtw_8225_agc[] = { 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; static uint8_t urtw_8225z2_agc[] = { 0x5e, 0x5e, 0x5e, 0x5e, 0x5d, 0x5b, 0x59, 0x57, 0x55, 0x53, 0x51, 0x4f, 0x4d, 0x4b, 0x49, 0x47, 0x45, 0x43, 0x41, 0x3f, 0x3d, 0x3b, 0x39, 0x37, 0x35, 0x33, 0x31, 0x2f, 0x2d, 0x2b, 0x29, 0x27, 0x25, 0x23, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x11, 0x0f, 0x0d, 0x0b, 0x09, 0x07, 0x05, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31 }; static uint32_t urtw_8225_channel[] = { 0x0000, /* dummy channel 0 */ 0x085c, /* 1 */ 0x08dc, /* 2 */ 0x095c, /* 3 */ 0x09dc, /* 4 */ 0x0a5c, /* 5 */ 0x0adc, /* 6 */ 0x0b5c, /* 7 */ 0x0bdc, /* 8 */ 0x0c5c, /* 9 */ 0x0cdc, /* 10 */ 0x0d5c, /* 11 */ 0x0ddc, /* 12 */ 0x0e5c, /* 13 */ 0x0f72, /* 14 */ }; static uint8_t urtw_8225_gain[] = { 0x23, 0x88, 0x7c, 0xa5, /* -82dbm */ 0x23, 0x88, 0x7c, 0xb5, /* -82dbm */ 0x23, 0x88, 0x7c, 0xc5, /* -82dbm */ 0x33, 0x80, 0x79, 0xc5, /* -78dbm */ 0x43, 0x78, 0x76, 0xc5, /* -74dbm */ 0x53, 0x60, 0x73, 0xc5, /* -70dbm */ 0x63, 0x58, 0x70, 0xc5, /* -66dbm */ }; static struct urtw_pair urtw_8225_rf_part1[] = { { 0x00, 0x0067 }, { 0x01, 0x0fe0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, { 0x04, 0x0486 }, { 0x05, 0x0bc0 }, { 0x06, 0x0ae6 }, { 0x07, 0x082a }, { 0x08, 0x001f }, { 0x09, 0x0334 }, { 0x0a, 0x0fd4 }, { 0x0b, 0x0391 }, { 0x0c, 0x0050 }, { 0x0d, 0x06db }, { 0x0e, 0x0029 }, { 0x0f, 0x0914 }, }; static struct urtw_pair urtw_8225_rf_part2[] = { { 0x00, 0x01 }, { 0x01, 0x02 }, { 0x02, 0x42 }, { 0x03, 0x00 }, { 0x04, 0x00 }, { 0x05, 0x00 }, { 0x06, 0x40 }, { 0x07, 0x00 }, { 0x08, 0x40 }, { 0x09, 0xfe }, { 0x0a, 0x09 }, { 0x0b, 0x80 }, { 0x0c, 0x01 }, { 0x0e, 0xd3 }, { 0x0f, 0x38 }, { 0x10, 0x84 }, { 0x11, 0x06 }, { 0x12, 0x20 }, { 0x13, 0x20 }, { 0x14, 0x00 }, { 0x15, 0x40 }, { 0x16, 0x00 }, { 0x17, 0x40 }, { 0x18, 0xef }, { 0x19, 0x19 }, { 0x1a, 0x20 }, { 0x1b, 0x76 }, { 0x1c, 0x04 }, { 0x1e, 0x95 }, { 0x1f, 0x75 }, { 0x20, 0x1f }, { 0x21, 0x27 }, { 0x22, 0x16 }, { 0x24, 0x46 }, { 0x25, 0x20 }, { 0x26, 0x90 }, { 0x27, 0x88 } }; static struct urtw_pair urtw_8225_rf_part3[] = { { 0x00, 0x98 }, { 0x03, 0x20 }, { 0x04, 0x7e }, { 0x05, 0x12 }, { 0x06, 0xfc }, { 0x07, 0x78 }, { 0x08, 0x2e }, { 0x10, 0x9b }, { 0x11, 0x88 }, { 0x12, 0x47 }, { 0x13, 0xd0 }, { 0x19, 0x00 }, { 0x1a, 0xa0 }, { 0x1b, 0x08 }, { 0x40, 0x86 }, { 0x41, 0x8d }, { 0x42, 0x15 }, { 0x43, 0x18 }, { 0x44, 0x1f }, { 0x45, 0x1e }, { 0x46, 0x1a }, { 0x47, 0x15 }, { 0x48, 0x10 }, { 0x49, 0x0a }, { 0x4a, 0x05 }, { 0x4b, 0x02 }, { 0x4c, 0x05 } }; static uint16_t urtw_8225_rxgain[] = { 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, 0x07aa, 0x07ab, 0x07ac, 0x07ad, 0x07b0, 0x07b1, 0x07b2, 0x07b3, 0x07b4, 0x07b5, 0x07b8, 0x07b9, 0x07ba, 0x07bb, 0x07bb }; static uint8_t urtw_8225_threshold[] = { 0x8d, 0x8d, 0x8d, 0x8d, 0x9d, 0xad, 0xbd, }; static uint8_t urtw_8225_tx_gain_cck_ofdm[] = { 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e }; static uint8_t urtw_8225_txpwr_cck[] = { 0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02, 0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02, 0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02, 0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02, 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03, 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03 }; static uint8_t urtw_8225_txpwr_cck_ch14[] = { 0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, 0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00 }; static uint8_t urtw_8225_txpwr_ofdm[]={ 0x80, 0x90, 0xa2, 0xb5, 0xcb, 0xe4 }; static uint8_t urtw_8225v2_gain_bg[]={ 0x23, 0x15, 0xa5, /* -82-1dbm */ 0x23, 0x15, 0xb5, /* -82-2dbm */ 0x23, 0x15, 0xc5, /* -82-3dbm */ 0x33, 0x15, 0xc5, /* -78dbm */ 0x43, 0x15, 0xc5, /* -74dbm */ 0x53, 0x15, 0xc5, /* -70dbm */ 0x63, 0x15, 0xc5, /* -66dbm */ }; static struct urtw_pair urtw_8225v2_rf_part1[] = { { 0x00, 0x02bf }, { 0x01, 0x0ee0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, { 0x04, 0x08c3 }, { 0x05, 0x0c72 }, { 0x06, 0x00e6 }, { 0x07, 0x082a }, { 0x08, 0x003f }, { 0x09, 0x0335 }, { 0x0a, 0x09d4 }, { 0x0b, 0x07bb }, { 0x0c, 0x0850 }, { 0x0d, 0x0cdf }, { 0x0e, 0x002b }, { 0x0f, 0x0114 } }; static struct urtw_pair urtw_8225v2b_rf_part0[] = { { 0x00, 0x00b7 }, { 0x01, 0x0ee0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, { 0x04, 0x08c3 }, { 0x05, 0x0c72 }, { 0x06, 0x00e6 }, { 0x07, 0x082a }, { 0x08, 0x003f }, { 0x09, 0x0335 }, { 0x0a, 0x09d4 }, { 0x0b, 0x07bb }, { 0x0c, 0x0850 }, { 0x0d, 0x0cdf }, { 0x0e, 0x002b }, { 0x0f, 0x0114 } }; static struct urtw_pair urtw_8225v2b_rf_part1[] = { {0x0f0, 0x32}, {0x0f1, 0x32}, {0x0f2, 0x00}, {0x0f3, 0x00}, {0x0f4, 0x32}, {0x0f5, 0x43}, {0x0f6, 0x00}, {0x0f7, 0x00}, {0x0f8, 0x46}, {0x0f9, 0xa4}, {0x0fa, 0x00}, {0x0fb, 0x00}, {0x0fc, 0x96}, {0x0fd, 0xa4}, {0x0fe, 0x00}, {0x0ff, 0x00}, {0x158, 0x4b}, {0x159, 0x00}, {0x15a, 0x4b}, {0x15b, 0x00}, {0x160, 0x4b}, {0x161, 0x09}, {0x162, 0x4b}, {0x163, 0x09}, {0x1ce, 0x0f}, {0x1cf, 0x00}, {0x1e0, 0xff}, {0x1e1, 0x0f}, {0x1e2, 0x00}, {0x1f0, 0x4e}, {0x1f1, 0x01}, {0x1f2, 0x02}, {0x1f3, 0x03}, {0x1f4, 0x04}, {0x1f5, 0x05}, {0x1f6, 0x06}, {0x1f7, 0x07}, {0x1f8, 0x08}, {0x24e, 0x00}, {0x20c, 0x04}, {0x221, 0x61}, {0x222, 0x68}, {0x223, 0x6f}, {0x224, 0x76}, {0x225, 0x7d}, {0x226, 0x84}, {0x227, 0x8d}, {0x24d, 0x08}, {0x250, 0x05}, {0x251, 0xf5}, {0x252, 0x04}, {0x253, 0xa0}, {0x254, 0x1f}, {0x255, 0x23}, {0x256, 0x45}, {0x257, 0x67}, {0x258, 0x08}, {0x259, 0x08}, {0x25a, 0x08}, {0x25b, 0x08}, {0x260, 0x08}, {0x261, 0x08}, {0x262, 0x08}, {0x263, 0x08}, {0x264, 0xcf}, {0x272, 0x56}, {0x273, 0x9a}, {0x034, 0xf0}, {0x035, 0x0f}, {0x05b, 0x40}, {0x084, 0x88}, {0x085, 0x24}, {0x088, 0x54}, {0x08b, 0xb8}, {0x08c, 0x07}, {0x08d, 0x00}, {0x094, 0x1b}, {0x095, 0x12}, {0x096, 0x00}, {0x097, 0x06}, {0x09d, 0x1a}, {0x09f, 0x10}, {0x0b4, 0x22}, {0x0be, 0x80}, {0x0db, 0x00}, {0x0ee, 0x00}, {0x091, 0x03}, {0x24c, 0x00}, {0x39f, 0x00}, {0x08c, 0x01}, {0x08d, 0x10}, {0x08e, 0x08}, {0x08f, 0x00} }; static struct urtw_pair urtw_8225v2_rf_part2[] = { { 0x00, 0x01 }, { 0x01, 0x02 }, { 0x02, 0x42 }, { 0x03, 0x00 }, { 0x04, 0x00 }, { 0x05, 0x00 }, { 0x06, 0x40 }, { 0x07, 0x00 }, { 0x08, 0x40 }, { 0x09, 0xfe }, { 0x0a, 0x08 }, { 0x0b, 0x80 }, { 0x0c, 0x01 }, { 0x0d, 0x43 }, { 0x0e, 0xd3 }, { 0x0f, 0x38 }, { 0x10, 0x84 }, { 0x11, 0x07 }, { 0x12, 0x20 }, { 0x13, 0x20 }, { 0x14, 0x00 }, { 0x15, 0x40 }, { 0x16, 0x00 }, { 0x17, 0x40 }, { 0x18, 0xef }, { 0x19, 0x19 }, { 0x1a, 0x20 }, { 0x1b, 0x15 }, { 0x1c, 0x04 }, { 0x1d, 0xc5 }, { 0x1e, 0x95 }, { 0x1f, 0x75 }, { 0x20, 0x1f }, { 0x21, 0x17 }, { 0x22, 0x16 }, { 0x23, 0x80 }, { 0x24, 0x46 }, { 0x25, 0x00 }, { 0x26, 0x90 }, { 0x27, 0x88 } }; static struct urtw_pair urtw_8225v2b_rf_part2[] = { { 0x00, 0x10 }, { 0x01, 0x0d }, { 0x02, 0x01 }, { 0x03, 0x00 }, { 0x04, 0x14 }, { 0x05, 0xfb }, { 0x06, 0xfb }, { 0x07, 0x60 }, { 0x08, 0x00 }, { 0x09, 0x60 }, { 0x0a, 0x00 }, { 0x0b, 0x00 }, { 0x0c, 0x00 }, { 0x0d, 0x5c }, { 0x0e, 0x00 }, { 0x0f, 0x00 }, { 0x10, 0x40 }, { 0x11, 0x00 }, { 0x12, 0x40 }, { 0x13, 0x00 }, { 0x14, 0x00 }, { 0x15, 0x00 }, { 0x16, 0xa8 }, { 0x17, 0x26 }, { 0x18, 0x32 }, { 0x19, 0x33 }, { 0x1a, 0x07 }, { 0x1b, 0xa5 }, { 0x1c, 0x6f }, { 0x1d, 0x55 }, { 0x1e, 0xc8 }, { 0x1f, 0xb3 }, { 0x20, 0x0a }, { 0x21, 0xe1 }, { 0x22, 0x2C }, { 0x23, 0x8a }, { 0x24, 0x86 }, { 0x25, 0x83 }, { 0x26, 0x34 }, { 0x27, 0x0f }, { 0x28, 0x4f }, { 0x29, 0x24 }, { 0x2a, 0x6f }, { 0x2b, 0xc2 }, { 0x2c, 0x6b }, { 0x2d, 0x40 }, { 0x2e, 0x80 }, { 0x2f, 0x00 }, { 0x30, 0xc0 }, { 0x31, 0xc1 }, { 0x32, 0x58 }, { 0x33, 0xf1 }, { 0x34, 0x00 }, { 0x35, 0xe4 }, { 0x36, 0x90 }, { 0x37, 0x3e }, { 0x38, 0x6d }, { 0x39, 0x3c }, { 0x3a, 0xfb }, { 0x3b, 0x07 } }; static struct urtw_pair urtw_8225v2_rf_part3[] = { { 0x00, 0x98 }, { 0x03, 0x20 }, { 0x04, 0x7e }, { 0x05, 0x12 }, { 0x06, 0xfc }, { 0x07, 0x78 }, { 0x08, 0x2e }, { 0x09, 0x11 }, { 0x0a, 0x17 }, { 0x0b, 0x11 }, { 0x10, 0x9b }, { 0x11, 0x88 }, { 0x12, 0x47 }, { 0x13, 0xd0 }, { 0x19, 0x00 }, { 0x1a, 0xa0 }, { 0x1b, 0x08 }, { 0x1d, 0x00 }, { 0x40, 0x86 }, { 0x41, 0x9d }, { 0x42, 0x15 }, { 0x43, 0x18 }, { 0x44, 0x36 }, { 0x45, 0x35 }, { 0x46, 0x2e }, { 0x47, 0x25 }, { 0x48, 0x1c }, { 0x49, 0x12 }, { 0x4a, 0x09 }, { 0x4b, 0x04 }, { 0x4c, 0x05 } }; static uint16_t urtw_8225v2_rxgain[] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0008, 0x0009, 0x000a, 0x000b, 0x0102, 0x0103, 0x0104, 0x0105, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0188, 0x0189, 0x018a, 0x018b, 0x0243, 0x0244, 0x0245, 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x0342, 0x0343, 0x0344, 0x0345, 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0388, 0x0389, 0x038a, 0x038b, 0x038c, 0x038d, 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb }; static uint16_t urtw_8225v2b_rxgain[] = { 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb }; static uint8_t urtw_8225v2_tx_gain_cck_ofdm[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, }; static uint8_t urtw_8225v2_txpwr_cck[] = { 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04 }; static uint8_t urtw_8225v2_txpwr_cck_ch14[] = { 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00 }; static uint8_t urtw_8225v2b_txpwr_cck[] = { 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04, 0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03, 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03, 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03 }; static uint8_t urtw_8225v2b_txpwr_cck_ch14[] = { 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00, 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00, 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00 }; static struct urtw_pair urtw_ratetable[] = { { 2, 0 }, { 4, 1 }, { 11, 2 }, { 12, 4 }, { 18, 5 }, { 22, 3 }, { 24, 6 }, { 36, 7 }, { 48, 8 }, { 72, 9 }, { 96, 10 }, { 108, 11 } }; #if 0 static const uint8_t urtw_8187b_reg_table[][3] = { { 0xf0, 0x32, 0 }, { 0xf1, 0x32, 0 }, { 0xf2, 0x00, 0 }, { 0xf3, 0x00, 0 }, { 0xf4, 0x32, 0 }, { 0xf5, 0x43, 0 }, { 0xf6, 0x00, 0 }, { 0xf7, 0x00, 0 }, { 0xf8, 0x46, 0 }, { 0xf9, 0xa4, 0 }, { 0xfa, 0x00, 0 }, { 0xfb, 0x00, 0 }, { 0xfc, 0x96, 0 }, { 0xfd, 0xa4, 0 }, { 0xfe, 0x00, 0 }, { 0xff, 0x00, 0 }, { 0x58, 0x4b, 1 }, { 0x59, 0x00, 1 }, { 0x5a, 0x4b, 1 }, { 0x5b, 0x00, 1 }, { 0x60, 0x4b, 1 }, { 0x61, 0x09, 1 }, { 0x62, 0x4b, 1 }, { 0x63, 0x09, 1 }, { 0xce, 0x0f, 1 }, { 0xcf, 0x00, 1 }, { 0xe0, 0xff, 1 }, { 0xe1, 0x0f, 1 }, { 0xe2, 0x00, 1 }, { 0xf0, 0x4e, 1 }, { 0xf1, 0x01, 1 }, { 0xf2, 0x02, 1 }, { 0xf3, 0x03, 1 }, { 0xf4, 0x04, 1 }, { 0xf5, 0x05, 1 }, { 0xf6, 0x06, 1 }, { 0xf7, 0x07, 1 }, { 0xf8, 0x08, 1 }, { 0x4e, 0x00, 2 }, { 0x0c, 0x04, 2 }, { 0x21, 0x61, 2 }, { 0x22, 0x68, 2 }, { 0x23, 0x6f, 2 }, { 0x24, 0x76, 2 }, { 0x25, 0x7d, 2 }, { 0x26, 0x84, 2 }, { 0x27, 0x8d, 2 }, { 0x4d, 0x08, 2 }, { 0x50, 0x05, 2 }, { 0x51, 0xf5, 2 }, { 0x52, 0x04, 2 }, { 0x53, 0xa0, 2 }, { 0x54, 0x1f, 2 }, { 0x55, 0x23, 2 }, { 0x56, 0x45, 2 }, { 0x57, 0x67, 2 }, { 0x58, 0x08, 2 }, { 0x59, 0x08, 2 }, { 0x5a, 0x08, 2 }, { 0x5b, 0x08, 2 }, { 0x60, 0x08, 2 }, { 0x61, 0x08, 2 }, { 0x62, 0x08, 2 }, { 0x63, 0x08, 2 }, { 0x64, 0xcf, 2 }, { 0x72, 0x56, 2 }, { 0x73, 0x9a, 2 }, { 0x34, 0xf0, 0 }, { 0x35, 0x0f, 0 }, { 0x5b, 0x40, 0 }, { 0x84, 0x88, 0 }, { 0x85, 0x24, 0 }, { 0x88, 0x54, 0 }, { 0x8b, 0xb8, 0 }, { 0x8c, 0x07, 0 }, { 0x8d, 0x00, 0 }, { 0x94, 0x1b, 0 }, { 0x95, 0x12, 0 }, { 0x96, 0x00, 0 }, { 0x97, 0x06, 0 }, { 0x9d, 0x1a, 0 }, { 0x9f, 0x10, 0 }, { 0xb4, 0x22, 0 }, { 0xbe, 0x80, 0 }, { 0xdb, 0x00, 0 }, { 0xee, 0x00, 0 }, { 0x91, 0x03, 0 }, { 0x4c, 0x00, 2 }, { 0x9f, 0x00, 3 }, { 0x8c, 0x01, 0 }, { 0x8d, 0x10, 0 }, { 0x8e, 0x08, 0 }, { 0x8f, 0x00, 0 } }; #endif static usb_callback_t urtw_bulk_rx_callback; static usb_callback_t urtw_bulk_tx_callback; static usb_callback_t urtw_bulk_tx_status_callback; static const struct usb_config urtw_8187b_usbconfig[URTW_8187B_N_XFERS] = { [URTW_8187B_BULK_RX] = { .type = UE_BULK, .endpoint = 0x83, .direction = UE_DIR_IN, .bufsize = MCLBYTES, .flags = { .ext_buffer = 1, .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = urtw_bulk_rx_callback }, [URTW_8187B_BULK_TX_STATUS] = { .type = UE_BULK, .endpoint = 0x89, .direction = UE_DIR_IN, .bufsize = sizeof(uint64_t), .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = urtw_bulk_tx_status_callback }, [URTW_8187B_BULK_TX_BE] = { .type = UE_BULK, .endpoint = URTW_8187B_TXPIPE_BE, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE * URTW_TX_DATA_LIST_COUNT, .flags = { .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT }, [URTW_8187B_BULK_TX_BK] = { .type = UE_BULK, .endpoint = URTW_8187B_TXPIPE_BK, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE, .flags = { .ext_buffer = 1, .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT }, [URTW_8187B_BULK_TX_VI] = { .type = UE_BULK, .endpoint = URTW_8187B_TXPIPE_VI, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE, .flags = { .ext_buffer = 1, .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT }, [URTW_8187B_BULK_TX_VO] = { .type = UE_BULK, .endpoint = URTW_8187B_TXPIPE_VO, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE, .flags = { .ext_buffer = 1, .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT }, [URTW_8187B_BULK_TX_EP12] = { .type = UE_BULK, .endpoint = 0xc, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE, .flags = { .ext_buffer = 1, .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT } }; static const struct usb_config urtw_8187l_usbconfig[URTW_8187L_N_XFERS] = { [URTW_8187L_BULK_RX] = { .type = UE_BULK, .endpoint = 0x81, .direction = UE_DIR_IN, .bufsize = MCLBYTES, .flags = { .ext_buffer = 1, .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = urtw_bulk_rx_callback }, [URTW_8187L_BULK_TX_LOW] = { .type = UE_BULK, .endpoint = 0x2, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE * URTW_TX_DATA_LIST_COUNT, .flags = { .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT }, [URTW_8187L_BULK_TX_NORMAL] = { .type = UE_BULK, .endpoint = 0x3, .direction = UE_DIR_OUT, .bufsize = URTW_TX_MAXSIZE, .flags = { .ext_buffer = 1, .force_short_xfer = 1, .pipe_bof = 1, }, .callback = urtw_bulk_tx_callback, .timeout = URTW_DATA_TIMEOUT }, }; static struct ieee80211vap *urtw_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void urtw_vap_delete(struct ieee80211vap *); static void urtw_init(struct urtw_softc *); static void urtw_stop(struct urtw_softc *); static void urtw_parent(struct ieee80211com *); static int urtw_transmit(struct ieee80211com *, struct mbuf *); static void urtw_start(struct urtw_softc *); static int urtw_alloc_rx_data_list(struct urtw_softc *); static int urtw_alloc_tx_data_list(struct urtw_softc *); static int urtw_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void urtw_scan_start(struct ieee80211com *); static void urtw_scan_end(struct ieee80211com *); static void urtw_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static void urtw_set_channel(struct ieee80211com *); static void urtw_update_promisc(struct ieee80211com *); static void urtw_update_mcast(struct ieee80211com *); static int urtw_tx_start(struct urtw_softc *, struct ieee80211_node *, struct mbuf *, struct urtw_data *, int); static int urtw_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void urtw_led_ch(void *); static void urtw_ledtask(void *, int); static void urtw_watchdog(void *); static void urtw_set_multi(void *); static int urtw_isbmode(uint16_t); static uint16_t urtw_rtl2rate(uint32_t); static usb_error_t urtw_set_rate(struct urtw_softc *); static usb_error_t urtw_update_msr(struct urtw_softc *); static usb_error_t urtw_read8_c(struct urtw_softc *, int, uint8_t *); static usb_error_t urtw_read16_c(struct urtw_softc *, int, uint16_t *); static usb_error_t urtw_read32_c(struct urtw_softc *, int, uint32_t *); static usb_error_t urtw_write8_c(struct urtw_softc *, int, uint8_t); static usb_error_t urtw_write16_c(struct urtw_softc *, int, uint16_t); static usb_error_t urtw_write32_c(struct urtw_softc *, int, uint32_t); static usb_error_t urtw_eprom_cs(struct urtw_softc *, int); static usb_error_t urtw_eprom_ck(struct urtw_softc *); static usb_error_t urtw_eprom_sendbits(struct urtw_softc *, int16_t *, int); static usb_error_t urtw_eprom_read32(struct urtw_softc *, uint32_t, uint32_t *); static usb_error_t urtw_eprom_readbit(struct urtw_softc *, int16_t *); static usb_error_t urtw_eprom_writebit(struct urtw_softc *, int16_t); static usb_error_t urtw_get_macaddr(struct urtw_softc *); static usb_error_t urtw_get_txpwr(struct urtw_softc *); static usb_error_t urtw_get_rfchip(struct urtw_softc *); static usb_error_t urtw_led_init(struct urtw_softc *); static usb_error_t urtw_8185_rf_pins_enable(struct urtw_softc *); static usb_error_t urtw_8185_tx_antenna(struct urtw_softc *, uint8_t); static usb_error_t urtw_8187_write_phy(struct urtw_softc *, uint8_t, uint32_t); static usb_error_t urtw_8187_write_phy_ofdm_c(struct urtw_softc *, uint8_t, uint32_t); static usb_error_t urtw_8187_write_phy_cck_c(struct urtw_softc *, uint8_t, uint32_t); static usb_error_t urtw_8225_setgain(struct urtw_softc *, int16_t); static usb_error_t urtw_8225_usb_init(struct urtw_softc *); static usb_error_t urtw_8225_write_c(struct urtw_softc *, uint8_t, uint16_t); static usb_error_t urtw_8225_write_s16(struct urtw_softc *, uint8_t, int, uint16_t *); static usb_error_t urtw_8225_read(struct urtw_softc *, uint8_t, uint32_t *); static usb_error_t urtw_8225_rf_init(struct urtw_softc *); static usb_error_t urtw_8225_rf_set_chan(struct urtw_softc *, int); static usb_error_t urtw_8225_rf_set_sens(struct urtw_softc *, int); static usb_error_t urtw_8225_set_txpwrlvl(struct urtw_softc *, int); static usb_error_t urtw_8225_rf_stop(struct urtw_softc *); static usb_error_t urtw_8225v2_rf_init(struct urtw_softc *); static usb_error_t urtw_8225v2_rf_set_chan(struct urtw_softc *, int); static usb_error_t urtw_8225v2_set_txpwrlvl(struct urtw_softc *, int); static usb_error_t urtw_8225v2_setgain(struct urtw_softc *, int16_t); static usb_error_t urtw_8225_isv2(struct urtw_softc *, int *); static usb_error_t urtw_8225v2b_rf_init(struct urtw_softc *); static usb_error_t urtw_8225v2b_rf_set_chan(struct urtw_softc *, int); static usb_error_t urtw_read8e(struct urtw_softc *, int, uint8_t *); static usb_error_t urtw_write8e(struct urtw_softc *, int, uint8_t); static usb_error_t urtw_8180_set_anaparam(struct urtw_softc *, uint32_t); static usb_error_t urtw_8185_set_anaparam2(struct urtw_softc *, uint32_t); static usb_error_t urtw_intr_enable(struct urtw_softc *); static usb_error_t urtw_intr_disable(struct urtw_softc *); static usb_error_t urtw_reset(struct urtw_softc *); static usb_error_t urtw_led_on(struct urtw_softc *, int); static usb_error_t urtw_led_ctl(struct urtw_softc *, int); static usb_error_t urtw_led_blink(struct urtw_softc *); static usb_error_t urtw_led_mode0(struct urtw_softc *, int); static usb_error_t urtw_led_mode1(struct urtw_softc *, int); static usb_error_t urtw_led_mode2(struct urtw_softc *, int); static usb_error_t urtw_led_mode3(struct urtw_softc *, int); static usb_error_t urtw_rx_setconf(struct urtw_softc *); static usb_error_t urtw_rx_enable(struct urtw_softc *); static usb_error_t urtw_tx_enable(struct urtw_softc *sc); static void urtw_free_tx_data_list(struct urtw_softc *); static void urtw_free_rx_data_list(struct urtw_softc *); static void urtw_free_data_list(struct urtw_softc *, struct urtw_data data[], int, int); static usb_error_t urtw_set_macaddr(struct urtw_softc *, const uint8_t *); static usb_error_t urtw_adapter_start(struct urtw_softc *); static usb_error_t urtw_adapter_start_b(struct urtw_softc *); static usb_error_t urtw_set_mode(struct urtw_softc *, uint32_t); static usb_error_t urtw_8187b_cmd_reset(struct urtw_softc *); static usb_error_t urtw_do_request(struct urtw_softc *, struct usb_device_request *, void *); static usb_error_t urtw_8225v2b_set_txpwrlvl(struct urtw_softc *, int); static usb_error_t urtw_led_off(struct urtw_softc *, int); static void urtw_abort_xfers(struct urtw_softc *); static struct urtw_data * urtw_getbuf(struct urtw_softc *sc); static int urtw_compute_txtime(uint16_t, uint16_t, uint8_t, uint8_t); static void urtw_updateslot(struct ieee80211com *); static void urtw_updateslottask(void *, int); static void urtw_sysctl_node(struct urtw_softc *); static int urtw_match(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != URTW_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != URTW_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(urtw_devs, sizeof(urtw_devs), uaa)); } static int urtw_attach(device_t dev) { const struct usb_config *setup_start; int ret = ENXIO; struct urtw_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct ieee80211com *ic = &sc->sc_ic; uint8_t iface_index = URTW_IFACE_INDEX; /* XXX */ uint16_t n_setup; uint32_t data; usb_error_t error; device_set_usb_desc(dev); sc->sc_dev = dev; sc->sc_udev = uaa->device; if (USB_GET_DRIVER_INFO(uaa) == URTW_REV_RTL8187B) sc->sc_flags |= URTW_RTL8187B; #ifdef URTW_DEBUG sc->sc_debug = urtw_debug; #endif mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, MTX_DEF); usb_callout_init_mtx(&sc->sc_led_ch, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_led_task, 0, urtw_ledtask, sc); TASK_INIT(&sc->sc_updateslot_task, 0, urtw_updateslottask, sc); callout_init(&sc->sc_watchdog_ch, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); if (sc->sc_flags & URTW_RTL8187B) { setup_start = urtw_8187b_usbconfig; n_setup = URTW_8187B_N_XFERS; } else { setup_start = urtw_8187l_usbconfig; n_setup = URTW_8187L_N_XFERS; } error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, setup_start, n_setup, sc, &sc->sc_mtx); if (error) { device_printf(dev, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); ret = ENXIO; goto fail0; } if (sc->sc_flags & URTW_RTL8187B) { sc->sc_tx_dma_buf = usbd_xfer_get_frame_buffer(sc->sc_xfer[ URTW_8187B_BULK_TX_BE], 0); } else { sc->sc_tx_dma_buf = usbd_xfer_get_frame_buffer(sc->sc_xfer[ URTW_8187L_BULK_TX_LOW], 0); } URTW_LOCK(sc); urtw_read32_m(sc, URTW_RX, &data); sc->sc_epromtype = (data & URTW_RX_9356SEL) ? URTW_EEPROM_93C56 : URTW_EEPROM_93C46; error = urtw_get_rfchip(sc); if (error != 0) goto fail; error = urtw_get_macaddr(sc); if (error != 0) goto fail; error = urtw_get_txpwr(sc); if (error != 0) goto fail; error = urtw_led_init(sc); if (error != 0) goto fail; URTW_UNLOCK(sc); sc->sc_rts_retry = URTW_DEFAULT_RTS_RETRY; sc->sc_tx_retry = URTW_DEFAULT_TX_RETRY; sc->sc_currate = URTW_RIDX_CCK11; sc->sc_preamble_mode = urtw_preamble_mode; ic->ic_softc = sc; ic->ic_name = device_get_nameunit(dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA | /* station mode */ IEEE80211_C_MONITOR | /* monitor mode supported */ IEEE80211_C_TXPMGT | /* tx power management */ IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_BGSCAN | /* capable of bg scanning */ IEEE80211_C_WPA; /* 802.11i */ /* XXX TODO: setup regdomain if URTW_EPROM_CHANPLAN_BY_HW bit is set.*/ urtw_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_raw_xmit = urtw_raw_xmit; ic->ic_scan_start = urtw_scan_start; ic->ic_scan_end = urtw_scan_end; ic->ic_getradiocaps = urtw_getradiocaps; ic->ic_set_channel = urtw_set_channel; ic->ic_updateslot = urtw_updateslot; ic->ic_vap_create = urtw_vap_create; ic->ic_vap_delete = urtw_vap_delete; ic->ic_update_promisc = urtw_update_promisc; ic->ic_update_mcast = urtw_update_mcast; ic->ic_parent = urtw_parent; ic->ic_transmit = urtw_transmit; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), URTW_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), URTW_RX_RADIOTAP_PRESENT); urtw_sysctl_node(sc); if (bootverbose) ieee80211_announce(ic); return (0); fail: URTW_UNLOCK(sc); usbd_transfer_unsetup(sc->sc_xfer, (sc->sc_flags & URTW_RTL8187B) ? URTW_8187B_N_XFERS : URTW_8187L_N_XFERS); fail0: return (ret); } static int urtw_detach(device_t dev) { struct urtw_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; unsigned int x; unsigned int n_xfers; /* Prevent further ioctls */ URTW_LOCK(sc); sc->sc_flags |= URTW_DETACHED; urtw_stop(sc); URTW_UNLOCK(sc); ieee80211_draintask(ic, &sc->sc_updateslot_task); ieee80211_draintask(ic, &sc->sc_led_task); usb_callout_drain(&sc->sc_led_ch); callout_drain(&sc->sc_watchdog_ch); n_xfers = (sc->sc_flags & URTW_RTL8187B) ? URTW_8187B_N_XFERS : URTW_8187L_N_XFERS; /* prevent further allocations from RX/TX data lists */ URTW_LOCK(sc); STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); URTW_UNLOCK(sc); /* drain USB transfers */ for (x = 0; x != n_xfers; x++) usbd_transfer_drain(sc->sc_xfer[x]); /* free data buffers */ URTW_LOCK(sc); urtw_free_tx_data_list(sc); urtw_free_rx_data_list(sc); URTW_UNLOCK(sc); /* free USB transfers and some data buffers */ usbd_transfer_unsetup(sc->sc_xfer, n_xfers); ieee80211_ifdetach(ic); mbufq_drain(&sc->sc_snd); mtx_destroy(&sc->sc_mtx); return (0); } static void urtw_free_tx_data_list(struct urtw_softc *sc) { urtw_free_data_list(sc, sc->sc_tx, URTW_TX_DATA_LIST_COUNT, 0); } static void urtw_free_rx_data_list(struct urtw_softc *sc) { urtw_free_data_list(sc, sc->sc_rx, URTW_RX_DATA_LIST_COUNT, 1); } static void urtw_free_data_list(struct urtw_softc *sc, struct urtw_data data[], int ndata, int fillmbuf) { int i; for (i = 0; i < ndata; i++) { struct urtw_data *dp = &data[i]; if (fillmbuf == 1) { if (dp->m != NULL) { m_freem(dp->m); dp->m = NULL; dp->buf = NULL; } } else { dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static struct ieee80211vap * urtw_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct urtw_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct urtw_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = urtw_newstate; /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void urtw_vap_delete(struct ieee80211vap *vap) { struct urtw_vap *uvp = URTW_VAP(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void urtw_init(struct urtw_softc *sc) { usb_error_t error; int ret; URTW_ASSERT_LOCKED(sc); if (sc->sc_flags & URTW_RUNNING) urtw_stop(sc); error = (sc->sc_flags & URTW_RTL8187B) ? urtw_adapter_start_b(sc) : urtw_adapter_start(sc); if (error != 0) goto fail; /* reset softc variables */ sc->sc_txtimer = 0; if (!(sc->sc_flags & URTW_INIT_ONCE)) { ret = urtw_alloc_rx_data_list(sc); if (ret != 0) goto fail; ret = urtw_alloc_tx_data_list(sc); if (ret != 0) goto fail; sc->sc_flags |= URTW_INIT_ONCE; } error = urtw_rx_enable(sc); if (error != 0) goto fail; error = urtw_tx_enable(sc); if (error != 0) goto fail; if (sc->sc_flags & URTW_RTL8187B) usbd_transfer_start(sc->sc_xfer[URTW_8187B_BULK_TX_STATUS]); sc->sc_flags |= URTW_RUNNING; callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); fail: return; } static usb_error_t urtw_adapter_start_b(struct urtw_softc *sc) { uint8_t data8; usb_error_t error; error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_read8_m(sc, URTW_CONFIG3, &data8); urtw_write8_m(sc, URTW_CONFIG3, data8 | URTW_CONFIG3_ANAPARAM_WRITE | URTW_CONFIG3_GNT_SELECT); urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8187B_8225_ANAPARAM2_ON); urtw_write32_m(sc, URTW_ANAPARAM, URTW_8187B_8225_ANAPARAM_ON); urtw_write8_m(sc, URTW_ANAPARAM3, URTW_8187B_8225_ANAPARAM3_ON); urtw_write8_m(sc, 0x61, 0x10); urtw_read8_m(sc, 0x62, &data8); urtw_write8_m(sc, 0x62, data8 & ~(1 << 5)); urtw_write8_m(sc, 0x62, data8 | (1 << 5)); urtw_read8_m(sc, URTW_CONFIG3, &data8); data8 &= ~URTW_CONFIG3_ANAPARAM_WRITE; urtw_write8_m(sc, URTW_CONFIG3, data8); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; error = urtw_8187b_cmd_reset(sc); if (error) goto fail; error = sc->sc_rf_init(sc); if (error != 0) goto fail; urtw_write8_m(sc, URTW_CMD, URTW_CMD_RX_ENABLE | URTW_CMD_TX_ENABLE); /* fix RTL8187B RX stall */ error = urtw_intr_enable(sc); if (error) goto fail; error = urtw_write8e(sc, 0x41, 0xf4); if (error) goto fail; error = urtw_write8e(sc, 0x40, 0x00); if (error) goto fail; error = urtw_write8e(sc, 0x42, 0x00); if (error) goto fail; error = urtw_write8e(sc, 0x42, 0x01); if (error) goto fail; error = urtw_write8e(sc, 0x40, 0x0f); if (error) goto fail; error = urtw_write8e(sc, 0x42, 0x00); if (error) goto fail; error = urtw_write8e(sc, 0x42, 0x01); if (error) goto fail; urtw_read8_m(sc, 0xdb, &data8); urtw_write8_m(sc, 0xdb, data8 | (1 << 2)); urtw_write16_m(sc, 0x372, 0x59fa); urtw_write16_m(sc, 0x374, 0x59d2); urtw_write16_m(sc, 0x376, 0x59d2); urtw_write16_m(sc, 0x378, 0x19fa); urtw_write16_m(sc, 0x37a, 0x19fa); urtw_write16_m(sc, 0x37c, 0x00d0); urtw_write8_m(sc, 0x61, 0); urtw_write8_m(sc, 0x180, 0x0f); urtw_write8_m(sc, 0x183, 0x03); urtw_write8_m(sc, 0xda, 0x10); urtw_write8_m(sc, 0x24d, 0x08); urtw_write32_m(sc, URTW_HSSI_PARA, 0x0600321b); urtw_write16_m(sc, 0x1ec, 0x800); /* RX MAX SIZE */ fail: return (error); } static usb_error_t urtw_set_macaddr(struct urtw_softc *sc, const uint8_t *macaddr) { usb_error_t error; urtw_write32_m(sc, URTW_MAC0, ((const uint32_t *)macaddr)[0]); urtw_write16_m(sc, URTW_MAC4, ((const uint32_t *)macaddr)[1] & 0xffff); fail: return (error); } static usb_error_t urtw_adapter_start(struct urtw_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); const uint8_t *macaddr; usb_error_t error; error = urtw_reset(sc); if (error) goto fail; urtw_write8_m(sc, URTW_ADDR_MAGIC1, 0); urtw_write8_m(sc, URTW_GPIO, 0); /* for led */ urtw_write8_m(sc, URTW_ADDR_MAGIC1, 4); error = urtw_led_ctl(sc, URTW_LED_CTL_POWER_ON); if (error != 0) goto fail; error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; /* applying MAC address again. */ macaddr = vap ? vap->iv_myaddr : ic->ic_macaddr; urtw_set_macaddr(sc, macaddr); if (error) goto fail; error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; error = urtw_update_msr(sc); if (error) goto fail; urtw_write32_m(sc, URTW_INT_TIMEOUT, 0); urtw_write8_m(sc, URTW_WPA_CONFIG, 0); urtw_write8_m(sc, URTW_RATE_FALLBACK, URTW_RATE_FALLBACK_ENABLE | 0x1); error = urtw_set_rate(sc); if (error != 0) goto fail; error = sc->sc_rf_init(sc); if (error != 0) goto fail; if (sc->sc_rf_set_sens != NULL) sc->sc_rf_set_sens(sc, sc->sc_sens); /* XXX correct? to call write16 */ urtw_write16_m(sc, URTW_PSR, 1); urtw_write16_m(sc, URTW_ADDR_MAGIC2, 0x10); urtw_write8_m(sc, URTW_TALLY_SEL, 0x80); urtw_write8_m(sc, URTW_ADDR_MAGIC3, 0x60); /* XXX correct? to call write16 */ urtw_write16_m(sc, URTW_PSR, 0); urtw_write8_m(sc, URTW_ADDR_MAGIC1, 4); error = urtw_intr_enable(sc); if (error != 0) goto fail; fail: return (error); } static usb_error_t urtw_set_mode(struct urtw_softc *sc, uint32_t mode) { uint8_t data; usb_error_t error; urtw_read8_m(sc, URTW_EPROM_CMD, &data); data = (data & ~URTW_EPROM_CMD_MASK) | (mode << URTW_EPROM_CMD_SHIFT); data = data & ~(URTW_EPROM_CS | URTW_EPROM_CK); urtw_write8_m(sc, URTW_EPROM_CMD, data); fail: return (error); } static usb_error_t urtw_8187b_cmd_reset(struct urtw_softc *sc) { int i; uint8_t data8; usb_error_t error; /* XXX the code can be duplicate with urtw_reset(). */ urtw_read8_m(sc, URTW_CMD, &data8); data8 = (data8 & 0x2) | URTW_CMD_RST; urtw_write8_m(sc, URTW_CMD, data8); for (i = 0; i < 20; i++) { usb_pause_mtx(&sc->sc_mtx, 2); urtw_read8_m(sc, URTW_CMD, &data8); if (!(data8 & URTW_CMD_RST)) break; } if (i >= 20) { device_printf(sc->sc_dev, "reset timeout\n"); goto fail; } fail: return (error); } static usb_error_t urtw_do_request(struct urtw_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; URTW_ASSERT_LOCKED(sc); while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0) break; DPRINTF(sc, URTW_DEBUG_INIT, "Control request failed, %s (retrying)\n", usbd_errstr(err)); usb_pause_mtx(&sc->sc_mtx, hz / 100); } return (err); } static void urtw_stop(struct urtw_softc *sc) { uint8_t data8; usb_error_t error; URTW_ASSERT_LOCKED(sc); sc->sc_flags &= ~URTW_RUNNING; error = urtw_intr_disable(sc); if (error) goto fail; urtw_read8_m(sc, URTW_CMD, &data8); data8 &= ~(URTW_CMD_RX_ENABLE | URTW_CMD_TX_ENABLE); urtw_write8_m(sc, URTW_CMD, data8); error = sc->sc_rf_stop(sc); if (error != 0) goto fail; error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_read8_m(sc, URTW_CONFIG4, &data8); urtw_write8_m(sc, URTW_CONFIG4, data8 | URTW_CONFIG4_VCOOFF); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; fail: if (error) device_printf(sc->sc_dev, "failed to stop (%s)\n", usbd_errstr(error)); usb_callout_stop(&sc->sc_led_ch); callout_stop(&sc->sc_watchdog_ch); urtw_abort_xfers(sc); } static void urtw_abort_xfers(struct urtw_softc *sc) { int i, max; URTW_ASSERT_LOCKED(sc); max = (sc->sc_flags & URTW_RTL8187B) ? URTW_8187B_N_XFERS : URTW_8187L_N_XFERS; /* abort any pending transfers */ for (i = 0; i < max; i++) usbd_transfer_stop(sc->sc_xfer[i]); } static void urtw_parent(struct ieee80211com *ic) { struct urtw_softc *sc = ic->ic_softc; int startall = 0; URTW_LOCK(sc); if (sc->sc_flags & URTW_DETACHED) { URTW_UNLOCK(sc); return; } if (ic->ic_nrunning > 0) { if (sc->sc_flags & URTW_RUNNING) { if (ic->ic_promisc > 0 || ic->ic_allmulti > 0) urtw_set_multi(sc); } else { urtw_init(sc); startall = 1; } } else if (sc->sc_flags & URTW_RUNNING) urtw_stop(sc); URTW_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static int urtw_transmit(struct ieee80211com *ic, struct mbuf *m) { struct urtw_softc *sc = ic->ic_softc; int error; URTW_LOCK(sc); if ((sc->sc_flags & URTW_RUNNING) == 0) { URTW_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { URTW_UNLOCK(sc); return (error); } urtw_start(sc); URTW_UNLOCK(sc); return (0); } static void urtw_start(struct urtw_softc *sc) { struct urtw_data *bf; struct ieee80211_node *ni; struct mbuf *m; URTW_ASSERT_LOCKED(sc); if ((sc->sc_flags & URTW_RUNNING) == 0) return; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = urtw_getbuf(sc); if (bf == NULL) { mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; if (urtw_tx_start(sc, ni, m, bf, URTW_PRIORITY_NORMAL) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); ieee80211_free_node(ni); break; } sc->sc_txtimer = 5; callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); } } static int urtw_alloc_data_list(struct urtw_softc *sc, struct urtw_data data[], int ndata, int maxsz, void *dma_buf) { int i, error; for (i = 0; i < ndata; i++) { struct urtw_data *dp = &data[i]; dp->sc = sc; if (dma_buf == NULL) { dp->m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (dp->m == NULL) { device_printf(sc->sc_dev, "could not allocate rx mbuf\n"); error = ENOMEM; goto fail; } dp->buf = mtod(dp->m, uint8_t *); } else { dp->m = NULL; dp->buf = ((uint8_t *)dma_buf) + (i * maxsz); } dp->ni = NULL; } return (0); fail: urtw_free_data_list(sc, data, ndata, 1); return (error); } static int urtw_alloc_rx_data_list(struct urtw_softc *sc) { int error, i; error = urtw_alloc_data_list(sc, sc->sc_rx, URTW_RX_DATA_LIST_COUNT, MCLBYTES, NULL /* mbufs */); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < URTW_RX_DATA_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); return (0); } static int urtw_alloc_tx_data_list(struct urtw_softc *sc) { int error, i; error = urtw_alloc_data_list(sc, sc->sc_tx, URTW_TX_DATA_LIST_COUNT, URTW_TX_MAXSIZE, sc->sc_tx_dma_buf /* no mbufs */); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); for (i = 0; i < URTW_TX_DATA_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); return (0); } static int urtw_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct urtw_softc *sc = ic->ic_softc; struct urtw_data *bf; /* prevent management frames from being sent if we're not ready */ if (!(sc->sc_flags & URTW_RUNNING)) { m_freem(m); return ENETDOWN; } URTW_LOCK(sc); bf = urtw_getbuf(sc); if (bf == NULL) { m_freem(m); URTW_UNLOCK(sc); return (ENOBUFS); /* XXX */ } if (urtw_tx_start(sc, ni, m, bf, URTW_PRIORITY_LOW) != 0) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); URTW_UNLOCK(sc); return (EIO); } URTW_UNLOCK(sc); sc->sc_txtimer = 5; return (0); } static void urtw_scan_start(struct ieee80211com *ic) { /* XXX do nothing? */ } static void urtw_scan_end(struct ieee80211com *ic) { /* XXX do nothing? */ } static void urtw_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { uint8_t bands[IEEE80211_MODE_BYTES]; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0); } static void urtw_set_channel(struct ieee80211com *ic) { struct urtw_softc *sc = ic->ic_softc; uint32_t data, orig; usb_error_t error; /* * if the user set a channel explicitly using ifconfig(8) this function * can be called earlier than we're expected that in some cases the * initialization would be failed if setting a channel is called before * the init have done. */ if (!(sc->sc_flags & URTW_RUNNING)) return; if (sc->sc_curchan != NULL && sc->sc_curchan == ic->ic_curchan) return; URTW_LOCK(sc); /* * during changing th channel we need to temporarily be disable * TX. */ urtw_read32_m(sc, URTW_TX_CONF, &orig); data = orig & ~URTW_TX_LOOPBACK_MASK; urtw_write32_m(sc, URTW_TX_CONF, data | URTW_TX_LOOPBACK_MAC); error = sc->sc_rf_set_chan(sc, ieee80211_chan2ieee(ic, ic->ic_curchan)); if (error != 0) goto fail; usb_pause_mtx(&sc->sc_mtx, 10); urtw_write32_m(sc, URTW_TX_CONF, orig); urtw_write16_m(sc, URTW_ATIM_WND, 2); urtw_write16_m(sc, URTW_ATIM_TR_ITV, 100); urtw_write16_m(sc, URTW_BEACON_INTERVAL, 100); urtw_write16_m(sc, URTW_BEACON_INTERVAL_TIME, 100); fail: URTW_UNLOCK(sc); sc->sc_curchan = ic->ic_curchan; if (error != 0) device_printf(sc->sc_dev, "could not change the channel\n"); } static void urtw_update_promisc(struct ieee80211com *ic) { struct urtw_softc *sc = ic->ic_softc; URTW_LOCK(sc); if (sc->sc_flags & URTW_RUNNING) urtw_rx_setconf(sc); URTW_UNLOCK(sc); } static void urtw_update_mcast(struct ieee80211com *ic) { /* XXX do nothing? */ } static int urtw_tx_start(struct urtw_softc *sc, struct ieee80211_node *ni, struct mbuf *m0, struct urtw_data *data, int prior) { struct ieee80211_frame *wh = mtod(m0, struct ieee80211_frame *); struct ieee80211_key *k; const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct usb_xfer *rtl8187b_pipes[URTW_8187B_TXPIPE_MAX] = { sc->sc_xfer[URTW_8187B_BULK_TX_BE], sc->sc_xfer[URTW_8187B_BULK_TX_BK], sc->sc_xfer[URTW_8187B_BULK_TX_VI], sc->sc_xfer[URTW_8187B_BULK_TX_VO] }; struct usb_xfer *xfer; int dur = 0, rtsdur = 0, rtsenable = 0, ctsenable = 0, rate, type, pkttime = 0, txdur = 0, isshort = 0, xferlen, ismcast; uint16_t acktime, rtstime, ctstime; uint32_t flags; usb_error_t error; URTW_ASSERT_LOCKED(sc); ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; /* * Software crypto. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { device_printf(sc->sc_dev, "ieee80211_crypto_encap returns NULL.\n"); /* XXX we don't expect the fragmented frames */ m_freem(m0); return (ENOBUFS); } /* in case packet header moved, reset pointer */ wh = mtod(m0, struct ieee80211_frame *); } if (ieee80211_radiotap_active_vap(vap)) { struct urtw_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; ieee80211_radiotap_tx(vap, m0); } if (type == IEEE80211_FC0_TYPE_MGT || type == IEEE80211_FC0_TYPE_CTL || (m0->m_flags & M_EAPOL) != 0) { rate = tp->mgmtrate; } else { /* for data frames */ if (ismcast) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else rate = urtw_rtl2rate(sc->sc_currate); } sc->sc_stats.txrates[sc->sc_currate]++; if (ismcast) txdur = pkttime = urtw_compute_txtime(m0->m_pkthdr.len + IEEE80211_CRC_LEN, rate, 0, 0); else { acktime = urtw_compute_txtime(14, 2,0, 0); if ((m0->m_pkthdr.len + 4) > vap->iv_rtsthreshold) { rtsenable = 1; ctsenable = 0; rtstime = urtw_compute_txtime(URTW_ACKCTS_LEN, 2, 0, 0); ctstime = urtw_compute_txtime(14, 2, 0, 0); pkttime = urtw_compute_txtime(m0->m_pkthdr.len + IEEE80211_CRC_LEN, rate, 0, isshort); rtsdur = ctstime + pkttime + acktime + 3 * URTW_ASIFS_TIME; txdur = rtstime + rtsdur; } else { rtsenable = ctsenable = rtsdur = 0; pkttime = urtw_compute_txtime(m0->m_pkthdr.len + IEEE80211_CRC_LEN, rate, 0, isshort); txdur = pkttime + URTW_ASIFS_TIME + acktime; } if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) dur = urtw_compute_txtime(m0->m_pkthdr.len + IEEE80211_CRC_LEN, rate, 0, isshort) + 3 * URTW_ASIFS_TIME + 2 * acktime; else dur = URTW_ASIFS_TIME + acktime; } USETW(wh->i_dur, dur); xferlen = m0->m_pkthdr.len; xferlen += (sc->sc_flags & URTW_RTL8187B) ? (4 * 8) : (4 * 3); if ((0 == xferlen % 64) || (0 == xferlen % 512)) xferlen += 1; memset(data->buf, 0, URTW_TX_MAXSIZE); flags = m0->m_pkthdr.len & 0xfff; flags |= URTW_TX_FLAG_NO_ENC; if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) && (sc->sc_preamble_mode == URTW_PREAMBLE_MODE_SHORT) && (sc->sc_currate != 0)) flags |= URTW_TX_FLAG_SPLCP; if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) flags |= URTW_TX_FLAG_MOREFRAG; flags |= (sc->sc_currate & 0xf) << URTW_TX_FLAG_TXRATE_SHIFT; if (sc->sc_flags & URTW_RTL8187B) { struct urtw_8187b_txhdr *tx; tx = (struct urtw_8187b_txhdr *)data->buf; if (ctsenable) flags |= URTW_TX_FLAG_CTS; if (rtsenable) { flags |= URTW_TX_FLAG_RTS; flags |= URTW_RIDX_CCK5 << URTW_TX_FLAG_RTSRATE_SHIFT; tx->rtsdur = rtsdur; } tx->flag = htole32(flags); tx->txdur = txdur; if (type == IEEE80211_FC0_TYPE_MGT && (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) tx->retry = 1; else tx->retry = URTW_TX_MAXRETRY; m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(tx + 1)); } else { struct urtw_8187l_txhdr *tx; tx = (struct urtw_8187l_txhdr *)data->buf; if (rtsenable) { flags |= URTW_TX_FLAG_RTS; tx->rtsdur = rtsdur; } flags |= URTW_RIDX_CCK5 << URTW_TX_FLAG_RTSRATE_SHIFT; tx->flag = htole32(flags); tx->retry = 3; /* CW minimum */ tx->retry |= 7 << 4; /* CW maximum */ tx->retry |= URTW_TX_MAXRETRY << 8; /* retry limitation */ m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(tx + 1)); } data->buflen = xferlen; data->ni = ni; data->m = m0; if (sc->sc_flags & URTW_RTL8187B) { switch (type) { case IEEE80211_FC0_TYPE_CTL: case IEEE80211_FC0_TYPE_MGT: xfer = sc->sc_xfer[URTW_8187B_BULK_TX_EP12]; break; default: KASSERT(M_WME_GETAC(m0) < URTW_8187B_TXPIPE_MAX, ("unsupported WME pipe %d", M_WME_GETAC(m0))); xfer = rtl8187b_pipes[M_WME_GETAC(m0)]; break; } } else xfer = (prior == URTW_PRIORITY_LOW) ? sc->sc_xfer[URTW_8187L_BULK_TX_LOW] : sc->sc_xfer[URTW_8187L_BULK_TX_NORMAL]; STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); usbd_transfer_start(xfer); error = urtw_led_ctl(sc, URTW_LED_CTL_TX); if (error != 0) device_printf(sc->sc_dev, "could not control LED (%d)\n", error); return (0); } static int urtw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct urtw_softc *sc = ic->ic_softc; struct urtw_vap *uvp = URTW_VAP(vap); struct ieee80211_node *ni; usb_error_t error = 0; DPRINTF(sc, URTW_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); sc->sc_state = nstate; IEEE80211_UNLOCK(ic); URTW_LOCK(sc); usb_callout_stop(&sc->sc_led_ch); callout_stop(&sc->sc_watchdog_ch); switch (nstate) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: break; case IEEE80211_S_RUN: ni = ieee80211_ref_node(vap->iv_bss); /* setting bssid. */ urtw_write32_m(sc, URTW_BSSID, ((uint32_t *)ni->ni_bssid)[0]); urtw_write16_m(sc, URTW_BSSID + 4, ((uint16_t *)ni->ni_bssid)[2]); urtw_update_msr(sc); /* XXX maybe the below would be incorrect. */ urtw_write16_m(sc, URTW_ATIM_WND, 2); urtw_write16_m(sc, URTW_ATIM_TR_ITV, 100); urtw_write16_m(sc, URTW_BEACON_INTERVAL, 0x64); urtw_write16_m(sc, URTW_BEACON_INTERVAL_TIME, 100); error = urtw_led_ctl(sc, URTW_LED_CTL_LINK); if (error != 0) device_printf(sc->sc_dev, "could not control LED (%d)\n", error); ieee80211_free_node(ni); break; default: break; } fail: URTW_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } static void urtw_watchdog(void *arg) { struct urtw_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; if (sc->sc_txtimer > 0) { if (--sc->sc_txtimer == 0) { device_printf(sc->sc_dev, "device timeout\n"); counter_u64_add(ic->ic_oerrors, 1); ieee80211_restart_all(ic); return; } callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); } } static void urtw_set_multi(void *arg) { /* XXX don't know how to set a device. Lack of docs. */ } static usb_error_t urtw_set_rate(struct urtw_softc *sc) { int i, basic_rate, min_rr_rate, max_rr_rate; uint16_t data; usb_error_t error; basic_rate = URTW_RIDX_OFDM24; min_rr_rate = URTW_RIDX_OFDM6; max_rr_rate = URTW_RIDX_OFDM24; urtw_write8_m(sc, URTW_RESP_RATE, max_rr_rate << URTW_RESP_MAX_RATE_SHIFT | min_rr_rate << URTW_RESP_MIN_RATE_SHIFT); urtw_read16_m(sc, URTW_BRSR, &data); data &= ~URTW_BRSR_MBR_8185; for (i = 0; i <= basic_rate; i++) data |= (1 << i); urtw_write16_m(sc, URTW_BRSR, data); fail: return (error); } static uint16_t urtw_rtl2rate(uint32_t rate) { unsigned int i; for (i = 0; i < nitems(urtw_ratetable); i++) { if (rate == urtw_ratetable[i].val) return urtw_ratetable[i].reg; } return (0); } static usb_error_t urtw_update_msr(struct urtw_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint8_t data; usb_error_t error; urtw_read8_m(sc, URTW_MSR, &data); data &= ~URTW_MSR_LINK_MASK; if (sc->sc_state == IEEE80211_S_RUN) { switch (ic->ic_opmode) { case IEEE80211_M_STA: case IEEE80211_M_MONITOR: data |= URTW_MSR_LINK_STA; if (sc->sc_flags & URTW_RTL8187B) data |= URTW_MSR_LINK_ENEDCA; break; case IEEE80211_M_IBSS: data |= URTW_MSR_LINK_ADHOC; break; case IEEE80211_M_HOSTAP: data |= URTW_MSR_LINK_HOSTAP; break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unsupported operation mode 0x%x\n", ic->ic_opmode); error = USB_ERR_INVAL; goto fail; } } else data |= URTW_MSR_LINK_NONE; urtw_write8_m(sc, URTW_MSR, data); fail: return (error); } static usb_error_t urtw_read8_c(struct urtw_softc *sc, int val, uint8_t *data) { struct usb_device_request req; usb_error_t error; URTW_ASSERT_LOCKED(sc); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = URTW_8187_GETREGS_REQ; USETW(req.wValue, (val & 0xff) | 0xff00); USETW(req.wIndex, (val >> 8) & 0x3); USETW(req.wLength, sizeof(uint8_t)); error = urtw_do_request(sc, &req, data); return (error); } static usb_error_t urtw_read16_c(struct urtw_softc *sc, int val, uint16_t *data) { struct usb_device_request req; usb_error_t error; URTW_ASSERT_LOCKED(sc); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = URTW_8187_GETREGS_REQ; USETW(req.wValue, (val & 0xff) | 0xff00); USETW(req.wIndex, (val >> 8) & 0x3); USETW(req.wLength, sizeof(uint16_t)); error = urtw_do_request(sc, &req, data); return (error); } static usb_error_t urtw_read32_c(struct urtw_softc *sc, int val, uint32_t *data) { struct usb_device_request req; usb_error_t error; URTW_ASSERT_LOCKED(sc); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = URTW_8187_GETREGS_REQ; USETW(req.wValue, (val & 0xff) | 0xff00); USETW(req.wIndex, (val >> 8) & 0x3); USETW(req.wLength, sizeof(uint32_t)); error = urtw_do_request(sc, &req, data); return (error); } static usb_error_t urtw_write8_c(struct urtw_softc *sc, int val, uint8_t data) { struct usb_device_request req; URTW_ASSERT_LOCKED(sc); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = URTW_8187_SETREGS_REQ; USETW(req.wValue, (val & 0xff) | 0xff00); USETW(req.wIndex, (val >> 8) & 0x3); USETW(req.wLength, sizeof(uint8_t)); return (urtw_do_request(sc, &req, &data)); } static usb_error_t urtw_write16_c(struct urtw_softc *sc, int val, uint16_t data) { struct usb_device_request req; URTW_ASSERT_LOCKED(sc); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = URTW_8187_SETREGS_REQ; USETW(req.wValue, (val & 0xff) | 0xff00); USETW(req.wIndex, (val >> 8) & 0x3); USETW(req.wLength, sizeof(uint16_t)); return (urtw_do_request(sc, &req, &data)); } static usb_error_t urtw_write32_c(struct urtw_softc *sc, int val, uint32_t data) { struct usb_device_request req; URTW_ASSERT_LOCKED(sc); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = URTW_8187_SETREGS_REQ; USETW(req.wValue, (val & 0xff) | 0xff00); USETW(req.wIndex, (val >> 8) & 0x3); USETW(req.wLength, sizeof(uint32_t)); return (urtw_do_request(sc, &req, &data)); } static usb_error_t urtw_get_macaddr(struct urtw_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t data; usb_error_t error; error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR, &data); if (error != 0) goto fail; ic->ic_macaddr[0] = data & 0xff; ic->ic_macaddr[1] = (data & 0xff00) >> 8; error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 1, &data); if (error != 0) goto fail; ic->ic_macaddr[2] = data & 0xff; ic->ic_macaddr[3] = (data & 0xff00) >> 8; error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 2, &data); if (error != 0) goto fail; ic->ic_macaddr[4] = data & 0xff; ic->ic_macaddr[5] = (data & 0xff00) >> 8; fail: return (error); } static usb_error_t urtw_eprom_read32(struct urtw_softc *sc, uint32_t addr, uint32_t *data) { #define URTW_READCMD_LEN 3 int addrlen, i; int16_t addrstr[8], data16, readcmd[] = { 1, 1, 0 }; usb_error_t error; /* NB: make sure the buffer is initialized */ *data = 0; /* enable EPROM programming */ urtw_write8_m(sc, URTW_EPROM_CMD, URTW_EPROM_CMD_PROGRAM_MODE); DELAY(URTW_EPROM_DELAY); error = urtw_eprom_cs(sc, URTW_EPROM_ENABLE); if (error != 0) goto fail; error = urtw_eprom_ck(sc); if (error != 0) goto fail; error = urtw_eprom_sendbits(sc, readcmd, URTW_READCMD_LEN); if (error != 0) goto fail; if (sc->sc_epromtype == URTW_EEPROM_93C56) { addrlen = 8; addrstr[0] = addr & (1 << 7); addrstr[1] = addr & (1 << 6); addrstr[2] = addr & (1 << 5); addrstr[3] = addr & (1 << 4); addrstr[4] = addr & (1 << 3); addrstr[5] = addr & (1 << 2); addrstr[6] = addr & (1 << 1); addrstr[7] = addr & (1 << 0); } else { addrlen=6; addrstr[0] = addr & (1 << 5); addrstr[1] = addr & (1 << 4); addrstr[2] = addr & (1 << 3); addrstr[3] = addr & (1 << 2); addrstr[4] = addr & (1 << 1); addrstr[5] = addr & (1 << 0); } error = urtw_eprom_sendbits(sc, addrstr, addrlen); if (error != 0) goto fail; error = urtw_eprom_writebit(sc, 0); if (error != 0) goto fail; for (i = 0; i < 16; i++) { error = urtw_eprom_ck(sc); if (error != 0) goto fail; error = urtw_eprom_readbit(sc, &data16); if (error != 0) goto fail; (*data) |= (data16 << (15 - i)); } error = urtw_eprom_cs(sc, URTW_EPROM_DISABLE); if (error != 0) goto fail; error = urtw_eprom_ck(sc); if (error != 0) goto fail; /* now disable EPROM programming */ urtw_write8_m(sc, URTW_EPROM_CMD, URTW_EPROM_CMD_NORMAL_MODE); fail: return (error); #undef URTW_READCMD_LEN } static usb_error_t urtw_eprom_cs(struct urtw_softc *sc, int able) { uint8_t data; usb_error_t error; urtw_read8_m(sc, URTW_EPROM_CMD, &data); if (able == URTW_EPROM_ENABLE) urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_CS); else urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_CS); DELAY(URTW_EPROM_DELAY); fail: return (error); } static usb_error_t urtw_eprom_ck(struct urtw_softc *sc) { uint8_t data; usb_error_t error; /* masking */ urtw_read8_m(sc, URTW_EPROM_CMD, &data); urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_CK); DELAY(URTW_EPROM_DELAY); /* unmasking */ urtw_read8_m(sc, URTW_EPROM_CMD, &data); urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_CK); DELAY(URTW_EPROM_DELAY); fail: return (error); } static usb_error_t urtw_eprom_readbit(struct urtw_softc *sc, int16_t *data) { uint8_t data8; usb_error_t error; urtw_read8_m(sc, URTW_EPROM_CMD, &data8); *data = (data8 & URTW_EPROM_READBIT) ? 1 : 0; DELAY(URTW_EPROM_DELAY); fail: return (error); } static usb_error_t urtw_eprom_writebit(struct urtw_softc *sc, int16_t bit) { uint8_t data; usb_error_t error; urtw_read8_m(sc, URTW_EPROM_CMD, &data); if (bit != 0) urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_WRITEBIT); else urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_WRITEBIT); DELAY(URTW_EPROM_DELAY); fail: return (error); } static usb_error_t urtw_eprom_sendbits(struct urtw_softc *sc, int16_t *buf, int buflen) { int i = 0; usb_error_t error = 0; for (i = 0; i < buflen; i++) { error = urtw_eprom_writebit(sc, buf[i]); if (error != 0) goto fail; error = urtw_eprom_ck(sc); if (error != 0) goto fail; } fail: return (error); } static usb_error_t urtw_get_txpwr(struct urtw_softc *sc) { int i, j; uint32_t data; usb_error_t error; error = urtw_eprom_read32(sc, URTW_EPROM_TXPW_BASE, &data); if (error != 0) goto fail; sc->sc_txpwr_cck_base = data & 0xf; sc->sc_txpwr_ofdm_base = (data >> 4) & 0xf; for (i = 1, j = 0; i < 6; i += 2, j++) { error = urtw_eprom_read32(sc, URTW_EPROM_TXPW0 + j, &data); if (error != 0) goto fail; sc->sc_txpwr_cck[i] = data & 0xf; sc->sc_txpwr_cck[i + 1] = (data & 0xf00) >> 8; sc->sc_txpwr_ofdm[i] = (data & 0xf0) >> 4; sc->sc_txpwr_ofdm[i + 1] = (data & 0xf000) >> 12; } for (i = 1, j = 0; i < 4; i += 2, j++) { error = urtw_eprom_read32(sc, URTW_EPROM_TXPW1 + j, &data); if (error != 0) goto fail; sc->sc_txpwr_cck[i + 6] = data & 0xf; sc->sc_txpwr_cck[i + 6 + 1] = (data & 0xf00) >> 8; sc->sc_txpwr_ofdm[i + 6] = (data & 0xf0) >> 4; sc->sc_txpwr_ofdm[i + 6 + 1] = (data & 0xf000) >> 12; } if (sc->sc_flags & URTW_RTL8187B) { error = urtw_eprom_read32(sc, URTW_EPROM_TXPW2, &data); if (error != 0) goto fail; sc->sc_txpwr_cck[1 + 6 + 4] = data & 0xf; sc->sc_txpwr_ofdm[1 + 6 + 4] = (data & 0xf0) >> 4; error = urtw_eprom_read32(sc, 0x0a, &data); if (error != 0) goto fail; sc->sc_txpwr_cck[2 + 6 + 4] = data & 0xf; sc->sc_txpwr_ofdm[2 + 6 + 4] = (data & 0xf0) >> 4; error = urtw_eprom_read32(sc, 0x1c, &data); if (error != 0) goto fail; sc->sc_txpwr_cck[3 + 6 + 4] = data & 0xf; sc->sc_txpwr_cck[3 + 6 + 4 + 1] = (data & 0xf00) >> 8; sc->sc_txpwr_ofdm[3 + 6 + 4] = (data & 0xf0) >> 4; sc->sc_txpwr_ofdm[3 + 6 + 4 + 1] = (data & 0xf000) >> 12; } else { for (i = 1, j = 0; i < 4; i += 2, j++) { error = urtw_eprom_read32(sc, URTW_EPROM_TXPW2 + j, &data); if (error != 0) goto fail; sc->sc_txpwr_cck[i + 6 + 4] = data & 0xf; sc->sc_txpwr_cck[i + 6 + 4 + 1] = (data & 0xf00) >> 8; sc->sc_txpwr_ofdm[i + 6 + 4] = (data & 0xf0) >> 4; sc->sc_txpwr_ofdm[i + 6 + 4 + 1] = (data & 0xf000) >> 12; } } fail: return (error); } static usb_error_t urtw_get_rfchip(struct urtw_softc *sc) { int ret; uint8_t data8; uint32_t data; usb_error_t error; if (sc->sc_flags & URTW_RTL8187B) { urtw_read8_m(sc, 0xe1, &data8); switch (data8) { case 0: sc->sc_flags |= URTW_RTL8187B_REV_B; break; case 1: sc->sc_flags |= URTW_RTL8187B_REV_D; break; case 2: sc->sc_flags |= URTW_RTL8187B_REV_E; break; default: device_printf(sc->sc_dev, "unknown type: %#x\n", data8); sc->sc_flags |= URTW_RTL8187B_REV_B; break; } } else { urtw_read32_m(sc, URTW_TX_CONF, &data); switch (data & URTW_TX_HWMASK) { case URTW_TX_R8187vD_B: sc->sc_flags |= URTW_RTL8187B; break; case URTW_TX_R8187vD: break; default: device_printf(sc->sc_dev, "unknown RTL8187L type: %#x\n", data & URTW_TX_HWMASK); break; } } error = urtw_eprom_read32(sc, URTW_EPROM_RFCHIPID, &data); if (error != 0) goto fail; switch (data & 0xff) { case URTW_EPROM_RFCHIPID_RTL8225U: error = urtw_8225_isv2(sc, &ret); if (error != 0) goto fail; if (ret == 0) { sc->sc_rf_init = urtw_8225_rf_init; sc->sc_rf_set_sens = urtw_8225_rf_set_sens; sc->sc_rf_set_chan = urtw_8225_rf_set_chan; sc->sc_rf_stop = urtw_8225_rf_stop; } else { sc->sc_rf_init = urtw_8225v2_rf_init; sc->sc_rf_set_chan = urtw_8225v2_rf_set_chan; sc->sc_rf_stop = urtw_8225_rf_stop; } sc->sc_max_sens = URTW_8225_RF_MAX_SENS; sc->sc_sens = URTW_8225_RF_DEF_SENS; break; case URTW_EPROM_RFCHIPID_RTL8225Z2: sc->sc_rf_init = urtw_8225v2b_rf_init; sc->sc_rf_set_chan = urtw_8225v2b_rf_set_chan; sc->sc_max_sens = URTW_8225_RF_MAX_SENS; sc->sc_sens = URTW_8225_RF_DEF_SENS; sc->sc_rf_stop = urtw_8225_rf_stop; break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unsupported RF chip %d\n", data & 0xff); error = USB_ERR_INVAL; goto fail; } device_printf(sc->sc_dev, "%s rf %s hwrev %s\n", (sc->sc_flags & URTW_RTL8187B) ? "rtl8187b" : "rtl8187l", ((data & 0xff) == URTW_EPROM_RFCHIPID_RTL8225U) ? "rtl8225u" : "rtl8225z2", (sc->sc_flags & URTW_RTL8187B) ? ((data8 == 0) ? "b" : (data8 == 1) ? "d" : "e") : "none"); fail: return (error); } static usb_error_t urtw_led_init(struct urtw_softc *sc) { uint32_t rev; usb_error_t error; urtw_read8_m(sc, URTW_PSR, &sc->sc_psr); error = urtw_eprom_read32(sc, URTW_EPROM_SWREV, &rev); if (error != 0) goto fail; switch (rev & URTW_EPROM_CID_MASK) { case URTW_EPROM_CID_ALPHA0: sc->sc_strategy = URTW_SW_LED_MODE1; break; case URTW_EPROM_CID_SERCOMM_PS: sc->sc_strategy = URTW_SW_LED_MODE3; break; case URTW_EPROM_CID_HW_LED: sc->sc_strategy = URTW_HW_LED; break; case URTW_EPROM_CID_RSVD0: case URTW_EPROM_CID_RSVD1: default: sc->sc_strategy = URTW_SW_LED_MODE0; break; } sc->sc_gpio_ledpin = URTW_LED_PIN_GPIO0; fail: return (error); } static usb_error_t urtw_8225_rf_init(struct urtw_softc *sc) { unsigned int i; uint16_t data; usb_error_t error; error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); if (error) goto fail; error = urtw_8225_usb_init(sc); if (error) goto fail; urtw_write32_m(sc, URTW_RF_TIMING, 0x000a8008); urtw_read16_m(sc, URTW_BRSR, &data); /* XXX ??? */ urtw_write16_m(sc, URTW_BRSR, 0xffff); urtw_write32_m(sc, URTW_RF_PARA, 0x100044); error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_write8_m(sc, URTW_CONFIG3, 0x44); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; error = urtw_8185_rf_pins_enable(sc); if (error) goto fail; usb_pause_mtx(&sc->sc_mtx, 1000); for (i = 0; i < nitems(urtw_8225_rf_part1); i++) { urtw_8225_write(sc, urtw_8225_rf_part1[i].reg, urtw_8225_rf_part1[i].val); usb_pause_mtx(&sc->sc_mtx, 1); } usb_pause_mtx(&sc->sc_mtx, 100); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); usb_pause_mtx(&sc->sc_mtx, 200); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); usb_pause_mtx(&sc->sc_mtx, 200); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC3); for (i = 0; i < 95; i++) { urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, urtw_8225_rxgain[i]); } urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC4); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC5); for (i = 0; i < 128; i++) { urtw_8187_write_phy_ofdm(sc, 0xb, urtw_8225_agc[i]); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80); usb_pause_mtx(&sc->sc_mtx, 1); } for (i = 0; i < nitems(urtw_8225_rf_part2); i++) { urtw_8187_write_phy_ofdm(sc, urtw_8225_rf_part2[i].reg, urtw_8225_rf_part2[i].val); usb_pause_mtx(&sc->sc_mtx, 1); } error = urtw_8225_setgain(sc, 4); if (error) goto fail; for (i = 0; i < nitems(urtw_8225_rf_part3); i++) { urtw_8187_write_phy_cck(sc, urtw_8225_rf_part3[i].reg, urtw_8225_rf_part3[i].val); usb_pause_mtx(&sc->sc_mtx, 1); } urtw_write8_m(sc, URTW_TESTR, 0x0d); error = urtw_8225_set_txpwrlvl(sc, 1); if (error) goto fail; urtw_8187_write_phy_cck(sc, 0x10, 0x9b); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8187_write_phy_ofdm(sc, 0x26, 0x90); usb_pause_mtx(&sc->sc_mtx, 1); /* TX ant A, 0x0 for B */ error = urtw_8185_tx_antenna(sc, 0x3); if (error) goto fail; urtw_write32_m(sc, URTW_HSSI_PARA, 0x3dc00002); error = urtw_8225_rf_set_chan(sc, 1); fail: return (error); } static usb_error_t urtw_8185_rf_pins_enable(struct urtw_softc *sc) { usb_error_t error = 0; urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1ff7); fail: return (error); } static usb_error_t urtw_8185_tx_antenna(struct urtw_softc *sc, uint8_t ant) { usb_error_t error; urtw_write8_m(sc, URTW_TX_ANTENNA, ant); usb_pause_mtx(&sc->sc_mtx, 1); fail: return (error); } static usb_error_t urtw_8187_write_phy_ofdm_c(struct urtw_softc *sc, uint8_t addr, uint32_t data) { data = data & 0xff; return urtw_8187_write_phy(sc, addr, data); } static usb_error_t urtw_8187_write_phy_cck_c(struct urtw_softc *sc, uint8_t addr, uint32_t data) { data = data & 0xff; return urtw_8187_write_phy(sc, addr, data | 0x10000); } static usb_error_t urtw_8187_write_phy(struct urtw_softc *sc, uint8_t addr, uint32_t data) { uint32_t phyw; usb_error_t error; phyw = ((data << 8) | (addr | 0x80)); urtw_write8_m(sc, URTW_PHY_MAGIC4, ((phyw & 0xff000000) >> 24)); urtw_write8_m(sc, URTW_PHY_MAGIC3, ((phyw & 0x00ff0000) >> 16)); urtw_write8_m(sc, URTW_PHY_MAGIC2, ((phyw & 0x0000ff00) >> 8)); urtw_write8_m(sc, URTW_PHY_MAGIC1, ((phyw & 0x000000ff))); usb_pause_mtx(&sc->sc_mtx, 1); fail: return (error); } static usb_error_t urtw_8225_setgain(struct urtw_softc *sc, int16_t gain) { usb_error_t error; urtw_8187_write_phy_ofdm(sc, 0x0d, urtw_8225_gain[gain * 4]); urtw_8187_write_phy_ofdm(sc, 0x1b, urtw_8225_gain[gain * 4 + 2]); urtw_8187_write_phy_ofdm(sc, 0x1d, urtw_8225_gain[gain * 4 + 3]); urtw_8187_write_phy_ofdm(sc, 0x23, urtw_8225_gain[gain * 4 + 1]); fail: return (error); } static usb_error_t urtw_8225_usb_init(struct urtw_softc *sc) { uint8_t data; usb_error_t error; urtw_write8_m(sc, URTW_RF_PINS_SELECT + 1, 0); urtw_write8_m(sc, URTW_GPIO, 0); error = urtw_read8e(sc, 0x53, &data); if (error) goto fail; error = urtw_write8e(sc, 0x53, data | (1 << 7)); if (error) goto fail; urtw_write8_m(sc, URTW_RF_PINS_SELECT + 1, 4); urtw_write8_m(sc, URTW_GPIO, 0x20); urtw_write8_m(sc, URTW_GP_ENABLE, 0); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, 0x80); urtw_write16_m(sc, URTW_RF_PINS_SELECT, 0x80); urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x80); usb_pause_mtx(&sc->sc_mtx, 500); fail: return (error); } static usb_error_t urtw_8225_write_c(struct urtw_softc *sc, uint8_t addr, uint16_t data) { uint16_t d80, d82, d84; usb_error_t error; urtw_read16_m(sc, URTW_RF_PINS_OUTPUT, &d80); d80 &= URTW_RF_PINS_MAGIC1; urtw_read16_m(sc, URTW_RF_PINS_ENABLE, &d82); urtw_read16_m(sc, URTW_RF_PINS_SELECT, &d84); d84 &= URTW_RF_PINS_MAGIC2; urtw_write16_m(sc, URTW_RF_PINS_ENABLE, d82 | URTW_RF_PINS_MAGIC3); urtw_write16_m(sc, URTW_RF_PINS_SELECT, d84 | URTW_RF_PINS_MAGIC3); DELAY(10); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80); DELAY(10); error = urtw_8225_write_s16(sc, addr, 0x8225, &data); if (error != 0) goto fail; urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); DELAY(10); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); urtw_write16_m(sc, URTW_RF_PINS_SELECT, d84); usb_pause_mtx(&sc->sc_mtx, 2); fail: return (error); } static usb_error_t urtw_8225_write_s16(struct urtw_softc *sc, uint8_t addr, int index, uint16_t *data) { uint8_t buf[2]; uint16_t data16; struct usb_device_request req; usb_error_t error = 0; data16 = *data; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = URTW_8187_SETREGS_REQ; USETW(req.wValue, addr); USETW(req.wIndex, index); USETW(req.wLength, sizeof(uint16_t)); buf[0] = (data16 & 0x00ff); buf[1] = (data16 & 0xff00) >> 8; error = urtw_do_request(sc, &req, buf); return (error); } static usb_error_t urtw_8225_rf_set_chan(struct urtw_softc *sc, int chan) { usb_error_t error; error = urtw_8225_set_txpwrlvl(sc, chan); if (error) goto fail; urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); usb_pause_mtx(&sc->sc_mtx, 10); fail: return (error); } static usb_error_t urtw_8225_rf_set_sens(struct urtw_softc *sc, int sens) { usb_error_t error; if (sens < 0 || sens > 6) return -1; if (sens > 4) urtw_8225_write(sc, URTW_8225_ADDR_C_MAGIC, URTW_8225_ADDR_C_DATA_MAGIC1); else urtw_8225_write(sc, URTW_8225_ADDR_C_MAGIC, URTW_8225_ADDR_C_DATA_MAGIC2); sens = 6 - sens; error = urtw_8225_setgain(sc, sens); if (error) goto fail; urtw_8187_write_phy_cck(sc, 0x41, urtw_8225_threshold[sens]); fail: return (error); } static usb_error_t urtw_8225_set_txpwrlvl(struct urtw_softc *sc, int chan) { int i, idx, set; uint8_t *cck_pwltable; uint8_t cck_pwrlvl_max, ofdm_pwrlvl_min, ofdm_pwrlvl_max; uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; usb_error_t error; cck_pwrlvl_max = 11; ofdm_pwrlvl_max = 25; /* 12 -> 25 */ ofdm_pwrlvl_min = 10; /* CCK power setting */ cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? cck_pwrlvl_max : cck_pwrlvl; idx = cck_pwrlvl % 6; set = cck_pwrlvl / 6; cck_pwltable = (chan == 14) ? urtw_8225_txpwr_cck_ch14 : urtw_8225_txpwr_cck; urtw_write8_m(sc, URTW_TX_GAIN_CCK, urtw_8225_tx_gain_cck_ofdm[set] >> 1); for (i = 0; i < 8; i++) { urtw_8187_write_phy_cck(sc, 0x44 + i, cck_pwltable[idx * 8 + i]); } usb_pause_mtx(&sc->sc_mtx, 1); /* OFDM power setting */ ofdm_pwrlvl = (ofdm_pwrlvl > (ofdm_pwrlvl_max - ofdm_pwrlvl_min)) ? ofdm_pwrlvl_max : ofdm_pwrlvl + ofdm_pwrlvl_min; ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; idx = ofdm_pwrlvl % 6; set = ofdm_pwrlvl / 6; error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); if (error) goto fail; urtw_8187_write_phy_ofdm(sc, 2, 0x42); urtw_8187_write_phy_ofdm(sc, 6, 0); urtw_8187_write_phy_ofdm(sc, 8, 0); urtw_write8_m(sc, URTW_TX_GAIN_OFDM, urtw_8225_tx_gain_cck_ofdm[set] >> 1); urtw_8187_write_phy_ofdm(sc, 0x5, urtw_8225_txpwr_ofdm[idx]); urtw_8187_write_phy_ofdm(sc, 0x7, urtw_8225_txpwr_ofdm[idx]); usb_pause_mtx(&sc->sc_mtx, 1); fail: return (error); } static usb_error_t urtw_8225_rf_stop(struct urtw_softc *sc) { uint8_t data; usb_error_t error; urtw_8225_write(sc, 0x4, 0x1f); error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_read8_m(sc, URTW_CONFIG3, &data); urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); if (sc->sc_flags & URTW_RTL8187B) { urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8187B_8225_ANAPARAM2_OFF); urtw_write32_m(sc, URTW_ANAPARAM, URTW_8187B_8225_ANAPARAM_OFF); urtw_write32_m(sc, URTW_ANAPARAM3, URTW_8187B_8225_ANAPARAM3_OFF); } else { urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8225_ANAPARAM2_OFF); urtw_write32_m(sc, URTW_ANAPARAM, URTW_8225_ANAPARAM_OFF); } urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; fail: return (error); } static usb_error_t urtw_8225v2_rf_init(struct urtw_softc *sc) { unsigned int i; uint16_t data; uint32_t data32; usb_error_t error; error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); if (error) goto fail; error = urtw_8225_usb_init(sc); if (error) goto fail; urtw_write32_m(sc, URTW_RF_TIMING, 0x000a8008); urtw_read16_m(sc, URTW_BRSR, &data); /* XXX ??? */ urtw_write16_m(sc, URTW_BRSR, 0xffff); urtw_write32_m(sc, URTW_RF_PARA, 0x100044); error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_write8_m(sc, URTW_CONFIG3, 0x44); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; error = urtw_8185_rf_pins_enable(sc); if (error) goto fail; usb_pause_mtx(&sc->sc_mtx, 500); for (i = 0; i < nitems(urtw_8225v2_rf_part1); i++) { urtw_8225_write(sc, urtw_8225v2_rf_part1[i].reg, urtw_8225v2_rf_part1[i].val); } usb_pause_mtx(&sc->sc_mtx, 50); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC1); for (i = 0; i < 95; i++) { urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, urtw_8225v2_rxgain[i]); } urtw_8225_write(sc, URTW_8225_ADDR_3_MAGIC, URTW_8225_ADDR_3_DATA_MAGIC1); urtw_8225_write(sc, URTW_8225_ADDR_5_MAGIC, URTW_8225_ADDR_5_DATA_MAGIC1); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC2); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); usb_pause_mtx(&sc->sc_mtx, 100); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); usb_pause_mtx(&sc->sc_mtx, 100); error = urtw_8225_read(sc, URTW_8225_ADDR_6_MAGIC, &data32); if (error != 0) goto fail; if (data32 != URTW_8225_ADDR_6_DATA_MAGIC1) device_printf(sc->sc_dev, "expect 0xe6!! (0x%x)\n", data32); if (!(data32 & URTW_8225_ADDR_6_DATA_MAGIC2)) { urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); usb_pause_mtx(&sc->sc_mtx, 100); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); usb_pause_mtx(&sc->sc_mtx, 50); error = urtw_8225_read(sc, URTW_8225_ADDR_6_MAGIC, &data32); if (error != 0) goto fail; if (!(data32 & URTW_8225_ADDR_6_DATA_MAGIC2)) device_printf(sc->sc_dev, "RF calibration failed\n"); } usb_pause_mtx(&sc->sc_mtx, 100); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC6); for (i = 0; i < 128; i++) { urtw_8187_write_phy_ofdm(sc, 0xb, urtw_8225_agc[i]); urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80); } for (i = 0; i < nitems(urtw_8225v2_rf_part2); i++) { urtw_8187_write_phy_ofdm(sc, urtw_8225v2_rf_part2[i].reg, urtw_8225v2_rf_part2[i].val); } error = urtw_8225v2_setgain(sc, 4); if (error) goto fail; for (i = 0; i < nitems(urtw_8225v2_rf_part3); i++) { urtw_8187_write_phy_cck(sc, urtw_8225v2_rf_part3[i].reg, urtw_8225v2_rf_part3[i].val); } urtw_write8_m(sc, URTW_TESTR, 0x0d); error = urtw_8225v2_set_txpwrlvl(sc, 1); if (error) goto fail; urtw_8187_write_phy_cck(sc, 0x10, 0x9b); urtw_8187_write_phy_ofdm(sc, 0x26, 0x90); /* TX ant A, 0x0 for B */ error = urtw_8185_tx_antenna(sc, 0x3); if (error) goto fail; urtw_write32_m(sc, URTW_HSSI_PARA, 0x3dc00002); error = urtw_8225_rf_set_chan(sc, 1); fail: return (error); } static usb_error_t urtw_8225v2_rf_set_chan(struct urtw_softc *sc, int chan) { usb_error_t error; error = urtw_8225v2_set_txpwrlvl(sc, chan); if (error) goto fail; urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); usb_pause_mtx(&sc->sc_mtx, 10); fail: return (error); } static usb_error_t urtw_8225_read(struct urtw_softc *sc, uint8_t addr, uint32_t *data) { int i; int16_t bit; uint8_t rlen = 12, wlen = 6; uint16_t o1, o2, o3, tmp; uint32_t d2w = ((uint32_t)(addr & 0x1f)) << 27; uint32_t mask = 0x80000000, value = 0; usb_error_t error; urtw_read16_m(sc, URTW_RF_PINS_OUTPUT, &o1); urtw_read16_m(sc, URTW_RF_PINS_ENABLE, &o2); urtw_read16_m(sc, URTW_RF_PINS_SELECT, &o3); urtw_write16_m(sc, URTW_RF_PINS_ENABLE, o2 | URTW_RF_PINS_MAGIC4); urtw_write16_m(sc, URTW_RF_PINS_SELECT, o3 | URTW_RF_PINS_MAGIC4); o1 &= ~URTW_RF_PINS_MAGIC4; urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_EN); DELAY(5); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1); DELAY(5); for (i = 0; i < (wlen / 2); i++, mask = mask >> 1) { bit = ((d2w & mask) != 0) ? 1 : 0; urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_CLK); DELAY(2); mask = mask >> 1; if (i == 2) break; bit = ((d2w & mask) != 0) ? 1 : 0; urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1); DELAY(1); } urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_RW); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW); DELAY(2); mask = 0x800; for (i = 0; i < rlen; i++, mask = mask >> 1) { urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); DELAY(2); urtw_read16_m(sc, URTW_RF_PINS_INPUT, &tmp); value |= ((tmp & URTW_BB_HOST_BANG_CLK) ? mask : 0); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW); DELAY(2); } urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_EN | URTW_BB_HOST_BANG_RW); DELAY(2); urtw_write16_m(sc, URTW_RF_PINS_ENABLE, o2); urtw_write16_m(sc, URTW_RF_PINS_SELECT, o3); urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, URTW_RF_PINS_OUTPUT_MAGIC1); if (data != NULL) *data = value; fail: return (error); } static usb_error_t urtw_8225v2_set_txpwrlvl(struct urtw_softc *sc, int chan) { int i; uint8_t *cck_pwrtable; uint8_t cck_pwrlvl_max = 15, ofdm_pwrlvl_max = 25, ofdm_pwrlvl_min = 10; uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; usb_error_t error; /* CCK power setting */ cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? cck_pwrlvl_max : cck_pwrlvl; cck_pwrlvl += sc->sc_txpwr_cck_base; cck_pwrlvl = (cck_pwrlvl > 35) ? 35 : cck_pwrlvl; cck_pwrtable = (chan == 14) ? urtw_8225v2_txpwr_cck_ch14 : urtw_8225v2_txpwr_cck; for (i = 0; i < 8; i++) urtw_8187_write_phy_cck(sc, 0x44 + i, cck_pwrtable[i]); urtw_write8_m(sc, URTW_TX_GAIN_CCK, urtw_8225v2_tx_gain_cck_ofdm[cck_pwrlvl]); usb_pause_mtx(&sc->sc_mtx, 1); /* OFDM power setting */ ofdm_pwrlvl = (ofdm_pwrlvl > (ofdm_pwrlvl_max - ofdm_pwrlvl_min)) ? ofdm_pwrlvl_max : ofdm_pwrlvl + ofdm_pwrlvl_min; ofdm_pwrlvl += sc->sc_txpwr_ofdm_base; ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); if (error) goto fail; urtw_8187_write_phy_ofdm(sc, 2, 0x42); urtw_8187_write_phy_ofdm(sc, 5, 0x0); urtw_8187_write_phy_ofdm(sc, 6, 0x40); urtw_8187_write_phy_ofdm(sc, 7, 0x0); urtw_8187_write_phy_ofdm(sc, 8, 0x40); urtw_write8_m(sc, URTW_TX_GAIN_OFDM, urtw_8225v2_tx_gain_cck_ofdm[ofdm_pwrlvl]); usb_pause_mtx(&sc->sc_mtx, 1); fail: return (error); } static usb_error_t urtw_8225v2_setgain(struct urtw_softc *sc, int16_t gain) { uint8_t *gainp; usb_error_t error; /* XXX for A? */ gainp = urtw_8225v2_gain_bg; urtw_8187_write_phy_ofdm(sc, 0x0d, gainp[gain * 3]); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8187_write_phy_ofdm(sc, 0x1b, gainp[gain * 3 + 1]); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8187_write_phy_ofdm(sc, 0x1d, gainp[gain * 3 + 2]); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8187_write_phy_ofdm(sc, 0x21, 0x17); usb_pause_mtx(&sc->sc_mtx, 1); fail: return (error); } static usb_error_t urtw_8225_isv2(struct urtw_softc *sc, int *ret) { uint32_t data; usb_error_t error; *ret = 1; urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, URTW_RF_PINS_MAGIC5); urtw_write16_m(sc, URTW_RF_PINS_SELECT, URTW_RF_PINS_MAGIC5); urtw_write16_m(sc, URTW_RF_PINS_ENABLE, URTW_RF_PINS_MAGIC5); usb_pause_mtx(&sc->sc_mtx, 500); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC1); error = urtw_8225_read(sc, URTW_8225_ADDR_8_MAGIC, &data); if (error != 0) goto fail; if (data != URTW_8225_ADDR_8_DATA_MAGIC1) *ret = 0; else { error = urtw_8225_read(sc, URTW_8225_ADDR_9_MAGIC, &data); if (error != 0) goto fail; if (data != URTW_8225_ADDR_9_DATA_MAGIC1) *ret = 0; } urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC2); fail: return (error); } static usb_error_t urtw_8225v2b_rf_init(struct urtw_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); const uint8_t *macaddr; unsigned int i; uint8_t data8; usb_error_t error; error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; /* * initialize extra registers on 8187 */ urtw_write16_m(sc, URTW_BRSR_8187B, 0xfff); /* retry limit */ urtw_read8_m(sc, URTW_CW_CONF, &data8); data8 |= URTW_CW_CONF_PERPACKET_RETRY; urtw_write8_m(sc, URTW_CW_CONF, data8); /* TX AGC */ urtw_read8_m(sc, URTW_TX_AGC_CTL, &data8); data8 |= URTW_TX_AGC_CTL_PERPACKET_GAIN; urtw_write8_m(sc, URTW_TX_AGC_CTL, data8); /* Auto Rate Fallback Control */ #define URTW_ARFR 0x1e0 urtw_write16_m(sc, URTW_ARFR, 0xfff); urtw_read8_m(sc, URTW_RATE_FALLBACK, &data8); urtw_write8_m(sc, URTW_RATE_FALLBACK, data8 | URTW_RATE_FALLBACK_ENABLE); urtw_read8_m(sc, URTW_MSR, &data8); urtw_write8_m(sc, URTW_MSR, data8 & 0xf3); urtw_read8_m(sc, URTW_MSR, &data8); urtw_write8_m(sc, URTW_MSR, data8 | URTW_MSR_LINK_ENEDCA); urtw_write8_m(sc, URTW_ACM_CONTROL, sc->sc_acmctl); urtw_write16_m(sc, URTW_ATIM_WND, 2); urtw_write16_m(sc, URTW_BEACON_INTERVAL, 100); #define URTW_FEMR_FOR_8187B 0x1d4 urtw_write16_m(sc, URTW_FEMR_FOR_8187B, 0xffff); /* led type */ urtw_read8_m(sc, URTW_CONFIG1, &data8); data8 = (data8 & 0x3f) | 0x80; urtw_write8_m(sc, URTW_CONFIG1, data8); /* applying MAC address again. */ macaddr = vap ? vap->iv_myaddr : ic->ic_macaddr; error = urtw_set_macaddr(sc, macaddr); if (error) goto fail; error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; urtw_write8_m(sc, URTW_WPA_CONFIG, 0); /* * MAC configuration */ for (i = 0; i < nitems(urtw_8225v2b_rf_part1); i++) urtw_write8_m(sc, urtw_8225v2b_rf_part1[i].reg, urtw_8225v2b_rf_part1[i].val); urtw_write16_m(sc, URTW_TID_AC_MAP, 0xfa50); urtw_write16_m(sc, URTW_INT_MIG, 0x0000); urtw_write32_m(sc, 0x1f0, 0); urtw_write32_m(sc, 0x1f4, 0); urtw_write8_m(sc, 0x1f8, 0); urtw_write32_m(sc, URTW_RF_TIMING, 0x4001); #define URTW_RFSW_CTRL 0x272 urtw_write16_m(sc, URTW_RFSW_CTRL, 0x569a); /* * initialize PHY */ error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_read8_m(sc, URTW_CONFIG3, &data8); urtw_write8_m(sc, URTW_CONFIG3, data8 | URTW_CONFIG3_ANAPARAM_WRITE); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; /* setup RFE initial timing */ urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, 0x0480); urtw_write16_m(sc, URTW_RF_PINS_SELECT, 0x2488); urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1fff); usb_pause_mtx(&sc->sc_mtx, 1100); for (i = 0; i < nitems(urtw_8225v2b_rf_part0); i++) { urtw_8225_write(sc, urtw_8225v2b_rf_part0[i].reg, urtw_8225v2b_rf_part0[i].val); usb_pause_mtx(&sc->sc_mtx, 1); } urtw_8225_write(sc, 0x00, 0x01b7); for (i = 0; i < 95; i++) { urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, urtw_8225v2b_rxgain[i]); usb_pause_mtx(&sc->sc_mtx, 1); } urtw_8225_write(sc, URTW_8225_ADDR_3_MAGIC, 0x080); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8225_write(sc, URTW_8225_ADDR_5_MAGIC, 0x004); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, 0x0b7); usb_pause_mtx(&sc->sc_mtx, 1); usb_pause_mtx(&sc->sc_mtx, 3000); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, 0xc4d); usb_pause_mtx(&sc->sc_mtx, 2000); urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, 0x44d); usb_pause_mtx(&sc->sc_mtx, 1); urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, 0x2bf); usb_pause_mtx(&sc->sc_mtx, 1); urtw_write8_m(sc, URTW_TX_GAIN_CCK, 0x03); urtw_write8_m(sc, URTW_TX_GAIN_OFDM, 0x07); urtw_write8_m(sc, URTW_TX_ANTENNA, 0x03); urtw_8187_write_phy_ofdm(sc, 0x80, 0x12); for (i = 0; i < 128; i++) { uint32_t addr, data; data = (urtw_8225z2_agc[i] << 8) | 0x0000008f; addr = ((i + 0x80) << 8) | 0x0000008e; urtw_8187_write_phy_ofdm(sc, data & 0x7f, (data >> 8) & 0xff); urtw_8187_write_phy_ofdm(sc, addr & 0x7f, (addr >> 8) & 0xff); urtw_8187_write_phy_ofdm(sc, 0x0e, 0x00); } urtw_8187_write_phy_ofdm(sc, 0x80, 0x10); for (i = 0; i < nitems(urtw_8225v2b_rf_part2); i++) urtw_8187_write_phy_ofdm(sc, i, urtw_8225v2b_rf_part2[i].val); urtw_write32_m(sc, URTW_8187B_AC_VO, (7 << 12) | (3 << 8) | 0x1c); urtw_write32_m(sc, URTW_8187B_AC_VI, (7 << 12) | (3 << 8) | 0x1c); urtw_write32_m(sc, URTW_8187B_AC_BE, (7 << 12) | (3 << 8) | 0x1c); urtw_write32_m(sc, URTW_8187B_AC_BK, (7 << 12) | (3 << 8) | 0x1c); urtw_8187_write_phy_ofdm(sc, 0x97, 0x46); urtw_8187_write_phy_ofdm(sc, 0xa4, 0xb6); urtw_8187_write_phy_ofdm(sc, 0x85, 0xfc); urtw_8187_write_phy_cck(sc, 0xc1, 0x88); fail: return (error); } static usb_error_t urtw_8225v2b_rf_set_chan(struct urtw_softc *sc, int chan) { usb_error_t error; error = urtw_8225v2b_set_txpwrlvl(sc, chan); if (error) goto fail; urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); usb_pause_mtx(&sc->sc_mtx, 10); fail: return (error); } static usb_error_t urtw_8225v2b_set_txpwrlvl(struct urtw_softc *sc, int chan) { int i; uint8_t *cck_pwrtable; uint8_t cck_pwrlvl_max = 15; uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; usb_error_t error; /* CCK power setting */ cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? ((sc->sc_flags & URTW_RTL8187B_REV_B) ? cck_pwrlvl_max : 22) : (cck_pwrlvl + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 0 : 7)); cck_pwrlvl += sc->sc_txpwr_cck_base; cck_pwrlvl = (cck_pwrlvl > 35) ? 35 : cck_pwrlvl; cck_pwrtable = (chan == 14) ? urtw_8225v2b_txpwr_cck_ch14 : urtw_8225v2b_txpwr_cck; if (sc->sc_flags & URTW_RTL8187B_REV_B) cck_pwrtable += (cck_pwrlvl <= 6) ? 0 : ((cck_pwrlvl <= 11) ? 8 : 16); else cck_pwrtable += (cck_pwrlvl <= 5) ? 0 : ((cck_pwrlvl <= 11) ? 8 : ((cck_pwrlvl <= 17) ? 16 : 24)); for (i = 0; i < 8; i++) urtw_8187_write_phy_cck(sc, 0x44 + i, cck_pwrtable[i]); urtw_write8_m(sc, URTW_TX_GAIN_CCK, urtw_8225v2_tx_gain_cck_ofdm[cck_pwrlvl] << 1); usb_pause_mtx(&sc->sc_mtx, 1); /* OFDM power setting */ ofdm_pwrlvl = (ofdm_pwrlvl > 15) ? ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 17 : 25) : (ofdm_pwrlvl + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 2 : 10)); ofdm_pwrlvl += sc->sc_txpwr_ofdm_base; ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; urtw_write8_m(sc, URTW_TX_GAIN_OFDM, urtw_8225v2_tx_gain_cck_ofdm[ofdm_pwrlvl] << 1); if (sc->sc_flags & URTW_RTL8187B_REV_B) { if (ofdm_pwrlvl <= 11) { urtw_8187_write_phy_ofdm(sc, 0x87, 0x60); urtw_8187_write_phy_ofdm(sc, 0x89, 0x60); } else { urtw_8187_write_phy_ofdm(sc, 0x87, 0x5c); urtw_8187_write_phy_ofdm(sc, 0x89, 0x5c); } } else { if (ofdm_pwrlvl <= 11) { urtw_8187_write_phy_ofdm(sc, 0x87, 0x5c); urtw_8187_write_phy_ofdm(sc, 0x89, 0x5c); } else if (ofdm_pwrlvl <= 17) { urtw_8187_write_phy_ofdm(sc, 0x87, 0x54); urtw_8187_write_phy_ofdm(sc, 0x89, 0x54); } else { urtw_8187_write_phy_ofdm(sc, 0x87, 0x50); urtw_8187_write_phy_ofdm(sc, 0x89, 0x50); } } usb_pause_mtx(&sc->sc_mtx, 1); fail: return (error); } static usb_error_t urtw_read8e(struct urtw_softc *sc, int val, uint8_t *data) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = URTW_8187_GETREGS_REQ; USETW(req.wValue, val | 0xfe00); USETW(req.wIndex, 0); USETW(req.wLength, sizeof(uint8_t)); error = urtw_do_request(sc, &req, data); return (error); } static usb_error_t urtw_write8e(struct urtw_softc *sc, int val, uint8_t data) { struct usb_device_request req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = URTW_8187_SETREGS_REQ; USETW(req.wValue, val | 0xfe00); USETW(req.wIndex, 0); USETW(req.wLength, sizeof(uint8_t)); return (urtw_do_request(sc, &req, &data)); } static usb_error_t urtw_8180_set_anaparam(struct urtw_softc *sc, uint32_t val) { uint8_t data; usb_error_t error; error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_read8_m(sc, URTW_CONFIG3, &data); urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); urtw_write32_m(sc, URTW_ANAPARAM, val); urtw_read8_m(sc, URTW_CONFIG3, &data); urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; fail: return (error); } static usb_error_t urtw_8185_set_anaparam2(struct urtw_softc *sc, uint32_t val) { uint8_t data; usb_error_t error; error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); if (error) goto fail; urtw_read8_m(sc, URTW_CONFIG3, &data); urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); urtw_write32_m(sc, URTW_ANAPARAM2, val); urtw_read8_m(sc, URTW_CONFIG3, &data); urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); if (error) goto fail; fail: return (error); } static usb_error_t urtw_intr_enable(struct urtw_softc *sc) { usb_error_t error; urtw_write16_m(sc, URTW_INTR_MASK, 0xffff); fail: return (error); } static usb_error_t urtw_intr_disable(struct urtw_softc *sc) { usb_error_t error; urtw_write16_m(sc, URTW_INTR_MASK, 0); fail: return (error); } static usb_error_t urtw_reset(struct urtw_softc *sc) { uint8_t data; usb_error_t error; error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); if (error) goto fail; error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); if (error) goto fail; error = urtw_intr_disable(sc); if (error) goto fail; usb_pause_mtx(&sc->sc_mtx, 100); error = urtw_write8e(sc, 0x18, 0x10); if (error != 0) goto fail; error = urtw_write8e(sc, 0x18, 0x11); if (error != 0) goto fail; error = urtw_write8e(sc, 0x18, 0x00); if (error != 0) goto fail; usb_pause_mtx(&sc->sc_mtx, 100); urtw_read8_m(sc, URTW_CMD, &data); data = (data & 0x2) | URTW_CMD_RST; urtw_write8_m(sc, URTW_CMD, data); usb_pause_mtx(&sc->sc_mtx, 100); urtw_read8_m(sc, URTW_CMD, &data); if (data & URTW_CMD_RST) { device_printf(sc->sc_dev, "reset timeout\n"); goto fail; } error = urtw_set_mode(sc, URTW_EPROM_CMD_LOAD); if (error) goto fail; usb_pause_mtx(&sc->sc_mtx, 100); error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); if (error) goto fail; error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); if (error) goto fail; fail: return (error); } static usb_error_t urtw_led_ctl(struct urtw_softc *sc, int mode) { usb_error_t error = 0; switch (sc->sc_strategy) { case URTW_SW_LED_MODE0: error = urtw_led_mode0(sc, mode); break; case URTW_SW_LED_MODE1: error = urtw_led_mode1(sc, mode); break; case URTW_SW_LED_MODE2: error = urtw_led_mode2(sc, mode); break; case URTW_SW_LED_MODE3: error = urtw_led_mode3(sc, mode); break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unsupported LED mode %d\n", sc->sc_strategy); error = USB_ERR_INVAL; break; } return (error); } static usb_error_t urtw_led_mode0(struct urtw_softc *sc, int mode) { switch (mode) { case URTW_LED_CTL_POWER_ON: sc->sc_gpio_ledstate = URTW_LED_POWER_ON_BLINK; break; case URTW_LED_CTL_TX: if (sc->sc_gpio_ledinprogress == 1) return (0); sc->sc_gpio_ledstate = URTW_LED_BLINK_NORMAL; sc->sc_gpio_blinktime = 2; break; case URTW_LED_CTL_LINK: sc->sc_gpio_ledstate = URTW_LED_ON; break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unsupported LED mode 0x%x", mode); return (USB_ERR_INVAL); } switch (sc->sc_gpio_ledstate) { case URTW_LED_ON: if (sc->sc_gpio_ledinprogress != 0) break; urtw_led_on(sc, URTW_LED_GPIO); break; case URTW_LED_BLINK_NORMAL: if (sc->sc_gpio_ledinprogress != 0) break; sc->sc_gpio_ledinprogress = 1; sc->sc_gpio_blinkstate = (sc->sc_gpio_ledon != 0) ? URTW_LED_OFF : URTW_LED_ON; usb_callout_reset(&sc->sc_led_ch, hz, urtw_led_ch, sc); break; case URTW_LED_POWER_ON_BLINK: urtw_led_on(sc, URTW_LED_GPIO); usb_pause_mtx(&sc->sc_mtx, 100); urtw_led_off(sc, URTW_LED_GPIO); break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unknown LED status 0x%x", sc->sc_gpio_ledstate); return (USB_ERR_INVAL); } return (0); } static usb_error_t urtw_led_mode1(struct urtw_softc *sc, int mode) { return (USB_ERR_INVAL); } static usb_error_t urtw_led_mode2(struct urtw_softc *sc, int mode) { return (USB_ERR_INVAL); } static usb_error_t urtw_led_mode3(struct urtw_softc *sc, int mode) { return (USB_ERR_INVAL); } static usb_error_t urtw_led_on(struct urtw_softc *sc, int type) { usb_error_t error; if (type == URTW_LED_GPIO) { switch (sc->sc_gpio_ledpin) { case URTW_LED_PIN_GPIO0: urtw_write8_m(sc, URTW_GPIO, 0x01); urtw_write8_m(sc, URTW_GP_ENABLE, 0x00); break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unsupported LED PIN type 0x%x", sc->sc_gpio_ledpin); error = USB_ERR_INVAL; goto fail; } } else { DPRINTF(sc, URTW_DEBUG_STATE, "unsupported LED type 0x%x", type); error = USB_ERR_INVAL; goto fail; } sc->sc_gpio_ledon = 1; fail: return (error); } static usb_error_t urtw_led_off(struct urtw_softc *sc, int type) { usb_error_t error; if (type == URTW_LED_GPIO) { switch (sc->sc_gpio_ledpin) { case URTW_LED_PIN_GPIO0: urtw_write8_m(sc, URTW_GPIO, URTW_GPIO_DATA_MAGIC1); urtw_write8_m(sc, URTW_GP_ENABLE, URTW_GP_ENABLE_DATA_MAGIC1); break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unsupported LED PIN type 0x%x", sc->sc_gpio_ledpin); error = USB_ERR_INVAL; goto fail; } } else { DPRINTF(sc, URTW_DEBUG_STATE, "unsupported LED type 0x%x", type); error = USB_ERR_INVAL; goto fail; } sc->sc_gpio_ledon = 0; fail: return (error); } static void urtw_led_ch(void *arg) { struct urtw_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; ieee80211_runtask(ic, &sc->sc_led_task); } static void urtw_ledtask(void *arg, int pending) { struct urtw_softc *sc = arg; if (sc->sc_strategy != URTW_SW_LED_MODE0) { DPRINTF(sc, URTW_DEBUG_STATE, "could not process a LED strategy 0x%x", sc->sc_strategy); return; } URTW_LOCK(sc); urtw_led_blink(sc); URTW_UNLOCK(sc); } static usb_error_t urtw_led_blink(struct urtw_softc *sc) { uint8_t ing = 0; usb_error_t error; if (sc->sc_gpio_blinkstate == URTW_LED_ON) error = urtw_led_on(sc, URTW_LED_GPIO); else error = urtw_led_off(sc, URTW_LED_GPIO); sc->sc_gpio_blinktime--; if (sc->sc_gpio_blinktime == 0) ing = 1; else { if (sc->sc_gpio_ledstate != URTW_LED_BLINK_NORMAL && sc->sc_gpio_ledstate != URTW_LED_BLINK_SLOWLY && sc->sc_gpio_ledstate != URTW_LED_BLINK_CM3) ing = 1; } if (ing == 1) { if (sc->sc_gpio_ledstate == URTW_LED_ON && sc->sc_gpio_ledon == 0) error = urtw_led_on(sc, URTW_LED_GPIO); else if (sc->sc_gpio_ledstate == URTW_LED_OFF && sc->sc_gpio_ledon == 1) error = urtw_led_off(sc, URTW_LED_GPIO); sc->sc_gpio_blinktime = 0; sc->sc_gpio_ledinprogress = 0; return (0); } sc->sc_gpio_blinkstate = (sc->sc_gpio_blinkstate != URTW_LED_ON) ? URTW_LED_ON : URTW_LED_OFF; switch (sc->sc_gpio_ledstate) { case URTW_LED_BLINK_NORMAL: usb_callout_reset(&sc->sc_led_ch, hz, urtw_led_ch, sc); break; default: DPRINTF(sc, URTW_DEBUG_STATE, "unknown LED status 0x%x", sc->sc_gpio_ledstate); return (USB_ERR_INVAL); } return (0); } static usb_error_t urtw_rx_enable(struct urtw_softc *sc) { uint8_t data; usb_error_t error; usbd_transfer_start((sc->sc_flags & URTW_RTL8187B) ? sc->sc_xfer[URTW_8187B_BULK_RX] : sc->sc_xfer[URTW_8187L_BULK_RX]); error = urtw_rx_setconf(sc); if (error != 0) goto fail; if ((sc->sc_flags & URTW_RTL8187B) == 0) { urtw_read8_m(sc, URTW_CMD, &data); urtw_write8_m(sc, URTW_CMD, data | URTW_CMD_RX_ENABLE); } fail: return (error); } static usb_error_t urtw_tx_enable(struct urtw_softc *sc) { uint8_t data8; uint32_t data; usb_error_t error; if (sc->sc_flags & URTW_RTL8187B) { urtw_read32_m(sc, URTW_TX_CONF, &data); data &= ~URTW_TX_LOOPBACK_MASK; data &= ~(URTW_TX_DPRETRY_MASK | URTW_TX_RTSRETRY_MASK); data &= ~(URTW_TX_NOCRC | URTW_TX_MXDMA_MASK); data &= ~URTW_TX_SWPLCPLEN; data |= URTW_TX_HW_SEQNUM | URTW_TX_DISREQQSIZE | (7 << 8) | /* short retry limit */ (7 << 0) | /* long retry limit */ (7 << 21); /* MAX TX DMA */ urtw_write32_m(sc, URTW_TX_CONF, data); urtw_read8_m(sc, URTW_MSR, &data8); data8 |= URTW_MSR_LINK_ENEDCA; urtw_write8_m(sc, URTW_MSR, data8); return (error); } urtw_read8_m(sc, URTW_CW_CONF, &data8); data8 &= ~(URTW_CW_CONF_PERPACKET_CW | URTW_CW_CONF_PERPACKET_RETRY); urtw_write8_m(sc, URTW_CW_CONF, data8); urtw_read8_m(sc, URTW_TX_AGC_CTL, &data8); data8 &= ~URTW_TX_AGC_CTL_PERPACKET_GAIN; data8 &= ~URTW_TX_AGC_CTL_PERPACKET_ANTSEL; data8 &= ~URTW_TX_AGC_CTL_FEEDBACK_ANT; urtw_write8_m(sc, URTW_TX_AGC_CTL, data8); urtw_read32_m(sc, URTW_TX_CONF, &data); data &= ~URTW_TX_LOOPBACK_MASK; data |= URTW_TX_LOOPBACK_NONE; data &= ~(URTW_TX_DPRETRY_MASK | URTW_TX_RTSRETRY_MASK); data |= sc->sc_tx_retry << URTW_TX_DPRETRY_SHIFT; data |= sc->sc_rts_retry << URTW_TX_RTSRETRY_SHIFT; data &= ~(URTW_TX_NOCRC | URTW_TX_MXDMA_MASK); data |= URTW_TX_MXDMA_2048 | URTW_TX_CWMIN | URTW_TX_DISCW; data &= ~URTW_TX_SWPLCPLEN; data |= URTW_TX_NOICV; urtw_write32_m(sc, URTW_TX_CONF, data); urtw_read8_m(sc, URTW_CMD, &data8); urtw_write8_m(sc, URTW_CMD, data8 | URTW_CMD_TX_ENABLE); fail: return (error); } static usb_error_t urtw_rx_setconf(struct urtw_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t data; usb_error_t error; urtw_read32_m(sc, URTW_RX, &data); data = data &~ URTW_RX_FILTER_MASK; if (sc->sc_flags & URTW_RTL8187B) { data = data | URTW_RX_FILTER_MNG | URTW_RX_FILTER_DATA | URTW_RX_FILTER_MCAST | URTW_RX_FILTER_BCAST | URTW_RX_FIFO_THRESHOLD_NONE | URTW_MAX_RX_DMA_2048 | URTW_RX_AUTORESETPHY | URTW_RCR_ONLYERLPKT; } else { data = data | URTW_RX_FILTER_MNG | URTW_RX_FILTER_DATA; data = data | URTW_RX_FILTER_BCAST | URTW_RX_FILTER_MCAST; if (ic->ic_opmode == IEEE80211_M_MONITOR) { data = data | URTW_RX_FILTER_ICVERR; data = data | URTW_RX_FILTER_PWR; } if (sc->sc_crcmon == 1 && ic->ic_opmode == IEEE80211_M_MONITOR) data = data | URTW_RX_FILTER_CRCERR; data = data &~ URTW_RX_FIFO_THRESHOLD_MASK; data = data | URTW_RX_FIFO_THRESHOLD_NONE | URTW_RX_AUTORESETPHY; data = data &~ URTW_MAX_RX_DMA_MASK; data = data | URTW_MAX_RX_DMA_2048 | URTW_RCR_ONLYERLPKT; } /* XXX allmulti should not be checked here... */ if (ic->ic_opmode == IEEE80211_M_MONITOR || ic->ic_promisc > 0 || ic->ic_allmulti > 0) { data = data | URTW_RX_FILTER_CTL; data = data | URTW_RX_FILTER_ALLMAC; } else { data = data | URTW_RX_FILTER_NICMAC; data = data | URTW_RX_CHECK_BSSID; } urtw_write32_m(sc, URTW_RX, data); fail: return (error); } static struct mbuf * urtw_rxeof(struct usb_xfer *xfer, struct urtw_data *data, int *rssi_p, int8_t *nf_p) { int actlen, flen, rssi; struct ieee80211_frame *wh; struct mbuf *m, *mnew; struct urtw_softc *sc = data->sc; struct ieee80211com *ic = &sc->sc_ic; uint8_t noise = 0, rate; uint64_t mactime; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); if (sc->sc_flags & URTW_RTL8187B) { struct urtw_8187b_rxhdr *rx; if (actlen < sizeof(*rx) + IEEE80211_ACK_LEN) goto fail; rx = (struct urtw_8187b_rxhdr *)(data->buf + (actlen - (sizeof(struct urtw_8187b_rxhdr)))); flen = le32toh(rx->flag) & 0xfff; if (flen > actlen - sizeof(*rx)) goto fail; rate = (le32toh(rx->flag) >> URTW_RX_FLAG_RXRATE_SHIFT) & 0xf; /* XXX correct? */ rssi = rx->rssi & URTW_RX_RSSI_MASK; noise = rx->noise; if (ieee80211_radiotap_active(ic)) mactime = rx->mactime; } else { struct urtw_8187l_rxhdr *rx; if (actlen < sizeof(*rx) + IEEE80211_ACK_LEN) goto fail; rx = (struct urtw_8187l_rxhdr *)(data->buf + (actlen - (sizeof(struct urtw_8187l_rxhdr)))); flen = le32toh(rx->flag) & 0xfff; if (flen > actlen - sizeof(*rx)) goto fail; rate = (le32toh(rx->flag) >> URTW_RX_FLAG_RXRATE_SHIFT) & 0xf; /* XXX correct? */ rssi = rx->rssi & URTW_RX_8187L_RSSI_MASK; noise = rx->noise; if (ieee80211_radiotap_active(ic)) mactime = rx->mactime; } if (flen < IEEE80211_ACK_LEN) goto fail; mnew = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (mnew == NULL) goto fail; m = data->m; data->m = mnew; data->buf = mtod(mnew, uint8_t *); /* finalize mbuf */ m->m_pkthdr.len = m->m_len = flen - IEEE80211_CRC_LEN; if (ieee80211_radiotap_active(ic)) { struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_tsf = mactime; tap->wr_flags = 0; tap->wr_dbm_antsignal = (int8_t)rssi; } wh = mtod(m, struct ieee80211_frame *); if (IEEE80211_IS_DATA(wh)) sc->sc_currate = (rate > 0) ? rate : sc->sc_currate; *rssi_p = rssi; *nf_p = noise; /* XXX correct? */ return (m); fail: counter_u64_add(ic->ic_ierrors, 1); return (NULL); } static void urtw_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct urtw_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; struct epoch_tracker et; struct mbuf *m = NULL; struct urtw_data *data; int8_t nf = -95; int rssi = 1; URTW_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); m = urtw_rxeof(xfer, data, &rssi, &nf); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: setup: data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) { KASSERT(m == NULL, ("mbuf isn't NULL")); return; } STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ URTW_UNLOCK(sc); if (m != NULL) { if (m->m_pkthdr.len >= sizeof(struct ieee80211_frame_min)) { ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); } else ni = NULL; NET_EPOCH_ENTER(et); if (ni != NULL) { (void) ieee80211_input(ni, m, rssi, nf); /* node is no longer needed */ ieee80211_free_node(ni); } else (void) ieee80211_input_all(ic, m, rssi, nf); NET_EPOCH_EXIT(et); m = NULL; } URTW_LOCK(sc); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto setup; } break; } } #define URTW_STATUS_TYPE_TXCLOSE 1 #define URTW_STATUS_TYPE_BEACON_INTR 0 static void urtw_txstatus_eof(struct usb_xfer *xfer) { struct urtw_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; int actlen, type, pktretry, seq; uint64_t val; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); if (actlen != sizeof(uint64_t)) return; val = le64toh(sc->sc_txstatus); type = (val >> 30) & 0x3; if (type == URTW_STATUS_TYPE_TXCLOSE) { pktretry = val & 0xff; seq = (val >> 16) & 0xff; if (pktretry == URTW_TX_MAXRETRY) counter_u64_add(ic->ic_oerrors, 1); DPRINTF(sc, URTW_DEBUG_TXSTATUS, "pktretry %d seq %#x\n", pktretry, seq); } } static void urtw_bulk_tx_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct urtw_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; void *dma_buf = usbd_xfer_get_frame_buffer(xfer, 0); URTW_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: urtw_txstatus_eof(xfer); /* FALLTHROUGH */ case USB_ST_SETUP: setup: memcpy(dma_buf, &sc->sc_txstatus, sizeof(uint64_t)); usbd_xfer_set_frame_len(xfer, 0, sizeof(uint64_t)); usbd_transfer_submit(xfer); break; default: if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto setup; } break; } } static void urtw_txeof(struct usb_xfer *xfer, struct urtw_data *data) { struct urtw_softc *sc = usbd_xfer_softc(xfer); URTW_ASSERT_LOCKED(sc); if (data->m) { /* XXX status? */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } sc->sc_txtimer = 0; } static void urtw_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct urtw_softc *sc = usbd_xfer_softc(xfer); struct urtw_data *data; URTW_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto setup; STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); urtw_txeof(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: setup: data = STAILQ_FIRST(&sc->sc_tx_pending); if (data == NULL) { DPRINTF(sc, URTW_DEBUG_XMIT, "%s: empty pending queue\n", __func__); return; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); usbd_transfer_submit(xfer); urtw_start(sc); break; default: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto setup; if (data->ni != NULL) { if_inc_counter(data->ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(data->ni); data->ni = NULL; } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto setup; } break; } } static struct urtw_data * _urtw_getbuf(struct urtw_softc *sc) { struct urtw_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); else bf = NULL; if (bf == NULL) DPRINTF(sc, URTW_DEBUG_XMIT, "%s: %s\n", __func__, "out of xmit buffers"); return (bf); } static struct urtw_data * urtw_getbuf(struct urtw_softc *sc) { struct urtw_data *bf; URTW_ASSERT_LOCKED(sc); bf = _urtw_getbuf(sc); if (bf == NULL) DPRINTF(sc, URTW_DEBUG_XMIT, "%s: stop queue\n", __func__); return (bf); } static int urtw_isbmode(uint16_t rate) { return ((rate <= 22 && rate != 12 && rate != 18) || rate == 44) ? (1) : (0); } static uint16_t urtw_rate2dbps(uint16_t rate) { switch(rate) { case 12: case 18: case 24: case 36: case 48: case 72: case 96: case 108: return (rate * 2); default: break; } return (24); } static int urtw_compute_txtime(uint16_t framelen, uint16_t rate, uint8_t ismgt, uint8_t isshort) { uint16_t ceiling, frametime, n_dbps; if (urtw_isbmode(rate)) { if (ismgt || !isshort || rate == 2) frametime = (uint16_t)(144 + 48 + (framelen * 8 / (rate / 2))); else frametime = (uint16_t)(72 + 24 + (framelen * 8 / (rate / 2))); if ((framelen * 8 % (rate / 2)) != 0) frametime++; } else { n_dbps = urtw_rate2dbps(rate); ceiling = (16 + 8 * framelen + 6) / n_dbps + (((16 + 8 * framelen + 6) % n_dbps) ? 1 : 0); frametime = (uint16_t)(16 + 4 + 4 * ceiling + 6); } return (frametime); } /* * Callback from the 802.11 layer to update the * slot time based on the current setting. */ static void urtw_updateslot(struct ieee80211com *ic) { struct urtw_softc *sc = ic->ic_softc; ieee80211_runtask(ic, &sc->sc_updateslot_task); } static void urtw_updateslottask(void *arg, int pending) { struct urtw_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; int error; URTW_LOCK(sc); if ((sc->sc_flags & URTW_RUNNING) == 0) { URTW_UNLOCK(sc); return; } if (sc->sc_flags & URTW_RTL8187B) { urtw_write8_m(sc, URTW_SIFS, 0x22); if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SHSLOT); else urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SLOT); urtw_write8_m(sc, URTW_8187B_EIFS, 0x5b); urtw_write8_m(sc, URTW_CARRIER_SCOUNT, 0x5b); } else { urtw_write8_m(sc, URTW_SIFS, 0x22); if (sc->sc_state == IEEE80211_S_ASSOC && ic->ic_flags & IEEE80211_F_SHSLOT) urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SHSLOT); else urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SLOT); if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) { urtw_write8_m(sc, URTW_DIFS, 0x14); urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x14); urtw_write8_m(sc, URTW_CW_VAL, 0x73); } else { urtw_write8_m(sc, URTW_DIFS, 0x24); urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x24); urtw_write8_m(sc, URTW_CW_VAL, 0xa5); } } fail: URTW_UNLOCK(sc); } static void urtw_sysctl_node(struct urtw_softc *sc) { #define URTW_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child, *parent; struct sysctl_oid *tree; struct urtw_stats *stats = &sc->sc_stats; ctx = device_get_sysctl_ctx(sc->sc_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); - tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, - NULL, "URTW statistics"); + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "URTW statistics"); parent = SYSCTL_CHILDREN(tree); /* Tx statistics. */ - tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", CTLFLAG_RD, - NULL, "Tx MAC statistics"); + tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", + CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Tx MAC statistics"); child = SYSCTL_CHILDREN(tree); URTW_SYSCTL_STAT_ADD32(ctx, child, "1m", &stats->txrates[0], "1 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "2m", &stats->txrates[1], "2 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "5.5m", &stats->txrates[2], "5.5 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "6m", &stats->txrates[4], "6 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "9m", &stats->txrates[5], "9 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "11m", &stats->txrates[3], "11 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "12m", &stats->txrates[6], "12 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "18m", &stats->txrates[7], "18 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "24m", &stats->txrates[8], "24 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "36m", &stats->txrates[9], "36 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "48m", &stats->txrates[10], "48 Mbit/s"); URTW_SYSCTL_STAT_ADD32(ctx, child, "54m", &stats->txrates[11], "54 Mbit/s"); #undef URTW_SYSCTL_STAT_ADD32 } static device_method_t urtw_methods[] = { DEVMETHOD(device_probe, urtw_match), DEVMETHOD(device_attach, urtw_attach), DEVMETHOD(device_detach, urtw_detach), DEVMETHOD_END }; static driver_t urtw_driver = { .name = "urtw", .methods = urtw_methods, .size = sizeof(struct urtw_softc) }; static devclass_t urtw_devclass; DRIVER_MODULE(urtw, uhub, urtw_driver, urtw_devclass, NULL, 0); MODULE_DEPEND(urtw, wlan, 1, 1, 1); MODULE_DEPEND(urtw, usb, 1, 1, 1); MODULE_VERSION(urtw, 1); USB_PNP_HOST_INFO(urtw_devs); Index: head/sys/dev/usb/wlan/if_zyd.c =================================================================== --- head/sys/dev/usb/wlan/if_zyd.c (revision 357971) +++ head/sys/dev/usb/wlan/if_zyd.c (revision 357972) @@ -1,2923 +1,2924 @@ /* $OpenBSD: if_zyd.c,v 1.52 2007/02/11 00:08:04 jsg Exp $ */ /* $NetBSD: if_zyd.c,v 1.7 2007/06/21 04:04:29 kiyohara Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 2006 by Damien Bergamini * Copyright (c) 2006 by Florian Stoehr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /* * ZyDAS ZD1211/ZD1211B USB WLAN driver. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #ifdef USB_DEBUG static int zyd_debug = 0; -static SYSCTL_NODE(_hw_usb, OID_AUTO, zyd, CTLFLAG_RW, 0, "USB zyd"); +static SYSCTL_NODE(_hw_usb, OID_AUTO, zyd, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB zyd"); SYSCTL_INT(_hw_usb_zyd, OID_AUTO, debug, CTLFLAG_RWTUN, &zyd_debug, 0, "zyd debug level"); enum { ZYD_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ ZYD_DEBUG_RECV = 0x00000002, /* basic recv operation */ ZYD_DEBUG_RESET = 0x00000004, /* reset processing */ ZYD_DEBUG_INIT = 0x00000008, /* device init */ ZYD_DEBUG_TX_PROC = 0x00000010, /* tx ISR proc */ ZYD_DEBUG_RX_PROC = 0x00000020, /* rx ISR proc */ ZYD_DEBUG_STATE = 0x00000040, /* 802.11 state transitions */ ZYD_DEBUG_STAT = 0x00000080, /* statistic */ ZYD_DEBUG_FW = 0x00000100, /* firmware */ ZYD_DEBUG_CMD = 0x00000200, /* fw commands */ ZYD_DEBUG_ANY = 0xffffffff }; #define DPRINTF(sc, m, fmt, ...) do { \ if (zyd_debug & (m)) \ printf("%s: " fmt, __func__, ## __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, m, fmt, ...) do { \ (void) sc; \ } while (0) #endif #define zyd_do_request(sc,req,data) \ usbd_do_request_flags((sc)->sc_udev, &(sc)->sc_mtx, req, data, 0, NULL, 5000) static device_probe_t zyd_match; static device_attach_t zyd_attach; static device_detach_t zyd_detach; static usb_callback_t zyd_intr_read_callback; static usb_callback_t zyd_intr_write_callback; static usb_callback_t zyd_bulk_read_callback; static usb_callback_t zyd_bulk_write_callback; static struct ieee80211vap *zyd_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void zyd_vap_delete(struct ieee80211vap *); static void zyd_tx_free(struct zyd_tx_data *, int); static void zyd_setup_tx_list(struct zyd_softc *); static void zyd_unsetup_tx_list(struct zyd_softc *); static int zyd_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int zyd_cmd(struct zyd_softc *, uint16_t, const void *, int, void *, int, int); static int zyd_read16(struct zyd_softc *, uint16_t, uint16_t *); static int zyd_read32(struct zyd_softc *, uint16_t, uint32_t *); static int zyd_write16(struct zyd_softc *, uint16_t, uint16_t); static int zyd_write32(struct zyd_softc *, uint16_t, uint32_t); static int zyd_rfwrite(struct zyd_softc *, uint32_t); static int zyd_lock_phy(struct zyd_softc *); static int zyd_unlock_phy(struct zyd_softc *); static int zyd_rf_attach(struct zyd_softc *, uint8_t); static const char *zyd_rf_name(uint8_t); static int zyd_hw_init(struct zyd_softc *); static int zyd_read_pod(struct zyd_softc *); static int zyd_read_eeprom(struct zyd_softc *); static int zyd_get_macaddr(struct zyd_softc *); static int zyd_set_macaddr(struct zyd_softc *, const uint8_t *); static int zyd_set_bssid(struct zyd_softc *, const uint8_t *); static int zyd_switch_radio(struct zyd_softc *, int); static int zyd_set_led(struct zyd_softc *, int, int); static void zyd_set_multi(struct zyd_softc *); static void zyd_update_mcast(struct ieee80211com *); static int zyd_set_rxfilter(struct zyd_softc *); static void zyd_set_chan(struct zyd_softc *, struct ieee80211_channel *); static int zyd_set_beacon_interval(struct zyd_softc *, int); static void zyd_rx_data(struct usb_xfer *, int, uint16_t); static int zyd_tx_start(struct zyd_softc *, struct mbuf *, struct ieee80211_node *); static int zyd_transmit(struct ieee80211com *, struct mbuf *); static void zyd_start(struct zyd_softc *); static int zyd_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void zyd_parent(struct ieee80211com *); static void zyd_init_locked(struct zyd_softc *); static void zyd_stop(struct zyd_softc *); static int zyd_loadfirmware(struct zyd_softc *); static void zyd_scan_start(struct ieee80211com *); static void zyd_scan_end(struct ieee80211com *); static void zyd_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static void zyd_set_channel(struct ieee80211com *); static int zyd_rfmd_init(struct zyd_rf *); static int zyd_rfmd_switch_radio(struct zyd_rf *, int); static int zyd_rfmd_set_channel(struct zyd_rf *, uint8_t); static int zyd_al2230_init(struct zyd_rf *); static int zyd_al2230_switch_radio(struct zyd_rf *, int); static int zyd_al2230_set_channel(struct zyd_rf *, uint8_t); static int zyd_al2230_set_channel_b(struct zyd_rf *, uint8_t); static int zyd_al2230_init_b(struct zyd_rf *); static int zyd_al7230B_init(struct zyd_rf *); static int zyd_al7230B_switch_radio(struct zyd_rf *, int); static int zyd_al7230B_set_channel(struct zyd_rf *, uint8_t); static int zyd_al2210_init(struct zyd_rf *); static int zyd_al2210_switch_radio(struct zyd_rf *, int); static int zyd_al2210_set_channel(struct zyd_rf *, uint8_t); static int zyd_gct_init(struct zyd_rf *); static int zyd_gct_switch_radio(struct zyd_rf *, int); static int zyd_gct_set_channel(struct zyd_rf *, uint8_t); static int zyd_gct_mode(struct zyd_rf *); static int zyd_gct_set_channel_synth(struct zyd_rf *, int, int); static int zyd_gct_write(struct zyd_rf *, uint16_t); static int zyd_gct_txgain(struct zyd_rf *, uint8_t); static int zyd_maxim2_init(struct zyd_rf *); static int zyd_maxim2_switch_radio(struct zyd_rf *, int); static int zyd_maxim2_set_channel(struct zyd_rf *, uint8_t); static const struct zyd_phy_pair zyd_def_phy[] = ZYD_DEF_PHY; static const struct zyd_phy_pair zyd_def_phyB[] = ZYD_DEF_PHYB; /* various supported device vendors/products */ #define ZYD_ZD1211 0 #define ZYD_ZD1211B 1 #define ZYD_ZD1211_DEV(v,p) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, ZYD_ZD1211) } #define ZYD_ZD1211B_DEV(v,p) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, ZYD_ZD1211B) } static const STRUCT_USB_HOST_ID zyd_devs[] = { /* ZYD_ZD1211 */ ZYD_ZD1211_DEV(3COM2, 3CRUSB10075), ZYD_ZD1211_DEV(ABOCOM, WL54), ZYD_ZD1211_DEV(ASUS, WL159G), ZYD_ZD1211_DEV(CYBERTAN, TG54USB), ZYD_ZD1211_DEV(DRAYTEK, VIGOR550), ZYD_ZD1211_DEV(PLANEX2, GWUS54GD), ZYD_ZD1211_DEV(PLANEX2, GWUS54GZL), ZYD_ZD1211_DEV(PLANEX3, GWUS54GZ), ZYD_ZD1211_DEV(PLANEX3, GWUS54MINI), ZYD_ZD1211_DEV(SAGEM, XG760A), ZYD_ZD1211_DEV(SENAO, NUB8301), ZYD_ZD1211_DEV(SITECOMEU, WL113), ZYD_ZD1211_DEV(SWEEX, ZD1211), ZYD_ZD1211_DEV(TEKRAM, QUICKWLAN), ZYD_ZD1211_DEV(TEKRAM, ZD1211_1), ZYD_ZD1211_DEV(TEKRAM, ZD1211_2), ZYD_ZD1211_DEV(TWINMOS, G240), ZYD_ZD1211_DEV(UMEDIA, ALL0298V2), ZYD_ZD1211_DEV(UMEDIA, TEW429UB_A), ZYD_ZD1211_DEV(UMEDIA, TEW429UB), ZYD_ZD1211_DEV(WISTRONNEWEB, UR055G), ZYD_ZD1211_DEV(ZCOM, ZD1211), ZYD_ZD1211_DEV(ZYDAS, ZD1211), ZYD_ZD1211_DEV(ZYXEL, AG225H), ZYD_ZD1211_DEV(ZYXEL, ZYAIRG220), ZYD_ZD1211_DEV(ZYXEL, G200V2), /* ZYD_ZD1211B */ ZYD_ZD1211B_DEV(ACCTON, SMCWUSBG_NF), ZYD_ZD1211B_DEV(ACCTON, SMCWUSBG), ZYD_ZD1211B_DEV(ACCTON, ZD1211B), ZYD_ZD1211B_DEV(ASUS, A9T_WIFI), ZYD_ZD1211B_DEV(BELKIN, F5D7050_V4000), ZYD_ZD1211B_DEV(BELKIN, ZD1211B), ZYD_ZD1211B_DEV(CISCOLINKSYS, WUSBF54G), ZYD_ZD1211B_DEV(FIBERLINE, WL430U), ZYD_ZD1211B_DEV(MELCO, KG54L), ZYD_ZD1211B_DEV(PHILIPS, SNU5600), ZYD_ZD1211B_DEV(PLANEX2, GW_US54GXS), ZYD_ZD1211B_DEV(SAGEM, XG76NA), ZYD_ZD1211B_DEV(SITECOMEU, ZD1211B), ZYD_ZD1211B_DEV(UMEDIA, TEW429UBC1), ZYD_ZD1211B_DEV(USR, USR5423), ZYD_ZD1211B_DEV(VTECH, ZD1211B), ZYD_ZD1211B_DEV(ZCOM, ZD1211B), ZYD_ZD1211B_DEV(ZYDAS, ZD1211B), ZYD_ZD1211B_DEV(ZYXEL, M202), ZYD_ZD1211B_DEV(ZYXEL, G202), ZYD_ZD1211B_DEV(ZYXEL, G220V2) }; static const struct usb_config zyd_config[ZYD_N_TRANSFER] = { [ZYD_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = ZYD_MAX_TXBUFSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = zyd_bulk_write_callback, .ep_index = 0, .timeout = 10000, /* 10 seconds */ }, [ZYD_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = ZYX_MAX_RXBUFSZ, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = zyd_bulk_read_callback, .ep_index = 0, }, [ZYD_INTR_WR] = { .type = UE_BULK_INTR, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(struct zyd_cmd), .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = zyd_intr_write_callback, .timeout = 1000, /* 1 second */ .ep_index = 1, }, [ZYD_INTR_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(struct zyd_cmd), .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = zyd_intr_read_callback, }, }; #define zyd_read16_m(sc, val, data) do { \ error = zyd_read16(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define zyd_write16_m(sc, val, data) do { \ error = zyd_write16(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define zyd_read32_m(sc, val, data) do { \ error = zyd_read32(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) #define zyd_write32_m(sc, val, data) do { \ error = zyd_write32(sc, val, data); \ if (error != 0) \ goto fail; \ } while (0) static int zyd_match(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != ZYD_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != ZYD_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(zyd_devs, sizeof(zyd_devs), uaa)); } static int zyd_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct zyd_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; uint8_t iface_index; int error; if (uaa->info.bcdDevice < 0x4330) { device_printf(dev, "device version mismatch: 0x%X " "(only >= 43.30 supported)\n", uaa->info.bcdDevice); return (EINVAL); } device_set_usb_desc(dev); sc->sc_dev = dev; sc->sc_udev = uaa->device; sc->sc_macrev = USB_GET_DRIVER_INFO(uaa); mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, MTX_DEF); STAILQ_INIT(&sc->sc_rqh); mbufq_init(&sc->sc_snd, ifqmaxlen); iface_index = ZYD_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, zyd_config, ZYD_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto detach; } ZYD_LOCK(sc); if ((error = zyd_get_macaddr(sc)) != 0) { device_printf(sc->sc_dev, "could not read EEPROM\n"); ZYD_UNLOCK(sc); goto detach; } ZYD_UNLOCK(sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA /* station mode */ | IEEE80211_C_MONITOR /* monitor mode */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_BGSCAN /* capable of bg scanning */ | IEEE80211_C_WPA /* 802.11i */ ; zyd_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_raw_xmit = zyd_raw_xmit; ic->ic_scan_start = zyd_scan_start; ic->ic_scan_end = zyd_scan_end; ic->ic_getradiocaps = zyd_getradiocaps; ic->ic_set_channel = zyd_set_channel; ic->ic_vap_create = zyd_vap_create; ic->ic_vap_delete = zyd_vap_delete; ic->ic_update_mcast = zyd_update_mcast; ic->ic_update_promisc = zyd_update_mcast; ic->ic_parent = zyd_parent; ic->ic_transmit = zyd_transmit; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), ZYD_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), ZYD_RX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); return (0); detach: zyd_detach(dev); return (ENXIO); /* failure */ } static void zyd_drain_mbufq(struct zyd_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; ZYD_LOCK_ASSERT(sc, MA_OWNED); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static int zyd_detach(device_t dev) { struct zyd_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; unsigned int x; /* * Prevent further allocations from RX/TX data * lists and ioctls: */ ZYD_LOCK(sc); sc->sc_flags |= ZYD_FLAG_DETACHED; zyd_drain_mbufq(sc); STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); ZYD_UNLOCK(sc); /* drain USB transfers */ for (x = 0; x != ZYD_N_TRANSFER; x++) usbd_transfer_drain(sc->sc_xfer[x]); /* free TX list, if any */ ZYD_LOCK(sc); zyd_unsetup_tx_list(sc); ZYD_UNLOCK(sc); /* free USB transfers and some data buffers */ usbd_transfer_unsetup(sc->sc_xfer, ZYD_N_TRANSFER); if (ic->ic_softc == sc) ieee80211_ifdetach(ic); mtx_destroy(&sc->sc_mtx); return (0); } static struct ieee80211vap * zyd_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct zyd_vap *zvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); zvp = malloc(sizeof(struct zyd_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &zvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(zvp, M_80211_VAP); return (NULL); } /* override state transition machine */ zvp->newstate = vap->iv_newstate; vap->iv_newstate = zyd_newstate; ieee80211_ratectl_init(vap); ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void zyd_vap_delete(struct ieee80211vap *vap) { struct zyd_vap *zvp = ZYD_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(zvp, M_80211_VAP); } static void zyd_tx_free(struct zyd_tx_data *data, int txerr) { struct zyd_softc *sc = data->sc; if (data->m != NULL) { ieee80211_tx_complete(data->ni, data->m, txerr); data->m = NULL; data->ni = NULL; } STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } static void zyd_setup_tx_list(struct zyd_softc *sc) { struct zyd_tx_data *data; int i; sc->tx_nfree = 0; STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); for (i = 0; i < ZYD_TX_LIST_CNT; i++) { data = &sc->tx_data[i]; data->sc = sc; STAILQ_INSERT_TAIL(&sc->tx_free, data, next); sc->tx_nfree++; } } static void zyd_unsetup_tx_list(struct zyd_softc *sc) { struct zyd_tx_data *data; int i; /* make sure any subsequent use of the queues will fail */ sc->tx_nfree = 0; STAILQ_INIT(&sc->tx_q); STAILQ_INIT(&sc->tx_free); /* free up all node references and mbufs */ for (i = 0; i < ZYD_TX_LIST_CNT; i++) { data = &sc->tx_data[i]; if (data->m != NULL) { m_freem(data->m); data->m = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } } static int zyd_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct zyd_vap *zvp = ZYD_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct zyd_softc *sc = ic->ic_softc; int error; DPRINTF(sc, ZYD_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); ZYD_LOCK(sc); switch (nstate) { case IEEE80211_S_AUTH: zyd_set_chan(sc, ic->ic_curchan); break; case IEEE80211_S_RUN: if (vap->iv_opmode == IEEE80211_M_MONITOR) break; /* turn link LED on */ error = zyd_set_led(sc, ZYD_LED1, 1); if (error != 0) break; /* make data LED blink upon Tx */ zyd_write32_m(sc, sc->sc_fwbase + ZYD_FW_LINK_STATUS, 1); IEEE80211_ADDR_COPY(sc->sc_bssid, vap->iv_bss->ni_bssid); zyd_set_bssid(sc, sc->sc_bssid); break; default: break; } fail: ZYD_UNLOCK(sc); IEEE80211_LOCK(ic); return (zvp->newstate(vap, nstate, arg)); } /* * Callback handler for interrupt transfer */ static void zyd_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct zyd_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni; struct zyd_cmd *cmd = &sc->sc_ibuf; struct usb_page_cache *pc; int datalen; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, cmd, sizeof(*cmd)); switch (le16toh(cmd->code)) { case ZYD_NOTIF_RETRYSTATUS: { struct zyd_notif_retry *retry = (struct zyd_notif_retry *)cmd->data; uint16_t count = le16toh(retry->count); DPRINTF(sc, ZYD_DEBUG_TX_PROC, "retry intr: rate=0x%x addr=%s count=%d (0x%x)\n", le16toh(retry->rate), ether_sprintf(retry->macaddr), count & 0xff, count); /* * Find the node to which the packet was sent and * update its retry statistics. In BSS mode, this node * is the AP we're associated to so no lookup is * actually needed. */ ni = ieee80211_find_txnode(vap, retry->macaddr); if (ni != NULL) { struct ieee80211_ratectl_tx_status *txs = &sc->sc_txs; int retrycnt = count & 0xff; txs->flags = IEEE80211_RATECTL_STATUS_LONG_RETRY; txs->long_retries = retrycnt; if (count & 0x100) { txs->status = IEEE80211_RATECTL_TX_FAIL_LONG; } else { txs->status = IEEE80211_RATECTL_TX_SUCCESS; } ieee80211_ratectl_tx_complete(ni, txs); ieee80211_free_node(ni); } if (count & 0x100) /* too many retries */ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, 1); break; } case ZYD_NOTIF_IORD: { struct zyd_rq *rqp; if (le16toh(*(uint16_t *)cmd->data) == ZYD_CR_INTERRUPT) break; /* HMAC interrupt */ datalen = actlen - sizeof(cmd->code); datalen -= 2; /* XXX: padding? */ STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { int i; int count; if (rqp->olen != datalen) continue; count = rqp->olen / sizeof(struct zyd_pair); for (i = 0; i < count; i++) { if (*(((const uint16_t *)rqp->idata) + i) != (((struct zyd_pair *)cmd->data) + i)->reg) break; } if (i != count) continue; /* copy answer into caller-supplied buffer */ memcpy(rqp->odata, cmd->data, rqp->olen); DPRINTF(sc, ZYD_DEBUG_CMD, "command %p complete, data = %*D \n", rqp, rqp->olen, (char *)rqp->odata, ":"); wakeup(rqp); /* wakeup caller */ break; } if (rqp == NULL) { device_printf(sc->sc_dev, "unexpected IORD notification %*D\n", datalen, cmd->data, ":"); } break; } default: device_printf(sc->sc_dev, "unknown notification %x\n", le16toh(cmd->code)); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF(sc, ZYD_DEBUG_CMD, "error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void zyd_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct zyd_softc *sc = usbd_xfer_softc(xfer); struct zyd_rq *rqp, *cmd; struct usb_page_cache *pc; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: cmd = usbd_xfer_get_priv(xfer); DPRINTF(sc, ZYD_DEBUG_CMD, "command %p transferred\n", cmd); STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { /* Ensure the cached rq pointer is still valid */ if (rqp == cmd && (rqp->flags & ZYD_CMD_FLAG_READ) == 0) wakeup(rqp); /* wakeup caller */ } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { if (rqp->flags & ZYD_CMD_FLAG_SENT) continue; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, rqp->cmd, rqp->ilen); usbd_xfer_set_frame_len(xfer, 0, rqp->ilen); usbd_xfer_set_priv(xfer, rqp); rqp->flags |= ZYD_CMD_FLAG_SENT; usbd_transfer_submit(xfer); break; } break; default: /* Error */ DPRINTF(sc, ZYD_DEBUG_ANY, "error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int zyd_cmd(struct zyd_softc *sc, uint16_t code, const void *idata, int ilen, void *odata, int olen, int flags) { struct zyd_cmd cmd; struct zyd_rq rq; int error; if (ilen > (int)sizeof(cmd.data)) return (EINVAL); cmd.code = htole16(code); memcpy(cmd.data, idata, ilen); DPRINTF(sc, ZYD_DEBUG_CMD, "sending cmd %p = %*D\n", &rq, ilen, idata, ":"); rq.cmd = &cmd; rq.idata = idata; rq.odata = odata; rq.ilen = sizeof(uint16_t) + ilen; rq.olen = olen; rq.flags = flags; STAILQ_INSERT_TAIL(&sc->sc_rqh, &rq, rq); usbd_transfer_start(sc->sc_xfer[ZYD_INTR_RD]); usbd_transfer_start(sc->sc_xfer[ZYD_INTR_WR]); /* wait at most one second for command reply */ error = mtx_sleep(&rq, &sc->sc_mtx, 0 , "zydcmd", hz); if (error) device_printf(sc->sc_dev, "command timeout\n"); STAILQ_REMOVE(&sc->sc_rqh, &rq, zyd_rq, rq); DPRINTF(sc, ZYD_DEBUG_CMD, "finsihed cmd %p, error = %d \n", &rq, error); return (error); } static int zyd_read16(struct zyd_softc *sc, uint16_t reg, uint16_t *val) { struct zyd_pair tmp; int error; reg = htole16(reg); error = zyd_cmd(sc, ZYD_CMD_IORD, ®, sizeof(reg), &tmp, sizeof(tmp), ZYD_CMD_FLAG_READ); if (error == 0) *val = le16toh(tmp.val); return (error); } static int zyd_read32(struct zyd_softc *sc, uint16_t reg, uint32_t *val) { struct zyd_pair tmp[2]; uint16_t regs[2]; int error; regs[0] = htole16(ZYD_REG32_HI(reg)); regs[1] = htole16(ZYD_REG32_LO(reg)); error = zyd_cmd(sc, ZYD_CMD_IORD, regs, sizeof(regs), tmp, sizeof(tmp), ZYD_CMD_FLAG_READ); if (error == 0) *val = le16toh(tmp[0].val) << 16 | le16toh(tmp[1].val); return (error); } static int zyd_write16(struct zyd_softc *sc, uint16_t reg, uint16_t val) { struct zyd_pair pair; pair.reg = htole16(reg); pair.val = htole16(val); return zyd_cmd(sc, ZYD_CMD_IOWR, &pair, sizeof(pair), NULL, 0, 0); } static int zyd_write32(struct zyd_softc *sc, uint16_t reg, uint32_t val) { struct zyd_pair pair[2]; pair[0].reg = htole16(ZYD_REG32_HI(reg)); pair[0].val = htole16(val >> 16); pair[1].reg = htole16(ZYD_REG32_LO(reg)); pair[1].val = htole16(val & 0xffff); return zyd_cmd(sc, ZYD_CMD_IOWR, pair, sizeof(pair), NULL, 0, 0); } static int zyd_rfwrite(struct zyd_softc *sc, uint32_t val) { struct zyd_rf *rf = &sc->sc_rf; struct zyd_rfwrite_cmd req; uint16_t cr203; int error, i; zyd_read16_m(sc, ZYD_CR203, &cr203); cr203 &= ~(ZYD_RF_IF_LE | ZYD_RF_CLK | ZYD_RF_DATA); req.code = htole16(2); req.width = htole16(rf->width); for (i = 0; i < rf->width; i++) { req.bit[i] = htole16(cr203); if (val & (1 << (rf->width - 1 - i))) req.bit[i] |= htole16(ZYD_RF_DATA); } error = zyd_cmd(sc, ZYD_CMD_RFCFG, &req, 4 + 2 * rf->width, NULL, 0, 0); fail: return (error); } static int zyd_rfwrite_cr(struct zyd_softc *sc, uint32_t val) { int error; zyd_write16_m(sc, ZYD_CR244, (val >> 16) & 0xff); zyd_write16_m(sc, ZYD_CR243, (val >> 8) & 0xff); zyd_write16_m(sc, ZYD_CR242, (val >> 0) & 0xff); fail: return (error); } static int zyd_lock_phy(struct zyd_softc *sc) { int error; uint32_t tmp; zyd_read32_m(sc, ZYD_MAC_MISC, &tmp); tmp &= ~ZYD_UNLOCK_PHY_REGS; zyd_write32_m(sc, ZYD_MAC_MISC, tmp); fail: return (error); } static int zyd_unlock_phy(struct zyd_softc *sc) { int error; uint32_t tmp; zyd_read32_m(sc, ZYD_MAC_MISC, &tmp); tmp |= ZYD_UNLOCK_PHY_REGS; zyd_write32_m(sc, ZYD_MAC_MISC, tmp); fail: return (error); } /* * RFMD RF methods. */ static int zyd_rfmd_init(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini[] = ZYD_RFMD_PHY; static const uint32_t rfini[] = ZYD_RFMD_RF; int i, error; /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) { zyd_write16_m(sc, phyini[i].reg, phyini[i].val); } /* init RFMD radio */ for (i = 0; i < nitems(rfini); i++) { if ((error = zyd_rfwrite(sc, rfini[i])) != 0) return (error); } fail: return (error); } static int zyd_rfmd_switch_radio(struct zyd_rf *rf, int on) { int error; struct zyd_softc *sc = rf->rf_sc; zyd_write16_m(sc, ZYD_CR10, on ? 0x89 : 0x15); zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x81); fail: return (error); } static int zyd_rfmd_set_channel(struct zyd_rf *rf, uint8_t chan) { int error; struct zyd_softc *sc = rf->rf_sc; static const struct { uint32_t r1, r2; } rfprog[] = ZYD_RFMD_CHANTABLE; error = zyd_rfwrite(sc, rfprog[chan - 1].r1); if (error != 0) goto fail; error = zyd_rfwrite(sc, rfprog[chan - 1].r2); if (error != 0) goto fail; fail: return (error); } /* * AL2230 RF methods. */ static int zyd_al2230_init(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY; static const struct zyd_phy_pair phy2230s[] = ZYD_AL2230S_PHY_INIT; static const struct zyd_phy_pair phypll[] = { { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x3f }, { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 } }; static const uint32_t rfini1[] = ZYD_AL2230_RF_PART1; static const uint32_t rfini2[] = ZYD_AL2230_RF_PART2; static const uint32_t rfini3[] = ZYD_AL2230_RF_PART3; int i, error; /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) zyd_write16_m(sc, phyini[i].reg, phyini[i].val); if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) { for (i = 0; i < nitems(phy2230s); i++) zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val); } /* init AL2230 radio */ for (i = 0; i < nitems(rfini1); i++) { error = zyd_rfwrite(sc, rfini1[i]); if (error != 0) goto fail; } if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) error = zyd_rfwrite(sc, 0x000824); else error = zyd_rfwrite(sc, 0x0005a4); if (error != 0) goto fail; for (i = 0; i < nitems(rfini2); i++) { error = zyd_rfwrite(sc, rfini2[i]); if (error != 0) goto fail; } for (i = 0; i < nitems(phypll); i++) zyd_write16_m(sc, phypll[i].reg, phypll[i].val); for (i = 0; i < nitems(rfini3); i++) { error = zyd_rfwrite(sc, rfini3[i]); if (error != 0) goto fail; } fail: return (error); } static int zyd_al2230_fini(struct zyd_rf *rf) { int error, i; struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phy[] = ZYD_AL2230_PHY_FINI_PART1; for (i = 0; i < nitems(phy); i++) zyd_write16_m(sc, phy[i].reg, phy[i].val); if (sc->sc_newphy != 0) zyd_write16_m(sc, ZYD_CR9, 0xe1); zyd_write16_m(sc, ZYD_CR203, 0x6); fail: return (error); } static int zyd_al2230_init_b(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phy1[] = ZYD_AL2230_PHY_PART1; static const struct zyd_phy_pair phy2[] = ZYD_AL2230_PHY_PART2; static const struct zyd_phy_pair phy3[] = ZYD_AL2230_PHY_PART3; static const struct zyd_phy_pair phy2230s[] = ZYD_AL2230S_PHY_INIT; static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY_B; static const uint32_t rfini_part1[] = ZYD_AL2230_RF_B_PART1; static const uint32_t rfini_part2[] = ZYD_AL2230_RF_B_PART2; static const uint32_t rfini_part3[] = ZYD_AL2230_RF_B_PART3; static const uint32_t zyd_al2230_chtable[][3] = ZYD_AL2230_CHANTABLE; int i, error; for (i = 0; i < nitems(phy1); i++) zyd_write16_m(sc, phy1[i].reg, phy1[i].val); /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) zyd_write16_m(sc, phyini[i].reg, phyini[i].val); if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) { for (i = 0; i < nitems(phy2230s); i++) zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val); } for (i = 0; i < 3; i++) { error = zyd_rfwrite_cr(sc, zyd_al2230_chtable[0][i]); if (error != 0) return (error); } for (i = 0; i < nitems(rfini_part1); i++) { error = zyd_rfwrite_cr(sc, rfini_part1[i]); if (error != 0) return (error); } if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) error = zyd_rfwrite(sc, 0x241000); else error = zyd_rfwrite(sc, 0x25a000); if (error != 0) goto fail; for (i = 0; i < nitems(rfini_part2); i++) { error = zyd_rfwrite_cr(sc, rfini_part2[i]); if (error != 0) return (error); } for (i = 0; i < nitems(phy2); i++) zyd_write16_m(sc, phy2[i].reg, phy2[i].val); for (i = 0; i < nitems(rfini_part3); i++) { error = zyd_rfwrite_cr(sc, rfini_part3[i]); if (error != 0) return (error); } for (i = 0; i < nitems(phy3); i++) zyd_write16_m(sc, phy3[i].reg, phy3[i].val); error = zyd_al2230_fini(rf); fail: return (error); } static int zyd_al2230_switch_radio(struct zyd_rf *rf, int on) { struct zyd_softc *sc = rf->rf_sc; int error, on251 = (sc->sc_macrev == ZYD_ZD1211) ? 0x3f : 0x7f; zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); zyd_write16_m(sc, ZYD_CR251, on ? on251 : 0x2f); fail: return (error); } static int zyd_al2230_set_channel(struct zyd_rf *rf, uint8_t chan) { int error, i; struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phy1[] = { { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 }, }; static const struct { uint32_t r1, r2, r3; } rfprog[] = ZYD_AL2230_CHANTABLE; error = zyd_rfwrite(sc, rfprog[chan - 1].r1); if (error != 0) goto fail; error = zyd_rfwrite(sc, rfprog[chan - 1].r2); if (error != 0) goto fail; error = zyd_rfwrite(sc, rfprog[chan - 1].r3); if (error != 0) goto fail; for (i = 0; i < nitems(phy1); i++) zyd_write16_m(sc, phy1[i].reg, phy1[i].val); fail: return (error); } static int zyd_al2230_set_channel_b(struct zyd_rf *rf, uint8_t chan) { int error, i; struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phy1[] = ZYD_AL2230_PHY_PART1; static const struct { uint32_t r1, r2, r3; } rfprog[] = ZYD_AL2230_CHANTABLE_B; for (i = 0; i < nitems(phy1); i++) zyd_write16_m(sc, phy1[i].reg, phy1[i].val); error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r1); if (error != 0) goto fail; error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r2); if (error != 0) goto fail; error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r3); if (error != 0) goto fail; error = zyd_al2230_fini(rf); fail: return (error); } #define ZYD_AL2230_PHY_BANDEDGE6 \ { \ { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, \ { ZYD_CR47, 0x1e } \ } static int zyd_al2230_bandedge6(struct zyd_rf *rf, struct ieee80211_channel *c) { int error = 0, i; struct zyd_softc *sc = rf->rf_sc; struct ieee80211com *ic = &sc->sc_ic; struct zyd_phy_pair r[] = ZYD_AL2230_PHY_BANDEDGE6; int chan = ieee80211_chan2ieee(ic, c); if (chan == 1 || chan == 11) r[0].val = 0x12; for (i = 0; i < nitems(r); i++) zyd_write16_m(sc, r[i].reg, r[i].val); fail: return (error); } /* * AL7230B RF methods. */ static int zyd_al7230B_init(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini_1[] = ZYD_AL7230B_PHY_1; static const struct zyd_phy_pair phyini_2[] = ZYD_AL7230B_PHY_2; static const struct zyd_phy_pair phyini_3[] = ZYD_AL7230B_PHY_3; static const uint32_t rfini_1[] = ZYD_AL7230B_RF_1; static const uint32_t rfini_2[] = ZYD_AL7230B_RF_2; int i, error; /* for AL7230B, PHY and RF need to be initialized in "phases" */ /* init RF-dependent PHY registers, part one */ for (i = 0; i < nitems(phyini_1); i++) zyd_write16_m(sc, phyini_1[i].reg, phyini_1[i].val); /* init AL7230B radio, part one */ for (i = 0; i < nitems(rfini_1); i++) { if ((error = zyd_rfwrite(sc, rfini_1[i])) != 0) return (error); } /* init RF-dependent PHY registers, part two */ for (i = 0; i < nitems(phyini_2); i++) zyd_write16_m(sc, phyini_2[i].reg, phyini_2[i].val); /* init AL7230B radio, part two */ for (i = 0; i < nitems(rfini_2); i++) { if ((error = zyd_rfwrite(sc, rfini_2[i])) != 0) return (error); } /* init RF-dependent PHY registers, part three */ for (i = 0; i < nitems(phyini_3); i++) zyd_write16_m(sc, phyini_3[i].reg, phyini_3[i].val); fail: return (error); } static int zyd_al7230B_switch_radio(struct zyd_rf *rf, int on) { int error; struct zyd_softc *sc = rf->rf_sc; zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); zyd_write16_m(sc, ZYD_CR251, on ? 0x3f : 0x2f); fail: return (error); } static int zyd_al7230B_set_channel(struct zyd_rf *rf, uint8_t chan) { struct zyd_softc *sc = rf->rf_sc; static const struct { uint32_t r1, r2; } rfprog[] = ZYD_AL7230B_CHANTABLE; static const uint32_t rfsc[] = ZYD_AL7230B_RF_SETCHANNEL; int i, error; zyd_write16_m(sc, ZYD_CR240, 0x57); zyd_write16_m(sc, ZYD_CR251, 0x2f); for (i = 0; i < nitems(rfsc); i++) { if ((error = zyd_rfwrite(sc, rfsc[i])) != 0) return (error); } zyd_write16_m(sc, ZYD_CR128, 0x14); zyd_write16_m(sc, ZYD_CR129, 0x12); zyd_write16_m(sc, ZYD_CR130, 0x10); zyd_write16_m(sc, ZYD_CR38, 0x38); zyd_write16_m(sc, ZYD_CR136, 0xdf); error = zyd_rfwrite(sc, rfprog[chan - 1].r1); if (error != 0) goto fail; error = zyd_rfwrite(sc, rfprog[chan - 1].r2); if (error != 0) goto fail; error = zyd_rfwrite(sc, 0x3c9000); if (error != 0) goto fail; zyd_write16_m(sc, ZYD_CR251, 0x3f); zyd_write16_m(sc, ZYD_CR203, 0x06); zyd_write16_m(sc, ZYD_CR240, 0x08); fail: return (error); } /* * AL2210 RF methods. */ static int zyd_al2210_init(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini[] = ZYD_AL2210_PHY; static const uint32_t rfini[] = ZYD_AL2210_RF; uint32_t tmp; int i, error; zyd_write32_m(sc, ZYD_CR18, 2); /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) zyd_write16_m(sc, phyini[i].reg, phyini[i].val); /* init AL2210 radio */ for (i = 0; i < nitems(rfini); i++) { if ((error = zyd_rfwrite(sc, rfini[i])) != 0) return (error); } zyd_write16_m(sc, ZYD_CR47, 0x1e); zyd_read32_m(sc, ZYD_CR_RADIO_PD, &tmp); zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp & ~1); zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp | 1); zyd_write32_m(sc, ZYD_CR_RFCFG, 0x05); zyd_write32_m(sc, ZYD_CR_RFCFG, 0x00); zyd_write16_m(sc, ZYD_CR47, 0x1e); zyd_write32_m(sc, ZYD_CR18, 3); fail: return (error); } static int zyd_al2210_switch_radio(struct zyd_rf *rf, int on) { /* vendor driver does nothing for this RF chip */ return (0); } static int zyd_al2210_set_channel(struct zyd_rf *rf, uint8_t chan) { int error; struct zyd_softc *sc = rf->rf_sc; static const uint32_t rfprog[] = ZYD_AL2210_CHANTABLE; uint32_t tmp; zyd_write32_m(sc, ZYD_CR18, 2); zyd_write16_m(sc, ZYD_CR47, 0x1e); zyd_read32_m(sc, ZYD_CR_RADIO_PD, &tmp); zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp & ~1); zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp | 1); zyd_write32_m(sc, ZYD_CR_RFCFG, 0x05); zyd_write32_m(sc, ZYD_CR_RFCFG, 0x00); zyd_write16_m(sc, ZYD_CR47, 0x1e); /* actually set the channel */ error = zyd_rfwrite(sc, rfprog[chan - 1]); if (error != 0) goto fail; zyd_write32_m(sc, ZYD_CR18, 3); fail: return (error); } /* * GCT RF methods. */ static int zyd_gct_init(struct zyd_rf *rf) { #define ZYD_GCT_INTR_REG 0x85c1 struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini[] = ZYD_GCT_PHY; static const uint32_t rfini[] = ZYD_GCT_RF; static const uint16_t vco[11][7] = ZYD_GCT_VCO; int i, idx = -1, error; uint16_t data; /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) zyd_write16_m(sc, phyini[i].reg, phyini[i].val); /* init cgt radio */ for (i = 0; i < nitems(rfini); i++) { if ((error = zyd_rfwrite(sc, rfini[i])) != 0) return (error); } error = zyd_gct_mode(rf); if (error != 0) return (error); for (i = 0; i < (int)(nitems(vco) - 1); i++) { error = zyd_gct_set_channel_synth(rf, 1, 0); if (error != 0) goto fail; error = zyd_gct_write(rf, vco[i][0]); if (error != 0) goto fail; zyd_write16_m(sc, ZYD_GCT_INTR_REG, 0xf); zyd_read16_m(sc, ZYD_GCT_INTR_REG, &data); if ((data & 0xf) == 0) { idx = i; break; } } if (idx == -1) { error = zyd_gct_set_channel_synth(rf, 1, 1); if (error != 0) goto fail; error = zyd_gct_write(rf, 0x6662); if (error != 0) goto fail; } rf->idx = idx; zyd_write16_m(sc, ZYD_CR203, 0x6); fail: return (error); #undef ZYD_GCT_INTR_REG } static int zyd_gct_mode(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const uint32_t mode[] = { 0x25f98, 0x25f9a, 0x25f94, 0x27fd4 }; int i, error; for (i = 0; i < nitems(mode); i++) { if ((error = zyd_rfwrite(sc, mode[i])) != 0) break; } return (error); } static int zyd_gct_set_channel_synth(struct zyd_rf *rf, int chan, int acal) { int error, idx = chan - 1; struct zyd_softc *sc = rf->rf_sc; static uint32_t acal_synth[] = ZYD_GCT_CHANNEL_ACAL; static uint32_t std_synth[] = ZYD_GCT_CHANNEL_STD; static uint32_t div_synth[] = ZYD_GCT_CHANNEL_DIV; error = zyd_rfwrite(sc, (acal == 1) ? acal_synth[idx] : std_synth[idx]); if (error != 0) return (error); return zyd_rfwrite(sc, div_synth[idx]); } static int zyd_gct_write(struct zyd_rf *rf, uint16_t value) { struct zyd_softc *sc = rf->rf_sc; return zyd_rfwrite(sc, 0x300000 | 0x40000 | value); } static int zyd_gct_switch_radio(struct zyd_rf *rf, int on) { int error; struct zyd_softc *sc = rf->rf_sc; error = zyd_rfwrite(sc, on ? 0x25f94 : 0x25f90); if (error != 0) return (error); zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); zyd_write16_m(sc, ZYD_CR251, on ? ((sc->sc_macrev == ZYD_ZD1211B) ? 0x7f : 0x3f) : 0x2f); fail: return (error); } static int zyd_gct_set_channel(struct zyd_rf *rf, uint8_t chan) { int error, i; struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair cmd[] = { { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR79, 0x58 }, { ZYD_CR12, 0xf0 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x58 }, }; static const uint16_t vco[11][7] = ZYD_GCT_VCO; error = zyd_gct_set_channel_synth(rf, chan, 0); if (error != 0) goto fail; error = zyd_gct_write(rf, (rf->idx == -1) ? 0x6662 : vco[rf->idx][((chan - 1) / 2)]); if (error != 0) goto fail; error = zyd_gct_mode(rf); if (error != 0) return (error); for (i = 0; i < nitems(cmd); i++) zyd_write16_m(sc, cmd[i].reg, cmd[i].val); error = zyd_gct_txgain(rf, chan); if (error != 0) return (error); zyd_write16_m(sc, ZYD_CR203, 0x6); fail: return (error); } static int zyd_gct_txgain(struct zyd_rf *rf, uint8_t chan) { struct zyd_softc *sc = rf->rf_sc; static uint32_t txgain[] = ZYD_GCT_TXGAIN; uint8_t idx = sc->sc_pwrint[chan - 1]; if (idx >= nitems(txgain)) { device_printf(sc->sc_dev, "could not set TX gain (%d %#x)\n", chan, idx); return 0; } return zyd_rfwrite(sc, 0x700000 | txgain[idx]); } /* * Maxim2 RF methods. */ static int zyd_maxim2_init(struct zyd_rf *rf) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; static const uint32_t rfini[] = ZYD_MAXIM2_RF; uint16_t tmp; int i, error; /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) zyd_write16_m(sc, phyini[i].reg, phyini[i].val); zyd_read16_m(sc, ZYD_CR203, &tmp); zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); /* init maxim2 radio */ for (i = 0; i < nitems(rfini); i++) { if ((error = zyd_rfwrite(sc, rfini[i])) != 0) return (error); } zyd_read16_m(sc, ZYD_CR203, &tmp); zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); fail: return (error); } static int zyd_maxim2_switch_radio(struct zyd_rf *rf, int on) { /* vendor driver does nothing for this RF chip */ return (0); } static int zyd_maxim2_set_channel(struct zyd_rf *rf, uint8_t chan) { struct zyd_softc *sc = rf->rf_sc; static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; static const uint32_t rfini[] = ZYD_MAXIM2_RF; static const struct { uint32_t r1, r2; } rfprog[] = ZYD_MAXIM2_CHANTABLE; uint16_t tmp; int i, error; /* * Do the same as we do when initializing it, except for the channel * values coming from the two channel tables. */ /* init RF-dependent PHY registers */ for (i = 0; i < nitems(phyini); i++) zyd_write16_m(sc, phyini[i].reg, phyini[i].val); zyd_read16_m(sc, ZYD_CR203, &tmp); zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); /* first two values taken from the chantables */ error = zyd_rfwrite(sc, rfprog[chan - 1].r1); if (error != 0) goto fail; error = zyd_rfwrite(sc, rfprog[chan - 1].r2); if (error != 0) goto fail; /* init maxim2 radio - skipping the two first values */ for (i = 2; i < nitems(rfini); i++) { if ((error = zyd_rfwrite(sc, rfini[i])) != 0) return (error); } zyd_read16_m(sc, ZYD_CR203, &tmp); zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); fail: return (error); } static int zyd_rf_attach(struct zyd_softc *sc, uint8_t type) { struct zyd_rf *rf = &sc->sc_rf; rf->rf_sc = sc; rf->update_pwr = 1; switch (type) { case ZYD_RF_RFMD: rf->init = zyd_rfmd_init; rf->switch_radio = zyd_rfmd_switch_radio; rf->set_channel = zyd_rfmd_set_channel; rf->width = 24; /* 24-bit RF values */ break; case ZYD_RF_AL2230: case ZYD_RF_AL2230S: if (sc->sc_macrev == ZYD_ZD1211B) { rf->init = zyd_al2230_init_b; rf->set_channel = zyd_al2230_set_channel_b; } else { rf->init = zyd_al2230_init; rf->set_channel = zyd_al2230_set_channel; } rf->switch_radio = zyd_al2230_switch_radio; rf->bandedge6 = zyd_al2230_bandedge6; rf->width = 24; /* 24-bit RF values */ break; case ZYD_RF_AL7230B: rf->init = zyd_al7230B_init; rf->switch_radio = zyd_al7230B_switch_radio; rf->set_channel = zyd_al7230B_set_channel; rf->width = 24; /* 24-bit RF values */ break; case ZYD_RF_AL2210: rf->init = zyd_al2210_init; rf->switch_radio = zyd_al2210_switch_radio; rf->set_channel = zyd_al2210_set_channel; rf->width = 24; /* 24-bit RF values */ break; case ZYD_RF_MAXIM_NEW: case ZYD_RF_GCT: rf->init = zyd_gct_init; rf->switch_radio = zyd_gct_switch_radio; rf->set_channel = zyd_gct_set_channel; rf->width = 24; /* 24-bit RF values */ rf->update_pwr = 0; break; case ZYD_RF_MAXIM_NEW2: rf->init = zyd_maxim2_init; rf->switch_radio = zyd_maxim2_switch_radio; rf->set_channel = zyd_maxim2_set_channel; rf->width = 18; /* 18-bit RF values */ break; default: device_printf(sc->sc_dev, "sorry, radio \"%s\" is not supported yet\n", zyd_rf_name(type)); return (EINVAL); } return (0); } static const char * zyd_rf_name(uint8_t type) { static const char * const zyd_rfs[] = { "unknown", "unknown", "UW2451", "UCHIP", "AL2230", "AL7230B", "THETA", "AL2210", "MAXIM_NEW", "GCT", "AL2230S", "RALINK", "INTERSIL", "RFMD", "MAXIM_NEW2", "PHILIPS" }; return zyd_rfs[(type > 15) ? 0 : type]; } static int zyd_hw_init(struct zyd_softc *sc) { int error; const struct zyd_phy_pair *phyp; struct zyd_rf *rf = &sc->sc_rf; uint16_t val; /* specify that the plug and play is finished */ zyd_write32_m(sc, ZYD_MAC_AFTER_PNP, 1); zyd_read16_m(sc, ZYD_FIRMWARE_BASE_ADDR, &sc->sc_fwbase); DPRINTF(sc, ZYD_DEBUG_FW, "firmware base address=0x%04x\n", sc->sc_fwbase); /* retrieve firmware revision number */ zyd_read16_m(sc, sc->sc_fwbase + ZYD_FW_FIRMWARE_REV, &sc->sc_fwrev); zyd_write32_m(sc, ZYD_CR_GPI_EN, 0); zyd_write32_m(sc, ZYD_MAC_CONT_WIN_LIMIT, 0x7f043f); /* set mandatory rates - XXX assumes 802.11b/g */ zyd_write32_m(sc, ZYD_MAC_MAN_RATE, 0x150f); /* disable interrupts */ zyd_write32_m(sc, ZYD_CR_INTERRUPT, 0); if ((error = zyd_read_pod(sc)) != 0) { device_printf(sc->sc_dev, "could not read EEPROM\n"); goto fail; } /* PHY init (resetting) */ error = zyd_lock_phy(sc); if (error != 0) goto fail; phyp = (sc->sc_macrev == ZYD_ZD1211B) ? zyd_def_phyB : zyd_def_phy; for (; phyp->reg != 0; phyp++) zyd_write16_m(sc, phyp->reg, phyp->val); if (sc->sc_macrev == ZYD_ZD1211 && sc->sc_fix_cr157 != 0) { zyd_read16_m(sc, ZYD_EEPROM_PHY_REG, &val); zyd_write32_m(sc, ZYD_CR157, val >> 8); } error = zyd_unlock_phy(sc); if (error != 0) goto fail; /* HMAC init */ zyd_write32_m(sc, ZYD_MAC_ACK_EXT, 0x00000020); zyd_write32_m(sc, ZYD_CR_ADDA_MBIAS_WT, 0x30000808); zyd_write32_m(sc, ZYD_MAC_SNIFFER, 0x00000000); zyd_write32_m(sc, ZYD_MAC_RXFILTER, 0x00000000); zyd_write32_m(sc, ZYD_MAC_GHTBL, 0x00000000); zyd_write32_m(sc, ZYD_MAC_GHTBH, 0x80000000); zyd_write32_m(sc, ZYD_MAC_MISC, 0x000000a4); zyd_write32_m(sc, ZYD_CR_ADDA_PWR_DWN, 0x0000007f); zyd_write32_m(sc, ZYD_MAC_BCNCFG, 0x00f00401); zyd_write32_m(sc, ZYD_MAC_PHY_DELAY2, 0x00000000); zyd_write32_m(sc, ZYD_MAC_ACK_EXT, 0x00000080); zyd_write32_m(sc, ZYD_CR_ADDA_PWR_DWN, 0x00000000); zyd_write32_m(sc, ZYD_MAC_SIFS_ACK_TIME, 0x00000100); zyd_write32_m(sc, ZYD_CR_RX_PE_DELAY, 0x00000070); zyd_write32_m(sc, ZYD_CR_PS_CTRL, 0x10000000); zyd_write32_m(sc, ZYD_MAC_RTSCTSRATE, 0x02030203); zyd_write32_m(sc, ZYD_MAC_AFTER_PNP, 1); zyd_write32_m(sc, ZYD_MAC_BACKOFF_PROTECT, 0x00000114); zyd_write32_m(sc, ZYD_MAC_DIFS_EIFS_SIFS, 0x0a47c032); zyd_write32_m(sc, ZYD_MAC_CAM_MODE, 0x3); if (sc->sc_macrev == ZYD_ZD1211) { zyd_write32_m(sc, ZYD_MAC_RETRY, 0x00000002); zyd_write32_m(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0640); } else { zyd_write32_m(sc, ZYD_MACB_MAX_RETRY, 0x02020202); zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL4, 0x007f003f); zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL3, 0x007f003f); zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL2, 0x003f001f); zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL1, 0x001f000f); zyd_write32_m(sc, ZYD_MACB_AIFS_CTL1, 0x00280028); zyd_write32_m(sc, ZYD_MACB_AIFS_CTL2, 0x008C003C); zyd_write32_m(sc, ZYD_MACB_TXOP, 0x01800824); zyd_write32_m(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0eff); } /* init beacon interval to 100ms */ if ((error = zyd_set_beacon_interval(sc, 100)) != 0) goto fail; if ((error = zyd_rf_attach(sc, sc->sc_rfrev)) != 0) { device_printf(sc->sc_dev, "could not attach RF, rev 0x%x\n", sc->sc_rfrev); goto fail; } /* RF chip init */ error = zyd_lock_phy(sc); if (error != 0) goto fail; error = (*rf->init)(rf); if (error != 0) { device_printf(sc->sc_dev, "radio initialization failed, error %d\n", error); goto fail; } error = zyd_unlock_phy(sc); if (error != 0) goto fail; if ((error = zyd_read_eeprom(sc)) != 0) { device_printf(sc->sc_dev, "could not read EEPROM\n"); goto fail; } fail: return (error); } static int zyd_read_pod(struct zyd_softc *sc) { int error; uint32_t tmp; zyd_read32_m(sc, ZYD_EEPROM_POD, &tmp); sc->sc_rfrev = tmp & 0x0f; sc->sc_ledtype = (tmp >> 4) & 0x01; sc->sc_al2230s = (tmp >> 7) & 0x01; sc->sc_cckgain = (tmp >> 8) & 0x01; sc->sc_fix_cr157 = (tmp >> 13) & 0x01; sc->sc_parev = (tmp >> 16) & 0x0f; sc->sc_bandedge6 = (tmp >> 21) & 0x01; sc->sc_newphy = (tmp >> 31) & 0x01; sc->sc_txled = ((tmp & (1 << 24)) && (tmp & (1 << 29))) ? 0 : 1; fail: return (error); } static int zyd_read_eeprom(struct zyd_softc *sc) { uint16_t val; int error, i; /* read Tx power calibration tables */ for (i = 0; i < 7; i++) { zyd_read16_m(sc, ZYD_EEPROM_PWR_CAL + i, &val); sc->sc_pwrcal[i * 2] = val >> 8; sc->sc_pwrcal[i * 2 + 1] = val & 0xff; zyd_read16_m(sc, ZYD_EEPROM_PWR_INT + i, &val); sc->sc_pwrint[i * 2] = val >> 8; sc->sc_pwrint[i * 2 + 1] = val & 0xff; zyd_read16_m(sc, ZYD_EEPROM_36M_CAL + i, &val); sc->sc_ofdm36_cal[i * 2] = val >> 8; sc->sc_ofdm36_cal[i * 2 + 1] = val & 0xff; zyd_read16_m(sc, ZYD_EEPROM_48M_CAL + i, &val); sc->sc_ofdm48_cal[i * 2] = val >> 8; sc->sc_ofdm48_cal[i * 2 + 1] = val & 0xff; zyd_read16_m(sc, ZYD_EEPROM_54M_CAL + i, &val); sc->sc_ofdm54_cal[i * 2] = val >> 8; sc->sc_ofdm54_cal[i * 2 + 1] = val & 0xff; } fail: return (error); } static int zyd_get_macaddr(struct zyd_softc *sc) { struct usb_device_request req; usb_error_t error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = ZYD_READFWDATAREQ; USETW(req.wValue, ZYD_EEPROM_MAC_ADDR_P1); USETW(req.wIndex, 0); USETW(req.wLength, IEEE80211_ADDR_LEN); error = zyd_do_request(sc, &req, sc->sc_ic.ic_macaddr); if (error != 0) { device_printf(sc->sc_dev, "could not read EEPROM: %s\n", usbd_errstr(error)); } return (error); } static int zyd_set_macaddr(struct zyd_softc *sc, const uint8_t *addr) { int error; uint32_t tmp; tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; zyd_write32_m(sc, ZYD_MAC_MACADRL, tmp); tmp = addr[5] << 8 | addr[4]; zyd_write32_m(sc, ZYD_MAC_MACADRH, tmp); fail: return (error); } static int zyd_set_bssid(struct zyd_softc *sc, const uint8_t *addr) { int error; uint32_t tmp; tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; zyd_write32_m(sc, ZYD_MAC_BSSADRL, tmp); tmp = addr[5] << 8 | addr[4]; zyd_write32_m(sc, ZYD_MAC_BSSADRH, tmp); fail: return (error); } static int zyd_switch_radio(struct zyd_softc *sc, int on) { struct zyd_rf *rf = &sc->sc_rf; int error; error = zyd_lock_phy(sc); if (error != 0) goto fail; error = (*rf->switch_radio)(rf, on); if (error != 0) goto fail; error = zyd_unlock_phy(sc); fail: return (error); } static int zyd_set_led(struct zyd_softc *sc, int which, int on) { int error; uint32_t tmp; zyd_read32_m(sc, ZYD_MAC_TX_PE_CONTROL, &tmp); tmp &= ~which; if (on) tmp |= which; zyd_write32_m(sc, ZYD_MAC_TX_PE_CONTROL, tmp); fail: return (error); } static u_int zyd_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t *hash = arg; uint8_t v; v = ((uint8_t *)LLADDR(sdl))[5] >> 2; if (v < 32) hash[0] |= 1 << v; else hash[1] |= 1 << (v - 32); return (1); } static void zyd_set_multi(struct zyd_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t hash[2]; int error; if ((sc->sc_flags & ZYD_FLAG_RUNNING) == 0) return; hash[0] = 0x00000000; hash[1] = 0x80000000; if (ic->ic_opmode == IEEE80211_M_MONITOR || ic->ic_allmulti > 0 || ic->ic_promisc > 0) { hash[0] = 0xffffffff; hash[1] = 0xffffffff; } else { struct ieee80211vap *vap; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if_foreach_llmaddr(vap->iv_ifp, zyd_hash_maddr, &hash); } /* reprogram multicast global hash table */ zyd_write32_m(sc, ZYD_MAC_GHTBL, hash[0]); zyd_write32_m(sc, ZYD_MAC_GHTBH, hash[1]); fail: if (error != 0) device_printf(sc->sc_dev, "could not set multicast hash table\n"); } static void zyd_update_mcast(struct ieee80211com *ic) { struct zyd_softc *sc = ic->ic_softc; ZYD_LOCK(sc); zyd_set_multi(sc); ZYD_UNLOCK(sc); } static int zyd_set_rxfilter(struct zyd_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t rxfilter; switch (ic->ic_opmode) { case IEEE80211_M_STA: rxfilter = ZYD_FILTER_BSS; break; case IEEE80211_M_IBSS: case IEEE80211_M_HOSTAP: rxfilter = ZYD_FILTER_HOSTAP; break; case IEEE80211_M_MONITOR: rxfilter = ZYD_FILTER_MONITOR; break; default: /* should not get there */ return (EINVAL); } return zyd_write32(sc, ZYD_MAC_RXFILTER, rxfilter); } static void zyd_set_chan(struct zyd_softc *sc, struct ieee80211_channel *c) { int error; struct ieee80211com *ic = &sc->sc_ic; struct zyd_rf *rf = &sc->sc_rf; uint32_t tmp; int chan; chan = ieee80211_chan2ieee(ic, c); if (chan == 0 || chan == IEEE80211_CHAN_ANY) { /* XXX should NEVER happen */ device_printf(sc->sc_dev, "%s: invalid channel %x\n", __func__, chan); return; } error = zyd_lock_phy(sc); if (error != 0) goto fail; error = (*rf->set_channel)(rf, chan); if (error != 0) goto fail; if (rf->update_pwr) { /* update Tx power */ zyd_write16_m(sc, ZYD_CR31, sc->sc_pwrint[chan - 1]); if (sc->sc_macrev == ZYD_ZD1211B) { zyd_write16_m(sc, ZYD_CR67, sc->sc_ofdm36_cal[chan - 1]); zyd_write16_m(sc, ZYD_CR66, sc->sc_ofdm48_cal[chan - 1]); zyd_write16_m(sc, ZYD_CR65, sc->sc_ofdm54_cal[chan - 1]); zyd_write16_m(sc, ZYD_CR68, sc->sc_pwrcal[chan - 1]); zyd_write16_m(sc, ZYD_CR69, 0x28); zyd_write16_m(sc, ZYD_CR69, 0x2a); } } if (sc->sc_cckgain) { /* set CCK baseband gain from EEPROM */ if (zyd_read32(sc, ZYD_EEPROM_PHY_REG, &tmp) == 0) zyd_write16_m(sc, ZYD_CR47, tmp & 0xff); } if (sc->sc_bandedge6 && rf->bandedge6 != NULL) { error = (*rf->bandedge6)(rf, c); if (error != 0) goto fail; } zyd_write32_m(sc, ZYD_CR_CONFIG_PHILIPS, 0); error = zyd_unlock_phy(sc); if (error != 0) goto fail; sc->sc_rxtap.wr_chan_freq = sc->sc_txtap.wt_chan_freq = htole16(c->ic_freq); sc->sc_rxtap.wr_chan_flags = sc->sc_txtap.wt_chan_flags = htole16(c->ic_flags); fail: return; } static int zyd_set_beacon_interval(struct zyd_softc *sc, int bintval) { int error; uint32_t val; zyd_read32_m(sc, ZYD_CR_ATIM_WND_PERIOD, &val); sc->sc_atim_wnd = val; zyd_read32_m(sc, ZYD_CR_PRE_TBTT, &val); sc->sc_pre_tbtt = val; sc->sc_bcn_int = bintval; if (sc->sc_bcn_int <= 5) sc->sc_bcn_int = 5; if (sc->sc_pre_tbtt < 4 || sc->sc_pre_tbtt >= sc->sc_bcn_int) sc->sc_pre_tbtt = sc->sc_bcn_int - 1; if (sc->sc_atim_wnd >= sc->sc_pre_tbtt) sc->sc_atim_wnd = sc->sc_pre_tbtt - 1; zyd_write32_m(sc, ZYD_CR_ATIM_WND_PERIOD, sc->sc_atim_wnd); zyd_write32_m(sc, ZYD_CR_PRE_TBTT, sc->sc_pre_tbtt); zyd_write32_m(sc, ZYD_CR_BCN_INTERVAL, sc->sc_bcn_int); fail: return (error); } static void zyd_rx_data(struct usb_xfer *xfer, int offset, uint16_t len) { struct zyd_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct zyd_plcphdr plcp; struct zyd_rx_stat stat; struct usb_page_cache *pc; struct mbuf *m; int rlen, rssi; if (len < ZYD_MIN_FRAGSZ) { DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too short (length=%d)\n", device_get_nameunit(sc->sc_dev), len); counter_u64_add(ic->ic_ierrors, 1); return; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, offset, &plcp, sizeof(plcp)); usbd_copy_out(pc, offset + len - sizeof(stat), &stat, sizeof(stat)); if (stat.flags & ZYD_RX_ERROR) { DPRINTF(sc, ZYD_DEBUG_RECV, "%s: RX status indicated error (%x)\n", device_get_nameunit(sc->sc_dev), stat.flags); counter_u64_add(ic->ic_ierrors, 1); return; } /* compute actual frame length */ rlen = len - sizeof(struct zyd_plcphdr) - sizeof(struct zyd_rx_stat) - IEEE80211_CRC_LEN; /* allocate a mbuf to store the frame */ if (rlen > (int)MCLBYTES) { DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too long (length=%d)\n", device_get_nameunit(sc->sc_dev), rlen); counter_u64_add(ic->ic_ierrors, 1); return; } else if (rlen > (int)MHLEN) m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); else m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) { DPRINTF(sc, ZYD_DEBUG_RECV, "%s: could not allocate rx mbuf\n", device_get_nameunit(sc->sc_dev)); counter_u64_add(ic->ic_ierrors, 1); return; } m->m_pkthdr.len = m->m_len = rlen; usbd_copy_out(pc, offset + sizeof(plcp), mtod(m, uint8_t *), rlen); if (ieee80211_radiotap_active(ic)) { struct zyd_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; if (stat.flags & (ZYD_RX_BADCRC16 | ZYD_RX_BADCRC32)) tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; /* XXX toss, no way to express errors */ if (stat.flags & ZYD_RX_DECRYPTERR) tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; tap->wr_rate = ieee80211_plcp2rate(plcp.signal, (stat.flags & ZYD_RX_OFDM) ? IEEE80211_T_OFDM : IEEE80211_T_CCK); tap->wr_antsignal = stat.rssi + -95; tap->wr_antnoise = -95; /* XXX */ } rssi = (stat.rssi > 63) ? 127 : 2 * stat.rssi; sc->sc_rx_data[sc->sc_rx_count].rssi = rssi; sc->sc_rx_data[sc->sc_rx_count].m = m; sc->sc_rx_count++; } static void zyd_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct zyd_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; struct epoch_tracker et; struct zyd_rx_desc desc; struct mbuf *m; struct usb_page_cache *pc; uint32_t offset; uint8_t rssi; int8_t nf; int i; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); sc->sc_rx_count = 0; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, actlen - sizeof(desc), &desc, sizeof(desc)); offset = 0; if (UGETW(desc.tag) == ZYD_TAG_MULTIFRAME) { DPRINTF(sc, ZYD_DEBUG_RECV, "%s: received multi-frame transfer\n", __func__); for (i = 0; i < ZYD_MAX_RXFRAMECNT; i++) { uint16_t len16 = UGETW(desc.len[i]); if (len16 == 0 || len16 > actlen) break; zyd_rx_data(xfer, offset, len16); /* next frame is aligned on a 32-bit boundary */ len16 = (len16 + 3) & ~3; offset += len16; if (len16 > actlen) break; actlen -= len16; } } else { DPRINTF(sc, ZYD_DEBUG_RECV, "%s: received single-frame transfer\n", __func__); zyd_rx_data(xfer, 0, actlen); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * At the end of a USB callback it is always safe to unlock * the private mutex of a device! That is why we do the * "ieee80211_input" here, and not some lines up! */ ZYD_UNLOCK(sc); NET_EPOCH_ENTER(et); for (i = 0; i < sc->sc_rx_count; i++) { rssi = sc->sc_rx_data[i].rssi; m = sc->sc_rx_data[i].m; sc->sc_rx_data[i].m = NULL; nf = -95; /* XXX */ ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); if (ni != NULL) { (void)ieee80211_input(ni, m, rssi, nf); ieee80211_free_node(ni); } else (void)ieee80211_input_all(ic, m, rssi, nf); } NET_EPOCH_EXIT(et); ZYD_LOCK(sc); zyd_start(sc); break; default: /* Error */ DPRINTF(sc, ZYD_DEBUG_ANY, "frame error: %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static uint8_t zyd_plcp_signal(struct zyd_softc *sc, int rate) { switch (rate) { /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return (0xb); case 18: return (0xf); case 24: return (0xa); case 36: return (0xe); case 48: return (0x9); case 72: return (0xd); case 96: return (0x8); case 108: return (0xc); /* CCK rates (NB: not IEEE std, device-specific) */ case 2: return (0x0); case 4: return (0x1); case 11: return (0x2); case 22: return (0x3); } device_printf(sc->sc_dev, "unsupported rate %d\n", rate); return (0x0); } static void zyd_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct zyd_softc *sc = usbd_xfer_softc(xfer); struct ieee80211vap *vap; struct zyd_tx_data *data; struct mbuf *m; struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF(sc, ZYD_DEBUG_ANY, "transfer complete, %u bytes\n", actlen); /* free resources */ data = usbd_xfer_get_priv(xfer); zyd_tx_free(data, 0); usbd_xfer_set_priv(xfer, NULL); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->tx_q); if (data) { STAILQ_REMOVE_HEAD(&sc->tx_q, next); m = data->m; if (m->m_pkthdr.len > (int)ZYD_MAX_TXBUFSZ) { DPRINTF(sc, ZYD_DEBUG_ANY, "data overflow, %u bytes\n", m->m_pkthdr.len); m->m_pkthdr.len = ZYD_MAX_TXBUFSZ; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &data->desc, ZYD_TX_DESC_SIZE); usbd_m_copy_in(pc, ZYD_TX_DESC_SIZE, m, 0, m->m_pkthdr.len); vap = data->ni->ni_vap; if (ieee80211_radiotap_active_vap(vap)) { struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = data->rate; ieee80211_radiotap_tx(vap, m); } usbd_xfer_set_frame_len(xfer, 0, ZYD_TX_DESC_SIZE + m->m_pkthdr.len); usbd_xfer_set_priv(xfer, data); usbd_transfer_submit(xfer); } zyd_start(sc); break; default: /* Error */ DPRINTF(sc, ZYD_DEBUG_ANY, "transfer error, %s\n", usbd_errstr(error)); counter_u64_add(sc->sc_ic.ic_oerrors, 1); data = usbd_xfer_get_priv(xfer); usbd_xfer_set_priv(xfer, NULL); if (data != NULL) zyd_tx_free(data, error); if (error != USB_ERR_CANCELLED) { if (error == USB_ERR_TIMEOUT) device_printf(sc->sc_dev, "device timeout\n"); /* * Try to clear stall first, also if other * errors occur, hence clearing stall * introduces a 50 ms delay: */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int zyd_tx_start(struct zyd_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct zyd_tx_desc *desc; struct zyd_tx_data *data; struct ieee80211_frame *wh; const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211_key *k; int rate, totlen, type, ismcast; static const uint8_t ratediv[] = ZYD_TX_RATEDIV; uint8_t phy; uint16_t pktlen; uint32_t bits; wh = mtod(m0, struct ieee80211_frame *); data = STAILQ_FIRST(&sc->tx_free); STAILQ_REMOVE_HEAD(&sc->tx_free, next); sc->tx_nfree--; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; if (type == IEEE80211_FC0_TYPE_MGT || type == IEEE80211_FC0_TYPE_CTL || (m0->m_flags & M_EAPOL) != 0) { rate = tp->mgmtrate; } else { /* for data frames */ if (ismcast) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else { (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; } } if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { return (ENOBUFS); } /* packet header may have moved, reset our local pointer */ wh = mtod(m0, struct ieee80211_frame *); } data->ni = ni; data->m = m0; data->rate = rate; /* fill Tx descriptor */ desc = &data->desc; phy = zyd_plcp_signal(sc, rate); desc->phy = phy; if (ZYD_RATE_IS_OFDM(rate)) { desc->phy |= ZYD_TX_PHY_OFDM; if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) desc->phy |= ZYD_TX_PHY_5GHZ; } else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) desc->phy |= ZYD_TX_PHY_SHPREAMBLE; totlen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; desc->len = htole16(totlen); desc->flags = ZYD_TX_FLAG_BACKOFF; if (!ismcast) { /* multicast frames are not sent at OFDM rates in 802.11b/g */ if (totlen > vap->iv_rtsthreshold) { desc->flags |= ZYD_TX_FLAG_RTS; } else if (ZYD_RATE_IS_OFDM(rate) && (ic->ic_flags & IEEE80211_F_USEPROT)) { if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) desc->flags |= ZYD_TX_FLAG_CTS_TO_SELF; else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) desc->flags |= ZYD_TX_FLAG_RTS; } } else desc->flags |= ZYD_TX_FLAG_MULTICAST; if ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_PS_POLL)) desc->flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); /* actual transmit length (XXX why +10?) */ pktlen = ZYD_TX_DESC_SIZE + 10; if (sc->sc_macrev == ZYD_ZD1211) pktlen += totlen; desc->pktlen = htole16(pktlen); bits = (rate == 11) ? (totlen * 16) + 10 : ((rate == 22) ? (totlen * 8) + 10 : (totlen * 8)); desc->plcp_length = htole16(bits / ratediv[phy]); desc->plcp_service = 0; if (rate == 22 && (bits % 11) > 0 && (bits % 11) <= 3) desc->plcp_service |= ZYD_PLCP_LENGEXT; desc->nextlen = 0; if (ieee80211_radiotap_active_vap(vap)) { struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; ieee80211_radiotap_tx(vap, m0); } DPRINTF(sc, ZYD_DEBUG_XMIT, "%s: sending data frame len=%zu rate=%u\n", device_get_nameunit(sc->sc_dev), (size_t)m0->m_pkthdr.len, rate); STAILQ_INSERT_TAIL(&sc->tx_q, data, next); usbd_transfer_start(sc->sc_xfer[ZYD_BULK_WR]); return (0); } static int zyd_transmit(struct ieee80211com *ic, struct mbuf *m) { struct zyd_softc *sc = ic->ic_softc; int error; ZYD_LOCK(sc); if ((sc->sc_flags & ZYD_FLAG_RUNNING) == 0) { ZYD_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { ZYD_UNLOCK(sc); return (error); } zyd_start(sc); ZYD_UNLOCK(sc); return (0); } static void zyd_start(struct zyd_softc *sc) { struct ieee80211_node *ni; struct mbuf *m; ZYD_LOCK_ASSERT(sc, MA_OWNED); while (sc->tx_nfree > 0 && (m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; if (zyd_tx_start(sc, m, ni) != 0) { m_freem(m); if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); break; } } } static int zyd_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct zyd_softc *sc = ic->ic_softc; ZYD_LOCK(sc); /* prevent management frames from being sent if we're not ready */ if (!(sc->sc_flags & ZYD_FLAG_RUNNING)) { ZYD_UNLOCK(sc); m_freem(m); return (ENETDOWN); } if (sc->tx_nfree == 0) { ZYD_UNLOCK(sc); m_freem(m); return (ENOBUFS); /* XXX */ } /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. * XXX raw path */ if (zyd_tx_start(sc, m, ni) != 0) { ZYD_UNLOCK(sc); m_freem(m); return (EIO); } ZYD_UNLOCK(sc); return (0); } static void zyd_parent(struct ieee80211com *ic) { struct zyd_softc *sc = ic->ic_softc; int startall = 0; ZYD_LOCK(sc); if (sc->sc_flags & ZYD_FLAG_DETACHED) { ZYD_UNLOCK(sc); return; } if (ic->ic_nrunning > 0) { if ((sc->sc_flags & ZYD_FLAG_RUNNING) == 0) { zyd_init_locked(sc); startall = 1; } else zyd_set_multi(sc); } else if (sc->sc_flags & ZYD_FLAG_RUNNING) zyd_stop(sc); ZYD_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static void zyd_init_locked(struct zyd_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct usb_config_descriptor *cd; int error; uint32_t val; ZYD_LOCK_ASSERT(sc, MA_OWNED); if (!(sc->sc_flags & ZYD_FLAG_INITONCE)) { error = zyd_loadfirmware(sc); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware (error=%d)\n", error); goto fail; } /* reset device */ cd = usbd_get_config_descriptor(sc->sc_udev); error = usbd_req_set_config(sc->sc_udev, &sc->sc_mtx, cd->bConfigurationValue); if (error) device_printf(sc->sc_dev, "reset failed, continuing\n"); error = zyd_hw_init(sc); if (error) { device_printf(sc->sc_dev, "hardware initialization failed\n"); goto fail; } device_printf(sc->sc_dev, "HMAC ZD1211%s, FW %02x.%02x, RF %s S%x, PA%x LED %x " "BE%x NP%x Gain%x F%x\n", (sc->sc_macrev == ZYD_ZD1211) ? "": "B", sc->sc_fwrev >> 8, sc->sc_fwrev & 0xff, zyd_rf_name(sc->sc_rfrev), sc->sc_al2230s, sc->sc_parev, sc->sc_ledtype, sc->sc_bandedge6, sc->sc_newphy, sc->sc_cckgain, sc->sc_fix_cr157); /* read regulatory domain (currently unused) */ zyd_read32_m(sc, ZYD_EEPROM_SUBID, &val); sc->sc_regdomain = val >> 16; DPRINTF(sc, ZYD_DEBUG_INIT, "regulatory domain %x\n", sc->sc_regdomain); /* we'll do software WEP decryption for now */ DPRINTF(sc, ZYD_DEBUG_INIT, "%s: setting encryption type\n", __func__); zyd_write32_m(sc, ZYD_MAC_ENCRYPTION_TYPE, ZYD_ENC_SNIFFER); sc->sc_flags |= ZYD_FLAG_INITONCE; } if (sc->sc_flags & ZYD_FLAG_RUNNING) zyd_stop(sc); DPRINTF(sc, ZYD_DEBUG_INIT, "setting MAC address to %6D\n", vap ? vap->iv_myaddr : ic->ic_macaddr, ":"); error = zyd_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr); if (error != 0) return; /* set basic rates */ if (ic->ic_curmode == IEEE80211_MODE_11B) zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0x0003); else if (ic->ic_curmode == IEEE80211_MODE_11A) zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0x1500); else /* assumes 802.11b/g */ zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0xff0f); /* promiscuous mode */ zyd_write32_m(sc, ZYD_MAC_SNIFFER, 0); /* multicast setup */ zyd_set_multi(sc); /* set RX filter */ error = zyd_set_rxfilter(sc); if (error != 0) goto fail; /* switch radio transmitter ON */ error = zyd_switch_radio(sc, 1); if (error != 0) goto fail; /* set default BSS channel */ zyd_set_chan(sc, ic->ic_curchan); /* * Allocate Tx and Rx xfer queues. */ zyd_setup_tx_list(sc); /* enable interrupts */ zyd_write32_m(sc, ZYD_CR_INTERRUPT, ZYD_HWINT_MASK); sc->sc_flags |= ZYD_FLAG_RUNNING; usbd_xfer_set_stall(sc->sc_xfer[ZYD_BULK_WR]); usbd_transfer_start(sc->sc_xfer[ZYD_BULK_RD]); usbd_transfer_start(sc->sc_xfer[ZYD_INTR_RD]); return; fail: zyd_stop(sc); return; } static void zyd_stop(struct zyd_softc *sc) { int error; ZYD_LOCK_ASSERT(sc, MA_OWNED); sc->sc_flags &= ~ZYD_FLAG_RUNNING; zyd_drain_mbufq(sc); /* * Drain all the transfers, if not already drained: */ ZYD_UNLOCK(sc); usbd_transfer_drain(sc->sc_xfer[ZYD_BULK_WR]); usbd_transfer_drain(sc->sc_xfer[ZYD_BULK_RD]); ZYD_LOCK(sc); zyd_unsetup_tx_list(sc); /* Stop now if the device was never set up */ if (!(sc->sc_flags & ZYD_FLAG_INITONCE)) return; /* switch radio transmitter OFF */ error = zyd_switch_radio(sc, 0); if (error != 0) goto fail; /* disable Rx */ zyd_write32_m(sc, ZYD_MAC_RXFILTER, 0); /* disable interrupts */ zyd_write32_m(sc, ZYD_CR_INTERRUPT, 0); fail: return; } static int zyd_loadfirmware(struct zyd_softc *sc) { struct usb_device_request req; size_t size; u_char *fw; uint8_t stat; uint16_t addr; if (sc->sc_flags & ZYD_FLAG_FWLOADED) return (0); if (sc->sc_macrev == ZYD_ZD1211) { fw = (u_char *)zd1211_firmware; size = sizeof(zd1211_firmware); } else { fw = (u_char *)zd1211b_firmware; size = sizeof(zd1211b_firmware); } req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = ZYD_DOWNLOADREQ; USETW(req.wIndex, 0); addr = ZYD_FIRMWARE_START_ADDR; while (size > 0) { /* * When the transfer size is 4096 bytes, it is not * likely to be able to transfer it. * The cause is port or machine or chip? */ const int mlen = min(size, 64); DPRINTF(sc, ZYD_DEBUG_FW, "loading firmware block: len=%d, addr=0x%x\n", mlen, addr); USETW(req.wValue, addr); USETW(req.wLength, mlen); if (zyd_do_request(sc, &req, fw) != 0) return (EIO); addr += mlen / 2; fw += mlen; size -= mlen; } /* check whether the upload succeeded */ req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = ZYD_DOWNLOADSTS; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, sizeof(stat)); if (zyd_do_request(sc, &req, &stat) != 0) return (EIO); sc->sc_flags |= ZYD_FLAG_FWLOADED; return (stat & 0x80) ? (EIO) : (0); } static void zyd_scan_start(struct ieee80211com *ic) { struct zyd_softc *sc = ic->ic_softc; ZYD_LOCK(sc); /* want broadcast address while scanning */ zyd_set_bssid(sc, ieee80211broadcastaddr); ZYD_UNLOCK(sc); } static void zyd_scan_end(struct ieee80211com *ic) { struct zyd_softc *sc = ic->ic_softc; ZYD_LOCK(sc); /* restore previous bssid */ zyd_set_bssid(sc, sc->sc_bssid); ZYD_UNLOCK(sc); } static void zyd_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { uint8_t bands[IEEE80211_MODE_BYTES]; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0); } static void zyd_set_channel(struct ieee80211com *ic) { struct zyd_softc *sc = ic->ic_softc; ZYD_LOCK(sc); zyd_set_chan(sc, ic->ic_curchan); ZYD_UNLOCK(sc); } static device_method_t zyd_methods[] = { /* Device interface */ DEVMETHOD(device_probe, zyd_match), DEVMETHOD(device_attach, zyd_attach), DEVMETHOD(device_detach, zyd_detach), DEVMETHOD_END }; static driver_t zyd_driver = { .name = "zyd", .methods = zyd_methods, .size = sizeof(struct zyd_softc) }; static devclass_t zyd_devclass; DRIVER_MODULE(zyd, uhub, zyd_driver, zyd_devclass, NULL, 0); MODULE_DEPEND(zyd, usb, 1, 1, 1); MODULE_DEPEND(zyd, wlan, 1, 1, 1); MODULE_VERSION(zyd, 1); USB_PNP_HOST_INFO(zyd_devs);