Giter Club home page Giter Club logo

linux-usbtmc's Introduction

linux-usbtmc driver

This is an experimental linux driver for usb test measurement & control instruments. It adds support for missing functions in USBTMC-USB488 spec, the ability to handle SRQ notifications with fasync or poll/select and a number of features required to support the IVI library. This package is provided for folks wanting to test or use driver features not yet supported by the standard usbtmc driver in their kernel.

The following functions have not yet been incorporated into a kernel.org release:

  • module params
  • 32 bit support for IVI USBTMC_IOCTL_CTRL_REQUEST and USBTMC_IOCTL__READ/WRITE on 64 bit platforms.

Note: The initial version incorporated into the 14.6.0 kernel release used POLLIN / select readfds for SRQ notifications. This was changed to POLLPRI / select exceptfds in the version incorporated in the 4.19.0 and subsequent kernel release.

All the USBTMC-USB488 features are available in the standard kernel.org releases >= 4.19.0

The IVI extensions have been incorporated into kernel releases >= 4.20.0

For details on the IVI extensions please see Guido Kiener's repo

Installation

Prerequisite: You need a prebuilt kernel with the configuration and kernel header files that were used to build it. Most distros have a "kernel headers" package for this

To obtain the driver source files either clone the repo with git clone https://github.com/dpenkler/linux-usbtmc.git linux-usbtmc or download the zip file and extract the zip file to a directory linux-usbtmc

To build the driver simply run make in the directory containing the driver source code (linux-usbtmc/ or linux-usbtmc-master/).

To install the driver run make install as root.

To load the driver execute rmmod usbtmc; insmod usbtmc.ko as root.

Enable debug messages with insmod usbtmc.ko dyndbg=+p and use dmesg to see debug output.

To compile your instrument control program ensure that it includes the tmc.h file from this repo. An example test program for an Agilent/Keysight scope is also provided. See the file ttmc.c To build the provided program run make ttmc

To clean the directory of build files run make clean

To run usbtmc applications as non-root, insert a file e.g. /etc/udev/rules.d/99-usbtmc.rules with the content:

KERNEL=="usbtmc[0-9]*", MODE="0660", GROUP="usbtmc"

and add yourself to the usbtmc group

sudo usermod -G usbtmc -a LOGIN

where LOGIN is your username.

Features

The new features supported by this driver are based on the specifications contained in the following document from the USB Implementers Forum, Inc.

Universal Serial Bus
Test and Measurement Class,
Subclass USB488 Specification
(USBTMC-USB488)
Revision 1.0
April 14, 2003

Individual feature descriptions:

ioctl to support the USBTMC-USB488 READ_STATUS_BYTE operation.

USBTMC488_IOCTL_READ_STB

When performing a read on an instrument that is executing a function that runs longer than the USB timeout the instrument may hang and require a device reset to recover. The READ_STATUS_BYTE operation always returns even when the instrument is busy, permitting the application to poll for the appropriate condition without blocking as would be the case with an "*STB?" query.

USBTMC488_IOCTL_READ_STB always reads the STB from the device and if the SRQ condition is asserted in the driver it sets the RQS bit in the returned STB.

Note: The READ_STATUS_BYTE ioctl clears the SRQ condition in the driver but it has no effect on the status byte of the device.

Support for receiving USBTMC-USB488 SRQ notifications with fasync

By configuring an instrument's service request enable register various conditions can be reported via an SRQ notification. When the FASYNC flag is set on the file descriptor corresponding to the usb connected instrument a SIGIO signal is sent to the owning process when the instrument asserts a service request.

Example

  signal(SIGIO, &srq_handler); /* dummy sample; sigaction( ) is better */
  fcntl(fd, F_SETOWN, getpid( ));
  oflags = fcntl(fd, F_GETFL);
  if (0 > fcntl(fd, F_SETFL, oflags | FASYNC)) {
	  perror("fcntl to set fasync failed\n");
	  exit(1);
  }

