the Chromium logo

The Chromium Projects

Getting Started with Chamelium V3

🎉 Congratulations on receiving your Chamelium V3 (Cv3) device.🎊 If you made it here, you're one of the lucky ones who received the all-new Cv3 device.🥳

Please follow the following steps to successfully setup your Cv3 experience:

1. Check Your Package

Every Chamelium package should include the Cv3 board (yellow) and a power adapter.

2. Join the Chamelium Group

Head to Chameleon-v3-users-external and request to join the group. This is where you'll find relevant discussions about Chamelium, and where you can ask any questions about it and someone familiar with the topic would be able to help out. New Releases will also be announced on this group. Releases are sent out regularly with the latest firmware and daemon updates. Example of the latest release email.

3. Setup Your New Shiny Device

To setup your Cv3, head to Chameleon v3 Setup, Deployment, and Testing. The doc should include everything you need to know to setup your device with the latest release.

4. Test Out Cv3 with Your DUT

Before you run any tests, let's make sure Cv3 runs well and is reachable. We will interact manually with Cv3 to validate everything

a. Grab Cv3's IP

Cv3 advertises its IP on boot. If you don't know the IP, reset Cv3's power and follow steps 4-5 in the setup doc: Boot the Linux System I recommend you save it in your ~/.ssh/config for posterity

Host cv3
  HostName 123.45.678.910 # the IP you grabbed above
  User cros
  StrictHostKeyChecking no
  VerifyHostKeyDNS no
  CheckHostIP no
  UserKnownHostsFile /dev/null
  IdentitiesOnly yes
  IdentityFile %d/.ssh/testing_rsa # # nocheck

b. Understand the Ports

While the info looks a bit daunting as you're about to get started, the Single Port subsection below is important to get your validation in place. The Hardware Cv3 has 4 physical ports to physically plug

|  DP 1  |  DP 2   |
| HDMI 1 | HDMI 2  |

Single Port If you're using the most simple case, you can only active in 1 at a time as the ITE ship can only drive 1 port. You can physically connected as many as you want, but you can only tell Cv3 to "active" (Plug() in Cv3 lingo) 1 ITE port.

ITE_DP1 = 0
ITE_DP2 = 1

Multiple Ports If you require more than 1 physically plugged monitors to be active at the same time, Cv3 can support more ports, but we will have to use different port IDs.

FPGA_DP1 = 4
FPGA_DP2 = 5

The ITE ship can still support only 1 display, so you can only get 1 HDMI, but the FPGA chip can support multiple physical displays, so the most you can do here is 3 simultaneous ports: (ITE_HDMI1|2), FPGA_DP1 and FPGA_DP2. NOTE: Physically speaking, ITE_DP1&FPGA_DP1 relate to the same physical port (same for *_DP2), but the port ID is what tells Chameleon whether it's able to drive multiple displays and reroutes the correct hardware. Activate a Port To "turn on" a port, 2 steps are needed: Setting an EDID and Plugging the port. You can't plug a port without setting an EDID. There is no default EDIDs set. After Reset() all EDIDs are cleared. Test code needs to explicitly call ApplyEdid() before plugging the port. If you don't care what EDID is set, there is a predefined EDID 0 ready to be applied so CreateEdid() call can be skipped and following pattern can be used:

X = chameleond.GetSupportedPorts()[0] # discover any port, here I take the first one
chameleond.ApplyEdid(X, 0)

MST The discussion is long, so jump to Understand MST section below

c. Test Out the Screenshot Tool

d. Interactively Play with Cv3

  1. If you're on corp network, follow the same workaround here ssh cv3 -L 9992:localhost:9992
  2. In your chromeos repo, navigate to cd src/platform/chameleon
  3. call python client/ --chameleon_host cv3 (on corp, this would be python client/ --chameleon_host localhost)
  4. From the interactive shell, plug the display you want >>> p.Plug(0)

If you can't do that, then your Cv3 is unreachable. Make sure it's on and you can ssh to it

NOTE: This interactive shell can be run from within Chamelium itself

  1. ssh cv3
  2. python chameleon/client/ --chameleon_host localhost
  3. >>> p.ApplyEdid(0, 0)
  4. >>> p.Plug(0)

All calls are found in the interface file

5. Understand MST

With V3 that now supports MST, things get a bit more fancy and ports are a bit more dynamic than described earlier. Here is explanation for the current state:

ITE_DP1 = 0
ITE_DP2 = 1
FPGA_DP1 = 4
FPGA_DP2 = 5

NOTE: For tests, while the ports are constant right now, it is better to use other properties (such as GetConnectorType()) or iterate over ports instead of hardcoding these numbers. Port Conflict and Discoverability

def IsConflict(self, port_id_a, port_id_b)
def GetMutuallyExclusivePorts(self, port_id)

IsConflict() allows to discover if two particular ports can be plugged at the same time and GetMutuallyExclusivePorts discovers all ports that will be in conflict. Tests should check for this before plugging multiple ports at the same time. Assume portA is in conflict with portB and portA is plugged. When you try to plug portB, Chameleond will raise an exception. portA should be explicitly unplugged first so plugging portB will succeed.

def GetPortName(self, port_id)

This is an API for interactive users to get human friendly name e.g. FPGA DP1 (left) or ITE DP2 (right) The MST Port

def IsMst(self, port_id)

