I'm looking for a way to turn power off (and back on) for a USB port. Solution can be in C, bash, etc. I'm using a BeagleBone running 32-bit Ubuntu 16.04 for armhf.
> uname -srvm
Linux 4.4.6-ti-r15 #1 SMP Tue Apr 5 12:32:22 UTC 2016 armv7l
I've tried many things discussed on StackOverflow and AskUbuntu, including:
#include <linux/usbdevice_fs.h>
int main(void)
{
int fd = open( "/dev/bus/usb/001/002", O_WRONLY );
if (fd < 0) return 1;
int rc = ioctl( fd, USBDEVFS_RESET, 0 );
if (rc < 0) return 2;
close( fd );
return 0;
}
The USB device I need to turn off (and eventually back on) is a Champtek FS310 barcode reader which shows up as a magnetic card strip reader when I run lsusb:
> lsusb
Bus 001 Device 002: ID 040b:6543 Weltrend Semiconductor Manhattan Magnetic Card Strip Reader
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
> lsusb -t
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=musb-hdrc/1p, 480M
|__ Port 1: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M
|__ Port 1: Dev 2, If 1, Class=Human Interface Device, Driver=usbhid, 1.5M
I've found that running these two commands results in the device turning off:
echo "1-1" > /sys/bus/usb/drivers/usb/unbind
echo "1-1" > /sys/bus/usb/drivers/usb/bind
Strangely enough, it only turns off during "bind", not "unbind". But once it turns off this way, the only way I've found to turn it back on is to reboot the computer, which is not a usable solution.
Indeed, that other question did have a technique that worked for what I was trying to do. Note this isn't a generic Linux answer, it will only work on BeagleBone Black and similar devices. (I tested on a BeagleBone Green.) Working backwards from the devmem2 example, this block of C++ code turns the USB power off, then back on:
const size_t page_size_in_bytes = getpagesize();
const size_t address_gpio3_13 = 0x47401c60; // see comment below
const size_t address_start = address_gpio3_13 / page_size_in_bytes * page_size_in_bytes;
const size_t address_offset = address_gpio3_13 - address_start;
int fd = open("/dev/mem", O_RDWR);
void *addr = mmap( 0, page_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, address_start );
uint8_t *byte_ptr = reinterpret_cast<uint8_t*>(addr);
byte_ptr[address_offset] = 0x00; // turn off USB
std::this_thread::sleep_for( std::chrono::milliseconds(500) );
byte_ptr[address_offset] = 0x01; // turn on USB
munmap( addr, page_size_in_bytes );
close(fd);
(Error handling not included.)
The magic number 0x47401c60 really is a magic number. According to some posts, it looks like a NDA needs to be signed to get access to some of the USB-related documentation. In the ARM335X Technical Reference Manual, the only mention of the 0x47401Cxx address space is the following on page 156:
Block Name Start Address End Address
USB1 Core 0x4740_1C00 0x4740_1FFF
Related
I'm playing with some driver code for a special kind of keyboard. And this keyboard does have special modes. According to the specification those modes could only be enabled by sending and getting feature reports.
I'm using 'hid.c' file and user mode to send HID reports. But both 'hid_read' and 'hid_get_feature_report' failed with error number -1.
I already tried detaching keyboard from kernel drivers using libusb, but when I do that, 'hid_open' fails. I guess this is due to that HID interface already using by 'input' or some driver by the kernel. So I may not need to unbind kernel hidraw driver, instead I should try unbinding the keyboard ('input') driver top of 'hidraw' driver. Am I correct?
And any idea how I could do that? And how to find what are drivers using which drivers and which low level driver bind to which driver?
I found the answer to this myself.
The answer is to dig this project and find it's hid implementation on libusb.
Or you could directly receive the report.
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
{
int res = -1;
int skipped_report_id = 0;
int report_number = data[0];
if (report_number == 0x0) {
/* Offset the return buffer by 1, so that the report ID
will remain in byte 0. */
data++;
length--;
skipped_report_id = 1;
}
res = libusb_control_transfer(dev->device_handle,
LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN,
0x01/*HID get_report*/,
(3/*HID feature*/ << 8) | report_number,
dev->interface,
(unsigned char *)data, length,
1000/*timeout millis*/);
if (res < 0)
return -1;
if (skipped_report_id)
res++;
return res;
}
I'm sorry I can't post my actual code due to some legal reasons. However the above code is from hidapi implementation.
So even you work with an old kernel , you still have the chance to make your driver working.
This answers to this question too: https://stackoverflow.com/questions/30565999/kernel-version-2-6-32-does-not-support-hidiocgfeature
I'm trying to talk to an arduino via a rs485 serial link. I have a usb to serial rs485 adapter plugged in my pc and a max485 in the arduino side. To get started i simply uploaded a sketch that send back what it receives.
#include <SoftwareSerial.h>
#define SSerialRX 10 //Serial Receive pin
#define SSerialTX 11 //Serial Transmit pin
#define SSerialTxControl 3 //RS485 Direction control
#define RS485Transmit HIGH
#define RS485Receive LOW
#define Pin13LED 13
SoftwareSerial RS485Serial(SSerialRX, SSerialTX); // RX, TX
void setup()
{
pinMode(Pin13LED, OUTPUT);
pinMode(SSerialTxControl, OUTPUT);
digitalWrite(SSerialTxControl, RS485Receive);
RS485Serial.begin(300);
}//--(end setup )---
void loop()
{
if (RS485Serial.available()){
delay(100);
char x = RS485Serial.read();
digitalWrite(Pin13LED, HIGH);
digitalWrite(SSerialTxControl, RS485Transmit); // Enable RS485 Transmit
Serial.write(x);
RS485Serial.write(x);
Serial.flush();
delay(10);
digitalWrite(SSerialTxControl, RS485Receive); // Disable RS485 Transmit
digitalWrite(Pin13LED, LOW);
delay(200);
}
}//--(end main loop )---
Then, to test the connection, i run a python script in the pc that writes a character to the serial port and listens to the response:
import serial
import time
PORT1 = "/dev/ttyUSB0"
try:
rs485 = serial.Serial(PORT1, 300)
while True:
print 'loop'
rs485.write('r')
time.sleep(0.5)
data = rs485.read()
if data == '\0':
print 'null'
else:
print data
time.sleep(1)
except:
rs485.close()
The first time I launch the script it happens something like this:
giulio#giulio-vaio:~$ python rs485io.py
loop
r
loop
r
loop
^C
If i try to launch again the script, it writes nothing more than:
giulio#giulio-vaio:~$ python rs485io.py
loop
null
loop
null
loop
null
^C
It starts working again only if i reboot my pc (with ubuntu by the way). If i unplug and plug again the usb to serial converter, nothing changes, if i reboot the arduino, same story. I tried the same configuration with a raspberry pi and the result is identical. Changing usb port doesn't work, upload again the same sketch in the arduino, nothing happens.
The led on the pin 13 blinks, so the arduino is receiving and sending something, the serial.read() function returns, so something is arriving, but is (after the first time) a null carachter '\x00'. In one positive case, after rebooting, I tried to let the script go on for a few time and everything went fine, until i hitted ctrl-c and started again the script.
This is dmesg after i plug in the serial converter:
[ 6116.508264] usb 1-2.1.3: new full-speed USB device number 27 using ehci-pci
[ 6116.617247] usb 1-2.1.3: New USB device found, idVendor=1a86, idProduct=7523
[ 6116.617257] usb 1-2.1.3: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[ 6116.617264] usb 1-2.1.3: Product: USB2.0-Serial
[ 6116.617694] ch341 1-2.1.3:1.0: ch341-uart converter detected
[ 6116.621498] usb 1-2.1.3: ch341-uart converter now attached to ttyUSB0
I don't know what to do after this, any help would be appreciated.
EDIT :
the pc doesn't need to be rebooted, it works if i unload and then load again the module ch341, the one that handles the usb converter.
I added two lines at the top of the code:
subprocess.Popen("modprobe -r ch341".split()).wait()
subprocess.Popen("modprobe ch341".split()).wait()
I know it doesn't really solve the problem, but it works.
EDIT 2 :
Looking at the documentation http://lxr.free-electrons.com/source/Documentation/serial/serial-rs485.txt , I tried to send an ioctl signal to the driver, using this code in c (i'm not good with C, so please forgive me)
#include (all the libraries needed)
/* Driver-specific ioctls: */
#define TIOCGRS485 0x542E
#define TIOCSRS485 0x542F
/* Open your specific device (e.g., /dev/mydevice): */
int main(){
const char *file1 = "/dev/ttyUSB0";
const char *file2 = "output_cprog.txt";
int fd = open (file1,O_WRONLY);
if(fd < 0){
printf("%s: %s\n","error opening ttyusb",strerror( errno ) );
}
int fq = open(file2,O_WRONLY);
if( fq < 0){
printf("%s: %s\n","errore apertura file",strerror( errno ));
}
struct serial_rs485 rs485conf;
// Enable RS485 mode:
rs485conf.flags |= SER_RS485_ENABLED;
// Set logical level for RTS pin equal to 1 when sending:
rs485conf.flags |= SER_RS485_RTS_ON_SEND;
// or, set logical level for RTS pin equal to 0 after sending:
rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);
// Set rts delay before send, if needed:
rs485conf.delay_rts_before_send = 10;
// Set rts delay after send, if needed:
rs485conf.delay_rts_after_send = 10;
int ioc = ioctl (fd, TIOCSRS485, &rs485conf);
if(ioc < 0){
printf("%i\n",ioc);
printf("ioctl error: %s\n", strerror( errno ));
}
char *character = "f\n";
write(fd,character,2);
char *buffer;
buffer = (char *) malloc(10);
read(fd, buffer, 1);
write(fq, buffer, 1);
printf("received character: %s\n",buffer[0]);
if( close(fd) < 0){
printf("%s: %s\n","error closing port",strerror( errno ));
}
if (close(fq) < 0){
printf("%s: %s\n","error closing file",strerror( errno ));
}
return 0;
}
but compiling and running the program what i see is only this:
giulio#giulio-vaio:~$ ./ioctlRS485.o
-1
ioctl error: Inappropriate ioctl for device
received character: (null)
I think, by looking at the driver https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ch341.c that it doesn't have the appropriate function to handle an ioctl at all.
I was trying to activate Linux phyless Ethernet driver. There are no much information on net. I am using ARM based Linux kernel SOC is connected to a 1GBPS RGMII port back to back without having a real PHY. Came to know Linux has fixed phy support. Some of the files have used fixed_phy_add function. But still not getting clear idea how to activate. Any kind of help or pointer will really help here.
Yeah. I have just done this for our board. What really confused me was that you need to add the fixed phy before the fixed mdio bus is activated. So either you need to add it early in your platform init code or hack it into the fixed mdio code like I did (just to get things working, of course). This patch did it for me.
diff --git a/drivers/net/phy/fixed.c b/drivers/net/phy/fixed.c
index ba55adf..7013ef0 100644
--- a/drivers/net/phy/fixed.c
+++ b/drivers/net/phy/fixed.c
## -203,11 +203,24 ## err_regs:
}
EXPORT_SYMBOL_GPL(fixed_phy_add);
+static struct fixed_phy_status fixed_phy_status = {
+ .link = 1,
+ .speed = 100,
+ .duplex = 0,
+};
+
static int __init fixed_mdio_bus_init(void)
{
struct fixed_mdio_bus *fmb = &platform_fmb;
int ret;
+ ret = fixed_phy_add(PHY_POLL, 0, &fixed_phy_status);
+ if (ret < 0)
+ {
+ pr_err("could not add fixed phy.\n");
+ return ret;
+ }
+
pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0);
if (IS_ERR(pdev)) {
ret = PTR_ERR(pdev);
The next step is to use this phy in your driver, it should be enough to use the name fixed-0:00 as the phy name when you look it up (the :00 is the fixed phy id 0 in the
fixed_phy_add above)
snprintf(phy_id, sizeof(slave_data->phy_id), PHY_ID_FMT, "fixed-0", 0);
Then the phy_connect your driver already has should find the fixed phy and it should work
as long as the fixed configuration matches the other side (a switch in our case):
phy_connect(ndev, phy_id, &_adjust_link, phy_if);
There should be a method to make all this work with device tree too but no-one has gotten there yet, AFAICS.
Good Luck.
I'm writing a linux device driver to allow an FPGA (currently connected to the PC via PCI express) to DMA data directly into CPU RAM. This needs to happen without any interaction and user space needs to have access to the data. Some details:
- Running 64 bit Fedora 14
- System has 8GB of RAM
- The FPGA (Cyclone IV) is on a PCIe card
In an attempt to accomplish this I performed the following:
- Reserved the upper 2GB of RAM in grub with memmap 6GB$2GB (will not boot is I add mem=2GB). I can see that the upper 2GB of RAM is reserved in /proc/meminfo
- Mapped BAR0 to allow reading and writing to FPGA registers (this works perfectly)
- Implemented an mmap function in my driver with remap_pfn_range()
- Use ioremap to get the virtual address of the buffer
- Added ioctl calls (for testing) to write data to the buffer
- Tested the mmap by making an ioctl call to write data into the buffer and verified the data was in the buffer from user space
The problem I'm facing is when the FPGA starts to DMA data to the buffer address I provide. I constantly get PTE errors (from DMAR:) or with the code below I get the following error:
DMAR: [DMA Write] Request device [01:00.0] fault addr 186dc5000
DMAR: [fault reason 01] Present bit in root entry is clear
DRHD: handling fault status reg 3
The address in the first line increments by 0x1000 each time based on the DMA from the FPGA
Here's my init() code:
#define IMG_BUF_OFFSET 0x180000000UL // Location in RAM (6GB)
#define IMG_BUF_SIZE 0x80000000UL // Size of the Buffer (2GB)
#define pci_dma_h(addr) ((addr >> 16) >> 16)
#define pci_dma_l(addr) (addr & 0xffffffffUL)
if((pdev = pci_get_device(FPGA_VEN_ID, FPGA_DEV_ID, NULL)))
{
printk("FPGA Found on the PCIe Bus\n");
// Enable the device
if(pci_enable_device(pdev))
{
printk("Failed to enable PCI device\n");
return(-1);
}
// Enable bus master
pci_set_master(pdev);
pci_read_config_word(pdev, PCI_VENDOR_ID, &id);
printk("Vendor id: %x\n", id);
pci_read_config_word(pdev, PCI_DEVICE_ID, &id);
printk("Device id: %x\n", id);
pci_read_config_word(pdev, PCI_STATUS, &id);
printk("Device Status: %x\n", id);
pci_read_config_dword(pdev, PCI_COMMAND, &temp);
printk("Command Register : : %x\n", temp);
printk("Resources Allocated :\n");
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &temp);
printk("BAR0 : %x\n", temp);
// Get the starting address of BAR0
bar0_ptr = (unsigned int*)pcim_iomap(pdev, 0, FPGA_CONFIG_SIZE);
if(!bar0_ptr)
{
printk("Error mapping Bar0\n");
return -1;
}
printk("Remapped BAR0\n");
// Set DMA Masking
if(!pci_set_dma_mask(pdev, DMA_BIT_MASK(64)))
{
pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
printk("Device setup for 64bit DMA\n");
}
else if(!pci_set_dma_mask(pdev, DMA_BIT_MASK(32)))
{
pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
printk("Device setup for 32bit DMA\n");
}
else
{
printk(KERN_WARNING"No suitable DMA available.\n");
return -1;
}
// Get a pointer to reserved lower RAM in kernel address space (virtual address)
virt_addr = ioremap(IMG_BUF_OFFSET, IMG_BUF_SIZE);
kernel_image_buffer_ptr = (unsigned char*)virt_addr;
memset(kernel_image_buffer_ptr, 0, IMG_BUF_SIZE);
printk("Remapped image buffer: 0x%p\n", (void*)virt_addr);
}
Here's my mmap code:
unsigned long image_buffer;
unsigned int low;
unsigned int high;
if(remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
vma->vm_end - vma->vm_start,
vma->vm_page_prot))
{
return(-EAGAIN);
}
image_buffer = (vma->vm_pgoff << PAGE_SHIFT);
if(0 > check_mem_region(IMG_BUF_OFFSET, IMG_BUF_SIZE))
{
printk("Failed to check region...memory in use\n");
return -1;
}
request_mem_region(IMG_BUF_OFFSET, IMG_BUF_SIZE, DRV_NAME);
// Get the bus address from the virtual address above
//dma_page = virt_to_page(addr);
//dma_offset = ((unsigned long)addr & ~PAGE_MASK);
//dma_addr = pci_map_page(pdev, dma_page, dma_offset, IMG_BUF_SIZE, PCI_DMA_FROMDEVICE);
//dma_addr = pci_map_single(pdev, image_buffer, IMG_BUF_SIZE, PCI_DMA_FROMDEVICE);
//dma_addr = IMG_BUF_OFFSET;
//printk("DMA Address: 0x%p\n", (void*)dma_addr);
// Write start or image buffer address to the FPGA
low = pci_dma_l(image_buffer);
low &= 0xfffffffc;
high = pci_dma_h(image_buffer);
if(high != 0)
low |= 0x00000001;
*(bar0_ptr + (17024/4)) = 0;
//printk("DMA Address LOW : 0x%x\n", cpu_to_le32(low));
//printk("DMA Address HIGH: 0x%x\n", cpu_to_le32(high));
*(bar0_ptr + (4096/4)) = cpu_to_le32(low); //2147483649;
*(bar0_ptr + (4100/4)) = cpu_to_le32(high);
*(bar0_ptr + (17052/4)) = cpu_to_le32(low & 0xfffffffe);//2147483648;
printk("Process Read Command: Addr:0x%x Ret:0x%x\n", 4096, *(bar0_ptr + (4096/4)));
printk("Process Read Command: Addr:0x%x Ret:0x%x\n", 4100, *(bar0_ptr + (4100/4)));
printk("Process Read Command: Addr:0x%x Ret:0x%x\n", 17052, *(bar0_ptr + (17052/4)));
return(0);
Thank you for any help you can provide.
Do you control the RTL code that writes the TLP packets yourself, or can you name the DMA engine and PCIe BFM (bus functional model) you are using? What do your packets look like in the simulator? Most decent BFM should trap this rather than let you find it post-deploy with a PCIe hardware capture system.
To target the upper 2GB of RAM you will need to be sending 2DW (64-bit) addresses from the device. Are the bits in your Fmt/Type set to do this? The faulting address looks like a masked 32-bit bus address, so something at this level is likely incorrect. Also bear in mind that because PCIe is big-endian take care when writing the target addresses to the PCIe device endpoint. You might have the lower bytes of the target address dropping into the payload if Fmt is incorrect - again a decent BFM should spot the resulting packet length mismatch.
If you have a recent motherboard/modern CPU, the PCIe endpoint should do PCIe AER (advanced error reporting), so if running a recent Centos/RHEL 6.3 you should get a dmesg report of endpoint faults. This is very useful as the report capture the first handful of DW's of the packet to special capture registers, so you can review the TLP as received.
In your kernel driver, I see you setup the DMA mask, that is not sufficient as you have not programmed the mmu to allow writes to the pages from the device. Look at the implementation of pci_alloc_consistent() to see what else you should be calling to achieve this.
If you are still looking for a reason, then it goes like this:
Your kernel has DMA_REMAPPING flags enabled by default, thus IOMMU is throwing the above error, as IOMMU context/domain entries are not programmed for your device.
You can try using intel_iommu=off in the kernel command line or putting IOMMU in bypass mode for your device.
Regards,
Samir
I have a big problem. At present I am accessing a serial port via the following hooks:
fd = open( "/dev/ttyS1", O_RDWR | O_NOCTTY )
then I read from it using the following chunk of code
i = select( fd + 1, &rfds, NULL, NULL, &tv )
...
iLen = read( fd, buf, MAX_PACKET_LEN )
the problem is that before I read, I need to detect if there were any buffer overruns. Both at the serial port level and the internal tty flip buffers.
We tried cat /proc/tty/driver/serial but it doesn't seem to list the overruns (see output below)
1: uart:16550A port:000002F8 irq:3 tx:70774 rx:862484 fe:44443 pe:270023 brk:30301 RTS|CTS|DTR
According to the kernel sources, you should use the TIOCGICOUNT ioctl. The third ioctl argument should be a pointer to the following struct, defined in <linux/serial.h> :
/*
* Serial input interrupt line counters -- external structure
* Four lines can interrupt: CTS, DSR, RI, DCD
*/
struct serial_icounter_struct {
int cts, dsr, rng, dcd;
int rx, tx;
int frame, overrun, parity, brk;
int buf_overrun;
int reserved[9];
};
I don't know if every driver detect all conditions however.
Dark Templer,
Your serial driver should update the icount.frame/overrun errors.
struct uart_port in serial_core.h has member struct uart_icount, which should be updated in platform serial device driver as per interrupts received.