查看: 4237|回复: 1
收起左侧

树莓派BootLoader(转载)

2013-5-26 11:51:48 | 显示全部楼层 |阅读模式
made by Rk
本文由浙江大学《嵌入式系统》课程提供强力支持。
感谢翁恺老师 @翁恺BA5AG
/*************************************************************/

实验内容:David Welch的GitHub的 bootloader05给出了一个非常简单的RPi bootloader,他的代码链接在内存的0x00020000位置,一直在监听串口是 否有XMODEM协议的文件下载,如果有就开始接收数据,并复制到0x00008000位置,传输完成后跳转到 0x00008000去执行。
TA写了一个Python脚本,按照下面的命令调用脚本可以下载并执行用户程序


python xmodem-loader.py -p com3 -baud 115200 output.bin
你的任务是修改bootloader和python脚本实现如下功能:


调用命令 python xmodem-loader.py -p com3 -baud 115200 启动脚本并且与板卡建立串口连接,之后可以发送下面的命令。
load *.bin 下载程序*.bin
go 执行已下载的程序
peek addr 以一个字为单位读取内存中addr位置的数据(addr是4字节对齐,十六进行的形式,长度为8,例如 0x00008000),并以十六进制的形式输出
poke addr data 以一个字为单位修改内存中addr位置的数据为data(addr是4字节对齐,十六进行的形式,长 度为8, data也是十六进行的形式,长度为8)
verify *.bin 验证已下载的程序和*.bin是否完全相同。

背景在国外大神的README中介绍了相关背景:
This repo serves as a collection of low level examples.  No operating
system, embedded or low level embedded or deeply embedded or bare metal,
whatever your term is for this.
介绍了教程来源

I am in no way shape or form associated with the raspberry pi organization
nor broadcom.  I just happen to own one (some) and am sharing my
experiences.  The raspberry pi is about education, and I feel low
level education is just as important as Python programming.
介绍了作者对于树莓派的想法

From what we know so far there is a gpu on chip which:

1) boots off of an on chip rom of some sort
2) reads the sd card and looks for additional gpu specific boot files
bootcode.bin and start.elf in the root dir of the first partition
(fat32 formatted, loader.bin no longer used/required)
3) in the same dir it looks for config.txt which you can do things like
change the arm speed from the default 700MHz, change the address where
to load kernel.img, and many others
4) it reads kernel.img the arm boot binary file and copies it to memory
5) releases reset on the arm such that it runs from the address where
the kernel.img data was written
树莓派通过板上的GPU加载启动信息,
1、读取SD卡第一个分区根目录里面的bootcode二进制文件以及start.elf。
2、在同一个目录下加载系统配置文件,比如配置CPU主频等,然后切换到读取kernel.img的地址。
3、读取kelnel后加载ARM启动二进制文件然后拷贝到内存,发送重置信号,使得树莓派可以从kernel.img的写入数据区开始加载命令继续执行。

The memory is split between the GPU and the ARM, I believe the default
is to split the memory in half.  And there are ways to change that
split (to give the ARM more).  Not going to worry about that here.

内存被分为GPU和ARM使用两部分,目测对半分。

From the ARMs perspective the kernel.img file is loaded, by default,
to address 0x8000.  (there are ways to change that, not going to worry
about that right now).
从ARM的角度来看,kernel镜像从地址0x8000开始加载。

Hardware and programming information:


You will want to go here
http://elinux.org/RPi_Hardware
And get the datasheet for the part
http://www.raspberrypi.org/wp-co ... ARM-Peripherals.pdf
(might be an old link, find the one on the wiki page)
And the schematic for the board
http://www.raspberrypi.org/wp-co ... Schematics-R1.0.pdf
(might be an old link, find the one on the wiki page)