MST ports are NOT discoverable by GetSupportedPorts. Only FPGA_DP2 can be discovered because it also functions as a standalone port. IsMst is introduced to test if port is capable of MST. Calling it for IsMst(FPGA_DP2) will return true. To find out how many extra ports can be configured behind the MST port GetChildren can be used. It will return a tuple of children ports (not discoverable by other APIs). The MST Children Ports

def GetChildren(self, port_id)
def PlugWithChildren(self, port_id, children_ids)

Calling GetChildren(FPGA_DP2) return 3 Port IDS: FPGA_DP2_MST1, FPGA_DP2_MST2, FPGA_DP2_MST3 so in total there is up to 4 monitors on this port. Children ports can be used with all APIs except Plug() and Unplug(). To Plug them, call PlugWithChildren. For example, if you want to plug MST0, MST2 and MST3 displays, then do like the following Python Example:

# Example of discovering required ports
mst_port = None
for port in chameleond.GetSupportedPorts():
  if chameleond.IsMst(port):
    mst_port = port
children = chameleond.GetChildren(mst_port)
# Get last two ports
children = children[-2:]

# Assume there is EDID with id X already created
chameleond.ApplyEdid(mst_port, X)
for child in children:
  chameleond.ApplyEdid(child, X)

# Finally plug all three monitors
chameleond.PlugWithChildren(mst_port, children)

Note: The first MST port MST0 (parent port) will always be used if MST is used, as it is the port controlling the HPD line. Using the standard Plug() on the MST parent will plug just this port without any children. Using PlugWithChildren on any port with empty children list (i.e. ITE_HDMI1) will have the same effect as Plug on that port.


def Unplug(mst_port)

To unplug all MST ports just use regular Unplug.

6. ChameleonD

ChameleonD is the daemon that runs on Cv3 and is responsible for all the heavy lifting. It's the one that interacts with the hardware and exposes the API for the clients to interact with. The daemon is written in Python and can be found in the chameleond directory. This is what you will interact with through the XMLRPC interface calls, or interactively as described above. All available calls are listed here

7. Run Your First Test

The following guide assumes you're familiar with running IGT and can run a test such as kms_atomic on your DUT. If not, please refer to the IGT guide to get started. The guide will also help you building and deploying IGT on your DUT if they're not already there.

Configure the device to run a chamelium test

For a chamelium test, you run it like any other IGT test, but there is a one-time setup (per DUT image) that needs to be done to tell your DUT how to find its associated chamelium. MOTE: This is also document in IGT docs

  1. ssh to your DUT (you would be accessing through root@)
  2. Remove rootfs verification if you haven't already (cros device)$ sudo /usr/share/vboot/bin/ --remove_rootfs_verification -f
  3. Create a new file on your DUT ~/.igtrc
  4. Add the following in .igtrc (Modify it as needed: especially the URL(leave the port number as 9992) and ChameliumPortID)
# The path to dump frames that fail comparison checks


# IP address of the Chamelium
# The ONLY mandatory field (the rest of the file can be blank yet IGT will be usable)
# Replace the URL below by yours

# The ID of the chamelium port <> DUT port mapping
# DUT port can be found using `modetest -c`
# Replace the IDs below by yours
# It's an optional field. When no set, Chamelium will perform autodiscovery.

The steps above can be generated automatically to you using

Bash functions

If you're building using cros_sdk, you can add the following functions to your .bashrc to make your life easier. The following assumes you did cros-workon for IGT.

  1. Build and deploy to the DUT all of IGT, useful when you change more than 1 test and/or a library/helper
igtdeploy() { # expecting dest
  local dest="${1:-"crbook"}"
  cros_sdk emerge-${BOARD} igt-gpu-tools && cros_sdk cros deploy ${dest} igt-gpu-tools --root /usr/local
  1. Build and deploy a single IGT test
igtscp() { # expecting test name and dest
  local dest="${2:-"crbook"}"
  cros_sdk emerge-${BOARD} igt-gpu-tools && scp /sda/cros/chroot/build/${BOARD}/usr/libexec/igt-gpu-tools/$1 ${dest}:///usr/local/libexec/igt-gpu-tools
  1. When using a new device, build all dependencies needed for subsequent IGT builds
igtdeploydeps() { #expecting DUT
  local dest="${1:-"crbook"}" #crbook is my default DUT hostname
  cros_sdk emerge-${BOARD} sys-libs/llvm-libunwind  && cros_sdk cros deploy ${dest} sys-libs/llvm-libunwind &
  cros_sdk emerge-${BOARD} x11-libs/libpciaccess  && cros_sdk cros deploy ${dest} x11-libs/libpciaccess &
  cros_sdk emerge-${BOARD} dev-libs/xmlrpc-c  && cros_sdk cros deploy ${dest} dev-libs/xmlrpc-c &
  cros_sdk emerge-${BOARD} sci-libs/gsl  && cros_sdk cros deploy ${dest} sci-libs/gsl &
  igtdeploy $1
  1. rebase my current work on the latest master # nocheck
igtsync() {
  local br_name=$(git rev-parse --abbrev-ref HEAD)
  local old_stash_count=$(git stash list | wc -l)
  git stash
  git checkout upstream/master # nocheck
  git pull
  git checkout $br_name
  git rebase upstream/master # nocheck
  local new_stash_count=$(git stash list | wc -l)
  if [ $old_stash_count -lt $new_stash_count ]
    git stash pop