Support for receiving USBTMC-USB488 SRQ notifications via poll/select

In many situations operations on multiple instruments need to be synchronized. poll/select provide a convenient way of waiting on a number of different instruments and other peripherals simultaneously. When the instrument sends an SRQ notification the fd is notified of an exceptional condition. To reset the poll/select condition either a USBTMC488_IOCTL_READ_STB or USBTMC_IOCTL_GET_SRQ_STB must be performed.

Example with select()

  FD_SET(fd,&fdsel[0]);
  n = select(fd+1,
	  (fd_set *)(&fdsel[0]),
	  (fd_set *)(&fdsel[1]),
	  (fd_set *)(&fdsel[2]),
	  NULL);
  
  if (FD_ISSET(fd,&fdsel[2])) {
          ioctl(fd,USBTMC488_IOCTL_READ_STB,&stb)
	  if (stb & 16) { /* test for message available bit */
	      len = read(fd,buf,sizeof(buf));
	      /*
	      process buffer
	          ....
	      */
	  }
  }

Example with poll()

/* Wait for SRQ using poll() */
void wait_for_srq(int fd) {
	struct pollfd pfd;
	pfd.fd = fd;
	pfd.events = POLLPRI;
	poll(&pfd,1,-1);
}

USBTMC_IOCTL_GET_SRQ_STB

This ioctl, instead of requesting the STB from the device, returns the STB that was sent by the device in the last SRQ message. If no other SRQ occurs between two successive calls to USBTMC_IOCTL_GET_SRQ_STB an ENOMSG error is signaled.

Note: The GET_SRQ_STB ioctl clears the SRQ condition in the driver but it has no effect on the status byte of the device.

USBTMC_IOCTL_GET_STB

This ioctl always reads the STB from the device and returns the unmodified STB in the argument. It does not clear the SRQ condition in the driver.

New ioctls to enable and disable local controls on an instrument

These ioctls provide support for the USBTMC-USB488 control requests for REN_CONTROL, GO_TO_LOCAL and LOCAL_LOCKOUT

ioctl to cause a device to trigger

This is equivalent to the IEEE 488 GET (Group Execute Trigger) action. While the "*TRG" command can be sent to perform the same operation, in some situations an instrument will be busy and unable to process the command immediately in which case the USBTMC488_IOCTL_TRIGGER can be used.

Utility ioctl to retrieve USBTMC-USB488 capabilities

This is a convenience function to obtain an instrument's capabilities from its file descriptor without having to access sysfs from the user program. The driver encoded usb488 capability masks are defined in the tmc.h include file.

Two new module parameters

io_buffer_size specifies the size of the buffer in bytes that is used for usb bulk transfers. The default size is 4096. The minimum size is 64. Positive values given for this parameter are automatically rounded down to the nearest multiple of 4. If io_buffer_size is zero the wMaxPacketSize for the IN and OUT bulk endpoints are used. This is needed for some Rigol scopes.

usb_timeout specifies the timeout in milliseconds that is used for usb transfers. The default value is 5000 and the minimum value is 500.

To set the parameters

insmod usbtmc.ko [io_buffer_size=nnn] [usb_timeout=nnn]

For example to set the buffer size to 256KB:

insmod usbtmc.ko io_buffer_size=262144

ioctl's to set/get the usb timeout value

Separate ioctl's to set and get the usb timeout value for a device. By default the timeout is set to 5000 milliseconds unless changed by the usb_timeout module parameter.

USBTMC_IOCTL_SET_TIMEOUT will return with error EINVAL if timeout < 500

Example

	unsigned int timeout, oldtimeout;
....
	ioctl(fd,USBTMC_IOCTL_GET_TIMEOUT,&oldtimeout)
	timeout = 1000;
	ioctl(fd,USBTMC_IOCTL_SET_TIMEOUT,&timeout)

