Re: [alsa-devel] Help requested: new HSS1394 MIDI back-end
Sean M. Pappalardo - D.J. Pegasus wrote:
On 05/30/2012 06:51 AM, Clemens Ladisch wrote:
Sean M. Pappalardo - D.J. Pegasus wrote:
four-byte platter messages that start with 0xF9 and the remaining three can range from 0x00 to 0xFF.
This is not MIDI. Into what MIDI messages should the driver convert this?
It shouldn't convert it at all. Just deliver it as-is in a single four-byte message, if possible.
The problem is the communication between the driver and userspace; _that_ must be valid MIDI.
0xF9 is a valid, if undefined, MIDI status.
Undefined means invalid. Furthermore, data bytes must not have the eighth bit set.
I will also need the ability to use the HSS1394 SendUserControl function to set the device's timers, and that is definitely not MIDI at all. I wonder if it would be a good idea to define a special SYSEX message that the SCS1x driver would interpret and convert to a call to that function?
As it happens, the actual SysEx commands use the wrong manufacturer ID ("00 01 02" is Crystal Semiconductor); I could just use the real ID (Stanton is "00 01 60") to escape non-MIDI HSS1394 messages. Let's add "HSS" to identify this, and to allow the full byte range, each HSS1394 byte is split into two nibbles. So:
00 F9 xx yy zz -> F0 00 01 60 48 53 53 00 00 0F 09 0x 0x 0y 0y 0z 0z F7 13 xx yy zz <- F0 00 01 60 48 53 53 01 03 0x 0x 0y 0y 0z 0z F7 14 xx yy zz <- F0 00 01 60 48 53 53 01 04 0x 0x 0y 0y 0z 0z F7 (0x13 = 0x10 + 3 = kUserTagBase + uUserTag)
Regards, Clemens
I wrote:
00 F9 xx yy zz -> F0 00 01 60 48 53 53 00 00 0F 09 0x 0x 0y 0y 0z 0z F7 13 xx yy zz <- F0 00 01 60 48 53 53 01 03 0x 0x 0y 0y 0z 0z F7 14 xx yy zz <- F0 00 01 60 48 53 53 01 04 0x 0x 0y 0y 0z 0z F7
New patch below.
Regards, Clemens
--- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -33,4 +33,17 @@ config SND_ISIGHT To compile this driver as a module, choose M here: the module will be called snd-isight.
+config SND_SCS1X + tristate "Stanton Control System 1 MIDI" + select SND_PCM + select SND_RAWMIDI + select SND_FIREWIRE_LIB + help + Say Y here to include support for the MIDI ports of the Stanton + SCS.1d/SCS.1m DJ controllers. (SCS.1m audio is still handled + by FFADO.) + + To compile this driver as a module, choose M here: the module + will be called snd-scs1x. + endif # SND_FIREWIRE --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -2,7 +2,9 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.o snd-firewire-speakers-objs := speakers.o snd-isight-objs := isight.o +snd-scs1x-objs := scs1x.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o --- /dev/null +++ b/sound/firewire/scs1x.c @@ -0,0 +1,498 @@ +/* + * Stanton Control System 1 MIDI driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#include "lib.h" + +#define OUI_STANTON 0x001260 +#define MODEL_SCS_1M 0x001000 +#define MODEL_SCS_1D 0x002000 + +#define HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 + +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 + +struct scs { + struct snd_card *card; + struct fw_unit *unit; + struct fw_address_handler hss_handler; + struct fw_transaction transaction; + bool transaction_running; + bool output_idle; + u8 midi_status; + u8 midi_bytes; + bool output_escaped; + bool escape_high_nibble; + struct snd_rawmidi_substream *output; + struct snd_rawmidi_substream *input; + struct tasklet_struct tasklet; + wait_queue_head_t idle_wait; + u8 *buffer; +}; + +static const u8 sysex_escape_prefix[] = { + 0xf0, /* SysEx begin */ + 0x00, 0x01, 0x60, /* Stanton DJ */ + 0x48, 0x53, 0x53, /* "HSS" */ +}; + +static int scs_output_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->midi_status = 0; + scs->midi_bytes = 1; + scs->output_escaped = false; + + return 0; +} + +static int scs_output_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void scs_output_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct scs *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->output) = up ? stream : NULL; + if (up) { + scs->output_idle = false; + tasklet_schedule(&scs->tasklet); + } +} + +static void scs_write_callback(struct fw_card *card, int rcode, + void *data, size_t length, void *callback_data) +{ + struct scs *scs = callback_data; + + if (rcode == RCODE_GENERATION) { + /* TODO: retry this packet */ + } + + scs->transaction_running = false; + tasklet_schedule(&scs->tasklet); +} + +static bool is_valid_running_status(u8 status) +{ + return status >= 0x80 && status <= 0xef; +} + +static bool is_one_byte_cmd(u8 status) +{ + return status == 0xf6 || + status >= 0xf8; +} + +static bool is_two_bytes_cmd(u8 status) +{ + return (status >= 0xc0 && status <= 0xdf) || + status == 0xf1 || + status == 0xf3; +} + +static bool is_three_bytes_cmd(u8 status) +{ + return (status >= 0x80 && status <= 0xbf) || + (status >= 0xe0 && status <= 0xef) || + status == 0xf2; +} + +static bool is_invalid_cmd(u8 status) +{ + return status == 0xf4 || + status == 0xf5 || + status == 0xf9 || + status == 0xfd; +} + +static void scs_output_tasklet(unsigned long data) +{ + struct scs *scs = (void *)data; + struct snd_rawmidi_substream *stream; + unsigned int i; + u8 byte; + struct fw_device *dev; + int generation; + + if (scs->transaction_running) + return; + + stream = ACCESS_ONCE(scs->output); + if (!stream) { + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + + i = scs->midi_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->midi_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what the device expects (no + * running status, one command per packet, unescaped SysExs). + */ + if (scs->output_escaped && byte < 0x80) { + if (scs->escape_high_nibble) { + if (i < HSS1394_MAX_PACKET_SIZE) { + scs->buffer[i] = byte << 4; + scs->escape_high_nibble = false; + } + } else { + scs->buffer[i++] |= byte & 0x0f; + scs->escape_high_nibble = true; + } + } else if (byte < 0x80) { + if (i == 1) { + if (!is_valid_running_status(scs->midi_status)) + continue; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = scs->midi_status; + } + scs->buffer[i++] = byte; + if ((i == 3 && is_two_bytes_cmd(scs->midi_status)) || + (i == 4 && is_three_bytes_cmd(scs->midi_status))) + break; + if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) && + !memcmp(scs->buffer + 1, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix))) { + scs->output_escaped = true; + scs->escape_high_nibble = true; + i = 0; + } + if (i >= HSS1394_MAX_PACKET_SIZE) + i = 1; + } else if (byte == 0xf7) { + if (scs->output_escaped) { + if (i >= 1 && scs->escape_high_nibble && + scs->buffer[0] != HSS1394_TAG_CHANGE_ADDRESS) + break; + } else { + if (i > 1 && scs->midi_status == 0xf0) { + scs->buffer[i++] = 0xf7; + break; + } + } + i = 1; + scs->output_escaped = false; + } else if (!is_invalid_cmd(byte) && + byte < 0xf8) { + i = 1; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = byte; + scs->midi_status = byte; + scs->output_escaped = false; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->midi_bytes = 1; + scs->output_escaped = false; + + scs->transaction_running = true; + dev = fw_parent_device(scs->unit); + generation = dev->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(dev->card, &scs->transaction, TCODE_WRITE_BLOCK_REQUEST, + dev->node_id, generation, dev->max_speed, + HSS1394_ADDRESS, scs->buffer, i, + scs_write_callback, scs); +} + +static void scs_output_drain(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + wait_event(scs->idle_wait, scs->output_idle); +} + +static struct snd_rawmidi_ops output_ops = { + .open = scs_output_open, + .close = scs_output_close, + .trigger = scs_output_trigger, + .drain = scs_output_drain, +}; + +static int scs_input_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int scs_input_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void scs_input_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct scs *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->input) = up ? stream : NULL; +} + +static void scs_input_packet(struct snd_rawmidi_substream *stream, + const u8 *data, unsigned int bytes) +{ + unsigned int i; + u8 nibbles[2]; + + if (data[0] == HSS1394_TAG_USER_DATA && bytes >= 2 && + data[1] >= 0x80 && !is_invalid_cmd(data[1])) { + snd_rawmidi_receive(stream, data + 1, bytes - 1); + } else { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + for (i = 0; i < bytes; ++i) { + nibbles[0] = data[i] >> 4; + nibbles[1] = data[i] & 0x0f; + snd_rawmidi_receive(stream, nibbles, 2); + } + snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1); + } +} + +static struct snd_rawmidi_ops input_ops = { + .open = scs_input_open, + .close = scs_input_close, + .trigger = scs_input_trigger, +}; + +static int scs_create_midi(struct scs *scs) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(scs->card, "SCS.1x", 0, 1, 1, &rmidi); + if (err < 0) + return err; + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", scs->card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = scs; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &input_ops); + + return 0; +} + +static void handle_hss(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct scs *scs = callback_data; + struct snd_rawmidi_substream *stream; + + if (offset != scs->hss_handler.offset) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + if (tcode != TCODE_WRITE_QUADLET_REQUEST && + tcode != TCODE_WRITE_BLOCK_REQUEST) { + fw_send_response(card, request, RCODE_TYPE_ERROR); + return; + } + + if (length >= 1) { + stream = ACCESS_ONCE(scs->input); + if (stream) + scs_input_packet(stream, data, length); + } + + fw_send_response(card, request, RCODE_COMPLETE); +} + +static int scs_init_hss_address(struct scs *scs) +{ + u8 data[8]; + int err; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + err = snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); + if (err < 0) + dev_err(&scs->unit->device, "HSS1394 communication failed\n"); + + return err; +} + +static void scs_card_free(struct snd_card *card) +{ + struct scs *scs = card->private_data; + + fw_core_remove_address_handler(&scs->hss_handler); + kfree(scs->buffer); +} + +static int scs_probe(struct device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct scs *scs; + int err; + + err = snd_card_create(-16, NULL, THIS_MODULE, sizeof(*scs), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + scs = card->private_data; + scs->card = card; + scs->unit = unit; + tasklet_init(&scs->tasklet, scs_output_tasklet, (unsigned long)scs); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + + scs->buffer = kmalloc(HSS1394_MAX_PACKET_SIZE, GFP_KERNEL); + if (!scs->buffer) + goto err_card; + + scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; + scs->hss_handler.address_callback = handle_hss; + scs->hss_handler.callback_data = scs; + err = fw_core_add_address_handler(&scs->hss_handler, + &fw_high_memory_region); + if (err < 0) + goto err_buffer; + + card->private_free = scs_card_free; + + strcpy(card->driver, "SCS.1x"); + strcpy(card->shortname, "SCS.1x"); + fw_csr_string(unit->directory, CSR_MODEL, + card->shortname, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "Stanton DJ %s (GUID %08x%08x) at %s, S%d", + card->shortname, fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, card->shortname); + + err = scs_init_hss_address(scs); + if (err < 0) + goto err_card; + + err = scs_create_midi(scs); + if (err < 0) + goto err_card; + + err = snd_card_register(card); + if (err < 0) + goto err_card; + + dev_set_drvdata(unit_dev, scs); + + return 0; + +err_buffer: + kfree(scs->buffer); +err_card: + snd_card_free(card); + return err; +} + +static int scs_remove(struct device *dev) +{ + struct scs *scs = dev_get_drvdata(dev); + + snd_card_disconnect(scs->card); + + ACCESS_ONCE(scs->output) = NULL; + ACCESS_ONCE(scs->input) = NULL; + + wait_event(scs->idle_wait, scs->output_idle); + + tasklet_kill(&scs->tasklet); + + snd_card_free_when_closed(scs->card); + + return 0; +} + +static void scs_update(struct fw_unit *unit) +{ + struct scs *scs = dev_get_drvdata(&unit->device); + int generation; + u8 data[8]; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + generation = fw_parent_device(unit)->generation; + smp_rmb(); /* node_id vs. generation */ + snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); +} + +static const struct ieee1394_device_id scs_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = MODEL_SCS_1M, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = MODEL_SCS_1D, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, scs_id_table); + +MODULE_DESCRIPTION("SCS.1x MIDI driver"); +MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); +MODULE_LICENSE("GPL v2"); + +static struct fw_driver scs_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + .probe = scs_probe, + .remove = scs_remove, + }, + .update = scs_update, + .id_table = scs_id_table, +}; + +static int __init alsa_scs1x_init(void) +{ + return driver_register(&scs_driver.driver); +} + +static void __exit alsa_scs1x_exit(void) +{ + driver_unregister(&scs_driver.driver); +} + +module_init(alsa_scs1x_init); +module_exit(alsa_scs1x_exit);
Hello again.
Sorry for the delay. I've been quite busy in the last week.
On 05/31/2012 10:00 PM, Clemens Ladisch wrote:
I wrote:
00 F9 xx yy zz -> F0 00 01 60 48 53 53 00 00 0F 09 0x 0x 0y 0y 0z 0z F7
This appears to work fine. But where did you get the leading 00 from? libhss1394 just sends F9 xx yy zz.
13 xx yy zz<- F0 00 01 60 48 53 53 01 03 0x 0x 0y 0y 0z 0z F7 14 xx yy zz<- F0 00 01 60 48 53 53 01 04 0x 0x 0y 0y 0z 0z F7 (0x13 = 0x10 + 3 = kUserTagBase + uUserTag)
I'm not clear on how you arrived at that formula. HSS1394.h shows:
//! Send a user control message via the channel to the target //! node. Returns the number of bytes sent on success, zero on //! failure. uUserTag is valid in the range 0x00 - 0xDF. All other //! tag values will be rejected (0 return). virtual uint SendUserControl(uint8 uUserTag, const uint8 *pUserData, uint uDataBytes) = 0;
It appears to work with arbitrary-length user data strings, though the Stanton docs only specify 3-byte ones at this time.
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
On 05/31/2012 10:00 PM, Clemens Ladisch wrote:
I wrote:
00 F9 xx yy zz -> F0 00 01 60 48 53 53 00 00 0F 09 0x 0x 0y 0y 0z 0z F7
This appears to work fine. But where did you get the leading 00 from?
This is how the FireWire packets are constructed. The first byte is a tag; 00 means that the rest of the packet contains MIDI data, ...
13 xx yy zz<- F0 00 01 60 48 53 53 01 03 0x 0x 0y 0y 0z 0z F7 14 xx yy zz<- F0 00 01 60 48 53 53 01 04 0x 0x 0y 0y 0z 0z F7 (0x13 = 0x10 + 3 = kUserTagBase + uUserTag)
... while tags in the range 10..EF are "user tags".
When you use this function:
// uUserTag is valid in the range 0x00 - 0xDF. virtual uint SendUserControl(uint8 uUserTag, const uint8 *pUserData, uint uDataBytes) = 0;
libhss1394 will add 0x10 to the tag.
I just modified Mixxx's SCS.1d script to work with your custom sysex messages and tested with your latest patch. The .1d doesn't actually work 100% with this since Mixxx never receives some of the standard MIDI messages amid all of the sysex ones, and it's much worse when the platter is spinning and sending the tons of messages it does. This could be due to PortMIDI's lack of a callback mechanism, forcing Mixxx to poll it every 1ms because it's much more difficult to reproduce the problem using amidi -d.
That's possible. However, at 4 KB, ALSA's buffer should be big enough for those messages.
I also see an F9 message slip through once in awhile according to amidi -d: F9 55 91 C4
The driver currently assumes that there is exactly one MIDI message per packet. It appears that this is not true, and if F9 and real MIDI commands are mixed in one packet, the result is not correct.
As soon as I start the JACK server to handle the audio interface on the SCS.1m, the ALSA driver stops sending MIDI for that device unless I stop JACK and power-cycle the device. Its last words are: F0 00 01 60 48 53 53 0F 03 00 03 00 00 0B 01 F7
This is a ping response (tag F3). Apparently, Jack's FFADO driver reinitializes the device and registers its own address for received packets. Since it does _not_ handle MIDI messages, it really should not do this.
Regards, Clemens
On 06/09/2012 01:07 PM, Clemens Ladisch wrote:
I just modified Mixxx's SCS.1d script to work with your custom sysex messages and tested with your latest patch. The .1d doesn't actually work 100% with this since Mixxx never receives some of the standard MIDI messages amid all of the sysex ones, and it's much worse when the platter is spinning and sending the tons of messages it does. This could be due to PortMIDI's lack of a callback mechanism, forcing Mixxx to poll it every 1ms because it's much more difficult to reproduce the problem using amidi -d.
That's possible. However, at 4 KB, ALSA's buffer should be big enough for those messages.
Yes, I subsequently found out that it was a bug in Mixxx's PortMIDI code. I've corrected that and it works much better now. (Nothing like a demanding device to ferret out bugs, eh?)
As soon as I start the JACK server to handle the audio interface on the SCS.1m, the ALSA driver stops sending MIDI for that device unless I stop JACK and power-cycle the device. Its last words are: F0 00 01 60 48 53 53 0F 03 00 03 00 00 0B 01 F7
This is a ping response (tag F3). Apparently, Jack's FFADO driver reinitializes the device and registers its own address for received packets. Since it does _not_ handle MIDI messages, it really should not do this.
I think the test-scs program uses JACK-MIDI, so that might be why. But test-scs is obsoleted by your driver. I'm CC'ing the FFADO-devel list so they can take a look.
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
I wrote:
Sean M. Pappalardo - D.J. Pegasus wrote:
I also see an F9 message slip through once in awhile according to amidi -d:
The driver currently assumes that there is exactly one MIDI message per packet. It appears that this is not true, and if F9 and real MIDI commands are mixed in one packet, the result is not correct.
New patch below.
Regards, Clemens
--- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -33,4 +33,17 @@ config SND_ISIGHT To compile this driver as a module, choose M here: the module will be called snd-isight.
+config SND_SCS1X + tristate "Stanton Control System 1 MIDI" + select SND_PCM + select SND_RAWMIDI + select SND_FIREWIRE_LIB + help + Say Y here to include support for the MIDI ports of the Stanton + SCS.1d/SCS.1m DJ controllers. (SCS.1m audio is still handled + by FFADO.) + + To compile this driver as a module, choose M here: the module + will be called snd-scs1x. + endif # SND_FIREWIRE --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -2,7 +2,9 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.o snd-firewire-speakers-objs := speakers.o snd-isight-objs := isight.o +snd-scs1x-objs := scs1x.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o --- /dev/null +++ b/sound/firewire/scs1x.c @@ -0,0 +1,527 @@ +/* + * Stanton Control System 1 MIDI driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#include "lib.h" + +#define OUI_STANTON 0x001260 +#define MODEL_SCS_1M 0x001000 +#define MODEL_SCS_1D 0x002000 + +#define HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 + +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 + +struct scs { + struct snd_card *card; + struct fw_unit *unit; + struct fw_address_handler hss_handler; + struct fw_transaction transaction; + bool transaction_running; + bool output_idle; + u8 output_status; + u8 output_bytes; + bool output_escaped; + bool output_escape_high_nibble; + u8 input_escape_count; + struct snd_rawmidi_substream *output; + struct snd_rawmidi_substream *input; + struct tasklet_struct tasklet; + wait_queue_head_t idle_wait; + u8 *buffer; +}; + +static const u8 sysex_escape_prefix[] = { + 0xf0, /* SysEx begin */ + 0x00, 0x01, 0x60, /* Stanton DJ */ + 0x48, 0x53, 0x53, /* "HSS" */ +}; + +static int scs_output_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->output_status = 0; + scs->output_bytes = 1; + scs->output_escaped = false; + + return 0; +} + +static int scs_output_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void scs_output_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct scs *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->output) = up ? stream : NULL; + if (up) { + scs->output_idle = false; + tasklet_schedule(&scs->tasklet); + } +} + +static void scs_write_callback(struct fw_card *card, int rcode, + void *data, size_t length, void *callback_data) +{ + struct scs *scs = callback_data; + + if (rcode == RCODE_GENERATION) { + /* TODO: retry this packet */ + } + + scs->transaction_running = false; + tasklet_schedule(&scs->tasklet); +} + +static bool is_valid_running_status(u8 status) +{ + return status >= 0x80 && status <= 0xef; +} + +static bool is_one_byte_cmd(u8 status) +{ + return status == 0xf6 || + status >= 0xf8; +} + +static bool is_two_bytes_cmd(u8 status) +{ + return (status >= 0xc0 && status <= 0xdf) || + status == 0xf1 || + status == 0xf3; +} + +static bool is_three_bytes_cmd(u8 status) +{ + return (status >= 0x80 && status <= 0xbf) || + (status >= 0xe0 && status <= 0xef) || + status == 0xf2; +} + +static bool is_invalid_cmd(u8 status) +{ + return status == 0xf4 || + status == 0xf5 || + status == 0xf9 || + status == 0xfd; +} + +static void scs_output_tasklet(unsigned long data) +{ + struct scs *scs = (void *)data; + struct snd_rawmidi_substream *stream; + unsigned int i; + u8 byte; + struct fw_device *dev; + int generation; + + if (scs->transaction_running) + return; + + stream = ACCESS_ONCE(scs->output); + if (!stream) { + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + + i = scs->output_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->output_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what I think the device expects (no + * running status, one command per packet, unescaped SysExs). + */ + if (scs->output_escaped && byte < 0x80) { + if (scs->output_escape_high_nibble) { + if (i < HSS1394_MAX_PACKET_SIZE) { + scs->buffer[i] = byte << 4; + scs->output_escape_high_nibble = false; + } + } else { + scs->buffer[i++] |= byte & 0x0f; + scs->output_escape_high_nibble = true; + } + } else if (byte < 0x80) { + if (i == 1) { + if (!is_valid_running_status(scs->output_status)) + continue; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = scs->output_status; + } + scs->buffer[i++] = byte; + if ((i == 3 && is_two_bytes_cmd(scs->output_status)) || + (i == 4 && is_three_bytes_cmd(scs->output_status))) + break; + if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) && + !memcmp(scs->buffer + 1, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix))) { + scs->output_escaped = true; + scs->output_escape_high_nibble = true; + i = 0; + } + if (i >= HSS1394_MAX_PACKET_SIZE) + i = 1; + } else if (byte == 0xf7) { + if (scs->output_escaped) { + if (i >= 1 && scs->output_escape_high_nibble && + scs->buffer[0] != HSS1394_TAG_CHANGE_ADDRESS) + break; + } else { + if (i > 1 && scs->output_status == 0xf0) { + scs->buffer[i++] = 0xf7; + break; + } + } + i = 1; + scs->output_escaped = false; + } else if (!is_invalid_cmd(byte) && + byte < 0xf8) { + i = 1; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = byte; + scs->output_status = byte; + scs->output_escaped = false; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->output_bytes = 1; + scs->output_escaped = false; + + scs->transaction_running = true; + dev = fw_parent_device(scs->unit); + generation = dev->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(dev->card, &scs->transaction, TCODE_WRITE_BLOCK_REQUEST, + dev->node_id, generation, dev->max_speed, + HSS1394_ADDRESS, scs->buffer, i, + scs_write_callback, scs); +} + +static void scs_output_drain(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + wait_event(scs->idle_wait, scs->output_idle); +} + +static struct snd_rawmidi_ops output_ops = { + .open = scs_output_open, + .close = scs_output_close, + .trigger = scs_output_trigger, + .drain = scs_output_drain, +}; + +static int scs_input_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->input_escape_count = 0; + + return 0; +} + +static int scs_input_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void scs_input_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct scs *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->input) = up ? stream : NULL; +} + +static void scs_input_escaped_byte(struct snd_rawmidi_substream *stream, + u8 byte) +{ + u8 nibbles[2]; + + nibbles[0] = byte >> 4; + nibbles[1] = byte & 0x0f; + snd_rawmidi_receive(stream, nibbles, 2); +} + +static void scs_input_midi_byte(struct scs *scs, + struct snd_rawmidi_substream *stream, + u8 byte) +{ + if (scs->input_escape_count > 0) { + scs_input_escaped_byte(stream, byte); + scs->input_escape_count--; + if (scs->input_escape_count == 0) + snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1); + } else if (byte == 0xf9) { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + scs_input_escaped_byte(stream, 0x00); + scs_input_escaped_byte(stream, 0xf9); + scs->input_escape_count = 3; + } else { + snd_rawmidi_receive(stream, &byte, 1); + } +} + +static void scs_input_packet(struct scs *scs, + struct snd_rawmidi_substream *stream, + const u8 *data, unsigned int bytes) +{ + unsigned int i; + + if (data[0] == HSS1394_TAG_USER_DATA) { + for (i = 0; i < bytes; ++i) + scs_input_midi_byte(scs, stream, data[i]); + } else { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + for (i = 0; i < bytes; ++i) + scs_input_escaped_byte(stream, data[i]); + snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1); + } +} + +static struct snd_rawmidi_ops input_ops = { + .open = scs_input_open, + .close = scs_input_close, + .trigger = scs_input_trigger, +}; + +static int scs_create_midi(struct scs *scs) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(scs->card, "SCS.1x", 0, 1, 1, &rmidi); + if (err < 0) + return err; + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", scs->card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = scs; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &input_ops); + + return 0; +} + +static void handle_hss(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct scs *scs = callback_data; + struct snd_rawmidi_substream *stream; + + if (offset != scs->hss_handler.offset) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + if (tcode != TCODE_WRITE_QUADLET_REQUEST && + tcode != TCODE_WRITE_BLOCK_REQUEST) { + fw_send_response(card, request, RCODE_TYPE_ERROR); + return; + } + + if (length >= 1) { + stream = ACCESS_ONCE(scs->input); + if (stream) + scs_input_packet(scs, stream, data, length); + } + + fw_send_response(card, request, RCODE_COMPLETE); +} + +static int scs_init_hss_address(struct scs *scs) +{ + u8 data[8]; + int err; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + err = snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); + if (err < 0) + dev_err(&scs->unit->device, "HSS1394 communication failed\n"); + + return err; +} + +static void scs_card_free(struct snd_card *card) +{ + struct scs *scs = card->private_data; + + fw_core_remove_address_handler(&scs->hss_handler); + kfree(scs->buffer); +} + +static int scs_probe(struct device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct scs *scs; + int err; + + err = snd_card_create(-16, NULL, THIS_MODULE, sizeof(*scs), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + scs = card->private_data; + scs->card = card; + scs->unit = unit; + tasklet_init(&scs->tasklet, scs_output_tasklet, (unsigned long)scs); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + + scs->buffer = kmalloc(HSS1394_MAX_PACKET_SIZE, GFP_KERNEL); + if (!scs->buffer) + goto err_card; + + scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; + scs->hss_handler.address_callback = handle_hss; + scs->hss_handler.callback_data = scs; + err = fw_core_add_address_handler(&scs->hss_handler, + &fw_high_memory_region); + if (err < 0) + goto err_buffer; + + card->private_free = scs_card_free; + + strcpy(card->driver, "SCS.1x"); + strcpy(card->shortname, "SCS.1x"); + fw_csr_string(unit->directory, CSR_MODEL, + card->shortname, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "Stanton DJ %s (GUID %08x%08x) at %s, S%d", + card->shortname, fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, card->shortname); + + err = scs_init_hss_address(scs); + if (err < 0) + goto err_card; + + err = scs_create_midi(scs); + if (err < 0) + goto err_card; + + err = snd_card_register(card); + if (err < 0) + goto err_card; + + dev_set_drvdata(unit_dev, scs); + + return 0; + +err_buffer: + kfree(scs->buffer); +err_card: + snd_card_free(card); + return err; +} + +static int scs_remove(struct device *dev) +{ + struct scs *scs = dev_get_drvdata(dev); + + snd_card_disconnect(scs->card); + + ACCESS_ONCE(scs->output) = NULL; + ACCESS_ONCE(scs->input) = NULL; + + wait_event(scs->idle_wait, scs->output_idle); + + tasklet_kill(&scs->tasklet); + + snd_card_free_when_closed(scs->card); + + return 0; +} + +static void scs_update(struct fw_unit *unit) +{ + struct scs *scs = dev_get_drvdata(&unit->device); + u8 data[8]; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); +} + +static const struct ieee1394_device_id scs_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = MODEL_SCS_1M, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = MODEL_SCS_1D, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, scs_id_table); + +MODULE_DESCRIPTION("SCS.1x MIDI driver"); +MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); +MODULE_LICENSE("GPL v2"); + +static struct fw_driver scs_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + .probe = scs_probe, + .remove = scs_remove, + }, + .update = scs_update, + .id_table = scs_id_table, +}; + +static int __init alsa_scs1x_init(void) +{ + return driver_register(&scs_driver.driver); +} + +static void __exit alsa_scs1x_exit(void) +{ + driver_unregister(&scs_driver.driver); +} + +module_init(alsa_scs1x_init); +module_exit(alsa_scs1x_exit);
Hello again.
I'm very sorry for my absence. As usual with works of passion, life has gotten in the way the past few months.
On 06/10/2012 03:00 PM, Clemens Ladisch wrote:
New patch below.
This one seems to work perfectly with Mixxx! However, the amidi dump still shows an extra 00 byte after every message:
F0 00 01 60 48 53 53 00 00 0F 09 02 0C 09 0A 05 0C F7 00 F0 00 01 60 48 53 53 00 00 0F 09 03 00 08 03 05 0C F7 00 F0 00 01 60 48 53 53 00 00 0F 09 03 04 06 0D 05 0C F7 00 80 20 00 00 F0 00 01 60 48 53 53 00 00 0F 09 03 08 05 06 05 0C F7 00 F0 00 01 60 48 53 53 00 00 0F 09 03 0C 03 0F 05 0C F7 00 90 20 0D 00 F0 00 01 60 48 53 53 00 00 0F 09 04 00 02 09 05 0C F7 00 F0 00 01 60 48 53 53 00 00 0F 09 04 04 01 02 05 0C F7 00 F0 00 01 60 48 53 53 00 00 0F 09 04 07 0F 0B 05 0C F7 00
We should probably fix that.
BTW, which ALSA version (and Linux kernel version) did the last patch go into, if any?
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
the amidi dump still shows an extra 00 byte after every message
Silly off-by-one error. New patch below.
BTW, which ALSA version (and Linux kernel version) did the last patch go into, if any?
I was waiting for your Tested-by ...
Regards, Clemens
sound/firewire/Kconfig | 13 ++ sound/firewire/Makefile | 2 + sound/firewire/scs1x.c | 531 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 546 insertions(+), 0 deletions(-) create mode 100644 sound/firewire/scs1x.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 2607148..ea063e1 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -33,4 +33,17 @@ config SND_ISIGHT To compile this driver as a module, choose M here: the module will be called snd-isight.
+config SND_SCS1X + tristate "Stanton Control System 1 MIDI" + select SND_PCM + select SND_RAWMIDI + select SND_FIREWIRE_LIB + help + Say Y here to include support for the MIDI ports of the Stanton + SCS.1d/SCS.1m DJ controllers. (SCS.1m audio is still handled + by FFADO.) + + To compile this driver as a module, choose M here: the module + will be called snd-scs1x. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index d71ed89..460179d 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -2,7 +2,9 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.o snd-firewire-speakers-objs := speakers.o snd-isight-objs := isight.o +snd-scs1x-objs := scs1x.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o diff --git a/sound/firewire/scs1x.c b/sound/firewire/scs1x.c new file mode 100644 index 0000000..40b4798 --- /dev/null +++ b/sound/firewire/scs1x.c @@ -0,0 +1,531 @@ +/* + * Stanton Control System 1 MIDI driver + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#include "lib.h" + +#define OUI_STANTON 0x001260 +#define MODEL_SCS_1M 0x001000 +#define MODEL_SCS_1D 0x002000 + +#define HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 + +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 + +struct scs { + struct snd_card *card; + struct fw_unit *unit; + struct fw_address_handler hss_handler; + struct fw_transaction transaction; + bool transaction_running; + bool output_idle; + u8 output_status; + u8 output_bytes; + bool output_escaped; + bool output_escape_high_nibble; + u8 input_escape_count; + struct snd_rawmidi_substream *output; + struct snd_rawmidi_substream *input; + struct tasklet_struct tasklet; + wait_queue_head_t idle_wait; + u8 *buffer; +}; + +static const u8 sysex_escape_prefix[] = { + 0xf0, /* SysEx begin */ + 0x00, 0x01, 0x60, /* Stanton DJ */ + 0x48, 0x53, 0x53, /* "HSS" */ +}; + +static int scs_output_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->output_status = 0; + scs->output_bytes = 1; + scs->output_escaped = false; + + return 0; +} + +static int scs_output_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void scs_output_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct scs *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->output) = up ? stream : NULL; + if (up) { + scs->output_idle = false; + tasklet_schedule(&scs->tasklet); + } +} + +static void scs_write_callback(struct fw_card *card, int rcode, + void *data, size_t length, void *callback_data) +{ + struct scs *scs = callback_data; + + if (rcode == RCODE_GENERATION) { + /* TODO: retry this packet */ + } + + scs->transaction_running = false; + tasklet_schedule(&scs->tasklet); +} + +static bool is_valid_running_status(u8 status) +{ + return status >= 0x80 && status <= 0xef; +} + +static bool is_one_byte_cmd(u8 status) +{ + return status == 0xf6 || + status >= 0xf8; +} + +static bool is_two_bytes_cmd(u8 status) +{ + return (status >= 0xc0 && status <= 0xdf) || + status == 0xf1 || + status == 0xf3; +} + +static bool is_three_bytes_cmd(u8 status) +{ + return (status >= 0x80 && status <= 0xbf) || + (status >= 0xe0 && status <= 0xef) || + status == 0xf2; +} + +static bool is_invalid_cmd(u8 status) +{ + return status == 0xf4 || + status == 0xf5 || + status == 0xf9 || + status == 0xfd; +} + +static void scs_output_tasklet(unsigned long data) +{ + struct scs *scs = (void *)data; + struct snd_rawmidi_substream *stream; + unsigned int i; + u8 byte; + struct fw_device *dev; + int generation; + + if (scs->transaction_running) + return; + + stream = ACCESS_ONCE(scs->output); + if (!stream) { + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + + i = scs->output_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->output_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what I think the device expects (no + * running status, one command per packet, unescaped SysExs). + */ + if (scs->output_escaped && byte < 0x80) { + if (scs->output_escape_high_nibble) { + if (i < HSS1394_MAX_PACKET_SIZE) { + scs->buffer[i] = byte << 4; + scs->output_escape_high_nibble = false; + } + } else { + scs->buffer[i++] |= byte & 0x0f; + scs->output_escape_high_nibble = true; + } + } else if (byte < 0x80) { + if (i == 1) { + if (!is_valid_running_status(scs->output_status)) + continue; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = scs->output_status; + } + scs->buffer[i++] = byte; + if ((i == 3 && is_two_bytes_cmd(scs->output_status)) || + (i == 4 && is_three_bytes_cmd(scs->output_status))) + break; + if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) && + !memcmp(scs->buffer + 1, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix))) { + scs->output_escaped = true; + scs->output_escape_high_nibble = true; + i = 0; + } + if (i >= HSS1394_MAX_PACKET_SIZE) + i = 1; + } else if (byte == 0xf7) { + if (scs->output_escaped) { + if (i >= 1 && scs->output_escape_high_nibble && + scs->buffer[0] != HSS1394_TAG_CHANGE_ADDRESS) + break; + } else { + if (i > 1 && scs->output_status == 0xf0) { + scs->buffer[i++] = 0xf7; + break; + } + } + i = 1; + scs->output_escaped = false; + } else if (!is_invalid_cmd(byte) && + byte < 0xf8) { + i = 1; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = byte; + scs->output_status = byte; + scs->output_escaped = false; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->output_bytes = 1; + scs->output_escaped = false; + + scs->transaction_running = true; + dev = fw_parent_device(scs->unit); + generation = dev->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(dev->card, &scs->transaction, TCODE_WRITE_BLOCK_REQUEST, + dev->node_id, generation, dev->max_speed, + HSS1394_ADDRESS, scs->buffer, i, + scs_write_callback, scs); +} + +static void scs_output_drain(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + wait_event(scs->idle_wait, scs->output_idle); +} + +static struct snd_rawmidi_ops output_ops = { + .open = scs_output_open, + .close = scs_output_close, + .trigger = scs_output_trigger, + .drain = scs_output_drain, +}; + +static int scs_input_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->input_escape_count = 0; + + return 0; +} + +static int scs_input_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void scs_input_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct scs *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->input) = up ? stream : NULL; +} + +static void scs_input_escaped_byte(struct snd_rawmidi_substream *stream, + u8 byte) +{ + u8 nibbles[2]; + + nibbles[0] = byte >> 4; + nibbles[1] = byte & 0x0f; + snd_rawmidi_receive(stream, nibbles, 2); +} + +static void scs_input_midi_byte(struct scs *scs, + struct snd_rawmidi_substream *stream, + u8 byte) +{ + if (scs->input_escape_count > 0) { + scs_input_escaped_byte(stream, byte); + scs->input_escape_count--; + if (scs->input_escape_count == 0) + snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1); + } else if (byte == 0xf9) { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + scs_input_escaped_byte(stream, 0x00); + scs_input_escaped_byte(stream, 0xf9); + scs->input_escape_count = 3; + } else { + snd_rawmidi_receive(stream, &byte, 1); + } +} + +static void scs_input_packet(struct scs *scs, + struct snd_rawmidi_substream *stream, + const u8 *data, unsigned int bytes) +{ + unsigned int i; + + if (data[0] == HSS1394_TAG_USER_DATA) { + for (i = 1; i < bytes; ++i) + scs_input_midi_byte(scs, stream, data[i]); + } else { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + for (i = 0; i < bytes; ++i) + scs_input_escaped_byte(stream, data[i]); + snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1); + } +} + +static struct snd_rawmidi_ops input_ops = { + .open = scs_input_open, + .close = scs_input_close, + .trigger = scs_input_trigger, +}; + +static int scs_create_midi(struct scs *scs) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(scs->card, "SCS.1x", 0, 1, 1, &rmidi); + if (err < 0) + return err; + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", scs->card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = scs; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &input_ops); + + return 0; +} + +static void handle_hss(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct scs *scs = callback_data; + struct snd_rawmidi_substream *stream; + + if (offset != scs->hss_handler.offset) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + if (tcode != TCODE_WRITE_QUADLET_REQUEST && + tcode != TCODE_WRITE_BLOCK_REQUEST) { + fw_send_response(card, request, RCODE_TYPE_ERROR); + return; + } + + if (length >= 1) { + stream = ACCESS_ONCE(scs->input); + if (stream) + scs_input_packet(scs, stream, data, length); + } + + fw_send_response(card, request, RCODE_COMPLETE); +} + +static int scs_init_hss_address(struct scs *scs) +{ + u8 data[8]; + int err; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + err = snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8, 0); + if (err < 0) + dev_err(&scs->unit->device, "HSS1394 communication failed\n"); + + return err; +} + +static void scs_card_free(struct snd_card *card) +{ + struct scs *scs = card->private_data; + + fw_core_remove_address_handler(&scs->hss_handler); + kfree(scs->buffer); +} + +static int scs_probe(struct device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct scs *scs; + int err; + + err = snd_card_create(-16, NULL, THIS_MODULE, sizeof(*scs), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + scs = card->private_data; + scs->card = card; + scs->unit = unit; + tasklet_init(&scs->tasklet, scs_output_tasklet, (unsigned long)scs); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + + scs->buffer = kmalloc(HSS1394_MAX_PACKET_SIZE, GFP_KERNEL); + if (!scs->buffer) + goto err_card; + + scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; + scs->hss_handler.address_callback = handle_hss; + scs->hss_handler.callback_data = scs; + err = fw_core_add_address_handler(&scs->hss_handler, + &fw_high_memory_region); + if (err < 0) + goto err_buffer; + + card->private_free = scs_card_free; + + strcpy(card->driver, "SCS.1x"); + strcpy(card->shortname, "SCS.1x"); + fw_csr_string(unit->directory, CSR_MODEL, + card->shortname, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "Stanton DJ %s (GUID %08x%08x) at %s, S%d", + card->shortname, fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, card->shortname); + + err = scs_init_hss_address(scs); + if (err < 0) + goto err_card; + + err = scs_create_midi(scs); + if (err < 0) + goto err_card; + + err = snd_card_register(card); + if (err < 0) + goto err_card; + + dev_set_drvdata(unit_dev, scs); + + return 0; + +err_buffer: + kfree(scs->buffer); +err_card: + snd_card_free(card); + return err; +} + +static int scs_remove(struct device *dev) +{ + struct scs *scs = dev_get_drvdata(dev); + + snd_card_disconnect(scs->card); + + ACCESS_ONCE(scs->output) = NULL; + ACCESS_ONCE(scs->input) = NULL; + + wait_event(scs->idle_wait, scs->output_idle); + + tasklet_kill(&scs->tasklet); + + snd_card_free_when_closed(scs->card); + + return 0; +} + +static void scs_update(struct fw_unit *unit) +{ + struct scs *scs = dev_get_drvdata(&unit->device); + int generation; + u8 data[8]; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + generation = fw_parent_device(unit)->generation; + smp_rmb(); /* node_id vs. generation */ + snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8, + FW_FIXED_GENERATION | generation); +} + +static const struct ieee1394_device_id scs_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = MODEL_SCS_1M, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = MODEL_SCS_1D, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, scs_id_table); + +MODULE_DESCRIPTION("SCS.1x MIDI driver"); +MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); +MODULE_LICENSE("GPL v2"); + +static struct fw_driver scs_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + .probe = scs_probe, + .remove = scs_remove, + }, + .update = scs_update, + .id_table = scs_id_table, +}; + +static int __init alsa_scs1x_init(void) +{ + return driver_register(&scs_driver.driver); +} + +static void __exit alsa_scs1x_exit(void) +{ + driver_unregister(&scs_driver.driver); +} + +module_init(alsa_scs1x_init); +module_exit(alsa_scs1x_exit);
On 10/25/2012 09:23 PM, Clemens Ladisch wrote:
I was waiting for your Tested-by ...
Oopsie... I thought I replied saying that
Tested-by: Sean M. Pappalardo - D.J. Pegasus spappalardo@mixxx.org
...was fine. Ah well.
Thanks again for all your hard work on this!
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
On 10/25/2012 09:23 PM, Clemens Ladisch wrote:
I was waiting for your Tested-by ...
Oopsie... I thought I replied saying that
Tested-by: Sean M. Pappalardo - D.J. Pegasus spappalardo@mixxx.org
...was fine.
Sorry, you did; what I meant to say was that I was waiting for the test *result*. (Which turned out to be negative after all.)
Regards, Clemens
On 10/25/2012 09:23 PM, Clemens Ladisch wrote:
Silly off-by-one error. New patch below.
Okay! That looks and works great! (I had to remove the extra parameter on the snd_fw_transaction calls since I'm still using kernel 3.2.18.) Thank you very much for all your time and work on this!
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Takashi, please pull the following change since commit a0d271cbfed1dd50278c6b06bead3d00ba0a88f9:
Linux 3.6 (2012-09-30 16:47:46 -0700)
which is available in the git repository at: git://git.alsa-project.org/alsa-kprivate.git stanton-cs1-driver
Clemens Ladisch (1): ALSA: firewire: add Stanton SCS.1d/1m driver
sound/firewire/Kconfig | 13 ++ sound/firewire/Makefile | 2 + sound/firewire/scs1x.c | 527 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 542 insertions(+), 0 deletions(-) create mode 100644 sound/firewire/scs1x.c
Hello again.
Since your patch works fine, I just wanted to know in which ALSA and kernel version it will appear, so I can tell end users what they need.
Thank you very much for your time.
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
At Thu, 25 Oct 2012 21:23:41 +0200, Clemens Ladisch wrote:
+static int scs_init_hss_address(struct scs *scs) +{
- u8 data[8];
- int err;
- *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset);
Wouldn't it be safer to use unaligned access or a union?
+static void scs_update(struct fw_unit *unit) +{
- struct scs *scs = dev_get_drvdata(&unit->device);
- int generation;
- u8 data[8];
- *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset);
Ditto.
Takashi
Takashi Iwai wrote:
Clemens Ladisch wrote:
+static int scs_init_hss_address(struct scs *scs) +{
- u8 data[8];
- int err;
- *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset);
Wouldn't it be safer to use unaligned access or a union?
Oops, indeed. But safest would be to avoid playing byte-array games:
__be64 data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | scs->hss_handler.offset);
Now fixed in the same branch.
Regards, Clemens
At Mon, 12 Nov 2012 12:33:47 +0100, Clemens Ladisch wrote:
Takashi Iwai wrote:
Clemens Ladisch wrote:
+static int scs_init_hss_address(struct scs *scs) +{
- u8 data[8];
- int err;
- *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset);
Wouldn't it be safer to use unaligned access or a union?
Oops, indeed. But safest would be to avoid playing byte-array games:
__be64 data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | scs->hss_handler.offset);
Now fixed in the same branch.
OK, let me know when you push out the changes, so that I'll pull it.
thanks,
Takashi
At Mon, 12 Nov 2012 12:45:50 +0100, Clemens Ladisch wrote:
Takashi Iwai wrote:
Clemens Ladisch wrote:
Now fixed in the same branch.
OK, let me know when you push out the changes, so that I'll pull it.
Pushed (as amended commit).
Thanks, pulled now.
Takashi
Hello again.
I just modified Mixxx's SCS.1d script to work with your custom sysex messages and tested with your latest patch. The .1d doesn't actually work 100% with this since Mixxx never receives some of the standard MIDI messages amid all of the sysex ones, and it's much worse when the platter is spinning and sending the tons of messages it does. This could be due to PortMIDI's lack of a callback mechanism, forcing Mixxx to poll it every 1ms because it's much more difficult to reproduce the problem using amidi -d. (Of course, amidi isn't sending data to the controller at the same time like Mixxx does.)
That said, the scratching works better in Linux that it does on Windows via libhss1394! (Not really a surprise since Windows has issues anyway.)
I also see an F9 message slip through once in awhile according to amidi -d: F9 55 91 C4
That's most likely to happen if the unit is sending alot of standard MIDI messages (such as when I quickly move a slider that sends a CC) since the device is always sending F9 messages as timestamps. More F9 messages slip through when the platter is spinning because then they come more frequently.
Thank you again very much for your time and work!
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Oh one more thing, sorry for the multiple messages.
As soon as I start the JACK server to handle the audio interface on the SCS.1m, the ALSA driver stops sending MIDI for that device unless I stop JACK and power-cycle the device. Its last words are: F0 00 01 60 48 53 53 0F 03 00 03 00 00 0B 01 F7 It does, however, continue to respond to MIDI messages send to it.
The SCS.1d (connected through the SCS.1m) continues as before.
(On Windows via libhss1394, it's possible to use the audio interface and HSS1394 control on both devices simultaneously.)
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
On 05/30/2012 09:18 AM, Clemens Ladisch wrote:
As it happens, the actual SysEx commands use the wrong manufacturer ID ("00 01 02" is Crystal Semiconductor); I could just use the real ID (Stanton is "00 01 60") to escape non-MIDI HSS1394 messages. Let's add "HSS" to identify this, and to allow the full byte range, each HSS1394 byte is split into two nibbles.
That all sounds good, but that now means that applications that want to use these device features must have different code for Linux than they do for Windows or OSX. In the case of Mixxx, that also means a different controller preset. But I guess that's not the end of the world if it's a choice between this and no support at all.
Sigh...
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
On 05/30/2012 09:18 AM, Clemens Ladisch wrote:
As it happens, the actual SysEx commands use the wrong manufacturer ID ("00 01 02" is Crystal Semiconductor); I could just use the real ID (Stanton is "00 01 60") to escape non-MIDI HSS1394 messages. Let's add "HSS" to identify this, and to allow the full byte range, each HSS1394 byte is split into two nibbles.
That all sounds good, but that now means that applications that want to use these device features must have different code for Linux than they do for Windows or OSX.
IIRC it was decided to use a MIDI driver in order to allow other applications to access these devices with standard MIDI APIs. (Does this imply that on Windows and OS X, it is not possible to access them without using the HSS1394 library?)
You could still port the HSS1394 library so that it sits on top of the MIDI driver. (In other words, the Linux MIDI backend would be the equivalent of the Windows and OS X FireWire backends.)
Regards, Clemens
participants (3)
-
Clemens Ladisch
-
Sean M. Pappalardo - D.J. Pegasus
-
Takashi Iwai