I'm studying Linux network device driver with Linux 4.12.14, and here is what I've learnt:
In device driver, it sets some variables when the device is opened:
dev->watchdog_timeo = TX_TIMEOUT;
...
.ndo_tx_timeout = el3_tx_timeout,
Linux kernel activates a timer when the device is opened:
static void dev_watchdog(unsigned long arg)
{
struct net_device *dev = (struct net_device *)arg;
netif_tx_lock(dev);
if (!qdisc_tx_is_noop(dev)) {
if (netif_device_present(dev) &&
netif_running(dev) &&
netif_carrier_ok(dev)) {
int some_queue_timedout = 0;
unsigned int i;
unsigned long trans_start;
for (i = 0; i < dev->num_tx_queues; i++) {
struct netdev_queue *txq;
txq = netdev_get_tx_queue(dev, i);
trans_start = txq->trans_start;
if (netif_xmit_stopped(txq) &&
time_after(jiffies, (trans_start +
dev->watchdog_timeo))) {
some_queue_timedout = 1;
txq->trans_timeout++;
break;
}
}
if (some_queue_timedout) {
WARN_ONCE(1, KERN_INFO "NETDEV WATCHDOG: %s (%s): transmit queue %u timed out\n",
dev->name, netdev_drivername(dev), i);
dev->netdev_ops->ndo_tx_timeout(dev);
}
if (!mod_timer(&dev->watchdog_timer,
round_jiffies(jiffies +
dev->watchdog_timeo)))
dev_hold(dev);
}
}
netif_tx_unlock(dev);
dev_put(dev);
}
So, after kernel transfers a skb, it updates txq->trans_start:
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
const struct net_device_ops *ops = dev->netdev_ops;
int rc;
rc = __netdev_start_xmit(ops, skb, dev, more);
if (rc == NETDEV_TX_OK)
txq_trans_update(txq);
return rc;
}
And the function to update txq->trans_start:
static inline void txq_trans_update(struct netdev_queue *txq)
{
if (txq->xmit_lock_owner != -1)
txq->trans_start = jiffies;
}
However, I didn't find anywhere updates the "txq->trans_start" again, so my question is:
How come a successful transmit doesn't make the watchdog timer timeout?
Related
I am now developing a kernel module to handle two digits 7-segment led indicator via I2C device on RaspberryPi4 with RaspberryPiOS.
This module uses kernel thread to handle 7-segment led display, change digit position.
Sending command to i2c device requires i2c_client* structure, it can be obtained via formal argument of probe function.
Kernel thread does not have formal argument of i2c_client structure.
My solution is to store a pointer of i2c_client* structure into a global variable and use this pointer inside kernel thread, and call i2c function with this pointer.
My kernel module works well, at least now...
Do you have better solution to use i2c function in kernel thread? or exists better solution?
My entire code is below...
//i2c_7seg2_udev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/kthread.h>
MODULE_LICENSE("GPL v2");
#define DEVICE_NAME "i2c_7seg2_udev"
#define MINOR_BASE 0
#define MINOR_NUM 1
static unsigned int major_num;
struct cdev i2c_7seg2_udev_cdev;
void init_i2c_gpio(void);
void turn_off_7seg(void);
void turn_off_7seg(void);
void set_7seg(unsigned int number);
void change_7seg_keta(void);
#define LOOP_SLEEP_US (5000)
static struct task_struct *kthread;
static int dynamic_7seg_kthread(void *data);
static void init_kthread(void)
;
static int dynamic_7seg_kthread(void *data)
{
while(!kthread_should_stop()){
change_7seg_keta();
usleep_range(LOOP_SLEEP_US, LOOP_SLEEP_US * 2);
}
return 0;
}
static void init_kthread(void)
{
kthread = kthread_create(dynamic_7seg_kthread, NULL, "dynamic_7seg_kthread");
wake_up_process(kthread);
}
static int init_regist_device(void);
static struct i2c_client *i2c_client_data;
#define DRIVER_NAME "i2c_7seg2_udev"
static struct i2c_device_id i2c_7seg2_udev_device_idtable[] = {
{"I2C_7SEG2_UDEV", 0},
{ }
};
MODULE_DEVICE_TABLE(i2c, i2c_7seg2_udev_device_idtable);
static int i2c_7seg2_udev_device_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
if(init_regist_device() < 0){
printk(KERN_DEBUG "i2c_7seg2_udev: initialize failed.\n");
return -1;
}
printk(KERN_DEBUG "i2c_7seg2_udev_device connected.\n");
printk(KERN_DEBUG "id.name = %s, id.driver_data = %ld", id->name, id->driver_data);
printk(KERN_DEBUG "device address is: 0x%02X\n", client->addr);
i2c_client_data = client;
init_i2c_gpio();
set_7seg(12);
init_kthread();
return 0;
}
static int i2c_7seg2_udev_device_remove(struct i2c_client *client)
{
dev_t dev;
turn_off_7seg();
printk(KERN_DEBUG "i2c_7seg2_udev: wait for thread to be terminated.\n");
kthread_stop(kthread);
printk(KERN_DEBUG "i2c_7seg2_udev: thread terminated.\n");
dev = MKDEV(major_num, MINOR_BASE);
cdev_del(&i2c_7seg2_udev_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
printk(KERN_DEBUG "i2c_7seg2_udev_device disconnected.\n");
return 0;
}
static struct i2c_driver i2c_7seg2_udev_device_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.id_table = i2c_7seg2_udev_device_idtable,
.probe = i2c_7seg2_udev_device_probe,
.remove = i2c_7seg2_udev_device_remove,
};
#define MAX_7SEG_KETA (2)
//NOTE: "KETA" means digit position, in Japanese.
static const unsigned int seg7_pattern[] =
//--gfedcba
{0b00111111, //0
0b00000110, //1
0b01011011, //2
0b01001111, //3
0b01100110, //4
0b01101101, //5
0b01111100, //6
0b00100111, //7
0b01111111, //8
0b01100111 //9
};
unsigned char value_7seg[MAX_7SEG_KETA];
DEFINE_MUTEX(__mutex_value_7seg);
void set_7seg(unsigned int number){
unsigned int i;
printk(KERN_DEBUG "i2c_7seg2_udev: value %d .\n", number);
for( i = 0; i < MAX_7SEG_KETA; i++){
value_7seg[MAX_7SEG_KETA - i - 1] = (number % 10) + '0';
number = number / 10;
}
}
void change_7seg_keta(void)
{
static unsigned int keta = 0;
unsigned char keta_shift;
unsigned int number;
keta_shift = 0x01 << keta;
i2c_smbus_write_byte_data(i2c_client_data, 0x03, ~keta_shift); //P1に出力
number = value_7seg[MAX_7SEG_KETA - keta - 1] - '0';
i2c_smbus_write_byte_data(i2c_client_data, 0x02, ~seg7_pattern[number]); //P0に出力
keta ++;
if( keta >= MAX_7SEG_KETA ){
keta = 0;
}
}
void turn_off_7seg(void){
i2c_smbus_write_byte_data(i2c_client_data, 0x02, 0x7F); //P0に出力
}
void init_i2c_gpio(void){
i2c_smbus_write_byte_data(i2c_client_data, 0x06, 0x00);
i2c_smbus_write_byte_data(i2c_client_data, 0x07, 0x00);
}
static int i2c_7seg2_udev_open(struct inode *inode, struct file *file)
{
printk(KERN_DEBUG "i2c_7seg2_udev: opened.\n");
return 0;
}
static int i2c_7seg2_udev_close(struct inode *inode, struct file *file)
{
printk(KERN_DEBUG "i2c_7seg2_udev: closed.\n");
return 0;
}
static ssize_t i2c_7seg2_udev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
unsigned char read_datas[MAX_7SEG_KETA + 2];
unsigned long bytes_left;
const unsigned long bytes_to_send = MAX_7SEG_KETA + 2;
unsigned int i;
for(i = 0; i < MAX_7SEG_KETA; i++ ){
read_datas[i] = value_7seg[i];
}
read_datas[i] = '\n';
read_datas[i + 1] = '\0';
bytes_left = copy_to_user(buf, read_datas, bytes_to_send);
return (bytes_to_send - bytes_left);
}
static ssize_t i2c_7seg2_udev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
unsigned char write_values[MAX_7SEG_KETA + 2];
unsigned long bytes_left;
const unsigned long bytes_to_send = MAX_7SEG_KETA + 2;
int i;
printk(KERN_DEBUG "i2c_7seg2_udev: write.");
printk(KERN_DEBUG "i2c_7seg2_udev: write.");
bytes_left = copy_from_user(write_values, buf, bytes_to_send);
mutex_lock(&__mutex_value_7seg);
for(i = 0; i < MAX_7SEG_KETA; i++){
if( (write_values[i] >= '0') && (write_values[i] <= '9') ){
value_7seg[i] = write_values[i];
}
}
mutex_unlock(&__mutex_value_7seg);
return (bytes_to_send - bytes_left);
}
struct file_operations s_i2c_7seg2_udev_fops = {
.open = i2c_7seg2_udev_open,
.release = i2c_7seg2_udev_close,
.read = i2c_7seg2_udev_read,
.write = i2c_7seg2_udev_write,
};
int init_regist_device(void)
{
int device_num = 0;
int devnum_err = 0;
int cdev_err = 0;
dev_t dev;
devnum_err = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DEVICE_NAME);
if (devnum_err != 0) {
printk(KERN_ERR "devnum_err = %d\n", devnum_err);
return -1;
}
if (device_num != 0) {
printk(KERN_ERR "i2c_7seg2_udev: error_init_regist_device , %d\n", device_num);
return -1;
}
major_num = MAJOR(dev);
cdev_init(&i2c_7seg2_udev_cdev, &s_i2c_7seg2_udev_fops);
i2c_7seg2_udev_cdev.owner = THIS_MODULE;
cdev_err = cdev_add(&i2c_7seg2_udev_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", cdev_err);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
printk(KERN_DEBUG "i2c_7seg2_udev: device registerd.\n");
return 0;
}
static int i2c_7seg2_udev_device_init(void)
{
printk(KERN_DEBUG "i2c_7seg2_udev device driver loaded.\n");
i2c_add_driver(&i2c_7seg2_udev_device_driver);
return 0;
}
static void i2c_7seg2_udev_device_exit(void)
{
printk(KERN_DEBUG "i2c_7seg2_udev device driver unloading.\n");
i2c_del_driver(&i2c_7seg2_udev_device_driver);
}
module_init(i2c_7seg2_udev_device_init);
module_exit(i2c_7seg2_udev_device_exit);
The second argument of the function kthread_create() is void *data.
Right now you are passing NULL, you can pass your pointer to the struct i2c_client instead.
Then you can do:
static int dynamic_7seg_kthread(void *data) {
struct i2c_client *client = data;
while (!kthread_should_stop()) {
change_7seg_keta(client);
usleep_range(LOOP_SLEEP_US, LOOP_SLEEP_US * 2);
}
return 0;
}
Thus eliminating global variable from the code.
I am writing a dummy PHY driver as a LKM. This is a platform driver
and I can't call the driver probe since it depends on the compatible string.
How can I make my driver invoke probe in my host machine, which is running
Ubuntu 18.04, kernel version - 5.3.0-40-generic. My machine is X86. I am installing this phy driver on my X86 machine.
Here is my code:
static int phy_platform_probe(struct platform_device *pdev) {
struct custom_port_phy_platform_local *lp;
int ret;
printk(KERN_ALERT "abc..phy_platform_probe..Invoked\r\n");
lp = devm_kzalloc(&pdev->dev,sizeof(struct custom_port_phy_platform_local),GFP_KERNEL);
if (!lp) {
dev_err(&(pdev->dev),"Failed to allocatate platform_local\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, lp);
lp->dev = &pdev->dev;
ret = custom_port_phy_mdio_setup(lp, pdev->dev.of_node);
if (ret < 0) {
dev_err(&(pdev->dev),"Failed to setup MDIO bus\n");
return ret;
}
return 0;
}
static int phy_platform_remove(struct platform_device *pdev) {
struct custom_port_phy_platform_local *lp = platform_get_drvdata(pdev);
dev_info(&pdev->dev,"%s Enter\n",__func__);
custom_port_phy_mdio_teardown(lp);
return 0;
}
static struct phy_driver custom_phy_driver = {
.phy_id = 0x00000002,
.phy_id_mask = 0xffffffff,
.name = "custom_port_phy",
};
static struct platform_driver custom_phy_platform_driver = {
.probe = phy_platform_probe,
.remove = phy_platform_remove,
#if 1
.driver = {
.name = "custom_port_phy"
.of_match_table = port_phy_of_match,
}
#endif
};
#if 1
/* Match table for of_platform binding */
static struct of_device_id port_phy_of_match[] = {
{ .compatible = "custom,custom-port-phy", },
{},
};
#endif
static int __init phy_init(void)
{
int ret = 0;
printk(KERN_ALERT "abc >>> phy_driver_register started\r\n");
ret = phy_driver_register(&custom_phy_driver, THIS_MODULE);
printk(KERN_ALERT "abc >>> phy_driver_register END\r\n");
if(ret < 0) {
printk(KERN_ALERT "custom phy driver registration failed\r\n");
return ret;
}
ret = platform_driver_register(&custom_phy_platform_driver);
if(ret < 0) {
phy_driver_unregister(&custom_phy_driver);
printk("%s: Failed to register as Platform Driver\r\n", __func__);
return ret;
}
return ret;
}
static void __exit phy_exit(void)
{
pr_info("Goodbye phy_driver .\n");
phy_driver_unregister(&custom_phy_driver);
platform_driver_unregister(&custom_phy_platform_driver);
}
module_init(phy_init);
module_exit(phy_exit);
MODULE_LICENSE("GPL");
My code doesn't do probe since it can't find compatible flag.
How can I make it work on my host machine? I know I need dtb file. Even if
I have dtb file, how do I supply it and how to write it that way?
I am new to DPDK and trying to create a packet to send it from one DPDK enabled machine to another connected directly via an ethernet. I modified an example/rxtx_callbacks/main.c provided with DPDK at both side. However, I am not receiving anything at the receiver. What wrong am I doing?
Modified function at transmitter: lcore_main is modified:
static __attribute__((noreturn)) void lcore_main()
{
uint16_t port;
struct ether_hdr *eth_hdr;
struct ether_addr daddr;
daddr.addr_bytes[0] = 116;
daddr.addr_bytes[1] = 225;
daddr.addr_bytes[2] = 228;
daddr.addr_bytes[3] = 204;
daddr.addr_bytes[4] = 106;
daddr.addr_bytes[5] = 82;
//rte_eth_macaddr_get(portid, &addr);
struct ipv4_hdr *ipv4_hdr;
int32_t i;
int ret;
RTE_ETH_FOREACH_DEV(port)
if (rte_eth_dev_socket_id(port) > 0 &&
rte_eth_dev_socket_id(port) !=
(int)rte_socket_id())
printf("WARNING, port %u is on remote NUMA node to "
"polling thread.\n\tPerformance will "
"not be optimal.\n", port);
printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n",
rte_lcore_id());
//struct rte_mbuf *m_head = rte_pktmbuf_alloc(mbuf_pool);
struct rte_mbuf *m_head[BURST_SIZE];
for (;;) {
RTE_ETH_FOREACH_DEV(port) {
if(rte_pktmbuf_alloc_bulk(mbuf_pool, m_head, BURST_SIZE)!=0)
{
printf("Allocation problem\n");
}
for(i = 0; i < BURST_SIZE; i++) {
eth_hdr = rte_pktmbuf_mtod(m_head[i], struct ether_hdr *);
//eth_hdr = (struct ether_hdr *)rte_pktmbuf_append(m_head[i],
// sizeof(struct ether_hdr));
eth_hdr->ether_type = htons(ETHER_TYPE_IPv4);
rte_memcpy(&(eth_hdr->s_addr), &addr, sizeof(struct ether_addr));
rte_memcpy(&(eth_hdr->d_addr), &daddr, sizeof(struct ether_addr));
}
const uint16_t nb_tx = rte_eth_tx_burst(port, 0, m_head, BURST_SIZE);
if (unlikely(nb_tx < BURST_SIZE)) {
uint16_t buf;
for (buf = nb_tx; buf < BURST_SIZE; buf++)
rte_pktmbuf_free(m_head[buf]);
}
}
}
}
receiver side RTE_ETH_FOREACH_DEV of tx part is modified to:
RTE_ETH_FOREACH_DEV(port) {
struct rte_mbuf *bufs[BURST_SIZE];
const uint16_t nb_rx = rte_eth_rx_burst(port, bufs, BURST_SIZE);
//printf("Number of Packets received %d\n", nb_rx);
for(i = 0; i < nb_rx; i++) {
//ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
// sizeof(struct ether_hdr));
//printf("Packet ip received %d\n", ipv4_hdr->src_addr);
eth_hdr = rte_pktmbuf_mtod(bufs[i], struct ether_hdr *);
printf("Packet ip received %d\n", eth_hdr->ether_type);
}
if (unlikely(nb_rx == 0))
continue;
const uint16_t nb_tx = 0; // = rte_eth_tx_burst(port ^ 1, 0, bufs, nb_rx);
if (unlikely(nb_tx < nb_rx)) {
uint16_t buf;
for (buf = nb_tx; buf < nb_rx; buf++)
rte_pktmbuf_free(bufs[buf]);
}
}
Please let me know if I missed something.
There are few issues with the code:
eth_hdr = rte_pktmbuf_mtod(m_head[i], struct ether_hdr *);
Unlike rte_pktmbuf_append(), the rte_pktmbuf_mtod() does not change the packet length, so it should be set manually before the tx.
eth_hdr->ether_type = htons(ETHER_TYPE_IPv4);
If we set ETHER_TYPE_IPv4, a correct IPv4 header must follow. So we need either to add the header or to change the ether_type.
rte_memcpy(&(eth_hdr->s_addr), &addr, sizeof(struct ether_addr));
Where is the source address comes from?
const uint16_t nb_tx = rte_eth_tx_burst(port, 0, m_head, BURST_SIZE);
Looks like we transmit a burst of zero-sized packets with invalid IPv4 headers. Please also make sure the source/destination addresses are correct.
As suggested by #andriy-berestovsky, I used rte_eth_stats_get() and it shows packets are present in ethernet ring via the field ipackets but rte_eth_rx_burst is not returning any packets. Full code is included here, please let me know what I am doing wrong. (I am using testpmd at transmitter side)
#include <stdint.h>
#include <inttypes.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_ether.h>
#include <rte_cycles.h>
#include <rte_lcore.h>
#include <rte_ip.h>
#include <rte_mbuf.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#define MAX_SOURCE_SIZE (0x100000)
#define RX_RING_SIZE 1024
#define TX_RING_SIZE 1024
#define NUM_MBUFS 8191
#define MBUF_CACHE_SIZE 250
#define BURST_SIZE 32
static const struct rte_eth_conf port_conf_default = {
.rxmode = {
.max_rx_pkt_len = ETHER_MAX_LEN,
},
};
static struct {
uint64_t total_cycles;
uint64_t total_pkts;
} latency_numbers;
static volatile bool force_quit;
struct rte_mempool *mbuf_pool;
static void
signal_handler(int signum)
{
struct rte_eth_stats eth_stats;
int i;
if (signum == SIGINT || signum == SIGTERM) {
printf("\n\nSignal %d received, preparing to exit...\n",
signum);
RTE_ETH_FOREACH_DEV(i) {
rte_eth_stats_get(i, ð_stats);
printf("Total number of packets received %llu, dropped rx full %llu and rest= %llu, %llu, %llu\n", eth_stats.ipackets, eth_stats.imissed, eth_stats.ierrors, eth_stats.rx_nombuf, eth_stats.q_ipackets[0]);
}
force_quit = true;
}
}
struct ether_addr addr;
/*
* Initialises a given port using global settings and with the rx buffers
* coming from the mbuf_pool passed as parameter
*/
static inline int
port_init(uint16_t port, struct rte_mempool *mbuf_pool)
{
struct rte_eth_conf port_conf = port_conf_default;
const uint16_t rx_rings = 1, tx_rings = 1;
uint16_t nb_rxd = RX_RING_SIZE;
uint16_t nb_txd = TX_RING_SIZE;
int retval;
uint16_t q;
struct rte_eth_dev_info dev_info;
struct rte_eth_txconf txconf;
if (!rte_eth_dev_is_valid_port(port))
return -1;
rte_eth_dev_info_get(port, &dev_info);
if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_MBUF_FAST_FREE)
port_conf.txmode.offloads |=
DEV_TX_OFFLOAD_MBUF_FAST_FREE;
retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
if (retval != 0)
return retval;
retval = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd);
if (retval != 0) {
printf("Error in adjustment\n");
return retval;
}
for (q = 0; q < rx_rings; q++) {
retval = rte_eth_rx_queue_setup(port, q, nb_rxd,
rte_eth_dev_socket_id(port), NULL, mbuf_pool);
if (retval < 0) {
printf("RX queue setup prob\n");
return retval;
}
}
txconf = dev_info.default_txconf;
txconf.offloads = port_conf.txmode.offloads;
for (q = 0; q < tx_rings; q++) {
retval = rte_eth_tx_queue_setup(port, q, nb_txd,
rte_eth_dev_socket_id(port), &txconf);
if (retval < 0)
return retval;
}
retval = rte_eth_dev_start(port);
if (retval < 0) {
printf("Error in start\n");
return retval;
}
rte_eth_macaddr_get(port, &addr);
printf("Port %u MAC: %02"PRIx8" %02"PRIx8" %02"PRIx8
" %02"PRIx8" %02"PRIx8" %02"PRIx8"\n",
(unsigned)port,
addr.addr_bytes[0], addr.addr_bytes[1],
addr.addr_bytes[2], addr.addr_bytes[3],
addr.addr_bytes[4], addr.addr_bytes[5]);
rte_eth_promiscuous_enable(port);
return 0;
}
/*
* Main thread that does the work, reading from INPUT_PORT
* and writing to OUTPUT_PORT
*/
static __attribute__((noreturn)) void
lcore_main(void)
{
uint16_t port;
struct ether_hdr *eth_hdr;
//struct ether_addr addr;
//rte_eth_macaddr_get(portid, &addr);
struct ipv4_hdr *ipv4_hdr;
int32_t i;
RTE_ETH_FOREACH_DEV(port)
{
if (rte_eth_dev_socket_id(port) > 0 &&
rte_eth_dev_socket_id(port) !=
(int)rte_socket_id())
printf("WARNING, port %u is on remote NUMA node to "
"polling thread.\n\tPerformance will "
"not be optimal.\n", port);
}
printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n",
rte_lcore_id());
for (;;) {
RTE_ETH_FOREACH_DEV(port) {
struct rte_mbuf *bufs[BURST_SIZE];
const uint16_t nb_rx = rte_eth_rx_burst(port, 0,bufs, BURST_SIZE);
for(i = 0; i < nb_rx; i++) {
ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *, sizeof(struct ether_hdr));
printf("Packet ip received %d\n", ipv4_hdr->src_addr);
}
if (unlikely(nb_rx == 0))
continue;
const uint16_t nb_tx = 0; // = rte_eth_tx_burst(port ^ 1, 0, bufs, nb_rx);
if (unlikely(nb_tx < nb_rx)) {
uint16_t buf;
for (buf = nb_tx; buf < nb_rx; buf++)
rte_pktmbuf_free(bufs[buf]);
}
}
if(force_quit)
break;
}
}
/* Main function, does initialisation and calls the per-lcore functions */
int
main(int argc, char *argv[])
{
uint16_t nb_ports;
uint16_t portid, port;
/* init EAL */
int ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");
argc -= ret;
argv += ret;
force_quit = false;
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
nb_ports = rte_eth_dev_count_avail();
printf("size ordered %lld\n", NUM_MBUFS *nb_ports);
mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL",
NUM_MBUFS * nb_ports, MBUF_CACHE_SIZE, 0,
RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
if (nb_ports < 1)
rte_exit(EXIT_FAILURE, "Error: number of ports must be greater than %d\n", nb_ports);
if (mbuf_pool == NULL)
rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");
// initialize all ports
RTE_ETH_FOREACH_DEV(portid)
if (port_init(portid, mbuf_pool) != 0)
rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu8"\n",
portid);
if (rte_lcore_count() > 1)
printf("\nWARNING: Too much enabled lcores - "
"App uses only 1 lcore\n");
// call lcore_main on master core only
lcore_main();
return 0;
}
It seems to be a problem of ethernet card with ubuntu 14.04. With ubuntu 16.04 it is working fine.
I'm new to Linux USB driver development, and I absolutely love it so far! I'm currently creating a driver for an Xbox One controller, and I have a question for you guys. In the below code, you will see that I fill an interrupt IN URB in the open function, and I would like to print the contents of the URB buffer in the xb1_int_in_callback() function. What would be the best way to do this? At the moment, I'm using printk(KERN_INFO "int_in_buffer: %s", dev->int_in_buffer), however I don't see the entire URB buffer contents printed, and get a weird string printed to dmesg.
Sorry if this is a simple question, I'm new to programming and C, so I'm still learning as I go, but I absolutely love it so far!
Code:
static void xb1_int_in_callback(struct urb *int_in_urb) {
struct xb1_controller *dev = int_in_urb->context;
printk(KERN_INFO "xb1_int_in_callback successfully called");
printk(KERN_INFO "int_in_buffer: %s", dev->int_in_buffer);
}
static int xb1_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "open function called..");
struct xb1_controller *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&xb1_driver, subminor);
if(!interface) {
printk(KERN_INFO "Unable to locate interface in open
function");
retval = -ENODEV;
goto exit;
}
dev = usb_get_intfdata(interface);
if(!dev) {
printk(KERN_INFO "Unable to locate dev structure in open
function");
retval = -ENODEV;
goto exit;
}
usb_fill_int_urb(dev->int_in_urb, dev->udev, usb_rcvintpipe(dev-
>udev, dev->int_in_endpoint->bEndpointAddress),
dev->int_in_buffer, dev->int_in_endpoint-
>wMaxPacketSize, xb1_int_in_callback,
dev, dev->int_in_endpoint->bInterval);
dev->int_in_running = 1;
retval = usb_submit_urb(dev->int_in_urb, GFP_KERNEL);
if(retval) {
printk(KERN_INFO "Unable to submit int_in_urb in open
function");
dev->int_in_running = 0;
goto exit;
}
file->private_data = dev;
exit:
return retval;
}
static int xb1_probe(struct usb_interface *interface, const struct
usb_device_id *id) {
struct usb_device *udev = interface_to_usbdev(interface);
struct xb1_controller *dev = NULL;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int i;
int retval = -ENODEV;
if(!udev) {
printk(KERN_INFO "udev is NULL in probe function");
xb1_abort(dev);
return retval;
}
dev = kzalloc(sizeof(struct xb1_controller), GFP_KERNEL);
if(!dev) {
printk(KERN_INFO "Unable to allocate memory for dev in probe
function");
xb1_abort(dev);
return retval;
}
dev->udev = udev;
dev->interface = interface;
iface_desc = interface->cur_altsetting;
for(i=0; i<iface_desc->desc.bNumEndpoints; i++) {
endpoint = &iface_desc->endpoint[i].desc;
if(((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
USB_DIR_IN)
&& ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
USB_ENDPOINT_XFER_INT)) {
dev->int_in_endpoint = endpoint;
}
}
if(!dev->int_in_endpoint) {
printk(KERN_INFO "Unable to locate interrupt in endpoint for
interface in probe function");
xb1_abort(dev);
return retval;
}
else {
printk(KERN_INFO "Interrupt in endpoint found!");
}
For printing a small buffers (up to 64 bytes long) use printk format:
Raw buffer as a hex string:
%*ph 00 01 02 ... 3f
%*phC 00:01:02: ... :3f
%*phD 00-01-02- ... -3f
%*phN 000102 ... 3f
For larger buffers use print_hex_dump(). Reference here.
I want to check the presence of an SD card, and receive notifications for SD card add/remove.
So far I have used libudev, and I've made a small application which listens for SD card events.
The code is listed below:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <sys/time.h> //debug -> remove me
#include <libudev.h>
#define ADD_FILTER "add"
#define REMOVE_FILTER "remove"
#define SUBSYSTEM_FILTER "block"
#define ATTR_FILTER "ID_MODEL"
#define SD_ATTR_VALUE "SD_MMC"
#define ATTR_ACTIVE_SD "ID_PART_TABLE_TYPE"
static bool isDeviceSD(struct udev_device *device);
static bool isDevPresent(struct udev *device);
static void print_device(struct udev_device *device, const char *source); //for debugging -> remove me
static bool s_bSD_present;
int main()
{
struct udev *udev;
struct udev_monitor *udev_monitor = NULL;
fd_set readfds;
s_bSD_present = false;
udev = udev_new();
if (udev == NULL)
{
printf("udev_new FAILED \n");
return 1;
}
s_bSD_present = isDevPresent(udev);
if(s_bSD_present)
{
printf("+++SD is plugged in \n");
}
else
{
printf("---SD is not plugged in \n");
}
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (udev_monitor == NULL) {
printf("udev_monitor_new_from_netlink FAILED \n");
return 1;
}
//add some filters
if( udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, SUBSYSTEM_FILTER, NULL) < 0 )
{
printf("udev_monitor_filter_add_match_subsystem_devtype FAILED \n");
return 1;
}
if (udev_monitor_enable_receiving(udev_monitor) < 0)
{
printf("udev_monitor_enable_receiving FAILED \n");
return 1;
}
while (1) {
printf("Polling for new data... \n");
int fdcount = 0;
FD_ZERO(&readfds);
if (udev_monitor != NULL)
{
FD_SET(udev_monitor_get_fd(udev_monitor), &readfds);
}
fdcount = select(udev_monitor_get_fd(udev_monitor)+1, &readfds, NULL, NULL, NULL);
if (fdcount < 0)
{
if (errno != EINTR)
printf("Error receiving uevent message\n");
continue;
}
if ((udev_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(udev_monitor), &readfds))
{
struct udev_device *device;
device = udev_monitor_receive_device(udev_monitor);
if (device == NULL)
continue;
//check the action
const char* szAction = udev_device_get_action(device);
if( strcmp(szAction, ADD_FILTER) == 0)
{
if( !s_bSD_present && isDeviceSD(device) )
{
s_bSD_present = true;
printf("+++SD has been plugged in \n");
}
}
else if( strcmp(szAction, REMOVE_FILTER) == 0 )
{
if( s_bSD_present && isDeviceSD(device) )
{
s_bSD_present = false;
printf("---SD has been removed \n");
}
}
udev_device_unref(device);
}
}
return 0;
}
static bool isDeviceSD(struct udev_device *device)
{
bool retVal = false;
struct udev_list_entry *list_entry = 0;
struct udev_list_entry* model_entry = 0;
struct udev_list_entry* active_sd_entry = 0;
list_entry = udev_device_get_properties_list_entry(device);
model_entry = udev_list_entry_get_by_name(list_entry, ATTR_FILTER);
if( 0 != model_entry )
{
const char* szModelValue = udev_list_entry_get_value(model_entry);
active_sd_entry = udev_list_entry_get_by_name(list_entry, ATTR_ACTIVE_SD);
if(strcmp(szModelValue, SD_ATTR_VALUE) == 0 && active_sd_entry != 0)
{
printf("Device is SD \n");
retVal = true;
//print_device(device, "UDEV");
}
}
return retVal;
}
static bool isDevPresent(struct udev *device)
{
bool retVal = false;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *dev_list_entry;
enumerate = udev_enumerate_new(device);
udev_enumerate_add_match_subsystem(enumerate, SUBSYSTEM_FILTER);
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices)
{
struct udev_device *dev;
const char* dev_path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(device, dev_path);
if( true == isDeviceSD(dev) )
{
retVal = true;
udev_device_unref(dev);
break;
}
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
return retVal;
}
static void print_device(struct udev_device *device, const char *source)
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
printf("%-6s[%llu.%06u] %-8s %s (%s)\n",
source,
(unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec,
udev_device_get_action(device),
udev_device_get_devpath(device),
udev_device_get_subsystem(device));
struct udev_list_entry *list_entry;
udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
printf("%s=%s\n",
udev_list_entry_get_name(list_entry),
udev_list_entry_get_value(list_entry));
printf("\n");
}
This code will get notifications for SD card add/remove (and the initial state of the SD - plugged/unplugged). However, it is more of a hack, and it doesn't work in all cases.
I currently use the ID_MODEL attribute of the device and check if it is SD_MMC - for SD cards. I need only this type of card for now, so it's enough.
When an SD card is inserted, the following events are sent for the subsystem block: 2 change events, and 1 add event for each partition. The event properties are listed below:
<----- change event - subsystem block - disk type disk ----->
UDEV [1339412734.522055] change /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd (block)
UDEV_LOG=3
ACTION=change
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd
SUBSYSTEM=block
DEVNAME=/dev/sdd
DEVTYPE=disk
SEQNUM=3168
ID_VENDOR=Generic-
ID_VENDOR_ENC=Generic-
ID_VENDOR_ID=0bda
ID_MODEL=SD_MMC
ID_MODEL_ENC=SD\x2fMMC\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=0151
ID_REVISION=1.00
ID_SERIAL=Generic-_SD_MMC_20060413092100000-0:2
ID_SERIAL_SHORT=20060413092100000
ID_TYPE=disk
ID_INSTANCE=0:2
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
ID_PART_TABLE_TYPE=dos
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION_TABLE=1
UDISKS_PARTITION_TABLE_SCHEME=mbr
UDISKS_PARTITION_TABLE_COUNT=2
MAJOR=8
MINOR=48
DEVLINKS=/dev/block/8:48 /dev/disk/by-id/usb-Generic-_SD_MMC_20060413092100000-0:2 /dev/disk/by-path/pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
<----- add event partition 1 - subsystem block - disk type partition ----->
UDEV [1339412734.719107] add /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd1 (block)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd1
SUBSYSTEM=block
DEVNAME=/dev/sdd1
DEVTYPE=partition
SEQNUM=3169
ID_VENDOR=Generic-
ID_VENDOR_ENC=Generic-
ID_VENDOR_ID=0bda
ID_MODEL=SD_MMC
ID_MODEL_ENC=SD\x2fMMC\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=0151
ID_REVISION=1.00
ID_SERIAL=Generic-_SD_MMC_20060413092100000-0:2
ID_SERIAL_SHORT=20060413092100000
ID_TYPE=disk
ID_INSTANCE=0:2
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
ID_PART_TABLE_TYPE=dos
ID_FS_UUID=6343c7b9-92a9-4d8f-bdd8-893f1190f294
ID_FS_UUID_ENC=6343c7b9-92a9-4d8f-bdd8-893f1190f294
ID_FS_VERSION=1.0
ID_FS_TYPE=ext2
ID_FS_USAGE=filesystem
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION=1
UDISKS_PARTITION_SCHEME=mbr
UDISKS_PARTITION_NUMBER=1
UDISKS_PARTITION_TYPE=0x83
UDISKS_PARTITION_SIZE=1006919680
UDISKS_PARTITION_SLAVE=/sys/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd
UDISKS_PARTITION_OFFSET=11618304
UDISKS_PARTITION_ALIGNMENT_OFFSET=0
MAJOR=8
MINOR=49
DEVLINKS=/dev/block/8:49 /dev/disk/by-id/usb-Generic-_SD_MMC_20060413092100000-0:2-part1 /dev/disk/by-path/pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2-part1 /dev/disk/by-uuid/6343c7b9-92a9-4d8f-bdd8-893f1190f294
<----- add event partition 2 - subsystem block - disk type partition ----->
UDEV [1339412734.731338] add /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd2 (block)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd2
SUBSYSTEM=block
DEVNAME=/dev/sdd2
DEVTYPE=partition
SEQNUM=3170
ID_VENDOR=Generic-
ID_VENDOR_ENC=Generic-
ID_VENDOR_ID=0bda
ID_MODEL=SD_MMC
ID_MODEL_ENC=SD\x2fMMC\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=0151
ID_REVISION=1.00
ID_SERIAL=Generic-_SD_MMC_20060413092100000-0:2
ID_SERIAL_SHORT=20060413092100000
ID_TYPE=disk
ID_INSTANCE=0:2
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
ID_PART_TABLE_TYPE=dos
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION=1
UDISKS_PARTITION_SCHEME=mbr
UDISKS_PARTITION_NUMBER=2
UDISKS_PARTITION_TYPE=0xda
UDISKS_PARTITION_SIZE=11618304
UDISKS_PARTITION_FLAGS=boot
UDISKS_PARTITION_SLAVE=/sys/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd
UDISKS_PARTITION_OFFSET=1022410752
UDISKS_PARTITION_ALIGNMENT_OFFSET=0
MAJOR=8
MINOR=50
DEVLINKS=/dev/block/8:50 /dev/disk/by-id/usb-Generic-_SD_MMC_20060413092100000-0:2-part2 /dev/disk/by-path/pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2-part2
From the change event I cannot extract any information regarding whether the device was added or removed. I have tried to open the device name \dev\sdd but I don't have permissions, so that option falls...
For now I'm just checking the action attribute for the partitions ( add / remove ).
This version of the program works pretty well for SD cards that have partitions. When there are no partitions, only the change events are received.
So my question is: is there any way I can check whether the media was added/removed from the change event? Or is there some other way to check if a device is available (keeping in mind the partition issue)?
Any suggestion on improving the device attribute iteration or the method of getting the notifications would be welcomed.
P.S. And I can't use libusb :).
Ok. So I got it working on the PC for SD cards without partitions.
The updated code is this:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <sys/time.h> //debug -> remove me
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libudev.h>
#define ADD_FILTER "add"
#define REMOVE_FILTER "remove"
#define SUBSYSTEM_FILTER "block"
#define DEVTYPE_FILTER "disk"
#define ATTR_FILTER "ID_MODEL"
#define SD_ATTR_VALUE "SD_MMC"
#define ATTR_ADDED_DISK "UDISKS_PARTITION_TABLE" // attribute is available for "change" event when SD card is added (n/a when removed)
static bool isDeviceSD(struct udev_device *device); //checks if device is SD card (MMC)
static bool isDevPresent(struct udev *device); //checks if device is present (SD + added)
static bool isDeviceAdded(struct udev_device *device); //checks if device is added (presence of attribute ATTR_ADDED_DISK)
static void print_device(struct udev_device *device, const char *source); //for debugging -> remove me
static bool s_bSD_present;
int main()
{
struct udev *udev;
struct udev_monitor *udev_monitor = NULL;
fd_set readfds;
s_bSD_present = false;
udev = udev_new();
if (udev == NULL)
{
printf("udev_new FAILED \n");
return 1;
}
if( isDevPresent(udev) )
{
s_bSD_present = true;
printf("+++SD is plugged in \n");
}
else
{
printf("---SD is not plugged in \n");
}
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (udev_monitor == NULL) {
printf("udev_monitor_new_from_netlink FAILED \n");
return 1;
}
//add some filters
if( udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, SUBSYSTEM_FILTER, DEVTYPE_FILTER) < 0 )
{
printf("udev_monitor_filter_add_match_subsystem_devtype FAILED \n");
return 1;
}
if (udev_monitor_enable_receiving(udev_monitor) < 0)
{
printf("udev_monitor_enable_receiving FAILED \n");
return 1;
}
while (1) {
printf("Polling for new data... \n");
int fdcount = 0;
FD_ZERO(&readfds);
if (udev_monitor != NULL)
{
FD_SET(udev_monitor_get_fd(udev_monitor), &readfds);
}
fdcount = select(udev_monitor_get_fd(udev_monitor)+1, &readfds, NULL, NULL, NULL);
if (fdcount < 0)
{
if (errno != EINTR)
printf("Error receiving uevent message\n");
continue;
}
if ((udev_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(udev_monitor), &readfds))
{
struct udev_device *device;
device = udev_monitor_receive_device(udev_monitor);
if (device == NULL)
continue;
//check presence
if( isDeviceSD(device) && isDeviceAdded(device) )
{
if(!s_bSD_present) //guard for double "change" events
{
s_bSD_present = true;
printf("+++SD has been plugged in \n");
}
}
else
{
if(s_bSD_present) //not needed -> just keeping consistency
{
s_bSD_present = false;
printf("---SD has been removed \n");
}
}
udev_device_unref(device);
}
}
return 0;
}
static bool isDeviceSD(struct udev_device *device)
{
bool retVal = false;
struct udev_list_entry *list_entry = 0;
struct udev_list_entry* model_entry = 0;
list_entry = udev_device_get_properties_list_entry(device);
model_entry = udev_list_entry_get_by_name(list_entry, ATTR_FILTER);
if( 0 != model_entry )
{
const char* szModelValue = udev_list_entry_get_value(model_entry);
if( strcmp( szModelValue, SD_ATTR_VALUE) == 0 )
{
//printf("Device is SD \n");
retVal = true;
//print_device(device, "UDEV");
}
}
return retVal;
}
static bool isDeviceAdded(struct udev_device *device)
{
bool retVal = false;
struct udev_list_entry *list_entry = 0;
struct udev_list_entry* added_disk_entry = 0;
list_entry = udev_device_get_properties_list_entry(device);
added_disk_entry = udev_list_entry_get_by_name(list_entry,/* "DEVNAME" */ ATTR_ADDED_DISK);
if( 0 != added_disk_entry )
{
retVal = true;
}
return retVal;
}
static bool isDevPresent(struct udev *device)
{
bool retVal = false;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *dev_list_entry;
enumerate = udev_enumerate_new(device);
udev_enumerate_add_match_subsystem(enumerate, SUBSYSTEM_FILTER);
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices)
{
struct udev_device *dev;
const char* dev_path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(device, dev_path);
if( isDeviceSD(dev) && isDeviceAdded(dev) )
{
retVal = true;
udev_device_unref(dev);
break;
}
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
return retVal;
}
static void print_device(struct udev_device *device, const char *source)
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
printf("%-6s[%llu.%06u] %-8s %s (%s)\n",
source,
(unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec,
udev_device_get_action(device),
udev_device_get_devpath(device),
udev_device_get_subsystem(device));
struct udev_list_entry *list_entry;
udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
printf("%s=%s\n",
udev_list_entry_get_name(list_entry),
udev_list_entry_get_value(list_entry));
printf("\n");
}
The solution (still not very bright) is checking for some attribute that is available only when SD card is added (like UDISKS_PARTITION_TABLE).
This works well on x86.