Early in the BCM2835 document you see a memory map.  I am going to
operate based on the middle map, this is how the ARM comes up.  The
left side is the system which we dont have direct access to in that
form, the gpu probably, not the ARM.  The ARM comes up with a memory
space that is basically 0x40000000 bytes in size as it mentions in
the middle chart.  The bottom of this picture shows total system
sdram (memory) and somewhere between zero and the top of ram is a
split between sdram for the ARM on the bottom and a chunk of that
for the VC SDRAM, basically memory for the gpu and memory shared
between the ARM and GPU to allow the ARM to ask the GPU to draw stuff
on the video screen.  256MBytes is 0x10000000, and 512MBytes is
0x20000000.  Some models of raspberry pi have 256MB, newer models have
512MB total ram which is split between the GPU and the ARM.  Assume
the ARM gets at least half of this.  Peripherals (uart, gpio, etc)
are mapped into arm address space at 0x20000000.  When you see
0x7Exxxxxx in the manual replace that with 0x20xxxxxx as your ARM
physical address.  Experimentally I have seen the memory repeats every
0x40000000, read 0x40008000 and you see the data from 0x8000.  I
wouldnt rely on this, just an observation (likely ignoring the upper
address bits in the memory controller).
第一次编译运行
在Mac系统上的编译一开始遇到一些问题,需要修改loader文件为以下内容:
  1. MEMORY
  2. {
  3.     ram : ORIGIN = 0x8000, LENGTH = 0x1000000
  4. }

  5. SECTIONS
  6. {
  7.     .text : { *(.text*) *(.rodata.str1.4) *(.rodata) } > ram
  8.     .bss : { *(.bss*) } > ram
  9. }
复制代码
修改bootloader05.c文件,原理是:
将原有的3个控制状态和128个数据读写状态修改为7个控制状态以及128个数据读写状态。
其中新增的状态有:
1、GO状态,跳转到Base Address读取并且执行代码;
2、Verify状态,根据error address的不同进行检查,error address初始化为0,一旦在verify过程中检查到已读到的数据与新的bin文件不一致,记录当前错误地址,并且回到状态0;
3、Peek状态,根据后面指令输入的address,通过GET(address)函数(定义在vector.s中)得到该内存地址上存的值,打印输出;
4、Poke状态,根据指令后面的地址以及新的数据,通过PUT(address,data value)函数(定义在vector.s中)修改内存地址,并且打印输出修改地址和修改后的值(通过GET得到)
5、LOAD状态,也就是加载二进制文件。

源代码中检查block块有两次检验,个人感觉第二次可以去掉,于是尝试了一下,事实证明也是可以的。

