Friday 15 August 2014

No Need For CC Debugger... You Got The Raspberry Pi!

Its all started when I decided that I want to study ZigBee in order to assemble a smart home system for my home. At the time I decided that the best cheap and good choice for studying ZigBee is the TI CC2530 chip. I Bought two modules from Ebay at 10$ a piece.



I didn't buy a programmer\debugger like CC Debugger basically because I am cheap... I thought I will use the Raspberry Pi as a programmer with this simple Github project and the debugger feature in unnecessary, I HAVE NEVER BEEN SO WRONG.



First, I compile the cc2530prog project on the Raspberry Pi and it didn't work, the program didn't recognise the chip id so even the basic command get_chip_id isn't working. After long examination of the signals coming out from the Raspberry Pi with crappy scope and after looking at the chip datasheet with this document I found that the switching time of the Raspberry Pi is too fast for the cc2530 debug interface. I change the cc2530prog code (even made pull request!) and added sleep commands between switching signals, and its working!!
In my understanding it cant only write raw data as firmware and not TI hex files, but its not too bad.



I started programming my ZigBee software with IAR Workbench and soon realised that in order to study, make things work, and really understanding the ZigBee specs I need a debugger.
The cc2530prog project didn't offer any debugging features and I couldn't find any alternative so I decided to write cc2530 debugger with the Raspberry Pi by myself.
There are three options that I saw at the moment to make a debugger. First one is to make entire environment for debugging, for example with Eclipse. I need to make something like "Pseudo GDB" for the cc2530 debug interface, implementing commands like halt, continue, set breakpoint, step, get pc etc... all of these command the cc2530 debug interface can do (from the previous datasheet):



After making the "Pseudo GDB", I need to parse the compiler symbols, and understanding the address of each command and variable. This choice is very complicated and will take me weeks to implement.

The second choice is based on the fact that the CCDebugger firmware used in IAR environment is available for downloading (e.g cebal_fw_srf05dbg.hex).
The CC Debugger is a 8051 chip (maybe C8051F320?), So I could write an emulator that emulates the 8051 architecture and all the chip controllers like the memory, GPIO, USB, etc.. and make the emulation run the the CC Debugger firmware, but again really complicated and will take me weeks to do.

The third choice, which is the one I implemented in the end, is really cool!
I use the IAR environment to program my code, and the IAR work only with the CC Debugger, so... I can say to my PC that the Raspberry Pi is a CC Debugger USB device, and then the IAR will think that its sending all of its USB commands to a CC Debugger but really its sending all of its commands to my Raspberry Pi which forward them to the cc2530 debug interface!
The difficult part here is emulating USB device. I searched for ways of emulating usb devices and found that QEMU project have USB modules features, so basically you can write any usb device that you want. In the code I need to write the interfaces, the device description and more.. basically implementing the USB specs. This isn't very convenient to write my USB emulation module because each time I need to recompile, restart the vm and my IAR need to run inside the vm which is not good.
I kept searching for ways to emulate USB device and I found this project which called USB Over IP. The goal of this project is to send USB devices over IP eliminating the need for USB driver in the host machine which is very good for routers with USB ports for example, very cool project!
I can use this project to emulate my CC Debugger, and send to the driver fake TCP packets.

My way of starting write things was trying to understand what is going on in the CC Debuger USB. I dont have any CC Debugger device so my only tool was the web and the CC Debugger firmware. I started with the CC-Tool project. This project is open-source driver implementation of the CC Debugger, but again like the cc2530prog its only allowing to program not debug the chips, but still I learn a lot about the the USB protocol that the CC Debugger used, for example I learn the USB product/vendor id which in the file: src/programmer/cc_programmer.cpp:


I also found that the CC Debugger use three USB interfaces, one for control, one for writing data and one for reading data, which also described at the struct above (0x84\0x4...).

After I understand the USB structure of the CC Debugger I need to understand the data that is going in those interfaces. At this time I need some real data from IAR going to my fake USB device.. so its time for writing some code. I started with writing basic QEMU USB module that only emulates the USB structure (interfaces and description).
I took the dev-serial.c from the QEMU project and change few parts:

I change the interfaces addresses from 0x01 and 0x02 to 0x82 and 0x04 as the DeviceTable struct suggests.
 
Then I change the description (product and vendor id) to 0x0451, 0x16a2.