ioctl to send generic usb control requests

Allows user programs to send control messages to a device over the control pipe.

ioctl to control setting EOM bit

Enables or disables setting the EOM bit on write. By default the EOM bit is set on the last transfer of a write.

Will return with error EINVAL if eom is not 0 or 1

Example

	unsigned char eom;
....
	eom = 0; // disable setting of EOM bit on write 
	ioctl(fd,USBTMC_IOCTL_EOM_ENABLE,&eom)

ioctl to configure TermChar and TermCharEnable

Allows enabling/disabling of terminating a read on reception of term_char. By default TermCharEnabled is false and TermChar is '\n' (0x0a).

Will return with error EINVAL if term_char_enabled is not 0 or 1 or if attempting to enable term_char when the device does not support terminating a read when a byte matches the specified term_char.

Example

	struct usbtmc_termc termc;
....
	termc.term_char_enabled = 1; // enable terminating reads on term_char
	termc.term_char = '\n';     
	ioctl(fd,USBTMC_IOCTL_CONFIG_TERMCHAR,&termc)

Issues and enhancement requests

Use the Issue feature in github to post requests for enhancements or bugfixes.

linux-usbtmc's People

Contributors

dpenkler avatar guidokiener avatar sessl3r avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

linux-usbtmc's Issues

Setting timeout from user-space program

The fixed timeout of 5 seconds has been a serious problem of the linux usbtmc driver. With our equipment, there are many cases where 5 seconds is either to long or too short.
Making it configurable via module_param() is a good thing, but requires reloading of the module and root permissions.

It would be better, if the timeout could be set by the user program. If I can do this with the Linux-GPIB library, why not with the linux USBTMC driver? ;)

Issue with TransferSize in DEV_DEP_MSG_OUT headder

The driver is not working the way the USBTMC specification states it should. The specification states that the TransferSize should not include the alignment bytes. I am referring to the DEV_DEP_MSG_OUT Bulk-OUT header. In there, bytes four through seven represent the TransferSize. This transfer size is the total number of USBTMC message data bytes to be sent in this USB transfer. This does not include the number of bytes in this Bulk-OUT Header or alignment bytes. The kernel driver is including the alignment bytes in the transfer size.

dkms and MODULE_VERSION

Well, the dkms code got changed a few week back, and a lot of module code started throwing errors on kernel updates.

Turns out, modules should have a something like
MODULE_VERSION("1.4")
in the source, but no one seems to have noticed (because there were no errors?)

IMO, it really is needed for DAQ-related modules, because some interfaces will work for particular versions, and you can't exactly run them with "--version".

So, forked, put in the MODULE_VERSION, and a bunch of other 'packaging' stuff to generate rpms for dkms installs. Take a look, see if there's anything you want.

"fallthrough" compile error on kernel < 5.4

From 2f5cb7d22560f01acef2df59ea2b346fdb6740dd Mon Sep 17 00:00:00 2001
From: Charles Lane [email protected]
Date: Wed, 23 Dec 2020 12:02:24 -0500
Subject: [PATCH] Add #define fallthrough if missing in the kernel
include/linux/compiler_attributes.h (the fallthrough was added in kernel
5.4), needed to compile cleanly. Tested on Fedora31 (kernel 5.8.18, patch not
needed, but works) and Centos8 (kernel 4.18.0, patch needed, works).


usbtmc.c | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/usbtmc.c b/usbtmc.c
index 36d3e71..9b8dec6 100644
--- a/usbtmc.c
+++ b/usbtmc.c
@@ -24,6 +24,19 @@
#include <linux/compat.h>
#include "tmc.h"

+/* Linux kernel 5.4 has a definition of 'fallthrough', earlier kernels

+#if !defined(fallthrough)
+#if __has_attribute(fallthrough)
+# define fallthrough attribute((fallthrough))
+#else
+# define fallthrough do {} while (0) /* fallthrough /
+#endif
+#endif
+
/
Increment API VERSION when changing tmc.h with new flags or ioctls

  • or when changing a significant behavior of the driver.
    */
    --
    2.18.4

