nmxact/nmble/master.go (208 lines of code) (raw):

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package nmble import ( "fmt" "sync" log "github.com/sirupsen/logrus" "mynewt.apache.org/newtmgr/nmxact/nmxutil" ) type masterState int const ( MASTER_STATE_IDLE masterState = iota MASTER_STATE_SECONDARY MASTER_STATE_PRIMARY MASTER_STATE_PRIMARY_SECONDARY_PENDING ) func (s masterState) String() string { m := map[masterState]string{ MASTER_STATE_IDLE: "idle", MASTER_STATE_SECONDARY: "secondary", MASTER_STATE_PRIMARY: "primary", MASTER_STATE_PRIMARY_SECONDARY_PENDING: "primary_secondary_pending", } return m[s] } type primary struct { token interface{} ch chan error } type Preemptable interface { Preempt() error } // Represents the Bluetooth device's "master privileges." The device can only // do one of the following actions at a time: // * initiate connection // * scan // // Clients are divided into two groups: // * primary // * secondary // // This struct restricts master privileges to a single client at a time. It // uses the following procedure to determine which of several clients to serve: // If there is one or more waiting primaries: // If a secondary is active, preempt it. // Service the primaries in the order of their requests. // Else (no waiting primaries): // Service waiting secondary if there is one. type Master struct { primaries []primary secondary Preemptable state masterState secondaryReadyCh chan error mtx sync.Mutex } func NewMaster(x *BleXport) Master { return Master{ secondaryReadyCh: make(chan error), } } func (m *Master) GetSecondary() Preemptable { return m.secondary } func (m *Master) SetSecondary(s Preemptable) error { m.mtx.Lock() defer m.mtx.Unlock() if m.state == MASTER_STATE_SECONDARY || m.state == MASTER_STATE_PRIMARY_SECONDARY_PENDING { return fmt.Errorf("cannot replace master secondary while it is in use") } m.secondary = s return nil } func (m *Master) setState(s masterState) { log.Debugf("Master state change: %s --> %s", m.state, s) m.state = s } func (m *Master) appendPrimary(token interface{}) primary { c := primary{ token: token, ch: make(chan error), } m.primaries = append(m.primaries, c) return c } func (m *Master) AcquirePrimary(token interface{}) error { initiate := func() (*primary, error) { m.mtx.Lock() defer m.mtx.Unlock() switch m.state { case MASTER_STATE_IDLE: m.setState(MASTER_STATE_PRIMARY) return nil, nil case MASTER_STATE_SECONDARY: c := m.appendPrimary(token) go m.secondary.Preempt() return &c, nil case MASTER_STATE_PRIMARY, MASTER_STATE_PRIMARY_SECONDARY_PENDING: c := m.appendPrimary(token) return &c, nil default: nmxutil.Assert(false) return nil, fmt.Errorf("internal error: invalid master state=%+v", m.state) } } c, err := initiate() if err != nil { return err } if c == nil { return nil } return <-c.ch } func (m *Master) AcquireSecondary() error { initiate := func() (chan error, error) { m.mtx.Lock() defer m.mtx.Unlock() switch m.state { case MASTER_STATE_IDLE: // The resource is unused; just acquire it. m.setState(MASTER_STATE_SECONDARY) return nil, nil case MASTER_STATE_SECONDARY, MASTER_STATE_PRIMARY_SECONDARY_PENDING: nmxutil.Assert(false) return nil, fmt.Errorf("Attempt to perform more than one " + "secondary master procedure") case MASTER_STATE_PRIMARY: m.setState(MASTER_STATE_PRIMARY_SECONDARY_PENDING) return m.secondaryReadyCh, nil default: nmxutil.Assert(false) return nil, fmt.Errorf( "internal error: invalid master state=%+v", m.state) } } ch, err := initiate() if err != nil { return err } if ch == nil { return nil } return <-ch } func (m *Master) serviceSecondary(err error) { m.secondaryReadyCh <- err } func (m *Master) servicePrimary(err error) { nmxutil.Assert(len(m.primaries) > 0) next := m.primaries[0] m.primaries = m.primaries[1:] next.ch <- err } func (m *Master) Release() { m.mtx.Lock() defer m.mtx.Unlock() switch m.state { case MASTER_STATE_IDLE: nmxutil.Assert(false) case MASTER_STATE_SECONDARY: if len(m.primaries) == 0 { m.setState(MASTER_STATE_IDLE) } else { m.setState(MASTER_STATE_PRIMARY) m.servicePrimary(nil) } case MASTER_STATE_PRIMARY: if len(m.primaries) == 0 { m.setState(MASTER_STATE_IDLE) } else { m.servicePrimary(nil) } case MASTER_STATE_PRIMARY_SECONDARY_PENDING: if len(m.primaries) == 0 { m.setState(MASTER_STATE_SECONDARY) m.serviceSecondary(nil) } else { m.servicePrimary(nil) } default: nmxutil.Assert(false) } } func (m *Master) findPrimaryIdx(token interface{}) int { for i, c := range m.primaries { if c.token == token { return i } } return -1 } // Removes the specified primary from the wait queue. func (m *Master) StopWaitingPrimary(token interface{}, err error) { m.mtx.Lock() defer m.mtx.Unlock() idx := m.findPrimaryIdx(token) if idx == -1 { return } m.primaries = append( m.primaries[0:idx], m.primaries[idx+1:len(m.primaries)]...) } // Removes the specified secondary from the wait queue. func (m *Master) StopWaitingSecondary(err error) { m.mtx.Lock() defer m.mtx.Unlock() if m.state == MASTER_STATE_PRIMARY_SECONDARY_PENDING { m.setState(MASTER_STATE_PRIMARY) m.serviceSecondary(fmt.Errorf("secondary aborted master acquisition")) } } // Releases the resource and clears the wait queue. func (m *Master) Abort(err error) { m.mtx.Lock() defer m.mtx.Unlock() // Set state such that there are no waiters. We can't clear the current // owner of the master; it must release on its own. switch m.state { case MASTER_STATE_IDLE: case MASTER_STATE_SECONDARY: go m.secondary.Preempt() case MASTER_STATE_PRIMARY: case MASTER_STATE_PRIMARY_SECONDARY_PENDING: m.serviceSecondary(err) m.setState(MASTER_STATE_PRIMARY) } for len(m.primaries) > 0 { m.servicePrimary(err) } }