After the module is ready, I boot up QEMU with some linux distro and attach my USB module with QEMU cli. With the module attached, I am running the USB-IP server on my linux and redirecting the traffic to to my windows PC running usb-ip client. I record all the communication with Wireshark in my Windows in order to make python script that will do all of that automatically. At this point you should have the CC Debugger in your device manager in Windows which is awesome!! IAR wont work with it so far because its not really handling commands.
So after sniffing the communication of the USB-IP client server I saw that the server sends to the client the description of the device along with all of its interfaces. I saved the data in text editor for later.
Another way to understand how the USB-IP protocol works is by reading its code, I didn't choose this method because for me its was more difficult. Another way is reading this USB-IP unofficial protocol documentation which can be inaccurate.... in the end I prefer the live QEMU way.

Now, I need to write python script that will send data to the USBIP client.
I started writing python code that will emulate my USB device over USB/IP and write the data to the Raspberry Pi GPIO which is going to the cc2530 debug interface.

First, communicating with the cc2530 on the Rasbbery Pi. I changed the C code from the cc2530prog to match my needs. Be aware, when I wrote this code I have no idea that it will work, so sorry for the bad commented code with memory leaks.

I added some function to cc2530prog:

int send_bulk(char *cmd, int size, char *returned)
{
    //printf("here\n");
    int i;
    int  j;
    char *read_bit; 
    //printf("here - size:%d\n", size);
    j=0;
    for(i=0; i < size; i+=(cmd[i+1] & 3) + 2){
       //printf("loop-> %d\n", i);
       //printf("send: %02x length: %d \n", cmd[i+1], cmd[i+1] & 3);
       send_raw_command(&cmd[i+1], (cmd[i+1] & 3) + 1);
       read_raw_command(&read_bit, 1, 1); 
       //printf("read: %02x\n", read_bit);
       if (cmd[i] == 0x1) {
           returned[j]=read_bit;
           j++;
       }
    }
    printf("ret legnth: %d", j);
    return j;
}
int send_raw_command(char *cmd, int size)
{
 int bytes;
 bcm2835_gpio_fsel(DATA_GPIO, BCM2835_GPIO_FSEL_OUTP);
 //gpio_set_direction(DATA_GPIO, GPIO_DIRECTION_OUT);
 for (bytes = 0; bytes < size; bytes++)
  //printf("sending %02x size: %d pos: %d\n", cmd[bytes], size, bytes);
                send_byte(cmd[bytes]);
 return 0;
}

int read_raw_command(char *output, int size, int first)
{
 FILE *fp;
        int bytes;
 unsigned int timeout = DEFAULT_TIMEOUT;
 bool val;
 unsigned char *answer;
 answer = malloc(size);
 bcm2835_gpio_fsel(DATA_GPIO, BCM2835_GPIO_FSEL_INPT);
 if (first > 0) {
 if (bcm2835_gpio_lev(DATA_GPIO) == HIGH) {val=1;} else {val = 0;}
        while (val && timeout--) {
                for (bytes = 0; bytes < 8; bytes++) {
                        bcm2835_gpio_write(CCLK_GPIO, HIGH);
                        bcm2835_gpio_write(CCLK_GPIO, LOW);
                }
                if (bcm2835_gpio_lev(DATA_GPIO) == HIGH) {val=1;} else {val = 0;}
        }
 }
 for (bytes = 0; bytes < size; bytes++) 
        read_byte(&answer[bytes]);
 //printf("recived: %2x\n", answer[0]);
 memcpy(output, answer, size);
 //fp = fopen ("/tmp/file.txt", "w+");
 //fprintf(fp,"%02X", answer);
 //fclose(fp);
 return 0;
}

plaintext source: http://pastebin.com/A5QD4czt
I then compile this modified cc2530prog code into so for later use.
python code that is running on the Rasbeery Pi:

First, loading the modifided cc2530prog so to python with ctypes:

import socket


from ctypes import *
cc25 = cdll.LoadLibrary('/home/pi/ti/cc2530prog/cc2530prog.so')
print 'init gpio'
cc25.cc2530_gpio_init()
charptr = POINTER(c_char)

cc25.send_raw_command.argtypes = [charptr, c_int]
cc25.send_raw_command.restype = c_int

cc25.send_bulk.argtypes = [charptr, c_int, charptr]
cc25.send_bulk.restype = c_int

cc25.read_raw_command.argtypes = [charptr, c_int, c_int]
cc25.read_raw_command.restype = c_int

Listening to USB/IP requests

HOST = '10.0.0.2'                 # Symbolic name meaning all available interfaces
PORT = 3240               # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr

building usb packets
def build_res(seq, leng, data):
    leng = str(hex(leng))[2:]
    leng = leng.zfill(8)
    return '00000003%s00000000000000000000000000000000%s0000000000000000000000000000000000000000%s' % (seq, leng, data)