Timeout

We have a Thorlabs CLD1010LP laser driver that we want to control over USB. We are able to do so with pyVISA and the pyVISA-py pure python driver (uses pyUSB backed by libusb). However, with the linux usbtmc kernel driver we are getting timeouts. Here is the dmesg log immediately after plugging in the device:

user@machine:~$ sudo dmesg --clear
user@machine:~$ dmesg
[  424.730305] usbtmc: Experimental driver version 1.1 unloaded
[  433.586940] usb 1-8: new full-speed USB device number 12 using xhci_hcd
[  438.736767] usb 1-8: New USB device found, idVendor=1313, idProduct=804f
[  438.736769] usb 1-8: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  438.736770] usb 1-8: Product: CLD1010LP
[  438.736772] usb 1-8: Manufacturer: Thorlabs
[  438.736773] usb 1-8: SerialNumber: M00420016
[  438.737361] usbtmc 1-8:1.0: usbtmc_probe called
[  438.737361] usbtmc: Experimental driver version 1.1 loaded
[  438.737362] usbtmc: Params: io_buffer_size = 2048, usb_timeout = 5000
[  438.737364] usbtmc 1-8:1.0: Trying to find if device Vendor 0x1313 Product 0x804F has the RIGOL quirk
[  438.737366] usbtmc 1-8:1.0: Found bulk in endpoint at 130
[  438.737367] usbtmc 1-8:1.0: Found Bulk out endpoint at 2
[  438.737368] usbtmc 1-8:1.0: Found Int in endpoint at 129
[  438.737476] usb 1-8: GET_CAPABILITIES returned 1
[  438.737477] usb 1-8: Interface capabilities are 4
[  438.737478] usb 1-8: Device capabilities are 1
[  438.737479] usb 1-8: USB488 interface capabilities are 6
[  438.737480] usb 1-8: USB488 device capabilities are f
[  438.737535] usbtmc 1-8:1.0: Using minor number 0

And the log after trying to cat /dev/usbtmc0:

user@machine:~$ sudo dmesg --clear
user@machine:~$ sudo cat /dev/usbtmc0
cat: /dev/usbtmc0: Connection timed out
user@machine:~$ dmesg
[  479.122503] usbtmc 1-8:1.0: usb_bulk_msg_in: remaining(131072), count(131072)
[  483.946882] usbtmc 1-8:1.0: usb_bulk_msg: retval(4294967186), done(0), remaining(131072), actual(0)
[  483.946884] usbtmc 1-8:1.0: Unable to read data, error -110
user@machine:~$ 

Do you have any idea where this timeout is occurring or how we can further debug this?

Thanks!

Rigol DS1054Z data issue caused by small packet sizes supported by this device

``Hello,

first of all I'm not really familiar with USB and TMC but with drivers.
I use a Rigol DS1054Z which is known to be somewhat limited in regards to downloading screenshots as it always chunks response data to 52 Bytes of payload. See PyViSA-Issue.

I had the same problem with PyVISA and it's pyvisa_py usbtmc implementation. In this code I were able to Monkey-Patch the RECV_CHUNK size down to 52 Bytes ( source ).

from pyvisa_py.protocols.usbtmc import USBTMC as USBTMC
USBTMC.RECV_CHUNK = 64-12

I tested this driver and I think I run into a similar issue, at least related.
When trying to download data the first packet (which includes some binary header) is ok, all others are rejected by the usbtmc_read function:

[691990.970573] usbtmc 3-2.1.4:1.0: usbtmc_write(size:26 align:40)
[691990.970606] usbtmc 01 01 fe 00 1a 00 00 00 01 00 00 00 3a 64 69 73  ............:dis
[691990.970616] usbtmc 70 6c 61 79 3a 64 61 74 61 3f 20 6f 6e 2c 6f 66  play:data? on,of
[691990.970622] usbtmc 66 2c 70 6e 67 0a 00 00                          f,png...
[691990.970650] usbtmc 3-2.1.4:1.0: usbtmc_generic_write: size=0 flags=0x2 sema=15
[691990.970712] usbtmc 3-2.1.4:1.0: usbtmc_write_bulk_cb - write bulk total size: 40
[691990.970804] usbtmc 3-2.1.4:1.0: usbtmc_generic_write: done=40, retval=0, urbstat=0
[691996.716002] usbtmc 3-2.1.4:1.0: usbtmc_read(count:131072)
[691996.716262] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[691996.716277] usbtmc 3-2.1.4:1.0: Bulk-IN header: N_characters(36013), bTransAttr(1)
[691996.716292] usbtmc 02 02 fd 00 ad 8c 00 00 01 00 00 00 23 39 30 30  ............#900
[691996.716294] usbtmc 30 30 33 36 30 30 31 89 50 4e 47 0d 0a 1a 0a 00  0036001.PNG.....
[691996.716295] usbtmc 00 00 0d 49 48 44 52 00 00 03 20 00 00 01 e0 08  ...IHDR... .....
[691996.716296] usbtmc 02 00 00 00 d2 65 9e a2 00 00 00 0c 74 45 58 74  .....e......tEXt
[691996.716317] usbtmc 3-2.1.4:1.0: usbtmc_read(count:131072)
[691996.716519] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[691996.716533] usbtmc 3-2.1.4:1.0: Device sent reply with wrong MsgID: 65 != 2

