키보드를 만듭시다. 어때요~ 참 쉽죠? (10)

나빌레라의 이미지

MSC USB 펌웨어를 작성하는 것도 UART 펌웨어나 HID 펌웨어를 만들 때랑 같은 전략을 썻다. 인터넷에서 적당한 예제를 찾은 다음, 그대로 가져다 붙이고 필요한 부분만 수정하는 것이다. 진지하게 시간들여 공부할 필요도 없고 결과도 빨리 나오고 재미있다. 오픈 소스란 좋은 것이다. 물론 나도 그렇게 만든 결과물을 공개해야 한다.

Composite USB

우리는 많은 USB 장치들을 쓰며 산다. 그 들 중에 어떤 USB는 꼽으면 디바이스 드라이버 두 개가 잡히는 녀석들이 있다. 예를 들어 키보드에 트랙볼이 일체형으로 나오는 제품들 중에 HID 장치 하나로 잡히고 키보드, 트랙볼이 동작하는 제품이 있는가 하면 어떤 제품은 키보드와 마우스 두 개가 따로 잡히고 각각 키보드, 마우스로 작동하는 제품이 있다.

같은 방식으로 HID class USB device와 MSC USB device를 composite USB로 구성할 수 있다. 이러면 키보드와 대용량 저장 장치가 같이 인식되고, 키보드를 쓰면서 펌웨어와 키맵을 업그레이드 할 수 있다. 문제는 키보드가 꼽혀 있는 내내 대용량 저장 장치도 같이 인식되어 있다는 것이다. 딱히 문제일건 없는데 좀 어색하다. 그래서 이 어색함을 해결하려면 펌웨어에서 USB 초기화를 두 개로 나눠야 한다. 하나는 HID 키보드 전용이고, 다른 하나는 HID와 MSC가 composite으로 설정된 것이다. 그래서 키보드에 USB 케이블을 그냥 꼽으면 HID 키보드 전용으로만 동작하고 특정 조건 하에서 USB 케이블이 연결되면 HID와 MSC가 composite으로 인식되게 펌웨어가 동작하는 방식이다.

괜찮은 방법같다. 일단 이렇게 구상하고 우선 MSC가 되게 하는 것부터 해 보자. 참, 결론부터 말하자만 composite USB로 구현하지는 않았다. 다음 글에서 설명할 펌웨어 업데이트 문제 때문이다.

MSC 펌웨어

STM32F103의 MSC 펌웨어도 SDK의 예제인지 비슷하게 생긴 예제들이 엄청 많다. 그 중에서 앞서 작업했던 HID 펌웨어와 제일 비슷하게 생긴 놈을 골라서 일단 HID 펌웨어 코드와 겹치는 부분을 본다. 겹치는 부분이 미들웨어에 잘 분리된다면 그 놈을 쓴다. 다들 비슷하므로 웬만해서는 그냥 아무거나 골라 써도 될꺼다.

이미 HID 펌웨어가 USB 미들웨어를 포함해서 자리잡고 있다. MSC 펌웨어를 추가하면 USB 미들웨어 코드가 같은 파일명으로 겹치게 있다. 파일명을 보고 겹치는 파일 내용을 분석하면서 분리해야 한다. 이 작업은 사실 꽤나 기계적인 작업이다. 사실상 같은 원본인 SDK에서 온 것이라 거의 비슷하기 때문에 어느 시점에서 레이어가 구분되어야 하는지 쉽게 알 수 있다.

app
├── stm32f103
│   ├── HID
│   │   ├── usbd_hid.c
│   │   ├── usbd_hid_desc.c
│   │   ├── usbd_hid_desc.h
│   │   ├── usbd_hid.h
│   │   ├── usb_hid_keyboard.c
│   │   └── usb_hid_keyboard.h
│   ├── KeyHw.c
│   ├── KeyHw.h
│   ├── Keymap.c
│   ├── keymap.h
│   ├── MSC
│   │   ├── usbd_msc_bot.c
│   │   ├── usbd_msc_bot.h
│   │   ├── usbd_msc.c
│   │   ├── usbd_msc_data.c
│   │   ├── usbd_msc_data.h
│   │   ├── usbd_msc_desc.c
│   │   ├── usbd_msc_desc.h
│   │   ├── usbd_msc.h
│   │   ├── usbd_msc_scsi.c
│   │   ├── usbd_msc_scsi.h
│   │   ├── usbd_storage_if.c
│   │   ├── usbd_storage_if.h
│   │   ├── usb_msc_device.c
│   │   └── usb_msc_device.h
│   └── USB
│       ├── usbd_conf.c
│       ├── usbd_conf.h
│       ├── usbd_core.c
│       ├── usbd_core.h
│       ├── usbd_ctlreq.c
│       ├── usbd_ctlreq.h
│       ├── usbd_def.h
│       ├── usbd_ioreq.c
│       └── usbd_ioreq.h

최종적으로 이렇게 파일을 분리했다. USB 디렉터리 밑에는 USB 미들웨어 공용 파일이 위치하고 HID 전용 소스 코드는 HID 밑에, MSC 전용 코드는 MSC 밑에 배치했다. 개별 구현 코드는 굳이 설명하지 않겠다. 사실 나도 이해를 안 (못 한 것이 아니라 안 한거다. 굳이 공부할 필요가 없으니까.) 하고 있기도 하고 거의다 스펙에 있는 디스크립터 구현이다. 그리고 STM32F103 SDK의 레이어 설계에 맞춘 콜백 함수들 구현이다. 거의 손 댈 필요 없다.