代码以及注释如下:
  1. //-------------------------------------------------------------------------
  2. //-------------------------------------------------------------------------

  3. // The raspberry pi firmware at the time this was written defaults
  4. // loading at address 0x8000.  Although this bootloader could easily
  5. // load at 0x0000, it loads at 0x8000 so that the same binaries built
  6. // for the SD card work with this bootloader.  Change the ARMBASE
  7. // below to use a different location.

  8. #define ARMBASE 0x8000
  9. #define true 1

  10. #define LOAD    0x00
  11. #define GO      0x01
  12. #define PEEK    0x02
  13. #define POKE    0x03
  14. #define VERIFY  0x04

  15. extern void PUT32 ( unsigned int, unsigned int );
  16. extern void PUT16 ( unsigned int, unsigned int );
  17. extern void PUT8 ( unsigned int, unsigned int );
  18. extern unsigned int GET32 ( unsigned int );
  19. extern unsigned int GET8 ( unsigned int );
  20. extern unsigned int GETPC ( void );
  21. extern void BRANCHTO ( unsigned int );
  22. extern void dummy ( unsigned int );

  23. extern void uart_init ( void );
  24. extern unsigned int uart_lcr ( void );
  25. extern void uart_flush ( void );
  26. extern void uart_send ( unsigned int );
  27. extern unsigned int uart_recv ( void );
  28. extern void hexstring ( unsigned int );
  29. extern void hexstrings ( unsigned int );
  30. extern void timer_init ( void );
  31. extern unsigned int timer_tick ( void );

  32. extern void timer_init ( void );
  33. extern unsigned int timer_tick ( void );

  34. void print_pi(char* s) {
  35.     int i = 0;
  36.     while(s[i] != '\0') {
  37.         uart_send(s[i++]);
  38.     }
  39.     uart_send(0x0D);    //send carriage return
  40.     uart_send(0x0A);    //send new line
  41. }

  42. //------------------------------------------------------------------------
  43. unsigned char xstring[256];
  44. //------------------------------------------------------------------------
  45. int notmain ( void ) {
  46.     unsigned int ra;
  47.     //unsigned int rb;
  48.     unsigned int rx;
  49.     unsigned int addr;
  50.     unsigned int block;
  51.     unsigned int state;

  52.     unsigned int crc;
  53.     unsigned int error_addr;

  54.     uart_init();            //init serial
  55.     hexstring(0x12345678);  //translate to hex value and send
  56.     hexstring(GETPC());
  57.     hexstring(ARMBASE);
  58.     print_pi("This is Raspberry Pi!");
  59.     uart_send(0x04);
  60.     timer_init();

  61. //SOH 0x01
  62. //ACK 0x06
  63. //NAK 0x15
  64. //EOT 0x04

  65. //block numbers start with 1

  66. //132 byte packet
  67. //starts with SOH
  68. //block number byte
  69. //255-block number
  70. //128 bytes of data
  71. //checksum byte (whole packet)
  72. //a single EOT instead of SOH when done, send an ACK on it too
  73.    
  74.     addr=ARMBASE;        //base RAM address
  75.     error_addr = 0; //record the address of error
  76.     block=1;        //the
  77.     state=0;                //initial state
  78.     crc=0;          //check sum
  79.     rx=timer_tick();

  80.     while(true)
  81.     {
  82.         ra=timer_tick();
  83.         if((ra-rx)>=4000000)
  84.         {
  85.             uart_send(0x15);                //send long time no-response signal
  86.             rx+=4000000;
  87.         }

  88.         if((uart_lcr()&0x01)==0)
  89.         {
  90.             continue;               //test if input string begin with 0x01
  91.         }

  92.         xstring[state]=uart_recv();
  93.         rx=timer_tick();

  94.         switch(state)
  95.         {
  96.             case 0:     //initial state, decide which action to take
  97.             {
  98.                 if(xstring[state]==0x01)
  99.                 {
  100.                     crc=xstring[state];
  101.                     state++;
  102.                 }
  103.                 else if (xstring[state] == 0x04)    //End Of transmission
  104.                 {                                   //decide the action
  105.                     uart_send(0x06);
  106.                     if (xstring[1] == LOAD)
  107.                     {
  108.                         print_pi("This is LOAD command!");
  109.                         uart_send(0x04);
  110.                         uart_flush();
  111.                     }
  112.                     else if (xstring[1] == VERIFY)
  113.                     {
  114.                         if (error_addr == 0)
  115.                         {
  116.                             print_pi("Verify successful!");
  117.                             uart_send(0x04);
  118.                         }
  119.                         else
  120.                         {
  121.                             print_pi("Verify error");
  122.                             print_pi("Error Adress:");
  123.                             hexstring(error_addr);
  124.                             print_pi("Error Value:");
  125.                             hexstring(GET32(error_addr));
  126.                             uart_send(0x04);
  127.                         }
  128.                         uart_flush();
  129.                     }
  130.                     addr = ARMBASE;
  131.                     error_addr = 0;
  132.                     block = 1;
  133.                     state = 0;
  134.                     crc = 0;
  135.                 }
  136.                 else
  137.                 {
  138.                     state=0;
  139.                     uart_send(0x15);
  140.                     print_pi("Init Error!");
  141.                     uart_send(0x04);
  142.                     uart_flush();
  143.                 }
  144.                 break;
  145.             }
  146.             case 1:                             //decide the action
  147.             {
  148.                 if (xstring[1] > VERIFY)
  149.                 {
  150.                     state = 0;
  151.                     uart_send(0x15);
  152.                     print_pi("Command error!");
  153.                     uart_send(0x04);
  154.                     uart_flush();
  155.                 }
  156.                 else if (xstring[1] == GO)
  157.                 {
  158.                     state = 0;
  159.                     uart_send(0x06);
  160.                     print_pi("Branch to the base address!");
  161.                     uart_send(0x04);
  162.                     uart_flush();
  163.                     BRANCHTO(ARMBASE);
  164.                 }
  165.                 else if (xstring[1] == PEEK)
  166.                 {
  167.                     state = 133;
  168.                 }
  169.                 else if (xstring[1] == POKE)
  170.                 {
  171.                     state = 133;
  172.                 }
  173.                 else
  174.                 {
  175.                     state++;
  176.                 }
  177.                 break;
  178.             }
  179.             case 2:
  180.             {
  181.                 if(xstring[state] == block)//if the data has the right block number
  182.                 {
  183.                     crc += xstring[state];
  184.                     state++;
  185.                 }
  186.                 else
  187.                 {
  188.                     state = 0;
  189.                     uart_send(0x15);
  190.                     print_pi("Data block error!");
  191.                     uart_send(0x04);
  192.                     uart_flush();
  193.                 }
  194.                 break;
  195.             }
  196.             
  197.             case 132:   //receive and verify progress
  198.             {
  199.                 crc &= 0xFF;
  200.                 if(xstring[state]==crc)
  201.                 {
  202.                     if (xstring[1] == LOAD)
  203.                     {
  204.                         for(ra=0; ra<128; ra++)
  205.                         {
  206.                             PUT8(addr++,xstring[ra+4]);
  207.                         }
  208.                         uart_send(0x06);
  209.                     }
  210.                     else
  211.                     {
  212.                         //Verify progress
  213.                         for (ra=0; ra<128; ra++,addr++)
  214.                         {
  215.                             if (xstring[ra + 4] != (GET8(addr) & 0xff))
  216.                             {
  217.                                 error_addr = addr;  //get the error address
  218.                                 break;
  219.                             }
  220.                         }
  221.                         uart_send(0x06);
  222.                     }
  223.                     block=(block+1) & 0xFF; //if the data flow has not stopped
  224.                 }
  225.                 else
  226.                 {
  227.                     uart_send(0x15);
  228.                     print_pi("Check sum error!");
  229.                     uart_send(0x04);
  230.                     uart_flush();
  231.                 }
  232.                 state=0;
  233.                 break;
  234.             }
  235.             case 136:
  236.             {
  237.                 if (xstring[1] == PEEK)
  238.                 {
  239.                     unsigned int peek_addr = 0;
  240.                     for (ra = 0; ra < 4; ra++)
  241.                     {   //generate the address
  242.                         peek_addr = peek_addr << 8 | xstring[ra + 133];
  243.                     }
  244.                     uart_send(0x06);
  245.                     print_pi("Peek command value:");
  246.                     hexstring(GET32(peek_addr));
  247.                     uart_send(0x04);
  248.                     uart_flush();
  249.                     state = 0;
  250.                 }
  251.                 else
  252.                 {
  253.                     state++;
  254.                 }
  255.                 break;
  256.             }
  257.             case 140:
  258.             {
  259.                 if (xstring[1] == POKE)
  260.                 {
  261.                     unsigned int poke_addr = 0x00000000;
  262.                     unsigned int poke_data = 0;
  263.                     for (ra = 0; ra < 4; ra++)
  264.                     {
  265.                         poke_addr = poke_addr << 8 | xstring[ra + 133];
  266.                         poke_data = poke_data << 8 | xstring[ra + 137];
  267.                     }
  268.                     uart_send(0x06);
  269.                     print_pi("Poke command:");
  270.                     PUT32(poke_addr, poke_data);
  271.                     print_pi("Poke address:");
  272.                     hexstring(poke_addr);           //get the Poke address
  273.                     print_pi("Poke value:");
  274.                     hexstring(GET32(poke_addr));   //get the value after edit action
  275.                     uart_send(0x04);
  276.                     uart_flush();
  277.                     state = 0;
  278.                 }
  279.                 else
  280.                 {
  281.                     state = 0;
  282.                 }
  283.                 break;
  284.             }
  285.             default:
  286.             {
  287.                 crc+=xstring[state];
  288.                 state++;
  289.                 break;
  290.             }
  291.         }
  292.     }
  293.     return(0);
  294. }

  295. //-------------------------------------------------------------------------
  296. //-------------------------------------------------------------------------


  297. //-------------------------------------------------------------------------
  298. //
  299. // Copyright (c) 2012 David Welch dwelch@dwelch.com
  300. //
  301. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  302. //
  303. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  304. //
  305. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  306. //
  307. //-------------------------------------------------------------------------