After some first try to debug (basically ignoring most error cases in usbtmc_read which are related to data buffers and doing the debug print before error checking I get the follwing trace as received data:

[692565.224716] usbtmc 3-2.1.4:1.0: usbtmc_write(size:26 align:40)
[692565.224737] usbtmc 01 01 fe 00 1a 00 00 00 01 00 00 00 3a 64 69 73  ............:dis
[692565.224742] usbtmc 70 6c 61 79 3a 64 61 74 61 3f 20 6f 6e 2c 6f 66  play:data? on,of
[692565.224745] usbtmc 66 2c 70 6e 67 0a 00 00                          f,png...
[692565.224762] usbtmc 3-2.1.4:1.0: usbtmc_generic_write: size=0 flags=0x2 sema=15
[692565.224800] usbtmc 3-2.1.4:1.0: usbtmc_write_bulk_cb - write bulk total size: 40
[692565.224878] usbtmc 3-2.1.4:1.0: usbtmc_generic_write: done=40, retval=0, urbstat=0
[692568.226105] usbtmc 3-2.1.4:1.0: usbtmc_read(count:4096)
[692568.226378] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[692568.226400] usbtmc 3-2.1.4:1.0: Bulk-IN header: N_characters(4096), bTransAttr(0)
[692568.226404] usbtmc 02 02 fd 00 00 10 00 00 00 00 00 00 23 39 30 30  ............#900
[692568.226407] usbtmc 30 30 33 36 30 30 31 89 50 4e 47 0d 0a 1a 0a 00  0036001.PNG.....
[692568.226408] usbtmc 00 00 0d 49 48 44 52 00 00 03 20 00 00 01 e0 08  ...IHDR... .....
[692568.226410] usbtmc 02 00 00 00 d2 65 9e a2 00 00 00 0c 74 45 58 74  .....e......tEXt
[692568.226478] usbtmc 3-2.1.4:1.0: usbtmc_read(count:4096)
[692568.226570] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[692568.226591] usbtmc 3-2.1.4:1.0: Bulk-IN header: N_characters(1375761007), bTransAttr(73)
[692568.226595] usbtmc 41 75 74 68 6f 72 00 52 49 47 4f 4c e7 88 8c c9  Author.RIGOL....
[692568.226598] usbtmc 00 00 00 11 74 45 58 74 53 6f 75 72 63 65 00 44  ....tEXtSource.D
[692568.226601] usbtmc 53 5a 20 73 65 72 69 65 73 3f 5a 1d 97 00 00 00  SZ series?Z.....
[692568.226603] usbtmc 29 74 45 58 74 44 65 73 63 72 69 70 74 69 6f 6e  )tEXtDescription
[692568.226658] usbtmc 3-2.1.4:1.0: usbtmc_read(count:4096)
[692568.226752] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[692568.226757] usbtmc 3-2.1.4:1.0: Bulk-IN header: N_characters(1769099359), bTransAttr(110)
[692568.226761] usbtmc 00 44 53 5a 5f 50 72 69 6e 74 69 6e 67 20 65 71  .DSZ_Printing eq
[692568.226763] usbtmc 75 69 70 6d 65 6e 74 20 6f 75 74 70 75 74 c1 a5  uipment output..
[692568.226766] usbtmc 35 c1 00 00 00 1c 74 45 58 74 43 6f 70 79 72 69  5.....tEXtCopyri
[692568.226769] usbtmc 67 68 74 00 52 49 47 4f 4c 20 54 45 43 48 4e 4f  ght.RIGOL TECHNO
[692568.226823] usbtmc 3-2.1.4:1.0: usbtmc_read(count:4096)
[692568.226900] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[692568.226905] usbtmc 3-2.1.4:1.0: Bulk-IN header: N_characters(350442309), bTransAttr(97)
[692568.226910] usbtmc 4c 4f 47 49 45 53 e3 14 61 af 00 00 8b d6 49 44  LOGIES..a.....ID
[692568.226913] usbtmc 41 54 78 01 ed bd 2d 98 24 d7 95 ad 9d 40 a0 81  ATx...-.$....@..
[692568.226916] usbtmc 40 01 81 02 02 05 04 1a 08 34 30 28 20 50 40 a0  @........40( P@.
[692568.226919] usbtmc 81 41 03 83 02 02 02 06 02 03 04 06 08 0c 68 70  .A............hp
[692568.226999] usbtmc 3-2.1.4:1.0: usbtmc_read(count:4096)
[692568.227069] usbtmc 3-2.1.4:1.0: usbtmc_read: bulk_msg retval(0), actual(64)
[692568.227074] usbtmc 3-2.1.4:1.0: Bulk-IN header: N_characters(4213234343), bTransAttr(216)
[692568.227079] usbtmc e2 e8 98 09 a7 da 20 fb d8 76 b3 06 b2 d9 8b 48  ...... ..v.....H
[692568.227082] usbtmc 13 37 45 89 e3 9d 79 58 ee b1 25 d7 fc ac 45 80  .7E...yX..%...E.
[692568.227085] usbtmc c5 d0 43 23 a9 82 bf e1 12 f0 52 ca 08 12 c8 d5  ..C#......R.....
[692568.227089] usbtmc 15 3c c1 b6 0a 1b 45 20 b9 fb 2c f3 f3 d7 59 d2  .<....E ..,...Y.
[........]

However this also does not really work. So the question I have in the moment is, is this device sending correct data frames (with pyvisa_py's usbtmc implementation it works) and is the code making too simple assumptions here? I expect the second as the USBTMC specification states for multi-transaction Bulk-IN transfers where only the first one includes the header.

From the dataframes I would expect that the usbtmc_read function would need to handle this kind of chunked data where no header information is available?

As this is not really reproducible on your side I expect, please ask if you need any kind of dump (not sure if I could trace the USB traffic?)

Thanks for your support & Regards,
sessl3r

Revert back to original driver?

Is there a way to revert back to the original behavior? It appears that this is now incorporated in Centos 7.6, and breaks our existing software. It is not practical for us to change our code, existing applications must work without modification.
Any suggestions appreciated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.