다만 레이어 호출 구조 정도는 알아야 한다. 이것만 알면 코드를 분석하지 않아도 된다.

호스트 <----> STM32F103 USB 컨트롤러 <-> USB HAL <-> usbd_core

호스트에서 온 USB 프로토콜 데이터가 HAL을 거쳐서 미들웨어로 가는 순서다. 미들웨어에 있는 파일 중에 usbd_core.c에 구현한 함수를 통한다. 여기에 보면 컨트롤 처리와 IO 처리를 하는 함수를 분리해 놨다.

                       --> 컨트롤 처리 : usbd_ctlreq.c
 usbd_core --<
                       --> IO 처리 : usbd_ioreq.c

이렇게 분리해서 처리한다. 미들웨어 레이어는 실제 구현 함수를 보통 콜백으로 처리한다. 그래서 필요한 콜백을 등록하고 콜백외에 디스크립터 바이너리 시퀀스 데이터를 전달하는 함수가 필요한데 이 함수가 초기화 함수다.

  • HID 초기화 함수 : usb_hid_keyboard.c
  • MSC 초기화 함수 : usb_msc_device.c

그리고 USB 장치 성격을 호스트에 알려 주는 장치 class 디스크립터를 작성한 파일이 각각 있다.

  • HID class 디스크립터 구현 : usb_hid_desc.c
  • MSC class 디스크립터 구현 : usb_msc_desc.c

USB 장치는 각 장치마다 서로 다른 스펙이 있고 이 스펙에 따른 장치 디스크립터가 있다. 이 디스크립터를 구현한 파일이 각각 있다.

  • HID 장치 디스크립터 구현 : usb_hid.c
  • MSC 장치 디스크립터 구현 : usb_msc.c

SCSI 프로토콜

비슷한 구성의 USB 장치라서 구현 파일도 서로 짝 맞춰서 HID와 MSC 용이 있는데, MSC 구현 소스 파일이 HID 보다 몇개 더 많다. 왜냐면 HID에서 키보드만 구현하고 호스에서 num lock 등의 컨트롤 신호를 받지 안으면 디바이스가 호스트와 통신할 필요가 없다. 그냥 일방적으로 스캔 코드를 보내기만 하면 된다. 그러나 MSC는 호스트랑 주고 받는 데이터가 좀 있다. 통신을 해야 하는 것이다. 그 통신 프로토콜로 SCSI 프로토콜을 쓰는데 방대한 SCSI 프로토콜 중 극히 일부만 구현하면 된다.

USB는 input과 output으로 호스트 통신을 구분한다. SCSI는 read와 write로 구분하고 이것을 command 코드로 구분한다. 그래서 USB의 input/output과 SCSI의 read/write를 연결해야 한다. 어려운 일이다. 양쪽의 스펙을 다 알아야 구현할 수 있는 것이니까. 그런데 실제로 내가 어려웠던 것은 없다. 구현은 다 되 있고 나는 관계만 파악하면 되는 거니까.

USB input/output <---> SCSI command

이렇게 연결되는 것이고 구현은

  • USB input/output : usbd_msc_bot.c
  • SCSI command : usbd_msc_scsi.c

이렇게 코드가 있다.

FAT16 구현

SCSI는 호스트 프로토콜이고 윈도우나 리눅스에서 드라이브로 인식하려면 파일 시스템이 있어야 한다. 보통 USB thumb 드라이브를 꼽고 인식하려면 운영체제에서 드라이브를 포멧한다. 포멧할 때 드라이브의 스토리지에 논리적으로 생성되는 정보가 파일 시스템이다. 그래서 파일 시스템이 없으면 그냥 인식만 될 뿐이고 파일 시스템이 있으면 그제서야 드라이브로 마운트(mount)된다. 리눅스에 있는 그 mount 명령으로 인식하는 거다. 윈도우도 원리상 동일하다.

키보드 펌웨어는 실제로 스토리지는 없고 키맵과 펌웨어 바이너리를 다운받기 위해 가짜로 드라이브로 꾸며서 정보를 호스트에 보내야 드라이브로 마운트될 수 있다. 그러려면 파일 시스템 정보를 펌웨어에서 가짜로 만들어야 한다. 최신 OS에서 사용하는 파일 시스템은 매우 복잡하다. 그래서 이해하기 어렵다. 다행인 것은 이 바닥에 하위 호환이라는 것은 매우 중요해서 최신 OS도 30년전 파일 시스템을 인식한다.

모든 분야에 적용되는 것은 아니지만 소프트웨어 분야에서 만큼은 이런게 좀 있다. 오래된 기술일 수록 단순하다. 아마도 그 때 당시 하드웨어 제약 때문에 단순하게 만들 수 밖에 없었으리라. 그래서 오래된 기술은 이해하기 쉽다. 파일 시스템도 마찬가지다. 오래된 FAT16은 단순하다. 그래서 이해하기 쉽다. 내가 인터넷에서 가져온 코드도 FAT16을 구현하고 있다.

문제는 여기서 생겼다. 내가 긁어온 소스 코드의 FAT16이 동작하지 않았다. 지금까지 포팅 작업한게 있어서 FAT16 때문에 전체 코드를 교체할 순 없었다. 그래서 FAT16 정도는 직접 구현하기로 결정했다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.