sending the initial device description and interfaces from Wireshark records with QEMU.
while 1:
    if contex == 0:
        data = conn.recv(1024)
        #print 'revc ->',  data.encode('hex')
        conn.sendall('0106000500000000'.decode('hex')); print 'sent'
        conn.sendall('00000001'.decode('hex')); print 'sent'
        conn.sendall('2f7379732f646576696365732f706369303030303a30302f303030303a30303a30312e322f757362312f312d3100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000312d310000000000000000000000000000000000000000000000000000000000000000010000000200000002045116a20400000000010101'.decode('hex')); print 'sent'
        conn.sendall('ffffff00'.decode('hex')); print 'sent'
        conn, addr = s.accept()
        data = conn.recv(8); 
        #print 'recv ->', data.encode('hex')
        data = conn.recv(1024)
        #print 'recv ->', data.encode('hex')
        conn.sendall('0106000300000000'.decode('hex')); print 'sent'
        conn.sendall('2f7379732f646576696365732f706369303030303a30302f303030303a30303a30312e322f757362312f312d3100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000312d310000000000000000000000000000000000000000000000000000000000000000010000000200000002045116a20400000000010101'.decode('hex')); print 'sent'

Handling CC Debugger requests
# handle rquests
        #print 'enter command loop'
        while 1:
            data=conn.recv(0x30)
            command = data[:4].encode('hex')
            seq = data[4:8].encode('hex')
            #devid = int(data[8:12].encode('hex'))
            direction = data[12:16].encode('hex')
            ep = int('0x' + data[16:20].encode('hex'), 0)
            #transfer_flags = int(data[20:24].encode('hex'))
            transfer_buffer_length = int('0x' + data[24:28].encode('hex'), 0)
            #start_frame = int(data[28:32].encode('hex'))
            #number_of_packets = int(data[32:36].encode('hex'))
            #interval = int(data[36:40].encode('hex'))
            setup = data[40:48]
            #print 'got command ' + command.encode('hex')
            #
            print '--------got packet------------'
            print 'command -> ', command
            print 'dir -> ', direction
            print 'ep -> ', ep
            print 'setup -> ', setup.encode('hex')
            print 'length -> ', transfer_buffer_length
            if setup.encode('hex') == '8006000100001200':
                conn.sendall(('00000003%s0000000000000000000000000000000000000012000000000000000000000000000000000000000012010002000000085104a216000401020401' %(seq)).decode('hex'))
                print 'sent 8006 control 1'
            elif setup.encode('hex') == '8006000300000201':
                conn.sendall(('00000003%s0000000000000000000000000000000000000004000000000000000000000000000000000000000004030904' %(seq)).decode('hex'))
                print 'sent 8006 control 2'
            elif setup.encode('hex') == '8006000200000900':
                conn.sendall(('00000003%s00000000000000000000000000000000000000090000000000000000000000000000000000000000090220000101008032' %(seq)).decode('hex'))
            elif setup.encode('hex') == '8006000200002000':
                conn.sendall(('00000003%s000000000000000000000000000000000000002000000000000000000000000000000000000000000902200001010080320904000002ffffff000705840240000007050402400000' %(seq)).decode('hex'))
                print 'sent 8006 control 4'
            elif setup.encode('hex') == 'c0c0000000000001'  and ep == 0:
                print 'get programmer state.'
                conn.sendall(build_res(seq,8, '3025cc0542000008').decode('hex'))
            elif setup.encode('hex') == '40c9000001000000'  and ep == 0:
                print 'reset usb'
                conn.sendall(build_res(seq,0, '').decode('hex'))
            elif setup.encode('hex') == '40c5000000000000' and ep == 0:
                print 'prepate debug'
                payload = conn.recv(transfer_buffer_length)
                print 'payload :', payload.encode('hex')
                cmd = '!!!!!!!!!!!!enter_debug!!!!!!!!!!!!!!!!!!!!'
                cc25.cc2530_enter_debug()
                #s2.send(str(len(cmd)).zfill(10) + cmd)
                #conn.sendall(build_res(seq,0, '').decode('hex'))
            elif setup.encode('hex') == 'c0c6000000000100' and ep == 0:
                print 'prepare for some usb shit...'
                conn.sendall(build_res(seq,0, '').decode('hex'))
            else:
                print 'probaby data' 
                if direction == '00000000':  
                    bulk_output = False
                    print 'out!'
                    payload = conn.recv(transfer_buffer_length)
                    print 'payload :', payload.encode('hex')
                    known_flag = False
                    append=False
                    if payload.encode('hex').startswith('3e') and len(payload) > 2:
                        print 'sending command: ', payload[1].encode('hex')
                        cc25.send_raw_command(payload[1], 1)
                        output=''.zfill(1024)
                        cc25.read_raw_command(output, 2, 1)
                        html_res = output[:2].encode('hex')
                        print 'html res -> ' , html_res
                        payload = payload[2:]
                        print 'new payload: ', payload.encode('hex')
                        known_flag = False 
                        append=True
                    if payload.encode('hex').startswith('2e'):
                        known_flag = True    
                    elif payload.encode('hex').startswith('1c'):
                        known_flag = True
                    elif payload.encode('hex').startswith('4c'):
                        known_flag = True
                    elif payload.encode('hex').startswith('1f'):
                        known_flag = True
                    elif payload.encode('hex').startswith('be'):
                        i=1
                        html_res = ''
                        while 1:
                            if i >= len(payload):
                                break
                            if ord(payload[i]) < 0x50 or ord(payload[i]) > 0x57:
                                break
                            
                            num_of_opcode = ord(payload[i]) & 3
                            payload_to_send = payload[i:i+num_of_opcode+1]
                            cc25.send_raw_command(payload_to_send, len(payload_to_send))
                            output=''.zfill(1024)
                            cc25.read_raw_command(output, 1, 1)
                            i=i+num_of_opcode+2
                        known_flag = False
                        
                    elif payload.encode('hex').startswith('40'):
                        print 'bulk'
                        inbulk=True
                        #payload = tokens[1].decode('hex')
                        i=1
                        out_ret = ''
                        payload_to_send = ''
                        while 1:
                            if i >= len(payload):
                                break
                            if ord(payload[i]) < 0x50 or ord(payload[i]) > 0x57:
                                break
                            if ord(payload[i-1]) == 0x90 or ord(payload[i-1]) == 0x91:
                                break
                            num_of_opcode = ord(payload[i]) & 3
                            if ord(payload[i-1]) is 0x4f or ord(payload[i-1]) is 0x4e or ord(payload[i-1]) is 0x7f or ord(payload[i-1]) is 0x7e:
                                payload_to_send += chr(0x1) + payload[i:i+num_of_opcode+1]
                            else:
                                payload_to_send += chr(0x0)+ payload[i:i+num_of_opcode+1]
                            i=i+num_of_opcode+2 
                        
                        out_ret = ''.zfill(1024)
                        print 'bulk c payload: ', payload_to_send.encode('hex')
                        ret_len = cc25.send_bulk(payload_to_send, len(payload_to_send), out_ret)
                        if append:
                            html_res += out_ret[:ret_len].encode('hex')
                        else:
                            html_res = out_ret[:ret_len].encode('hex')
                        print 'bulk putput: ', html_res
                        #cmd = 'bulk;%s' %( payload.encode('hex'))
                        #s2.send(str(len(cmd)).zfill(10) + cmd)
                        known_flag = False
                        #bulk_output = True
                    elif payload.encode('hex').startswith('5e'):
                        print 'bulk'
                        inbulk=True
                        #payload = tokens[1].decode('hex')
                        i=1
                        out_ret = ''
                        payload_to_send = ''
                        while 1:
                            if i >= len(payload):
                                break
                            if ord(payload[i]) < 0x50 or ord(payload[i]) > 0x57:
                                break
           
                            num_of_opcode = ord(payload[i]) & 3
                            if ord(payload[i-1]) is 0x4f or ord(payload[i-1]) is 0x4e or ord(payload[i-1]) is 0x7f:
                                payload_to_send += chr(0x1) + payload[i:i+num_of_opcode+1]
                            else:
                                payload_to_send += chr(0x0)+ payload[i:i+num_of_opcode+1]
                            i=i+num_of_opcode+2 
                        out_ret = ''.zfill(1024)
                        ret_len = cc25.send_bulk(payload_to_send, len(payload_to_send), out_ret)
                        html_res = out_ret[:ret_len].encode('hex')
                        #cmd = 'bulk;%s' %( payload.encode('hex'))
                        #s2.send(str(len(cmd)).zfill(10) + cmd)
                        known_flag = False
                        #bulk_output = True         
                    elif payload.encode('hex').startswith('3f'):
                        known_flag = True
                    elif payload.encode('hex').startswith('2e'):
                        known_flag = True
                    elif payload.encode('hex').startswith('4e'):
                        print 'bulk'
                        inbulk=True
                        #payload = tokens[1].decode('hex')
                        i=1
                        out_ret = ''
                        payload_to_send = ''
                        while 1:
                            if i >= len(payload):
                                break
                            if ord(payload[i]) < 0x50 or ord(payload[i]) > 0x57:
                                break
                            if ord(payload[i-1]) == 0x90 or ord(payload[i-1]) == 0x91:
                                break
                            num_of_opcode = ord(payload[i]) & 3
                            if ord(payload[i-1]) is 0x4f or ord(payload[i-1]) is 0x4e or ord(payload[i-1]) is 0x7f or ord(payload[i-1]) is 0x7e:
                                payload_to_send += chr(0x1) + payload[i:i+num_of_opcode+1]
                            else:
                                payload_to_send += chr(0x0)+ payload[i:i+num_of_opcode+1]
                            i=i+num_of_opcode+2 
                        
                        out_ret = ''.zfill(1024)
                        print 'bulk c payload: ', payload_to_send.encode('hex')
                        ret_len = cc25.send_bulk(payload_to_send, len(payload_to_send), out_ret)
                        if append:
                            html_res += out_ret[:ret_len].encode('hex')
                        else:
                            html_res = out_ret[:ret_len].encode('hex')
                        print 'bulk putput: ', html_res
                        #cmd = 'bulk;%s' %( payload.encode('hex'))
                        #s2.send(str(len(cmd)).zfill(10) + cmd)
                        known_flag = False
                        known_flag = True
                    elif payload.encode('hex').startswith('ee'):
                        known_flag = True
                    else:
                        print 'unknown'
                    if known_flag:
                        #cmd = 'do_command;%s' %( payload.encode('hex')[2:])
                        #s2.send(str(len(cmd)).zfill(10) + cmd)
                        #cmd = 'read_output;1;1'
                        #s2.send(str(len(cmd)).zfill(10) + cmd)
                        #html_res = s2.recv(10)
                        #html_res = s2.recv(int(html_res))
                        #html_res = requests.get('http://10.0.0.2:8080/programmer/?command=do_command&payload=%s' %( payload.encode('hex')[2:])).text
                        print 'sending command: ', payload[1:].encode('hex')
                        cc25.send_raw_command(payload[1:], len(payload[1:]))
                        output=''.zfill(1024)
                        cc25.read_raw_command(output, 1, 1)
                        html_res = output[:1].encode('hex')
                        #out_str = output[:read_length].encode('hex')
                        print 'html res -> ' , html_res

                    conn.sendall(build_res(seq,0, '').decode('hex'))
                    j = j+1
                else:
                    print 'in'
                    #if bulk_output:
                    #    print 'read bulk'
                    #    #cmd = 'read_bulk'
                    #    #s2.send(str(len(cmd)).zfill(10) + cmd)
                    #    #length = s2.recv(10)
                    #    #html_res = s2.recv(int(length))
                    #    
                    # print 'html res -> ' , html_res
                    if transfer_buffer_length - 1 > 0:
                        #cmd = 'read_output;%s;0'%(transfer_buffer_length-1)
                        #s2.send(str(len(cmd)).zfill(10) + cmd)
                        #html_res2 = s2.recv(10)
                        #html_res2 = s2.recv(int(html_res2))
                        #html_res = requests.get('http://10.0.0.2:8080/programmer/?command=read_output&read_length=%s' %(transfer_buffer_length)).text
                        print 'html: ', html_res[:transfer_buffer_length*2] 
                        conn.sendall(build_res(seq,transfer_buffer_length, html_res[:transfer_buffer_length*2]).decode('hex')) 
                    else:
                        print 'html: ', html_res[:transfer_buffer_length*2] 
                        conn.sendall(build_res(seq,transfer_buffer_length, html_res[:transfer_buffer_length*2]).decode('hex')) 

The entire python file is available here:
plaintext source:http://pastebin.com/cBV7GyZW

I can program hex file with IAR workbench without any problem which is very cool!
I can partially debug programs, sometimes it works sometimes it doesn't... I guess its because I don't truly understand the way that the IAR and CC Debugger communicates. I Also tried reversing the CC Debugger firmware with IDA. To do that I need to add the cc2530 to IDA config files, I added to the configuration the SFR registers, where is the RAM and ROM and other things that IDA requires.

I worked on this project about two weeks, on my free time. When I finished my father asked me why I am working so hard.. I told him about what I am working on.. then he told that I don't need to.. he got CC Debugger at work next to him and can bring as much as I want... so FACEPALM!!!!!!



Nevertheless, I hope someone could find this project helpful and use this code of mine better than me, if not for CC Debugger emulation then for USB emulation.
If you got any questions you are more the welcomed to ask in the comments.
Thanks for reading

No comments:

Post a Comment