Related
I'm writing a Linux KVM hypervisor for x86 16-bit guests running in real mode. When doing interrupt calls (int ... instruction), I've encountered the KVM_INTERNAL_ERROR_SIMUL_EX error on Linux kernel 3.13.0. The same code is running fine on Linux kernel 3.16.0. Am I missing something? Is there a workaround I can add to my code to make it work with Linux kernel 3.13.0 (and possibly earlier)?
The test guest calls int 0x18 ... int 0x4f, all of which is handled in the hypervisor (C code after KVM_RUN has returned). When it's working correctly, all of the interrupt calls work. On Linux kernel 3.13.0, int 0x21 starts failing (and then int 0x22, int 0x23 and int 0x24 would also fail).
I was trying to write the shortest example C code to demonstrate the problem, here it is:
/* Based on: https://gist.github.com/zserge/d68683f17c68709818f8baab0ded2d15
* Based on: https://gist.githubusercontent.com/zserge/d68683f17c68709818f8baab0ded2d15/raw/b79033254b092ec9121bb891938b27dd128030d7/kvm-host-simple.c
*
* Compile: gcc -ansi -pedantic -s -O2 -W -Wall -o kvm16 kvm16.c && ./kvm16
*
* Expected correct output (e.g. on Linux 3.16.0 compiled for i386 (i686)):
*
* ...
* info: int 0x4f iret to: ...
* info: success, exiting
*
* Failure output (e.g. on Linux 3.13.0 compiled for amd64 (x86_64)):
*
* info: int 0x20 iret to: cs=0x0070 ip=0x0013
* fatal: KVM internal error suberror=2
*
* // Encounter unexpected simultaneous exceptions.
* #define KVM_INTERNAL_ERROR_SIMUL_EX 2
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <linux/kvm.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#define INT_NUM 0x21 /* Also works for INT_NUM == 0x20. */
int main(int argc, char *argv[]) {
int kvm_fd, vm_fd, vcpu_fd;
void *mem;
struct kvm_userspace_memory_region region;
struct kvm_run *run;
struct kvm_regs regs;
struct kvm_sregs sregs;
(void)argc; (void)argv;
if ((kvm_fd = open("/dev/kvm", O_RDWR)) < 0) {
perror("failed to open /dev/kvm");
return 1;
}
if ((vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0)) < 0) {
perror("failed to create vm");
return 1;
}
if ((mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0)) == NULL) {
perror("mmap");
return 1;
}
memset(®ion, 0, sizeof(region));
region.slot = 0;
region.guest_phys_addr = 0;
region.memory_size = 0x1000;
region.userspace_addr = (uintptr_t)mem;
if (ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion) < 0) {
perror("KVM_SET_USER_MEMORY_REGION");
return 1;
}
{ /* 8086 real mode machine code. */
char *p = (char*)mem + 0x700;
unsigned int_num;
for (int_num = 0; int_num < 0x100; ++int_num) {
*(unsigned short*)((char*)mem + int_num * 4) = int_num; /* Interrupt vector INT_NUM offset := INT_NUM. */
*(unsigned short*)((char*)mem + int_num * 4 + 2) = 0x54; /* Interrupt vector INT_NUM segment := 0x54. */
}
*p++ = (char)0xf4; /* hlt. */
for (int_num = 0x18; int_num < 0x50; ++int_num) {
*p++ = (char)0xcd; /* int int_num. */
*p++ = (char)int_num;
}
*p++ = (char)0xf4;
}
memset((char*)mem + 0x540, '\xf4', 0x100); /* 256 times hlt. Interrupt vectors point here. */
if ((vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0)) < 0) {
perror("KVM_CREATE_VCPU");
return 1;
}
{
int kvm_run_mmap_size = ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
if (kvm_run_mmap_size < 0) {
perror("KVM_GET_VCPU_MMAP_SIZE");
return 1;
}
run = (struct kvm_run *)mmap(
NULL, kvm_run_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu_fd, 0);
if (run == NULL) {
perror("mmap kvm_run");
return 1;
}
}
memset(®s, '\0', sizeof(regs));
if (ioctl(vcpu_fd, KVM_GET_SREGS, &sregs) < 0) {
perror("KVM_GET_SREGS");
return 1;
}
{
int fd = open("kvm16.sregs", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}
if (write(fd, &sregs, sizeof(sregs)) != sizeof(sregs)) {
perror("write");
return 1;
}
if (close(fd) != 0) {
perror("close");
return 1;
}
}
sregs.cs.base = (sregs.cs.selector = 0x70) << 4;
sregs.ds.base = (sregs.ds.selector = sregs.cs.selector) << 4;
sregs.es.base = (sregs.es.selector = sregs.cs.selector) << 4;
sregs.ss.base = (sregs.ss.selector = sregs.cs.selector) << 4;
if (ioctl(vcpu_fd, KVM_GET_REGS, ®s) < 0) {
perror("KVM_GET_REGS");
return 1;
}
regs.rflags = 1 << 1; /* Reserved bit in EFLAGS. Even needed after KVM_GET_REGS. */
regs.rip = 0;
regs.rsp = 0x1000 - 0x700;
if (ioctl(vcpu_fd, KVM_SET_SREGS, &sregs) < 0) {
perror("KVM_SET_SREGS");
return 1;
}
if (ioctl(vcpu_fd, KVM_SET_REGS, ®s) < 0) {
perror("KVM_SET_REGS");
return 1;
}
for (;;) {
int ret = ioctl(vcpu_fd, KVM_RUN, 0);
unsigned short cs, ip;
if (ret < 0) {
perror("KVM_RUN");
return 1;
}
if (ioctl(vcpu_fd, KVM_GET_SREGS, &sregs) < 0) {
perror("KVM_GET_SREGS");
return 1;
}
if (ioctl(vcpu_fd, KVM_GET_REGS, ®s) < 0) {
perror("KVM_GET_REGS");
return 1;
}
cs = sregs.cs.selector;
ip = regs.rip;
if (run->exit_reason == KVM_EXIT_HLT) {
fprintf(stderr, "info: hlt: cs=0x%04x ip=0x%04x\n", cs, ip - 1);
if (cs == 0x70) {
if (ip != 0 + 1) {
fprintf(stderr, "info: success, exiting\n");
return 0; /* EXIT_SUCCESS after the second `hlt' in the code. */
}
} else if (cs == 0x54) { /* Simulate iret. */
const char *csip_ptr = (const char*)mem + ((unsigned short)sregs.ss.selector << 4) + (unsigned short)regs.rsp;
const unsigned short int_ip = ((const unsigned short*)csip_ptr)[0];
const unsigned short int_cs = ((const unsigned short*)csip_ptr)[1];
const unsigned short int_flags = ((const unsigned short*)csip_ptr)[2];
fprintf(stderr, "info: int 0x%02x iret to: cs=0x%04x ip=0x%04x\n", ip - 1, int_cs, int_ip);
sregs.cs.base = (sregs.cs.selector = int_cs) << 4;
regs.rip = int_ip;
if (int_flags & (1 << 9)) regs.rflags |= (1 << 9); /* Set IF back to 1 if it was 1. */
regs.rsp += 6; /* pop ip, pop cs, popfw . */
if (ioctl(vcpu_fd, KVM_SET_SREGS, &sregs) < 0) {
perror("KVM_SET_SREGS");
return 1;
}
if (ioctl(vcpu_fd, KVM_SET_REGS, ®s) < 0) {
perror("KVM_SET_REGS");
return 1;
}
} else {
fprintf(stderr, "fatal: unexpected hlt: cs=0x%04x ip=0x%04x\n", cs, ip - 1);
return 5;
}
} else if (run->exit_reason == KVM_EXIT_INTERNAL_ERROR) {
fprintf(stderr, "fatal: KVM internal error suberror=%d\n", (unsigned)run->internal.suberror);
return 4;
} else {
fprintf(stderr, "fatal: unexpected KVM exit: exit_reason=%d cs=0x%04x ip=0x%04x\n", run->exit_reason, cs, ip);
return 2;
}
}
}
#include <unistd.h> //Needed for I2C port
#include <fcntl.h> //Needed for I2C port
#include <sys/ioctl.h> //Needed for I2C port
#include <linux/i2c-dev.h> //Needed for I2C port
#include <stdio.h>
#include <time.h>
int file_i2c;
int length;
unsigned char buffer[60] = {0};
unsigned char cmdbuffer[60] = {0};
//PRINTS (SENSOR STATUS) IN BINARY
void printBin(unsigned char value)
{
for (int i = sizeof(char) * 7; i >= 0; i--) {
printf("%d", (value & (1 << i )) >> i);
}
putc('\n', stdout);
}
//CREATES DELAY IN MS
void delay(int milli)
{
long pause;
clock_t now,then;
pause = milli * (CLOCKS_PER_SEC / 1000);
now = then = clock();
while ((now-then) < pause) {
now = clock();
}
}
//TIMESTAMPS OUTPUT
void timestamp()
{
time_t ltime;
ltime=time(NULL);
printf("%s", asctime(localtime(<ime)));
}
//PORT SELECT FOR TCA9548A Addresses range from 0x70-0x77
void portSelect(int port, int addressTCA)
{
if (port > 7 || port < 0)
return;
if (ioctl(file_i2c, I2C_SLAVE, addressTCA) < 0) {
printf("Failed to acquire bus access and/or talk to slave.\n");
return;
}
cmdbuffer[0] = 1 << port;
length = 1;
write(file_i2c, cmdbuffer, length);
}
int main()
{
//----- OPEN THE I2C BUS -----
char *filename = (char*)"/dev/i2c-1";
if ((file_i2c = open(filename, O_RDWR)) < 0) {
//ERROR HANDLING: you can check errno to see what went wrong
printf("Failed to open the i2c bus");
return 0;
}
portSelect(1, 0x70);
//CONFIGURE SLAVE AND ATTEMPT CONNECTION
if (ioctl(file_i2c, I2C_SLAVE, 0x28) < 0) {
printf("Failed to acquire bus access and/or talk to slave.\n");
return 0;
}
flag:
cmdbuffer[0] = 0xAA;
cmdbuffer[1] = 0x00;
cmdbuffer[2] = 0x00;
length = 3;
if (write(file_i2c, cmdbuffer, length) != length)
//write() returns the number of bytes actually written, if it doesn't match then an error occurred (e.g. no response from the device)
{
printf("Failed to write to the i2c bus.\n"); //FAILS HERE
return 0;
}
length = 7; //<<< Number of bytes to read
if (read(file_i2c, buffer, length) != length)
//read() returns the number of bytes actually read, if it doesn't match then an error occurred (e.g. no response from the device)
{
printf("Failed to read from the i2c bus.\n");
} else {
//timestamp();
printf("Status:\n");
printBin(buffer[0]);
int pressure = (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
int temperature = (buffer[4] << 16) + (buffer[5] << 8) + buffer[6];
printf("Pressure : %d\n", pressure);
printf("Temperature : %d\n\n", temperature);
}
delay(5);
goto flag;
return 0;
}
Current code shown above. I am trying to read from 8 different Honeywell pressure sensors but for the moment I am just trying to get one working. I am able to read/write just fine without the multiplexer. Documentation is unhelpful as it only references Python or Arduino. I have scanned with console and confirmed the addresses of the sensors and mux.
I'm using the C/C++ SDK of the Pi Pico and trying to use the DMA to read I2C data in the background. However, there is no example script in Pico-Examples that shows how to use the DMA to read from I2C. There is one for SPI, called spi-dma. But It doesn't directly correlate to I2C because I have to give the device address too along with the register address for I2C.
Can anyone help me understand what to change in the following lines for it to work with an I2C device?
const uint dma_rx = dma_claim_unused_channel(true);
static uint8_t rxbuf[1024];
dma_channel_config c = dma_channel_get_default_config(dma_rx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_dreq(&c, spi_get_dreq(spi_default, false));
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
dma_channel_configure(dma_rx, &c,
rxbuf, // write address
&spi_get_hw(spi_default)->dr, // read address
TEST_SIZE, // element count (each element is of size transfer_data_size)
false); // don't start yet
dma_start_channel_mask(1u << dma_rx);
dma_channel_wait_for_finish_blocking(dma_rx);
dma_channel_unclaim(dma_rx);
I Know a few changes to be made like
channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, false));
dma_channel_configure(dma_rx, &c,
rxbuf, // write address
i2c_get_hw(i2c_default), // read address
TEST_SIZE, // element count (each element is of size transfer_data_size)
true); // don't start yet
But what more after this?
Don't like the SPI protocol, when you read from an I2C device, you also need to send some register address to the device.
There are some helper functions to illustrate the process.
long long rx_ind = 0;
uint16_t rx_buf[300];
static int gi2c_read_blocking_internal(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop,
check_timeout_fn timeout_check, timeout_state_t *ts) {
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
invalid_params_if(I2C, i2c_reserved_addr(addr));
invalid_params_if(I2C, len == 0);
invalid_params_if(I2C, ((int)len) < 0);
int kk = rx_ind;
i2c->hw->enable = 0;
i2c->hw->tar = addr;
i2c->hw->enable = 1;
bool abort = false;
bool timeout = false;
uint32_t abort_reason;
int byte_ctr;
int ilen = (int)len;
for (byte_ctr = 0; byte_ctr < ilen; ++byte_ctr) {
bool first = byte_ctr == 0;
bool last = byte_ctr == ilen - 1;
while (!i2c_get_write_available(i2c))
tight_loop_contents();
rx_buf[rx_ind++] =
bool_to_bit(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
bool_to_bit(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
I2C_IC_DATA_CMD_CMD_BITS;
i2c->hw->data_cmd =
bool_to_bit(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
bool_to_bit(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
I2C_IC_DATA_CMD_CMD_BITS; // -> 1 for read
do {
abort_reason = i2c->hw->tx_abrt_source;
abort = (bool) i2c->hw->clr_tx_abrt;
if (timeout_check) {
timeout = timeout_check(ts);
abort |= timeout;
}
} while (!abort && !i2c_get_read_available(i2c));
if (abort)
break;
*dst++ = (uint8_t) i2c->hw->data_cmd;
rx_buf[rx_ind++] = *(dst-1);
}
int rval;
if (abort) {
if (timeout)
rval = PICO_ERROR_TIMEOUT;
else if (!abort_reason || abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK_BITS) {
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
rval = PICO_ERROR_GENERIC;
} else {
// panic("Unknown abort from I2C instance #%08x: %08x\n", (uint32_t) i2c->hw, abort_reason);
rval = PICO_ERROR_GENERIC;
}
} else {
rval = byte_ctr;
}
i2c->restart_on_next = nostop;
do{
printf("rx %#08x\n", rx_buf[kk++]);
} while (rx_ind > kk);
return rval;
}
long long tx_ind = 0;
uint16_t tx_buf[300];
static int gi2c_write_blocking_internal(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
check_timeout_fn timeout_check, struct timeout_state *ts) {
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
invalid_params_if(I2C, i2c_reserved_addr(addr));
// Synopsys hw accepts start/stop flags alongside data items in the same
// FIFO word, so no 0 byte transfers.
invalid_params_if(I2C, len == 0);
invalid_params_if(I2C, ((int)len) < 0);
int kk = tx_ind;
i2c->hw->enable = 0;
i2c->hw->tar = addr;
i2c->hw->enable = 1;
bool abort = false;
bool timeout = false;
uint32_t abort_reason = 0;
int byte_ctr;
int ilen = (int)len;
for (byte_ctr = 0; byte_ctr < ilen; ++byte_ctr) {
bool first = byte_ctr == 0;
bool last = byte_ctr == ilen - 1;
tx_buf[tx_ind++] =
bool_to_bit(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
bool_to_bit(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
*src;
i2c->hw->data_cmd =
bool_to_bit(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
bool_to_bit(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
*src++;
// Wait until the transmission of the address/data from the internal
// shift register has completed. For this to function correctly, the
// TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag
// was set in i2c_init.
do {
if (timeout_check) {
timeout = timeout_check(ts);
abort |= timeout;
}
tight_loop_contents();
} while (!timeout && !(i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_TX_EMPTY_BITS));
// If there was a timeout, don't attempt to do anything else.
if (!timeout) {
abort_reason = i2c->hw->tx_abrt_source;
if (abort_reason) {
// Note clearing the abort flag also clears the reason, and
// this instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
i2c->hw->clr_tx_abrt;
abort = true;
}
if (abort || (last && !nostop)) {
// If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occured.
// TODO Could there be an abort while waiting for the STOP
// condition here? If so, additional code would be needed here
// to take care of the abort.
do {
if (timeout_check) {
timeout = timeout_check(ts);
abort |= timeout;
}
tight_loop_contents();
} while (!timeout && !(i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_STOP_DET_BITS));
// If there was a timeout, don't attempt to do anything else.
if (!timeout) {
i2c->hw->clr_stop_det;
}
}
}
// Note the hardware issues a STOP automatically on an abort condition.
// Note also the hardware clears RX FIFO as well as TX on abort,
// because we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0.
if (abort)
break;
}
int rval;
// A lot of things could have just happened due to the ingenious and
// creative design of I2C. Try to figure things out.
if (abort) {
if (timeout)
rval = PICO_ERROR_TIMEOUT;
else if (!abort_reason || abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK_BITS) {
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
rval = PICO_ERROR_GENERIC;
} else if (abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK_BITS) {
// Address acknowledged, some data not acknowledged
rval = byte_ctr;
} else {
//panic("Unknown abort from I2C instance #%08x: %08x\n", (uint32_t) i2c->hw, abort_reason);
rval = PICO_ERROR_GENERIC;
}
} else {
rval = byte_ctr;
}
// nostop means we are now at the end of a *message* but not the end of a *transfer*
i2c->restart_on_next = nostop;
do{
printf("tx %#08x\n", tx_buf[kk++]);
} while (tx_ind > kk);
return rval;
}
int gi2c_write_blocking(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) {
return gi2c_write_blocking_internal(i2c, addr, src, len, nostop, NULL, NULL);
}
int gi2c_read_blocking(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) {
return gi2c_read_blocking_internal(i2c, addr, dst, len, nostop, NULL, NULL);
}
// Write 1 byte to the specified register
int reg_write( i2c_inst_t *i2c,
const uint addr,
const uint8_t reg,
uint8_t *buf,
const uint8_t nbytes) {
int num_bytes_read = 0;
uint8_t msg[nbytes + 1];
// Check to make sure caller is sending 1 or more bytes
if (nbytes < 1) {
return 0;
}
// Append register address to front of data packet
msg[0] = reg;
for (int i = 0; i < nbytes; i++) {
msg[i + 1] = buf[i];
}
printf("write func.\n");
// Write data to register(s) over I2C
gi2c_write_blocking(i2c, addr, msg, (nbytes + 1), false);
return num_bytes_read;
}
// Read byte(s) from specified register. If nbytes > 1, read from consecutive
// registers.
int reg_read( i2c_inst_t *i2c,
const uint addr,
const uint8_t reg,
uint8_t *buf,
const uint8_t nbytes) {
int num_bytes_read = 0;
// Check to make sure caller is asking for 1 or more bytes
if (nbytes < 1) {
return 0;
}
printf("write func.\n");
// Read data from register(s) over I2C
gi2c_write_blocking(i2c, addr, ®, 1, true);
printf("read func.\n");
num_bytes_read = gi2c_read_blocking(i2c, addr, buf, nbytes, false);
return num_bytes_read;
}
In gi2c_read_blocking_internal function, you can see first you need to write the i2c->hw->data_cmd to control the stop, restart... states in I2C protocol, and then you can read from i2c->hw->data_cmd to get information from I2C device.
In reg_read function, first send the register address by call gi2c_write_blocking, and then you can read the register by call gi2c_read_blocking
After look up the whole process, you need two dma channel trigger simultaneously to simulate the reading from I2C device process(one for write i2c->hw->data_cmd and one for read i2c->hw->data_cmd).
I am learning socket programming in C language, and this is an incomprehensible problem I encountered during my study.
Today I am trying to send a HTTP request to my test server which host an Apache example website, then receive the response from test server. Here is a part of my receive code.
unsigned long recv_size = 0;
unsigned long response_size = 4096;
int ret = 0;
char *recv_buff = (char *)malloc(response_size);
while (1)
{
// ret = recv(socket, recv_buff, response_size, MSG_WAITALL); // cannot get all data
ret = read(socket, recv_buff, response_size); // same effect as the above
recv_size += ret;
if (ret < 0)
error(strerror(errno));
else if (ret == 0)
break; // all data recved
}
The normal result of my test with burpsuit is this.
But what I received with the C language program was incomplete data.
I searched the reason for one night, but I still did not find a solution for my problem. Whether it is to set the buff to a super large size or any other method, the complete data cannot be accepted at all.
The traffic monitored from wireshark is ok, but my program still cannot receive the complete data. What is the problem?
If you know why, please let me know. THX. (o゜▽゜)o☆
UPDATE
The while loop will execute twice, and first time the value of ret is 3343, and second time is 0, so the loop will stop here.
You can get a short read on a socket.
But, your code to handle that has a few issues.
You're allocating a buffer of size response_size. You are always reading that amount instead of reducing the amount read by the amount you've already read on a prior loop iteration.
This can cause you to read past the end of the buffer causing UB (undefined behavior).
Your termination condition is if (ret == 0). This can fail if another packet arrives "early". You'll never see a ret of 0, because the partial data from the next packet will make it non-zero
Here's the corrected code:
#if 0
unsigned long recv_size = 0;
#endif
unsigned long response_size = 4096;
int ret = 0;
char *recv_buff = (char *) malloc(response_size);
#if 1
unsigned long remaining_size = response_size;
unsigned long offset = 0;
#endif
for (; remaining_size > 0; remaining_size -= ret, offset += ret) {
ret = read(socket, &recv_buff[offset], remaining_size);
if (ret < 0)
error(strerror(errno));
}
UPDATE:
The above code corrects some of the issues. But, for a variable length source [such as http], we don't know how much to read at the outset.
So, we have to parse the headers and look for the "Content-Length" field. This will tell us how much to read.
So, we'd like to have line oriented input for the headers. Or, manage our own buffer
Assuming we can parse that value, we have to wait for the empty line to denote the start of the payload. And, then we can loop on that exact amount.
Here's some code that attempts the header parsing and saving of the payload. I've coded it, but not compiled it. So, you can take it as pseudo code:
unsigned long recv_size = 0;
unsigned long response_size = 4096;
char *recv_buff = malloc(response_size + 1);
// line oriented header buffer
char *endl = NULL;
unsigned long linelen;
char linebuf[1000];
int ret = 0;
// read headers
while (1) {
// fill up a chunk of data
while (recv_size < response_size) {
recv_buff[recv_size] = 0;
// do we have a line end?
endl = strstr(recv_buff,"\r\n");
if (endl != NULL)
break;
ret = read(socket, &recv_buff[recv_size], response_size - recv_size);
if (ret < 0)
error(strerror(errno));
if (ret == 0)
break;
recv_size += ret;
}
// error -- no line end but short read
if (endl == NULL)
error(strerror(errno));
// copy header to work buffer
linelen = endl - recv_buff;
memcpy(linebuf,recv_buff,linelen);
linebuf[linelen] = 0;
// remove header from receive buffer
linelen += 2;
recv_size -= linelen;
if (recv_size > 0)
memcpy(recv_buff,&recv_buff[linelen],recv_size);
// stop on end of headers (back to back "\r\n")
if ((recv_size >= 2) && (recv_buff[0] == '\r') && (recv_buff[1] == '\n')) {
memcpy(recv_buff,&recv_buff[2],recv_size - 2);
recv_size -= 2;
break;
}
// parse line work buffer for keywords ... (e.g.)
content_length = ...;
}
// save payload to file
while (content_length > 0) {
// write out prior payload amount
if (recv_size > 0) {
write(file_fd,recv_buff,recv_size);
content_length -= recv_size;
recv_size = 0;
continue;
}
recv_size = read(socket,recv_buff,response_size);
if (recv_size < 0)
error(strerror(errno));
if (recv_size == 0)
break;
}
UPDATE #2:
Yeah, it hard to make the pseudo code run, and the returned values are all garbled
Okay, here is a soup-to-nuts working version that I've tested against my own http server.
I had to create my own routines for the parts you didn't post (e.g. connect, etc.).
At the core, there might have been a minor tweak to the buffer slide code [it was sliding by an extra 2 bytes in one place], but, otherwise it was pretty close to my previous version
// htprcv/htprcv.c -- HTTP receiver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <error.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
typedef unsigned char byte;
#define HTPSLIDE(_rmlen) \
recv_size = htpslide(recv_buff,recv_size,_rmlen)
#define _dbgprt(_fmt...) \
fprintf(stderr,_fmt)
#if DEBUG || _USE_ZPRT_
#define dbgprt(_lvl,_fmt...) \
do { \
if (dbgok(_lvl)) \
_dbgprt(_fmt); \
} while (0)
#define dbgexec(_lvl,_expr) \
do { \
if (dbgok(_lvl)) \
_expr; \
} while (0)
#else
#define dbgprt(_lvl,_fmt...) \
do { \
} while (0)
#define dbgexec(_lvl,_expr) \
do { \
} while (0)
#endif
#define dbgok(_lvl) \
opt_d[(byte) #_lvl[0]]
byte opt_d[256];
char *opt_o;
#define HEXMAX 16
// htpconn -- do connect to server
int
htpconn(const char *hostname,unsigned short portno)
{
struct addrinfo hints, *res;
struct hostent *hostent;
int ret;
char portstr[20];
int sockfd;
/* Prepare hint (socket address input). */
hostent = gethostbyname(hostname);
if (hostent == NULL)
error(1,errno,"htpconn: gethostbyname -- %s\n",hostname);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // ipv4
hints.ai_socktype = SOCK_STREAM; // tcp
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
sprintf(portstr, "%d", portno);
getaddrinfo(NULL, portstr, &hints, &res);
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0)
error(1,errno,"htpconn: socket\n");
// do the actual connection
ret = connect(sockfd, res->ai_addr, res->ai_addrlen);
if (ret < 0)
error(1,errno,"htprcv: read header\n");
return sockfd;
}
// htpslide -- slide buffer (strip out processed data)
size_t
htpslide(char *recv_buff,size_t recv_size,int slidelen)
{
size_t new_size;
if (slidelen > recv_size)
slidelen = recv_size;
new_size = recv_size - slidelen;
dbgprt(S,"htpslide: slidelen=%d recv_size=%zu new_size=%zu\n",
slidelen,recv_size,new_size);
memcpy(&recv_buff[0],&recv_buff[slidelen],new_size);
return new_size;
}
// _htphex -- dump a line in hex
void
_htphex(unsigned long off,const void *vp,size_t xlen)
{
const byte *bp = vp;
int idx;
int chr;
char hexbuf[200];
char alfbuf[200];
char *hexptr = hexbuf;
char *alfptr = alfbuf;
for (idx = 0; idx < HEXMAX; ++idx) {
chr = bp[idx];
if ((idx % 4) == 0)
*hexptr++ = ' ';
if (idx < xlen) {
hexptr += sprintf(hexptr,"%2.2X",chr);
if ((chr < 0x20) || (chr > 0x7E))
chr = '.';
}
else {
hexptr += sprintf(hexptr," ");
chr = ' ';
}
*alfptr++ = chr;
}
*hexptr = 0;
*alfptr = 0;
_dbgprt(" %8.8lX: %s *%s*\n",off,hexbuf,alfbuf);
}
// htphex -- dump a buffer in hex
void
htphex(const char *buf,size_t buflen,const char *reason)
{
size_t off = 0;
size_t xlen;
if (reason != NULL)
_dbgprt("htphex: DUMP buf=%p buflen=%zu (from %s)\n",
buf,buflen,reason);
for (; buflen > 0; buflen -= xlen, buf += xlen, off += xlen) {
xlen = buflen;
if (xlen > HEXMAX)
xlen = HEXMAX;
_htphex(off,buf,xlen);
}
}
// htpsym -- get symbol/value
int
htpsym(char *linebuf,char *sym,char *val)
{
char *cp;
int match;
dbgprt(H,"htpsym: PARAM linebuf='%s'\n",linebuf);
// FORMAT:
// foo-bar: baz
do {
match = 0;
cp = strchr(linebuf,':');
if (cp == NULL)
break;
*cp++ = 0;
strcpy(sym,linebuf);
for (; (*cp == ' ') || (*cp == '\t'); ++cp);
strcpy(val,cp);
match = 1;
dbgprt(H,"htpsym: SYMBOL sym='%s' val='%s'\n",sym,val);
} while (0);
return match;
}
// htprcv -- receive server response
void
htprcv(int sockfd,int fdout)
{
size_t recv_size = 0;
size_t response_size = 4096;
char *recv_buff = malloc(response_size + 1);
// line oriented header buffer
char *endl = NULL;
size_t linelen;
char linebuf[1000];
ssize_t ret = 0;
off_t content_length = 0;
// read headers
while (1) {
// fill up a chunk of data
while (recv_size < response_size) {
recv_buff[recv_size] = 0;
// do we have a line end?
endl = strstr(recv_buff,"\r\n");
if (endl != NULL)
break;
// read a chunk of data
ret = read(sockfd,&recv_buff[recv_size],response_size - recv_size);
if (ret < 0)
error(1,errno,"htprcv: read header\n");
if (ret == 0)
break;
recv_size += ret;
dbgprt(R,"htprcv: READ ret=%zd\n",ret);
dbgexec(R,htphex(recv_buff,recv_size,"htprcv/READ"));
}
// error -- no line end but short read
if (endl == NULL)
error(1,0,"htprcv: no endl\n");
// copy header to work buffer
linelen = endl - recv_buff;
memcpy(linebuf,recv_buff,linelen);
linebuf[linelen] = 0;
// remove header from receive buffer
linelen += 2;
HTPSLIDE(linelen);
// stop on end of headers (back to back "\r\n")
if ((recv_size >= 2) &&
(recv_buff[0] == '\r') && (recv_buff[1] == '\n')) {
HTPSLIDE(2);
break;
}
// parse line work buffer for keywords ...
char sym[100];
char val[1000];
if (! htpsym(linebuf,sym,val))
continue;
if (strcasecmp(sym,"Content-Length") == 0) {
content_length = atoi(val);
continue;
}
}
// save payload to file
while (content_length > 0) {
// write out prior payload amount
if (recv_size > 0) {
dbgexec(W,htphex(recv_buff,recv_size,"htprcv/WRITE"));
ret = write(fdout,recv_buff,recv_size);
if (ret < 0)
error(1,errno,"htprcv: write body\n");
content_length -= recv_size;
recv_size = 0;
continue;
}
// read in new chunk of payload
ret = read(sockfd,recv_buff,response_size);
if (ret < 0)
error(1,errno,"htprcv: read body\n");
if (ret == 0)
break;
recv_size = ret;
}
free(recv_buff);
}
// htpget -- do initial dialog
void
htpget(int sockfd,const char *hostname,const char *file)
{
char *bp;
char buf[1024];
ssize_t resid;
ssize_t xlen;
size_t off;
bp = buf;
if (file == NULL)
file = "/";
bp += sprintf(bp,"GET %s HTTP/1.1\r\n",file);
if (hostname == NULL)
hostname = "localhost";
bp += sprintf(bp,"Host: %s\r\n",hostname);
if (0) {
bp += sprintf(bp,"User-Agent: %s\r\n","curl/7.61.1");
}
else {
bp += sprintf(bp,"User-Agent: %s\r\n","htprcv");
}
bp += sprintf(bp,"Accept: */*\r\n");
bp += sprintf(bp,"\r\n");
resid = bp - buf;
off = 0;
for (; resid > 0; resid -= xlen, off += xlen) {
xlen = write(sockfd,buf,resid);
if (xlen < 0)
error(1,errno,"htpget: write error\n");
}
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
char *portstr;
unsigned short portno;
int sockfd;
int filefd;
char url[1000];
--argc;
++argv;
//setlinebuf(stdout);
setlinebuf(stderr);
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch(cp[-1]) {
case 'd': // debug options
if (*cp == 0)
cp = "SHRW";
for (; *cp != 0; ++cp)
opt_d[(byte) *cp] = 1;
break;
case 'o': // output file
opt_o = cp;
break;
}
}
// get the remote host:port
do {
if (argc <= 0) {
strcpy(url,"localhost:80");
break;
}
strcpy(url,*argv++);
--argc;
} while (0);
// get remote port number
portstr = strchr(url,':');
if (portstr != NULL)
*portstr++ = 0;
else
portstr = "80";
portno = atoi(portstr);
// open the output file (or send to stdout)
do {
if (opt_o == NULL) {
filefd = 1;
break;
}
filefd = open(opt_o,O_WRONLY | O_CREAT,0644);
if (filefd < 0)
filefd = 1;
} while (0);
// establish connection
sockfd = htpconn(url,portno);
// send the file request
htpget(sockfd,NULL,"/");
// receive the server response
htprcv(sockfd,filefd);
close(sockfd);
return 0;
}
Here's the code from rt_imx_uart.c :
static ssize_t rt_imx_uart_write(struct rtdm_fd *fd, const void *buf,
size_t nbyte)
{
struct rt_imx_uart_ctx *ctx;
rtdm_lockctx_t lock_ctx;
size_t written = 0;
int free;
int block;
int subblock;
int out_pos;
char *in_pos = (char *)buf;
rtdm_toseq_t timeout_seq;
ssize_t ret;
if (nbyte == 0)
return 0;
if (rtdm_fd_is_user(fd) && !rtdm_read_user_ok(fd, buf, nbyte))
return -EFAULT;
ctx = rtdm_fd_to_private(fd);
rtdm_toseq_init(&timeout_seq, ctx->config.rx_timeout);
/* Make write operation atomic. */
ret = rtdm_mutex_timedlock(&ctx->out_lock, ctx->config.rx_timeout,
&timeout_seq);
if (ret)
return ret;
while (nbyte > 0) {
rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
free = OUT_BUFFER_SIZE - ctx->out_npend;
if (free > 0) {
block = subblock = (nbyte <= free) ? nbyte : free;
out_pos = ctx->out_tail;
rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
/* Do we have to wrap around the buffer end? */
if (out_pos + subblock > OUT_BUFFER_SIZE) {
/* Treat the block between head and buffer
* end separately.
*/
subblock = OUT_BUFFER_SIZE - out_pos;
if (rtdm_fd_is_user(fd)) {
if (rtdm_copy_from_user
(fd,
&ctx->out_buf[out_pos],
in_pos, subblock) != 0) {
ret = -EFAULT;
break;
}
} else
memcpy(&ctx->out_buf[out_pos], in_pos,
subblock);
written += subblock;
in_pos += subblock;
subblock = block - subblock;
out_pos = 0;
}
if (rtdm_fd_is_user(fd)) {
if (rtdm_copy_from_user
(fd, &ctx->out_buf[out_pos],
in_pos, subblock) != 0) {
ret = -EFAULT;
break;
}
} else
memcpy(&ctx->out_buf[out_pos], in_pos, block);
written += subblock;
in_pos += subblock;
nbyte -= block;
rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
ctx->out_tail =
(ctx->out_tail + block) & (OUT_BUFFER_SIZE - 1);
ctx->out_npend += block;
ctx->ier_status |= IER_TX;
rt_imx_uart_start_tx(ctx);
rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
continue;
}
rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
ret = rtdm_event_timedwait(&ctx->out_event,
ctx->config.tx_timeout,
&timeout_seq);
if (ret < 0) {
if (ret == -EIDRM) {
/* Device has been closed -
* return immediately.
*/
ret = -EBADF;
}
break;
}
}
rtdm_mutex_unlock(&ctx->out_lock);
if ((written > 0) && ((ret == 0) || (ret == -EAGAIN) ||
(ret == -ETIMEDOUT)))
ret = written;
return ret;
}
I understand this function is meant to be used when a user-space program wants to write into the device. However I dont understand how this function can do that, since nowhere in the program do we ever write into the Transmitter Register. The start_tx function used only enables a flag and that's all.
PS: here's the link for this driver: Link to the driver
It looks like the function puts the bytes in a buffer and enables the transmit interrupt. The interrupt service routine probably writes to the uart transmit register.