Saturday October 03, 2009 | Artem's Weblog |
|
New in 124: physical eject button OpenSolaris build 124 is out and includes a new feature: now you can use the eject button on your CD/DVD drive to eject the disc, even if it is mounted. To quote the heads-up message: Pressing Eject button on CD/DVD drive's front panel will have the same effect as typing 'eject' in the terminal or clicking the corresponding GUI icon in the File Browser. Most modern drives support this feature, though some older ones don't. You may see a harmless pop-up message "Unable to unmount volume" due to GNOME bug 9805. It can be safely ignored, but if it bothers you, the workaround is: svccfg -s rmvolmgr setprop rmvolmgr/eject_button=boolean: false svcadm restart rmvolmgr The side effect of the workaround is being able to use physical eject button only with a GNOME session running, not when logged into the text console. Aside from the issues listed in the build notes, be aware of the "Console User" problems. If you are curious about how the eject button works in OpenSolaris, read ARC case 2009/058. It is quite remarkable that Solaris managed to go without this capability for so long: other OSes have had it for years. I should have fixed it as part of the Tamarack project, but had to sacrifice some features to meet the schedule. I then switched to networking projects, and the feeling of unfinished business has been nagging me ever since. Walking around with a solution to a problem in your head is, I imagine, not unlike being pregnant: you cannot carry it inside forever. I knew how, I had the code visualized mentally. It was a great relief when one evening I finally initiated the mind->computer transfer. The hard part was to properly test the code: too much weird or buggy hardware out there. All of the PCs, x86 laptops and recent Sun products worked just fine. I did encounter some old Sun gear that did not support the eject button - for those, the eject(1M) command continues to be the only option. I'd like to thank Neal, Phi and Larry for their help with the last mile effort. (2009-10-03 12:20:08.0) Permalink Comments [3]New in snv_93: dladm link property persistence I once joined a pickup basketball game and one of my teammates only noticed me after three or four possessions. Another teammate tried to be supportive and used the word "stealth" to characterize my role in the game (my own choice was "zero impact"). Story of my life. But sometimes that's a good thing. A lot of OS improvements are like that, incremental in nature, not visible to most users, but contributing to the overall movement towards better future. Not quite the shouting from the rooftops material, but noteworthy to geeks and search engines. Such is my
recent putback, a followup to the earlier
Brussels framework putback. Originally, properties set via ifconfig link0 plumb dladm init-linkprop My putback makes it automatic. No need to reapply the properties manually anymore. What happens under the hood is a bit more complicated. dladm shows you all links the system knows about: # dladm show-link LINK CLASS MTU STATE OVER bge0 phys 1500 up -- iprb0 phys 1500 unknown -- iprb1 phys 1500 unknown -- This information, along with link properties, is stored in /etc/dladm/datalink.conf. The kernel, however, only needs to know about links that are currently in use. What the kernel knows can be extracted from mdb: # mdb -k > *i_dls_devnet_id_hash::modhash -e | ::modent -v | ::print dls_devnet_t dd_spa dd_spa = [ "bge0/0" ] Generally, a link becomes in use when its corresponding device file under /dev/net
is first opened. That's what happens, for instance, when you plumb the link.
This is the moment the kernel learns about the link. This is also the moment
a new thread is launched, which communicates with the datalink management daemon,
dlmgmtd(1M). It requests the daemon to perform the equivalent of There are still two cases when persistence is not guaranteed: wifi links and private properties. The following CRs have been filed and are being worked on:
6691666 link property persistence for wifi drivers
It all started when a friend offered me a spare ticket to the San Francisco Opera. Being a jazz dude, I couldn't pass up the opportunity to hear something new. Last time I went to opera, I was in Moscow and Yeltsin was ruling the country. And last time I wore a sports jacket, I was 40 lbs larger, so I had to buy a new one. Driving to SF was out of the question due to the Pride Celebration that day. Took the Caltrain. I should have taken the bus from the Caltrain station and not walk, because, as it turned out, loud disco music and uncovered genitalia do not mix well with 17th century costumes and classical singing. I had to reset my brain, the way a perfume shopper resets her nose with a whiff of coffee beans between two different fragrances. We saw Donizetti's "Lucia di Lammermoor" and Natalie Dessay took my breath away. I had tears in my eyes, like that time I heard Billy Harper play "Priestess" live. Magical. Celebrations continued with grilled meats and fermented drinks and climaxed with July 4th fireworks in Bellevue, WA, where I was visiting my old friends from college. It was great to see that some folks are not yet affected by the economic downturn. Redmond inhabitants are still keeping up with their Joneses, not letting them have bigger TVs, longer camera lenses, etc. And hey, after a few beers, I, too, felt like there was no tomorrow.
Extended Self-ID for USB devices Most USB devices already have some sort of non-volatile memory that contains firmware and some self-identification data, presented to the host OS via USB descriptors - the kind of stuff you'd see as device properties in 'prtconf -v' output. This non-volatile memory is often in the form of tiny EEPROM arrays connected to the internal microcontroller via I2C, or something similar. Instead, now that USB flash memory is becoming dirt cheap, why not put a decent amount of it in every USB device, as an additional logical device ("interface" in USB speak, or "logical unit")? It would be cool, for instance, if instead of lame, wasteful "installation CDs", devices could carry their own drivers (though it would be even cooler if all devices were class compliant). Just connect your shiny new webcam or a phone and the OS can install drivers right away. Or at least pick up a URI of a Web Service for finding the drivers. It's just a matter of standardizing on file formats and Web APIs. Kinda like a more generic version of U3.
In 2002, I visited Microsoft campus for the IEEE 1394 developer conference. It was great to see engineers from Apple, Microsoft, consumer electronics companies share pure passion for technology, with no bias or self-promotion. A hippyish dude from Mitsubishi demoed his baby: a rear-projection HDTV with FireWire I/O and Java-based GUI. Returning back to California, I bought a similar Mitsubishi set and built a decent home theater around it. I also planned to extend my av1394 driver to support video streams in this format, which is slightly different from DV. Years passed, I minimized my material possessions, the home theater is also gone (it did not turn bad movies into good movies - shocking). Yet I'd like to try out the latest HD technology without spending too much money on outdated-in-one-year crap. I already have a widescreen Dell 2407 with HDCP support. My computer is not up to the task though: old Athlon 64, weak video card. With Socket 754 on the mobo, I can't even upgrade to a faster single core, let alone a dual core, which is pretty much a must for smooth hi-def experience. That means I need to build a new system. I used newegg.com to estimate the cost:
Plus tax and shipping, comes down to a grand total of about $1200. Quite a bit of dough, just for wows. I will have to upgrade eventually, but my old box still has some steam. Standalone Blu-ray player is about $400, but I'm hesitant to go down that road again. I also suspect that once hooked on HD, I will want to upgrade my cable subscription, too, and that's more money down the drain. I guess for now, I'll just buy $10 worth of beer and pay a visit to a less parsimonious friend.
Reportedly, gangs of geeks, disguised in bifocals and armed with sharp pencils, are breaking into various properties and furiously inspect code, sometimes all night long. I'm speculating here, but it could be those Google dudes getting out of control... Stuff like this just makes me sick. And angry. Then sick again.
Seen on Rengstorff Ave, Mountain View, CA
After 7 years of pretending to know something about I/O, I decided to see if I can pull the same trick with networking. The first project I chose to sabotage is Brussels. Project's mission can be described with a thousand words, but, the creative type that I am, I drew you a cool picture:
The first bit of code I've just contributed (here's the webrev) is mainly for the MAC services module. I added per-link property handles, which the network drivers can use like so:
err = mac_prop_init("driver", instance, &handle);
val = mac_prop_get_uint64(handle, "property");
mac_prop_fini(handle);
For each plumbed link, MAC keeps a list of properties that ever entered the kernel. Pointers to these lists are stored in a hash table, using link name as a key. I also added MDB support for these data structures. The > ::walk mac_proplist | ::walk mac_prop | ::print mac_prop_t Even more convenient is the new
> ::help mac_prop
NAME
mac_prop - display MAC properties of a link or all links
SYNOPSIS
::mac_prop [link]
ATTRIBUTES
Target: kvm
Module: mac
Interface Stability: Unstable
> ::mac_prop
ADDR LINK PROPERTY SIZE VALUE
fffffffec7d80480 bge0 default_mtu 8 1500
fffffffec7d80a80 bge0 adv_autoneg_cap 1 1
>
There wasn't an existing MDB module for MAC, so I created it too. Here's a good opportunity for other contributors to the MAC layer, hint hint, to add more MAC data structures to MDB.
Last week I went to San Francisco to have my fingerprints taken. The government office at 250 Broadway is a hole in the wall on the outside, and a remodeled warehouse on the inside, about 10000 sq ft. There are eight stations equipped with inkless fingerprint scanners, Samsung LCDs and what seem like generic PCs. Each finger is scanned at least once, its image appears on the screen and, before proceeding to next finger, the worker has to wait several seconds while the computer analyzes it. My mind wandered about how much energy it takes to run an office like this and what role computers play in it. CPU vendors like to brag about performance per watt, but things might look different in the bigger scheme. In this case, if CPUs were to analyze images faster, the office performance measured in "people per day" would increase. Even if faster CPUs were more power-hungry, their contribution into the total office power consumption would be negligible. Say, 50 watts per CPU increase is: 8 PCs * 50 W * 8 hours = 3200 Wh
Add air conditioning and you get the picture. The "people per watt" metric would still improve. Though this is hugely simplified, it illustrates the importance of the holistic approach. Anyway, this silly stuff kept me occupied while waiting in line, in addition to reverse engineering fellow immigrants' life stories from their looks (and sometimes smells). As of last week, I'm in The System and, what, I have to wear gloves now? Darn.
"... and even if he's a lazy man, I have to frequently copy bug IDs from plain text emails to the Bugster web interface. Really wears me down. I even think my wrist is growing one of those tunnels named after military personnel. So I decided to sit down and do something about it. Because I was already sitting, half of the job had been accomplished, and that gave me strength. I wrote a Firefox+Thunderbird extension that allows the following:
- double-click on a bug ID opens its web page If you want to give it a try, save the below file and install it via Tools>Extensions/Addons dialog (or just click in Firefox). Compatible with Thunderbird and Firefox versions 1.5 - 2.0.0.*. Screenshots:
My little Solaris security cheat sheet This returned me to sanity a few times while learning about Solaris security. Like many others, I'm not a security expert and I often need a short version to fit in my head. authorization A right assigned to users that is checked by privileged programs to determine whether users can execute restricted functionality. More in auth_attr(4). privilege An attribute that provides fine-grained control over the actions of processes, as opposed to traditional unix all-or-nothing, super-user vs user, model. More in privileges(5). profile A logical grouping of authorizations and commands. Profile shells, pf[ck]sh, interpret profiles to form a secure execution environment. More in prof_attr(4), exec_attr(4). role A type of user account, with associated authorizations and profiles. Roles cannot be logged in directly - users assume roles using su(1M).
1Was also available much earlier in Trusted Solaris. Tags:
Solaris
OpenSolaris
Security
Tim Bray revived some of my old reflections. It is easy for a jazz listener to drown in the ocean of tonal and rhythmic subtleties imposed by the sometimes overly self-indulgent musicians. But jazz is and should be fun, not necessarily requiring intense mental concentration. Especially if one recalls ragtime and dixieland were at the roots. I remember how I got into jazz during my college days in Moscow. Coming from the rock-n-roll background, my dad listening to The Beatles beside my crib, I got tired by predictability and triviality of modern rock and found what I was looking for in jazz. Russian jazz is often too academic. For instance, some of the most prominent Russian jazzmen are also professors in a music conservatory. When I started going to live gigs, I was blown away by the visiting American musicians jamming with the locals: they always looked and sounded like giants, totally overshadowing the Russians, like uh, I dunno... Robin Williams and Matthew Broderick should they happen to be in the same scene (and speaking of acting parallels, where had method acting originated?). A great Russian saxophone player Igor Butman spent 10 years in America learning jazz and seeing him in a Moscow jazz cafe for the first time was a revelation for me. He's fun, exuberant, spontaneous and loves tequila. Tim wasn't impressed with Medeski and Co; well, they might be more fun in a club setting and perhaps require a certain dead state of mind. The folks are balancing at the bleeding edge, cut 'em some slack. They really shine on Scofield's A Go Go - essential listening, and massively entertaining, too. Keith Jarret is another example of a jazz musician who successfully combines complexity with entertainment, he rarely disappoints. I can't imagine Joshua Redman being boring, no matter what Tim says. Pat Metheny is always up to something. There are a lot of great performers out there. I just wish Jaco was still alive.
Robert Fripp of the King Crimson fame and a genius in his own right has recorded a bunch of sounds that will be used in Windows Vista (formerly Longhorn). Mixed emotions flying by: Wow; This is what happens when you have too much money; Robert Fripp must be pretty desperate; Robert Fripp must be pretty adventurous; They're not going to lure me with their sound schemes; and so on. Interesting how they defined the theme as "clear, connected, confident" (capturing confidence through ambient sounds is especially intriguing ;) and translated into Fripp's own sound coloring system - green and blue. By the way, if you haven't seen Channel 9 yet, poke around. As a kernel developer, I found particularly interesting the Going Deep series. Check out a Windows kernel architect delving into NT history and comparing it with other OSes.
Here are a few DSD coding tips (all code herein is under CDDL). Naming convention Module names for STREAMS drivers are limited to 8 characters. By convention a USB serial driver name should start with "usbs", which leaves only 4 characters to identify device vendor. For example, our Keyspan driver will be called "usbsksp". (The Edgeport driver is called "usbser_edge", but that's a bug). I will be using "usbsxx" for driver name in this blog. Common driver code Most Solaris drivers share the same structure: define cb_ops, dev_ops, modldrv and modlinkage structures; STREAMS drivers additionally define module_info and streamtab; then define the soft state opaque pointer; and finally the entry point functions. So do USB serial drivers except they also need to define ds_ops. STREAMS structures look like this:
struct module_info usbsxx_modinfo = {
0, /* module id */
"usbsxx", /* module name */
USBSER_MIN_PKTSZ, /* min pkt size */
USBSER_MAX_PKTSZ, /* max pkt size */
USBSER_HIWAT, /* hi watermark */
USBSER_LOWAT /* low watermark */
};
static struct qinit usbsxx_rinit = {
putq,
usbser_rsrv,
usbsxx_open,
usbser_close,
NULL,
&usbsxx_modinfo,
NULL
};
static struct qinit usbsxx_winit = {
usbser_wput,
usbser_wsrv,
NULL,
NULL,
NULL,
&usbsxx_modinfo,
NULL
};
struct streamtab usbsxx_str_info = {
&usbsxx_rinit, &usbsxx_winit, NULL, NULL
};
Other driver structures are nothing different from any other driver. Don't forget to put streamtab address in cb_ops. _init(9E) is special in that it should use usbser_soft_state_size() for soft state allocation:
static void *usbsxx_statep; /* opaque state pointer */
int
_init(void)
{
int error;
if ((error = mod_install(&modlinkage)) == 0) {
error = ddi_soft_state_init(&usbsxx_statep,
usbser_soft_state_size(), 1);
}
return (error);
}
GSD provides standard implementations of driver entry points, see for instance the qinit structures above. Functions that require the opaque state pointer need to be called explicitly:
int
usbsxx_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
return (usbser_getinfo(dip, infocmd, arg, result,
usbsxx_statep));
}
static int
usbsxx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
return (usbser_attach(dip, cmd, usbsxx_statep, &ds_ops));
}
static int
usbsxx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
return (usbser_detach(dip, cmd, usbsxx_statep));
}
static int
usbsxx_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr)
{
return (usbser_open(rq, dev, flag, sflag, cr, usbsxx_statep));
}
Attach ds_attach() should allocate and initialize the soft state, configure the device, figure out number of ports and register itself with the USB framework.
static int
usbsxx_ds_attach(ds_attach_info_t *aip)
{
usbsxx_state_t *xxp;
xxp = (usbsxx_state_t *)kmem_zalloc(sizeof (usbsxx_state_t), KM_SLEEP);
xxp->xx_dip = aip->ai_dip;
xxp->xx_usb_events = aip->ai_usb_events;
*aip->ai_hdl = (ds_hdl_t)xxp;
if (usb_client_attach(xxp->xx_dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
usbsxx_cleanup(xxp, 1);
return (USB_FAILURE);
}
if (usb_get_dev_data(xxp->xx_dip, &xxp->xx_dev_data, USB_PARSE_LVL_IF,
0) != USB_SUCCESS) {
usbsxx_cleanup(xxp, 2);
return (USB_FAILURE);
}
xxp->xx_def_pipe_handle = xxp->xx_dev_data->dev_default_ph;
mutex_init(&xxp->xx_mutex, NULL, MUTEX_DRIVER,
xxp->xx_dev_data->dev_iblock_cookie);
/* ... device specific code ... */
xxp->xx_dev_state = USB_DEV_ONLINE;
/* ... register USB events .. */
*aip->ai_port_cnt = 1;
return (USB_SUCCESS);
}
Open and close Typically ds_open_port() notifies the device of a port open with a special command, does per-port state initialization, opens USB pipes and kicks off data receipt (by submitting a Bulk In request or starting interrupt endpoint polling). ds_close_port() should dismiss any leftover data characters (GSD is expected to drain and flush before closing, but we want to be on the safe side) and in general reverse what ds_open_port() has done.
static int
usbsxx_open_port(ds_hdl_t hdl, uint_t port_num)
{
usbsxx_state_t *xxp = (usbsxx_state_t *)hdl;
usbsxx_port_t *pp = &xxp->xx_ports[port_num];
if (usbsxx_open_pipes(pp) != USB_SUCCESS) {
return (USB_FAILURE);
}
if (usbsxx_send_cmd(pp, USBSXX_HW_OPEN) != USB_SUCCESS) {
return (USB_FAILURE);
}
pp->port_state = USBSXX_PORT_OPEN;
usbsxx_rx_start(pp); /* start data receipt */
return (USB_SUCCESS);
}
static int
usbsxx_close_port(ds_hdl_t hdl, uint_t port_num)
{
usbsxx_state_t *xxp = (usbsxx_state_t *)hdl;
usbsxx_port_t *pp = &xxp->xx_ports[port_num];
usbsxx_fifo_flush(hdl, port_num, DS_TX | DS_RX);
usbsxx_rx_stop(pp);
(void) usbsxx_send_cmd(pp, USBSXX_HW_CLOSE);
usbsxx_close_pipes(pp);
pp->port_state = USBSXX_PORT_CLOSED;
return (USB_SUCCESS);
}
Here is an example of a synchronous command request using Control pipe:
static int
usbsxx_send_cmd(usbsxx_port_t *pp, uint16_t value, int16_t index)
{
usb_ctrl_setup_t setup = { USBSXX_HW_WRITE_REQ_TYPE,
USBSXX_HW_WRITE_REQ, 0, 0,
USBSXX_HW_WRITE_LENGTH, 0 };
usb_cb_flags_t cb_flags;
usb_cr_t cr;
setup.wValue = value;
setup.wIndex = index;
return (usb_pipe_ctrl_xfer_wait(pp->ctrl_ph, &setup, NULL,
&cr, &cb_flags, 0));
}
Port parameters Port parameters need to be parsed and turned into device-specific actions. Baud rate may require additional conversion of the baud constants (B9600, etc) into device-specific values, like absolute rate values or UART divisors.
/* zero means unsupported rate */
static int usbsxx_speedtab[] = {
0, /* B0 */
0, /* B50 */
75, /* B75 */
0, /* B110 */
0, /* B134 */
150, /* B150 */
0, /* B200 */
300, /* B300 */
600, /* B600 */
1200, /* B1200 */
1800, /* B1800 */
2400, /* B2400 */
4800, /* B4800 */
9600, /* B9600 */
19200, /* B19200 */
38400, /* B38400 */
57600, /* B57600 */
0, /* B76800 */
115200, /* B115200 */
0, /* B153600 */
230400 /* B230400 */
};
#define NELEM(a) (sizeof (a) / sizeof (*(a)))
static int
usbsxx_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t *tp)
{
usbsxx_state_t *xxp = (usbsxx_state_t *)hdl;
usbsxx_port_t *pp = &xxp->xx_ports[port_num];
int i;
uint_t ui;
ds_port_param_entry_t *pe;
pe = tp->tp_entries;
for (i = 0; i < tp->tp_cnt; i++, pe++) {
switch (pe->param) {
case DS_PARAM_BAUD:
ui = pe->val.ui;
/* for unsupported speeds return failure */
if ((ui >= NELEM(usbsxx_speedtab)) ||
((ui > 0) && (usbsxx_speedtab[ui] == 0))) {
return (USB_FAILURE);
}
/* set baud rate */
break;
case DS_PARAM_PARITY:
if (pe->val.ui & PARENB) {
if (pe->val.ui & PARODD) {
/* set odd parity */
} else {
/* set even parity */
}
} else {
/* disable parity */
}
break;
case DS_PARAM_STOPB:
if (pe->val.ui & CSTOPB) {
/* set stop bits */
} else {
/* set stop bits */
}
break;
case DS_PARAM_CHARSZ:
switch (pe->val.ui) {
case CS5:
/* set 5 bits */
break;
case CS6:
/* set 6 bits */
break;
case CS7:
/* set 7 bits */
break;
case CS8:
default:
/* set 8 bits */
break;
}
break;
case DS_PARAM_XON_XOFF:
if (pe->val.ui & IXON || pe->val.ui & IXOFF) {
uint8_t xon_char, xoff_char;
xon_char = pe->val.uc[0];
xoff_char = pe->val.uc[1];
/* set XON/XOFF chars */
}
break;
case DS_PARAM_FLOW_CTL:
if (pe->val.ui & CTSXON) {
/* enable hardware flow control */
}
break;
default:
break;
}
}
return (USB_SUCCESS);
}
Data transmission Typical ds_tx() would queue up the data block and kick off the transmission, if not already.
static void
usbsxx_put_tail(mblk_t **mpp, mblk_t *bp)
{
if (*mpp) {
linkb(*mpp, bp);
} else {
*mpp = bp;
}
}
static int
usbsxx_tx(ds_hdl_t hdl, uint_t port_num, mblk_t *mp)
{
usbsxx_state_t *xxp = (usbsxx_state_t *)hdl;
usbsxx_port_t *pp = &xxp->xx_ports[port_num];
int xferd;
/* sanity checks */
if (mp == NULL) {
return (USB_SUCCESS);
}
if (MBLKL(mp) <= 0) {
freemsg(mp);
return (USB_SUCCESS);
}
mutex_enter(&pp->mutex);
usbsxx_put_tail(&pp->tx_mp, mp);
usbsxx_tx_start(pp, &xferd);
mutex_exit(&pp->mutex);
return (USB_SUCCESS);
}
usbsxx_tx_start() is device specific, but typically the alrogithm is to take as much data off the queue as the device and the controller (see usb_pipe_get_max_bulk_transfer_size()) allow, submit a bulk request, and when the request completion callback - repeat until no data is left on the queue. Transmission ends by calling GSD's tx_cb() callback. It might also be necessary to wake up the data draining code by signalling the respective conditional variable. Here's an example of a Bulk Out request:
static int
usbsxx_send_data(usbsxx_port_t *pp, mblk_t *data)
{
usb_bulk_req_t *br;
int rval;
br = usb_alloc_bulk_req(pp->dip, 0, USB_FLAGS_SLEEP);
br->bulk_data = data;
br->bulk_len = MBLKL(data);
br->bulk_timeout = USBSXX_BULKOUT_TIMEOUT;
br->bulk_cb = usbsxx_bulkout_cb;
br->bulk_exc_cb = usbsxx_bulkout_cb;
br->bulk_client_private = (usb_opaque_t)pp;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING;
rval = usb_pipe_bulk_xfer(pp->bulkout_ph, br, 0);
if (rval != USB_SUCCESS) {
usb_free_bulk_req(br);
}
return (rval);
}
Data receipt The driver is usually notified of the received data by a Bulk In or an Interrupt callback. The data is added to the list of received data, the GSD callback is invoked and the next request for receive is submitted.
static void
usbsxx_put_head(mblk_t **mpp, mblk_t *bp)
{
if (*mpp) {
linkb(bp, *mpp);
}
*mpp = bp;
}
void
usbsxx_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
usbsxx_port_t *pp = (usbsxx_state_t *)req->bulk_client_private;
usbsxx_state_t *xxp = pp->soft_state;
mblk_t *data;
int data_len;
data = req->bulk_data;
data_len = (data) ? MBLKL(data) : 0;
if ((pp->port_state == USBSXX_PORT_OPEN) && (data_len) &&
(req->bulk_completion_reason == USB_CR_OK)) {
/* prevent USBA from freeing data along with the request */
req->bulk_data = NULL;
/* save data on the receive list */
usbsxx_put_tail(&pp->rx_mp, data);
/* invoke GSD receive callback */
if (pp->cb.cb_rx) {
pp->cb.cb_rx(pp->cb.cb_arg);
}
}
usb_free_bulk_req(req);
usbsxx_rx_start(pp); /* receive more */
}
The only thing left for ds_rx() to do is simply return pp->rx_mp. Flush and drain
static int
usbsxx_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir)
{
usbsxx_state_t *xxp = (usbsxx_state_t *)hdl;
usbsxx_port_t *pp = &xxp->xx_ports[port_num];
if ((dir & DS_TX) && pp->tx_mp) {
freemsg(pp->tx_mp);
pp->tx_mp = NULL;
}
if ((dir & DS_RX) && pp->rx_mp) {
freemsg(pp->rx_mp);
pp->rx_mp = NULL;
}
return (USB_SUCCESS);
}
Notice that freemsg() is used, but freeb(), because we want to free all b_cont-linked messages. Data drain can occur at two levels: first draining DSD's internal buffer by waiting on a conditional variable and then draining device's buffer by sending a special command. Compile and install USB serial driver modules should be linked with the following parameters: ld -r -dy -Nmisc/usba -Nmisc/usbser -o usbsxx usbsxx.o This is to ensure that 'usba' (USB architecture) and 'usbser' (GSD) misc modules are loaded into the kernel memory before DSD is loaded. Drivers should be installed using the standard add_drv(1M) command. In addition to that, an autopush entry should be added to /etc/iu.ap: usbsxx -1 0 ldterm ttcompat This is to ensure that ldterm(7M) and ttcompat(7M) are automatically pushed on top of the DSD. Verifying that the entry works is easy: # strconf < /dev/cua/0 ttcompat ldterm usbsxx That's it, folks. I hope this blog proves useful to someone either writing a new USB serial driver for Solaris or debugging an existing driver. As always, email your comments and questions to artem dot kachitchkin at sun dot com. Tags:
Solaris
OpenSolaris
USB
Here I discuss some aspects of DSDI, the Device Specific Driver Interface - the interface between GSD and DSD. All DSDI definitions are in usbser_dsdi.h header file. There are plenty of comments there, so I'll skip the least interesting parts. Versioning DSDI provides simple versioning via ds_version. DSD should always set it to DS_OPS_VERSION, which is then resolved to the right value during compilation:
enum {
DS_OPS_VERSION_V0 = 0,
DS_OPS_VERSION = DS_OPS_VERSION_V0
};
Version number is passed to the GSD with the ds_ops structure as an argument to ds_attach(), which is the very first DSD->GSD call. The GSD will be able to provide DSD with the right version of interfaces or fail attach if it doesn't support this version. Initial configuration ds_attach() is called during driver attach(9E) phase:
int (*ds_attach)(ds_attach_info_t *aip);
The only argument is a pointer to the structure:
typedef struct ds_attach_info {
/*
* passed to DSD:
*/
dev_info_t *ai_dip; /* devinfo */
/*
* these event callbacks should be registered by DSD
* using usb_register_event_cbs()
*/
usb_event_t *ai_usb_events;
/*
* returned by DSD:
*/
ds_hdl_t *ai_hdl; /* handle to be used by GSD in other calls */
uint_t *ai_port_cnt; /* number of ports */
} ds_attach_info_t;
Pretty self-explanatory, ai_dip and ai_usb_events are input parameters, ai_hdl and ai_port_cnt are output parameters. Attach is the right place to allocate and initialize per-device and per-port resources, download firmware, reset the device into a known state. Some drivers may also open USB pipes at attach time, although doing it at port open time is more preferable. After attach, the driver would typically register callbacks using ds_register_cb():
typedef struct ds_cb {
void (*cb_tx)(caddr_t); /* transmit callback */
void (*cb_rx)(caddr_t); /* receive callback */
void (*cb_status)(caddr_t); /* status change callback */
caddr_t cb_arg; /* callback argument */
} ds_cb_t;
int (*ds_register_cb)(ds_hdl_t, uint_t port_num, ds_cb_t *cb);
Note that callback registration is per port. Typically the function pointers will be the same, but cb_arg is different to uniquely identify a port. Working with ports Before GSD uses a port, it opens it using ds_open_port(). This is usually done when application uses open(2) system call on the serial device.
int (*ds_open_port)(ds_hdl_t, uint_t port_num);
Port open initializes the port, opens per-port USB pipes. It is also a good idea to ensure clean software and hardware state - do not assume that the preceding close performed all necessary cleanup. Right after opening, port settings can be in an unknown state, so GSD sets port parameters, such as baud rate and parity, using ds_set_port_params(): int (*ds_set_port_params)(ds_hdl_t, uint_t port_num, ds_port_params_t *tp); The ds_port_params_t structure contains a variable-length array of parameters. This function can be called at any time. In order to transmit one or more characters, the GSD calls ds_tx(): int (*ds_tx)(ds_hdl_t, uint_t port_num, mblk_t *mp); The data is passed in a STREAMS message block. Currently this operation must always succeed: if DSD cannot transmit immediately, it should buffer the data. When data transfer is completed, DSD should notify GSD by calling the previously registered cb_tx() callback. Data receipt is backwards: when data arrives, DSD buffers it, calls cb_rx() callback. GSD then calls ds_rx(), which returns all available data: mblk_t *(*ds_rx)(ds_hdl_t, uint_t port_num); The returned mblk_t can be a linked list of blocks (through the b_cont field). See also Part 2 for description how error bytes are represented. Other operations are pretty self-explanatory and/or described in the header file. In the fourth and the last part I will provide C code that can be used as a starting point for writing a new DSD. Tags:
Solaris
OpenSolaris
USB
[Part 1] The generic serial driver (GSD) hides a great deal of termio(7I) complexity from the USB serial driver writers. Another major benefit is that it ensures compliance with UNIX standards, such as Single UNIX specification (see chapter 11, General Terminal Interfaces). VSX-PCTS test suite includes terminal interface tests that all USB serial drivers should pass. Today I am going to discuss some aspects of GSD implementation. Open Open(2) implementation for serial devices is quite complicated, there a many rules to follow. Recall that there are two types of device nodes per serial port: /dev/term (aka tty lines) and /dev/cua (aka dial-out lines). When an application attempts to open a tty line, the open(2) system call should block until Carrier Detect signal is asserted. Dial-out opens do not block and succeed immediately. Now recall that there can be multiple applications attempting to open the same device simultaneously. For example, while one application is blocked in open(2) waiting for Carrier Detect, another opens the corresponding dial-out line; in this case, the dial-out open should succeed and the first application's open(2) should unblock and fail. Open behavior also varies depending on the O_NONBLOCK/O_NDELAY flags, soft carrier setting and "ignore-cd" device properties. usbser_open_setup() function accounts for all possible scenarios through the used of a state machine. Close Quite a few things should happen before a serial device can be closed:
This is done in usbser_close(). Threads The driver uses two threads, one each for read and write message processing. Strictly speaking, separate threads are not needed in Solaris 9 and up since the STREAMS scheduler was improved with dynamic task queues. However, GSD was first written for Solaris 8, and at that time is was problematic using blocking USB requests in the STREAMS context. So instead of doing processing in the STREAMS service routine, this is done in usbser_wq_thread and usbser_rq_thread, which sleep until woken for new requests. Look for calls to usbser_thr_wake(). Due to asynchronous nature of USB requests, operations that usually happen atomically, take several steps. To prevent multiple operations from stepping on each other, some of them are serialized using usbser_serialize_port_act() and usbser_release_port_act(). The core of write-side processing is usbser_wmsg: it dequeues one message at a time and dispatches it depending on type, such as M_STOP, M_DATA or M_IOCTL. Ioctl usbser_ioctl() handles various terminal ioctls, passed via M_IOCTL messages. Setting port parameters, sending break, draining and flushing data, enabling internal loopback mode (great for testing), getting and setting port signals - all this is done here by calling down to DSD. Not all ioctls are handled by GSD, however, some of them are passed to the ttycommon_ioctl() function, which is a generic terminal ioctl implementation used by all Solaris serial drivers. Status changes When the DSD detects modem status line changes, such as CTS or CD, it notifies GSD with the status callback. usbser_status_cb(), the callback handler, does not process this request immediately. Instead, it sets a special flag, USBSER_FL_STATUS_CB, and wakes up the write thread. usbser_wq_thread() checks the flag and if it's set, calls the actual handler, usbser_status_proc_cb(). Because the status callback is called from DSD, and the callback handler may need to call other DSD functions, we avoid recursive calls into DSD by delegating status handling to the write thread. Back in Solaris 8, the DSD callback was called in the interrupt context, so a recursive call into DSD could lead to a deadlock. Outgoing data usbser_data() simply takes an M_DATA message and passes it on to the DSD by calling USBSER_DS_TX() for transmission. The DSD can't refuse the message, if it needs to postpone transmission, it has to maintain its own queue. When DSD is done transmitting the data, is would call back into GSD. usbser_tx_cb() callback handler simply wakes up the write thread to check for any new data to transfer. Incoming data When DSD receives new data, it lets GSD know via RX callback. See usbser_rx_cb() how it retrieves the data from DSD's queue by calling USBSER_DS_RX(), processes it and sends upstream. Data processing is the interesting part. If data was received without errors, then it's just a linked list of message blocks (mblk_t), which will be put on the stream's read queue after being processed by usbser_rx_massage_data(). This extra step is required for standards compliance: under certain conditions, a received character '\377' (0xFF) should be read by application as two such characters, i.e. '\377' followed by another '\377'. If a character was received with a framing or parity error, the DSD must pass it it to GSD as an M_BREAK message. The first byte should contain the error code, with the character in the second byte. The GSD then does the right thing for termio in usbser_rx_massage_mbreak(). Flow control Flow control is needed when a receive buffer on either end of communication becomes full and it signals the transmitting end to suspend transmission temporarily, until signalled to resume. There are two types of signalling:
Both types of flow control can be implemented in hardware. However, not all devices support flow control, and because GSD should work with any device, it implements both types in software. Inbound flow control happens when our local queue becomes full (reaches the high watermark) and we want to ask the device to stop transmission. Both hardware and software type of flow control are done in usbser_inbound_flow_ctl(). Outbound flow control happens when the other end asks us to stop transmission. For the hardware type,the GSD detects CTS transition through the status callback, see usbser_status_proc_cb(). When CTS turns 0, an M_STOP message is put on GSD's local write queue; when CTS turns 1, an M_START message is put. usbser_wmsg() then calls usbser_stop() and usbser_start() respectively, to suspend or resume data transmittion. Next time - DSDI. Tags:
Solaris
OpenSolaris
USB
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||