复制代码
在vector.s中增加GET8:
  1. .globl GET8
  2. GET8:
  3.     ldrb r0,[r0]
  4.     bx lr
复制代码
修改py文件对串口数据进行操作:
  1. import sys, getopt
  2. import serial
  3. import time

  4. def open(aport='/dev/tty.usbserial', abaudrate=115200) :#此处修改为本机串口地址,可以参见博文 http://blog.csdn.net/rk2900/article/details/8632713
  5.   return serial.Serial(
  6.       port=aport,
  7.       baudrate=abaudrate,     # baudrate
  8.       bytesize=8,             # number of databits
  9.       parity=serial.PARITY_NONE,
  10.       stopbits=1,
  11.       xonxoff=0,              # enable software flow control
  12.       rtscts=0,               # disable RTS/CTS flow control
  13.       timeout=None               # set a timeout value, None for waiting forever
  14.   )

  15. def printLog(sp):
  16.   temp = sp.read()
  17.   while ord(temp) != 0x04:
  18.       write(temp)
  19.       temp = sp.read()

  20. if __name__ == "__main__":

  21.   # Import Psyco if available
  22.   try:
  23.       import psyco
  24.       psyco.full()
  25.       print "Using Psyco..."
  26.   except ImportError:
  27.       pass

  28.   conf = {
  29.       'port': '/dev/tty.usbserial',#此处修改为本机串口地址
  30.       'baud': 115200,
  31.   }

  32.   try:
  33.       opts, args = getopt.getopt(sys.argv[1:], "hqVewvrp:b:a:l:")
  34.   except getopt.GetoptError, err:
  35.       print str(err)
  36.       sys.exit(2)

  37.   for o, a in opts:
  38.       if o == '-p':
  39.           conf['port'] = a
  40.       elif o == '-b':
  41.           conf['baud'] = eval(a)
  42.       else:
  43.           assert False, "option error!"

  44.   sp = open(conf['port'], conf['baud'])

  45. # print args[0]
  46. # print conf['port']
  47. # print conf['baud']

  48.   write=sys.stdout.write

  49.   isLoaded = False

  50.   while True:
  51.       print ''
  52.       cmd = raw_input('>> ').split(' ');
  53.       sp.flushInput()

  54.       if cmd[0] == 'go':
  55.           if not isLoaded:
  56.               confirm = raw_input("No file loaded, sure to go? [Y/N]")
  57.               if confirm == '' or confirm[0] == 'N' or confirm[0] == 'n':
  58.                   continue

  59.           success = False
  60.           while success == False:
  61.               sp.write(chr(0x01))
  62.               sp.write(chr(0x01))
  63.               sp.flush()

  64.               temp=sp.read()

  65.               if ord(temp)==0x06:
  66.                   success = True
  67.               else:
  68.                   print ord(temp)

  69.                   printLog(sp)

  70.       elif cmd[0] == 'peek':
  71.           if len(cmd) < 2:
  72.               print "Incorrect command, should be 'peek addr'"
  73.               continue

  74.           addr = int(cmd[1], 16) & 0xffffffff

  75.           success = False
  76.           while success == False:
  77.               sp.write(chr(0x01))
  78.               sp.write(chr(0x02))

  79.               for i in range(0,4):
  80.                   sp.write(chr(addr >> 24 & 0xff))
  81.                   addr = addr << 8

  82.               sp.flush()

  83.               temp=sp.read()

  84.               if ord(temp)==0x06:
  85.                   success = True
  86.               else:
  87.                   print ord(temp)

  88.                   printLog(sp)

  89.       elif cmd[0] == 'poke':
  90.           if len(cmd) < 3:
  91.               print "Incorrect command, should be 'poke addr data'"
  92.               continue

  93.           addr = int(cmd[1], 16) & 0xffffffff
  94.           data = int(cmd[2], 16) & 0xffffffff

  95.           success = False
  96.           while success == False:
  97.               sp.write(chr(0x01))
  98.               sp.write(chr(0x03))

  99.               for i in range(0,4):
  100.                   sp.write(chr(addr >> 24 & 0xff))
  101.                   addr = addr << 8
  102.               for i in range(0,4):
  103.                   sp.write(chr(data >> 24 & 0xff))
  104.                   data = data << 8

  105.               sp.flush()

  106.               temp=sp.read()

  107.               if ord(temp)==0x06:
  108.                   success = True
  109.               else:
  110.                   print ord(temp)

  111.                   printLog(sp)

  112.       elif cmd[0] == 'load' or cmd[0] == 'verify':
  113.           if len(cmd) < 2:
  114.               print "Please input the filename"
  115.               continue

  116.           try:
  117.               data = map(lambda c: ord(c), file(cmd[1],"rb").read())
  118.           except:
  119.               print "File path error"
  120.               continue

  121.           temp = sp.read()
  122.           buf = ""

  123.           dataLength = len(data)
  124.           blockNum = (dataLength-1)/128+1
  125.           print "The size of the image is ",dataLength,"!"
  126.           print "Total block number is ",blockNum,"!"
  127.           print "Download start,",blockNum,"block(s) in total!"

  128.           for i in range(1,blockNum+1):
  129.               success = False
  130.               while success == False:
  131.                   sp.write(chr(0x01))
  132.                   if cmd[0] == 'load':
  133.                       sp.write(chr(0x00))
  134.                   else:
  135.                       sp.write(chr(0x04))
  136.                   sp.write(chr(i&0xFF))
  137.                   sp.write(chr(0xFF-i&0xFF))
  138.                   crc = 0x01+0xFF

  139.                   for j in range(0,128):
  140.                       if len(data)>(i-1)*128+j:
  141.                           sp.write(chr(data[(i-1)*128+j]))
  142.                           crc += data[(i-1)*128+j]
  143.                       else:
  144.                           sp.write(chr(0xff))
  145.                           crc += 0xff

  146.                   crc &= 0xff
  147.                   sp.write(chr(crc))
  148.                   sp.flush()

  149.                   temp=sp.read()
  150.                   sp.flushInput()

  151.                   if ord(temp)==0x06:
  152.                       success = True
  153.                       print "Block",i,"has finished!"
  154.                   else:
  155.                       print ord(temp)
  156.                       print "Error,send again!"

  157.                       printLog(sp)

  158.           sp.write(chr(0x04))
  159.           sp.flush()
  160.           temp=sp.read()

  161.           if ord(temp)==0x06:
  162.               if (cmd[0] == 'load'):
  163.                   isLoaded = True
  164.               print "Download has finished!\n"
  165.       elif cmd[0] == 'q':
  166.           sys.exit(0)
  167.       else:
  168.           print "Invalid command!"


  169.       printLog(sp)


  170.   # while True:
  171.   #   write(sp.read())

  172.   sp.close()
复制代码
实验结果LOAD指令:



输入GO指令,此处我加载的是外国人搞的blinker二进制文件,效果就是树莓派上ACT的小灯闪烁。
效果如图:
1.png

进行PEEK与POKE操作
此处POKE将修改后的内存值打印出来是需要通过GET操作得到后来该内存地址处的值,而非打印POKE操作输入的参数。
2.png

POKE操作之前进行Verify:
3.png

POKE操作之后进行verify:
4.png

转载自:http://blog.csdn.net/rk2900/article/details/8936580
回复

使用道具 举报

2013-9-29 07:35:23 | 显示全部楼层
脚印了,感谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关注我们,了解更多

官方微信

服务时间:10:00-16:00

13714503811

公司地址:深圳市龙岗区南湾街道东门头路8号

Copyright © 2012-2020 Powered by 树莓派论坛 2019.4  粤ICP备15075382号-1
快速回复 返回列表 返回顶部